From 452c57692727193dd722d3670ad3d4e809ebe065 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Mon, 2 Mar 2026 10:45:43 +0100 Subject: [PATCH 0001/1296] core/varlink-unit: use VARLINK_ERROR_UNIT_NO_SUCH_UNIT macro Follow-up for 1fc868ac6b74d61c75d00a62aa4331961dead3ed --- src/core/varlink-unit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index 2b01aa12b4b17..261ade0e9a3c6 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -516,7 +516,7 @@ int vl_method_list_units(sd_varlink *link, sd_json_variant *parameters, sd_varli if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)) return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, NULL); - r = varlink_set_sentinel(link, "io.systemd.Unit.NoSuchUnit"); + r = varlink_set_sentinel(link, VARLINK_ERROR_UNIT_NO_SUCH_UNIT); if (r < 0) return r; From 93d768e0f36a62afed7ebbf3abe3385cfd186480 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Mon, 2 Mar 2026 10:49:17 +0100 Subject: [PATCH 0002/1296] systemd/sd-varlink-idl.h: fix ABI breakage Follow-up for 2e51ed7fcb8a4215432ca189f8b3d2ad848ea93b --- src/systemd/sd-varlink-idl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systemd/sd-varlink-idl.h b/src/systemd/sd-varlink-idl.h index c9e0bfca49234..ab85a95cc7512 100644 --- a/src/systemd/sd-varlink-idl.h +++ b/src/systemd/sd-varlink-idl.h @@ -67,9 +67,9 @@ __extension__ typedef enum _SD_ENUM_TYPE_S64(sd_varlink_field_type_t) { SD_VARLINK_FLOAT, SD_VARLINK_STRING, SD_VARLINK_OBJECT, - SD_VARLINK_ANY, SD_VARLINK_ENUM_VALUE, _SD_VARLINK_FIELD_COMMENT, /* Not really a field, just a comment about a field */ + SD_VARLINK_ANY, _SD_VARLINK_FIELD_TYPE_MAX, _SD_VARLINK_FIELD_TYPE_INVALID = -EINVAL, _SD_ENUM_FORCE_S64(SD_VARLINK_FIELD) From 3eacf0d321067ba2b2d25d463e4bf2366b68a90c Mon Sep 17 00:00:00 2001 From: noxiouz Date: Mon, 2 Mar 2026 10:04:37 +0000 Subject: [PATCH 0003/1296] login: fix ReleaseSession.Id dispatch flag in io.systemd.Login Varlink handler The ReleaseSession method's Id field is declared as nullable (?string) in the IDL, allowing callers to omit it so that the method releases the caller's own session via session_is_self(NULL). The SD_JSON_MANDATORY flag in the dispatch table contradicts this and makes omitting Id return -EINVAL ("Invalid argument", parameter "Id") instead. Drop the flag so omitting Id is treated as passing NULL. Fixes: 2baca6c22b2d75b8ba2d0bd8a9e7f4a8579752ed --- src/login/logind-varlink.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/login/logind-varlink.c b/src/login/logind-varlink.c index a1fdac01c907b..ae2e32d3fafa1 100644 --- a/src/login/logind-varlink.c +++ b/src/login/logind-varlink.c @@ -308,7 +308,7 @@ static int vl_method_release_session(sd_varlink *link, sd_json_variant *paramete } p; static const sd_json_dispatch_field dispatch_table[] = { - { "Id", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, id), SD_JSON_MANDATORY }, + { "Id", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, id), 0 }, {} }; From 3d54a40daec6e5bcfbf12c9d0ef2e3cf947d9f07 Mon Sep 17 00:00:00 2001 From: noxiouz Date: Mon, 2 Mar 2026 10:04:48 +0000 Subject: [PATCH 0004/1296] login: add missing NoSessionPIDFD error to io.systemd.Login IDL The error is emitted by vl_method_create_session() when the session leader process does not have a pidfd available, but was never declared in the IDL. Fixes: 3180c4d46151673a9c985e60f205d4c76a81573f --- src/shared/varlink-io.systemd.Login.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/shared/varlink-io.systemd.Login.c b/src/shared/varlink-io.systemd.Login.c index cf09d18286679..9a07155ef20ca 100644 --- a/src/shared/varlink-io.systemd.Login.c +++ b/src/shared/varlink-io.systemd.Login.c @@ -94,6 +94,7 @@ static SD_VARLINK_DEFINE_ERROR(AlreadySessionMember); static SD_VARLINK_DEFINE_ERROR(VirtualTerminalAlreadyTaken); static SD_VARLINK_DEFINE_ERROR(TooManySessions); static SD_VARLINK_DEFINE_ERROR(UnitAllocationFailed); +static SD_VARLINK_DEFINE_ERROR(NoSessionPIDFD); SD_VARLINK_DEFINE_INTERFACE( io_systemd_Login, @@ -120,4 +121,6 @@ SD_VARLINK_DEFINE_INTERFACE( SD_VARLINK_SYMBOL_COMMENT("Maximum number of sessions reached"), &vl_error_TooManySessions, SD_VARLINK_SYMBOL_COMMENT("Failed to allocate a unit for the session"), - &vl_error_UnitAllocationFailed); + &vl_error_UnitAllocationFailed, + SD_VARLINK_SYMBOL_COMMENT("The session leader process does not have a pidfd"), + &vl_error_NoSessionPIDFD); From 8b3d3d6d6768d4330d7b567d2be5d2be76d94122 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 2 Mar 2026 11:34:42 +0100 Subject: [PATCH 0005/1296] journal-remote: fix error number confusion See: https://lists.freedesktop.org/archives/systemd-devel/2026-February/051924.html --- src/journal-remote/microhttpd-util.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/journal-remote/microhttpd-util.c b/src/journal-remote/microhttpd-util.c index e69f32f7ab536..73b6ed4c9466c 100644 --- a/src/journal-remote/microhttpd-util.c +++ b/src/journal-remote/microhttpd-util.c @@ -68,9 +68,7 @@ int mhd_respondf_internal( assert(connection); assert(format); - if (error < 0) - error = -error; - errno = -error; + errno = ERRNO_VALUE(error); va_start(ap, format); r = vasprintf(&m, format, ap); va_end(ap); From 4b4eba1666fdfb3289a6310e259ff7f4e7ef4ad7 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 2 Mar 2026 16:06:33 +0900 Subject: [PATCH 0006/1296] tree-wide: use _contains() rather than _get()/_find() --- src/bootctl/bootctl-cleanup.c | 2 +- src/libsystemd/sd-bus/bus-objects.c | 4 ++-- src/login/logind-dbus.c | 2 +- src/machine/machined-dbus.c | 4 ++-- src/machine/machined-resolve-hook.c | 2 +- src/network/networkd-link.c | 2 +- src/network/test-network.c | 2 +- src/portable/portable.c | 2 +- src/resolve/test-resolved-etc-hosts.c | 24 ++++++++++---------- src/shared/install.c | 2 +- src/sysupdate/sysupdate-cache.c | 2 +- src/sysusers/sysusers.c | 10 ++++----- src/test/test-engine.c | 32 +++++++++++++-------------- src/test/test-hashmap-plain.c | 24 ++++++++++---------- 14 files changed, 57 insertions(+), 57 deletions(-) diff --git a/src/bootctl/bootctl-cleanup.c b/src/bootctl/bootctl-cleanup.c index 8cc51dd597ed0..59f67edb999bc 100644 --- a/src/bootctl/bootctl-cleanup.c +++ b/src/bootctl/bootctl-cleanup.c @@ -33,7 +33,7 @@ static int list_remove_orphaned_file( if (event != RECURSE_DIR_ENTRY) return RECURSE_DIR_CONTINUE; - if (hashmap_get(known_files, path)) + if (hashmap_contains(known_files, path)) return RECURSE_DIR_CONTINUE; /* keep! */ if (arg_dry_run) diff --git a/src/libsystemd/sd-bus/bus-objects.c b/src/libsystemd/sd-bus/bus-objects.c index 30dcc3a81f860..42e2332001b8a 100644 --- a/src/libsystemd/sd-bus/bus-objects.c +++ b/src/libsystemd/sd-bus/bus-objects.c @@ -2401,7 +2401,7 @@ static int object_added_append_all_prefix( * skip it on any of its parents. The child vtables * always fully override any conflicting vtables of * any parent node. */ - if (ordered_set_get(s, c->interface)) + if (ordered_set_contains(s, c->interface)) continue; r = ordered_set_put(s, c->interface); @@ -2616,7 +2616,7 @@ static int object_removed_append_all_prefix( * skip it on any of its parents. The child vtables * always fully override any conflicting vtables of * any parent node. */ - if (ordered_set_get(s, c->interface)) + if (ordered_set_contains(s, c->interface)) continue; r = node_vtable_get_userdata(bus, path, c, &u, &error); diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index b50e69809fea0..14ec1f90ab400 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -3880,7 +3880,7 @@ static int method_inhibit(sd_bus_message *message, void *userdata, sd_bus_error if (asprintf(&id, "%" PRIu64, ++m->inhibit_counter) < 0) return -ENOMEM; - } while (hashmap_get(m->inhibitors, id)); + } while (hashmap_contains(m->inhibitors, id)); _cleanup_(inhibitor_freep) Inhibitor *i = NULL; r = manager_add_inhibitor(m, id, &i); diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index 0130fbf77988f..a85b827ea06b8 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -464,7 +464,7 @@ static int method_create_or_register_machine( supervisor_pidref = TAKE_PIDREF(client_pidref); } - if (hashmap_get(manager->machines, name)) + if (hashmap_contains(manager->machines, name)) return sd_bus_error_setf(error, BUS_ERROR_MACHINE_EXISTS, "Machine '%s' already exists", name); return machine_add_from_params( @@ -616,7 +616,7 @@ static int method_create_or_register_machine_ex( if (!isempty(root_directory) && (!path_is_absolute(root_directory) || !path_is_valid(root_directory))) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Root directory must be empty or an absolute path"); - if (hashmap_get(manager->machines, name)) + if (hashmap_contains(manager->machines, name)) return sd_bus_error_setf(error, BUS_ERROR_MACHINE_EXISTS, "Machine '%s' already exists", name); /* If a PID is specified that's the leader, but if the client process is different from it, than that's the supervisor */ diff --git a/src/machine/machined-resolve-hook.c b/src/machine/machined-resolve-hook.c index d022edc1691fb..1d1a9ad0a9331 100644 --- a/src/machine/machined-resolve-hook.c +++ b/src/machine/machined-resolve-hook.c @@ -174,7 +174,7 @@ int vl_method_resolve_record( if (r == 0) break; - nxdomain = !!hashmap_get(m->machines, q); + nxdomain = hashmap_contains(m->machines, q); } } diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index cb79ddb9eb74a..e49d5946aa67d 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -869,7 +869,7 @@ static int link_put_carrier(Link *link, Link *carrier, Hashmap **h) { if (link == carrier) return 0; - if (hashmap_get(*h, INT_TO_PTR(carrier->ifindex))) + if (hashmap_contains(*h, INT_TO_PTR(carrier->ifindex))) return 0; r = hashmap_ensure_put(h, NULL, INT_TO_PTR(carrier->ifindex), carrier); diff --git a/src/network/test-network.c b/src/network/test-network.c index 253e0d660d64f..6582d3b074079 100644 --- a/src/network/test-network.c +++ b/src/network/test-network.c @@ -128,7 +128,7 @@ TEST(route_tables) { test_route_tables_one(manager, "bbb", 11111); test_route_tables_one(manager, "ccc", 22222); - ASSERT_NULL(hashmap_get(manager->route_table_numbers_by_name, "ddd")); + ASSERT_FALSE(hashmap_contains(manager->route_table_numbers_by_name, "ddd")); test_route_tables_one(manager, "default", 253); test_route_tables_one(manager, "main", 254); diff --git a/src/portable/portable.c b/src/portable/portable.c index c86a6c27228e3..2f6db85ff9121 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -381,7 +381,7 @@ static int extract_now( continue; /* Filter out duplicates */ - if (hashmap_get(unit_files, de->d_name)) + if (hashmap_contains(unit_files, de->d_name)) continue; if (!IN_SET(de->d_type, DT_LNK, DT_REG)) diff --git a/src/resolve/test-resolved-etc-hosts.c b/src/resolve/test-resolved-etc-hosts.c index 0528a411dc347..76c20b37e9c6e 100644 --- a/src/resolve/test-resolved-etc-hosts.c +++ b/src/resolve/test-resolved-etc-hosts.c @@ -92,7 +92,7 @@ TEST(parse_etc_hosts) { /* See https://tools.ietf.org/html/rfc1035#section-2.3.1 */ FOREACH_STRING(s, "bad-dash-", "-bad-dash", "-bad-dash.bad-") - assert_se(!hashmap_get(hosts.by_name, s)); + ASSERT_FALSE(hashmap_contains(hosts.by_name, s)); assert_se(bn = hashmap_get(hosts.by_name, "before.comment")); assert_se(set_size(bn->addresses) == 4); @@ -101,17 +101,17 @@ TEST(parse_etc_hosts) { assert_se(has_4(bn->addresses, "1.2.3.11")); assert_se(has_4(bn->addresses, "1.2.3.12")); - assert_se(!hashmap_get(hosts.by_name, "within.comment")); - assert_se(!hashmap_get(hosts.by_name, "within.comment2")); - assert_se(!hashmap_get(hosts.by_name, "within.comment3")); - assert_se(!hashmap_get(hosts.by_name, "#")); - - assert_se(!hashmap_get(hosts.by_name, "short.address")); - assert_se(!hashmap_get(hosts.by_name, "long.address")); - assert_se(!hashmap_get(hosts.by_name, "multi.colon")); - assert_se(!set_contains(hosts.no_address, "short.address")); - assert_se(!set_contains(hosts.no_address, "long.address")); - assert_se(!set_contains(hosts.no_address, "multi.colon")); + ASSERT_FALSE(hashmap_contains(hosts.by_name, "within.comment")); + ASSERT_FALSE(hashmap_contains(hosts.by_name, "within.comment2")); + ASSERT_FALSE(hashmap_contains(hosts.by_name, "within.comment3")); + ASSERT_FALSE(hashmap_contains(hosts.by_name, "#")); + + ASSERT_FALSE(hashmap_contains(hosts.by_name, "short.address")); + ASSERT_FALSE(hashmap_contains(hosts.by_name, "long.address")); + ASSERT_FALSE(hashmap_contains(hosts.by_name, "multi.colon")); + ASSERT_FALSE(set_contains(hosts.no_address, "short.address")); + ASSERT_FALSE(set_contains(hosts.no_address, "long.address")); + ASSERT_FALSE(set_contains(hosts.no_address, "multi.colon")); assert_se(bn = hashmap_get(hosts.by_name, "some.other")); assert_se(set_size(bn->addresses) == 1); diff --git a/src/shared/install.c b/src/shared/install.c index d3e5a1fb5ae66..b8e364011ec04 100644 --- a/src/shared/install.c +++ b/src/shared/install.c @@ -3499,7 +3499,7 @@ static int pattern_match_multiple_instances( if (r < 0) return r; - if (strv_find(rule.instances, instance_name)) + if (strv_contains(rule.instances, instance_name)) return 1; } return 0; diff --git a/src/sysupdate/sysupdate-cache.c b/src/sysupdate/sysupdate-cache.c index 23cef57b088fc..222b0889159e4 100644 --- a/src/sysupdate/sysupdate-cache.c +++ b/src/sysupdate/sysupdate-cache.c @@ -42,7 +42,7 @@ int web_cache_add_item( if (item && memcmp_nn(item->data, item->size, data, size) == 0) return 0; - if (hashmap_size(*web_cache) >= (size_t) (WEB_CACHE_ENTRIES_MAX + !!hashmap_get(*web_cache, url))) + if (hashmap_size(*web_cache) >= (size_t) (WEB_CACHE_ENTRIES_MAX + hashmap_contains(*web_cache, url))) return -ENOSPC; r = hashmap_ensure_allocated(web_cache, &web_cache_hash_ops); diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index a113928f97789..4e604f9a752a4 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -1053,7 +1053,7 @@ static int uid_is_ok( assert(c); /* Let's see if we already have assigned the UID a second time */ - if (ordered_hashmap_get(c->todo_uids, UID_TO_PTR(uid))) + if (ordered_hashmap_contains(c->todo_uids, UID_TO_PTR(uid))) return 0; /* Try to avoid using uids that are already used by a group @@ -1292,7 +1292,7 @@ static int gid_is_ok( assert(c); assert(groupname); - if (ordered_hashmap_get(c->todo_gids, GID_TO_PTR(gid))) + if (ordered_hashmap_contains(c->todo_gids, GID_TO_PTR(gid))) return 0; /* Avoid reusing gids that are already used by a different user */ @@ -1568,7 +1568,7 @@ static int add_implicit(Context *c) { /* Implicitly create additional users and groups, if they were listed in "m" lines */ ORDERED_HASHMAP_FOREACH_KEY(l, g, c->members) { STRV_FOREACH(m, l) - if (!ordered_hashmap_get(c->users, *m)) { + if (!ordered_hashmap_contains(c->users, *m)) { _cleanup_(item_freep) Item *j = item_new(ADD_USER, *m, /* filename= */ NULL, /* line= */ 0); if (!j) @@ -1584,8 +1584,8 @@ static int add_implicit(Context *c) { TAKE_PTR(j); } - if (!(ordered_hashmap_get(c->users, g) || - ordered_hashmap_get(c->groups, g))) { + if (!(ordered_hashmap_contains(c->users, g) || + ordered_hashmap_contains(c->groups, g))) { _cleanup_(item_freep) Item *j = item_new(ADD_GROUP, g, /* filename= */ NULL, /* line= */ 0); if (!j) diff --git a/src/test/test-engine.c b/src/test/test-engine.c index 06a1d9e6fd49a..e1a2f7ea04823 100644 --- a/src/test/test-engine.c +++ b/src/test/test-engine.c @@ -184,32 +184,32 @@ int main(int argc, char *argv[]) { assert_se(manager_add_job(m, JOB_START, a_conj, JOB_REPLACE, NULL, &j) == -EDEADLK); manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); - assert_se(!hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); - assert_se(!hashmap_get(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a)); - assert_se(!hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), c)); - assert_se(!hashmap_get(unit_get_dependencies(c, UNIT_RELOAD_PROPAGATED_FROM), a)); + ASSERT_FALSE(hashmap_contains(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); + ASSERT_FALSE(hashmap_contains(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a)); + ASSERT_FALSE(hashmap_contains(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), c)); + ASSERT_FALSE(hashmap_contains(unit_get_dependencies(c, UNIT_RELOAD_PROPAGATED_FROM), a)); assert_se(unit_add_dependency(a, UNIT_PROPAGATES_RELOAD_TO, b, true, UNIT_DEPENDENCY_UDEV) >= 0); assert_se(unit_add_dependency(a, UNIT_PROPAGATES_RELOAD_TO, c, true, UNIT_DEPENDENCY_PROC_SWAP) >= 0); - assert_se( hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); - assert_se( hashmap_get(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a)); - assert_se( hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), c)); - assert_se( hashmap_get(unit_get_dependencies(c, UNIT_RELOAD_PROPAGATED_FROM), a)); + ASSERT_TRUE(hashmap_contains(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); + ASSERT_TRUE(hashmap_contains(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a)); + ASSERT_TRUE(hashmap_contains(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), c)); + ASSERT_TRUE(hashmap_contains(unit_get_dependencies(c, UNIT_RELOAD_PROPAGATED_FROM), a)); unit_remove_dependencies(a, UNIT_DEPENDENCY_UDEV); - assert_se(!hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); - assert_se(!hashmap_get(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a)); - assert_se( hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), c)); - assert_se( hashmap_get(unit_get_dependencies(c, UNIT_RELOAD_PROPAGATED_FROM), a)); + ASSERT_FALSE(hashmap_contains(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); + ASSERT_FALSE(hashmap_contains(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a)); + ASSERT_TRUE(hashmap_contains(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), c)); + ASSERT_TRUE(hashmap_contains(unit_get_dependencies(c, UNIT_RELOAD_PROPAGATED_FROM), a)); unit_remove_dependencies(a, UNIT_DEPENDENCY_PROC_SWAP); - assert_se(!hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); - assert_se(!hashmap_get(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a)); - assert_se(!hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), c)); - assert_se(!hashmap_get(unit_get_dependencies(c, UNIT_RELOAD_PROPAGATED_FROM), a)); + ASSERT_FALSE(hashmap_contains(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); + ASSERT_FALSE(hashmap_contains(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a)); + ASSERT_FALSE(hashmap_contains(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), c)); + ASSERT_FALSE(hashmap_contains(unit_get_dependencies(c, UNIT_RELOAD_PROPAGATED_FROM), a)); assert_se(manager_load_unit(m, "unit-with-multiple-dashes.service", NULL, NULL, &unit_with_multiple_dashes) >= 0); diff --git a/src/test/test-hashmap-plain.c b/src/test/test-hashmap-plain.c index 5cc84e884d92f..da5866ce4f60c 100644 --- a/src/test/test-hashmap-plain.c +++ b/src/test/test-hashmap-plain.c @@ -264,7 +264,7 @@ TEST(hashmap_remove1) { r = hashmap_get(m, "key 2"); ASSERT_STREQ(r, "val 2"); - assert_se(!hashmap_get(m, "key 1")); + ASSERT_FALSE(hashmap_contains(m, "key 1")); } TEST(hashmap_remove2) { @@ -295,7 +295,7 @@ TEST(hashmap_remove2) { r = hashmap_get(m, key2); ASSERT_STREQ(r, val2); - assert_se(!hashmap_get(m, key1)); + ASSERT_FALSE(hashmap_contains(m, key1)); } TEST(hashmap_remove_value) { @@ -322,14 +322,14 @@ TEST(hashmap_remove_value) { r = hashmap_get(m, "key 2"); ASSERT_STREQ(r, "val 2"); - assert_se(!hashmap_get(m, "key 1")); + ASSERT_FALSE(hashmap_contains(m, "key 1")); r = hashmap_remove_value(m, "key 2", val1); ASSERT_NULL(r); r = hashmap_get(m, "key 2"); ASSERT_STREQ(r, "val 2"); - assert_se(!hashmap_get(m, "key 1")); + ASSERT_FALSE(hashmap_contains(m, "key 1")); } TEST(hashmap_remove_and_put) { @@ -354,7 +354,7 @@ TEST(hashmap_remove_and_put) { r = hashmap_get(m, "key 2"); ASSERT_STREQ(r, "val 2"); - assert_se(!hashmap_get(m, "key 1")); + ASSERT_FALSE(hashmap_contains(m, "key 1")); valid = hashmap_put(m, "key 3", (void*) (const char *) "val 3"); assert_se(valid == 1); @@ -388,7 +388,7 @@ TEST(hashmap_remove_and_replace) { r = hashmap_get(m, key2); assert_se(r == key2); - assert_se(!hashmap_get(m, key1)); + ASSERT_FALSE(hashmap_contains(m, key1)); valid = hashmap_put(m, key3, key3); assert_se(valid == 1); @@ -396,7 +396,7 @@ TEST(hashmap_remove_and_replace) { assert_se(valid == 0); r = hashmap_get(m, key2); assert_se(r == key2); - assert_se(!hashmap_get(m, key3)); + ASSERT_FALSE(hashmap_contains(m, key3)); /* Repeat this test several times to increase the chance of hitting * the less likely case in hashmap_remove_and_replace where it @@ -410,7 +410,7 @@ TEST(hashmap_remove_and_replace) { UINT_TO_PTR(10*i + 2), UINT_TO_PTR(10*i + 2)); assert_se(valid == 0); - assert_se(!hashmap_get(m, UINT_TO_PTR(10*i + 1))); + ASSERT_FALSE(hashmap_contains(m, UINT_TO_PTR(10*i + 1))); for (j = 2; j < 7; j++) { r = hashmap_get(m, UINT_TO_PTR(10*i + j)); assert_se(r == UINT_TO_PTR(10*i + j)); @@ -914,10 +914,10 @@ TEST(path_hashmap) { assert_se(hashmap_get(h, "/.///./foox//.//") == INT_TO_PTR(4)); assert_se(hashmap_get(h, "/foox/") == INT_TO_PTR(4)); assert_se(hashmap_get(h, "/foox") == INT_TO_PTR(4)); - assert_se(!hashmap_get(h, "foox")); + ASSERT_FALSE(hashmap_contains(h, "foox")); assert_se(hashmap_get(h, "foo/bar/quux") == INT_TO_PTR(6)); assert_se(hashmap_get(h, "foo////bar////quux/////") == INT_TO_PTR(6)); - assert_se(!hashmap_get(h, "/foo////bar////quux/////")); + ASSERT_FALSE(hashmap_contains(h, "/foo////bar////quux/////")); assert_se(hashmap_get(h, "foo././//ba.r////.quux///.//.") == INT_TO_PTR(9)); } @@ -950,14 +950,14 @@ TEST(string_strv_hashmap) { ASSERT_TRUE(strv_equal(s, STRV_MAKE("BAR"))); string_strv_hashmap_remove(m, "foo", "BAR"); - ASSERT_NULL(hashmap_get(m, "foo")); + ASSERT_FALSE(hashmap_contains(m, "foo")); string_strv_hashmap_remove(m, "xxx", "BAR"); ASSERT_NOT_NULL((s = hashmap_get(m, "xxx"))); ASSERT_TRUE(strv_equal(s, STRV_MAKE("bar"))); string_strv_hashmap_remove(m, "xxx", "bar"); - ASSERT_NULL(hashmap_get(m, "xxx")); + ASSERT_FALSE(hashmap_contains(m, "xxx")); ASSERT_TRUE(hashmap_isempty(m)); } From 13db63e920ac57f06c551f4b1fe840255c4681d8 Mon Sep 17 00:00:00 2001 From: Skye Soss Date: Mon, 2 Mar 2026 10:50:12 -0600 Subject: [PATCH 0007/1296] network: add DHCPv6 message types to string table (#40912) Adds the DHCPv6 message types ADDR-REG-INFORM and ADDR-REG-REPLY to the DHCPv6 message types string table. Follow-up for 1e55da38aab0a7e7d5ba4de3243512fa70401df9. --- src/libsystemd-network/dhcp6-protocol.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libsystemd-network/dhcp6-protocol.c b/src/libsystemd-network/dhcp6-protocol.c index be0f651f1ab4a..2633a23861ced 100644 --- a/src/libsystemd-network/dhcp6-protocol.c +++ b/src/libsystemd-network/dhcp6-protocol.c @@ -52,6 +52,8 @@ static const char * const dhcp6_message_type_table[_DHCP6_MESSAGE_TYPE_MAX] = { [DHCP6_MESSAGE_DISCONNECT] = "Disconnect", [DHCP6_MESSAGE_STATE] = "State", [DHCP6_MESSAGE_CONTACT] = "Contact", + [DHCP6_MESSAGE_ADDR_REG_INFORM] = "Address Registration Inform", + [DHCP6_MESSAGE_ADDR_REG_REPLY] = "Address Registration Reply", }; DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, DHCP6MessageType); From e144a25b13212028ec36fd0b299c0b9b31f27d84 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 2 Mar 2026 17:56:53 +0100 Subject: [PATCH 0008/1296] update TODO --- TODO | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/TODO b/TODO index 072ce83f0aeee..42b57364596c2 100644 --- a/TODO +++ b/TODO @@ -121,6 +121,26 @@ Deprecations and removals: Features: +* drop NV_ORDERLY flag from the product uuid nvpcr. Effect of the flag is that + it pushes the thing into TPM RAM, but a TPM usually has very little of that, + less than NVRAM. hence setting the flag amplifies space issues. Unsetting the + flag increases wear issues on the NVRAM, however, but this should be limited + for the product uuid nvpcr, since its only changed once per boot. this needs + to be configurable by nvpcr however, as other nvpcrs are differnt, + i.e. verity one receives many writes during system uptime quite + possibly. (also, NV_ORDERLY makes stuff faster, and dropping it costs + possibly up to 100ms supposedly) + +* create a hwdb database that contains tpm quirks, i.e. knows whether NV_ORDERLY + + TPM2_NT_EXTEND can be safely mixed or + not. (see https://github.com/systemd/systemd/issues/40485#issuecomment-3984855537) + +* instead of going directly for DefineSpace when initializing nvpcrs, check if + they exist first. apparently DEfineSpace is broken on some tpms, and also + creates log spam if the nvindex already exists. + +* on first login of a user, measure its identity to some nvpcr + * sd-lldp: pick up 802.3 maximum frame size/mtu, to be able to detect jumbo frame capable networks From 6e59d22380a915d5b9bb195e33a96c509320c0c4 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 2 Mar 2026 23:41:26 +0900 Subject: [PATCH 0009/1296] udev/varlink: ignore polkit related field Follow-up for da7374b2ae07b4d3801f5187aacc199978793680. --- src/udev/udev-varlink.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/udev/udev-varlink.c b/src/udev/udev-varlink.c index 78bcc11fd4f4b..355e5a7860530 100644 --- a/src/udev/udev-varlink.c +++ b/src/udev/udev-varlink.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "bus-polkit.h" #include "fd-util.h" #include "json-util.h" #include "log.h" @@ -18,7 +19,9 @@ static int vl_method_reload(sd_varlink *link, sd_json_variant *parameters, sd_va assert(link); - r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL); + /* Currently, udevd does not support polkit, but the varlink IDL says that io.systemd.service.Reload + * optionally takes the polkit field. Let's silently ignore the field. */ + r = sd_varlink_dispatch(link, parameters, dispatch_table_polkit_only, /* userdata= */ NULL); if (r != 0) return r; From 83b8daa032cd0adb538cfd9467e6acf2c44aa661 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Wed, 25 Feb 2026 19:13:37 +0100 Subject: [PATCH 0010/1296] nspawn: actually mask certain files under /proc/ /run/systemd/inaccessible/ exists only on host - in the container we have /run/host/inaccessible/, and since all the inaccessible mounts have MOUNT_IN_USERNS we need to use the latter one, otherwise the masking gets silently skipped: ~# SYSTEMD_LOG_LEVEL=debug systemd-nspawn -q --directory=foo ls -la /proc/kallsyms ... Bind-mounting /run/systemd/inaccessible/reg on /proc/kallsyms (MS_BIND "")... Failed to mount /run/systemd/inaccessible/reg (type n/a) on /proc/kallsyms (MS_BIND ""): No such file or directory Changing mount flags /proc/kallsyms (MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_REMOUNT|MS_BIND "")... Failed to mount n/a (type n/a) on /proc/kallsyms (MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_REMOUNT|MS_BIND ""): Invalid argument Bind-mounting /run/systemd/inaccessible/reg on /proc/kcore (MS_BIND "")... Failed to mount /run/systemd/inaccessible/reg (type n/a) on /proc/kcore (MS_BIND ""): No such file or directory Changing mount flags /proc/kcore (MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_REMOUNT|MS_BIND "")... Failed to mount n/a (type n/a) on /proc/kcore (MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_REMOUNT|MS_BIND ""): Invalid argument ... Inner child finished, invoking payload. -r--r--r--. 1 root root 0 Feb 25 13:19 /proc/kallsyms --- src/nspawn/nspawn-mount.c | 2 +- test/units/TEST-13-NSPAWN.nspawn.sh | 40 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c index cfb4aac6ff35b..282a29c359f70 100644 --- a/src/nspawn/nspawn-mount.c +++ b/src/nspawn/nspawn-mount.c @@ -534,7 +534,7 @@ int mount_all(const char *dest, const char *selinux_apifs_context) { #define PROC_INACCESSIBLE_REG(path) \ - { "/run/systemd/inaccessible/reg", (path), NULL, NULL, MS_BIND, \ + { "/run/host/inaccessible/reg", (path), NULL, NULL, MS_BIND, \ MOUNT_IN_USERNS|MOUNT_APPLY_APIVFS_RO }, /* Bind mount first ... */ \ { NULL, (path), NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, \ MOUNT_IN_USERNS|MOUNT_APPLY_APIVFS_RO } /* Then, make it r/o */ diff --git a/test/units/TEST-13-NSPAWN.nspawn.sh b/test/units/TEST-13-NSPAWN.nspawn.sh index c2fa9eaaf8940..c753734c33137 100755 --- a/test/units/TEST-13-NSPAWN.nspawn.sh +++ b/test/units/TEST-13-NSPAWN.nspawn.sh @@ -396,6 +396,46 @@ EOF (! systemd-nspawn --rlimit==) } +testcase_check_default_inaccessible_paths() { + local root container inaccessible_paths path exp + + # Taken from src/nspawn/nspawn-mount.c:mount_all() + inaccessible_paths=( + "/proc/kallsyms" + "/proc/kcore" + "/proc/keys" + "/proc/sysrq-trigger" + "/proc/timer_list" + ) + + root="$(mktemp -d /var/lib/machines/TEST-13-NSPAWN.default_inaccessible_paths.XXX)" + container="$(basename "$root")" + create_dummy_container "$root" + + # Each inaccessible path should have zeroed permissions, which stat's %a reports as a single 0 + for path in "${inaccessible_paths[@]}"; do + systemd-nspawn --directory="$root" \ + bash -xec "ls -l $path; [[ \$(stat --format=%a $path) -eq 0 ]]" + done + + # SYSTEMD_NSPAWN_API_VFS_WRITABLE=yes mounts certain API directories under /sys/ and /proc/sys/ + # as writable, and it also skips the path masking (by dropping the MOUNT_APPLY_APIVFS_RO flag) + for path in "${inaccessible_paths[@]}"; do + exp="$(stat --format=%a "$path")" + SYSTEMD_NSPAWN_API_VFS_WRITABLE=yes systemd-nspawn --directory="$root" \ + bash -xec "ls -l $path; [[ \$(stat --format=%a $path) -eq $exp ]]" + done + + # SYSTEMD_NSPAWN_API_VFS_WRITABLE=network mounts only /proc/sys/net/ as writable but doesn't + # drop the MOUNT_APPLY_APIVFS_RO flag, so the masking should still apply + for path in "${inaccessible_paths[@]}"; do + SYSTEMD_NSPAWN_API_VFS_WRITABLE=network systemd-nspawn --directory="$root" \ + bash -xec "ls -l $path; [[ \$(stat --format=%a $path) -eq 0 ]]" + done + + rm -fr "$root" +} + nspawn_settings_cleanup() { for dev in sd-host-only sd-shared{1,2,3} sd-macvlan{1,2} sd-ipvlan{1,2}; do ip link del "$dev" || : From 864c08f5758ff8376bf5a59d84790135fcce51aa Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Mon, 2 Mar 2026 20:58:09 +0100 Subject: [PATCH 0011/1296] import,nspawn: fix a couple of typos in mountfsd --- src/import/pull-oci.c | 2 +- src/import/pull-tar.c | 2 +- src/nspawn/nspawn.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/import/pull-oci.c b/src/import/pull-oci.c index e9e0a1c6b3419..cbbad44eb1e06 100644 --- a/src/import/pull-oci.c +++ b/src/import/pull-oci.c @@ -489,7 +489,7 @@ static int oci_pull_job_on_open_disk(PullJob *j) { DISSECT_IMAGE_FOREIGN_UID, &st->tree_fd); if (r < 0) - return log_error_errno(r, "Failed to mount directory via mountsd: %m"); + return log_error_errno(r, "Failed to mount directory via mountfsd: %m"); } else { if (i->flags & IMPORT_BTRFS_SUBVOL) r = btrfs_subvol_make_fallback(AT_FDCWD, st->temp_path, 0755); diff --git a/src/import/pull-tar.c b/src/import/pull-tar.c index bfc218b6bcbd0..f4a8bfca62276 100644 --- a/src/import/pull-tar.c +++ b/src/import/pull-tar.c @@ -280,7 +280,7 @@ static int tar_pull_make_local_copy(TarPull *p) { _cleanup_(sd_varlink_unrefp) sd_varlink *mountfsd_link = NULL; r = mountfsd_connect(&mountfsd_link); if (r < 0) - return log_error_errno(r, "Failed to connect to mountsd: %m"); + return log_error_errno(r, "Failed to connect to mountfsd: %m"); /* Usually, tar_pull_job_on_open_disk_tar() would allocate ->tree_fd for us, but if * already downloaded the image before, and are just making a copy of the original diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 722be8bbf7cb6..fa92b0a8861ec 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -6212,7 +6212,7 @@ static int run(int argc, char *argv[]) { r = mountfsd_connect(&mountfsd_link); if (r < 0) { - log_error_errno(r, "Failed to connect to mountsd: %m"); + log_error_errno(r, "Failed to connect to mountfsd: %m"); goto finish; } From 8d421c087035a6728421663f1b202e055dbb510b Mon Sep 17 00:00:00 2001 From: noxiouz Date: Thu, 26 Feb 2026 03:31:24 +0000 Subject: [PATCH 0012/1296] network: fix LLDP field type in Interface Varlink IDL sd_lldp_tx_describe() returns a single object (the LLDP TX configuration), but the IDL declared LLDP as SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE. This caused server-side validation failures ("Field 'LLDP' should be an array, but it is of type 'object'") whenever networkctl status was called on an interface with LLDP TX active. Also fix the field comment: the LLDP field represents the transmit configuration, not received neighbors. Follow-up for dd2934d44e2c9cd1a92ae0fd6806985c4bc031e6. --- src/shared/varlink-io.systemd.Network.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/varlink-io.systemd.Network.c b/src/shared/varlink-io.systemd.Network.c index c67b857f12dcb..9ae737714fb61 100644 --- a/src/shared/varlink-io.systemd.Network.c +++ b/src/shared/varlink-io.systemd.Network.c @@ -532,8 +532,8 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(DHCPv4Client, SD_VARLINK_OBJECT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("DHCPv6 client configuration and lease information"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(DHCPv6Client, DHCPv6Client, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("LLDP neighbors discovered on this interface"), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(LLDP, LLDPNeighbor, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE)); + SD_VARLINK_FIELD_COMMENT("LLDP transmit configuration for this interface"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(LLDP, LLDPNeighbor, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD( Describe, From 66a43b02e6e6d0e5c6ef0834ea757eb7e331bbd9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 2 Mar 2026 22:57:56 +0100 Subject: [PATCH 0013/1296] docs: document the "verity" NvPCR measurements I forgot this when I posted 32f405074a3aa221982ad92a7f61560b9f6a2b03, let's add it now. --- docs/TPM2_PCR_MEASUREMENTS.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/TPM2_PCR_MEASUREMENTS.md b/docs/TPM2_PCR_MEASUREMENTS.md index 53317b732748e..571a4c1077201 100644 --- a/docs/TPM2_PCR_MEASUREMENTS.md +++ b/docs/TPM2_PCR_MEASUREMENTS.md @@ -293,3 +293,18 @@ volume name, a ":" separator, the UUID of the LUKS superblock, a ":" separator, a brief string identifying the unlock mechanism, a ":" separator, and finally the LUKS slot number used. Example string: `cryptsetup-keyslot:root:1e023a55-60f9-4b6b-9b80-67438dc5f065:tpm2:1` + +## PCR/NvPCR Measurements Made by `systemd-veritysetup` + image dissection logic (Userspace) + +### NvPCR `verity` (base+2), Verity root hash + signature info of activated Verity images + +The `systemd-veritysetup@.service` service as well as any component using the +image dissection logic (i.e. `RootImage=` in unit files, or `systemd-nspawn +--image=`, `systemd-tmpfiles --image=` and similar) will measure information +about activated Verity images before they are activated. + +→ **Measured hash** covers the string `verity:`, followed by the Verity device +name, followed by `:`, followed by a hexadecimal formatted string indicating +the root hash of the Verity image, followed by `:`, followed by a comma +separatec list of PKCS#7 signature key's serial (formatted in hexadecimal), `/`, and +key issuer (formatted in Base64). From fbf533859f312de19e25979ebf243d6929c00d1d Mon Sep 17 00:00:00 2001 From: noxiouz Date: Mon, 2 Mar 2026 22:42:16 +0000 Subject: [PATCH 0014/1296] network: fix error aggregation in wwan_check_and_set_configuration() When removing marked routes, the condition `if (ret)` incorrectly overwrites any previously accumulated error in `ret` with the latest return value `r`, even if `r >= 0` (success). This means an earlier real error can be silently cleared by a subsequent successful route_remove() call. The parallel address_remove() block just above uses the correct `if (r < 0)` pattern. Apply the same fix to the route_remove() block. --- src/network/networkd-wwan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/networkd-wwan.c b/src/network/networkd-wwan.c index 325d2b028188b..2b10eb3e4ee96 100644 --- a/src/network/networkd-wwan.c +++ b/src/network/networkd-wwan.c @@ -529,7 +529,7 @@ static int link_apply_bearer_impl(Link *link, Bearer *b) { continue; r = route_remove(route, link->manager); - if (ret) + if (r < 0) ret = r; } From 1f0bc341c5a62b48e139eb84606e480d8740ff1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20L=C3=BCke?= Date: Tue, 3 Mar 2026 10:37:49 +0900 Subject: [PATCH 0015/1296] meson: Work around Meson install_subdir limitation When install_subdir encounters a mkosi.tools tree with a /bin to /usr/bin symlink it fails to copy it because it dereferences but still treats it like a file. Work around the Meson bug by excluding the mkosi.tools tree from installation like mkosi.local is excluded. We anyway don't want the tools tree end up there. --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index a6ec8e11ac446..3672005d75b17 100644 --- a/meson.build +++ b/meson.build @@ -2777,7 +2777,7 @@ if install_tests install_subdir('mkosi', install_dir : testsdir, exclude_files : ['mkosi.local.conf', 'mkosi.key', 'mkosi.crt'], - exclude_directories : ['mkosi.local']) + exclude_directories : ['mkosi.local', 'mkosi.tools']) endif ############################################################ From 36d7e177ad0814eb85bcafb603d622794e51fe10 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 2 Mar 2026 18:09:23 +0100 Subject: [PATCH 0016/1296] fd-util: make use of XAT_FDROOT in path_is_root_at() --- src/basic/fd-util.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index 99eff99874d14..d043927a2c9ec 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -1061,10 +1061,6 @@ int path_is_root_at(int dir_fd, const char *path) { dir_fd = fd; } - _cleanup_close_ int root_fd = open("/", O_PATH|O_DIRECTORY|O_CLOEXEC); - if (root_fd < 0) - return -errno; - /* Even if the root directory has the same inode as our fd, the fd may not point to the root * directory "/", and we also need to check that the mount ids are the same. Otherwise, a construct * like the following could be used to trick us: @@ -1073,7 +1069,7 @@ int path_is_root_at(int dir_fd, const char *path) { * $ mount --bind / /tmp/x */ - return fds_are_same_mount(dir_fd, root_fd); + return fds_are_same_mount(dir_fd, XAT_FDROOT); } int fds_are_same_mount(int fd1, int fd2) { From 97fe03e12faa4e50d25a3ca8999967801c7e2da9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Feb 2026 12:13:31 +0100 Subject: [PATCH 0017/1296] stat-util: add statx() flavours of stat_verify_regular() + stat_verify_socket() --- src/basic/stat-util.c | 71 ++++++++++++++++++++++++++++--------------- src/basic/stat-util.h | 2 ++ 2 files changed, 48 insertions(+), 25 deletions(-) diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 43f82dd926809..c5702ca6ab3df 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -49,22 +49,35 @@ static int verify_stat_at( return verify ? r : r >= 0; } +static int mode_verify_regular(mode_t mode) { + if (S_ISDIR(mode)) + return -EISDIR; + + if (S_ISLNK(mode)) + return -ELOOP; + + if (!S_ISREG(mode)) + return -EBADFD; + + return 0; +} + int stat_verify_regular(const struct stat *st) { assert(st); /* Checks whether the specified stat() structure refers to a regular file. If not returns an * appropriate error code. */ - if (S_ISDIR(st->st_mode)) - return -EISDIR; + return mode_verify_regular(st->st_mode); +} - if (S_ISLNK(st->st_mode)) - return -ELOOP; +int statx_verify_regular(const struct statx *stx) { + assert(stx); - if (!S_ISREG(st->st_mode)) - return -EBADFD; + if (!FLAGS_SET(stx->stx_mask, STATX_TYPE)) + return -ENODATA; - return 0; + return mode_verify_regular(stx->stx_mode); } int verify_regular_at(int fd, const char *path, bool follow) { @@ -78,31 +91,29 @@ int fd_verify_regular(int fd) { return verify_regular_at(fd, /* path= */ NULL, /* follow= */ false); } -int stat_verify_directory(const struct stat *st) { - assert(st); - - if (S_ISLNK(st->st_mode)) +static int mode_verify_directory(mode_t mode) { + if (S_ISLNK(mode)) return -ELOOP; - if (!S_ISDIR(st->st_mode)) + if (!S_ISDIR(mode)) return -ENOTDIR; return 0; } +int stat_verify_directory(const struct stat *st) { + assert(st); + + return mode_verify_directory(st->st_mode); +} + int statx_verify_directory(const struct statx *stx) { assert(stx); if (!FLAGS_SET(stx->stx_mask, STATX_TYPE)) return -ENODATA; - if (S_ISLNK(stx->stx_mode)) - return -ELOOP; - - if (!S_ISDIR(stx->stx_mode)) - return -ENOTDIR; - - return 0; + return mode_verify_directory(stx->stx_mode); } int fd_verify_directory(int fd) { @@ -142,21 +153,31 @@ int is_symlink(const char *path) { return verify_stat_at(AT_FDCWD, path, false, stat_verify_symlink, false); } -int stat_verify_socket(const struct stat *st) { - assert(st); - - if (S_ISLNK(st->st_mode)) +static mode_t mode_verify_socket(mode_t mode) { + if (S_ISLNK(mode)) return -ELOOP; - if (S_ISDIR(st->st_mode)) + if (S_ISDIR(mode)) return -EISDIR; - if (!S_ISSOCK(st->st_mode)) + if (!S_ISSOCK(mode)) return -ENOTSOCK; return 0; } +int stat_verify_socket(const struct stat *st) { + assert(st); + + return mode_verify_socket(st->st_mode); +} + +int statx_verify_socket(const struct statx *stx) { + assert(stx); + + return mode_verify_socket(stx->stx_mode); +} + int is_socket(const char *path) { assert(!isempty(path)); return verify_stat_at(AT_FDCWD, path, /* follow= */ true, stat_verify_socket, /* verify= */ false); diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index 0e4bec513b1ad..2336c775e9607 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -7,6 +7,7 @@ #include "basic-forward.h" int stat_verify_regular(const struct stat *st); +int statx_verify_regular(const struct statx *stx); int verify_regular_at(int fd, const char *path, bool follow); int fd_verify_regular(int fd); @@ -21,6 +22,7 @@ int fd_verify_symlink(int fd); int is_symlink(const char *path); int stat_verify_socket(const struct stat *st); +int statx_verify_socket(const struct statx *stx); int is_socket(const char *path); int stat_verify_linked(const struct stat *st); From 004e60e0fca861db6a924c5e0e7c6cb02ea02fd2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Feb 2026 11:43:11 +0100 Subject: [PATCH 0018/1296] chase: put limit on overall chase cycles Let's add some protections in case we deal with inodes owned by an untrusted person, with concurrent access: let's put a limit on how long we traverse, and fail eventually so that live changes cannot send us in circles indefinitely. This reworks the current CHASE_MAX logic so that it not only applies to symlinks transitions, but to any transitions. This also bumps CHASE_MAX a bit, given that it's now bumped on every single iteration of the loop. --- src/basic/chase.c | 14 +++++++------- src/basic/chase.h | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/basic/chase.c b/src/basic/chase.c index abe85a2a0892b..11cbcbe3e4d7b 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -128,7 +128,6 @@ static int chaseat_needs_absolute(int dir_fd, const char *path) { int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd) { _cleanup_free_ char *buffer = NULL, *done = NULL; _cleanup_close_ int fd = -EBADF, root_fd = -EBADF; - unsigned max_follow = CHASE_MAX; /* how many symlinks to follow before giving up and returning ELOOP */ bool exists = true, append_trail_slash = false; struct stat st; /* stat obtained from fd */ bool need_absolute = false; /* allocate early to avoid compiler warnings around goto */ @@ -334,12 +333,18 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY) + FLAGS_SET(flags, CHASE_MUST_BE_REGULAR) + FLAGS_SET(flags, CHASE_MUST_BE_SOCKET) > 1) return -EBADSLT; - for (todo = buffer;;) { + todo = buffer; + for (unsigned n_steps = 0;; n_steps++) { _cleanup_free_ char *first = NULL; _cleanup_close_ int child = -EBADF; struct stat st_child; const char *e; + /* If people change our tree behind our back, they might send us in circles. Put a limit on + * things */ + if (n_steps > CHASE_MAX) + return -ELOOP; + r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e); if (r < 0) return r; @@ -495,11 +500,6 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (FLAGS_SET(flags, CHASE_PROHIBIT_SYMLINKS)) return log_prohibited_symlink(child, flags); - /* This is a symlink, in this case read the destination. But let's make sure we - * don't follow symlinks without bounds. */ - if (--max_follow <= 0) - return -ELOOP; - r = readlinkat_malloc(fd, first, &destination); if (r < 0) return r; diff --git a/src/basic/chase.h b/src/basic/chase.h index d0674aae73c99..b770f45a1ecd8 100644 --- a/src/basic/chase.h +++ b/src/basic/chase.h @@ -35,7 +35,7 @@ typedef enum ChaseFlags { bool unsafe_transition(const struct stat *a, const struct stat *b); /* How many iterations to execute before returning -ELOOP */ -#define CHASE_MAX 32 +#define CHASE_MAX 128U int chase(const char *path_with_prefix, const char *root, ChaseFlags flags, char **ret_path, int *ret_fd); From c8031ff37eb44a1bebb4a2c3ccfae51ac09982a5 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Feb 2026 12:15:14 +0100 Subject: [PATCH 0019/1296] chase: port to statx() In one of the next commits we want to acquire .stx_mnt_id from statx() for each inode we traverse (plain fstat() doesn't provide that field). Hence let's port chase() over to statx() as preparation for that. No change in behaviour. --- src/basic/chase.c | 125 ++++++++++++++++++++++++++-------------- src/basic/chase.h | 3 +- src/tmpfiles/tmpfiles.c | 2 +- 3 files changed, 86 insertions(+), 44 deletions(-) diff --git a/src/basic/chase.c b/src/basic/chase.c index 11cbcbe3e4d7b..1d3596bb796e4 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -31,15 +31,33 @@ CHASE_MUST_BE_REGULAR | \ CHASE_MUST_BE_SOCKET) -bool unsafe_transition(const struct stat *a, const struct stat *b) { - /* Returns true if the transition from a to b is safe, i.e. that we never transition from unprivileged to - * privileged files or directories. Why bother? So that unprivileged code can't symlink to privileged files - * making us believe we read something safe even though it isn't safe in the specific context we open it in. */ +static bool uid_unsafe_transition(uid_t a, uid_t b) { + /* Returns true if the transition from a to b is safe, i.e. that we never transition from + * unprivileged to privileged files or directories. Why bother? So that unprivileged code can't + * symlink to privileged files making us believe we read something safe even though it isn't safe in + * the specific context we open it in. */ - if (a->st_uid == 0) /* Transitioning from privileged to unprivileged is always fine */ + if (a == 0) /* Transitioning from privileged to unprivileged is always fine */ return false; - return a->st_uid != b->st_uid; /* Otherwise we need to stay within the same UID */ + return a != b; /* Otherwise we need to stay within the same UID */ +} + +int statx_unsafe_transition(const struct statx *a, const struct statx *b) { + assert(a); + assert(b); + + if (!FLAGS_SET(a->stx_mask, STATX_UID) || !FLAGS_SET(b->stx_mask, STATX_UID)) + return -ENODATA; + + return uid_unsafe_transition(a->stx_uid, b->stx_uid); +} + +bool stat_unsafe_transition(const struct stat *a, const struct stat *b) { + assert(a); + assert(b); + + return uid_unsafe_transition(a->st_uid, b->st_uid); } static int log_unsafe_transition(int a, int b, const char *path, ChaseFlags flags) { @@ -129,9 +147,10 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int _cleanup_free_ char *buffer = NULL, *done = NULL; _cleanup_close_ int fd = -EBADF, root_fd = -EBADF; bool exists = true, append_trail_slash = false; - struct stat st; /* stat obtained from fd */ + struct statx stx; /* statx obtained from fd */ bool need_absolute = false; /* allocate early to avoid compiler warnings around goto */ const char *todo; + unsigned mask = STATX_TYPE|STATX_UID|STATX_INO; int r; assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT)); @@ -245,8 +264,9 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd < 0) return -errno; - if (fstat(fd, &st) < 0) - return -errno; + r = xstatx(fd, /* path= */ NULL, /* flags= */ 0, mask, &stx); + if (r < 0) + return r; exists = true; goto success; @@ -306,8 +326,9 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd < 0) return -errno; - if (fstat(fd, &st) < 0) - return -errno; + r = xstatx(fd, /* path= */ NULL, /* flags= */ 0, mask, &stx); + if (r < 0) + return r; /* If we get AT_FDCWD, we always resolve symlinks relative to the host's root. Only if a positive * directory file descriptor is provided we will look at CHASE_AT_RESOLVE_IN_ROOT to determine @@ -337,7 +358,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int for (unsigned n_steps = 0;; n_steps++) { _cleanup_free_ char *first = NULL; _cleanup_close_ int child = -EBADF; - struct stat st_child; + struct statx stx_child; const char *e; /* If people change our tree behind our back, they might send us in circles. Put a limit on @@ -359,7 +380,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (streq(first, "..")) { _cleanup_free_ char *parent = NULL; _cleanup_close_ int fd_parent = -EBADF; - struct stat st_parent; + struct statx stx_parent; /* If we already are at the top, then going up will not change anything. This is * in-line with how the kernel handles this. */ @@ -373,13 +394,14 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd_parent < 0) return -errno; - if (fstat(fd_parent, &st_parent) < 0) - return -errno; + r = xstatx(fd_parent, /* path= */ NULL, /* flags= */ 0, mask, &stx_parent); + if (r < 0) + return r; /* If we opened the same directory, that _may_ indicate that we're at the host root * directory. Let's confirm that in more detail with dir_fd_is_root(). And if so, * going up won't change anything. */ - if (stat_inode_same(&st_parent, &st)) { + if (statx_inode_same(&stx_parent, &stx)) { r = dir_fd_is_root(fd); if (r < 0) return r; @@ -419,35 +441,44 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (FLAGS_SET(flags, CHASE_STEP)) goto chased_one; - if (FLAGS_SET(flags, CHASE_SAFE) && - unsafe_transition(&st, &st_parent)) - return log_unsafe_transition(fd, fd_parent, path, flags); + if (FLAGS_SET(flags, CHASE_SAFE)) { + r = statx_unsafe_transition(&stx, &stx_parent); + if (r < 0) + return r; + if (r > 0) + return log_unsafe_transition(fd, fd_parent, path, flags); + } /* If the path ends on a "..", and CHASE_PARENT is specified then our current 'fd' is * the child of the returned normalized path, not the parent as requested. To correct * this we have to go *two* levels up. */ if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) { _cleanup_close_ int fd_grandparent = -EBADF; - struct stat st_grandparent; + struct statx stx_grandparent; fd_grandparent = openat(fd_parent, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY); if (fd_grandparent < 0) return -errno; - if (fstat(fd_grandparent, &st_grandparent) < 0) - return -errno; + r = xstatx(fd_grandparent, /* path= */ NULL, /* flags= */ 0, mask, &stx_grandparent); + if (r < 0) + return r; - if (FLAGS_SET(flags, CHASE_SAFE) && - unsafe_transition(&st_parent, &st_grandparent)) - return log_unsafe_transition(fd_parent, fd_grandparent, path, flags); + if (FLAGS_SET(flags, CHASE_SAFE)) { + r = statx_unsafe_transition(&stx_parent, &stx_grandparent); + if (r < 0) + return r; + if (r > 0) + return log_unsafe_transition(fd_parent, fd_grandparent, path, flags); + } - st = st_grandparent; + stx = stx_grandparent; close_and_replace(fd, fd_grandparent); break; } /* update fd and stat */ - st = st_parent; + stx = stx_parent; close_and_replace(fd, fd_parent); continue; } @@ -483,18 +514,23 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int } /* ... and then check what it actually is. */ - if (fstat(child, &st_child) < 0) - return -errno; + r = xstatx(child, /* path= */ NULL, /* flags= */ 0, mask, &stx_child); + if (r < 0) + return r; - if (FLAGS_SET(flags, CHASE_SAFE) && - unsafe_transition(&st, &st_child)) - return log_unsafe_transition(fd, child, path, flags); + if (FLAGS_SET(flags, CHASE_SAFE)) { + r = statx_unsafe_transition(&stx, &stx_child); + if (r < 0) + return r; + if (r > 0) + return log_unsafe_transition(fd, child, path, flags); + } if (FLAGS_SET(flags, CHASE_NO_AUTOFS) && fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0) return log_autofs_mount_point(child, path, flags); - if (S_ISLNK(st_child.st_mode) && !(FLAGS_SET(flags, CHASE_NOFOLLOW) && isempty(todo))) { + if (S_ISLNK(stx_child.stx_mode) && !(FLAGS_SET(flags, CHASE_NOFOLLOW) && isempty(todo))) { _cleanup_free_ char *destination = NULL; if (FLAGS_SET(flags, CHASE_PROHIBIT_SYMLINKS)) @@ -516,12 +552,17 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd < 0) return fd; - if (fstat(fd, &st) < 0) - return -errno; + r = xstatx(fd, /* path= */ NULL, /* flags= */ 0, mask, &stx); + if (r < 0) + return r; - if (FLAGS_SET(flags, CHASE_SAFE) && - unsafe_transition(&st_child, &st)) - return log_unsafe_transition(child, fd, path, flags); + if (FLAGS_SET(flags, CHASE_SAFE)) { + r = statx_unsafe_transition(&stx_child, &stx); + if (r < 0) + return r; + if (r > 0) + return log_unsafe_transition(child, fd, path, flags); + } /* When CHASE_AT_RESOLVE_IN_ROOT is not set, now the chased path may be * outside of the specified dir_fd. Let's make the result absolute. */ @@ -555,26 +596,26 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int break; /* And iterate again, but go one directory further down. */ - st = st_child; + stx = stx_child; close_and_replace(fd, child); } success: if (exists) { if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY)) { - r = stat_verify_directory(&st); + r = statx_verify_directory(&stx); if (r < 0) return r; } if (FLAGS_SET(flags, CHASE_MUST_BE_REGULAR)) { - r = stat_verify_regular(&st); + r = statx_verify_regular(&stx); if (r < 0) return r; } if (FLAGS_SET(flags, CHASE_MUST_BE_SOCKET)) { - r = stat_verify_socket(&st); + r = statx_verify_socket(&stx); if (r < 0) return r; } diff --git a/src/basic/chase.h b/src/basic/chase.h index b770f45a1ecd8..d779658ba15f8 100644 --- a/src/basic/chase.h +++ b/src/basic/chase.h @@ -32,7 +32,8 @@ typedef enum ChaseFlags { CHASE_MUST_BE_SOCKET = 1 << 16, /* Fail if returned inode fd is not a socket */ } ChaseFlags; -bool unsafe_transition(const struct stat *a, const struct stat *b); +int statx_unsafe_transition(const struct statx *a, const struct statx *b); +bool stat_unsafe_transition(const struct stat *a, const struct stat *b); /* How many iterations to execute before returning -ELOOP */ #define CHASE_MAX 128U diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index da75ffb818127..090884b228303 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -2633,7 +2633,7 @@ static int rm_if_wrong_type_safe( } /* Fail before removing anything if this is an unsafe transition. */ - if (follow_links && unsafe_transition(parent_st, &st)) { + if (follow_links && stat_unsafe_transition(parent_st, &st)) { (void) fd_get_path(parent_fd, &parent_name); return log_error_errno(SYNTHETIC_ERRNO(ENOLINK), "Unsafe transition from \"%s\" to \"%s\".", parent_name ?: "...", name); From 3538b77f9dc139f76561ec88eda17b1df6567c82 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Feb 2026 12:26:42 +0100 Subject: [PATCH 0020/1296] chase: tighten checks on ".." once we hit the root of an CHASE_AT_RESOLVE_IN_ROOT root tree Let's harden things in case concurrent access is allowed to a root tree passed via CHASE_AT_RESOLVE_IN_ROOT: let's not just validate via the path if we hit the root of the tree, but also by comparing inodes + mount ids. Hardening opportunity reported by Sebastian Wick. --- src/basic/chase.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/basic/chase.c b/src/basic/chase.c index 1d3596bb796e4..6e8cc15f2efe7 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -147,10 +147,10 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int _cleanup_free_ char *buffer = NULL, *done = NULL; _cleanup_close_ int fd = -EBADF, root_fd = -EBADF; bool exists = true, append_trail_slash = false; - struct statx stx; /* statx obtained from fd */ + struct statx root_stx, stx; bool need_absolute = false; /* allocate early to avoid compiler warnings around goto */ const char *todo; - unsigned mask = STATX_TYPE|STATX_UID|STATX_INO; + unsigned mask = STATX_TYPE|STATX_UID|STATX_INO|STATX_MNT_ID; int r; assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT)); @@ -244,7 +244,6 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (r < 0) return r; if (r > 0) { - /* Shortcut the common case where no root dir is specified, and no special flags are given to * a regular open() */ if (!ret_path && @@ -329,6 +328,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int r = xstatx(fd, /* path= */ NULL, /* flags= */ 0, mask, &stx); if (r < 0) return r; + root_stx = stx; /* remember stat data of the root, so that we can recognize it later */ /* If we get AT_FDCWD, we always resolve symlinks relative to the host's root. Only if a positive * directory file descriptor is provided we will look at CHASE_AT_RESOLVE_IN_ROOT to determine @@ -383,8 +383,13 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int struct statx stx_parent; /* If we already are at the top, then going up will not change anything. This is - * in-line with how the kernel handles this. */ - if (empty_or_root(done) && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) { + * in-line with how the kernel handles this. We check this both by path and by + * inode/mount identity check. The latter is load-bearing if concurrent access of the + * root tree we operate in is allowed, where an inode is moved up the tree while we + * look at it, and thus get the current path wrong and think we are deeper down than + * we actually are. */ + if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT) && + (empty_or_root(done) || (statx_inode_same(&stx, &root_stx) && statx_mount_same(&stx, &root_stx)))) { if (FLAGS_SET(flags, CHASE_STEP)) goto chased_one; continue; From 0384875582fca36ced71eea1655b644fa00179e4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Feb 2026 12:51:29 +0100 Subject: [PATCH 0021/1296] chase: drop wrong optimization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The same optimization is already applied in the very similar dir_fd_is_root() check a few lines up – with the exception that it doesn't accept AT_FCWD there. And frankly turning off CHASE_AT_RESOLVE_IN_ROOT if we operate on AT_FCWD is simply wrong. Hence remove this code. --- src/basic/chase.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/basic/chase.c b/src/basic/chase.c index 6e8cc15f2efe7..8030c74079666 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -277,15 +277,6 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int dir_fd = _dir_fd; flags &= ~CHASE_AT_RESOLVE_IN_ROOT; - } else if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) { - /* If we get AT_FDCWD or dir_fd points to "/", then we always resolve symlinks relative to - * the host's root. Hence, CHASE_AT_RESOLVE_IN_ROOT is meaningless. */ - - r = dir_fd_is_root_or_cwd(dir_fd); - if (r < 0) - return r; - if (r > 0) - flags &= ~CHASE_AT_RESOLVE_IN_ROOT; } if (!ret_path && ret_fd && (flags & (CHASE_AT_RESOLVE_IN_ROOT|CHASE_NO_SHORTCUT_MASK)) == 0) { From 4023840ffa263d29132f7417dea674fff01f44af Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Feb 2026 13:06:32 +0100 Subject: [PATCH 0022/1296] fd-util: rename fds_are_same_mount() fds_inode_and_mount_same() The old name suggested this would only check if the two inodes are on the same mount, but it actually checks if they are the same inodes too. Let's rename it to make this clearer, in particular as we have both statx_inode_same() and statx_mount_same() already, and they are even used here, and hence very confusing. This also drops two checks from the test case, which are simply wrong. Given they apparently weren't load bearing (since no CI tripped up), let's just drop them. --- src/basic/fd-util.c | 4 ++-- src/basic/fd-util.h | 2 +- src/shared/switch-root.c | 2 +- src/test/test-fd-util.c | 9 +++------ src/test/test-fs-util.c | 8 ++++---- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index d043927a2c9ec..4d720e014eef9 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -1069,10 +1069,10 @@ int path_is_root_at(int dir_fd, const char *path) { * $ mount --bind / /tmp/x */ - return fds_are_same_mount(dir_fd, XAT_FDROOT); + return fds_inode_and_mount_same(dir_fd, XAT_FDROOT); } -int fds_are_same_mount(int fd1, int fd2) { +int fds_inode_and_mount_same(int fd1, int fd2) { struct statx sx1, sx2; int r; diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index ee1dc870df859..60caa424b4e1d 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -179,7 +179,7 @@ static inline int dir_fd_is_root_or_cwd(int dir_fd) { return IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT) ? true : path_is_root_at(dir_fd, NULL); } -int fds_are_same_mount(int fd1, int fd2); +int fds_inode_and_mount_same(int fd1, int fd2); int resolve_xat_fdroot(int *fd, const char **path, char **ret_buffer); diff --git a/src/shared/switch-root.c b/src/shared/switch-root.c index 6017200d79c3e..08710a8966400 100644 --- a/src/shared/switch-root.c +++ b/src/shared/switch-root.c @@ -54,7 +54,7 @@ int switch_root(const char *new_root, if (new_root_fd < 0) return log_error_errno(errno, "Failed to open target directory '%s': %m", new_root); - r = fds_are_same_mount(old_root_fd, new_root_fd); /* checks if referenced inodes and mounts match */ + r = fds_inode_and_mount_same(old_root_fd, new_root_fd); /* checks if referenced inodes and mounts match */ if (r < 0) return log_error_errno(r, "Failed to check if old and new root directory/mount are the same: %m"); if (r > 0) { diff --git a/src/test/test-fd-util.c b/src/test/test-fd-util.c index d43c8735164d5..e99d7d04726b4 100644 --- a/src/test/test-fd-util.c +++ b/src/test/test-fd-util.c @@ -738,7 +738,7 @@ TEST(path_is_root_at) { test_path_is_root_at_one(true); } -TEST(fds_are_same_mount) { +TEST(fds_inode_and_mount_same) { _cleanup_close_ int fd1 = -EBADF, fd2 = -EBADF, fd3 = -EBADF, fd4 = -EBADF; fd1 = open("/sys", O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW); @@ -749,11 +749,8 @@ TEST(fds_are_same_mount) { if (fd1 < 0 || fd2 < 0 || fd3 < 0 || fd4 < 0) return (void) log_tests_skipped_errno(errno, "Failed to open /sys or /proc or /"); - if (fds_are_same_mount(fd1, fd4) > 0 && fds_are_same_mount(fd2, fd4) > 0) - return (void) log_tests_skipped("Cannot test fds_are_same_mount() as /sys and /proc are not mounted"); - - assert_se(fds_are_same_mount(fd1, fd2) == 0); - assert_se(fds_are_same_mount(fd2, fd3) > 0); + assert_se(fds_inode_and_mount_same(fd1, fd2) == 0); + assert_se(fds_inode_and_mount_same(fd2, fd3) > 0); } TEST(fd_get_path) { diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index a56f8f92ada9a..7cb720938ccef 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -871,10 +871,10 @@ TEST(xat_fdroot) { ASSERT_OK_POSITIVE(path_is_root_at(fd, ".")); ASSERT_OK_POSITIVE(path_is_root_at(fd, "/")); - ASSERT_OK_POSITIVE(fds_are_same_mount(fd, fd)); - ASSERT_OK_POSITIVE(fds_are_same_mount(XAT_FDROOT, XAT_FDROOT)); - ASSERT_OK_POSITIVE(fds_are_same_mount(fd, XAT_FDROOT)); - ASSERT_OK_POSITIVE(fds_are_same_mount(XAT_FDROOT, fd)); + ASSERT_OK_POSITIVE(fds_inode_and_mount_same(fd, fd)); + ASSERT_OK_POSITIVE(fds_inode_and_mount_same(XAT_FDROOT, XAT_FDROOT)); + ASSERT_OK_POSITIVE(fds_inode_and_mount_same(fd, XAT_FDROOT)); + ASSERT_OK_POSITIVE(fds_inode_and_mount_same(XAT_FDROOT, fd)); ASSERT_OK_POSITIVE(dir_fd_is_root(XAT_FDROOT)); ASSERT_OK_POSITIVE(dir_fd_is_root(fd)); From e4206858ad29b8e9ea9b26381a08453efe7f309e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Feb 2026 13:08:34 +0100 Subject: [PATCH 0023/1296] fd-util: minor shortcut --- src/basic/fd-util.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index 4d720e014eef9..e032bb62ac110 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -1085,6 +1085,9 @@ int fds_inode_and_mount_same(int fd1, int fd2) { if (r < 0) return r; + if (fd1 == fd2) /* Shortcut things if fds are the same (only after validating the fd) */ + return true; + r = xstatx(fd2, /* path = */ NULL, AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &sx2); From 6d61bb671de3b096a5b3eee7e5f1de50e2bc1a41 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Feb 2026 13:08:48 +0100 Subject: [PATCH 0024/1296] mountwork: use statx_mount_same() where appropriate --- src/mountfsd/mountwork.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mountfsd/mountwork.c b/src/mountfsd/mountwork.c index 7115f33e36be9..2e75ac532f8d8 100644 --- a/src/mountfsd/mountwork.c +++ b/src/mountfsd/mountwork.c @@ -947,7 +947,7 @@ static DirectoryOwnership validate_directory_fd( return DIRECTORY_IS_OTHERWISE_OWNED; } - if (stx.stx_mnt_id != new_stx.stx_mnt_id) { + if (!statx_mount_same(&stx, &new_stx)) { /* NB, this check is probably redundant, given we also check * STATX_ATTR_MOUNT_ROOT. The only reason we have it here is to provide extra safety * in case the mount tree is rearranged concurrently with our traversal, so that From 2e9208199f98fa07f09c7d01828688c52d955651 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Feb 2026 15:10:12 +0100 Subject: [PATCH 0025/1296] stat-util: teach statx_mount_same() STATX_MNT_ID_UNIQUE So far statx_mount_same() assumed STATX_MNT_ID_UNIQUE didn't exist. However it does exist, hence do something useful if we see it set. Note that this creates a certain ambiguity: if we compare one statx struct with STATX_MNT_ID_UNIQUE and one without it (but with the regular mnt id), then we cnanot really come to a clear conclusion, hence need to introduce a third, unknown state. Note that we don't request STATX_MNT_ID_UNIQUE yet wherever we call statx_mount_same(). THis will be added in a later commit. --- src/basic/chase.c | 19 ++++++++++++++----- src/basic/fd-util.c | 6 +++++- src/basic/stat-util.c | 9 +++++---- src/basic/stat-util.h | 2 +- src/mountfsd/mountwork.c | 5 ++++- 5 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/basic/chase.c b/src/basic/chase.c index 8030c74079666..2243b129729a1 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -379,11 +379,20 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int * root tree we operate in is allowed, where an inode is moved up the tree while we * look at it, and thus get the current path wrong and think we are deeper down than * we actually are. */ - if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT) && - (empty_or_root(done) || (statx_inode_same(&stx, &root_stx) && statx_mount_same(&stx, &root_stx)))) { - if (FLAGS_SET(flags, CHASE_STEP)) - goto chased_one; - continue; + if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) { + bool is_root = empty_or_root(done); + if (!is_root && statx_inode_same(&stx, &root_stx)) { + r = statx_mount_same(&stx, &root_stx); + if (r < 0) + return r; + + is_root = r > 0; + } + if (is_root) { + if (FLAGS_SET(flags, CHASE_STEP)) + goto chased_one; + continue; + } } fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY); diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index e032bb62ac110..6fb4e78c2f6b6 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -1094,7 +1094,11 @@ int fds_inode_and_mount_same(int fd1, int fd2) { if (r < 0) return r; - return statx_inode_same(&sx1, &sx2) && statx_mount_same(&sx1, &sx2); + r = statx_mount_same(&sx1, &sx2); + if (r <= 0) + return r; + + return statx_inode_same(&sx1, &sx2); } int resolve_xat_fdroot(int *fd, const char **path, char **ret_buffer) { diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index c5702ca6ab3df..7f065b5c7f03b 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -675,14 +675,15 @@ bool statx_inode_same(const struct statx *a, const struct statx *b) { a->stx_ino == b->stx_ino; } -bool statx_mount_same(const struct statx *a, const struct statx *b) { +int statx_mount_same(const struct statx *a, const struct statx *b) { if (!statx_is_set(a) || !statx_is_set(b)) return false; - assert(FLAGS_SET(a->stx_mask, STATX_MNT_ID)); - assert(FLAGS_SET(b->stx_mask, STATX_MNT_ID)); + if ((FLAGS_SET(a->stx_mask, STATX_MNT_ID) && FLAGS_SET(b->stx_mask, STATX_MNT_ID)) || + (FLAGS_SET(a->stx_mask, STATX_MNT_ID_UNIQUE) && FLAGS_SET(b->stx_mask, STATX_MNT_ID_UNIQUE))) + return a->stx_mnt_id == b->stx_mnt_id; - return a->stx_mnt_id == b->stx_mnt_id; + return -ENODATA; } usec_t statx_timestamp_load(const struct statx_timestamp *ts) { diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index 2336c775e9607..230535aad41ca 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -110,7 +110,7 @@ bool stat_inode_same(const struct stat *a, const struct stat *b); bool stat_inode_unmodified(const struct stat *a, const struct stat *b); bool statx_inode_same(const struct statx *a, const struct statx *b); -bool statx_mount_same(const struct statx *a, const struct statx *b); +int statx_mount_same(const struct statx *a, const struct statx *b); int xstatfsat(int dir_fd, const char *path, struct statfs *ret); diff --git a/src/mountfsd/mountwork.c b/src/mountfsd/mountwork.c index 2e75ac532f8d8..54d92b5585614 100644 --- a/src/mountfsd/mountwork.c +++ b/src/mountfsd/mountwork.c @@ -947,7 +947,10 @@ static DirectoryOwnership validate_directory_fd( return DIRECTORY_IS_OTHERWISE_OWNED; } - if (!statx_mount_same(&stx, &new_stx)) { + r = statx_mount_same(&stx, &new_stx); + if (r < 0) + return log_debug_errno(r, "Failed to compare mount IDs: %m"); + if (!r) { /* NB, this check is probably redundant, given we also check * STATX_ATTR_MOUNT_ROOT. The only reason we have it here is to provide extra safety * in case the mount tree is rearranged concurrently with our traversal, so that From d2b27a7a8c894052e0847d8905cfb52575d59a1a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Feb 2026 14:16:47 +0100 Subject: [PATCH 0026/1296] xstatx_full(): add flag to acquire STATX_MNT_ID_UNIQUE if we can, with fallback. --- src/basic/chase.c | 56 ++++++++++++++++++++++++++++++++----- src/basic/dirent-util.c | 1 + src/basic/mountpoint-util.c | 1 + src/basic/stat-util.c | 21 +++++++++++--- src/basic/stat-util.h | 11 ++++++-- src/basic/xattr-util.c | 8 ++++-- src/mountfsd/mountwork.c | 6 ++-- src/shared/find-esp.c | 1 + src/tmpfiles/tmpfiles.c | 8 ++++-- 9 files changed, 92 insertions(+), 21 deletions(-) diff --git a/src/basic/chase.c b/src/basic/chase.c index 2243b129729a1..82946eae5f199 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -150,7 +150,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int struct statx root_stx, stx; bool need_absolute = false; /* allocate early to avoid compiler warnings around goto */ const char *todo; - unsigned mask = STATX_TYPE|STATX_UID|STATX_INO|STATX_MNT_ID; + unsigned mask = STATX_TYPE|STATX_UID|STATX_INO; int r; assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT)); @@ -263,7 +263,14 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd < 0) return -errno; - r = xstatx(fd, /* path= */ NULL, /* flags= */ 0, mask, &stx); + r = xstatx_full(fd, + /* path= */ NULL, + /* statx_flags= */ 0, + XSTATX_MNT_ID_BEST, + mask, + /* optional_mask= */ 0, + /* mandatory_attributes= */ 0, + &stx); if (r < 0) return r; @@ -316,7 +323,14 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd < 0) return -errno; - r = xstatx(fd, /* path= */ NULL, /* flags= */ 0, mask, &stx); + r = xstatx_full(fd, + /* path= */ NULL, + /* statx_flags= */ 0, + XSTATX_MNT_ID_BEST, + mask, + /* optional_mask= */ 0, + /* mandatory_attributes= */ 0, + &stx); if (r < 0) return r; root_stx = stx; /* remember stat data of the root, so that we can recognize it later */ @@ -399,7 +413,14 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd_parent < 0) return -errno; - r = xstatx(fd_parent, /* path= */ NULL, /* flags= */ 0, mask, &stx_parent); + r = xstatx_full(fd_parent, + /* path= */ NULL, + /* statx_flags= */ 0, + XSTATX_MNT_ID_BEST, + mask, + /* optional_mask= */ 0, + /* mandatory_attributes= */ 0, + &stx_parent); if (r < 0) return r; @@ -465,7 +486,14 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd_grandparent < 0) return -errno; - r = xstatx(fd_grandparent, /* path= */ NULL, /* flags= */ 0, mask, &stx_grandparent); + r = xstatx_full(fd_grandparent, + /* path= */ NULL, + /* statx_flags= */ 0, + XSTATX_MNT_ID_BEST, + mask, + /* optional_mask= */ 0, + /* mandatory_attributes= */ 0, + &stx_grandparent); if (r < 0) return r; @@ -519,7 +547,14 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int } /* ... and then check what it actually is. */ - r = xstatx(child, /* path= */ NULL, /* flags= */ 0, mask, &stx_child); + r = xstatx_full(child, + /* path= */ NULL, + /* statx_flags= */ 0, + XSTATX_MNT_ID_BEST, + mask, + /* optional_mask= */ 0, + /* mandatory_attributes= */ 0, + &stx_child); if (r < 0) return r; @@ -557,7 +592,14 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd < 0) return fd; - r = xstatx(fd, /* path= */ NULL, /* flags= */ 0, mask, &stx); + r = xstatx_full(fd, + /* path= */ NULL, + /* statx_flags= */ 0, + XSTATX_MNT_ID_BEST, + mask, + /* optional_mask= */ 0, + /* mandatory_attributes= */ 0, + &stx); if (r < 0) return r; diff --git a/src/basic/dirent-util.c b/src/basic/dirent-util.c index a1508747777a0..91a7040e408e7 100644 --- a/src/basic/dirent-util.c +++ b/src/basic/dirent-util.c @@ -27,6 +27,7 @@ int dirent_ensure_type(int dir_fd, struct dirent *de) { r = xstatx_full(dir_fd, de->d_name, AT_SYMLINK_NOFOLLOW|AT_NO_AUTOMOUNT, + /* xstatx_flags= */ 0, /* mandatory_mask= */ STATX_TYPE, /* optional_mask= */ STATX_INO, /* mandatory_attributes= */ 0, diff --git a/src/basic/mountpoint-util.c b/src/basic/mountpoint-util.c index 06d4d4450a22d..79b51cb099fcd 100644 --- a/src/basic/mountpoint-util.c +++ b/src/basic/mountpoint-util.c @@ -250,6 +250,7 @@ int is_mount_point_at(int dir_fd, const char *path, int flags) { at_flags_normalize_nofollow(flags) | AT_NO_AUTOMOUNT | /* don't trigger automounts – mounts are a local concept, hence no need to trigger automounts to determine STATX_ATTR_MOUNT_ROOT */ AT_STATX_DONT_SYNC, /* don't go to the network for this – for similar reasons */ + /* xstatx_flags = */ 0, STATX_TYPE|STATX_INO, /* optional_mask = */ 0, STATX_ATTR_MOUNT_ROOT, diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 7f065b5c7f03b..ef39562992ca0 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -328,7 +328,8 @@ DEFINE_STATX_BITS_TO_STRING(statx_attributes, uint64_t, statx_attribute_to_name, int xstatx_full(int fd, const char *path, - int flags, + int statx_flags, + XStatXFlags xstatx_flags, unsigned mandatory_mask, unsigned optional_mask, uint64_t mandatory_attributes, @@ -344,10 +345,13 @@ int xstatx_full(int fd, * 3. Takes separate mandatory and optional mask params, plus mandatory attributes. * Returns -EUNATCH if statx() does not return all masks specified as mandatory, * > 0 if all optional masks are supported, 0 otherwise. + * 4. Supports a new flag XSTATX_MNT_ID_BEST which acquires STATX_MNT_ID_UNIQUE if available and + * STATX_MNT_ID if not. */ assert(fd >= 0 || IN_SET(fd, AT_FDCWD, XAT_FDROOT)); assert((mandatory_mask & optional_mask) == 0); + assert(!FLAGS_SET(xstatx_flags, XSTATX_MNT_ID_BEST) || !((mandatory_mask|optional_mask) & (STATX_MNT_ID|STATX_MNT_ID_UNIQUE))); assert(ret); _cleanup_free_ char *p = NULL; @@ -355,12 +359,21 @@ int xstatx_full(int fd, if (r < 0) return r; - if (statx(fd, strempty(path), - flags|(isempty(path) ? AT_EMPTY_PATH : 0), - mandatory_mask|optional_mask, + unsigned request_mask = mandatory_mask|optional_mask; + if (FLAGS_SET(xstatx_flags, XSTATX_MNT_ID_BEST)) + request_mask |= STATX_MNT_ID|STATX_MNT_ID_UNIQUE; + + if (statx(fd, + strempty(path), + statx_flags|(isempty(path) ? AT_EMPTY_PATH : 0), + request_mask, &sx) < 0) return negative_errno(); + if (FLAGS_SET(xstatx_flags, XSTATX_MNT_ID_BEST) && + !(sx.stx_mask & (STATX_MNT_ID|STATX_MNT_ID_UNIQUE))) + return log_debug_errno(SYNTHETIC_ERRNO(EUNATCH), "statx() did not return either STATX_MNT_ID or STATX_MNT_ID_UNIQUE."); + if (!FLAGS_SET(sx.stx_mask, mandatory_mask)) { if (DEBUG_LOGGING) { _cleanup_free_ char *mask_str = statx_mask_to_string(mandatory_mask & ~sx.stx_mask); diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index 230535aad41ca..c261014cd2953 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -47,9 +47,14 @@ static inline int null_or_empty_path(const char *fn) { return null_or_empty_path_with_root(fn, NULL); } +typedef enum XStatXFlags { + XSTATX_MNT_ID_BEST = 1 << 0, /* Like STATX_MNT_ID_UNIQUE if available, STATX_MNT_ID otherwise */ +} XStatXFlags; + int xstatx_full(int fd, const char *path, - int flags, + int statx_flags, + XStatXFlags xstatx_flags, unsigned mandatory_mask, unsigned optional_mask, uint64_t mandatory_attributes, @@ -58,11 +63,11 @@ int xstatx_full(int fd, static inline int xstatx( int fd, const char *path, - int flags, + int statx_flags, unsigned mandatory_mask, struct statx *ret) { - return xstatx_full(fd, path, flags, mandatory_mask, 0, 0, ret); + return xstatx_full(fd, path, statx_flags, 0, mandatory_mask, 0, 0, ret); } int fd_is_read_only_fs(int fd); diff --git a/src/basic/xattr-util.c b/src/basic/xattr-util.c index 68ee83d899ede..e77d0bc8e84ef 100644 --- a/src/basic/xattr-util.c +++ b/src/basic/xattr-util.c @@ -429,11 +429,13 @@ int getcrtime_at( * concept is useful for determining how "old" a file really is, and hence using the older of the two makes * most sense. */ - r = xstatx_full(fd, path, + r = xstatx_full(fd, + path, at_flags_normalize_nofollow(at_flags)|AT_STATX_DONT_SYNC, - /* mandatory_mask = */ 0, + /* xstatx_flags= */ 0, + /* mandatory_mask= */ 0, STATX_BTIME, - /* mandatory_attributes = */ 0, + /* mandatory_attributes= */ 0, &sx); if (r > 0 && sx.stx_btime.tv_sec != 0) /* > 0: all optional masks are supported */ a = statx_timestamp_load(&sx.stx_btime); diff --git a/src/mountfsd/mountwork.c b/src/mountfsd/mountwork.c index 54d92b5585614..922bcb1b18995 100644 --- a/src/mountfsd/mountwork.c +++ b/src/mountfsd/mountwork.c @@ -866,7 +866,8 @@ static DirectoryOwnership validate_directory_fd( r = xstatx_full(fd, /* path= */ NULL, AT_EMPTY_PATH, - /* mandatory_mask= */ STATX_TYPE|STATX_UID|STATX_MNT_ID|STATX_INO, + /* xstatx_flags= */ XSTATX_MNT_ID_BEST, + /* mandatory_mask= */ STATX_TYPE|STATX_UID|STATX_INO, /* optional_mask= */ 0, /* mandatory_attributes= */ STATX_ATTR_MOUNT_ROOT, &stx); @@ -933,7 +934,8 @@ static DirectoryOwnership validate_directory_fd( r = xstatx_full(new_parent_fd, /* path= */ NULL, AT_EMPTY_PATH, - /* mandatory_mask= */ STATX_UID|STATX_MNT_ID|STATX_INO, + /* xstatx_flags= */ XSTATX_MNT_ID_BEST, + /* mandatory_mask= */ STATX_UID|STATX_INO, /* optional_mask= */ 0, /* mandatory_attributes= */ STATX_ATTR_MOUNT_ROOT, &new_stx); diff --git a/src/shared/find-esp.c b/src/shared/find-esp.c index 3ba9b8a4b601b..a2a2093fafb93 100644 --- a/src/shared/find-esp.c +++ b/src/shared/find-esp.c @@ -287,6 +287,7 @@ static int verify_fsroot_dir( r = xstatx_full(dir_fd, f, AT_SYMLINK_NOFOLLOW, + /* xstatx_flags= */ 0, STATX_TYPE|STATX_INO, /* optional_mask = */ 0, STATX_ATTR_MOUNT_ROOT, diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 090884b228303..0d62b847b65cb 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -586,9 +586,12 @@ static int opendir_and_stat( return 0; } - r = xstatx_full(dirfd(d), /* path = */ NULL, AT_EMPTY_PATH, + r = xstatx_full(dirfd(d), + /* path= */ NULL, + AT_EMPTY_PATH, + /* xstatx_flags= */ 0, STATX_MODE|STATX_INO|STATX_ATIME|STATX_MTIME, - /* optional_mask = */ 0, + /* optional_mask= */ 0, STATX_ATTR_MOUNT_ROOT, &sx); if (r < 0) @@ -687,6 +690,7 @@ static int dir_cleanup( struct statx sx; r = xstatx_full(dirfd(d), de->d_name, AT_SYMLINK_NOFOLLOW|AT_NO_AUTOMOUNT, + /* xstatx_flags= */ 0, STATX_TYPE|STATX_MODE|STATX_UID, STATX_ATIME|STATX_MTIME|STATX_CTIME|STATX_BTIME, STATX_ATTR_MOUNT_ROOT, From 5a04e542c283a6331016739147a8639725e586fe Mon Sep 17 00:00:00 2001 From: Rodrigo Campos Date: Fri, 27 Feb 2026 18:10:40 +0100 Subject: [PATCH 0027/1296] portable: Add ExtensionImage drop-in for any extension Before this patch, when running: portablectl attach --extension ext.raw ./base.raw No drop-in is added for the "ExtensionImages" if there aren't units from the extension loaded. But the extension can just overlay files, as in my case. So before this patch, I also need to manually add a drop-in with "ExtensionImages=" for it to really be loaded. Let's just always add the drop-in for extensions. This way, it works for extensions that just overlay files too. Please note this commit just removes the if (simpler to view the diff with git show -w). Also, the if checked for m->image_path being not NULL, but removing it shouldn't cause a NULL pointer dereference. Because m->image_path is not used inside the if (it was needed just for the if itself) and image_path is asserted at the beginning of the function to be non-NULL too. This was like this since the beginning of time in 907952bbc9 ("portabled: add --extension parameter for layered images support") --- src/portable/portable.c | 99 ++++++++++++++++++++--------------------- 1 file changed, 49 insertions(+), 50 deletions(-) diff --git a/src/portable/portable.c b/src/portable/portable.c index 2f6db85ff9121..df505bbe2d5d3 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -1491,65 +1491,64 @@ static int install_chroot_dropin( if (r < 0) return r; - if (m->image_path && !path_equal(m->image_path, image_path)) - ORDERED_HASHMAP_FOREACH(ext, extension_images) { + ORDERED_HASHMAP_FOREACH(ext, extension_images) { - const char *extension_setting = extension_setting_from_image(ext->type); - if (!extension_setting) - return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Image type '%s' not supported for extensions: %m", image_type_to_string(ext->type)); + const char *extension_setting = extension_setting_from_image(ext->type); + if (!extension_setting) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Image type '%s' not supported for extensions: %m", image_type_to_string(ext->type)); - _cleanup_free_ char *extension_base_name = NULL; - r = path_extract_filename(ext->path, &extension_base_name); - if (r < 0) - return log_debug_errno(r, "Failed to extract basename from '%s': %m", ext->path); + _cleanup_free_ char *extension_base_name = NULL; + r = path_extract_filename(ext->path, &extension_base_name); + if (r < 0) + return log_debug_errno(r, "Failed to extract basename from '%s': %m", ext->path); - if (!strextend(&text, + if (!strextend(&text, + "\n", + extension_setting, + ext->path, + /* With --force tell PID1 to avoid enforcing that the image and + * extension-release. have to match. */ + !IN_SET(ext->type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME) && + FLAGS_SET(flags, PORTABLE_FORCE_EXTENSION) ? + ":x-systemd.relax-extension-release-check\n" : "\n", - extension_setting, - ext->path, - /* With --force tell PID1 to avoid enforcing that the image and - * extension-release. have to match. */ - !IN_SET(ext->type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME) && - FLAGS_SET(flags, PORTABLE_FORCE_EXTENSION) ? - ":x-systemd.relax-extension-release-check\n" : - "\n", - /* In PORTABLE= we list the 'main' image name for this unit - * (the image where the unit was extracted from), but we are - * stacking multiple images, so list those too. */ - "LogExtraFields=PORTABLE_EXTENSION=", extension_base_name, "\n")) - return -ENOMEM; - - if (pinned_ext_image_policy && !IN_SET(ext->type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME)) { - _cleanup_free_ char *policy_str = NULL; - - r = image_policy_to_string(pinned_ext_image_policy, /* simplify= */ true, &policy_str); - if (r < 0) - return log_debug_errno(r, "Failed to serialize pinned image policy: %m"); + /* In PORTABLE= we list the 'main' image name for this unit + * (the image where the unit was extracted from), but we are + * stacking multiple images, so list those too. */ + "LogExtraFields=PORTABLE_EXTENSION=", extension_base_name, "\n")) + return -ENOMEM; - if (!strextend(&text, - "ExtensionImagePolicy=", policy_str, "\n")) - return -ENOMEM; - } + if (pinned_ext_image_policy && !IN_SET(ext->type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME)) { + _cleanup_free_ char *policy_str = NULL; - /* Look for image/version identifiers in the extension release files. We - * look for all possible IDs, but typically only 1 or 2 will be set, so - * the number of fields added shouldn't be too large. We prefix the DDI - * name to the value, so that we can add the same field multiple times and - * still be able to identify what applies to what. */ - r = append_release_log_fields(&text, - ordered_hashmap_get(extension_releases, ext->name), - IMAGE_SYSEXT, - "PORTABLE_EXTENSION_NAME_AND_VERSION"); + r = image_policy_to_string(pinned_ext_image_policy, /* simplify= */ true, &policy_str); if (r < 0) - return r; + return log_debug_errno(r, "Failed to serialize pinned image policy: %m"); - r = append_release_log_fields(&text, - ordered_hashmap_get(extension_releases, ext->name), - IMAGE_CONFEXT, - "PORTABLE_EXTENSION_NAME_AND_VERSION"); - if (r < 0) - return r; + if (!strextend(&text, + "ExtensionImagePolicy=", policy_str, "\n")) + return -ENOMEM; } + + /* Look for image/version identifiers in the extension release files. We + * look for all possible IDs, but typically only 1 or 2 will be set, so + * the number of fields added shouldn't be too large. We prefix the DDI + * name to the value, so that we can add the same field multiple times and + * still be able to identify what applies to what. */ + r = append_release_log_fields(&text, + ordered_hashmap_get(extension_releases, ext->name), + IMAGE_SYSEXT, + "PORTABLE_EXTENSION_NAME_AND_VERSION"); + if (r < 0) + return r; + + r = append_release_log_fields(&text, + ordered_hashmap_get(extension_releases, ext->name), + IMAGE_CONFEXT, + "PORTABLE_EXTENSION_NAME_AND_VERSION"); + if (r < 0) + return r; + } } r = write_string_file(dropin, text, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_SYNC); From 9c56f3020d87c027f6b401877237cfef4b38ef06 Mon Sep 17 00:00:00 2001 From: Rodrigo Campos Date: Mon, 2 Mar 2026 16:02:06 +0100 Subject: [PATCH 0028/1296] test/portable: Ensure ExtensionImages is set for any dep The previous commit made portablectl attach add a drop-in for any extension image. Let's add a test for that too. --- test/units/TEST-29-PORTABLE.image.sh | 13 +++++++++++++ test/units/util.sh | 12 ++++++++++++ 2 files changed, 25 insertions(+) diff --git a/test/units/TEST-29-PORTABLE.image.sh b/test/units/TEST-29-PORTABLE.image.sh index 36d187a75288d..3018e86ecdee1 100755 --- a/test/units/TEST-29-PORTABLE.image.sh +++ b/test/units/TEST-29-PORTABLE.image.sh @@ -212,6 +212,19 @@ portablectl inspect --force --cat --extension /tmp/app0.raw --extension /tmp/con portablectl detach --now --runtime --extension /tmp/app0.raw --extension /tmp/conf0.raw /usr/share/minimal_0.raw app0 +# Ensure that ExtensionImages= is added to the drop-in even when the extension has no unit files +# (all units come from the base image). +portablectl "${ARGS[@]}" attach --force --now --runtime --extension /tmp/app-data-only.raw /usr/share/minimal_0.raw minimal-app0 + +systemctl is-active minimal-app0.service +status="$(portablectl is-attached --extension app-data-only minimal_0)" +[[ "${status}" == "running-runtime" ]] + +grep -q -F "ExtensionImages=/tmp/app-data-only.raw" /run/systemd/system.attached/minimal-app0.service.d/20-portable.conf +grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app-data-only.raw" /run/systemd/system.attached/minimal-app0.service.d/20-portable.conf + +portablectl detach --now --runtime --extension /tmp/app-data-only.raw /usr/share/minimal_0.raw minimal-app0 + # Ensure that mixed mode copies the images and units (client-owned) but symlinks the profile (OS owned) portablectl "${ARGS[@]}" attach --copy=mixed --runtime --extension /tmp/app0.raw /usr/share/minimal_0.raw app0 test -f /run/portables/app0.raw diff --git a/test/units/util.sh b/test/units/util.sh index 6f03f5e33996c..d9e561e79186a 100755 --- a/test/units/util.sh +++ b/test/units/util.sh @@ -401,6 +401,18 @@ EOF echo MARKER=1 >"$initdir/usr/lib/systemd/system/other_file" mksquashfs "$initdir" /tmp/app1.raw -noappend + # Create a data-only extension image (no unit files) to test that + # ExtensionImages= is added to the drop-in even when the extension + # does not carry any units. + initdir="/var/tmp/app-data-only" + mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/opt" + ( + echo "ID=_any" + echo "ARCHITECTURE=_any" + ) >"$initdir/usr/lib/extension-release.d/extension-release.app-data-only" + echo "MARKER_DATA_ONLY=1" >"$initdir/opt/data-file" + mksquashfs "$initdir" /tmp/app-data-only.raw -noappend + initdir="/var/tmp/app-nodistro" mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/usr/lib/systemd/system" ( From fafd7cabff4238473f29366a6aa717f386a5c7e0 Mon Sep 17 00:00:00 2001 From: Jim Spentzos Date: Tue, 3 Mar 2026 08:58:26 +0000 Subject: [PATCH 0029/1296] po: Translated using Weblate (Greek) Currently translated at 100.0% (266 of 266 strings) Co-authored-by: Jim Spentzos Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/el/ Translation: systemd/main --- po/el.po | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/po/el.po b/po/el.po index daaabfc354542..d340c9a9fc5f2 100644 --- a/po/el.po +++ b/po/el.po @@ -10,8 +10,8 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2026-02-27 16:58+0000\n" -"Last-Translator: Efstathios Iosifidis \n" +"PO-Revision-Date: 2026-03-03 08:58+0000\n" +"Last-Translator: Jim Spentzos \n" "Language-Team: Greek \n" "Language: el\n" @@ -807,8 +807,7 @@ msgstr "" #: src/login/org.freedesktop.login1.policy:363 msgid "Indicate to the firmware to boot to setup interface" -msgstr "" -"Υπόδειξη στο υλικολογισμικό για εκκίνηση στη διεπαφή ρυθμίσεων (BIOS/UEFI)" +msgstr "Υπόδειξη στο υλικολογισμικό για εκκίνηση στο περιβάλλον ρυθμίσεων" #: src/login/org.freedesktop.login1.policy:364 msgid "" From 179ed677accc171ef576a7073b6aa582cb086a02 Mon Sep 17 00:00:00 2001 From: naly zzwd Date: Tue, 3 Mar 2026 08:58:26 +0000 Subject: [PATCH 0030/1296] po: Translated using Weblate (Catalan) Currently translated at 100.0% (266 of 266 strings) Co-authored-by: naly zzwd Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/ca/ Translation: systemd/main --- po/ca.po | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/po/ca.po b/po/ca.po index 19beafda1142c..e6c14bf7d4804 100644 --- a/po/ca.po +++ b/po/ca.po @@ -3,12 +3,12 @@ # Catalan translation for systemd. # Walter Garcia-Fontes , 2016. # Robert Antoni Buj Gelonch , 2018. #zanata -# naly zzwd , 2025. +# naly zzwd , 2025, 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2025-07-28 17:25+0000\n" +"PO-Revision-Date: 2026-03-03 08:58+0000\n" "Last-Translator: naly zzwd \n" "Language-Team: Catalan \n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.12.2\n" +"X-Generator: Weblate 5.16.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1103,12 +1103,11 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Gestiona els enllaços de xarxa" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" -"Es requereix autenticació per tornar a carregar la configuració de xarxa." +msgstr "Es requereix autenticació per gestionar els enllaços de xarxa." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" From 4bdfcc2ee259139dff81b65b57d42cc4b80d5dc9 Mon Sep 17 00:00:00 2001 From: Eisuke Kawashima Date: Tue, 3 Mar 2026 17:25:55 +0900 Subject: [PATCH 0031/1296] shell-completion: update run0 completion --- shell-completion/zsh/_run0 | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/shell-completion/zsh/_run0 b/shell-completion/zsh/_run0 index dc95486bef319..f76be2e5ff0dc 100644 --- a/shell-completion/zsh/_run0 +++ b/shell-completion/zsh/_run0 @@ -39,20 +39,29 @@ _run0_slices() { local -a args=( '--no-ask-password[Do not query the user for authentication]' '--unit=[Use this unit name instead of an automatically generated one]' - {--property=,-p+}'[Sets a property on the service unit created]:property:_run0_unit_properties' - '--description=[Provide a description for the service unit]' + {'*--property=','*-p+'}'[Sets a property on the service unit created]:property:_run0_unit_properties' + '--description=[Provide a description for the service unit]:TEXT' '--slice=[Make the new .service unit part of the specified slice]:slice unit:_run0_slices' '--slice-inherit[Make the new service unit part of the current slice]' - {--user=,-u+}'[Switch to the specified user]:user:_users' - {--group=,-g+}'[Switch to the specified group]:group:_groups' + '(--user -u)'{--user=,-u+}'[Switch to the specified user]:user:_users' + '(--group -g)'{--group=,-g+}'[Switch to the specified group]:group:_groups' '--nice=[Run with specified nice level]:nice value' - {--chdir=,-D+}'[Run within the specified working directory]:directory:_files -/' - '--setenv=[Set the specified environment variable in the session]:environment variable:_parameters -g "*export*" -S = -q' + '(--chdir -D -i --same-root-dir)'{--chdir=,-D+}'[Run within the specified working directory]:directory:_files -/' + '(-i)'--via-shell"[Invoke command via target user's login shell]" + '(--via-shell --chdir -D --same-root-dir)'-i"[Shortcut for --via-shell --chdir='~']" + '*--setenv=[Set the specified environment variable in the session]:environment variable:_parameters -g "*export*" -S = -q' '--background=[Change the terminal background color to the specified ANSI color]:ansi color' + '(--pty-late --pipe)'--pty'[Request allocation of a pseudo TTY for stdio]' + '(--pty --pipe)'--pty-late'[Just like --pty, but leave TTY access to agents until unit is started up]' + "(--pty --pty-late)--pipe[request passing the caller's STDIO file descriptors directly through]" + '--shell-prompt-prefix=[Set $SHELL_PROMPT_PREFIX]:PREFIX' + '--lightweight=[Control whether to register a session with service manager or without]:bool:_values bool true false' '--machine=[Execute the operation on a local container]:machine:_sd_machines' - {-h,--help}'[Show the help text and exit]' - '--version[Print a short version string and exit]' + '--area=[Home area to log into]:AREA' + '(- *)'{-h,--help}'[Show the help text and exit]' + '(- *)'{-V,--version}'[Print a short version string and exit]' '--empower[Give privileges to selected or current user]' + '(--chdir -D -i)--same-root-dir[Execute the run0 session in the same root directory that the run0 command is executed in]' ) _arguments -S $args '*:: :{_normal -p $service}' From 166e62215f1fbc9b9823f40eb219dab81da8d6cf Mon Sep 17 00:00:00 2001 From: Bouke van der Bijl Date: Tue, 3 Mar 2026 13:21:15 +0100 Subject: [PATCH 0032/1296] man: fix typo in docs for notify-ready option --- man/systemd-nspawn.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 99e6147b2b142..bf299b0a4afd3 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -660,7 +660,7 @@ Configures support for notifications from the container's init process. - takes a boolean. If false systemd-vmpawn + takes a boolean. If false systemd-nspawn notifies the calling service manager with a READY=1 message when the init process is created. If true it waits for a READY=1 message from the init process in the VM before sending its own to the service manager. For more details about notifications see From 950eabc7914fd2bb8705ca420415e117afedc8cb Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 3 Mar 2026 22:11:44 +0900 Subject: [PATCH 0033/1296] TODO: fix typo --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index 42b57364596c2..0f97998abbaee 100644 --- a/TODO +++ b/TODO @@ -126,7 +126,7 @@ Features: less than NVRAM. hence setting the flag amplifies space issues. Unsetting the flag increases wear issues on the NVRAM, however, but this should be limited for the product uuid nvpcr, since its only changed once per boot. this needs - to be configurable by nvpcr however, as other nvpcrs are differnt, + to be configurable by nvpcr however, as other nvpcrs are different, i.e. verity one receives many writes during system uptime quite possibly. (also, NV_ORDERLY makes stuff faster, and dropping it costs possibly up to 100ms supposedly) From ec4ff79f07604667b034758e48aed3b67086f3dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 3 Mar 2026 15:05:31 +0100 Subject: [PATCH 0034/1296] NEWS: mention the sd_varlink_field_type_t breakage Follow-up for 93d768e0f36a62afed7ebbf3abe3385cfd186480. The commit with the fix didn't mention this, but the reported reproducer was: > Install openSUSE Tumbleweed with account-utils and systemd v258. > Compile and install systemd v260. Run "varlinkctl list-methods > /run/account/newidmapd-socket" -> the newidmap service crashes in > varlink_idl_format_all_fields(). Recompile newidmap with systemd v260 > headers -> varlinkctl list-methods works again. Other people might hit the same issue, so let's mention that this was fixed. --- NEWS | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index f858e9119f346..2593d0d720c2b 100644 --- a/NEWS +++ b/NEWS @@ -68,12 +68,17 @@ CHANGES WITH 260 in spe: * The org.systemd.login1.Manager D-Bus interface has a minor API break. The CanPowerOff(), CanReboot(), CanSuspend(), etc. family of methods have introduced new return values which may break downstream - consumers, such as desktop environments. The new return values more + consumers such as desktop environments. The new return values more precisely communicate the status of inhibitors: 'inhibited', 'inhibitor-blocked', and 'challenge-inhibitor-blocked'. This allows desktops to differentiate between system administrator policy and temporary restrictions imposed by inhibitors. + * In systemd-260-rc1, the sd_varlink_field_type_t enum was extended in + a way that changed the numerical values of existing fields. This was + reverted for -rc2. Programs using sd-varlink and compiled with the + headers from -rc1 must be recompiled. + New system interfaces and components: * The os-release(5) gained a new field FANCY_NAME= that is similar to From d90858544c23ac09858d7f51e49eca72eb3eb143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 3 Mar 2026 15:22:08 +0100 Subject: [PATCH 0035/1296] systemctl: rename enqueue-marked-jobs to enqueue-marked Closes #40883. As described in the issue, it's not "jobs" that are marked, and also the name is unnecessarilly long. I think we don't need any compatibility measures here. At least in the rpm world, package upgrade scripts go through the helper which is part of the package so the new systemctl and the new helper are upgraded together. --- NEWS | 2 +- man/systemctl.xml | 4 ++-- shell-completion/bash/systemctl.in | 2 +- src/rpm/systemd-update-helper.in | 4 ++-- src/systemctl/systemctl-main.c | 4 ++-- src/systemctl/systemctl-start-unit.c | 6 +++--- src/systemctl/systemctl.c | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/NEWS b/NEWS index 2593d0d720c2b..6ae49bed90fc9 100644 --- a/NEWS +++ b/NEWS @@ -170,7 +170,7 @@ CHANGES WITH 260 in spe: * EnqueueMarkedJobs() D-Bus method now has a Varlink counterpart. - * systemctl gained a new 'enqueue-marked-jobs' verb, which calls the + * systemctl gained a new 'enqueue-marked' verb, which calls the EnqueueMarkedJobs() D-Bus method. The '--marked' parameter, which was previously used for the same purpose, is now deprecated. diff --git a/man/systemctl.xml b/man/systemctl.xml index c514c849265bd..f24a87739512a 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -501,7 +501,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err - enqueue-marked-jobs + enqueue-marked Enqueue start/stop/restart/reload jobs for all units that have the respective needs-* markers set. When a unit marked for reload does not support reload, @@ -521,7 +521,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err are not running yet, they will be started. When used in combination with , it is a deprecated alias of - enqueue-marked-jobs. + enqueue-marked. diff --git a/shell-completion/bash/systemctl.in b/shell-completion/bash/systemctl.in index b1582252c30cc..c34c7fb10ebc2 100644 --- a/shell-completion/bash/systemctl.in +++ b/shell-completion/bash/systemctl.in @@ -234,7 +234,7 @@ _systemctl () { list-timers list-units list-unit-files poweroff reboot rescue show-environment suspend get-default is-system-running preset-all list-automounts list-paths - enqueue-marked-jobs' + enqueue-marked' [FILE]='link switch-root' [TARGETS]='set-default' [MACHINES]='list-machines' diff --git a/src/rpm/systemd-update-helper.in b/src/rpm/systemd-update-helper.in index 9063a2cc3bdab..e8bc1e5921520 100755 --- a/src/rpm/systemd-update-helper.in +++ b/src/rpm/systemd-update-helper.in @@ -99,7 +99,7 @@ case "$command" in fi if [[ "$command" =~ restart ]]; then - systemctl enqueue-marked-jobs + systemctl enqueue-marked fi ;; @@ -120,7 +120,7 @@ case "$command" in for user in $users; do SYSTEMD_BUS_TIMEOUT={{UPDATE_HELPER_USER_TIMEOUT_SEC}}s \ - systemctl --user -M "$user@" enqueue-marked-jobs & + systemctl --user -M "$user@" enqueue-marked & done wait fi diff --git a/src/systemctl/systemctl-main.c b/src/systemctl/systemctl-main.c index 3b8a5e9088e4a..565b3a8878adb 100644 --- a/src/systemctl/systemctl-main.c +++ b/src/systemctl/systemctl-main.c @@ -65,9 +65,9 @@ static int systemctl_main(int argc, char *argv[]) { { "reload", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, { "restart", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, { "try-restart", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, - { "enqueue-marked-jobs", 1, 1, VERB_ONLINE_ONLY, verb_start }, + { "enqueue-marked", 1, 1, VERB_ONLINE_ONLY, verb_start }, { "reload-or-restart", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, - { "reload-or-try-restart", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, /* For compatibility with old systemctl <= 228 */ + { "reload-or-try-restart", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, /* For compatibility with systemctl <= 228 */ { "try-reload-or-restart", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, { "force-reload", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, /* For compatibility with SysV */ { "condreload", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, /* For compatibility with ALTLinux */ diff --git a/src/systemctl/systemctl-start-unit.c b/src/systemctl/systemctl-start-unit.c index 6a2981d9f7a21..ded4437bc6728 100644 --- a/src/systemctl/systemctl-start-unit.c +++ b/src/systemctl/systemctl-start-unit.c @@ -332,16 +332,16 @@ int verb_start(int argc, char *argv[], void *userdata) { job_type = "start"; mode = "isolate"; suffix = ".target"; - } else if (streq(argv[0], "enqueue-marked-jobs") || arg_marked) { + } else if (streq(argv[0], "enqueue-marked") || arg_marked) { is_enqueue_marked_jobs = true; method = job_type = mode = NULL; if (arg_show_transaction) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--show-transaction is not supported for enqueue-marked-jobs."); + "--show-transaction is not supported for enqueue-marked."); if (arg_marked) - log_warning("--marked is deprecated. Please use systemctl enqueue-marked-jobs instead."); + log_warning("--marked is deprecated. Please use systemctl enqueue-marked instead."); } else { /* A command in style of "systemctl start …", "systemctl stop …" and so on */ diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 0c9dd3b0afb01..3d3ed98fcd2ed 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -143,7 +143,7 @@ static int systemctl_help(void) { " reload UNIT... Reload one or more units\n" " restart UNIT... Start or restart one or more units\n" " try-restart UNIT... Restart one or more units if active\n" - " enqueue-marked-jobs Enqueue all marked unit jobs\n" + " enqueue-marked Enqueue jobs for all marked units\n" " reload-or-restart UNIT... Reload one or more units if possible,\n" " otherwise start or restart\n" " try-reload-or-restart UNIT... If active, reload one or more units,\n" From 9aad3336ff0174428a8a1ee138190b6d5122af81 Mon Sep 17 00:00:00 2001 From: Mikhail Novosyolov Date: Tue, 3 Mar 2026 16:57:30 +0300 Subject: [PATCH 0036/1296] hwdb/keyboard: Map FN key on Positron Proxima 15 After kernel commit 907bc9268a ("Input: atkbd - map F23 key to support default copilot shortcut") Fn+F5 combination (switch touchpad on/off) stopped working correctly. Fn produces F23, it is probably a bug in BIOS, ther eis no "Copilot" key. It was ignored before that commit, but now we have to remap it here in hwdb. This workaround is similar to systemd commit d2502f55a2d ("hwdb/keyboard: Map FN key on TUXEDO InfinityFlex 14 Gen1") Hardware probe of this notebook: https://linux-hardware.org/?probe=7aca7ed668 See also: https://bugzilla.rosa.ru/show_bug.cgi?id=19950 --- hwdb.d/60-keyboard.hwdb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hwdb.d/60-keyboard.hwdb b/hwdb.d/60-keyboard.hwdb index 2af76e1ca2b7d..70264a467ef52 100644 --- a/hwdb.d/60-keyboard.hwdb +++ b/hwdb.d/60-keyboard.hwdb @@ -2133,6 +2133,10 @@ evdev:atkbd:dmi:*:svnTUXEDO:*:rvnNB02:* evdev:atkbd:dmi:*:svnTUXEDO:*:rnDN50Z-140HC-YD:* KEYBOARD_KEY_6e=fn +# Positron Proxima 15 (G1569) +evdev:atkbd:dmi:bvn*:bvr*:bd*:svn*Positron*:pnG1569*:* + KEYBOARD_KEY_6e=fn + ########################################################### # VIA ########################################################### From 38a527b0c6b029ffe098dac820739599862c4efb Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Tue, 3 Mar 2026 17:50:19 +0100 Subject: [PATCH 0037/1296] systemctl-start-unit: enclose command in single quotes --- src/systemctl/systemctl-start-unit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systemctl/systemctl-start-unit.c b/src/systemctl/systemctl-start-unit.c index ded4437bc6728..b349483836804 100644 --- a/src/systemctl/systemctl-start-unit.c +++ b/src/systemctl/systemctl-start-unit.c @@ -341,7 +341,7 @@ int verb_start(int argc, char *argv[], void *userdata) { "--show-transaction is not supported for enqueue-marked."); if (arg_marked) - log_warning("--marked is deprecated. Please use systemctl enqueue-marked instead."); + log_warning("--marked is deprecated. Please use 'systemctl enqueue-marked' instead."); } else { /* A command in style of "systemctl start …", "systemctl stop …" and so on */ From 71a230d4a67d43d3087dc8d4eef08833725963aa Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 3 Mar 2026 17:42:40 +0000 Subject: [PATCH 0038/1296] Update hwdb ninja -C build update-hwdb --- hwdb.d/20-OUI.hwdb | 223 +- hwdb.d/20-acpi-vendor.hwdb | 6 + hwdb.d/20-acpi-vendor.hwdb.patch | 96 +- hwdb.d/20-pci-vendor-model.hwdb | 24 +- hwdb.d/acpi_id_registry.csv | 4 +- hwdb.d/ma-large.txt | 7810 +++++++++++++++--------------- hwdb.d/ma-medium.txt | 111 +- hwdb.d/ma-small.txt | 128 +- hwdb.d/pci.ids | 17 +- 9 files changed, 4553 insertions(+), 3866 deletions(-) diff --git a/hwdb.d/20-OUI.hwdb b/hwdb.d/20-OUI.hwdb index 0235e11838b22..b4b95ef1feb1f 100644 --- a/hwdb.d/20-OUI.hwdb +++ b/hwdb.d/20-OUI.hwdb @@ -9804,7 +9804,7 @@ OUI:000CDD* ID_OUI_FROM_DATABASE=AOS technologies AG OUI:000CDE* - ID_OUI_FROM_DATABASE=ABB AG. + ID_OUI_FROM_DATABASE=ABB AG OUI:000CDF* ID_OUI_FROM_DATABASE=JAI Manufacturing @@ -17676,7 +17676,7 @@ OUI:00171D* ID_OUI_FROM_DATABASE=DIGIT OUI:00171E* - ID_OUI_FROM_DATABASE=Theo Benning GmbH & Co. KG + ID_OUI_FROM_DATABASE=Benning Elektrotechnik und Elektronik GmbH & Co. KG OUI:00171F* ID_OUI_FROM_DATABASE=IMV Corporation @@ -34256,6 +34256,9 @@ OUI:007E56* OUI:007E95* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:007F1D* + ID_OUI_FROM_DATABASE=Fantasia Trading LLC + OUI:007F28* ID_OUI_FROM_DATABASE=Actiontec Electronics, Inc @@ -45443,6 +45446,9 @@ OUI:1047E7* OUI:1048B1* ID_OUI_FROM_DATABASE=Beijing Duokan Technology Limited +OUI:10490E* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:104963* ID_OUI_FROM_DATABASE=HARTING K.K. @@ -46133,6 +46139,9 @@ OUI:10C0D5* OUI:10C172* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:10C197* + ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd + OUI:10C22F* ID_OUI_FROM_DATABASE=China Entropy Co., Ltd. @@ -46364,6 +46373,9 @@ OUI:10E4C2* OUI:10E66B* ID_OUI_FROM_DATABASE=Kaon Broadband CO., LTD. +OUI:10E676* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + OUI:10E68F* ID_OUI_FROM_DATABASE=KWANGSUNG ELECTRONICS KOREA CO.,LTD. @@ -46526,6 +46538,9 @@ OUI:1409B4* OUI:1409DC* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:140A02* + ID_OUI_FROM_DATABASE=SHENZHEN BILIAN ELECTRONIC CO.,LTD + OUI:140A29* ID_OUI_FROM_DATABASE=Tiinlab Corporation @@ -50972,6 +50987,9 @@ OUI:1CE209* OUI:1CE2CC* ID_OUI_FROM_DATABASE=Texas Instruments +OUI:1CE4DD* + ID_OUI_FROM_DATABASE=Technicolor (China) Technology Co., Ltd. + OUI:1CE504* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -61325,6 +61343,12 @@ OUI:38AFD7* OUI:38B12D* ID_OUI_FROM_DATABASE=Sonotronic Nagel GmbH +OUI:38B14E2* + ID_OUI_FROM_DATABASE=Marssun + +OUI:38B14E8* + ID_OUI_FROM_DATABASE=QNION Co.,Ltd + OUI:38B19E0* ID_OUI_FROM_DATABASE=Triple Jump Medical @@ -61595,6 +61619,9 @@ OUI:38E08E* OUI:38E13D* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:38E158* + ID_OUI_FROM_DATABASE=Flaircomm Microelectronics,Inc. + OUI:38E1AA* ID_OUI_FROM_DATABASE=zte corporation @@ -62672,6 +62699,9 @@ OUI:3C7D0A* OUI:3C7DB1* ID_OUI_FROM_DATABASE=Texas Instruments +OUI:3C7F6E* + ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd + OUI:3C7F6F* ID_OUI_FROM_DATABASE=Telechips, Inc. @@ -62966,6 +62996,9 @@ OUI:3CB87A* OUI:3CB8D6* ID_OUI_FROM_DATABASE=Bluebank Communication Technology Co.,Ltd. +OUI:3CB922* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:3CB9A6* ID_OUI_FROM_DATABASE=Belden Deutschland GmbH @@ -64061,6 +64094,9 @@ OUI:407911* OUI:407912* ID_OUI_FROM_DATABASE=Texas Instruments +OUI:407955* + ID_OUI_FROM_DATABASE=Datacolor + OUI:407A80* ID_OUI_FROM_DATABASE=Nokia Corporation @@ -64430,6 +64466,9 @@ OUI:40B8C2* OUI:40B93C* ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise +OUI:40BA09* + ID_OUI_FROM_DATABASE=Dell Inc. + OUI:40BA61* ID_OUI_FROM_DATABASE=ARIMA Communications Corp. @@ -65510,6 +65549,9 @@ OUI:447654* OUI:4476E7* ID_OUI_FROM_DATABASE=TECNO MOBILE LIMITED +OUI:447831* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:44783E* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -69584,6 +69626,9 @@ OUI:50338B* OUI:5033F0* ID_OUI_FROM_DATABASE=YICHEN (SHENZHEN) TECHNOLOGY CO.LTD +OUI:5037CD* + ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co., Ltd. + OUI:50382F* ID_OUI_FROM_DATABASE=ASE Group Chung-Li @@ -69935,6 +69980,9 @@ OUI:506255E* OUI:506313* ID_OUI_FROM_DATABASE=Hon Hai Precision Ind. Co.,Ltd. +OUI:506382* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:506391* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -72131,6 +72179,9 @@ OUI:54E63F* OUI:54E6FC* ID_OUI_FROM_DATABASE=TP-LINK TECHNOLOGIES CO.,LTD. +OUI:54E6FD* + ID_OUI_FROM_DATABASE=Sony Interactive Entertainment Inc. + OUI:54E7D5* ID_OUI_FROM_DATABASE=Sun Cupid Technology (HK) LTD @@ -72422,6 +72473,9 @@ OUI:5820B1* OUI:582136* ID_OUI_FROM_DATABASE=KMB systems, s.r.o. +OUI:58219D* + ID_OUI_FROM_DATABASE=Shanghai Timar Integrated Circuit Co., LTD + OUI:5821E9* ID_OUI_FROM_DATABASE=TWPI @@ -72497,6 +72551,9 @@ OUI:58278C* OUI:582A93* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:582ABD* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:582AF7* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -75746,6 +75803,9 @@ OUI:6052D0* OUI:605317* ID_OUI_FROM_DATABASE=Sandstone Technologies +OUI:605355* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:605375* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -80648,6 +80708,9 @@ OUI:6CBAB8* OUI:6CBEE9* ID_OUI_FROM_DATABASE=Alcatel-Lucent IPD +OUI:6CBF2F* + ID_OUI_FROM_DATABASE=eero inc. + OUI:6CBFB5* ID_OUI_FROM_DATABASE=Noon Technology Co., Ltd @@ -81713,6 +81776,9 @@ OUI:70708B* OUI:7070AA* ID_OUI_FROM_DATABASE=Amazon Technologies Inc. +OUI:7070D5* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:7070FC* ID_OUI_FROM_DATABASE=GOLD&WATER INDUSTRIAL LIMITED @@ -94988,6 +95054,9 @@ OUI:74249F* OUI:7424CA* ID_OUI_FROM_DATABASE=Guangzhou Shiyuan Electronic Technology Company Limited +OUI:742554* + ID_OUI_FROM_DATABASE=NVIDIA Corporation + OUI:7425840* ID_OUI_FROM_DATABASE=Alcon Wireless Private Limited @@ -96839,6 +96908,9 @@ OUI:7845B3* OUI:7845C4* ID_OUI_FROM_DATABASE=Dell Inc. +OUI:7845DC* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:78465C* ID_OUI_FROM_DATABASE=CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. @@ -102182,6 +102254,9 @@ OUI:84AD58* OUI:84AD8D* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:84AEDE* + ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd + OUI:84AF1F* ID_OUI_FROM_DATABASE=GopherTec Inc. @@ -105308,6 +105383,9 @@ OUI:8C1F641C2* OUI:8C1F641C4* ID_OUI_FROM_DATABASE=EDGX bv +OUI:8C1F641C5* + ID_OUI_FROM_DATABASE=Breas Medical AB + OUI:8C1F641C9* ID_OUI_FROM_DATABASE=Pneumax Spa @@ -105458,6 +105536,9 @@ OUI:8C1F64211* OUI:8C1F64215* ID_OUI_FROM_DATABASE=XLOGIC srl +OUI:8C1F64216* + ID_OUI_FROM_DATABASE=Hiwin Mikrosystem Corp. + OUI:8C1F64219* ID_OUI_FROM_DATABASE=Guangzhou Desam Audio Co.,Ltd @@ -105605,6 +105686,9 @@ OUI:8C1F64269* OUI:8C1F6426B* ID_OUI_FROM_DATABASE=Profcon AB +OUI:8C1F6426C* + ID_OUI_FROM_DATABASE=Diatech co.,ltd. + OUI:8C1F6426E* ID_OUI_FROM_DATABASE=Koizumi Lighting Technology Corp. @@ -106487,12 +106571,18 @@ OUI:8C1F6442D* OUI:8C1F6442F* ID_OUI_FROM_DATABASE=Tomorrow Companies Inc +OUI:8C1F64431* + ID_OUI_FROM_DATABASE=Pneumax Spa + OUI:8C1F64432* ID_OUI_FROM_DATABASE=Rebel Systems OUI:8C1F64434* ID_OUI_FROM_DATABASE=netmon +OUI:8C1F64436* + ID_OUI_FROM_DATABASE=Vision Systems Safety Tech + OUI:8C1F64437* ID_OUI_FROM_DATABASE=Gogo BA @@ -106955,6 +107045,9 @@ OUI:8C1F6451A* OUI:8C1F64521* ID_OUI_FROM_DATABASE=MP-SENSOR GmbH +OUI:8C1F64522* + ID_OUI_FROM_DATABASE=CloudRAN.ai + OUI:8C1F64523* ID_OUI_FROM_DATABASE=SPEKTRA Schwingungstechnik und Akustik GmbH Dresden @@ -107381,6 +107474,9 @@ OUI:8C1F645FB* OUI:8C1F645FC* ID_OUI_FROM_DATABASE=Lance Design LLC +OUI:8C1F645FE* + ID_OUI_FROM_DATABASE=Pneumax Spa + OUI:8C1F645FF* ID_OUI_FROM_DATABASE=DAVE SRL @@ -107519,6 +107615,9 @@ OUI:8C1F64647* OUI:8C1F64648* ID_OUI_FROM_DATABASE=Gridpulse c.o.o. +OUI:8C1F6464C* + ID_OUI_FROM_DATABASE=ACS Motion Control + OUI:8C1F6464D* ID_OUI_FROM_DATABASE=NEWONE CO.,LTD. @@ -108413,6 +108512,9 @@ OUI:8C1F64803* OUI:8C1F64804* ID_OUI_FROM_DATABASE=EA Elektro-Automatik GmbH +OUI:8C1F64805* + ID_OUI_FROM_DATABASE=ATAL s.r.o. + OUI:8C1F64806* ID_OUI_FROM_DATABASE=Matrixspace @@ -110249,6 +110351,9 @@ OUI:8C1F64BC3* OUI:8C1F64BC4* ID_OUI_FROM_DATABASE=EasyNet Industry (Shenzhen) Co., Ltd +OUI:8C1F64BC5* + ID_OUI_FROM_DATABASE=DORLET SAU + OUI:8C1F64BC6* ID_OUI_FROM_DATABASE=Chengdu ZiChen Time&Frequency Technology Co.,Ltd @@ -110273,6 +110378,9 @@ OUI:8C1F64BCD* OUI:8C1F64BCE* ID_OUI_FROM_DATABASE=BESO sp. z o.o. +OUI:8C1F64BCF* + ID_OUI_FROM_DATABASE=Erba Lachema s.r.o. + OUI:8C1F64BD0* ID_OUI_FROM_DATABASE=Mesa Labs, Inc. @@ -110954,6 +111062,9 @@ OUI:8C1F64D29* OUI:8C1F64D2A* ID_OUI_FROM_DATABASE=Anteus Kft. +OUI:8C1F64D2C* + ID_OUI_FROM_DATABASE=DEUTA Werke GmbH + OUI:8C1F64D2D* ID_OUI_FROM_DATABASE=Eskomar Ltd. @@ -111155,6 +111266,9 @@ OUI:8C1F64D9D* OUI:8C1F64D9E* ID_OUI_FROM_DATABASE=Wagner Group GmbH +OUI:8C1F64DA0* + ID_OUI_FROM_DATABASE=Sensata Technologies Inc. + OUI:8C1F64DA1* ID_OUI_FROM_DATABASE=Hangteng (HK) Technology Co., Limited @@ -111389,6 +111503,9 @@ OUI:8C1F64E23* OUI:8C1F64E24* ID_OUI_FROM_DATABASE=COMETA SAS +OUI:8C1F64E25* + ID_OUI_FROM_DATABASE=HEITEC AG + OUI:8C1F64E26* ID_OUI_FROM_DATABASE=HyperSilicon Co.,Ltd @@ -113765,6 +113882,9 @@ OUI:900DCB* OUI:900E83* ID_OUI_FROM_DATABASE=Monico Monitoring, Inc. +OUI:900E84* + ID_OUI_FROM_DATABASE=eero inc. + OUI:900E9E* ID_OUI_FROM_DATABASE=Shenzhen SuperElectron Technology Co.,Ltd. @@ -114269,6 +114389,9 @@ OUI:90623F* OUI:90633B* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:90649B* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:9064AD* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -119429,6 +119552,9 @@ OUI:9CCAD9* OUI:9CCBF7* ID_OUI_FROM_DATABASE=CLOUD STAR TECHNOLOGY CO., LTD. +OUI:9CCC01* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:9CCC83* ID_OUI_FROM_DATABASE=Juniper Networks @@ -122297,6 +122423,9 @@ OUI:A4934C* OUI:A493AD* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. +OUI:A493FE* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:A49426* ID_OUI_FROM_DATABASE=Elgama-Elektronika Ltd. @@ -122675,6 +122804,9 @@ OUI:A4D73C* OUI:A4D795* ID_OUI_FROM_DATABASE=Wingtech Mobile Communications Co.,Ltd +OUI:A4D7D6* + ID_OUI_FROM_DATABASE=Shenzhen Linkoh Network Technology Co;Ltd + OUI:A4D856* ID_OUI_FROM_DATABASE=Gimbal, Inc @@ -124580,6 +124712,9 @@ OUI:AC44F2* OUI:AC4500* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:AC45B0* + ID_OUI_FROM_DATABASE=Shenzhen Jidao Technology Co Ltd + OUI:AC45CA* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD @@ -125858,6 +125993,9 @@ OUI:B02A1F* OUI:B02A43* ID_OUI_FROM_DATABASE=Google, Inc. +OUI:B02B64* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + OUI:B02EBA* ID_OUI_FROM_DATABASE=Earda Technologies co Ltd @@ -126962,6 +127100,9 @@ OUI:B0EE7B* OUI:B0F00C* ID_OUI_FROM_DATABASE=Dongguan Wecxw CO.,Ltd. +OUI:B0F079* + ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + OUI:B0F1A3* ID_OUI_FROM_DATABASE=Fengfan (BeiJing) Technology Co., Ltd. @@ -128549,6 +128690,9 @@ OUI:B808D7* OUI:B8098A* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:B80B9A* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:B80B9D* ID_OUI_FROM_DATABASE=ROPEX Industrie-Elektronik GmbH @@ -128912,6 +129056,9 @@ OUI:B856BD* OUI:B85776* ID_OUI_FROM_DATABASE=lignex1 +OUI:B857D6* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + OUI:B857D8* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -130463,6 +130610,9 @@ OUI:BC6778* OUI:BC6784* ID_OUI_FROM_DATABASE=Environics Oy +OUI:BC68C3* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:BC69CB* ID_OUI_FROM_DATABASE=Panasonic Electric Works Networks Co., Ltd. @@ -133445,6 +133595,48 @@ OUI:C4823F* OUI:C4824E* ID_OUI_FROM_DATABASE=Changzhou Uchip Electronics Co., LTD. +OUI:C482720* + ID_OUI_FROM_DATABASE=Gabriel Tecnologia + +OUI:C482721* + ID_OUI_FROM_DATABASE=Private + +OUI:C482722* + ID_OUI_FROM_DATABASE=Digisine Energytech Co., Ltd. + +OUI:C482724* + ID_OUI_FROM_DATABASE=Melecs EWS GmbH + +OUI:C482725* + ID_OUI_FROM_DATABASE=Schunk SE & Co. KG + +OUI:C482726* + ID_OUI_FROM_DATABASE=Mantenimiento y paileria + +OUI:C482727* + ID_OUI_FROM_DATABASE=Mode Sensors AS + +OUI:C482728* + ID_OUI_FROM_DATABASE=Shanghai Smart Logic Technology Ltd. + +OUI:C482729* + ID_OUI_FROM_DATABASE=Satways Ltd + +OUI:C48272A* + ID_OUI_FROM_DATABASE=Tolt Technologies LLC + +OUI:C48272B* + ID_OUI_FROM_DATABASE=MyPlace Australia Pty Ltd + +OUI:C48272C* + ID_OUI_FROM_DATABASE=E2-CAD + +OUI:C48272D* + ID_OUI_FROM_DATABASE=Posital B.V. + +OUI:C48272E* + ID_OUI_FROM_DATABASE=Smart Radar System, Inc + OUI:C482E1* ID_OUI_FROM_DATABASE=Tuya Smart Inc. @@ -134552,6 +134744,9 @@ OUI:C82496* OUI:C825E1* ID_OUI_FROM_DATABASE=Lemobile Information Technology (Beijing) Co., Ltd +OUI:C82691* + ID_OUI_FROM_DATABASE=Arista Networks, Inc. + OUI:C826E2* ID_OUI_FROM_DATABASE=CHINA DRAGON TECHNOLOGY LIMITED @@ -135575,6 +135770,9 @@ OUI:C8C791* OUI:C8C83F* ID_OUI_FROM_DATABASE=Texas Instruments +OUI:C8C873* + ID_OUI_FROM_DATABASE=CHIPSEN INC. + OUI:C8C9A3* ID_OUI_FROM_DATABASE=Espressif Inc. @@ -136007,6 +136205,9 @@ OUI:CC08FB* OUI:CC09C8* ID_OUI_FROM_DATABASE=IMAQLIQ LTD +OUI:CC0C9C* + ID_OUI_FROM_DATABASE=CIG SHANGHAI CO LTD + OUI:CC0CDA* ID_OUI_FROM_DATABASE=Miljovakt AS @@ -140138,6 +140339,9 @@ OUI:D49F29* OUI:D49FDD* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. +OUI:D49FF9* + ID_OUI_FROM_DATABASE=Earda Technologies co Ltd + OUI:D4A02A* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -141176,6 +141380,9 @@ OUI:D86162* OUI:D86194* ID_OUI_FROM_DATABASE=Objetivos y Sevicios de Valor Añadido +OUI:D862CA* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + OUI:D862DB* ID_OUI_FROM_DATABASE=Eno Inc. @@ -144764,6 +144971,9 @@ OUI:E0FFF7* OUI:E40177* ID_OUI_FROM_DATABASE=SafeOwl, Inc. +OUI:E40274* + ID_OUI_FROM_DATABASE=FW Murphy Production Controls + OUI:E4029B* ID_OUI_FROM_DATABASE=Intel Corporate @@ -146237,6 +146447,9 @@ OUI:E82281* OUI:E822B8* ID_OUI_FROM_DATABASE=Shenzhen Skyworth Digital Technology CO., Ltd +OUI:E823FB* + ID_OUI_FROM_DATABASE=Redder + OUI:E82404* ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co.,Ltd. @@ -146534,6 +146747,9 @@ OUI:E866C4* OUI:E86819* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:E868B1* + ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + OUI:E868E7* ID_OUI_FROM_DATABASE=Espressif Inc. @@ -150116,6 +150332,9 @@ OUI:F0CD31* OUI:F0CF4D* ID_OUI_FROM_DATABASE=BitRecords GmbH +OUI:F0D018* + ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise + OUI:F0D08C* ID_OUI_FROM_DATABASE=TCT mobile ltd diff --git a/hwdb.d/20-acpi-vendor.hwdb b/hwdb.d/20-acpi-vendor.hwdb index 3828fea8ef9b9..c34293fef8fe6 100644 --- a/hwdb.d/20-acpi-vendor.hwdb +++ b/hwdb.d/20-acpi-vendor.hwdb @@ -252,6 +252,9 @@ acpi:KIOX*: acpi:KOMF*: ID_VENDOR_FROM_DATABASE=Kontron France +acpi:LECA*: + ID_VENDOR_FROM_DATABASE=Theo End (Shenzhen) Computing Technology Co., Ltd. + acpi:LNRO*: ID_VENDOR_FROM_DATABASE=Linaro, Ltd. @@ -417,6 +420,9 @@ acpi:TOSB*: acpi:TXNW*: ID_VENDOR_FROM_DATABASE=Texas Instruments +acpi:TYHX*: + ID_VENDOR_FROM_DATABASE=Nanjing Tianyihexin Electronics Ltd + acpi:UBLX*: ID_VENDOR_FROM_DATABASE=u-blox AG diff --git a/hwdb.d/20-acpi-vendor.hwdb.patch b/hwdb.d/20-acpi-vendor.hwdb.patch index 99c4c4d2bb724..497ee1243c5ed 100644 --- a/hwdb.d/20-acpi-vendor.hwdb.patch +++ b/hwdb.d/20-acpi-vendor.hwdb.patch @@ -1,5 +1,5 @@ ---- 20-acpi-vendor.hwdb.base 2026-02-24 18:35:45.671934479 +0000 -+++ 20-acpi-vendor.hwdb 2026-02-24 18:35:45.675934543 +0000 +--- 20-acpi-vendor.hwdb.base 2026-03-03 17:42:00.020243611 +0000 ++++ 20-acpi-vendor.hwdb 2026-03-03 17:42:00.024243673 +0000 @@ -3,6 +3,8 @@ # Data imported from: # https://uefi.org/uefi-pnp-export @@ -9,7 +9,7 @@ acpi:3GVR*: ID_VENDOR_FROM_DATABASE=VR Technology Holdings Limited -@@ -454,6 +456,9 @@ +@@ -460,6 +462,9 @@ acpi:AAA*: ID_VENDOR_FROM_DATABASE=Avolites Ltd @@ -19,7 +19,7 @@ acpi:AAE*: ID_VENDOR_FROM_DATABASE=Anatek Electronics Inc. -@@ -481,6 +486,9 @@ +@@ -487,6 +492,9 @@ acpi:ABO*: ID_VENDOR_FROM_DATABASE=D-Link Systems Inc @@ -29,7 +29,7 @@ acpi:ABS*: ID_VENDOR_FROM_DATABASE=Abaco Systems, Inc. -@@ -526,7 +534,7 @@ +@@ -532,7 +540,7 @@ acpi:ACO*: ID_VENDOR_FROM_DATABASE=Allion Computer Inc. @@ -38,7 +38,7 @@ ID_VENDOR_FROM_DATABASE=Aspen Tech Inc acpi:ACR*: -@@ -805,6 +813,9 @@ +@@ -811,6 +819,9 @@ acpi:AMT*: ID_VENDOR_FROM_DATABASE=AMT International Industry @@ -48,7 +48,7 @@ acpi:AMX*: ID_VENDOR_FROM_DATABASE=AMX LLC -@@ -853,6 +864,9 @@ +@@ -859,6 +870,9 @@ acpi:AOA*: ID_VENDOR_FROM_DATABASE=AOpen Inc. @@ -58,7 +58,7 @@ acpi:AOE*: ID_VENDOR_FROM_DATABASE=Advanced Optics Electronics, Inc. -@@ -862,6 +876,9 @@ +@@ -868,6 +882,9 @@ acpi:AOT*: ID_VENDOR_FROM_DATABASE=Alcatel @@ -68,7 +68,7 @@ acpi:APC*: ID_VENDOR_FROM_DATABASE=American Power Conversion -@@ -1043,7 +1060,7 @@ +@@ -1049,7 +1066,7 @@ ID_VENDOR_FROM_DATABASE=ALPS ALPINE CO., LTD. acpi:AUO*: @@ -77,7 +77,7 @@ acpi:AUR*: ID_VENDOR_FROM_DATABASE=Aureal Semiconductor -@@ -1123,6 +1140,9 @@ +@@ -1129,6 +1146,9 @@ acpi:AXE*: ID_VENDOR_FROM_DATABASE=Axell Corporation @@ -87,7 +87,7 @@ acpi:AXI*: ID_VENDOR_FROM_DATABASE=American Magnetics -@@ -1282,6 +1302,9 @@ +@@ -1288,6 +1308,9 @@ acpi:BML*: ID_VENDOR_FROM_DATABASE=BIOMED Lab @@ -97,7 +97,7 @@ acpi:BMS*: ID_VENDOR_FROM_DATABASE=BIOMEDISYS -@@ -1294,6 +1317,9 @@ +@@ -1300,6 +1323,9 @@ acpi:BNO*: ID_VENDOR_FROM_DATABASE=Bang & Olufsen @@ -107,7 +107,7 @@ acpi:BNS*: ID_VENDOR_FROM_DATABASE=Boulder Nonlinear Systems -@@ -1540,6 +1566,9 @@ +@@ -1546,6 +1572,9 @@ acpi:CHA*: ID_VENDOR_FROM_DATABASE=Chase Research PLC @@ -117,7 +117,7 @@ acpi:CHD*: ID_VENDOR_FROM_DATABASE=ChangHong Electric Co.,Ltd -@@ -1705,6 +1734,9 @@ +@@ -1711,6 +1740,9 @@ acpi:COD*: ID_VENDOR_FROM_DATABASE=CODAN Pty. Ltd. @@ -127,7 +127,7 @@ acpi:COI*: ID_VENDOR_FROM_DATABASE=Codec Inc. -@@ -2123,7 +2155,7 @@ +@@ -2129,7 +2161,7 @@ ID_VENDOR_FROM_DATABASE=Dragon Information Technology acpi:DJE*: @@ -136,7 +136,7 @@ acpi:DJP*: ID_VENDOR_FROM_DATABASE=Maygay Machines, Ltd -@@ -2476,6 +2508,9 @@ +@@ -2482,6 +2514,9 @@ acpi:EIN*: ID_VENDOR_FROM_DATABASE=Elegant Invention @@ -146,7 +146,7 @@ acpi:EKA*: ID_VENDOR_FROM_DATABASE=MagTek Inc. -@@ -2746,6 +2781,9 @@ +@@ -2752,6 +2787,9 @@ acpi:FCG*: ID_VENDOR_FROM_DATABASE=First International Computer Ltd @@ -156,7 +156,7 @@ acpi:FCS*: ID_VENDOR_FROM_DATABASE=Focus Enhancements, Inc. -@@ -3122,7 +3160,7 @@ +@@ -3128,7 +3166,7 @@ ID_VENDOR_FROM_DATABASE=General Standards Corporation acpi:GSM*: @@ -165,7 +165,7 @@ acpi:GSN*: ID_VENDOR_FROM_DATABASE=Grandstream Networks, Inc. -@@ -3232,6 +3270,9 @@ +@@ -3238,6 +3276,9 @@ acpi:HEC*: ID_VENDOR_FROM_DATABASE=Hisense Electric Co., Ltd. @@ -175,7 +175,7 @@ acpi:HEL*: ID_VENDOR_FROM_DATABASE=Hitachi Micro Systems Europe Ltd -@@ -3367,6 +3408,9 @@ +@@ -3373,6 +3414,9 @@ acpi:HSD*: ID_VENDOR_FROM_DATABASE=HannStar Display Corp @@ -185,7 +185,7 @@ acpi:HSM*: ID_VENDOR_FROM_DATABASE=AT&T Microelectronics -@@ -3493,6 +3537,9 @@ +@@ -3499,6 +3543,9 @@ acpi:ICI*: ID_VENDOR_FROM_DATABASE=Infotek Communication Inc @@ -195,7 +195,7 @@ acpi:ICM*: ID_VENDOR_FROM_DATABASE=Intracom SA -@@ -3589,6 +3636,9 @@ +@@ -3595,6 +3642,9 @@ acpi:IKE*: ID_VENDOR_FROM_DATABASE=Ikegami Tsushinki Co. Ltd. @@ -205,7 +205,7 @@ acpi:IKS*: ID_VENDOR_FROM_DATABASE=Ikos Systems Inc -@@ -3637,6 +3687,9 @@ +@@ -3643,6 +3693,9 @@ acpi:IMX*: ID_VENDOR_FROM_DATABASE=arpara Technology Co., Ltd. @@ -215,7 +215,7 @@ acpi:INA*: ID_VENDOR_FROM_DATABASE=Inventec Corporation -@@ -4165,6 +4218,9 @@ +@@ -4171,6 +4224,9 @@ acpi:LAN*: ID_VENDOR_FROM_DATABASE=Sodeman Lancom Inc @@ -225,7 +225,7 @@ acpi:LAS*: ID_VENDOR_FROM_DATABASE=LASAT Comm. A/S -@@ -4216,6 +4272,9 @@ +@@ -4222,6 +4278,9 @@ acpi:LED*: ID_VENDOR_FROM_DATABASE=Long Engineering Design Inc @@ -235,7 +235,7 @@ acpi:LEG*: ID_VENDOR_FROM_DATABASE=Legerity, Inc -@@ -4234,6 +4293,9 @@ +@@ -4240,6 +4299,9 @@ acpi:LGD*: ID_VENDOR_FROM_DATABASE=LG Display @@ -245,7 +245,7 @@ acpi:LGI*: ID_VENDOR_FROM_DATABASE=Logitech Inc -@@ -4300,6 +4362,9 @@ +@@ -4306,6 +4368,9 @@ acpi:LND*: ID_VENDOR_FROM_DATABASE=Land Computer Company Ltd @@ -255,7 +255,7 @@ acpi:LNK*: ID_VENDOR_FROM_DATABASE=Link Tech Inc -@@ -4334,7 +4399,7 @@ +@@ -4340,7 +4405,7 @@ ID_VENDOR_FROM_DATABASE=Design Technology acpi:LPL*: @@ -264,7 +264,7 @@ acpi:LSC*: ID_VENDOR_FROM_DATABASE=LifeSize Communications -@@ -4510,6 +4575,9 @@ +@@ -4516,6 +4581,9 @@ acpi:MCX*: ID_VENDOR_FROM_DATABASE=Millson Custom Solutions Inc. @@ -274,7 +274,7 @@ acpi:MDA*: ID_VENDOR_FROM_DATABASE=Media4 Inc -@@ -4756,6 +4824,9 @@ +@@ -4762,6 +4830,9 @@ acpi:MOM*: ID_VENDOR_FROM_DATABASE=Momentum Data Systems @@ -284,7 +284,7 @@ acpi:MOS*: ID_VENDOR_FROM_DATABASE=Moses Corporation -@@ -4996,6 +5067,9 @@ +@@ -5002,6 +5073,9 @@ acpi:NAL*: ID_VENDOR_FROM_DATABASE=Network Alchemy @@ -294,7 +294,7 @@ acpi:NAT*: ID_VENDOR_FROM_DATABASE=NaturalPoint Inc. -@@ -5536,6 +5610,9 @@ +@@ -5542,6 +5616,9 @@ acpi:PCX*: ID_VENDOR_FROM_DATABASE=PC Xperten @@ -304,7 +304,7 @@ acpi:PDM*: ID_VENDOR_FROM_DATABASE=Psion Dacom Plc. -@@ -5599,9 +5676,6 @@ +@@ -5605,9 +5682,6 @@ acpi:PHE*: ID_VENDOR_FROM_DATABASE=Philips Medical Systems Boeblingen GmbH @@ -314,7 +314,7 @@ acpi:PHL*: ID_VENDOR_FROM_DATABASE=Philips Consumer Electronics Company -@@ -5692,9 +5766,6 @@ +@@ -5698,9 +5772,6 @@ acpi:PNL*: ID_VENDOR_FROM_DATABASE=Panelview, Inc. @@ -324,7 +324,7 @@ acpi:PNR*: ID_VENDOR_FROM_DATABASE=Planar Systems, Inc. -@@ -6172,9 +6243,6 @@ +@@ -6178,9 +6249,6 @@ acpi:RTI*: ID_VENDOR_FROM_DATABASE=Rancho Tech Inc @@ -334,7 +334,7 @@ acpi:RTL*: ID_VENDOR_FROM_DATABASE=Realtek Semiconductor Company Ltd -@@ -6349,9 +6417,6 @@ +@@ -6355,9 +6423,6 @@ acpi:SEE*: ID_VENDOR_FROM_DATABASE=SeeColor Corporation @@ -344,7 +344,7 @@ acpi:SEI*: ID_VENDOR_FROM_DATABASE=Seitz & Associates Inc -@@ -6835,6 +6900,9 @@ +@@ -6841,6 +6906,9 @@ acpi:SVD*: ID_VENDOR_FROM_DATABASE=SVD Computer @@ -354,7 +354,7 @@ acpi:SVI*: ID_VENDOR_FROM_DATABASE=Sun Microsystems -@@ -6919,6 +6987,9 @@ +@@ -6925,6 +6993,9 @@ acpi:SZM*: ID_VENDOR_FROM_DATABASE=Shenzhen MTC Co., Ltd @@ -364,7 +364,7 @@ acpi:TAA*: ID_VENDOR_FROM_DATABASE=Tandberg -@@ -7009,6 +7080,9 @@ +@@ -7015,6 +7086,9 @@ acpi:TDG*: ID_VENDOR_FROM_DATABASE=Six15 Technologies @@ -374,7 +374,7 @@ acpi:TDM*: ID_VENDOR_FROM_DATABASE=Tandem Computer Europe Inc -@@ -7051,6 +7125,9 @@ +@@ -7057,6 +7131,9 @@ acpi:TEV*: ID_VENDOR_FROM_DATABASE=Televés, S.A. @@ -384,7 +384,7 @@ acpi:TEZ*: ID_VENDOR_FROM_DATABASE=Tech Source Inc. -@@ -7180,9 +7257,6 @@ +@@ -7186,9 +7263,6 @@ acpi:TNC*: ID_VENDOR_FROM_DATABASE=TNC Industrial Company Ltd @@ -394,7 +394,7 @@ acpi:TNM*: ID_VENDOR_FROM_DATABASE=TECNIMAGEN SA -@@ -7495,14 +7569,14 @@ +@@ -7501,14 +7575,14 @@ acpi:UNC*: ID_VENDOR_FROM_DATABASE=Unisys Corporation @@ -415,7 +415,7 @@ acpi:UNI*: ID_VENDOR_FROM_DATABASE=Uniform Industry Corp. -@@ -7537,6 +7611,9 @@ +@@ -7543,6 +7617,9 @@ acpi:USA*: ID_VENDOR_FROM_DATABASE=Utimaco Safeware AG @@ -425,7 +425,7 @@ acpi:USD*: ID_VENDOR_FROM_DATABASE=U.S. Digital Corporation -@@ -7798,9 +7875,6 @@ +@@ -7804,9 +7881,6 @@ acpi:WAL*: ID_VENDOR_FROM_DATABASE=Wave Access @@ -435,7 +435,7 @@ acpi:WAV*: ID_VENDOR_FROM_DATABASE=Wavephore -@@ -7928,7 +8002,7 @@ +@@ -7934,7 +8008,7 @@ ID_VENDOR_FROM_DATABASE=WyreStorm Technologies LLC acpi:WYS*: @@ -444,7 +444,7 @@ acpi:WYT*: ID_VENDOR_FROM_DATABASE=Wooyoung Image & Information Co.,Ltd. -@@ -7942,9 +8016,6 @@ +@@ -7948,9 +8022,6 @@ acpi:XDM*: ID_VENDOR_FROM_DATABASE=XDM Ltd. @@ -454,7 +454,7 @@ acpi:XES*: ID_VENDOR_FROM_DATABASE=Extreme Engineering Solutions, Inc. -@@ -7978,9 +8049,6 @@ +@@ -7984,9 +8055,6 @@ acpi:XNT*: ID_VENDOR_FROM_DATABASE=XN Technologies, Inc. @@ -464,7 +464,7 @@ acpi:XQU*: ID_VENDOR_FROM_DATABASE=SHANGHAI SVA-DAV ELECTRONICS CO., LTD -@@ -8047,6 +8115,9 @@ +@@ -8053,6 +8121,9 @@ acpi:ZBX*: ID_VENDOR_FROM_DATABASE=Zebax Technologies diff --git a/hwdb.d/20-pci-vendor-model.hwdb b/hwdb.d/20-pci-vendor-model.hwdb index e36c665ca2a39..074eb5c4312a3 100644 --- a/hwdb.d/20-pci-vendor-model.hwdb +++ b/hwdb.d/20-pci-vendor-model.hwdb @@ -12003,7 +12003,7 @@ pci:v00001002d0000745E* ID_MODEL_FROM_DATABASE=Navi 31 [Radeon Pro W7800] pci:v00001002d00007460* - ID_MODEL_FROM_DATABASE=Navi32 GL-XL [AMD Radeon PRO V710] + ID_MODEL_FROM_DATABASE=Navi 32 GL-XL [AMD Radeon PRO V710] pci:v00001002d00007461* ID_MODEL_FROM_DATABASE=Navi 32 [AMD Radeon PRO V710] @@ -39993,7 +39993,7 @@ pci:v000010DEd00002C39* ID_MODEL_FROM_DATABASE=GB203GLM [RTX PRO 4000 Blackwell Generation Laptop GPU] pci:v000010DEd00002C3A* - ID_MODEL_FROM_DATABASE=GB203GL [RTX PRO 4500 Blackwell] + ID_MODEL_FROM_DATABASE=GB203GL [RTX PRO 4500 Blackwell Server Edition] pci:v000010DEd00002C58* ID_MODEL_FROM_DATABASE=GB203M / GN22-X11 [GeForce RTX 5090 Max-Q / Mobile] @@ -51846,7 +51846,7 @@ pci:v0000125Bd00009100sv0000A000sd00007000* ID_MODEL_FROM_DATABASE=AX99100 PCIe to Multi I/O Controller (Local Bus) pci:v0000125Bd00009100sv0000EA50sd00001C10* - ID_MODEL_FROM_DATABASE=AX99100 PCIe to Multi I/O Controller (RXi2-BP) + ID_MODEL_FROM_DATABASE=AX99100 PCIe to Multi I/O Controller (RXi2-BP Serial Port) pci:v0000125Bd00009105* ID_MODEL_FROM_DATABASE=AX99100 PCIe to I/O Bridge @@ -61880,6 +61880,9 @@ pci:v000014C3d00007663* pci:v000014C3d00007902* ID_MODEL_FROM_DATABASE=MT7902 802.11ax PCIe Wireless Network Adapter [Filogic 310] +pci:v000014C3d00007906* + ID_MODEL_FROM_DATABASE=MT7916A/MT7916D normal link PCIe Wi-Fi 6(802.11ax) 160MHz 2x2 Wireless Network Adapter [Filogic 630] + pci:v000014C3d00007915* ID_MODEL_FROM_DATABASE=MT7915A/MT7915D normal link PCIe Wi-Fi 6(802.11ax) 80MHz 4x4/2x2 Wireless Network Adapter [Filogic 615] @@ -64673,6 +64676,9 @@ pci:v000014E4d00005F72* pci:v000014E4d00005FA0* ID_MODEL_FROM_DATABASE=BRCM4377 Bluetooth Controller +pci:v000014E4d00006865* + ID_MODEL_FROM_DATABASE=BCM68650 [Aspen] XGSPON OLT + pci:v000014E4d00008411* ID_MODEL_FROM_DATABASE=BCM47xx PCIe Bridge @@ -74945,6 +74951,9 @@ pci:v00001A03d00002000sv000015D9sd00000832* pci:v00001A03d00002000sv000015D9sd0000086B* ID_MODEL_FROM_DATABASE=ASPEED Graphics Family (X10DRS (AST2400 BMC)) +pci:v00001A03d00002000sv000015D9sd0000086D* + ID_MODEL_FROM_DATABASE=ASPEED Graphics Family (X10SDV (AST2400 BMC)) + pci:v00001A03d00002000sv000015D9sd00001B95* ID_MODEL_FROM_DATABASE=ASPEED Graphics Family (H12SSL-i (AST2500 BMC)) @@ -85061,6 +85070,9 @@ pci:v00001F0Fd00003504sv00001F0Fsd00000001* pci:v00001F0Fd00003504sv00001F0Fsd00000002* ID_MODEL_FROM_DATABASE=M18305 Family BASE-T (S2025XT, 2x 10GbE, Base-T, PCIe 4.0 x8) +pci:v00001F0Fd00003504sv00001F0Fsd00000003* + ID_MODEL_FROM_DATABASE=M18305 Family BASE-T (S2045XT, 4x 10GbE, Base-T, PCIe 4.0 x8) + pci:v00001F0Fd0000350A* ID_MODEL_FROM_DATABASE=M18305 Family Virtual Function @@ -85196,6 +85208,9 @@ pci:v00001F31d0000451B* pci:v00001F31d00004622* ID_MODEL_FROM_DATABASE=NEM-PAC NVMe SSD (DRAM-less) +pci:v00001F32* + ID_VENDOR_FROM_DATABASE=Wuhan YuXin Semiconductor Co., Ltd. + pci:v00001F3F* ID_VENDOR_FROM_DATABASE=3SNIC Ltd @@ -87275,6 +87290,9 @@ pci:v00002106d00000001* pci:v00002106d00000001sv00002106sd00000001* ID_MODEL_FROM_DATABASE=HL100 Accelerator Controller (HLC100 Accelerator Card) +pci:v00002108* + ID_VENDOR_FROM_DATABASE=HuiLink Technologies (Xiamen) Co., Ltd. + pci:v00002116* ID_VENDOR_FROM_DATABASE=ZyDAS Technology Corp. diff --git a/hwdb.d/acpi_id_registry.csv b/hwdb.d/acpi_id_registry.csv index d4daa95c28997..df362829c59e3 100644 --- a/hwdb.d/acpi_id_registry.csv +++ b/hwdb.d/acpi_id_registry.csv @@ -147,4 +147,6 @@ IDEMIA,IDEM,06/26/2018 "KAYA N CO., LTD.",KAYA,01/06/2026 Mesiontech,MITH,01/30/2026 "Nexthop Systems Inc.",NXHP,02/23/2026 -"Megapolis-Telecom Region LLC",MPTR,02/23/2026 \ No newline at end of file +"Megapolis-Telecom Region LLC",MPTR,02/23/2026 +"Nanjing Tianyihexin Electronics Ltd",TYHX,02/27/2026 +"Theo End (Shenzhen) Computing Technology Co., Ltd.",LECA,02/27/2026 \ No newline at end of file diff --git a/hwdb.d/ma-large.txt b/hwdb.d/ma-large.txt index 50bcdf32fb476..e5b2503eb2ca3 100644 --- a/hwdb.d/ma-large.txt +++ b/hwdb.d/ma-large.txt @@ -22799,12 +22799,6 @@ D42751 (base 16) Infopia Co., Ltd Regensburg Bayern 93059 DE -F4-C6-D7 (hex) blackned GmbH -F4C6D7 (base 16) blackned GmbH - Am Hartholz 21 - Alling Bavaria 82239 - DE - 4C-CA-53 (hex) Skyera, Inc. 4CCA53 (base 16) Skyera, Inc. 1704 Automation Pkwy @@ -44366,11 +44360,17 @@ A401DE (base 16) SERCOMM PHILIPPINES INC Singapore 408533 SG -A8-ED-71 (hex) Analogue Enterprises Limited -A8ED71 (base 16) Analogue Enterprises Limited - 2-6 Foo Ming Street, 2J Po Ming Building - Causeway Bay 999077 - HK +0C-8D-DB (hex) Cisco Meraki +0C8DDB (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US + +CC-03-D9 (hex) Cisco Meraki +CC03D9 (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US 10-D6-57 (hex) Siemens Industrial Automation Products Ltd., Chengdu 10D657 (base 16) Siemens Industrial Automation Products Ltd., Chengdu @@ -44384,11 +44384,11 @@ A8ED71 (base 16) Analogue Enterprises Limited shenzhen guangdong 518057 CN -48-C3-81 (hex) TP-Link Systems Inc. -48C381 (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 - US +A8-ED-71 (hex) Analogue Enterprises Limited +A8ED71 (base 16) Analogue Enterprises Limited + 2-6 Foo Ming Street, 2J Po Ming Building + Causeway Bay 999077 + HK 0C-1C-31 (hex) MERCUSYS TECHNOLOGIES CO., LTD. 0C1C31 (base 16) MERCUSYS TECHNOLOGIES CO., LTD. @@ -44402,16 +44402,10 @@ A8ED71 (base 16) Analogue Enterprises Limited Shanghai 200233 CN -0C-8D-DB (hex) Cisco Meraki -0C8DDB (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 - US - -CC-03-D9 (hex) Cisco Meraki -CC03D9 (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 +48-C3-81 (hex) TP-Link Systems Inc. +48C381 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 US F0-40-EC (hex) RainX PTE. LTD. @@ -44462,6 +44456,18 @@ A8469D (base 16) Cisco Meraki San Francisco 94158 US +D4-24-DD (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +D424DD (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +BC-2E-C3 (hex) Shenzhen Tianruixiang Communication Equipment Co.,Ltd +BC2EC3 (base 16) Shenzhen Tianruixiang Communication Equipment Co.,Ltd + 12/F, Building B,Longhua Digital Innovation Center,Longhua District, Shenzhen,China. + Shenzhen Guangdong 518000 + CN + 38-70-F2 (hex) HUAWEI TECHNOLOGIES CO.,LTD 3870F2 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -44480,56 +44486,56 @@ C4BB03 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -BC-2E-C3 (hex) Shenzhen Tianruixiang Communication Equipment Co.,Ltd -BC2EC3 (base 16) Shenzhen Tianruixiang Communication Equipment Co.,Ltd - 12/F, Building B,Longhua Digital Innovation Center,Longhua District, Shenzhen,China. - Shenzhen Guangdong 518000 - CN - -D0-12-CB (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -D012CB (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +98-9B-CB (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +989BCB (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -D4-24-DD (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -D424DD (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +D0-12-CB (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +D012CB (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -08-9B-F1 (hex) eero inc. -089BF1 (base 16) eero inc. +9C-57-BC (hex) eero inc. +9C57BC (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -30-34-22 (hex) eero inc. -303422 (base 16) eero inc. +84-70-D7 (hex) eero inc. +8470D7 (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -9C-57-BC (hex) eero inc. -9C57BC (base 16) eero inc. +78-76-89 (hex) eero inc. +787689 (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -98-9B-CB (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -989BCB (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE +28-EC-22 (hex) eero inc. +28EC22 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US -84-70-D7 (hex) eero inc. -8470D7 (base 16) eero inc. +C8-C6-FE (hex) eero inc. +C8C6FE (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -78-76-89 (hex) eero inc. -787689 (base 16) eero inc. +08-9B-F1 (hex) eero inc. +089BF1 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +30-34-22 (hex) eero inc. +303422 (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US @@ -44648,41 +44654,17 @@ DC7CF7 (base 16) China Mobile Group Device Co.,Ltd. Beijing 100053 CN -28-EC-22 (hex) eero inc. -28EC22 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -C8-C6-FE (hex) eero inc. -C8C6FE (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -30-29-2B (hex) eero inc. -30292B (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - 14-08-08 (hex) Espressif Inc. 140808 (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area Shanghai Shanghai 201203 CN -8C-E4-DB (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd -8CE4DB (base 16) Shenzhen MiaoMing Intelligent Technology Co.,Ltd - 3F, Building 2, No. 1310, Kukeng Sightseeing Road, Kukeng Community, Guanlan Street, Longhua District - Shenzhen Guangdong 518110 - CN - -30-24-50 (hex) Hangzhou Huacheng Network Technology Co.,Ltd -302450 (base 16) Hangzhou Huacheng Network Technology Co.,Ltd - 13th Floor, Building 3, No. 582, Liye Road, Changhe Street, Binjiang District, Hangzhou, China (Zhejiang) Pilot Free Trade Zone - Hangzhou 311200 - CN +30-29-2B (hex) eero inc. +30292B (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US 34-09-C9 (hex) Dongguan Huayin Electronic Technology Co., Ltd. 3409C9 (base 16) Dongguan Huayin Electronic Technology Co., Ltd. @@ -44702,6 +44684,24 @@ BC9D37 (base 16) Telink Micro LLC Santa Clara 95054 US +8C-E4-DB (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd +8CE4DB (base 16) Shenzhen MiaoMing Intelligent Technology Co.,Ltd + 3F, Building 2, No. 1310, Kukeng Sightseeing Road, Kukeng Community, Guanlan Street, Longhua District + Shenzhen Guangdong 518110 + CN + +30-24-50 (hex) Hangzhou Huacheng Network Technology Co.,Ltd +302450 (base 16) Hangzhou Huacheng Network Technology Co.,Ltd + 13th Floor, Building 3, No. 582, Liye Road, Changhe Street, Binjiang District, Hangzhou, China (Zhejiang) Pilot Free Trade Zone + Hangzhou 311200 + CN + +94-8E-6D (hex) Nintendo Co.,Ltd +948E6D (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP + 0C-27-79 (hex) New H3C Technologies Co., Ltd 0C2779 (base 16) New H3C Technologies Co., Ltd 466 Changhe Road, Binjiang District @@ -44768,12 +44768,6 @@ B87029 (base 16) Shenzhen Ruiyuanchuangxin Technology Co.,Ltd. Shanghai Shanghai 201203 CN -94-8E-6D (hex) Nintendo Co.,Ltd -948E6D (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP - C8-08-8B (hex) Arista Networks C8088B (base 16) Arista Networks 5453 Great America Parkway @@ -44822,11 +44816,14 @@ E04934 (base 16) Calix Inc. Bac Ninh 16000 VN -58-E6-C5 (hex) Espressif Inc. -58E6C5 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +CC-BA-BD (hex) TP-Link Systems Inc. +CCBABD (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US + +5C-E7-53 (hex) Private +5CE753 (base 16) Private B4-5B-86 (hex) Funshion Online Technologies Co.,Ltd B45B86 (base 16) Funshion Online Technologies Co.,Ltd @@ -44858,11 +44855,17 @@ AC017A (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD Chengdu Sichuan 611330 CN -CC-BA-BD (hex) TP-Link Systems Inc. -CCBABD (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 - US +58-E6-C5 (hex) Espressif Inc. +58E6C5 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +08-73-6F (hex) EM Microelectronic +08736F (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH 78-0F-81 (hex) Cisco Meraki 780F81 (base 16) Cisco Meraki @@ -44870,9 +44873,6 @@ CCBABD (base 16) TP-Link Systems Inc. San Francisco 94158 US -5C-E7-53 (hex) Private -5CE753 (base 16) Private - B4-1F-4D (hex) Sony Interactive Entertainment Inc. B41F4D (base 16) Sony Interactive Entertainment Inc. 1-7-1 Konan @@ -44927,12 +44927,6 @@ A02B44 (base 16) WaveGo Tech LLC Cupertino CA 95014 US -08-73-6F (hex) EM Microelectronic -08736F (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH - C8-90-09 (hex) Budderfly Inc. C89009 (base 16) Budderfly Inc. 2 Trap Falls Road @@ -44945,36 +44939,60 @@ F8F7D2 (base 16) Equal Optics, LLC Newport Beach CA 92660 US +90-4C-02 (hex) vivo Mobile Communication Co., Ltd. +904C02 (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN + +04-1F-B8 (hex) vivo Mobile Communication Co., Ltd. +041FB8 (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN + A4-7B-52 (hex) JoulWatt Technology Co., Ltd A47B52 (base 16) JoulWatt Technology Co., Ltd 9th Floor, Chuangye Building, No.99 Huaxing Road, Xihu District, Hangzhou, China Hangzhou Zhejiang 311100 CN -30-C9-CC (hex) Samsung Electronics Co.,Ltd -30C9CC (base 16) Samsung Electronics Co.,Ltd - 129, Samsung-ro, Youngtongl-Gu - Suwon Gyeonggi-Do 16677 - KR - -3C-C5-C7 (hex) HUAWEI TECHNOLOGIES CO.,LTD -3CC5C7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +44-09-C6 (hex) HUAWEI TECHNOLOGIES CO.,LTD +4409C6 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -44-09-C6 (hex) HUAWEI TECHNOLOGIES CO.,LTD -4409C6 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +3C-C5-C7 (hex) HUAWEI TECHNOLOGIES CO.,LTD +3CC5C7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN +30-C9-CC (hex) Samsung Electronics Co.,Ltd +30C9CC (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 + KR + 04-55-B2 (hex) Huaqin Technology Co.,Ltd 0455B2 (base 16) Huaqin Technology Co.,Ltd Pudong New Area Shanghai 201203 CN +1C-D3-AF (hex) LG Innotek +1CD3AF (base 16) LG Innotek + 26, HANAMSANDAN 5BEON-RO + Gwangju Gwangsan-gu 506-731 + KR + +C8-26-E2 (hex) CHINA DRAGON TECHNOLOGY LIMITED +C826E2 (base 16) CHINA DRAGON TECHNOLOGY LIMITED + B4 Building,No.3 First industrial Zone,Nanpu Road,Lao Community,Xinqian Street,Baoan District,Shenzhen,City + ShenZhen 518100 + CN + FC-4C-EA (hex) Dell Inc. FC4CEA (base 16) Dell Inc. One Dell Way @@ -44987,28 +45005,16 @@ FC4CEA (base 16) Dell Inc. Santa Clara CA 95054 US -1C-D3-AF (hex) LG Innotek -1CD3AF (base 16) LG Innotek - 26, HANAMSANDAN 5BEON-RO - Gwangju Gwangsan-gu 506-731 - KR - F4-4E-B4 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. F44EB4 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China Nanning Guangxi 530007 CN -90-4C-02 (hex) vivo Mobile Communication Co., Ltd. -904C02 (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 - CN - -04-1F-B8 (hex) vivo Mobile Communication Co., Ltd. -041FB8 (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 +F4-AB-5C (hex) Quectel Wireless Solutions Co.,Ltd. +F4AB5C (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 CN 80-AE-3C (hex) Taicang T&W Electronics @@ -45023,6 +45029,30 @@ F06FCE (base 16) Ruckus Wireless Sunnyvale CA 94089 US +A0-1B-9E (hex) Samsung Electronics Co.,Ltd +A01B9E (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +D8-71-54 (hex) Samsung Electronics Co.,Ltd +D87154 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +78-33-C6 (hex) Samsung Electronics Co.,Ltd +7833C6 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +2C-C1-F4 (hex) Nokia Solutions and Networks India Private Limited +2CC1F4 (base 16) Nokia Solutions and Networks India Private Limited + Plot 45, Fathima Nagar, Nemilicherry + Chennai Tamilnadu 600044 + IN + 34-FD-70 (hex) Intel Corporate 34FD70 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 @@ -45041,40 +45071,16 @@ B07C8E (base 16) Brother Industries, LTD. NAGOYA 4678561 JP -A0-1B-D6 (hex) Nautitech Mining Systems Pty. Ltd. -A01BD6 (base 16) Nautitech Mining Systems Pty. Ltd. - 3/9 Packard AvenueCastle Hill - CASTLE HILL 2154 - AU - -90-F0-05 (hex) Xi'an Molead Technology Co., Ltd -90F005 (base 16) Xi'an Molead Technology Co., Ltd - No.34 Fenghui South Road,High-Tech Zone - Xian Shaanxi 710065 +F0-7A-55 (hex) zte corporation +F07A55 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -A0-1B-9E (hex) Samsung Electronics Co.,Ltd -A01B9E (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -D8-71-54 (hex) Samsung Electronics Co.,Ltd -D87154 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -78-33-C6 (hex) Samsung Electronics Co.,Ltd -7833C6 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -C8-26-E2 (hex) CHINA DRAGON TECHNOLOGY LIMITED -C826E2 (base 16) CHINA DRAGON TECHNOLOGY LIMITED - B4 Building,No.3 First industrial Zone,Nanpu Road,Lao Community,Xinqian Street,Baoan District,Shenzhen,City - ShenZhen 518100 +D4-61-95 (hex) zte corporation +D46195 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN E0-D5-5D (hex) Intel Corporate @@ -45095,29 +45101,23 @@ A08527 (base 16) Intel Corporate Kulim Kedah 09000 MY -F0-7A-55 (hex) zte corporation -F07A55 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN - -D4-61-95 (hex) zte corporation -D46195 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +90-F0-05 (hex) Xi'an Molead Technology Co., Ltd +90F005 (base 16) Xi'an Molead Technology Co., Ltd + No.34 Fenghui South Road,High-Tech Zone + Xian Shaanxi 710065 CN -F4-AB-5C (hex) Quectel Wireless Solutions Co.,Ltd. -F4AB5C (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 - CN +A0-1B-D6 (hex) Nautitech Mining Systems Pty. Ltd. +A01BD6 (base 16) Nautitech Mining Systems Pty. Ltd. + 3/9 Packard AvenueCastle Hill + CASTLE HILL 2154 + AU -2C-C1-F4 (hex) Nokia Solutions and Networks India Private Limited -2CC1F4 (base 16) Nokia Solutions and Networks India Private Limited - Plot 45, Fathima Nagar, Nemilicherry - Chennai Tamilnadu 600044 - IN +60-73-C8 (hex) Voyetra Turtle Beach, Inc. +6073C8 (base 16) Voyetra Turtle Beach, Inc. + 15822 Bernardo Center Drive, Suite 105 + San Diego CA 92127 + US 5C-E1-A4 (hex) Pleneo 5CE1A4 (base 16) Pleneo @@ -45131,12 +45131,6 @@ FCE498 (base 16) IEEE Registration Authority Piscataway NJ 08554 US -60-73-C8 (hex) Voyetra Turtle Beach, Inc. -6073C8 (base 16) Voyetra Turtle Beach, Inc. - 15822 Bernardo Center Drive, Suite 105 - San Diego CA 92127 - US - 24-B5-B9 (hex) Motorola Mobility LLC, a Lenovo Company 24B5B9 (base 16) Motorola Mobility LLC, a Lenovo Company 222 West Merchandise Mart Plaza @@ -45179,6 +45173,12 @@ ECBB78 (base 16) Cisco Systems, Inc Rueil Malmaison Cedex hauts de seine 92848 FR +54-9B-24 (hex) Mellanox Technologies, Inc. +549B24 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + 50-62-45 (hex) Annapurna labs 506245 (base 16) Annapurna labs Matam Scientific Industries Center, Building 8.2 @@ -45218,18 +45218,6 @@ D4532A (base 16) Beijing Xiaomi Mobile Software Co., Ltd Beijing Beijing 100085 CN -F0-57-8D (hex) JetHome LLC -F0578D (base 16) JetHome LLC - Serebristy boulevard, 21, letter A, office 79-N - St. Petersburg 197341 - RU - -78-C8-84 (hex) Huawei Device Co., Ltd. -78C884 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - 98-7E-B5 (hex) Huawei Device Co., Ltd. 987EB5 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone @@ -45242,11 +45230,11 @@ F0578D (base 16) JetHome LLC Dongguan Guangdong 523808 CN -54-9B-24 (hex) Mellanox Technologies, Inc. -549B24 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US +78-C8-84 (hex) Huawei Device Co., Ltd. +78C884 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN 18-95-78 (hex) DENSO CORPORATION 189578 (base 16) DENSO CORPORATION @@ -45284,12 +45272,6 @@ F8D554 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. beijing beijing 100000 CN -00-50-CA (hex) Zhone Technologies, Inc. -0050CA (base 16) Zhone Technologies, Inc. - 680 CENTRAL AVENUE - STE. #301 - DOVER NH 03820 - US - 4C-82-0C (hex) Apple, Inc. 4C820C (base 16) Apple, Inc. 1 Infinite Loop @@ -45308,34 +45290,22 @@ F8D554 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. Cupertino CA 95014 US -F4-06-3C (hex) Texas Instruments -F4063C (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US - -E0-DE-F2 (hex) Texas Instruments -E0DEF2 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US - 74-95-33 (hex) Westala Technologies Inc. 749533 (base 16) Westala Technologies Inc. 3333 Preston Road STE 300 FRISCO TX 75034 US -44-8D-D5 (hex) Cisco Systems, Inc -448DD5 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +F0-57-8D (hex) JetHome LLC +F0578D (base 16) JetHome LLC + Serebristy boulevard, 21, letter A, office 79-N + St. Petersburg 197341 + RU -8C-91-A4 (hex) Apple, Inc. -8C91A4 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +00-50-CA (hex) Zhone Technologies, Inc. +0050CA (base 16) Zhone Technologies, Inc. + 680 CENTRAL AVENUE - STE. #301 + DOVER NH 03820 US 94-97-4F (hex) Liteon Technology Corporation @@ -45356,11 +45326,17 @@ E0DEF2 (base 16) Texas Instruments Irvine CA 92618 US -F4-D7-E4 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. -F4D7E4 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. - No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. - Chongqing China 401120 - CN +E0-DE-F2 (hex) Texas Instruments +E0DEF2 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +44-8D-D5 (hex) Cisco Systems, Inc +448DD5 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US 20-0A-87 (hex) Guangzhou On-Bright Electronics Co., Ltd. 200A87 (base 16) Guangzhou On-Bright Electronics Co., Ltd. @@ -45368,12 +45344,6 @@ F4D7E4 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. Guangzhou Guangdong 510520 CN -BC-34-D6 (hex) Extreme Networks Headquarters -BC34D6 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US - E0-8C-FE (hex) Espressif Inc. E08CFE (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area @@ -45386,6 +45356,24 @@ E08CFE (base 16) Espressif Inc. KYOTO KYOTO 601-8501 JP +F4-06-3C (hex) Texas Instruments +F4063C (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +8C-91-A4 (hex) Apple, Inc. +8C91A4 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +18-16-28 (hex) SharkNinja Operating LLC +181628 (base 16) SharkNinja Operating LLC + 89 A Street, Suite 100 02494 Needham + Needham MA 02494 + US + 4C-C5-D9 (hex) Dell Inc. 4CC5D9 (base 16) Dell Inc. One Dell Way @@ -45404,6 +45392,18 @@ E08CFE (base 16) Espressif Inc. Beijing Beijing 100085 CN +BC-34-D6 (hex) Extreme Networks Headquarters +BC34D6 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US + +CC-E4-D1 (hex) Arista Networks +CCE4D1 (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 + US + E8-7E-EF (hex) Xiaomi Communications Co Ltd E87EEF (base 16) Xiaomi Communications Co Ltd #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road @@ -45416,17 +45416,11 @@ E87EEF (base 16) Xiaomi Communications Co Ltd Beijing Haidian District 100085 CN -18-16-28 (hex) SharkNinja Operating LLC -181628 (base 16) SharkNinja Operating LLC - 89 A Street, Suite 100 02494 Needham - Needham MA 02494 - US - -CC-E4-D1 (hex) Arista Networks -CCE4D1 (base 16) Arista Networks - 5453 Great America Parkway - Santa Clara CA 95054 - US +F4-D7-E4 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. +F4D7E4 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. + No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. + Chongqing China 401120 + CN 0C-9A-E6 (hex) SZ DJI TECHNOLOGY CO.,LTD 0C9AE6 (base 16) SZ DJI TECHNOLOGY CO.,LTD @@ -45440,12 +45434,6 @@ CCE4D1 (base 16) Arista Networks Shanghai Shanghai 201203 CN -C0-40-8D (hex) ALPSALPINE CO,.LTD -C0408D (base 16) ALPSALPINE CO,.LTD - nishida 6-1 - Kakuda-City Miyagi-Pref 981-1595 - JP - BC-09-B9 (hex) Hui Zhou Gaoshengda Technology Co.,LTD BC09B9 (base 16) Hui Zhou Gaoshengda Technology Co.,LTD No.2,Jin-da Road,Huinan Industrial Park @@ -45476,23 +45464,17 @@ FC8B1F (base 16) GUTOR Electronic Dongguan 523808 CN -24-FA-D4 (hex) ShenZhen More Star Technology Co.,LTD -24FAD4 (base 16) ShenZhen More Star Technology Co.,LTD - Room 301, 3F, Building 25, Keyuan West, No. 5, Kezhi West Road, Yuehai street - Shenzhen GuangDong 518000 - CN - F4-B0-FF (hex) Shanghai Baud Data Communication Co.,Ltd. F4B0FF (base 16) Shanghai Baud Data Communication Co.,Ltd. NO.123 JULI RD PUDONG ZHANGJIANG HIGH-TECH PARK SHANGHAI 201203 CN -10-2B-1C (hex) Motorola Mobility LLC, a Lenovo Company -102B1C (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 - US +C0-40-8D (hex) ALPSALPINE CO,.LTD +C0408D (base 16) ALPSALPINE CO,.LTD + nishida 6-1 + Kakuda-City Miyagi-Pref 981-1595 + JP 04-C8-B0 (hex) Google, Inc. 04C8B0 (base 16) Google, Inc. @@ -45506,24 +45488,18 @@ D86DD0 (base 16) InnoCare Optoelectronics Tainan 74144 TW -EC-46-84 (hex) Microsoft Corporation -EC4684 (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 - US - -D4-A7-EA (hex) Solar76 -D4A7EA (base 16) Solar76 - 400 Maple Street - Commerce TX 75428 - US - C4-D4-D0 (hex) SHENZHEN TECNO TECHNOLOGY C4D4D0 (base 16) SHENZHEN TECNO TECHNOLOGY 101,Building 24,Waijing Industrial Park,Fumin Community,Fucheng Street,Longhua District,Shenzhen City,P.R.China Shenzhen guangdong 518000 CN +24-FA-D4 (hex) ShenZhen More Star Technology Co.,LTD +24FAD4 (base 16) ShenZhen More Star Technology Co.,LTD + Room 301, 3F, Building 25, Keyuan West, No. 5, Kezhi West Road, Yuehai street + Shenzhen GuangDong 518000 + CN + 64-F2-FB (hex) Hangzhou Ezviz Software Co.,Ltd. 64F2FB (base 16) Hangzhou Ezviz Software Co.,Ltd. 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District @@ -45536,11 +45512,11 @@ C4D4D0 (base 16) SHENZHEN TECNO TECHNOLOGY Hangzhou Zhejiang 310051 CN -68-E6-D4 (hex) FURUNO SYSTEMS CO.,LTD. -68E6D4 (base 16) FURUNO SYSTEMS CO.,LTD. - 6F, JEI Ryogoku Building, 3-25-5 Ryogoku - Sumida-ku Tokyo 130-0026 - JP +10-2B-1C (hex) Motorola Mobility LLC, a Lenovo Company +102B1C (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US DC-D8-3B (hex) Cisco Systems, Inc DCD83B (base 16) Cisco Systems, Inc @@ -45548,30 +45524,54 @@ DCD83B (base 16) Cisco Systems, Inc San Jose CA 94568 US -C8-6C-9A (hex) SNUC System -C86C9A (base 16) SNUC System - 495 Round Rock West Drive - Round Rock TX 78681 - US - 90-FE-E2 (hex) ISIF 90FEE2 (base 16) ISIF Lasnamäe linnaosa, Sepapaja tn 6 Tallinn Harju maakond 15551 EE +EC-46-84 (hex) Microsoft Corporation +EC4684 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US + +D4-A7-EA (hex) Solar76 +D4A7EA (base 16) Solar76 + 400 Maple Street + Commerce TX 75428 + US + 6C-43-29 (hex) COSMIQ EDUSNAP PRIVATE LIMITED 6C4329 (base 16) COSMIQ EDUSNAP PRIVATE LIMITED C-185, 2nd Floor, Naraina Industrial Area, Phase 1 NEW DELHI DELHI 110028 IN +68-E6-D4 (hex) FURUNO SYSTEMS CO.,LTD. +68E6D4 (base 16) FURUNO SYSTEMS CO.,LTD. + 6F, JEI Ryogoku Building, 3-25-5 Ryogoku + Sumida-ku Tokyo 130-0026 + JP + +C8-6C-9A (hex) SNUC System +C86C9A (base 16) SNUC System + 495 Round Rock West Drive + Round Rock TX 78681 + US + 00-0E-72 (hex) Sesami Technologies Srl 000E72 (base 16) Sesami Technologies Srl via Statale 17 Bollengo Torino 10012 IT +44-39-AA (hex) G.Tech Technology Ltd. +4439AA (base 16) G.Tech Technology Ltd. + No.8,Jinyuan 1st Road,Tangjiawan Town, High-tech Zone + Zhuhai Guangdong 519085 + CN + 58-27-45 (hex) Angelbird Technologies GmbH 582745 (base 16) Angelbird Technologies GmbH Steinebach 18 @@ -45584,34 +45584,40 @@ C86C9A (base 16) SNUC System Suzhou 215021 CN +30-F6-5D (hex) Hewlett Packard Enterprise +30F65D (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US + F0-3E-05 (hex) Murata Manufacturing Co., Ltd. F03E05 (base 16) Murata Manufacturing Co., Ltd. 1-10-1, Higashikotari Nagaokakyo-shi Kyoto 617-8555 JP +64-FA-2B (hex) Sagemcom Broadband SAS +64FA2B (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR + B0-A6-04 (hex) Espressif Inc. B0A604 (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area Shanghai Shanghai 201203 CN -44-39-AA (hex) G.Tech Technology Ltd. -4439AA (base 16) G.Tech Technology Ltd. - No.8,Jinyuan 1st Road,Tangjiawan Town, High-tech Zone - Zhuhai Guangdong 519085 - CN - C0-2E-DF (hex) AltoBeam Inc. C02EDF (base 16) AltoBeam Inc. B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian Beijing Beijing 100083 CN -8C-3B-4A (hex) Universal Global Scientific Industrial., Ltd -8C3B4A (base 16) Universal Global Scientific Industrial., Ltd - 141, Lane 351, Taiping Road, Sec.1,Tsao Tuen - Nan-Tou Taiwan 54261 +70-3E-76 (hex) Arcadyan Corporation +703E76 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 TW F4-5C-42 (hex) Huawei Device Co., Ltd. @@ -45638,6 +45644,18 @@ E4AEE4 (base 16) Tuya Smart Inc. Mannheim 68167 DE +80-48-63 (hex) Electralsys Networks +804863 (base 16) Electralsys Networks + 45 Bharat Nagar, New Friends Colony + NEW DELHI DELHI 110025 + IN + +7C-CF-4E (hex) FINE TRIUMPH TECHNOLOGY CORP.,LTD. +7CCF4E (base 16) FINE TRIUMPH TECHNOLOGY CORP.,LTD. + kihitech Industrial Park,DongChen Town, RuGao,jiangsu + RuGao,jiangsu 226571 + CN + 70-F3-95 (hex) Universal Global Scientific Industrial., Ltd 70F395 (base 16) Universal Global Scientific Industrial., Ltd 141, LANE 351,SEC.1, TAIPING RD. @@ -45656,23 +45674,23 @@ E02A82 (base 16) Universal Global Scientific Industrial., Ltd Nan-Tou Taiwan 54261 TW -30-F6-5D (hex) Hewlett Packard Enterprise -30F65D (base 16) Hewlett Packard Enterprise - 6280 America Center Dr - San Jose CA 95002 - US +8C-3B-4A (hex) Universal Global Scientific Industrial., Ltd +8C3B4A (base 16) Universal Global Scientific Industrial., Ltd + 141, Lane 351, Taiping Road, Sec.1,Tsao Tuen + Nan-Tou Taiwan 54261 + TW -64-FA-2B (hex) Sagemcom Broadband SAS -64FA2B (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +8C-19-52 (hex) Amazon Technologies Inc. +8C1952 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US -70-3E-76 (hex) Arcadyan Corporation -703E76 (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW +04-72-EF (hex) Apple, Inc. +0472EF (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US D4-FF-1A (hex) Apple, Inc. D4FF1A (base 16) Apple, Inc. @@ -45698,12 +45716,6 @@ F4B599 (base 16) Apple, Inc. Cupertino CA 95014 US -24-6D-10 (hex) Apple, Inc. -246D10 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - A0-F7-C3 (hex) Ficosa Automotive SLU A0F7C3 (base 16) Ficosa Automotive SLU Pol. Ind. Can Mitjans,Vial San Jordi s/n @@ -45716,18 +45728,6 @@ B8FBB3 (base 16) TP-Link Systems Inc. Irvine CA 92618 US -20-46-3A (hex) Apple, Inc. -20463A (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -04-72-EF (hex) Apple, Inc. -0472EF (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - 50-93-CE (hex) HUAWEI TECHNOLOGIES CO.,LTD 5093CE (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -45740,24 +45740,24 @@ B8FBB3 (base 16) TP-Link Systems Inc. Dongguan 523808 CN -80-48-63 (hex) Electralsys Networks -804863 (base 16) Electralsys Networks - 45 Bharat Nagar, New Friends Colony - NEW DELHI DELHI 110025 - IN - -7C-CF-4E (hex) FINE TRIUMPH TECHNOLOGY CORP.,LTD. -7CCF4E (base 16) FINE TRIUMPH TECHNOLOGY CORP.,LTD. - kihitech Industrial Park,DongChen Town, RuGao,jiangsu - RuGao,jiangsu 226571 - CN +24-6D-10 (hex) Apple, Inc. +246D10 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -8C-19-52 (hex) Amazon Technologies Inc. -8C1952 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 +20-46-3A (hex) Apple, Inc. +20463A (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US +10-E8-A7 (hex) WNC Corporation +10E8A7 (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + AC-91-9B (hex) WNC Corporation AC919B (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park @@ -45782,12 +45782,24 @@ DC4BA1 (base 16) WNC Corporation Hsin-Chu R.O.C. 308 TW -74-6F-F7 (hex) WNC Corporation -746FF7 (base 16) WNC Corporation +B0-00-73 (hex) WNC Corporation +B00073 (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + +98-49-14 (hex) WNC Corporation +984914 (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park Hsin-Chu R.O.C. 308 TW +30-41-DB (hex) vivo Mobile Communication Co., Ltd. +3041DB (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN + A8-54-B2 (hex) WNC Corporation A854B2 (base 16) WNC Corporation 20 Park Avenue II, Hsin Science Park, Hsinchu 308, Taiwan @@ -45800,29 +45812,23 @@ A854B2 (base 16) WNC Corporation Hsinchu 308 TW -B0-00-73 (hex) WNC Corporation -B00073 (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW - -98-49-14 (hex) WNC Corporation -984914 (base 16) WNC Corporation +74-6F-F7 (hex) WNC Corporation +746FF7 (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park Hsin-Chu R.O.C. 308 TW -10-E8-A7 (hex) WNC Corporation -10E8A7 (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW +2C-65-8D (hex) Cisco Systems, Inc +2C658D (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -30-41-DB (hex) vivo Mobile Communication Co., Ltd. -3041DB (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 - CN +94-AA-07 (hex) Nokia +94AA07 (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA 68-A3-4F (hex) Nokia 68A34F (base 16) Nokia @@ -45836,17 +45842,17 @@ B00073 (base 16) WNC Corporation Nanzi Dist. Kaohsiung 811643 TW -EC-79-C0 (hex) zte corporation -EC79C0 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN +20-CB-CC (hex) GridVisibility, inc. +20CBCC (base 16) GridVisibility, inc. + 1502 Meeker Dr + Longmont CO 80504 + US -6C-11-BA (hex) zte corporation -6C11BA (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN +F4-9A-B1 (hex) Hewlett Packard Enterprise +F49AB1 (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US FC-9E-53 (hex) Intel Corporate FC9E53 (base 16) Intel Corporate @@ -45860,6 +45866,30 @@ D494A9 (base 16) Intel Corporate Kulim Kedah 09000 MY +84-08-3A (hex) Intel Corporate +84083A (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +EC-79-C0 (hex) zte corporation +EC79C0 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +6C-11-BA (hex) zte corporation +6C11BA (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +10-16-B1 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +1016B1 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN + E4-65-66 (hex) Maple IoT Solutions LLC E46566 (base 16) Maple IoT Solutions LLC N8408 MUIRFIELD WAY @@ -45872,41 +45902,47 @@ E46566 (base 16) Maple IoT Solutions LLC Palo Alto CA 94301 US -2C-65-8D (hex) Cisco Systems, Inc -2C658D (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -94-AA-07 (hex) Nokia -94AA07 (base 16) Nokia - 600 March Road - Kanata Ontario K2K 2E6 - CA +40-08-77 (hex) Xiaomi Communications Co Ltd +400877 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN -84-08-3A (hex) Intel Corporate -84083A (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +08-B3-39 (hex) Xiaomi Communications Co Ltd +08B339 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN -10-16-B1 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -1016B1 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 +B8-53-84 (hex) Xiaomi Communications Co Ltd +B85384 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 CN -20-CB-CC (hex) GridVisibility, inc. -20CBCC (base 16) GridVisibility, inc. - 1502 Meeker Dr - Longmont CO 80504 +CC-2D-D2 (hex) Ruckus Wireless +CC2DD2 (base 16) Ruckus Wireless + 350 West Java Drive + Sunnyvale CA 94089 US -F4-9A-B1 (hex) Hewlett Packard Enterprise -F49AB1 (base 16) Hewlett Packard Enterprise - 6280 America Center Dr - San Jose CA 95002 - US +B0-D7-DE (hex) Hangzhou Linovision Co., Ltd. +B0D7DE (base 16) Hangzhou Linovision Co., Ltd. + No. 181 Wuchang Road + Hangzhou Zhejiang 310023 + CN + +98-42-AB (hex) GN Hearing A/S +9842AB (base 16) GN Hearing A/S + Lautrupbjerg 7 + Ballerup 2750 + DK + +5C-33-B1 (hex) EM Microelectronic +5C33B1 (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH E0-FA-5B (hex) Arista Networks E0FA5B (base 16) Arista Networks @@ -45920,10 +45956,10 @@ E0FA5B (base 16) Arista Networks Piscataway NJ 08554 US -40-08-77 (hex) Xiaomi Communications Co Ltd -400877 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +9C-CD-42 (hex) Dongguan Liesheng Electronic Co., Ltd. +9CCD42 (base 16) Dongguan Liesheng Electronic Co., Ltd. + F5, Building B, North Block, Gaosheng Tech Park, No. 84 Zhongli Road, Nancheng District, Dongguan Ci + dongguan guangdong 523000 CN 7C-D4-A8 (hex) Sagemcom Broadband SAS @@ -45938,78 +45974,18 @@ E0FA5B (base 16) Arista Networks SHENZHEN Guangdong Province 518052 CN -CC-2D-D2 (hex) Ruckus Wireless -CC2DD2 (base 16) Ruckus Wireless - 350 West Java Drive - Sunnyvale CA 94089 - US - -B0-D7-DE (hex) Hangzhou Linovision Co., Ltd. -B0D7DE (base 16) Hangzhou Linovision Co., Ltd. - No. 181 Wuchang Road - Hangzhou Zhejiang 310023 - CN - 18-AC-C2 (hex) TCL King Electrical Appliances(Huizhou)Co.,Ltd 18ACC2 (base 16) TCL King Electrical Appliances(Huizhou)Co.,Ltd B Area, 10th floor, TCL multimedia Building, TCL International E City, #1001 Zhonshanyuan road,Shenzhen guangdong China 518058 CN -08-B3-39 (hex) Xiaomi Communications Co Ltd -08B339 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN - -B8-53-84 (hex) Xiaomi Communications Co Ltd -B85384 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN - 00-BC-99 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. 00BC99 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. No.555 Qianmo Road Hangzhou Zhejiang 310052 CN -5C-33-B1 (hex) EM Microelectronic -5C33B1 (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH - -00-15-EA (hex) Hensoldt South Africa (Pty) Ltd -0015EA (base 16) Hensoldt South Africa (Pty) Ltd - 64/74 White Road - Cape Town Western Province 7945 - ZA - -98-42-AB (hex) GN Hearing A/S -9842AB (base 16) GN Hearing A/S - Lautrupbjerg 7 - Ballerup 2750 - DK - -9C-CD-42 (hex) Dongguan Liesheng Electronic Co., Ltd. -9CCD42 (base 16) Dongguan Liesheng Electronic Co., Ltd. - F5, Building B, North Block, Gaosheng Tech Park, No. 84 Zhongli Road, Nancheng District, Dongguan Ci - dongguan guangdong 523000 - CN - -70-4B-CA (hex) Espressif Inc. -704BCA (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -8C-FD-49 (hex) Espressif Inc. -8CFD49 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 4C-3C-8F (hex) Shenzhen Jingxun Technology Co., Ltd. 4C3C8F (base 16) Shenzhen Jingxun Technology Co., Ltd. 3/F, A5 Building, Zhiyuan Community, No. 1001, Xueyuan Road, Nanshan District @@ -46028,17 +46004,11 @@ BCD767 (base 16) BAE Systems Sunnyvale CA 94085 US -C4-3D-C7 (hex) NETGEAR -C43DC7 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US - -4C-60-DE (hex) NETGEAR -4C60DE (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US +00-15-EA (hex) Hensoldt South Africa (Pty) Ltd +0015EA (base 16) Hensoldt South Africa (Pty) Ltd + 64/74 White Road + Cape Town Western Province 7945 + ZA F8-10-37 (hex) ENTOUCH Controls F81037 (base 16) ENTOUCH Controls @@ -46052,16 +46022,16 @@ F81037 (base 16) ENTOUCH Controls Piscataway NJ 08554 US -80-8F-97 (hex) Xiaomi Communications Co Ltd -808F97 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +70-4B-CA (hex) Espressif Inc. +704BCA (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 CN -4C-E2-0F (hex) Xiaomi Communications Co Ltd -4CE20F (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +8C-FD-49 (hex) Espressif Inc. +8CFD49 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 CN 10-0C-6B (hex) NETGEAR @@ -46082,30 +46052,12 @@ F81037 (base 16) ENTOUCH Controls San Jose CA 95134 US -30-91-8F (hex) Vantiva Technologies Belgium -30918F (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE - -E0-B9-E5 (hex) Vantiva Technologies Belgium -E0B9E5 (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE - 44-FB-76 (hex) vivo Mobile Communication Co., Ltd. 44FB76 (base 16) vivo Mobile Communication Co., Ltd. No.1, vivo Road, Chang'an Dongguan Guangdong 523860 CN -A0-55-2E (hex) zte corporation -A0552E (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN - B0-7F-B9 (hex) NETGEAR B07FB9 (base 16) NETGEAR 3553 N. First Street @@ -46118,11 +46070,17 @@ B07FB9 (base 16) NETGEAR San Jose CA 95134 US -9C-97-26 (hex) Vantiva Technologies Belgium -9C9726 (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE +C4-3D-C7 (hex) NETGEAR +C43DC7 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US + +4C-60-DE (hex) NETGEAR +4C60DE (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US 08-BD-43 (hex) NETGEAR 08BD43 (base 16) NETGEAR @@ -46154,12 +46112,42 @@ C05724 (base 16) Honor Device Co., Ltd. Milan IT20126 IT +80-8F-97 (hex) Xiaomi Communications Co Ltd +808F97 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + +4C-E2-0F (hex) Xiaomi Communications Co Ltd +4CE20F (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + C4-CD-50 (hex) Shenzhen C-Data Technology Co., Ltd. C4CD50 (base 16) Shenzhen C-Data Technology Co., Ltd. #201, Building A4, Nanshan Zhiyuan, No.1001, Xueyuan Avenue, Changyuan Community,Taoyuan,Nanshan Shenzhen Guangdong 518055 CN +9C-97-26 (hex) Vantiva Technologies Belgium +9C9726 (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE + +30-91-8F (hex) Vantiva Technologies Belgium +30918F (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE + +E0-B9-E5 (hex) Vantiva Technologies Belgium +E0B9E5 (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE + AC-8A-C7 (hex) HUAWEI TECHNOLOGIES CO.,LTD AC8AC7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -46172,12 +46160,6 @@ AC8AC7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -DC-04-5A (hex) Nanjing Qinheng Microelectronics Co., Ltd. -DC045A (base 16) Nanjing Qinheng Microelectronics Co., Ltd. - No.18, Ningshuang Road - Nanjing Jiangsu 210012 - CN - 24-DB-94 (hex) Juniper Networks 24DB94 (base 16) Juniper Networks 1133 Innovation Way @@ -46190,11 +46172,11 @@ DC045A (base 16) Nanjing Qinheng Microelectronics Co., Ltd. Sunnyvale CA 94089 US -8C-77-79 (hex) Arcadyan Corporation -8C7779 (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW +A0-55-2E (hex) zte corporation +A0552E (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN 54-AE-BC (hex) CHINA DRAGON TECHNOLOGY LIMITED 54AEBC (base 16) CHINA DRAGON TECHNOLOGY LIMITED @@ -46232,17 +46214,17 @@ C8806D (base 16) Apple, Inc. Cupertino CA 95014 US -98-CF-7D (hex) Apple, Inc. -98CF7D (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +DC-04-5A (hex) Nanjing Qinheng Microelectronics Co., Ltd. +DC045A (base 16) Nanjing Qinheng Microelectronics Co., Ltd. + No.18, Ningshuang Road + Nanjing Jiangsu 210012 + CN -74-29-59 (hex) Apple, Inc. -742959 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +8C-77-79 (hex) Arcadyan Corporation +8C7779 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW 04-C9-DE (hex) Qingdao HaierTechnology Co.,Ltd 04C9DE (base 16) Qingdao HaierTechnology Co.,Ltd @@ -46256,17 +46238,17 @@ C8806D (base 16) Apple, Inc. Redmond WA 98052 US -80-B8-2A (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. -80B82A (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. - No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. - Chongqing China 401120 - CN +98-CF-7D (hex) Apple, Inc. +98CF7D (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -44-25-38 (hex) WNC Corporation -442538 (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW +74-29-59 (hex) Apple, Inc. +742959 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US E8-6E-3E (hex) Sichuan Tianyi Comheart Telecom Co.,LTD E86E3E (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD @@ -46280,6 +46262,12 @@ D8D7F3 (base 16) New H3C Technologies Co., Ltd Hangzhou Zhejiang 310052 CN +44-25-38 (hex) WNC Corporation +442538 (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + 1C-CF-82 (hex) Palo Alto Networks 1CCF82 (base 16) Palo Alto Networks 3000 Tannery Way @@ -46292,42 +46280,18 @@ B0435D (base 16) MechoShade Vista CA 92081 US -9C-04-B6 (hex) Quectel Wireless Solutions Co.,Ltd. -9C04B6 (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 +80-B8-2A (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. +80B82A (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. + No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. + Chongqing China 401120 CN -34-55-E5 (hex) SJIT Co., Ltd. -3455E5 (base 16) SJIT Co., Ltd. - 54-33 Dongtanhana 1-gil - Hwaseong-si Gyeonggi-do 18423 - KR - -BC-AA-82 (hex) Fiberhome Telecommunication Technologies Co.,LTD -BCAA82 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 +18-5C-A1 (hex) Jiangxi Risound Electronics Co.,LTD +185CA1 (base 16) Jiangxi Risound Electronics Co.,LTD + No 271,innovation Avenue, Jinggangshan economic and Technological Development Zone + Ji'an Jiangxi 343100 CN -C8-CC-21 (hex) eero inc. -C8CC21 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -B8-F4-A4 (hex) Google, Inc. -B8F4A4 (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 - US - -E0-1A-DF (hex) Google, Inc. -E01ADF (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 - US - 3C-2A-B3 (hex) Telesystem communications Pte Ltd 3C2AB3 (base 16) Telesystem communications Pte Ltd 3F, No.7 Xing Hua Rd., @@ -46346,34 +46310,52 @@ F85B1B (base 16) Espressif Inc. Shanghai Shanghai 201203 CN +9C-04-B6 (hex) Quectel Wireless Solutions Co.,Ltd. +9C04B6 (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 + CN + +34-55-E5 (hex) SJIT Co., Ltd. +3455E5 (base 16) SJIT Co., Ltd. + 54-33 Dongtanhana 1-gil + Hwaseong-si Gyeonggi-do 18423 + KR + 4C-D7-C8 (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. 4CD7C8 (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. 13/F, Building 1, No.13 Bohua 4th Road, Huangpu District Guangzhou Guangdong 510663 CN -18-5C-A1 (hex) Jiangxi Risound Electronics Co.,LTD -185CA1 (base 16) Jiangxi Risound Electronics Co.,LTD - No 271,innovation Avenue, Jinggangshan economic and Technological Development Zone - Ji'an Jiangxi 343100 +BC-AA-82 (hex) Fiberhome Telecommunication Technologies Co.,LTD +BCAA82 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 CN -C8-91-43 (hex) Nintendo Co.,Ltd -C89143 (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP - 44-93-8D (hex) Innolux Corporation 44938D (base 16) Innolux Corporation No. 160, Kexue Rd., Zhunan Township Miaoli County 35053 TW -70-AD-43 (hex) Blink by Amazon -70AD43 (base 16) Blink by Amazon - 100 Riverpark Drive - North Reading MA 01864 +C8-CC-21 (hex) eero inc. +C8CC21 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +B8-F4-A4 (hex) Google, Inc. +B8F4A4 (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US + +E0-1A-DF (hex) Google, Inc. +E01ADF (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 US 70-3A-8C (hex) Shenzhen Skyworth Digital Technology CO., Ltd @@ -46388,12 +46370,6 @@ C89143 (base 16) Nintendo Co.,Ltd Werkendam 4251 LT NL -88-5E-54 (hex) Samsung Electronics Co.,Ltd -885E54 (base 16) Samsung Electronics Co.,Ltd - 129, Samsung-ro, Youngtongl-Gu - Suwon Gyeonggi-Do 16677 - KR - D0-98-B1 (hex) GScoolink Microelectronics (Beijing) Co.,LTD D098B1 (base 16) GScoolink Microelectronics (Beijing) Co.,LTD Room 101, 3rd Floor, Building 23, No. 8 Dongbeiwang West Road, Haidian District @@ -46412,6 +46388,12 @@ D098B1 (base 16) GScoolink Microelectronics (Beijing) Co.,LTD Dongguan 523808 CN +C8-91-43 (hex) Nintendo Co.,Ltd +C89143 (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP + C8-AF-F0 (hex) CDVI Wireless SpA C8AFF0 (base 16) CDVI Wireless SpA via Piave 23 @@ -46442,29 +46424,11 @@ E4FAE4 (base 16) Shenzhen SDMC Technology CP,.LTD Gumi Gyeongbuk 730-350 KR -B8-1E-61 (hex) TELLESCOM INDUSTRIA E COMERCIO EM TELECOMUNICACAO -B81E61 (base 16) TELLESCOM INDUSTRIA E COMERCIO EM TELECOMUNICACAO - Av. Buriti, 1900 – Setor B – Distrito Industrial - Manaus Amazonas 69075-000 - BR - -40-6E-0F (hex) SKYASTAR TECHNOLOGLES(ZHUHAI) LTD -406E0F (base 16) SKYASTAR TECHNOLOGLES(ZHUHAI) LTD - 3F, 5# Building, Maker Town, Jinwan, Zhuhai, Guangdong, 519090 China - ZHUHAI Guangdong 519090 - CN - -EC-B5-0A (hex) Quectel Wireless Solutions Co.,Ltd. -ECB50A (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 - CN - -38-E0-54 (hex) Security Design, Inc. -38E054 (base 16) Security Design, Inc. - Nishiki-cho Trad Square 4F, 3-20 Kanda Nishiki-cho - Chiyoda-ku Tokyo 101-0054 - JP +88-5E-54 (hex) Samsung Electronics Co.,Ltd +885E54 (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 + KR 8C-A3-EC (hex) Samsung Electronics Co.,Ltd 8CA3EC (base 16) Samsung Electronics Co.,Ltd @@ -46496,6 +46460,18 @@ AC3AE2 (base 16) NVIDIA Corporation Santa Clara CA 95050 US +70-AD-43 (hex) Blink by Amazon +70AD43 (base 16) Blink by Amazon + 100 Riverpark Drive + North Reading MA 01864 + US + +D4-00-CA (hex) AUMOVIO Systems Romania S.R.L. +D400CA (base 16) AUMOVIO Systems Romania S.R.L. + Str. Salzburg Nr. 8, 550018 + Sibiu Sibiu 550018 + RO + 40-85-56 (hex) AUMOVIO Technologies Romania S.R.L. 408556 (base 16) AUMOVIO Technologies Romania S.R.L. Str Siemens no.1, 300701 Timisoara, Romania @@ -46520,6 +46496,42 @@ D494FB (base 16) AUMOVIO Systems, Inc. Deer Park IL 60010 US +B8-1E-61 (hex) TELLESCOM INDUSTRIA E COMERCIO EM TELECOMUNICACAO +B81E61 (base 16) TELLESCOM INDUSTRIA E COMERCIO EM TELECOMUNICACAO + Av. Buriti, 1900 – Setor B – Distrito Industrial + Manaus Amazonas 69075-000 + BR + +40-6E-0F (hex) SKYASTAR TECHNOLOGLES(ZHUHAI) LTD +406E0F (base 16) SKYASTAR TECHNOLOGLES(ZHUHAI) LTD + 3F, 5# Building, Maker Town, Jinwan, Zhuhai, Guangdong, 519090 China + ZHUHAI Guangdong 519090 + CN + +EC-B5-0A (hex) Quectel Wireless Solutions Co.,Ltd. +ECB50A (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 + CN + +38-E0-54 (hex) Security Design, Inc. +38E054 (base 16) Security Design, Inc. + Nishiki-cho Trad Square 4F, 3-20 Kanda Nishiki-cho + Chiyoda-ku Tokyo 101-0054 + JP + +44-7C-AC (hex) Invictus-AV +447CAC (base 16) Invictus-AV + 17650 Hillcrest Drive + Meadow Vista CA 95722 + US + +6C-D5-52 (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD +6CD552 (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD + NO.268, Fuqian Rd, Jutang community, Guanlan Town, Longhua New district + shenzhen guangdong 518000 + CN + 44-20-63 (hex) AUMOVIO Germany GmbH 442063 (base 16) AUMOVIO Germany GmbH Siemensstr. 12 @@ -46532,24 +46544,6 @@ E41E33 (base 16) AUMOVIO Germany GmbH Villingen-Schwenningen 78052 DE -6C-D5-52 (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD -6CD552 (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD - NO.268, Fuqian Rd, Jutang community, Guanlan Town, Longhua New district - shenzhen guangdong 518000 - CN - -D4-00-CA (hex) AUMOVIO Systems Romania S.R.L. -D400CA (base 16) AUMOVIO Systems Romania S.R.L. - Str. Salzburg Nr. 8, 550018 - Sibiu Sibiu 550018 - RO - -44-7C-AC (hex) Invictus-AV -447CAC (base 16) Invictus-AV - 17650 Hillcrest Drive - Meadow Vista CA 95722 - US - 00-02-DC (hex) GENERAL Inc. 0002DC (base 16) GENERAL Inc. 3-3-17,Suenaga,Takatsu-ku @@ -46586,24 +46580,6 @@ D02C39 (base 16) Cisco Systems, Inc San Jose CA 94568 US -1C-FF-3F (hex) Cust2mate -1CFF3F (base 16) Cust2mate - 4 Ariel Sharon St - Givatayim 5320047 - IL - -74-83-A0 (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd -7483A0 (base 16) Shenzhen MiaoMing Intelligent Technology Co.,Ltd - 3F, Building 2, No. 1310, Kukeng Sightseeing Road, Kukeng Community, Guanlan Street, Longhua District - Shenzhen Guangdong 518110 - CN - -18-69-45 (hex) TP-Link Systems Inc. -186945 (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 - US - 48-76-96 (hex) Huaan Zhongyun Co., Ltd. 487696 (base 16) Huaan Zhongyun Co., Ltd. Room 201, 2nd Floor, Building A, No. 128 Qiming Road, Yinzhou District, Ningbo City @@ -46616,17 +46592,11 @@ D02C39 (base 16) Cisco Systems, Inc Dongguan 523808 CN -20-4B-2E (hex) Pizzato Elettrica S.r.l. -204B2E (base 16) Pizzato Elettrica S.r.l. - Via Torino, 1 - Marostica VI 36063 - IT - -50-61-7E (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd -50617E (base 16) Shenzhen MiaoMing Intelligent Technology Co.,Ltd - 3F, Building 2, No. 1310, Kukeng Sightseeing Road, Kukeng Community, Guanlan Street, Longhua District - Shenzhen Guangdong 518110 - CN +18-69-45 (hex) TP-Link Systems Inc. +186945 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US 80-BF-21 (hex) vivo Mobile Communication Co., Ltd. 80BF21 (base 16) vivo Mobile Communication Co., Ltd. @@ -46646,6 +46616,36 @@ D0B324 (base 16) Apple, Inc. Cupertino CA 95014 US +1C-FF-3F (hex) Cust2mate +1CFF3F (base 16) Cust2mate + 4 Ariel Sharon St + Givatayim 5320047 + IL + +74-83-A0 (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd +7483A0 (base 16) Shenzhen MiaoMing Intelligent Technology Co.,Ltd + 3F, Building 2, No. 1310, Kukeng Sightseeing Road, Kukeng Community, Guanlan Street, Longhua District + Shenzhen Guangdong 518110 + CN + +60-25-ED (hex) Hewlett Packard Enterprise +6025ED (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US + +20-4B-2E (hex) Pizzato Elettrica S.r.l. +204B2E (base 16) Pizzato Elettrica S.r.l. + Via Torino, 1 + Marostica VI 36063 + IT + +50-61-7E (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd +50617E (base 16) Shenzhen MiaoMing Intelligent Technology Co.,Ltd + 3F, Building 2, No. 1310, Kukeng Sightseeing Road, Kukeng Community, Guanlan Street, Longhua District + Shenzhen Guangdong 518110 + CN + 2C-79-BE (hex) TP-LINK TECHNOLOGIES CO.,LTD. 2C79BE (base 16) TP-LINK TECHNOLOGIES CO.,LTD. Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan @@ -46658,47 +46658,23 @@ D0B324 (base 16) Apple, Inc. Santa Clara CA 95054 US -74-DC-13 (hex) Telink Micro LLC -74DC13 (base 16) Telink Micro LLC - 2975 Scott Blvd #120 - Santa Clara 95054 - US - -60-25-ED (hex) Hewlett Packard Enterprise -6025ED (base 16) Hewlett Packard Enterprise - 6280 America Center Dr - San Jose CA 95002 - US - -00-21-04 (hex) Gigaset Technologies GmbH -002104 (base 16) Gigaset Technologies GmbH - Frankenstrasse 2 - 46395 Bocholt - DE - -AC-D2-0C (hex) Chengdu SingCore Technology Co.,Ltd. -ACD20C (base 16) Chengdu SingCore Technology Co.,Ltd. - Room 4, 16th Floor, Building 10, No. 399 West Fucheng Avenue, Chengdu High-Tech Zone, China (Sichuan) Pilot Free Trade Zone,Chengdu, Sichuan Province, China. - Chengdu Sichuan 610041 - CN - 04-64-FA (hex) Dell Inc. 0464FA (base 16) Dell Inc. One Dell Way Round Rock TX 78682 US -8C-37-B7 (hex) Hosin Global Electronics Co.,Ltd -8C37B7 (base 16) Hosin Global Electronics Co.,Ltd - Rm 2501, Bldg 2, Shenzhen Next Generation Industrial Park, No.136 Zhongkang Rd, Futian Dist - Shenzhen 518000 +68-78-A8 (hex) Fiberhome Telecommunication Technologies Co.,LTD +6878A8 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 CN -F0-6D-93 (hex) EM Microelectronic -F06D93 (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH +74-DC-13 (hex) Telink Micro LLC +74DC13 (base 16) Telink Micro LLC + 2975 Scott Blvd #120 + Santa Clara 95054 + US B8-1E-0B (hex) Extreme Networks Headquarters B81E0B (base 16) Extreme Networks Headquarters @@ -46706,6 +46682,12 @@ B81E0B (base 16) Extreme Networks Headquarters Morrisville NC 27560 US +AC-D2-0C (hex) Chengdu SingCore Technology Co.,Ltd. +ACD20C (base 16) Chengdu SingCore Technology Co.,Ltd. + Room 4, 16th Floor, Building 10, No. 399 West Fucheng Avenue, Chengdu High-Tech Zone, China (Sichuan) Pilot Free Trade Zone,Chengdu, Sichuan Province, China. + Chengdu Sichuan 610041 + CN + 8C-94-DF (hex) Espressif Inc. 8C94DF (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area @@ -46724,12 +46706,48 @@ B81E0B (base 16) Extreme Networks Headquarters Guangzhou 510540 CN -68-78-A8 (hex) Fiberhome Telecommunication Technologies Co.,LTD -6878A8 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 - CN - +00-21-04 (hex) Gigaset Technologies GmbH +002104 (base 16) Gigaset Technologies GmbH + Frankenstrasse 2 + 46395 Bocholt + DE + +8C-37-B7 (hex) Hosin Global Electronics Co.,Ltd +8C37B7 (base 16) Hosin Global Electronics Co.,Ltd + Rm 2501, Bldg 2, Shenzhen Next Generation Industrial Park, No.136 Zhongkang Rd, Futian Dist + Shenzhen 518000 + CN + +F0-6D-93 (hex) EM Microelectronic +F06D93 (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH + +0C-C9-8A (hex) Intel Corporate +0CC98A (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +EC-F3-3C (hex) Intel Corporate +ECF33C (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +40-EC-BD (hex) Intel Corporate +40ECBD (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +6C-10-41 (hex) Shenzhen Skyworth Digital Technology CO., Ltd +6C1041 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, + Shenzhen Guangdong 518057 + CN + 90-0A-75 (hex) New H3C Technologies Co., Ltd 900A75 (base 16) New H3C Technologies Co., Ltd 466 Changhe Road, Binjiang District @@ -46742,6 +46760,12 @@ B81E0B (base 16) Extreme Networks Headquarters Dongguan Guangdong 523808 CN +8C-8C-29 (hex) Espressif Inc. +8C8C29 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + E4-06-E0 (hex) HUAWEI TECHNOLOGIES CO.,LTD E406E0 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -46760,14 +46784,20 @@ DCB43F (base 16) eero inc. San Francisco CA 94107 US -6C-10-41 (hex) Shenzhen Skyworth Digital Technology CO., Ltd -6C1041 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd - 4F,Block A, Skyworth?Building, - Shenzhen Guangdong 518057 +14-90-7A (hex) Beijing Xiaomi Mobile Software Co., Ltd +14907A (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN -8C-8C-29 (hex) Espressif Inc. -8C8C29 (base 16) Espressif Inc. +1C-8F-57 (hex) Espressif Inc. +1C8F57 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +10-BD-A3 (hex) Espressif Inc. +10BDA3 (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area Shanghai Shanghai 201203 CN @@ -46802,48 +46832,6 @@ C05BBD (base 16) HUAWEI TECHNOLOGIES CO.,LTD Chengdu Sichuan 611330 CN -EC-F3-3C (hex) Intel Corporate -ECF33C (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -40-EC-BD (hex) Intel Corporate -40ECBD (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -0C-C9-8A (hex) Intel Corporate -0CC98A (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -14-90-7A (hex) Beijing Xiaomi Mobile Software Co., Ltd -14907A (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 - CN - -1C-8F-57 (hex) Espressif Inc. -1C8F57 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -94-AE-E3 (hex) Belden Hirschmann industries (Suzhou) Limited -94AEE3 (base 16) Belden Hirschmann industries (Suzhou) Limited - 333 Yanhu Road, Huaqiao Town - Kunshan Jiangsu 215332 - CN - -94-10-5A (hex) Dell Inc. -94105A (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US - 44-83-46 (hex) Texas Instruments 448346 (base 16) Texas Instruments 12500 TI Blvd @@ -46868,17 +46856,17 @@ DCDEE3 (base 16) Texas Instruments ShenZhen 518100 CN -10-BD-A3 (hex) Espressif Inc. -10BDA3 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +94-AE-E3 (hex) Belden Hirschmann industries (Suzhou) Limited +94AEE3 (base 16) Belden Hirschmann industries (Suzhou) Limited + 333 Yanhu Road, Huaqiao Town + Kunshan Jiangsu 215332 CN -E4-72-9D (hex) Nokia Shanghai Bell Co., Ltd. -E4729D (base 16) Nokia Shanghai Bell Co., Ltd. - No.388 Ning Qiao Road,Jin Qiao Pudong Shanghai - Shanghai 201206 - CN +94-10-5A (hex) Dell Inc. +94105A (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 + US 7C-CF-0F (hex) LCFC(Hefei) Electronics Technology co., ltd 7CCF0F (base 16) LCFC(Hefei) Electronics Technology co., ltd @@ -46910,10 +46898,10 @@ A02605 (base 16) Belden Hirschmann industries (Suzhou) Limited Suzhou Jiangsu 215332 CN -C0-2F-CD (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -C02FCD (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 +E4-72-9D (hex) Nokia Shanghai Bell Co., Ltd. +E4729D (base 16) Nokia Shanghai Bell Co., Ltd. + No.388 Ning Qiao Road,Jin Qiao Pudong Shanghai + Shanghai 201206 CN F8-84-75 (hex) i5LED, LLC @@ -46922,11 +46910,29 @@ F88475 (base 16) i5LED, LLC Sacramento CA 95827 US -44-9F-79 (hex) onsemi -449F79 (base 16) onsemi - 5701 N Pima Rd - Scottsdale AZ 85250 - US +C0-2F-CD (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +C02FCD (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN + +04-DB-D9 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +04DBD9 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN + +54-01-4A (hex) Guangzhou Shiyuan Electronic Technology Company Limited +54014A (base 16) Guangzhou Shiyuan Electronic Technology Company Limited + No.6, 4th Yunpu Road, Yunpu industry District + Guangzhou Guangdong 510530 + CN + +FC-8F-A4 (hex) NXP Semiconductors Taiwan Ltd. +FC8FA4 (base 16) NXP Semiconductors Taiwan Ltd. + No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan + Nanzi Dist. Kaohsiung 811643 + TW A4-61-77 (hex) AMOSENSE A46177 (base 16) AMOSENSE @@ -46937,35 +46943,23 @@ A46177 (base 16) AMOSENSE 58-DF-70 (hex) Private 58DF70 (base 16) Private -54-01-4A (hex) Guangzhou Shiyuan Electronic Technology Company Limited -54014A (base 16) Guangzhou Shiyuan Electronic Technology Company Limited - No.6, 4th Yunpu Road, Yunpu industry District - Guangzhou Guangdong 510530 - CN - 50-EE-9B (hex) AltoBeam Inc. 50EE9B (base 16) AltoBeam Inc. B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian Beijing Beijing 100083 CN -EC-73-F6 (hex) Sichuan AI-Link Technology Co., Ltd. -EC73F6 (base 16) Sichuan AI-Link Technology Co., Ltd. - Anzhou, Industrial Park - Mianyang Sichuan 622650 - CN - -04-DB-D9 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -04DBD9 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN +90-DF-06 (hex) Ciena Corporation +90DF06 (base 16) Ciena Corporation + 7035 Ridge Road + Hanover MD 21076 + US -FC-8F-A4 (hex) NXP Semiconductors Taiwan Ltd. -FC8FA4 (base 16) NXP Semiconductors Taiwan Ltd. - No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan - Nanzi Dist. Kaohsiung 811643 - TW +44-9F-79 (hex) onsemi +449F79 (base 16) onsemi + 5701 N Pima Rd + Scottsdale AZ 85250 + US 2C-DE-F5 (hex) TVS REGZA Corporation 2CDEF5 (base 16) TVS REGZA Corporation @@ -46973,17 +46967,11 @@ FC8FA4 (base 16) NXP Semiconductors Taiwan Ltd. Kawasaki-shi Kanagawa 2120013 JP -90-DF-06 (hex) Ciena Corporation -90DF06 (base 16) Ciena Corporation - 7035 Ridge Road - Hanover MD 21076 - US - -50-EE-87 (hex) HPRO -50EE87 (base 16) HPRO - 8500 Balboa Blvd - Northridge CA 91329 - US +EC-73-F6 (hex) Sichuan AI-Link Technology Co., Ltd. +EC73F6 (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou, Industrial Park + Mianyang Sichuan 622650 + CN 00-26-89 (hex) General Dynamics Land Systems Inc. 002689 (base 16) General Dynamics Land Systems Inc. @@ -46997,12 +46985,90 @@ FC8FA4 (base 16) NXP Semiconductors Taiwan Ltd. Cupertino CA 95014 US +50-EE-87 (hex) HPRO +50EE87 (base 16) HPRO + 8500 Balboa Blvd + Northridge CA 91329 + US + +10-C1-97 (hex) Xiaomi Communications Co Ltd +10C197 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + +3C-B9-22 (hex) HUAWEI TECHNOLOGIES CO.,LTD +3CB922 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +AC-45-B0 (hex) Shenzhen Jidao Technology Co Ltd +AC45B0 (base 16) Shenzhen Jidao Technology Co Ltd + Room 605, Building 1, Saiba Technology Building, No. 16, North Keji Er Road + Shenzhen Guangdong 518057 + CN + +70-70-D5 (hex) HUAWEI TECHNOLOGIES CO.,LTD +7070D5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +60-53-55 (hex) HUAWEI TECHNOLOGIES CO.,LTD +605355 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +B0-2B-64 (hex) Cisco Systems, Inc +B02B64 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +10-E6-76 (hex) Cisco Systems, Inc +10E676 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + FC-50-0C (hex) Sitehop Ltd FC500C (base 16) Sitehop Ltd 9 South Street Sheffield South Yorkshire S2 5QX GB +58-2A-BD (hex) Espressif Inc. +582ABD (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +C8-C8-73 (hex) CHIPSEN INC. +C8C873 (base 16) CHIPSEN INC. + 501, Gwangmyeong M-cluster 17, Deogan-ro 104beon-gil + Gwangmyeong-si Gyeonggi-do 14353 + KR + +D4-9F-F9 (hex) Earda Technologies co Ltd +D49FF9 (base 16) Earda Technologies co Ltd + Block A,Lianfeng Creative Park, #2 Jisheng Rd., Nansha District + Guangzhou Guangdong 511455 + CN + +90-0E-84 (hex) eero inc. +900E84 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +F4-C6-D7 (hex) blackned GmbH +F4C6D7 (base 16) blackned GmbH + Zugspitzstrasse 1 + Bavaria Heimertingen 87751 + DE + 00-01-30 (hex) Extreme Networks Headquarters 000130 (base 16) Extreme Networks Headquarters 2121 RDU Center Drive @@ -77855,12 +77921,6 @@ CC0080 (base 16) BETTINI SRL Oslo NO-0216 NO -00-13-AE (hex) Radiance Technologies, Inc. -0013AE (base 16) Radiance Technologies, Inc. - 350 Wynn Dr. - Huntsville Alabama 35805 - US - 00-13-44 (hex) Fargo Electronics Inc. 001344 (base 16) Fargo Electronics Inc. 6533 Flying Cloud Drive @@ -90836,6 +90896,18 @@ A4AD9E (base 16) NEXAIOT Shenzhen Guangdong 518057 CN +94-EF-50 (hex) TP-Link Systems Inc. +94EF50 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US + +90-3F-C3 (hex) Huawei Device Co., Ltd. +903FC3 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + A8-0C-CA (hex) Shenzhen Sundray Technologies company Limited A80CCA (base 16) Shenzhen Sundray Technologies company Limited 6th Floor,Block A1, Nanshan iPark, No.1001 XueYuan Road, Nanshan District @@ -90854,24 +90926,12 @@ A80CCA (base 16) Shenzhen Sundray Technologies company Limited Shenzhen Guangdong 518057 CN -90-3F-C3 (hex) Huawei Device Co., Ltd. -903FC3 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - C4-49-3E (hex) Motorola Mobility LLC, a Lenovo Company C4493E (base 16) Motorola Mobility LLC, a Lenovo Company 222 West Merchandise Mart Plaza Chicago IL 60654 US -94-EF-50 (hex) TP-Link Systems Inc. -94EF50 (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 - US - FC-A2-DF (hex) IEEE Registration Authority FCA2DF (base 16) IEEE Registration Authority 445 Hoes Lane @@ -90902,78 +90962,60 @@ CC6200 (base 16) Honor Device Co., Ltd. Dongguan 523808 CN -7C-C8-82 (hex) HUAWEI TECHNOLOGIES CO.,LTD -7CC882 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +18-CE-DF (hex) Quectel Wireless Solutions Co.,Ltd. +18CEDF (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 CN -3C-A6-2F (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -3CA62F (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +00-33-7A (hex) Tuya Smart Inc. +00337A (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 + US + +C0-54-4D (hex) Nokia Shanghai Bell Co., Ltd. +C0544D (base 16) Nokia Shanghai Bell Co., Ltd. + No.388 Ning Qiao Road,Jin Qiao Pudong Shanghai + Shanghai 201206 + CN + +CC-CE-1E (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +CCCE1E (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -3C-37-12 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -3C3712 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +7C-FF-4D (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +7CFF4D (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -0C-72-74 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -0C7274 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +3C-A6-2F (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +3CA62F (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -04-B4-FE (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -04B4FE (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +3C-37-12 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +3C3712 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -C0-54-4D (hex) Nokia Shanghai Bell Co., Ltd. -C0544D (base 16) Nokia Shanghai Bell Co., Ltd. - No.388 Ning Qiao Road,Jin Qiao Pudong Shanghai - Shanghai 201206 - CN - -CC-CE-1E (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -CCCE1E (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +0C-72-74 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +0C7274 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -7C-FF-4D (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -7CFF4D (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +04-B4-FE (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +04B4FE (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -60-57-7D (hex) eero inc. -60577D (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -AC-EC-85 (hex) eero inc. -ACEC85 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -0C-1C-1A (hex) eero inc. -0C1C1A (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -64-C2-69 (hex) eero inc. -64C269 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - 74-B6-B6 (hex) eero inc. 74B6B6 (base 16) eero inc. 660 3rd Street @@ -91010,18 +91052,12 @@ F8BBBF (base 16) eero inc. San Francisco CA 94107 US -18-CE-DF (hex) Quectel Wireless Solutions Co.,Ltd. -18CEDF (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 +7C-C8-82 (hex) HUAWEI TECHNOLOGIES CO.,LTD +7CC882 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -00-33-7A (hex) Tuya Smart Inc. -00337A (base 16) Tuya Smart Inc. - 160 Greentree Drive, Suite 101 - Dover DE 19904 - US - 48-1F-66 (hex) China Mobile Group Device Co.,Ltd. 481F66 (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District @@ -91082,6 +91118,30 @@ DC152D (base 16) China Mobile Group Device Co.,Ltd. Beijing 100053 CN +60-57-7D (hex) eero inc. +60577D (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +AC-EC-85 (hex) eero inc. +ACEC85 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +0C-1C-1A (hex) eero inc. +0C1C1A (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +64-C2-69 (hex) eero inc. +64C269 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + 70-93-C1 (hex) eero inc. 7093C1 (base 16) eero inc. 660 3rd Street @@ -91106,12 +91166,6 @@ DC152D (base 16) China Mobile Group Device Co.,Ltd. San Francisco CA 94107 US -54-92-6A (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -54926A (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 - CN - 0C-93-A5 (hex) eero inc. 0C93A5 (base 16) eero inc. 660 3rd Street @@ -91148,18 +91202,18 @@ B4B9E6 (base 16) eero inc. San Francisco CA 94107 US -D8-3E-EF (hex) COOSEA GROUP (HK) COMPANY LIMITED -D83EEF (base 16) COOSEA GROUP (HK) COMPANY LIMITED - Unit 56 16F Multifield Plaza,37A Part Avenue.Tsim Sha Tsui,KL,Hong Kong,SAR CHINA - Hong Kong 999077 - CN - 70-7D-A1 (hex) Sagemcom Broadband SAS 707DA1 (base 16) Sagemcom Broadband SAS 250, route de l'Empereur Rueil Malmaison Cedex hauts de seine 92848 FR +D8-3E-EF (hex) COOSEA GROUP (HK) COMPANY LIMITED +D83EEF (base 16) COOSEA GROUP (HK) COMPANY LIMITED + Unit 56 16F Multifield Plaza,37A Part Avenue.Tsim Sha Tsui,KL,Hong Kong,SAR CHINA + Hong Kong 999077 + CN + 04-58-5D (hex) IEEE Registration Authority 04585D (base 16) IEEE Registration Authority 445 Hoes Lane @@ -91178,11 +91232,11 @@ C4864F (base 16) Beijing BitIntelligence Information Technology Co. Ltd. Beijing Beijing 100080 CN -E0-E6-E3 (hex) TeamF1 Networks Pvt Limited -E0E6E3 (base 16) TeamF1 Networks Pvt Limited - Ascendas IT Park, Capella Block, Floor #2, Plot No: 17, Software Units Layout, Madhapur - Hyderabad Telangana 500081 - IN +54-92-6A (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +54926A (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 + CN C8-7F-2B (hex) INGRAM MICRO SERVICES C87F2B (base 16) INGRAM MICRO SERVICES @@ -91214,6 +91268,12 @@ C87F2B (base 16) INGRAM MICRO SERVICES San Francisco 94158 US +E0-E6-E3 (hex) TeamF1 Networks Pvt Limited +E0E6E3 (base 16) TeamF1 Networks Pvt Limited + Ascendas IT Park, Capella Block, Floor #2, Plot No: 17, Software Units Layout, Madhapur + Hyderabad Telangana 500081 + IN + 34-FA-1C (hex) Beijing Xiaomi Mobile Software Co., Ltd 34FA1C (base 16) Beijing Xiaomi Mobile Software Co., Ltd The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District @@ -91226,42 +91286,18 @@ C87F2B (base 16) INGRAM MICRO SERVICES Beijing 100029 CN -44-35-B9 (hex) NetComm Wireless Pty Ltd -4435B9 (base 16) NetComm Wireless Pty Ltd - Level 1, 18-20 Orion Road - Sydney NSW 2066 - AU - 4C-CF-7C (hex) HP Inc. 4CCF7C (base 16) HP Inc. 10300 Energy Dr Spring TX 77389 US -DC-BB-3D (hex) Extreme Networks Headquarters -DCBB3D (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville 27560 - US - 20-3A-0C (hex) eero inc. 203A0C (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -FC-B2-14 (hex) Apple, Inc. -FCB214 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -2C-95-20 (hex) Apple, Inc. -2C9520 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - 00-15-FF (hex) Inseego Wireless, Inc 0015FF (base 16) Inseego Wireless, Inc 9710 Scranton Rd., Suite 200 @@ -91304,30 +91340,30 @@ FC9F2A (base 16) Zyxel Communications Corporation Hsichu Taiwan 300 TW +44-35-B9 (hex) NetComm Wireless Pty Ltd +4435B9 (base 16) NetComm Wireless Pty Ltd + Level 1, 18-20 Orion Road + Sydney NSW 2066 + AU + 64-75-DA (hex) Arcadyan Corporation 6475DA (base 16) Arcadyan Corporation No.8, Sec.2, Guangfu Rd. Hsinchu City Hsinchu 30071 TW +DC-BB-3D (hex) Extreme Networks Headquarters +DCBB3D (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville 27560 + US + B0-CC-CE (hex) IEEE Registration Authority B0CCCE (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US -DC-B4-D9 (hex) Espressif Inc. -DCB4D9 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -98-32-68 (hex) Silicon Laboratories -983268 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US - B8-9C-13 (hex) Alps Alpine B89C13 (base 16) Alps Alpine 20-1, Yoshima Industrial Park @@ -91358,22 +91394,34 @@ A81F79 (base 16) Yingling Innovations Pte. Ltd. Midview 573970 SG +FC-B2-14 (hex) Apple, Inc. +FCB214 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +2C-95-20 (hex) Apple, Inc. +2C9520 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + 80-23-95 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH 802395 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -04-8F-00 (hex) Rong-Paisa Electronics Co., Ltd. -048F00 (base 16) Rong-Paisa Electronics Co., Ltd. - Carrera 43f #14A-112 - Medellin Antioquia 050021 - CO +98-32-68 (hex) Silicon Laboratories +983268 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US -8C-5C-53 (hex) AltoBeam Inc. -8C5C53 (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 +DC-B4-D9 (hex) Espressif Inc. +DCB4D9 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 CN EC-7C-BA (hex) Hewlett Packard Enterprise @@ -91388,17 +91436,17 @@ EC7CBA (base 16) Hewlett Packard Enterprise Beckwith Knowle Harrogate HG3 1UF GB -50-2E-91 (hex) AzureWave Technology Inc. -502E91 (base 16) AzureWave Technology Inc. - 8F., No. 94, Baozhong Rd. - New Taipei City Taiwan 231 - TW +04-8F-00 (hex) Rong-Paisa Electronics Co., Ltd. +048F00 (base 16) Rong-Paisa Electronics Co., Ltd. + Carrera 43f #14A-112 + Medellin Antioquia 050021 + CO -E4-9F-7D (hex) Samsung Electronics Co.,Ltd -E49F7D (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +8C-5C-53 (hex) AltoBeam Inc. +8C5C53 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN E8-BF-E1 (hex) Intel Corporate E8BFE1 (base 16) Intel Corporate @@ -91442,6 +91490,12 @@ B43A96 (base 16) Arista Networks Fitzroy Victoria 3065 AU +E4-9F-7D (hex) Samsung Electronics Co.,Ltd +E49F7D (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + 60-98-49 (hex) Nokia Solutions and Networks India Private Limited 609849 (base 16) Nokia Solutions and Networks India Private Limited Radiance Ivy terrace, Block 4, 9R, Egattur, Chennai @@ -91454,6 +91508,12 @@ B43A96 (base 16) Arista Networks Chennai TamilNadu 600130 IN +50-2E-91 (hex) AzureWave Technology Inc. +502E91 (base 16) AzureWave Technology Inc. + 8F., No. 94, Baozhong Rd. + New Taipei City Taiwan 231 + TW + 68-F7-D8 (hex) Microsoft Corporation 68F7D8 (base 16) Microsoft Corporation One Microsoft Way @@ -91472,10 +91532,10 @@ C0CDD6 (base 16) Espressif Inc. Shanghai Shanghai 201203 CN -8C-7A-B3 (hex) Guangzhou Shiyuan Electronic Technology Company Limited -8C7AB3 (base 16) Guangzhou Shiyuan Electronic Technology Company Limited - No.6, 4th Yunpu Road, Yunpu industry District - Guangzhou Guangdong 510530 +88-B9-51 (hex) Xiaomi Communications Co Ltd +88B951 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 CN E8-CD-15 (hex) Vantiva USA LLC @@ -91496,10 +91556,10 @@ E8CD15 (base 16) Vantiva USA LLC Shanghai Shanghai 201203 CN -88-B9-51 (hex) Xiaomi Communications Co Ltd -88B951 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +8C-7A-B3 (hex) Guangzhou Shiyuan Electronic Technology Company Limited +8C7AB3 (base 16) Guangzhou Shiyuan Electronic Technology Company Limited + No.6, 4th Yunpu Road, Yunpu industry District + Guangzhou Guangdong 510530 CN 74-24-CA (hex) Guangzhou Shiyuan Electronic Technology Company Limited @@ -91514,6 +91574,12 @@ E8CD15 (base 16) Vantiva USA LLC Sunnyvale CA 94089 US +EC-31-11 (hex) Sichuan AI-Link Technology Co., Ltd. +EC3111 (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou, Industrial Park + Mianyang Sichuan 622650 + CN + 00-6A-5E (hex) IEEE Registration Authority 006A5E (base 16) IEEE Registration Authority 445 Hoes Lane @@ -91538,12 +91604,6 @@ FC50D6 (base 16) Huawei Device Co., Ltd. Dongguan Guangdong 523808 CN -EC-31-11 (hex) Sichuan AI-Link Technology Co., Ltd. -EC3111 (base 16) Sichuan AI-Link Technology Co., Ltd. - Anzhou, Industrial Park - Mianyang Sichuan 622650 - CN - D0-B1-CA (hex) Shenzhen Skyworth Digital Technology CO., Ltd D0B1CA (base 16) Shenzhen Skyworth Digital Technology CO., Ltd 4F,Block A, Skyworth?Building, @@ -91562,6 +91622,30 @@ D801EB (base 16) Infinity Electronics Ltd Stockholm SE-164 80 SE +28-35-3A (hex) HUAWEI TECHNOLOGIES CO.,LTD +28353A (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +60-30-B3 (hex) HUAWEI TECHNOLOGIES CO.,LTD +6030B3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +E8-7E-1C (hex) HUAWEI TECHNOLOGIES CO.,LTD +E87E1C (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +C0-15-1B (hex) Sony Interactive Entertainment Inc. +C0151B (base 16) Sony Interactive Entertainment Inc. + 1-7-1 Konan + Minato-ku Tokyo 108-0075 + JP + D0-68-27 (hex) eero inc. D06827 (base 16) eero inc. 660 3rd Street @@ -91580,18 +91664,6 @@ BC4529 (base 16) zte corporation shenzhen guangdong 518057 CN -E8-7E-1C (hex) HUAWEI TECHNOLOGIES CO.,LTD -E87E1C (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -C0-15-1B (hex) Sony Interactive Entertainment Inc. -C0151B (base 16) Sony Interactive Entertainment Inc. - 1-7-1 Konan - Minato-ku Tokyo 108-0075 - JP - 9C-65-EE (hex) Zhone Technologies, Inc. 9C65EE (base 16) Zhone Technologies, Inc. DASAN Tower 8F, 49 Daewangpangyo-ro644beon-gil Bundang-gu @@ -91604,24 +91676,24 @@ CC6C52 (base 16) Zhone Technologies, Inc. Plano TX 75024 US -28-35-3A (hex) HUAWEI TECHNOLOGIES CO.,LTD -28353A (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -60-30-B3 (hex) HUAWEI TECHNOLOGIES CO.,LTD -6030B3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - 8C-73-DA (hex) Silicon Laboratories 8C73DA (base 16) Silicon Laboratories 400 West Cesar Chavez Austin TX 78701 US +34-55-06 (hex) GUANGDONG GENIUS TECHNOLOGY CO., LTD. +345506 (base 16) GUANGDONG GENIUS TECHNOLOGY CO., LTD. + No.168, Middle Road Of East Gate + Xiaobian Community Chang'an Town 523851 + CN + +D4-7A-97 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. +D47A97 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. + No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. + Chongqing China 401120 + CN + D4-E9-F4 (hex) Espressif Inc. D4E9F4 (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area @@ -91634,6 +91706,12 @@ D4E9F4 (base 16) Espressif Inc. Shanghai Shanghai 201203 CN +6C-1A-EA (hex) Texas Instruments +6C1AEA (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + 68-44-06 (hex) Texas Instruments 684406 (base 16) Texas Instruments 12500 TI Blvd @@ -91664,23 +91742,17 @@ E4B16C (base 16) Apple, Inc. Cupertino CA 95014 US -BC-5A-34 (hex) New H3C Technologies Co., Ltd -BC5A34 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 - CN - 28-D5-B1 (hex) Apple, Inc. 28D5B1 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -80-D1-CE (hex) Apple, Inc. -80D1CE (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +BC-5A-34 (hex) New H3C Technologies Co., Ltd +BC5A34 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN 2C-CC-7A (hex) AltoBeam Inc. 2CCC7A (base 16) AltoBeam Inc. @@ -91688,18 +91760,12 @@ BC5A34 (base 16) New H3C Technologies Co., Ltd Beijing Beijing 100083 CN -6C-1A-EA (hex) Texas Instruments -6C1AEA (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 +80-D1-CE (hex) Apple, Inc. +80D1CE (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -34-55-06 (hex) GUANGDONG GENIUS TECHNOLOGY CO., LTD. -345506 (base 16) GUANGDONG GENIUS TECHNOLOGY CO., LTD. - No.168, Middle Road Of East Gate - Xiaobian Community Chang'an Town 523851 - CN - F0-68-E3 (hex) AzureWave Technology Inc. F068E3 (base 16) AzureWave Technology Inc. 8F., No. 94, Baozhong Rd. @@ -91712,24 +91778,6 @@ F068E3 (base 16) AzureWave Technology Inc. Cupertino CA 95014 US -14-D5-C6 (hex) slash dev slash agents, inc -14D5C6 (base 16) slash dev slash agents, inc - 334 Brannan St, Floor 2 - San Francisco CA 94107 - US - -D8-85-AC (hex) Espressif Inc. -D885AC (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -D4-7A-97 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. -D47A97 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. - No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. - Chongqing China 401120 - CN - 4C-8E-19 (hex) Xiaomi Communications Co Ltd 4C8E19 (base 16) Xiaomi Communications Co Ltd #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road @@ -91742,12 +91790,36 @@ D47A97 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. Shenzhen 518102 CN +D8-85-AC (hex) Espressif Inc. +D885AC (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +14-D5-C6 (hex) slash dev slash agents, inc +14D5C6 (base 16) slash dev slash agents, inc + 334 Brannan St, Floor 2 + San Francisco CA 94107 + US + 44-38-F3 (hex) EM Microelectronic 4438F3 (base 16) EM Microelectronic Rue des Sors 3 Marin-Epagnier Neuchatel 2074 CH +1C-D1-1A (hex) Fortinet, Inc. +1CD11A (base 16) Fortinet, Inc. + 899 Kifer Road + Sunnyvale 94086 + US + +50-51-4F (hex) Netbeam Technology Limited +50514F (base 16) Netbeam Technology Limited + Hudsun Chambers, P.O.Box 986, Road Town + Tortola VG1110 + VG + F8-D0-0E (hex) Vantiva USA LLC F8D00E (base 16) Vantiva USA LLC 4855 Peachtree Industrial Blvd, Suite 200 @@ -91766,18 +91838,6 @@ E4BD96 (base 16) Chengdu Hurray Data Technology co., Ltd. Chengdu 610000 CN -84-00-55 (hex) VusionGroup -840055 (base 16) VusionGroup - Kalsdorfer Straße 12 - Fernitz-Mellach Steiermark 8072 - AT - -14-B9-03 (hex) HUAWEI TECHNOLOGIES CO.,LTD -14B903 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - 18-69-0A (hex) Silicon Laboratories 18690A (base 16) Silicon Laboratories 400 West Cesar Chavez @@ -91796,22 +91856,16 @@ A46B40 (base 16) CHINA DRAGON TECHNOLOGY LIMITED Zhongshan Guangdong 528400 CN -1C-D1-1A (hex) Fortinet, Inc. -1CD11A (base 16) Fortinet, Inc. - 899 Kifer Road - Sunnyvale 94086 - US - -50-51-4F (hex) Netbeam Technology Limited -50514F (base 16) Netbeam Technology Limited - Hudsun Chambers, P.O.Box 986, Road Town - Tortola VG1110 - VG +84-00-55 (hex) VusionGroup +840055 (base 16) VusionGroup + Kalsdorfer Straße 12 + Fernitz-Mellach Steiermark 8072 + AT -60-C4-18 (hex) TPV Display Technology (Xiamen) Co.,Ltd. -60C418 (base 16) TPV Display Technology (Xiamen) Co.,Ltd. - No.1, Xianghai Road, Xiamen Torch Hi-Tech Industrial Development Zone - XM Fujian 361101 +14-B9-03 (hex) HUAWEI TECHNOLOGIES CO.,LTD +14B903 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN 50-0B-23 (hex) HUAWEI TECHNOLOGIES CO.,LTD @@ -91826,12 +91880,30 @@ C8B78A (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN +60-C4-18 (hex) TPV Display Technology (Xiamen) Co.,Ltd. +60C418 (base 16) TPV Display Technology (Xiamen) Co.,Ltd. + No.1, Xianghai Road, Xiamen Torch Hi-Tech Industrial Development Zone + XM Fujian 361101 + CN + +B0-2E-BA (hex) Earda Technologies co Ltd +B02EBA (base 16) Earda Technologies co Ltd + Block A,Lianfeng Creative Park, #2 Jisheng Rd., Nansha District + Guangzhou Guangdong 511455 + CN + 0C-3D-5E (hex) Nanjing Qinheng Microelectronics Co., Ltd. 0C3D5E (base 16) Nanjing Qinheng Microelectronics Co., Ltd. No.18, Ningshuang Road Nanjing Jiangsu 210012 CN +B8-CE-ED (hex) Broadcom +B8CEED (base 16) Broadcom + 1320 Ridder Park + San Jose CA 95131 + US + CC-0D-CB (hex) Microsoft Corporation CC0DCB (base 16) Microsoft Corporation One Microsoft Way @@ -91844,18 +91916,18 @@ CC0DCB (base 16) Microsoft Corporation Mountain View CA 94043 US -B8-CE-ED (hex) Broadcom -B8CEED (base 16) Broadcom - 1320 Ridder Park - San Jose CA 95131 - US - -B0-2E-BA (hex) Earda Technologies co Ltd -B02EBA (base 16) Earda Technologies co Ltd - Block A,Lianfeng Creative Park, #2 Jisheng Rd., Nansha District - Guangzhou Guangdong 511455 +EC-97-E0 (hex) Hangzhou Ezviz Software Co.,Ltd. +EC97E0 (base 16) Hangzhou Ezviz Software Co.,Ltd. + 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District + Hangzhou Zhejiang 310051 CN +60-5E-65 (hex) Mellanox Technologies, Inc. +605E65 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + 54-BA-D9 (hex) Intelbras 54BAD9 (base 16) Intelbras BR 101, km 210, S/N° @@ -91880,35 +91952,23 @@ EC96BF (base 16) Kontron eSystems GmbH shenzhen guangdong 518057 CN -A4-F0-0F (hex) Espressif Inc. -A4F00F (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - F0-92-58 (hex) China Electronics Cloud Computing Technology Co., Ltd F09258 (base 16) China Electronics Cloud Computing Technology Co., Ltd N3013,3F,N R&D building, A.I. Technology Park, Economic and Technological Development Zone Wuhan Hubei 430090 CN -EC-97-E0 (hex) Hangzhou Ezviz Software Co.,Ltd. -EC97E0 (base 16) Hangzhou Ezviz Software Co.,Ltd. - 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District - Hangzhou Zhejiang 310051 +A4-F0-0F (hex) Espressif Inc. +A4F00F (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 CN -60-5E-65 (hex) Mellanox Technologies, Inc. -605E65 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US - -0C-C5-74 (hex) FRITZ! Technology GmbH -0CC574 (base 16) FRITZ! Technology GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE +2C-8D-48 (hex) Smart Innovation LLC +2C8D48 (base 16) Smart Innovation LLC + 7F,Tower B,Jianxing + ShenZhen GuangZhou 518055 + CN 38-8C-EF (hex) Samsung Electronics Co.,Ltd 388CEF (base 16) Samsung Electronics Co.,Ltd @@ -91922,22 +91982,34 @@ EC97E0 (base 16) Hangzhou Ezviz Software Co.,Ltd. shenzhen guangdong 518100 CN +0C-C5-74 (hex) FRITZ! Technology GmbH +0CC574 (base 16) FRITZ! Technology GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +84-70-03 (hex) Axon Networks Inc. +847003 (base 16) Axon Networks Inc. + 15420 Laguna Canyon rd. + Irvine CA 92618 + US + A0-FF-FD (hex) HMD Global Oy A0FFFD (base 16) HMD Global Oy Bertel Jungin aukio 9 Espoo 02600 FI -2C-8D-48 (hex) Smart Innovation LLC -2C8D48 (base 16) Smart Innovation LLC - 7F,Tower B,Jianxing - ShenZhen GuangZhou 518055 - CN +30-7A-D2 (hex) Apple, Inc. +307AD2 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -84-70-03 (hex) Axon Networks Inc. -847003 (base 16) Axon Networks Inc. - 15420 Laguna Canyon rd. - Irvine CA 92618 +D4-2D-CC (hex) Apple, Inc. +D42DCC (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US 04-2E-C1 (hex) Apple, Inc. @@ -91964,32 +92036,29 @@ B45575 (base 16) Apple, Inc. Cupertino CA 95014 US -A0-E3-90 (hex) Apple, Inc. -A0E390 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - 6C-E4-A4 (hex) Silicon Laboratories 6CE4A4 (base 16) Silicon Laboratories 400 West Cesar Chavez Austin TX 78701 US +90-3F-86 (hex) New H3C Technologies Co., Ltd +903F86 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN + +6C-88-5F (hex) Private +6C885F (base 16) Private + 60-D4-AF (hex) Honor Device Co., Ltd. 60D4AF (base 16) Honor Device Co., Ltd. Suite 3401, Unit A, Building 6, Shum Yip Sky Park, No. 8089, Hongli West Road, Xiangmihu Street, Futian District Shenzhen Guangdong 518040 CN -30-7A-D2 (hex) Apple, Inc. -307AD2 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -D4-2D-CC (hex) Apple, Inc. -D42DCC (base 16) Apple, Inc. +A0-E3-90 (hex) Apple, Inc. +A0E390 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US @@ -92018,15 +92087,6 @@ D42DCC (base 16) Apple, Inc. Dongguan 523808 CN -90-3F-86 (hex) New H3C Technologies Co., Ltd -903F86 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 - CN - -6C-88-5F (hex) Private -6C885F (base 16) Private - 6C-7F-49 (hex) Huawei Device Co., Ltd. 6C7F49 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone @@ -92045,14 +92105,20 @@ D42DCC (base 16) Apple, Inc. Nan-Tou Taiwan 54261 TW -28-24-FF (hex) WNC Corporation -2824FF (base 16) WNC Corporation +B8-9F-09 (hex) WNC Corporation +B89F09 (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park Hsin-Chu R.O.C. 308 TW -B8-9F-09 (hex) WNC Corporation -B89F09 (base 16) WNC Corporation +88-5A-85 (hex) WNC Corporation +885A85 (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + +28-24-FF (hex) WNC Corporation +2824FF (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park Hsin-Chu R.O.C. 308 TW @@ -92081,12 +92147,6 @@ A8A092 (base 16) CHINA DRAGON TECHNOLOGY LIMITED Kanata Ontario K2K 2E6 CA -5C-BF-03 (hex) EMOCO -5CBF03 (base 16) EMOCO - Valhallavägen 5 - Lidingö 18151 - SE - EC-9E-68 (hex) Anhui Taoyun Technology Co., Ltd EC9E68 (base 16) Anhui Taoyun Technology Co., Ltd 6/F and 23/F, Scientific Research Building, Building 2, Zone A, China Sound Valley, No. 3333, Xiyou Road, High tech Zone Hefei Anhui @@ -92111,23 +92171,11 @@ B882F2 (base 16) WNC Corporation Hsin-Chu R.O.C. 308 TW -88-5A-85 (hex) WNC Corporation -885A85 (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW - -80-13-16 (hex) Intel Corporate -801316 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -2C-EA-FC (hex) Intel Corporate -2CEAFC (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +5C-BF-03 (hex) EMOCO +5CBF03 (base 16) EMOCO + Valhallavägen 5 + Lidingö 18151 + SE 04-24-05 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD 042405 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD @@ -92147,6 +92195,18 @@ D056F2 (base 16) BUFFALO.INC Nagoya Aichi Pref. 460-8315 JP +80-13-16 (hex) Intel Corporate +801316 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +2C-EA-FC (hex) Intel Corporate +2CEAFC (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + 74-F9-2C (hex) Ubiquiti Inc 74F92C (base 16) Ubiquiti Inc 685 Third Avenue, 27th Floor @@ -92165,12 +92225,6 @@ D056F2 (base 16) BUFFALO.INC Zoetermeer Zoetermeer 2712PN NL -30-4D-1F (hex) Amazon Technologies Inc. -304D1F (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US - AC-F9-32 (hex) NXP Semiconductor (Tianjin) LTD. ACF932 (base 16) NXP Semiconductor (Tianjin) LTD. No.15 Xinghua Avenue, Xiqing Economic Development Area @@ -92195,6 +92249,12 @@ ACF932 (base 16) NXP Semiconductor (Tianjin) LTD. Austin TX 78701 US +30-4D-1F (hex) Amazon Technologies Inc. +304D1F (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + F0-4E-A4 (hex) HP Inc. F04EA4 (base 16) HP Inc. 10300 Energy Dr @@ -92219,18 +92279,18 @@ E072A1 (base 16) Espressif Inc. Shanghai Shanghai 201203 CN -74-67-5F (hex) COMPAL INFORMATION(KUNSHAN)CO.,LTD. -74675F (base 16) COMPAL INFORMATION(KUNSHAN)CO.,LTD. - No.25 , THE 3RD Street KUNSHAN EXPORT PROCESSING ZONE - KUNSHAN SUZHOU 215300 - CN - AC-A7-04 (hex) Espressif Inc. ACA704 (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area Shanghai Shanghai 201203 CN +74-67-5F (hex) COMPAL INFORMATION(KUNSHAN)CO.,LTD. +74675F (base 16) COMPAL INFORMATION(KUNSHAN)CO.,LTD. + No.25 , THE 3RD Street KUNSHAN EXPORT PROCESSING ZONE + KUNSHAN SUZHOU 215300 + CN + 0C-BF-B4 (hex) IEEE Registration Authority 0CBFB4 (base 16) IEEE Registration Authority 445 Hoes Lane @@ -92243,18 +92303,6 @@ ACA704 (base 16) Espressif Inc. Courbevoie 92400 FR -00-1F-33 (hex) NETGEAR -001F33 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US - -00-1B-2F (hex) NETGEAR -001B2F (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US - 50-61-3F (hex) eero inc. 50613F (base 16) eero inc. 660 3rd Street @@ -92351,17 +92399,29 @@ E8FCAF (base 16) NETGEAR Kąty Wrocławskie dolnośląskie 55-080 PL -A4-2A-26 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -A42A26 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 - CN +00-1F-33 (hex) NETGEAR +001F33 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US -74-08-AA (hex) Ruijie Networks Co.,LTD -7408AA (base 16) Ruijie Networks Co.,LTD - Building 19,Juyuanzhou Industrial Park, No.618 Jinshan Avenue, Cangshan District - Fuzhou 35000 - CN +00-1B-2F (hex) NETGEAR +001B2F (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US + +38-33-C5 (hex) Microsoft Corporation +3833C5 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US + +90-1C-9E (hex) Alcatel-Lucent Enterprise +901C9E (base 16) Alcatel-Lucent Enterprise + 2000 Corporate Center Dr Suite A + Thousand Oaks 91320 + US 18-24-39 (hex) YIPPEE ELECTRONICS CP.,LIMITED 182439 (base 16) YIPPEE ELECTRONICS CP.,LIMITED @@ -92375,18 +92435,18 @@ A42A26 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. Planegg 82152 DE -38-33-C5 (hex) Microsoft Corporation -3833C5 (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 - US - 50-AB-29 (hex) Trackunit ApS 50AB29 (base 16) Trackunit ApS Gasvaerksvej 24, 4. sal Aalborg 9000 DK +A4-2A-26 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +A42A26 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 + CN + EC-A7-8D (hex) Cisco Systems, Inc ECA78D (base 16) Cisco Systems, Inc 80 West Tasman Drive @@ -92411,11 +92471,11 @@ FC7288 (base 16) Cisco Systems, Inc Dongguan Guangdong 523860 CN -90-1C-9E (hex) Alcatel-Lucent Enterprise -901C9E (base 16) Alcatel-Lucent Enterprise - 2000 Corporate Center Dr Suite A - Thousand Oaks 91320 - US +74-08-AA (hex) Ruijie Networks Co.,LTD +7408AA (base 16) Ruijie Networks Co.,LTD + Building 19,Juyuanzhou Industrial Park, No.618 Jinshan Avenue, Cangshan District + Fuzhou 35000 + CN 50-E0-F9 (hex) GE Vernova 50E0F9 (base 16) GE Vernova @@ -92465,22 +92525,16 @@ A0B53C (base 16) Vantiva Technologies Belgium Cedarburg WI 53012 US -24-19-A5 (hex) New H3C Technologies Co., Ltd -2419A5 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 - CN - -6C-AF-AB (hex) UAB Teltonika Telematics -6CAFAB (base 16) UAB Teltonika Telematics - Saltoniskiu str. 9B-1 - Vilnius LT-08105 - LT +0C-88-32 (hex) Nokia Solutions and Networks India Private Limited +0C8832 (base 16) Nokia Solutions and Networks India Private Limited + Plot 45, Fathima NagarNemilicherry,Chrompet + Chennai Taminadu 600044 + IN -1C-8E-2A (hex) Apple, Inc. -1C8E2A (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +94-3B-22 (hex) NETGEAR +943B22 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US 30-0E-43 (hex) Apple, Inc. @@ -92501,18 +92555,6 @@ A0B53C (base 16) Vantiva Technologies Belgium New Taipei City 238035 TW -0C-88-32 (hex) Nokia Solutions and Networks India Private Limited -0C8832 (base 16) Nokia Solutions and Networks India Private Limited - Plot 45, Fathima NagarNemilicherry,Chrompet - Chennai Taminadu 600044 - IN - -94-3B-22 (hex) NETGEAR -943B22 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US - B8-A7-92 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD B8A792 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County @@ -92525,42 +92567,54 @@ C8E713 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. Nanjing Jiangsu 211800 CN +1C-8E-2A (hex) Apple, Inc. +1C8E2A (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + 58-76-07 (hex) IEEE Registration Authority 587607 (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US +24-19-A5 (hex) New H3C Technologies Co., Ltd +2419A5 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN + +6C-AF-AB (hex) UAB Teltonika Telematics +6CAFAB (base 16) UAB Teltonika Telematics + Saltoniskiu str. 9B-1 + Vilnius LT-08105 + LT + 54-83-BB (hex) Honda Motor Co., Ltd 5483BB (base 16) Honda Motor Co., Ltd Toranomon Alcea Tower, 2-2-3 Toranomon, Minato-ku, Tokyo 105-8404 JP -E0-96-E8 (hex) Fiberhome Telecommunication Technologies Co.,LTD -E096E8 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 - CN - A8-13-78 (hex) Nokia A81378 (base 16) Nokia 600 March Road Kanata Ontario K2K 2E6 CA -B4-64-15 (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. -B46415 (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. - 13/F, Building 1, No.13 Bohua 4th Road, Huangpu District - Guangzhou Guangdong 510663 - CN - 1C-8E-E6 (hex) VTECH TELECOMMUNICATIONS LIMITED 1C8EE6 (base 16) VTECH TELECOMMUNICATIONS LIMITED BLOCK 01 23-24/F TAT PING INDUSTRIAL CENTRE 57 TING KOK ROAD TAI PONT DONG GUAN GUANG ZHOU 52300 CN +E0-96-E8 (hex) Fiberhome Telecommunication Technologies Co.,LTD +E096E8 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 + CN + 84-5B-0C (hex) eFAB P.S.A. 845B0C (base 16) eFAB P.S.A. al. Solidarości 129/131/197VATID: PL5272968735 @@ -92579,6 +92633,24 @@ F0C88B (base 16) Wyze Labs Inc BOTHELL WA 98021 US +34-02-9C (hex) D-Link Corporation +34029C (base 16) D-Link Corporation + No.289, Sinhu 3rd Rd., Neihu District, + Taipei City 114 + TW + +B4-64-15 (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. +B46415 (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. + 13/F, Building 1, No.13 Bohua 4th Road, Huangpu District + Guangzhou Guangdong 510663 + CN + +6C-77-F0 (hex) Huawei Device Co., Ltd. +6C77F0 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + 24-62-C6 (hex) Huawei Device Co., Ltd. 2462C6 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone @@ -92615,18 +92687,6 @@ B472D4 (base 16) zte corporation Guangzhou Guangdong 511455 CN -6C-77-F0 (hex) Huawei Device Co., Ltd. -6C77F0 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - -34-02-9C (hex) D-Link Corporation -34029C (base 16) D-Link Corporation - No.289, Sinhu 3rd Rd., Neihu District, - Taipei City 114 - TW - 5C-1B-17 (hex) Bosch Automotive Electronics India Pvt. Ltd. 5C1B17 (base 16) Bosch Automotive Electronics India Pvt. Ltd. Naganathapura @@ -92639,14 +92699,14 @@ B472D4 (base 16) zte corporation SHENZHEN Guangdong Province 518052 CN -A8-D1-62 (hex) Samsung Electronics Co.,Ltd -A8D162 (base 16) Samsung Electronics Co.,Ltd +78-60-89 (hex) Samsung Electronics Co.,Ltd +786089 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR -4C-EB-B0 (hex) Samsung Electronics Co.,Ltd -4CEBB0 (base 16) Samsung Electronics Co.,Ltd +A8-D1-62 (hex) Samsung Electronics Co.,Ltd +A8D162 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR @@ -92657,14 +92717,8 @@ A8D162 (base 16) Samsung Electronics Co.,Ltd Beijing 100190 CN -8C-05-28 (hex) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD -8C0528 (base 16) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD - 6-8 Floor, Tower E3, No. 1001, Zhongshanyuan Road, Nanshan District, Shenzhen,China - Shenzhen 518052 - CN - -78-60-89 (hex) Samsung Electronics Co.,Ltd -786089 (base 16) Samsung Electronics Co.,Ltd +4C-EB-B0 (hex) Samsung Electronics Co.,Ltd +4CEBB0 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR @@ -92681,30 +92735,6 @@ FC5708 (base 16) Broadcom Limited Austin TX 78735 US -9C-28-BF (hex) AUMOVIO Czech Republic s.r.o. -9C28BF (base 16) AUMOVIO Czech Republic s.r.o. - Průmyslová 1851 - Brandýs nad Labem 250 01 - CZ - -18-4C-AE (hex) AUMOVIO France S.A.S. -184CAE (base 16) AUMOVIO France S.A.S. - 1 AVENUE PAUL OURLIAC - TOULOUSE 31100 - FR - -E8-2D-79 (hex) AltoBeam Inc. -E82D79 (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 - CN - -AC-D3-FB (hex) Arycs Technologies Inc -ACD3FB (base 16) Arycs Technologies Inc - 718 University Ave Suite 200 - Los Gatos 95032 - US - 34-87-FB (hex) GTAI 3487FB (base 16) GTAI Room 208, Building B11, Yantian Industrial Zone, Yantian Community, Xixiang Street, Bao 'an District, @@ -92729,14 +92759,11 @@ E07291 (base 16) Silicon Laboratories Austin TX 78701 US -6C-81-66 (hex) Private -6C8166 (base 16) Private - -D0-EA-11 (hex) Routerboard.com -D0EA11 (base 16) Routerboard.com - Mikrotikls SIA - Riga Riga LV1009 - LV +AC-D3-FB (hex) Arycs Technologies Inc +ACD3FB (base 16) Arycs Technologies Inc + 718 University Ave Suite 200 + Los Gatos 95032 + US 2C-9D-90 (hex) Mellanox Technologies, Inc. 2C9D90 (base 16) Mellanox Technologies, Inc. @@ -92750,6 +92777,30 @@ E46DAB (base 16) Mellanox Technologies, Inc. Sunnyvale CA 94085 US +8C-05-28 (hex) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD +8C0528 (base 16) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD + 6-8 Floor, Tower E3, No. 1001, Zhongshanyuan Road, Nanshan District, Shenzhen,China + Shenzhen 518052 + CN + +90-74-AE (hex) AzureWave Technology Inc. +9074AE (base 16) AzureWave Technology Inc. + 8F., No. 94, Baozhong Rd. + New Taipei City Taiwan 231 + TW + +E8-2D-79 (hex) AltoBeam Inc. +E82D79 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN + +18-4C-AE (hex) AUMOVIO France S.A.S. +184CAE (base 16) AUMOVIO France S.A.S. + 1 AVENUE PAUL OURLIAC + TOULOUSE 31100 + FR + 00-54-AF (hex) AUMOVIO Systems, Inc. 0054AF (base 16) AUMOVIO Systems, Inc. 21440 W. Lake Cook Rd. @@ -92762,17 +92813,14 @@ E46DAB (base 16) Mellanox Technologies, Inc. Deer Park IL 60010 US -90-74-AE (hex) AzureWave Technology Inc. -9074AE (base 16) AzureWave Technology Inc. - 8F., No. 94, Baozhong Rd. - New Taipei City Taiwan 231 - TW +6C-81-66 (hex) Private +6C8166 (base 16) Private -B8-51-1D (hex) TELECHIPS, INC -B8511D (base 16) TELECHIPS, INC - 27, Geumto-ro 80beon-gil, Sujeong-gu, - Seongnam-si, Gyeonggi-do, 13453 - KR +D0-EA-11 (hex) Routerboard.com +D0EA11 (base 16) Routerboard.com + Mikrotikls SIA + Riga Riga LV1009 + LV D8-FC-92 (hex) Tuya Smart Inc. D8FC92 (base 16) Tuya Smart Inc. @@ -92786,23 +92834,17 @@ B4E25B (base 16) HP Inc. Spring TX 77389 US -F4-70-18 (hex) Hangzhou Ezviz Software Co.,Ltd. -F47018 (base 16) Hangzhou Ezviz Software Co.,Ltd. - 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District - Hangzhou Zhejiang 310051 - CN - DC-74-CE (hex) ITOCHU Techno-Solutions Corporation DC74CE (base 16) ITOCHU Techno-Solutions Corporation Kamiyacho Trust Tower, 4-1-1, Toranomon, Minato-ku, Tokyo Tokyo 105-6950 JP -4C-55-B2 (hex) Xiaomi Communications Co Ltd -4C55B2 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN +B8-51-1D (hex) TELECHIPS, INC +B8511D (base 16) TELECHIPS, INC + 27, Geumto-ro 80beon-gil, Sujeong-gu, + Seongnam-si, Gyeonggi-do, 13453 + KR 10-03-CD (hex) Calix Inc. 1003CD (base 16) Calix Inc. @@ -92810,47 +92852,11 @@ DC74CE (base 16) ITOCHU Techno-Solutions Corporation San Jose CA 95131 US -98-2A-FD (hex) HUAWEI TECHNOLOGIES CO.,LTD -982AFD (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -2C-AE-46 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. -2CAE46 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. - B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China - Nanning Guangxi 530007 - CN - -6C-77-42 (hex) zte corporation -6C7742 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN - -10-CD-54 (hex) HUAWEI TECHNOLOGIES CO.,LTD -10CD54 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -94-A2-5D (hex) HUAWEI TECHNOLOGIES CO.,LTD -94A25D (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -B4-C3-D9 (hex) HUAWEI TECHNOLOGIES CO.,LTD -B4C3D9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -54-26-18 (hex) HUAWEI TECHNOLOGIES CO.,LTD -542618 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +9C-28-BF (hex) AUMOVIO Czech Republic s.r.o. +9C28BF (base 16) AUMOVIO Czech Republic s.r.o. + Průmyslová 1851 + Brandýs nad Labem 250 01 + CZ 0C-0F-D8 (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED 0C0FD8 (base 16) GSD VIET NAM TECHNOLOGY COMPANY LIMITED @@ -92870,6 +92876,12 @@ D41368 (base 16) Shenzhen Intellirocks Tech. Co. Ltd. Shenzhen Guangdong 518000 CN +F4-70-18 (hex) Hangzhou Ezviz Software Co.,Ltd. +F47018 (base 16) Hangzhou Ezviz Software Co.,Ltd. + 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District + Hangzhou Zhejiang 310051 + CN + E4-53-41 (hex) Apple, Inc. E45341 (base 16) Apple, Inc. 1 Infinite Loop @@ -92888,6 +92900,12 @@ E45341 (base 16) Apple, Inc. Cupertino CA 95014 US +4C-55-B2 (hex) Xiaomi Communications Co Ltd +4C55B2 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + A4-22-B6 (hex) Motorola Mobility LLC, a Lenovo Company A422B6 (base 16) Motorola Mobility LLC, a Lenovo Company 222 West Merchandise Mart Plaza @@ -92906,16 +92924,46 @@ E0BA78 (base 16) Apple, Inc. Cupertino CA 95014 US -90-20-D7 (hex) Microsoft Corporation -9020D7 (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 - US +98-2A-FD (hex) HUAWEI TECHNOLOGIES CO.,LTD +982AFD (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -80-2A-F6 (hex) Honor Device Co., Ltd. -802AF6 (base 16) Honor Device Co., Ltd. - Suite 3401, Unit A, Building 6, Shum Yip Sky Park, No. 8089, Hongli West Road, Xiangmihu Street, Futian District - Shenzhen Guangdong 518040 +2C-AE-46 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +2CAE46 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. + B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China + Nanning Guangxi 530007 + CN + +6C-77-42 (hex) zte corporation +6C7742 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +10-CD-54 (hex) HUAWEI TECHNOLOGIES CO.,LTD +10CD54 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +94-A2-5D (hex) HUAWEI TECHNOLOGIES CO.,LTD +94A25D (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +B4-C3-D9 (hex) HUAWEI TECHNOLOGIES CO.,LTD +B4C3D9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +54-26-18 (hex) HUAWEI TECHNOLOGIES CO.,LTD +542618 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN 88-99-86 (hex) TP-LINK TECHNOLOGIES CO.,LTD. @@ -92924,11 +92972,11 @@ E0BA78 (base 16) Apple, Inc. Shenzhen Guangdong 518057 CN -60-95-F8 (hex) Arcadyan Corporation -6095F8 (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW +90-20-D7 (hex) Microsoft Corporation +9020D7 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US 28-B2-0B (hex) NXP USA, Inc 28B20B (base 16) NXP USA, Inc @@ -92936,12 +92984,6 @@ E0BA78 (base 16) Apple, Inc. Austin TX 78735 US -00-11-1E (hex) B&R Industrial Automation GmbH -00111E (base 16) B&R Industrial Automation GmbH - B&R Strasse 1 - Eggelsberg       5142 - AT - 80-AF-9F (hex) eero inc. 80AF9F (base 16) eero inc. 660 3rd Street @@ -92954,17 +92996,11 @@ BC9C8D (base 16) Ruckus Wireless Sunnyvale CA 94089 US -EC-50-A6 (hex) Sagemcom Broadband SAS -EC50A6 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -0C-52-7F (hex) Check Point Software Technologies Ltd. -0C527F (base 16) Check Point Software Technologies Ltd. - 5 Ha'solelim St - Tel Aviv 67897 - IL +64-70-84 (hex) AltoBeam Inc. +647084 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN 00-15-1E (hex) B&R Industrial Automation GmbH 00151E (base 16) B&R Industrial Automation GmbH @@ -92972,12 +93008,42 @@ EC50A6 (base 16) Sagemcom Broadband SAS Eggelsberg       5142 AT +80-2A-F6 (hex) Honor Device Co., Ltd. +802AF6 (base 16) Honor Device Co., Ltd. + Suite 3401, Unit A, Building 6, Shum Yip Sky Park, No. 8089, Hongli West Road, Xiangmihu Street, Futian District + Shenzhen Guangdong 518040 + CN + +00-A3-07 (hex) Honor Device Co., Ltd. +00A307 (base 16) Honor Device Co., Ltd. + Suite 3401, Unit A, Building 6, Shum Yip Sky Park, No. 8089, Hongli West Road, Xiangmihu Street, Futian District + Shenzhen Guangdong 518040 + CN + 64-D5-62 (hex) Huawei Device Co., Ltd. 64D562 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN +60-95-F8 (hex) Arcadyan Corporation +6095F8 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW + +00-11-1E (hex) B&R Industrial Automation GmbH +00111E (base 16) B&R Industrial Automation GmbH + B&R Strasse 1 + Eggelsberg       5142 + AT + +DC-15-5C (hex) Anntec (Beijing) Technology Co.,Ltd. +DC155C (base 16) Anntec (Beijing) Technology Co.,Ltd. + F803, Shangdi Third Street, No.9,HaiDian District + Beijing 100080 + CN + 08-94-EC (hex) Huawei Device Co., Ltd. 0894EC (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone @@ -92990,12 +93056,6 @@ CCB775 (base 16) Huawei Device Co., Ltd. Dongguan Guangdong 523808 CN -64-70-84 (hex) AltoBeam Inc. -647084 (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 - CN - 98-A3-75 (hex) Huawei Device Co., Ltd. 98A375 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone @@ -93008,18 +93068,6 @@ B8752E (base 16) Huawei Device Co., Ltd. Dongguan Guangdong 523808 CN -00-A3-07 (hex) Honor Device Co., Ltd. -00A307 (base 16) Honor Device Co., Ltd. - Suite 3401, Unit A, Building 6, Shum Yip Sky Park, No. 8089, Hongli West Road, Xiangmihu Street, Futian District - Shenzhen Guangdong 518040 - CN - -DC-15-5C (hex) Anntec (Beijing) Technology Co.,Ltd. -DC155C (base 16) Anntec (Beijing) Technology Co.,Ltd. - F803, Shangdi Third Street, No.9,HaiDian District - Beijing 100080 - CN - 10-A8-79 (hex) Intel Corporate 10A879 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 @@ -93050,18 +93098,24 @@ DC155C (base 16) Anntec (Beijing) Technology Co.,Ltd. Kulim Kedah 09000 MY +EC-50-A6 (hex) Sagemcom Broadband SAS +EC50A6 (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR + +0C-52-7F (hex) Check Point Software Technologies Ltd. +0C527F (base 16) Check Point Software Technologies Ltd. + 5 Ha'solelim St + Tel Aviv 67897 + IL + 88-FE-B6 (hex) ASKEY COMPUTER CORP 88FEB6 (base 16) ASKEY COMPUTER CORP 10F,No.119,JIANKANG RD,ZHONGHE DIST NEW TAIPEI TAIWAN 23585 TW -EC-9B-75 (hex) Roku, Inc -EC9B75 (base 16) Roku, Inc - 1173 Coleman Ave - San Jose CA 95110 - US - 6C-56-40 (hex) BLU Products Inc 6C5640 (base 16) BLU Products Inc 8600 NW 36th Street Suite 200 @@ -93086,18 +93140,6 @@ EC9B75 (base 16) Roku, Inc Chengdu Sichuan 611330 CN -A4-A6-4E (hex) Mellanox Technologies, Inc. -A4A64E (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US - -2C-B1-B7 (hex) Mellanox Technologies, Inc. -2CB1B7 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US - 9C-47-11 (hex) ACCTON TECHNOLOGY CORPORATION 9C4711 (base 16) ACCTON TECHNOLOGY CORPORATION No.1, Creation Road 3, Hsinchu Science Park, @@ -93110,34 +93152,28 @@ A4A64E (base 16) Mellanox Technologies, Inc. San Jose CA 95131 US -94-9C-BE (hex) HUAWEI TECHNOLOGIES CO.,LTD -949CBE (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -88-BA-74 (hex) Silicon Laboratories -88BA74 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US - -88-C0-93 (hex) GIGAMEDIA -88C093 (base 16) GIGAMEDIA - 312 RUE DES HAUTS DE SAIGHIN CRT4 - LESQUIN FRANCE 59811 - FR - E8-EA-7C (hex) Shenzhen Amazwear Holdings Co., Ltd E8EA7C (base 16) Shenzhen Amazwear Holdings Co., Ltd 34th Floor, Chang Jiang Center, Crossroads of Renmin Road and Jianshe Road, Jingxin Community, Longhua Street,Longhua District Shenzhen Guangdong 518000 CN -18-5F-27 (hex) Motorola Mobility LLC, a Lenovo Company -185F27 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 +EC-9B-75 (hex) Roku, Inc +EC9B75 (base 16) Roku, Inc + 1173 Coleman Ave + San Jose CA 95110 + US + +A4-A6-4E (hex) Mellanox Technologies, Inc. +A4A64E (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +2C-B1-B7 (hex) Mellanox Technologies, Inc. +2CB1B7 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 US 0C-85-09 (hex) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD @@ -93146,6 +93182,18 @@ E8EA7C (base 16) Shenzhen Amazwear Holdings Co., Ltd Shenzhen 518052 CN +94-9C-BE (hex) HUAWEI TECHNOLOGIES CO.,LTD +949CBE (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +88-BA-74 (hex) Silicon Laboratories +88BA74 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + 80-79-EF (hex) SUB-ZERO GROUP, INC. 8079EF (base 16) SUB-ZERO GROUP, INC. 2835 Buds Drive @@ -93164,29 +93212,29 @@ E8EA7C (base 16) Shenzhen Amazwear Holdings Co., Ltd Shanghai 200233 CN -B4-C0-C3 (hex) TP-Link Systems Inc. -B4C0C3 (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 +18-5F-27 (hex) Motorola Mobility LLC, a Lenovo Company +185F27 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 US -3C-BE-8E (hex) Guangzhou Shiyuan Electronic Technology Company Limited -3CBE8E (base 16) Guangzhou Shiyuan Electronic Technology Company Limited - No.6, 4th Yunpu Road, Yunpu industry District - Guangzhou Guangdong 510530 - CN - 98-F0-4C (hex) Cisco Systems, Inc 98F04C (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -00-05-BA (hex) XK22 Enterprises, LLC -0005BA (base 16) XK22 Enterprises, LLC - 2646 Wooster Rd. - Rocky River OH 44116 - US +88-C0-93 (hex) GIGAMEDIA +88C093 (base 16) GIGAMEDIA + 312 RUE DES HAUTS DE SAIGHIN CRT4 + LESQUIN FRANCE 59811 + FR + +3C-BE-8E (hex) Guangzhou Shiyuan Electronic Technology Company Limited +3CBE8E (base 16) Guangzhou Shiyuan Electronic Technology Company Limited + No.6, 4th Yunpu Road, Yunpu industry District + Guangzhou Guangdong 510530 + CN 34-4A-86 (hex) Honor Device Co., Ltd. 344A86 (base 16) Honor Device Co., Ltd. @@ -93200,22 +93248,10 @@ DC69CC (base 16) LG Innotek Gwangju Gwangsan-gu 506-731 KR -C0-2D-10 (hex) MOHAN ELECTRONICS AND SYSTEMS -C02D10 (base 16) MOHAN ELECTRONICS AND SYSTEMS - 571, STREET NO. 6, CHANDERLOK, SHAHDARA, DELHI - DELHI DELHI 110093 - IN - -74-98-F4 (hex) BUFFALO.INC -7498F4 (base 16) BUFFALO.INC - AKAMONDORI Bld.,30-20,Ohsu 3-chome,Naka-ku - Nagoya Aichi Pref. 460-8315 - JP - -0C-83-F4 (hex) Canopy Works, Inc. -0C83F4 (base 16) Canopy Works, Inc. - 1875 Mission St, Ste 103 - San Francisco CA 94103 +B4-C0-C3 (hex) TP-Link Systems Inc. +B4C0C3 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 US 20-33-89 (hex) Google, Inc. @@ -93224,10 +93260,10 @@ C02D10 (base 16) MOHAN ELECTRONICS AND SYSTEMS Mountain View CA 94043 US -D0-C6-BE (hex) HPRO-Video -D0C6BE (base 16) HPRO-Video - 8500 Balboa Blvd - Northridge CA 91329 +00-05-BA (hex) XK22 Enterprises, LLC +0005BA (base 16) XK22 Enterprises, LLC + 2646 Wooster Rd. + Rocky River OH 44116 US F8-1E-49 (hex) Apple, Inc. @@ -93248,6 +93284,12 @@ BC74EA (base 16) Apple, Inc. Cupertino CA 95014 US +C0-2D-10 (hex) MOHAN ELECTRONICS AND SYSTEMS +C02D10 (base 16) MOHAN ELECTRONICS AND SYSTEMS + 571, STREET NO. 6, CHANDERLOK, SHAHDARA, DELHI + DELHI DELHI 110093 + IN + 18-B8-42 (hex) Apple, Inc. 18B842 (base 16) Apple, Inc. 1 Infinite Loop @@ -93260,6 +93302,60 @@ BC74EA (base 16) Apple, Inc. Cupertino CA 95014 US +74-98-F4 (hex) BUFFALO.INC +7498F4 (base 16) BUFFALO.INC + AKAMONDORI Bld.,30-20,Ohsu 3-chome,Naka-ku + Nagoya Aichi Pref. 460-8315 + JP + +0C-83-F4 (hex) Canopy Works, Inc. +0C83F4 (base 16) Canopy Works, Inc. + 1875 Mission St, Ste 103 + San Francisco CA 94103 + US + +D0-C6-BE (hex) HPRO-Video +D0C6BE (base 16) HPRO-Video + 8500 Balboa Blvd + Northridge CA 91329 + US + +84-AE-DE (hex) Xiaomi Communications Co Ltd +84AEDE (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + +CC-0C-9C (hex) CIG SHANGHAI CO LTD +CC0C9C (base 16) CIG SHANGHAI CO LTD + 5th Floor, Building 8 No 2388 Chenhang Road + SHANGHAI 201114 + CN + +A4-D7-D6 (hex) Shenzhen Linkoh Network Technology Co;Ltd +A4D7D6 (base 16) Shenzhen Linkoh Network Technology Co;Ltd + Yangguang Industrial Park, Hangcheng, Bao'an + Shenzhen Guangdong 518000 + CN + +B8-0B-9A (hex) HUAWEI TECHNOLOGIES CO.,LTD +B80B9A (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +C8-26-91 (hex) Arista Networks, Inc. +C82691 (base 16) Arista Networks, Inc. + 5453 Great America Parkway + Santa Clara 95054 + US + +00-13-AE (hex) Radiance Technologies, Inc. +0013AE (base 16) Radiance Technologies, Inc. + 310 Bob Heath Dr. + Huntsville 35806 + US + 6C-87-20 (hex) New H3C Technologies Co., Ltd 6C8720 (base 16) New H3C Technologies Co., Ltd 466 Changhe Road, Binjiang District @@ -127262,12 +127358,6 @@ B4B5AF (base 16) Minsung Electronics Kanagawa 215-0034 JP -00-07-8B (hex) Wegener Communications, Inc. -00078B (base 16) Wegener Communications, Inc. - 11350 Technology Circle - Duluth GA 30097 - US - 00-07-83 (hex) SynCom Network, Inc. 000783 (base 16) SynCom Network, Inc. 4F, No. 31, Hsintai Road, Chupei City, @@ -137318,23 +137408,23 @@ F4F50B (base 16) TP-Link Systems Inc. Irvine CA 92618 US -A4-D5-30 (hex) Avaya LLC -A4D530 (base 16) Avaya LLC - 350 Mt Kimble - Morristown NJ 07960 - US - 34-56-FE (hex) Cisco Meraki 3456FE (base 16) Cisco Meraki 500 Terry A. Francois Blvd San Francisco 94158 US -4C-EF-56 (hex) Shenzhen Sundray Technologies company Limited -4CEF56 (base 16) Shenzhen Sundray Technologies company Limited - 5th Floor, Block A4, Nanshan ipark,NO.1001 Xue Yuan Road, Nanshan District, Shenzhen 518055, P.R. China - Shenzhen Guangdong 518057 - CN +B8-07-56 (hex) Cisco Meraki +B80756 (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US + +A4-D5-30 (hex) Avaya LLC +A4D530 (base 16) Avaya LLC + 350 Mt Kimble + Morristown NJ 07960 + US 94-14-57 (hex) Shenzhen Sundray Technologies company Limited 941457 (base 16) Shenzhen Sundray Technologies company Limited @@ -137354,17 +137444,29 @@ C8A23B (base 16) Shenzhen Sundray Technologies company Limited Shenzhen Guangdong 518057 CN +7C-15-2D (hex) Renesas Electronics (Penang) Sdn. Bhd. +7C152D (base 16) Renesas Electronics (Penang) Sdn. Bhd. + Phase 3, Bayan Lepas FIZ + Bayan Lepas Penang 11900 + MY + +D8-F1-2E (hex) TP-Link Systems Inc. +D8F12E (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US + A0-88-5E (hex) Anhui Xiangyao New Energy Technology Co., Ltd. A0885E (base 16) Anhui Xiangyao New Energy Technology Co., Ltd. No. 2, District 4, Intelligent Industrial Park, South District, Lieshan Economic Development Zone Huaibei City Anhui Province 235065 CN -7C-15-2D (hex) Renesas Electronics (Penang) Sdn. Bhd. -7C152D (base 16) Renesas Electronics (Penang) Sdn. Bhd. - Phase 3, Bayan Lepas FIZ - Bayan Lepas Penang 11900 - MY +4C-EF-56 (hex) Shenzhen Sundray Technologies company Limited +4CEF56 (base 16) Shenzhen Sundray Technologies company Limited + 5th Floor, Block A4, Nanshan ipark,NO.1001 Xue Yuan Road, Nanshan District, Shenzhen 518055, P.R. China + Shenzhen Guangdong 518057 + CN A4-DB-4C (hex) RAI Institute A4DB4C (base 16) RAI Institute @@ -137378,12 +137480,6 @@ A4DB4C (base 16) RAI Institute San Francisco CA 94107 US -B8-07-56 (hex) Cisco Meraki -B80756 (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 - US - 2C-91-AB (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH 2C91AB (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 @@ -137444,11 +137540,11 @@ E00855 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Berlin Berlin 10559 DE -D8-F1-2E (hex) TP-Link Systems Inc. -D8F12E (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 - US +10-3D-3E (hex) China Mobile Group Device Co.,Ltd. +103D3E (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN 90-47-3C (hex) China Mobile Group Device Co.,Ltd. 90473C (base 16) China Mobile Group Device Co.,Ltd. @@ -137486,6 +137582,36 @@ CC5CDE (base 16) China Mobile Group Device Co.,Ltd. Beijing 100053 CN +94-BE-09 (hex) China Mobile Group Device Co.,Ltd. +94BE09 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +BC-9E-2C (hex) China Mobile Group Device Co.,Ltd. +BC9E2C (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +C8-0C-53 (hex) China Mobile Group Device Co.,Ltd. +C80C53 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +54-4D-D4 (hex) China Mobile Group Device Co.,Ltd. +544DD4 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +C0-2D-2E (hex) China Mobile Group Device Co.,Ltd. +C02D2E (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + 70-89-CC (hex) China Mobile Group Device Co.,Ltd. 7089CC (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District @@ -137504,12 +137630,6 @@ AC710C (base 16) China Mobile Group Device Co.,Ltd. Beijing 100053 CN -10-3D-3E (hex) China Mobile Group Device Co.,Ltd. -103D3E (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - D0-CB-DD (hex) eero inc. D0CBDD (base 16) eero inc. 660 3rd Street @@ -137618,36 +137738,6 @@ B0CF0E (base 16) Mellanox Technologies, Inc. Sunnyvale CA 94085 US -94-BE-09 (hex) China Mobile Group Device Co.,Ltd. -94BE09 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - -BC-9E-2C (hex) China Mobile Group Device Co.,Ltd. -BC9E2C (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - -C8-0C-53 (hex) China Mobile Group Device Co.,Ltd. -C80C53 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - -54-4D-D4 (hex) China Mobile Group Device Co.,Ltd. -544DD4 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - -C0-2D-2E (hex) China Mobile Group Device Co.,Ltd. -C02D2E (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - 88-57-21 (hex) Espressif Inc. 885721 (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area @@ -137666,6 +137756,12 @@ D4A5B4 (base 16) Hengji Jiaye (Hangzhou) Technology Co., Ltd Hangzhou Zhejiang 310000 CN +A0-3C-20 (hex) Sagemcom Broadband SAS +A03C20 (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR + 20-4D-52 (hex) Mellanox Technologies, Inc. 204D52 (base 16) Mellanox Technologies, Inc. 350 Oakmead Parkway, Suite 100 @@ -137684,6 +137780,30 @@ F02C59 (base 16) Chipsea Technologies (Shenzhen) Crop. Shenzhen 518108 CN +FC-39-5A (hex) SonicWall +FC395A (base 16) SonicWall + 1033 McCarthy Blvd + Milpitas CA 95035 + US + +0C-FE-7B (hex) Vantiva USA LLC +0CFE7B (base 16) Vantiva USA LLC + 4855 Peachtree Industrial Blvd, Suite 200 + Norcross GA 30902 + US + +B0-D5-FB (hex) Google, Inc. +B0D5FB (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US + +48-D0-1C (hex) AltoBeam Inc. +48D01C (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN + 58-7A-B1 (hex) Shanghai Lixun Information Technology Co., Ltd. 587AB1 (base 16) Shanghai Lixun Information Technology Co., Ltd. Room 2111-L, No. 89 Yunling East Road @@ -137702,12 +137822,6 @@ F02C59 (base 16) Chipsea Technologies (Shenzhen) Crop. Hangzhou Zhejiang 310052 CN -A0-3C-20 (hex) Sagemcom Broadband SAS -A03C20 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - C4-9A-89 (hex) Suzhou K-Hiragawa Electronic Technology Co.,Ltd C49A89 (base 16) Suzhou K-Hiragawa Electronic Technology Co.,Ltd No.1 Zhipu Road, Qiandeng Town @@ -137720,30 +137834,6 @@ ACBDF7 (base 16) Cisco Meraki San Francisco 94158 US -FC-39-5A (hex) SonicWall -FC395A (base 16) SonicWall - 1033 McCarthy Blvd - Milpitas CA 95035 - US - -0C-FE-7B (hex) Vantiva USA LLC -0CFE7B (base 16) Vantiva USA LLC - 4855 Peachtree Industrial Blvd, Suite 200 - Norcross GA 30902 - US - -B0-D5-FB (hex) Google, Inc. -B0D5FB (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 - US - -48-D0-1C (hex) AltoBeam Inc. -48D01C (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 - CN - C8-17-F5 (hex) Nanjing Qinheng Microelectronics Co., Ltd. C817F5 (base 16) Nanjing Qinheng Microelectronics Co., Ltd. No.18, Ningshuang Road @@ -137762,47 +137852,41 @@ D8B2AA (base 16) zte corporation Ashburton Devon TQ13 7UP GB +2C-F8-14 (hex) Cisco Systems, Inc +2CF814 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + F4-15-63 (hex) F5 Inc. F41563 (base 16) F5 Inc. 1322 North Whitman Lane Liberty Lake WA 99019 US -34-DA-A1 (hex) Apple, Inc. -34DAA1 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -CC-22-FE (hex) Apple, Inc. -CC22FE (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - CC-67-D8 (hex) Telin Semiconductor (Wuhan) Co.,Ltd CC67D8 (base 16) Telin Semiconductor (Wuhan) Co.,Ltd Room 1003 Buliding 5 , 2377 Shenkun Road ,Minhang District Shanghai 201106 CN -2C-F8-14 (hex) Cisco Systems, Inc -2CF814 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +64-AC-2B (hex) Juniper Networks +64AC2B (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US -88-DA-04 (hex) HUAWEI TECHNOLOGIES CO.,LTD -88DA04 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +CC-22-FE (hex) Apple, Inc. +CC22FE (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -04-74-9E (hex) HUAWEI TECHNOLOGIES CO.,LTD -04749E (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +34-DA-A1 (hex) Apple, Inc. +34DAA1 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US CC-FA-F1 (hex) Sagemcom Broadband SAS CCFAF1 (base 16) Sagemcom Broadband SAS @@ -137816,18 +137900,24 @@ CCFAF1 (base 16) Sagemcom Broadband SAS Fuzhou FUJIAN 350015 CN +88-DA-04 (hex) HUAWEI TECHNOLOGIES CO.,LTD +88DA04 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +04-74-9E (hex) HUAWEI TECHNOLOGIES CO.,LTD +04749E (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + 24-34-08 (hex) Edgecore Americas Networking Corporation 243408 (base 16) Edgecore Americas Networking Corporation 20 Mason Irvine CA 92618 US -64-AC-2B (hex) Juniper Networks -64AC2B (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US - 84-D0-DB (hex) Guangdong Juan Intelligent Technology Joint Stock Co., Ltd. 84D0DB (base 16) Guangdong Juan Intelligent Technology Joint Stock Co., Ltd. The first and second floors of Building 2  (Plant No. 2), West Side of Shanxi Village, Dashi Street,Panyu District, Guangzhou @@ -137846,24 +137936,6 @@ A86D04 (base 16) Siemens AG Guangzhou Guangdong 510663 CN -44-38-8C (hex) Sumitomo Electric Industries, Ltd -44388C (base 16) Sumitomo Electric Industries, Ltd - 1-1-3, Shimaya, Konohana-ku - Osaka 554-0024 - JP - -7C-7B-BF (hex) Samsung Electronics Co.,Ltd -7C7BBF (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -8C-2E-72 (hex) Samsung Electronics Co.,Ltd -8C2E72 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - DC-70-35 (hex) Shengzhen Gongjin Electronics DC7035 (base 16) Shengzhen Gongjin Electronics No. 2 Danzi North Road, Kengzi Street, Pingshan District @@ -137894,6 +137966,12 @@ F8CF52 (base 16) Intel Corporate Kulim Kedah 09000 MY +44-38-8C (hex) Sumitomo Electric Industries, Ltd +44388C (base 16) Sumitomo Electric Industries, Ltd + 1-1-3, Shimaya, Konohana-ku + Osaka 554-0024 + JP + BC-51-5F (hex) Nokia Solutions and Networks India Private Limited BC515F (base 16) Nokia Solutions and Networks India Private Limited Plot 45, Fathima NagarNemilicherry,Chrompet @@ -137912,24 +137990,36 @@ D49D9D (base 16) Shenzhen Goodocom lnformation Technology Co.,Ltd. Shenzhen 518000 CN +7C-7B-BF (hex) Samsung Electronics Co.,Ltd +7C7BBF (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +8C-2E-72 (hex) Samsung Electronics Co.,Ltd +8C2E72 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + 80-F1-B2 (hex) Espressif Inc. 80F1B2 (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area Shanghai Shanghai 201203 CN -88-12-7D (hex) Shenzhen Melon Electronics Co.,Ltd -88127D (base 16) Shenzhen Melon Electronics Co.,Ltd - 2F, Building E, Digital Silicon Valley, No.89 Hengping Road, Yuanshan Subdistrict, Longgang District, Shenzhen, Guangdong, China - Shenzhen Guangdong 518100 - CN - 00-70-07 (hex) Espressif Inc. 007007 (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area Shanghai Shanghai 201203 CN +88-12-7D (hex) Shenzhen Melon Electronics Co.,Ltd +88127D (base 16) Shenzhen Melon Electronics Co.,Ltd + 2F, Building E, Digital Silicon Valley, No.89 Hengping Road, Yuanshan Subdistrict, Longgang District, Shenzhen, Guangdong, China + Shenzhen Guangdong 518100 + CN + 44-CB-AD (hex) Xiaomi Communications Co Ltd 44CBAD (base 16) Xiaomi Communications Co Ltd #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road @@ -138002,12 +138092,6 @@ C4DBAD (base 16) Ring LLC Beijing Beijing 100083 CN -40-1C-D4 (hex) Huawei Device Co., Ltd. -401CD4 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - 00-4B-0D (hex) Huawei Device Co., Ltd. 004B0D (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone @@ -138020,6 +138104,18 @@ E04027 (base 16) Huawei Device Co., Ltd. Dongguan Guangdong 523808 CN +40-1C-D4 (hex) Huawei Device Co., Ltd. +401CD4 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +D4-7B-6B (hex) Shanghai Cygnus Semiconductor Co., Ltd. +D47B6B (base 16) Shanghai Cygnus Semiconductor Co., Ltd. + Rooms 401 and 402, Building 5, 690 Bibo Road, China (Shanghai) Pilot Free Trade Zone + Shanghai Shanghai 201203 + CN + 00-02-71 (hex) Zhone Technologies, Inc. 000271 (base 16) Zhone Technologies, Inc. 7001 Oakport Street @@ -138032,11 +138128,17 @@ E04027 (base 16) Huawei Device Co., Ltd. Oakland CA 94621 US -F8-83-06 (hex) Beijing Xiaomi Mobile Software Co., Ltd -F88306 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 - CN +D0-96-FB (hex) Zhone Technologies, Inc. +D096FB (base 16) Zhone Technologies, Inc. + DASAN Tower 8F, 49 Daewangpangyo-ro644beon-gil Bundang-gu + Seongnam-si Gyeonggi-do 13493 + KR + +30-4F-75 (hex) Zhone Technologies, Inc. +304F75 (base 16) Zhone Technologies, Inc. + DASAN Tower 8F, 49 Daewangpangyo-ro644beon-gil Bundang-gu + Seongnam-si Gyeonggi-do 13493 + KR 7C-C5-18 (hex) vivo Mobile Communication Co., Ltd. 7CC518 (base 16) vivo Mobile Communication Co., Ltd. @@ -138050,22 +138152,16 @@ F88306 (base 16) Beijing Xiaomi Mobile Software Co., Ltd shenzhen guangdong 518057 CN -D0-96-FB (hex) Zhone Technologies, Inc. -D096FB (base 16) Zhone Technologies, Inc. - DASAN Tower 8F, 49 Daewangpangyo-ro644beon-gil Bundang-gu - Seongnam-si Gyeonggi-do 13493 - KR - -30-4F-75 (hex) Zhone Technologies, Inc. -304F75 (base 16) Zhone Technologies, Inc. - DASAN Tower 8F, 49 Daewangpangyo-ro644beon-gil Bundang-gu - Seongnam-si Gyeonggi-do 13493 - KR +A8-9A-8C (hex) zte corporation +A89A8C (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -D4-7B-6B (hex) Shanghai Cygnus Semiconductor Co., Ltd. -D47B6B (base 16) Shanghai Cygnus Semiconductor Co., Ltd. - Rooms 401 and 402, Building 5, 690 Bibo Road, China (Shanghai) Pilot Free Trade Zone - Shanghai Shanghai 201203 +F8-83-06 (hex) Beijing Xiaomi Mobile Software Co., Ltd +F88306 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN 98-61-10 (hex) HUAWEI TECHNOLOGIES CO.,LTD @@ -138104,12 +138200,6 @@ AC82F0 (base 16) Apple, Inc. Cupertino CA 95014 US -A8-9A-8C (hex) zte corporation -A89A8C (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN - 68-4A-5F (hex) Apple, Inc. 684A5F (base 16) Apple, Inc. 1 Infinite Loop @@ -138128,6 +138218,12 @@ E898EE (base 16) Apple, Inc. Sunnyvale CA 94089 US +D8-1D-13 (hex) Texas Instruments +D81D13 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + 10-5A-95 (hex) TP-Link Systems Inc. 105A95 (base 16) TP-Link Systems Inc. 10 Mauchly @@ -138158,12 +138254,6 @@ CC5EA5 (base 16) Palo Alto Networks Santa Clara CA 95054 US -D8-1D-13 (hex) Texas Instruments -D81D13 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US - 14-75-E5 (hex) ELMAX Srl 1475E5 (base 16) ELMAX Srl Via dei Parietai, 2 @@ -138194,54 +138284,66 @@ E456CA (base 16) Fractal BMS Piscataway NJ 08554 US +B8-FE-90 (hex) Cisco Systems, Inc +B8FE90 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +34-C3-FD (hex) Cisco Systems, Inc +34C3FD (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +64-84-34 (hex) BEMER Int. AG +648434 (base 16) BEMER Int. AG + Austrasse 15 + Triesen 9495 + LI + F0-44-D3 (hex) Silicon Laboratories F044D3 (base 16) Silicon Laboratories 400 West Cesar Chavez Austin TX 78701 US -6C-47-25 (hex) Rochester Network Supply, Inc. -6C4725 (base 16) Rochester Network Supply, Inc. - 1319 Research Forest, - Macedon NY 14502 - US - -B4-43-89 (hex) HUAWEI TECHNOLOGIES CO.,LTD -B44389 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +08-FD-58 (hex) HUAWEI TECHNOLOGIES CO.,LTD +08FD58 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -08-FD-58 (hex) HUAWEI TECHNOLOGIES CO.,LTD -08FD58 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +B4-43-89 (hex) HUAWEI TECHNOLOGIES CO.,LTD +B44389 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN +6C-47-25 (hex) Rochester Network Supply, Inc. +6C4725 (base 16) Rochester Network Supply, Inc. + 1319 Research Forest, + Macedon NY 14502 + US + 80-49-BF (hex) Taicang T&W Electronics 8049BF (base 16) Taicang T&W Electronics 89# Jiang Nan RD Suzhou Jiangsu 215412 CN +6C-D8-FB (hex) Qorvo Utrecht B.V. +6CD8FB (base 16) Qorvo Utrecht B.V. + Leidseveer 10 + Utrecht Utrecht 3511 SB + NL + B0-97-E6 (hex) FUJIAN FUCAN WECON CO LTD B097E6 (base 16) FUJIAN FUCAN WECON CO LTD Wecon Tech Park, No.58 Jiangbin East Avenue, Mawei, Fuzhou, China FUZHOU FUJIAN 350015 CN -B8-FE-90 (hex) Cisco Systems, Inc -B8FE90 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -34-C3-FD (hex) Cisco Systems, Inc -34C3FD (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - B4-5C-B5 (hex) Mellanox Technologies, Inc. B45CB5 (base 16) Mellanox Technologies, Inc. 350 Oakmead Parkway, Suite 100 @@ -138254,24 +138356,6 @@ E8F60A (base 16) Espressif Inc. Shanghai Shanghai 201203 CN -6C-D8-FB (hex) Qorvo Utrecht B.V. -6CD8FB (base 16) Qorvo Utrecht B.V. - Leidseveer 10 - Utrecht Utrecht 3511 SB - NL - -64-84-34 (hex) BEMER Int. AG -648434 (base 16) BEMER Int. AG - Austrasse 15 - Triesen 9495 - LI - -24-A1-0D (hex) IEEE Registration Authority -24A10D (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US - BC-89-F8 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. BC89F8 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. Midea Global Innovation Center,Beijiao Town,Shunde @@ -138284,6 +138368,48 @@ BC89F8 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. Wuxi jiangsu 214174 CN +F8-EF-B1 (hex) Hangzhou Zhongxinghui Intelligent Technology Co., Ltd. +F8EFB1 (base 16) Hangzhou Zhongxinghui Intelligent Technology Co., Ltd. + Room 809, Building B, No. 567 Yueming Road, Xixing Street, + Hangzhou Binjiang Distric 310000 + CN + +C0-2E-5F (hex) Zyxel Communications Corporation +C02E5F (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW + +24-A1-0D (hex) IEEE Registration Authority +24A10D (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +00-27-13 (hex) Universal Global Scientific Industrial., Ltd +002713 (base 16) Universal Global Scientific Industrial., Ltd + 141, LANE 351,SEC.1, TAIPING RD. + TSAOTUEN, NANTOU 54261 + TW + +CC-52-AF (hex) Universal Global Scientific Industrial., Ltd +CC52AF (base 16) Universal Global Scientific Industrial., Ltd + 141, LANE 351, TAIPING RD. + nan tou NAN-TOU 542 + TW + +FC-4D-D4 (hex) Universal Global Scientific Industrial., Ltd +FC4DD4 (base 16) Universal Global Scientific Industrial., Ltd + 141, Lane 351, Taiping Rd. Sec. 1, Tsao Tuen, + Nan-Tou Hsien, 542 + TW + +08-3A-88 (hex) Universal Global Scientific Industrial., Ltd +083A88 (base 16) Universal Global Scientific Industrial., Ltd + 141, Lane 351, Taiping Road, Sec.1,Tsao Tuen + Nan-Tou Taiwan 54261 + TW + F8-F2-F0 (hex) Chipsea Technologies (Shenzhen) Crop. F8F2F0 (base 16) Chipsea Technologies (Shenzhen) Crop. Room 301, Building 1, Shenzhen Bay Innovation and Technology Center, Keyuan Avenue, High-tech Zone Community, Yuehai Subdistrict, Nanshan District, Shenzhen @@ -138296,18 +138422,24 @@ E42F37 (base 16) Apple, Inc. Cupertino CA 95014 US -64-BD-6D (hex) Apple, Inc. -64BD6D (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +A4-93-AD (hex) Huawei Device Co., Ltd. +A493AD (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -F8-EF-B1 (hex) Hangzhou Zhongxinghui Intelligent Technology Co., Ltd. -F8EFB1 (base 16) Hangzhou Zhongxinghui Intelligent Technology Co., Ltd. - Room 809, Building B, No. 567 Yueming Road, Xixing Street, - Hangzhou Binjiang Distric 310000 +2C-3A-B1 (hex) Huawei Device Co., Ltd. +2C3AB1 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN +20-F1-B2 (hex) Tuya Smart Inc. +20F1B2 (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 + US + A8-C0-50 (hex) Quectel Wireless Solutions Co.,Ltd. A8C050 (base 16) Quectel Wireless Solutions Co.,Ltd. 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District @@ -138320,23 +138452,35 @@ A8C050 (base 16) Quectel Wireless Solutions Co.,Ltd. Shanghai Shanghai 201203 CN +58-04-4F (hex) TP-Link Systems Inc. +58044F (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US + B4-B8-53 (hex) Honor Device Co., Ltd. B4B853 (base 16) Honor Device Co., Ltd. Suite 3401, Unit A, Building 6, Shum Yip Sky Park, No. 8089, Hongli West Road, Xiangmihu Street, Futian District Shenzhen Guangdong 518040 CN +E0-4F-43 (hex) Universal Global Scientific Industrial., Ltd +E04F43 (base 16) Universal Global Scientific Industrial., Ltd + 141, Lane 351, Taiping Road, Sec.1,Tsao Tuen + Nan-Tou Taiwan 54261 + TW + 08-3B-C1 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. 083BC1 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. No.555 Qianmo Road Hangzhou Zhejiang 310052 CN -C0-2E-5F (hex) Zyxel Communications Corporation -C02E5F (base 16) Zyxel Communications Corporation - No. 6 Innovation Road II, Science Park - Hsichu Taiwan 300 - TW +64-BD-6D (hex) Apple, Inc. +64BD6D (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US E8-C3-86 (hex) Apple, Inc. E8C386 (base 16) Apple, Inc. @@ -138344,28 +138488,46 @@ E8C386 (base 16) Apple, Inc. Cupertino CA 95014 US -00-27-13 (hex) Universal Global Scientific Industrial., Ltd -002713 (base 16) Universal Global Scientific Industrial., Ltd - 141, LANE 351,SEC.1, TAIPING RD. - TSAOTUEN, NANTOU 54261 +2C-DC-AD (hex) WNC Corporation +2CDCAD (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 TW -CC-52-AF (hex) Universal Global Scientific Industrial., Ltd -CC52AF (base 16) Universal Global Scientific Industrial., Ltd - 141, LANE 351, TAIPING RD. - nan tou NAN-TOU 542 +B8-B7-F1 (hex) WNC Corporation +B8B7F1 (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 TW -FC-4D-D4 (hex) Universal Global Scientific Industrial., Ltd -FC4DD4 (base 16) Universal Global Scientific Industrial., Ltd - 141, Lane 351, Taiping Rd. Sec. 1, Tsao Tuen, - Nan-Tou Hsien, 542 +44-E4-EE (hex) WNC Corporation +44E4EE (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 TW -08-3A-88 (hex) Universal Global Scientific Industrial., Ltd -083A88 (base 16) Universal Global Scientific Industrial., Ltd - 141, Lane 351, Taiping Road, Sec.1,Tsao Tuen - Nan-Tou Taiwan 54261 +2C-9F-FB (hex) WNC Corporation +2C9FFB (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + +1C-D6-BE (hex) WNC Corporation +1CD6BE (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + +70-61-BE (hex) WNC Corporation +7061BE (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + +60-E6-F0 (hex) WNC Corporation +60E6F0 (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 TW 20-0D-3D (hex) Quectel Wireless Solutions Co., Ltd. @@ -138386,12 +138548,6 @@ B0CBD8 (base 16) Espressif Inc. Shanghai Shanghai 201203 CN -8C-57-9B (hex) WNC Corporation -8C579B (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW - 80-EA-23 (hex) WNC Corporation 80EA23 (base 16) WNC Corporation 20 Park Avenue II, Hsin Science Park, Hsinchu 308, Taiwan @@ -138416,6 +138572,12 @@ BC307E (base 16) WNC Corporation Hsinchu 30808854 TW +00-1B-B1 (hex) WNC Corporation +001BB1 (base 16) WNC Corporation + No. 10-1, Li-hsin Road I, Hsinchu Science Park, + Hsinchu 300 + TW + E4-8E-C5 (hex) HUAWEI TECHNOLOGIES CO.,LTD E48EC5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -138428,71 +138590,41 @@ E48EC5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -A4-93-AD (hex) Huawei Device Co., Ltd. -A493AD (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - -2C-3A-B1 (hex) Huawei Device Co., Ltd. -2C3AB1 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - -20-F1-B2 (hex) Tuya Smart Inc. -20F1B2 (base 16) Tuya Smart Inc. - 160 Greentree Drive, Suite 101 - Dover DE 19904 - US - -58-04-4F (hex) TP-Link Systems Inc. -58044F (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 - US - -E0-4F-43 (hex) Universal Global Scientific Industrial., Ltd -E04F43 (base 16) Universal Global Scientific Industrial., Ltd - 141, Lane 351, Taiping Road, Sec.1,Tsao Tuen - Nan-Tou Taiwan 54261 - TW - A4-61-85 (hex) Tools for Humanity Corporation A46185 (base 16) Tools for Humanity Corporation 650 7th St San Francisco CA 94103 US -D4-4F-14 (hex) Tesla,Inc. -D44F14 (base 16) Tesla,Inc. - 3500 Deer Creek Rd. - PALO ALTO CA 94304 - US - -2C-9F-FB (hex) WNC Corporation -2C9FFB (base 16) WNC Corporation +8C-57-9B (hex) WNC Corporation +8C579B (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park Hsin-Chu R.O.C. 308 TW -1C-D6-BE (hex) WNC Corporation -1CD6BE (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW +7C-A2-36 (hex) Verizon Connect +7CA236 (base 16) Verizon Connect + 5055 North Point Pkwy + Alpharetta GA 30022 + US -70-61-BE (hex) WNC Corporation -7061BE (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW +88-A6-8D (hex) Shanghai MXCHIP Information Technology Co., Ltd. +88A68D (base 16) Shanghai MXCHIP Information Technology Co., Ltd. + 9th Floor, No. 5 Building, 2145 Jinshajiang Rd., Putuo District + Shanghai 200333 + CN -60-E6-F0 (hex) WNC Corporation -60E6F0 (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW +D4-4F-14 (hex) Tesla,Inc. +D44F14 (base 16) Tesla,Inc. + 3500 Deer Creek Rd. + PALO ALTO CA 94304 + US + +34-26-E6 (hex) CIG SHANGHAI CO LTD +3426E6 (base 16) CIG SHANGHAI CO LTD + 5th Floor, Building 8 No 2388 Chenhang Road + SHANGHAI 201114 + CN 38-B8-00 (hex) WNC Corporation 38B800 (base 16) WNC Corporation @@ -138506,42 +138638,6 @@ D44F14 (base 16) Tesla,Inc. Hsin-Chu R.O.C. 308 TW -00-1B-B1 (hex) WNC Corporation -001BB1 (base 16) WNC Corporation - No. 10-1, Li-hsin Road I, Hsinchu Science Park, - Hsinchu 300 - TW - -2C-DC-AD (hex) WNC Corporation -2CDCAD (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW - -B8-B7-F1 (hex) WNC Corporation -B8B7F1 (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW - -44-E4-EE (hex) WNC Corporation -44E4EE (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW - -7C-A2-36 (hex) Verizon Connect -7CA236 (base 16) Verizon Connect - 5055 North Point Pkwy - Alpharetta GA 30022 - US - -88-A6-8D (hex) Shanghai MXCHIP Information Technology Co., Ltd. -88A68D (base 16) Shanghai MXCHIP Information Technology Co., Ltd. - 9th Floor, No. 5 Building, 2145 Jinshajiang Rd., Putuo District - Shanghai 200333 - CN - 28-2E-89 (hex) WNC Corporation 282E89 (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park @@ -138578,6 +138674,12 @@ C06E3D (base 16) SHENZHEN TECNO TECHNOLOGY Wuhan Hubei 430074 CN +94-27-0E (hex) Intel Corporate +94270E (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + 7C-01-3E (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED 7C013E (base 16) GSD VIET NAM TECHNOLOGY COMPANY LIMITED PART OF FACTORY 2, LOT C2.10, D1 STREET, DONG AN 2 INDUSTRIAL PARK, BINHDUONG WARD @@ -138596,29 +138698,29 @@ C06E3D (base 16) SHENZHEN TECNO TECHNOLOGY Shimizu Village Shizuoka Prefecture 424-0926 JP -34-26-E6 (hex) CIG SHANGHAI CO LTD -3426E6 (base 16) CIG SHANGHAI CO LTD - 5th Floor, Building 8 No 2388 Chenhang Road - SHANGHAI 201114 - CN - 54-96-CB (hex) AMPAK Technology Inc. 5496CB (base 16) AMPAK Technology Inc. 6F., No23, Huanke 1st Rd. Zhubei City Hsinchu County 302047 TW +90-41-B2 (hex) Ubiquiti Inc +9041B2 (base 16) Ubiquiti Inc + 685 Third Avenue, 27th Floor + New York NY New York NY 10017 + US + D0-17-B7 (hex) Atios AG D017B7 (base 16) Atios AG Obere Postmatte 19 Seedorf Uri 6462 CH -94-27-0E (hex) Intel Corporate -94270E (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +F0-D3-2B (hex) Juniper Networks +F0D32B (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US 00-1E-D1 (hex) TKH Security B.V. 001ED1 (base 16) TKH Security B.V. @@ -138626,11 +138728,29 @@ D017B7 (base 16) Atios AG Zoetermeer ZH 2712PN NL -90-41-B2 (hex) Ubiquiti Inc -9041B2 (base 16) Ubiquiti Inc - 685 Third Avenue, 27th Floor - New York NY New York NY 10017 - US +18-FD-00 (hex) Marelli +18FD00 (base 16) Marelli + Avenida da Emancipação, 801 + Hostolândia São Paulo 13184-9074 + BR + +A4-C1-39 (hex) Dongguan Huayin Electronic Technology Co., Ltd. +A4C139 (base 16) Dongguan Huayin Electronic Technology Co., Ltd. + Room 101, No.8 Xinglong 3rd Road, Shipai Town + Dongguan Guangdong 523000 + CN + +60-D8-77 (hex) Phyplus Technology (Shanghai) Co., Ltd +60D877 (base 16) Phyplus Technology (Shanghai) Co., Ltd + 3th Floor, Building 23, 676 Wuxing Road, Pudong New District, Shanghai + Shanghai Shanghai 201204 + CN + +84-FC-14 (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD +84FC14 (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD + NO.268, Fuqian Rd, Jutang community, Guanlan Town, Longhua New district + shenzhen guangdong 518000 + CN 54-39-76 (hex) Groq 543976 (base 16) Groq @@ -138668,35 +138788,23 @@ D017B7 (base 16) Atios AG Austin TX 78701 US -F0-D3-2B (hex) Juniper Networks -F0D32B (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US - -18-FD-00 (hex) Marelli -18FD00 (base 16) Marelli - Avenida da Emancipação, 801 - Hostolândia São Paulo 13184-9074 - BR - -A4-C1-39 (hex) Dongguan Huayin Electronic Technology Co., Ltd. -A4C139 (base 16) Dongguan Huayin Electronic Technology Co., Ltd. - Room 101, No.8 Xinglong 3rd Road, Shipai Town - Dongguan Guangdong 523000 +D8-3A-36 (hex) AltoBeam Inc. +D83A36 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 CN -60-D8-77 (hex) Phyplus Technology (Shanghai) Co., Ltd -60D877 (base 16) Phyplus Technology (Shanghai) Co., Ltd - 3th Floor, Building 23, 676 Wuxing Road, Pudong New District, Shanghai - Shanghai Shanghai 201204 +F0-55-82 (hex) Arashi Vision Inc. +F05582 (base 16) Arashi Vision Inc. + Room 1101, 1102, 1103, 11th Floor, Building 2, Jinlitong Financial Center, 1100 Xingye Road, Haiwang Community, Xin'an Street, Bao'an District, Shenzhen, Guangdong, China + Shenzhen 518000 CN -84-FC-14 (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD -84FC14 (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD - NO.268, Fuqian Rd, Jutang community, Guanlan Town, Longhua New district - shenzhen guangdong 518000 - CN +6C-76-F7 (hex) MainStreaming SpA +6C76F7 (base 16) MainStreaming SpA + Viale Sarca, 336/F + Milan MI 20126 + IT 68-AB-A9 (hex) Sagemcom Broadband SAS 68ABA9 (base 16) Sagemcom Broadband SAS @@ -138704,11 +138812,11 @@ A4C139 (base 16) Dongguan Huayin Electronic Technology Co., Ltd. Rueil Malmaison Cedex hauts de seine 92848 FR -6C-76-F7 (hex) MainStreaming SpA -6C76F7 (base 16) MainStreaming SpA - Viale Sarca, 336/F - Milan MI 20126 - IT +F0-BC-50 (hex) Mellanox Technologies, Inc. +F0BC50 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US 40-A4-4A (hex) Google, Inc. 40A44A (base 16) Google, Inc. @@ -138716,22 +138824,34 @@ A4C139 (base 16) Dongguan Huayin Electronic Technology Co., Ltd. Mountain View CA 94043 US -D8-3A-36 (hex) AltoBeam Inc. -D83A36 (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 +0C-F2-F5 (hex) Sichuan AI-Link Technology Co., Ltd. +0CF2F5 (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou, Industrial Park + Mianyang Sichuan 622650 CN -F0-55-82 (hex) Arashi Vision Inc. -F05582 (base 16) Arashi Vision Inc. - Room 1101, 1102, 1103, 11th Floor, Building 2, Jinlitong Financial Center, 1100 Xingye Road, Haiwang Community, Xin'an Street, Bao'an District, Shenzhen, Guangdong, China - Shenzhen 518000 +D0-45-8D (hex) Shenzhen Skyworth Digital Technology CO., Ltd +D0458D (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, + Shenzhen Guangdong 518057 CN -F0-BC-50 (hex) Mellanox Technologies, Inc. -F0BC50 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 +9C-24-10 (hex) Bouffalo Lab (Nanjing) Co., Ltd. +9C2410 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. + 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China + Nanjing Jiangsu 211800 + CN + +94-18-65 (hex) NETGEAR +941865 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US + +E0-46-EE (hex) NETGEAR +E046EE (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US 00-18-4D (hex) NETGEAR @@ -138782,35 +138902,41 @@ B03956 (base 16) NETGEAR San Jose CA 95134 US -0C-F2-F5 (hex) Sichuan AI-Link Technology Co., Ltd. -0CF2F5 (base 16) Sichuan AI-Link Technology Co., Ltd. - Anzhou, Industrial Park - Mianyang Sichuan 622650 - CN - -D0-45-8D (hex) Shenzhen Skyworth Digital Technology CO., Ltd -D0458D (base 16) Shenzhen Skyworth Digital Technology CO., Ltd - 4F,Block A, Skyworth?Building, - Shenzhen Guangdong 518057 - CN - 3C-1A-CC (hex) Quectel Wireless Solutions Co.,Ltd. 3C1ACC (base 16) Quectel Wireless Solutions Co.,Ltd. 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District Shanghai 200233 CN +38-FB-A0 (hex) Shenzhen Baseus Technology CoLtd +38FBA0 (base 16) Shenzhen Baseus Technology CoLtd + 2nd Floor, Building B, Baseus Intelligence Park, No.2008, Xuegang Rd,Gangtou Community,Bantian Street, Longgang District + Shenzhen 518172 + CN + A4-B2-56 (hex) Shenzhen Incar Technology Co., Ltd. A4B256 (base 16) Shenzhen Incar Technology Co., Ltd. 18th Floor, Zhongxi ECO Building, Shuiku Road, Xixiang Street, Bao'an District, Shenzhen City Guangdong Province 518102 CN -9C-24-10 (hex) Bouffalo Lab (Nanjing) Co., Ltd. -9C2410 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. - 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China - Nanjing Jiangsu 211800 - CN +58-98-35 (hex) Vantiva Technologies Belgium +589835 (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE + +C4-EA-1D (hex) Vantiva Technologies Belgium +C4EA1D (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE + +A4-91-B1 (hex) Vantiva Technologies Belgium +A491B1 (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE D8-E3-74 (hex) Xiaomi Communications Co Ltd D8E374 (base 16) Xiaomi Communications Co Ltd @@ -138818,17 +138944,17 @@ D8E374 (base 16) Xiaomi Communications Co Ltd Beijing Haidian District 100085 CN -94-18-65 (hex) NETGEAR -941865 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 +D8-78-F0 (hex) Silicon Laboratories +D878F0 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 US -E0-46-EE (hex) NETGEAR -E046EE (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US +D4-35-1D (hex) Vantiva Technologies Belgium +D4351D (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE 60-3F-FB (hex) Telink Micro LLC 603FFB (base 16) Telink Micro LLC @@ -138836,6 +138962,18 @@ E046EE (base 16) NETGEAR Santa Clara 95054 US +00-7A-A4 (hex) FRITZ! Technology GmbH +007AA4 (base 16) FRITZ! Technology GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +20-B8-3D (hex) UNIONMAN TECHNOLOGY CO.,LTD +20B83D (base 16) UNIONMAN TECHNOLOGY CO.,LTD + No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway + Huizhou Guangdong 516025 + CN + AC-EA-70 (hex) ZUNDA Inc. ACEA70 (base 16) ZUNDA Inc. 3/F Kamon Bldg, Shibuya 2-6-11 @@ -138854,30 +138992,6 @@ ACEA70 (base 16) ZUNDA Inc. Nanzi Dist. Kaohsiung 811643 TW -38-FB-A0 (hex) Shenzhen Baseus Technology CoLtd -38FBA0 (base 16) Shenzhen Baseus Technology CoLtd - 2nd Floor, Building B, Baseus Intelligence Park, No.2008, Xuegang Rd,Gangtou Community,Bantian Street, Longgang District - Shenzhen 518172 - CN - -C4-EA-1D (hex) Vantiva Technologies Belgium -C4EA1D (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE - -A4-91-B1 (hex) Vantiva Technologies Belgium -A491B1 (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE - -D4-35-1D (hex) Vantiva Technologies Belgium -D4351D (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE - EC-5B-71 (hex) Inventec(Chongqing) Corporation EC5B71 (base 16) Inventec(Chongqing) Corporation No.66 West District 2nd Rd, Shapingba District @@ -138896,11 +139010,17 @@ EC5B71 (base 16) Inventec(Chongqing) Corporation Dongguan 523808 CN -D8-78-F0 (hex) Silicon Laboratories -D878F0 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US +B0-FB-15 (hex) Ezurio, LLC +B0FB15 (base 16) Ezurio, LLC + 3F.-1, No.145, Xianzheng 9th Rd., + Zhubei 30251 + TW + +EC-C0-7A (hex) Ezurio, LLC +ECC07A (base 16) Ezurio, LLC + 3F.-1, No.145, Xianzheng 9th Rd., + Zhubei 30251 + TW 2C-2F-F4 (hex) eero inc. 2C2FF4 (base 16) eero inc. @@ -138908,60 +139028,12 @@ D878F0 (base 16) Silicon Laboratories San Francisco CA 94107 US -58-98-35 (hex) Vantiva Technologies Belgium -589835 (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE - -3C-49-FF (hex) UNIONMAN TECHNOLOGY CO.,LTD -3C49FF (base 16) UNIONMAN TECHNOLOGY CO.,LTD - No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway - Huizhou Guangdong 516025 - CN - -00-7A-A4 (hex) FRITZ! Technology GmbH -007AA4 (base 16) FRITZ! Technology GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE - -20-B8-3D (hex) UNIONMAN TECHNOLOGY CO.,LTD -20B83D (base 16) UNIONMAN TECHNOLOGY CO.,LTD - No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway - Huizhou Guangdong 516025 - CN - -4C-E6-50 (hex) Apple, Inc. -4CE650 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -24-55-9A (hex) Apple, Inc. -24559A (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - 24-53-ED (hex) Dell Inc. 2453ED (base 16) Dell Inc. One Dell Way Round Rock TX 78682 US -B0-FB-15 (hex) Ezurio, LLC -B0FB15 (base 16) Ezurio, LLC - 3F.-1, No.145, Xianzheng 9th Rd., - Zhubei 30251 - TW - -EC-C0-7A (hex) Ezurio, LLC -ECC07A (base 16) Ezurio, LLC - 3F.-1, No.145, Xianzheng 9th Rd., - Zhubei 30251 - TW - A4-CF-03 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. A4CF03 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. @@ -139004,28 +139076,22 @@ B082E2 (base 16) ASUSTek COMPUTER INC. Taipei Taiwan 112 TW -6C-68-A4 (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. -6C68A4 (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. - 13/F, Building 1, No.13 Bohua 4th Road, Huangpu District - Guangzhou Guangdong 510663 +3C-49-FF (hex) UNIONMAN TECHNOLOGY CO.,LTD +3C49FF (base 16) UNIONMAN TECHNOLOGY CO.,LTD + No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway + Huizhou Guangdong 516025 CN -90-C9-52 (hex) Durin, Inc -90C952 (base 16) Durin, Inc - 440 N Wolfe Rd - Sunnyvale CA 94085 +24-55-9A (hex) Apple, Inc. +24559A (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -DC-22-6F (hex) HangZhou Nano IC Technologies Co., Ltd -DC226F (base 16) HangZhou Nano IC Technologies Co., Ltd - No. 11, F Building, 1st Floor, Building A, Tiantang Software Park, 3 West Doudimen Road, Xihu District - Hangzhou Zhejiang 310013 - CN - -0C-C7-63 (hex) eero inc. -0CC763 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +4C-E6-50 (hex) Apple, Inc. +4CE650 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US EC-1A-C3 (hex) Ugreen Group Limited @@ -139046,12 +139112,6 @@ BCBCCA (base 16) Huawei Device Co., Ltd. Dongguan Guangdong 523808 CN -B8-64-68 (hex) BBSakura Networks, Inc. -B86468 (base 16) BBSakura Networks, Inc. - Sumitomo Fudosan Nishishinjuku Building, 7-20-1 Nishi-shinjuku - Shinjuku-ku Tokyo 160-0023 - JP - A0-FD-D9 (hex) UNIONMAN TECHNOLOGY CO.,LTD A0FDD9 (base 16) UNIONMAN TECHNOLOGY CO.,LTD No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway @@ -139064,17 +139124,11 @@ BC2B1E (base 16) Cresyn Co., Ltd. Seoul CRESYN B/D, Gangnam-daero 107-gil 06254 KR -08-6A-0B (hex) Cisco Meraki -086A0B (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 - US - -C8-63-40 (hex) Cisco Meraki -C86340 (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 - US +6C-68-A4 (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. +6C68A4 (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. + 13/F, Building 1, No.13 Bohua 4th Road, Huangpu District + Guangzhou Guangdong 510663 + CN A4-C0-B0 (hex) Drivenets A4C0B0 (base 16) Drivenets @@ -139082,28 +139136,22 @@ A4C0B0 (base 16) Drivenets Raanana Israel 4366235 IL -34-F0-15 (hex) Beijing Xiaomi Mobile Software Co., Ltd -34F015 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 - CN +90-C9-52 (hex) Durin, Inc +90C952 (base 16) Durin, Inc + 440 N Wolfe Rd + Sunnyvale CA 94085 + US -A0-F2-62 (hex) Espressif Inc. -A0F262 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +DC-22-6F (hex) HangZhou Nano IC Technologies Co., Ltd +DC226F (base 16) HangZhou Nano IC Technologies Co., Ltd + No. 11, F Building, 1st Floor, Building A, Tiantang Software Park, 3 West Doudimen Road, Xihu District + Hangzhou Zhejiang 310013 CN -A0-58-66 (hex) Qorvo Utrecht B.V. -A05866 (base 16) Qorvo Utrecht B.V. - Leidseveer 10 - Utrecht Utrecht 3511 SB - NL - -7C-6D-12 (hex) Microsoft Corporation -7C6D12 (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 +0C-C7-63 (hex) eero inc. +0CC763 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 US 98-A9-42 (hex) Tozed Kangwei Tech Co., Ltd @@ -139124,48 +139172,36 @@ ACE011 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -CC-C4-B2 (hex) Shenzhen Trolink Technology Co.,LTD -CCC4B2 (base 16) Shenzhen Trolink Technology Co.,LTD - 7th Floor, Building 5, Phase 2, Donghua Intelligent Manufacturing Park, Sanwei Community, Hangcheng Street, Bao'an District - Shenzhen Guangdong 518101 - CN +B8-64-68 (hex) BBSakura Networks, Inc. +B86468 (base 16) BBSakura Networks, Inc. + Sumitomo Fudosan Nishishinjuku Building, 7-20-1 Nishi-shinjuku + Shinjuku-ku Tokyo 160-0023 + JP -60-B4-A2 (hex) Samsung Electronics Co.,Ltd -60B4A2 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +08-6A-0B (hex) Cisco Meraki +086A0B (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US -14-0B-9E (hex) Samsung Electronics Co.,Ltd -140B9E (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +C8-63-40 (hex) Cisco Meraki +C86340 (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US -64-CE-0C (hex) Funshion Online Technologies Co.,Ltd -64CE0C (base 16) Funshion Online Technologies Co.,Ltd - 2101, Floor 1-2, Building 9, Anzhen Xili District 3, Chaoyang District, Beijing - Beijing 100029 +34-F0-15 (hex) Beijing Xiaomi Mobile Software Co., Ltd +34F015 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN -84-48-80 (hex) Amazon Technologies Inc. -844880 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US - FC-26-8C (hex) Signify B.V. FC268C (base 16) Signify B.V. High Tech Campus 7 Eindhoven 5656AE NL -30-F0-3A (hex) UEI Electronics Private Ltd. -30F03A (base 16) UEI Electronics Private Ltd. - #49, 1 st floor, East wing, Khanjabhavan, Racecourse Road. - Bengaluru Karnataka 560001 - IN - E8-3D-C1 (hex) Espressif Inc. E83DC1 (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area @@ -139190,12 +139226,60 @@ B8C924 (base 16) Cisco Systems, Inc San Jose CA 94568 US +A0-F2-62 (hex) Espressif Inc. +A0F262 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +A0-58-66 (hex) Qorvo Utrecht B.V. +A05866 (base 16) Qorvo Utrecht B.V. + Leidseveer 10 + Utrecht Utrecht 3511 SB + NL + +7C-6D-12 (hex) Microsoft Corporation +7C6D12 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US + +CC-C4-B2 (hex) Shenzhen Trolink Technology Co.,LTD +CCC4B2 (base 16) Shenzhen Trolink Technology Co.,LTD + 7th Floor, Building 5, Phase 2, Donghua Intelligent Manufacturing Park, Sanwei Community, Hangcheng Street, Bao'an District + Shenzhen Guangdong 518101 + CN + +60-B4-A2 (hex) Samsung Electronics Co.,Ltd +60B4A2 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +14-0B-9E (hex) Samsung Electronics Co.,Ltd +140B9E (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +64-CE-0C (hex) Funshion Online Technologies Co.,Ltd +64CE0C (base 16) Funshion Online Technologies Co.,Ltd + 2101, Floor 1-2, Building 9, Anzhen Xili District 3, Chaoyang District, Beijing + Beijing 100029 + CN + 88-13-C2 (hex) Tendyron Corporation 8813C2 (base 16) Tendyron Corporation Tendyron Building,Zhongguancun NO.1 Park,Beiqing Road,Haidian District,Beijing,China Beijing 100000 CN +84-48-80 (hex) Amazon Technologies Inc. +844880 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + 00-12-4F (hex) Chemelex LLC 00124F (base 16) Chemelex LLC 1665 Utica Avenue, Suite 700 @@ -139214,17 +139298,11 @@ B4DDD0 (base 16) AUMOVIO Hungary Kft. Budapest Pest H-1106 HU -44-BD-C8 (hex) Xiaomi Communications Co Ltd -44BDC8 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN - -30-48-7D (hex) Tuya Smart Inc. -30487D (base 16) Tuya Smart Inc. - 160 Greentree Drive, Suite 101 - Dover DE 19904 - US +30-F0-3A (hex) UEI Electronics Private Ltd. +30F03A (base 16) UEI Electronics Private Ltd. + #49, 1 st floor, East wing, Khanjabhavan, Racecourse Road. + Bengaluru Karnataka 560001 + IN 94-2D-3A (hex) PRIZOR VIZTECH LIMITED 942D3A (base 16) PRIZOR VIZTECH LIMITED @@ -139238,30 +139316,24 @@ B4DDD0 (base 16) AUMOVIO Hungary Kft. San Jose CA 95002 US +44-BD-C8 (hex) Xiaomi Communications Co Ltd +44BDC8 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + +30-48-7D (hex) Tuya Smart Inc. +30487D (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 + US + D0-CD-BF (hex) LG Electronics D0CDBF (base 16) LG Electronics 222 LG-ro, JINWI-MYEON Pyeongtaek-si Gyeonggi-do 451-713 KR -A4-F0-1F (hex) CANON INC. -A4F01F (base 16) CANON INC. - 30-2 Shimomaruko 3-chome, - Ohta-ku Tokyo 146-8501 - JP - -90-B9-F9 (hex) Motorola Mobility LLC, a Lenovo Company -90B9F9 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 - US - -04-6F-00 (hex) LG Electronics -046F00 (base 16) LG Electronics - Lot CN02, Trang Due Industrial Park,An Phong Ward, Hai Phong City, Vietnam - Hai Phong 184956 - VN - 94-EA-E7 (hex) Lynq Technologies 94EAE7 (base 16) Lynq Technologies 11101 West 120th Avenue @@ -139280,23 +139352,23 @@ A4F01F (base 16) CANON INC. Hawthorne 90250 US -D8-3E-EB (hex) AltoBeam Inc. -D83EEB (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 - CN +A4-F0-1F (hex) CANON INC. +A4F01F (base 16) CANON INC. + 30-2 Shimomaruko 3-chome, + Ohta-ku Tokyo 146-8501 + JP -5C-48-79 (hex) HUAWEI TECHNOLOGIES CO.,LTD -5C4879 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +90-B9-F9 (hex) Motorola Mobility LLC, a Lenovo Company +90B9F9 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US -DC-57-5C (hex) PR Electronics A/S -DC575C (base 16) PR Electronics A/S - Lerbakken 10 - Følle 8410 - DK +04-6F-00 (hex) LG Electronics +046F00 (base 16) LG Electronics + Lot CN02, Trang Due Industrial Park,An Phong Ward, Hai Phong City, Vietnam + Hai Phong 184956 + VN 5C-13-AC (hex) Apple, Inc. 5C13AC (base 16) Apple, Inc. @@ -139316,18 +139388,6 @@ DC575C (base 16) PR Electronics A/S Cupertino CA 95014 US -54-6C-50 (hex) Nanjing Qinheng Microelectronics Co., Ltd. -546C50 (base 16) Nanjing Qinheng Microelectronics Co., Ltd. - No.18, Ningshuang Road - Nanjing Jiangsu 210012 - CN - -E4-85-FB (hex) Quectel Wireless Solutions Co.,Ltd. -E485FB (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 - CN - 24-2A-EA (hex) Apple, Inc. 242AEA (base 16) Apple, Inc. 1 Infinite Loop @@ -139340,18 +139400,24 @@ E485FB (base 16) Quectel Wireless Solutions Co.,Ltd. Cupertino CA 95014 US -B8-BA-66 (hex) Microsoft Corporation -B8BA66 (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 - US - -60-65-F4 (hex) Chipsea Technologies (Shenzhen) Crop. -6065F4 (base 16) Chipsea Technologies (Shenzhen) Crop. - Room 301, Building 1, Shenzhen Bay Innovation and Technology Center, Keyuan Avenue, High-tech Zone Community, Yuehai Subdistrict, Nanshan District, Shenzhen - Shenzhen 518000 +D8-3E-EB (hex) AltoBeam Inc. +D83EEB (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 CN +5C-48-79 (hex) HUAWEI TECHNOLOGIES CO.,LTD +5C4879 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +DC-57-5C (hex) PR Electronics A/S +DC575C (base 16) PR Electronics A/S + Lerbakken 10 + Følle 8410 + DK + 44-66-90 (hex) TP-LINK TECHNOLOGIES CO.,LTD. 446690 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan @@ -139370,23 +139436,23 @@ B8BA66 (base 16) Microsoft Corporation Shenzhen Guangdong 518000 CN -58-9E-C6 (hex) Gigaset Technologies GmbH -589EC6 (base 16) Gigaset Technologies GmbH - Frankenstrasse 2 - Bocholt NRW 46395 - DE +54-6C-50 (hex) Nanjing Qinheng Microelectronics Co., Ltd. +546C50 (base 16) Nanjing Qinheng Microelectronics Co., Ltd. + No.18, Ningshuang Road + Nanjing Jiangsu 210012 + CN -E8-47-F3 (hex) upscale ai -E847F3 (base 16) upscale ai - 3101 Jay St. - Santa Clara CA 95054 - US +E4-85-FB (hex) Quectel Wireless Solutions Co.,Ltd. +E485FB (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 + CN -B0-7A-16 (hex) ROEHN -B07A16 (base 16) ROEHN - Av. Manuel Bandeira, 291 - Sao Paulo Sp 05317020 - BR +B8-BA-66 (hex) Microsoft Corporation +B8BA66 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US 28-AD-EA (hex) Mallow SAS 28ADEA (base 16) Mallow SAS @@ -139394,17 +139460,23 @@ B07A16 (base 16) ROEHN Paris IDF 75001 FR -98-12-23 (hex) Tarmoc Network LTD -981223 (base 16) Tarmoc Network LTD - Taihao Road No.22, 6th Floor, Sandong Town, Huicheng District, Huizhou City - Huizhou City GuangDong 518000 +60-65-F4 (hex) Chipsea Technologies (Shenzhen) Crop. +6065F4 (base 16) Chipsea Technologies (Shenzhen) Crop. + Room 301, Building 1, Shenzhen Bay Innovation and Technology Center, Keyuan Avenue, High-tech Zone Community, Yuehai Subdistrict, Nanshan District, Shenzhen + Shenzhen 518000 CN -84-C6-65 (hex) Taicang T&W Electronics -84C665 (base 16) Taicang T&W Electronics - 89# Jiang Nan RD - Suzhou Jiangsu 215412 - CN +B0-7A-16 (hex) ROEHN +B07A16 (base 16) ROEHN + Av. Manuel Bandeira, 291 + Sao Paulo Sp 05317020 + BR + +58-9E-C6 (hex) Gigaset Technologies GmbH +589EC6 (base 16) Gigaset Technologies GmbH + Frankenstrasse 2 + Bocholt NRW 46395 + DE B8-61-FC (hex) Juniper Networks B861FC (base 16) Juniper Networks @@ -139412,10 +139484,10 @@ B861FC (base 16) Juniper Networks Sunnyvale CA 94089 US -08-3C-03 (hex) IEEE Registration Authority -083C03 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +E8-47-F3 (hex) upscale ai +E847F3 (base 16) upscale ai + 3101 Jay St. + Santa Clara CA 95054 US 40-68-F9 (hex) Shenzhen SuperElectron Technology Co.,Ltd. @@ -139424,41 +139496,23 @@ B861FC (base 16) Juniper Networks Shenzhen Guangdong 518000 CN -14-49-C5 (hex) Huawei Device Co., Ltd. -1449C5 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - -B4-54-F2 (hex) Huawei Device Co., Ltd. -B454F2 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - -80-C2-F0 (hex) Xiamen Yeastar Digital Technology Co.,Ltd -80C2F0 (base 16) Xiamen Yeastar Digital Technology Co.,Ltd - Building C09, Software Park Phase III, Xiamen 361024, Fujian, China - XIAMEN FUJIAN 361024 +98-12-23 (hex) Tarmoc Network LTD +981223 (base 16) Tarmoc Network LTD + Taihao Road No.22, 6th Floor, Sandong Town, Huicheng District, Huizhou City + Huizhou City GuangDong 518000 CN -FC-66-37 (hex) Sagemcom Broadband SAS -FC6637 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 2C-F8-EC (hex) Quectel Wireless Solutions Co.,Ltd. 2CF8EC (base 16) Quectel Wireless Solutions Co.,Ltd. 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District Shanghai 200233 CN -68-F9-0F (hex) Intel Corporate -68F90F (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +84-C6-65 (hex) Taicang T&W Electronics +84C665 (base 16) Taicang T&W Electronics + 89# Jiang Nan RD + Suzhou Jiangsu 215412 + CN 6C-91-88 (hex) Nokia 6C9188 (base 16) Nokia @@ -139466,6 +139520,12 @@ FC6637 (base 16) Sagemcom Broadband SAS Kanata Ontario K2K 2E6 CA +08-3C-03 (hex) IEEE Registration Authority +083C03 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + 00-09-D8 (hex) Telia Company AB 0009D8 (base 16) Telia Company AB Östermalmsgatan 63A @@ -139478,24 +139538,48 @@ FC6637 (base 16) Sagemcom Broadband SAS TAIPEI 66.Chung Shan RD, TU-CHENG Industrial , district new TAIPEI 33859 CN -54-A0-AB (hex) Maiyue Future Intelligent Technology (Suzhou) Co.,Ltd. -54A0AB (base 16) Maiyue Future Intelligent Technology (Suzhou) Co.,Ltd. - Room 1283, Building 3, No.288 Jiushenggang Road, Guoxiang Street, Economic Development Zone, Suzhou,China - Suzhou 215000 +14-49-C5 (hex) Huawei Device Co., Ltd. +1449C5 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -DC-7E-F5 (hex) HUAWEI TECHNOLOGIES CO.,LTD -DC7EF5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +B4-54-F2 (hex) Huawei Device Co., Ltd. +B454F2 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN +68-F9-0F (hex) Intel Corporate +68F90F (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + D0-8E-17 (hex) ACCTON TECHNOLOGY CORPORATION D08E17 (base 16) ACCTON TECHNOLOGY CORPORATION No.1, Creation Road 3, Hsinchu Science Park, Hsinchu 30077 TW +54-A0-AB (hex) Maiyue Future Intelligent Technology (Suzhou) Co.,Ltd. +54A0AB (base 16) Maiyue Future Intelligent Technology (Suzhou) Co.,Ltd. + Room 1283, Building 3, No.288 Jiushenggang Road, Guoxiang Street, Economic Development Zone, Suzhou,China + Suzhou 215000 + CN + +80-C2-F0 (hex) Xiamen Yeastar Digital Technology Co.,Ltd +80C2F0 (base 16) Xiamen Yeastar Digital Technology Co.,Ltd + Building C09, Software Park Phase III, Xiamen 361024, Fujian, China + XIAMEN FUJIAN 361024 + CN + +FC-66-37 (hex) Sagemcom Broadband SAS +FC6637 (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR + E4-C8-01 (hex) BLU Products Inc E4C801 (base 16) BLU Products Inc 8600 NW 36th Street Suite 200 @@ -139508,12 +139592,42 @@ E4C801 (base 16) BLU Products Inc PARIS IdF 75008 FR +24-64-77 (hex) Beijing Xiaomi Mobile Software Co., Ltd +246477 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 + CN + +7C-B4-0F (hex) Fibocom Wireless Inc. +7CB40F (base 16) Fibocom Wireless Inc. + 1101,Tower A, Building 6, Shenzhen International Innovation Valley, Dashi 1st Rd, Nanshan + Shenzhen Guangdong 518055 + CN + +50-9F-B9 (hex) Shenzhen Skyworth Digital Technology CO., Ltd +509FB9 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, + Shenzhen Guangdong 518057 + CN + +DC-7E-F5 (hex) HUAWEI TECHNOLOGIES CO.,LTD +DC7EF5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + 8C-D0-66 (hex) Texas Instruments 8CD066 (base 16) Texas Instruments 12500 TI Blvd Dallas TX 75243 US +44-63-C2 (hex) Zyxel Communications Corporation +4463C2 (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW + 34-20-D3 (hex) SHENZHEN IP-COM NETWORKS CO.,LTD. 3420D3 (base 16) SHENZHEN IP-COM NETWORKS CO.,LTD. Room 101, Unit A, First Floor, Tower E3, No. 1001, Zhongshanyuan Road, Nanshan District, Shenzhen,China @@ -139526,47 +139640,53 @@ E4C801 (base 16) BLU Products Inc Haizhu District Guangzhou 510000 CN -24-64-77 (hex) Beijing Xiaomi Mobile Software Co., Ltd -246477 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 - CN - 64-6B-E7 (hex) Qingdao Intelligent&Precise Electronics Co.,Ltd. 646BE7 (base 16) Qingdao Intelligent&Precise Electronics Co.,Ltd. No.218 Qianwangang Road Qingdao Shangdong 266510 CN -7C-B4-0F (hex) Fibocom Wireless Inc. -7CB40F (base 16) Fibocom Wireless Inc. - 1101,Tower A, Building 6, Shenzhen International Innovation Valley, Dashi 1st Rd, Nanshan - Shenzhen Guangdong 518055 - CN - -50-9F-B9 (hex) Shenzhen Skyworth Digital Technology CO., Ltd -509FB9 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd - 4F,Block A, Skyworth?Building, - Shenzhen Guangdong 518057 - CN - 24-8A-B3 (hex) ICTK Co., Ltd. 248AB3 (base 16) ICTK Co., Ltd. 13F, JACE Tower, 16, Gangnam-daero 84-gil, Gangnam-gu Seoul Select State 06241 KR +7C-62-E7 (hex) Cisco Systems, Inc +7C62E7 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + 10-D8-B1 (hex) AUO Corporation 10D8B1 (base 16) AUO Corporation No. 1, Li-Hsin Rd. 2, Hsinchu Science Park, Hsinchu 300094 TW -44-63-C2 (hex) Zyxel Communications Corporation -4463C2 (base 16) Zyxel Communications Corporation - No. 6 Innovation Road II, Science Park - Hsichu Taiwan 300 - TW +BC-00-23 (hex) Honor Device Co., Ltd. +BC0023 (base 16) Honor Device Co., Ltd. + Suite 3401, Unit A, Building 6, Shum Yip Sky Park, No. 8089, Hongli West Road, Xiangmihu Street, Futian District + Shenzhen Guangdong 518040 + CN + +88-7C-C1 (hex) Zebronics India Pvt Ltd +887CC1 (base 16) Zebronics India Pvt Ltd + No 13/7, Smith Road, Royapettah + Chennai Tamil Nadu 600002 + IN + +3C-CB-01 (hex) Beijing Lingji innovations Technology Co., LTD. +3CCB01 (base 16) Beijing Lingji innovations Technology Co., LTD. + Room 106, 1F, A1 Bldg. Zhongguancun Dongsheng Technology Park (Northern Territory), No. 66, Xixiaokou Rd, Haidian Dist., Beijing, China + Beijing Beijing 100192 + CN + +18-14-54 (hex) CIG SHANGHAI CO LTD +181454 (base 16) CIG SHANGHAI CO LTD + 5th Floor, Building 8 No 2388 Chenhang Road + SHANGHAI 201114 + CN 9C-FA-96 (hex) T3 Technology Co., Ltd. 9CFA96 (base 16) T3 Technology Co., Ltd. @@ -139586,60 +139706,30 @@ E4FEE4 (base 16) Ciena Corporation Kyoto 600-8530 JP -60-7A-D8 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -607AD8 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 - CN - 48-78-5B (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. 48785B (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. No.555 Qianmo Road Hangzhou Zhejiang 310052 CN +60-7A-D8 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +607AD8 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 + CN + F0-74-C1 (hex) Blink by Amazon F074C1 (base 16) Blink by Amazon 100 Riverpark Drive North Reading MA 01864 US -7C-62-E7 (hex) Cisco Systems, Inc -7C62E7 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -18-14-54 (hex) CIG SHANGHAI CO LTD -181454 (base 16) CIG SHANGHAI CO LTD - 5th Floor, Building 8 No 2388 Chenhang Road - SHANGHAI 201114 - CN - 00-0B-7C (hex) Electro-Voice Dynacord LLC 000B7C (base 16) Electro-Voice Dynacord LLC 130 Perinton Parkway Fairport NY 14450 US -BC-00-23 (hex) Honor Device Co., Ltd. -BC0023 (base 16) Honor Device Co., Ltd. - Suite 3401, Unit A, Building 6, Shum Yip Sky Park, No. 8089, Hongli West Road, Xiangmihu Street, Futian District - Shenzhen Guangdong 518040 - CN - -88-7C-C1 (hex) Zebronics India Pvt Ltd -887CC1 (base 16) Zebronics India Pvt Ltd - No 13/7, Smith Road, Royapettah - Chennai Tamil Nadu 600002 - IN - -3C-CB-01 (hex) Beijing Lingji innovations Technology Co., LTD. -3CCB01 (base 16) Beijing Lingji innovations Technology Co., LTD. - Room 106, 1F, A1 Bldg. Zhongguancun Dongsheng Technology Park (Northern Territory), No. 66, Xixiaokou Rd, Haidian Dist., Beijing, China - Beijing Beijing 100192 - CN - 8C-4E-BB (hex) Amazon Technologies Inc. 8C4EBB (base 16) Amazon Technologies Inc. P.O Box 8102 @@ -139664,6 +139754,66 @@ CCBE61 (base 16) Apple, Inc. Cupertino CA 95014 US +B8-57-D6 (hex) Cisco Systems, Inc +B857D6 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +E4-02-74 (hex) FW Murphy Production Controls +E40274 (base 16) FW Murphy Production Controls + 4646 S Harvard Ave + Tulsa OK 74135 + US + +14-0A-02 (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD +140A02 (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD + NO.268, Fuqian Rd, Jutang community, Guanlan Town, Longhua New district + shenzhen guangdong 518000 + CN + +38-E1-58 (hex) Flaircomm Microelectronics,Inc. +38E158 (base 16) Flaircomm Microelectronics,Inc. + 7F,Guomai Building,Guomai Science and Technology Park,116 Jiangbin East Avenue,Mawei District,Fuzhou City + Fuzhou FUJIAN 350015 + CN + +00-7F-1D (hex) Fantasia Trading LLC +007F1D (base 16) Fantasia Trading LLC + 5350 Ontario Mills Pkwy, Suite 100 + Ontario CA 91764 + US + +58-21-9D (hex) Shanghai Timar Integrated Circuit Co., LTD +58219D (base 16) Shanghai Timar Integrated Circuit Co., LTD + Room 1208, No. 999 West Zhongshan Road, Changning District, Shanghai, China + shanghai shanghai 200030 + CN + +3C-7F-6E (hex) Xiaomi Communications Co Ltd +3C7F6E (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + +90-64-9B (hex) Espressif Inc. +90649B (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +40-79-55 (hex) Datacolor +407955 (base 16) Datacolor + 2 Shengpu Road, Suzhou Industrial Park, Export Processing Zone B, Suzhou, Jiangsu, P.R. China + Suzhou 215000 + CN + +00-07-8B (hex) Wegener Communications, Inc. +00078B (base 16) Wegener Communications, Inc. + 930 Interstate Ridge Drive, Ste. A, + Gainesville GA 30501 + US + B0-0C-9D (hex) Quectel Wireless Solutions Co.,Ltd. B00C9D (base 16) Quectel Wireless Solutions Co.,Ltd. 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District @@ -169556,12 +169706,6 @@ A07332 (base 16) Cashmaster International Limited Pune Maharashtra 411 038 IN -00-17-1E (hex) Theo Benning GmbH & Co. KG -00171E (base 16) Theo Benning GmbH & Co. KG - Muensterstraße 135-137 - Bocholt NRW 46397 - DE - 00-17-12 (hex) ISCO International 001712 (base 16) ISCO International 1001 Cambridge Drive @@ -184202,23 +184346,17 @@ E467A6 (base 16) BSH Hausgeräte GmbH Suzhou 215000 CN -74-A5-7E (hex) Panasonic Ecology Systems -74A57E (base 16) Panasonic Ecology Systems - 4017, Azashimonakata, Takaki-cho - Kasugai Aichi 4868522 - JP - -7C-E9-13 (hex) Fantasia Trading LLC -7CE913 (base 16) Fantasia Trading LLC - 5350 Ontario Mills Pkwy, Suite 100 - Ontario CA 91764 - US +30-D5-1F (hex) Prolights +30D51F (base 16) Prolights + Via Adriano Olivetti snc + Minturno Latina 04026 + IT -E0-CB-BC (hex) Cisco Meraki -E0CBBC (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 - US +18-FB-8E (hex) VusionGroup +18FB8E (base 16) VusionGroup + Kalsdorfer Straße 12 + Fernitz-Mellach Steiermark 8072 + AT 68-3A-1E (hex) Cisco Meraki 683A1E (base 16) Cisco Meraki @@ -184238,23 +184376,35 @@ F89E28 (base 16) Cisco Meraki San Francisco 94158 US +74-A5-7E (hex) Panasonic Ecology Systems +74A57E (base 16) Panasonic Ecology Systems + 4017, Azashimonakata, Takaki-cho + Kasugai Aichi 4868522 + JP + +6C-15-DB (hex) Arcadyan Corporation +6C15DB (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW + +7C-E9-13 (hex) Fantasia Trading LLC +7CE913 (base 16) Fantasia Trading LLC + 5350 Ontario Mills Pkwy, Suite 100 + Ontario CA 91764 + US + 38-2A-8B (hex) nFore Technology Co., Ltd. 382A8B (base 16) nFore Technology Co., Ltd. 5F, No.31, Ln. 258, Ruiguang Rd., Neihu Dist., Taipei city 114 TW -18-FB-8E (hex) VusionGroup -18FB8E (base 16) VusionGroup - Kalsdorfer Straße 12 - Fernitz-Mellach Steiermark 8072 - AT - -30-D5-1F (hex) Prolights -30D51F (base 16) Prolights - Via Adriano Olivetti snc - Minturno Latina 04026 - IT +E0-CB-BC (hex) Cisco Meraki +E0CBBC (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US D4-68-BA (hex) Shenzhen Sundray Technologies company Limited D468BA (base 16) Shenzhen Sundray Technologies company Limited @@ -184274,6 +184424,12 @@ D468BA (base 16) Shenzhen Sundray Technologies company Limited Shenzhen Guangdong 518057 CN +08-B3-D6 (hex) Huawei Device Co., Ltd. +08B3D6 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + 78-2B-60 (hex) Huawei Device Co., Ltd. 782B60 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone @@ -184292,36 +184448,24 @@ FC79DD (base 16) Huawei Device Co., Ltd. Dongguan Guangdong 523808 CN -F4-14-BF (hex) LG Innotek -F414BF (base 16) LG Innotek - 26, HANAMSANDAN 5BEON-RO - Gwangju Gwangsan-gu 506-731 - KR +C8-53-E1 (hex) Douyin Vision Co., Ltd +C853E1 (base 16) Douyin Vision Co., Ltd + No.1 Building, Zhonghang Square, West Road of the Northern 3rd Circuit, Haidian Distrct + Beijing Beijing 100098 + CN -5C-49-79 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -5C4979 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +DC-39-6F (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +DC396F (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -DC-39-6F (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -DC396F (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +5C-49-79 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +5C4979 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -C8-53-E1 (hex) Douyin Vision Co., Ltd -C853E1 (base 16) Douyin Vision Co., Ltd - No.1 Building, Zhonghang Square, West Road of the Northern 3rd Circuit, Haidian Distrct - Beijing Beijing 100098 - CN - -08-B3-D6 (hex) Huawei Device Co., Ltd. -08B3D6 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - 2C-6F-37 (hex) Nokia 2C6F37 (base 16) Nokia 600 March Road @@ -184334,18 +184478,24 @@ C853E1 (base 16) Douyin Vision Co., Ltd Kanata Ontario K2K 2E6 CA -6C-15-DB (hex) Arcadyan Corporation -6C15DB (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW - 58-79-61 (hex) Microsoft Corporation 587961 (base 16) Microsoft Corporation One Microsoft Way REDMOND WA 98052 US +F4-14-BF (hex) LG Innotek +F414BF (base 16) LG Innotek + 26, HANAMSANDAN 5BEON-RO + Gwangju Gwangsan-gu 506-731 + KR + +50-E6-36 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +50E636 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + 60-B5-8D (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH 60B58D (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 @@ -184358,6 +184508,12 @@ C853E1 (base 16) Douyin Vision Co., Ltd Berlin Berlin 10559 DE +48-DD-0C (hex) eero inc. +48DD0C (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + 3C-5C-F1 (hex) eero inc. 3C5CF1 (base 16) eero inc. 660 3rd Street @@ -184376,12 +184532,6 @@ C4F174 (base 16) eero inc. San Francisco CA 94107 US -64-97-14 (hex) eero inc. -649714 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - 40-47-5E (hex) eero inc. 40475E (base 16) eero inc. 660 3rd Street @@ -184406,54 +184556,54 @@ D88ED4 (base 16) eero inc. San Francisco CA 94107 US -14-22-DB (hex) eero inc. -1422DB (base 16) eero inc. +64-97-14 (hex) eero inc. +649714 (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -18-90-88 (hex) eero inc. -189088 (base 16) eero inc. +20-BE-CD (hex) eero inc. +20BECD (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -48-DD-0C (hex) eero inc. -48DD0C (base 16) eero inc. +C8-B8-2F (hex) eero inc. +C8B82F (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -50-E6-36 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -50E636 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE - -E8-D3-EB (hex) eero inc. -E8D3EB (base 16) eero inc. +14-22-DB (hex) eero inc. +1422DB (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -C0-6F-98 (hex) eero inc. -C06F98 (base 16) eero inc. +18-90-88 (hex) eero inc. +189088 (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -20-BE-CD (hex) eero inc. -20BECD (base 16) eero inc. +E8-D3-EB (hex) eero inc. +E8D3EB (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -C8-B8-2F (hex) eero inc. -C8B82F (base 16) eero inc. +C0-6F-98 (hex) eero inc. +C06F98 (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US +8C-53-D2 (hex) China Mobile Group Device Co.,Ltd. +8C53D2 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + 24-61-5A (hex) China Mobile Group Device Co.,Ltd. 24615A (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District @@ -184514,20 +184664,26 @@ C875F4 (base 16) China Mobile Group Device Co.,Ltd. Beijing 100053 CN -B8-CE-F6 (hex) Mellanox Technologies, Inc. -B8CEF6 (base 16) Mellanox Technologies, Inc. +B8-E9-24 (hex) Mellanox Technologies, Inc. +B8E924 (base 16) Mellanox Technologies, Inc. 350 Oakmead Parkway, Suite 100 Sunnyvale CA 94085 US -C4-70-BD (hex) Mellanox Technologies, Inc. -C470BD (base 16) Mellanox Technologies, Inc. +2C-5E-AB (hex) Mellanox Technologies, Inc. +2C5EAB (base 16) Mellanox Technologies, Inc. 350 Oakmead Parkway, Suite 100 Sunnyvale CA 94085 US -B8-E9-24 (hex) Mellanox Technologies, Inc. -B8E924 (base 16) Mellanox Technologies, Inc. +B8-CE-F6 (hex) Mellanox Technologies, Inc. +B8CEF6 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +C4-70-BD (hex) Mellanox Technologies, Inc. +C470BD (base 16) Mellanox Technologies, Inc. 350 Oakmead Parkway, Suite 100 Sunnyvale CA 94085 US @@ -184562,16 +184718,10 @@ E83A4B (base 16) China Mobile Group Device Co.,Ltd. Dongguan Guangdong 523860 CN -2C-5E-AB (hex) Mellanox Technologies, Inc. -2C5EAB (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US - -8C-53-D2 (hex) China Mobile Group Device Co.,Ltd. -8C53D2 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 +58-72-C9 (hex) zte corporation +5872C9 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN 38-E5-63 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD @@ -184580,10 +184730,10 @@ E83A4B (base 16) China Mobile Group Device Co.,Ltd. DONG GUAN GUANG DONG 523860 CN -58-72-C9 (hex) zte corporation -5872C9 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +58-FC-E3 (hex) Funshion Online Technologies Co.,Ltd +58FCE3 (base 16) Funshion Online Technologies Co.,Ltd + 2101, Floor 1-2, Building 9, Anzhen Xili District 3, Chaoyang District, Beijing + Beijing 100029 CN 30-C5-99 (hex) ASUSTek COMPUTER INC. @@ -184598,22 +184748,10 @@ E83A4B (base 16) China Mobile Group Device Co.,Ltd. Qingdao 266101 CN -B8-DD-E8 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD -B8DDE8 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD - No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County - Chengdu Sichuan 611330 - CN - -30-FE-FA (hex) Cisco Systems, Inc -30FEFA (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -6C-4F-A1 (hex) Cisco Systems, Inc -6C4FA1 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +00-23-E9 (hex) F5 Inc. +0023E9 (base 16) F5 Inc. + 401 Elliott Ave. W. + Seattle WA 98119 US 40-BC-68 (hex) Funshion Online Technologies Co.,Ltd @@ -184628,17 +184766,29 @@ B8DDE8 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD Gunpo-si Gyeonggi-do 15849 KR +B8-DD-E8 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +B8DDE8 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County + Chengdu Sichuan 611330 + CN + 78-A1-D8 (hex) ShenzhenEnjoyTechnologyCo.,Ltd 78A1D8 (base 16) ShenzhenEnjoyTechnologyCo.,Ltd Building A, No.1 Qianwan 1st Road, QianHai Shenzhen HongKong Cooperation Zone, Shenzhen,China shenzhen guangdong 518108 CN -58-FC-E3 (hex) Funshion Online Technologies Co.,Ltd -58FCE3 (base 16) Funshion Online Technologies Co.,Ltd - 2101, Floor 1-2, Building 9, Anzhen Xili District 3, Chaoyang District, Beijing - Beijing 100029 - CN +30-FE-FA (hex) Cisco Systems, Inc +30FEFA (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +6C-4F-A1 (hex) Cisco Systems, Inc +6C4FA1 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US 40-95-95 (hex) TP-Link Systems Inc. 409595 (base 16) TP-Link Systems Inc. @@ -184646,12 +184796,6 @@ B8DDE8 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD Irvine CA 92618 US -00-23-E9 (hex) F5 Inc. -0023E9 (base 16) F5 Inc. - 401 Elliott Ave. W. - Seattle WA 98119 - US - 48-CA-68 (hex) Apple, Inc. 48CA68 (base 16) Apple, Inc. 1 Infinite Loop @@ -184670,12 +184814,6 @@ D842F7 (base 16) Tozed Kangwei Tech Co.,Ltd GuangZhou 511466 CN -E0-86-14 (hex) Inseego Wireless, Inc -E08614 (base 16) Inseego Wireless, Inc - 9710 Scranton Rd., Suite 200 - San Diego CA 92121 - US - 18-86-C3 (hex) Nokia 1886C3 (base 16) Nokia 600 March Road @@ -184706,23 +184844,11 @@ E8CA50 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. Sunnyvale CA 94085 US -68-FE-71 (hex) Espressif Inc. -68FE71 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -D8-6B-83 (hex) Nintendo Co.,Ltd -D86B83 (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP - -40-EB-21 (hex) HUAWEI TECHNOLOGIES CO.,LTD -40EB21 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +E0-86-14 (hex) Inseego Wireless, Inc +E08614 (base 16) Inseego Wireless, Inc + 9710 Scranton Rd., Suite 200 + San Diego CA 92121 + US A8-C4-07 (hex) HUAWEI TECHNOLOGIES CO.,LTD A8C407 (base 16) HUAWEI TECHNOLOGIES CO.,LTD @@ -184742,11 +184868,23 @@ DC121D (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -2C-03-69 (hex) ACCTON TECHNOLOGY CORPORATION -2C0369 (base 16) ACCTON TECHNOLOGY CORPORATION - No.1, Creation Road 3, Hsinchu Science Park, - Hsinchu 30077 - TW +40-EB-21 (hex) HUAWEI TECHNOLOGIES CO.,LTD +40EB21 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +68-FE-71 (hex) Espressif Inc. +68FE71 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +D8-6B-83 (hex) Nintendo Co.,Ltd +D86B83 (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP C0-74-15 (hex) IntelPro Inc. C07415 (base 16) IntelPro Inc. @@ -184772,23 +184910,17 @@ C07415 (base 16) IntelPro Inc. Hangzhou Zhejiang 310052 CN -54-78-F0 (hex) zte corporation -5478F0 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN - BC-D2-2C (hex) Intel Corporate BCD22C (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -E0-3A-AA (hex) Intel Corporate -E03AAA (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +2C-03-69 (hex) ACCTON TECHNOLOGY CORPORATION +2C0369 (base 16) ACCTON TECHNOLOGY CORPORATION + No.1, Creation Road 3, Hsinchu Science Park, + Hsinchu 30077 + TW 50-99-03 (hex) Meta Platforms, Inc. 509903 (base 16) Meta Platforms, Inc. @@ -184796,29 +184928,47 @@ E03AAA (base 16) Intel Corporate Menlo Park CA 94025 US +64-68-1A (hex) DASAN Network Solutions +64681A (base 16) DASAN Network Solutions + 401, 20, Gwacheon-daero 7-gil, + Gwacheon-si Gyeonggi-do 13493 + KR + 40-26-8E (hex) Shenzhen Photon Leap Technology Co., Ltd. 40268E (base 16) Shenzhen Photon Leap Technology Co., Ltd. 15/F, Building 2, Yongxin Times Square, Interchange of Dongbin Road and Nanguang Road Shenzhen 518054 CN +54-78-F0 (hex) zte corporation +5478F0 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + 74-F4-41 (hex) Samsung Electronics Co.,Ltd 74F441 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR +E0-3A-AA (hex) Intel Corporate +E03AAA (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + 34-39-16 (hex) Google, Inc. 343916 (base 16) Google, Inc. 1600 Amphitheatre Parkway Mountain View CA 94043 US -64-68-1A (hex) DASAN Network Solutions -64681A (base 16) DASAN Network Solutions - 401, 20, Gwacheon-daero 7-gil, - Gwacheon-si Gyeonggi-do 13493 - KR +64-D9-C2 (hex) eero inc. +64D9C2 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US 20-E7-C8 (hex) Espressif Inc. 20E7C8 (base 16) Espressif Inc. @@ -184832,12 +184982,24 @@ E03AAA (base 16) Intel Corporate San Diego CA 92127 US -64-D9-C2 (hex) eero inc. -64D9C2 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +28-87-61 (hex) LG Innotek +288761 (base 16) LG Innotek + 26, HANAMSANDAN 5BEON-RO + Gwangju Gwangsan-gu 506-731 + KR + +78-0C-71 (hex) Inseego Wireless, Inc +780C71 (base 16) Inseego Wireless, Inc + 9710 Scranton Rd., Suite 200 + San Diego CA 92121 US +A8-2B-DD (hex) LCFC(Hefei) Electronics Technology co., ltd +A82BDD (base 16) LCFC(Hefei) Electronics Technology co., ltd + No. 3188-1 Yungu Road (Comprehensive Bonded Zone), Hefei Economic & Technological Development Area,Anhui + HEFEI ANHUI 230601 + CN + 80-82-FE (hex) Arcadyan Corporation 8082FE (base 16) Arcadyan Corporation No.8, Sec.2, Guangfu Rd. @@ -184850,16 +185012,10 @@ CCCFFE (base 16) Henan Lingyunda Information Technology Co., Ltd Zhengzhou Henan Province 450000 CN -28-87-61 (hex) LG Innotek -288761 (base 16) LG Innotek - 26, HANAMSANDAN 5BEON-RO - Gwangju Gwangsan-gu 506-731 - KR - -78-0C-71 (hex) Inseego Wireless, Inc -780C71 (base 16) Inseego Wireless, Inc - 9710 Scranton Rd., Suite 200 - San Diego CA 92121 +34-B5-F3 (hex) IEEE Registration Authority +34B5F3 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US D4-27-FF (hex) Sagemcom Broadband SAS @@ -184874,18 +185030,24 @@ D427FF (base 16) Sagemcom Broadband SAS San Francisco CA 94107 US -A8-2B-DD (hex) LCFC(Hefei) Electronics Technology co., ltd -A82BDD (base 16) LCFC(Hefei) Electronics Technology co., ltd - No. 3188-1 Yungu Road (Comprehensive Bonded Zone), Hefei Economic & Technological Development Area,Anhui - HEFEI ANHUI 230601 - CN +C0-AF-F2 (hex) Dyson Limited +C0AFF2 (base 16) Dyson Limited + Tetbury Hill + Malmesbury Wiltshire SN16 0RP + GB -34-B5-F3 (hex) IEEE Registration Authority -34B5F3 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +20-10-B1 (hex) Amazon Technologies Inc. +2010B1 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US +84-53-CD (hex) China Mobile Group Device Co.,Ltd. +8453CD (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + F8-55-4B (hex) WirelessMobility Engineering Centre SDN. BHD F8554B (base 16) WirelessMobility Engineering Centre SDN. BHD SummerSkye Square, NO. 1-2-13 & 1-2, 13A, Jalan Sungai Tiram 8, 11900 Bayan Lepas @@ -184910,12 +185072,6 @@ E489CA (base 16) Cisco Systems, Inc San Jose CA 95126 US -C0-AF-F2 (hex) Dyson Limited -C0AFF2 (base 16) Dyson Limited - Tetbury Hill - Malmesbury Wiltshire SN16 0RP - GB - 14-BC-68 (hex) Cisco Systems, Inc 14BC68 (base 16) Cisco Systems, Inc 80 West Tasman Drive @@ -184928,10 +185084,34 @@ C0AFF2 (base 16) Dyson Limited Shanghai 200000 CN -AC-84-FA (hex) Zhejiang Weilai Jingling Artificial Intelligence Technology Co., Ltd. -AC84FA (base 16) Zhejiang Weilai Jingling Artificial Intelligence Technology Co., Ltd. - B2, 6th Floor (6-7 section), Xixi Campus, Ai Cheng Street, Wuchang Sub-district, Yuhang District, Hangzhou, Zhejiang Province, China - Hangzhou Zhejiang 310024 +98-F6-7A (hex) Chipsea Technologies (Shenzhen) Crop. +98F67A (base 16) Chipsea Technologies (Shenzhen) Crop. + Room 301, Building 1, Shenzhen Bay Innovation and Technology Center, Keyuan Avenue, High-tech Zone Community, Yuehai Subdistrict, Nanshan District, Shenzhen + Shenzhen 518000 + CN + +C4-9A-31 (hex) Zyxel Communications Corporation +C49A31 (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW + +48-0E-13 (hex) ittim +480E13 (base 16) ittim + 1202, No.6, Zhongguancun South Street, Haidian District, + beijing 100080 + CN + +74-34-91 (hex) Shenzhen Kings IoT Co., Ltd +743491 (base 16) Shenzhen Kings IoT Co., Ltd + D102, Yibao Garden, Hangcheng Road, Xixiang, BaoanBaoan district + Shenzhen City 518126 + CN + +08-D9-45 (hex) HUAWEI TECHNOLOGIES CO.,LTD +08D945 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN 00-A0-1B (hex) Zhone Technologies, Inc. @@ -184958,36 +185138,24 @@ AC84FA (base 16) Zhejiang Weilai Jingling Artificial Intelligence Technolog Plano TX 75024 US +AC-84-FA (hex) Zhejiang Weilai Jingling Artificial Intelligence Technology Co., Ltd. +AC84FA (base 16) Zhejiang Weilai Jingling Artificial Intelligence Technology Co., Ltd. + B2, 6th Floor (6-7 section), Xixi Campus, Ai Cheng Street, Wuchang Sub-district, Yuhang District, Hangzhou, Zhejiang Province, China + Hangzhou Zhejiang 310024 + CN + 3C-40-15 (hex) 12mm Health Technology (Hainan) Co., Ltd. 3C4015 (base 16) 12mm Health Technology (Hainan) Co., Ltd. Room A20-860, 5th Floor, Building A,Entrepreneurship Incubation Center,No. 266 Nanhai Avenue,National Hi-Tech Industrial Development Zone,Haikou City, Hainan Province, China Haikou Hainan 570100 CN -20-10-B1 (hex) Amazon Technologies Inc. -2010B1 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US - -84-53-CD (hex) China Mobile Group Device Co.,Ltd. -8453CD (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - -98-F6-7A (hex) Chipsea Technologies (Shenzhen) Crop. -98F67A (base 16) Chipsea Technologies (Shenzhen) Crop. - Room 301, Building 1, Shenzhen Bay Innovation and Technology Center, Keyuan Avenue, High-tech Zone Community, Yuehai Subdistrict, Nanshan District, Shenzhen - Shenzhen 518000 +64-2E-41 (hex) HUAWEI TECHNOLOGIES CO.,LTD +642E41 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -C4-9A-31 (hex) Zyxel Communications Corporation -C49A31 (base 16) Zyxel Communications Corporation - No. 6 Innovation Road II, Science Park - Hsichu Taiwan 300 - TW - 0C-1A-61 (hex) Neox FZCO 0C1A61 (base 16) Neox FZCO S60517 Jebel Ali Freezone @@ -185036,24 +185204,6 @@ F4289D (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. Nanning Guangxi 530007 CN -48-0E-13 (hex) ittim -480E13 (base 16) ittim - 1202, No.6, Zhongguancun South Street, Haidian District, - beijing 100080 - CN - -74-34-91 (hex) Shenzhen Kings IoT Co., Ltd -743491 (base 16) Shenzhen Kings IoT Co., Ltd - D102, Yibao Garden, Hangcheng Road, Xixiang, BaoanBaoan district - Shenzhen City 518126 - CN - -08-D9-45 (hex) HUAWEI TECHNOLOGIES CO.,LTD -08D945 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - 9C-C3-94 (hex) Apple, Inc. 9CC394 (base 16) Apple, Inc. 1 Infinite Loop @@ -185066,11 +185216,11 @@ F4289D (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. Cupertino CA 95014 US -64-2E-41 (hex) HUAWEI TECHNOLOGIES CO.,LTD -642E41 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +9C-3B-91 (hex) VSSL +9C3B91 (base 16) VSSL + 192 North Old Highway 91, Building 1 + Hurricane UT 84737 + US E0-26-11 (hex) Apple, Inc. E02611 (base 16) Apple, Inc. @@ -185084,6 +185234,24 @@ F4979D (base 16) IEEE Registration Authority Piscataway NJ 08554 US +D8-E0-16 (hex) Extreme Networks Headquarters +D8E016 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US + +B0-F1-AE (hex) eero inc. +B0F1AE (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +0C-58-7B (hex) Quectel Wireless Solutions Co.,Ltd. +0C587B (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 + CN + E8-A5-5A (hex) Juniper Networks E8A55A (base 16) Juniper Networks 1133 Innovation Way @@ -185114,12 +185282,6 @@ B0BC8E (base 16) SkyMirr Melbourne FL 32901 US -9C-3B-91 (hex) VSSL -9C3B91 (base 16) VSSL - 192 North Old Highway 91, Building 1 - Hurricane UT 84737 - US - 88-54-6B (hex) Texas Instruments 88546B (base 16) Texas Instruments 12500 TI Blvd @@ -185138,46 +185300,16 @@ B014DF (base 16) MitraStar Technology Corp. Seongnam-si 13517 KR -28-05-A5 (hex) Espressif Inc. -2805A5 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -B0-F1-AE (hex) eero inc. -B0F1AE (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -0C-58-7B (hex) Quectel Wireless Solutions Co.,Ltd. -0C587B (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 - CN - -D8-E0-16 (hex) Extreme Networks Headquarters -D8E016 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US - AC-F4-66 (hex) HP Inc. ACF466 (base 16) HP Inc. 10300 Energy Dr Spring TX 77389 US -40-3E-22 (hex) VusionGroup -403E22 (base 16) VusionGroup - Kalsdorfer Straße 12 - Fernitz-Mellach Steiermark 8072 - AT - -D4-A2-54 (hex) HUAWEI TECHNOLOGIES CO.,LTD -D4A254 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +74-68-59 (hex) SUNITEC TECHNOLOGY CO.,LIMITED +746859 (base 16) SUNITEC TECHNOLOGY CO.,LIMITED + Floor 1-4, building C, Weixlangtal industrial park, no, 725, Dasan Village、Xingfu community, Fucheng Street, Longhua district + Shenzhen 518110 CN C0-62-F2 (hex) Beijing Cotytech Co.,LTD @@ -185186,16 +185318,34 @@ C062F2 (base 16) Beijing Cotytech Co.,LTD Beijing 100071 CN +28-05-A5 (hex) Espressif Inc. +2805A5 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + 28-B2-7C (hex) Sailing Northern Technology 28B27C (base 16) Sailing Northern Technology Unit A4009, 4th floor, BuiIding 1, No. 2 Yongcheng North Road Beijing 100094 CN -74-68-59 (hex) SUNITEC TECHNOLOGY CO.,LIMITED -746859 (base 16) SUNITEC TECHNOLOGY CO.,LIMITED - Floor 1-4, building C, Weixlangtal industrial park, no, 725, Dasan Village、Xingfu community, Fucheng Street, Longhua district - Shenzhen 518110 +24-EE-5D (hex) Vizio, Inc +24EE5D (base 16) Vizio, Inc + 39 Tesla + Irvine CA 92618 + US + +40-3E-22 (hex) VusionGroup +403E22 (base 16) VusionGroup + Kalsdorfer Straße 12 + Fernitz-Mellach Steiermark 8072 + AT + +D4-A2-54 (hex) HUAWEI TECHNOLOGIES CO.,LTD +D4A254 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN EC-81-52 (hex) HUAWEI TECHNOLOGIES CO.,LTD @@ -185204,22 +185354,22 @@ EC8152 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN +68-CC-AE (hex) Fortinet, Inc. +68CCAE (base 16) Fortinet, Inc. + 899 Kifer Road + Sunnyvale 94086 + US + 10-BD-43 (hex) Robert Bosch Elektronikai Kft. 10BD43 (base 16) Robert Bosch Elektronikai Kft. Robert Bosch út 1. Hatvan Heves 3000 HU -24-EE-5D (hex) Vizio, Inc -24EE5D (base 16) Vizio, Inc - 39 Tesla - Irvine CA 92618 - US - -68-CC-AE (hex) Fortinet, Inc. -68CCAE (base 16) Fortinet, Inc. - 899 Kifer Road - Sunnyvale 94086 +78-11-9D (hex) Cisco Systems, Inc +78119D (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US 58-8F-CF (hex) Hangzhou Ezviz Software Co.,Ltd. @@ -185246,30 +185396,6 @@ EC8152 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Hangzhou Zhejiang 310051 CN -78-11-9D (hex) Cisco Systems, Inc -78119D (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -00-0B-0F (hex) Bosch Rexroth AG -000B0F (base 16) Bosch Rexroth AG - Bgm.-Dr.Nebel-Str.2 - Lohr am Main 97816 - NL - -A4-05-FD (hex) Bouffalo Lab (Nanjing) Co., Ltd. -A405FD (base 16) Bouffalo Lab (Nanjing) Co., Ltd. - 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China - Nanjing Jiangsu 211800 - CN - -3C-22-7F (hex) Quectel Wireless Solutions Co., Ltd. -3C227F (base 16) Quectel Wireless Solutions Co., Ltd. - Building 5, Shanghai Business Park Phase III (Area B), No.1016 Tianlin Road, Minhang District - Shanghai 200233 - CN - D4-0D-AB (hex) Shenzhen Cudy Technology Co., Ltd. D40DAB (base 16) Shenzhen Cudy Technology Co., Ltd. 7th Floor, West Tower, Lepu building, Nanshan @@ -185282,6 +185408,12 @@ D40DAB (base 16) Shenzhen Cudy Technology Co., Ltd. shenzhen guangdong 518057 CN +00-0B-0F (hex) Bosch Rexroth AG +000B0F (base 16) Bosch Rexroth AG + Bgm.-Dr.Nebel-Str.2 + Lohr am Main 97816 + NL + 84-93-EC (hex) Guangzhou Shiyuan Electronic Technology Company Limited 8493EC (base 16) Guangzhou Shiyuan Electronic Technology Company Limited No.6, 4th Yunpu Road, Yunpu industry District @@ -185294,17 +185426,41 @@ F07084 (base 16) Actiontec Electronics Inc. Santa Clara CA 95054 US +40-44-F7 (hex) Nintendo Co.,Ltd +4044F7 (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP + +A4-05-FD (hex) Bouffalo Lab (Nanjing) Co., Ltd. +A405FD (base 16) Bouffalo Lab (Nanjing) Co., Ltd. + 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China + Nanjing Jiangsu 211800 + CN + +3C-22-7F (hex) Quectel Wireless Solutions Co., Ltd. +3C227F (base 16) Quectel Wireless Solutions Co., Ltd. + Building 5, Shanghai Business Park Phase III (Area B), No.1016 Tianlin Road, Minhang District + Shanghai 200233 + CN + A0-90-B5 (hex) Tiinlab Corporation A090B5 (base 16) Tiinlab Corporation Building A Room 201 Cooperation District between Shenzhen and HongKong,Qianwan Road No.1,Shenzhen City, Business Address:No. 3333, Liuxian AvenueTower A, 35th Floor,Tanglang City, Nanshan District, Shenzhen, China Shenzhen Guangdong 518000 CN -6C-7A-63 (hex) Arista Networks -6C7A63 (base 16) Arista Networks - 5453 Great America Parkway - Santa Clara CA 95054 - US +28-83-28 (hex) EMALDO TECHNOLOGY(HK)LIMITED +288328 (base 16) EMALDO TECHNOLOGY(HK)LIMITED + 13/F., Golden Dragon Comm. Bldg., 522 Nathan Road, Yau Ma Tei, Kowloon + HONG KONG 999077 + HK + +B0-1F-F4 (hex) Sagemcom Broadband SAS +B01FF4 (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR AC-EB-E6 (hex) Espressif Inc. ACEBE6 (base 16) Espressif Inc. @@ -185312,17 +185468,29 @@ ACEBE6 (base 16) Espressif Inc. Shanghai Shanghai 201203 CN -28-83-28 (hex) EMALDO TECHNOLOGY(HK)LIMITED -288328 (base 16) EMALDO TECHNOLOGY(HK)LIMITED - 13/F., Golden Dragon Comm. Bldg., 522 Nathan Road, Yau Ma Tei, Kowloon - HONG KONG 999077 - HK +E8-B3-EE (hex) Pixelent Inc. +E8B3EE (base 16) Pixelent Inc. + #402 HanGuk Mediventure Center + 76, Dongnae-ro, Dong-gu Daegu 41061 + KR -40-44-F7 (hex) Nintendo Co.,Ltd -4044F7 (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP +6C-7A-63 (hex) Arista Networks +6C7A63 (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 + US + +C4-16-8F (hex) Apple, Inc. +C4168F (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +F8-2A-E2 (hex) Apple, Inc. +F82AE2 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US 84-5C-31 (hex) Dell Inc. 845C31 (base 16) Dell Inc. @@ -185372,6 +185540,18 @@ ACEBE6 (base 16) Espressif Inc. TSAOTUEN, NANTOU 54261 TW +1C-7D-51 (hex) HANSHOW TECHNOLOGY CO.,LTD. +1C7D51 (base 16) HANSHOW TECHNOLOGY CO.,LTD. + The 1st Floor Podium and Floor 4 of Building 1, Floor 7 of Building 5, JiaxingPhotovoltaic Technology Innovation Park, No.1288, Kanghe Road, Xiuzhou District,Jiaxing City, Zhejiang Prov,P.R.China + JIAXING 314000 + CN + +3C-0F-02 (hex) Espressif Inc. +3C0F02 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + 40-2C-F4 (hex) Universal Global Scientific Industrial., Ltd 402CF4 (base 16) Universal Global Scientific Industrial., Ltd 141, Lane 351, Taiping Rd. Sec. 1, Tsao Tuen, @@ -185390,16 +185570,10 @@ ACEBE6 (base 16) Espressif Inc. Nan-Tou Taiwan 54261 TW -B0-1F-F4 (hex) Sagemcom Broadband SAS -B01FF4 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -E8-B3-EE (hex) Pixelent Inc. -E8B3EE (base 16) Pixelent Inc. - #402 HanGuk Mediventure Center - 76, Dongnae-ro, Dong-gu Daegu 41061 +50-FB-FF (hex) Franklin Technology Inc. +50FBFF (base 16) Franklin Technology Inc. + 906(Gasan-Dong, JEI Platz), 186, Gasan digital 1-ro, Geumcheon-gu + Seoul 08502 KR E0-CD-B8 (hex) Huawei Device Co., Ltd. @@ -185432,29 +185606,23 @@ B4E5C5 (base 16) Huawei Device Co., Ltd. Dongguan 523808 CN -C4-16-8F (hex) Apple, Inc. -C4168F (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -F8-2A-E2 (hex) Apple, Inc. -F82AE2 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +88-29-BF (hex) Amazon Technologies Inc. +8829BF (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US -60-02-B4 (hex) WNC Corporation -6002B4 (base 16) WNC Corporation - No.20 Park Avenue II - Hsinchu 308 - TW +00-1A-B9 (hex) Groupe Carrus +001AB9 (base 16) Groupe Carrus + 56, avenue Raspail + Saint Maur 94100 + FR -00-0B-6B (hex) WNC Corporation -000B6B (base 16) WNC Corporation - No. 10-1, Li-Hsin Road I, Science-based - Hsinchu 300 - TW +C4-67-A1 (hex) Accelight Technologies (Wuhan) Inc. +C467A1 (base 16) Accelight Technologies (Wuhan) Inc. + 777 Guanggu 3rd, Bldg. #16, 5th Floor, + Wuhan Hubei, P. R. 430205 + CN E0-37-BF (hex) WNC Corporation E037BF (base 16) WNC Corporation @@ -185468,12 +185636,6 @@ D86162 (base 16) WNC Corporation Hsin-Chu R.O.C. 308 TW -50-FB-FF (hex) Franklin Technology Inc. -50FBFF (base 16) Franklin Technology Inc. - 906(Gasan-Dong, JEI Platz), 186, Gasan digital 1-ro, Geumcheon-gu - Seoul 08502 - KR - 64-FF-0A (hex) WNC Corporation 64FF0A (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park @@ -185492,35 +185654,29 @@ F46C68 (base 16) WNC Corporation Hsin-Chu R.O.C. 308 TW -1C-7D-51 (hex) HANSHOW TECHNOLOGY CO.,LTD. -1C7D51 (base 16) HANSHOW TECHNOLOGY CO.,LTD. - The 1st Floor Podium and Floor 4 of Building 1, Floor 7 of Building 5, JiaxingPhotovoltaic Technology Innovation Park, No.1288, Kanghe Road, Xiuzhou District,Jiaxing City, Zhejiang Prov,P.R.China - JIAXING 314000 - CN - -3C-0F-02 (hex) Espressif Inc. -3C0F02 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 58-96-71 (hex) WNC Corporation 589671 (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park Hsin-Chu R.O.C. 308 TW -88-29-BF (hex) Amazon Technologies Inc. -8829BF (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US +D8-33-2A (hex) Ruijie Networks Co.,LTD +D8332A (base 16) Ruijie Networks Co.,LTD + Building 19,Juyuanzhou Industrial Park, No.618 Jinshan Avenue, Cangshan District + Fuzhou 35000 + CN -00-1A-B9 (hex) Groupe Carrus -001AB9 (base 16) Groupe Carrus - 56, avenue Raspail - Saint Maur 94100 - FR +60-02-B4 (hex) WNC Corporation +6002B4 (base 16) WNC Corporation + No.20 Park Avenue II + Hsinchu 308 + TW + +00-0B-6B (hex) WNC Corporation +000B6B (base 16) WNC Corporation + No. 10-1, Li-Hsin Road I, Science-based + Hsinchu 300 + TW 24-D5-3B (hex) Motorola Mobility LLC, a Lenovo Company 24D53B (base 16) Motorola Mobility LLC, a Lenovo Company @@ -185534,24 +185690,42 @@ C834E5 (base 16) Cisco Systems, Inc San Jose CA 94568 US -80-61-32 (hex) Cisco Systems, Inc -806132 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +98-9E-80 (hex) tonies GmbH +989E80 (base 16) tonies GmbH + Oststraße 119 + Düsseldorf NRW 40210 + DE + +24-C3-5D (hex) Duke University +24C35D (base 16) Duke University + 300 Fuller Street Box 104100 + Durham NC 27708 US -C4-67-A1 (hex) Accelight Technologies (Wuhan) Inc. -C467A1 (base 16) Accelight Technologies (Wuhan) Inc. - 777 Guanggu 3rd, Bldg. #16, 5th Floor, - Wuhan Hubei, P. R. 430205 +50-92-6A (hex) Beijing Xiaomi Mobile Software Co., Ltd +50926A (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN -D8-33-2A (hex) Ruijie Networks Co.,LTD -D8332A (base 16) Ruijie Networks Co.,LTD - Building 19,Juyuanzhou Industrial Park, No.618 Jinshan Avenue, Cangshan District - Fuzhou 35000 +04-1C-DB (hex) Siba Service +041CDB (base 16) Siba Service + 6F, Kobe Commerce, Industry and Trade Center Building, 5-1-14 Hamabe-dori, Chuo-ku + Kobe-shi Hyogo-ken 6510083 + JP + +98-3F-A4 (hex) zte corporation +983FA4 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN +80-61-32 (hex) Cisco Systems, Inc +806132 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + 88-18-F1 (hex) Nokia 8818F1 (base 16) Nokia 600 March Road @@ -185570,17 +185744,23 @@ E41613 (base 16) Extreme Networks Headquarters Morrisville NC 27560 US -98-3F-A4 (hex) zte corporation -983FA4 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN +F0-FB-7F (hex) Mellanox Technologies, Inc. +F0FB7F (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US -E0-C9-32 (hex) Intel Corporate -E0C932 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +84-45-A0 (hex) Tube investments of India Limited +8445A0 (base 16) Tube investments of India Limited + Chola crest, 4th floor, No. C 54-55 & Super B4, Thiru Vi Ka Industrial Area, Guindy, Chennai - 600032 + Chennai Other 600032 + IN + +30-BC-4F (hex) Beijing Jianguo Bite Technology Co., Ltd. +30BC4F (base 16) Beijing Jianguo Bite Technology Co., Ltd. + RM1321, Building 2, Taihua Longqi Square, 19 Huangping ROAD, Changping district + Beijing Beijing 100096 + CN 54-36-31 (hex) Intel Corporate 543631 (base 16) Intel Corporate @@ -185600,29 +185780,17 @@ E0C932 (base 16) Intel Corporate Kulim Kedah 09000 MY -98-9E-80 (hex) tonies GmbH -989E80 (base 16) tonies GmbH - Oststraße 119 - Düsseldorf NRW 40210 - DE - -24-C3-5D (hex) Duke University -24C35D (base 16) Duke University - 300 Fuller Street Box 104100 - Durham NC 27708 - US - -50-92-6A (hex) Beijing Xiaomi Mobile Software Co., Ltd -50926A (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 - CN +94-53-FF (hex) Intel Corporate +9453FF (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -04-1C-DB (hex) Siba Service -041CDB (base 16) Siba Service - 6F, Kobe Commerce, Industry and Trade Center Building, 5-1-14 Hamabe-dori, Chuo-ku - Kobe-shi Hyogo-ken 6510083 - JP +E0-C9-32 (hex) Intel Corporate +E0C932 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY A4-3F-A7 (hex) Hewlett Packard Enterprise A43FA7 (base 16) Hewlett Packard Enterprise @@ -185636,11 +185804,17 @@ A43FA7 (base 16) Hewlett Packard Enterprise New York NY New York NY 10017 US -94-53-FF (hex) Intel Corporate -9453FF (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +54-C1-D3 (hex) Guangzhou TR Intelligent Manufacturing Technology Co., Ltd +54C1D3 (base 16) Guangzhou TR Intelligent Manufacturing Technology Co., Ltd + Room 3101, TCL Tower, 18 Haizhou Road, Haizhu District, Guangzhou, Guangdong + Guangzhou City Guangdong Province 510000 + CN + +E0-31-5D (hex) EM Microelectronic +E0315D (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH 00-12-C1 (hex) Check Point Software Technologies Ltd. 0012C1 (base 16) Check Point Software Technologies Ltd. @@ -185666,46 +185840,10 @@ F0ABFA (base 16) Shenzhen Rayin Technology Co.,Ltd shenzhen guangdong 518000 CN -F0-FB-7F (hex) Mellanox Technologies, Inc. -F0FB7F (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US - -84-45-A0 (hex) Tube investments of India Limited -8445A0 (base 16) Tube investments of India Limited - Chola crest, 4th floor, No. C 54-55 & Super B4, Thiru Vi Ka Industrial Area, Guindy, Chennai - 600032 - Chennai Other 600032 - IN - -30-BC-4F (hex) Beijing Jianguo Bite Technology Co., Ltd. -30BC4F (base 16) Beijing Jianguo Bite Technology Co., Ltd. - RM1321, Building 2, Taihua Longqi Square, 19 Huangping ROAD, Changping district - Beijing Beijing 100096 - CN - -68-9F-D4 (hex) Amazon Technologies Inc. -689FD4 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US - -54-C1-D3 (hex) Guangzhou TR Intelligent Manufacturing Technology Co., Ltd -54C1D3 (base 16) Guangzhou TR Intelligent Manufacturing Technology Co., Ltd - Room 3101, TCL Tower, 18 Haizhou Road, Haizhu District, Guangzhou, Guangdong - Guangzhou City Guangdong Province 510000 - CN - -E0-31-5D (hex) EM Microelectronic -E0315D (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH - -50-D0-6D (hex) Bird Buddy -50D06D (base 16) Bird Buddy - 169 Madison Avenue, Suite 15233 - New York NY 10016 +A4-4A-64 (hex) Maverick Mobile LLC +A44A64 (base 16) Maverick Mobile LLC + 8350 N. Central Expwy #1900 + Dallas TX 75206 US 5C-C4-1D (hex) Stone Devices Sdn. Bhd. @@ -185714,22 +185852,10 @@ E0315D (base 16) EM Microelectronic SENAI JOHOR 81400 MY -30-76-F5 (hex) Espressif Inc. -3076F5 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -A4-4A-64 (hex) Maverick Mobile LLC -A44A64 (base 16) Maverick Mobile LLC - 8350 N. Central Expwy #1900 - Dallas TX 75206 - US - -AC-E6-BB (hex) Google, Inc. -ACE6BB (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 +68-9F-D4 (hex) Amazon Technologies Inc. +689FD4 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US DC-44-B1 (hex) Hilti Corporation @@ -185744,6 +185870,12 @@ F4525B (base 16) Antare Technology Ltd London WC2A 2JR GB +50-D0-6D (hex) Bird Buddy +50D06D (base 16) Bird Buddy + 169 Madison Avenue, Suite 15233 + New York NY 10016 + US + 34-EF-8B (hex) NTT DOCOMO BUSINESS, Inc. 34EF8B (base 16) NTT DOCOMO BUSINESS, Inc. NTT DOCOMO BUSINESS Karagasaki Bldg. 1-11-7 Chuo-cho @@ -185762,28 +185894,22 @@ E0A366 (base 16) Motorola Mobility LLC, a Lenovo Company Shenzhen 518052 CN +30-76-F5 (hex) Espressif Inc. +3076F5 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + 38-44-BE (hex) Espressif Inc. 3844BE (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area Shanghai Shanghai 201203 CN -30-46-9A (hex) NETGEAR -30469A (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US - -E0-46-9A (hex) NETGEAR -E0469A (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US - -A0-04-60 (hex) NETGEAR -A00460 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 +AC-E6-BB (hex) Google, Inc. +ACE6BB (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 US 2C-30-33 (hex) NETGEAR @@ -185792,11 +185918,17 @@ A00460 (base 16) NETGEAR San Jose CA 95134 US -50-4A-6E (hex) NETGEAR -504A6E (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US +E4-1B-43 (hex) Beijing Xiaomi Mobile Software Co., Ltd +E41B43 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 + CN + +38-0F-E4 (hex) Dedicated Network Partners Oy +380FE4 (base 16) Dedicated Network Partners Oy + Valimotie 13a + Helsinki 00380 + FI 54-07-7D (hex) NETGEAR 54077D (base 16) NETGEAR @@ -185840,24 +185972,6 @@ BCA511 (base 16) NETGEAR San Jose CA 95134 US -E4-1B-43 (hex) Beijing Xiaomi Mobile Software Co., Ltd -E41B43 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 - CN - -28-B6-7C (hex) KEBODA Intelligent TECHNOLOGY CO., LTD. -28B67C (base 16) KEBODA Intelligent TECHNOLOGY CO., LTD. - Building 7, Lane 36, Xuelin Road, Pudong New Area - Shanghai Shanghai 200120 - CN - -F0-ED-51 (hex) Qingdao Intelligent&Precise Electronics Co.,Ltd. -F0ED51 (base 16) Qingdao Intelligent&Precise Electronics Co.,Ltd. - No.218 Qianwangang Road - Qingdao Shangdong 266510 - CN - 60-A9-54 (hex) Cisco Systems, Inc 60A954 (base 16) Cisco Systems, Inc 80 West Tasman Drive @@ -185870,16 +185984,34 @@ F0ED51 (base 16) Qingdao Intelligent&Precise Electronics Co.,Ltd. San Jose CA 94568 US -38-0F-E4 (hex) Dedicated Network Partners Oy -380FE4 (base 16) Dedicated Network Partners Oy - Valimotie 13a - Helsinki 00380 - FI +30-46-9A (hex) NETGEAR +30469A (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US -68-2A-DD (hex) zte corporation -682ADD (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +E0-46-9A (hex) NETGEAR +E0469A (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US + +A0-04-60 (hex) NETGEAR +A00460 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US + +50-4A-6E (hex) NETGEAR +504A6E (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US + +F0-ED-51 (hex) Qingdao Intelligent&Precise Electronics Co.,Ltd. +F0ED51 (base 16) Qingdao Intelligent&Precise Electronics Co.,Ltd. + No.218 Qianwangang Road + Qingdao Shangdong 266510 CN FC-3D-98 (hex) ACCTON TECHNOLOGY CORPORATION @@ -185888,6 +186020,18 @@ FC3D98 (base 16) ACCTON TECHNOLOGY CORPORATION Hsinchu 30077 TW +28-B6-7C (hex) KEBODA Intelligent TECHNOLOGY CO., LTD. +28B67C (base 16) KEBODA Intelligent TECHNOLOGY CO., LTD. + Building 7, Lane 36, Xuelin Road, Pudong New Area + Shanghai Shanghai 200120 + CN + +68-2A-DD (hex) zte corporation +682ADD (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + 60-29-72 (hex) Arista Networks 602972 (base 16) Arista Networks 5453 Great America Parkway @@ -185918,6 +186062,12 @@ A4B1E9 (base 16) Vantiva Technologies Belgium Toronto Ontario M2N 6L7 CA +48-FC-7C (hex) Shenzhen Huidu Technology Co., Ltd. +48FC7C (base 16) Shenzhen Huidu Technology Co., Ltd. + 18F, No. 196 Tangtou Street, Shiyan Town, Baoan District, Shenzhen + Shenzhen 518000 + CN + 9C-DF-8A (hex) HUAWEI TECHNOLOGIES CO.,LTD 9CDF8A (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -185942,17 +186092,23 @@ FCA27E (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -48-FC-7C (hex) Shenzhen Huidu Technology Co., Ltd. -48FC7C (base 16) Shenzhen Huidu Technology Co., Ltd. - 18F, No. 196 Tangtou Street, Shiyan Town, Baoan District, Shenzhen - Shenzhen 518000 - CN +00-92-35 (hex) Apple, Inc. +009235 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -F4-64-B6 (hex) Sercomm Corporation. -F464B6 (base 16) Sercomm Corporation. - 3F,No.81,Yu-Yih Rd.,Chu-Nan Chen - Miao-Lih Hsuan 115 - TW +F0-2F-BA (hex) Apple, Inc. +F02FBA (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +E4-CE-58 (hex) Anhui Realloong Automotive Electronics Co.,Ltd +E4CE58 (base 16) Anhui Realloong Automotive Electronics Co.,Ltd + Anhui Realloong Automotive Electronics Co.,Ltd + Hefei Anhui 230088 + CN 74-14-D0 (hex) Apple, Inc. 7414D0 (base 16) Apple, Inc. @@ -185960,12 +186116,6 @@ F464B6 (base 16) Sercomm Corporation. Cupertino CA 95014 US -C0-EE-40 (hex) Ezurio, LLC -C0EE40 (base 16) Ezurio, LLC - 50 South Main St - Akron 44308 - US - 3C-FB-02 (hex) Apple, Inc. 3CFB02 (base 16) Apple, Inc. 1 Infinite Loop @@ -185990,22 +186140,43 @@ F478AC (base 16) Apple, Inc. Cupertino CA 95014 US -00-92-35 (hex) Apple, Inc. -009235 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +7C-24-6A (hex) Scita Solutions +7C246A (base 16) Scita Solutions + 218, 2nd Cross, ISRO Layout + Bangalore Karnataka 560078 + IN -F0-2F-BA (hex) Apple, Inc. -F02FBA (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +F4-64-B6 (hex) Sercomm Corporation. +F464B6 (base 16) Sercomm Corporation. + 3F,No.81,Yu-Yih Rd.,Chu-Nan Chen + Miao-Lih Hsuan 115 + TW + +C0-EE-40 (hex) Ezurio, LLC +C0EE40 (base 16) Ezurio, LLC + 50 South Main St + Akron 44308 US -E4-CE-58 (hex) Anhui Realloong Automotive Electronics Co.,Ltd -E4CE58 (base 16) Anhui Realloong Automotive Electronics Co.,Ltd - Anhui Realloong Automotive Electronics Co.,Ltd - Hefei Anhui 230088 +C8-AD-E7 (hex) Shenzhen Shengxi Industrial Co.,Ltd +C8ADE7 (base 16) Shenzhen Shengxi Industrial Co.,Ltd + Unit 402, Building 10, Yuanling New Village, Yuanling Community + Yuanling Street Futian District 518028 + CN + +AC-3D-FA (hex) Hangzhou Huacheng Network Technology Co.,Ltd +AC3DFA (base 16) Hangzhou Huacheng Network Technology Co.,Ltd + 13th Floor, Building 3, No. 582, Liye Road, Changhe Street, Binjiang District, Hangzhou, China (Zhejiang) Pilot Free Trade Zone + Hangzhou 311200 + CN + +8C-A4-54 (hex) Private +8CA454 (base 16) Private + +C8-74-1B (hex) Fiberhome Telecommunication Technologies Co.,LTD +C8741B (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 CN F4-E2-5D (hex) AltoBeam Inc. @@ -186014,12 +186185,6 @@ F4E25D (base 16) AltoBeam Inc. Beijing Beijing 100083 CN -7C-24-6A (hex) Scita Solutions -7C246A (base 16) Scita Solutions - 218, 2nd Cross, ISRO Layout - Bangalore Karnataka 560078 - IN - CC-36-BB (hex) Silicon Laboratories CC36BB (base 16) Silicon Laboratories 400 West Cesar Chavez @@ -186038,63 +186203,24 @@ CC7645 (base 16) Microsoft Corporation Singapore 068902 SG -C8-AD-E7 (hex) Shenzhen Shengxi Industrial Co.,Ltd -C8ADE7 (base 16) Shenzhen Shengxi Industrial Co.,Ltd - Unit 402, Building 10, Yuanling New Village, Yuanling Community - Yuanling Street Futian District 518028 - CN - -AC-3D-FA (hex) Hangzhou Huacheng Network Technology Co.,Ltd -AC3DFA (base 16) Hangzhou Huacheng Network Technology Co.,Ltd - 13th Floor, Building 3, No. 582, Liye Road, Changhe Street, Binjiang District, Hangzhou, China (Zhejiang) Pilot Free Trade Zone - Hangzhou 311200 - CN - -54-56-18 (hex) Huawei Device Co., Ltd. -545618 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - -8C-5D-54 (hex) Kisi -8C5D54 (base 16) Kisi - 45 Main St - Brooklyn NY 11210 - US - -C8-74-1B (hex) Fiberhome Telecommunication Technologies Co.,LTD -C8741B (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 - CN - F0-16-1D (hex) Espressif Inc. F0161D (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area Shanghai Shanghai 201203 CN -64-A3-37 (hex) Garmin International -64A337 (base 16) Garmin International - 1200 E. 151st St - Olathe KS 66062 +8C-5D-54 (hex) Kisi +8C5D54 (base 16) Kisi + 45 Main St + Brooklyn NY 11210 US -8C-A4-54 (hex) Private -8CA454 (base 16) Private - -C0-CF-64 (hex) Hangzhou Zenith Electron Co.,Ltd -C0CF64 (base 16) Hangzhou Zenith Electron Co.,Ltd - Room 1702, No.888, Zhongxin Road, Beigan Street. Xiaoshan District, Hangzhou City, Zhejiang - Hangzhou Zhejiang 310000 +54-56-18 (hex) Huawei Device Co., Ltd. +545618 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -30-77-DF (hex) Terex Corporation -3077DF (base 16) Terex Corporation - 18620 NE 67th Ct - Redmond WA 98052 - US - 58-50-9F (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. 58509F (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China @@ -186119,17 +186245,17 @@ C0CF64 (base 16) Hangzhou Zenith Electron Co.,Ltd Beijing 100029 CN -B4-38-36 (hex) HUAWEI TECHNOLOGIES CO.,LTD -B43836 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +64-A3-37 (hex) Garmin International +64A337 (base 16) Garmin International + 1200 E. 151st St + Olathe KS 66062 + US -38-2F-B0 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -382FB0 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 - CN +5C-51-36 (hex) Samsung Electronics Co.,Ltd +5C5136 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR 34-56-ED (hex) Goerdyna Group Co., Ltd 3456ED (base 16) Goerdyna Group Co., Ltd @@ -186137,18 +186263,18 @@ B43836 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Qingdao City Shandong Province 266000 CN -BC-AF-6E (hex) Arcadyan Corporation -BCAF6E (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW - -08-9C-74 (hex) UNIONMAN TECHNOLOGY CO.,LTD -089C74 (base 16) UNIONMAN TECHNOLOGY CO.,LTD - No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway - Huizhou Guangdong 516025 +C0-CF-64 (hex) Hangzhou Zenith Electron Co.,Ltd +C0CF64 (base 16) Hangzhou Zenith Electron Co.,Ltd + Room 1702, No.888, Zhongxin Road, Beigan Street. Xiaoshan District, Hangzhou City, Zhejiang + Hangzhou Zhejiang 310000 CN +30-77-DF (hex) Terex Corporation +3077DF (base 16) Terex Corporation + 18620 NE 67th Ct + Redmond WA 98052 + US + 90-1F-09 (hex) Silicon Laboratories 901F09 (base 16) Silicon Laboratories 400 West Cesar Chavez @@ -186167,17 +186293,17 @@ B4BFE9 (base 16) Espressif Inc. Shanghai Shanghai 201203 CN -5C-51-36 (hex) Samsung Electronics Co.,Ltd -5C5136 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +B4-38-36 (hex) HUAWEI TECHNOLOGIES CO.,LTD +B43836 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -BC-27-7A (hex) Samsung Electronics Co.,Ltd -BC277A (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +B0-42-B7 (hex) HUAWEI TECHNOLOGIES CO.,LTD +B042B7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN 80-0D-3F (hex) Samsung Electronics Co.,Ltd 800D3F (base 16) Samsung Electronics Co.,Ltd @@ -186185,10 +186311,10 @@ BC277A (base 16) Samsung Electronics Co.,Ltd Suwon Gyeonggi-Do 16677 KR -B0-42-B7 (hex) HUAWEI TECHNOLOGIES CO.,LTD -B042B7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +38-2F-B0 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +382FB0 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 CN 30-8B-23 (hex) Annapurna labs @@ -186221,11 +186347,23 @@ A49DB8 (base 16) SHENZHEN TECNO TECHNOLOGY Shenzhen guangdong 518000 CN -AC-C3-58 (hex) AUMOVIO Czech Republic s.r.o. -ACC358 (base 16) AUMOVIO Czech Republic s.r.o. - Průmyslová 1851 - Brandýs nad Labem 250 01 - CZ +08-9C-74 (hex) UNIONMAN TECHNOLOGY CO.,LTD +089C74 (base 16) UNIONMAN TECHNOLOGY CO.,LTD + No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway + Huizhou Guangdong 516025 + CN + +BC-27-7A (hex) Samsung Electronics Co.,Ltd +BC277A (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +BC-AF-6E (hex) Arcadyan Corporation +BCAF6E (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW E4-12-26 (hex) AUMOVIO Technologies Romania S.R.L. E41226 (base 16) AUMOVIO Technologies Romania S.R.L. @@ -186233,23 +186371,11 @@ E41226 (base 16) AUMOVIO Technologies Romania S.R.L. Timisoara 300701 RO -A8-57-BA (hex) Shenzhen YOUHUA Technology Co., Ltd -A857BA (base 16) Shenzhen YOUHUA Technology Co., Ltd - Room 407 Shenzhen University-town Business Park,Lishan Road,Taoyuan Street,Nanshan District - Shenzhen Guangdong 518055 - CN - -64-18-DF (hex) Sagemcom Broadband SAS -6418DF (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -98-78-00 (hex) TCT mobile ltd -987800 (base 16) TCT mobile ltd - No.86 hechang 7th road, zhongkai, Hi-Tech District - Hui Zhou Guang Dong 516006 - CN +AC-C3-58 (hex) AUMOVIO Czech Republic s.r.o. +ACC358 (base 16) AUMOVIO Czech Republic s.r.o. + Průmyslová 1851 + Brandýs nad Labem 250 01 + CZ 00-05-DB (hex) PSI Software SE, 0005DB (base 16) PSI Software SE, @@ -186257,6 +186383,18 @@ A857BA (base 16) Shenzhen YOUHUA Technology Co., Ltd Karlsruhe 76131 DE +C0-A4-B9 (hex) Sichuan AI-Link Technology Co., Ltd. +C0A4B9 (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou, Industrial Park + Mianyang Sichuan 622650 + CN + +40-5A-DD (hex) Actions Microelectronics +405ADD (base 16) Actions Microelectronics + 201, No. 9 Building, Software Park, KeJiZhongEr Road., GaoXingQu, NanShan + Shenzhen Guangdong 518057 + CN + 80-E6-3C (hex) Xiaomi Communications Co Ltd 80E63C (base 16) Xiaomi Communications Co Ltd #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road @@ -186275,22 +186413,10 @@ A857BA (base 16) Shenzhen YOUHUA Technology Co., Ltd San Jose CA 95002 US -88-45-58 (hex) Amicro Technology Co., Ltd. -884558 (base 16) Amicro Technology Co., Ltd. - 14F Novotown Huixin Office Building,No. 88, Zhishui Road, Hengqin - Zhuhai Guangdong 519000 - CN - -10-CB-33 (hex) NXP Semiconductors Taiwan Ltd. -10CB33 (base 16) NXP Semiconductors Taiwan Ltd. - No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan - Nanzi Dist. Kaohsiung 811643 - TW - -40-5A-DD (hex) Actions Microelectronics -405ADD (base 16) Actions Microelectronics - 201, No. 9 Building, Software Park, KeJiZhongEr Road., GaoXingQu, NanShan - Shenzhen Guangdong 518057 +A8-57-BA (hex) Shenzhen YOUHUA Technology Co., Ltd +A857BA (base 16) Shenzhen YOUHUA Technology Co., Ltd + Room 407 Shenzhen University-town Business Park,Lishan Road,Taoyuan Street,Nanshan District + Shenzhen Guangdong 518055 CN 7C-87-67 (hex) Cisco Systems, Inc @@ -186305,29 +186431,29 @@ A857BA (base 16) Shenzhen YOUHUA Technology Co., Ltd San Jose CA 94568 US -24-A5-FF (hex) Fairbanks Scales -24A5FF (base 16) Fairbanks Scales - 2176 Portland Street - St.Johnsbury VT 05819 - US +64-18-DF (hex) Sagemcom Broadband SAS +6418DF (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR -C0-A4-B9 (hex) Sichuan AI-Link Technology Co., Ltd. -C0A4B9 (base 16) Sichuan AI-Link Technology Co., Ltd. - Anzhou, Industrial Park - Mianyang Sichuan 622650 +98-78-00 (hex) TCT mobile ltd +987800 (base 16) TCT mobile ltd + No.86 hechang 7th road, zhongkai, Hi-Tech District + Hui Zhou Guang Dong 516006 CN -8C-22-D2 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. -8C22D2 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. - No.555 Qianmo Road - Hangzhou Zhejiang 310052 +88-45-58 (hex) Amicro Technology Co., Ltd. +884558 (base 16) Amicro Technology Co., Ltd. + 14F Novotown Huixin Office Building,No. 88, Zhishui Road, Hengqin + Zhuhai Guangdong 519000 CN -20-9B-DD (hex) HUAWEI TECHNOLOGIES CO.,LTD -209BDD (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +10-CB-33 (hex) NXP Semiconductors Taiwan Ltd. +10CB33 (base 16) NXP Semiconductors Taiwan Ltd. + No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan + Nanzi Dist. Kaohsiung 811643 + TW C4-49-1B (hex) Apple, Inc. C4491B (base 16) Apple, Inc. @@ -186347,16 +186473,10 @@ C4491B (base 16) Apple, Inc. Cupertino CA 95014 US -08-02-99 (hex) HC Corporation -080299 (base 16) HC Corporation - 1201, 12th F, Seongnam Woolim Lion’s Valley 1Bldg, 27, Dunchen-Daero 457beon-gil, Jungwon-gu - Seongnam Gyengido 13219 - KR - -80-77-86 (hex) IEEE Registration Authority -807786 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +24-A5-FF (hex) Fairbanks Scales +24A5FF (base 16) Fairbanks Scales + 2176 Portland Street + St.Johnsbury VT 05819 US 74-29-20 (hex) MCX-PRO Kft. @@ -186371,29 +186491,29 @@ C4491B (base 16) Apple, Inc. Cupertino CA 95014 US -F8-0C-9A (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -F80C9A (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN - -54-1F-CD (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -541FCD (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN - 60-47-0A (hex) Shenzhen Zenith Intelligent Technology Co., Ltd. 60470A (base 16) Shenzhen Zenith Intelligent Technology Co., Ltd. Room 1606, Building C3, Nanshan Kexing Science Park, Nanshan District Shenzhen 518000 CN -94-FC-87 (hex) Hirschmann Automation and Control GmbH -94FC87 (base 16) Hirschmann Automation and Control GmbH - Stuttgarter Straße 45-51 - Neckartenzlingen D-72654 - DE +8C-22-D2 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +8C22D2 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 + CN + +20-9B-DD (hex) HUAWEI TECHNOLOGIES CO.,LTD +209BDD (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +B8-32-8F (hex) eero inc. +B8328F (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US F4-A3-C2 (hex) Shenzhen iComm Semiconductor CO.,LTD F4A3C2 (base 16) Shenzhen iComm Semiconductor CO.,LTD @@ -186407,11 +186527,11 @@ F4A3C2 (base 16) Shenzhen iComm Semiconductor CO.,LTD Shenzhen GuangDong 518000 CN -64-31-36 (hex) Mellanox Technologies, Inc. -643136 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US +08-02-99 (hex) HC Corporation +080299 (base 16) HC Corporation + 1201, 12th F, Seongnam Woolim Lion’s Valley 1Bldg, 27, Dunchen-Daero 457beon-gil, Jungwon-gu + Seongnam Gyengido 13219 + KR 3C-65-D1 (hex) HUAWEI TECHNOLOGIES CO.,LTD 3C65D1 (base 16) HUAWEI TECHNOLOGIES CO.,LTD @@ -186419,34 +186539,52 @@ F4A3C2 (base 16) Shenzhen iComm Semiconductor CO.,LTD Dongguan 523808 CN -B8-32-8F (hex) eero inc. -B8328F (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +80-77-86 (hex) IEEE Registration Authority +807786 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -E8-F0-94 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -E8F094 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 +F8-0C-9A (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +F80C9A (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -B8-CD-58 (hex) Shenzhen SuperElectron Technology Co.,Ltd. -B8CD58 (base 16) Shenzhen SuperElectron Technology Co.,Ltd. - 1213-1214, haosheng business center, dongbin road, nanshan street, nanshan district, shenzhen city - Shenzhen Guangdong 518000 +54-1F-CD (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +541FCD (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -BC-8D-2D (hex) Fiberhome Telecommunication Technologies Co.,LTD -BC8D2D (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 +AC-27-6E (hex) Espressif Inc. +AC276E (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 CN -EC-30-DD (hex) eero inc. -EC30DD (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +64-31-36 (hex) Mellanox Technologies, Inc. +643136 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +88-F1-55 (hex) Espressif Inc. +88F155 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +94-FC-87 (hex) Hirschmann Automation and Control GmbH +94FC87 (base 16) Hirschmann Automation and Control GmbH + Stuttgarter Straße 45-51 + Neckartenzlingen D-72654 + DE + +E4-79-3F (hex) Juniper Networks +E4793F (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US B8-55-EA (hex) Yantai Jahport Electronic Technology Co., Ltd. @@ -186461,18 +186599,36 @@ B855EA (base 16) Yantai Jahport Electronic Technology Co., Ltd. HO CHI MINH CITY HO CHI MINH 820000 VN -88-F1-55 (hex) Espressif Inc. -88F155 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +B8-CD-58 (hex) Shenzhen SuperElectron Technology Co.,Ltd. +B8CD58 (base 16) Shenzhen SuperElectron Technology Co.,Ltd. + 1213-1214, haosheng business center, dongbin road, nanshan street, nanshan district, shenzhen city + Shenzhen Guangdong 518000 CN -AC-27-6E (hex) Espressif Inc. -AC276E (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +BC-8D-2D (hex) Fiberhome Telecommunication Technologies Co.,LTD +BC8D2D (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 CN +EC-30-DD (hex) eero inc. +EC30DD (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +E8-F0-94 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +E8F094 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 + CN + +3C-F7-5D (hex) Zyxel Communications Corporation +3CF75D (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW + 00-B8-1D (hex) Extreme Networks Headquarters 00B81D (base 16) Extreme Networks Headquarters 2121 RDU Center Drive @@ -186485,24 +186641,12 @@ AC276E (base 16) Espressif Inc. Qingdao 266101 CN -E4-79-3F (hex) Juniper Networks -E4793F (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US - CC-58-C7 (hex) Nokia CC58C7 (base 16) Nokia 600 March Road Kanata Ontario K2K 2E6 CA -B0-95-01 (hex) EM Microelectronic -B09501 (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH - 64-1B-85 (hex) Vantiva USA LLC 641B85 (base 16) Vantiva USA LLC 4855 Peachtree Industrial Blvd, Suite 200 @@ -186515,6 +186659,12 @@ B09501 (base 16) EM Microelectronic Qingdao 266000 CN +B0-95-01 (hex) EM Microelectronic +B09501 (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH + D8-5B-27 (hex) WNC Corporation D85B27 (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park @@ -186527,6 +186677,12 @@ C42C7B (base 16) VIETNAM POST AND TELECOMMUNICATION INDUSTRY TECHNOLOGY JOI Hanoi 100000 VN +F4-A1-57 (hex) Huawei Device Co., Ltd. +F4A157 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + A8-72-4D (hex) Intel Corporate A8724D (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 @@ -186539,18 +186695,6 @@ A8724D (base 16) Intel Corporate Kulim Kedah 09000 MY -3C-F7-5D (hex) Zyxel Communications Corporation -3CF75D (base 16) Zyxel Communications Corporation - No. 6 Innovation Road II, Science Park - Hsichu Taiwan 300 - TW - -F4-A1-57 (hex) Huawei Device Co., Ltd. -F4A157 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - 34-D7-F5 (hex) IEEE Registration Authority 34D7F5 (base 16) IEEE Registration Authority 445 Hoes Lane @@ -186575,12 +186719,6 @@ C47BE3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -DC-5D-31 (hex) ITEL MOBILE LIMITED -DC5D31 (base 16) ITEL MOBILE LIMITED - RM B3 & B4 BLOCK B, KO FAI INDUSTRIAL BUILDING NO.7 KO FAI ROAD, YAU TONG, KLN, H.K - Hong Kong KOWLOON 999077 - HK - 60-72-0B (hex) BLU Products Inc 60720B (base 16) BLU Products Inc 8600 NW 36th Street Suite 200 @@ -186593,11 +186731,11 @@ DC5D31 (base 16) ITEL MOBILE LIMITED Miami FL 33166 US -74-6A-84 (hex) Texas Instruments -746A84 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US +A8-CA-87 (hex) ZHEJIANG DAHUA ZHILIAN CO.,LTD +A8CA87 (base 16) ZHEJIANG DAHUA ZHILIAN CO.,LTD + No.28, Dongqiao Road, Dongzhou Street, Fuyang District, Hangzhou, P.R. China + HANGZHOU 311400 + CN A8-61-EC (hex) Texas Instruments A861EC (base 16) Texas Instruments @@ -186605,11 +186743,11 @@ A861EC (base 16) Texas Instruments Dallas TX 75243 US -A8-CA-87 (hex) ZHEJIANG DAHUA ZHILIAN CO.,LTD -A8CA87 (base 16) ZHEJIANG DAHUA ZHILIAN CO.,LTD - No.28, Dongqiao Road, Dongzhou Street, Fuyang District, Hangzhou, P.R. China - HANGZHOU 311400 - CN +74-6A-84 (hex) Texas Instruments +746A84 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US 14-63-93 (hex) Espressif Inc. 146393 (base 16) Espressif Inc. @@ -186623,11 +186761,11 @@ B08CB3 (base 16) FN-LINK TECHNOLOGY Ltd. Changsha Hunan 410329 CN -F0-0C-51 (hex) zte corporation -F00C51 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN +DC-5D-31 (hex) ITEL MOBILE LIMITED +DC5D31 (base 16) ITEL MOBILE LIMITED + RM B3 & B4 BLOCK B, KO FAI INDUSTRIAL BUILDING NO.7 KO FAI ROAD, YAU TONG, KLN, H.K + Hong Kong KOWLOON 999077 + HK 80-E8-69 (hex) AltoBeam Inc. 80E869 (base 16) AltoBeam Inc. @@ -186641,18 +186779,6 @@ D489C1 (base 16) Ubiquiti Inc New York NY New York NY 10017 US -24-D6-60 (hex) Silicon Laboratories -24D660 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US - -18-C3-E4 (hex) IEEE Registration Authority -18C3E4 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US - 8C-0F-7E (hex) TCL King Electrical Appliances(Huizhou)Co.,Ltd 8C0F7E (base 16) TCL King Electrical Appliances(Huizhou)Co.,Ltd B Area, 10th floor, TCL multimedia Building, TCL International E City, #1001 Zhonshanyuan road,Shenzhen @@ -186671,6 +186797,12 @@ D489C1 (base 16) Ubiquiti Inc Nanzi Dist. Kaohsiung 811643 TW +F0-0C-51 (hex) zte corporation +F00C51 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + 08-95-36 (hex) Actiontec Electronics Inc. 089536 (base 16) Actiontec Electronics Inc. 2445 Augustine Dr #501 @@ -186683,18 +186815,48 @@ D489C1 (base 16) Ubiquiti Inc San Jose CA 94568 US -C0-3A-55 (hex) TP-Link Systems Inc. -C03A55 (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 - US - 90-6F-18 (hex) Afero, Inc. 906F18 (base 16) Afero, Inc. 4410 El Camino Real, Suite 200 Los Altos 94022 US +24-D6-60 (hex) Silicon Laboratories +24D660 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + +18-C3-E4 (hex) IEEE Registration Authority +18C3E4 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +18-4F-5D (hex) Japan Radio Co., Ltd +184F5D (base 16) Japan Radio Co., Ltd + NAKANO CENTRAL PARK EAST 10-1, Nakano 4-chome + Nakano-ku Tokyo 164-8570 + JP + +6C-28-13 (hex) nFore Technology Co., Ltd. +6C2813 (base 16) nFore Technology Co., Ltd. + 5F, No.31, Ln. 258, Ruiguang Rd., Neihu Dist., + Taipei city 114 + TW + +08-35-7D (hex) Microsoft Corporation +08357D (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US + +C0-3A-55 (hex) TP-Link Systems Inc. +C03A55 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US + B8-87-88 (hex) HP Inc. B88788 (base 16) HP Inc. 10300 Energy Dr @@ -186725,23 +186887,17 @@ F8CB15 (base 16) Apple, Inc. Cupertino CA 95014 US -18-4F-5D (hex) Japan Radio Co., Ltd -184F5D (base 16) Japan Radio Co., Ltd - NAKANO CENTRAL PARK EAST 10-1, Nakano 4-chome - Nakano-ku Tokyo 164-8570 - JP - -6C-28-13 (hex) nFore Technology Co., Ltd. -6C2813 (base 16) nFore Technology Co., Ltd. - 5F, No.31, Ln. 258, Ruiguang Rd., Neihu Dist., - Taipei city 114 - TW +78-45-DC (hex) HUAWEI TECHNOLOGIES CO.,LTD +7845DC (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -08-35-7D (hex) Microsoft Corporation -08357D (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 - US +10-49-0E (hex) HUAWEI TECHNOLOGIES CO.,LTD +10490E (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN D4-CE-40 (hex) Apple, Inc. D4CE40 (base 16) Apple, Inc. @@ -186755,6 +186911,60 @@ D4CE40 (base 16) Apple, Inc. Cupertino CA 95014 US +F0-D0-18 (hex) Hewlett Packard Enterprise +F0D018 (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US + +00-17-1E (hex) Benning Elektrotechnik und Elektronik GmbH & Co. KG +00171E (base 16) Benning Elektrotechnik und Elektronik GmbH & Co. KG + Muensterstraße 135-137 + Bocholt NRW 46397 + DE + +D8-62-CA (hex) Cisco Systems, Inc +D862CA (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +44-78-31 (hex) HUAWEI TECHNOLOGIES CO.,LTD +447831 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +50-37-CD (hex) Quectel Wireless Solutions Co., Ltd. +5037CD (base 16) Quectel Wireless Solutions Co., Ltd. + Building 5, Shanghai Business Park Phase III (Area B), No.1016 Tianlin Road, Minhang District + Shanghai 200233 + CN + +74-25-54 (hex) NVIDIA Corporation +742554 (base 16) NVIDIA Corporation + 2701 San Tomas Expressway + Santa Clara CA 95050 + US + +54-E6-FD (hex) Sony Interactive Entertainment Inc. +54E6FD (base 16) Sony Interactive Entertainment Inc. + 1-7-1 Konan + Minato-ku Tokyo 108-0075 + JP + +6C-BF-2F (hex) eero inc. +6CBF2F (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +1C-E4-DD (hex) Technicolor (China) Technology Co., Ltd. +1CE4DD (base 16) Technicolor (China) Technology Co., Ltd. + No.A2181,2F,Zhongguancun Dongsheng Science and Technology Park, Jia No.18, Xueqing Rd., Haidian District + Beijing 100083 + CN + C8-5C-E2 (hex) IEEE Registration Authority C85CE2 (base 16) IEEE Registration Authority 445 Hoes Lane @@ -230861,18 +231071,6 @@ A083B4 (base 16) Velorum B.V Haps 5443NA NL -8C-05-72 (hex) Huawei Device Co., Ltd. -8C0572 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - -9C-7D-C0 (hex) Tech4home, Lda -9C7DC0 (base 16) Tech4home, Lda - Rua de Fundoes N151 - Sao Joao da Madeira Aveiro 3700-121 - PT - 60-0A-8C (hex) Shenzhen Sundray Technologies company Limited 600A8C (base 16) Shenzhen Sundray Technologies company Limited 1st Floor Building A1, Nanshan i Park, No.1001 Xueyuan Road, Nanshan District, Shenzhen, Guangdong Province, P. R. China @@ -230891,17 +231089,23 @@ B00B22 (base 16) Huawei Device Co., Ltd. Dongguan Guangdong 523808 CN +8C-05-72 (hex) Huawei Device Co., Ltd. +8C0572 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + 10-25-CE (hex) ELKA - Torantriebe GmbH u. Co. Betriebs KG 1025CE (base 16) ELKA - Torantriebe GmbH u. Co. Betriebs KG Dithmarscher Straße 9 Tönning 25832 DE -B4-E5-3E (hex) Ruckus Wireless -B4E53E (base 16) Ruckus Wireless - 350 West Java Drive - Sunnyvale CA 94089 - US +9C-7D-C0 (hex) Tech4home, Lda +9C7DC0 (base 16) Tech4home, Lda + Rua de Fundoes N151 + Sao Joao da Madeira Aveiro 3700-121 + PT 20-A7-16 (hex) Silicon Laboratories 20A716 (base 16) Silicon Laboratories @@ -230957,6 +231161,24 @@ F0B014 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Berlin Berlin 10559 DE +48-5D-35 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +485D35 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +B4-FC-7D (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +B4FC7D (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +98-A9-65 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +98A965 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + EC-74-27 (hex) eero inc. EC7427 (base 16) eero inc. 660 3rd Street @@ -230987,24 +231209,6 @@ A08E24 (base 16) eero inc. San Francisco CA 94107 US -74-42-7F (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -74427F (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE - -1C-ED-6F (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -1CED6F (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE - -48-5D-35 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -485D35 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE - B4-20-46 (hex) eero inc. B42046 (base 16) eero inc. 660 3rd Street @@ -231029,38 +231233,14 @@ D405DE (base 16) eero inc. San Francisco CA 94107 US -7C-49-CF (hex) eero inc. -7C49CF (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -24-2D-6C (hex) eero inc. -242D6C (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -30-3A-4A (hex) eero inc. -303A4A (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -DC-69-B5 (hex) eero inc. -DC69B5 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -B4-FC-7D (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -B4FC7D (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +74-42-7F (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +74427F (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -98-A9-65 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -98A965 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +1C-ED-6F (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +1CED6F (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE @@ -231077,6 +231257,12 @@ A8B088 (base 16) eero inc. San Francisco CA 94107 US +B4-E5-3E (hex) Ruckus Wireless +B4E53E (base 16) Ruckus Wireless + 350 West Java Drive + Sunnyvale CA 94089 + US + 88-67-46 (hex) eero inc. 886746 (base 16) eero inc. 660 3rd Street @@ -231113,36 +231299,6 @@ FC3D73 (base 16) eero inc. Beijing 100053 CN -E4-C0-CC (hex) China Mobile Group Device Co.,Ltd. -E4C0CC (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - -C0-16-92 (hex) China Mobile Group Device Co.,Ltd. -C01692 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - -00-E2-2C (hex) China Mobile Group Device Co.,Ltd. -00E22C (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - -E0-E0-C2 (hex) China Mobile Group Device Co.,Ltd. -E0E0C2 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - -78-2E-56 (hex) China Mobile Group Device Co.,Ltd. -782E56 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - 00-CF-C0 (hex) China Mobile Group Device Co.,Ltd. 00CFC0 (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District @@ -231167,26 +231323,32 @@ E0456D (base 16) China Mobile Group Device Co.,Ltd. Beijing 100053 CN -5C-75-C6 (hex) China Mobile Group Device Co.,Ltd. -5C75C6 (base 16) China Mobile Group Device Co.,Ltd. +E4-C0-CC (hex) China Mobile Group Device Co.,Ltd. +E4C0CC (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District Beijing 100053 CN -24-12-81 (hex) China Mobile Group Device Co.,Ltd. -241281 (base 16) China Mobile Group Device Co.,Ltd. +C0-16-92 (hex) China Mobile Group Device Co.,Ltd. +C01692 (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District Beijing 100053 CN -64-C5-82 (hex) China Mobile Group Device Co.,Ltd. -64C582 (base 16) China Mobile Group Device Co.,Ltd. +00-E2-2C (hex) China Mobile Group Device Co.,Ltd. +00E22C (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District Beijing 100053 CN -44-8E-EC (hex) China Mobile Group Device Co.,Ltd. -448EEC (base 16) China Mobile Group Device Co.,Ltd. +E0-E0-C2 (hex) China Mobile Group Device Co.,Ltd. +E0E0C2 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +78-2E-56 (hex) China Mobile Group Device Co.,Ltd. +782E56 (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District Beijing 100053 CN @@ -231209,6 +231371,48 @@ A088C2 (base 16) Mellanox Technologies, Inc. Sunnyvale CA 94085 US +7C-49-CF (hex) eero inc. +7C49CF (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +24-2D-6C (hex) eero inc. +242D6C (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +30-3A-4A (hex) eero inc. +303A4A (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +DC-69-B5 (hex) eero inc. +DC69B5 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +5C-75-C6 (hex) China Mobile Group Device Co.,Ltd. +5C75C6 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +24-12-81 (hex) China Mobile Group Device Co.,Ltd. +241281 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +64-C5-82 (hex) China Mobile Group Device Co.,Ltd. +64C582 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + E0-9D-73 (hex) Mellanox Technologies, Inc. E09D73 (base 16) Mellanox Technologies, Inc. 350 Oakmead Parkway, Suite 100 @@ -231221,6 +231425,12 @@ E09D73 (base 16) Mellanox Technologies, Inc. Sunnyvale CA 94085 US +44-8E-EC (hex) China Mobile Group Device Co.,Ltd. +448EEC (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + 50-CF-56 (hex) China Mobile Group Device Co.,Ltd. 50CF56 (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District @@ -231233,11 +231443,11 @@ C82478 (base 16) Edifier International Hong Kong 070 CN -D4-A0-FB (hex) IEEE Registration Authority -D4A0FB (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US +F8-F2-95 (hex) Annapurna labs +F8F295 (base 16) Annapurna labs + Matam Scientific Industries Center, Building 8.2 + Mail box 15123 Haifa 3508409 + IL E0-42-6D (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD E0426D (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD @@ -231245,18 +231455,18 @@ E0426D (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD DONG GUAN GUANG DONG 523860 CN -F8-F2-95 (hex) Annapurna labs -F8F295 (base 16) Annapurna labs - Matam Scientific Industries Center, Building 8.2 - Mail box 15123 Haifa 3508409 - IL - 80-03-0D (hex) CANON INC. 80030D (base 16) CANON INC. 30-2 Shimomaruko 3-chome, Ohta-ku Tokyo 146-8501 JP +D4-A0-FB (hex) IEEE Registration Authority +D4A0FB (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + 18-C1-E2 (hex) Qolsys Inc. 18C1E2 (base 16) Qolsys Inc. 1919 S Bascom Ave Suit 600 @@ -231299,18 +231509,6 @@ B0F3E9 (base 16) PATEO CONNECT (Xiamen) Co., Ltd. shenzhen guangdong 518057 CN -00-C8-4E (hex) Hewlett Packard Enterprise -00C84E (base 16) Hewlett Packard Enterprise - 6280 America Center Dr - San Jose CA 95002 - US - -9C-13-9E (hex) Espressif Inc. -9C139E (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 84-31-A8 (hex) Funshion Online Technologies Co.,Ltd 8431A8 (base 16) Funshion Online Technologies Co.,Ltd 2101, Floor 1-2, Building 9, Anzhen Xili District 3, Chaoyang District, Beijing @@ -231323,28 +231521,40 @@ B0F3E9 (base 16) PATEO CONNECT (Xiamen) Co., Ltd. Beijing 100029 CN +D4-7A-EC (hex) Funshion Online Technologies Co.,Ltd +D47AEC (base 16) Funshion Online Technologies Co.,Ltd + 2101, Floor 1-2, Building 9, Anzhen Xili District 3, Chaoyang District, Beijing + Beijing 100029 + CN + 40-A7-86 (hex) TECNO MOBILE LIMITED 40A786 (base 16) TECNO MOBILE LIMITED ROOMS 05-15, 13A/F., SOUTH TOWER, WORLD FINANCE CENTRE, HARBOUR CITY, 17 CANTON ROAD, TSIM SHA TSUI, KOWLOON, HONG KONG Hong Kong Hong Kong 999077 HK -D4-7A-EC (hex) Funshion Online Technologies Co.,Ltd -D47AEC (base 16) Funshion Online Technologies Co.,Ltd - 2101, Floor 1-2, Building 9, Anzhen Xili District 3, Chaoyang District, Beijing - Beijing 100029 +9C-13-9E (hex) Espressif Inc. +9C139E (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 CN +00-C8-4E (hex) Hewlett Packard Enterprise +00C84E (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US + 88-DA-36 (hex) Calix Inc. 88DA36 (base 16) Calix Inc. 2777 Orchard Pkwy San Jose CA 95131 US -40-10-ED (hex) G.Tech Technology Ltd. -4010ED (base 16) G.Tech Technology Ltd. - No.8,Jinyuan 1st Road,Tangjiawan Town, High-tech Zone - Zhuhai Guangdong 519085 +98-17-1A (hex) Beijing Xiaomi Mobile Software Co., Ltd +98171A (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN EC-10-55 (hex) Beijing Xiaomi Electronics Co.,Ltd @@ -231359,12 +231569,6 @@ EC1055 (base 16) Beijing Xiaomi Electronics Co.,Ltd Shanghai Shanghai 201203 CN -98-17-1A (hex) Beijing Xiaomi Mobile Software Co., Ltd -98171A (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 - CN - 2C-DC-C1 (hex) EM Microelectronic 2CDCC1 (base 16) EM Microelectronic Rue des Sors 3 @@ -231389,16 +231593,10 @@ D853AD (base 16) Cisco Meraki San Francisco 94158 US -30-F8-56 (hex) Extreme Networks Headquarters -30F856 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville 27560 - US - -80-40-05 (hex) Guangdong COROS Sports Technology Co.,Ltd -804005 (base 16) Guangdong COROS Sports Technology Co.,Ltd - Room 601 & 701, Bld. 2, No.2, Science and Technology 9 Rd, Songshan Lake Hi-Tech Zone, Dongguan 523808, Guandong, China - Dongguan Guangdong 523808 +40-10-ED (hex) G.Tech Technology Ltd. +4010ED (base 16) G.Tech Technology Ltd. + No.8,Jinyuan 1st Road,Tangjiawan Town, High-tech Zone + Zhuhai Guangdong 519085 CN 68-A5-93 (hex) Apple, Inc. @@ -231431,10 +231629,10 @@ B8011F (base 16) Apple, Inc. Hui Zhou Guangdong 516025 CN -B0-25-AA (hex) AIstone Global Limited -B025AA (base 16) AIstone Global Limited - 29/F. , One Exchange Square 8 - Connaught Place Centa Hong Kong 999077 +80-40-05 (hex) Guangdong COROS Sports Technology Co.,Ltd +804005 (base 16) Guangdong COROS Sports Technology Co.,Ltd + Room 601 & 701, Bld. 2, No.2, Science and Technology 9 Rd, Songshan Lake Hi-Tech Zone, Dongguan 523808, Guandong, China + Dongguan Guangdong 523808 CN DC-93-96 (hex) Apple, Inc. @@ -231455,24 +231653,18 @@ CCEA27 (base 16) GE Appliances Louisville KY 40225 US +30-F8-56 (hex) Extreme Networks Headquarters +30F856 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville 27560 + US + 8C-3D-16 (hex) Shenzhen Four Seas Global Link Network Technology Co.,Ltd 8C3D16 (base 16) Shenzhen Four Seas Global Link Network Technology Co.,Ltd 9/F, Block H, South China Digital Valley, No.1 South China Road, Longhua District, Shenzhen ,China Shenzhen 518000 CN -48-F6-EE (hex) Espressif Inc. -48F6EE (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -7C-31-FA (hex) Silicon Laboratories -7C31FA (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US - C0-88-40 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. C08840 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. Midea Global Innovation Center,Beijiao Town,Shunde @@ -231491,6 +231683,12 @@ D0C67F (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN +B0-25-AA (hex) AIstone Global Limited +B025AA (base 16) AIstone Global Limited + 29/F. , One Exchange Square 8 + Connaught Place Centa Hong Kong 999077 + CN + A0-39-F9 (hex) Sagemcom Broadband SAS A039F9 (base 16) Sagemcom Broadband SAS 250, route de l'Empereur @@ -231503,12 +231701,6 @@ B48931 (base 16) Silicon Laboratories Austin TX 78701 US -10-5E-AE (hex) New H3C Technologies Co., Ltd -105EAE (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 - CN - 4C-AD-DF (hex) Công ty Cổ phần Thiết bị Công nghiệp GEIC 4CADDF (base 16) Công ty Cổ phần Thiết bị Công nghiệp GEIC 52 Lê Đại Hành, phường Lê Đại Hành, quận Hai Bà Trưng @@ -231527,11 +231719,17 @@ B48931 (base 16) Silicon Laboratories Reno NV 89507 US -08-EB-21 (hex) Intel Corporate -08EB21 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +48-F6-EE (hex) Espressif Inc. +48F6EE (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +7C-31-FA (hex) Silicon Laboratories +7C31FA (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US 3C-A0-70 (hex) Blink by Amazon 3CA070 (base 16) Blink by Amazon @@ -231539,11 +231737,11 @@ B48931 (base 16) Silicon Laboratories North Reading MA 01864 US -E8-C9-13 (hex) Samsung Electronics Co.,Ltd -E8C913 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +10-5E-AE (hex) New H3C Technologies Co., Ltd +105EAE (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN 24-F4-0A (hex) Samsung Electronics Co.,Ltd 24F40A (base 16) Samsung Electronics Co.,Ltd @@ -231551,35 +231749,41 @@ E8C913 (base 16) Samsung Electronics Co.,Ltd Gumi Gyeongbuk 730-350 KR -58-E4-EB (hex) FN-LINK TECHNOLOGY Ltd. -58E4EB (base 16) FN-LINK TECHNOLOGY Ltd. - No.8, Litong Road, Liuyang Economic & Technical Development Zone, Changsha, Hunan,China - Changsha Hunan 410329 - CN - 78-C1-1D (hex) Samsung Electronics Co.,Ltd 78C11D (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR +E8-C9-13 (hex) Samsung Electronics Co.,Ltd +E8C913 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + 4C-A9-54 (hex) Intel Corporate 4CA954 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY +08-EB-21 (hex) Intel Corporate +08EB21 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + 14-C2-4D (hex) ATW TECHNOLOGY, INC. 14C24D (base 16) ATW TECHNOLOGY, INC. 1F, No.236 Ba’ai Street, Shulin District New Taipei City 23845 TW -14-05-89 (hex) Motorola Mobility LLC, a Lenovo Company -140589 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 - US +58-E4-EB (hex) FN-LINK TECHNOLOGY Ltd. +58E4EB (base 16) FN-LINK TECHNOLOGY Ltd. + No.8, Litong Road, Liuyang Economic & Technical Development Zone, Changsha, Hunan,China + Changsha Hunan 410329 + CN 98-3A-1F (hex) Google, Inc. 983A1F (base 16) Google, Inc. @@ -231611,6 +231815,24 @@ B06B11 (base 16) Hui Zhou Gaoshengda Technology Co.,LTD Beijing Haidian District 100085 CN +14-05-89 (hex) Motorola Mobility LLC, a Lenovo Company +140589 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US + +80-6A-34 (hex) Bouffalo Lab (Nanjing) Co., Ltd. +806A34 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. + 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China + Nanjing Jiangsu 211800 + CN + +54-DD-21 (hex) Huawei Device Co., Ltd. +54DD21 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + AC-10-65 (hex) KT Micro, Inc. AC1065 (base 16) KT Micro, Inc. Building 76, National Cybersecurity Industry Park, Beiwucun Road 23, Haidian District, Beijing @@ -231629,18 +231851,6 @@ D4FF26 (base 16) OHSUNG Reno NV 89507 US -80-6A-34 (hex) Bouffalo Lab (Nanjing) Co., Ltd. -806A34 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. - 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China - Nanjing Jiangsu 211800 - CN - -54-DD-21 (hex) Huawei Device Co., Ltd. -54DD21 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - A8-24-50 (hex) Beijing Huadianzhongxin Tech.Co.,Ltd A82450 (base 16) Beijing Huadianzhongxin Tech.Co.,Ltd Room 318,the 3rd Floorl,Xingtianhaiyuan Building,Xianghuangqi East Rd,Nongda South Rd, Haidian District,Beijing,P.R.C @@ -231665,12 +231875,6 @@ C8E31D (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -E4-56-AC (hex) Silicon Laboratories -E456AC (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US - 0C-33-1B (hex) TydenBrooks 0C331B (base 16) TydenBrooks 2727 Paces Ferry Rd, Building 2, Suite 300 @@ -231701,36 +231905,6 @@ E46E8A (base 16) BYD Lithium Battery Co., Ltd. Shen Zhen Guang Dong 518100 CN -C8-C8-3F (hex) Texas Instruments -C8C83F (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US - -E0-D4-91 (hex) Cisco Systems, Inc -E0D491 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -A4-DC-D5 (hex) Cisco Systems, Inc -A4DCD5 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -D8-52-FA (hex) Texas Instruments -D852FA (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US - -38-E2-C4 (hex) Texas Instruments -38E2C4 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US - 28-57-5D (hex) Apple, Inc. 28575D (base 16) Apple, Inc. 1 Infinite Loop @@ -231743,12 +231917,24 @@ D852FA (base 16) Texas Instruments Cupertino CA 95014 US +E4-56-AC (hex) Silicon Laboratories +E456AC (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + 54-91-E1 (hex) Vitalacy Inc. 5491E1 (base 16) Vitalacy Inc. 11859 Wilshire Blvd #500 Los Angeles CA 90025 US +D8-52-FA (hex) Texas Instruments +D852FA (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + F4-33-B7 (hex) Apple, Inc. F433B7 (base 16) Apple, Inc. 1 Infinite Loop @@ -231767,17 +231953,29 @@ D42F4B (base 16) Hon Hai Precision Industry Co.,LTD TAIPEI 66.Chung Shan RD, TU-CHENG Industrial , district new TAIPEI 33859 CN -90-29-62 (hex) Linkpower Microelectronics Co., Ltd. -902962 (base 16) Linkpower Microelectronics Co., Ltd. - 905, B1, 999-8, Gaolang East Road, Wuxi Economic Development Zone, Jiangsu Province - wuxi jiangsu 214131 - CN +38-E2-C4 (hex) Texas Instruments +38E2C4 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US -84-9D-4B (hex) Shenzhen Boomtech Industrial Corporation -849D4B (base 16) Shenzhen Boomtech Industrial Corporation - 905/906,BuildingA, Huizhi R&D Center. Xixiang,Bao'an District - Shenzhen 518100 - CN +C8-C8-3F (hex) Texas Instruments +C8C83F (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +E0-D4-91 (hex) Cisco Systems, Inc +E0D491 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +A4-DC-D5 (hex) Cisco Systems, Inc +A4DCD5 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US 54-FB-66 (hex) ASRock Incorporation 54FB66 (base 16) ASRock Incorporation @@ -231785,6 +231983,12 @@ D42F4B (base 16) Hon Hai Precision Industry Co.,LTD Taipei 112 TW +90-29-62 (hex) Linkpower Microelectronics Co., Ltd. +902962 (base 16) Linkpower Microelectronics Co., Ltd. + 905, B1, 999-8, Gaolang East Road, Wuxi Economic Development Zone, Jiangsu Province + wuxi jiangsu 214131 + CN + 2C-15-7E (hex) RADIODATA GmbH 2C157E (base 16) RADIODATA GmbH Newtonstraße 18 @@ -231809,12 +232013,24 @@ D42F4B (base 16) Hon Hai Precision Industry Co.,LTD shenzhen guangdong 518000 CN +A4-3A-39 (hex) AURORA TECHNOLOGIES CO.,LTD. +A43A39 (base 16) AURORA TECHNOLOGIES CO.,LTD. + ROOM 1006, BLOCK B, QIANHAI ECONOMIC AND TRADE CENTER, CHINA MERCHANTS GROUP, NO.151 WEST FREE TRADE STREET, QIANHAI, + SHENZHEN 518000 + CN + 34-E1-D7 (hex) NXP Semiconductors Taiwan Ltd. 34E1D7 (base 16) NXP Semiconductors Taiwan Ltd. No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan Nanzi Dist. Kaohsiung 811643 TW +84-9D-4B (hex) Shenzhen Boomtech Industrial Corporation +849D4B (base 16) Shenzhen Boomtech Industrial Corporation + 905/906,BuildingA, Huizhi R&D Center. Xixiang,Bao'an District + Shenzhen 518100 + CN + 70-A3-A4 (hex) Beijing Guming Communication Technology Co., Ltd. 70A3A4 (base 16) Beijing Guming Communication Technology Co., Ltd. Room 202-6, 2nd Floor, Building 1, No. 8 Courtyard, Yongchang Middle Road, Beijing Economic and Technological Development Area, Beijing @@ -231833,18 +232049,6 @@ D42F4B (base 16) Hon Hai Precision Industry Co.,LTD Hsinchu City Hsinchu 30071 TW -A4-3A-39 (hex) AURORA TECHNOLOGIES CO.,LTD. -A43A39 (base 16) AURORA TECHNOLOGIES CO.,LTD. - ROOM 1006, BLOCK B, QIANHAI ECONOMIC AND TRADE CENTER, CHINA MERCHANTS GROUP, NO.151 WEST FREE TRADE STREET, QIANHAI, - SHENZHEN 518000 - CN - -C0-2E-1D (hex) Nokia Solutions and Networks GmbH & Co. KG -C02E1D (base 16) Nokia Solutions and Networks GmbH & Co. KG - Werinherstrasse 91 - München Bavaria D-81541 - DE - 20-36-D0 (hex) Motorola Mobility LLC, a Lenovo Company 2036D0 (base 16) Motorola Mobility LLC, a Lenovo Company 222 West Merchandise Mart Plaza @@ -231863,11 +232067,17 @@ C02E1D (base 16) Nokia Solutions and Networks GmbH & Co. KG Shenzhen Guangdong 518172 CN -BC-87-53 (hex) Sera Network Inc. -BC8753 (base 16) Sera Network Inc. - 2F, No.60, 321 Ln., Yangguang St., Neihu Dist., - Taipei Taiwan 114717 - TW +68-4A-6E (hex) Quectel Wireless Solutions Co.,Ltd. +684A6E (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 + CN + +64-24-4D (hex) Hangzhou Ezviz Software Co.,Ltd. +64244D (base 16) Hangzhou Ezviz Software Co.,Ltd. + 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District + Hangzhou Zhejiang 310051 + CN 0C-A6-4C (hex) Hangzhou Ezviz Software Co.,Ltd. 0CA64C (base 16) Hangzhou Ezviz Software Co.,Ltd. @@ -231887,17 +232097,17 @@ AC1C26 (base 16) Hangzhou Ezviz Software Co.,Ltd. Hangzhou Zhejiang 310051 CN -64-24-4D (hex) Hangzhou Ezviz Software Co.,Ltd. -64244D (base 16) Hangzhou Ezviz Software Co.,Ltd. - 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District - Hangzhou Zhejiang 310051 - CN +C0-2E-1D (hex) Nokia Solutions and Networks GmbH & Co. KG +C02E1D (base 16) Nokia Solutions and Networks GmbH & Co. KG + Werinherstrasse 91 + München Bavaria D-81541 + DE -68-4A-6E (hex) Quectel Wireless Solutions Co.,Ltd. -684A6E (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 - CN +BC-87-53 (hex) Sera Network Inc. +BC8753 (base 16) Sera Network Inc. + 2F, No.60, 321 Ln., Yangguang St., Neihu Dist., + Taipei Taiwan 114717 + TW 50-FA-CB (hex) IEEE Registration Authority 50FACB (base 16) IEEE Registration Authority @@ -231917,12 +232127,6 @@ B85213 (base 16) zte corporation shenzhen guangdong 518057 CN -2C-27-E4 (hex) Luxshare Precision Industry (Xuancheng) Co.,Ltd. -2C27E4 (base 16) Luxshare Precision Industry (Xuancheng) Co.,Ltd. - Address: No.5 Baishou Road, Xuancheng High-Tech Industrial Development Zone - Xuancheng Anhui 242000 - CN - 9C-6D-92 (hex) Shanghai Kanghai Infomation System CO.,LTD 9C6D92 (base 16) Shanghai Kanghai Infomation System CO.,LTD Room 207, Building 1, 6055 Songze Avenue , Qingpu District, Shanghai @@ -231983,10 +232187,10 @@ AC393D (base 16) eero inc. LAKE FOREST CA 92630 US -B4-04-29 (hex) Guangzhou Shiyuan Electronic Technology Company Limited -B40429 (base 16) Guangzhou Shiyuan Electronic Technology Company Limited - No.6, 4th Yunpu Road, Yunpu industry District - Guangzhou Guangdong 510530 +2C-27-E4 (hex) Luxshare Precision Industry (Xuancheng) Co.,Ltd. +2C27E4 (base 16) Luxshare Precision Industry (Xuancheng) Co.,Ltd. + Address: No.5 Baishou Road, Xuancheng High-Tech Industrial Development Zone + Xuancheng Anhui 242000 CN 4C-D7-4A (hex) Vantiva USA LLC @@ -232007,6 +232211,12 @@ FCCF9F (base 16) EM Microelectronic Marin-Epagnier Neuchatel 2074 CH +B4-04-29 (hex) Guangzhou Shiyuan Electronic Technology Company Limited +B40429 (base 16) Guangzhou Shiyuan Electronic Technology Company Limited + No.6, 4th Yunpu Road, Yunpu industry District + Guangzhou Guangdong 510530 + CN + D4-25-DE (hex) New H3C Technologies Co., Ltd D425DE (base 16) New H3C Technologies Co., Ltd 466 Changhe Road, Binjiang District @@ -232061,6 +232271,12 @@ B0E8E8 (base 16) Silicon Laboratories Hsin-Chu R.O.C. 308 TW +F8-6D-CC (hex) WNC Corporation +F86DCC (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + 70-EB-A5 (hex) Huawei Device Co., Ltd. 70EBA5 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone @@ -232073,12 +232289,6 @@ C890F7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -F8-6D-CC (hex) WNC Corporation -F86DCC (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW - 20-58-43 (hex) WNC Corporation 205843 (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park @@ -232097,12 +232307,6 @@ F040AF (base 16) IEEE Registration Authority Piscataway NJ 08554 US -E4-7C-1A (hex) mercury corperation -E47C1A (base 16) mercury corperation - 90,gajaeul-ro,seo-gu,incheon - incheon 22830 - KR - 28-B4-46 (hex) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD 28B446 (base 16) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD Unit East Block22-24/F,Skyworth semiconductor design Bldg., Gaoxin Ave.4.S.,Nanshan District,Shenzhen,China @@ -232157,6 +232361,12 @@ C878F7 (base 16) Cisco Systems, Inc Shenzhen Guangdong 518109 CN +E4-7C-1A (hex) mercury corperation +E47C1A (base 16) mercury corperation + 90,gajaeul-ro,seo-gu,incheon + incheon 22830 + KR + 04-5F-A6 (hex) Shenzhen SDMC Technology CP,.LTD 045FA6 (base 16) Shenzhen SDMC Technology CP,.LTD 19/F, Changhong Science &Technology Mansion,No.18, Keji South 12th Road High-tech IndustrialPark Nanshan District,Shenzhen,China @@ -232346,10 +232556,10 @@ C03F0E (base 16) NETGEAR San Jose CA 95134 US -CC-03-3D (hex) Beijing Xiaomi Mobile Software Co., Ltd -CC033D (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 +3C-EF-A5 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +3CEFA5 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. + B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China + Nanning Guangxi 530007 CN 04-17-4C (hex) Nanjing SCIYON Wisdom Technology Group Co.,Ltd. @@ -232358,6 +232568,18 @@ CC033D (base 16) Beijing Xiaomi Mobile Software Co., Ltd Nanjing 211800 CN +CC-03-3D (hex) Beijing Xiaomi Mobile Software Co., Ltd +CC033D (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 + CN + +50-31-23 (hex) FN-LINK TECHNOLOGY Ltd. +503123 (base 16) FN-LINK TECHNOLOGY Ltd. + No.8, Litong Road, Liuyang Economic & Technical Development Zone, Changsha, Hunan,China + Changsha Hunan 410329 + CN + E0-C2-50 (hex) NETGEAR E0C250 (base 16) NETGEAR 3553 N. First Street @@ -232394,12 +232616,6 @@ A040A0 (base 16) NETGEAR San Jose CA 95134 US -3C-EF-A5 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. -3CEFA5 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. - B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China - Nanning Guangxi 530007 - CN - 30-CB-89 (hex) OnLogic Inc 30CB89 (base 16) OnLogic Inc 435 Community Drive @@ -232412,24 +232628,6 @@ E48F09 (base 16) ithinx GmbH Koeln / Cologne 51063 DE -50-31-23 (hex) FN-LINK TECHNOLOGY Ltd. -503123 (base 16) FN-LINK TECHNOLOGY Ltd. - No.8, Litong Road, Liuyang Economic & Technical Development Zone, Changsha, Hunan,China - Changsha Hunan 410329 - CN - -10-13-31 (hex) Vantiva Technologies Belgium -101331 (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE - -D4-92-5E (hex) Vantiva Technologies Belgium -D4925E (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE - BC-D9-FB (hex) China Mobile Group Device Co.,Ltd. BCD9FB (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District @@ -232442,6 +232640,18 @@ BCD9FB (base 16) China Mobile Group Device Co.,Ltd. Regensburg Bayern 93059 DE +D4-92-5E (hex) Vantiva Technologies Belgium +D4925E (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE + +10-13-31 (hex) Vantiva Technologies Belgium +101331 (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE + 20-0E-0F (hex) Panasonic Marketing Middle East & Africa FZE 200E0F (base 16) Panasonic Marketing Middle East & Africa FZE P.O Box 17985 Jebel Ali @@ -232460,20 +232670,26 @@ D8031A (base 16) Ezurio, LLC Zhubei 30251 TW +18-C2-93 (hex) Ezurio, LLC +18C293 (base 16) Ezurio, LLC + 3F.-1, No.145, Xianzheng 9th Rd., + Zhubei 30251 + TW + 88-F9-C0 (hex) KTS Kommunikationstechnik und Systeme GmbH 88F9C0 (base 16) KTS Kommunikationstechnik und Systeme GmbH Schlossstrasse 123 Moenchengladbach NRW 41238 DE -10-88-D3 (hex) HUAWEI TECHNOLOGIES CO.,LTD -1088D3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +14-5E-BC (hex) HUAWEI TECHNOLOGIES CO.,LTD +145EBC (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -14-5E-BC (hex) HUAWEI TECHNOLOGIES CO.,LTD -145EBC (base 16) HUAWEI TECHNOLOGIES CO.,LTD +10-88-D3 (hex) HUAWEI TECHNOLOGIES CO.,LTD +1088D3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN @@ -232490,11 +232706,17 @@ E40177 (base 16) SafeOwl, Inc. Dallas TX 75206 US -18-C2-93 (hex) Ezurio, LLC -18C293 (base 16) Ezurio, LLC - 3F.-1, No.145, Xianzheng 9th Rd., - Zhubei 30251 - TW +28-1D-AA (hex) ASTI India Private Limited +281DAA (base 16) ASTI India Private Limited + Plot No. 75, Ukardi,Japanese Industrial Zone,Ukardi, Taluka-Mandal,Ahmedabad + Ahmedabad Gujarat 382120 + IN + +C0-18-8C (hex) Altus Sistemas de Automação S.A. +C0188C (base 16) Altus Sistemas de Automação S.A. + Av. Theodomiro Porto da Fonseca, 3101 - lote 01 - Cristo Rei + São Leopoldo Rio Grande do Sul 93022-715 + BR 90-7A-BE (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED 907ABE (base 16) GSD VIET NAM TECHNOLOGY COMPANY LIMITED @@ -232514,24 +232736,30 @@ FC8827 (base 16) Apple, Inc. Cupertino CA 95014 US +60-DE-18 (hex) Apple, Inc. +60DE18 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + 30-EC-A3 (hex) Alfatron Electronics INC 30ECA3 (base 16) Alfatron Electronics INC 6518 Old Wake Forest Road STE A Raleigh NC 27616 US +40-38-02 (hex) Silicon Laboratories +403802 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + 00-89-C9 (hex) Extreme Networks Headquarters 0089C9 (base 16) Extreme Networks Headquarters 2121 RDU Center Drive Morrisville 27560 US -60-DE-18 (hex) Apple, Inc. -60DE18 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - 10-BC-36 (hex) Huawei Device Co., Ltd. 10BC36 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone @@ -232550,17 +232778,11 @@ B4F49B (base 16) Huawei Device Co., Ltd. Mumbai Maharashtra 400104 IN -28-1D-AA (hex) ASTI India Private Limited -281DAA (base 16) ASTI India Private Limited - Plot No. 75, Ukardi,Japanese Industrial Zone,Ukardi, Taluka-Mandal,Ahmedabad - Ahmedabad Gujarat 382120 - IN - -C0-18-8C (hex) Altus Sistemas de Automação S.A. -C0188C (base 16) Altus Sistemas de Automação S.A. - Av. Theodomiro Porto da Fonseca, 3101 - lote 01 - Cristo Rei - São Leopoldo Rio Grande do Sul 93022-715 - BR +80-F1-A8 (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. +80F1A8 (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. + 601,Building B2,No.162,Science Avenue,Science City,Guangzhou High-tech Industrial Development Zone,Guangdong Province,China + Guangzhou Guangdong 510663 + CN 74-24-35 (hex) Huawei Device Co., Ltd. 742435 (base 16) Huawei Device Co., Ltd. @@ -232586,12 +232808,6 @@ E880E7 (base 16) Huawei Device Co., Ltd. REDMOND WA 98052 US -40-38-02 (hex) Silicon Laboratories -403802 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US - 5C-5C-75 (hex) IEEE Registration Authority 5C5C75 (base 16) IEEE Registration Authority 445 Hoes Lane @@ -232601,42 +232817,6 @@ E880E7 (base 16) Huawei Device Co., Ltd. A4-F4-CA (hex) Private A4F4CA (base 16) Private -80-F1-A8 (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. -80F1A8 (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. - 601,Building B2,No.162,Science Avenue,Science City,Guangzhou High-tech Industrial Development Zone,Guangdong Province,China - Guangzhou Guangdong 510663 - CN - -F8-91-F5 (hex) Dingtian Technologies Co., Ltd -F891F5 (base 16) Dingtian Technologies Co., Ltd - Rm.3306, Building6, Runyueshan, No.33 Huangzhukeng Rd.,Biling Street,Pingshan District - Shenzhen Guangdong 518100 - CN - -4C-43-F6 (hex) SZ DJI TECHNOLOGY CO.,LTD -4C43F6 (base 16) SZ DJI TECHNOLOGY CO.,LTD - DJI Sky City, No55 Xianyuan Road, Nanshan District - Shenzhen Guangdong 518057 - CN - -7C-A5-3E (hex) Motorola Mobility LLC, a Lenovo Company -7CA53E (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 - US - -D8-31-39 (hex) zte corporation -D83139 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN - -A0-59-11 (hex) Cisco Meraki -A05911 (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 - US - F0-16-53 (hex) YEALINK(XIAMEN) NETWORK TECHNOLOGY CO.,LTD. F01653 (base 16) YEALINK(XIAMEN) NETWORK TECHNOLOGY CO.,LTD. 309, 3th Floor, No.16, Yun Ding North Road, Huli District @@ -232673,28 +232853,34 @@ C46DD1 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -70-97-51 (hex) Beijing Xiaomi Mobile Software Co., Ltd -709751 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 +F8-91-F5 (hex) Dingtian Technologies Co., Ltd +F891F5 (base 16) Dingtian Technologies Co., Ltd + Rm.3306, Building6, Runyueshan, No.33 Huangzhukeng Rd.,Biling Street,Pingshan District + Shenzhen Guangdong 518100 CN -5C-D3-3D (hex) Samsung Electronics Co.,Ltd -5CD33D (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +4C-43-F6 (hex) SZ DJI TECHNOLOGY CO.,LTD +4C43F6 (base 16) SZ DJI TECHNOLOGY CO.,LTD + DJI Sky City, No55 Xianyuan Road, Nanshan District + Shenzhen Guangdong 518057 + CN -AC-DE-01 (hex) Ruckus Wireless -ACDE01 (base 16) Ruckus Wireless - 350 West Java Drive - Sunnyvale CA 94089 +7C-A5-3E (hex) Motorola Mobility LLC, a Lenovo Company +7CA53E (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 US -58-AD-08 (hex) IEEE Registration Authority -58AD08 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +D8-31-39 (hex) zte corporation +D83139 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +A0-59-11 (hex) Cisco Meraki +A05911 (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 US 54-7A-F4 (hex) Bouffalo Lab (Nanjing) Co., Ltd. @@ -232715,28 +232901,22 @@ ACDE01 (base 16) Ruckus Wireless San Jose CA 94568 US -58-BD-35 (hex) SHANGHAI XIANGCHENG COMMUNICATION TECHNOLOGY CO., LTD -58BD35 (base 16) SHANGHAI XIANGCHENG COMMUNICATION TECHNOLOGY CO., LTD - Room 211-5, Building 1, No. 290 Wankang Road, Minhang District - Shanghai Shanghai 201100 - CN - -D4-C1-A8 (hex) KYKXCOM Co., Ltd. -D4C1A8 (base 16) KYKXCOM Co., Ltd. - Building 2, No.8, Yuanhua Road, Xianlin UniversityTown, Xianlin Subdistrict, Qixia District - Nanjing Jiangsu 210033 +70-97-51 (hex) Beijing Xiaomi Mobile Software Co., Ltd +709751 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN -80-99-9B (hex) Murata Manufacturing Co., Ltd. -80999B (base 16) Murata Manufacturing Co., Ltd. - 1-10-1, Higashikotari - Nagaokakyo-shi Kyoto 617-8555 - JP +5C-D3-3D (hex) Samsung Electronics Co.,Ltd +5CD33D (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -B8-58-FF (hex) Arista Networks -B858FF (base 16) Arista Networks - 5453 Great America Parkway - Santa Clara CA 95054 +AC-DE-01 (hex) Ruckus Wireless +ACDE01 (base 16) Ruckus Wireless + 350 West Java Drive + Sunnyvale CA 94089 US C0-B5-50 (hex) Broadcom Limited @@ -232751,23 +232931,23 @@ C0B550 (base 16) Broadcom Limited Thalwil Switzerland CH-8800 CH -40-82-56 (hex) AUMOVIO Germany GmbH -408256 (base 16) AUMOVIO Germany GmbH - VDO-Strasse 1 - Babenhausen Garmany 64832 - DE +58-AD-08 (hex) IEEE Registration Authority +58AD08 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US -54-B2-7E (hex) Sagemcom Broadband SAS -54B27E (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +58-BD-35 (hex) SHANGHAI XIANGCHENG COMMUNICATION TECHNOLOGY CO., LTD +58BD35 (base 16) SHANGHAI XIANGCHENG COMMUNICATION TECHNOLOGY CO., LTD + Room 211-5, Building 1, No. 290 Wankang Road, Minhang District + Shanghai Shanghai 201100 + CN -40-E7-62 (hex) Calix Inc. -40E762 (base 16) Calix Inc. - 2777 Orchard Pkwy - San Jose CA 95131 - US +80-99-9B (hex) Murata Manufacturing Co., Ltd. +80999B (base 16) Murata Manufacturing Co., Ltd. + 1-10-1, Higashikotari + Nagaokakyo-shi Kyoto 617-8555 + JP 00-1E-AE (hex) AUMOVIO Systems, Inc. 001EAE (base 16) AUMOVIO Systems, Inc. @@ -232775,6 +232955,24 @@ C0B550 (base 16) Broadcom Limited Deer Park IL 60010 US +D4-C1-A8 (hex) KYKXCOM Co., Ltd. +D4C1A8 (base 16) KYKXCOM Co., Ltd. + Building 2, No.8, Yuanhua Road, Xianlin UniversityTown, Xianlin Subdistrict, Qixia District + Nanjing Jiangsu 210033 + CN + +B8-58-FF (hex) Arista Networks +B858FF (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 + US + +40-82-56 (hex) AUMOVIO Germany GmbH +408256 (base 16) AUMOVIO Germany GmbH + VDO-Strasse 1 + Babenhausen Garmany 64832 + DE + 18-F7-F6 (hex) Ericsson AB 18F7F6 (base 16) Ericsson AB Torshamnsgatan 36 @@ -232805,12 +233003,60 @@ D89999 (base 16) TECNO MOBILE LIMITED Hong Kong Hong Kong 999077 HK +54-B2-7E (hex) Sagemcom Broadband SAS +54B27E (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR + 84-C7-E2 (hex) VusionGroup 84C7E2 (base 16) VusionGroup Kalsdorfer Straße 12 Fernitz-Mellach Steiermark 8072 AT +40-E7-62 (hex) Calix Inc. +40E762 (base 16) Calix Inc. + 2777 Orchard Pkwy + San Jose CA 95131 + US + +68-1A-47 (hex) Apple, Inc. +681A47 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +28-49-E9 (hex) Apple, Inc. +2849E9 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +78-96-0D (hex) Apple, Inc. +78960D (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +80-1D-39 (hex) Apple, Inc. +801D39 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +CC-72-2A (hex) Apple, Inc. +CC722A (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +AC-40-1E (hex) vivo Mobile Communication Co., Ltd. +AC401E (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN + 34-17-DD (hex) Sercomm France Sarl 3417DD (base 16) Sercomm France Sarl 2/4 Rue Maurice Hartmann 92370 Issy Les Moulineaux France @@ -232823,12 +233069,6 @@ D89999 (base 16) TECNO MOBILE LIMITED Piscataway NJ 08554 US -58-D8-12 (hex) TP-Link Systems Inc. -58D812 (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 - US - 74-E6-C7 (hex) LUXSHARE-ICT Co., Ltd. 74E6C7 (base 16) LUXSHARE-ICT Co., Ltd. 1F, No. 22, Lane 35, Jihu Road, Neihu district @@ -232841,10 +233081,10 @@ D89999 (base 16) TECNO MOBILE LIMITED shenzhen guangdong 518057 CN -AC-40-1E (hex) vivo Mobile Communication Co., Ltd. -AC401E (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 +D0-F8-15 (hex) HUAWEI TECHNOLOGIES CO.,LTD +D0F815 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN 64-D4-F0 (hex) NETVUE,INC. @@ -232859,40 +233099,34 @@ AC401E (base 16) vivo Mobile Communication Co., Ltd. SINGAPORE SINGAPORE 199591 SG -68-1A-47 (hex) Apple, Inc. -681A47 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -28-49-E9 (hex) Apple, Inc. -2849E9 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +04-5E-0A (hex) SHENZHEN TRANSCHAN TECHNOLOGY LIMITED +045E0A (base 16) SHENZHEN TRANSCHAN TECHNOLOGY LIMITED + Room 03, 23/F, Unit B Building, No 9, Shenzhen Bay Eco -Technology Park, Yuehai Street, Nanshan District, Shenzhen, China + Shenzhen 518000 + CN -78-96-0D (hex) Apple, Inc. -78960D (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +A8-6A-CB (hex) EVAR +A86ACB (base 16) EVAR + 42, Changeop-ro, Sujeong-gu, Seongnam-si, Gyeonggi-do, Republic of Korea + Seoul Gyunggi-do 13449 + KR -80-1D-39 (hex) Apple, Inc. -801D39 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +58-D8-12 (hex) TP-Link Systems Inc. +58D812 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 US -CC-72-2A (hex) Apple, Inc. -CC722A (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +70-79-2D (hex) Mellanox Technologies, Inc. +70792D (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 US -D0-F8-15 (hex) HUAWEI TECHNOLOGIES CO.,LTD -D0F815 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +A4-43-1B (hex) Dreamtek Intelligent Technology Co., Ltd +A4431B (base 16) Dreamtek Intelligent Technology Co., Ltd + Room 508, Building A2, Area one of Zhongan Chuanggu Science Park, No. 900 of Wangjiang West Road, High-tech Zone, Hefei, Anhui, China + Shanghai 230000 CN B8-84-11 (hex) Shenzhen Shokz Co., Ltd. @@ -232907,18 +233141,6 @@ B88411 (base 16) Shenzhen Shokz Co., Ltd. Shenzhen Guangdong 518057 CN -70-79-2D (hex) Mellanox Technologies, Inc. -70792D (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US - -A4-43-1B (hex) Dreamtek Intelligent Technology Co., Ltd -A4431B (base 16) Dreamtek Intelligent Technology Co., Ltd - Room 508, Building A2, Area one of Zhongan Chuanggu Science Park, No. 900 of Wangjiang West Road, High-tech Zone, Hefei, Anhui, China - Shanghai 230000 - CN - 94-6A-7C (hex) OnePlus Technology (Shenzhen) Co., Ltd 946A7C (base 16) OnePlus Technology (Shenzhen) Co., Ltd 18C02, 18C03, 18C04 ,18C05,TAIRAN BUILDING, @@ -232931,17 +233153,11 @@ A4431B (base 16) Dreamtek Intelligent Technology Co., Ltd Shanghai Shanghai 201203 CN -04-5E-0A (hex) SHENZHEN TRANSCHAN TECHNOLOGY LIMITED -045E0A (base 16) SHENZHEN TRANSCHAN TECHNOLOGY LIMITED - Room 03, 23/F, Unit B Building, No 9, Shenzhen Bay Eco -Technology Park, Yuehai Street, Nanshan District, Shenzhen, China - Shenzhen 518000 - CN - -A8-6A-CB (hex) EVAR -A86ACB (base 16) EVAR - 42, Changeop-ro, Sujeong-gu, Seongnam-si, Gyeonggi-do, Republic of Korea - Seoul Gyunggi-do 13449 - KR +0C-88-2F (hex) Frog Innovations Limited +0C882F (base 16) Frog Innovations Limited + C23, Sector 80, Phase-II + Noida Uttar Pradesh 201305 + IN F0-4F-E0 (hex) Vizio, Inc F04FE0 (base 16) Vizio, Inc @@ -232955,35 +233171,71 @@ A4CB8F (base 16) Espressif Inc. Shanghai Shanghai 201203 CN -14-2E-43 (hex) Hisense broadband multimedia technology Co.,Ltd -142E43 (base 16) Hisense broadband multimedia technology Co.,Ltd - Song ling Road 399 - Qingdao 266000 +2C-63-A1 (hex) Huawei Device Co., Ltd. +2C63A1 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN +50-0F-C6 (hex) solum +500FC6 (base 16) solum + 2354, Yonggu-daero, Giheung-gu + Yongin-si Gyeonggi-do 16921 + KR + 04-22-E7 (hex) Fiberhome Telecommunication Technologies Co.,LTD 0422E7 (base 16) Fiberhome Telecommunication Technologies Co.,LTD No.5 DongXin Road Wuhan Hubei 430074 CN -2C-63-A1 (hex) Huawei Device Co., Ltd. -2C63A1 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - 2C-E1-87 (hex) New H3C Technologies Co., Ltd 2CE187 (base 16) New H3C Technologies Co., Ltd 466 Changhe Road, Binjiang District Hangzhou Zhejiang 310052 CN -0C-88-2F (hex) Frog Innovations Limited -0C882F (base 16) Frog Innovations Limited - C23, Sector 80, Phase-II - Noida Uttar Pradesh 201305 - IN +14-2E-43 (hex) Hisense broadband multimedia technology Co.,Ltd +142E43 (base 16) Hisense broadband multimedia technology Co.,Ltd + Song ling Road 399 + Qingdao 266000 + CN + +48-92-C1 (hex) OHSUNG +4892C1 (base 16) OHSUNG + 335-4,SANHODAERO,GUMI,GYEONG BUK,KOREA + GUMI GYEONG BUK 730-030 + KR + +4C-30-6A (hex) Nintendo Co.,Ltd +4C306A (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP + +2C-2B-DB (hex) eero inc. +2C2BDB (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +DC-F3-4C (hex) PT HAN SUNG ELECTORONICS INDONESIA +DCF34C (base 16) PT HAN SUNG ELECTORONICS INDONESIA + JL.PALEM 1 BLOK DS-6 + KAWASAN INDUSTRI BATIK LIPPO CIKARANG, DESA CIBATU, KECAMATAN CIKARANG SELATAN BEKASI JAWA BARAT 17550 + ID + +B4-91-07 (hex) HUAWEI TECHNOLOGIES CO.,LTD +B49107 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +FC-EF-D7 (hex) HUAWEI TECHNOLOGIES CO.,LTD +FCEFD7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN EC-58-65 (hex) Shenzhen Xinguodu Technology Co., Ltd EC5865 (base 16) Shenzhen Xinguodu Technology Co., Ltd @@ -233003,12 +233255,6 @@ EC5865 (base 16) Shenzhen Xinguodu Technology Co., Ltd Shanghai Shanghai 201203 CN -50-0F-C6 (hex) solum -500FC6 (base 16) solum - 2354, Yonggu-daero, Giheung-gu - Yongin-si Gyeonggi-do 16921 - KR - 44-61-DF (hex) Skyquad Electronics & Appliances Pvt. Ltd. 4461DF (base 16) Skyquad Electronics & Appliances Pvt. Ltd. 12-50/4/A, Adj to Industrial Estate, MedchalR R District, Hyderabad - 501401, Telangana, India. @@ -233027,42 +233273,12 @@ EC5865 (base 16) Shenzhen Xinguodu Technology Co., Ltd Kulim Kedah 09000 MY -48-92-C1 (hex) OHSUNG -4892C1 (base 16) OHSUNG - 335-4,SANHODAERO,GUMI,GYEONG BUK,KOREA - GUMI GYEONG BUK 730-030 - KR - -FC-EF-D7 (hex) HUAWEI TECHNOLOGIES CO.,LTD -FCEFD7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -4C-30-6A (hex) Nintendo Co.,Ltd -4C306A (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP - -2C-2B-DB (hex) eero inc. -2C2BDB (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +1C-8C-6E (hex) Arista Networks +1C8C6E (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 US -DC-F3-4C (hex) PT HAN SUNG ELECTORONICS INDONESIA -DCF34C (base 16) PT HAN SUNG ELECTORONICS INDONESIA - JL.PALEM 1 BLOK DS-6 - KAWASAN INDUSTRI BATIK LIPPO CIKARANG, DESA CIBATU, KECAMATAN CIKARANG SELATAN BEKASI JAWA BARAT 17550 - ID - -B4-91-07 (hex) HUAWEI TECHNOLOGIES CO.,LTD -B49107 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - 84-1D-E8 (hex) CJ intelligent technology LTD. 841DE8 (base 16) CJ intelligent technology LTD. 4F, No. 16, Zhongxin St., Shulin Dist. @@ -233075,17 +233291,11 @@ C484C0 (base 16) Motorola Mobility LLC, a Lenovo Company Chicago IL 60654 US -CC-35-D9 (hex) Ubiquiti Inc -CC35D9 (base 16) Ubiquiti Inc - 685 Third Avenue, 27th Floor - New York NY New York NY 10017 - US - -A4-F8-FF (hex) Ubiquiti Inc -A4F8FF (base 16) Ubiquiti Inc - 685 Third Avenue, 27th Floor - New York NY New York NY 10017 - US +E8-A9-27 (hex) LEAR +E8A927 (base 16) LEAR + Carrer Fuster 54 + Valls Tarragona 43800 + ES 64-9B-8F (hex) Texas Instruments 649B8F (base 16) Texas Instruments @@ -233105,10 +233315,16 @@ CCC530 (base 16) AzureWave Technology Inc. New Taipei City Taiwan 231 TW -1C-8C-6E (hex) Arista Networks -1C8C6E (base 16) Arista Networks - 5453 Great America Parkway - Santa Clara CA 95054 +A4-F8-FF (hex) Ubiquiti Inc +A4F8FF (base 16) Ubiquiti Inc + 685 Third Avenue, 27th Floor + New York NY New York NY 10017 + US + +CC-35-D9 (hex) Ubiquiti Inc +CC35D9 (base 16) Ubiquiti Inc + 685 Third Avenue, 27th Floor + New York NY New York NY 10017 US 6C-47-80 (hex) IEEE Registration Authority @@ -233117,36 +233333,6 @@ CCC530 (base 16) AzureWave Technology Inc. Piscataway NJ 08554 US -80-C4-29 (hex) Renesas Electronics Operations Services Limited -80C429 (base 16) Renesas Electronics Operations Services Limited - Dukes Meadow, Millboard Raod Bourne End BU - Bourne End BU SL8 5FH - GB - -00-E0-AD (hex) Brandywine Communications UK Ltd. -00E0AD (base 16) Brandywine Communications UK Ltd. - 20a Westside Centre London Road, Stanway, Colchester - ESSEX England CO3 8PH - GB - -50-71-64 (hex) Cisco Systems, Inc -507164 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -E8-A9-27 (hex) LEAR -E8A927 (base 16) LEAR - Carrer Fuster 54 - Valls Tarragona 43800 - ES - -00-1C-44 (hex) Electro Voice Dynacord BV -001C44 (base 16) Electro Voice Dynacord BV - Achtseweg Zuid 173 - 5651 GW Eindhoven Eindhoven 5651 - NL - 50-C3-A2 (hex) nFore Technology Co., Ltd. 50C3A2 (base 16) nFore Technology Co., Ltd. 5F., No.31, Ln. 258, Ruiguang Rd. Neihu Dist., Taipei City 114, Taiwan @@ -233165,35 +233351,23 @@ A40450 (base 16) nFore Technology Co., Ltd. Taipei Neihu District 11491 TW -B0-37-31 (hex) FUJIAN STAR-NET COMMUNICATION CO.,LTD -B03731 (base 16) FUJIAN STAR-NET COMMUNICATION CO.,LTD - 19-22# Building, Star-net Science Plaza, Juyuanzhou, - FUZHOU FUJIAN 350002 - CN - -6C-40-33 (hex) Beijing Megwang Technology Co., Ltd. -6C4033 (base 16) Beijing Megwang Technology Co., Ltd. - Room 1316, 1st Floor, Building 12, Jianzhong Road, Xisanqi Building Materials City, Haidian District, Beijing, China - Beijing 100096 - CN - 44-10-30 (hex) Google, Inc. 441030 (base 16) Google, Inc. 1600 Amphitheatre Parkway Mountain View CA 94043 US -60-A1-FE (hex) HPRO -60A1FE (base 16) HPRO - 8500 Balboa Blvd - Northridge CA 91329 - US +80-C4-29 (hex) Renesas Electronics Operations Services Limited +80C429 (base 16) Renesas Electronics Operations Services Limited + Dukes Meadow, Millboard Raod Bourne End BU + Bourne End BU SL8 5FH + GB -24-99-00 (hex) FRITZ! Technology GmbH -249900 (base 16) FRITZ! Technology GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE +00-E0-AD (hex) Brandywine Communications UK Ltd. +00E0AD (base 16) Brandywine Communications UK Ltd. + 20a Westside Centre London Road, Stanway, Colchester + ESSEX England CO3 8PH + GB 58-8C-CF (hex) Silicon Laboratories 588CCF (base 16) Silicon Laboratories @@ -233201,17 +233375,41 @@ B03731 (base 16) FUJIAN STAR-NET COMMUNICATION CO.,LTD Austin TX 78701 US +50-71-64 (hex) Cisco Systems, Inc +507164 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +00-1C-44 (hex) Electro Voice Dynacord BV +001C44 (base 16) Electro Voice Dynacord BV + Achtseweg Zuid 173 + 5651 GW Eindhoven Eindhoven 5651 + NL + +60-A1-FE (hex) HPRO +60A1FE (base 16) HPRO + 8500 Balboa Blvd + Northridge CA 91329 + US + A4-18-94 (hex) IQSIGHT B.V. A41894 (base 16) IQSIGHT B.V. Achtseweg Zuid 173 Eindhoven 5651 GW NL -E8-8F-8E (hex) Hoags Technologies India Private Limited -E88F8E (base 16) Hoags Technologies India Private Limited - M-138, 9TH A MAIN, JEEVAN BHEEMA NAGAR, - Bangalore KA 560075 - IN +B0-37-31 (hex) FUJIAN STAR-NET COMMUNICATION CO.,LTD +B03731 (base 16) FUJIAN STAR-NET COMMUNICATION CO.,LTD + 19-22# Building, Star-net Science Plaza, Juyuanzhou, + FUZHOU FUJIAN 350002 + CN + +24-99-00 (hex) FRITZ! Technology GmbH +249900 (base 16) FRITZ! Technology GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE 18-A0-84 (hex) Apple, Inc. 18A084 (base 16) Apple, Inc. @@ -233231,6 +233429,36 @@ E88F8E (base 16) Hoags Technologies India Private Limited Cupertino CA 95014 US +E8-8F-8E (hex) Hoags Technologies India Private Limited +E88F8E (base 16) Hoags Technologies India Private Limited + M-138, 9TH A MAIN, JEEVAN BHEEMA NAGAR, + Bangalore KA 560075 + IN + +6C-40-33 (hex) Beijing Megwang Technology Co., Ltd. +6C4033 (base 16) Beijing Megwang Technology Co., Ltd. + Room 1316, 1st Floor, Building 12, Jianzhong Road, Xisanqi Building Materials City, Haidian District, Beijing, China + Beijing 100096 + CN + +E8-FC-5F (hex) Ruckus Wireless +E8FC5F (base 16) Ruckus Wireless + 350 West Java Drive + Sunnyvale CA 94089 + US + +50-63-82 (hex) HUAWEI TECHNOLOGIES CO.,LTD +506382 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +BC-68-C3 (hex) HUAWEI TECHNOLOGIES CO.,LTD +BC68C3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + 3C-BF-D7 (hex) Apple, Inc. 3CBFD7 (base 16) Apple, Inc. 1 Infinite Loop @@ -233249,14 +233477,44 @@ E88F8E (base 16) Hoags Technologies India Private Limited Cupertino CA 95014 US -00-0C-DE (hex) ABB AG. -000CDE (base 16) ABB AG. +9C-CC-01 (hex) Espressif Inc. +9CCC01 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +A4-93-FE (hex) HUAWEI TECHNOLOGIES CO.,LTD +A493FE (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +00-0C-DE (hex) ABB AG +000CDE (base 16) ABB AG Eppelheimer Straße 82 Heidelberg Baden-Württemberg 69123 DE -E8-FC-5F (hex) Ruckus Wireless -E8FC5F (base 16) Ruckus Wireless - 350 West Java Drive - Sunnyvale CA 94089 +E8-23-FB (hex) Redder +E823FB (base 16) Redder + Via B. Ferracina, 2 + Camisano Vicentino VI 36043 + IT + +E8-68-B1 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +E868B1 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN + +B0-F0-79 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +B0F079 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN + +40-BA-09 (hex) Dell Inc. +40BA09 (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 US diff --git a/hwdb.d/ma-medium.txt b/hwdb.d/ma-medium.txt index 0f268976eed05..66da0bc2dbaed 100644 --- a/hwdb.d/ma-medium.txt +++ b/hwdb.d/ma-medium.txt @@ -7607,17 +7607,35 @@ E8-6C-C7 (hex) ebblo Western Europe Neuhausen am Rheinfall Schaffhausen 8212 CH +18-C3-E4 (hex) Trusted Technology Solutions, Inc. +400000-4FFFFF (base 16) Trusted Technology Solutions, Inc. + 346 River Street + Lemont IL 60439 + US + 18-C3-E4 (hex) BRS Sistemas Eletrônicos 700000-7FFFFF (base 16) BRS Sistemas Eletrônicos Rua Capistrano de Abreu, 68 Canoas RS 92120130 BR -18-C3-E4 (hex) Trusted Technology Solutions, Inc. -400000-4FFFFF (base 16) Trusted Technology Solutions, Inc. - 346 River Street - Lemont IL 60439 - US +C4-82-72 (hex) Digisine Energytech Co., Ltd. +200000-2FFFFF (base 16) Digisine Energytech Co., Ltd. + 2F, No. 196, Sec. 2, Zhongxing Rd., Xindian Dist., + New Taipei City 231 + TW + +C4-82-72 (hex) Mantenimiento y paileria +600000-6FFFFF (base 16) Mantenimiento y paileria + Avenida 16 de Septiembre 21 + Cuautitlán Estado de México 54831 + MX + +C4-82-72 (hex) Satways Ltd +900000-9FFFFF (base 16) Satways Ltd + 15 Megalou Konstantinou Street + Irakleio, Attica 14122 + GR B8-4C-87 (hex) Shenzhen Link-all Technology Co., Ltd 300000-3FFFFF (base 16) Shenzhen Link-all Technology Co., Ltd @@ -14702,6 +14720,12 @@ A00000-AFFFFF (base 16) Prolink Surveillance Technology Co.Ltd London WC2A 3TH GB +C4-82-72 (hex) MyPlace Australia Pty Ltd +B00000-BFFFFF (base 16) MyPlace Australia Pty Ltd + 115 Vulcan Rd + Canning Vale WA 6155 + AU + 6C-47-80 (hex) Alban Giacomo S.p.a. E00000-EFFFFF (base 16) Alban Giacomo S.p.a. Via Alcide De Gasperi, 75 @@ -22415,18 +22439,51 @@ D00000-DFFFFF (base 16) Schenker Storen AG Saarbrücken Saarland 66121 DE -18-C3-E4 (hex) Sodalec -000000-0FFFFF (base 16) Sodalec - 6 rue Allory - Pacé 35740 +C4-82-72 (hex) E2-CAD +C00000-CFFFFF (base 16) E2-CAD + 13-17 Allée Rosa Luxemburg + Eragny sur oise 95610 FR +C4-82-72 (hex) Private +100000-1FFFFF (base 16) Private + +C4-82-72 (hex) Mode Sensors AS +700000-7FFFFF (base 16) Mode Sensors AS + Sluppenveien 6 + Trondheim 7037 + NO + +C4-82-72 (hex) Tolt Technologies LLC +A00000-AFFFFF (base 16) Tolt Technologies LLC + 19520 Mountain View Road NE + Duvall WA 98019-8822 + US + +C4-82-72 (hex) Schunk SE & Co. KG +500000-5FFFFF (base 16) Schunk SE & Co. KG + Bahnhofstraße 106-134 + Lauffen am Neckar 74348 + DE + 18-C3-E4 (hex) Fime SAS A00000-AFFFFF (base 16) Fime SAS 8 rue du Commodore JH HALLET CAEN 14000 FR +18-C3-E4 (hex) Sodalec +000000-0FFFFF (base 16) Sodalec + 6 rue Allory + Pacé 35740 + FR + +C4-82-72 (hex) Smart Radar System, Inc +E00000-EFFFFF (base 16) Smart Radar System, Inc + 7F, Innovalley A, 253 Pangyo-ro Bundang-gu + Seongnam-si Gyeonggi-do Korea 13486 + KR + D0-14-11 (hex) P.B. Elettronica srl 100000-1FFFFF (base 16) P.B. Elettronica srl Via Santorelli, 8 @@ -29822,6 +29879,18 @@ C00000-CFFFFF (base 16) Bit Part LLC 6C-47-80 (hex) Private 800000-8FFFFF (base 16) Private +C4-82-72 (hex) Gabriel Tecnologia +000000-0FFFFF (base 16) Gabriel Tecnologia + Rua Doutor Virgilio de Carvalho Pinto, 142 + São Paulo SP 05415-020 + BR + +C4-82-72 (hex) Posital B.V. +D00000-DFFFFF (base 16) Posital B.V. + ECI 13 + Roermond 6041MA + NL + C8-5C-E2 (hex) Fela Management AG 000000-0FFFFF (base 16) Fela Management AG Basadingerstrasse 18 @@ -37378,3 +37447,27 @@ B00000-BFFFFF (base 16) Cascadia Motion LLC ul. Piatkowska 163 Poznan 60-650 PL + +C4-82-72 (hex) Shanghai Smart Logic Technology Ltd. +800000-8FFFFF (base 16) Shanghai Smart Logic Technology Ltd. + Room 1010,No.2,Lane 288,Kangning Road,Jing'an IDistrict + shanghai shanghai 200020 + CN + +38-B1-4E (hex) QNION Co.,Ltd +800000-8FFFFF (base 16) QNION Co.,Ltd + 165, Jukdong-ro Yuseong-gu + Daejeon Daejeon 34127 + KR + +38-B1-4E (hex) Marssun +200000-2FFFFF (base 16) Marssun + 8 F., No. 13, Ln. 332, Sec. 2, Zhongshan Rd., Zhonghe Dist. + New Taipei 235 + TW + +C4-82-72 (hex) Melecs EWS GmbH +400000-4FFFFF (base 16) Melecs EWS GmbH + GZO-Technologiestrasse 1 + Siegendorf 7011 + AT diff --git a/hwdb.d/ma-small.txt b/hwdb.d/ma-small.txt index bf3496a60810e..8c5e4c6f693a1 100644 --- a/hwdb.d/ma-small.txt +++ b/hwdb.d/ma-small.txt @@ -8207,24 +8207,36 @@ B0E000-B0EFFF (base 16) Watthour Engineering Co., Inc. Yokohama Kanagawa Prefecture 2310023 JP -8C-1F-64 (hex) Becton Dickinson -597000-597FFF (base 16) Becton Dickinson - 7 Loveton Circle - Sparks MD 21152 - US - 8C-1F-64 (hex) RoboCore Tecnologia 8C9000-8C9FFF (base 16) RoboCore Tecnologia Av Honorio Alvares Penteado, 97 - Galpao 77 Santana de Parnaiba SP 06543-320 BR +8C-1F-64 (hex) Becton Dickinson +597000-597FFF (base 16) Becton Dickinson + 7 Loveton Circle + Sparks MD 21152 + US + 8C-1F-64 (hex) Corespan Systems 584000-584FFF (base 16) Corespan Systems 200 Innovative Way Suite 1360 Nashua 03062 US +8C-1F-64 (hex) Sensata Technologies Inc. +DA0000-DA0FFF (base 16) Sensata Technologies Inc. + 529 Pleasant Street + Attleboro MA 02703 + US + +8C-1F-64 (hex) Hiwin Mikrosystem Corp. +216000-216FFF (base 16) Hiwin Mikrosystem Corp. + NO 6 JINGKE CENTRAL RD TAICHUNG CITY TAIWAN 40841 + TAICHUNG 40841 + TW + 8C-1F-64 (hex) Jacobs Technology, Inc. A98000-A98FFF (base 16) Jacobs Technology, Inc. 7765 Old Telegraph Road @@ -16553,9 +16565,39 @@ A66000-A66FFF (base 16) vtt systems Inc. Dover DE 19901 US +8C-1F-64 (hex) Breas Medical AB +1C5000-1C5FFF (base 16) Breas Medical AB + Företagsvägen 1 + Mölnlycke SE-435 33 + SE + +8C-1F-64 (hex) CloudRAN.ai +522000-522FFF (base 16) CloudRAN.ai + 12 WOODLANDS SQUARE, #10-73, WOODS, SQUARE, SINGAPORE (737715) + Singapore Singapore 737715 + CN + 8C-1F-64 (hex) Private D26000-D26FFF (base 16) Private +8C-1F-64 (hex) Pneumax Spa +5FE000-5FEFFF (base 16) Pneumax Spa + via cascina barbellina, 10 + Lurano Bergamo 24050 + IT + +8C-1F-64 (hex) Erba Lachema s.r.o. +BCF000-BCFFFF (base 16) Erba Lachema s.r.o. + Karasek1d + Brno 62100 + CZ + +8C-1F-64 (hex) HEITEC AG +E25000-E25FFF (base 16) HEITEC AG + Dr.-Otto-Leich-Str. 16 + Eckental Bavaria 90542 + DE + 8C-1F-64 (hex) Vision Systems Safety Tech E6F000-E6FFFF (base 16) Vision Systems Safety Tech 5 Chemin de Chiradie @@ -24902,6 +24944,24 @@ D25000-D25FFF (base 16) therlys GmbH Hamburg 20097 DE +8C-1F-64 (hex) DORLET SAU +BC5000-BC5FFF (base 16) DORLET SAU + C/ ALBERT EINSTEIN 34, PARQUE TECNOLOGICO DE ALAVA + VITORIA - GASTEIZ ALAVA 01510 + ES + +8C-1F-64 (hex) ACS Motion Control +64C000-64CFFF (base 16) ACS Motion Control + 5 Ha'Tnufa st. + Yokneam 2066717 + IL + +8C-1F-64 (hex) Vision Systems Safety Tech +436000-436FFF (base 16) Vision Systems Safety Tech + 5 Chemin de Chiradie + Brignais 69530 + FR + 8C-1F-64 (hex) Flow Power 82B000-82BFFF (base 16) Flow Power Suite 2, Level 3, 18 - 20 York St @@ -33131,6 +33191,18 @@ A78000-A78FFF (base 16) TAIT Global LLC Nordhorn Germany 48531 DE +8C-1F-64 (hex) Diatech co.,ltd. +26C000-26CFFF (base 16) Diatech co.,ltd. + 201 City Dolce Iogi 2-2-9 Igusa + Suginami Ku Tokyo 167-0021 + JP + +8C-1F-64 (hex) Pneumax Spa +431000-431FFF (base 16) Pneumax Spa + via cascina barbellina, 10 + Lurano Bergamo 24050 + IT + 8C-1F-64 (hex) Mobileye D63000-D63FFF (base 16) Mobileye 13 Hartom st. @@ -41267,6 +41339,24 @@ CEA000-CEAFFF (base 16) Mootek Technologies Private Limited 8C-1F-64 (hex) Private B94000-B94FFF (base 16) Private +8C-1F-64 (hex) Terragene +248000-248FFF (base 16) Terragene + Ruta Nacional N°9 - Km 280. Parque Industrial Micropi + Alvear Santa Fe 2130 + AR + +8C-1F-64 (hex) Campus Genevois de Haute Horlogerie +BA5000-BA5FFF (base 16) Campus Genevois de Haute Horlogerie + Rue André-De-Garrini 7 + Meyrin 1217 + CH + +70-B3-D5 (hex) RoboCore Tecnologia +0AC000-0ACFFF (base 16) RoboCore Tecnologia + Av Honorio Alvares Penteado, 97 - Galpao 77 + Santana de Parnaiba SP 06543-320 + BR + 8C-1F-64 (hex) Vojensky Technicky Ustav, s.p. B51000-B51FFF (base 16) Vojensky Technicky Ustav, s.p. Mladoboleslavska 944, Kbely, Praha 9Reg. n: 24272523VAT n.: CZ24272523 @@ -41279,20 +41369,14 @@ B51000-B51FFF (base 16) Vojensky Technicky Ustav, s.p. Taipei 10474 TW -70-B3-D5 (hex) RoboCore Tecnologia -0AC000-0ACFFF (base 16) RoboCore Tecnologia - Av Honorio Alvares Penteado, 97 - Galpao 77 - Santana de Parnaiba SP 06543-320 - BR - -8C-1F-64 (hex) Terragene -248000-248FFF (base 16) Terragene - Ruta Nacional N°9 - Km 280. Parque Industrial Micropi - Alvear Santa Fe 2130 - AR +8C-1F-64 (hex) ATAL s.r.o. +805000-805FFF (base 16) ATAL s.r.o. + Lesni 47 + Tabor 39001 + CZ -8C-1F-64 (hex) Campus Genevois de Haute Horlogerie -BA5000-BA5FFF (base 16) Campus Genevois de Haute Horlogerie - Rue André-De-Garrini 7 - Meyrin 1217 - CH +8C-1F-64 (hex) DEUTA Werke GmbH +D2C000-D2CFFF (base 16) DEUTA Werke GmbH + ET + Bergisch Gladbach NRW 51465 + DE diff --git a/hwdb.d/pci.ids b/hwdb.d/pci.ids index 369ef78ff6dce..ddacf4f196d3e 100644 --- a/hwdb.d/pci.ids +++ b/hwdb.d/pci.ids @@ -1,8 +1,8 @@ # # List of PCI IDs # -# Version: 2026.02.24 -# Date: 2026-02-24 03:15:02 +# Version: 2026.03.03 +# Date: 2026-03-03 03:15:02 # # Maintained by Albert Pool, Martin Mares, and other volunteers from # the PCI ID Project at https://pci-ids.ucw.cz/. @@ -4121,7 +4121,7 @@ 1eae 7901 RX-79XMERCB9 [SPEEDSTER MERC 310 RX 7900 XTX] 1eae 790a RX-79GMERCBR [XFX RX 7900 GRE] 745e Navi 31 [Radeon Pro W7800] - 7460 Navi32 GL-XL [AMD Radeon PRO V710] + 7460 Navi 32 GL-XL [AMD Radeon PRO V710] 7461 Navi 32 [AMD Radeon PRO V710] 7470 Navi 32 [Radeon PRO W7700] 747e Navi 32 [Radeon RX 7700 XT / 7800 XT] @@ -13529,7 +13529,7 @@ 2c34 GB203GL [RTX PRO 4000 Blackwell] 2c38 GB203GLM [RTX PRO 5000 Blackwell Generation Laptop GPU] 2c39 GB203GLM [RTX PRO 4000 Blackwell Generation Laptop GPU] - 2c3a GB203GL [RTX PRO 4500 Blackwell] + 2c3a GB203GL [RTX PRO 4500 Blackwell Server Edition] 2c58 GB203M / GN22-X11 [GeForce RTX 5090 Max-Q / Mobile] 2c59 GB203M / GN22-X9 [GeForce RTX 5080 Max-Q / Mobile] 2c77 GB203GLM [RTX PRO 5000 Blackwell Embedded GPU] @@ -17535,7 +17535,7 @@ a000 2000 Parallel Port a000 6000 SPI a000 7000 Local Bus - ea50 1c10 RXi2-BP + ea50 1c10 RXi2-BP Serial Port 9105 AX99100 PCIe to I/O Bridge 125c Aurora Technologies, Inc. 0101 Saturn 4520P @@ -20925,6 +20925,7 @@ 7662 MT7662E 802.11ac PCI Express Wireless Network Adapter 7663 MT7663 802.11ac PCI Express Wireless Network Adapter 7902 MT7902 802.11ax PCIe Wireless Network Adapter [Filogic 310] + 7906 MT7916A/MT7916D normal link PCIe Wi-Fi 6(802.11ax) 160MHz 2x2 Wireless Network Adapter [Filogic 630] 7915 MT7915A/MT7915D normal link PCIe Wi-Fi 6(802.11ax) 80MHz 4x4/2x2 Wireless Network Adapter [Filogic 615] # MT7905D/MT7975 contain MT7915. If it works at G1 speed this extra device appears for extra bandwidth 7916 MT7915A/MT7915D hif link PCIe Wi-Fi 6(802.11ax) 80MHz 4x4/2x2 Wireless Network Adapter [Filogic 615] @@ -21879,6 +21880,7 @@ 5f72 BCM4388 Bluetooth Controller # Bluetooth PCI function of the BRCM4377 Wireless Network Adapter 5fa0 BRCM4377 Bluetooth Controller + 6865 BCM68650 [Aspen] XGSPON OLT 8411 BCM47xx PCIe Bridge 8602 BCM7400/BCM7405 Serial ATA Controller 9026 CN99xx [ThunderX2] Integrated USB 3.0 xHCI Host Controller @@ -25418,6 +25420,7 @@ 15d9 0821 X10DRW-i (AST2400 BMC) 15d9 0832 X10SRL-F (AST2400 BMC) 15d9 086b X10DRS (AST2400 BMC) + 15d9 086d X10SDV (AST2400 BMC) 15d9 1b95 H12SSL-i (AST2500 BMC) 15d9 1d50 X14DBG-AP (AST2600 BMC) 1849 2000 Onboard Graphics @@ -28945,6 +28948,7 @@ 3504 M18305 Family BASE-T 1f0f 0001 S2025XT, 2x 10GbE, Base-T, PCIe 4.0 x8, Fan 1f0f 0002 S2025XT, 2x 10GbE, Base-T, PCIe 4.0 x8 + 1f0f 0003 S2045XT, 4x 10GbE, Base-T, PCIe 4.0 x8 350a M18305 Family Virtual Function 1f0f 0001 M18305 Family Virtual Function 9088 D1055AS PCI Express Switch Downstream Port @@ -28990,6 +28994,7 @@ 4512 NE1N NVMe SSD 451b NN4LE NVMe SSD (DRAM-less) 4622 NEM-PAC NVMe SSD (DRAM-less) +1f32 Wuhan YuXin Semiconductor Co., Ltd. 1f3f 3SNIC Ltd 2100 SSSHBA SAS/SATA HBA 1f3f 0120 HBA 32 Ports @@ -29693,6 +29698,8 @@ 2106 ZCHL Technology Co., Ltd 0001 HL100 Accelerator Controller 2106 0001 HLC100 Accelerator Card +# HXQ +2108 HuiLink Technologies (Xiamen) Co., Ltd. 2116 ZyDAS Technology Corp. 21b4 Hunan Goke Microelectronics Co., Ltd 21c3 21st Century Computer Corp. From 05c2147a52d332283b7bcb542c787973aed95129 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 3 Mar 2026 17:44:49 +0000 Subject: [PATCH 0039/1296] NEWS: update contributors list --- NEWS | 54 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/NEWS b/NEWS index 6ae49bed90fc9..6f4689b81138f 100644 --- a/NEWS +++ b/NEWS @@ -472,33 +472,39 @@ CHANGES WITH 260 in spe: to be active. Contributions from: Adam Williamson, Adrian Vovk, Alessandro Astone, - Alexis-Emmanuel Haeringer, Allison Karlitskaya, André Paiusco, - Antonio Alvarez Feijoo, Artur Kowalski, AshishKumar Mishra, - Baurzhan Muftakhidinov, Ben Boeckel, Betacentury, - Carlos Peón Costa, Carolina Jubran, Cathy Hu, Chris Down, - Chris Lindee, Christian Brauner, Christian Glombek, - Christian Hesse, Christopher Head, Daan De Meyer, Daniel Foster, - Daniel Rusek, David Santamaría Rogado, David Tardon, - Derek J. Clark, Dirk Su, Dmitry V. Levin, Dmytro Bagrii, - Ettore Atalan, Florian Klink, Franck Bui, Govind Venugopal, - Graham Reed, Guiorgy, Han Sol Jin, Hans de Goede, Heran Yang, + Alexis-Emmanuel Haeringer, Allison Karlitskaya, Américo Monteiro, + André Paiusco, Anton Tiurin, Antonio Alvarez Feijoo, + Artur Kowalski, AshishKumar Mishra, Baurzhan Muftakhidinov, + Ben Boeckel, Betacentury, Bouke van der Bijl, Carlos Peón Costa, + Carolina Jubran, Cathy Hu, Chris Down, Chris Lindee, + Christian Brauner, Christian Glombek, Christian Hesse, + Christopher Cooper, Christopher Head, Daan De Meyer, + Daniel Foster, Daniel Nylander, Daniel Rusek, + David Santamaría Rogado, David Tardon, Derek J. Clark, Dirk Su, + Dmitry V. Levin, Dmytro Bagrii, Efstathios Iosifidis, + Eisuke Kawashima, Ettore Atalan, Florian Klink, Franck Bui, + Frantisek Sumsal, Govind Venugopal, Graham Reed, Guiorgy, + Han Sol Jin, Hans de Goede, Heran Yang, IntenseWiggling, Ivan Kruglov, Ivan Shapovalov, James Le Cuirot, Jeff Layton, Jeremy Kerr, Jian Wen, Jim Spentzos, Julian Sparber, Jörg Behrmann, Kai Lüke, Lennart Poettering, Louis Stagg, - Luca Boccassi, Lucas Werkmeister, Luiz Amaral, Marc Pervaz Boocha, - Mario Limonciello (AMD), Matt Fleming, Matteo Croce, - Matthijs Kooijman, Max Gautier, Maximilian Bosch, Miao Wang, - Michael Vogt, Michal Sekletár, Mike Gilbert, Mike Yuan, - Nandakumar Raghavan, Nick Rosbrook, Nicolas Dorier, Oblivionsage, - Oleksandr Andrushchenko, Pablo Fraile Alonso, Peter Oliver, - Philip Withnall, Popax21, Ryan Zeigler, Sriman Achanta, - Tabis Kabis, Thorsten Kukuk, Tobias Heider, Tobias Stoeckmann, - Ulrich Ölmann, Usama Arif, Vitaly Kuznetsov, Vunny Sodhi, - Yaping Li, Yaron Shahrabani, Yu Watanabe, ZauberNerd, - Zbigniew Jędrzejewski-Szmek, Zongyuan He, andre4ik3, calm329, cdown, - cyclopentane, francescoza6, gvenugo3, kiamvdd, nikstur, novenary, - r-vdp, safforddr, scarlet-storm, sd416, seidlerv, smosia, tuhaowen, - zefr0x + Luca Boccassi, Lucas Werkmeister, Luiz Amaral, Léane GRASSER, + Marc Pervaz Boocha, Mario Limonciello, Mario Limonciello (AMD), + Matt Fleming, Matteo Croce, Matthijs Kooijman, Max Gautier, + Maximilian Bosch, Miao Wang, Michael Vogt, Michal Sekletár, + Mike Gilbert, Mike Yuan, Mikhail Novosyolov, Nandakumar Raghavan, + Nick Rosbrook, Nicolas Dorier, Oblivionsage, + Oleksandr Andrushchenko, Oğuz Ersen, Pablo Fraile Alonso, + Peter Oliver, Philip Withnall, Pontus Lundkvist, Popax21, + Ronan Pigott, Ryan Zeigler, Skye Soss, Sriman Achanta, + Tabis Kabis, Temuri Doghonadze, Thomas Weißschuh, Thorsten Kukuk, + Tobias Heider, Tobias Stoeckmann, Ulrich Ölmann, Usama Arif, + Val Markovic, Vitaly Kuznetsov, Vunny Sodhi, Weixie Cui, + Yaping Li, Yaron Shahrabani, Yu Watanabe, Yuri Chornoivan, + ZauberNerd, Zbigniew Jędrzejewski-Szmek, Zongyuan He, andre4ik3, + calm329, cdown, cyclopentane, francescoza6, gvenugo3, joo es, kiamvdd, + lumingzh, naly zzwd, nikstur, novenary, noxiouz, r-vdp, safforddr, + scarlet-storm, sd416, seidlerv, smosia, tuhaowen, zefr0x — Edinburgh, 2026/02/25 From 6e0f0510d2332d30a6e25dfeab009dd1a8327191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 3 Mar 2026 18:45:29 +0100 Subject: [PATCH 0040/1296] NEWS: move interesting items closer to top and mention PrivateTmp changes In https://bugzilla.redhat.com/show_bug.cgi?id=2443620 it was reported that the changes to unit ordering were surprising. Let's add a note about the PrivateTmp= handling changes. Follow-up for https://github.com/systemd/systemd/pull/39790. --- NEWS | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/NEWS b/NEWS index 6ae49bed90fc9..c730b49bd5d5b 100644 --- a/NEWS +++ b/NEWS @@ -138,6 +138,18 @@ CHANGES WITH 260 in spe: Changes in the system and service manager: + * A new unit setting RootMStack= has been introduced, to support the + new "mstack" feature for services (see above). + + * The unit setting PrivateUsers= gained a new possible value "managed", + which automatically assigns a dynamic and transient range of 65536 + UIDs/GIDs to the unit, acquired via systemd-nsresourced. + + * The implementation for PrivateUsers=full has been updated to map the + full range of IDs. The workaround to allow nested systemd older than + 257 to correctly detect that it is under such a mapping has been + dropped. + * systemd now uses the CSI 18 terminal sequence to query terminal size. This allows the query to be made without changing the position of the cursor. Terminal emulators which do not yet support the @@ -156,17 +168,13 @@ CHANGES WITH 260 in spe: can be used to skip or fail the unit if the given path is not a socket. - * A new unit setting RootMStack= has been introduced, to support the - new "mstack" feature for services (see above). - - * The unit setting PrivateUsers= gained a new possible value "managed", - which automatically assigns a dynamic and transient range of 65536 - UIDs/GIDs to the unit, acquired via systemd-nsresourced. - - * The implementation for PrivateUsers=full has been updated to map the - full range of IDs. The workaround to allow nested systemd older than - 257 to correctly detect that it is under such a mapping has been - dropped. + * For units which specify PrivateTmp=yes and DefaultDependencies=no + without an explicit requirement for /tmp/, a disconnected /tmp/ will + be used, as if PrivateTmp=disconnected was specified. Also, if there + is no explicit ordering for /var/, the private mount for /var/tmp/ + will not be created. Those changes avoid race conditions with + creation of those private directories during early boot and may + result in changes to unit ordering. * EnqueueMarkedJobs() D-Bus method now has a Varlink counterpart. From ec718b44a11f19aaec28349ac7490042d76379ab Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 3 Mar 2026 18:22:29 +0100 Subject: [PATCH 0041/1296] machined: add comment explaining access to machine objects a bit --- src/machine/machine.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/machine/machine.h b/src/machine/machine.h index d02bb9a965edf..7941eb365c15c 100644 --- a/src/machine/machine.h +++ b/src/machine/machine.h @@ -34,6 +34,17 @@ typedef enum KillWhom { } KillWhom; typedef struct Machine { + /* Note: machine objects registered with the --system instance can be allocated by privileged *and* + * unprivileged clients. We generally do this to make DNS-style name resolution work, and since + * that's a system-wide concept, the machine registrations need to be system-wide too. + * + * polkit manages access to machines registered by unprivileged clients. The general rule should be + * that local users (i.e. those with a seat) may register machines, and do basic interaction with + * their own machines without having to authenticate as administrator – however any more complex + * (such as: copying files in + out of a container; or logging in interactively) should only be + * available after administrator authentication, following the logic that users better use their own + * per-user instance of systemd-machined for that. */ + Manager *manager; char *name; From acd23fc49123e1c97b984ebbde173dd9eec360bc Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 3 Mar 2026 17:02:48 +0100 Subject: [PATCH 0042/1296] sd-messages: fix typo (This was introduced in v260, i.e. not yet released, hence not API break) --- src/systemd/sd-messages.h | 4 ++-- src/tpm2-setup/tpm2-setup.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h index 7bc4199408f13..865f7247ac21f 100644 --- a/src/systemd/sd-messages.h +++ b/src/systemd/sd-messages.h @@ -306,8 +306,8 @@ _SD_BEGIN_DECLARATIONS; #define SD_MESSAGE_SYSTEM_ACCOUNT_REQUIRED SD_ID128_MAKE(34,05,20,5d,36,8e,49,fe,b5,ab,39,25,fe,e1,38,74) #define SD_MESSAGE_SYSTEM_ACCOUNT_REQUIRED_STR SD_ID128_MAKE_STR(34,05,20,5d,36,8e,49,fe,b5,ab,39,25,fe,e1,38,74) -#define SD_MESSAGE_TPM_INVINDEX_EXHAUSTED SD_ID128_MAKE(ab,98,4e,a0,08,96,4f,b8,8d,6e,38,9f,b5,13,fb,94) -#define SD_MESSAGE_TPM_INVINDEX_EXHAUSTED_STR SD_ID128_MAKE_STR(ab,98,4e,a0,08,96,4f,b8,8d,6e,38,9f,b5,13,fb,94) +#define SD_MESSAGE_TPM_NVINDEX_EXHAUSTED SD_ID128_MAKE(ab,98,4e,a0,08,96,4f,b8,8d,6e,38,9f,b5,13,fb,94) +#define SD_MESSAGE_TPM_NVINDEX_EXHAUSTED_STR SD_ID128_MAKE_STR(ab,98,4e,a0,08,96,4f,b8,8d,6e,38,9f,b5,13,fb,94) _SD_END_DECLARATIONS; diff --git a/src/tpm2-setup/tpm2-setup.c b/src/tpm2-setup/tpm2-setup.c index a811ea436dbee..a4d323a70cefc 100644 --- a/src/tpm2-setup/tpm2-setup.c +++ b/src/tpm2-setup/tpm2-setup.c @@ -426,7 +426,7 @@ static int setup_nvpcr_one( c->n_failed++; return log_struct_errno(LOG_ERR, r, LOG_MESSAGE("The TPM's NV index space is exhausted, unable to allocate NvPCR '%s': %m", name), - LOG_MESSAGE_ID(SD_MESSAGE_TPM_INVINDEX_EXHAUSTED_STR)); + LOG_MESSAGE_ID(SD_MESSAGE_TPM_NVINDEX_EXHAUSTED_STR)); } if (r < 0) { c->n_failed++; From 8e82ea14688e0fbf23da8ef3b1ebdaee0e0c8724 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 3 Mar 2026 22:34:27 +0900 Subject: [PATCH 0043/1296] udev/scsi: use hexchar() --- src/udev/scsi_id/scsi_serial.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/udev/scsi_id/scsi_serial.c b/src/udev/scsi_id/scsi_serial.c index 39937006154e7..20caf695bf47a 100644 --- a/src/udev/scsi_id/scsi_serial.c +++ b/src/udev/scsi_id/scsi_serial.c @@ -15,6 +15,7 @@ #include #include "devnum-util.h" +#include "hexdecoct.h" #include "log.h" #include "random-util.h" #include "scsi.h" @@ -55,8 +56,6 @@ static const struct scsi_id_search_values id_search_list[] = { { SCSI_ID_VENDOR_SPECIFIC, SCSI_ID_NAA_DONT_CARE, SCSI_ID_ASCII }, }; -static const char hex_str[]="0123456789abcdef"; - /* * Values returned in the result/status, only the ones used by the code * are used here. @@ -483,7 +482,7 @@ static int check_fill_0x83_id(struct scsi_id_device *dev_scsi, return 1; } - serial[0] = hex_str[id_search->id_type]; + serial[0] = hexchar(id_search->id_type); /* * For SCSI_ID_VENDOR_SPECIFIC prepend the vendor and model before @@ -509,8 +508,8 @@ static int check_fill_0x83_id(struct scsi_id_device *dev_scsi, * ASCII for each byte in the page_83. */ while (i < (4 + page_83[3])) { - serial[j++] = hex_str[(page_83[i] & 0xf0) >> 4]; - serial[j++] = hex_str[page_83[i] & 0x0f]; + serial[j++] = hexchar(page_83[i] >> 4); + serial[j++] = hexchar(page_83[i]); i++; } } @@ -533,13 +532,13 @@ static int check_fill_0x83_prespc3(struct scsi_id_device *dev_scsi, *id_search, char *serial, char *serial_short, int max_len) { int i, j; - serial[0] = hex_str[SCSI_ID_NAA]; + serial[0] = hexchar(SCSI_ID_NAA); /* serial has been memset to zero before */ j = strlen(serial); /* j = 1; */ for (i = 0; (i < page_83[3]) && (j < max_len-3); ++i) { - serial[j++] = hex_str[(page_83[4+i] & 0xf0) >> 4]; - serial[j++] = hex_str[ page_83[4+i] & 0x0f]; + serial[j++] = hexchar(page_83[4+i] >> 4); + serial[j++] = hexchar(page_83[4+i]); } serial[max_len-1] = 0; strncpy(serial_short, serial, max_len-1); @@ -672,7 +671,7 @@ static int do_scsi_page83_prespc3_inquiry(struct scsi_id_device *dev_scsi, int f if (page_83[6] == 0) return 2; - serial[0] = hex_str[SCSI_ID_NAA]; + serial[0] = hexchar(SCSI_ID_NAA); /* * The first four bytes contain data, not a descriptor. */ @@ -684,8 +683,8 @@ static int do_scsi_page83_prespc3_inquiry(struct scsi_id_device *dev_scsi, int f * in the page_83. */ while (i < (page_83[3]+4)) { - serial[j++] = hex_str[(page_83[i] & 0xf0) >> 4]; - serial[j++] = hex_str[page_83[i] & 0x0f]; + serial[j++] = hexchar(page_83[i] >> 4); + serial[j++] = hexchar(page_83[i]); i++; } return 0; From 43116c56621c7317852c01e67dbcaa78b5e7ff70 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 3 Mar 2026 22:34:41 +0900 Subject: [PATCH 0044/1296] tree-wide: use DIGITS and friends --- src/basic/hexdecoct.c | 15 ++++----------- src/basic/user-util.c | 19 +++++++++---------- src/boot/cpio.c | 2 +- src/boot/efi-string.c | 4 ++-- src/fundamental/string-util-fundamental.h | 1 + src/libsystemd/sd-bus/sd-bus.c | 4 ++-- src/libsystemd/sd-json/sd-json.c | 12 ++++++------ src/login/pam_systemd.c | 2 +- 8 files changed, 26 insertions(+), 33 deletions(-) diff --git a/src/basic/hexdecoct.c b/src/basic/hexdecoct.c index a00c6289cca02..bbe624cc4f2ed 100644 --- a/src/basic/hexdecoct.c +++ b/src/basic/hexdecoct.c @@ -33,8 +33,7 @@ int undecchar(char c) { } char hexchar(int x) { - static const char table[] = "0123456789abcdef"; - + const char *table = LOWERCASE_HEXDIGITS; return table[x & 15]; } @@ -165,9 +164,7 @@ int unhexmem_full( * useful when representing NSEC3 hashes, as one can then verify the * order of hashes directly from their representation. */ char base32hexchar(int x) { - static const char table[] = "0123456789" - "ABCDEFGHIJKLMNOPQRSTUV"; - + const char *table = DIGITS "ABCDEFGHIJKLMNOPQRSTUV"; return table[x & 31]; } @@ -516,9 +513,7 @@ int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *_l /* https://tools.ietf.org/html/rfc4648#section-4 */ char base64char(int x) { - static const char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; + const char *table = UPPERCASE_LETTERS LOWERCASE_LETTERS DIGITS "+/"; return table[x & 63]; } @@ -526,9 +521,7 @@ char base64char(int x) { * since we don't want "/" appear in interface names (since interfaces appear in sysfs as filenames). * See section #5 of RFC 4648. */ char urlsafe_base64char(int x) { - static const char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789-_"; + const char *table = UPPERCASE_LETTERS LOWERCASE_LETTERS DIGITS "-_"; return table[x & 63]; } diff --git a/src/basic/user-util.c b/src/basic/user-util.c index e434fbec8f985..a4ae020c2c6bc 100644 --- a/src/basic/user-util.c +++ b/src/basic/user-util.c @@ -762,18 +762,17 @@ bool valid_user_group_name(const char *u, ValidUserFlags flags) { * don't allow slashes. */ return false; - if (in_charset(u, "0123456789")) /* Don't allow fully numeric strings, they might be confused - * with UIDs (note that this test is more broad than - * the parse_uid() test above, as it will cover more than - * the 32-bit range, and it will detect 65535 (which is in - * invalid UID, even though in the unsigned 32 bit range) */ + if (in_charset(u, DIGITS)) /* Don't allow fully numeric strings, they might be confused with + * UIDs (note that this test is more broad than the parse_uid() + * test above, as it will cover more than the 32-bit range, and it + * will detect 65535 (which is in invalid UID, even though in the + * unsigned 32 bit range) */ return false; - if (u[0] == '-' && in_charset(u + 1, "0123456789")) /* Don't allow negative fully numeric - * strings either. After all some people - * write 65535 as -1 (even though that's - * not even true on 32-bit uid_t - * anyway) */ + if (u[0] == '-' && in_charset(u + 1, DIGITS)) /* Don't allow negative fully numeric strings + * either. After all some people write 65535 as + * -1 (even though that's not even true on + * 32-bit uid_t anyway) */ return false; if (dot_or_dot_dot(u)) /* User names typically become home directory names, and these two are diff --git a/src/boot/cpio.c b/src/boot/cpio.c index 8a15253dedad1..7ad4b470fae02 100644 --- a/src/boot/cpio.c +++ b/src/boot/cpio.c @@ -8,7 +8,7 @@ #include "util.h" static char *write_cpio_word(char *p, uint32_t v) { - static const char hex[] = "0123456789abcdef"; + const char *hex = LOWERCASE_HEXDIGITS; assert(p); diff --git a/src/boot/efi-string.c b/src/boot/efi-string.c index ee430c1b36a40..cde10d0abd437 100644 --- a/src/boot/efi-string.c +++ b/src/boot/efi-string.c @@ -510,7 +510,7 @@ char* line_get_key_value(char *s, const char *sep, size_t *pos, char **ret_key, } char16_t *hexdump(const void *data, size_t size) { - static const char hex[] = "0123456789abcdef"; + const char *hex = LOWERCASE_HEXDIGITS; const uint8_t *d = data; assert(data || size == 0); @@ -676,7 +676,7 @@ static bool push_str(FormatContext *ctx, SpecifierContext *sp) { } static bool push_num(FormatContext *ctx, SpecifierContext *sp, uint64_t u) { - const char *digits = sp->lowercase ? "0123456789abcdef" : "0123456789ABCDEF"; + const char *digits = sp->lowercase ? LOWERCASE_HEXDIGITS : UPPERCASE_HEXDIGITS; char16_t tmp[32]; size_t n = 0; diff --git a/src/fundamental/string-util-fundamental.h b/src/fundamental/string-util-fundamental.h index e2eb73a4a9dee..83b90e4e9e543 100644 --- a/src/fundamental/string-util-fundamental.h +++ b/src/fundamental/string-util-fundamental.h @@ -24,6 +24,7 @@ #define ALPHANUMERICAL LETTERS DIGITS #define HEXDIGITS DIGITS "abcdefABCDEF" #define LOWERCASE_HEXDIGITS DIGITS "abcdef" +#define UPPERCASE_HEXDIGITS DIGITS "ABCDEF" #define URI_RESERVED ":/?#[]@!$&'()*+;=" /* [RFC3986] */ #define URI_UNRESERVED ALPHANUMERICAL "-._~" /* [RFC3986] */ #define URI_VALID URI_RESERVED URI_UNRESERVED /* [RFC3986] */ diff --git a/src/libsystemd/sd-bus/sd-bus.c b/src/libsystemd/sd-bus/sd-bus.c index 9fdd8cbd66bf7..2a3ea8eb8f356 100644 --- a/src/libsystemd/sd-bus/sd-bus.c +++ b/src/libsystemd/sd-bus/sd-bus.c @@ -1480,7 +1480,7 @@ int bus_set_address_system_remote(sd_bus *b, const char *host) { got_forward_slash = true; } - if (!in_charset(p, "0123456789") || *p == '\0') { + if (!in_charset(p, DIGITS) || *p == '\0') { if (!hostname_is_valid(p, 0) || got_forward_slash) return -EINVAL; @@ -1496,7 +1496,7 @@ int bus_set_address_system_remote(sd_bus *b, const char *host) { interpret_port_as_machine_old_syntax: /* Let's make sure this is not a port of some kind, * and is a valid machine name. */ - if (!in_charset(m, "0123456789") && hostname_is_valid(m, 0)) + if (!in_charset(m, DIGITS) && hostname_is_valid(m, 0)) c = strjoina(",argv", p ? "7" : "5", "=--machine=", m); } diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index 647b56555a7bb..5b6bc90c02c23 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -2761,21 +2761,21 @@ static int json_parse_number(const char **p, JsonValue *ret) { x = 10.0 * x + (*c - '0'); c++; - } while (strchr("0123456789", *c) && *c != 0); + } while (strchr(DIGITS, *c) && *c != 0); } if (*c == '.') { is_real = true; c++; - if (!strchr("0123456789", *c) || *c == 0) + if (!strchr(DIGITS, *c) || *c == 0) return -EINVAL; do { y = 10.0 * y + (*c - '0'); shift = 10.0 * shift; c++; - } while (strchr("0123456789", *c) && *c != 0); + } while (strchr(DIGITS, *c) && *c != 0); } if (IN_SET(*c, 'e', 'E')) { @@ -2788,13 +2788,13 @@ static int json_parse_number(const char **p, JsonValue *ret) { } else if (*c == '+') c++; - if (!strchr("0123456789", *c) || *c == 0) + if (!strchr(DIGITS, *c) || *c == 0) return -EINVAL; do { exponent = 10.0 * exponent + (*c - '0'); c++; - } while (strchr("0123456789", *c) && *c != 0); + } while (strchr(DIGITS, *c) && *c != 0); } *p = c; @@ -2904,7 +2904,7 @@ int json_tokenize( *state = INT_TO_PTR(STATE_VALUE_POST); goto finish; - } else if (strchr("-0123456789", *c)) { + } else if (strchr("-" DIGITS, *c)) { r = json_parse_number(&c, ret_value); if (r < 0) diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index cf8fe30ebeac1..f7aa6f9b8f626 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -279,7 +279,7 @@ static int socket_from_display(const char *display) { if (!display_is_local(display)) return -EINVAL; - k = strspn(display+1, "0123456789"); + k = strspn(display + 1, DIGITS); /* Try abstract socket first. */ f = new(char, STRLEN("@/tmp/.X11-unix/X") + k + 1); From a8637c059bf2efb756e8e9e901af0788d381cd62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 3 Mar 2026 16:32:29 +0100 Subject: [PATCH 0045/1296] vmspawn: change order of fields in --extra-drive= Closes #40877. As requested, --extra-drive=path[:format] is changed to --extra-drive=[format:]path, so that the parsing is less ambiguous. (In the original request, it was requested that the empty format can be used also, but that was dropped in the second version of the patch.) --- NEWS | 4 ++-- man/systemd-vmspawn.xml | 9 +++++---- src/vmspawn/vmspawn.c | 23 +++++++++++++---------- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/NEWS b/NEWS index 495f12eda8089..82b30951d24e4 100644 --- a/NEWS +++ b/NEWS @@ -329,8 +329,8 @@ CHANGES WITH 260 in spe: the same switch in systemd-nspawn. * systemd-vmspawn gained a new switch --image-format= for selecting the - image format (i.e. support qcow2 in additin to raw) to boot - from. --extra-drive= now takes the image format as a colon separated + image format (i.e. support qcow2 in additin to raw) to boot from. + Also --extra-drive= now takes the image format as a colon separated parameter. Changes in systemd-nsresourced/systemd-mountfsd: diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 0b4fef2314a6d..136bd6534062b 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -489,12 +489,13 @@ - + Takes a disk image or block device on the host and supplies it to the virtual - machine as another drive. Optionally, the image format can be specified by appending a colon and - the format (raw or qcow2). Defaults to raw. - Note that qcow2 is only supported for regular files, not block devices. + machine as another drive. Optionally, the image format can be specified by prefixing the path with + raw or qcow2 and a colon. The format defaults to + raw. Note that qcow2 is only supported for regular files, not + block devices. diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index d68a621e060de..fc647b15638fa 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -238,9 +238,9 @@ static int help(void) { " Mount a file or directory from the host into the VM\n" " --bind-ro=SOURCE[:TARGET]\n" " Mount a file or directory, but read-only\n" - " --extra-drive=PATH[:FORMAT]\n" + " --extra-drive=[FORMAT:]PATH\n" " Adds an additional disk to the virtual machine\n" - " (format: raw, qcow2; default: raw)\n" + " (FORMAT: raw, qcow2; default: raw)\n" " --bind-user=NAME Bind user from host to virtual machine\n" " --bind-user-shell=BOOL|PATH\n" " Configure the shell to use for --bind-user= users\n" @@ -559,23 +559,26 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_EXTRA_DRIVE: { - _cleanup_free_ char *buf = NULL, *drive_path = NULL; ImageFormat format = IMAGE_FORMAT_RAW; + const char *dp = optarg; - const char *colon = strrchr(optarg, ':'); + const char *colon = strchr(dp, ':'); if (colon) { - ImageFormat f = image_format_from_string(colon + 1); + _cleanup_free_ char *fs = strndup(optarg, colon - optarg); + if (!fs) + return log_oom(); + + ImageFormat f = image_format_from_string(fs); if (f < 0) - log_debug_errno(f, "Failed to parse image format '%s', assuming it is a part of path, ignoring: %m", colon + 1); + log_debug_errno(f, "Cannot parse '%s' as an image format, assuming it is a part of path, ignoring.", fs); else { format = f; - buf = strndup(optarg, colon - optarg); - if (!buf) - return log_oom(); + dp = colon + 1; } } - r = parse_path_argument(buf ?: optarg, /* suppress_root= */ false, &drive_path); + _cleanup_free_ char *drive_path = NULL; + r = parse_path_argument(dp, /* suppress_root= */ false, &drive_path); if (r < 0) return r; From 3e7b9b7462b0be5862f471b64d3bdd9556cda849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Santamar=C3=ADa=20Rogado?= Date: Sun, 1 Mar 2026 19:55:27 +0100 Subject: [PATCH 0046/1296] udev: rules: improve usb integration detection usb hubs tend to expose removable attribute as unknown. This makes some problems like a hub for external usb ports in pogo pins is unknown and also soldered hubs in laptops for keyboard+touchpad. Let's set internal when the device removable attribute is fixed and external when removable, but when it's unknown lets check the parent ports (not the host devpath!=0) attribute to decide. This makes us to missdetect pogo ping connected external usb hubs but let us to correctly detect laptop internal keyboards and touchpads that are wired through hubs instead directly. This behaviour is more desirable, as actually there are a bunch of laptops with this setup. Fixes: a4381cae8bfacb1160967ac499c2919da7ff8c2b. --- rules.d/65-integration.rules | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/rules.d/65-integration.rules b/rules.d/65-integration.rules index 5f26416eb5cb5..5d78c94c387bd 100644 --- a/rules.d/65-integration.rules +++ b/rules.d/65-integration.rules @@ -7,16 +7,21 @@ ACTION=="remove", GOTO="integration_end" ENV{ID_BUS}=="", GOTO="integration_end" # ACPI, platform, PS/2, I2C, RMI, SPI and PCI devices: Internal by default. -ENV{ID_BUS}=="acpi|platform|i8042|i2c|rmi|spi|pci", ENV{ID_INTEGRATION}="internal" +ENV{ID_BUS}=="acpi|platform|i8042|i2c|rmi|spi|pci", ENV{ID_INTEGRATION}="internal", GOTO="libinput_integration_compat" # Bluetooth devices: External by default. -ENV{ID_BUS}=="bluetooth", ENV{ID_INTEGRATION}="external" +ENV{ID_BUS}=="bluetooth", ENV{ID_INTEGRATION}="external", GOTO="libinput_integration_compat" -# USB devices: Internal if it's connected to a fixed port, external to a removable or unknown. -ENV{ID_BUS}=="usb", DRIVERS=="usb", ATTRS{maxchild}=="0", ATTRS{removable}=="fixed", ENV{ID_INTEGRATION}="internal" -ENV{ID_BUS}=="usb", DRIVERS=="usb", ATTRS{maxchild}=="0", ATTRS{removable}=="removable|unknown", ENV{ID_INTEGRATION}="external" +# USB devices: Internal if it's connected to a fixed port, external to a removable and if it's unknown we use the main parent device attribute. +ENV{ID_BUS}!="usb", GOTO="usb_integration_end" +DRIVERS=="usb", ATTRS{maxchild}=="0", ATTRS{removable}=="fixed", ENV{ID_INTEGRATION}="internal", GOTO="libinput_integration_compat" +DRIVERS=="usb", ATTRS{maxchild}=="0", ATTRS{removable}=="removable", ENV{ID_INTEGRATION}="external", GOTO="libinput_integration_compat" +DRIVERS=="usb", ATTRS{devpath}!="0", ATTRS{removable}=="fixed", ENV{ID_INTEGRATION}="internal", GOTO="libinput_integration_compat" +DRIVERS=="usb", ATTRS{devpath}!="0", ATTRS{removable}=="removable|unknown", ENV{ID_INTEGRATION}="external", GOTO="libinput_integration_compat" +LABEL="usb_integration_end" # libinput compatibility, must be loaded before 70-touchpad.rules to allow hwdb quirks to override. +LABEL="libinput_integration_compat" ENV{ID_INPUT_TOUCHPAD}=="1", ENV{ID_INPUT_TOUCHPAD_INTEGRATION}="$env{ID_INTEGRATION}" LABEL="integration_end" From 085f92730a51938c17b011184e7f65ab2dd5fcd3 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Tue, 3 Mar 2026 22:24:39 +0100 Subject: [PATCH 0047/1296] tree-wide: use ALPHANUMERICAL where appropriate Prompted by 43116c56621c7317852c01e67dbcaa78b5e7ff70 --- src/basic/env-util.c | 2 +- src/basic/login-util.c | 2 +- src/basic/unit-name.c | 3 +-- src/hostname/hostnamed.c | 2 +- src/import/oci-util.c | 6 +++--- src/portable/portablectl.c | 2 +- 6 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/basic/env-util.c b/src/basic/env-util.c index 7964a368db265..4f6240e546519 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -19,7 +19,7 @@ /* We follow bash for the character set. Different shells have different rules. */ #define VALID_BASH_ENV_NAME_CHARS \ - DIGITS LETTERS \ + ALPHANUMERICAL \ "_" size_t sc_arg_max(void) { diff --git a/src/basic/login-util.c b/src/basic/login-util.c index 926e482fb0eac..7f9b84be3c180 100644 --- a/src/basic/login-util.c +++ b/src/basic/login-util.c @@ -10,7 +10,7 @@ bool session_id_valid(const char *id) { if (isempty(id)) return false; - return id[strspn(id, LETTERS DIGITS)] == '\0'; + return in_charset(id, ALPHANUMERICAL); } bool logind_running(void) { diff --git a/src/basic/unit-name.c b/src/basic/unit-name.c index 43b3f6831222a..8a04638b2f87b 100644 --- a/src/basic/unit-name.c +++ b/src/basic/unit-name.c @@ -17,8 +17,7 @@ /* Characters valid in a unit name. */ #define VALID_CHARS \ - DIGITS \ - LETTERS \ + ALPHANUMERICAL \ ":-_.\\" /* The same, but also permits the single @ character that may appear */ diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index a567221f8ea0b..04c72e8137bf1 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -49,7 +49,7 @@ #include "varlink-util.h" #include "virt.h" -#define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:") +#define VALID_DEPLOYMENT_CHARS (ALPHANUMERICAL "-.:") /* Properties we cache are indexed by an enum, to make invalidation easy and systematic (as we can iterate * through them all, and they are uniformly strings). */ diff --git a/src/import/oci-util.c b/src/import/oci-util.c index 37a617b28ce1a..96f7729fb6393 100644 --- a/src/import/oci-util.c +++ b/src/import/oci-util.c @@ -79,10 +79,10 @@ bool oci_tag_is_valid(const char *n) { * [a-zA-Z0-9_][a-zA-Z0-9._-]{0,127} */ - if (!strchr(LETTERS DIGITS "_", n[0])) + if (!strchr(ALPHANUMERICAL "_", n[0])) return false; - size_t l = strspn(n + 1, LETTERS DIGITS "._-"); + size_t l = strspn(n + 1, ALPHANUMERICAL "._-"); if (l > 126) return false; if (n[1+l] != 0) @@ -387,7 +387,7 @@ char* urlescape(const char *s) { char *p = t; for (; s && *s; s++) { - if (strchr(LETTERS DIGITS ".-_", *s)) + if (strchr(ALPHANUMERICAL ".-_", *s)) *(p++) = *s; else { *(p++) = '%'; diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index dfe6df3bf310d..d277504a81196 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -155,7 +155,7 @@ static int extract_prefix(const char *path, char **ret) { /* A slightly reduced version of what's permitted in unit names. With ':' and '\' are removed, as well as '_' * which we use as delimiter for the second part of the image string, which we ignore for now. */ - if (!in_charset(name, DIGITS LETTERS "-.")) + if (!in_charset(name, ALPHANUMERICAL "-.")) return -EINVAL; if (!filename_is_valid(name)) From 36d30330f7f643361d24f57088ee9cafb2c72a14 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Sun, 1 Mar 2026 14:20:53 +0100 Subject: [PATCH 0048/1296] ansi-color: in 256 mode, always set the fallback color first Linux console is very weird when it comes to ANSI color sequences. Not only that it isn't aware of ':' separator (c.f. https://github.com/systemd/systemd/pull/40878#issuecomment-3979826739), it even skips the whole CSI-m sequence if it contains parts it cannot parse. Hence when color mode is set to 256 (i.e. default when no extra info is available) let's always emit two distinct CSI-m sequences, and set the fallback 16 color first in case the terminal doesn't have complete support for the 256 one. Replaces #40905 --- src/basic/ansi-color.h | 99 +++++++++++++++++++++++------------------- 1 file changed, 55 insertions(+), 44 deletions(-) diff --git a/src/basic/ansi-color.h b/src/basic/ansi-color.h index e686e1167b14e..1ddb9c6681c87 100644 --- a/src/basic/ansi-color.h +++ b/src/basic/ansi-color.h @@ -111,41 +111,20 @@ bool looks_like_ansi_color_code(const char *str); return colors_enabled() ? ANSI_##NAME : ""; \ } -#define DEFINE_ANSI_FUNC_256(name, NAME, FALLBACK) \ - static inline const char* ansi_##name(void) { \ - switch (get_color_mode()) { \ - case COLOR_OFF: return ""; \ - case COLOR_16: return ANSI_##FALLBACK; \ - default : return ANSI_##NAME; \ - } \ - } - -static inline const char* ansi_underline(void) { - return underline_enabled() ? ANSI_UNDERLINE : ""; -} - -static inline const char* ansi_add_underline(void) { - return underline_enabled() ? ANSI_ADD_UNDERLINE : ""; -} - -static inline const char* ansi_add_underline_grey(void) { - return underline_enabled() ? - (colors_enabled() ? ANSI_ADD_UNDERLINE_GREY : ANSI_ADD_UNDERLINE) : ""; -} - -#define DEFINE_ANSI_FUNC_UNDERLINE(name, NAME) \ - static inline const char* ansi_##name(void) { \ - return underline_enabled() ? ANSI_##NAME##_UNDERLINE : \ - colors_enabled() ? ANSI_##NAME : ""; \ - } - -#define DEFINE_ANSI_FUNC_UNDERLINE_256(name, NAME, FALLBACK) \ - static inline const char* ansi_##name(void) { \ - switch (get_color_mode()) { \ - case COLOR_OFF: return ""; \ - case COLOR_16: return underline_enabled() ? ANSI_##FALLBACK##_UNDERLINE : ANSI_##FALLBACK; \ - default : return underline_enabled() ? ANSI_##NAME##_UNDERLINE: ANSI_##NAME; \ - } \ +/* NB: in 256 mode we always emit the fallback color first, in order to deal with terminals with + * incomplete 256 color support (most notably Linux console, which a) lacks support for ":" + * subcommand separator and b) skips over the whole CSI-m sequence if it sees an "invalid" command). + * In 24-bit mode we don't bother with this however, under the assumption that $COLORTERM and friends + * reflect the correct status. */ + +#define DEFINE_ANSI_FUNC_256(name, NAME, FALLBACK) \ + static inline const char* ansi_##name(void) { \ + switch (get_color_mode()) { \ + case COLOR_OFF: return ""; \ + case COLOR_16: return ANSI_##FALLBACK; \ + case COLOR_256: return ANSI_##FALLBACK ANSI_##NAME; \ + default: return ANSI_##NAME; \ + } \ } DEFINE_ANSI_FUNC(normal, NORMAL); @@ -184,15 +163,47 @@ static inline const char* _ansi_highlight_yellow(void) { return colors_enabled() ? _ANSI_HIGHLIGHT_YELLOW : ""; } -DEFINE_ANSI_FUNC_UNDERLINE(highlight_underline, HIGHLIGHT); -DEFINE_ANSI_FUNC_UNDERLINE_256(grey_underline, GREY, BRIGHT_BLACK); -DEFINE_ANSI_FUNC_UNDERLINE(highlight_red_underline, HIGHLIGHT_RED); -DEFINE_ANSI_FUNC_UNDERLINE(highlight_green_underline, HIGHLIGHT_GREEN); -DEFINE_ANSI_FUNC_UNDERLINE_256(highlight_yellow_underline, HIGHLIGHT_YELLOW, HIGHLIGHT_YELLOW_FALLBACK); -DEFINE_ANSI_FUNC_UNDERLINE(highlight_blue_underline, HIGHLIGHT_BLUE); -DEFINE_ANSI_FUNC_UNDERLINE(highlight_magenta_underline, HIGHLIGHT_MAGENTA); -DEFINE_ANSI_FUNC_UNDERLINE_256(highlight_grey_underline, HIGHLIGHT_GREY, HIGHLIGHT_GREY_FALLBACK); - static inline const char* ansi_highlight_green_red(bool b) { return b ? ansi_highlight_green() : ansi_highlight_red(); } + +static inline const char* ansi_underline(void) { + return underline_enabled() ? ANSI_UNDERLINE : ""; +} + +static inline const char* ansi_add_underline(void) { + return underline_enabled() ? ANSI_ADD_UNDERLINE : ""; +} + +static inline const char* ansi_add_underline_grey(void) { + return underline_enabled() ? + (colors_enabled() ? ANSI_ADD_UNDERLINE_GREY : ANSI_ADD_UNDERLINE) : ""; +} + +#define DEFINE_ANSI_FUNC_UNDERLINE(name, NAME) \ + static inline const char* ansi_##name##_underline(void) { \ + return underline_enabled() ? ANSI_##NAME##_UNDERLINE : \ + ansi_##name(); \ + } + +#define DEFINE_ANSI_FUNC_UNDERLINE_256(name, NAME, FALLBACK) \ + static inline const char* ansi_##name##_underline(void) { \ + if (!underline_enabled()) \ + return ansi_##name(); \ + \ + switch (get_color_mode()) { \ + case COLOR_OFF: return ""; \ + case COLOR_16: return ANSI_##FALLBACK##_UNDERLINE; \ + case COLOR_256: return ANSI_##FALLBACK##_UNDERLINE ANSI_##NAME##_UNDERLINE; \ + default: return ANSI_##NAME##_UNDERLINE; \ + } \ + } + +DEFINE_ANSI_FUNC_UNDERLINE(highlight, HIGHLIGHT); +DEFINE_ANSI_FUNC_UNDERLINE(highlight_red, HIGHLIGHT_RED); +DEFINE_ANSI_FUNC_UNDERLINE(highlight_green, HIGHLIGHT_GREEN); +DEFINE_ANSI_FUNC_UNDERLINE_256(highlight_yellow, HIGHLIGHT_YELLOW, HIGHLIGHT_YELLOW_FALLBACK); +DEFINE_ANSI_FUNC_UNDERLINE(highlight_blue, HIGHLIGHT_BLUE); +DEFINE_ANSI_FUNC_UNDERLINE(highlight_magenta, HIGHLIGHT_MAGENTA); +DEFINE_ANSI_FUNC_UNDERLINE_256(grey, GREY, BRIGHT_BLACK); +DEFINE_ANSI_FUNC_UNDERLINE_256(highlight_grey, HIGHLIGHT_GREY, HIGHLIGHT_GREY_FALLBACK); From 59a9d1d8ca7d4c24cc5a86e14e08e191410b925a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 3 Mar 2026 14:28:46 +0100 Subject: [PATCH 0049/1296] udevadm: fix --help text for udevadm test-builtin --- src/udev/udevadm-test-builtin.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/udev/udevadm-test-builtin.c b/src/udev/udevadm-test-builtin.c index a1ad9cb320384..b25c7ae2fa6a4 100644 --- a/src/udev/udevadm-test-builtin.c +++ b/src/udev/udevadm-test-builtin.c @@ -18,9 +18,9 @@ static int help(void) { printf("%s test-builtin [OPTIONS] COMMAND DEVPATH\n\n" "Test a built-in command.\n\n" " -h --help Print this message\n" - " -V --version Print version of the program\n\n" + " -V --version Print version of the program\n" " -a --action=ACTION|help Set action string\n" - "Commands:\n", + "\nCommands:\n", program_invocation_short_name); udev_builtin_list(); From 170a8f1d408c872d32b62536a7a5d7efaf8f13a7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 3 Mar 2026 16:00:00 +0100 Subject: [PATCH 0050/1296] tpm2-util: mark two functions as static that are not used outside of tpm2-util.c --- src/shared/tpm2-util.c | 6 ++++-- src/shared/tpm2-util.h | 2 -- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index e5694361422fd..b0f6387b03782 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -5976,7 +5976,8 @@ int tpm2_undefine_nv_index( return 0; } -int tpm2_define_nvpcr_nv_index( +#if HAVE_OPENSSL +static int tpm2_define_nvpcr_nv_index( Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE nv_index, @@ -6095,7 +6096,7 @@ int tpm2_define_nvpcr_nv_index( return 1; } -int tpm2_extend_nvpcr_nv_index( +static int tpm2_extend_nvpcr_nv_index( Tpm2Context *c, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle, @@ -6136,6 +6137,7 @@ int tpm2_extend_nvpcr_nv_index( return 0; } +#endif int tpm2_read_nv_index( Tpm2Context *c, diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 68289bec48a1c..282aaa1e8035a 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -304,8 +304,6 @@ int tpm2_tpm2b_public_to_fingerprint(const TPM2B_PUBLIC *public, void **ret_fing int tpm2_define_policy_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE requested_nv_index, const TPM2B_DIGEST *write_policy, TPM2_HANDLE *ret_nv_index, Tpm2Handle **ret_nv_handle, TPM2B_NV_PUBLIC *ret_nv_public); int tpm2_write_policy_nv_index(Tpm2Context *c, const Tpm2Handle *policy_session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle, const TPM2B_DIGEST *policy_digest); -int tpm2_define_nvpcr_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE nv_index, TPMI_ALG_HASH algorithm, Tpm2Handle **ret_nv_handle); -int tpm2_extend_nvpcr_nv_index(Tpm2Context *c, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle, const struct iovec *digest); int tpm2_undefine_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle); int tpm2_read_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle, struct iovec *ret_value); From 4dbb0cd0bf17a45aef21b70c9ddf8013d68c6a2d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 3 Mar 2026 21:07:57 +0100 Subject: [PATCH 0051/1296] parse_hwdb: introduce local variable for boolean syntax --- hwdb.d/parse_hwdb.py | 47 ++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/hwdb.d/parse_hwdb.py b/hwdb.d/parse_hwdb.py index 1a56f40c46102..3c81fa371f522 100755 --- a/hwdb.d/parse_hwdb.py +++ b/hwdb.d/parse_hwdb.py @@ -147,6 +147,7 @@ def property_grammar(): mount_matrix = Group(mount_matrix_row + ';' + mount_matrix_row + ';' + mount_matrix_row)('MOUNT_MATRIX') xkb_setting = Optional(Word(alphanums + '+-/@._')) id_input_setting = Optional(Or((Literal('0'), Literal('1')))) + zero_one = Or((Literal('0'), Literal('1'))) # Although this set doesn't cover all of characters in database entries, it's enough for test targets. name_literal = Word(printables + ' ') @@ -156,13 +157,13 @@ def property_grammar(): ('MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL', INTEGER), ('MOUSE_WHEEL_CLICK_COUNT', INTEGER), ('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', INTEGER), - ('ID_INPUT_3D_MOUSE', Or((Literal('0'), Literal('1')))), - ('ID_AUTOSUSPEND', Or((Literal('0'), Literal('1')))), + ('ID_INPUT_3D_MOUSE', zero_one), + ('ID_AUTOSUSPEND', zero_one), ('ID_AUTOSUSPEND_DELAY_MS', INTEGER), - ('ID_AV_PRODUCTION_CONTROLLER', Or((Literal('0'), Literal('1')))), - ('ID_AV_LIGHTS', Or((Literal('0'), Literal('1')))), - ('ID_PERSIST', Or((Literal('0'), Literal('1')))), - ('ID_PDA', Or((Literal('0'), Literal('1')))), + ('ID_AV_PRODUCTION_CONTROLLER', zero_one), + ('ID_AV_LIGHTS', zero_one), + ('ID_PERSIST', zero_one), + ('ID_PDA', zero_one), ('ID_INPUT', id_input_setting), ('ID_INPUT_ACCELEROMETER', id_input_setting), ('ID_INPUT_JOYSTICK', id_input_setting), @@ -176,12 +177,12 @@ def property_grammar(): ('ID_INPUT_TOUCHPAD', id_input_setting), ('ID_INPUT_TOUCHSCREEN', id_input_setting), ('ID_INPUT_TRACKBALL', id_input_setting), - ('ID_SIGNAL_ANALYZER', Or((Literal('0'), Literal('1')))), - ('ID_MAKER_TOOL', Or((Literal('0'), Literal('1')))), - ('ID_HARDWARE_WALLET', Or((Literal('0'), Literal('1')))), - ('ID_SOFTWARE_RADIO', Or((Literal('0'), Literal('1')))), - ('ID_MM_DEVICE_IGNORE', Or((Literal('0'), Literal('1')))), - ('ID_NET_AUTO_LINK_LOCAL_ONLY', Or((Literal('0'), Literal('1')))), + ('ID_SIGNAL_ANALYZER', zero_one), + ('ID_MAKER_TOOL', zero_one), + ('ID_HARDWARE_WALLET', zero_one), + ('ID_SOFTWARE_RADIO', zero_one), + ('ID_MM_DEVICE_IGNORE', zero_one), + ('ID_NET_AUTO_LINK_LOCAL_ONLY', zero_one), ('POINTINGSTICK_SENSITIVITY', INTEGER), ('ID_INTEGRATION', Or(('internal', 'external'))), ('ID_INPUT_TOUCHPAD_INTEGRATION', Or(('internal', 'external'))), @@ -193,25 +194,25 @@ def property_grammar(): ('ACCEL_MOUNT_MATRIX', mount_matrix), ('ACCEL_LOCATION', Or(('display', 'base'))), ('PROXIMITY_NEAR_LEVEL', INTEGER), - ('IEEE1394_UNIT_FUNCTION_MIDI', Or((Literal('0'), Literal('1')))), - ('IEEE1394_UNIT_FUNCTION_AUDIO', Or((Literal('0'), Literal('1')))), - ('IEEE1394_UNIT_FUNCTION_VIDEO', Or((Literal('0'), Literal('1')))), + ('IEEE1394_UNIT_FUNCTION_MIDI', zero_one), + ('IEEE1394_UNIT_FUNCTION_AUDIO', zero_one), + ('IEEE1394_UNIT_FUNCTION_VIDEO', zero_one), ('ID_VENDOR_FROM_DATABASE', name_literal), ('ID_MODEL_FROM_DATABASE', name_literal), ('ID_TAG_MASTER_OF_SEAT', Literal('1')), - ('ID_INFRARED_CAMERA', Or((Literal('0'), Literal('1')))), + ('ID_INFRARED_CAMERA', zero_one), ('ID_CAMERA_DIRECTION', Or(('front', 'rear'))), ('SOUND_FORM_FACTOR', Or(('internal', 'webcam', 'speaker', 'headphone', 'headset', 'handset', 'microphone'))), - ('ID_SYS_VENDOR_IS_RUBBISH', Or((Literal('0'), Literal('1')))), - ('ID_PRODUCT_NAME_IS_RUBBISH', Or((Literal('0'), Literal('1')))), - ('ID_PRODUCT_VERSION_IS_RUBBISH', Or((Literal('0'), Literal('1')))), - ('ID_BOARD_VERSION_IS_RUBBISH', Or((Literal('0'), Literal('1')))), - ('ID_PRODUCT_SKU_IS_RUBBISH', Or((Literal('0'), Literal('1')))), - ('ID_CHASSIS_ASSET_TAG_IS_RUBBISH', Or((Literal('0'), Literal('1')))), + ('ID_SYS_VENDOR_IS_RUBBISH', zero_one), + ('ID_PRODUCT_NAME_IS_RUBBISH', zero_one), + ('ID_PRODUCT_VERSION_IS_RUBBISH', zero_one), + ('ID_BOARD_VERSION_IS_RUBBISH', zero_one), + ('ID_PRODUCT_SKU_IS_RUBBISH', zero_one), + ('ID_CHASSIS_ASSET_TAG_IS_RUBBISH', zero_one), ('ID_CHASSIS', name_literal), ('ID_SYSFS_ATTRIBUTE_MODEL', name_literal), ('ID_NET_NAME_FROM_DATABASE', name_literal), - ('ID_NET_NAME_INCLUDE_DOMAIN', Or((Literal('0'), Literal('1')))), + ('ID_NET_NAME_INCLUDE_DOMAIN', zero_one), ) fixed_props = [Literal(name)('NAME') - Suppress('=') - val('VALUE') for name, val in props] From 96a8bfcd7a3766e336c81b5031d8600577a01ab4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 3 Mar 2026 21:09:27 +0100 Subject: [PATCH 0052/1296] parse_hwdb: sort general matches --- hwdb.d/parse_hwdb.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hwdb.d/parse_hwdb.py b/hwdb.d/parse_hwdb.py index 3c81fa371f522..7efa61ad4b8c3 100755 --- a/hwdb.d/parse_hwdb.py +++ b/hwdb.d/parse_hwdb.py @@ -100,13 +100,13 @@ # Patterns that are used to set general properties on a device GENERAL_MATCHES = {'acpi', 'bluetooth', - 'usb', + 'dmi', + 'ieee1394', + 'OUI', 'pci', 'sdio', + 'usb', 'vmbus', - 'OUI', - 'ieee1394', - 'dmi', } def upperhex_word(length): From 5f85409f932dfdc123d0e8ded8e8a9a6f9443119 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 3 Mar 2026 14:27:36 +0100 Subject: [PATCH 0053/1296] tpm2-util: also load libtss2-tcti-device.so.0 in dlopen_tpm2() This TCTI module is the one we need to actually access a Linux TPM device, we'll hence pretty much always need it if we do TPM at all. Given that we nowadays turn off dlopen() after fork() in the child, let's explicitly load it as part of dlopen_tpm2() so that it is available whenever TPM2 is used. --- src/basic/dlfcn-util.c | 20 +++++++++++++++++++- src/basic/dlfcn-util.h | 1 + src/shared/tpm2-util.c | 19 +++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/basic/dlfcn-util.c b/src/basic/dlfcn-util.c index 8574e99546b81..23bd1f666e32b 100644 --- a/src/basic/dlfcn-util.c +++ b/src/basic/dlfcn-util.c @@ -47,9 +47,11 @@ int dlsym_many_or_warn_sentinel(void *dl, int log_level, ...) { return r; } -int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_level, ...) { +int dlopen_verbose(void **dlp, const char *filename) { int r; + assert(dlp); + if (*dlp) return 0; /* Already loaded */ @@ -62,6 +64,22 @@ int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_l } log_debug("Loaded shared library '%s' via dlopen().", filename); + *dlp = TAKE_PTR(dl); + return 1; +} + +int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_level, ...) { + int r; + + assert(dlp); + + if (*dlp) + return 0; /* Already loaded */ + + _cleanup_(dlclosep) void *dl = NULL; + r = dlopen_verbose(&dl, filename); + if (r < 0) + return r; va_list ap; va_start(ap, log_level); diff --git a/src/basic/dlfcn-util.h b/src/basic/dlfcn-util.h index 367c47ddb8610..9760b31da4d05 100644 --- a/src/basic/dlfcn-util.h +++ b/src/basic/dlfcn-util.h @@ -11,6 +11,7 @@ static inline void dlclosep(void **dlp) { safe_dlclose(*dlp); } +int dlopen_verbose(void **dlp, const char *filename); int dlsym_many_or_warn_sentinel(void *dl, int log_level, ...) _sentinel_; int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_level, ...) _sentinel_; diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index b0f6387b03782..0d4f51dc1e762 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -53,6 +53,7 @@ static void *libtss2_esys_dl = NULL; static void *libtss2_rc_dl = NULL; static void *libtss2_mu_dl = NULL; +static void *libtss2_tcti_device_dl = NULL; static DLSYM_PROTOTYPE(Esys_Create) = NULL; static DLSYM_PROTOTYPE(Esys_CreateLoaded) = NULL; @@ -226,6 +227,20 @@ static int dlopen_tpm2_mu(void) { DLSYM_ARG(Tss2_MU_UINT32_Marshal)); } +static int dlopen_tpm2_tcti_device(void) { + /* The "device" TCTI is the most relevant one, let's also load it explicitly on dlopen_tpm2(), even + * if we don't resolve any symbols here. */ + + ELF_NOTE_DLOPEN("tpm", + "Support for TPM", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libtss2-tcti-device.so.0"); + + return dlopen_verbose( + &libtss2_tcti_device_dl, + "libtss2-tcti-device.so.0"); +} + #endif int dlopen_tpm2(void) { @@ -244,6 +259,10 @@ int dlopen_tpm2(void) { if (r < 0) return r; + r = dlopen_tpm2_tcti_device(); + if (r < 0) + return r; + return 0; #else return -EOPNOTSUPP; From 98a9844f3e012adfc92f9bc320628ad2fc54c3c8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 3 Mar 2026 12:27:55 +0100 Subject: [PATCH 0054/1296] tpm2-util: add tpm2_get_vendor_info() helper for getting TPM2 vendor info from the device --- src/shared/tpm2-util.c | 109 +++++++++++++++++++++++++++++++++++++++++ src/shared/tpm2-util.h | 17 +++++++ 2 files changed, 126 insertions(+) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 0d4f51dc1e762..64332fda32de3 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -43,6 +43,7 @@ #include "time-util.h" #include "tpm2-pcr.h" #include "tpm2-util.h" +#include "unaligned.h" #include "virt.h" #if HAVE_OPENSSL @@ -348,6 +349,114 @@ static int tpm2_get_capability( return more == TPM2_YES; } +static char *mangle_vendor_chars(char *c, size_t n) { + char *end = c; + assert(c || n == 0); + + /* Suppress all control characters, whitespace and non-ASCII bytes */ + for (char *x = c; x < c + n; x++) { + if (!IN_SET(*x, ' ', 0)) + end = x + 1; + + if ((unsigned char) *x <= (unsigned char) ' ' || + (unsigned char) *x >= 127) + *x = '_'; + } + + /* Drop trailing spaces and NUL bytes */ + *end = 0; + return c; +} + +int tpm2_get_vendor_info( + Tpm2Context *c, + Tpm2VendorInfo *ret) { + + int r; + + assert(c); + assert(ret); + + TPMU_CAPABILITIES capabilities = {}; + r = tpm2_get_capability( + c, + TPM2_CAP_TPM_PROPERTIES, + TPM2_PT_FAMILY_INDICATOR, + TPM2_PT_FIRMWARE_VERSION_2 - TPM2_PT_FAMILY_INDICATOR + 1, /* get all relevant fields at once */ + &capabilities); + if (r < 0) + return r; + + Tpm2VendorInfo info = {}; + + for (uint32_t i = 0; i < capabilities.tpmProperties.count; i++) { + TPMS_TAGGED_PROPERTY *p = capabilities.tpmProperties.tpmProperty + i; + + switch (p->property) { + + case TPM2_PT_FAMILY_INDICATOR: + unaligned_write_be32(info.family_indicator, p->value); + mangle_vendor_chars(info.family_indicator, sizeof(info.family_indicator)); + break; + + case TPM2_PT_LEVEL: + info.level = p->value; + break; + + case TPM2_PT_REVISION: + info.revision_major = p->value / 100; + info.revision_minor = p->value % 100; + break; + + case TPM2_PT_DAY_OF_YEAR: + info.day_of_year = p->value; + break; + + case TPM2_PT_YEAR: + info.year = p->value; + break; + + case TPM2_PT_MANUFACTURER: + unaligned_write_be32(info.manufacturer, p->value); + mangle_vendor_chars(info.manufacturer, sizeof(info.manufacturer)); + break; + + case TPM2_PT_VENDOR_STRING_1: + unaligned_write_be32(info.vendor_string+0, p->value); + break; + + case TPM2_PT_VENDOR_STRING_2: + unaligned_write_be32(info.vendor_string+4, p->value); + break; + + case TPM2_PT_VENDOR_STRING_3: + unaligned_write_be32(info.vendor_string+8, p->value); + break; + + case TPM2_PT_VENDOR_STRING_4: + unaligned_write_be32(info.vendor_string+12, p->value); + break; + + case TPM2_PT_VENDOR_TPM_TYPE: + info.vendor_tpm_type = p->value; + break; + + case TPM2_PT_FIRMWARE_VERSION_1: + info.firmware_version_major = p->value >> 16; + info.firmware_version_minor = p->value & 0xFFFFU; + break; + + case TPM2_PT_FIRMWARE_VERSION_2: + info.firmware_version2 = p->value; + break; + } + } + + mangle_vendor_chars(info.vendor_string, sizeof(info.vendor_string)); + *ret = TAKE_STRUCT(info); + return 0; +} + #define TPMA_CC_TO_TPM2_CC(cca) (((cca) & TPMA_CC_COMMANDINDEX_MASK) >> TPMA_CC_COMMANDINDEX_SHIFT) static int tpm2_cache_capabilities(Tpm2Context *c) { diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 282aaa1e8035a..cbdad197191ae 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -195,6 +195,23 @@ static inline int tpm2_digest_init(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest) { return tpm2_digest_many(alg, digest, NULL, 0, false); } +typedef struct Tpm2VendorInfo { + uint32_t level; + uint32_t revision_major; + uint32_t revision_minor; + uint32_t day_of_year; + uint32_t year; + uint32_t vendor_tpm_type; + uint16_t firmware_version_major; + uint16_t firmware_version_minor; + uint32_t firmware_version2; + char family_indicator[4+1]; + char manufacturer[4+1]; + char vendor_string[4*4+1]; +} Tpm2VendorInfo; + +int tpm2_get_vendor_info(Tpm2Context *c, Tpm2VendorInfo *ret); + void tpm2_log_debug_tpml_pcr_selection(const TPML_PCR_SELECTION *l, const char *msg); void tpm2_log_debug_pcr_value(const Tpm2PCRValue *pcr_value, const char *msg); void tpm2_log_debug_buffer(const void *buffer, size_t size, const char *msg); From 205b70e3284da39c7fc19e5f8e597ae45a316cd2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 3 Mar 2026 12:28:22 +0100 Subject: [PATCH 0055/1296] analyze: add "identify-tpm2" command that shows TPM2 chip information --- man/systemd-analyze.xml | 25 ++++++ shell-completion/bash/systemd-analyze | 2 +- src/analyze/analyze-has-tpm2.c | 122 ++++++++++++++++++++++++++ src/analyze/analyze-has-tpm2.h | 1 + src/analyze/analyze.c | 2 + 5 files changed, 151 insertions(+), 1 deletion(-) diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml index e64f9be57ee7f..6a022916664f6 100644 --- a/man/systemd-analyze.xml +++ b/man/systemd-analyze.xml @@ -195,6 +195,11 @@ OPTIONS has-tpm2 + + systemd-analyze + OPTIONS + identify-tpm2 + systemd-analyze OPTIONS @@ -1028,6 +1033,26 @@ default ignore - - + + <command>systemd-analyze identify-tpm2</command> + + Shows vendor information about the TPM 2.0 device discovered. + + + Example Output + + Family Indicator: 2.0 + Level: 0 + Revision: 1.59 +Specification Date: Mon 2023-01-09 + Manufacturer: STM + Vendor String: ST33KTPM2XSPI + Firmware Version: 9.258 + + + + + <command>systemd-analyze pcrs <optional><replaceable>PCR</replaceable>…</optional></command> diff --git a/shell-completion/bash/systemd-analyze b/shell-completion/bash/systemd-analyze index a863af7affd76..b194c74b63836 100644 --- a/shell-completion/bash/systemd-analyze +++ b/shell-completion/bash/systemd-analyze @@ -77,7 +77,7 @@ _systemd_analyze() { ) local -A VERBS=( - [STANDALONE]='time blame unit-files unit-paths exit-status compare-versions timestamp timespan pcrs nvpcrs srk has-tpm2 smbios11 chid image-policy' + [STANDALONE]='time blame unit-files unit-paths exit-status compare-versions timestamp timespan pcrs nvpcrs srk has-tpm2 identify-tpm2 smbios11 chid image-policy' [CRITICAL_CHAIN]='critical-chain' [DOT]='dot' [DUMP]='dump' diff --git a/src/analyze/analyze-has-tpm2.c b/src/analyze/analyze-has-tpm2.c index 3e13be9f160fd..a63c44ebc649e 100644 --- a/src/analyze/analyze-has-tpm2.c +++ b/src/analyze/analyze-has-tpm2.c @@ -2,8 +2,130 @@ #include "analyze.h" #include "analyze-has-tpm2.h" +#include "format-table.h" +#include "log.h" +#include "string-util.h" +#include "time-util.h" #include "tpm2-util.h" int verb_has_tpm2(int argc, char **argv, void *userdata) { return verb_has_tpm2_generic(arg_quiet); } + +int verb_identify_tpm2(int argc, char **argv, void *userdata) { +#if HAVE_TPM2 + int r; + + _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; + r = tpm2_context_new_or_warn(/* device= */ NULL, &c); + if (r < 0) + return r; + + Tpm2VendorInfo info; + r = tpm2_get_vendor_info(c, &info); + if (r < 0) + return log_error_errno(r, "Failed to acquire TPM2 vendor information: %m"); + + _cleanup_(table_unrefp) Table *table = table_new_vertical(); + if (!table) + return log_oom(); + + if (!isempty(info.family_indicator)) { + r = table_add_many( + table, + TABLE_FIELD, "Family Indicator", + TABLE_STRING, info.family_indicator); + if (r < 0) + return table_log_add_error(r); + } + + _cleanup_free_ char *rv = NULL; + if (asprintf(&rv, "%" PRIu32 ".%" PRIu32, + info.revision_major, + info.revision_minor) < 0) + return log_oom(); + + r = table_add_many( + table, + TABLE_FIELD, "Level", + TABLE_UINT32, info.level, + TABLE_FIELD, "Revision", + TABLE_STRING, rv); + if (r < 0) + return table_log_add_error(r); + + if (info.year >= 1900) { + struct tm tm = { + .tm_year = info.year - 1900, + .tm_mon = 0, /* january */ + .tm_mday = info.day_of_year, /* timegm() will normalize this */ + }; + + usec_t ts; + r = mktime_or_timegm_usec(&tm, /* utc= */ true, &ts); + if (r < 0) + log_debug_errno(r, "Failed to convert the specification date, ignoring."); + else { + r = table_add_many( + table, + TABLE_FIELD, "Specification Date", + TABLE_TIMESTAMP_DATE, ts); + if (r < 0) + return table_log_add_error(r); + } + } + + if (!isempty(info.manufacturer)) { + r = table_add_many( + table, + TABLE_FIELD, "Manufacturer", + TABLE_STRING, info.manufacturer); + if (r < 0) + return table_log_add_error(r); + } + + if (!isempty(info.vendor_string)) { + r = table_add_many( + table, + TABLE_FIELD, "Vendor String", + TABLE_STRING, info.vendor_string); + if (r < 0) + return table_log_add_error(r); + } + + if (info.vendor_tpm_type != 0) { + r = table_add_many( + table, + TABLE_FIELD, "Vendor TPM Type", + TABLE_UINT32_HEX_0x, info.vendor_tpm_type); + if (r < 0) + return table_log_add_error(r); + } + + /* Show the first two 16bit words of the firmware version as major/minor */ + _cleanup_free_ char *fw = NULL; + if (asprintf(&fw, "%" PRIu16 ".%" PRIu16, + info.firmware_version_major, + info.firmware_version_minor) < 0) + return log_oom(); + + /* Show the second 32bit as a single value, if non-zero */ + if (info.firmware_version2 != 0 && strextendf(&fw, ".%" PRIu32, info.firmware_version2) < 0) + return log_oom(); + + r = table_add_many( + table, + TABLE_FIELD, "Firmware Version", + TABLE_STRING, fw); + if (r < 0) + return table_log_add_error(r); + + r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, /* show_header= */ false); + if (r < 0) + return r; + + return EXIT_SUCCESS; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support not enabled at build time."); +#endif +} diff --git a/src/analyze/analyze-has-tpm2.h b/src/analyze/analyze-has-tpm2.h index c7c639228d87c..b7d750090b57b 100644 --- a/src/analyze/analyze-has-tpm2.h +++ b/src/analyze/analyze-has-tpm2.h @@ -2,3 +2,4 @@ #pragma once int verb_has_tpm2(int argc, char *argv[], void *userdata); +int verb_identify_tpm2(int argc, char *argv[], void *userdata); diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index c69f03ec155cc..af456314db446 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -258,6 +258,7 @@ static int help(int argc, char *argv[], void *userdata) { " dlopen-metadata FILE Parse and print ELF dlopen metadata\n" "\n%3$sTPM Operations:%4$s\n" " has-tpm2 Report whether TPM2 support is available\n" + " identify-tpm2 Show TPM2 vendor information\n" " pcrs [PCR...] Show TPM2 PCRs and their names\n" " nvpcrs [NVPCR...] Show additional TPM2 PCRs stored in NV indexes\n" " srk [>FILE] Write TPM2 SRK (to FILE)\n" @@ -810,6 +811,7 @@ static int run(int argc, char *argv[]) { { "fdstore", 2, VERB_ANY, 0, verb_fdstore }, { "image-policy", 2, 2, 0, verb_image_policy }, { "has-tpm2", VERB_ANY, 1, 0, verb_has_tpm2 }, + { "identify-tpm2", VERB_ANY, 1, 0, verb_identify_tpm2 }, { "pcrs", VERB_ANY, VERB_ANY, 0, verb_pcrs }, { "nvpcrs", VERB_ANY, VERB_ANY, 0, verb_nvpcrs }, { "srk", VERB_ANY, 1, 0, verb_srk }, From bafc13317825bfec1fe263b93c7e0b0bf0c5a250 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 3 Mar 2026 12:28:45 +0100 Subject: [PATCH 0056/1296] tpm2-util,analyze: add helper for generating hwdb lookup key from TPM2 vendor data Our goal is to find TPM2 metadata in hwdb, hence let's compile a "modalias"-style string from the TPM2 metadata, we can use as hwdb lookup key. --- man/systemd-analyze.xml | 3 ++- src/analyze/analyze-has-tpm2.c | 11 ++++++++++ src/shared/tpm2-util.c | 37 ++++++++++++++++++++++++++++++++++ src/shared/tpm2-util.h | 1 + 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml index 6a022916664f6..f3dfd7479f87f 100644 --- a/man/systemd-analyze.xml +++ b/man/systemd-analyze.xml @@ -1047,7 +1047,8 @@ default ignore - - Specification Date: Mon 2023-01-09 Manufacturer: STM Vendor String: ST33KTPM2XSPI - Firmware Version: 9.258 + Firmware Version: 9.258 + Modalias String: fi2.0:lv0:rv1.59:sy2023:sd9:mfSTM:vsST33KTPM2XSPI:ty0:fw9.258.0: diff --git a/src/analyze/analyze-has-tpm2.c b/src/analyze/analyze-has-tpm2.c index a63c44ebc649e..2e7890cea148f 100644 --- a/src/analyze/analyze-has-tpm2.c +++ b/src/analyze/analyze-has-tpm2.c @@ -120,6 +120,17 @@ int verb_identify_tpm2(int argc, char **argv, void *userdata) { if (r < 0) return table_log_add_error(r); + _cleanup_free_ char *m = NULL; + if (tpm2_vendor_info_to_modalias(&info, &m) < 0) + return log_oom(); + + r = table_add_many( + table, + TABLE_FIELD, "Modalias String", + TABLE_STRING, m); + if (r < 0) + return table_log_add_error(r); + r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, /* show_header= */ false); if (r < 0) return r; diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 64332fda32de3..2705f3b79426a 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -349,6 +349,43 @@ static int tpm2_get_capability( return more == TPM2_YES; } +int tpm2_vendor_info_to_modalias(const Tpm2VendorInfo *info, char **ret) { + _cleanup_free_ char *m = NULL; + + assert(info); + assert(ret); + + /* Closely inspired by kernel modalias strings, this distills information from the TPM vendor data + * into a string suitable for matching hwdb */ + + if (asprintf(&m, + "fi%s:" + "lv%" PRIu32 ":" + "rv%" PRIu32 ".%" PRIu32 ":" + "sy%" PRIu32 ":" + "sd%" PRIu32 ":" + "mf%s:" + "vs%s:" + "ty%" PRIx32 ":" + "fw%" PRIu16 ".%" PRIu16 ".%" PRIu32 ":", + info->family_indicator, + info->level, + info->revision_major, + info->revision_minor, + info->year, + info->day_of_year, + info->manufacturer, + info->vendor_string, + info->vendor_tpm_type, + info->firmware_version_major, + info->firmware_version_minor, + info->firmware_version2) < 0) + return -ENOMEM; + + *ret = TAKE_PTR(m); + return 0; +} + static char *mangle_vendor_chars(char *c, size_t n) { char *end = c; assert(c || n == 0); diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index cbdad197191ae..9bccdd9712cd0 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -210,6 +210,7 @@ typedef struct Tpm2VendorInfo { char vendor_string[4*4+1]; } Tpm2VendorInfo; +int tpm2_vendor_info_to_modalias(const Tpm2VendorInfo *info, char **ret); int tpm2_get_vendor_info(Tpm2Context *c, Tpm2VendorInfo *ret); void tpm2_log_debug_tpml_pcr_selection(const TPML_PCR_SELECTION *l, const char *msg); From dc75e542705d354d7ad67babb6d67c11c4920861 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 3 Mar 2026 12:32:11 +0100 Subject: [PATCH 0057/1296] udev: add tpm2_id builtin We need to be able to look up tpm2 metadata from hwdb, hence add a way to synthesize a whdb lookup key from with udev rules. --- rules.d/60-tpm2-id.rules | 9 +++++ rules.d/meson.build | 1 + src/udev/meson.build | 4 +++ src/udev/udev-builtin-tpm2_id.c | 60 +++++++++++++++++++++++++++++++++ src/udev/udev-builtin.c | 3 ++ src/udev/udev-builtin.h | 3 ++ src/udev/udev-def.h | 3 ++ src/udev/udevd.c | 2 ++ 8 files changed, 85 insertions(+) create mode 100644 rules.d/60-tpm2-id.rules create mode 100644 src/udev/udev-builtin-tpm2_id.c diff --git a/rules.d/60-tpm2-id.rules b/rules.d/60-tpm2-id.rules new file mode 100644 index 0000000000000..40ed0902bbe00 --- /dev/null +++ b/rules.d/60-tpm2-id.rules @@ -0,0 +1,9 @@ +# do not edit this file, it will be overwritten on update + +ACTION=="remove", GOTO="tpm2_id_end" +SUBSYSTEM!="tpmrm", GOTO="tpm2_id_end" +KERNEL!="tpmrm[0-9]*", GOTO="tpm2_id_end" + +IMPORT{program}="tpm2_id identify" + +LABEL="tpm2_id_end" diff --git a/rules.d/meson.build b/rules.d/meson.build index 839190658b40a..aa469ef7a7e58 100644 --- a/rules.d/meson.build +++ b/rules.d/meson.build @@ -23,6 +23,7 @@ rules = [ '60-persistent-v4l.rules', '60-sensor.rules', '60-serial.rules', + '60-tpm2-id.rules', '65-integration.rules', '70-camera.rules', '70-joystick.rules', diff --git a/src/udev/meson.build b/src/udev/meson.build index 7df5b3de857b1..700a8cc8d5ed3 100644 --- a/src/udev/meson.build +++ b/src/udev/meson.build @@ -59,6 +59,10 @@ if conf.get('HAVE_ACL') == 1 udevadm_extract_sources += files('udev-builtin-uaccess.c') endif +if conf.get('HAVE_TPM2') == 1 + udevadm_extract_sources += files('udev-builtin-tpm2_id.c') +endif + ############################################################ generate_keyboard_keys_list = files('generate-keyboard-keys-list.sh') diff --git a/src/udev/udev-builtin-tpm2_id.c b/src/udev/udev-builtin-tpm2_id.c new file mode 100644 index 0000000000000..6edf618e11112 --- /dev/null +++ b/src/udev/udev-builtin-tpm2_id.c @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "device-util.h" +#include "string-util.h" +#include "tpm2-util.h" +#include "udev-builtin.h" + +static int builtin_tpm2_id(UdevEvent *event, int argc, char *argv[]) { + sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + int r; + + if (argc != 2 || !streq(argv[1], "identify")) + return log_device_error_errno( + dev, SYNTHETIC_ERRNO(EINVAL), "%s: expected: identify", argv[0]); + + const char *dn; + r = sd_device_get_devname(dev, &dn); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to get device node for device: %m"); + + _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; + r = tpm2_context_new(dn, &c); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to open device node '%s': %m", dn); + + Tpm2VendorInfo info; + r = tpm2_get_vendor_info(c, &info); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to acquire TPM2 vendor information: %m"); + + if (!isempty(info.manufacturer)) { + r = udev_builtin_add_property(event, "ID_TPM2_MANUFACTURER", info.manufacturer); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to set field: %m"); + } + + if (!isempty(info.vendor_string)) { + r = udev_builtin_add_property(event, "ID_TPM2_VENDOR_STRING", info.vendor_string); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to set field: %m"); + } + + _cleanup_free_ char *m = NULL; + r = tpm2_vendor_info_to_modalias(&info, &m); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to get modalias string for TPM2 device: %m"); + + r = udev_builtin_add_property(event, "ID_TPM2_MODALIAS", m); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to set field: %m"); + + return 0; +} + +const UdevBuiltin udev_builtin_tpm2_id = { + .name = "tpm2_id", + .cmd = builtin_tpm2_id, + .help = "Identify TPM2 chips", + .run_once = true, +}; diff --git a/src/udev/udev-builtin.c b/src/udev/udev-builtin.c index 5f559f5c9a10c..121694e688fc5 100644 --- a/src/udev/udev-builtin.c +++ b/src/udev/udev-builtin.c @@ -29,6 +29,9 @@ static const UdevBuiltin *const builtins[_UDEV_BUILTIN_MAX] = { [UDEV_BUILTIN_NET_ID] = &udev_builtin_net_id, [UDEV_BUILTIN_NET_LINK] = &udev_builtin_net_setup_link, [UDEV_BUILTIN_PATH_ID] = &udev_builtin_path_id, +#if HAVE_TPM2 + [UDEV_BUILTIN_TPM2_ID] = &udev_builtin_tpm2_id, +#endif #if HAVE_ACL [UDEV_BUILTIN_UACCESS] = &udev_builtin_uaccess, #endif diff --git a/src/udev/udev-builtin.h b/src/udev/udev-builtin.h index 3e830c77ea2b0..504c4108c8055 100644 --- a/src/udev/udev-builtin.h +++ b/src/udev/udev-builtin.h @@ -43,6 +43,9 @@ extern const UdevBuiltin udev_builtin_net_driver; extern const UdevBuiltin udev_builtin_net_id; extern const UdevBuiltin udev_builtin_net_setup_link; extern const UdevBuiltin udev_builtin_path_id; +#if HAVE_TPM2 +extern const UdevBuiltin udev_builtin_tpm2_id; +#endif #if HAVE_ACL extern const UdevBuiltin udev_builtin_uaccess; #endif diff --git a/src/udev/udev-def.h b/src/udev/udev-def.h index 836dd7045460a..d5e02e50b623f 100644 --- a/src/udev/udev-def.h +++ b/src/udev/udev-def.h @@ -51,6 +51,9 @@ typedef enum UdevBuiltinCommand { UDEV_BUILTIN_NET_ID, UDEV_BUILTIN_NET_LINK, UDEV_BUILTIN_PATH_ID, +#if HAVE_TPM2 + UDEV_BUILTIN_TPM2_ID, +#endif #if HAVE_ACL UDEV_BUILTIN_UACCESS, #endif diff --git a/src/udev/udevd.c b/src/udev/udevd.c index ab3106334bee7..840febbd42b3d 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -19,6 +19,7 @@ #include "process-util.h" #include "rlimit-util.h" #include "terminal-util.h" +#include "tpm2-util.h" #include "udev-config.h" #include "udev-manager.h" #include "udevd.h" @@ -61,6 +62,7 @@ int run_udevd(int argc, char *argv[]) { (void) dlopen_libblkid(); (void) dlopen_libkmod(); (void) dlopen_libmount(); + (void) dlopen_tpm2(); if (arg_daemonize) { pid_t pid; From f2eed3fa25e8c38b7a90d6ab3d22ee90e3569271 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 3 Mar 2026 14:06:44 +0100 Subject: [PATCH 0058/1296] hwdb: introduce hwdb for tpm2 devices This hwdb is can carry hw quirks and similar for us, in particular tell us if nvpcrs have a chance of working. --- hwdb.d/60-tpm2.hwdb | 14 ++++++++++++++ hwdb.d/meson.build | 1 + hwdb.d/parse_hwdb.py | 2 ++ rules.d/60-tpm2-id.rules | 1 + 4 files changed, 18 insertions(+) create mode 100644 hwdb.d/60-tpm2.hwdb diff --git a/hwdb.d/60-tpm2.hwdb b/hwdb.d/60-tpm2.hwdb new file mode 100644 index 0000000000000..2772bf3cde169 --- /dev/null +++ b/hwdb.d/60-tpm2.hwdb @@ -0,0 +1,14 @@ +# This file is part of systemd. +# +# Use "systemd-analyze identify-tpm2" to generate the modalias string for your +# hardware. Don't forget to prefix it with "tpm:" for inclusion in a match here. +# +# Currently, the only relevant property to set here is TPM2_BROKEN_NVPCR=1, +# which should be set on TPMs where NvPCRs don't work. Specifically, because +# on some hardware the combination of TPMA_NV_ORDERLY + TPM2_NT_EXTEND cause +# NV_Extend() operations to time out. For details, see: +# https://github.com/systemd/systemd/issues/40485 + +# ST33TPHF2ESPI Firmware 73.4 +tpm2:*:mfSTM:*:fw73.4.*: + TPM2_BROKEN_NVPCR=1 diff --git a/hwdb.d/meson.build b/hwdb.d/meson.build index 36a9937a60a3f..9ba73b21d6393 100644 --- a/hwdb.d/meson.build +++ b/hwdb.d/meson.build @@ -26,6 +26,7 @@ hwdb_files_test = files( '60-keyboard.hwdb', '60-seat.hwdb', '60-sensor.hwdb', + '60-tpm2.hwdb', '70-analyzers.hwdb', '70-av-production.hwdb', '70-cameras.hwdb', diff --git a/hwdb.d/parse_hwdb.py b/hwdb.d/parse_hwdb.py index 7efa61ad4b8c3..e98510839b73f 100755 --- a/hwdb.d/parse_hwdb.py +++ b/hwdb.d/parse_hwdb.py @@ -105,6 +105,7 @@ 'OUI', 'pci', 'sdio', + 'tpm2', 'usb', 'vmbus', } @@ -213,6 +214,7 @@ def property_grammar(): ('ID_SYSFS_ATTRIBUTE_MODEL', name_literal), ('ID_NET_NAME_FROM_DATABASE', name_literal), ('ID_NET_NAME_INCLUDE_DOMAIN', zero_one), + ('TPM2_BROKEN_NVPCR', zero_one), ) fixed_props = [Literal(name)('NAME') - Suppress('=') - val('VALUE') for name, val in props] diff --git a/rules.d/60-tpm2-id.rules b/rules.d/60-tpm2-id.rules index 40ed0902bbe00..1e08f3b8e5b9d 100644 --- a/rules.d/60-tpm2-id.rules +++ b/rules.d/60-tpm2-id.rules @@ -5,5 +5,6 @@ SUBSYSTEM!="tpmrm", GOTO="tpm2_id_end" KERNEL!="tpmrm[0-9]*", GOTO="tpm2_id_end" IMPORT{program}="tpm2_id identify" +ENV{ID_TPM2_MODALIAS}!="", IMPORT{builtin}="hwdb 'tpm2:$env{ID_TPM2_MODALIAS}'" LABEL="tpm2_id_end" From 9c40895f133309b14fd578c37b89b334c61e2c74 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 3 Mar 2026 15:53:09 +0100 Subject: [PATCH 0059/1296] tpm2-util: remove strjoina() usage on user-controlled data --- src/shared/tpm2-util.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 2705f3b79426a..058983c965e68 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -881,7 +881,8 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { } if (device) { - const char *param, *driver, *fn; + _cleanup_free_ char *_driver = NULL; + const char *param, *driver; const TSS2_TCTI_INFO* info; TSS2_TCTI_INFO_FUNC func; size_t sz = 0; @@ -889,7 +890,9 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { param = strchr(device, ':'); if (param) { /* Syntax #1: Pair of driver string and arbitrary parameter */ - driver = strndupa_safe(device, param - device); + driver = _driver = strndup(device, param - device); + if (!driver) + return log_oom_debug(); if (isempty(driver)) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 driver name is empty, refusing."); @@ -903,7 +906,9 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { log_debug("Using TPM2 TCTI driver '%s' with device '%s'.", driver, param); - fn = strjoina("libtss2-tcti-", driver, ".so.0"); + _cleanup_free_ char *fn = strjoin("libtss2-tcti-", driver, ".so.0"); + if (!fn) + return log_oom_debug(); /* Better safe than sorry, let's refuse strings that cannot possibly be valid driver early, before going to disk. */ if (!filename_is_valid(fn)) From a420348cc346e74d0326dda3a37f54ca669918d6 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 3 Mar 2026 15:59:40 +0100 Subject: [PATCH 0060/1296] tpm2-util: check udev db to determine if NvPCRs are going to work Fixes: #40485 --- catalog/systemd.catalog.in | 9 ++++++ src/shared/tpm2-util.c | 59 +++++++++++++++++++++++++++++++++++++ src/shared/tpm2-util.h | 2 ++ src/systemd/sd-messages.h | 3 ++ src/tpm2-setup/tpm2-setup.c | 6 ++++ 5 files changed, 79 insertions(+) diff --git a/catalog/systemd.catalog.in b/catalog/systemd.catalog.in index 6f6ca9c4ea851..68ef28d1974c0 100644 --- a/catalog/systemd.catalog.in +++ b/catalog/systemd.catalog.in @@ -1015,3 +1015,12 @@ non-volatile indexes (NV Indexes), could not be initialized. This typically means the persistent NV index memory available on the TPM is taken up by other resources, or is extremely limited. A TPM reset might help recovering space (but will invalidate all TPM bound keys and resources). + +-- 8f07a5b814ca4762b89fcc3082e48aed +Subject: TPM NV index backed PCRs not supported on the local TPM +Defined-By: systemd +Support: %SUPPORT_URL% + +The Trusted Platform Module's (TPM) NV index support is too limited to properly +implement NV index backed additional PCRs. NvPCRs will not be allocated or +initialized, and will not be available on the system. diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 058983c965e68..9e05c847edf02 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -3,6 +3,8 @@ #include #include +#include "sd-device.h" + #include "alloc-util.h" #include "ansi-color.h" #include "bitfield.h" @@ -11,6 +13,8 @@ #include "constants.h" #include "creds-util.h" #include "cryptsetup-util.h" +#include "device-private.h" +#include "device-util.h" #include "dirent-util.h" #include "dlfcn-util.h" #include "efi-api.h" @@ -836,6 +840,9 @@ static Tpm2Context *tpm2_context_free(Tpm2Context *c) { c->capability_commands = mfree(c->capability_commands); c->capability_ecc_curves = mfree(c->capability_ecc_curves); + c->tcti_driver = mfree(c->tcti_driver); + c->tcti_param = mfree(c->tcti_param); + return mfree(c); } @@ -948,6 +955,14 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { if (rc != TPM2_RC_SUCCESS) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to initialize TCTI context: %s", sym_Tss2_RC_Decode(rc)); + + context->tcti_driver = strdup(driver); + if (!context->tcti_driver) + return log_oom_debug(); + + context->tcti_param = strdup(param); + if (!context->tcti_param) + return log_oom_debug(); } rc = sym_Esys_Initialize(&context->esys_context, context->tcti_context, NULL); @@ -7479,6 +7494,44 @@ int tpm2_nvpcr_acquire_anchor_secret(struct iovec *ret, bool sync_secondary) { #endif } +#if HAVE_OPENSSL +static int tpm2_context_can_nvindex(Tpm2Context *c) { + int r; + + assert(c); + + if (!streq_ptr(c->tcti_driver, "device")) { + log_debug("Not checking udev database, because not using 'device' TCTI."); + return 1; + } + + if (!c->tcti_param) { + log_debug("No device specified, not checking udev database."); + return 1; + } + + _cleanup_(sd_device_unrefp) sd_device *d = NULL; + r = sd_device_new_from_devname(&d, c->tcti_param); + if (r < 0) + return log_debug_errno(r, "Failed to acquire udev entry for device '%s': %m", c->tcti_param); + + r = device_get_property_bool(d, "TPM2_BROKEN_NVPCR"); + if (r == -ENOENT) { + log_device_debug_errno(d, r, "No TPM2_BROKEN_NVPCR property for '%s', assuming NvPCRs work.", c->tcti_param); + return 1; + } + if (r < 0) + return log_device_debug_errno(d, r, "Failed to query TPM2_BROKEN_NVPCR for '%s': %m", c->tcti_param); + if (r > 0) { + log_device_debug(d, "TPM2_BROKEN_NVPCR property for '%s' explicitly set, NvPCRs do not work.", c->tcti_param); + return 0; + } + + log_device_debug(d, "TPM2_BROKEN_NVPCR property for '%s' explicitly set to false, hence NvPCRs work.", c->tcti_param); + return 1; +} +#endif + int tpm2_nvpcr_initialize( Tpm2Context *c, const Tpm2Handle *session, @@ -7492,6 +7545,12 @@ int tpm2_nvpcr_initialize( assert(c); assert(name); + r = tpm2_context_can_nvindex(c); + if (r < 0) + return r; + if (r == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 device does not support NvPCRs, not initializing."); + _cleanup_(nvpcr_data_done) NvPCRData p = {}; r = nvpcr_data_load(name, &p); if (r < 0) diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 9bccdd9712cd0..51670e8b061a8 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -53,6 +53,8 @@ typedef struct Tpm2Context { void *tcti_dl; TSS2_TCTI_CONTEXT *tcti_context; ESYS_CONTEXT *esys_context; + char *tcti_driver; + char *tcti_param; /* Some selected cached capabilities of the TPM */ TPMS_ALG_PROPERTY *capability_algorithms; diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h index 865f7247ac21f..6aabe88196b98 100644 --- a/src/systemd/sd-messages.h +++ b/src/systemd/sd-messages.h @@ -309,6 +309,9 @@ _SD_BEGIN_DECLARATIONS; #define SD_MESSAGE_TPM_NVINDEX_EXHAUSTED SD_ID128_MAKE(ab,98,4e,a0,08,96,4f,b8,8d,6e,38,9f,b5,13,fb,94) #define SD_MESSAGE_TPM_NVINDEX_EXHAUSTED_STR SD_ID128_MAKE_STR(ab,98,4e,a0,08,96,4f,b8,8d,6e,38,9f,b5,13,fb,94) +#define SD_MESSAGE_TPM_NVPCR_UNSUPPORTED SD_ID128_MAKE(8f,07,a5,b8,14,ca,47,62,b8,9f,cc,30,82,e4,8a,ed) +#define SD_MESSAGE_TPM_NVPCR_UNSUPPORTED_STR SD_ID128_MAKE_STR(8f,07,a5,b8,14,ca,47,62,b8,9f,cc,30,82,e4,8a,ed) + _SD_END_DECLARATIONS; #endif diff --git a/src/tpm2-setup/tpm2-setup.c b/src/tpm2-setup/tpm2-setup.c index a4d323a70cefc..cfb118f6acf15 100644 --- a/src/tpm2-setup/tpm2-setup.c +++ b/src/tpm2-setup/tpm2-setup.c @@ -422,6 +422,12 @@ static int setup_nvpcr_one( r = tpm2_nvpcr_initialize(c->tpm2_context, /* session= */ NULL, name, &c->anchor_secret); } + if (r == -EOPNOTSUPP) { + c->n_failed++; + return log_struct_errno(LOG_ERR, r, + LOG_MESSAGE("The TPM does not correctly support NV indexes in NT_EXTEND mode, unable to allocate NvPCR '%s': %m", name), + LOG_MESSAGE_ID(SD_MESSAGE_TPM_NVPCR_UNSUPPORTED_STR)); + } if (r == -ENOSPC) { c->n_failed++; return log_struct_errno(LOG_ERR, r, From 9c8a2e53d337108a728ea0e54525bfec5db5c05e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 3 Mar 2026 21:20:13 +0100 Subject: [PATCH 0061/1296] test: add superficial testcase for tpm2 identification --- test/units/TEST-70-TPM2.nvpcr.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/units/TEST-70-TPM2.nvpcr.sh b/test/units/TEST-70-TPM2.nvpcr.sh index c5a6d1c213642..29319e601aced 100755 --- a/test/units/TEST-70-TPM2.nvpcr.sh +++ b/test/units/TEST-70-TPM2.nvpcr.sh @@ -103,3 +103,6 @@ systemd-dissect --image-policy='root=signed:=absent+unused' --mtree /var/tmp/nvp set +o pipefail diff /tmp/nvpcr/log-before /run/log/systemd/tpm2-measure.log | grep -F '"content":{"nvIndexName":"verity","string":"verity:' + +systemd-analyze identify-tpm2 +udevadm test-builtin 'tpm2_id identify' /dev/tpmrm0 From 3dc670901ee584cb61972415df2aaf8c7eed0ac3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 3 Mar 2026 21:50:49 +0100 Subject: [PATCH 0062/1296] tpm2-setup: use symbolic exit code 76 is the bsd exit code EX_PROTOCOL, which is kinda fitting here. Let#s hence use the symbolic exit code here. --- src/tpm2-setup/tpm2-setup.c | 5 +++-- units/systemd-tpm2-setup-early.service.in | 4 ++-- units/systemd-tpm2-setup.service.in | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/tpm2-setup/tpm2-setup.c b/src/tpm2-setup/tpm2-setup.c index cfb118f6acf15..3eed095d2260c 100644 --- a/src/tpm2-setup/tpm2-setup.c +++ b/src/tpm2-setup/tpm2-setup.c @@ -2,6 +2,7 @@ #include #include +#include #include "sd-messages.h" @@ -291,8 +292,8 @@ static int setup_srk(void) { log_struct_errno(LOG_INFO, r, LOG_MESSAGE("Insufficient permissions to access TPM, not generating SRK."), LOG_MESSAGE_ID(SD_MESSAGE_SRK_ENROLLMENT_NEEDS_AUTHORIZATION_STR)); - return 76; /* Special return value which means "Insufficient permissions to access TPM, - * cannot generate SRK". This isn't really an error when called at boot. */; + return EX_PROTOCOL; /* Special return value which means "Insufficient permissions to access TPM, + * cannot generate SRK". This isn't really an error when called at boot. */; } if (r < 0) return r; diff --git a/units/systemd-tpm2-setup-early.service.in b/units/systemd-tpm2-setup-early.service.in index 7fdb99b53f36a..bd69c9de7a5e1 100644 --- a/units/systemd-tpm2-setup-early.service.in +++ b/units/systemd-tpm2-setup-early.service.in @@ -22,5 +22,5 @@ Type=oneshot RemainAfterExit=yes ExecStart={{LIBEXECDIR}}/systemd-tpm2-setup --early=yes --graceful -# The tool returns 76 if the TPM cannot be accessed due to an authorization failure and we can't generate an SRK. -SuccessExitStatus=76 +# The tool returns EX_PROTOCOL if the TPM cannot be accessed due to an authorization failure and we can't generate an SRK. +SuccessExitStatus=PROTOCOL diff --git a/units/systemd-tpm2-setup.service.in b/units/systemd-tpm2-setup.service.in index 34404a24cb5e1..a81b028214034 100644 --- a/units/systemd-tpm2-setup.service.in +++ b/units/systemd-tpm2-setup.service.in @@ -23,5 +23,5 @@ Type=oneshot RemainAfterExit=yes ExecStart={{LIBEXECDIR}}/systemd-tpm2-setup --graceful -# The tool returns 76 if the TPM cannot be accessed due to an authorization failure and we can't generate an SRK. -SuccessExitStatus=76 +# The tool returns EX_PROTOCOL if the TPM cannot be accessed due to an authorization failure and we can't generate an SRK. +SuccessExitStatus=PROTOCOL From 64fd94faa0c73c3f64e05674d106893dc209c256 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 3 Mar 2026 21:51:38 +0100 Subject: [PATCH 0063/1296] tpm2-setup: don't fail service on two more types of failures Let's bubble up failures all the way until they reach the services, but then let's carefully gracefully handle some of them, that are about issues not immediately actionable to the admin, even if they are potentially quite problematic. --- src/tpm2-setup/tpm2-setup.c | 24 ++++++++++++++++------- units/systemd-tpm2-setup-early.service.in | 4 ++++ units/systemd-tpm2-setup.service.in | 4 ++++ 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/tpm2-setup/tpm2-setup.c b/src/tpm2-setup/tpm2-setup.c index 3eed095d2260c..d243f199e99b2 100644 --- a/src/tpm2-setup/tpm2-setup.c +++ b/src/tpm2-setup/tpm2-setup.c @@ -453,7 +453,7 @@ static int setup_nvpcr_one( static int setup_nvpcr(void) { _cleanup_(setup_nvpcr_context_done) SetupNvPCRContext c = {}; - int r = 0; + int r; _cleanup_strv_free_ char **l = NULL; r = conf_files_list_nulstr( @@ -478,13 +478,11 @@ static int setup_nvpcr(void) { RET_GATHER(ret, setup_nvpcr_one(&c, *i)); } - if (c.n_already > 0 && c.n_anchored == 0 && !arg_early) { + if (c.n_already > 0 && c.n_anchored == 0 && !arg_early) /* If we didn't anchor anything right now, but we anchored something earlier, then it might * have happened in the initrd, and thus the anchor ID was not committed to /var/ or the ESP * yet. Hence, let's explicitly do so now, to catch up. */ - RET_GATHER(ret, tpm2_nvpcr_acquire_anchor_secret(/* ret= */ NULL, /* sync_secondary= */ true)); - } if (c.n_failed > 0) log_warning("%zu NvPCRs failed to initialize, proceeding anyway.", c.n_failed); @@ -499,6 +497,13 @@ static int setup_nvpcr(void) { else if (c.n_failed == 0) log_debug("No NvPCRs defined, nothing initialized."); + /* Turn some errors into recognizable ones, which we can catch with + * SuccessExitStatus= in the service unit file. */ + if (ret == -EOPNOTSUPP) + return EX_UNAVAILABLE; /* e.g. no NvPCR support in TPM */ + if (ret == -ENOSPC) + return EX_CANTCREAT; /* NV index space on TPM exhausted */ + return ret; } @@ -518,10 +523,15 @@ static int run(int argc, char *argv[]) { umask(0022); + /* Execute both jobs, and then return unlisted errors preferably, and listed errors + * (i.e. EX_UNAVAILABLE, EX_CANTCREAT, EX_PROTOCOL) otherwise. */ r = setup_srk(); - RET_GATHER(r, setup_nvpcr()); - - return r; + int k = setup_nvpcr(); + if (r < 0) + return r; + if (k < 0) + return k; + return r != EXIT_SUCCESS ? r : k; } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/units/systemd-tpm2-setup-early.service.in b/units/systemd-tpm2-setup-early.service.in index bd69c9de7a5e1..ce1ee94cc4906 100644 --- a/units/systemd-tpm2-setup-early.service.in +++ b/units/systemd-tpm2-setup-early.service.in @@ -24,3 +24,7 @@ ExecStart={{LIBEXECDIR}}/systemd-tpm2-setup --early=yes --graceful # The tool returns EX_PROTOCOL if the TPM cannot be accessed due to an authorization failure and we can't generate an SRK. SuccessExitStatus=PROTOCOL +# The tool returns EX_UNAVAILABLE if some key functionality (e.g. NvPCR) support is not available in the TPM device. +SuccessExitStatus=UNAVAILABLE +# The tool returns EX_CANTCREAT if the NV index space is exhausted. +SuccessExitStatus=CANTCREAT diff --git a/units/systemd-tpm2-setup.service.in b/units/systemd-tpm2-setup.service.in index a81b028214034..dff516832d3c6 100644 --- a/units/systemd-tpm2-setup.service.in +++ b/units/systemd-tpm2-setup.service.in @@ -25,3 +25,7 @@ ExecStart={{LIBEXECDIR}}/systemd-tpm2-setup --graceful # The tool returns EX_PROTOCOL if the TPM cannot be accessed due to an authorization failure and we can't generate an SRK. SuccessExitStatus=PROTOCOL +# The tool returns EX_UNAVAILABLE if some key functionality (e.g. NvPCR) support is not available in the TPM device. +SuccessExitStatus=UNAVAILABLE +# The tool returns EX_CANTCREAT if the NV index space is exhausted. +SuccessExitStatus=CANTCREAT From 378b2f0d4a48c8f15f49d2a3936d56dd4e296b99 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 3 Mar 2026 21:24:51 +0100 Subject: [PATCH 0064/1296] update NEWS --- NEWS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/NEWS b/NEWS index 82b30951d24e4..792aee47617b9 100644 --- a/NEWS +++ b/NEWS @@ -231,6 +231,11 @@ CHANGES WITH 260 in spe: ID_INTEGRATION= because it was never used and the new variable covers the idea that variable was intended for better. + * A new udev builtin "tpm2_id" is now available which will extract + vendor/model identification from connected TPM2 devices as they are + probed. This is then used to import data from the udev database, + possibly containing quirk and other information about specific TPMs. + Changes in systemd-networkd: * MultiPathRoute= option now supports interface-bound ECMP routes. @@ -469,6 +474,9 @@ CHANGES WITH 260 in spe: system components to synchronously hook into the OOM killing logic, by registering a Varlink socket in a special directory. + * systemd-analyze learnt a new verb "identify-tpm2" which shows + vendor/model information extracted from the system's TPM. + Changes in units: * runlevel[0-6].target units that were removed in v258 have been From 899992bc1e1fd2a7983caa174744281a4ee81e77 Mon Sep 17 00:00:00 2001 From: Oleksandr Andrushchenko Date: Mon, 2 Mar 2026 17:01:31 +0200 Subject: [PATCH 0065/1296] network: Rename ModemManager .network section WRT tech, not project... and use dedicated knobs for every option used in former SimpleConnectProperties. New section is [MobileNetwork] with the following configuration options: APN= AllowedAuthenticationMechanisms== User= Password= IPFamily= AllowRoaming= PIN= OperatorId= --- NEWS | 10 +- man/systemd.network.xml | 137 ++++++++++++--------- src/network/networkd-network-gperf.gperf | 13 +- src/network/networkd-network.c | 11 +- src/network/networkd-network.h | 10 +- src/network/networkd-wwan-bus.c | 148 +++++++---------------- src/network/networkd-wwan.c | 102 +++++++++++++++- src/network/networkd-wwan.h | 2 + 8 files changed, 259 insertions(+), 174 deletions(-) diff --git a/NEWS b/NEWS index 792aee47617b9..2862a224350f8 100644 --- a/NEWS +++ b/NEWS @@ -241,11 +241,11 @@ CHANGES WITH 260 in spe: * MultiPathRoute= option now supports interface-bound ECMP routes. * systemd-networkd gained integration with ModemManager via the "simple - connect" protocol. A new [ModemManager] section has been added with - SimpleConnectProperties= (currently apn=, allowed-auth=, user=, - password=, ip-type=, allow-roaming=, pin=, and operator-id=), - RouteMetric=, and UseGateway= settings. This allows systemd-networkd - to establish a cellular modem connection to a broadband network. + connect" protocol. A new [MobileNetwork] section has been added with + APN=, AllowedAuthenticationMechanisms=, User=, Password=, IPFamily=, + AllowRoaming=, PIN=, OperatorId=, RouteMetric=, and UseGateway= + settings. This allows systemd-networkd to establish a cellular modem + connection to a broadband network. * systemd-networkd gained a pair of varlink methods io.systemd.Network.Link.Up()/Down(). 'networkctl up/down' now diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 3b08a292e0df0..58dae4f948c7e 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -6445,12 +6445,12 @@ ServerAddress=192.168.0.1/24 - [ModemManager] Section Options + [MobileNetwork] Section Options This section configures the default setting of the ModemManager integration. See for more information about ModemManager. - Regardless of the [ModemManager] section settings consider using the following for LTE modems (take into account + Regardless of the [MobileNetwork] section settings consider using the following for LTE modems (take into account that LTE modems do not typically support LLDP because LLDP is a Layer 2 protocol for Ethernet networks and an LTE modem connects to a cellular network, not a local Ethernet LAN): [Network] @@ -6460,75 +6460,89 @@ IPv6AcceptRA=no - The following options are available in the [ModemManager] section: + The following options are available in the [MobileNetwork] section: - SimpleConnectProperties= + APN= - Specifies the white-space separated list of simple connect properties used to connect a modem. See - for more - information about simple connect. If no properties provided then the connection is not initiated. + An Access Point Name (APN) is the name of a gateway between a mobile network + (GSM, GPRS, 3G, 4G and 5G) and another computer network. Required in 3GPP. + Defaults to unset and no attempt to establish the connection is made. - - =NAME - An Access Point Name (APN) is the name of a gateway between a mobile network - (GSM, GPRS, 3G, 4G and 5G) and another computer network. Required in 3GPP. - - - - - - =METHOD - Authentication method to use. Takes one of "none", "pap", "chap", "mschap", "mschapv2" or "eap". - Optional in 3GPP. + + + - - + + AllowedAuthenticationMechanisms= + + Authentication method to use. Specifies the white-space separated list of + none, pap, chap, + mschap, mschapv2, or eap + methods. Optional in 3GPP. Defaults to unset and an automatically picked + authentication method will be used. - - =NAME - User name (if any) required by the network. Optional in 3GPP. + + + - - + + User= + + User name (if any) required by the network. Optional in 3GPP. + Defaults to unset. - - =PASSWORD - Password (if any) required by the network. Optional in 3GPP. + + + - - + + Password= + + Password (if any) required by the network. Optional in 3GPP. + Defaults to unset. - - =TYPE - Addressing type. Takes one of "none", "ipv4", "ipv6", "ipv4v6" or "any". - Optional in 3GPP and CDMA. + + + - - + + IPFamily= + + Addressing type. Takes one of ipv4, ipv6, + both, or any. Optional in 3GPP and CDMA. + Defaults to unset and automatically selected. - - =BOOL - A boolean. When true, connection is allowed during roaming. When false, - connection is not allowed during roaming. Optional in 3GPP. + + + - - + + AllowRoaming= + + A boolean. When true, connection is allowed during roaming. When false, + connection is not allowed during roaming. + Optional in 3GPP. Defaults to yes. - - =PIN - SIM-PIN unlock code. + + + - - + + PIN= + + SIM-PIN unlock code. Defaults to unset. - - =ID - ETSI MCC-MNC of a network to force registration. + + + - - + + OperatorId= + + ETSI MCC-MNC of a network to force registration. Defaults to unset. + @@ -6896,14 +6910,23 @@ LLDP=no LinkLocalAddressing=no IPv6AcceptRA=no -[ModemManager] -SimpleConnectProperties=apn=internet pin=1111 +[MobileNetwork] +APN=internet +AllowedAuthenticationMechanisms=none pap chap +User=user +Password=pass +IPFamily=both +AllowRoaming=no +PIN=1111 +OperatorId=25503 RouteMetric=30 UseGateway=yes This connects a cellular modem to a broadband network matched with the network interface wwan0, - with APN name internet, SIM card pin unlock code 1111 and sets up a default - gateway with route metric of 30. + with APN name internet, allowed authentication none, pcap, or + chap, user name user, their password pass, allows both IPv4 and IPv6, + does not allow roaming, SIM card pin unlock code 1111, only allows connecting to operator with + MCC 25503, and sets up a default gateway with route metric of 30. diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 10566b7a4ed85..f1049cc7cc260 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -493,9 +493,16 @@ CAN.ClassicDataLengthCode, config_parse_can_control_mode, CAN.Termination, config_parse_can_termination, 0, 0 IPoIB.Mode, config_parse_ipoib_mode, 0, offsetof(Network, ipoib_mode) IPoIB.IgnoreUserspaceMulticastGroups, config_parse_tristate, 0, offsetof(Network, ipoib_umcast) -ModemManager.SimpleConnectProperties, config_parse_strv, 0, offsetof(Network, mm_simple_connect_props) -ModemManager.RouteMetric, config_parse_mm_route_metric, 0, 0 -ModemManager.UseGateway, config_parse_tristate, 0, offsetof(Network, mm_use_gateway) +MobileNetwork.APN, config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, offsetof(Network, mm_apn) +MobileNetwork.AllowRoaming, config_parse_bool, 0, offsetof(Network, mm_allow_roaming) +MobileNetwork.AllowedAuthenticationMechanisms, config_parse_mm_allowed_auth, 0, offsetof(Network, mm_allowed_auth) +MobileNetwork.IPFamily, config_parse_mm_ip_family, 0, offsetof(Network, mm_ip_family) +MobileNetwork.OperatorId, config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, offsetof(Network, mm_operator_id) +MobileNetwork.User, config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, offsetof(Network, mm_user) +MobileNetwork.Password, config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, offsetof(Network, mm_password) +MobileNetwork.PIN, config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, offsetof(Network, mm_pin) +MobileNetwork.RouteMetric, config_parse_mm_route_metric, 0, 0 +MobileNetwork.UseGateway, config_parse_tristate, 0, offsetof(Network, mm_use_gateway) QDisc.Parent, config_parse_qdisc_parent, _QDISC_KIND_INVALID, 0 QDisc.Handle, config_parse_qdisc_handle, _QDISC_KIND_INVALID, 0 BFIFO.Parent, config_parse_qdisc_parent, QDISC_KIND_BFIFO, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 8141a45432e45..1e159fa31027d 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -513,6 +513,9 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .ipoib_mode = _IP_OVER_INFINIBAND_MODE_INVALID, .ipoib_umcast = -1, + .mm_allow_roaming = true, + .mm_allowed_auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN, + .mm_ip_family = MM_BEARER_IP_FAMILY_NONE, .mm_use_gateway = -1, }; @@ -553,7 +556,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi "LLDP\0" "TrafficControlQueueingDiscipline\0" "CAN\0" - "ModemManager\0" + "MobileNetwork\0" "QDisc\0" "BFIFO\0" "CAKE\0" @@ -851,7 +854,11 @@ static Network *network_free(Network *network) { hashmap_free(network->tclasses_by_section); /* ModemManager */ - strv_free(network->mm_simple_connect_props); + free(network->mm_apn); + free(network->mm_operator_id); + free(network->mm_user); + free(network->mm_password); + free(network->mm_pin); return mfree(network); } diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index dcd9f68e78197..923828b2ea1e9 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -21,6 +21,7 @@ #include "networkd-ndisc.h" #include "networkd-radv.h" #include "networkd-sysctl.h" +#include "networkd-wwan-bus.h" #include "resolve-util.h" typedef enum KeepConfiguration { @@ -416,7 +417,14 @@ typedef struct Network { char **ntp; /* ModemManager support */ - char **mm_simple_connect_props; + char *mm_apn; + bool mm_allow_roaming; + MMBearerAllowedAuth mm_allowed_auth; + MMBearerIpFamily mm_ip_family; + char *mm_operator_id; + char *mm_user; + char *mm_password; + char *mm_pin; int mm_use_gateway; uint32_t mm_route_metric; bool mm_route_metric_set; diff --git a/src/network/networkd-wwan-bus.c b/src/network/networkd-wwan-bus.c index d87cdd3441185..5d9818987522e 100644 --- a/src/network/networkd-wwan-bus.c +++ b/src/network/networkd-wwan-bus.c @@ -59,7 +59,6 @@ #include "networkd-manager.h" #include "networkd-wwan.h" #include "networkd-wwan-bus.h" -#include "parse-util.h" #include "string-util.h" #include "strv.h" @@ -507,78 +506,6 @@ static int modem_connect_handler(sd_bus_message *message, void *userdata, sd_bus return 0; } -static MMBearerIpFamily prop_iptype_lookup(const char *key) { - static const struct { - MMBearerIpFamily family; - const char *str; - } table[] = { - { MM_BEARER_IP_FAMILY_NONE, "none" }, - { MM_BEARER_IP_FAMILY_IPV4, "ipv4" }, - { MM_BEARER_IP_FAMILY_IPV6, "ipv6" }, - { MM_BEARER_IP_FAMILY_IPV4V6, "ipv4v6" }, - { MM_BEARER_IP_FAMILY_ANY, "any" }, - {} - }; - - assert(key); - - FOREACH_ELEMENT(item, table) - if (streq(item->str, key)) - return item->family; - - log_warning("ModemManager: ignoring unknown ip-type: %s, using any", key); - return MM_BEARER_IP_FAMILY_ANY; -} - -static MMBearerAllowedAuth prop_auth_lookup(const char *key) { - static const struct { - MMBearerAllowedAuth auth; - const char *str; - } table[] = { - { MM_BEARER_ALLOWED_AUTH_NONE, "none" }, - { MM_BEARER_ALLOWED_AUTH_PAP, "pap" }, - { MM_BEARER_ALLOWED_AUTH_CHAP, "chap" }, - { MM_BEARER_ALLOWED_AUTH_MSCHAP, "mschap" }, - { MM_BEARER_ALLOWED_AUTH_MSCHAPV2, "mschapv2" }, - { MM_BEARER_ALLOWED_AUTH_EAP, "eap" }, - {} - }; - - assert(key); - - FOREACH_ELEMENT(item, table) - if (streq(item->str, key)) - return item->auth; - - log_warning("ModemManager: ignoring unknown allowed-auth: %s, using none", key); - return MM_BEARER_ALLOWED_AUTH_NONE; -} - -static const char* prop_type_lookup(const char *key) { - static const struct { - const char *prop; - const char *type; - } table[] = { - { "apn", "s" }, - { "allowed-auth", "u" }, - { "user", "s" }, - { "password", "s" }, - { "ip-type", "u" }, - { "allow-roaming", "b" }, - { "pin", "s" }, - { "operator-id", "s" }, - {} - }; - - if (!key) - return NULL; - - FOREACH_ELEMENT(item, table) - if (streq(item->prop, key)) - return item->type; - return NULL; -} - static int bus_call_method_async_props( sd_bus *bus, sd_bus_slot **slot, @@ -591,6 +518,7 @@ static int bus_call_method_async_props( Link *link) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + Network *network = ASSERT_PTR(ASSERT_PTR(link)->network); int r; assert(bus); @@ -603,38 +531,48 @@ static int bus_call_method_async_props( if (r < 0) return bus_log_create_error(r); - STRV_FOREACH(prop, link->network->mm_simple_connect_props) { - const char *type; - _cleanup_free_ char *left = NULL, *right = NULL; + if (network->mm_apn) { + r = sd_bus_message_append(m, "{sv}", "apn", "s", network->mm_apn); + if (r < 0) + return bus_log_create_error(r); + } - r = split_pair(*prop, "=", &left, &right); + r = sd_bus_message_append(m, "{sv}", "allow-roaming", "b", network->mm_allow_roaming); + if (r < 0) + return bus_log_create_error(r); + + if (network->mm_allowed_auth != MM_BEARER_ALLOWED_AUTH_UNKNOWN) { + r = sd_bus_message_append(m, "{sv}", "allowed-auth", "u", (uint32_t) network->mm_allowed_auth); if (r < 0) - return log_warning_errno(SYNTHETIC_ERRNO(r), - "ModemManager: failed to parse simple connect option: %s, file: %s", - *prop, link->network->filename); - - type = prop_type_lookup(left); - if (!type) - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "ModemManager: unknown simple connect option: %s, file: %s", - *prop, link->network->filename); - - if (streq(left, "ip-type")) { - MMBearerIpFamily ip_type = prop_iptype_lookup(right); - - r = sd_bus_message_append(m, "{sv}", left, type, (uint32_t)ip_type); - } if (streq(left, "allowed-auth")) { - MMBearerAllowedAuth auth = prop_auth_lookup(right); - - r = sd_bus_message_append(m, "{sv}", left, type, (uint32_t)auth); - } else if (streq(type, "b")) { - r = parse_boolean(right); - if (r < 0) - return -EINVAL; - r = sd_bus_message_append(m, "{sv}", left, type, r); - } else if (streq(type, "s")) - r = sd_bus_message_append(m, "{sv}", left, type, right); + return bus_log_create_error(r); + } + + if (network->mm_ip_family != MM_BEARER_IP_FAMILY_NONE) { + r = sd_bus_message_append(m, "{sv}", "ip-type", "u", (uint32_t) network->mm_ip_family); + if (r < 0) + return bus_log_create_error(r); + } + + if (network->mm_operator_id) { + r = sd_bus_message_append(m, "{sv}", "operator-id", "s", network->mm_operator_id); + if (r < 0) + return bus_log_create_error(r); + } + + if (network->mm_user) { + r = sd_bus_message_append(m, "{sv}", "user", "s", network->mm_user); + if (r < 0) + return bus_log_create_error(r); + } + + if (network->mm_password) { + r = sd_bus_message_append(m, "{sv}", "password", "s", network->mm_password); + if (r < 0) + return bus_log_create_error(r); + } + if (network->mm_pin) { + r = sd_bus_message_append(m, "{sv}", "pin", "s", network->mm_pin); if (r < 0) return bus_log_create_error(r); } @@ -675,9 +613,9 @@ static void modem_simple_connect(Modem *modem) { return (void) log_debug("ModemManager: no .network file provided for %s", modem->port_name); - /* Check if we are provided with simple connection properties */ - if (!link->network->mm_simple_connect_props) - return (void) log_debug("ModemManager: no simple connect properties provided for %s", + /* Check if we are provided with at least APN which is required. */ + if (!link->network->mm_apn) + return (void) log_debug("ModemManager: not enough simple connect properties provided for %s", modem->port_name); log_info("ModemManager: starting simple connect on %s %s interface %s", diff --git a/src/network/networkd-wwan.c b/src/network/networkd-wwan.c index 2b10eb3e4ee96..ccd72d0ee31b3 100644 --- a/src/network/networkd-wwan.c +++ b/src/network/networkd-wwan.c @@ -2,6 +2,7 @@ #include "alloc-util.h" #include "bus-util.h" +#include "extract-word.h" #include "hashmap.h" #include "networkd-address.h" #include "networkd-dhcp4.h" @@ -620,7 +621,7 @@ int config_parse_mm_route_metric( void *data, void *userdata) { - Network *network = userdata; + Network *network = ASSERT_PTR(userdata); int r; assert(filename); @@ -639,3 +640,102 @@ int config_parse_mm_route_metric( network->mm_route_metric_set = true; return 0; } + +int config_parse_mm_allowed_auth( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + static const struct { + MMBearerAllowedAuth auth; + const char *str; + } allowed_auth_map[] = { + { MM_BEARER_ALLOWED_AUTH_NONE, "none" }, + { MM_BEARER_ALLOWED_AUTH_PAP, "pap" }, + { MM_BEARER_ALLOWED_AUTH_CHAP, "chap" }, + { MM_BEARER_ALLOWED_AUTH_MSCHAP, "mschap" }, + { MM_BEARER_ALLOWED_AUTH_MSCHAPV2, "mschapv2" }, + { MM_BEARER_ALLOWED_AUTH_EAP, "eap" }, + }; + MMBearerAllowedAuth *allowed_auth = ASSERT_PTR(data); + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *allowed_auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN; + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *auth = NULL; + + r = extract_first_word(&p, &auth, /* separators */ NULL, /* flags */ 0); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + if (r == 0) + return 0; + + bool found = false; + FOREACH_ELEMENT(i, allowed_auth_map) + if (streq(auth, i->str)) { + *allowed_auth |= i->auth; + found = true; + break; + } + + if (!found) + log_syntax(unit, LOG_WARNING, filename, line, -EINVAL, + "Unknown auth value '%s', ignoring", auth); + } +} + +int config_parse_mm_ip_family( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + static const struct { + MMBearerIpFamily family; + const char *str; + } ip_family_map[] = { + { MM_BEARER_IP_FAMILY_IPV4, "ipv4" }, + { MM_BEARER_IP_FAMILY_IPV6, "ipv6" }, + { MM_BEARER_IP_FAMILY_IPV4V6, "both" }, + { MM_BEARER_IP_FAMILY_ANY, "any" }, + }; + MMBearerIpFamily *ip_family = ASSERT_PTR(data); + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *ip_family = MM_BEARER_IP_FAMILY_NONE; + return 0; + } + + FOREACH_ELEMENT(i, ip_family_map) + if (streq(rvalue, i->str)) { + *ip_family = i->family; + return 0; + } + + return log_syntax_parse_error(unit, filename, line, -EINVAL, lvalue, rvalue); +} diff --git a/src/network/networkd-wwan.h b/src/network/networkd-wwan.h index 962f76be2ec40..0542ac174d29d 100644 --- a/src/network/networkd-wwan.h +++ b/src/network/networkd-wwan.h @@ -77,4 +77,6 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Modem*, modem_free); int modem_get_by_path(Manager *m, const char *path, Modem **ret); int link_get_modem(Link *link, Modem **ret); +CONFIG_PARSER_PROTOTYPE(config_parse_mm_allowed_auth); +CONFIG_PARSER_PROTOTYPE(config_parse_mm_ip_family); CONFIG_PARSER_PROTOTYPE(config_parse_mm_route_metric); From acf00ba7b5e38119983f100a89a48655a3eee0b0 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Wed, 4 Mar 2026 12:32:15 +0100 Subject: [PATCH 0066/1296] test: don't register short-living containers with machined, again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Otherwise we might try to register the same scope again before the previous instance gets a chance to be cleaned up: [ 54.378392] systemd-nspawn[2554]: ░ Spawning container TEST-13-NSPAWN.defaultinaccessiblepaths.nxs on /var/lib/machines/TEST-13-NSPAWN.default_inaccessible_paths.nxs. [ 54.382202] systemd-nspawn[2554]: Failed to allocate scope: Unit TEST-13-NSPAWN.defaultinaccessiblepaths.nxs.scope was already loaded or has a fragment file. [ 54.411211] systemd[1]: TEST-13-NSPAWN.service: Main process exited, code=exited, status=1/FAILURE [ 54.411413] systemd[1]: TEST-13-NSPAWN.service: Failed with result 'exit-code'. [ 54.411885] systemd[1]: Failed to start TEST-13-NSPAWN.service - TEST-13-NSPAWN. This is basically the same change as in 6a05abb9b49900774bc0323316103dceab0c1a7d but for the newly added tests. Follow-up for 83b8daa032cd0adb538cfd9467e6acf2c44aa661. Resolves: #40945 --- test/units/TEST-13-NSPAWN.nspawn.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/units/TEST-13-NSPAWN.nspawn.sh b/test/units/TEST-13-NSPAWN.nspawn.sh index c753734c33137..d5cc05b89f582 100755 --- a/test/units/TEST-13-NSPAWN.nspawn.sh +++ b/test/units/TEST-13-NSPAWN.nspawn.sh @@ -414,7 +414,7 @@ testcase_check_default_inaccessible_paths() { # Each inaccessible path should have zeroed permissions, which stat's %a reports as a single 0 for path in "${inaccessible_paths[@]}"; do - systemd-nspawn --directory="$root" \ + systemd-nspawn --register=no --directory="$root" \ bash -xec "ls -l $path; [[ \$(stat --format=%a $path) -eq 0 ]]" done @@ -422,14 +422,14 @@ testcase_check_default_inaccessible_paths() { # as writable, and it also skips the path masking (by dropping the MOUNT_APPLY_APIVFS_RO flag) for path in "${inaccessible_paths[@]}"; do exp="$(stat --format=%a "$path")" - SYSTEMD_NSPAWN_API_VFS_WRITABLE=yes systemd-nspawn --directory="$root" \ + SYSTEMD_NSPAWN_API_VFS_WRITABLE=yes systemd-nspawn --register=no --directory="$root" \ bash -xec "ls -l $path; [[ \$(stat --format=%a $path) -eq $exp ]]" done # SYSTEMD_NSPAWN_API_VFS_WRITABLE=network mounts only /proc/sys/net/ as writable but doesn't # drop the MOUNT_APPLY_APIVFS_RO flag, so the masking should still apply for path in "${inaccessible_paths[@]}"; do - SYSTEMD_NSPAWN_API_VFS_WRITABLE=network systemd-nspawn --directory="$root" \ + SYSTEMD_NSPAWN_API_VFS_WRITABLE=network systemd-nspawn --register=no --directory="$root" \ bash -xec "ls -l $path; [[ \$(stat --format=%a $path) -eq 0 ]]" done From e5a5656b55725b3674419b67a3f0287f37781860 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Feb 2026 00:46:21 +0000 Subject: [PATCH 0067/1296] machined: do not allow unprivileged users to shell into the root namespace via varlink Forbid non-root from shelling into a machine that is running in the root user namespace. Follow-up for adaff8eb35d9c471af81fddaa4403bc5843a256f --- src/machine/machine-varlink.c | 18 ++++++++++++++++++ test/units/TEST-13-NSPAWN.unpriv.sh | 19 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c index a499dbc3be2d3..e8a8933251e38 100644 --- a/src/machine/machine-varlink.c +++ b/src/machine/machine-varlink.c @@ -552,6 +552,24 @@ int vl_method_open(sd_varlink *link, sd_json_variant *parameters, sd_varlink_met return r; if (manager->runtime_scope != RUNTIME_SCOPE_USER) { + /* Ensure only root can shell into the root namespace, unless it's specifically the host machine, + * which is owned by uid 0 anyway and cannot be self-registered. This is to avoid unprivileged + * users registering a process they own in the root user namespace, and then shelling in as root + * or another user. Note that the shell operation is privileged and requires 'auth_admin', so we + * do not need to check the caller's uid, as that will be checked by polkit, and if they machine's + * and the caller's do not match, authorization will be required. It's only the case where the + * caller owns the machine that will be shortcut and needs to be checked here. */ + if (machine->uid != 0 && machine->class != MACHINE_HOST) { + r = pidref_in_same_namespace(&PIDREF_MAKE_FROM_PID(1), &machine->leader, NAMESPACE_USER); + if (r < 0) + return log_debug_errno( + r, + "Failed to check if machine '%s' is running in the root user namespace: %m", + machine->name); + if (r > 0) + return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, NULL); + } + _cleanup_strv_free_ char **polkit_details = NULL; polkit_details = machine_open_polkit_details(p.mode, machine->name, user, path, command_line); diff --git a/test/units/TEST-13-NSPAWN.unpriv.sh b/test/units/TEST-13-NSPAWN.unpriv.sh index cbf332aa22093..25867de707115 100755 --- a/test/units/TEST-13-NSPAWN.unpriv.sh +++ b/test/units/TEST-13-NSPAWN.unpriv.sh @@ -31,6 +31,7 @@ at_exit() { rm -f /usr/share/polkit-1/rules.d/registermachinetest.rules rm -rf /var/tmp/mangletest rm -f /var/tmp/mangletest.tar.gz + rm -f /shouldnotwork } trap at_exit EXIT @@ -104,6 +105,24 @@ run0 -u testuser \ (! run0 -u testuser machinectl shell 0@shouldnotwork2 /usr/bin/id -u) (! run0 -u testuser machinectl shell testuser@shouldnotwork2 /usr/bin/id -u) +run0 -u testuser \ + systemd-run --unit sleep.service --user sleep infinity +sleep_pid="$(run0 -u testuser systemctl show --user -P MainPID sleep.service)" +run0 -u testuser \ + varlinkctl \ + call \ + /run/systemd/machine/io.systemd.Machine \ + io.systemd.Machine.Register \ + "{\"name\":\"shouldnotwork3\", \"class\":\"container\", \"leader\": $sleep_pid}" +(! run0 -u testuser \ + varlinkctl \ + call \ + /run/systemd/machine/io.systemd.Machine \ + io.systemd.Machine.Open \ + '{"name":"shouldnotwork3", "mode": "shell", "user":"root","path":"/usr/bin/bash","args":["bash","-c","''touch /shouldnotwork; sleep 20''"]}') +systemctl --user --machine testuser@ stop sleep.service +test ! -f /shouldnotwork + run0 -u testuser mkdir /var/tmp/image-tar run0 -u testuser importctl --user export-tar zurps /var/tmp/image-tar/kurps.tar.gz -m run0 -u testuser importctl --user import-tar /var/tmp/image-tar/kurps.tar.gz -m From b55a8ed469c0a3acc433ccd9d8b77e4251c67f6c Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 4 Mar 2026 12:23:59 +0000 Subject: [PATCH 0068/1296] NEWS: finalize place and date --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 2862a224350f8..c8c5931ae7920 100644 --- a/NEWS +++ b/NEWS @@ -522,7 +522,7 @@ CHANGES WITH 260 in spe: lumingzh, naly zzwd, nikstur, novenary, noxiouz, r-vdp, safforddr, scarlet-storm, sd416, seidlerv, smosia, tuhaowen, zefr0x - — Edinburgh, 2026/02/25 + — Edinburgh, 2026/03/04 CHANGES WITH 259: From ba6c437ef9cae827ff1ea090fba9c5adeb7607b5 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 4 Mar 2026 13:19:11 +0000 Subject: [PATCH 0069/1296] NEWS: update contributors list --- NEWS | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/NEWS b/NEWS index c8c5931ae7920..83825dd094114 100644 --- a/NEWS +++ b/NEWS @@ -512,15 +512,16 @@ CHANGES WITH 260 in spe: Nick Rosbrook, Nicolas Dorier, Oblivionsage, Oleksandr Andrushchenko, Oğuz Ersen, Pablo Fraile Alonso, Peter Oliver, Philip Withnall, Pontus Lundkvist, Popax21, - Ronan Pigott, Ryan Zeigler, Skye Soss, Sriman Achanta, - Tabis Kabis, Temuri Doghonadze, Thomas Weißschuh, Thorsten Kukuk, - Tobias Heider, Tobias Stoeckmann, Ulrich Ölmann, Usama Arif, - Val Markovic, Vitaly Kuznetsov, Vunny Sodhi, Weixie Cui, - Yaping Li, Yaron Shahrabani, Yu Watanabe, Yuri Chornoivan, - ZauberNerd, Zbigniew Jędrzejewski-Szmek, Zongyuan He, andre4ik3, - calm329, cdown, cyclopentane, francescoza6, gvenugo3, joo es, kiamvdd, - lumingzh, naly zzwd, nikstur, novenary, noxiouz, r-vdp, safforddr, - scarlet-storm, sd416, seidlerv, smosia, tuhaowen, zefr0x + Rodrigo Campos, Ronan Pigott, Ryan Zeigler, Skye Soss, + Sriman Achanta, Tabis Kabis, Temuri Doghonadze, Thomas Weißschuh, + Thorsten Kukuk, Tobias Heider, Tobias Stoeckmann, Ulrich Ölmann, + Usama Arif, Val Markovic, Vitaly Kuznetsov, Vunny Sodhi, + Weixie Cui, Yaping Li, Yaron Shahrabani, Yu Watanabe, + Yuri Chornoivan, ZauberNerd, Zbigniew Jędrzejewski-Szmek, + Zongyuan He, andre4ik3, calm329, cdown, cyclopentane, francescoza6, + gvenugo3, joo es, kiamvdd, lumingzh, naly zzwd, nikstur, novenary, + noxiouz, r-vdp, safforddr, scarlet-storm, sd416, seidlerv, smosia, + tuhaowen, zefr0x — Edinburgh, 2026/03/04 From 2c635c7baa58b5501561da35f4218a13243f8b31 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 4 Mar 2026 12:24:14 +0000 Subject: [PATCH 0070/1296] meson: bump version to v260~rc2 --- meson.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.version b/meson.version index b17ac6a71486d..4366da0c80afe 100644 --- a/meson.version +++ b/meson.version @@ -1 +1 @@ -260~rc1 +260~rc2 From 11f06ab7687ef45f35e361cc80714de3254e6962 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 4 Mar 2026 12:51:48 +0100 Subject: [PATCH 0071/1296] shared/acpi-fpdt: set _packed_ attr properly We were declaring a variable named _packed... --- src/shared/acpi-fpdt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/acpi-fpdt.c b/src/shared/acpi-fpdt.c index d1551acbc91ac..c94c0ed4ab820 100644 --- a/src/shared/acpi-fpdt.c +++ b/src/shared/acpi-fpdt.c @@ -56,7 +56,7 @@ struct acpi_fpdt_boot { uint64_t startup_start; uint64_t exit_services_entry; uint64_t exit_services_exit; -} _packed; +} _packed_; /* /dev/mem is deprecated on many systems, try using /sys/firmware/acpi/fpdt parsing instead. * This code requires kernel version 5.12 on x86 based machines or 6.2 for arm64 */ From 18d1f2dceda6b67e6f4b276faf8a7739473a88b8 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 4 Mar 2026 12:53:12 +0100 Subject: [PATCH 0072/1296] tree-wide: make private hash ops static --- src/network/networkd-address.c | 2 +- src/network/networkd-route.c | 2 +- src/report/report.c | 9 +++------ src/resolve/resolved-dns-stub.c | 2 +- src/resolve/resolved-hook.c | 2 +- src/shared/bus-polkit.c | 9 +++------ src/shared/logs-show.c | 6 +++--- src/sysupdate/sysupdated.c | 10 ++++++---- 8 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 5bb4cddbdac82..3d2bf95d9930e 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -162,7 +162,7 @@ DEFINE_HASH_OPS( address_hash_func, address_compare_func); -DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( address_section_hash_ops, ConfigSection, config_section_hash_func, diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index ec2a7a334a65c..2d84ba1db071e 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -234,7 +234,7 @@ DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( route_compare_func, route_unref); -DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( route_section_hash_ops, ConfigSection, config_section_hash_func, diff --git a/src/report/report.c b/src/report/report.c index 3c2f89e002909..349d1fb15bd43 100644 --- a/src/report/report.c +++ b/src/report/report.c @@ -78,13 +78,10 @@ static void context_done(Context *context) { } DEFINE_TRIVIAL_CLEANUP_FUNC(LinkInfo*, link_info_free); -DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( link_info_hash_ops, - void, - trivial_hash_func, - trivial_compare_func, - LinkInfo, - link_info_free); + void, trivial_hash_func, trivial_compare_func, + LinkInfo, link_info_free); static int metric_compare(sd_json_variant *const *a, sd_json_variant *const *b) { const char *name_a, *name_b, *object_a, *object_b; diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c index 05e5bf424d312..96f7c62670e05 100644 --- a/src/resolve/resolved-dns-stub.c +++ b/src/resolve/resolved-dns-stub.c @@ -146,7 +146,7 @@ static int stub_packet_compare_func(const DnsPacket *x, const DnsPacket *y) { return memcmp(DNS_PACKET_HEADER(x), DNS_PACKET_HEADER(y), sizeof(DnsPacketHeader)); } -DEFINE_HASH_OPS(stub_packet_hash_ops, DnsPacket, stub_packet_hash_func, stub_packet_compare_func); +DEFINE_PRIVATE_HASH_OPS(stub_packet_hash_ops, DnsPacket, stub_packet_hash_func, stub_packet_compare_func); static int reply_add_with_rrsig( DnsAnswer **reply, diff --git a/src/resolve/resolved-hook.c b/src/resolve/resolved-hook.c index 6940b7ef1450b..5cec36c3aa2d6 100644 --- a/src/resolve/resolved-hook.c +++ b/src/resolve/resolved-hook.c @@ -328,7 +328,7 @@ static void manager_gc_hooks(Manager *m, usec_t seen_usec) { } } -DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( hook_hash_ops, char, string_hash_func, string_compare_func, Hook, hook_unlink); diff --git a/src/shared/bus-polkit.c b/src/shared/bus-polkit.c index f19db2a6eb008..78e9ed377f384 100644 --- a/src/shared/bus-polkit.c +++ b/src/shared/bus-polkit.c @@ -250,13 +250,10 @@ static AsyncPolkitQuery* async_polkit_query_free(AsyncPolkitQuery *q) { DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(AsyncPolkitQuery, async_polkit_query, async_polkit_query_free); DEFINE_TRIVIAL_CLEANUP_FUNC(AsyncPolkitQuery*, async_polkit_query_unref); -DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( async_polkit_query_hash_ops, - void, - trivial_hash_func, - trivial_compare_func, - AsyncPolkitQuery, - async_polkit_query_unref); + void, trivial_hash_func, trivial_compare_func, + AsyncPolkitQuery, async_polkit_query_unref); static int async_polkit_defer(sd_event_source *s, void *userdata) { AsyncPolkitQuery *q = ASSERT_PTR(userdata); diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index 3666c1639845c..d125662410ba2 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -1099,9 +1099,9 @@ static JsonData* json_data_free(JsonData *d) { DEFINE_TRIVIAL_CLEANUP_FUNC(JsonData*, json_data_free); -DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(json_data_hash_ops_free, - char, string_hash_func, string_compare_func, - JsonData, json_data_free); +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(json_data_hash_ops_free, + char, string_hash_func, string_compare_func, + JsonData, json_data_free); static int update_json_data( Hashmap *h, diff --git a/src/sysupdate/sysupdated.c b/src/sysupdate/sysupdated.c index c123842c1084f..3fb88362e86aa 100644 --- a/src/sysupdate/sysupdated.c +++ b/src/sysupdate/sysupdated.c @@ -184,8 +184,9 @@ static Job *job_free(Job *j) { } DEFINE_TRIVIAL_CLEANUP_FUNC(Job*, job_free); -DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(job_hash_ops, uint64_t, uint64_hash_func, uint64_compare_func, - Job, job_free); +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(job_hash_ops, + uint64_t, uint64_hash_func, uint64_compare_func, + Job, job_free); static int job_new(JobType type, Target *t, sd_bus_message *msg, JobComplete complete_cb, Job **ret) { _cleanup_(job_freep) Job *j = NULL; @@ -728,8 +729,9 @@ static Target *target_free(Target *t) { } DEFINE_TRIVIAL_CLEANUP_FUNC(Target*, target_free); -DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(target_hash_ops, char, string_hash_func, string_compare_func, - Target, target_free); +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(target_hash_ops, + char, string_hash_func, string_compare_func, + Target, target_free); static int target_new(Manager *m, TargetClass class, const char *name, const char *path, Target **ret) { _cleanup_(target_freep) Target *t = NULL; From 192954ac68ca6473baae4dd912d7a880a16ba5f1 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 4 Mar 2026 13:25:03 +0100 Subject: [PATCH 0073/1296] bootctl: declare missing arg_* in header Follow-up for 90cf998875a2cfac2cdfe3e659c96d25457bf24b --- src/bootctl/bootctl.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bootctl/bootctl.h b/src/bootctl/bootctl.h index d979411c78226..07e98f8559491 100644 --- a/src/bootctl/bootctl.h +++ b/src/bootctl/bootctl.h @@ -21,6 +21,8 @@ extern char *arg_esp_path; extern char *arg_xbootldr_path; extern bool arg_print_esp_path; extern bool arg_print_dollar_boot_path; +extern bool arg_print_loader_path; +extern bool arg_print_stub_path; extern unsigned arg_print_root_device; extern int arg_touch_variables; extern bool arg_install_random_seed; From 769b0bf74658b78163221e69bfecb875ea98513b Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 4 Mar 2026 13:25:37 +0100 Subject: [PATCH 0074/1296] various: mark arg_* as static --- src/cryptenroll/cryptenroll.c | 2 +- src/mount/mount-tool.c | 2 +- src/nspawn/nspawn.c | 2 +- src/vmspawn/vmspawn.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index 2a914a9b49918..de907c4ad6240 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -54,7 +54,7 @@ static uint32_t arg_tpm2_public_key_pcr_mask = 0; static char *arg_tpm2_signature = NULL; static char *arg_tpm2_pcrlock = NULL; static char *arg_node = NULL; -PagerFlags arg_pager_flags = 0; +static PagerFlags arg_pager_flags = 0; static int *arg_wipe_slots = NULL; static size_t arg_n_wipe_slots = 0; static WipeScope arg_wipe_slots_scope = WIPE_EXPLICIT; diff --git a/src/mount/mount-tool.c b/src/mount/mount-tool.c index 24b93e1239718..b2ee90f4b7ada 100644 --- a/src/mount/mount-tool.c +++ b/src/mount/mount-tool.c @@ -40,7 +40,7 @@ #include "unit-name.h" #include "user-util.h" -enum { +static enum { ACTION_DEFAULT, ACTION_MOUNT, ACTION_AUTOMOUNT, diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index fa92b0a8861ec..1b3aa7d1ad50a 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -202,7 +202,7 @@ static char **arg_network_veth_extra = NULL; static char *arg_network_bridge = NULL; static char *arg_network_zone = NULL; static char *arg_network_namespace_path = NULL; -struct ether_addr arg_network_provided_mac = {}; +static struct ether_addr arg_network_provided_mac = {}; static PagerFlags arg_pager_flags = 0; static unsigned long arg_personality = PERSONALITY_INVALID; static char *arg_image = NULL; diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index fc647b15638fa..cacfc15f7e768 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -140,7 +140,7 @@ static char *arg_background = NULL; static bool arg_pass_ssh_key = true; static char *arg_ssh_key_type = NULL; static bool arg_discard_disk = true; -struct ether_addr arg_network_provided_mac = {}; +static struct ether_addr arg_network_provided_mac = {}; static char **arg_smbios11 = NULL; static uint64_t arg_grow_image = 0; static char *arg_tpm_state_path = NULL; From a0ea74092e9186e088c35f1ee5efbf7870e61075 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 4 Mar 2026 13:10:31 +0100 Subject: [PATCH 0075/1296] shared/varlink-*: normalize includes --- src/shared/varlink-io.systemd.FactoryReset.c | 2 -- src/shared/varlink-io.systemd.Machine.c | 2 -- src/shared/varlink-io.systemd.MachineImage.c | 2 -- src/shared/varlink-io.systemd.Metrics.c | 2 -- src/shared/varlink-io.systemd.MuteConsole.c | 2 -- src/shared/varlink-io.systemd.Repart.c | 2 -- src/shared/varlink-io.systemd.Unit.c | 1 + 7 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/shared/varlink-io.systemd.FactoryReset.c b/src/shared/varlink-io.systemd.FactoryReset.c index 7d55041e0f237..f7574e9e174f3 100644 --- a/src/shared/varlink-io.systemd.FactoryReset.c +++ b/src/shared/varlink-io.systemd.FactoryReset.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "sd-varlink-idl.h" - #include "varlink-io.systemd.FactoryReset.h" static SD_VARLINK_DEFINE_ENUM_TYPE( diff --git a/src/shared/varlink-io.systemd.Machine.c b/src/shared/varlink-io.systemd.Machine.c index 5f70e0a823848..31fb43fbac271 100644 --- a/src/shared/varlink-io.systemd.Machine.c +++ b/src/shared/varlink-io.systemd.Machine.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "sd-varlink-idl.h" - #include "bus-polkit.h" #include "varlink-idl-common.h" #include "varlink-io.systemd.Machine.h" diff --git a/src/shared/varlink-io.systemd.MachineImage.c b/src/shared/varlink-io.systemd.MachineImage.c index 2642852e03496..7f4e8f3940ca6 100644 --- a/src/shared/varlink-io.systemd.MachineImage.c +++ b/src/shared/varlink-io.systemd.MachineImage.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "sd-varlink-idl.h" - #include "bus-polkit.h" #include "varlink-io.systemd.MachineImage.h" diff --git a/src/shared/varlink-io.systemd.Metrics.c b/src/shared/varlink-io.systemd.Metrics.c index f4623f1f159a7..4c210c9b42276 100644 --- a/src/shared/varlink-io.systemd.Metrics.c +++ b/src/shared/varlink-io.systemd.Metrics.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "sd-varlink-idl.h" - #include "varlink-io.systemd.Metrics.h" static SD_VARLINK_DEFINE_ENUM_TYPE( diff --git a/src/shared/varlink-io.systemd.MuteConsole.c b/src/shared/varlink-io.systemd.MuteConsole.c index 723b19985e8be..2500e2cfa059b 100644 --- a/src/shared/varlink-io.systemd.MuteConsole.c +++ b/src/shared/varlink-io.systemd.MuteConsole.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "sd-varlink-idl.h" - #include "varlink-io.systemd.MuteConsole.h" static SD_VARLINK_DEFINE_METHOD_FULL( diff --git a/src/shared/varlink-io.systemd.Repart.c b/src/shared/varlink-io.systemd.Repart.c index 8d50454595117..dbfb8d0360d2f 100644 --- a/src/shared/varlink-io.systemd.Repart.c +++ b/src/shared/varlink-io.systemd.Repart.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "sd-varlink-idl.h" - #include "varlink-io.systemd.Repart.h" static SD_VARLINK_DEFINE_ENUM_TYPE( diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c index a008b506e9b1d..05676210ef2a4 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "varlink-idl-common.h" +#include "varlink-io.systemd.Unit.h" /* CGroupContext */ static SD_VARLINK_DEFINE_STRUCT_TYPE( From 5aeda638bb5d15d1b5a256e664fbd702de67464a Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 4 Mar 2026 13:11:50 +0100 Subject: [PATCH 0076/1296] core/varlink-metrics: make metric_family_table static --- src/core/varlink-metrics.c | 42 +++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/core/varlink-metrics.c b/src/core/varlink-metrics.c index c492b0c04d315..00af452d7776c 100644 --- a/src/core/varlink-metrics.c +++ b/src/core/varlink-metrics.c @@ -143,37 +143,37 @@ static int units_by_state_total_build_json(MetricFamilyContext *context, void *u return 0; } -const MetricFamily metric_family_table[] = { +static const MetricFamily metric_family_table[] = { /* Keep metrics ordered alphabetically */ { - .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "NRestarts", - .description = "Per unit metric: number of restarts", - .type = METRIC_FAMILY_TYPE_COUNTER, - .generate = nrestarts_build_json, + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "NRestarts", + .description = "Per unit metric: number of restarts", + .type = METRIC_FAMILY_TYPE_COUNTER, + .generate = nrestarts_build_json, }, { - .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UnitActiveState", - .description = "Per unit metric: active state", - .type = METRIC_FAMILY_TYPE_STRING, - .generate = unit_active_state_build_json, + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UnitActiveState", + .description = "Per unit metric: active state", + .type = METRIC_FAMILY_TYPE_STRING, + .generate = unit_active_state_build_json, }, { - .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UnitLoadState", - .description = "Per unit metric: load state", - .type = METRIC_FAMILY_TYPE_STRING, - .generate = unit_load_state_build_json, + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UnitLoadState", + .description = "Per unit metric: load state", + .type = METRIC_FAMILY_TYPE_STRING, + .generate = unit_load_state_build_json, }, { - .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UnitsByStateTotal", - .description = "Total number of units of different state", - .type = METRIC_FAMILY_TYPE_GAUGE, - .generate = units_by_state_total_build_json, + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UnitsByStateTotal", + .description = "Total number of units of different state", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = units_by_state_total_build_json, }, { - .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UnitsByTypeTotal", - .description = "Total number of units of different types", - .type = METRIC_FAMILY_TYPE_GAUGE, - .generate = units_by_type_total_build_json, + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UnitsByTypeTotal", + .description = "Total number of units of different types", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = units_by_type_total_build_json, }, {} }; From 08e4cf491a8eb2ada895af752b2aa3669c36ecef Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 4 Mar 2026 13:26:42 +0100 Subject: [PATCH 0077/1296] various: make bus objects static --- src/home/homed-home-bus.c | 2 +- src/machine/image-dbus.c | 2 +- src/machine/machined-dbus.c | 2 +- src/timedate/timedated.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/home/homed-home-bus.c b/src/home/homed-home-bus.c index f16756b32ef43..8b9a13f7ea5ef 100644 --- a/src/home/homed-home-bus.c +++ b/src/home/homed-home-bus.c @@ -850,7 +850,7 @@ static int bus_home_node_enumerator( return 1; } -const sd_bus_vtable home_vtable[] = { +static const sd_bus_vtable home_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("UserName", "s", diff --git a/src/machine/image-dbus.c b/src/machine/image-dbus.c index 341c4a228df4d..4a61b48f3f77d 100644 --- a/src/machine/image-dbus.c +++ b/src/machine/image-dbus.c @@ -443,7 +443,7 @@ static int image_node_enumerator(sd_bus *bus, const char *path, void *userdata, return 1; } -const sd_bus_vtable image_vtable[] = { +static const sd_bus_vtable image_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Image, name), 0), SD_BUS_PROPERTY("Path", "s", NULL, offsetof(Image, path), 0), diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index a85b827ea06b8..9010b58fb248c 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -1219,7 +1219,7 @@ static int method_map_to_machine_group(sd_bus_message *message, void *userdata, return sd_bus_reply_method_return(message, "sou", machine->name, o, (uint32_t) converted); } -const sd_bus_vtable manager_vtable[] = { +static const sd_bus_vtable manager_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("PoolPath", "s", property_get_pool_path, 0, 0), diff --git a/src/timedate/timedated.c b/src/timedate/timedated.c index 5ba8644334da5..43cf3fddb9da6 100644 --- a/src/timedate/timedated.c +++ b/src/timedate/timedated.c @@ -1109,7 +1109,7 @@ static const sd_bus_vtable timedate_vtable[] = { SD_BUS_VTABLE_END, }; -const BusObjectImplementation manager_object = { +static const BusObjectImplementation manager_object = { "/org/freedesktop/timedate1", "org.freedesktop.timedate1", .vtables = BUS_VTABLES(timedate_vtable), From a521abbf272a90d3bd02c1810b5b84bab1cfd154 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 4 Mar 2026 13:49:59 +0100 Subject: [PATCH 0078/1296] machined: move declaration of manager_object to machined-dbus.h --- src/machine/machined-dbus.c | 5 +++-- src/machine/machined-dbus.h | 6 ++++++ src/machine/machined.c | 1 + src/machine/machined.h | 2 -- 4 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 src/machine/machined-dbus.h diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index 9010b58fb248c..31237c811a9e7 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -26,6 +26,7 @@ #include "machine.h" #include "machine-dbus.h" #include "machined.h" +#include "machined-dbus.h" #include "namespace-util.h" #include "operation.h" #include "os-util.h" @@ -1451,8 +1452,8 @@ const BusObjectImplementation manager_object = { "/org/freedesktop/machine1", "org.freedesktop.machine1.Manager", .vtables = BUS_VTABLES(manager_vtable), - .children = BUS_IMPLEMENTATIONS( &machine_object, - &image_object ), + .children = BUS_IMPLEMENTATIONS(&machine_object, + &image_object), }; int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *error) { diff --git a/src/machine/machined-dbus.h b/src/machine/machined-dbus.h new file mode 100644 index 0000000000000..7a1d610c01980 --- /dev/null +++ b/src/machine/machined-dbus.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "machine-forward.h" + +extern const BusObjectImplementation manager_object; diff --git a/src/machine/machined.c b/src/machine/machined.c index dfb01abea646c..7c1f57baac3de 100644 --- a/src/machine/machined.c +++ b/src/machine/machined.c @@ -23,6 +23,7 @@ #include "hostname-util.h" #include "machine.h" #include "machined.h" +#include "machined-dbus.h" #include "machined-varlink.h" #include "main-func.h" #include "mkdir-label.h" diff --git a/src/machine/machined.h b/src/machine/machined.h index 7c8922ed3e213..daba5a9bbd322 100644 --- a/src/machine/machined.h +++ b/src/machine/machined.h @@ -42,8 +42,6 @@ typedef struct Manager { int manager_add_machine(Manager *m, const char *name, Machine **ret); int manager_get_machine_by_pidref(Manager *m, const PidRef *pidref, Machine **ret); -extern const BusObjectImplementation manager_object; - int match_reloading(sd_bus_message *message, void *userdata, sd_bus_error *error); int match_unit_removed(sd_bus_message *message, void *userdata, sd_bus_error *error); int match_properties_changed(sd_bus_message *message, void *userdata, sd_bus_error *error); From 2853e4cb1c6318e7976f29c4caa56d3ef87eef53 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 4 Mar 2026 13:12:41 +0100 Subject: [PATCH 0079/1296] shared/password-quality-util-*: make dl functions static --- src/shared/password-quality-util-passwdqc.c | 12 ++++++------ src/shared/password-quality-util-pwquality.c | 16 ++++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/shared/password-quality-util-passwdqc.c b/src/shared/password-quality-util-passwdqc.c index 31b84c6a0f926..5b0b22458bfb3 100644 --- a/src/shared/password-quality-util-passwdqc.c +++ b/src/shared/password-quality-util-passwdqc.c @@ -15,12 +15,12 @@ static void *passwdqc_dl = NULL; -DLSYM_PROTOTYPE(passwdqc_params_reset) = NULL; -DLSYM_PROTOTYPE(passwdqc_params_load) = NULL; -DLSYM_PROTOTYPE(passwdqc_params_parse) = NULL; -DLSYM_PROTOTYPE(passwdqc_params_free) = NULL; -DLSYM_PROTOTYPE(passwdqc_check) = NULL; -DLSYM_PROTOTYPE(passwdqc_random) = NULL; +static DLSYM_PROTOTYPE(passwdqc_params_reset) = NULL; +static DLSYM_PROTOTYPE(passwdqc_params_load) = NULL; +static DLSYM_PROTOTYPE(passwdqc_params_parse) = NULL; +static DLSYM_PROTOTYPE(passwdqc_params_free) = NULL; +static DLSYM_PROTOTYPE(passwdqc_check) = NULL; +static DLSYM_PROTOTYPE(passwdqc_random) = NULL; DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(passwdqc_params_t*, sym_passwdqc_params_free, passwdqc_params_freep, NULL); diff --git a/src/shared/password-quality-util-pwquality.c b/src/shared/password-quality-util-pwquality.c index 33cd16227199a..05d85bd69652b 100644 --- a/src/shared/password-quality-util-pwquality.c +++ b/src/shared/password-quality-util-pwquality.c @@ -18,14 +18,14 @@ static void *pwquality_dl = NULL; -DLSYM_PROTOTYPE(pwquality_check) = NULL; -DLSYM_PROTOTYPE(pwquality_default_settings) = NULL; -DLSYM_PROTOTYPE(pwquality_free_settings) = NULL; -DLSYM_PROTOTYPE(pwquality_generate) = NULL; -DLSYM_PROTOTYPE(pwquality_get_str_value) = NULL; -DLSYM_PROTOTYPE(pwquality_read_config) = NULL; -DLSYM_PROTOTYPE(pwquality_set_int_value) = NULL; -DLSYM_PROTOTYPE(pwquality_strerror) = NULL; +static DLSYM_PROTOTYPE(pwquality_check) = NULL; +static DLSYM_PROTOTYPE(pwquality_default_settings) = NULL; +static DLSYM_PROTOTYPE(pwquality_free_settings) = NULL; +static DLSYM_PROTOTYPE(pwquality_generate) = NULL; +static DLSYM_PROTOTYPE(pwquality_get_str_value) = NULL; +static DLSYM_PROTOTYPE(pwquality_read_config) = NULL; +static DLSYM_PROTOTYPE(pwquality_set_int_value) = NULL; +static DLSYM_PROTOTYPE(pwquality_strerror) = NULL; DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(pwquality_settings_t*, sym_pwquality_free_settings, pwquality_free_settingsp, NULL); From ae558ebd425192d674c6a8e46954a881dfdb49ca Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 4 Mar 2026 14:09:03 +0100 Subject: [PATCH 0080/1296] bpf-dlopen: mark bpf_get_error() as static Any use of this function should go via bpf_get_error_translated(). --- src/shared/bpf-dlopen.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/shared/bpf-dlopen.c b/src/shared/bpf-dlopen.c index 0e7632eb343e8..940c25a7260af 100644 --- a/src/shared/bpf-dlopen.c +++ b/src/shared/bpf-dlopen.c @@ -19,6 +19,10 @@ #define MODERN_LIBBPF 0 #endif +static void *bpf_dl = NULL; + +static DLSYM_PROTOTYPE(libbpf_get_error) = NULL; + DLSYM_PROTOTYPE(bpf_link__destroy) = NULL; DLSYM_PROTOTYPE(bpf_link__fd) = NULL; DLSYM_PROTOTYPE(bpf_link__open) = NULL; @@ -43,15 +47,12 @@ DLSYM_PROTOTYPE(bpf_program__attach) = NULL; DLSYM_PROTOTYPE(bpf_program__attach_cgroup) = NULL; DLSYM_PROTOTYPE(bpf_program__attach_lsm) = NULL; DLSYM_PROTOTYPE(bpf_program__name) = NULL; -DLSYM_PROTOTYPE(libbpf_get_error) = NULL; DLSYM_PROTOTYPE(libbpf_set_print) = NULL; DLSYM_PROTOTYPE(ring_buffer__epoll_fd) = NULL; DLSYM_PROTOTYPE(ring_buffer__free) = NULL; DLSYM_PROTOTYPE(ring_buffer__new) = NULL; DLSYM_PROTOTYPE(ring_buffer__poll) = NULL; -static void* bpf_dl = NULL; - /* new symbols available from libbpf 0.7.0 */ int (*sym_bpf_map_create)(enum bpf_map_type, const char *, __u32, __u32, __u32, const struct bpf_map_create_opts *); struct bpf_map* (*sym_bpf_object__next_map)(const struct bpf_object *obj, const struct bpf_map *map); From f1f38e2fc15f2eb98b28a0c2b0db4396a3863def Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 4 Mar 2026 14:11:52 +0100 Subject: [PATCH 0081/1296] sd-journal/catalog: make catalog_file_dirs static --- src/libsystemd/sd-journal/catalog.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libsystemd/sd-journal/catalog.c b/src/libsystemd/sd-journal/catalog.c index 9ddbc11089a65..a44ffe5585b7e 100644 --- a/src/libsystemd/sd-journal/catalog.c +++ b/src/libsystemd/sd-journal/catalog.c @@ -28,7 +28,7 @@ #include "strv.h" #include "tmpfile-util.h" -const char * const catalog_file_dirs[] = { +static const char * const catalog_file_dirs[] = { "/usr/local/lib/systemd/catalog/", "/usr/lib/systemd/catalog/", NULL From e8c542981bdd3df29aa65573e75057fd3a537109 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 4 Mar 2026 14:31:54 +0100 Subject: [PATCH 0082/1296] machine-dbus: do not check for overlapping condition Follow-up for c5e48e3a66b23313cd4931b9dc25a8f48cfb1035 This also makes things in line with the varlink method. --- src/machine/machine-dbus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index b09a2facb0bfa..d567cd6d503f7 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -380,7 +380,7 @@ int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bu r, "Failed to check if machine '%s' is running in the root user namespace: %m", m->name); - if (r != 0) + if (r > 0) return sd_bus_error_set( error, SD_BUS_ERROR_ACCESS_DENIED, From 35742907a72d76b30ba7f4932b8c426c9370bc79 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 4 Mar 2026 15:18:44 +0100 Subject: [PATCH 0083/1296] pcrextend-util: fix log message The NvPCR is actually named differently from what the log msg said. Fix it. --- src/shared/pcrextend-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/pcrextend-util.c b/src/shared/pcrextend-util.c index 431db8cc157d8..8586e85cbbd3f 100644 --- a/src/shared/pcrextend-util.c +++ b/src/shared/pcrextend-util.c @@ -285,7 +285,7 @@ int pcrextend_verity_now( return log_debug_errno(r, "Failed to issue io.systemd.PCRExtend.Extend() varlink call: %s", error_id); } - log_debug("Measurement of '%s' into 'images' NvPCR completed.", word); + log_debug("Measurement of '%s' into 'verity' NvPCR completed.", word); return 1; #else return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring Verity root hashes and signatures."); From 57b6859757c06c4433ce2746bc1a223aad063b0a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 12:58:47 +0000 Subject: [PATCH 0084/1296] build(deps): bump ninja from 1.11.1.4 to 1.13.0 in /.github/workflows Bumps [ninja](https://github.com/scikit-build/ninja-python-distributions) from 1.11.1.4 to 1.13.0. - [Release notes](https://github.com/scikit-build/ninja-python-distributions/releases) - [Changelog](https://github.com/scikit-build/ninja-python-distributions/blob/master/HISTORY.rst) - [Commits](https://github.com/scikit-build/ninja-python-distributions/compare/1.11.1.4...1.13.0) --- updated-dependencies: - dependency-name: ninja dependency-version: 1.13.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/requirements.txt | 38 ++++++++++++++++-------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/.github/workflows/requirements.txt b/.github/workflows/requirements.txt index 08af02c80fcb4..0334f8612219a 100644 --- a/.github/workflows/requirements.txt +++ b/.github/workflows/requirements.txt @@ -1,21 +1,23 @@ meson==1.10.1 \ --hash=sha256:c42296f12db316a4515b9375a5df330f2e751ccdd4f608430d41d7d6210e4317 \ --hash=sha256:fe43d1cc2e6de146fbea78f3a062194bcc0e779efc8a0f0d7c35544dfb86731f -ninja==1.11.1.4 \ - --hash=sha256:055f386fb550c2c9d6157e45e20a84d29c47968876b9c5794ae2aec46f952306 \ - --hash=sha256:096487995473320de7f65d622c3f1d16c3ad174797602218ca8c967f51ec38a0 \ - --hash=sha256:2ab67a41c90bea5ec4b795bab084bc0b3b3bb69d3cd21ca0294fc0fc15a111eb \ - --hash=sha256:4617b3c12ff64b611a7d93fd9e378275512bb36eff8babff7c83f5116b4f8d66 \ - --hash=sha256:5713cf50c5be50084a8693308a63ecf9e55c3132a78a41ab1363a28b6caaaee1 \ - --hash=sha256:6aa39f6e894e0452e5b297327db00019383ae55d5d9c57c73b04f13bf79d438a \ - --hash=sha256:9c29bb66d2aa46a2409ab369ea804c730faec7652e8c22c1e428cc09216543e5 \ - --hash=sha256:b33923c8da88e8da20b6053e38deb433f53656441614207e01d283ad02c5e8e7 \ - --hash=sha256:c3b96bd875f3ef1db782470e9e41d7508905a0986571f219d20ffed238befa15 \ - --hash=sha256:cede0af00b58e27b31f2482ba83292a8e9171cdb9acc2c867a3b6e40b3353e43 \ - --hash=sha256:cf4453679d15babc04ba023d68d091bb613091b67101c88f85d2171c6621c6eb \ - --hash=sha256:cf554e73f72c04deb04d0cf51f5fdb1903d9c9ca3d2344249c8ce3bd616ebc02 \ - --hash=sha256:cfdd09776436a1ff3c4a2558d3fc50a689fb9d7f1bdbc3e6f7b8c2991341ddb3 \ - --hash=sha256:d3090d4488fadf6047d0d7a1db0c9643a8d391f0d94729554dbb89b5bdc769d7 \ - --hash=sha256:d4a6f159b08b0ac4aca5ee1572e3e402f969139e71d85d37c0e2872129098749 \ - --hash=sha256:ecce44a00325a93631792974659cf253a815cc6da4ec96f89742925dfc295a0d \ - --hash=sha256:f6186d7607bb090c3be1e10c8a56b690be238f953616626f5032238c66e56867 +ninja==1.13.0 \ + --hash=sha256:11be2d22027bde06f14c343f01d31446747dbb51e72d00decca2eb99be911e2f \ + --hash=sha256:1c97223cdda0417f414bf864cfb73b72d8777e57ebb279c5f6de368de0062988 \ + --hash=sha256:3c0b40b1f0bba764644385319028650087b4c1b18cdfa6f45cb39a3669b81aa9 \ + --hash=sha256:3d00c692fb717fd511abeb44b8c5d00340c36938c12d6538ba989fe764e79630 \ + --hash=sha256:3d7d7779d12cb20c6d054c61b702139fd23a7a964ec8f2c823f1ab1b084150db \ + --hash=sha256:4a40ce995ded54d9dc24f8ea37ff3bf62ad192b547f6c7126e7e25045e76f978 \ + --hash=sha256:4be9c1b082d244b1ad7ef41eb8ab088aae8c109a9f3f0b3e56a252d3e00f42c1 \ + --hash=sha256:5f8e1e8a1a30835eeb51db05cf5a67151ad37542f5a4af2a438e9490915e5b72 \ + --hash=sha256:60056592cf495e9a6a4bea3cd178903056ecb0943e4de45a2ea825edb6dc8d3e \ + --hash=sha256:6739d3352073341ad284246f81339a384eec091d9851a886dfa5b00a6d48b3e2 \ + --hash=sha256:8cfbb80b4a53456ae8a39f90ae3d7a2129f45ea164f43fadfa15dc38c4aef1c9 \ + --hash=sha256:aa45b4037b313c2f698bc13306239b8b93b4680eb47e287773156ac9e9304714 \ + --hash=sha256:b4f2a072db3c0f944c32793e91532d8948d20d9ab83da9c0c7c15b5768072200 \ + --hash=sha256:be7f478ff9f96a128b599a964fc60a6a87b9fa332ee1bd44fa243ac88d50291c \ + --hash=sha256:d741a5e6754e0bda767e3274a0f0deeef4807f1fec6c0d7921a0244018926ae5 \ + --hash=sha256:e8bad11f8a00b64137e9b315b137d8bb6cbf3086fbdc43bf1f90fd33324d2e96 \ + --hash=sha256:fa2a8bfc62e31b08f83127d1613d10821775a0eb334197154c4d6067b7068ff1 \ + --hash=sha256:fb46acf6b93b8dd0322adc3a4945452a4e774b75b91293bafcc7b7f8e6517dfa \ + --hash=sha256:fb8ee8719f8af47fed145cced4a85f0755dd55d45b2bddaf7431fa89803c5f3e From f2f2d008c93a74f228984ec168df6591923daafb Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 4 Mar 2026 17:06:23 +0100 Subject: [PATCH 0085/1296] update TODO --- TODO | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/TODO b/TODO index 0f97998abbaee..71af3a5390a9a 100644 --- a/TODO +++ b/TODO @@ -131,10 +131,6 @@ Features: possibly. (also, NV_ORDERLY makes stuff faster, and dropping it costs possibly up to 100ms supposedly) -* create a hwdb database that contains tpm quirks, i.e. knows whether NV_ORDERLY - + TPM2_NT_EXTEND can be safely mixed or - not. (see https://github.com/systemd/systemd/issues/40485#issuecomment-3984855537) - * instead of going directly for DefineSpace when initializing nvpcrs, check if they exist first. apparently DEfineSpace is broken on some tpms, and also creates log spam if the nvindex already exists. @@ -171,8 +167,6 @@ Features: reference implementation uses a different address syntax, which needs to be taken into account. -* downgrade the uid/gid disposition enforcement in udev - * have a signal that reloads every unit that supports reloading * systemd: add storage API via varlink, where everyone can drop a socket in a @@ -198,7 +192,7 @@ Features: generates a random password, passes it as credential to sysusers for the root user, then displays it on screen. people can use this to remotely log in. -* Maybe introducean InodeRef structure inspired by PidRef, which references a +* Maybe introduce an InodeRef structure inspired by PidRef, which references a specific inode, and combines: a path, an O_PATH fd, and possibly a FID into one. Why? We often pass around path and fd separately in chaseat() and similar calls. Because passing around both separately is cumbersome we sometimes only @@ -255,8 +249,6 @@ Features: * measure all log-in attempts into a new nvpcr -* measure all DDI activations into a new nvpcr - * maybe rework systemd-modules-load to be a generator that just instantiates modprobe@.service a bunch of times @@ -370,7 +362,6 @@ Features: root disks) * complete varlink introspection comments: - - io.systemd.BootControl - io.systemd.Hostname - io.systemd.ManagedOOM - io.systemd.Network From 3abe09ebea1185e4f7a526278c415dc869454f09 Mon Sep 17 00:00:00 2001 From: Jan Kuparinen Date: Wed, 4 Mar 2026 19:58:26 +0000 Subject: [PATCH 0086/1296] po: Translated using Weblate (Finnish) Currently translated at 100.0% (266 of 266 strings) Co-authored-by: Jan Kuparinen Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/fi/ Translation: systemd/main --- po/fi.po | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/po/fi.po b/po/fi.po index 095027fd88596..c749e8d19b442 100644 --- a/po/fi.po +++ b/po/fi.po @@ -1,15 +1,15 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # # Finnish translation of systemd. -# Jan Kuparinen , 2021, 2022, 2023. +# Jan Kuparinen , 2021, 2022, 2023, 2026. # Ricky Tigg , 2022, 2024, 2025. # Jiri Grönroos , 2024. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2025-09-04 00:52+0000\n" -"Last-Translator: Ricky Tigg \n" +"PO-Revision-Date: 2026-03-04 19:58+0000\n" +"Last-Translator: Jan Kuparinen \n" "Language-Team: Finnish \n" "Language: fi\n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.13\n" +"X-Generator: Weblate 5.16.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1063,11 +1063,11 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Hallitse verkkoyhteyksiä" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" +msgstr "Verkkolinkkien hallintaan vaaditaan todennus." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" From d100a3bcd96f2611c7dbcd5f28040eaf1bd5e568 Mon Sep 17 00:00:00 2001 From: Martin Srebotnjak Date: Wed, 4 Mar 2026 19:58:26 +0000 Subject: [PATCH 0087/1296] po: Translated using Weblate (Slovenian) Currently translated at 100.0% (266 of 266 strings) Co-authored-by: Martin Srebotnjak Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/sl/ Translation: systemd/main --- po/sl.po | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/po/sl.po b/po/sl.po index 997a03a792443..fbc2f0086df04 100644 --- a/po/sl.po +++ b/po/sl.po @@ -1,13 +1,13 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # -# Martin Srebotnjak , 2024, 2025. +# Martin Srebotnjak , 2024, 2025, 2026. # Weblate Translation Memory , 2024. msgid "" msgstr "" "Project-Id-Version: systemd\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2025-12-09 15:58+0000\n" +"PO-Revision-Date: 2026-03-04 19:58+0000\n" "Last-Translator: Martin Srebotnjak \n" "Language-Team: Slovenian \n" @@ -17,7 +17,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || " "n%100==4 ? 2 : 3;\n" -"X-Generator: Weblate 5.14.3\n" +"X-Generator: Weblate 5.16.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1113,7 +1113,7 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Upravljaj omrežne povezave" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." From 568dbbd5a9514f3915d91c3a376d7f018fdbf60a Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 4 Mar 2026 22:33:47 +0100 Subject: [PATCH 0088/1296] journalctl-filter: drop doubled newline Follow-up for d8302c2fd92602eae780511037ca08ed8cb0667d --- src/journal/journalctl-filter.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/journal/journalctl-filter.c b/src/journal/journalctl-filter.c index 0430f24ce9e46..ce2a6cb18c6ed 100644 --- a/src/journal/journalctl-filter.c +++ b/src/journal/journalctl-filter.c @@ -154,7 +154,6 @@ int journal_add_unit_matches( } } - if (!strv_isempty(patterns)) { _cleanup_set_free_ Set *units = NULL; From c8f764e40db29046089876518ba40fd1c9bd59d5 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Thu, 5 Mar 2026 01:02:41 +0100 Subject: [PATCH 0089/1296] hwdb/60-tpm2: correct prefix in comment (tpm -> tpm2) Follow-up for f2eed3fa25e8c38b7a90d6ab3d22ee90e3569271 --- hwdb.d/60-tpm2.hwdb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hwdb.d/60-tpm2.hwdb b/hwdb.d/60-tpm2.hwdb index 2772bf3cde169..935737d1d9e15 100644 --- a/hwdb.d/60-tpm2.hwdb +++ b/hwdb.d/60-tpm2.hwdb @@ -1,7 +1,7 @@ # This file is part of systemd. # # Use "systemd-analyze identify-tpm2" to generate the modalias string for your -# hardware. Don't forget to prefix it with "tpm:" for inclusion in a match here. +# hardware. Don't forget to prefix it with "tpm2:" for inclusion in a match here. # # Currently, the only relevant property to set here is TPM2_BROKEN_NVPCR=1, # which should be set on TPMs where NvPCRs don't work. Specifically, because From ab7d40ff0c7250b637203b78cf0ec9831bbdc0ba Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Thu, 5 Mar 2026 01:33:20 +0100 Subject: [PATCH 0090/1296] machine-varlink: reference the right struct in VARLINK_DISPATCH_MACHINE_LOOKUP_FIELDS This practically shouldn't matter, as the layout for name and pidref fields are identical for all the structs. But let's get things right. --- src/machine/machine-varlink.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c index e8a8933251e38..118d8178f40fb 100644 --- a/src/machine/machine-varlink.c +++ b/src/machine/machine-varlink.c @@ -656,7 +656,7 @@ static void machine_map_paramaters_done(MachineMapParameters *p) { int vl_method_map_from(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { static const sd_json_dispatch_field dispatch_table[] = { - VARLINK_DISPATCH_MACHINE_LOOKUP_FIELDS(MachineOpenParameters), + VARLINK_DISPATCH_MACHINE_LOOKUP_FIELDS(MachineMapParameters), { "uid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(MachineMapParameters, uid), 0 }, { "gid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(MachineMapParameters, gid), 0 }, {} @@ -817,7 +817,7 @@ static void machine_mount_paramaters_done(MachineMountParameters *p) { int vl_method_bind_mount(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { static const sd_json_dispatch_field dispatch_table[] = { - VARLINK_DISPATCH_MACHINE_LOOKUP_FIELDS(MachineOpenParameters), + VARLINK_DISPATCH_MACHINE_LOOKUP_FIELDS(MachineMountParameters), { "source", SD_JSON_VARIANT_STRING, json_dispatch_const_path, offsetof(MachineMountParameters, src), SD_JSON_MANDATORY }, { "destination", SD_JSON_VARIANT_STRING, json_dispatch_const_path, offsetof(MachineMountParameters, dest), 0 }, { "readOnly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(MachineMountParameters, read_only), 0 }, From 240675efebe5a09e2df1f523cfea055311769c48 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 3 Mar 2026 18:18:53 +0000 Subject: [PATCH 0091/1296] man: clarify requirements around creds null sealing --- man/systemd-creds.xml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/man/systemd-creds.xml b/man/systemd-creds.xml index 60ded4219327e..a35e534bfa18d 100644 --- a/man/systemd-creds.xml +++ b/man/systemd-creds.xml @@ -339,8 +339,9 @@ where credentials shall be generated. Note that decryption of such credentials is refused on systems that have a TPM2 chip and where UEFI SecureBoot is enabled (this is done so that such a locked down system cannot be tricked into loading a credential generated this way that lacks authentication - information). If set to auto-initrd a TPM2 key is used if a TPM2 is found. If not - a fixed zero length key is used, equivalent to null mode. This option is + information. If either UEFI SecureBoot or a TPM2 are not available, then loading such credentials is + allowed by default). If set to auto-initrd a TPM2 key is used if a TPM2 is found. + If not, a fixed zero length key is used, equivalent to null mode. This option is particularly useful to generate credentials files that are encrypted/authenticated against TPM2 where available but still work on systems lacking support for this. The special value help may be used to list supported key types. @@ -424,7 +425,9 @@ - Allow decrypting credentials that use a null key. By default decryption of credentials encrypted/authenticated with a null key is only allowed if UEFI SecureBoot is off. + Allow decrypting credentials that use a null key. By default decryption of + credentials encrypted/authenticated with a null key is only allowed if UEFI SecureBoot is off or if + a TPM2 is not available. @@ -432,7 +435,8 @@ - Refuse decrypting credentials that use a null key, regardless of the UEFI SecureBoot state (see above). + Refuse decrypting credentials that use a null key, regardless of the UEFI SecureBoot + state or TPM2 availability (see above). From 86867d124068b56f47e04bb7625bfaa98df04a88 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Wed, 4 Mar 2026 19:04:26 +0100 Subject: [PATCH 0092/1296] coccinelle: ignore our own BPF programs Since they don't have access to systemd code, so we can't use our custom functions/macros in them anyway. --- coccinelle/run-coccinelle.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/coccinelle/run-coccinelle.sh b/coccinelle/run-coccinelle.sh index ecefbf5302d4e..61e226e333854 100755 --- a/coccinelle/run-coccinelle.sh +++ b/coccinelle/run-coccinelle.sh @@ -9,6 +9,8 @@ EXCLUDED_PATHS=( # Symlinked to test-bus-vtable-cc.cc, which causes issues with the IN_SET macro "src/libsystemd/sd-bus/test-bus-vtable.c" "src/libsystemd/sd-journal/lookup3.c" + # Our BPF programs don't have access to systemd stuff + "src/network/bpf/*" # Ignore man examples, as they redefine some macros we use internally, which makes Coccinelle complain # and ignore code that tries to use the redefined stuff "man/*" From f8d0ab46cf4bf8e4c2e22ce666602eae18aebbf1 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Wed, 4 Mar 2026 19:37:18 +0100 Subject: [PATCH 0093/1296] coccinelle: update the list of excluded directories - src/boot/efi/ was moved to src/boot/ in 97318131fd06a5bc35454da81dcbbc84f16d9940 - src/basic/include/linux/ was moved to src/include/uapi/linux/ in 1a60b97524d8408e5f059b09ae316987c698e671 --- coccinelle/run-coccinelle.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coccinelle/run-coccinelle.sh b/coccinelle/run-coccinelle.sh index 61e226e333854..8323a3abc3022 100755 --- a/coccinelle/run-coccinelle.sh +++ b/coccinelle/run-coccinelle.sh @@ -4,8 +4,8 @@ set -e # Exclude following paths from the Coccinelle transformations EXCLUDED_PATHS=( - "src/boot/efi/*" - "src/basic/include/linux/*" + "src/boot/*" + "src/include/uapi/*" # Symlinked to test-bus-vtable-cc.cc, which causes issues with the IN_SET macro "src/libsystemd/sd-bus/test-bus-vtable.c" "src/libsystemd/sd-journal/lookup3.c" From 77ee36a740be18ded2cbfa165b3666745c76a237 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Wed, 4 Mar 2026 19:17:42 +0100 Subject: [PATCH 0094/1296] coccinelle: simplify the SD_JSON_BUILD_PAIR_* transformations And also disable them on test-json.c, since there we use the macros intentionally in a "non-optimal" way to actually test them. --- coccinelle/sd_build_pair.cocci | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/coccinelle/sd_build_pair.cocci b/coccinelle/sd_build_pair.cocci index f0724ef8241c0..8c9af38cb2d13 100644 --- a/coccinelle/sd_build_pair.cocci +++ b/coccinelle/sd_build_pair.cocci @@ -1,26 +1,22 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ @@ +/* Disable this transformation on test-json.c */ +position p : script:python() { p[0].file != "src/test/test-json.c" }; expression key, val; @@ -- SD_JSON_BUILD_PAIR(key, SD_JSON_BUILD_BOOLEAN(val)) +( +- SD_JSON_BUILD_PAIR@p(key, SD_JSON_BUILD_BOOLEAN(val)) + SD_JSON_BUILD_PAIR_BOOLEAN(key, val) -@@ -expression key, val; -@@ -- SD_JSON_BUILD_PAIR(key, SD_JSON_BUILD_INTEGER(val)) +| +- SD_JSON_BUILD_PAIR@p(key, SD_JSON_BUILD_INTEGER(val)) + SD_JSON_BUILD_PAIR_INTEGER(key, val) -@@ -expression key, val; -@@ -- SD_JSON_BUILD_PAIR(key, SD_JSON_BUILD_STRING(val)) +| +- SD_JSON_BUILD_PAIR@p(key, SD_JSON_BUILD_STRING(val)) + SD_JSON_BUILD_PAIR_STRING(key, val) -@@ -expression key, val; -@@ -- SD_JSON_BUILD_PAIR(key, SD_JSON_BUILD_UNSIGNED(val)) +| +- SD_JSON_BUILD_PAIR@p(key, SD_JSON_BUILD_UNSIGNED(val)) + SD_JSON_BUILD_PAIR_UNSIGNED(key, val) -@@ -expression key, val; -@@ -- SD_JSON_BUILD_PAIR(key, SD_JSON_BUILD_VARIANT(val)) +| +- SD_JSON_BUILD_PAIR@p(key, SD_JSON_BUILD_VARIANT(val)) + SD_JSON_BUILD_PAIR_VARIANT(key, val) +) From 41d9419562031d482d1280cc666dac147413370d Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Wed, 4 Mar 2026 19:38:37 +0100 Subject: [PATCH 0095/1296] coccinelle: work around a bug in zlib.h parsing Currently, parsing zlib.h on Fedora (and possibly others) causes spatch to fail with an assertion. Let's work around that by defining two extra macros in our Coccinelle parsing hacks. --- coccinelle/parsing_hacks.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/coccinelle/parsing_hacks.h b/coccinelle/parsing_hacks.h index f88dae0c86b65..24a9f1be5ecab 100644 --- a/coccinelle/parsing_hacks.h +++ b/coccinelle/parsing_hacks.h @@ -81,3 +81,8 @@ * let's help it a little here by providing simplified one-line versions. */ #define CMSG_BUFFER_TYPE(x) union { uint8_t align_check[(size) >= CMSG_SPACE(0) && (size) == CMSG_ALIGN(size) ? 1 : -1]; } #define SD_ID128_MAKE(...) ((const sd_id128) {}) + +/* Work around a bug in zlib.h parsing on Fedora (and possibly others) + * See: https://github.com/coccinelle/coccinelle/issues/413 */ +#define Z_EXPORT +#define Z_EXTERN From a8335fb91f8cc57a85a77a7f745c698234fb9bce Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Wed, 4 Mar 2026 18:57:41 +0100 Subject: [PATCH 0096/1296] tree-wide: use typed SD_JSON_BUILD_PAIR_* macros more --- src/home/homed-home.c | 2 +- src/home/user-record-util.c | 2 +- src/import/import-generator.c | 8 +-- src/libsystemd/sd-bus/bus-dump-json.c | 22 ++++---- src/libsystemd/sd-json/sd-json.c | 12 ++--- src/machine/machined-varlink.c | 4 +- src/pcrlock/pcrlock.c | 44 ++++++++-------- src/shared/metrics.c | 2 +- src/test/test-format-table.c | 72 +++++++++++++-------------- src/test/test-varlink-idl.c | 4 +- src/test/test-varlink.c | 26 +++++----- 11 files changed, 99 insertions(+), 99 deletions(-) diff --git a/src/home/homed-home.c b/src/home/homed-home.c index 985b18661cf78..00b2e72f9fb99 100644 --- a/src/home/homed-home.c +++ b/src/home/homed-home.c @@ -2690,7 +2690,7 @@ int home_augment_status( r = sd_json_buildo(&status, SD_JSON_BUILD_PAIR_STRING("state", home_state_to_string(state)), SD_JSON_BUILD_PAIR("service", JSON_BUILD_CONST_STRING("io.systemd.Home")), - SD_JSON_BUILD_PAIR("useFallback", SD_JSON_BUILD_BOOLEAN(!HOME_STATE_IS_ACTIVE(state))), + SD_JSON_BUILD_PAIR_BOOLEAN("useFallback", !HOME_STATE_IS_ACTIVE(state)), SD_JSON_BUILD_PAIR("fallbackShell", JSON_BUILD_CONST_STRING(BINDIR "/systemd-home-fallback-shell")), SD_JSON_BUILD_PAIR("fallbackHomeDirectory", JSON_BUILD_CONST_STRING("/")), SD_JSON_BUILD_PAIR_CONDITION(disk_size != UINT64_MAX, "diskSize", SD_JSON_BUILD_UNSIGNED(disk_size)), diff --git a/src/home/user-record-util.c b/src/home/user-record-util.c index b15e8f141bc72..2563a53234de9 100644 --- a/src/home/user-record-util.c +++ b/src/home/user-record-util.c @@ -159,7 +159,7 @@ int group_record_synthesize(GroupRecord *g, UserRecord *h) { SD_JSON_BUILD_PAIR_STRING("description", description), SD_JSON_BUILD_PAIR("binding", SD_JSON_BUILD_OBJECT( SD_JSON_BUILD_PAIR(SD_ID128_TO_STRING(mid), SD_JSON_BUILD_OBJECT( - SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(user_record_gid(h))))))), + SD_JSON_BUILD_PAIR_UNSIGNED("gid", user_record_gid(h)))))), SD_JSON_BUILD_PAIR_CONDITION(h->disposition >= 0, "disposition", SD_JSON_BUILD_STRING(user_disposition_to_string(user_record_disposition(h)))), SD_JSON_BUILD_PAIR("status", SD_JSON_BUILD_OBJECT( SD_JSON_BUILD_PAIR(SD_ID128_TO_STRING(mid), SD_JSON_BUILD_OBJECT( diff --git a/src/import/import-generator.c b/src/import/import-generator.c index f5c39774050db..9803ad284140e 100644 --- a/src/import/import-generator.c +++ b/src/import/import-generator.c @@ -209,13 +209,13 @@ static int parse_pull_expression(const char *v) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; r = sd_json_buildo( &j, - SD_JSON_BUILD_PAIR("remote", SD_JSON_BUILD_STRING(remote)), - SD_JSON_BUILD_PAIR("local", SD_JSON_BUILD_STRING(local)), + SD_JSON_BUILD_PAIR_STRING("remote", remote), + SD_JSON_BUILD_PAIR_STRING("local", local), SD_JSON_BUILD_PAIR("class", JSON_BUILD_STRING_UNDERSCORIFY(image_class_to_string(class))), SD_JSON_BUILD_PAIR("type", JSON_BUILD_STRING_UNDERSCORIFY(import_type_to_string(type))), - SD_JSON_BUILD_PAIR("readOnly", SD_JSON_BUILD_BOOLEAN(ro)), + SD_JSON_BUILD_PAIR_BOOLEAN("readOnly", ro), SD_JSON_BUILD_PAIR("verify", JSON_BUILD_STRING_UNDERSCORIFY(import_verify_to_string(verify))), - SD_JSON_BUILD_PAIR("imageRoot", SD_JSON_BUILD_STRING(image_root))); + SD_JSON_BUILD_PAIR_STRING("imageRoot", image_root)); if (r < 0) return log_error_errno(r, "Failed to build import JSON object: %m"); diff --git a/src/libsystemd/sd-bus/bus-dump-json.c b/src/libsystemd/sd-bus/bus-dump-json.c index 92fcde359b5f4..8238ac587e908 100644 --- a/src/libsystemd/sd-bus/bus-dump-json.c +++ b/src/libsystemd/sd-bus/bus-dump-json.c @@ -57,8 +57,8 @@ static int json_transform_variant(sd_bus_message *m, const char *contents, sd_js return sd_json_buildo( ret, - SD_JSON_BUILD_PAIR("type", SD_JSON_BUILD_STRING(contents)), - SD_JSON_BUILD_PAIR("data", SD_JSON_BUILD_VARIANT(value))); + SD_JSON_BUILD_PAIR_STRING("type", contents), + SD_JSON_BUILD_PAIR_VARIANT("data", value)); } static int json_transform_dict_array(sd_bus_message *m, sd_json_variant **ret) { @@ -287,8 +287,8 @@ static int json_transform_message(sd_bus_message *m, const char *type, sd_json_v return sd_json_buildo( ret, - SD_JSON_BUILD_PAIR("type", SD_JSON_BUILD_STRING(type)), - SD_JSON_BUILD_PAIR("data", SD_JSON_BUILD_VARIANT(v))); + SD_JSON_BUILD_PAIR_STRING("type", type), + SD_JSON_BUILD_PAIR_VARIANT("data", v)); } _public_ int sd_bus_message_dump_json(sd_bus_message *m, uint64_t flags, sd_json_variant **ret) { @@ -325,13 +325,13 @@ _public_ int sd_bus_message_dump_json(sd_bus_message *m, uint64_t flags, sd_json return sd_json_buildo( ret, - SD_JSON_BUILD_PAIR("type", SD_JSON_BUILD_STRING(bus_message_type_to_string(m->header->type))), - SD_JSON_BUILD_PAIR("endian", SD_JSON_BUILD_STRING(CHAR_TO_STR(m->header->endian))), - SD_JSON_BUILD_PAIR("flags", SD_JSON_BUILD_INTEGER(m->header->flags)), - SD_JSON_BUILD_PAIR("version", SD_JSON_BUILD_INTEGER(m->header->version)), - SD_JSON_BUILD_PAIR("cookie", SD_JSON_BUILD_INTEGER(BUS_MESSAGE_COOKIE(m))), + SD_JSON_BUILD_PAIR_STRING("type", bus_message_type_to_string(m->header->type)), + SD_JSON_BUILD_PAIR_STRING("endian", CHAR_TO_STR(m->header->endian)), + SD_JSON_BUILD_PAIR_INTEGER("flags", m->header->flags), + SD_JSON_BUILD_PAIR_INTEGER("version", m->header->version), + SD_JSON_BUILD_PAIR_INTEGER("cookie", BUS_MESSAGE_COOKIE(m)), SD_JSON_BUILD_PAIR_CONDITION(m->reply_cookie != 0, "reply_cookie", SD_JSON_BUILD_INTEGER(m->reply_cookie)), - SD_JSON_BUILD_PAIR("timestamp-realtime", SD_JSON_BUILD_UNSIGNED(ts)), + SD_JSON_BUILD_PAIR_UNSIGNED("timestamp-realtime", ts), SD_JSON_BUILD_PAIR_CONDITION(!!m->sender, "sender", SD_JSON_BUILD_STRING(m->sender)), SD_JSON_BUILD_PAIR_CONDITION(!!m->destination, "destination", SD_JSON_BUILD_STRING(m->destination)), SD_JSON_BUILD_PAIR_CONDITION(!!m->path, "path", SD_JSON_BUILD_STRING(m->path)), @@ -341,5 +341,5 @@ _public_ int sd_bus_message_dump_json(sd_bus_message *m, uint64_t flags, sd_json SD_JSON_BUILD_PAIR_CONDITION(m->realtime != 0, "realtime", SD_JSON_BUILD_INTEGER(m->realtime)), SD_JSON_BUILD_PAIR_CONDITION(m->seqnum != 0, "seqnum", SD_JSON_BUILD_INTEGER(m->seqnum)), SD_JSON_BUILD_PAIR_CONDITION(!!m->error.name, "error_name", SD_JSON_BUILD_STRING(m->error.name)), - SD_JSON_BUILD_PAIR("payload", SD_JSON_BUILD_VARIANT(v))); + SD_JSON_BUILD_PAIR_VARIANT("payload", v)); } diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index 5b6bc90c02c23..7829e11880643 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -4182,8 +4182,8 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) { if (ratelimit_configured(rl)) { r = sd_json_buildo( &add, - SD_JSON_BUILD_PAIR("intervalUSec", SD_JSON_BUILD_UNSIGNED(rl->interval)), - SD_JSON_BUILD_PAIR("burst", SD_JSON_BUILD_UNSIGNED(rl->burst))); + SD_JSON_BUILD_PAIR_UNSIGNED("intervalUSec", rl->interval), + SD_JSON_BUILD_PAIR_UNSIGNED("burst", rl->burst)); if (r < 0) goto finish; } else @@ -4805,8 +4805,8 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) { goto finish; r = sd_json_buildo(&add_more, - SD_JSON_BUILD_PAIR("realtime", SD_JSON_BUILD_UNSIGNED(ts->realtime)), - SD_JSON_BUILD_PAIR("monotonic", SD_JSON_BUILD_UNSIGNED(ts->monotonic))); + SD_JSON_BUILD_PAIR_UNSIGNED("realtime", ts->realtime), + SD_JSON_BUILD_PAIR_UNSIGNED("monotonic", ts->monotonic)); if (r < 0) goto finish; } @@ -4835,8 +4835,8 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) { goto finish; r = sd_json_buildo(&add_more, - SD_JSON_BUILD_PAIR("intervalUSec", SD_JSON_BUILD_UNSIGNED(rl->interval)), - SD_JSON_BUILD_PAIR("burst", SD_JSON_BUILD_UNSIGNED(rl->burst))); + SD_JSON_BUILD_PAIR_UNSIGNED("intervalUSec", rl->interval), + SD_JSON_BUILD_PAIR_UNSIGNED("burst", rl->burst)); if (r < 0) goto finish; } diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c index 543e4c8ee7f9d..fb03ee953fb82 100644 --- a/src/machine/machined-varlink.c +++ b/src/machine/machined-varlink.c @@ -476,9 +476,9 @@ static int list_machine_one_and_maybe_read_metadata(sd_varlink *link, Machine *m r = sd_json_buildo( &v, - SD_JSON_BUILD_PAIR("name", SD_JSON_BUILD_STRING(m->name)), + SD_JSON_BUILD_PAIR_STRING("name", m->name), SD_JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(m->id), "id", SD_JSON_BUILD_ID128(m->id)), - SD_JSON_BUILD_PAIR("class", SD_JSON_BUILD_STRING(machine_class_to_string(m->class))), + SD_JSON_BUILD_PAIR_STRING("class", machine_class_to_string(m->class)), JSON_BUILD_PAIR_STRING_NON_EMPTY("service", m->service), JSON_BUILD_PAIR_STRING_NON_EMPTY("rootDirectory", m->root_directory), JSON_BUILD_PAIR_STRING_NON_EMPTY("unit", m->unit), diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index 138841f31cf56..a02846e785dbd 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -2785,15 +2785,15 @@ static int make_pcrlock_record( r = sd_json_variant_append_arraybo( &digests, - SD_JSON_BUILD_PAIR("hashAlg", SD_JSON_BUILD_STRING(a)), + SD_JSON_BUILD_PAIR_STRING("hashAlg", a), SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(hash, hash_usize))); if (r < 0) return log_error_errno(r, "Failed to build JSON digest object: %m"); } r = sd_json_buildo(ret_record, - SD_JSON_BUILD_PAIR("pcr", SD_JSON_BUILD_UNSIGNED(pcr)), - SD_JSON_BUILD_PAIR("digests", SD_JSON_BUILD_VARIANT(digests))); + SD_JSON_BUILD_PAIR_UNSIGNED("pcr", pcr), + SD_JSON_BUILD_PAIR_VARIANT("digests", digests)); if (r < 0) return log_error_errno(r, "Failed to build record object: %m"); @@ -2867,7 +2867,7 @@ static int make_pcrlock_record_from_stream( r = sd_json_variant_append_arraybo( &digests, - SD_JSON_BUILD_PAIR("hashAlg", SD_JSON_BUILD_STRING(a)), + SD_JSON_BUILD_PAIR_STRING("hashAlg", a), SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(hash, hash_usize))); if (r < 0) return log_error_errno(r, "Failed to build JSON digest object: %m"); @@ -2881,8 +2881,8 @@ static int make_pcrlock_record_from_stream( r = sd_json_buildo( &record, - SD_JSON_BUILD_PAIR("pcr", SD_JSON_BUILD_UNSIGNED(i)), - SD_JSON_BUILD_PAIR("digests", SD_JSON_BUILD_VARIANT(digests))); + SD_JSON_BUILD_PAIR_UNSIGNED("pcr", i), + SD_JSON_BUILD_PAIR_VARIANT("digests", digests)); if (r < 0) return log_error_errno(r, "Failed to build record object: %m"); @@ -2914,7 +2914,7 @@ static int write_pcrlock(sd_json_variant *array, const char *default_pcrlock_pat r = sd_json_buildo( &v, - SD_JSON_BUILD_PAIR("records", SD_JSON_BUILD_VARIANT(array))); + SD_JSON_BUILD_PAIR_VARIANT("records", array)); if (r < 0) return log_error_errno(r, "Failed to build JSON object: %m"); @@ -3238,7 +3238,7 @@ static int verb_lock_secureboot_authority(int argc, char *argv[], void *userdata LIST_FOREACH(banks, bank, rec->banks) { r = sd_json_variant_append_arraybo( &digests, - SD_JSON_BUILD_PAIR("hashAlg", SD_JSON_BUILD_STRING(tpm2_hash_alg_to_string(bank->algorithm))), + SD_JSON_BUILD_PAIR_STRING("hashAlg", tpm2_hash_alg_to_string(bank->algorithm)), SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(bank->hash.buffer, bank->hash.size))); if (r < 0) return log_error_errno(r, "Failed to build digests array: %m"); @@ -3246,8 +3246,8 @@ static int verb_lock_secureboot_authority(int argc, char *argv[], void *userdata r = sd_json_variant_append_arraybo( &array, - SD_JSON_BUILD_PAIR("pcr", SD_JSON_BUILD_UNSIGNED(rec->pcr)), - SD_JSON_BUILD_PAIR("digests", SD_JSON_BUILD_VARIANT(digests))); + SD_JSON_BUILD_PAIR_UNSIGNED("pcr", rec->pcr), + SD_JSON_BUILD_PAIR_VARIANT("digests", digests)); if (r < 0) return log_error_errno(r, "Failed to build record array: %m"); } @@ -3529,7 +3529,7 @@ static int verb_lock_firmware(int argc, char *argv[], void *userdata) { LIST_FOREACH(banks, bank, rec->banks) { r = sd_json_variant_append_arraybo( &digests, - SD_JSON_BUILD_PAIR("hashAlg", SD_JSON_BUILD_STRING(tpm2_hash_alg_to_string(bank->algorithm))), + SD_JSON_BUILD_PAIR_STRING("hashAlg", tpm2_hash_alg_to_string(bank->algorithm)), SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(bank->hash.buffer, bank->hash.size))); if (r < 0) return log_error_errno(r, "Failed to build digests array: %m"); @@ -3537,8 +3537,8 @@ static int verb_lock_firmware(int argc, char *argv[], void *userdata) { r = sd_json_variant_append_arraybo( FLAGS_SET(separator_seen_mask, bit) ? &array_late : &array_early, - SD_JSON_BUILD_PAIR("pcr", SD_JSON_BUILD_UNSIGNED(rec->pcr)), - SD_JSON_BUILD_PAIR("digests", SD_JSON_BUILD_VARIANT(digests))); + SD_JSON_BUILD_PAIR_UNSIGNED("pcr", rec->pcr), + SD_JSON_BUILD_PAIR_VARIANT("digests", digests)); if (r < 0) return log_error_errno(r, "Failed to build record array: %m"); } @@ -3748,7 +3748,7 @@ static int verb_lock_pe(int argc, char *argv[], void *userdata) { r = sd_json_variant_append_arraybo( &digests, - SD_JSON_BUILD_PAIR("hashAlg", SD_JSON_BUILD_STRING(a)), + SD_JSON_BUILD_PAIR_STRING("hashAlg", a), SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(hash, hash_size))); if (r < 0) return log_error_errno(r, "Failed to build JSON digest object: %m"); @@ -3756,8 +3756,8 @@ static int verb_lock_pe(int argc, char *argv[], void *userdata) { r = sd_json_variant_append_arraybo( &array, - SD_JSON_BUILD_PAIR("pcr", SD_JSON_BUILD_UNSIGNED(i)), - SD_JSON_BUILD_PAIR("digests", SD_JSON_BUILD_VARIANT(digests))); + SD_JSON_BUILD_PAIR_UNSIGNED("pcr", i), + SD_JSON_BUILD_PAIR_VARIANT("digests", digests)); if (r < 0) return log_error_errno(r, "Failed to append record object: %m"); } @@ -3804,7 +3804,7 @@ static int verb_lock_uki(int argc, char *argv[], void *userdata) { r = sd_json_variant_append_arraybo( &pe_digests, - SD_JSON_BUILD_PAIR("hashAlg", SD_JSON_BUILD_STRING(a)), + SD_JSON_BUILD_PAIR_STRING("hashAlg", a), SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(peh, hash_sizes[i]))); if (r < 0) return log_error_errno(r, "Failed to build JSON digest object: %m"); @@ -3816,8 +3816,8 @@ static int verb_lock_uki(int argc, char *argv[], void *userdata) { r = sd_json_variant_append_arraybo( &array, - SD_JSON_BUILD_PAIR("pcr", SD_JSON_BUILD_UNSIGNED(TPM2_PCR_BOOT_LOADER_CODE)), - SD_JSON_BUILD_PAIR("digests", SD_JSON_BUILD_VARIANT(pe_digests))); + SD_JSON_BUILD_PAIR_UNSIGNED("pcr", TPM2_PCR_BOOT_LOADER_CODE), + SD_JSON_BUILD_PAIR_VARIANT("digests", pe_digests)); if (r < 0) return log_error_errno(r, "Failed to append record object: %m"); @@ -3839,7 +3839,7 @@ static int verb_lock_uki(int argc, char *argv[], void *userdata) { r = sd_json_variant_append_arraybo( §ion_digests, - SD_JSON_BUILD_PAIR("hashAlg", SD_JSON_BUILD_STRING(a)), + SD_JSON_BUILD_PAIR_STRING("hashAlg", a), SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(hash, hash_sizes[i]))); if (r < 0) return log_error_errno(r, "Failed to build JSON digest object: %m"); @@ -3860,8 +3860,8 @@ static int verb_lock_uki(int argc, char *argv[], void *userdata) { /* And then append a record for the section contents digests as well */ r = sd_json_variant_append_arraybo( &array, - SD_JSON_BUILD_PAIR("pcr", SD_JSON_BUILD_UNSIGNED(TPM2_PCR_KERNEL_BOOT /* =11 */)), - SD_JSON_BUILD_PAIR("digests", SD_JSON_BUILD_VARIANT(section_digests))); + SD_JSON_BUILD_PAIR_UNSIGNED("pcr", TPM2_PCR_KERNEL_BOOT), + SD_JSON_BUILD_PAIR_VARIANT("digests", section_digests)); if (r < 0) return log_error_errno(r, "Failed to append record object: %m"); } diff --git a/src/shared/metrics.c b/src/shared/metrics.c index 3b8965dbdc2d5..75a81789584e9 100644 --- a/src/shared/metrics.c +++ b/src/shared/metrics.c @@ -163,7 +163,7 @@ static int metric_build_send(MetricFamilyContext *context, const char *object, s return sd_varlink_replybo(context->link, SD_JSON_BUILD_PAIR_STRING("name", context->metric_family->name), JSON_BUILD_PAIR_STRING_NON_EMPTY("object", object), - SD_JSON_BUILD_PAIR("value", SD_JSON_BUILD_VARIANT(value)), + SD_JSON_BUILD_PAIR_VARIANT("value", value), JSON_BUILD_PAIR_VARIANT_NON_NULL("fields", fields)); } diff --git a/src/test/test-format-table.c b/src/test/test-format-table.c index 7b501d11e4217..4305f77224e66 100644 --- a/src/test/test-format-table.c +++ b/src/test/test-format-table.c @@ -395,13 +395,13 @@ TEST(json) { SD_JSON_BUILD_ARRAY( SD_JSON_BUILD_OBJECT( SD_JSON_BUILD_PAIR("foo_bar", JSON_BUILD_CONST_STRING("v1")), - SD_JSON_BUILD_PAIR("quux", SD_JSON_BUILD_UNSIGNED(4711)), - SD_JSON_BUILD_PAIR("zzz", SD_JSON_BUILD_BOOLEAN(true)), + SD_JSON_BUILD_PAIR_UNSIGNED("quux", 4711), + SD_JSON_BUILD_PAIR_BOOLEAN("zzz", true), SD_JSON_BUILD_PAIR("asdf-custom", SD_JSON_BUILD_NULL)), SD_JSON_BUILD_OBJECT( SD_JSON_BUILD_PAIR("foo_bar", SD_JSON_BUILD_STRV(STRV_MAKE("a", "b", "c"))), SD_JSON_BUILD_PAIR("quux", SD_JSON_BUILD_NULL), - SD_JSON_BUILD_PAIR("zzz", SD_JSON_BUILD_UNSIGNED(0755)), + SD_JSON_BUILD_PAIR_UNSIGNED("zzz", 0755), SD_JSON_BUILD_PAIR("asdf-custom", SD_JSON_BUILD_NULL))))); ASSERT_TRUE(sd_json_variant_equal(v, w)); @@ -624,23 +624,23 @@ TEST(signed_integers) { ASSERT_OK(sd_json_build(&b, SD_JSON_BUILD_ARRAY( SD_JSON_BUILD_OBJECT( - SD_JSON_BUILD_PAIR("int", SD_JSON_BUILD_INTEGER(-1)), - SD_JSON_BUILD_PAIR("int8", SD_JSON_BUILD_INTEGER(-1)), - SD_JSON_BUILD_PAIR("int16", SD_JSON_BUILD_INTEGER(-1)), - SD_JSON_BUILD_PAIR("int32", SD_JSON_BUILD_INTEGER(-1)), - SD_JSON_BUILD_PAIR("int64", SD_JSON_BUILD_INTEGER(-1))), + SD_JSON_BUILD_PAIR_INTEGER("int", -1), + SD_JSON_BUILD_PAIR_INTEGER("int8", -1), + SD_JSON_BUILD_PAIR_INTEGER("int16", -1), + SD_JSON_BUILD_PAIR_INTEGER("int32", -1), + SD_JSON_BUILD_PAIR_INTEGER("int64", -1)), SD_JSON_BUILD_OBJECT( - SD_JSON_BUILD_PAIR("int", SD_JSON_BUILD_INTEGER(INT_MAX)), - SD_JSON_BUILD_PAIR("int8", SD_JSON_BUILD_INTEGER(INT8_MAX)), - SD_JSON_BUILD_PAIR("int16", SD_JSON_BUILD_INTEGER(INT16_MAX)), - SD_JSON_BUILD_PAIR("int32", SD_JSON_BUILD_INTEGER(INT32_MAX)), - SD_JSON_BUILD_PAIR("int64", SD_JSON_BUILD_INTEGER(INT64_MAX))), + SD_JSON_BUILD_PAIR_INTEGER("int", INT_MAX), + SD_JSON_BUILD_PAIR_INTEGER("int8", INT8_MAX), + SD_JSON_BUILD_PAIR_INTEGER("int16", INT16_MAX), + SD_JSON_BUILD_PAIR_INTEGER("int32", INT32_MAX), + SD_JSON_BUILD_PAIR_INTEGER("int64", INT64_MAX)), SD_JSON_BUILD_OBJECT( - SD_JSON_BUILD_PAIR("int", SD_JSON_BUILD_INTEGER(INT_MIN)), - SD_JSON_BUILD_PAIR("int8", SD_JSON_BUILD_INTEGER(INT8_MIN)), - SD_JSON_BUILD_PAIR("int16", SD_JSON_BUILD_INTEGER(INT16_MIN)), - SD_JSON_BUILD_PAIR("int32", SD_JSON_BUILD_INTEGER(INT32_MIN)), - SD_JSON_BUILD_PAIR("int64", SD_JSON_BUILD_INTEGER(INT64_MIN)))))); + SD_JSON_BUILD_PAIR_INTEGER("int", INT_MIN), + SD_JSON_BUILD_PAIR_INTEGER("int8", INT8_MIN), + SD_JSON_BUILD_PAIR_INTEGER("int16", INT16_MIN), + SD_JSON_BUILD_PAIR_INTEGER("int32", INT32_MIN), + SD_JSON_BUILD_PAIR_INTEGER("int64", INT64_MIN))))); sd_json_variant_dump(b, SD_JSON_FORMAT_NEWLINE, stdout, NULL); ASSERT_TRUE(sd_json_variant_equal(a, b)); @@ -686,21 +686,21 @@ TEST(unsigned_integers) { ASSERT_OK(sd_json_build(&b, SD_JSON_BUILD_ARRAY( SD_JSON_BUILD_OBJECT( - SD_JSON_BUILD_PAIR("uint", SD_JSON_BUILD_UNSIGNED(0)), - SD_JSON_BUILD_PAIR("uint8", SD_JSON_BUILD_UNSIGNED(0)), - SD_JSON_BUILD_PAIR("uint16", SD_JSON_BUILD_UNSIGNED(0)), - SD_JSON_BUILD_PAIR("uint32", SD_JSON_BUILD_UNSIGNED(0)), - SD_JSON_BUILD_PAIR("uhex32", SD_JSON_BUILD_UNSIGNED(0)), - SD_JSON_BUILD_PAIR("uint64", SD_JSON_BUILD_UNSIGNED(0)), - SD_JSON_BUILD_PAIR("uhex64", SD_JSON_BUILD_UNSIGNED(0))), + SD_JSON_BUILD_PAIR_UNSIGNED("uint", 0), + SD_JSON_BUILD_PAIR_UNSIGNED("uint8", 0), + SD_JSON_BUILD_PAIR_UNSIGNED("uint16", 0), + SD_JSON_BUILD_PAIR_UNSIGNED("uint32", 0), + SD_JSON_BUILD_PAIR_UNSIGNED("uhex32", 0), + SD_JSON_BUILD_PAIR_UNSIGNED("uint64", 0), + SD_JSON_BUILD_PAIR_UNSIGNED("uhex64", 0)), SD_JSON_BUILD_OBJECT( - SD_JSON_BUILD_PAIR("uint", SD_JSON_BUILD_UNSIGNED(UINT_MAX)), - SD_JSON_BUILD_PAIR("uint8", SD_JSON_BUILD_UNSIGNED(UINT8_MAX)), - SD_JSON_BUILD_PAIR("uint16", SD_JSON_BUILD_UNSIGNED(UINT16_MAX)), - SD_JSON_BUILD_PAIR("uint32", SD_JSON_BUILD_UNSIGNED(UINT32_MAX)), - SD_JSON_BUILD_PAIR("uhex32", SD_JSON_BUILD_UNSIGNED(UINT32_MAX)), - SD_JSON_BUILD_PAIR("uint64", SD_JSON_BUILD_UNSIGNED(UINT64_MAX)), - SD_JSON_BUILD_PAIR("uhex64", SD_JSON_BUILD_UNSIGNED(UINT64_MAX)))))); + SD_JSON_BUILD_PAIR_UNSIGNED("uint", UINT_MAX), + SD_JSON_BUILD_PAIR_UNSIGNED("uint8", UINT8_MAX), + SD_JSON_BUILD_PAIR_UNSIGNED("uint16", UINT16_MAX), + SD_JSON_BUILD_PAIR_UNSIGNED("uint32", UINT32_MAX), + SD_JSON_BUILD_PAIR_UNSIGNED("uhex32", UINT32_MAX), + SD_JSON_BUILD_PAIR_UNSIGNED("uint64", UINT64_MAX), + SD_JSON_BUILD_PAIR_UNSIGNED("uhex64", UINT64_MAX))))); sd_json_variant_dump(b, SD_JSON_FORMAT_NEWLINE, stdout, NULL); ASSERT_TRUE(sd_json_variant_equal(a, b)); @@ -734,10 +734,10 @@ TEST(vertical) { ASSERT_OK(table_to_json(t, &a)); ASSERT_OK(sd_json_build(&b, SD_JSON_BUILD_OBJECT( - SD_JSON_BUILD_PAIR("pfft_aa", SD_JSON_BUILD_STRING("foo")), - SD_JSON_BUILD_PAIR("dimpfelmoser", SD_JSON_BUILD_UNSIGNED(1024)), - SD_JSON_BUILD_PAIR("custom-quux", SD_JSON_BUILD_STRING("asdf")), - SD_JSON_BUILD_PAIR("lllllllllllo", SD_JSON_BUILD_STRING("jjjjjjjjjjjjjjjjj"))))); + SD_JSON_BUILD_PAIR_STRING("pfft_aa", "foo"), + SD_JSON_BUILD_PAIR_UNSIGNED("dimpfelmoser", 1024), + SD_JSON_BUILD_PAIR_STRING("custom-quux", "asdf"), + SD_JSON_BUILD_PAIR_STRING("lllllllllllo", "jjjjjjjjjjjjjjjjj")))); ASSERT_TRUE(sd_json_variant_equal(a, b)); } diff --git a/src/test/test-varlink-idl.c b/src/test/test-varlink-idl.c index c6aca36677745..039d36a85e42d 100644 --- a/src/test/test-varlink-idl.c +++ b/src/test/test-varlink-idl.c @@ -334,8 +334,8 @@ TEST(validate_json) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; assert_se(sd_json_build(&v, SD_JSON_BUILD_OBJECT( - SD_JSON_BUILD_PAIR("a", SD_JSON_BUILD_STRING("x")), - SD_JSON_BUILD_PAIR("b", SD_JSON_BUILD_UNSIGNED(44)), + SD_JSON_BUILD_PAIR_STRING("a", "x"), + SD_JSON_BUILD_PAIR_UNSIGNED("b", 44), SD_JSON_BUILD_PAIR("d", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_UNSIGNED(5), SD_JSON_BUILD_UNSIGNED(7), SD_JSON_BUILD_UNSIGNED(107))), SD_JSON_BUILD_PAIR("g", SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("f", SD_JSON_BUILD_REAL(0.5f)))))) >= 0); diff --git a/src/test/test-varlink.c b/src/test/test-varlink.c index bf1390fba1dc4..186564198c0ad 100644 --- a/src/test/test-varlink.c +++ b/src/test/test-varlink.c @@ -45,7 +45,7 @@ static int method_something(sd_varlink *link, sd_json_variant *parameters, sd_va y = sd_json_variant_integer(b); - r = sd_json_build(&ret, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("sum", SD_JSON_BUILD_INTEGER(x + y)))); + r = sd_json_build(&ret, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_INTEGER("sum", x + y))); if (r < 0) return r; @@ -75,7 +75,7 @@ static int method_something_more(sd_varlink *link, sd_json_variant *parameters, for (int i = 0; i < 5; i++) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL; - r = sd_json_build(&w, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("sum", SD_JSON_BUILD_INTEGER(s.x + (s.y * i))))); + r = sd_json_build(&w, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_INTEGER("sum", s.x + (s.y * i)))); if (r < 0) return r; @@ -84,7 +84,7 @@ static int method_something_more(sd_varlink *link, sd_json_variant *parameters, return r; } - r = sd_json_build(&ret, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("sum", SD_JSON_BUILD_INTEGER(s.x + (s.y * 5))))); + r = sd_json_build(&ret, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_INTEGER("sum", s.x + (s.y * 5)))); if (r < 0) return r; @@ -125,7 +125,7 @@ static int method_passfd(sd_varlink *link, sd_json_variant *parameters, sd_varli ASSERT_OK(vv = memfd_new_and_seal_string("data", "miau")); ASSERT_OK(ww = memfd_new_and_seal_string("data", "wuff")); - r = sd_json_build(&ret, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("yo", SD_JSON_BUILD_INTEGER(88)))); + r = sd_json_build(&ret, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_INTEGER("yo", 88))); if (r < 0) return r; @@ -222,7 +222,7 @@ static void flood_test(const char *address) { ASSERT_OK(asprintf(&t, "flood-%zu", k)); ASSERT_OK(sd_varlink_set_description(connections[k], t)); ASSERT_OK(sd_varlink_attach_event(connections[k], e, k)); - ASSERT_OK(sd_varlink_sendb(connections[k], "io.test.Rubbish", SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_INTEGER(k))))); + ASSERT_OK(sd_varlink_sendb(connections[k], "io.test.Rubbish", SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_INTEGER("id", k)))); } /* Then, create one more, which should fail */ @@ -253,8 +253,8 @@ static void *thread(void *arg) { const char *error_id, *e; int x = 0; - ASSERT_OK(sd_json_build(&i, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("a", SD_JSON_BUILD_INTEGER(88)), - SD_JSON_BUILD_PAIR("b", SD_JSON_BUILD_INTEGER(99))))); + ASSERT_OK(sd_json_build(&i, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_INTEGER("a", 88), + SD_JSON_BUILD_PAIR_INTEGER("b", 99)))); ASSERT_OK(sd_varlink_connect_address(&c, arg)); ASSERT_OK(sd_varlink_set_description(c, "thread-client")); @@ -262,8 +262,8 @@ static void *thread(void *arg) { ASSERT_OK(sd_varlink_set_allow_fd_passing_output(c, true)); /* Test that client is able to perform two sequential sd_varlink_collect calls if first resulted in an error */ - ASSERT_OK(sd_json_build(&wrong, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("a", SD_JSON_BUILD_INTEGER(88)), - SD_JSON_BUILD_PAIR("c", SD_JSON_BUILD_INTEGER(99))))); + ASSERT_OK(sd_json_build(&wrong, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_INTEGER("a", 88), + SD_JSON_BUILD_PAIR_INTEGER("c", 99)))); ASSERT_OK(sd_varlink_collect(c, "io.test.DoSomethingMore", wrong, &j, &error_id)); ASSERT_STREQ(error_id, "org.varlink.service.InvalidParameter"); @@ -292,7 +292,7 @@ static void *thread(void *arg) { ASSERT_OK_EQ(sd_varlink_push_fd(c, fd2), 1); ASSERT_OK_EQ(sd_varlink_push_fd(c, fd3), 2); - ASSERT_OK(sd_varlink_callb(c, "io.test.PassFD", &o, &e, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("fd", SD_JSON_BUILD_STRING("whoop"))))); + ASSERT_OK(sd_varlink_callb(c, "io.test.PassFD", &o, &e, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_STRING("fd", "whoop")))); ASSERT_NULL(e); int fd4, fd5; @@ -302,7 +302,7 @@ static void *thread(void *arg) { test_fd(fd4, "miau", 4); test_fd(fd5, "wuff", 4); - ASSERT_OK(sd_varlink_callb(c, "io.test.PassFD", &o, &e, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("fdx", SD_JSON_BUILD_STRING("whoopx"))))); + ASSERT_OK(sd_varlink_callb(c, "io.test.PassFD", &o, &e, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_STRING("fdx", "whoopx")))); ASSERT_TRUE(sd_varlink_error_is_invalid_parameter(e, o, "fd")); ASSERT_OK(sd_varlink_callb(c, "io.test.IDontExist", &o, &e, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("x", SD_JSON_BUILD_REAL(5.5))))); @@ -371,8 +371,8 @@ TEST(chat) { ASSERT_OK(sd_varlink_server_attach_event(s, e, 0)); ASSERT_OK(sd_varlink_server_set_connections_max(s, OVERLOAD_CONNECTIONS)); - ASSERT_OK(sd_json_build(&v, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("a", SD_JSON_BUILD_INTEGER(7)), - SD_JSON_BUILD_PAIR("b", SD_JSON_BUILD_INTEGER(22))))); + ASSERT_OK(sd_json_build(&v, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_INTEGER("a", 7), + SD_JSON_BUILD_PAIR_INTEGER("b", 22)))); ASSERT_OK(sd_varlink_connect_address(&c, sp)); ASSERT_OK(sd_varlink_set_description(c, "main-client")); From 90f5d2a202b5f1d4e735abae4c1a35238e1b6746 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Wed, 4 Mar 2026 19:03:54 +0100 Subject: [PATCH 0097/1296] tree-wide: various fixlets suggested by Coccinelle --- src/basic/socket-util.c | 4 +--- src/import/pull.c | 3 +-- src/libsystemd-network/test-dhcp-server.c | 2 +- src/libsystemd/sd-varlink/sd-varlink.c | 2 +- src/login/logind-session-device.c | 3 +-- src/machine/machined-dbus.c | 6 ++---- src/mount/mount-tool.c | 4 +--- src/network/generator/network-generator.c | 3 +-- src/network/networkd-wwan.c | 7 ++----- src/oom/oomd-manager.c | 3 +-- src/resolve/resolved-dns-browse-services.c | 6 +++--- src/resolve/resolved-hook.c | 2 +- src/resolve/test-dns-packet-append.c | 2 +- src/resolve/test-dns-packet-extract.c | 2 +- src/resolve/test-dns-query.c | 2 +- src/resolve/test-dns-rr.c | 2 +- src/shared/discover-image.c | 4 +--- src/shared/hostname-setup.c | 4 ++-- src/shared/libfido2-util.c | 2 +- src/ssh-generator/ssh-issue.c | 2 +- src/systemctl/systemctl-compat-shutdown.c | 3 +-- src/sysupdate/sysupdate.c | 3 +-- src/udev/udev-builtin-dissect_image.c | 2 +- src/udev/udev-worker.c | 4 +--- 24 files changed, 29 insertions(+), 48 deletions(-) diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c index 1194eafc1b0cd..159486f7f1e52 100644 --- a/src/basic/socket-util.c +++ b/src/basic/socket-util.c @@ -1150,10 +1150,8 @@ int flush_accept(int fd) { r = fd_wait_for_event(fd, POLLIN, 0); if (r == -EINTR) continue; - if (r < 0) + if (r <= 0) return r; - if (r == 0) - return 0; if (iteration >= MAX_FLUSH_ITERATIONS) return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), diff --git a/src/import/pull.c b/src/import/pull.c index e2f816152698f..f8b90ad725a36 100644 --- a/src/import/pull.c +++ b/src/import/pull.c @@ -454,8 +454,7 @@ static int parse_argv(int argc, char *argv[]) { "64 hex character SHA256 hash required when specifying explicit checksum, %zu specified", n * 2); iovec_done(&arg_checksum); - arg_checksum.iov_base = TAKE_PTR(h); - arg_checksum.iov_len = n; + arg_checksum = IOVEC_MAKE(TAKE_PTR(h), n); arg_import_flags &= ~(IMPORT_PULL_SETTINGS|IMPORT_PULL_ROOTHASH|IMPORT_PULL_ROOTHASH_SIGNATURE|IMPORT_PULL_VERITY); arg_verify = _IMPORT_VERIFY_INVALID; diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c index 7789037011e7d..e84d800ad8198 100644 --- a/src/libsystemd-network/test-dhcp-server.c +++ b/src/libsystemd-network/test-dhcp-server.c @@ -68,7 +68,7 @@ static int test_basic(bool bind_to_interface) { r = sd_dhcp_server_start(server); /* skip test if running in an environment with no full networking support, CONFIG_PACKET not * compiled in kernel, nor af_packet module available. */ - if (r == -EPERM || r == -EAFNOSUPPORT) + if (IN_SET(r, -EPERM, -EAFNOSUPPORT)) return r; ASSERT_OK(r); diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index 534e141554404..f868f11850b57 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -3411,7 +3411,7 @@ _public_ int sd_varlink_set_allow_fd_passing_output(sd_varlink *v, int b) { if (r < 0) return r; - v->allow_fd_passing_output = !!b; + v->allow_fd_passing_output = b; return 1; } diff --git a/src/login/logind-session-device.c b/src/login/logind-session-device.c index 7129f823e8aad..137db8e93e1d8 100644 --- a/src/login/logind-session-device.c +++ b/src/login/logind-session-device.c @@ -105,9 +105,8 @@ static void sd_eviocrevoke(int fd) { if (errno == EINVAL) { log_warning_errno(errno, "Kernel does not support evdev-revocation, continuing without revoking device access: %m"); warned = true; - } else if (errno != ENODEV) { + } else if (errno != ENODEV) log_warning_errno(errno, "Failed to revoke evdev device, continuing without revoking device access: %m"); - } } } diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index 31237c811a9e7..ab7ca94fd01bd 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -316,10 +316,8 @@ static int machine_add_from_params( details, &manager->polkit_registry, error); - if (r < 0) - return r; - if (r == 0) - return 0; /* Will call us back */ + if (r <= 0) + return r; /* 0 means Polkit will call us back, see method_create_machine() */ } r = manager_add_machine(manager, name, &m); diff --git a/src/mount/mount-tool.c b/src/mount/mount-tool.c index b2ee90f4b7ada..d2af4688abb27 100644 --- a/src/mount/mount-tool.c +++ b/src/mount/mount-tool.c @@ -1321,10 +1321,8 @@ static int acquire_removable(sd_device *d) { return r; r = device_in_subsystem(d, "block"); - if (r < 0) + if (r <= 0) return r; - if (r == 0) - return 0; } if (parse_boolean(v) <= 0) diff --git a/src/network/generator/network-generator.c b/src/network/generator/network-generator.c index 0988aef93f14b..d0204366fb6ab 100644 --- a/src/network/generator/network-generator.c +++ b/src/network/generator/network-generator.c @@ -1439,12 +1439,11 @@ void netdev_dump(NetDev *netdev, FILE *f) { if (netdev->mtu > 0) fprintf(f, "MTUBytes=%" PRIu32 "\n", netdev->mtu); - if (streq(netdev->kind, "vlan")) { + if (streq(netdev->kind, "vlan")) fprintf(f, "\n[VLAN]\n" "Id=%u\n", netdev->vlan_id); - } } void link_dump(Link *link, FILE *f) { diff --git a/src/network/networkd-wwan.c b/src/network/networkd-wwan.c index ccd72d0ee31b3..b84ace130102a 100644 --- a/src/network/networkd-wwan.c +++ b/src/network/networkd-wwan.c @@ -152,11 +152,8 @@ Modem* modem_free(Modem *modem) { if (!modem) return NULL; - if (modem->bearers_by_name) - hashmap_free(modem->bearers_by_name); - - if (modem->bearers_by_path) - hashmap_free(modem->bearers_by_path); + hashmap_free(modem->bearers_by_name); + hashmap_free(modem->bearers_by_path); if (modem->manager) hashmap_remove_value(modem->manager->modems_by_path, modem->path, modem); diff --git a/src/oom/oomd-manager.c b/src/oom/oomd-manager.c index b2142dd43cf33..97ad9c0a9f77f 100644 --- a/src/oom/oomd-manager.c +++ b/src/oom/oomd-manager.c @@ -422,7 +422,7 @@ static int monitor_swap_contexts_handler(sd_event_source *s, uint64_t usec, void if (r < 0) log_error_errno(r, "Failed to select any cgroups based on swap: %m"); else { - if (selected && r > 0) { + if (selected && r > 0) log_notice("Marked %s for killing due to memory used (%"PRIu64") / total (%"PRIu64") and " "swap used (%"PRIu64") / total (%"PRIu64") being more than " PERMYRIAD_AS_PERCENT_FORMAT_STR, @@ -430,7 +430,6 @@ static int monitor_swap_contexts_handler(sd_event_source *s, uint64_t usec, void m->system_context.mem_used, m->system_context.mem_total, m->system_context.swap_used, m->system_context.swap_total, PERMYRIAD_AS_PERCENT_FORMAT_VAL(m->swap_used_limit_permyriad)); - } return 0; } } diff --git a/src/resolve/resolved-dns-browse-services.c b/src/resolve/resolved-dns-browse-services.c index f08b83cf5dcb4..68dcbee349bb5 100644 --- a/src/resolve/resolved-dns-browse-services.c +++ b/src/resolve/resolved-dns-browse-services.c @@ -441,9 +441,9 @@ int mdns_manage_services_answer(DnsServiceBrowser *sb, DnsAnswer *answer, int ow browse_service_update_event_to_string( BROWSE_SERVICE_UPDATE_REMOVED)), SD_JSON_BUILD_PAIR_INTEGER("family", owner_family), - SD_JSON_BUILD_PAIR_STRING("name", name ?: ""), - SD_JSON_BUILD_PAIR_STRING("type", type ?: ""), - SD_JSON_BUILD_PAIR_STRING("domain", domain ?: ""), + SD_JSON_BUILD_PAIR_STRING("name", strempty(name)), + SD_JSON_BUILD_PAIR_STRING("type", strempty(type)), + SD_JSON_BUILD_PAIR_STRING("domain", strempty(domain)), SD_JSON_BUILD_PAIR_INTEGER("ifindex", ifindex)); if (r < 0) { log_error_errno(r, "Failed to build JSON for removed service: %m"); diff --git a/src/resolve/resolved-hook.c b/src/resolve/resolved-hook.c index 5cec36c3aa2d6..4938e2d2a104d 100644 --- a/src/resolve/resolved-hook.c +++ b/src/resolve/resolved-hook.c @@ -49,7 +49,7 @@ static Hook* hook_free(Hook *h) { if (!h) return NULL; - mfree(h->socket_path); + free(h->socket_path); sd_varlink_unref(h->filter_link); set_free(h->idle_links); diff --git a/src/resolve/test-dns-packet-append.c b/src/resolve/test-dns-packet-append.c index eaffc57184e96..862f3bea69692 100644 --- a/src/resolve/test-dns-packet-append.c +++ b/src/resolve/test-dns-packet-append.c @@ -1180,7 +1180,7 @@ static DnsSvcParam* add_svcb_param(DnsResourceRecord *rr, uint16_t key, const ch param->key = key; param->length = len; - if (value != NULL) + if (value) memcpy(param->value, value, len); LIST_APPEND(params, rr->svcb.params, param); diff --git a/src/resolve/test-dns-packet-extract.c b/src/resolve/test-dns-packet-extract.c index dfbfd832f2b86..ca90afb7eb1dc 100644 --- a/src/resolve/test-dns-packet-extract.c +++ b/src/resolve/test-dns-packet-extract.c @@ -3902,7 +3902,7 @@ static DnsSvcParam* add_svcb_param(DnsResourceRecord *rr, uint16_t key, const ch param->key = key; param->length = len; - if (value != NULL) + if (value) memcpy(param->value, value, len); LIST_APPEND(params, rr->svcb.params, param); diff --git a/src/resolve/test-dns-query.c b/src/resolve/test-dns-query.c index 3fcc93d9e4fab..9c1e2a73a55a9 100644 --- a/src/resolve/test-dns-query.c +++ b/src/resolve/test-dns-query.c @@ -858,7 +858,7 @@ static void exercise_dns_query_go(GoConfig *cfg, void (*check_query)(DnsQuery *q ASSERT_NOT_NULL(query); ASSERT_TRUE(dns_query_go(query)); - if (check_query != NULL) + if (check_query) check_query(query); } diff --git a/src/resolve/test-dns-rr.c b/src/resolve/test-dns-rr.c index c679e45beeb94..e45f1d34238b0 100644 --- a/src/resolve/test-dns-rr.c +++ b/src/resolve/test-dns-rr.c @@ -2043,7 +2043,7 @@ static DnsSvcParam* add_svcb_param(DnsResourceRecord *rr, uint16_t key, const ch param->key = key; param->length = len; - if (value != NULL) + if (value) memcpy(param->value, value, len); LIST_APPEND(params, rr->svcb.params, param); diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c index 4c0752c979128..dd924523158ba 100644 --- a/src/shared/discover-image.c +++ b/src/shared/discover-image.c @@ -2187,10 +2187,8 @@ int image_setup_pool(RuntimeScope scope, ImageClass class, bool use_btrfs_subvol return r; r = check_btrfs(pool); - if (r < 0) + if (r <= 0) return r; - if (r == 0) - return 0; if (!use_btrfs_subvol) return 0; diff --git a/src/shared/hostname-setup.c b/src/shared/hostname-setup.c index 50a0f077fa7bf..ee88e2a877b77 100644 --- a/src/shared/hostname-setup.c +++ b/src/shared/hostname-setup.c @@ -284,8 +284,8 @@ int hostname_substitute_wildcards(char *name) { struct siphash state; siphash24_init(&state, key.bytes); - siphash24_compress(&mid, sizeof(mid), &state); - siphash24_compress(&counter, sizeof(counter), &state); /* counter mode */ + siphash24_compress_typesafe(mid, &state); + siphash24_compress_typesafe(counter, &state); /* counter mode */ h = siphash24_finalize(&state); left_bits = sizeof(h) * 8; counter++; diff --git a/src/shared/libfido2-util.c b/src/shared/libfido2-util.c index 338b52881e8ab..b4aee235a9923 100644 --- a/src/shared/libfido2-util.c +++ b/src/shared/libfido2-util.c @@ -1244,7 +1244,7 @@ int fido2_list_devices(void) { goto finish; } - if (table_get_rows(t) > 1) + if (!table_isempty(t)) printf("\n" "%1$sLegend: RK %2$s Resident key%3$s\n" "%1$s CLIENTPIN %2$s PIN request%3$s\n" diff --git a/src/ssh-generator/ssh-issue.c b/src/ssh-generator/ssh-issue.c index 852dabdb852d1..d2f02bd3d8a4d 100644 --- a/src/ssh-generator/ssh-issue.c +++ b/src/ssh-generator/ssh-issue.c @@ -95,7 +95,7 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_ISSUE_PATH: - if (isempty(optarg) || streq(optarg, "-")) { + if (empty_or_dash(optarg)) { arg_issue_path = mfree(arg_issue_path); arg_issue_stdout = true; break; diff --git a/src/systemctl/systemctl-compat-shutdown.c b/src/systemctl/systemctl-compat-shutdown.c index 877ea8378b375..cfc917073ae0e 100644 --- a/src/systemctl/systemctl-compat-shutdown.c +++ b/src/systemctl/systemctl-compat-shutdown.c @@ -108,14 +108,13 @@ static int parse_shutdown_time_spec(const char *t, usec_t *ret) { if (r < 0) return r; - if (tm.tm_hour != requested_hour || tm.tm_min != requested_min) { + if (tm.tm_hour != requested_hour || tm.tm_min != requested_min) log_warning("Requested shutdown time %02d:%02d does not exist. " "Rescheduling to %02d:%02d.", requested_hour, requested_min, tm.tm_hour, tm.tm_min); - } } *ret = s; diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 7693ec373caac..69848c3fcb7eb 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -397,13 +397,12 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags assert(flags == UPDATE_INSTALLED); match = resource_find_instance(&t->target, cursor); - if (!match && !(extra_flags & (UPDATE_PARTIAL|UPDATE_PENDING))) { + if (!match && !(extra_flags & (UPDATE_PARTIAL|UPDATE_PENDING))) /* When we're looking for installed versions, let's be robust and treat * an incomplete installation as an installation. Otherwise, there are * situations that can lead to sysupdate wiping the currently booted OS. * See https://github.com/systemd/systemd/issues/33339 */ extra_flags |= UPDATE_INCOMPLETE; - } } cursor_instances[k] = match; diff --git a/src/udev/udev-builtin-dissect_image.c b/src/udev/udev-builtin-dissect_image.c index 8b706a7f9a32f..0fd58ef2f5606 100644 --- a/src/udev/udev-builtin-dissect_image.c +++ b/src/udev/udev-builtin-dissect_image.c @@ -192,7 +192,7 @@ static int verb_probe(UdevEvent *event, sd_device *dev) { (void) image_policy_to_string(image_policy, /* simplify= */ false, &a); (void) image_policy_to_string(image_policy_mangled, /* simplify= */ false, &b); - log_device_debug_errno(dev, ERFKILL, "Couldn't dissect block device with regular policy '%s', retrying with policy where root/usr are set to ignore '%s'.", a, b); + log_device_debug_errno(dev, SYNTHETIC_ERRNO(ERFKILL), "Couldn't dissect block device with regular policy '%s', retrying with policy where root/usr are set to ignore '%s'.", a, b); } r = dissect_loop_device( diff --git a/src/udev/udev-worker.c b/src/udev/udev-worker.c index 67617203d765f..a0c95d2ce330d 100644 --- a/src/udev/udev-worker.c +++ b/src/udev/udev-worker.c @@ -157,10 +157,8 @@ static int worker_mark_block_device_read_only(sd_device *dev) { return 0; r = device_in_subsystem(dev, "block"); - if (r < 0) + if (r <= 0) return r; - if (r == 0) - return 0; /* Exclude synthetic devices for now, this is supposed to be a safety feature to avoid modification * of physical devices, and what sits on top of those doesn't really matter if we don't allow the From ab73333c43aeec919fadf60b22e5cee7c379cdf2 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Wed, 4 Mar 2026 20:56:30 +0100 Subject: [PATCH 0098/1296] core: drop unused errno from debug message And properly guard unset parameters. --- src/core/varlink-unit.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index 261ade0e9a3c6..95ac16cb85916 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -408,13 +408,11 @@ static void unit_lookup_parameters_done(UnitLookupParameters *p) { } static int varlink_error_conflict_lookup_parameters(sd_varlink *v, const UnitLookupParameters *p) { - log_debug_errno( - ESRCH, - "Searching unit by lookup parameters name='%s' pid="PID_FMT" cgroup='%s' invocationID='%s' resulted in multiple different units", - p->name, - p->pidref.pid, - p->cgroup, - sd_id128_is_null(p->invocation_id) ? "" : SD_ID128_TO_UUID_STRING(p->invocation_id)); + log_debug("Searching unit by lookup parameters name='%s' pid='"PID_FMT"' cgroup='%s' invocationID='%s' resulted in multiple different units", + strnull(p->name), + pidref_is_set(&p->pidref) ? p->pidref.pid : 0, + strnull(p->cgroup), + sd_id128_is_null(p->invocation_id) ? "" : SD_ID128_TO_UUID_STRING(p->invocation_id)); return varlink_error_no_such_unit(v, /* name= */ NULL); } From f2b1010d99ce567fdb770812fa91b20cbf5b8078 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 5 Mar 2026 14:28:14 +0100 Subject: [PATCH 0099/1296] update TODO --- TODO | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/TODO b/TODO index 71af3a5390a9a..dc37ecb79f728 100644 --- a/TODO +++ b/TODO @@ -137,6 +137,13 @@ Features: * on first login of a user, measure its identity to some nvpcr +* optionally spawn an swtpm instance if a system doesn't have a native tpm, do + it via the tpm generator + +* add a secret key logic to sd-stub, that uses early-boot efi variables for + storing, that can be used as fallback logic for tpm-less systems for disk + encryption, and swtpm state encryption. + * sd-lldp: pick up 802.3 maximum frame size/mtu, to be able to detect jumbo frame capable networks From 8e48881f7510ffaf5a575aa17f156982a0431d39 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 5 Mar 2026 15:44:34 +0100 Subject: [PATCH 0100/1296] update TODO --- TODO | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/TODO b/TODO index dc37ecb79f728..ad70d8d718562 100644 --- a/TODO +++ b/TODO @@ -121,6 +121,13 @@ Deprecations and removals: Features: +* make systemd work without /bin/sh and associated shell tools around + - logind + agetty what to do? + - sushell + - get rid of /bin/rm in ExecStart= of system-update-cleanup.service + - make sure debug shell service has a nice failure mode, prints a message and reboots + - varlink interface for "systemctl start" and friends + * drop NV_ORDERLY flag from the product uuid nvpcr. Effect of the flag is that it pushes the thing into TPM RAM, but a TPM usually has very little of that, less than NVRAM. hence setting the flag amplifies space issues. Unsetting the From c6b2ab09e70b3a531f111678ce1c8775bed540fb Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 5 Mar 2026 16:34:16 +0100 Subject: [PATCH 0101/1296] update TODO --- TODO | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/TODO b/TODO index ad70d8d718562..42506aba8b810 100644 --- a/TODO +++ b/TODO @@ -121,11 +121,12 @@ Deprecations and removals: Features: -* make systemd work without /bin/sh and associated shell tools around - - logind + agetty what to do? - - sushell +* make systemd work nicely without /bin/sh, logins and associated shell tools around + - add a small unit that just prints "boot complete" which we can pull in + wherever we pull in getty@1.service, but is conditioned on /bin/sh being + gone. - get rid of /bin/rm in ExecStart= of system-update-cleanup.service - - make sure debug shell service has a nice failure mode, prints a message and reboots + - make sure debug shell service (sushell) has a nice failure mode, prints a message and reboots - varlink interface for "systemctl start" and friends * drop NV_ORDERLY flag from the product uuid nvpcr. Effect of the flag is that From 0c98e432d1def1e8428dbead50dc629ed0645366 Mon Sep 17 00:00:00 2001 From: Michal Sekletar Date: Wed, 25 Feb 2026 19:45:55 +0100 Subject: [PATCH 0102/1296] core: cleanup unit's dropin directories from global cache When user creates dropin files via API (e.g. systemctl set-property ...) we put the dropin directory path into unit_path_cache. Drop those directories from the cache in unit_free() and prevent memory leak. Follow-up for fce94c5c563b8f6ede2b8f7f283d2d2faff4e062. --- src/core/unit.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/core/unit.c b/src/core/unit.c index bb3430186cab0..ab0db25687826 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -677,6 +677,8 @@ static void unit_remove_transient(Unit *u) { if (!u->transient) return; + const char *dropin_directory = strjoina(u->id, ".d"); + STRV_FOREACH(i, u->dropin_paths) { _cleanup_free_ char *p = NULL, *pp = NULL; @@ -690,6 +692,10 @@ static void unit_remove_transient(Unit *u) { if (!path_equal(u->manager->lookup_paths.transient, pp)) continue; + /* Drop the transient drop-in directory also from unit path cache. */ + if (path_equal(last_path_component(p), dropin_directory)) + free(set_remove(u->manager->unit_path_cache, p)); + (void) unlink(*i); (void) rmdir(p); } From f8ed94ea9915d67af47954b48d6a9a4d755b6d8e Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 5 Mar 2026 14:20:06 +0100 Subject: [PATCH 0103/1296] boot: Make missing CHID DTB match a debug message instead of an error With distributions like Ubuntu and Fedora using systemd-stub to auto load DTB's on Windows on ARM laptops, the CHID DTB match failing is expected when that same UKI is instead booted on an ARM SystemReady system where no DTB is necessary. In the ARM SystemReady case showing a big red error message is undesirable and leads to confused users and bug-reports. Lower the message to debug level when the status is EFI_NOT_FOUND to avoid these false positive error messages. Link: https://bugzilla.redhat.com/show_bug.cgi?id=2444759 --- src/boot/pe.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/boot/pe.c b/src/boot/pe.c index 397a7a69404ba..255e8539de1d5 100644 --- a/src/boot/pe.c +++ b/src/boot/pe.c @@ -396,7 +396,8 @@ static void pe_locate_sections( EFI_STATUS err = chid_match(hwids, hwids_section[0].memory_size, DEVICE_TYPE_DEVICETREE, &device); if (err != EFI_SUCCESS) { - log_error_status(err, "HWID matching failed, no DT blob will be selected: %m"); + log_full(err, (err == EFI_NOT_FOUND) ? LOG_DEBUG : LOG_ERR, + "HWID matching failed, no DT blob will be selected: %m"); hwids = NULL; } } From df51a04a751eff890befa0430cc5cbc6204e642a Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Thu, 5 Mar 2026 16:40:54 +0100 Subject: [PATCH 0104/1296] path-util: drop unused paths_check_timestamp() --- src/basic/path-util.c | 37 ------------------------------------- src/basic/path-util.h | 2 -- 2 files changed, 39 deletions(-) diff --git a/src/basic/path-util.c b/src/basic/path-util.c index 0394d26420c58..8d60365767f41 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -17,7 +17,6 @@ #include "stat-util.h" #include "string-util.h" #include "strv.h" -#include "time-util.h" bool is_path(const char *p) { if (!p) /* A NULL pointer is definitely not a path */ @@ -785,42 +784,6 @@ int find_executable_full( return last_error; } -bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) { - bool changed = false, originally_unset; - - assert(timestamp); - - if (!paths) - return false; - - originally_unset = *timestamp == 0; - - STRV_FOREACH(i, paths) { - struct stat stats; - usec_t u; - - if (stat(*i, &stats) < 0) - continue; - - u = timespec_load(&stats.st_mtim); - - /* check first */ - if (*timestamp >= u) - continue; - - log_debug(originally_unset ? "Loaded timestamp for '%s'." : "Timestamp of '%s' changed.", *i); - - /* update timestamp */ - if (update) { - *timestamp = u; - changed = true; - } else - return true; - } - - return changed; -} - static int executable_is_good(const char *executable) { _cleanup_free_ char *p = NULL, *d = NULL; int r; diff --git a/src/basic/path-util.h b/src/basic/path-util.h index d70fc3b1bc686..e2e80ae91f7e7 100644 --- a/src/basic/path-util.h +++ b/src/basic/path-util.h @@ -105,8 +105,6 @@ static inline int find_executable(const char *name, char **ret_filename) { return find_executable_full(name, /* root= */ NULL, NULL, true, ret_filename, NULL); } -bool paths_check_timestamp(const char* const* paths, usec_t *paths_ts_usec, bool update); - int fsck_exists(void); int fsck_exists_for_fstype(const char *fstype); From 3e5a865c3f1d4bd11bb73ab2abc58e6b5f298b9e Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 5 Mar 2026 05:17:04 +0900 Subject: [PATCH 0105/1296] gitignore: ignore new default mkosi tools directories The default place has been changed since https://github.com/systemd/mkosi/commit/e9abfab744340dd2f608b589a9252a3e53b071c3 --- .gitignore | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index c5d98a4ece9a4..5c5f1ed586b59 100644 --- a/.gitignore +++ b/.gitignore @@ -24,11 +24,13 @@ __pycache__/ /ID /build* /install-tree -/mkosi/mkosi.key /mkosi/mkosi.crt +/mkosi/mkosi.key +/mkosi/mkosi.local.conf +/mkosi/mkosi.tools +/mkosi/mkosi.tools.manifest /mkosi.tools/ /mkosi.tools.manifest -/mkosi/mkosi.local.conf /tags .dir-locals-2.el .vscode/ From 535c6ef393d1b4a456944152f909063f3bb8b9d1 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 6 Mar 2026 03:10:21 +0900 Subject: [PATCH 0106/1296] po: update Japanese translations --- po/ja.po | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/po/ja.po b/po/ja.po index bd58347ec0821..3ea52c8b47037 100644 --- a/po/ja.po +++ b/po/ja.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" +"POT-Creation-Date: 2026-03-06 03:02+0900\n" "PO-Revision-Date: 2025-03-17 03:11+0000\n" "Last-Translator: Y T \n" "Language-Team: Japanese Date: Sun, 30 Nov 2025 16:08:49 +1030 Subject: [PATCH 0107/1296] pcrlock: Record predictions at start of component range Currently pcrlock won't predict PCR values that would be present at the start of the requested location range (unless there are no events for that PCR in the location range). This means predictions for the default range 760:940, which is intended to start just after entering the initrd, are not actually possible to fulfill until after the initrd is exited (or possibly even later, depending on what other events are recorded). Fix this by recording predictions immediately prior to processing components after the start point. Fixes #39946 --- src/pcrlock/pcrlock.c | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index a02846e785dbd..ab97c1a754e30 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -4004,8 +4004,7 @@ static int pcr_prediction_add_result( Tpm2PCRPrediction *context, Tpm2PCRPredictionResult *result, uint32_t pcr, - const char *path, - size_t offset) { + const char *path) { _cleanup_free_ Tpm2PCRPredictionResult *copy = NULL; int r; @@ -4040,18 +4039,11 @@ static const EVP_MD* evp_from_tpm2_alg(uint16_t alg) { } static int event_log_component_variant_calculate( - Tpm2PCRPrediction *context, Tpm2PCRPredictionResult *result, - EventLogComponent *component, EventLogComponentVariant *variant, - uint32_t pcr, - const char *path) { + uint32_t pcr) { - int r; - - assert(context); assert(result); - assert(component); assert(variant); FOREACH_ARRAY(rr, variant->records, variant->n_records) { @@ -4107,13 +4099,6 @@ static int event_log_component_variant_calculate( assert(l == (unsigned) sz); } - - /* This is a valid result once we hit the start location */ - if (arg_location_start && strcmp(component->id, arg_location_start) >= 0) { - r = pcr_prediction_add_result(context, result, pcr, path, rr - variant->records); - if (r < 0) - return r; - } } return 0; @@ -4137,7 +4122,7 @@ static int event_log_predict_pcrs( /* Check if we reached the end of the components, generate a result, and backtrack */ if (component_index >= el->n_components || (arg_location_end && strcmp(el->components[component_index]->id, arg_location_end) > 0)) { - r = pcr_prediction_add_result(context, parent_result, pcr, path, /* offset= */ 0); + r = pcr_prediction_add_result(context, parent_result, pcr, path); if (r < 0) return r; @@ -4146,6 +4131,13 @@ static int event_log_predict_pcrs( component = ASSERT_PTR(el->components[component_index]); + /* Check if we are just about to process a component after start, if so record a result and continue. */ + if (arg_location_start && strcmp(component->id, arg_location_start) > 0) { + r = pcr_prediction_add_result(context, parent_result, pcr, path); + if (r < 0) + return r; + } + FOREACH_ARRAY(ii, component->variants, component->n_variants) { _cleanup_free_ Tpm2PCRPredictionResult *result = NULL; EventLogComponentVariant *variant = *ii; @@ -4169,12 +4161,9 @@ static int event_log_predict_pcrs( return log_oom(); r = event_log_component_variant_calculate( - context, result, - component, variant, - pcr, - subpath); + pcr); if (r < 0) return r; From 88ed85137cd8aaf75ac3d821d37e51fede63e971 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 5 Mar 2026 17:19:19 +0000 Subject: [PATCH 0108/1296] libcrypt: also try to dlopen libcrypt.so.1.1 On top of libcrypt.so.2 and libcrypt.so.1, also try libcrypt.so.1.1 as a third fallback. This is used on debian alpha, and it was reported that it is intended to ship like that, with a different SONAME than other architectures: https://packages.debian.org/sid/alpha/libcrypt1/filelist --- src/shared/libcrypt-util.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/shared/libcrypt-util.c b/src/shared/libcrypt-util.c index 85069314be497..4760d66c92a93 100644 --- a/src/shared/libcrypt-util.c +++ b/src/shared/libcrypt-util.c @@ -30,25 +30,27 @@ int dlopen_libcrypt(void) { if (cached < 0) return cached; /* Already tried, and failed. */ - /* Several distributions like Debian/Ubuntu and OpenSUSE provide libxcrypt as libcrypt.so.1, - * while others like Fedora/CentOS and Arch provide it as libcrypt.so.2. */ + /* Several distributions like Debian/Ubuntu and OpenSUSE provide libxcrypt as libcrypt.so.1 + * (libcrypt.so.1.1 on some architectures), while others like Fedora/CentOS and Arch provide it as + * libcrypt.so.2. */ ELF_NOTE_DLOPEN("crypt", "Support for hashing passwords", ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, - "libcrypt.so.2", "libcrypt.so.1"); + "libcrypt.so.2", "libcrypt.so.1", "libcrypt.so.1.1"); _cleanup_(dlclosep) void *dl = NULL; - r = dlopen_safe("libcrypt.so.2", &dl, /* reterr_dlerror= */ NULL); - if (r < 0) { - const char *dle = NULL; - r = dlopen_safe("libcrypt.so.1", &dl, &dle); - if (r < 0) { - log_debug_errno(r, "libcrypt.so.2/libcrypt.so.1 is not available: %s", dle ?: STRERROR(r)); - return (cached = -EOPNOTSUPP); /* turn into recognizable error */ + const char *dle = NULL; + FOREACH_STRING(soname, "libcrypt.so.2", "libcrypt.so.1", "libcrypt.so.1.1") { + r = dlopen_safe(soname, &dl, &dle); + if (r >= 0) { + log_debug("Loaded '%s' via dlopen().", soname); + break; } - log_debug("Loaded 'libcrypt.so.1' via dlopen()"); - } else - log_debug("Loaded 'libcrypt.so.2' via dlopen()"); + } + if (r < 0) { + log_debug_errno(r, "Failed to load libcrypt: %s", dle ?: STRERROR(r)); + return (cached = -EOPNOTSUPP); /* turn into recognizable error */ + } r = dlsym_many_or_warn( dl, LOG_DEBUG, From ae4c3904afedc71ff7fa890206fc1396d67d2b67 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Thu, 5 Mar 2026 18:51:48 +0100 Subject: [PATCH 0109/1296] boot/pe: remove unneeded parens Follow-up for f8ed94ea9915d67af47954b48d6a9a4d755b6d8e --- src/boot/pe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/boot/pe.c b/src/boot/pe.c index 255e8539de1d5..4c5dfa0d7af47 100644 --- a/src/boot/pe.c +++ b/src/boot/pe.c @@ -396,7 +396,7 @@ static void pe_locate_sections( EFI_STATUS err = chid_match(hwids, hwids_section[0].memory_size, DEVICE_TYPE_DEVICETREE, &device); if (err != EFI_SUCCESS) { - log_full(err, (err == EFI_NOT_FOUND) ? LOG_DEBUG : LOG_ERR, + log_full(err, err == EFI_NOT_FOUND ? LOG_DEBUG : LOG_ERR, "HWID matching failed, no DT blob will be selected: %m"); hwids = NULL; } From e65a6e26ad4e57d4532266f3c85f05ce8f190740 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Thu, 5 Mar 2026 17:13:15 +0100 Subject: [PATCH 0110/1296] core/varlink-unit: distinguish PIDREF_AUTOMATIC from unset Follow-up for ab73333c43aeec919fadf60b22e5cee7c379cdf2 Methods that take numeric pid values use 0 to denote the peer, hence let's log about 0 on PIDREF_AUTOMATIC, -1 if truly unset. --- src/core/varlink-unit.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index 95ac16cb85916..d222148c77c3d 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -408,9 +408,9 @@ static void unit_lookup_parameters_done(UnitLookupParameters *p) { } static int varlink_error_conflict_lookup_parameters(sd_varlink *v, const UnitLookupParameters *p) { - log_debug("Searching unit by lookup parameters name='%s' pid='"PID_FMT"' cgroup='%s' invocationID='%s' resulted in multiple different units", + log_debug("Unit lookup by parameters name='%s' pid='"PID_FMT"' cgroup='%s' invocationID='%s' resulted in multiple different units.", strnull(p->name), - pidref_is_set(&p->pidref) ? p->pidref.pid : 0, + pidref_is_automatic(&p->pidref) ? 0 : pidref_is_set(&p->pidref) ? p->pidref.pid : (pid_t) -1, strnull(p->cgroup), sd_id128_is_null(p->invocation_id) ? "" : SD_ID128_TO_UUID_STRING(p->invocation_id)); From 8903e6b5f793d0a67f519a8b62234c167a013694 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 5 Mar 2026 06:48:25 +0900 Subject: [PATCH 0111/1296] tree-wide: suppress misc-use-internal-linkage warnings Suppress warnings like the following from clang tidy: ``` ../src/boot/addon.c:11:19: error: function 'efi_main' can be made static to enforce internal linkage [misc-use-internal-linkage,-warnings-as-errors] 11 | EFIAPI EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *system_table); | ^ ``` Some warnings are suppressed simply by setting comments to ignore the warning, some are by making global variables static, or include a suitable header. --- src/analyze/test-verify.c | 1 + src/basic/compress.c | 2 ++ src/boot/addon.c | 1 + src/boot/boot.c | 1 + src/boot/efi-log.c | 6 ++++++ src/boot/efi-string.c | 2 ++ src/boot/generate-hwids-section.py | 3 +++ src/boot/stub.c | 1 + src/libsystemd-network/test-dhcp-client.c | 2 +- src/libsystemd/sd-bus/test-bus-error.c | 4 ++-- src/test/test-chid.c | 2 +- src/test/test-exec-util.c | 2 +- src/test/test-hashmap.c | 4 ++++ src/test/test-load-fragment.c | 2 +- 14 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/analyze/test-verify.c b/src/analyze/test-verify.c index 8355ae6a807f5..048d866dbe73c 100644 --- a/src/analyze/test-verify.c +++ b/src/analyze/test-verify.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "analyze.h" #include "analyze-verify-util.h" #include "execute.h" #include "tests.h" diff --git a/src/basic/compress.c b/src/basic/compress.c index 82d856aa06783..c10448938e071 100644 --- a/src/basic/compress.c +++ b/src/basic/compress.c @@ -46,10 +46,12 @@ static DLSYM_PROTOTYPE(LZ4F_freeDecompressionContext) = NULL; static DLSYM_PROTOTYPE(LZ4F_isError) = NULL; static DLSYM_PROTOTYPE(LZ4_compress_HC) = NULL; /* These are used in test-compress.c so we don't make them static. */ +// NOLINTBEGIN(misc-use-internal-linkage) DLSYM_PROTOTYPE(LZ4_compress_default) = NULL; DLSYM_PROTOTYPE(LZ4_decompress_safe) = NULL; DLSYM_PROTOTYPE(LZ4_decompress_safe_partial) = NULL; DLSYM_PROTOTYPE(LZ4_versionNumber) = NULL; +// NOLINTEND(misc-use-internal-linkage) DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(LZ4F_compressionContext_t, sym_LZ4F_freeCompressionContext, LZ4F_freeCompressionContextp, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(LZ4F_decompressionContext_t, sym_LZ4F_freeDecompressionContext, LZ4F_freeDecompressionContextp, NULL); diff --git a/src/boot/addon.c b/src/boot/addon.c index 95b29daf5514a..17a361436127d 100644 --- a/src/boot/addon.c +++ b/src/boot/addon.c @@ -8,6 +8,7 @@ DECLARE_NOALLOC_SECTION(".sdmagic", "#### LoaderInfo: systemd-addon " GIT_VERSIO /* This is intended to carry data, not to be executed */ +// NOLINTNEXTLINE(misc-use-internal-linkage) EFIAPI EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *system_table); EFIAPI EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *system_table) { return EFI_UNSUPPORTED; diff --git a/src/boot/boot.c b/src/boot/boot.c index cdf36b9520305..db5cfd989df23 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -3255,4 +3255,5 @@ static EFI_STATUS run(EFI_HANDLE image) { } } +// NOLINTNEXTLINE(misc-use-internal-linkage) DEFINE_EFI_MAIN_FUNCTION(run, "systemd-boot", /* wait_for_debugger= */ false); diff --git a/src/boot/efi-log.c b/src/boot/efi-log.c index 3cecc7be06ae6..ed0a2746933e0 100644 --- a/src/boot/efi-log.c +++ b/src/boot/efi-log.c @@ -132,6 +132,7 @@ void log_wait(void) { log_count = 0; } +// NOLINTNEXTLINE(misc-use-internal-linkage) _used_ intptr_t __stack_chk_guard = (intptr_t) 0x70f6967de78acae3; /* We can only set a random stack canary if this function attribute is available, @@ -147,8 +148,10 @@ void __stack_chk_guard_init(void) { } #endif +// NOLINTBEGIN(misc-use-internal-linkage) _used_ _noreturn_ void __stack_chk_fail(void); _used_ _noreturn_ void __stack_chk_fail_local(void); +// NOLINTEND(misc-use-internal-linkage) void __stack_chk_fail(void) { panic(u"systemd-boot: Stack check failed, halting."); } @@ -157,6 +160,7 @@ void __stack_chk_fail_local(void) { } /* Called by libgcc for some fatal errors like integer overflow with -ftrapv. */ +// NOLINTNEXTLINE(misc-use-internal-linkage) _used_ _noreturn_ void abort(void); void abort(void) { panic(u"systemd-boot: Unknown error, halting."); @@ -164,8 +168,10 @@ void abort(void) { #if defined(__ARM_EABI__) /* These override the (weak) div0 handlers from libgcc as they would otherwise call raise() instead. */ +// NOLINTBEGIN(misc-use-internal-linkage) _used_ _noreturn_ int __aeabi_idiv0(int return_value); _used_ _noreturn_ long long __aeabi_ldiv0(long long return_value); +// NOLINTEND(misc-use-internal-linkage) int __aeabi_idiv0(int return_value) { panic(u"systemd-boot: Division by zero, halting."); diff --git a/src/boot/efi-string.c b/src/boot/efi-string.c index cde10d0abd437..0f8986b5984b9 100644 --- a/src/boot/efi-string.c +++ b/src/boot/efi-string.c @@ -1050,10 +1050,12 @@ char16_t *xvasprintf_status(EFI_STATUS status, const char *format, va_list ap) { # undef memcmp # undef memcpy # undef memset +// NOLINTBEGIN(misc-use-internal-linkage) _used_ void *memchr(const void *p, int c, size_t n); _used_ int memcmp(const void *p1, const void *p2, size_t n); _used_ void *memcpy(void * restrict dest, const void * restrict src, size_t n); _used_ void *memset(void *p, int c, size_t n); +// NOLINTEND(misc-use-internal-linkage) #else /* And for userspace unit testing we need to give them an efi_ prefix. */ # undef memchr diff --git a/src/boot/generate-hwids-section.py b/src/boot/generate-hwids-section.py index 621183c20fba0..cfe6aea739aa3 100755 --- a/src/boot/generate-hwids-section.py +++ b/src/boot/generate-hwids-section.py @@ -20,6 +20,7 @@ #include #include +// NOLINTNEXTLINE(misc-use-internal-linkage) const uint8_t hwids_section_data[] = { """, end='', @@ -34,6 +35,8 @@ print( """}; + +// NOLINTNEXTLINE(misc-use-internal-linkage) const size_t hwids_section_len =""", f'{len(hwids)};', ) diff --git a/src/boot/stub.c b/src/boot/stub.c index 65950262c69d5..90f28a8ae32f7 100644 --- a/src/boot/stub.c +++ b/src/boot/stub.c @@ -1325,4 +1325,5 @@ static EFI_STATUS run(EFI_HANDLE image) { return err; } +// NOLINTNEXTLINE(misc-use-internal-linkage) DEFINE_EFI_MAIN_FUNCTION(run, "systemd-stub", /* wait_for_debugger= */ false); diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index 35cfcec6aca04..7a8b149e20363 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -41,7 +41,7 @@ struct bootp_addr_data { int netmask_offset; int ip_offset; }; -struct bootp_addr_data *bootp_test_context; +static struct bootp_addr_data *bootp_test_context; static bool verbose = true; static int test_fd[2]; diff --git a/src/libsystemd/sd-bus/test-bus-error.c b/src/libsystemd/sd-bus/test-bus-error.c index db41141811f84..3f89a907eb654 100644 --- a/src/libsystemd/sd-bus/test-bus-error.c +++ b/src/libsystemd/sd-bus/test-bus-error.c @@ -154,13 +154,13 @@ TEST(errno_mapping_standard) { assert_se(sd_bus_error_set(NULL, "System.Error.WHATSIT", NULL) == -EIO); } -BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map test_errors[] = { +BUS_ERROR_MAP_ELF_REGISTER static const sd_bus_error_map test_errors[] = { SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error", 5), SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-2", 52), SD_BUS_ERROR_MAP_END }; -BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map test_errors2[] = { +BUS_ERROR_MAP_ELF_REGISTER static const sd_bus_error_map test_errors2[] = { SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-3", 33), SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-4", 44), SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-33", 333), diff --git a/src/test/test-chid.c b/src/test/test-chid.c index 564b09c183e7e..86b748016aba0 100644 --- a/src/test/test-chid.c +++ b/src/test/test-chid.c @@ -3,7 +3,7 @@ #include "chid-fundamental.h" #include "tests.h" -const char16_t *const test_fields[_CHID_SMBIOS_FIELDS_MAX] = { +static const char16_t *const test_fields[_CHID_SMBIOS_FIELDS_MAX] = { [CHID_SMBIOS_MANUFACTURER] = u"Micro-Star International Co., Ltd.", [CHID_SMBIOS_PRODUCT_NAME] = u"MS-7D70", [CHID_SMBIOS_PRODUCT_SKU] = u"To be filled by O.E.M.", diff --git a/src/test/test-exec-util.c b/src/test/test-exec-util.c index 7c3b872c10dfd..886d771fb5e01 100644 --- a/src/test/test-exec-util.c +++ b/src/test/test-exec-util.c @@ -231,7 +231,7 @@ static int gather_stdout_three(int fd, void *arg) { return 0; } -const gather_stdout_callback_t gather_stdouts[] = { +static const gather_stdout_callback_t gather_stdouts[] = { gather_stdout_one, gather_stdout_two, gather_stdout_three, diff --git a/src/test/test-hashmap.c b/src/test/test-hashmap.c index 7f25ad101141c..4029d4a478fd2 100644 --- a/src/test/test-hashmap.c +++ b/src/test/test-hashmap.c @@ -3,14 +3,17 @@ #include "hashmap.h" #include "tests.h" +// NOLINTNEXTLINE(misc-use-internal-linkage) unsigned custom_counter = 0; static void custom_destruct(void* p) { custom_counter--; free(p); } +// NOLINTBEGIN(misc-use-internal-linkage) DEFINE_HASH_OPS_FULL(boring_hash_ops, char, string_hash_func, string_compare_func, free, char, free); DEFINE_HASH_OPS_FULL(custom_hash_ops, char, string_hash_func, string_compare_func, custom_destruct, char, custom_destruct); +// NOLINTEND(misc-use-internal-linkage) TEST(ordered_hashmap_next) { _cleanup_ordered_hashmap_free_ OrderedHashmap *m = NULL; @@ -154,6 +157,7 @@ TEST(hashmap_put_strdup_null) { * they don't apply to ordered hashmaps. */ /* This variable allows us to assert that the tests from different compilation units were actually run. */ +// NOLINTNEXTLINE(misc-use-internal-linkage) int n_extern_tests_run = 0; static int intro(void) { diff --git a/src/test/test-load-fragment.c b/src/test/test-load-fragment.c index 892e471d8ab0c..69d2d05b765c7 100644 --- a/src/test/test-load-fragment.c +++ b/src/test/test-load-fragment.c @@ -37,7 +37,7 @@ static char *runtime_dir = NULL; STATIC_DESTRUCTOR_REGISTER(runtime_dir, rm_rf_physical_and_freep); /* For testing type compatibility. */ -_unused_ ConfigPerfItemLookup unused_lookup = load_fragment_gperf_lookup; +_unused_ static ConfigPerfItemLookup unused_lookup = load_fragment_gperf_lookup; TEST_RET(unit_file_get_list) { int r; From 2f32c49a18198d16b72b49f561148a77dbee1559 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 6 Mar 2026 03:45:28 +0900 Subject: [PATCH 0112/1296] network: slightly reword polkit message --- po/systemd.pot | 6 ++++-- src/network/org.freedesktop.network1.policy | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/po/systemd.pot b/po/systemd.pot index 825fd49cb0205..93bb42609816f 100644 --- a/po/systemd.pot +++ b/po/systemd.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: systemd\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -925,7 +925,9 @@ msgid "DHCP server sends force renew message" msgstr "" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "" #: src/network/org.freedesktop.network1.policy:154 diff --git a/src/network/org.freedesktop.network1.policy b/src/network/org.freedesktop.network1.policy index 9d3ed87d6a70e..875e52708d4fa 100644 --- a/src/network/org.freedesktop.network1.policy +++ b/src/network/org.freedesktop.network1.policy @@ -141,7 +141,7 @@ DHCP server sends force renew message - Authentication is required to send force renew message. + Authentication is required to send a force renew message from the DHCP server. auth_admin auth_admin From 307391b297c440e7b458b88e6fead0f7f7b74e91 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Thu, 5 Mar 2026 19:43:40 +0000 Subject: [PATCH 0113/1296] po: Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/ Translation: systemd/main --- po/ar.po | 8 ++++++-- po/be.po | 8 ++++++-- po/be@latin.po | 6 ++++-- po/bg.po | 8 ++++++-- po/ca.po | 8 ++++++-- po/cs.po | 8 ++++++-- po/da.po | 8 ++++++-- po/de.po | 8 ++++++-- po/el.po | 23 ++++++++++++++++------- po/es.po | 8 ++++++-- po/et.po | 8 ++++++-- po/eu.po | 11 +++++++++-- po/fi.po | 8 ++++++-- po/fr.po | 8 ++++++-- po/gl.po | 8 ++++++-- po/he.po | 8 ++++++-- po/hi.po | 8 ++++++-- po/hr.po | 8 ++++++-- po/hu.po | 8 ++++++-- po/ia.po | 6 ++++-- po/id.po | 8 ++++++-- po/it.po | 8 ++++++-- po/ja.po | 8 ++++++-- po/ka.po | 8 ++++++-- po/kab.po | 6 ++++-- po/kk.po | 8 ++++++-- po/km.po | 8 ++++++-- po/kn.po | 6 ++++-- po/ko.po | 8 ++++++-- po/kw.po | 6 ++++-- po/lt.po | 6 ++++-- po/nl.po | 8 ++++++-- po/pa.po | 12 +++++++++--- po/pl.po | 8 ++++++-- po/pt.po | 8 ++++++-- po/pt_BR.po | 8 ++++++-- po/ro.po | 8 ++++++-- po/ru.po | 8 ++++++-- po/si.po | 6 ++++-- po/sk.po | 6 ++++-- po/sl.po | 8 ++++++-- po/sr.po | 6 ++++-- po/sv.po | 8 ++++++-- po/tr.po | 8 ++++++-- po/uk.po | 8 ++++++-- po/zh_CN.po | 8 ++++++-- po/zh_TW.po | 8 ++++++-- 47 files changed, 280 insertions(+), 100 deletions(-) diff --git a/po/ar.po b/po/ar.po index f6c113f0e32b7..e4a8845ebfa35 100644 --- a/po/ar.po +++ b/po/ar.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: systemd\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" "PO-Revision-Date: 2026-02-26 13:58+0000\n" "Last-Translator: joo es \n" "Language-Team: Arabic \n" "Language-Team: Belarusian \n" "Language-Team: \n" @@ -1079,7 +1079,9 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:144 #, fuzzy -msgid "Authentication is required to send force renew message." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "" "Nieabchodna aŭtentyfikacyja dlia ŭstaliavannia ŭsieahuĺnaha paviedamliennia" diff --git a/po/bg.po b/po/bg.po index 0cbfd23c35c4f..fb84c6c9d7cd3 100644 --- a/po/bg.po +++ b/po/bg.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: systemd main\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" "PO-Revision-Date: 2025-02-11 01:17+0000\n" "Last-Translator: Alexander Shopov \n" "Language-Team: Bulgarian \n" "Language-Team: Catalan \n" "Language-Team: Czech \n" "Language-Team: Danish \n" "Language-Team: German \n" "Language-Team: Greek \n" "Language-Team: Spanish \n" @@ -987,7 +987,11 @@ msgid "DHCP server sends force renew message" msgstr "DHCP server saadab sunduuendamise sõnumi" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." +#, fuzzy +#| msgid "Authentication is required to send force renew message." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "Autentimine on vajalik, et saata sunduuendamis sõnumi." #: src/network/org.freedesktop.network1.policy:154 diff --git a/po/eu.po b/po/eu.po index 87bfef96df9ca..d292240069525 100644 --- a/po/eu.po +++ b/po/eu.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: systemd\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" "PO-Revision-Date: 2023-06-03 15:48+0000\n" "Last-Translator: Asier Sarasua Garmendia \n" "Language-Team: Basque \n" "Language-Team: Finnish \n" "Language-Team: French \n" "Language-Team: Galician \n" "Language-Team: Hebrew \n" "Language-Team: Hindi \n" "Language-Team: Croatian \n" "Language-Team: Hungarian \n" "Language-Team: Interlingua \n" "Language-Team: Indonesian \n" "Language-Team: Italian \n" "Language-Team: Japanese \n" "Language-Team: Georgian \n" @@ -928,7 +928,9 @@ msgid "DHCP server sends force renew message" msgstr "" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "" #: src/network/org.freedesktop.network1.policy:154 diff --git a/po/kk.po b/po/kk.po index 7d6e4d3455db4..9695d9566f090 100644 --- a/po/kk.po +++ b/po/kk.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: systemd\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" "PO-Revision-Date: 2026-02-26 13:58+0000\n" "Last-Translator: Baurzhan Muftakhidinov \n" "Language-Team: Kazakh \n" "Language-Team: Khmer (Central) \n" "Language-Team: Korean \n" "Language-Team: Lithuanian \n" "Language-Team: Dutch \n" "Language-Team: Punjabi \n" "Language-Team: Polish \n" "Language-Team: Portuguese \n" "Language-Team: Portuguese (Brazil) \n" "Language-Team: Romanian \n" "Language-Team: Russian \n" "Language-Team: Sinhala \n" "Language-Team: Slovak \n" "Language-Team: Slovenian \n" "Language-Team: Serbian \n" "Language-Team: Swedish \n" "Language-Team: Turkish \n" "Language-Team: Ukrainian \n" "Language-Team: Chinese (Simplified) \n" "Language-Team: Chinese (Traditional) Date: Thu, 5 Mar 2026 21:39:14 +0100 Subject: [PATCH 0114/1296] ci: Add claude code github action This will allow maintainers to mention claude in comments on issues and prs to do stuff like review something or try to reproduce a bug or other stuff. Let's give it a try and see whether we like it or not. --- .github/workflows/claude.yml | 62 ++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .github/workflows/claude.yml diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml new file mode 100644 index 0000000000000..79762a5d7c216 --- /dev/null +++ b/.github/workflows/claude.yml @@ -0,0 +1,62 @@ +# Integrates Claude Code as an AI assistant for issues and pull requests. +# Mention @claude in any issue comment, PR review comment, or PR review to +# interact with it, or assign the "claude" user to an issue. Claude +# authenticates via AWS Bedrock using OIDC — no long-lived API keys required. + +name: Claude Code + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request_review: + types: [submitted] + +jobs: + claude: + runs-on: ubuntu-latest + + if: | + github.repository_owner == 'systemd' && + ((github.event_name == 'issue_comment' && + contains(github.event.comment.body, '@claude') && + contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association)) || + (github.event_name == 'pull_request_review_comment' && + contains(github.event.comment.body, '@claude') && + contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association)) || + (github.event_name == 'pull_request_review' && + contains(github.event.review.body, '@claude') && + contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.review.author_association)) || + (github.event_name == 'issues' && + github.event.action == 'assigned' && + github.event.assignee.login == 'claude')) + + permissions: + contents: read # Read repository contents + issues: write # Post comments on issues + pull-requests: write # Post comments and reviews on PRs + id-token: write # Authenticate with AWS via OIDC + actions: read # Access workflow run metadata + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + fetch-depth: 1 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 + with: + role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }} + role-session-name: GitHubActions-Claude-${{ github.run_id }} + aws-region: us-east-1 + + - name: Run Claude Code + uses: anthropics/claude-code-action@1fc90f3ed982521116d8ff6d85b948c9b12cae3e + with: + use_bedrock: "true" + github_token: ${{ secrets.GITHUB_TOKEN }} + claude_args: | + --model us.anthropic.claude-opus-4-6-v1 From 802906bbbe05bffa67b921dc4df020b91eeaf614 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 6 Mar 2026 07:06:59 +0900 Subject: [PATCH 0115/1296] po: update Japanese translation --- po/ja.po | 2 -- 1 file changed, 2 deletions(-) diff --git a/po/ja.po b/po/ja.po index 41872c8f4177b..28bf9ae0f4f61 100644 --- a/po/ja.po +++ b/po/ja.po @@ -974,8 +974,6 @@ msgid "DHCP server sends force renew message" msgstr "DHCPサーバが強制的にIPアドレスを更新する" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy -#| msgid "Authentication is required to send force renew message." msgid "" "Authentication is required to send a force renew message from the DHCP " "server." From 0fc5c9ef2e70d5b17af1b55c17fc6c4e1087c1f9 Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Thu, 5 Mar 2026 15:42:30 -0700 Subject: [PATCH 0116/1296] zsh: fixup some recent zsh completers These two completers are written in a stacked _arguments style, and some generic options are valid before or after the verb. If the toplevel _arguments is permitted to match options after the verb, it will halt completion prematurely, so stop toplevel matching after the verb. This corrects the following error: $ userdbctl --output=class user # completes users $ userdbctl user --output=class # completes nothing --- shell-completion/zsh/_systemd-id128 | 2 +- shell-completion/zsh/_userdbctl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/shell-completion/zsh/_systemd-id128 b/shell-completion/zsh/_systemd-id128 index 2cc06de80b026..33522e1e37d22 100644 --- a/shell-completion/zsh/_systemd-id128 +++ b/shell-completion/zsh/_systemd-id128 @@ -32,7 +32,7 @@ _systemd-id128_names() { } local ret=1 -_arguments -s "$opt_common[@]" \ +_arguments -s -A '-*' "$opt_common[@]" \ ':command:->command' \ '*:: :->option-or-argument' && ret=0 diff --git a/shell-completion/zsh/_userdbctl b/shell-completion/zsh/_userdbctl index b3122fa908bfa..2a2106b62ef3d 100644 --- a/shell-completion/zsh/_userdbctl +++ b/shell-completion/zsh/_userdbctl @@ -26,7 +26,7 @@ local -a opt_user_group=( '-S[Equivalent to --disposition=system]' '-R[Equivalent to --disposition=regular]' '--uid-min=[Filter by minimum UID/GID]:uid:_numbers -t uids uid -d 0' - '--uid-max=[Filter by maximum UID/GID]:gid:_numbers -t gids gid -d 4294967294' + '--uid-max=[Filter by maximum UID/GID]:uid:_numbers -t uids uid -d 4294967294' '--uuid=[Filter by UUID]:uuid' '(-B)--boundaries=[Show/hide UID/GID range boundaries in output]:bool:(yes no)' '(--boundaries)-B[Equivalent to --boundaries=no]' @@ -44,7 +44,7 @@ local -a userdbctl_commands=( ) local ret=1 -_arguments -s \ +_arguments -s -A '-*' \ "$opt_common[@]" \ ':userdbctl command:->command' \ '*:: :->option-or-argument' && ret=0 From d213b85b794702f44e06d51ee5934fb2c31860ee Mon Sep 17 00:00:00 2001 From: Salvatore Cocuzza Date: Thu, 5 Mar 2026 22:10:05 +0000 Subject: [PATCH 0117/1296] po: Translated using Weblate (Italian) Currently translated at 100.0% (266 of 266 strings) Co-authored-by: Salvatore Cocuzza Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/it/ Translation: systemd/main --- po/it.po | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/po/it.po b/po/it.po index ba19c7c1d0802..848dcee155c32 100644 --- a/po/it.po +++ b/po/it.po @@ -3,15 +3,15 @@ # Italian translation for systemd package # Traduzione in italiano per il pacchetto systemd # Daniele Medri , 2013-2024. -# Salvatore Cocuzza , 2024. +# Salvatore Cocuzza , 2024, 2026. # Nathan , 2025. msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2025-08-06 15:08+0000\n" -"Last-Translator: Nathan \n" +"PO-Revision-Date: 2026-03-05 22:10+0000\n" +"Last-Translator: Salvatore Cocuzza \n" "Language-Team: Italian \n" "Language: it\n" @@ -19,7 +19,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.12.2\n" +"X-Generator: Weblate 5.16.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1052,12 +1052,12 @@ msgid "DHCP server sends force renew message" msgstr "Il server DHCP invia messaggi di rinnovo forzato" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy -#| msgid "Authentication is required to send force renew message." msgid "" "Authentication is required to send a force renew message from the DHCP " "server." -msgstr "Autenticazione richiesta per inviare messaggi di rinnovo forzato." +msgstr "" +"Per inviare un messaggio di rinnovo forzato dal server DHCP è richiesta " +"l'autenticazione." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" @@ -1098,11 +1098,11 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Gestire i collegamenti di rete" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" +msgstr "Per gestire i collegamenti di rete è richiesta l'autenticazione." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" From 02b2014a24854590bfa77fb612d099a4678542c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Am=C3=A9rico=20Monteiro?= Date: Thu, 5 Mar 2026 22:10:06 +0000 Subject: [PATCH 0118/1296] po: Translated using Weblate (Portuguese) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (266 of 266 strings) Co-authored-by: Américo Monteiro Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/pt/ Translation: systemd/main --- po/pt.po | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/po/pt.po b/po/pt.po index ac3b7e3171c5b..8d6e42865e353 100644 --- a/po/pt.po +++ b/po/pt.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2026-02-26 13:58+0000\n" +"PO-Revision-Date: 2026-03-05 22:10+0000\n" "Last-Translator: Américo Monteiro \n" "Language-Team: Portuguese \n" @@ -1047,12 +1047,12 @@ msgid "DHCP server sends force renew message" msgstr "Servidor DHCP envia mensagem de renovação forçada" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy -#| msgid "Authentication is required to send force renew message." msgid "" "Authentication is required to send a force renew message from the DHCP " "server." -msgstr "É necessária autenticação para enviar mensagem de renovação forçada." +msgstr "" +"É necessária autenticação para enviar uma mensagem de renovação forçada a " +"partir do servidor DHCP." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" From acce28884dd2e651d2bf09621c53b767edd4a050 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 6 Mar 2026 00:25:10 +0000 Subject: [PATCH 0119/1296] man: add tags for the next few versions --- man/version-info.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/man/version-info.xml b/man/version-info.xml index 54440febd0f2c..baff5df5f5852 100644 --- a/man/version-info.xml +++ b/man/version-info.xml @@ -84,4 +84,8 @@ Added in version 258. Added in version 259. Added in version 260. + Added in version 261. + Added in version 262. + Added in version 263. + Added in version 264. From 89f5f01caf98682326ad052e586fac614f38ed3f Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 6 Mar 2026 08:54:33 +0100 Subject: [PATCH 0120/1296] Move AI instructions to AGENTS.md This seems to be what all the tools are standardizing on, except claude (https://github.com/anthropics/claude-code/issues/6235) so add a symlink from CLAUDE.md to AGENTS.md for now until they support it as well. I also had claude extend the instructions a bit. Co-developed-by: Claude --- .github/copilot-instructions.md | 53 ---------- AGENTS.md | 171 ++++++++++++++++++++++++++++++++ CLAUDE.md | 1 + 3 files changed, 172 insertions(+), 53 deletions(-) delete mode 100644 .github/copilot-instructions.md create mode 100644 AGENTS.md create mode 120000 CLAUDE.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index 193935f3e6b10..0000000000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,53 +0,0 @@ -# systemd AI Coding Agent Instructions - -## Project Overview - -systemd is a system and service manager for Linux, written in C (GNU17 with extensions). The project is built with Meson and consists of ~140 components including PID 1, journald, udevd, networkd, and many other system daemons. - -## Key Files & Directories - -Always include the following files in the context: - -- [code organization details](../docs/ARCHITECTURE.md) -- [development workflow deep dive](../docs/HACKING.md) -- [full style guide](../docs/CODING_STYLE.md) - -Include any other files from the [documentation](../docs) in the context as needed based on whether you think it might be helpful to solve your current task or help to review the current PR. - -## Build + Test instructions - -**CRITICAL: Read and follow these instructions exactly.** - -- **NEVER** compile individual files or targets. **ALWAYS** run `mkosi -f box meson compile -C build` to build the entire project. Meson handles incremental compilation automatically. -- **NEVER** run `meson compile` followed by `meson test` as separate steps. **ALWAYS** run `mkosi -f box meson test -C build -v ` directly. Meson will automatically rebuild any required targets before running tests. -- **NEVER** invent your own build commands or try to optimize the build process. -- **NEVER** use `head`, `tail`, or pipe (`|`) the output of build or test commands. Always let the full output display. This is critical for diagnosing build and test failures. -- When asked to build and test code changes: - - **CORRECT**: Run `mkosi -f box -- meson test -C build -v ` (this builds and runs tests in one command) - - **WRONG**: Run `mkosi -f box -- meson compile -C build` followed by `mkosi -f box -- meson test -C build -v ` - - **WRONG**: Run `mkosi -f box -- meson compile -C build src/core/systemd` or similar individual target compilation - - **WRONG**: Run `mkosi -f box -- meson test -C build -v 2>&1 | tail -100` or similar piped commands - -## Pull Request review instructions - -- Focus on making sure the coding style is followed as documented in `docs/CODING_STYLE.md` -- Only leave comments for logic issues if you are very confident in your deduction -- Frame comments as questions -- Always consider you may be wrong -- Do not argue with contributors, assume they are right unless you are very confident in your deduction -- Be extremely thorough. Every single separate coding style violation should be reported - -## Testing Expectations - -- Unit tests for self contained functions with few dependencies -- Integration tests for system-level functionality -- CI must pass (build + unit + integration tests) -- Code coverage tracked via Coveralls - -## Integration with Development Tools - -- **clangd**: Use `mkosi.clangd` script to start a C/C++ LSP server for navigating C source and header files. Run `mkosi -f box -- meson setup build && mkosi -f box -- meson compile -C build gensources` first to prepare the environment. - -## AI Contribution Disclosure - -Per project policy: If you use AI code generation tools, you **must disclose** this in commit messages and PR descriptions. All AI-generated output requires thorough human review before submission. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000000..6d5741755563e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,171 @@ +# AGENTS.md + +This file provides guidance to AI coding agents when working with code in this repository. + +## Project Overview + +systemd is a system and service manager for Linux, written in C (GNU17 with extensions). The project is built with Meson and consists of ~140 components including PID 1, journald, udevd, networkd, and many other system daemons. + +## Key Documentation + +Always consult these files as needed: + +- `docs/ARCHITECTURE.md` — code organization and component relationships +- `docs/HACKING.md` — development workflow with mkosi +- `docs/CODING_STYLE.md` — full style guide (must-read before writing code) +- `docs/CONTRIBUTING.md` — contribution guidelines and PR workflow + +## Build and Test Commands + +**CRITICAL: Read and follow these instructions exactly.** + +- **NEVER** compile individual files or targets. **ALWAYS** run `mkosi -f box -- meson compile -C build` to build the entire project. Meson handles incremental compilation automatically. +- **NEVER** run `meson compile` followed by `meson test` as separate steps. **ALWAYS** run `mkosi -f box -- meson test -C build -v ` directly. Meson will automatically rebuild any required targets before running tests. +- **NEVER** invent your own build commands or try to optimize the build process. +- **NEVER** use `head`, `tail`, or pipe (`|`) the output of build or test commands. Always let the full output display. This is critical for diagnosing build and test failures. + +```sh +# Initial setup (one-time) +mkosi -f genkey +mkosi -f box -- meson setup build + +# Build everything +mkosi -f box -- meson compile -C build + +# Run a specific unit test (also rebuilds as needed) +mkosi -f box -- meson test -C build -v + +# Run all unit tests +mkosi -f box -- meson test -C build --print-errorlogs -q + +# Build and boot an OS image for integration testing +mkosi -f box -- meson compile -C build mkosi +mkosi boot # nspawn +mkosi vm # qemu +``` + +- **CORRECT**: `mkosi -f box -- meson test -C build -v ` +- **WRONG**: Separate compile then test steps +- **WRONG**: `mkosi -f box -- meson compile -C build src/core/systemd` (individual target) +- **WRONG**: `mkosi -f box -- meson test -C build -v 2>&1 | tail -100` (piped output) + +## Code Architecture + +### Shared Code Dependency Hierarchy (strict layering) + +``` +src/fundamental/ → no dependencies (used in EFI + userspace) + ↑ +src/basic/ → depends only on fundamental (userspace only) + ↑ +src/libsystemd/ → depends on basic + fundamental (public libsystemd.so) + ↑ +src/shared/ → depends on all above (libsystemd-shared-.so) + ↑ +src// → individual daemons and tools +``` + +Code should be linked as few times as possible. Place shared code at the lowest possible layer. + +### Key Components + +- `src/core/` — PID 1 service manager (system and user instances). Uses `systemd-executor` for process spawning via `posix_spawn()` to avoid fork+exec pitfalls. +- `src/udev/` — udev daemon and udevadm tool +- `src/journal/` — journald logging daemon +- `src/network/` — networkd network manager +- `src/resolve/` — resolved DNS resolver +- `src/login/` — logind session manager +- `src/boot/` — systemd-boot EFI bootloader +- `src/systemctl/`, `src/journalctl/`, `src/analyze/` — CLI tools + +### Unit Settings Implementation + +Adding a new unit setting requires changes in various places: +1. `src/core/load-fragment-gperf.gperf.in` + `src/core/load-fragment.c` — unit file parsing +1. `src/core/dbus-*.c` — D-Bus interface +1. `src/core/varlink-*.c` — Varlink interface +1. `src/shared/bus-unit-util.c` — client-side parsing for systemctl/systemd-run +1. `test/fuzz/fuzz-unit-file/` — add to fuzz corpus + +### Tests + +- **Unit tests**: `src/test/` — match source files (e.g., `src/test/test-path-util.c` tests `src/basic/path-util.c`). Use assertion macros from `tests.h` (`ASSERT_GE()`, `ASSERT_OK()`, `ASSERT_OK_ERRNO()` for glibc calls). +- **Fuzzers**: `src/fuzz/` for basic/shared code; next to component code otherwise. Input samples in `test/fuzz/`. +- **Integration tests**: `test/TEST-*` directories, run via systemd-nspawn or qemu. + +## Coding Style (Essential Rules) + +The full style guide is in `docs/CODING_STYLE.md`. Key rules: + +### Formatting +- 8-space indent (no tabs) for C; 4-space for shell scripts; 2-space for man pages (XML) +- ~109 character line limit +- Opening brace on same line: `void foo() {` +- Function parameters with double indent (16 spaces) when broken across lines +- Single-line `if` blocks without braces +- `/* comments */` for committed code; `//` reserved for temporary debug comments +- Pointer in return types: `const char* foo()` (star with type) +- Pointer in parameters: `const char *input` (star with name) +- Casts: `(const char*) s` (space before `s`, not after `*`) + +### Naming and Structure +- Structs: `PascalCase`; variables/functions: `snake_case` +- Return parameters prefixed `ret_` (success) or `reterr_` (failure) +- Command-line globals prefixed `arg_` +- Enum flags: use `1 << N` expressions, aligned vertically +- Non-flag enums: include `_FOO_MAX` and `_FOO_INVALID = -EINVAL` sentinels +- Commit messages: prefix with component name (e.g., `journal: `, `nspawn: `) + +### Error Handling +- Return negative errno values: `return -EINVAL` +- Use `RET_NERRNO()` to convert libc-style returns +- Combined log+return: `return log_error_errno(r, "Failed to ...: %m")` +- Use `SYNTHETIC_ERRNO()` for errors not from a called function +- Cast ignored errors to void: `(void) unlink("/foo/bar")` +- Always check OOM; use `log_oom()` in program code + +### Header Files +- Keep headers lean — prefer implementations in `.c` files +- Include the appropriate forward declaration header (`basic-forward.h`, `sd-forward.h`, `shared-forward.h`) instead of pulling in full headers +- Order: external headers (`<>`), then `sd-*` headers, then internal headers, alphabetically within each group +- No circular header dependencies + +### Memory and Types +- Use `_cleanup_free_` and friends for automatic cleanup +- Use `alloca_safe()`, `strdupa_safe()` instead of raw `alloca()`, `strdupa()` +- Never use `off_t`; use `uint64_t` instead +- Use `unsigned` not `unsigned int`; `uint8_t` for bytes, not `char` + +### Functions to Avoid +- `memset` → use `memzero()` or `zero()` +- `strcmp`/`strncmp` → use `streq()` / `strneq()` +- `strtol`/`atoi` → use `safe_atoli()`, `safe_atou32()`, etc. +- `basename`/`dirname` → use `path_extract_filename()` / `path_extract_directory()` +- `fgets` → use `read_line()` +- `exit()` → use `return` from main or `_exit()` in forked children +- `dup()` → use `fcntl(fd, F_DUPFD_CLOEXEC, 3)` +- `htonl`/`htons` → use `htobe32()` / `htobe16()` + +### File Descriptors +- Always use `O_CLOEXEC` / `SOCK_CLOEXEC` / `MSG_CMSG_CLOEXEC` / ... + +### Logging +- Library code (`src/basic/`, `src/shared/`): never log (except `LOG_DEBUG`) +- "Logging" functions log errors themselves; "non-logging" functions expect callers to log +- Non-fatal warnings: suffix with `, ignoring: %m"` or similar + +### Integration Tests +- Never use `grep -q` in pipelines; use `grep >/dev/null` instead (avoids `SIGPIPE`) + +## Pull Request Review Instructions + +- Focus on coding style compliance from `docs/CODING_STYLE.md` +- Only leave comments for logic issues if you are very confident in your deduction +- Frame comments as questions +- Always consider you may be wrong +- Do not argue with contributors; assume they are right unless you are very confident +- Be extremely thorough — every single coding style violation should be reported + +## AI Contribution Disclosure + +Per project policy: if you use AI code generation tools, you **must disclose** this in commit messages by adding e.g. `Co-developed-by: Claude `. All AI-generated output requires thorough human review before submission. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000000000..47dc3e3d863cf --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file From 83e872b4c2e680a7cb54c391525ec25b4c65361e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 6 Mar 2026 10:17:01 +0100 Subject: [PATCH 0121/1296] agent: Minimize the amount of instructions in AGENTS.md Let's only keep instructions for stuff that we've seen the AI mess up in practice rather than having a bunch of AI generated text that it can figure out for itself these days (given it was trained on systemd's source code in the first place). Also add a rule to use git worktrees and check out PRs locally when reviewing them, since I've seen it mess that up in practice. --- .gitignore | 1 + AGENTS.md | 159 ++++------------------------------------------------- 2 files changed, 13 insertions(+), 147 deletions(-) diff --git a/.gitignore b/.gitignore index 5c5f1ed586b59..a6aec324960a2 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ __pycache__/ .vscode/ /pkg/ .aider* +/worktrees diff --git a/AGENTS.md b/AGENTS.md index 6d5741755563e..e69b25a076a2e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,10 +1,7 @@ # AGENTS.md -This file provides guidance to AI coding agents when working with code in this repository. - -## Project Overview - -systemd is a system and service manager for Linux, written in C (GNU17 with extensions). The project is built with Meson and consists of ~140 components including PID 1, journald, udevd, networkd, and many other system daemons. +This file provides guidance to AI coding agents when working with code in this repository. Only add +instructions to this file if you've seen an AI agent mess up that particular bit of logic in practice. ## Key Documentation @@ -17,154 +14,22 @@ Always consult these files as needed: ## Build and Test Commands -**CRITICAL: Read and follow these instructions exactly.** - -- **NEVER** compile individual files or targets. **ALWAYS** run `mkosi -f box -- meson compile -C build` to build the entire project. Meson handles incremental compilation automatically. -- **NEVER** run `meson compile` followed by `meson test` as separate steps. **ALWAYS** run `mkosi -f box -- meson test -C build -v ` directly. Meson will automatically rebuild any required targets before running tests. -- **NEVER** invent your own build commands or try to optimize the build process. -- **NEVER** use `head`, `tail`, or pipe (`|`) the output of build or test commands. Always let the full output display. This is critical for diagnosing build and test failures. - -```sh -# Initial setup (one-time) -mkosi -f genkey -mkosi -f box -- meson setup build - -# Build everything -mkosi -f box -- meson compile -C build - -# Run a specific unit test (also rebuilds as needed) -mkosi -f box -- meson test -C build -v - -# Run all unit tests -mkosi -f box -- meson test -C build --print-errorlogs -q - -# Build and boot an OS image for integration testing -mkosi -f box -- meson compile -C build mkosi -mkosi boot # nspawn -mkosi vm # qemu -``` - -- **CORRECT**: `mkosi -f box -- meson test -C build -v ` -- **WRONG**: Separate compile then test steps -- **WRONG**: `mkosi -f box -- meson compile -C build src/core/systemd` (individual target) -- **WRONG**: `mkosi -f box -- meson test -C build -v 2>&1 | tail -100` (piped output) - -## Code Architecture - -### Shared Code Dependency Hierarchy (strict layering) - -``` -src/fundamental/ → no dependencies (used in EFI + userspace) - ↑ -src/basic/ → depends only on fundamental (userspace only) - ↑ -src/libsystemd/ → depends on basic + fundamental (public libsystemd.so) - ↑ -src/shared/ → depends on all above (libsystemd-shared-.so) - ↑ -src// → individual daemons and tools -``` - -Code should be linked as few times as possible. Place shared code at the lowest possible layer. - -### Key Components - -- `src/core/` — PID 1 service manager (system and user instances). Uses `systemd-executor` for process spawning via `posix_spawn()` to avoid fork+exec pitfalls. -- `src/udev/` — udev daemon and udevadm tool -- `src/journal/` — journald logging daemon -- `src/network/` — networkd network manager -- `src/resolve/` — resolved DNS resolver -- `src/login/` — logind session manager -- `src/boot/` — systemd-boot EFI bootloader -- `src/systemctl/`, `src/journalctl/`, `src/analyze/` — CLI tools - -### Unit Settings Implementation - -Adding a new unit setting requires changes in various places: -1. `src/core/load-fragment-gperf.gperf.in` + `src/core/load-fragment.c` — unit file parsing -1. `src/core/dbus-*.c` — D-Bus interface -1. `src/core/varlink-*.c` — Varlink interface -1. `src/shared/bus-unit-util.c` — client-side parsing for systemctl/systemd-run -1. `test/fuzz/fuzz-unit-file/` — add to fuzz corpus - -### Tests - -- **Unit tests**: `src/test/` — match source files (e.g., `src/test/test-path-util.c` tests `src/basic/path-util.c`). Use assertion macros from `tests.h` (`ASSERT_GE()`, `ASSERT_OK()`, `ASSERT_OK_ERRNO()` for glibc calls). -- **Fuzzers**: `src/fuzz/` for basic/shared code; next to component code otherwise. Input samples in `test/fuzz/`. -- **Integration tests**: `test/TEST-*` directories, run via systemd-nspawn or qemu. - -## Coding Style (Essential Rules) - -The full style guide is in `docs/CODING_STYLE.md`. Key rules: - -### Formatting -- 8-space indent (no tabs) for C; 4-space for shell scripts; 2-space for man pages (XML) -- ~109 character line limit -- Opening brace on same line: `void foo() {` -- Function parameters with double indent (16 spaces) when broken across lines -- Single-line `if` blocks without braces -- `/* comments */` for committed code; `//` reserved for temporary debug comments -- Pointer in return types: `const char* foo()` (star with type) -- Pointer in parameters: `const char *input` (star with name) -- Casts: `(const char*) s` (space before `s`, not after `*`) - -### Naming and Structure -- Structs: `PascalCase`; variables/functions: `snake_case` -- Return parameters prefixed `ret_` (success) or `reterr_` (failure) -- Command-line globals prefixed `arg_` -- Enum flags: use `1 << N` expressions, aligned vertically -- Non-flag enums: include `_FOO_MAX` and `_FOO_INVALID = -EINVAL` sentinels -- Commit messages: prefix with component name (e.g., `journal: `, `nspawn: `) - -### Error Handling -- Return negative errno values: `return -EINVAL` -- Use `RET_NERRNO()` to convert libc-style returns -- Combined log+return: `return log_error_errno(r, "Failed to ...: %m")` -- Use `SYNTHETIC_ERRNO()` for errors not from a called function -- Cast ignored errors to void: `(void) unlink("/foo/bar")` -- Always check OOM; use `log_oom()` in program code - -### Header Files -- Keep headers lean — prefer implementations in `.c` files -- Include the appropriate forward declaration header (`basic-forward.h`, `sd-forward.h`, `shared-forward.h`) instead of pulling in full headers -- Order: external headers (`<>`), then `sd-*` headers, then internal headers, alphabetically within each group -- No circular header dependencies - -### Memory and Types -- Use `_cleanup_free_` and friends for automatic cleanup -- Use `alloca_safe()`, `strdupa_safe()` instead of raw `alloca()`, `strdupa()` -- Never use `off_t`; use `uint64_t` instead -- Use `unsigned` not `unsigned int`; `uint8_t` for bytes, not `char` - -### Functions to Avoid -- `memset` → use `memzero()` or `zero()` -- `strcmp`/`strncmp` → use `streq()` / `strneq()` -- `strtol`/`atoi` → use `safe_atoli()`, `safe_atou32()`, etc. -- `basename`/`dirname` → use `path_extract_filename()` / `path_extract_directory()` -- `fgets` → use `read_line()` -- `exit()` → use `return` from main or `_exit()` in forked children -- `dup()` → use `fcntl(fd, F_DUPFD_CLOEXEC, 3)` -- `htonl`/`htons` → use `htobe32()` / `htobe16()` - -### File Descriptors -- Always use `O_CLOEXEC` / `SOCK_CLOEXEC` / `MSG_CMSG_CLOEXEC` / ... +- Never compile individual files or targets. Always run `mkosi -f box -- meson compile -C build` to build +the entire project. Meson handles incremental compilation automatically. +- Never run `meson compile` followed by `meson test` as separate steps. Always run +`mkosi -f box -- meson test -C build -v ` directly. Meson will automatically rebuild any required +targets before running tests. +- Never invent your own build commands or try to optimize the build process. +- Never use `head`, `tail`, or pipe (`|`) the output of build or test commands. Always let the full output +display. This is critical for diagnosing build and test failures. -### Logging -- Library code (`src/basic/`, `src/shared/`): never log (except `LOG_DEBUG`) -- "Logging" functions log errors themselves; "non-logging" functions expect callers to log -- Non-fatal warnings: suffix with `, ignoring: %m"` or similar +## Integration Tests -### Integration Tests - Never use `grep -q` in pipelines; use `grep >/dev/null` instead (avoids `SIGPIPE`) ## Pull Request Review Instructions -- Focus on coding style compliance from `docs/CODING_STYLE.md` -- Only leave comments for logic issues if you are very confident in your deduction -- Frame comments as questions -- Always consider you may be wrong -- Do not argue with contributors; assume they are right unless you are very confident -- Be extremely thorough — every single coding style violation should be reported +- Always check out the PR in a git worktree in `worktrees/`, review it locally and remove the worktree when finished. ## AI Contribution Disclosure From 4ef56f30b8ba7ec823811dd52a847fa030465e56 Mon Sep 17 00:00:00 2001 From: Antonio Alvarez Feijoo Date: Fri, 6 Mar 2026 08:45:36 +0100 Subject: [PATCH 0122/1296] mkosi/opensuse: fix package name systemd-network -> systemd-networkd ``` 'systemd-network' not found in package names. Trying capabilities. ``` Follow-up for 00f7afebb4d5bc0832ae3483c751aa803d1df99c --- mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf b/mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf index add9983ea9631..9308395763570 100644 --- a/mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf +++ b/mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf @@ -17,5 +17,5 @@ VolatilePackages= # Pull in systemd-container so that the import-generator is available systemd-container systemd-experimental - systemd-network + systemd-networkd udev From dfb1fa019778814fdab04390d3df2d02307ba631 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 6 Mar 2026 11:39:28 +0100 Subject: [PATCH 0123/1296] sd-varlink: pin error message while we invoke a reply callback Let's make sure the parameters/error pointers into the message remain valid as long as the reply callback is running, even if the reply otherwise resets the pending message. --- src/libsystemd/sd-varlink/sd-varlink.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index f868f11850b57..7ee85e9789542 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -1135,9 +1135,8 @@ static int varlink_sanitize_incoming_parameters(sd_json_variant **v) { } static int varlink_dispatch_reply(sd_varlink *v) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *parameters = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *parameters = NULL, *error = NULL; sd_varlink_reply_flags_t flags = 0; - const char *error = NULL; sd_json_variant *e; const char *k; int r; @@ -1162,7 +1161,7 @@ static int varlink_dispatch_reply(sd_varlink *v) { if (!sd_json_variant_is_string(e)) goto invalid; - error = sd_json_variant_string(e); + error = sd_json_variant_ref(e); flags |= SD_VARLINK_REPLY_ERROR; } else if (streq(k, "parameters")) { @@ -1204,7 +1203,7 @@ static int varlink_dispatch_reply(sd_varlink *v) { varlink_set_state(v, VARLINK_PROCESSING_REPLY); if (v->reply_callback) { - r = v->reply_callback(v, parameters, error, flags, v->userdata); + r = v->reply_callback(v, parameters, sd_json_variant_string(error), flags, v->userdata); if (r < 0) varlink_log_errno(v, r, "Reply callback returned error, ignoring: %m"); } From 5d223ff468a29908bfa63988d474f356c8f1274c Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 6 Mar 2026 15:06:36 +0100 Subject: [PATCH 0124/1296] ci: Make claude action review PRs only and fix the instructions Turns out the claude code action has issues reviewing PRs from forks (https://github.com/anthropics/claude-code-action/issues/939). Let's reuse the approach from https://github.com/pzmarzly/demo--claude-bot-reviews instead (which I've explicitly asked permission for to reuse). Unlike the linked demo, we still insist on a comment by a maintainer before claude reviews the PR. --- .github/workflows/claude-review.yml | 151 ++++++++++++++++++++++++++++ .github/workflows/claude.yml | 62 ------------ 2 files changed, 151 insertions(+), 62 deletions(-) create mode 100644 .github/workflows/claude-review.yml delete mode 100644 .github/workflows/claude.yml diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml new file mode 100644 index 0000000000000..bba7bdd1b5751 --- /dev/null +++ b/.github/workflows/claude-review.yml @@ -0,0 +1,151 @@ +# Integrates Claude Code as an AI assistant for reviewing pull requests. +# Mention @claude in any PR review to request a review. Claude authenticates +# via AWS Bedrock using OIDC — no long-lived API keys required. + +name: Claude Review + +on: + pull_request_review_comment: + types: [created] + pull_request_review: + types: [submitted] +concurrency: + group: claude-review-${{ github.event.pull_request.number }} + cancel-in-progress: true +jobs: + claude-review: + runs-on: ubuntu-latest + + if: | + github.repository_owner == 'systemd' && + (github.event_name == 'pull_request_review_comment' && + contains(github.event.comment.body, '@claude') && + contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association)) || + (github.event_name == 'pull_request_review' && + contains(github.event.review.body, '@claude') && + contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.review.author_association)) + + permissions: + contents: read # Read repository contents + pull-requests: write # Post comments and reviews on PRs + id-token: write # Authenticate with AWS via OIDC + actions: read # Access workflow run metadata + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 + with: + role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }} + role-session-name: GitHubActions-Claude-${{ github.run_id }} + aws-region: us-east-1 + + - name: Run Claude Code + uses: anthropics/claude-code-action@1fc90f3ed982521116d8ff6d85b948c9b12cae3e + with: + use_bedrock: "true" + github_token: ${{ secrets.GITHUB_TOKEN }} + claude_args: | + --model us.anthropic.claude-opus-4-6-v1 + --max-turns 100 + --allowedTools " + Read,Write,Edit,MultiEdit,LS,Grep,Glob,Task, + Bash(cat:*),Bash(test:*),Bash(printf:*),Bash(jq:*),Bash(head:*),Bash(git:*),Bash(gh:*), + mcp__github_inline_comment__create_inline_comment, + " + prompt: | + REPO: ${{ github.repository }} + PR NUMBER: ${{ github.event.pull_request.number }} + HEAD SHA: ${{ github.event.pull_request.head.sha }} + + Review this pull request. + You are in the upstream repo without the patch applied. Do not apply it. + + ## Phase 1: Gather context + + Fetch the patch, PR title/body, and list of existing comments (top-level, inline, and reviews): + - `gh pr diff ${{ github.event.pull_request.number }} --patch` + - `gh pr view ${{ github.event.pull_request.number }} --json title,body` + - `gh api --paginate repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments` + - `gh api --paginate repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/comments` + - `gh api --paginate repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews` + + ## Phase 2: Parallel review subagents + + Review: + - Code quality, style, and best practices + - Potential bugs, issues, incorrect logic + - Security implications + - CLAUDE.md - compliance + + For every category, launch subagents to review them in parallel. Group related sections + as needed — use 2-4 subagents based on PR size and scope. + + Give each subagent the PR title, description, full patch, and the list of changed files. + + Each subagent must return a JSON array of issues: + `[{"file": "path", "line": , "severity": "must-fix|suggestion|nit", "title": "...", "body": "..."}]` + + Subagents must ONLY return the JSON array — they must NOT post comments, + call `gh`, or use `mcp__github_inline_comment__create_inline_comment`. + All posting happens in Phase 3. + + Each subagent MUST verify its findings before returning them: + - For style/convention claims, check at least 3 existing examples in the codebase to confirm + the pattern actually exists before flagging a violation. + - For "use X instead of Y" suggestions, confirm X actually exists and works for this case. + - If unsure, don't include the issue. + + ## Phase 3: Collect and post + + After ALL subagents complete: + 1. Collect all issues. Merge duplicates (same file, lines within 3 of each other, same problem). + 2. Drop low-confidence findings. + 3. For CLAUDE.md violations that appear in 3+ existing places in the codebase, do NOT post inline comments. + Instead, add them to the 'CLAUDE.md improvements' section of the tracking comment + 4. Check existing inline review comments (fetched in Phase 1). Do NOT post an inline comment if + one already exists on the same file+line about the same problem. + 5. Check for author replies that dismiss or reject a previous comment. Do NOT re-raise an issue + the PR author has already responded to disagreeing with. + 6. Post new inline comments with `mcp__github_inline_comment__create_inline_comment`. + + Prefix ALL comments with "Claude: ". + Link format: https://github.com/${{ github.repository }}/blob/${{ github.event.pull_request.head.sha }}/README.md#L10-L15 + + Then maintain a single top-level "tracking comment" listing ALL issues as checkboxes. + Use a hidden HTML marker to find it: ``. + Look through the top-level comments fetched in Phase 1 for one containing that marker. + + **If no tracking comment exists (first run):** + Create one with `gh pr comment ${{ github.event.pull_request.number }} --body "..."` using this format: + ``` + Claude: review of # () + + + + ### Must fix + - [ ] **title** — `file:line` — short explanation + + ### Suggestions + - [ ] **title** — `file:line` — short explanation + + ### Nits + - [ ] **title** — `file:line` — short explanation + + ### CLAUDE.md improvements + - improvement suggestion + ``` + Omit empty sections. + + **If a tracking comment already exists (subsequent run):** + 1. Parse the existing checkboxes. For each old issue, check if the current patch still has + that problem (re-check the relevant lines in the new diff). If fixed, mark it `- [x]`. + If the author dismissed it, mark it `- [x] ~~title~~ (dismissed)`. + 2. Append any NEW issues found in this run that aren't already listed. + 3. Update the HEAD SHA in the header line. + 4. Edit the comment in-place. + ``` + printf '%s' "$BODY" > pr-review-body.txt + gh api --method PATCH repos/${{ github.repository }}/issues/comments/ -F body=@pr-review-body.txt + ``` diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml deleted file mode 100644 index 79762a5d7c216..0000000000000 --- a/.github/workflows/claude.yml +++ /dev/null @@ -1,62 +0,0 @@ -# Integrates Claude Code as an AI assistant for issues and pull requests. -# Mention @claude in any issue comment, PR review comment, or PR review to -# interact with it, or assign the "claude" user to an issue. Claude -# authenticates via AWS Bedrock using OIDC — no long-lived API keys required. - -name: Claude Code - -on: - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - issues: - types: [opened, assigned] - pull_request_review: - types: [submitted] - -jobs: - claude: - runs-on: ubuntu-latest - - if: | - github.repository_owner == 'systemd' && - ((github.event_name == 'issue_comment' && - contains(github.event.comment.body, '@claude') && - contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association)) || - (github.event_name == 'pull_request_review_comment' && - contains(github.event.comment.body, '@claude') && - contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association)) || - (github.event_name == 'pull_request_review' && - contains(github.event.review.body, '@claude') && - contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.review.author_association)) || - (github.event_name == 'issues' && - github.event.action == 'assigned' && - github.event.assignee.login == 'claude')) - - permissions: - contents: read # Read repository contents - issues: write # Post comments on issues - pull-requests: write # Post comments and reviews on PRs - id-token: write # Authenticate with AWS via OIDC - actions: read # Access workflow run metadata - - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - fetch-depth: 1 - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 - with: - role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }} - role-session-name: GitHubActions-Claude-${{ github.run_id }} - aws-region: us-east-1 - - - name: Run Claude Code - uses: anthropics/claude-code-action@1fc90f3ed982521116d8ff6d85b948c9b12cae3e - with: - use_bedrock: "true" - github_token: ${{ secrets.GITHUB_TOKEN }} - claude_args: | - --model us.anthropic.claude-opus-4-6-v1 From a67cb4428795e5f97c2139cf8aab2df633bba86d Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 6 Mar 2026 15:25:53 +0100 Subject: [PATCH 0125/1296] ci: React to issue_comment in claude review workflow issue_comment is the trigger that fires on regular pull request comments, so we have to trigger the review workflow on that as well. --- .github/workflows/claude-review.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index bba7bdd1b5751..e78ffbcfbee8a 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -5,6 +5,10 @@ name: Claude Review on: + # Strangely enough you have to use issue_comment to react to regular comments on PRs. + # See https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#pull_request_comment-use-issue_comment. + issue_comment: + types: [created] pull_request_review_comment: types: [created] pull_request_review: @@ -18,6 +22,9 @@ jobs: if: | github.repository_owner == 'systemd' && + ((github.event_name == 'issue_comment' && + contains(github.event.comment.body, '@claude') && + contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association)) || (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude') && contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association)) || From c7c0ea91aa6761b761909c99f9310bafb92b364d Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 6 Mar 2026 15:29:40 +0100 Subject: [PATCH 0126/1296] ci: Fix missing parentheses in claude review workflow --- .github/workflows/claude-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index e78ffbcfbee8a..1c2a136361673 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -30,7 +30,7 @@ jobs: contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association)) || (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') && - contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.review.author_association)) + contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.review.author_association))) permissions: contents: read # Read repository contents From 45830bc8d28513ebe7b57df178bfedeff75b80cf Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Fri, 6 Mar 2026 16:09:21 +0100 Subject: [PATCH 0127/1296] test: drop some extraneous whitespaces --- test/units/TEST-74-AUX-UTILS.userdbctl.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/units/TEST-74-AUX-UTILS.userdbctl.sh b/test/units/TEST-74-AUX-UTILS.userdbctl.sh index 40818bfa6fe26..42811e7415835 100755 --- a/test/units/TEST-74-AUX-UTILS.userdbctl.sh +++ b/test/units/TEST-74-AUX-UTILS.userdbctl.sh @@ -51,13 +51,13 @@ assert_eq "$(userdbctl user 2147352577 -j | jq -r .userName)" foreign-1 assert_eq "$(userdbctl user 2147418110 -j | jq -r .userName)" foreign-65534 # Make sure that -F shows same data as if we'd ask directly -userdbctl user root -j | userdbctl -F- user | cmp - <(userdbctl user root) -userdbctl user test-74-userdbctl -j | userdbctl -F- user | cmp - <(userdbctl user test-74-userdbctl) -userdbctl user 65534 -j | userdbctl -F- user | cmp - <(userdbctl user 65534) +userdbctl user root -j | userdbctl -F- user | cmp - <(userdbctl user root) +userdbctl user test-74-userdbctl -j | userdbctl -F- user | cmp - <(userdbctl user test-74-userdbctl) +userdbctl user 65534 -j | userdbctl -F- user | cmp - <(userdbctl user 65534) -userdbctl group root -j | userdbctl -F- group | cmp - <(userdbctl group root) -userdbctl group test-74-userdbctl -j | userdbctl -F- group | cmp - <(userdbctl group test-74-userdbctl) -userdbctl group 65534 -j | userdbctl -F- group | cmp - <(userdbctl group 65534) +userdbctl group root -j | userdbctl -F- group | cmp - <(userdbctl group root) +userdbctl group test-74-userdbctl -j | userdbctl -F- group | cmp - <(userdbctl group test-74-userdbctl) +userdbctl group 65534 -j | userdbctl -F- group | cmp - <(userdbctl group 65534) # Ensure NSS doesn't try to automount via open_tree if [[ ! -v ASAN_OPTIONS ]]; then From 1e2517bf2ee1b55c7c2406574f95b7d5788f6179 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Fri, 6 Mar 2026 16:09:35 +0100 Subject: [PATCH 0128/1296] shared: fix segfault when processing matchHostname field Fix a typo which causes a segfault when processing a user record with matchHostname when it's an array instead of a simple string: $ echo '{"userName":"crashhostarray","perMachine":[{"matchHostname":["host1","host2"],"locked":false}]}' | userdbctl -F - Segmentation fault (core dumped) $ coredumpctl info ... Message: Process 1172301 (userdbctl) of user 1000 dumped core. Module libz.so.1 from rpm zlib-ng-2.3.3-1.fc43.x86_64 Module libcrypto.so.3 from rpm openssl-3.5.4-2.fc43.x86_64 Stack trace of thread 1172301: #0 0x00007fded7b3a656 __strcmp_evex (libc.so.6 + 0x159656) #1 0x00007fded7e95397 per_machine_hostname_match (libsystemd-shared-260.so + 0x295397) #2 0x00007fded7e955b5 per_machine_match (libsystemd-shared-260.so + 0x2955b5) #3 0x00007fded7e957c6 dispatch_per_machine (libsystemd-shared-260.so + 0x2957c6) #4 0x00007fded7e96c97 user_record_load (libsystemd-shared-260.so + 0x296c97) #5 0x000000000040572d display_user (/home/fsumsal/repos/@systemd/systemd/build/userdbctl + 0x572d) #6 0x00007fded7ea9727 dispatch_verb (libsystemd-shared-260.so + 0x2a9727) #7 0x000000000041077c run (/home/fsumsal/repos/@systemd/systemd/build/userdbctl + 0x1077c) #8 0x00000000004107ce main (/home/fsumsal/repos/@systemd/systemd/build/userdbctl + 0x107ce) #9 0x00007fded79e45b5 __libc_start_call_main (libc.so.6 + 0x35b5) #10 0x00007fded79e4668 __libc_start_main@@GLIBC_2.34 (libc.so.6 + 0x3668) #11 0x00000000004038d5 _start (/home/fsumsal/repos/@systemd/systemd/build/userdbctl + 0x38d5) ELF object binary architecture: AMD x86-64 --- src/shared/user-record.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/user-record.c b/src/shared/user-record.c index e237f6e6ca2db..ff03bcafc3a1d 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -1161,7 +1161,7 @@ int per_machine_hostname_match(sd_json_variant *hns, sd_json_dispatch_flags_t fl continue; } - if (streq(sd_json_variant_string(hns), hn)) + if (streq(sd_json_variant_string(e), hn)) return true; } From 54ed92e17c7aaa8149491d5f36a1e16890d79bfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 23 Feb 2026 10:49:27 +0100 Subject: [PATCH 0129/1296] shared/format-table: use 'char*'-style in function signatures --- src/shared/format-table.c | 24 ++++++++++++------------ src/shared/format-table.h | 14 +++++++------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/shared/format-table.c b/src/shared/format-table.c index 8b5b88370f13c..18cebf8628551 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -159,7 +159,7 @@ struct Table { bool *reverse_map; }; -Table *table_new_raw(size_t n_columns) { +Table* table_new_raw(size_t n_columns) { _cleanup_(table_unrefp) Table *t = NULL; assert(n_columns > 0); @@ -179,7 +179,7 @@ Table *table_new_raw(size_t n_columns) { return TAKE_PTR(t); } -Table *table_new_internal(const char *first_header, ...) { +Table* table_new_internal(const char *first_header, ...) { _cleanup_(table_unrefp) Table *t = NULL; size_t n_columns = 1; va_list ap; @@ -216,7 +216,7 @@ Table *table_new_internal(const char *first_header, ...) { return TAKE_PTR(t); } -Table *table_new_vertical(void) { +Table* table_new_vertical(void) { _cleanup_(table_unrefp) Table *t = NULL; TableCell *cell; @@ -242,7 +242,7 @@ Table *table_new_vertical(void) { return TAKE_PTR(t); } -static TableData *table_data_free(TableData *d) { +static TableData* table_data_free(TableData *d) { assert(d); free(d->formatted); @@ -260,7 +260,7 @@ static TableData *table_data_free(TableData *d) { DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free); DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref); -Table *table_unref(Table *t) { +Table* table_unref(Table *t) { if (!t) return NULL; @@ -425,7 +425,7 @@ static bool table_data_matches( return memcmp_safe(data, d->data, l) == 0; } -static TableData *table_data_new( +static TableData* table_data_new( TableDataType type, const void *data, size_t minimum_width, @@ -1602,7 +1602,7 @@ static char* format_strv_width(char **strv, size_t column_width) { return buf; } -static const char *table_data_format( +static const char* table_data_format( Table *t, TableData *d, bool avoid_uppercasing, @@ -2115,7 +2115,7 @@ static const char *table_data_format( return d->formatted; } -static const char *table_data_format_strip_ansi( +static const char* table_data_format_strip_ansi( Table *t, TableData *d, bool avoid_uppercasing, @@ -2252,7 +2252,7 @@ static int table_data_requested_width_height( return truncation_applied; } -static char *align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) { +static char* align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) { size_t w = 0, space, lspace, old_length, clickable_length; _cleanup_free_ char *clickable = NULL; const char *p; @@ -2851,7 +2851,7 @@ int table_set_reverse(Table *t, size_t column, bool b) { return 0; } -TableCell *table_get_cell(Table *t, size_t row, size_t column) { +TableCell* table_get_cell(Table *t, size_t row, size_t column) { size_t i; assert(t); @@ -2866,7 +2866,7 @@ TableCell *table_get_cell(Table *t, size_t row, size_t column) { return TABLE_INDEX_TO_CELL(i); } -const void *table_get(Table *t, TableCell *cell) { +const void* table_get(Table *t, TableCell *cell) { TableData *d; assert(t); @@ -3124,7 +3124,7 @@ static int table_make_json_field_name(Table *t, TableData *d, char **ret) { return 0; } -static const char *table_get_json_field_name(Table *t, size_t idx) { +static const char* table_get_json_field_name(Table *t, size_t idx) { assert(t); return idx < t->n_json_fields ? t->json_fields[idx] : NULL; diff --git a/src/shared/format-table.h b/src/shared/format-table.h index ec9989c54c88a..997ac20eb6882 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -93,11 +93,11 @@ typedef enum TableErsatz { typedef struct Table Table; typedef struct TableCell TableCell; -Table *table_new_internal(const char *first_header, ...) _sentinel_; +Table* table_new_internal(const char *first_header, ...) _sentinel_; #define table_new(...) table_new_internal(__VA_ARGS__, NULL) -Table *table_new_raw(size_t n_columns); -Table *table_new_vertical(void); -Table *table_unref(Table *t); +Table* table_new_raw(size_t n_columns); +Table* table_new_vertical(void); +Table* table_unref(Table *t); DEFINE_TRIVIAL_CLEANUP_FUNC(Table*, table_unref); @@ -159,10 +159,10 @@ size_t table_get_columns(Table *t); size_t table_get_current_column(Table *t); -TableCell *table_get_cell(Table *t, size_t row, size_t column); +TableCell* table_get_cell(Table *t, size_t row, size_t column); -const void *table_get(Table *t, TableCell *cell); -const void *table_get_at(Table *t, size_t row, size_t column); +const void* table_get(Table *t, TableCell *cell); +const void* table_get_at(Table *t, size_t row, size_t column); int table_to_json(Table *t, sd_json_variant **ret); int table_print_json(Table *t, FILE *f, sd_json_format_flags_t json_flags); From 3abe1e53f657982a7a6ab6861cf0b3962cbbb3b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 3 Mar 2026 09:42:41 +0100 Subject: [PATCH 0130/1296] basic/allow-util: make free_many non-inline Definition of free_many is moved to the .c file, no particular reason for it to be inline and we can make the header file shorter. --- src/basic/alloc-util.c | 7 +++++++ src/basic/alloc-util.h | 7 +------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/basic/alloc-util.c b/src/basic/alloc-util.c index 58b70dede4b01..94102dd430c04 100644 --- a/src/basic/alloc-util.c +++ b/src/basic/alloc-util.c @@ -142,3 +142,10 @@ size_t malloc_sizeof_safe(void **xp) { assert_not_reached(); return sz; } + +void free_many(void **p, size_t n) { + assert(p || n == 0); + + FOREACH_ARRAY(i, p, n) + *i = mfree(*i); +} diff --git a/src/basic/alloc-util.h b/src/basic/alloc-util.h index cd062d5fe3efc..7d5c1da61f2dd 100644 --- a/src/basic/alloc-util.h +++ b/src/basic/alloc-util.h @@ -206,12 +206,7 @@ size_t malloc_sizeof_safe(void **xp); VOID_0)) /* Free every element of the array. */ -static inline void free_many(void **p, size_t n) { - assert(p || n == 0); - - FOREACH_ARRAY(i, p, n) - *i = mfree(*i); -} +void free_many(void **p, size_t n); /* Typesafe wrapper for char** rather than void**. Unfortunately C won't implicitly cast this. */ static inline void free_many_charp(char **c, size_t n) { From d1278e1847392ef5c46acd0f1da689d5c986b3d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 3 Mar 2026 09:55:09 +0100 Subject: [PATCH 0131/1296] basic/alloc-util: make realloc0 non-inline It's actually only used in one place in libsystemd and moving it even makes libsystemd smaller (in a non-optimized build): $ ls -l build/libsystemd.so.0.43.0* -rwxr-xr-x 1 zbyszek zbyszek 5763336 Mar 3 09:54 build/libsystemd.so.0.43.0-old -rwxr-xr-x 1 zbyszek zbyszek 5763216 Mar 3 09:54 build/libsystemd.so.0.43.0 Also, move the definitions in the .h file so that similar functions are grouped together and then move the definitions around in the .c file so that they are in the same order as in the header. --- src/basic/alloc-util.c | 56 ++++++++++++++++++++++---------- src/basic/alloc-util.h | 74 ++++++++++++++++-------------------------- 2 files changed, 66 insertions(+), 64 deletions(-) diff --git a/src/basic/alloc-util.c b/src/basic/alloc-util.c index 94102dd430c04..1eb98c1199d29 100644 --- a/src/basic/alloc-util.c +++ b/src/basic/alloc-util.c @@ -34,6 +34,44 @@ void* memdup_suffix0(const void *p, size_t l) { return memcpy_safe(ret, p, l); } +size_t malloc_sizeof_safe(void **xp) { + if (_unlikely_(!xp || !*xp)) + return 0; + + size_t sz = malloc_usable_size(*xp); + *xp = expand_to_usable(*xp, sz); + /* GCC doesn't see the _returns_nonnull_ when built with ubsan, so yet another hint to make it doubly + * clear that expand_to_usable won't return NULL. + * See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=79265 */ + if (!*xp) + assert_not_reached(); + return sz; +} + +void* expand_to_usable(void *ptr, size_t newsize _unused_) { + return ptr; +} + +void* realloc0(void *p, size_t new_size) { + size_t old_size; + void *q; + + /* Like realloc(), but initializes anything appended to zero */ + + old_size = MALLOC_SIZEOF_SAFE(p); + + q = realloc(p, new_size); + if (!q) + return NULL; + + new_size = MALLOC_SIZEOF_SAFE(q); /* Update with actually allocated space */ + + if (new_size > old_size) + memset((uint8_t*) q + old_size, 0, new_size - old_size); + + return q; +} + void* greedy_realloc( void **p, size_t need, @@ -125,24 +163,6 @@ void* greedy_realloc_append( return q; } -void *expand_to_usable(void *ptr, size_t newsize _unused_) { - return ptr; -} - -size_t malloc_sizeof_safe(void **xp) { - if (_unlikely_(!xp || !*xp)) - return 0; - - size_t sz = malloc_usable_size(*xp); - *xp = expand_to_usable(*xp, sz); - /* GCC doesn't see the _returns_nonnull_ when built with ubsan, so yet another hint to make it doubly - * clear that expand_to_usable won't return NULL. - * See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=79265 */ - if (!*xp) - assert_not_reached(); - return sz; -} - void free_many(void **p, size_t n) { assert(p || n == 0); diff --git a/src/basic/alloc-util.h b/src/basic/alloc-util.h index 7d5c1da61f2dd..2c40c4771cdc3 100644 --- a/src/basic/alloc-util.h +++ b/src/basic/alloc-util.h @@ -108,6 +108,34 @@ static inline void *memdup_suffix0_multiply(const void *p, size_t need, size_t s return memdup_suffix0(p, size * need); } +size_t malloc_sizeof_safe(void **xp); + +/* This returns the number of usable bytes in a malloc()ed region as per malloc_usable_size(), which may + * return a value larger than the size that was actually allocated. Access to that additional memory is + * discouraged because it violates the C standard; a compiler cannot see that this as valid. To help the + * compiler out, the MALLOC_SIZEOF_SAFE macro 'allocates' the usable size using a dummy allocator function + * expand_to_usable. There is a possibility of malloc_usable_size() returning different values during the + * lifetime of an object, which may cause problems, but the glibc allocator does not do that at the moment. */ +#define MALLOC_SIZEOF_SAFE(x) \ + malloc_sizeof_safe((void**) &__builtin_choose_expr(__builtin_constant_p(x), (void*) { NULL }, (x))) + +/* Inspired by ELEMENTSOF() but operates on malloc()'ed memory areas: typesafely returns the number of items + * that fit into the specified memory block */ +#define MALLOC_ELEMENTSOF(x) \ + (__builtin_choose_expr( \ + __builtin_types_compatible_p(typeof(x), typeof(&*(x))), \ + MALLOC_SIZEOF_SAFE(x)/sizeof((x)[0]), \ + VOID_0)) + +/* Dummy allocator to tell the compiler that the new size of p is newsize. The implementation returns the + * pointer as is; the only reason for its existence is as a conduit for the _alloc_ attribute. This must not + * be inlined (hence a non-static function with _noinline_ because LTO otherwise tries to inline it) because + * gcc then loses the attributes on the function. + * See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96503 */ +void *expand_to_usable(void *p, size_t newsize) _alloc_(2) _returns_nonnull_ _noinline_; + +void* realloc0(void *p, size_t new_size) _alloc_(2); + static inline size_t GREEDY_ALLOC_ROUND_UP(size_t l) { size_t m; @@ -179,32 +207,6 @@ void* greedy_realloc_append(void **p, size_t *n_p, const void *from, size_t n_fr # define msan_unpoison(r, s) #endif -/* Dummy allocator to tell the compiler that the new size of p is newsize. The implementation returns the - * pointer as is; the only reason for its existence is as a conduit for the _alloc_ attribute. This must not - * be inlined (hence a non-static function with _noinline_ because LTO otherwise tries to inline it) because - * gcc then loses the attributes on the function. - * See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96503 */ -void *expand_to_usable(void *p, size_t newsize) _alloc_(2) _returns_nonnull_ _noinline_; - -size_t malloc_sizeof_safe(void **xp); - -/* This returns the number of usable bytes in a malloc()ed region as per malloc_usable_size(), which may - * return a value larger than the size that was actually allocated. Access to that additional memory is - * discouraged because it violates the C standard; a compiler cannot see that this as valid. To help the - * compiler out, the MALLOC_SIZEOF_SAFE macro 'allocates' the usable size using a dummy allocator function - * expand_to_usable. There is a possibility of malloc_usable_size() returning different values during the - * lifetime of an object, which may cause problems, but the glibc allocator does not do that at the moment. */ -#define MALLOC_SIZEOF_SAFE(x) \ - malloc_sizeof_safe((void**) &__builtin_choose_expr(__builtin_constant_p(x), (void*) { NULL }, (x))) - -/* Inspired by ELEMENTSOF() but operates on malloc()'ed memory areas: typesafely returns the number of items - * that fit into the specified memory block */ -#define MALLOC_ELEMENTSOF(x) \ - (__builtin_choose_expr( \ - __builtin_types_compatible_p(typeof(x), typeof(&*(x))), \ - MALLOC_SIZEOF_SAFE(x)/sizeof((x)[0]), \ - VOID_0)) - /* Free every element of the array. */ void free_many(void **p, size_t n); @@ -212,23 +214,3 @@ void free_many(void **p, size_t n); static inline void free_many_charp(char **c, size_t n) { free_many((void**) c, n); } - -_alloc_(2) static inline void *realloc0(void *p, size_t new_size) { - size_t old_size; - void *q; - - /* Like realloc(), but initializes anything appended to zero */ - - old_size = MALLOC_SIZEOF_SAFE(p); - - q = realloc(p, new_size); - if (!q) - return NULL; - - new_size = MALLOC_SIZEOF_SAFE(q); /* Update with actually allocated space */ - - if (new_size > old_size) - memset((uint8_t*) q + old_size, 0, new_size - old_size); - - return q; -} From 52abc9fe9655b21329eda7cf7a7d73fc3a7bdde7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 3 Mar 2026 10:00:29 +0100 Subject: [PATCH 0132/1296] basic/stdio-util: introduce asprintf_safe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit asprintf is nice to use, but the _documented_ error return convention is unclear: > If memory allocation wasn't possible, or some other error occurs, > these functions will return -1, and the contents of strp are undefined. What exactly "undefined" means is up for debate: if it was really undefined, the caller wouldn't be able to meaningfully clean up, because they wouldn't know if strp is a valid pointer. So far we interpreted "undefined" — in some parts of the code base — as "either NULL or a valid pointer that needs to be freed", and — in other parts of the codebase — as "always NULL". I checked glibc and musl, and they both uncoditionally set the output pointer to NULL on failure. There is also no information _why_ asprintf failed. It could be an allocation error or format string error. But we just don't have this information. Let's add a wrapper that either returns a good string or a NULL pointer. Since there's just one failure result, we don't need a separate return value and an output argument and can simplify callers. --- docs/CODING_STYLE.md | 6 +++--- src/basic/meson.build | 1 + src/basic/stdio-util.c | 18 ++++++++++++++++++ src/basic/stdio-util.h | 2 ++ 4 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 src/basic/stdio-util.c diff --git a/docs/CODING_STYLE.md b/docs/CODING_STYLE.md index 767ab6734bb83..085c97df5ce44 100644 --- a/docs/CODING_STYLE.md +++ b/docs/CODING_STYLE.md @@ -754,9 +754,9 @@ SPDX-License-Identifier: LGPL-2.1-or-later section of the `alloca(3)` man page. - If you want to concatenate two or more strings, consider using `strjoina()` - or `strjoin()` rather than `asprintf()`, as the latter is a lot slower. This - matters particularly in inner loops (but note that `strjoina()` cannot be - used there). + or `strjoin()` rather than `asprintf()` or `asprintf_safe`, as the latter is + a lot slower. This matters particularly in inner loops (but note that + `strjoina()` cannot be used there). ## Runtime Behaviour diff --git a/src/basic/meson.build b/src/basic/meson.build index b8ffa80244d07..775dc1fa3d595 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -99,6 +99,7 @@ basic_sources = files( 'sort-util.c', 'stat-util.c', 'static-destruct.c', + 'stdio-util.c', 'strbuf.c', 'string-table.c', 'string-util.c', diff --git a/src/basic/stdio-util.c b/src/basic/stdio-util.c new file mode 100644 index 0000000000000..53267f08e2368 --- /dev/null +++ b/src/basic/stdio-util.c @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "stdio-util.h" + +char* asprintf_safe(const char *restrict fmt, ...) { + _cleanup_free_ char *buf = NULL; + va_list ap; + int r; + + va_start(ap, fmt); + r = vasprintf(&buf, fmt, ap); + va_end(ap); + + if (r < 0) + return NULL; + return TAKE_PTR(buf); +} diff --git a/src/basic/stdio-util.h b/src/basic/stdio-util.h index f8ab0f0012fb7..f8055b3853bcc 100644 --- a/src/basic/stdio-util.h +++ b/src/basic/stdio-util.h @@ -21,6 +21,8 @@ static inline char* snprintf_ok(char *buf, size_t len, const char *format, ...) #define xsprintf(buf, fmt, ...) \ assert_message_se(snprintf_ok(buf, ELEMENTSOF(buf), fmt, ##__VA_ARGS__), "xsprintf: buffer too small") +char* asprintf_safe(const char *restrict fmt, ...) _printf_(1, 2); + #define VA_FORMAT_ADVANCE(format, ap) \ do { \ int _argtypes[128]; \ From 1dc6445bbb911b962ee6e8ef59d56f515791cae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 2 Mar 2026 15:35:41 +0100 Subject: [PATCH 0133/1296] shared/format-table: fix potential memleaks of d->formatted We don't always return d->formatted, even if it is available. And depending on the cell type, we'd either overwrite it directly or free first. Let's always free it upfront and then set unconditionally. (In this case, we don't need to spend effort on preserving the existing value. It's just a cache.) Setting the variable directly allows many temporary variables to be eliminated. Also use asprintf_safe() to simplify the allocation of the buffer. This is probably a tiny bit slower than the direct allocation, but table formatting shouldn't be a hot path. --- src/shared/format-table.c | 397 ++++++++------------------------------ 1 file changed, 84 insertions(+), 313 deletions(-) diff --git a/src/shared/format-table.c b/src/shared/format-table.c index 18cebf8628551..f400602b343d5 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -1616,6 +1616,8 @@ static const char* table_data_format( (d->type != TABLE_STRV_WRAPPED || d->formatted_for_width == column_width)) return d->formatted; + d->formatted = mfree(d->formatted); + switch (d->type) { case TABLE_EMPTY: return table_ersatz_string(t); @@ -1649,18 +1651,13 @@ static const char* table_data_format( *q = 0; return d->formatted; - } else if (d->type == TABLE_FIELD) { - d->formatted = strjoin(s, ":"); - if (!d->formatted) - return NULL; - - return d->formatted; } - if (bn) { - d->formatted = TAKE_PTR(bn); - return d->formatted; - } + if (d->type == TABLE_FIELD) + return (d->formatted = strjoin(s, ":")); + + if (bn) + return (d->formatted = TAKE_PTR(bn)); return d->string; } @@ -1669,26 +1666,20 @@ static const char* table_data_format( if (strv_isempty(d->strv)) return table_ersatz_string(t); - d->formatted = strv_join(d->strv, "\n"); - if (!d->formatted) - return NULL; - break; + return (d->formatted = strv_join(d->strv, "\n")); - case TABLE_STRV_WRAPPED: { + case TABLE_STRV_WRAPPED: if (strv_isempty(d->strv)) return table_ersatz_string(t); - char *buf = format_strv_width(d->strv, column_width); - if (!buf) + d->formatted = format_strv_width(d->strv, column_width); + if (!d->formatted) return NULL; - free_and_replace(d->formatted, buf); d->formatted_for_width = column_width; if (have_soft) *have_soft = true; - - break; - } + return d->formatted; case TABLE_BOOLEAN: return yes_no(d->boolean); @@ -1702,12 +1693,12 @@ static const char* table_data_format( case TABLE_TIMESTAMP_RELATIVE_MONOTONIC: case TABLE_TIMESTAMP_LEFT: case TABLE_TIMESTAMP_DATE: { - _cleanup_free_ char *p = NULL; char *ret; - p = new(char, - IN_SET(d->type, TABLE_TIMESTAMP_RELATIVE, TABLE_TIMESTAMP_RELATIVE_MONOTONIC, TABLE_TIMESTAMP_LEFT) ? - FORMAT_TIMESTAMP_RELATIVE_MAX : FORMAT_TIMESTAMP_MAX); + _cleanup_free_ char *p = new( + char, + IN_SET(d->type, TABLE_TIMESTAMP_RELATIVE, TABLE_TIMESTAMP_RELATIVE_MONOTONIC, TABLE_TIMESTAMP_LEFT) ? + FORMAT_TIMESTAMP_RELATIVE_MAX : FORMAT_TIMESTAMP_MAX); if (!p) return NULL; @@ -1726,16 +1717,13 @@ static const char* table_data_format( if (!ret) return "-"; - d->formatted = TAKE_PTR(p); - break; + return (d->formatted = TAKE_PTR(p)); } case TABLE_TIMESPAN: case TABLE_TIMESPAN_MSEC: case TABLE_TIMESPAN_DAY: { - _cleanup_free_ char *p = NULL; - - p = new(char, FORMAT_TIMESPAN_MAX); + _cleanup_free_ char *p = new(char, FORMAT_TIMESPAN_MAX); if (!p) return NULL; @@ -1744,344 +1732,137 @@ static const char* table_data_format( d->type == TABLE_TIMESPAN_MSEC ? USEC_PER_MSEC : USEC_PER_DAY)) return "-"; - d->formatted = TAKE_PTR(p); - break; + return (d->formatted = TAKE_PTR(p)); } case TABLE_SIZE: { - _cleanup_free_ char *p = NULL; - - p = new(char, FORMAT_BYTES_MAX); + _cleanup_free_ char *p = new(char, FORMAT_BYTES_MAX); if (!p) return NULL; if (!format_bytes(p, FORMAT_BYTES_MAX, d->size)) return table_ersatz_string(t); - d->formatted = TAKE_PTR(p); - break; + return (d->formatted = TAKE_PTR(p)); } case TABLE_BPS: { - _cleanup_free_ char *p = NULL; - size_t n; - - p = new(char, FORMAT_BYTES_MAX+2); + _cleanup_free_ char *p = new(char, FORMAT_BYTES_MAX+2); if (!p) return NULL; if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, FORMAT_BYTES_BELOW_POINT)) return table_ersatz_string(t); - n = strlen(p); + size_t n = strlen(p); strscpy(p + n, FORMAT_BYTES_MAX + 2 - n, "bps"); - d->formatted = TAKE_PTR(p); - break; - } - - case TABLE_INT: { - _cleanup_free_ char *p = NULL; - - p = new(char, DECIMAL_STR_WIDTH(d->int_val) + 1); - if (!p) - return NULL; - - sprintf(p, "%i", d->int_val); - d->formatted = TAKE_PTR(p); - break; - } - - case TABLE_INT8: { - _cleanup_free_ char *p = NULL; - - p = new(char, DECIMAL_STR_WIDTH(d->int8) + 1); - if (!p) - return NULL; - - sprintf(p, "%" PRIi8, d->int8); - d->formatted = TAKE_PTR(p); - break; - } - - case TABLE_INT16: { - _cleanup_free_ char *p = NULL; - - p = new(char, DECIMAL_STR_WIDTH(d->int16) + 1); - if (!p) - return NULL; - - sprintf(p, "%" PRIi16, d->int16); - d->formatted = TAKE_PTR(p); - break; - } - - case TABLE_INT32: { - _cleanup_free_ char *p = NULL; - - p = new(char, DECIMAL_STR_WIDTH(d->int32) + 1); - if (!p) - return NULL; - - sprintf(p, "%" PRIi32, d->int32); - d->formatted = TAKE_PTR(p); - break; - } - - case TABLE_INT64: { - _cleanup_free_ char *p = NULL; - - p = new(char, DECIMAL_STR_WIDTH(d->int64) + 1); - if (!p) - return NULL; - - sprintf(p, "%" PRIi64, d->int64); - d->formatted = TAKE_PTR(p); - break; - } - - case TABLE_UINT: { - _cleanup_free_ char *p = NULL; - - p = new(char, DECIMAL_STR_WIDTH(d->uint_val) + 1); - if (!p) - return NULL; - - sprintf(p, "%u", d->uint_val); - d->formatted = TAKE_PTR(p); - break; - } - - case TABLE_UINT8: { - _cleanup_free_ char *p = NULL; - - p = new(char, DECIMAL_STR_WIDTH(d->uint8) + 1); - if (!p) - return NULL; - - sprintf(p, "%" PRIu8, d->uint8); - d->formatted = TAKE_PTR(p); - break; - } - - case TABLE_UINT16: { - _cleanup_free_ char *p = NULL; - - p = new(char, DECIMAL_STR_WIDTH(d->uint16) + 1); - if (!p) - return NULL; - - sprintf(p, "%" PRIu16, d->uint16); - d->formatted = TAKE_PTR(p); - break; - } - - case TABLE_UINT32: { - _cleanup_free_ char *p = NULL; - - p = new(char, DECIMAL_STR_WIDTH(d->uint32) + 1); - if (!p) - return NULL; - - sprintf(p, "%" PRIu32, d->uint32); - d->formatted = TAKE_PTR(p); - break; + return (d->formatted = TAKE_PTR(p)); } - case TABLE_UINT32_HEX: { - _cleanup_free_ char *p = NULL; - - p = new(char, 8 + 1); - if (!p) - return NULL; - - sprintf(p, "%" PRIx32, d->uint32); - d->formatted = TAKE_PTR(p); - break; - } - - case TABLE_UINT32_HEX_0x: { - _cleanup_free_ char *p = NULL; - - p = new(char, 2 + 8 + 1); - if (!p) - return NULL; - - sprintf(p, "0x%" PRIx32, d->uint32); - d->formatted = TAKE_PTR(p); - break; - } + case TABLE_INT: + return (d->formatted = asprintf_safe("%i", d->int_val)); - case TABLE_UINT64: { - _cleanup_free_ char *p = NULL; + case TABLE_INT8: + return (d->formatted = asprintf_safe("%" PRIi8, d->int8)); - p = new(char, DECIMAL_STR_WIDTH(d->uint64) + 1); - if (!p) - return NULL; + case TABLE_INT16: + return (d->formatted = asprintf_safe("%" PRIi16, d->int16)); - sprintf(p, "%" PRIu64, d->uint64); - d->formatted = TAKE_PTR(p); - break; - } + case TABLE_INT32: + return (d->formatted = asprintf_safe("%" PRIi32, d->int32)); - case TABLE_UINT64_HEX: { - _cleanup_free_ char *p = NULL; + case TABLE_INT64: + return (d->formatted = asprintf_safe("%" PRIi64, d->int64)); - p = new(char, 16 + 1); - if (!p) - return NULL; + case TABLE_UINT: + return (d->formatted = asprintf_safe("%u", d->uint_val)); - sprintf(p, "%" PRIx64, d->uint64); - d->formatted = TAKE_PTR(p); - break; - } + case TABLE_UINT8: + return (d->formatted = asprintf_safe("%" PRIu8, d->uint8)); - case TABLE_UINT64_HEX_0x: { - _cleanup_free_ char *p = NULL; + case TABLE_UINT16: + return (d->formatted = asprintf_safe("%" PRIu16, d->uint16)); - p = new(char, 2 + 16 + 1); - if (!p) - return NULL; + case TABLE_UINT32: + return (d->formatted = asprintf_safe("%" PRIu32, d->uint32)); - sprintf(p, "0x%" PRIx64, d->uint64); - d->formatted = TAKE_PTR(p); - break; - } + case TABLE_UINT32_HEX: + return (d->formatted = asprintf_safe("%" PRIx32, d->uint32)); - case TABLE_PERCENT: { - _cleanup_free_ char *p = NULL; + case TABLE_UINT32_HEX_0x: + return (d->formatted = asprintf_safe("0x%" PRIx32, d->uint32)); - p = new(char, DECIMAL_STR_WIDTH(d->percent) + 2); - if (!p) - return NULL; + case TABLE_UINT64: + return (d->formatted = asprintf_safe("%" PRIu64, d->uint64)); - sprintf(p, "%i%%" , d->percent); - d->formatted = TAKE_PTR(p); - break; - } + case TABLE_UINT64_HEX: + return (d->formatted = asprintf_safe("%" PRIx64, d->uint64)); - case TABLE_IFINDEX: { - _cleanup_free_ char *p = NULL; + case TABLE_UINT64_HEX_0x: + return (d->formatted = asprintf_safe("0x%" PRIx64, d->uint64)); - if (format_ifname_full_alloc(d->ifindex, FORMAT_IFNAME_IFINDEX, &p) < 0) - return NULL; + case TABLE_PERCENT: + return (d->formatted = asprintf_safe("%i%%" , d->percent)); - d->formatted = TAKE_PTR(p); - break; - } + case TABLE_IFINDEX: + (void) format_ifname_full_alloc(d->ifindex, FORMAT_IFNAME_IFINDEX, &d->formatted); + return d->formatted; case TABLE_IN_ADDR: - case TABLE_IN6_ADDR: { - _cleanup_free_ char *p = NULL; - - if (in_addr_to_string(d->type == TABLE_IN_ADDR ? AF_INET : AF_INET6, - &d->address, &p) < 0) - return NULL; - - d->formatted = TAKE_PTR(p); - break; - } - - case TABLE_ID128: { - char *p; + case TABLE_IN6_ADDR: + (void) in_addr_to_string(d->type == TABLE_IN_ADDR ? AF_INET : AF_INET6, + &d->address, + &d->formatted); + return d->formatted; - p = new(char, SD_ID128_STRING_MAX); - if (!p) + case TABLE_ID128: + d->formatted = new(char, SD_ID128_STRING_MAX); + if (!d->formatted) return NULL; - d->formatted = sd_id128_to_string(d->id128, p); - break; - } - - case TABLE_UUID: { - char *p; + return sd_id128_to_string(d->id128, d->formatted); - p = new(char, SD_ID128_UUID_STRING_MAX); - if (!p) + case TABLE_UUID: + d->formatted = new(char, SD_ID128_UUID_STRING_MAX); + if (!d->formatted) return NULL; - d->formatted = sd_id128_to_uuid_string(d->id128, p); - break; - } - - case TABLE_UID: { - char *p; + return sd_id128_to_uuid_string(d->id128, d->formatted); + case TABLE_UID: if (!uid_is_valid(d->uid)) return table_ersatz_string(t); - p = new(char, DECIMAL_STR_WIDTH(d->uid) + 1); - if (!p) - return NULL; - sprintf(p, UID_FMT, d->uid); - - d->formatted = p; - break; - } - - case TABLE_GID: { - char *p; + return (d->formatted = asprintf_safe(UID_FMT, d->uid)); + case TABLE_GID: if (!gid_is_valid(d->gid)) return table_ersatz_string(t); - p = new(char, DECIMAL_STR_WIDTH(d->gid) + 1); - if (!p) - return NULL; - sprintf(p, GID_FMT, d->gid); - - d->formatted = p; - break; - } - - case TABLE_PID: { - char *p; + return (d->formatted = asprintf_safe(GID_FMT, d->gid)); + case TABLE_PID: if (!pid_is_valid(d->pid)) return table_ersatz_string(t); - p = new(char, DECIMAL_STR_WIDTH(d->pid) + 1); - if (!p) - return NULL; - sprintf(p, PID_FMT, d->pid); - - d->formatted = p; - break; - } + return (d->formatted = asprintf_safe(PID_FMT, d->pid)); case TABLE_SIGNAL: { - const char *suffix; - char *p; - - suffix = signal_to_string(d->int_val); + const char *suffix = signal_to_string(d->int_val); if (!suffix) return table_ersatz_string(t); - p = strjoin("SIG", suffix); - if (!p) - return NULL; - - d->formatted = p; - break; + return (d->formatted = strjoin("SIG", suffix)); } - case TABLE_MODE: { - char *p; - + case TABLE_MODE: if (d->mode == MODE_INVALID) return table_ersatz_string(t); - p = new(char, 4 + 1); - if (!p) - return NULL; - - sprintf(p, "%04o", d->mode & 07777); - d->formatted = p; - break; - } + return (d->formatted = asprintf_safe("%04o", d->mode & 07777)); case TABLE_MODE_INODE_TYPE: - if (d->mode == MODE_INVALID) return table_ersatz_string(t); @@ -2091,28 +1872,18 @@ static const char* table_data_format( if (devnum_is_zero(d->devnum)) return table_ersatz_string(t); - if (asprintf(&d->formatted, DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(d->devnum)) < 0) - return NULL; - - break; + return (d->formatted = asprintf_safe(DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(d->devnum))); - case TABLE_JSON: { + case TABLE_JSON: if (!d->json) return table_ersatz_string(t); - char *p; - if (sd_json_variant_format(d->json, /* flags= */ 0, &p) < 0) - return NULL; - - d->formatted = p; - break; - } + (void) sd_json_variant_format(d->json, /* flags= */ 0, &d->formatted); + return d->formatted; default: assert_not_reached(); } - - return d->formatted; } static const char* table_data_format_strip_ansi( From dc0b4388365ead78584db4e027f6e02904c3631f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 3 Mar 2026 10:51:11 +0100 Subject: [PATCH 0134/1296] various: use asprintf_safe in more places --- src/basic/in-addr-util.c | 14 +++--- src/basic/rlimit-util.c | 17 ++++--- src/basic/string-table.c | 9 ++-- src/bless-boot/bless-boot.c | 17 ++++--- src/core/execute.c | 12 ++--- src/core/socket.c | 96 +++++++++++++++++-------------------- 6 files changed, 75 insertions(+), 90 deletions(-) diff --git a/src/basic/in-addr-util.c b/src/basic/in-addr-util.c index a47bdf7149121..957ce2a46655f 100644 --- a/src/basic/in-addr-util.c +++ b/src/basic/in-addr-util.c @@ -559,20 +559,18 @@ int in_addr_port_ifindex_name_to_string(int family, const union in_addr_union *u if (port > 0) { if (family == AF_INET6) { if (ifindex > 0) - r = asprintf(&x, "[%s]:%"PRIu16"%%%i%s%s", ip_str, port, ifindex, separator, server_name); + x = asprintf_safe("[%s]:%"PRIu16"%%%i%s%s", ip_str, port, ifindex, separator, server_name); else - r = asprintf(&x, "[%s]:%"PRIu16"%s%s", ip_str, port, separator, server_name); + x = asprintf_safe("[%s]:%"PRIu16"%s%s", ip_str, port, separator, server_name); } else - r = asprintf(&x, "%s:%"PRIu16"%s%s", ip_str, port, separator, server_name); + x = asprintf_safe("%s:%"PRIu16"%s%s", ip_str, port, separator, server_name); } else { if (ifindex > 0) - r = asprintf(&x, "%s%%%i%s%s", ip_str, ifindex, separator, server_name); - else { + x = asprintf_safe("%s%%%i%s%s", ip_str, ifindex, separator, server_name); + else x = strjoin(ip_str, separator, server_name); - r = x ? 0 : -ENOMEM; - } } - if (r < 0) + if (!x) return -ENOMEM; *ret = TAKE_PTR(x); diff --git a/src/basic/rlimit-util.c b/src/basic/rlimit-util.c index 331f2d1a5b607..ba1af81bb4270 100644 --- a/src/basic/rlimit-util.c +++ b/src/basic/rlimit-util.c @@ -289,26 +289,25 @@ int rlimit_parse(int resource, const char *val, struct rlimit *ret) { } int rlimit_format(const struct rlimit *rl, char **ret) { - _cleanup_free_ char *s = NULL; - int r; + char *s; assert(rl); assert(ret); if (rl->rlim_cur >= RLIM_INFINITY && rl->rlim_max >= RLIM_INFINITY) - r = free_and_strdup(&s, "infinity"); + s = strdup("infinity"); else if (rl->rlim_cur >= RLIM_INFINITY) - r = asprintf(&s, "infinity:" RLIM_FMT, rl->rlim_max); + s = asprintf_safe("infinity:" RLIM_FMT, rl->rlim_max); else if (rl->rlim_max >= RLIM_INFINITY) - r = asprintf(&s, RLIM_FMT ":infinity", rl->rlim_cur); + s = asprintf_safe(RLIM_FMT ":infinity", rl->rlim_cur); else if (rl->rlim_cur == rl->rlim_max) - r = asprintf(&s, RLIM_FMT, rl->rlim_cur); + s = asprintf_safe(RLIM_FMT, rl->rlim_cur); else - r = asprintf(&s, RLIM_FMT ":" RLIM_FMT, rl->rlim_cur, rl->rlim_max); - if (r < 0) + s = asprintf_safe(RLIM_FMT ":" RLIM_FMT, rl->rlim_cur, rl->rlim_max); + if (!s) return -ENOMEM; - *ret = TAKE_PTR(s); + *ret = s; return 0; } diff --git a/src/basic/string-table.c b/src/basic/string-table.c index 069cb40ec1a0d..461f94f9ee301 100644 --- a/src/basic/string-table.c +++ b/src/basic/string-table.c @@ -3,6 +3,7 @@ #include #include "parse-util.h" +#include "stdio-util.h" #include "string-table.h" #include "string-util.h" @@ -43,11 +44,11 @@ int string_table_lookup_to_string_fallback(const char * const *table, size_t len if (i < 0 || i > (ssize_t) max) return -ERANGE; - if (i < (ssize_t) len && table[i]) { + if (i < (ssize_t) len && table[i]) s = strdup(table[i]); - if (!s) - return -ENOMEM; - } else if (asprintf(&s, "%zd", i) < 0) + else + s = asprintf_safe("%zd", i); + if (!s) return -ENOMEM; *ret = s; diff --git a/src/bless-boot/bless-boot.c b/src/bless-boot/bless-boot.c index 9c717ace9d87d..ac989630aff0d 100644 --- a/src/bless-boot/bless-boot.c +++ b/src/bless-boot/bless-boot.c @@ -15,6 +15,7 @@ #include "parse-util.h" #include "path-util.h" #include "pretty-print.h" +#include "stdio-util.h" #include "string-util.h" #include "strv.h" #include "sync-util.h" @@ -299,7 +300,7 @@ static int make_good(const char *prefix, const char *suffix, char **ret) { } static int make_bad(const char *prefix, uint64_t done, const char *suffix, char **ret) { - _cleanup_free_ char *bad = NULL; + char *bad; assert(prefix); assert(suffix); @@ -308,16 +309,14 @@ static int make_bad(const char *prefix, uint64_t done, const char *suffix, char /* Generate the path we'd use on bad boots. Let's simply set the 'left' counter to zero, and keep the 'done' * counter. The information might be interesting to boot loaders, after all. */ - if (done == 0) { + if (done == 0) bad = strjoin(prefix, "+0", suffix); - if (!bad) - return -ENOMEM; - } else { - if (asprintf(&bad, "%s+0-%" PRIu64 "%s", prefix, done, suffix) < 0) - return -ENOMEM; - } + else + bad = asprintf_safe("%s+0-%" PRIu64 "%s", prefix, done, suffix); + if (!bad) + return -ENOMEM; - *ret = TAKE_PTR(bad); + *ret = bad; return 0; } diff --git a/src/core/execute.c b/src/core/execute.c index 80c22623be797..56fd650a48342 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -1930,14 +1930,12 @@ char** exec_context_get_syscall_filter(const ExecContext *c) { if (num >= 0) { e = seccomp_errno_or_action_to_string(num); - if (e) { + if (e) s = strjoin(name, ":", e); - if (!s) - return NULL; - } else { - if (asprintf(&s, "%s:%d", name, num) < 0) - return NULL; - } + else + s = asprintf_safe("%s:%d", name, num); + if (!s) + return NULL; } else s = TAKE_PTR(name); diff --git a/src/core/socket.c b/src/core/socket.c index c18f28aad6843..43f61e456dcf2 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -882,16 +882,14 @@ static int instance_from_socket( a = be32toh(local.in.sin_addr.s_addr), b = be32toh(remote.in.sin_addr.s_addr); - if (asprintf(&s, - "%u-%" PRIu64 "-%u.%u.%u.%u:%u-%u.%u.%u.%u:%u", - nr, - cookie, - a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF, - be16toh(local.in.sin_port), - b >> 24, (b >> 16) & 0xFF, (b >> 8) & 0xFF, b & 0xFF, - be16toh(remote.in.sin_port)) < 0) - return -ENOMEM; - + s = asprintf_safe( + "%u-%" PRIu64 "-%u.%u.%u.%u:%u-%u.%u.%u.%u:%u", + nr, + cookie, + a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF, + be16toh(local.in.sin_port), + b >> 24, (b >> 16) & 0xFF, (b >> 8) & 0xFF, b & 0xFF, + be16toh(remote.in.sin_port)); break; } @@ -906,27 +904,23 @@ static int instance_from_socket( *a = local.in6.sin6_addr.s6_addr+12, *b = remote.in6.sin6_addr.s6_addr+12; - if (asprintf(&s, - "%u-%" PRIu64 "-%u.%u.%u.%u:%u-%u.%u.%u.%u:%u", - nr, - cookie, - a[0], a[1], a[2], a[3], - be16toh(local.in6.sin6_port), - b[0], b[1], b[2], b[3], - be16toh(remote.in6.sin6_port)) < 0) - return -ENOMEM; - } else { - if (asprintf(&s, - "%u-%" PRIu64 "-%s:%u-%s:%u", - nr, - cookie, - IN6_ADDR_TO_STRING(&local.in6.sin6_addr), - be16toh(local.in6.sin6_port), - IN6_ADDR_TO_STRING(&remote.in6.sin6_addr), - be16toh(remote.in6.sin6_port)) < 0) - return -ENOMEM; - } - + s = asprintf_safe( + "%u-%" PRIu64 "-%u.%u.%u.%u:%u-%u.%u.%u.%u:%u", + nr, + cookie, + a[0], a[1], a[2], a[3], + be16toh(local.in6.sin6_port), + b[0], b[1], b[2], b[3], + be16toh(remote.in6.sin6_port)); + } else + s = asprintf_safe( + "%u-%" PRIu64 "-%s:%u-%s:%u", + nr, + cookie, + IN6_ADDR_TO_STRING(&local.in6.sin6_addr), + be16toh(local.in6.sin6_port), + IN6_ADDR_TO_STRING(&remote.in6.sin6_addr), + be16toh(remote.in6.sin6_port)); break; } @@ -939,42 +933,38 @@ static int instance_from_socket( uint64_t pidfd_id; if (pidfd >= 0 && pidfd_get_inode_id(pidfd, &pidfd_id) >= 0) - r = asprintf(&s, "%u-%" PRIu64 "-" PID_FMT "_%" PRIu64 "-" UID_FMT, - nr, cookie, ucred.pid, pidfd_id, ucred.uid); + s = asprintf_safe( + "%u-%" PRIu64 "-" PID_FMT "_%" PRIu64 "-" UID_FMT, + nr, cookie, ucred.pid, pidfd_id, ucred.uid); else - r = asprintf(&s, "%u-%" PRIu64 "-" PID_FMT "-" UID_FMT, - nr, cookie, ucred.pid, ucred.uid); - if (r < 0) - return -ENOMEM; - } else if (r == -ENODATA) { + s = asprintf_safe( + "%u-%" PRIu64 "-" PID_FMT "-" UID_FMT, + nr, cookie, ucred.pid, ucred.uid); + } else if (r == -ENODATA) /* This handles the case where somebody is connecting from another pid/uid namespace * (e.g. from outside of our container). */ - if (asprintf(&s, - "%u-%" PRIu64 "-unknown", - nr, - cookie) < 0) - return -ENOMEM; - } else + s = asprintf_safe("%u-%" PRIu64 "-unknown", nr, cookie); + else return r; - break; } case AF_VSOCK: - if (asprintf(&s, - "%u-%" PRIu64 "-%u:%u-%u:%u", - nr, - cookie, - local.vm.svm_cid, local.vm.svm_port, - remote.vm.svm_cid, remote.vm.svm_port) < 0) - return -ENOMEM; - + s = asprintf_safe( + "%u-%" PRIu64 "-%u:%u-%u:%u", + nr, + cookie, + local.vm.svm_cid, local.vm.svm_port, + remote.vm.svm_cid, remote.vm.svm_port); break; default: assert_not_reached(); } + if (!s) + return -ENOMEM; + *ret = s; return 0; } From 76ab7861ff8ce505cf8deff880ce2d6c1bd05e0c Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Fri, 6 Mar 2026 16:36:52 +0100 Subject: [PATCH 0135/1296] shared: don't exclude valid min/max values for cgroup weight fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1 and 10000 are valid cgroup weight values, but the condition was incorrectly excluding them: $ echo '{"userName":"crashhostarray","cpuWeight":1}' | userdbctl -F - :1:42: JSON field 'cpuWeight' is not in valid range 1…10000. $ echo '{"userName":"crashhostarray","cpuWeight":10000}' | userdbctl -F - :1:42: JSON field 'cpuWeight' is not in valid range 1…10000. --- src/shared/user-record.c | 6 +++--- test/units/TEST-74-AUX-UTILS.userdbctl.sh | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/shared/user-record.c b/src/shared/user-record.c index ff03bcafc3a1d..6458ee93b2006 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -554,11 +554,11 @@ static int json_dispatch_weight(const char *name, sd_json_variant *variant, sd_j return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer.", strna(name)); k = sd_json_variant_unsigned(variant); - if (k <= CGROUP_WEIGHT_MIN || k >= CGROUP_WEIGHT_MAX) + if (k < CGROUP_WEIGHT_MIN || k > CGROUP_WEIGHT_MAX) return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' is not in valid range %" PRIu64 "%s%" PRIu64 ".", - strna(name), (uint64_t) CGROUP_WEIGHT_MIN, - glyph(GLYPH_ELLIPSIS), (uint64_t) CGROUP_WEIGHT_MAX); + strna(name), CGROUP_WEIGHT_MIN, + glyph(GLYPH_ELLIPSIS), CGROUP_WEIGHT_MAX); *weight = k; return 0; diff --git a/test/units/TEST-74-AUX-UTILS.userdbctl.sh b/test/units/TEST-74-AUX-UTILS.userdbctl.sh index 42811e7415835..fdfff1a65caf4 100755 --- a/test/units/TEST-74-AUX-UTILS.userdbctl.sh +++ b/test/units/TEST-74-AUX-UTILS.userdbctl.sh @@ -81,3 +81,7 @@ userdbctl group "$DISK_GID" | grep -F 'io.systemd.NameServiceSwitch' >/dev/null (! busctl call org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager LookupDynamicUserByName "s" disk) (! busctl call org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager LookupDynamicUserByUID "u" "$DISK_GID") systemctl stop "$UNIT" + +# Probe specific user records +echo '{"userName":"weightmin","cpuWeight":1,"ioWeight":1}' | userdbctl -F - +echo '{"userName":"weightmax","cpuWeight":10000,"ioWeight":10000}' | userdbctl -F - From 3c7bd947b29775c6dd035a27462f445d5945447b Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Fri, 6 Mar 2026 17:16:31 +0100 Subject: [PATCH 0136/1296] shared: don't leak memory from array fields The fido2_hmac_salt/fido2_hmac_credential/recovery_key fields kept leaking memory as the array itself wasn't deallocated after deallocating each of its elements data: $ build-san/userdbctl -F fuzz-corpus-userdb/auth-fido2.json ... ================================================================= ==1292840==ERROR: LeakSanitizer: detected memory leaks Direct leak of 112 byte(s) in 1 object(s) allocated from: #0 0x7f56f00e5e4b in realloc.part.0 (/lib64/libasan.so.8+0xe5e4b) (BuildId: 25975f766867e9e604dc5a71a8befeaed3301942) #1 0x7f56ed869e42 in greedy_realloc ../src/basic/alloc-util.c:65 #2 0x7f56ed7ff5e9 in dispatch_fido2_hmac_salt ../src/shared/user-record.c:836 #3 0x7f56edd73cbc in sd_json_dispatch_full ../src/libsystemd/sd-json/sd-json.c:5204 #4 0x7f56edd745fc in sd_json_dispatch ../src/libsystemd/sd-json/sd-json.c:5276 #5 0x7f56ed80100b in dispatch_privileged ../src/shared/user-record.c:998 #6 0x7f56edd73cbc in sd_json_dispatch_full ../src/libsystemd/sd-json/sd-json.c:5204 #7 0x7f56edd745fc in sd_json_dispatch ../src/libsystemd/sd-json/sd-json.c:5276 #8 0x7f56ed80622c in user_record_load ../src/shared/user-record.c:1697 #9 0x000000408c15 in display_user ../src/userdb/userdbctl.c:447 #10 0x7f56ed83cc9a in dispatch_verb ../src/shared/verbs.c:137 #11 0x00000041df2b in run ../src/userdb/userdbctl.c:1908 #12 0x00000041dfbe in main ../src/userdb/userdbctl.c:1911 #13 0x7f56ec8105b4 in __libc_start_call_main (/lib64/libc.so.6+0x35b4) (BuildId: 2b5beec0fd24fe9c9f43eddfdd5facf0b8a1b805) #14 0x7f56ec810667 in __libc_start_main@@GLIBC_2.34 (/lib64/libc.so.6+0x3667) (BuildId: 2b5beec0fd24fe9c9f43eddfdd5facf0b8a1b805) #15 0x000000404a44 in _start (/home/fsumsal/repos/@systemd/systemd/build-san/userdbctl+0x404a44) (BuildId: 19e8b7e7b7038d2cea20bc18a55bea2a9e4406d5) Direct leak of 64 byte(s) in 1 object(s) allocated from: #0 0x7f56f00e5e4b in realloc.part.0 (/lib64/libasan.so.8+0xe5e4b) (BuildId: 25975f766867e9e604dc5a71a8befeaed3301942) #1 0x7f56ed869e42 in greedy_realloc ../src/basic/alloc-util.c:65 #2 0x7f56ed7fe779 in dispatch_fido2_hmac_credential_array ../src/shared/user-record.c:775 #3 0x7f56edd73cbc in sd_json_dispatch_full ../src/libsystemd/sd-json/sd-json.c:5204 #4 0x7f56edd745fc in sd_json_dispatch ../src/libsystemd/sd-json/sd-json.c:5276 #5 0x7f56ed80622c in user_record_load ../src/shared/user-record.c:1697 #6 0x000000408c15 in display_user ../src/userdb/userdbctl.c:447 #7 0x7f56ed83cc9a in dispatch_verb ../src/shared/verbs.c:137 #8 0x00000041df2b in run ../src/userdb/userdbctl.c:1908 #9 0x00000041dfbe in main ../src/userdb/userdbctl.c:1911 #10 0x7f56ec8105b4 in __libc_start_call_main (/lib64/libc.so.6+0x35b4) (BuildId: 2b5beec0fd24fe9c9f43eddfdd5facf0b8a1b805) #11 0x7f56ec810667 in __libc_start_main@@GLIBC_2.34 (/lib64/libc.so.6+0x3667) (BuildId: 2b5beec0fd24fe9c9f43eddfdd5facf0b8a1b805) #12 0x000000404a44 in _start (/home/fsumsal/repos/@systemd/systemd/build-san/userdbctl+0x404a44) (BuildId: 19e8b7e7b7038d2cea20bc18a55bea2a9e4406d5) SUMMARY: AddressSanitizer: 176 byte(s) leaked in 2 allocation(s). --- src/shared/user-record.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/shared/user-record.c b/src/shared/user-record.c index 6458ee93b2006..4b6ba997a6098 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -211,12 +211,15 @@ static UserRecord* user_record_free(UserRecord *h) { for (size_t i = 0; i < h->n_fido2_hmac_credential; i++) fido2_hmac_credential_done(h->fido2_hmac_credential + i); + free(h->fido2_hmac_credential); for (size_t i = 0; i < h->n_fido2_hmac_salt; i++) fido2_hmac_salt_done(h->fido2_hmac_salt + i); + free(h->fido2_hmac_salt); strv_free(h->recovery_key_type); for (size_t i = 0; i < h->n_recovery_key; i++) recovery_key_done(h->recovery_key + i); + free(h->recovery_key); strv_free(h->self_modifiable_fields); strv_free(h->self_modifiable_blobs); From ad1c7df5f93a2602e73ba5673e483b3e5d1b5422 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Fri, 6 Mar 2026 17:30:52 +0100 Subject: [PATCH 0137/1296] man: fix short option for userdbctl's --from-file= --- man/userdbctl.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/userdbctl.xml b/man/userdbctl.xml index 4123190a458ec..ce8367d9a8d16 100644 --- a/man/userdbctl.xml +++ b/man/userdbctl.xml @@ -256,7 +256,7 @@ - + When used with the user or group command, read the user definition in JSON format from the specified file, instead of querying it from the From be0db50cadadb35fdbc117ed68e133f34604b97b Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Fri, 6 Mar 2026 17:58:02 +0100 Subject: [PATCH 0138/1296] fuzz: add a fuzzer for user records Add a simple fuzzer that verifies our machinery for parsing user records from JSON works as intended. The initial corpus was created with the help of Claude, so we have a bunch of valid user records with as much fields as possible for the initial corpus. --- src/fuzz/fuzz-user-record.c | 46 ++++ src/fuzz/meson.build | 1 + test/fuzz/fuzz-user-record/auth-fido2.json | 40 +++ test/fuzz/fuzz-user-record/auth-pkcs11.json | 33 +++ test/fuzz/fuzz-user-record/auth-recovery.json | 27 ++ .../fuzz-user-record/auto-resize-grow.json | 14 + .../fuzz-user-record/auto-resize-modes.json | 14 + .../fuzz-user-record/auto-resize-off.json | 14 + test/fuzz/fuzz-user-record/basic-regular.json | 9 + test/fuzz/fuzz-user-record/basic-system.json | 7 + .../fuzz-user-record/capabilities-full.json | 56 ++++ test/fuzz/fuzz-user-record/default-area.json | 16 ++ .../disposition-container.json | 8 + .../fuzz-user-record/disposition-dynamic.json | 9 + .../fuzz-user-record/disposition-foreign.json | 8 + .../disposition-intrinsic.json | 8 + .../disposition-reserved.json | 9 + .../fuzz-user-record/edge-empty-arrays.json | 34 +++ .../fuzz-user-record/edge-long-strings.json | 24 ++ .../fuzz-user-record/edge-max-values.json | 41 +++ .../fuzz-user-record/edge-min-values.json | 41 +++ .../edge-missing-optionals.json | 9 + .../fuzz-user-record/edge-null-values.json | 18 ++ .../edge-unicode-strings.json | 21 ++ test/fuzz/fuzz-user-record/full-featured.json | 245 ++++++++++++++++++ test/fuzz/fuzz-user-record/luks-complete.json | 36 +++ test/fuzz/fuzz-user-record/minimal.json | 3 + .../fuzz-user-record/password-policy.json | 24 ++ .../fuzz-user-record/per-machine-complex.json | 58 +++++ test/fuzz/fuzz-user-record/rlimits-all.json | 27 ++ test/fuzz/fuzz-user-record/session-prefs.json | 14 + test/fuzz/fuzz-user-record/ssh-keys.json | 18 ++ test/fuzz/fuzz-user-record/storage-cifs.json | 15 ++ .../fuzz-user-record/storage-classic.json | 10 + .../fuzz-user-record/storage-directory.json | 13 + .../fuzz-user-record/storage-fscrypt.json | 14 + .../fuzz-user-record/storage-subvolume.json | 14 + .../fuzz-user-record/time-constraints.json | 13 + test/fuzz/fuzz-user-record/tmpfs-limits.json | 13 + test/fuzz/fuzz-user-record/with-realm.json | 10 + 40 files changed, 1034 insertions(+) create mode 100644 src/fuzz/fuzz-user-record.c create mode 100644 test/fuzz/fuzz-user-record/auth-fido2.json create mode 100644 test/fuzz/fuzz-user-record/auth-pkcs11.json create mode 100644 test/fuzz/fuzz-user-record/auth-recovery.json create mode 100644 test/fuzz/fuzz-user-record/auto-resize-grow.json create mode 100644 test/fuzz/fuzz-user-record/auto-resize-modes.json create mode 100644 test/fuzz/fuzz-user-record/auto-resize-off.json create mode 100644 test/fuzz/fuzz-user-record/basic-regular.json create mode 100644 test/fuzz/fuzz-user-record/basic-system.json create mode 100644 test/fuzz/fuzz-user-record/capabilities-full.json create mode 100644 test/fuzz/fuzz-user-record/default-area.json create mode 100644 test/fuzz/fuzz-user-record/disposition-container.json create mode 100644 test/fuzz/fuzz-user-record/disposition-dynamic.json create mode 100644 test/fuzz/fuzz-user-record/disposition-foreign.json create mode 100644 test/fuzz/fuzz-user-record/disposition-intrinsic.json create mode 100644 test/fuzz/fuzz-user-record/disposition-reserved.json create mode 100644 test/fuzz/fuzz-user-record/edge-empty-arrays.json create mode 100644 test/fuzz/fuzz-user-record/edge-long-strings.json create mode 100644 test/fuzz/fuzz-user-record/edge-max-values.json create mode 100644 test/fuzz/fuzz-user-record/edge-min-values.json create mode 100644 test/fuzz/fuzz-user-record/edge-missing-optionals.json create mode 100644 test/fuzz/fuzz-user-record/edge-null-values.json create mode 100644 test/fuzz/fuzz-user-record/edge-unicode-strings.json create mode 100644 test/fuzz/fuzz-user-record/full-featured.json create mode 100644 test/fuzz/fuzz-user-record/luks-complete.json create mode 100644 test/fuzz/fuzz-user-record/minimal.json create mode 100644 test/fuzz/fuzz-user-record/password-policy.json create mode 100644 test/fuzz/fuzz-user-record/per-machine-complex.json create mode 100644 test/fuzz/fuzz-user-record/rlimits-all.json create mode 100644 test/fuzz/fuzz-user-record/session-prefs.json create mode 100644 test/fuzz/fuzz-user-record/ssh-keys.json create mode 100644 test/fuzz/fuzz-user-record/storage-cifs.json create mode 100644 test/fuzz/fuzz-user-record/storage-classic.json create mode 100644 test/fuzz/fuzz-user-record/storage-directory.json create mode 100644 test/fuzz/fuzz-user-record/storage-fscrypt.json create mode 100644 test/fuzz/fuzz-user-record/storage-subvolume.json create mode 100644 test/fuzz/fuzz-user-record/time-constraints.json create mode 100644 test/fuzz/fuzz-user-record/tmpfs-limits.json create mode 100644 test/fuzz/fuzz-user-record/with-realm.json diff --git a/src/fuzz/fuzz-user-record.c b/src/fuzz/fuzz-user-record.c new file mode 100644 index 0000000000000..c520e6a03bb4e --- /dev/null +++ b/src/fuzz/fuzz-user-record.c @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "alloc-util.h" +#include "fuzz.h" +#include "user-record.h" + +#include "sd-json.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + _cleanup_(user_record_unrefp) UserRecord *ur = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + _cleanup_free_ char *str = NULL; + unsigned line = 0; + int r; + + if (outside_size_range(size, 0, 65536)) + return 0; + + assert_se(str = memdup_suffix0(data, size)); + assert_se(ur = user_record_new()); + + fuzz_setup_logging(); + + r = sd_json_parse(str, 0, &v, &line, /* reterr_column= */ NULL); + if (r < 0) { + (void) log_syntax(/* unit= */ NULL, LOG_DEBUG, "", line, r, "JSON parse failure."); + return 0; + } + + r = user_record_load(ur, v, USER_RECORD_LOAD_FULL|USER_RECORD_PERMISSIVE); + if (r >= 0) { + /* We have a valid record, so let's excercise a couple more functions */ + _cleanup_(user_record_unrefp) UserRecord *cloned = NULL; + (void) user_record_clone(ur, USER_RECORD_LOAD_FULL, &cloned); + + (void) user_record_test_blocked(ur); + (void) user_record_test_password_change_required(ur); + (void) user_record_can_authenticate(ur); + (void) user_record_luks_discard(ur); + (void) user_record_drop_caches(ur); + } + + return 0; +} diff --git a/src/fuzz/meson.build b/src/fuzz/meson.build index a1a13950f8c6a..43539422b0f47 100644 --- a/src/fuzz/meson.build +++ b/src/fuzz/meson.build @@ -10,6 +10,7 @@ simple_fuzzers += files( 'fuzz-json.c', 'fuzz-time-util.c', 'fuzz-udev-database.c', + 'fuzz-user-record.c', 'fuzz-varlink.c', 'fuzz-varlink-idl.c', ) diff --git a/test/fuzz/fuzz-user-record/auth-fido2.json b/test/fuzz/fuzz-user-record/auth-fido2.json new file mode 100644 index 0000000000000..cd8282f5bf293 --- /dev/null +++ b/test/fuzz/fuzz-user-record/auth-fido2.json @@ -0,0 +1,40 @@ +{ + "userName": "fido2user", + "realName": "FIDO2 Security Key User", + "uid": 2002, + "gid": 2002, + "homeDirectory": "/home/fido2user", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "luks", + "imagePath": "/home/fido2user.home", + "fido2HmacCredential": [ + "AQIDBAUAAQ==", + "BQYHCAkKCw==" + ], + "privileged": { + "fido2HmacSalt": [ + { + "credential": "AQIDBAUAAQ==", + "salt": "Zmlyc3RzYWx0Zm9yZmlkbzJ0ZXN0aW5n", + "hashedPassword": "$6$fido2salt1$hashedpasswordforfido2credential1", + "up": true, + "uv": false, + "clientPin": false + }, + { + "credential": "BQYHCAkKCw==", + "salt": "c2Vjb25kc2FsdGZvcmZpZG8ydGVzdA==", + "hashedPassword": "$6$fido2salt2$hashedpasswordforfido2credential2", + "up": true, + "uv": true, + "clientPin": true + } + ] + }, + "secret": { + "tokenPin": ["1234"], + "fido2UserPresencePermitted": true, + "fido2UserVerificationPermitted": true + } +} diff --git a/test/fuzz/fuzz-user-record/auth-pkcs11.json b/test/fuzz/fuzz-user-record/auth-pkcs11.json new file mode 100644 index 0000000000000..d4604e8f0b6f1 --- /dev/null +++ b/test/fuzz/fuzz-user-record/auth-pkcs11.json @@ -0,0 +1,33 @@ +{ + "userName": "pkcs11user", + "realName": "PKCS#11 Token User", + "uid": 2001, + "gid": 2001, + "homeDirectory": "/home/pkcs11user", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "luks", + "imagePath": "/home/pkcs11user.home", + "pkcs11TokenUri": [ + "pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=00000000;token=pkcs11user", + "pkcs11:model=YubiKey;manufacturer=Yubico;serial=12345678;token=PIV" + ], + "privileged": { + "pkcs11EncryptedKey": [ + { + "uri": "pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=00000000;token=pkcs11user", + "data": "SGVsbG8gV29ybGQhIFRoaXMgaXMgYSB0ZXN0IGVuY3J5cHRlZCBrZXkgZm9yIFBLQ1MjMTEu", + "hashedPassword": "$6$pkcs11salt$encryptedkeyhashforpkcs11authentication" + }, + { + "uri": "pkcs11:model=YubiKey;manufacturer=Yubico;serial=12345678;token=PIV", + "data": "QW5vdGhlciBlbmNyeXB0ZWQga2V5IGZvciBhIGRpZmZlcmVudCB0b2tlbi4=", + "hashedPassword": "$6$yubisalt$encryptedkeyhashforyubikey" + } + ] + }, + "secret": { + "tokenPin": ["123456"], + "pkcs11ProtectedAuthenticationPathPermitted": true + } +} diff --git a/test/fuzz/fuzz-user-record/auth-recovery.json b/test/fuzz/fuzz-user-record/auth-recovery.json new file mode 100644 index 0000000000000..f704e1ec1c614 --- /dev/null +++ b/test/fuzz/fuzz-user-record/auth-recovery.json @@ -0,0 +1,27 @@ +{ + "userName": "recoveryuser", + "realName": "Recovery Key User", + "uid": 2003, + "gid": 2003, + "homeDirectory": "/home/recoveryuser", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "luks", + "imagePath": "/home/recoveryuser.home", + "recoveryKeyType": ["modhex64", "modhex64"], + "privileged": { + "hashedPassword": [ + "$6$mainsalt$mainpasswordhashforrecoveryuser" + ], + "recoveryKey": [ + { + "type": "modhex64", + "hashedPassword": "$6$recovery1$hashedrecoverykey1" + }, + { + "type": "modhex64", + "hashedPassword": "$6$recovery2$hashedrecoverykey2" + } + ] + } +} diff --git a/test/fuzz/fuzz-user-record/auto-resize-grow.json b/test/fuzz/fuzz-user-record/auto-resize-grow.json new file mode 100644 index 0000000000000..d72a797a11712 --- /dev/null +++ b/test/fuzz/fuzz-user-record/auto-resize-grow.json @@ -0,0 +1,14 @@ +{ + "userName": "growonly", + "realName": "Grow Only User", + "uid": 3008, + "gid": 3008, + "homeDirectory": "/home/growonly", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "luks", + "imagePath": "/home/growonly.home", + "diskSize": 53687091200, + "autoResizeMode": "grow", + "rebalanceWeight": true +} diff --git a/test/fuzz/fuzz-user-record/auto-resize-modes.json b/test/fuzz/fuzz-user-record/auto-resize-modes.json new file mode 100644 index 0000000000000..75d50aed7b628 --- /dev/null +++ b/test/fuzz/fuzz-user-record/auto-resize-modes.json @@ -0,0 +1,14 @@ +{ + "userName": "autoresizeuser", + "realName": "Auto Resize Test User", + "uid": 3006, + "gid": 3006, + "homeDirectory": "/home/autoresizeuser", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "luks", + "imagePath": "/home/autoresizeuser.home", + "diskSize": 53687091200, + "autoResizeMode": "shrink-and-grow", + "rebalanceWeight": 100 +} diff --git a/test/fuzz/fuzz-user-record/auto-resize-off.json b/test/fuzz/fuzz-user-record/auto-resize-off.json new file mode 100644 index 0000000000000..8479985aca74c --- /dev/null +++ b/test/fuzz/fuzz-user-record/auto-resize-off.json @@ -0,0 +1,14 @@ +{ + "userName": "noautoresize", + "realName": "No Auto Resize User", + "uid": 3007, + "gid": 3007, + "homeDirectory": "/home/noautoresize", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "luks", + "imagePath": "/home/noautoresize.home", + "diskSize": 53687091200, + "autoResizeMode": "off", + "rebalanceWeight": false +} diff --git a/test/fuzz/fuzz-user-record/basic-regular.json b/test/fuzz/fuzz-user-record/basic-regular.json new file mode 100644 index 0000000000000..594f348aaefa7 --- /dev/null +++ b/test/fuzz/fuzz-user-record/basic-regular.json @@ -0,0 +1,9 @@ +{ + "userName": "testuser", + "realName": "Test User", + "uid": 1000, + "gid": 1000, + "homeDirectory": "/home/testuser", + "shell": "/bin/bash", + "disposition": "regular" +} diff --git a/test/fuzz/fuzz-user-record/basic-system.json b/test/fuzz/fuzz-user-record/basic-system.json new file mode 100644 index 0000000000000..9cfb3a2f63575 --- /dev/null +++ b/test/fuzz/fuzz-user-record/basic-system.json @@ -0,0 +1,7 @@ +{ + "userName": "httpd", + "uid": 473, + "gid": 473, + "disposition": "system", + "locked": true +} diff --git a/test/fuzz/fuzz-user-record/capabilities-full.json b/test/fuzz/fuzz-user-record/capabilities-full.json new file mode 100644 index 0000000000000..08d5f362beaca --- /dev/null +++ b/test/fuzz/fuzz-user-record/capabilities-full.json @@ -0,0 +1,56 @@ +{ + "userName": "capuser", + "realName": "Capabilities Test User", + "uid": 3010, + "gid": 3010, + "homeDirectory": "/home/capuser", + "shell": "/bin/bash", + "disposition": "regular", + "capabilityBoundingSet": [ + "CAP_AUDIT_CONTROL", + "CAP_AUDIT_READ", + "CAP_AUDIT_WRITE", + "CAP_BLOCK_SUSPEND", + "CAP_BPF", + "CAP_CHECKPOINT_RESTORE", + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_DAC_READ_SEARCH", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_IPC_LOCK", + "CAP_IPC_OWNER", + "CAP_KILL", + "CAP_LEASE", + "CAP_LINUX_IMMUTABLE", + "CAP_MAC_ADMIN", + "CAP_MAC_OVERRIDE", + "CAP_MKNOD", + "CAP_NET_ADMIN", + "CAP_NET_BIND_SERVICE", + "CAP_NET_BROADCAST", + "CAP_NET_RAW", + "CAP_PERFMON", + "CAP_SETFCAP", + "CAP_SETGID", + "CAP_SETPCAP", + "CAP_SETUID", + "CAP_SYS_ADMIN", + "CAP_SYS_BOOT", + "CAP_SYS_CHROOT", + "CAP_SYS_MODULE", + "CAP_SYS_NICE", + "CAP_SYS_PACCT", + "CAP_SYS_PTRACE", + "CAP_SYS_RAWIO", + "CAP_SYS_RESOURCE", + "CAP_SYS_TIME", + "CAP_SYS_TTY_CONFIG", + "CAP_SYSLOG", + "CAP_WAKE_ALARM" + ], + "capabilityAmbientSet": [ + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW" + ] +} diff --git a/test/fuzz/fuzz-user-record/default-area.json b/test/fuzz/fuzz-user-record/default-area.json new file mode 100644 index 0000000000000..b27f40b5d930b --- /dev/null +++ b/test/fuzz/fuzz-user-record/default-area.json @@ -0,0 +1,16 @@ +{ + "userName": "areauser", + "realName": "Default Area Test User", + "uid": 3012, + "gid": 3012, + "homeDirectory": "/home/areauser", + "shell": "/bin/bash", + "disposition": "regular", + "defaultArea": "work", + "perMachine": [ + { + "matchHostname": "personal", + "defaultArea": "personal" + } + ] +} diff --git a/test/fuzz/fuzz-user-record/disposition-container.json b/test/fuzz/fuzz-user-record/disposition-container.json new file mode 100644 index 0000000000000..67d5995467b7c --- /dev/null +++ b/test/fuzz/fuzz-user-record/disposition-container.json @@ -0,0 +1,8 @@ +{ + "userName": "containeruser", + "uid": 100000, + "gid": 100000, + "homeDirectory": "/home/containeruser", + "shell": "/bin/sh", + "disposition": "container" +} diff --git a/test/fuzz/fuzz-user-record/disposition-dynamic.json b/test/fuzz/fuzz-user-record/disposition-dynamic.json new file mode 100644 index 0000000000000..1678d994860cc --- /dev/null +++ b/test/fuzz/fuzz-user-record/disposition-dynamic.json @@ -0,0 +1,9 @@ +{ + "userName": "dynamicuser", + "uid": 61234, + "gid": 61234, + "homeDirectory": "/run/dynamicuser", + "shell": "/usr/sbin/nologin", + "disposition": "dynamic", + "locked": true +} diff --git a/test/fuzz/fuzz-user-record/disposition-foreign.json b/test/fuzz/fuzz-user-record/disposition-foreign.json new file mode 100644 index 0000000000000..9f3b2ff18f03a --- /dev/null +++ b/test/fuzz/fuzz-user-record/disposition-foreign.json @@ -0,0 +1,8 @@ +{ + "userName": "foreignuser", + "uid": 200000, + "gid": 200000, + "homeDirectory": "/home/foreign", + "shell": "/bin/sh", + "disposition": "foreign" +} diff --git a/test/fuzz/fuzz-user-record/disposition-intrinsic.json b/test/fuzz/fuzz-user-record/disposition-intrinsic.json new file mode 100644 index 0000000000000..9a66fa1b4f7b7 --- /dev/null +++ b/test/fuzz/fuzz-user-record/disposition-intrinsic.json @@ -0,0 +1,8 @@ +{ + "userName": "root", + "uid": 0, + "gid": 0, + "homeDirectory": "/root", + "shell": "/bin/bash", + "disposition": "intrinsic" +} diff --git a/test/fuzz/fuzz-user-record/disposition-reserved.json b/test/fuzz/fuzz-user-record/disposition-reserved.json new file mode 100644 index 0000000000000..74cf4085464a9 --- /dev/null +++ b/test/fuzz/fuzz-user-record/disposition-reserved.json @@ -0,0 +1,9 @@ +{ + "userName": "reserveduser", + "uid": 999, + "gid": 999, + "homeDirectory": "/nonexistent", + "shell": "/usr/sbin/nologin", + "disposition": "reserved", + "locked": true +} diff --git a/test/fuzz/fuzz-user-record/edge-empty-arrays.json b/test/fuzz/fuzz-user-record/edge-empty-arrays.json new file mode 100644 index 0000000000000..8e46c3d31ef02 --- /dev/null +++ b/test/fuzz/fuzz-user-record/edge-empty-arrays.json @@ -0,0 +1,34 @@ +{ + "userName": "emptyarrays", + "realName": "Empty Arrays Test User", + "uid": 3001, + "gid": 3001, + "homeDirectory": "/home/emptyarrays", + "shell": "/bin/bash", + "disposition": "regular", + "aliases": [], + "environment": [], + "additionalLanguages": [], + "memberOf": [], + "capabilityBoundingSet": [], + "capabilityAmbientSet": [], + "pkcs11TokenUri": [], + "fido2HmacCredential": [], + "recoveryKeyType": [], + "selfModifiableFields": [], + "selfModifiableBlobs": [], + "selfModifiablePrivileged": [], + "privileged": { + "hashedPassword": [], + "sshAuthorizedKeys": [], + "pkcs11EncryptedKey": [], + "fido2HmacSalt": [], + "recoveryKey": [] + }, + "perMachine": [], + "signature": [], + "secret": { + "password": [], + "tokenPin": [] + } +} diff --git a/test/fuzz/fuzz-user-record/edge-long-strings.json b/test/fuzz/fuzz-user-record/edge-long-strings.json new file mode 100644 index 0000000000000..b7883637e70d3 --- /dev/null +++ b/test/fuzz/fuzz-user-record/edge-long-strings.json @@ -0,0 +1,24 @@ +{ + "userName": "longstringsuser", + "realName": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "uid": 3004, + "gid": 3004, + "homeDirectory": "/home/longstringsuser", + "shell": "/bin/bash", + "disposition": "regular", + "location": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "environment": [ + "LONGVAR=CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" + ], + "memberOf": [ + "group1", "group2", "group3", "group4", "group5", + "group6", "group7", "group8", "group9", "group10", + "group11", "group12", "group13", "group14", "group15", + "group16", "group17", "group18", "group19", "group20" + ], + "privileged": { + "sshAuthorizedKeys": [ + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDVeryLongKeyDataHereThatGoesOnAndOnAndOnForAWhileToTestLongSSHKeysAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA user@host" + ] + } +} diff --git a/test/fuzz/fuzz-user-record/edge-max-values.json b/test/fuzz/fuzz-user-record/edge-max-values.json new file mode 100644 index 0000000000000..52a34109f774b --- /dev/null +++ b/test/fuzz/fuzz-user-record/edge-max-values.json @@ -0,0 +1,41 @@ +{ + "userName": "maxuser", + "realName": "Maximum Values Test User", + "uid": 4294967294, + "gid": 4294967294, + "homeDirectory": "/home/maxuser", + "shell": "/bin/bash", + "disposition": "regular", + "umask": 511, + "niceLevel": 19, + "cpuWeight": 10000, + "ioWeight": 10000, + "diskSize": 18446744073709551615, + "diskSizeRelative": 4294967295, + "tasksMax": 18446744073709551614, + "memoryHigh": 18446744073709551614, + "memoryMax": 18446744073709551614, + "accessMode": 511, + "luksPbkdfForceIterations": 18446744073709551615, + "luksPbkdfTimeCostUSec": 18446744073709551615, + "luksPbkdfMemoryCost": 18446744073709551615, + "luksPbkdfParallelThreads": 18446744073709551615, + "luksSectorSize": 4096, + "luksVolumeKeySize": 512, + "rebalanceWeight": 10000, + "rateLimitIntervalUSec": 18446744073709551615, + "rateLimitBurst": 18446744073709551615, + "stopDelayUSec": 18446744073709551615, + "lastChangeUSec": 18446744073709551615, + "lastPasswordChangeUSec": 18446744073709551615, + "notBeforeUSec": 0, + "notAfterUSec": 18446744073709551615, + "passwordChangeMinUSec": 0, + "passwordChangeMaxUSec": 18446744073709551615, + "passwordChangeWarnUSec": 18446744073709551615, + "passwordChangeInactiveUSec": 18446744073709551615, + "tmpLimit": 18446744073709551615, + "tmpLimitScale": 4294967295, + "devShmLimit": 18446744073709551615, + "devShmLimitScale": 4294967295 +} diff --git a/test/fuzz/fuzz-user-record/edge-min-values.json b/test/fuzz/fuzz-user-record/edge-min-values.json new file mode 100644 index 0000000000000..b0b569a34e8c4 --- /dev/null +++ b/test/fuzz/fuzz-user-record/edge-min-values.json @@ -0,0 +1,41 @@ +{ + "userName": "minuser", + "realName": "Minimum Values Test User", + "uid": 0, + "gid": 0, + "homeDirectory": "/", + "shell": "/", + "disposition": "intrinsic", + "umask": 0, + "niceLevel": -20, + "cpuWeight": 1, + "ioWeight": 1, + "diskSize": 1, + "diskSizeRelative": 1, + "tasksMax": 1, + "memoryHigh": 1, + "memoryMax": 1, + "accessMode": 0, + "luksPbkdfForceIterations": 1, + "luksPbkdfTimeCostUSec": 1, + "luksPbkdfMemoryCost": 1, + "luksPbkdfParallelThreads": 1, + "luksSectorSize": 512, + "luksVolumeKeySize": 1, + "rebalanceWeight": 1, + "rateLimitIntervalUSec": 1, + "rateLimitBurst": 1, + "stopDelayUSec": 0, + "lastChangeUSec": 0, + "lastPasswordChangeUSec": 0, + "notBeforeUSec": 0, + "notAfterUSec": 1, + "passwordChangeMinUSec": 0, + "passwordChangeMaxUSec": 1, + "passwordChangeWarnUSec": 0, + "passwordChangeInactiveUSec": 0, + "tmpLimit": 1, + "tmpLimitScale": 1, + "devShmLimit": 1, + "devShmLimitScale": 1 +} diff --git a/test/fuzz/fuzz-user-record/edge-missing-optionals.json b/test/fuzz/fuzz-user-record/edge-missing-optionals.json new file mode 100644 index 0000000000000..e06884f9768d4 --- /dev/null +++ b/test/fuzz/fuzz-user-record/edge-missing-optionals.json @@ -0,0 +1,9 @@ +{ + "userName": "missingoptionals", + "realName": "Missing Optional Fields User", + "uid": 3002, + "gid": 3002, + "homeDirectory": "/home/missingoptionals", + "shell": "/bin/bash", + "disposition": "regular" +} diff --git a/test/fuzz/fuzz-user-record/edge-null-values.json b/test/fuzz/fuzz-user-record/edge-null-values.json new file mode 100644 index 0000000000000..cb995f38bdd7f --- /dev/null +++ b/test/fuzz/fuzz-user-record/edge-null-values.json @@ -0,0 +1,18 @@ +{ + "userName": "nullvalues", + "realName": "Null Values Test User", + "uid": 3019, + "gid": 3019, + "homeDirectory": "/home/nullvalues", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "luks", + "autoResizeMode": null, + "rebalanceWeight": null, + "luksDiscard": null, + "luksOfflineDiscard": null, + "tmpLimit": null, + "tmpLimitScale": null, + "devShmLimit": null, + "devShmLimitScale": null +} diff --git a/test/fuzz/fuzz-user-record/edge-unicode-strings.json b/test/fuzz/fuzz-user-record/edge-unicode-strings.json new file mode 100644 index 0000000000000..ce1204d08060c --- /dev/null +++ b/test/fuzz/fuzz-user-record/edge-unicode-strings.json @@ -0,0 +1,21 @@ +{ + "userName": "unicodeuser", + "realName": "Ünïcödé Üsér 日本語 中文 العربية", + "uid": 3003, + "gid": 3003, + "homeDirectory": "/home/unicodeuser", + "shell": "/bin/bash", + "disposition": "regular", + "emailAddress": "unicode@例え.jp", + "location": "東京, 日本 🌸", + "environment": [ + "GREETING=Привет мир", + "HELLO=你好世界" + ], + "preferredLanguage": "ja_JP.UTF-8", + "additionalLanguages": ["zh_CN.UTF-8", "ar_SA.UTF-8", "ru_RU.UTF-8"], + "timeZone": "Asia/Tokyo", + "privileged": { + "passwordHint": "お気に入りの食べ物は何ですか?" + } +} diff --git a/test/fuzz/fuzz-user-record/full-featured.json b/test/fuzz/fuzz-user-record/full-featured.json new file mode 100644 index 0000000000000..4ed15a92998b9 --- /dev/null +++ b/test/fuzz/fuzz-user-record/full-featured.json @@ -0,0 +1,245 @@ +{ + "userName": "fuzzuser", + "realm": "example.com", + "aliases": ["fuzz", "fuzzer", "testfuzz"], + "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "blobDirectory": "/var/cache/systemd/homed/fuzzuser/", + "blobManifest": { + "avatar": "c0636851d25a62d817ff7da4e081d1e646e42c74d0ecb53425f75fcf1ba43b52", + "login-background": "da7ad0222a6edbc6cd095149c72d38d92fd3114f606e4b57469857ef47fade18", + "custom-file": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + }, + "realName": "Fuzz Test User", + "emailAddress": "fuzzuser@example.com", + "iconName": "user-available", + "location": "Fuzzing Lab, Room 42", + "disposition": "regular", + "lastChangeUSec": 1700000000000000, + "lastPasswordChangeUSec": 1699000000000000, + "shell": "/bin/bash", + "umask": 22, + "environment": [ + "EDITOR=vim", + "PAGER=less", + "LANG=en_US.UTF-8", + "FUZZ_VAR=test_value" + ], + "timeZone": "Europe/Berlin", + "preferredLanguage": "en_US.UTF-8", + "additionalLanguages": ["de_DE.UTF-8", "fr_FR.UTF-8", "es_ES.UTF-8"], + "niceLevel": 5, + "resourceLimits": { + "RLIMIT_NOFILE": { "cur": 1024, "max": 65536 }, + "RLIMIT_NPROC": { "cur": 4096, "max": 8192 }, + "RLIMIT_MEMLOCK": { "cur": 65536, "max": 131072 }, + "RLIMIT_AS": { "cur": 4294967296, "max": 8589934592 }, + "RLIMIT_FSIZE": { "cur": 1073741824, "max": 2147483648 }, + "RLIMIT_STACK": { "cur": 8388608, "max": 16777216 }, + "RLIMIT_CORE": { "cur": 0, "max": 0 }, + "RLIMIT_RSS": { "cur": 4294967296, "max": 8589934592 }, + "RLIMIT_CPU": { "cur": 3600, "max": 7200 }, + "RLIMIT_DATA": { "cur": 4294967296, "max": 8589934592 }, + "RLIMIT_NICE": { "cur": 0, "max": 0 }, + "RLIMIT_RTPRIO": { "cur": 0, "max": 0 }, + "RLIMIT_RTTIME": { "cur": 1000000, "max": 2000000 }, + "RLIMIT_SIGPENDING": { "cur": 128, "max": 256 }, + "RLIMIT_MSGQUEUE": { "cur": 819200, "max": 1638400 }, + "RLIMIT_LOCKS": { "cur": 1024, "max": 2048 } + }, + "locked": false, + "notBeforeUSec": 1600000000000000, + "notAfterUSec": 1900000000000000, + "storage": "luks", + "diskSize": 107374182400, + "diskSizeRelative": 2147483648, + "skeletonDirectory": "/etc/skel", + "accessMode": 448, + "tasksMax": 4096, + "memoryHigh": 4294967296, + "memoryMax": 8589934592, + "cpuWeight": 100, + "ioWeight": 100, + "mountNoDevices": true, + "mountNoSuid": true, + "mountNoExecute": false, + "cifsDomain": "WORKGROUP", + "cifsUserName": "fuzzuser", + "cifsService": "//server.example.com/homes/fuzzuser", + "cifsExtraMountOptions": "vers=3.0,seal", + "imagePath": "/home/fuzzuser.home", + "homeDirectory": "/home/fuzzuser", + "uid": 60001, + "gid": 60001, + "memberOf": ["wheel", "users", "audio", "video", "docker", "libvirt"], + "capabilityBoundingSet": ["CAP_NET_ADMIN", "CAP_SYS_PTRACE", "CAP_SETUID", "CAP_SETGID"], + "capabilityAmbientSet": ["CAP_NET_BIND_SERVICE"], + "fileSystemType": "ext4", + "partitionUuid": "41f9ce04-c827-4b74-a981-c669f93eb4dc", + "luksUuid": "e63581ba-79fb-4226-b9de-1888393f7573", + "fileSystemUuid": "758e88c8-5851-4a2a-b88f-e7474279c111", + "luksDiscard": true, + "luksOfflineDiscard": false, + "luksCipher": "aes", + "luksCipherMode": "xts-plain64", + "luksVolumeKeySize": 64, + "luksPbkdfHashAlgorithm": "sha512", + "luksPbkdfType": "argon2id", + "luksPbkdfForceIterations": 1000, + "luksPbkdfTimeCostUSec": 1000000, + "luksPbkdfMemoryCost": 1073741824, + "luksPbkdfParallelThreads": 4, + "luksSectorSize": 4096, + "luksExtraMountOptions": "discard,noatime", + "dropCaches": true, + "autoResizeMode": "shrink-and-grow", + "rebalanceWeight": 100, + "service": "io.systemd.Home", + "rateLimitIntervalUSec": 60000000, + "rateLimitBurst": 5, + "enforcePasswordPolicy": true, + "autoLogin": false, + "preferredSessionType": "wayland", + "preferredSessionLauncher": "gnome", + "stopDelayUSec": 180000000, + "killProcesses": true, + "passwordChangeMinUSec": 86400000000, + "passwordChangeMaxUSec": 7776000000000, + "passwordChangeWarnUSec": 1209600000000, + "passwordChangeInactiveUSec": 2592000000000, + "passwordChangeNow": false, + "pkcs11TokenUri": [ + "pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=00000000;token=fuzzuser" + ], + "fido2HmacCredential": [ + "AQIDBA==" + ], + "recoveryKeyType": ["modhex64"], + "selfModifiableFields": [ + "realName", + "emailAddress", + "iconName", + "location", + "preferredLanguage", + "additionalLanguages", + "timeZone", + "preferredSessionType", + "preferredSessionLauncher" + ], + "selfModifiableBlobs": ["avatar", "login-background"], + "selfModifiablePrivileged": ["passwordHint"], + "tmpLimit": 1073741824, + "tmpLimitScale": 858993459, + "devShmLimit": 536870912, + "devShmLimitScale": 429496729, + "defaultArea": "work", + "privileged": { + "passwordHint": "Your favorite fuzzing target", + "hashedPassword": [ + "$6$rounds=656000$somesalt$hashedpassworddata1234567890abcdefghijklmnopqrstuvwxyz", + "$y$j9T$somesalt$yescrypthash1234567890" + ], + "sshAuthorizedKeys": [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG7FVK5YkKL3IYQr7Z6UDYqvB8S8bM7b8vNjVp4S6Y9Y fuzzuser@example.com", + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC0123456789abcdef fuzzuser@backup" + ], + "pkcs11EncryptedKey": [ + { + "uri": "pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=00000000;token=fuzzuser", + "data": "SGVsbG8gV29ybGQhIFRoaXMgaXMgYSB0ZXN0IGVuY3J5cHRlZCBrZXku", + "hashedPassword": "$6$rounds=656000$somesalt$encryptedkeyhash" + } + ], + "fido2HmacSalt": [ + { + "credential": "AQIDBA==", + "salt": "c29tZXNhbHRmb3JmaWRvMg==", + "hashedPassword": "$6$rounds=656000$fidosalt$fidohashedpassword", + "up": true, + "uv": true, + "clientPin": false + } + ], + "recoveryKey": [ + { + "type": "modhex64", + "hashedPassword": "$6$rounds=656000$recoverysalt$recoverykeyhash" + } + ] + }, + "perMachine": [ + { + "matchMachineId": "15e19cf24e004b949ddaac60c74aa165", + "diskSize": 214748364800, + "memoryMax": 17179869184, + "cpuWeight": 200 + }, + { + "matchHostname": "workstation", + "shell": "/bin/zsh", + "niceLevel": 0, + "autoLogin": true + }, + { + "matchNotMachineId": "00000000000000000000000000000000", + "matchNotHostname": "server", + "locked": false + } + ], + "binding": { + "15e19cf24e004b949ddaac60c74aa165": { + "blobDirectory": "/var/cache/systemd/homed/fuzzuser/", + "imagePath": "/home/fuzzuser.home", + "homeDirectory": "/home/fuzzuser", + "partitionUuid": "41f9ce04-c827-4b74-a981-c669f93eb4dc", + "luksUuid": "e63581ba-79fb-4226-b9de-1888393f7573", + "fileSystemUuid": "758e88c8-5851-4a2a-b88f-e7474279c111", + "uid": 60001, + "gid": 60001, + "storage": "luks", + "fileSystemType": "ext4", + "luksCipher": "aes", + "luksCipherMode": "xts-plain64", + "luksVolumeKeySize": 64 + } + }, + "status": { + "15e19cf24e004b949ddaac60c74aa165": { + "aliases": ["fuzz-status-alias"], + "diskUsage": 53687091200, + "diskFree": 53687091200, + "diskSize": 107374182400, + "diskCeiling": 214748364800, + "diskFloor": 1073741824, + "state": "active", + "service": "io.systemd.Home", + "signedLocally": true, + "goodAuthenticationCounter": 42, + "badAuthenticationCounter": 3, + "lastGoodAuthenticationUSec": 1700000000000000, + "lastBadAuthenticationUSec": 1699999000000000, + "rateLimitBeginUSec": 1699999500000000, + "rateLimitCount": 1, + "removable": false, + "accessMode": 448, + "fileSystemType": "ext4", + "fallbackShell": "/bin/sh", + "fallbackHomeDirectory": "/", + "useFallback": false, + "defaultArea": "work" + } + }, + "signature": [ + { + "data": "TFUvSGVWclBaU3ppM01KMFBWSHdENW0veGY1MVhEWUNyU3BiRFJOQmR0RjRmRFZock4wdDJJMk9xSC8xeVhpQmlkWGxWMHB0TXVRVnE4S1ZJQ2RFRHE9PQ==", + "key": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA/QT6kQWOAMhDJf56jBmszEQQpJHqDsGDMZOdiptBgRk=\n-----END PUBLIC KEY-----\n" + } + ], + "secret": { + "password": ["testpassword123", "alternatepassword456"], + "tokenPin": ["1234", "5678"], + "pkcs11Pin": ["0000"], + "pkcs11ProtectedAuthenticationPathPermitted": true, + "fido2UserPresencePermitted": true, + "fido2UserVerificationPermitted": true + } +} diff --git a/test/fuzz/fuzz-user-record/luks-complete.json b/test/fuzz/fuzz-user-record/luks-complete.json new file mode 100644 index 0000000000000..d74b3ae94a5e7 --- /dev/null +++ b/test/fuzz/fuzz-user-record/luks-complete.json @@ -0,0 +1,36 @@ +{ + "userName": "luksuser", + "realName": "Complete LUKS User", + "uid": 3015, + "gid": 3015, + "homeDirectory": "/home/luksuser", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "luks", + "imagePath": "/home/luksuser.home", + "diskSize": 107374182400, + "fileSystemType": "btrfs", + "partitionUuid": "12345678-1234-1234-1234-123456789abc", + "luksUuid": "abcdef12-3456-7890-abcd-ef1234567890", + "fileSystemUuid": "fedcba98-7654-3210-fedc-ba9876543210", + "luksDiscard": true, + "luksOfflineDiscard": true, + "luksCipher": "aes", + "luksCipherMode": "xts-plain64", + "luksVolumeKeySize": 64, + "luksPbkdfHashAlgorithm": "sha256", + "luksPbkdfType": "pbkdf2", + "luksPbkdfForceIterations": 100000, + "luksPbkdfTimeCostUSec": 500000, + "luksPbkdfMemoryCost": 67108864, + "luksPbkdfParallelThreads": 2, + "luksSectorSize": 4096, + "luksExtraMountOptions": "compress=zstd:3,noatime", + "dropCaches": true, + "mountNoDevices": true, + "mountNoSuid": true, + "mountNoExecute": false, + "accessMode": 448, + "autoResizeMode": "shrink-and-grow", + "rebalanceWeight": 100 +} diff --git a/test/fuzz/fuzz-user-record/minimal.json b/test/fuzz/fuzz-user-record/minimal.json new file mode 100644 index 0000000000000..a22cc44fb3d34 --- /dev/null +++ b/test/fuzz/fuzz-user-record/minimal.json @@ -0,0 +1,3 @@ +{ + "userName": "u" +} diff --git a/test/fuzz/fuzz-user-record/password-policy.json b/test/fuzz/fuzz-user-record/password-policy.json new file mode 100644 index 0000000000000..c4b336a4f5e99 --- /dev/null +++ b/test/fuzz/fuzz-user-record/password-policy.json @@ -0,0 +1,24 @@ +{ + "userName": "policuser", + "realName": "Password Policy User", + "uid": 3017, + "gid": 3017, + "homeDirectory": "/home/policyuser", + "shell": "/bin/bash", + "disposition": "regular", + "enforcePasswordPolicy": true, + "passwordChangeNow": true, + "passwordChangeMinUSec": 86400000000, + "passwordChangeMaxUSec": 7776000000000, + "passwordChangeWarnUSec": 1209600000000, + "passwordChangeInactiveUSec": 2592000000000, + "lastPasswordChangeUSec": 1700000000000000, + "rateLimitIntervalUSec": 60000000, + "rateLimitBurst": 3, + "privileged": { + "hashedPassword": [ + "$6$rounds=656000$salt$hashedpassword" + ], + "passwordHint": "Your first pet's name" + } +} diff --git a/test/fuzz/fuzz-user-record/per-machine-complex.json b/test/fuzz/fuzz-user-record/per-machine-complex.json new file mode 100644 index 0000000000000..327f3e5a92665 --- /dev/null +++ b/test/fuzz/fuzz-user-record/per-machine-complex.json @@ -0,0 +1,58 @@ +{ + "userName": "permachineuser", + "realName": "Per Machine Complex User", + "uid": 3005, + "gid": 3005, + "homeDirectory": "/home/permachineuser", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "luks", + "diskSize": 53687091200, + "memoryMax": 4294967296, + "cpuWeight": 100, + "perMachine": [ + { + "matchMachineId": "11111111111111111111111111111111", + "diskSize": 107374182400, + "memoryMax": 8589934592, + "cpuWeight": 200, + "storage": "luks", + "shell": "/bin/zsh" + }, + { + "matchMachineId": ["22222222222222222222222222222222", "33333333333333333333333333333333"], + "diskSize": 214748364800, + "memoryMax": 17179869184, + "ioWeight": 500 + }, + { + "matchHostname": "workstation", + "autoLogin": true, + "niceLevel": -5, + "environment": ["WORKSTATION=true"] + }, + { + "matchHostname": ["server1", "server2"], + "locked": false, + "killProcesses": false, + "stopDelayUSec": 300000000 + }, + { + "matchNotMachineId": "44444444444444444444444444444444", + "matchNotHostname": "restricted-host", + "tasksMax": 8192 + }, + { + "matchMachineId": "55555555555555555555555555555555", + "matchHostname": "special-host", + "blobDirectory": "/var/cache/special/blobs/", + "blobManifest": { + "special-file": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + }, + "selfModifiableFields": ["realName", "location"], + "selfModifiableBlobs": ["avatar"], + "preferredSessionType": "x11", + "preferredSessionLauncher": "plasma" + } + ] +} diff --git a/test/fuzz/fuzz-user-record/rlimits-all.json b/test/fuzz/fuzz-user-record/rlimits-all.json new file mode 100644 index 0000000000000..ea9299a69eeb5 --- /dev/null +++ b/test/fuzz/fuzz-user-record/rlimits-all.json @@ -0,0 +1,27 @@ +{ + "userName": "rlimitsuser", + "realName": "Resource Limits Test User", + "uid": 3009, + "gid": 3009, + "homeDirectory": "/home/rlimitsuser", + "shell": "/bin/bash", + "disposition": "regular", + "resourceLimits": { + "RLIMIT_AS": { "cur": 4294967296, "max": 8589934592 }, + "RLIMIT_CORE": { "cur": 0, "max": 9223372036854775807 }, + "RLIMIT_CPU": { "cur": 3600, "max": 9223372036854775807 }, + "RLIMIT_DATA": { "cur": 4294967296, "max": 9223372036854775807 }, + "RLIMIT_FSIZE": { "cur": 1073741824, "max": 9223372036854775807 }, + "RLIMIT_LOCKS": { "cur": 1024, "max": 9223372036854775807 }, + "RLIMIT_MEMLOCK": { "cur": 65536, "max": 9223372036854775807 }, + "RLIMIT_MSGQUEUE": { "cur": 819200, "max": 9223372036854775807 }, + "RLIMIT_NICE": { "cur": 0, "max": 40 }, + "RLIMIT_NOFILE": { "cur": 1024, "max": 1048576 }, + "RLIMIT_NPROC": { "cur": 4096, "max": 9223372036854775807 }, + "RLIMIT_RSS": { "cur": 4294967296, "max": 9223372036854775807 }, + "RLIMIT_RTPRIO": { "cur": 0, "max": 99 }, + "RLIMIT_RTTIME": { "cur": 1000000, "max": 9223372036854775807 }, + "RLIMIT_SIGPENDING": { "cur": 128, "max": 9223372036854775807 }, + "RLIMIT_STACK": { "cur": 8388608, "max": 9223372036854775807 } + } +} diff --git a/test/fuzz/fuzz-user-record/session-prefs.json b/test/fuzz/fuzz-user-record/session-prefs.json new file mode 100644 index 0000000000000..56344241f3532 --- /dev/null +++ b/test/fuzz/fuzz-user-record/session-prefs.json @@ -0,0 +1,14 @@ +{ + "userName": "sessionuser", + "realName": "Session Preferences User", + "uid": 3016, + "gid": 3016, + "homeDirectory": "/home/sessionuser", + "shell": "/bin/bash", + "disposition": "regular", + "autoLogin": true, + "preferredSessionType": "wayland", + "preferredSessionLauncher": "gnome", + "stopDelayUSec": 180000000, + "killProcesses": false +} diff --git a/test/fuzz/fuzz-user-record/ssh-keys.json b/test/fuzz/fuzz-user-record/ssh-keys.json new file mode 100644 index 0000000000000..00d947d6ba8e0 --- /dev/null +++ b/test/fuzz/fuzz-user-record/ssh-keys.json @@ -0,0 +1,18 @@ +{ + "userName": "sshuser", + "realName": "SSH Keys User", + "uid": 3013, + "gid": 3013, + "homeDirectory": "/home/sshuser", + "shell": "/bin/bash", + "disposition": "regular", + "privileged": { + "sshAuthorizedKeys": [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKlH9A7KdFhMmfBrV2fzONpPeaQaJAXyY3bMpZ1sT5Xy ed25519-key", + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC7s... rsa-key", + "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBA... ecdsa-key", + "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIA... security-key", + "command=\"/usr/bin/restricted\",no-port-forwarding,no-agent-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJx... restricted-key" + ] + } +} diff --git a/test/fuzz/fuzz-user-record/storage-cifs.json b/test/fuzz/fuzz-user-record/storage-cifs.json new file mode 100644 index 0000000000000..f30d5d676d0f7 --- /dev/null +++ b/test/fuzz/fuzz-user-record/storage-cifs.json @@ -0,0 +1,15 @@ +{ + "userName": "cifsuser", + "realName": "CIFS Home User", + "uid": 1001, + "gid": 1001, + "homeDirectory": "/home/cifsuser", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "cifs", + "cifsDomain": "EXAMPLE", + "cifsUserName": "cifsuser", + "cifsService": "//fileserver.example.com/homes/cifsuser", + "cifsExtraMountOptions": "vers=3.0,seal,sec=krb5", + "memberOf": ["users", "domain-users"] +} diff --git a/test/fuzz/fuzz-user-record/storage-classic.json b/test/fuzz/fuzz-user-record/storage-classic.json new file mode 100644 index 0000000000000..b77e07d09b252 --- /dev/null +++ b/test/fuzz/fuzz-user-record/storage-classic.json @@ -0,0 +1,10 @@ +{ + "userName": "classicuser", + "realName": "Classic Unix User", + "uid": 1005, + "gid": 1005, + "homeDirectory": "/home/classicuser", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "classic" +} diff --git a/test/fuzz/fuzz-user-record/storage-directory.json b/test/fuzz/fuzz-user-record/storage-directory.json new file mode 100644 index 0000000000000..12dbd6eb5e9a0 --- /dev/null +++ b/test/fuzz/fuzz-user-record/storage-directory.json @@ -0,0 +1,13 @@ +{ + "userName": "diruser", + "realName": "Directory Storage User", + "uid": 1002, + "gid": 1002, + "homeDirectory": "/home/diruser", + "imagePath": "/home/diruser.homedir", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "directory", + "accessMode": 448, + "diskSize": 10737418240 +} diff --git a/test/fuzz/fuzz-user-record/storage-fscrypt.json b/test/fuzz/fuzz-user-record/storage-fscrypt.json new file mode 100644 index 0000000000000..63c2887fd4e3b --- /dev/null +++ b/test/fuzz/fuzz-user-record/storage-fscrypt.json @@ -0,0 +1,14 @@ +{ + "userName": "fscryptuser", + "realName": "Fscrypt User", + "uid": 1004, + "gid": 1004, + "homeDirectory": "/home/fscryptuser", + "imagePath": "/home/fscryptuser.homedir", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "fscrypt", + "fileSystemType": "ext4", + "accessMode": 448, + "diskSize": 21474836480 +} diff --git a/test/fuzz/fuzz-user-record/storage-subvolume.json b/test/fuzz/fuzz-user-record/storage-subvolume.json new file mode 100644 index 0000000000000..8e80145d5c293 --- /dev/null +++ b/test/fuzz/fuzz-user-record/storage-subvolume.json @@ -0,0 +1,14 @@ +{ + "userName": "btrfsuser", + "realName": "Btrfs Subvolume User", + "uid": 1003, + "gid": 1003, + "homeDirectory": "/home/btrfsuser", + "imagePath": "/home/btrfsuser.homedir", + "shell": "/bin/bash", + "disposition": "regular", + "storage": "subvolume", + "fileSystemType": "btrfs", + "accessMode": 448, + "diskSize": 53687091200 +} diff --git a/test/fuzz/fuzz-user-record/time-constraints.json b/test/fuzz/fuzz-user-record/time-constraints.json new file mode 100644 index 0000000000000..238efa3e6902a --- /dev/null +++ b/test/fuzz/fuzz-user-record/time-constraints.json @@ -0,0 +1,13 @@ +{ + "userName": "timeuser", + "realName": "Time Constraints User", + "uid": 3018, + "gid": 3018, + "homeDirectory": "/home/timeuser", + "shell": "/bin/bash", + "disposition": "regular", + "notBeforeUSec": 1609459200000000, + "notAfterUSec": 1924905600000000, + "lastChangeUSec": 1700000000000000, + "locked": false +} diff --git a/test/fuzz/fuzz-user-record/tmpfs-limits.json b/test/fuzz/fuzz-user-record/tmpfs-limits.json new file mode 100644 index 0000000000000..725e6df8a2ef2 --- /dev/null +++ b/test/fuzz/fuzz-user-record/tmpfs-limits.json @@ -0,0 +1,13 @@ +{ + "userName": "tmpfsuser", + "realName": "Tmpfs Limits Test User", + "uid": 3014, + "gid": 3014, + "homeDirectory": "/home/tmpfsuser", + "shell": "/bin/bash", + "disposition": "regular", + "tmpLimit": 1073741824, + "tmpLimitScale": 214748364, + "devShmLimit": 536870912, + "devShmLimitScale": 107374182 +} diff --git a/test/fuzz/fuzz-user-record/with-realm.json b/test/fuzz/fuzz-user-record/with-realm.json new file mode 100644 index 0000000000000..ab77c824afcbe --- /dev/null +++ b/test/fuzz/fuzz-user-record/with-realm.json @@ -0,0 +1,10 @@ +{ + "userName": "realmuser", + "realm": "corp.example.com", + "realName": "Corporate Realm User", + "uid": 3011, + "gid": 3011, + "homeDirectory": "/home/realmuser@corp.example.com", + "shell": "/bin/bash", + "disposition": "regular" +} From a7f1670f62cc8bc37f52acee94d2209eff66cd10 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Fri, 6 Mar 2026 22:07:31 +0100 Subject: [PATCH 0139/1296] user-record: extract user_record_image_is_blockdev() common helper --- src/shared/user-record.c | 20 +++++++++++-------- .../crash-empty-image-path.json | 4 ++++ 2 files changed, 16 insertions(+), 8 deletions(-) create mode 100644 test/fuzz/fuzz-user-record/crash-empty-image-path.json diff --git a/src/shared/user-record.c b/src/shared/user-record.c index 4b6ba997a6098..d5e572dc094db 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -1829,6 +1829,16 @@ const char* user_record_image_path(UserRecord *h) { user_record_home_directory_real(h) : NULL; } +static bool user_record_image_is_blockdev(UserRecord *h) { + assert(h); + + const char *p = user_record_image_path(h); + if (!p) + return false; + + return path_startswith(p, "/dev/"); +} + const char* user_record_cifs_user_name(UserRecord *h) { assert(h); @@ -1880,24 +1890,18 @@ const char* user_record_real_name(UserRecord *h) { } bool user_record_luks_discard(UserRecord *h) { - const char *ip; - assert(h); if (h->luks_discard >= 0) return h->luks_discard; - ip = user_record_image_path(h); - if (!ip) - return false; - /* Use discard by default if we are referring to a real block device, but not when operating on a * loopback device. We want to optimize for SSD and flash storage after all, but we should be careful * when storing stuff on top of regular file systems in loopback files as doing discard then would * mean thin provisioning and we should not do that willy-nilly since it means we'll risk EIO later * on should the disk space to back our file systems not be available. */ - return path_startswith(ip, "/dev/"); + return user_record_image_is_blockdev(h); } bool user_record_luks_offline_discard(UserRecord *h) { @@ -2063,7 +2067,7 @@ int user_record_removable(UserRecord *h) { return -1; /* For now consider only LUKS home directories with a reference by path as removable */ - return storage == USER_LUKS && path_startswith(user_record_image_path(h), "/dev/"); + return storage == USER_LUKS && user_record_image_is_blockdev(h); } uint64_t user_record_ratelimit_interval_usec(UserRecord *h) { diff --git a/test/fuzz/fuzz-user-record/crash-empty-image-path.json b/test/fuzz/fuzz-user-record/crash-empty-image-path.json new file mode 100644 index 0000000000000..0506a71fc2012 --- /dev/null +++ b/test/fuzz/fuzz-user-record/crash-empty-image-path.json @@ -0,0 +1,4 @@ +{ + "userName": "root", + "storage": "luks" +} From eb80a679d16d36516dd4bb64ae26c37c66f9d193 Mon Sep 17 00:00:00 2001 From: Temuri Doghonadze Date: Sat, 7 Mar 2026 01:58:33 +0000 Subject: [PATCH 0140/1296] po: Translated using Weblate (Georgian) Currently translated at 100.0% (266 of 266 strings) Co-authored-by: Temuri Doghonadze Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/ka/ Translation: systemd/main --- po/ka.po | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/po/ka.po b/po/ka.po index 448a01cabf8c0..11d6599fb91b6 100644 --- a/po/ka.po +++ b/po/ka.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2026-02-26 13:58+0000\n" +"PO-Revision-Date: 2026-03-07 01:58+0000\n" "Last-Translator: Temuri Doghonadze \n" "Language-Team: Georgian \n" @@ -14,7 +14,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.16.1\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1013,12 +1013,12 @@ msgid "DHCP server sends force renew message" msgstr "DHCP სერვერმა ნაძალადევი განახლების შეტყობინება გამოაგზავნა" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy -#| msgid "Authentication is required to send force renew message." msgid "" "Authentication is required to send a force renew message from the DHCP " "server." -msgstr "ნაძალადევი განახლების შეტყობინების გასაგზავნად საჭიროა ავთენტიკაცია." +msgstr "" +"DHCP სერვერიდან ნაძალადევი განახლების შეტყობინების გასაგზავნად საჭიროა " +"ავთენტიკაცია." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" From f561fd63c600afdb135f9aad181659c61735dd45 Mon Sep 17 00:00:00 2001 From: Yuri Chornoivan Date: Sat, 7 Mar 2026 01:58:34 +0000 Subject: [PATCH 0141/1296] po: Translated using Weblate (Ukrainian) Currently translated at 100.0% (266 of 266 strings) Co-authored-by: Yuri Chornoivan Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/uk/ Translation: systemd/main --- po/uk.po | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/po/uk.po b/po/uk.po index 64e786d7c7e4d..45f816287fcc1 100644 --- a/po/uk.po +++ b/po/uk.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2026-02-26 13:58+0000\n" +"PO-Revision-Date: 2026-03-07 01:58+0000\n" "Last-Translator: Yuri Chornoivan \n" "Language-Team: Ukrainian \n" @@ -19,7 +19,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" -"X-Generator: Weblate 5.16.1\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1055,14 +1055,12 @@ msgid "DHCP server sends force renew message" msgstr "Сервер DHCP надсилає повідомлення щодо примусового оновлення" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy -#| msgid "Authentication is required to send force renew message." msgid "" "Authentication is required to send a force renew message from the DHCP " "server." msgstr "" -"Для надсилання повідомлення щодо примусового оновлення слід пройти " -"розпізнавання." +"Для надсилання повідомлення від сервера DHCP щодо примусового оновлення слід " +"пройти розпізнавання." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" From 4020a4d76dfb9e2c0500ab19af453e99601da585 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9ane=20GRASSER?= Date: Sat, 7 Mar 2026 01:58:34 +0000 Subject: [PATCH 0142/1296] po: Translated using Weblate (French) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (266 of 266 strings) Co-authored-by: Léane GRASSER Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/fr/ Translation: systemd/main --- po/fr.po | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/po/fr.po b/po/fr.po index 41b95c9e0f7cb..daa25ef8af250 100644 --- a/po/fr.po +++ b/po/fr.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2026-02-27 16:58+0000\n" +"PO-Revision-Date: 2026-03-07 01:58+0000\n" "Last-Translator: Léane GRASSER \n" "Language-Team: French \n" @@ -21,7 +21,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 5.16.1\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1089,14 +1089,12 @@ msgid "DHCP server sends force renew message" msgstr "Envoi par le serveur DHCP d'un message de renouvellement forcé" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy -#| msgid "Authentication is required to send force renew message." msgid "" "Authentication is required to send a force renew message from the DHCP " "server." msgstr "" "Une authentification est requise pour envoyer un message de renouvellement " -"forcé." +"forcé à partir du serveur DHCP." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" From 4e95ea0dabebc85edf59e3c4cfff9762fada1d0a Mon Sep 17 00:00:00 2001 From: A S Alam Date: Sat, 7 Mar 2026 01:58:35 +0000 Subject: [PATCH 0143/1296] po: Translated using Weblate (Punjabi) Currently translated at 34.5% (92 of 266 strings) Co-authored-by: A S Alam Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/pa/ Translation: systemd/main --- po/pa.po | 54 +++++++++++++++++++++++------------------------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/po/pa.po b/po/pa.po index 624e14a0c85ab..bd100302fea72 100644 --- a/po/pa.po +++ b/po/pa.po @@ -1,21 +1,21 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # # A S Alam , 2020, 2021, 2023. -# A S Alam , 2023, 2024. +# A S Alam , 2023, 2024, 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2024-01-16 14:35+0000\n" +"PO-Revision-Date: 2026-03-07 01:58+0000\n" "Last-Translator: A S Alam \n" "Language-Team: Punjabi \n" +"main/pa/>\n" "Language: pa\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 5.3.1\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -65,10 +65,9 @@ msgid "Dump the systemd state without rate limits" msgstr "" #: src/core/org.freedesktop.systemd1.policy.in:75 -#, fuzzy msgid "" "Authentication is required to dump the systemd state without rate limits." -msgstr "systemd ਹਾਲਤ ਲੋਡ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" +msgstr "ਬਿਨਾਂ ਰੇਟ ਲਿਮਟ ਦੇ systemd ਹਾਲਤ ਲੋਡ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/home/org.freedesktop.home1.policy:13 msgid "Create a home area" @@ -88,7 +87,7 @@ msgstr "ਵਰਤੋਂਕਾਰ ਦੇ ਹੋਮ ਖੇਤਰ ਨੂੰ ਹਟ #: src/home/org.freedesktop.home1.policy:33 msgid "Check credentials of a home area" -msgstr "" +msgstr "ਹੋਮ ਖੇਤਰ ਲਈ ਸਨਦਾਂ ਦੀ ਜਾਂਚ ਕਰੋ" #: src/home/org.freedesktop.home1.policy:34 msgid "" @@ -104,14 +103,12 @@ msgid "Authentication is required to update a user's home area." msgstr "ਵਰਤੋਂਕਾਰ ਦੇ ਹੋਮ ਖੇਤਰ ਨੂੰ ਅੱਪਡੇਟ ਕਰਨ ਲਈ ਪਰਮਾਣਿਕਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/home/org.freedesktop.home1.policy:53 -#, fuzzy msgid "Update your home area" -msgstr "ਹੋਮ ਖੇਤਰ ਨੂੰ ਅੱਪਡੇਟ ਕਰੋ" +msgstr "ਆਪਣੇ ਹੋਮ ਖੇਤਰ ਨੂੰ ਅੱਪਡੇਟ ਕਰੋ" #: src/home/org.freedesktop.home1.policy:54 -#, fuzzy msgid "Authentication is required to update your home area." -msgstr "ਵਰਤੋਂਕਾਰ ਦੇ ਹੋਮ ਖੇਤਰ ਨੂੰ ਅੱਪਡੇਟ ਕਰਨ ਲਈ ਪਰਮਾਣਿਕਤਾ ਚਾਹੀਦੀ ਹੈ।" +msgstr "ਤੁਹਾਡੇ ਹੋਮ ਖੇਤਰ ਨੂੰ ਅੱਪਡੇਟ ਕਰਨ ਲਈ ਪਰਮਾਣਿਕਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/home/org.freedesktop.home1.policy:63 msgid "Resize a home area" @@ -131,14 +128,12 @@ msgid "" msgstr "ਵਰਤੋਂਕਾਰ ਦੇ ਹੋਮ ਖੇਤਰ ਲਈ ਪਾਸਵਰਡ ਨੂੰ ਬਦਲਣ ਲਈ ਪਰਮਾਣਿਕਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/home/org.freedesktop.home1.policy:83 -#, fuzzy msgid "Activate a home area" -msgstr "ਹੋਮ ਖੇਤਰ ਬਣਾਓ" +msgstr "ਹੋਮ ਖੇਤਰ ਸਰਗਰਮ ਕਰੋ" #: src/home/org.freedesktop.home1.policy:84 -#, fuzzy msgid "Authentication is required to activate a user's home area." -msgstr "ਵਰਤੋਂਕਾਰ ਦੇ ਹੋਮ ਖੇਤਰ ਨੂੰ ਬਣਾਉਣ ਲਈ ਪਰਮਾਣਿਕਤਾ ਚਾਹੀਦੀ ਹੈ।" +msgstr "ਵਰਤੋਂਕਾਰ ਦੇ ਹੋਮ ਖੇਤਰ ਨੂੰ ਸਰਗਰਮ ਕਰਨ ਲਈ ਪਰਮਾਣਿਕਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/home/org.freedesktop.home1.policy:93 msgid "Manage Home Directory Signing Keys" @@ -355,40 +350,36 @@ msgstr "ਸਿਸਟਮ ਜਾਣਕਾਰੀ ਲੈਣ ਲਈ ਪਰਮਾਣ #: src/import/org.freedesktop.import1.policy:22 msgid "Import a disk image" -msgstr "" +msgstr "ਡਿਸਕ ਈਮੇਜ਼ ਨੂੰ ਇੰਪੋਰਟ ਕਰੋ" #: src/import/org.freedesktop.import1.policy:23 -#, fuzzy msgid "Authentication is required to import an image." -msgstr "'$(unit)' ਨੂੰ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" +msgstr "ਈਮੇਲ ਨੂੰ ਇੰਪੋਰਟ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/import/org.freedesktop.import1.policy:32 msgid "Export a disk image" -msgstr "" +msgstr "ਡਿਸਕ ਈਮੇਜ਼ ਨੂੰ ਐਕਸਪੋਰਟ ਕਰੋ" #: src/import/org.freedesktop.import1.policy:33 -#, fuzzy msgid "Authentication is required to export disk image." -msgstr "ਸਿਸਟਮ ਟਾਈਮ ਸੈੱਟ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" +msgstr "ਡਿਸਕ ਈਮੇਜ਼ ਨੂੰ ਐਕਸਪੋਰਟ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/import/org.freedesktop.import1.policy:42 msgid "Download a disk image" -msgstr "" +msgstr "ਡਿਸਕ ਈਮੇਜ਼ ਨੂੰ ਡਾਊਨਲੋਡ ਕਰੋ" #: src/import/org.freedesktop.import1.policy:43 -#, fuzzy msgid "Authentication is required to download a disk image." -msgstr "systemd ਹਾਲਤ ਲੋਡ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" +msgstr "ਡਿਸਕ ਈਮੇਜ਼ ਨੂੰ ਡਾਊਨਲੋਡ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/import/org.freedesktop.import1.policy:52 msgid "Cancel transfer of a disk image" -msgstr "" +msgstr "ਡਿਸਕ ਈਮੇਜ਼ ਟਰਾਂਸਫਰ ਨੂੰ ਰੱਦ ਕਰੋ" #: src/import/org.freedesktop.import1.policy:53 -#, fuzzy msgid "" "Authentication is required to cancel the ongoing transfer of a disk image." -msgstr "ਵਰਤੋਂਕਾਰ ਦੇ ਹੋਮ ਖੇਤਰ ਲਈ ਪਾਸਵਰਡ ਨੂੰ ਬਦਲਣ ਲਈ ਪਰਮਾਣਿਕਤਾ ਚਾਹੀਦੀ ਹੈ।" +msgstr "ਚੱਲ ਰਹੇ ਡਿਸਕ ਈਮੇਜ਼ ਟਰਾਂਸਫਰ ਨੂੰ ਰੱਦ ਕਰਨ ਲਈ ਪਰਮਾਣਿਕਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/locale/org.freedesktop.locale1.policy:22 msgid "Set system locale" @@ -538,7 +529,7 @@ msgstr "ਸਿਸਟਮ ਨੂੰ ਬੰਦ ਕਰੋ" #: src/login/org.freedesktop.login1.policy:170 msgid "Authentication is required to power off the system." -msgstr "" +msgstr "ਸਿਸਟਮ ਨੂੰ ਬੰਦ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/login/org.freedesktop.login1.policy:180 msgid "Power off the system while other users are logged in" @@ -566,17 +557,18 @@ msgstr "ਸਿਸਟਮ ਨੂੰ ਮੁੜ ਚਾਲੂ ਕਰੋ" #: src/login/org.freedesktop.login1.policy:203 msgid "Authentication is required to reboot the system." -msgstr "" +msgstr "ਸਿਸਟਮ ਨੂੰ ਮੁੜ-ਚਾਲੂ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/login/org.freedesktop.login1.policy:213 msgid "Reboot the system while other users are logged in" -msgstr "" +msgstr "ਜਦੋਂ ਹੋਰ ਵਰਤੋਂਕਾਰ ਲਾਗਇਨ ਕੀਤੇ ਹੋਣ ਤਾਂ ਵੀ ਸਿਸਟਮ ਨੂੰ ਮੁੜ-ਚਾਲੂ ਕਰੋ" #: src/login/org.freedesktop.login1.policy:214 msgid "" "Authentication is required to reboot the system while other users are logged " "in." msgstr "" +"ਜਦੋਂ ਹੋਰ ਵਰਤੋਂਕਾਰ ਲਾਗਇਨ ਕੀਤੇ ਹੋਣ ਤਾਂ ਸਿਸਟਮ ਨੂੰ ਮੁੜ-ਚਾਲੂ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/login/org.freedesktop.login1.policy:224 msgid "Reboot the system while an application is inhibiting this" @@ -594,7 +586,7 @@ msgstr "ਸਿਸਟਮ ਨੂੰ ਰੋਕ ਦਿਓ" #: src/login/org.freedesktop.login1.policy:236 msgid "Authentication is required to halt the system." -msgstr "" +msgstr "ਸਿਸਟਮ ਨੂੰ ਰੋਕਣ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/login/org.freedesktop.login1.policy:246 msgid "Halt the system while other users are logged in" From 4089bc7485c984180ee7534830d425c47b75e810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9D=B8=EC=88=98?= Date: Sat, 7 Mar 2026 01:58:35 +0000 Subject: [PATCH 0144/1296] po: Translated using Weblate (Korean) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (266 of 266 strings) Co-authored-by: 김인수 Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/ko/ Translation: systemd/main --- po/ko.po | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/po/ko.po b/po/ko.po index 621ee4c90bc31..457422083a4ec 100644 --- a/po/ko.po +++ b/po/ko.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2026-02-27 16:58+0000\n" +"PO-Revision-Date: 2026-03-07 01:58+0000\n" "Last-Translator: 김인수 \n" "Language-Team: Korean \n" @@ -18,7 +18,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.16.1\n" +"X-Generator: Weblate 5.16.2\n" "X-Poedit-SourceCharset: UTF-8\n" #: src/core/org.freedesktop.systemd1.policy.in:22 @@ -966,12 +966,10 @@ msgid "DHCP server sends force renew message" msgstr "DHCP 서버에서 새 메시지 강제 전송" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy -#| msgid "Authentication is required to send force renew message." msgid "" "Authentication is required to send a force renew message from the DHCP " "server." -msgstr "강제로 새 메시지를 보내려면 인증이 필요합니다." +msgstr "DHCP 서버에서 강제로 새 메시지를 보내려면 인증이 필요합니다." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" From aadee8f81788734558ac4696e5e021c342951fb2 Mon Sep 17 00:00:00 2001 From: Baurzhan Muftakhidinov Date: Sat, 7 Mar 2026 01:58:36 +0000 Subject: [PATCH 0145/1296] po: Translated using Weblate (Kazakh) Currently translated at 100.0% (266 of 266 strings) Co-authored-by: Baurzhan Muftakhidinov Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/kk/ Translation: systemd/main --- po/kk.po | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/po/kk.po b/po/kk.po index 9695d9566f090..4ba1dd5f4dae3 100644 --- a/po/kk.po +++ b/po/kk.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: systemd\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2026-02-26 13:58+0000\n" +"PO-Revision-Date: 2026-03-07 01:58+0000\n" "Last-Translator: Baurzhan Muftakhidinov \n" "Language-Team: Kazakh \n" @@ -16,7 +16,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.16.1\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1017,12 +1017,12 @@ msgid "DHCP server sends force renew message" msgstr "DHCP сервері мәжбүрлі жаңарту хабарламасын жібереді" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy -#| msgid "Authentication is required to send force renew message." msgid "" "Authentication is required to send a force renew message from the DHCP " "server." -msgstr "Мәжбүрлі жаңарту хабарламасын жіберу үшін аутентификация қажет." +msgstr "" +"DHCP серверінен мәжбүрлі жаңарту хабарламасын жіберу үшін аутентификация " +"қажет." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" From 93528036197dd8feadbe7963049c296ebd72d4d4 Mon Sep 17 00:00:00 2001 From: Daniel Nylander Date: Sat, 7 Mar 2026 01:58:36 +0000 Subject: [PATCH 0146/1296] po: Translated using Weblate (Swedish) Currently translated at 100.0% (266 of 266 strings) Co-authored-by: Daniel Nylander Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/sv/ Translation: systemd/main --- po/sv.po | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/po/sv.po b/po/sv.po index 5cd8a4672a911..96ee57502a8ce 100644 --- a/po/sv.po +++ b/po/sv.po @@ -13,7 +13,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2026-02-26 13:58+0000\n" +"PO-Revision-Date: 2026-03-07 01:58+0000\n" "Last-Translator: Daniel Nylander \n" "Language-Team: Swedish \n" @@ -22,7 +22,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.16.1\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1021,12 +1021,12 @@ msgid "DHCP server sends force renew message" msgstr "DHCP-servern skickar tvingande förnyelsemeddelande" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy -#| msgid "Authentication is required to send force renew message." msgid "" "Authentication is required to send a force renew message from the DHCP " "server." -msgstr "Autentisering krävs för att skicka tvingande förnyelsemeddelande." +msgstr "" +"Autentisering krävs för att skicka ett tvingande förnyelsemeddelande från " +"DHCP-servern." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" From 41bb2bcb1da20bfe71388c4e553eb049e771db71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Sat, 7 Mar 2026 01:58:37 +0000 Subject: [PATCH 0147/1296] po: Translated using Weblate (Turkish) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (266 of 266 strings) Co-authored-by: Oğuz Ersen Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/tr/ Translation: systemd/main --- po/tr.po | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/po/tr.po b/po/tr.po index 1af3f9e21a292..a197891714b68 100644 --- a/po/tr.po +++ b/po/tr.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2026-02-26 13:58+0000\n" +"PO-Revision-Date: 2026-03-07 01:58+0000\n" "Last-Translator: Oğuz Ersen \n" "Language-Team: Turkish \n" @@ -20,7 +20,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.16.1\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1069,12 +1069,12 @@ msgid "DHCP server sends force renew message" msgstr "DHCP sunucusu zorunlu yenileme mesajı gönderiyor" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy -#| msgid "Authentication is required to send force renew message." msgid "" "Authentication is required to send a force renew message from the DHCP " "server." -msgstr "Zorunlu yenileme mesajı göndermek için kimlik doğrulaması gereklidir." +msgstr "" +"DHCP sunucusundan zorunlu yenileme mesajı göndermek için kimlik doğrulaması " +"gereklidir." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" From da1cedd1445c3b929642541b5bd44e3758ecdb47 Mon Sep 17 00:00:00 2001 From: Arjun-C-S Date: Sat, 7 Mar 2026 18:30:46 +0530 Subject: [PATCH 0148/1296] hwdb: map Xiaomi Mi Notebook Pro star key to KEY_MACRO The Xiaomi Mi Notebook Pro keyboard has a "star" key that generates AT keyboard scancode 0x72 but is not mapped in the default hwdb. Map it to KEY_MACRO so it appears as a usable input key. Verified using evtest. Signed-off-by: Arjun --- hwdb.d/60-keyboard.hwdb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hwdb.d/60-keyboard.hwdb b/hwdb.d/60-keyboard.hwdb index 70264a467ef52..4d3f79cadedc5 100644 --- a/hwdb.d/60-keyboard.hwdb +++ b/hwdb.d/60-keyboard.hwdb @@ -2174,6 +2174,10 @@ evdev:name:FTSC1000:00 2808:509C Keyboard:dmi:*:svnXiaomiInc:pnMipad2:* KEYBOARD_KEY_70029=leftmeta # Esc -> LeftMeta (Windows key / Win8 tablets home) KEYBOARD_KEY_7002a=back # Backspace -> back +# Xiaomi Mi NoteBook Pro star key +evdev:atkbd:dmi:bvnTIMI*:bvr*:bd*:svnTIMI*:pnMiNoteBookPro*:* + KEYBOARD_KEY_72=macro + ########################################################### # Zepto ########################################################### From 09c92eef12323eab06e9af8e55920654814c00b7 Mon Sep 17 00:00:00 2001 From: The-An0nym <114821295+The-An0nym@users.noreply.github.com> Date: Sat, 7 Mar 2026 09:41:00 +0100 Subject: [PATCH 0149/1296] Fix media keys for Lenovo ThinkBook 14 2-in-1 G5 IAU --- hwdb.d/60-keyboard.hwdb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hwdb.d/60-keyboard.hwdb b/hwdb.d/60-keyboard.hwdb index 4d3f79cadedc5..61f9737fb30af 100644 --- a/hwdb.d/60-keyboard.hwdb +++ b/hwdb.d/60-keyboard.hwdb @@ -1131,6 +1131,8 @@ evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO:*:pvrIdeaPadSlim5* evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO:pn81Q7*:pvrLenovoYogaS940:* # Lenovo ThinkBook 16G6IRL evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO:pn21KH*:pvrThinkBook16G6IRL:* +# Lenovo ThinkBook 14 2-in-1 G5 IAU +evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO:pn21SQ*:pvrThinkBook142-in-1G5IAU:* KEYBOARD_KEY_a0=!mute KEYBOARD_KEY_ae=!volumedown KEYBOARD_KEY_b0=!volumeup From c2d046558258890e384204f0ac05e7f7dbc0a01e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Santamar=C3=ADa=20Rogado?= Date: Sun, 8 Mar 2026 03:45:10 +0100 Subject: [PATCH 0150/1296] hwdb: sensor: bncf reformat match --- hwdb.d/60-sensor.hwdb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hwdb.d/60-sensor.hwdb b/hwdb.d/60-sensor.hwdb index e2c91594166aa..75fc9428a15ba 100644 --- a/hwdb.d/60-sensor.hwdb +++ b/hwdb.d/60-sensor.hwdb @@ -221,7 +221,7 @@ sensor:modalias:acpi:KIOX010A:*:dmi:*:svnAMI:*:skuH8Y6:* # MaxBook Y14 # BNCF ######################################### -sensor:modalias:acpi:NSA2513:NSA2513*:dmi:*svnBNCF:pnNewBook11* # NewBook 11 2-in-1 +sensor:modalias:acpi:NSA2513:*:dmi:*:svnBNCF:pnNewBook11:* # NewBook 11 2-in-1 ACCEL_MOUNT_MATRIX=0, 1, 0; -1, 0, 0; 0, 0, -1 ######################################### From 42e3801efc05dc8787a473a351c138fa91a29049 Mon Sep 17 00:00:00 2001 From: Andrii Zora Date: Sat, 7 Mar 2026 17:24:28 +0200 Subject: [PATCH 0151/1296] hwdb: update HP Envy x360 patterns to cover newer 14-fc0xxx models Signed-off-by: Andrii Zora --- hwdb.d/60-keyboard.hwdb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hwdb.d/60-keyboard.hwdb b/hwdb.d/60-keyboard.hwdb index 61f9737fb30af..bd2e07788fecc 100644 --- a/hwdb.d/60-keyboard.hwdb +++ b/hwdb.d/60-keyboard.hwdb @@ -713,7 +713,7 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnHP*:pnHPENVYx360Convertible13*:* evdev:name:Intel HID events:dmi:bvn*:bvr*:bd*:svnHP*:pn*HP[sS][pP][eE][cC][tT][rR][eE]*x3602-in-1*:* # ENVY x360 evdev:name:Intel HID events:dmi:bvn*:bvr*:bd*:svnHP*:pnHPENVYx360Convertible*:* -evdev:name:Intel HID events:dmi:bvn*:bvr*:bd*:svnHP*:pnHPENVYx3602-in-1*:* +evdev:name:Intel HID events:dmi:bvn*:bvr*:bd*:svnHP*:pnHP[eE][nN][vV][yY]x3602-in-1*:* KEYBOARD_KEY_08=unknown # Prevents random airplane mode activation # HP Elite x2 1013 G3 From 3db5a017d39bc3632c9ecdda0ad18b679d8a3020 Mon Sep 17 00:00:00 2001 From: Martin Srebotnjak Date: Sun, 8 Mar 2026 23:58:27 +0000 Subject: [PATCH 0152/1296] po: Translated using Weblate (Slovenian) Currently translated at 100.0% (266 of 266 strings) Co-authored-by: Martin Srebotnjak Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/sl/ Translation: systemd/main --- po/sl.po | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/po/sl.po b/po/sl.po index a3f6141a3c95c..79c6a180051bc 100644 --- a/po/sl.po +++ b/po/sl.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: systemd\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2026-03-04 19:58+0000\n" +"PO-Revision-Date: 2026-03-08 23:58+0000\n" "Last-Translator: Martin Srebotnjak \n" "Language-Team: Slovenian \n" @@ -17,7 +17,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || " "n%100==4 ? 2 : 3;\n" -"X-Generator: Weblate 5.16.1\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1067,14 +1067,12 @@ msgid "DHCP server sends force renew message" msgstr "Strežnik DHCP pošlje sporočilo o prisilnem podaljšanju" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy -#| msgid "Authentication is required to send force renew message." msgid "" "Authentication is required to send a force renew message from the DHCP " "server." msgstr "" "Preverjanje pristnosti je potrebno za pošiljanje sporočila o prisilnem " -"podaljšanju." +"podaljšanju s strežnika DHCP." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" From 20911fe1779657f42d4da0adf167ec632bb18a32 Mon Sep 17 00:00:00 2001 From: Malcolm Frazier Date: Sat, 7 Mar 2026 18:18:56 -0800 Subject: [PATCH 0153/1296] man: fix SendHostname= and Hostname= descriptions to allow multi-label DNS names in [DHCPv4] --- man/systemd.network.xml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 58dae4f948c7e..4c777ef4e0876 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -2382,9 +2382,12 @@ MultiPathRoute=2001:db8::1@eth0 When true (the default), the machine's hostname (or the value specified with Hostname=, described below) will be sent to the DHCP server. Note that the - hostname must consist only of 7-bit ASCII lower-case characters and no spaces or dots, and be - formatted as a valid DNS domain name. Otherwise, the hostname is not sent even if this option - is true. + hostname must consist only of 7-bit ASCII lower-case characters and no spaces, and be + formatted as a valid DNS domain name. A single-label hostname is sent as DHCP option 12 + (Host Name, RFC 2132); + a multi-label hostname (FQDN) is sent instead as DHCP option 81 (Client FQDN, + RFC 4702). + Otherwise, the hostname is not sent even if this option is true. @@ -2395,7 +2398,8 @@ MultiPathRoute=2001:db8::1@eth0 Use this value for the hostname which is sent to the DHCP server, instead of machine's hostname. Note that the specified hostname must consist only of 7-bit ASCII lower-case - characters and no spaces or dots, and be formatted as a valid DNS domain name. + characters and no spaces, and be formatted as a valid DNS domain name. Multi-label hostnames + (FQDNs) are acceptable; see SendHostname= above for details. From ec4f646759d6625bbafcd4ce67224fb63b99a96c Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 9 Mar 2026 10:57:06 +0900 Subject: [PATCH 0154/1296] network: introduce link_is_up() helper function --- src/network/netdev/bond.c | 2 +- src/network/networkd-link.c | 21 +++++++++++---------- src/network/networkd-link.h | 1 + src/network/networkd-nexthop.c | 4 ++-- src/network/networkd-route.c | 2 +- src/network/networkd-setlink.c | 11 +++++------ 6 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/network/netdev/bond.c b/src/network/netdev/bond.c index 0ae5fbdc43004..38f06d43b7fbd 100644 --- a/src/network/netdev/bond.c +++ b/src/network/netdev/bond.c @@ -73,7 +73,7 @@ static int netdev_bond_fill_message_create(NetDev *netdev, Link *link, sd_netlin return r; } - bool up = link && FLAGS_SET(link->flags, IFF_UP); + bool up = link && link_is_up(link); bool has_slaves = link && !set_isempty(link->slaves); if (b->mode != _NETDEV_BOND_MODE_INVALID && !up && !has_slaves) { diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index e49d5946aa67d..c77b02b8e0192 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -815,7 +815,6 @@ int link_ipv6ll_gained(Link *link) { int link_handle_bound_to_list(Link *link) { bool required_up = false; - bool link_is_up = false; Link *l; assert(link); @@ -826,18 +825,15 @@ int link_handle_bound_to_list(Link *link) { if (hashmap_isempty(link->bound_to_links)) return 0; - if (link->flags & IFF_UP) - link_is_up = true; - HASHMAP_FOREACH(l, link->bound_to_links) if (link_has_carrier(l)) { required_up = true; break; } - if (!required_up && link_is_up) + if (!required_up && link_is_up(link)) return link_request_to_bring_up_or_down(link, /* up= */ false); - if (required_up && !link_is_up) + if (required_up && !link_is_up(link)) return link_request_to_bring_up_or_down(link, /* up= */ true); return 0; @@ -2012,7 +2008,7 @@ void link_update_operstate(Link *link, bool also_update_master) { carrier_state = LINK_CARRIER_STATE_ENSLAVED; else carrier_state = LINK_CARRIER_STATE_CARRIER; - } else if (link->flags & IFF_UP) + } else if (link_is_up(link)) carrier_state = LINK_CARRIER_STATE_NO_CARRIER; else carrier_state = LINK_CARRIER_STATE_OFF; @@ -2147,6 +2143,11 @@ bool link_has_carrier(Link *link) { return netif_has_carrier(link->kernel_operstate, link->flags); } +bool link_is_up(Link *link) { + assert(link); + return FLAGS_SET(link->flags, IFF_UP); +} + bool link_multicast_enabled(Link *link) { assert(link); @@ -2226,7 +2227,7 @@ static int link_update_flags(Link *link, sd_netlink_message *message) { log_link_debug(link, "Unknown link flags lost, ignoring: %#.5x", unknown_flags_removed); } - link_was_admin_up = link->flags & IFF_UP; + link_was_admin_up = link_is_up(link); had_carrier = link_has_carrier(link); link->flags = flags; @@ -2236,9 +2237,9 @@ static int link_update_flags(Link *link, sd_netlink_message *message) { r = 0; - if (!link_was_admin_up && (link->flags & IFF_UP)) + if (!link_was_admin_up && link_is_up(link)) r = link_admin_state_up(link); - else if (link_was_admin_up && !(link->flags & IFF_UP)) + else if (link_was_admin_up && !link_is_up(link)) r = link_admin_state_down(link); if (r < 0) return r; diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index c5b9421bc0b2b..9c641fad39bdb 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -229,6 +229,7 @@ void link_check_ready(Link *link); void link_update_operstate(Link *link, bool also_update_master); bool link_has_carrier(Link *link); +bool link_is_up(Link *link); bool link_multicast_enabled(Link *link); bool link_ipv6_enabled(Link *link); diff --git a/src/network/networkd-nexthop.c b/src/network/networkd-nexthop.c index 5e5f0d0e6858b..23941527cfdcf 100644 --- a/src/network/networkd-nexthop.c +++ b/src/network/networkd-nexthop.c @@ -780,7 +780,7 @@ static bool nexthop_is_ready_to_configure(Link *link, const NextHop *nexthop) { * kernel. */ if (link->set_flags_messages > 0) return false; - if (!FLAGS_SET(link->flags, IFF_UP)) + if (!link_is_up(link)) return false; } @@ -995,7 +995,7 @@ void link_forget_nexthops(Link *link) { assert(link); assert(link->manager); assert(link->ifindex > 0); - assert(!FLAGS_SET(link->flags, IFF_UP)); + assert(!link_is_up(link)); /* See comments in link_forget_routes(). */ diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index 2d84ba1db071e..593832df51aa8 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -1641,7 +1641,7 @@ int link_drop_routes(Link *link, bool only_static) { void link_forget_routes(Link *link) { assert(link); assert(link->ifindex > 0); - assert(!FLAGS_SET(link->flags, IFF_UP)); + assert(!link_is_up(link)); /* When an interface went down, IPv4 non-local routes bound to the interface are silently removed by * the kernel, without any notifications. Let's forget them in that case. Otherwise, when the link diff --git a/src/network/networkd-setlink.c b/src/network/networkd-setlink.c index 7069b101f9f55..f5a43788ef792 100644 --- a/src/network/networkd-setlink.c +++ b/src/network/networkd-setlink.c @@ -562,7 +562,7 @@ static int link_is_ready_to_set_link(Link *link, Request *req) { case REQUEST_TYPE_SET_LINK_CAN: /* Do not check link->set_flags_messages here, as it is ok even if link->flags * is outdated, and checking the counter causes a deadlock. */ - if (FLAGS_SET(link->flags, IFF_UP)) { + if (link_is_up(link)) { /* The CAN interface must be down to configure bitrate, etc... */ r = link_down_now(link); if (r < 0) @@ -626,14 +626,14 @@ static int link_is_ready_to_set_link(Link *link, Request *req) { /* Do not check link->set_flags_messages here, as it is ok even if link->flags is outdated, * and checking the counter causes a deadlock. */ - if (link->network->bond && FLAGS_SET(link->flags, IFF_UP)) { + if (link->network->bond && link_is_up(link)) { /* link must be down when joining to bond master. */ r = link_down_now(link); if (r < 0) return r; } - if (link->network->bridge && !FLAGS_SET(link->flags, IFF_UP) && link->dev) { + if (link->network->bridge && !link_is_up(link) && link->dev) { /* Some devices require the port to be up before joining the bridge. * * E.g. Texas Instruments SoC Ethernet running in switch mode: @@ -755,8 +755,7 @@ int link_request_to_set_addrgen_mode(Link *link) { * link goes down. Hence, we need to reset the interface. However, setting the mode by sysctl * does not need that. Let's use the sysctl interface when the link is already up. * See also issue #22424. */ - if (mode != IPV6_LINK_LOCAL_ADDRESSS_GEN_MODE_NONE && - FLAGS_SET(link->flags, IFF_UP)) { + if (mode != IPV6_LINK_LOCAL_ADDRESSS_GEN_MODE_NONE && link_is_up(link)) { r = link_set_ipv6ll_addrgen_mode(link, mode); if (r < 0) log_link_warning_errno(link, r, "Cannot set IPv6 address generation mode, ignoring: %m"); @@ -1223,7 +1222,7 @@ static bool link_is_ready_to_bring_up_or_down(Link *link, bool up) { if (link_get_by_index(link->manager, link->dsa_master_ifindex, &master) < 0) return false; - if (!FLAGS_SET(master->flags, IFF_UP)) + if (!link_is_up(master)) return false; } From 43989de6427954f0755e2667bda1cf3839d27964 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 9 Mar 2026 11:30:54 +0900 Subject: [PATCH 0155/1296] network: check if gateway is ready only when the nexthop is bound to link Currently, we support three types of nexthop: 1. simple nexthop, which is bound to link, may have specific gateway address, 2. blackhole nexthop, which is global configuration and is not bound to any links, 3. group nexthop, which is also global configuration and is not bound to any links. Thus, gateway_is_ready() is only necessary to call for simple nexthop case. Let's make the logic simpler. --- src/network/networkd-nexthop.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/network/networkd-nexthop.c b/src/network/networkd-nexthop.c index 23941527cfdcf..81eb85b4a06c1 100644 --- a/src/network/networkd-nexthop.c +++ b/src/network/networkd-nexthop.c @@ -772,26 +772,38 @@ static bool nexthop_is_ready_to_configure(Link *link, const NextHop *nexthop) { if (!link_is_ready_to_configure(link, false)) return false; + /* Currently, we support the following three types of nexthops: + * 1. Simple nexthop - bound to the link, requires the underlying link is up. + * 2. Blackhole nexthop - not bound to the link. + * 3. Group nexthop - not bound to the link, but all group members must be configured first. + * + * Note, the kernel also supports fdb nexthop, but currently we do not support it. Note, fdb nexthop + * does not require IFF_UP. See rtm_to_nh_config() in net/ipv4/nexthop.c of kernel. */ + + /* Simple nexthop */ if (nexthop_bound_to_link(nexthop)) { assert(nexthop->ifindex == link->ifindex); - /* TODO: fdb nexthop does not require IFF_UP. The conditions below needs to be updated - * when fdb nexthop support is added. See rtm_to_nh_config() in net/ipv4/nexthop.c of - * kernel. */ if (link->set_flags_messages > 0) return false; if (!link_is_up(link)) return false; + + return gateway_is_ready(link, FLAGS_SET(nexthop->flags, RTNH_F_ONLINK), nexthop->family, &nexthop->gw.address); } - /* All group members must be configured first. */ + /* Blackhole nexthop */ + if (nexthop->blackhole) + return true; + + /* Group nexthop */ HASHMAP_FOREACH(nhg, nexthop->group) { r = nexthop_is_ready(link->manager, nhg->id, NULL); if (r <= 0) return r; } - return gateway_is_ready(link, FLAGS_SET(nexthop->flags, RTNH_F_ONLINK), nexthop->family, &nexthop->gw.address); + return true; } static int nexthop_process_request(Request *req, Link *link, NextHop *nexthop) { From ed54648b47779d2ba24ceffd16b7b63ec0845043 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 9 Mar 2026 11:46:01 +0900 Subject: [PATCH 0156/1296] network: route bound to a link requires the link is up We checked if the link is up only when configuring (explicit) nexthop, but we did not checked that when configuring route which has (implicit) nexthop. Let's move the checks from nexthop_is_ready_to_configure() to gateway_is_ready(), which is called for both implicit and explict nexthops. Fixes #40106. --- src/network/networkd-nexthop.c | 5 ----- src/network/networkd-route-util.c | 6 ++++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/network/networkd-nexthop.c b/src/network/networkd-nexthop.c index 81eb85b4a06c1..0e453e159748a 100644 --- a/src/network/networkd-nexthop.c +++ b/src/network/networkd-nexthop.c @@ -784,11 +784,6 @@ static bool nexthop_is_ready_to_configure(Link *link, const NextHop *nexthop) { if (nexthop_bound_to_link(nexthop)) { assert(nexthop->ifindex == link->ifindex); - if (link->set_flags_messages > 0) - return false; - if (!link_is_up(link)) - return false; - return gateway_is_ready(link, FLAGS_SET(nexthop->flags, RTNH_F_ONLINK), nexthop->family, &nexthop->gw.address); } diff --git a/src/network/networkd-route-util.c b/src/network/networkd-route-util.c index 37ace2d335c60..3a7156fc4129f 100644 --- a/src/network/networkd-route-util.c +++ b/src/network/networkd-route-util.c @@ -139,6 +139,12 @@ bool gateway_is_ready(Link *link, bool onlink, int family, const union in_addr_u assert(link); assert(link->manager); + if (link->set_flags_messages > 0) + return false; + + if (!link_is_up(link)) + return false; + if (onlink) return true; From f47d180b855dd4eb0a11d44d001c99f475dda56b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 9 Mar 2026 12:31:41 +0900 Subject: [PATCH 0157/1296] test-network: add test case for issue #40106 --- .../25-route-static-issue-40106-dummy.network | 7 ++++ .../25-route-static-issue-40106-vlan.netdev | 7 ++++ .../25-route-static-issue-40106-vlan.network | 15 +++++++ test/test-network/systemd-networkd-tests.py | 41 +++++++++++++++++++ 4 files changed, 70 insertions(+) create mode 100644 test/test-network/conf/25-route-static-issue-40106-dummy.network create mode 100644 test/test-network/conf/25-route-static-issue-40106-vlan.netdev create mode 100644 test/test-network/conf/25-route-static-issue-40106-vlan.network diff --git a/test/test-network/conf/25-route-static-issue-40106-dummy.network b/test/test-network/conf/25-route-static-issue-40106-dummy.network new file mode 100644 index 0000000000000..08553b97f5f19 --- /dev/null +++ b/test/test-network/conf/25-route-static-issue-40106-dummy.network @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=dummy99 + +[Network] +ConfigureWithoutCarrier=true +VLAN=vlan99 diff --git a/test/test-network/conf/25-route-static-issue-40106-vlan.netdev b/test/test-network/conf/25-route-static-issue-40106-vlan.netdev new file mode 100644 index 0000000000000..c40e7b755aefa --- /dev/null +++ b/test/test-network/conf/25-route-static-issue-40106-vlan.netdev @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[NetDev] +Kind=vlan +Name=vlan99 + +[VLAN] +Id=99 diff --git a/test/test-network/conf/25-route-static-issue-40106-vlan.network b/test/test-network/conf/25-route-static-issue-40106-vlan.network new file mode 100644 index 0000000000000..442cf480fbc51 --- /dev/null +++ b/test/test-network/conf/25-route-static-issue-40106-vlan.network @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=vlan99 + +[Network] +ConfigureWithoutCarrier=true +BindCarrier=dummy99 + +[Address] +Address=192.0.2.1/24 + +[Route] +Destination=198.51.100.1 +Type=multicast +Scope=link diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index bbbab8e3fe636..6f2fe1b9eb65c 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -4603,6 +4603,47 @@ def test_route_via_ipv6(self): self.assertRegex(output, '149.10.124.48/28 proto kernel scope link src 149.10.124.58') self.assertRegex(output, '149.10.124.66 via inet6 2001:1234:5:8fff:ff:ff:ff:ff proto static') + def test_route_static_issue_40106(self): + check_output('ip link add dummy99 type dummy') + check_output('ip link set dummy99 up carrier off') + copy_network_unit( + '25-route-static-issue-40106-dummy.network', + '25-route-static-issue-40106-vlan.netdev', + '25-route-static-issue-40106-vlan.network', + ) + start_networkd() + self.wait_online('dummy99:no-carrier') + self.wait_operstate('vlan99', operstate='off', setup_state='configuring') + + # address can be configured even when the interface is down. + self.wait_address('vlan99', '192.0.2.1/24', ipv='-4', timeout_sec=10) + print('### ip -4 address show dev vlan99') + output = check_output('ip -4 address show dev vlan99') + print(output) + self.assertIn('inet 192.0.2.1/24 brd 192.0.2.255 scope global vlan99', output) + + # route cannot be configured when the interface is down. + print('### ip -4 route show dev vlan99') + output = check_output('ip -4 route show dev vlan99') + print(output) + self.assertEqual(output, '') + + # When cable is connected, the vlan becomes up by BindCarrier=, then + # the pending route is also configured. + check_output('ip link set dummy99 carrier on') + self.wait_online('dummy99:degraded', 'vlan99:routable') + + print('### ip -4 address show dev vlan99') + output = check_output('ip -4 address show dev vlan99') + print(output) + self.assertIn('inet 192.0.2.1/24 brd 192.0.2.255 scope global vlan99', output) + + print('### ip -4 route show dev vlan99') + output = check_output('ip -4 route show dev vlan99') + print(output) + self.assertIn('192.0.2.0/24 proto kernel scope link src 192.0.2.1', output) + self.assertIn('multicast 198.51.100.1 proto static scope link', output) + @expectedFailureIfModuleIsNotAvailable('tcp_dctcp') def test_route_congctl(self): copy_network_unit('25-route-congctl.network', '12-dummy.netdev') From a023d2bbdbe6983807bd2e0fde681af1a8d52532 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 6 Mar 2026 15:58:06 +0100 Subject: [PATCH 0158/1296] ci: privilege-separate Claude review workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The workflow is split into two jobs for least-privilege: 1. 'review' job — runs Claude with read-only permissions (contents: read, id-token: write for AWS OIDC, actions: read). Claude produces a structured JSON review via --json-schema with a 'comments' array and a 'summary' string. Its tools are restricted to read-only operations (Read, LS, Grep, Glob, Task, and various Bash prefixes for common read-only commands). Claude also has access to CI MCP tools to analyze failed workflow runs. 2. 'post' job — only has pull-requests: write. Reads the structured JSON output from the review job and posts inline comments individually (so re-runs only add new comments). Maintains a tracking comment with a marker that is created on first run and updated in-place on subsequent runs, preserving existing item order, wording, and checkbox state. Posts a notification comment when the tracking comment is updated or left unchanged. Comment deduplication is handled by Claude in the prompt rather than in the posting script, allowing for better semantic understanding of whether two comments address the same issue. The PR number is resolved via github.event.pull_request.number with a fallback to github.event.issue.number for issue_comment events where github.event.pull_request is not populated. The concurrency group uses the same fallback. Co-developed-by: Claude --- .github/workflows/claude-review.yml | 342 ++++++++++++++++++++++------ 1 file changed, 270 insertions(+), 72 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 1c2a136361673..33097f95ad407 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -1,6 +1,10 @@ # Integrates Claude Code as an AI assistant for reviewing pull requests. -# Mention @claude in any PR review to request a review. Claude authenticates +# Mention @claude in any PR comment to request a review. Claude authenticates # via AWS Bedrock using OIDC — no long-lived API keys required. +# +# Architecture: The workflow is split into two jobs for least-privilege: +# 1. "review" — runs Claude with read-only permissions, produces structured JSON +# 2. "post" — reads the JSON and posts comments to the PR with write permissions name: Claude Review @@ -13,12 +17,16 @@ on: types: [created] pull_request_review: types: [submitted] + concurrency: - group: claude-review-${{ github.event.pull_request.number }} - cancel-in-progress: true + group: claude-review-${{ github.event.pull_request.number || github.event.issue.number }} + cancel-in-progress: true + jobs: - claude-review: + review: runs-on: ubuntu-latest + env: + PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} if: | github.repository_owner == 'systemd' && @@ -33,14 +41,27 @@ jobs: contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.review.author_association))) permissions: - contents: read # Read repository contents - pull-requests: write # Post comments and reviews on PRs + contents: read id-token: write # Authenticate with AWS via OIDC - actions: read # Access workflow run metadata + actions: read + + outputs: + structured_output: ${{ steps.claude.outputs.structured_output }} + pr_number: ${{ steps.pr.outputs.number }} + head_sha: ${{ steps.pr.outputs.head_sha }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + - name: Resolve PR metadata + id: pr + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "number=$PR_NUMBER" >> "$GITHUB_OUTPUT" + gh pr view "$PR_NUMBER" --json headRefOid --jq '.headRefOid' | \ + xargs -I{} echo "head_sha={}" >> "$GITHUB_OUTPUT" + - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 with: @@ -49,34 +70,95 @@ jobs: aws-region: us-east-1 - name: Run Claude Code + id: claude uses: anthropics/claude-code-action@1fc90f3ed982521116d8ff6d85b948c9b12cae3e + env: + REVIEW_SCHEMA: >- + { + "type": "object", + "required": ["comments", "summary"], + "properties": { + "summary": { + "type": "string", + "description": "A markdown summary of the review to post as a top-level tracking comment" + }, + "comments": { + "type": "array", + "items": { + "type": "object", + "required": ["file", "line", "severity", "body"], + "properties": { + "file": { + "type": "string", + "description": "Path to the file relative to the repo root" + }, + "line": { + "type": "integer", + "description": "Line number in the diff (new file side) to attach the comment to" + }, + "severity": { + "type": "string", + "enum": ["must-fix", "suggestion", "nit"] + }, + "body": { + "type": "string", + "description": "The review comment body in markdown" + } + } + } + } + } + } with: use_bedrock: "true" + # We still have to pass GITHUB_TOKEN here because claude-code-action + # requires it, but we restrict Claude's tools to read-only operations + # so it cannot post comments or modify the PR. github_token: ${{ secrets.GITHUB_TOKEN }} + track_progress: false + additional_permissions: | + actions: read claude_args: | --model us.anthropic.claude-opus-4-6-v1 --max-turns 100 --allowedTools " - Read,Write,Edit,MultiEdit,LS,Grep,Glob,Task, - Bash(cat:*),Bash(test:*),Bash(printf:*),Bash(jq:*),Bash(head:*),Bash(git:*),Bash(gh:*), - mcp__github_inline_comment__create_inline_comment, + Read,LS,Grep,Glob,Task, + Bash(cat:*),Bash(test:*),Bash(printf:*),Bash(jq:*),Bash(head:*),Bash(tail:*), + Bash(git:*),Bash(gh:*),Bash(grep:*),Bash(find:*),Bash(ls:*),Bash(wc:*), + Bash(diff:*),Bash(sed:*),Bash(awk:*),Bash(sort:*),Bash(uniq:*), + mcp__github_ci__get_ci_status, + mcp__github_ci__get_workflow_run_details, + mcp__github_ci__download_job_log, " + --json-schema '${{ env.REVIEW_SCHEMA }}' prompt: | REPO: ${{ github.repository }} - PR NUMBER: ${{ github.event.pull_request.number }} - HEAD SHA: ${{ github.event.pull_request.head.sha }} + PR NUMBER: ${{ steps.pr.outputs.number }} + HEAD SHA: ${{ steps.pr.outputs.head_sha }} - Review this pull request. - You are in the upstream repo without the patch applied. Do not apply it. + You are a code reviewer for the systemd project. Review this pull request and + produce a structured JSON result containing your review comments. Do NOT attempt + to post comments yourself — just return the JSON. You are in the upstream repo + without the patch applied. Do not apply it. ## Phase 1: Gather context Fetch the patch, PR title/body, and list of existing comments (top-level, inline, and reviews): - - `gh pr diff ${{ github.event.pull_request.number }} --patch` - - `gh pr view ${{ github.event.pull_request.number }} --json title,body` - - `gh api --paginate repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments` - - `gh api --paginate repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/comments` - - `gh api --paginate repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews` + - `gh pr diff ${{ steps.pr.outputs.number }} --patch` + - `gh pr view ${{ steps.pr.outputs.number }} --json title,body` + - `gh api --paginate repos/${{ github.repository }}/issues/${{ steps.pr.outputs.number }}/comments` + - `gh api --paginate repos/${{ github.repository }}/pulls/${{ steps.pr.outputs.number }}/comments` + - `gh api --paginate repos/${{ github.repository }}/pulls/${{ steps.pr.outputs.number }}/reviews` + + Also look for an existing tracking comment (containing ``) + in the top-level issue comments. If one exists, you will use it as the basis for + your `summary` in Phase 3. + + Check CI status for the PR head commit using `mcp__github_ci__get_ci_status`. + If any workflow runs have failed, use `mcp__github_ci__get_workflow_run_details` + and `mcp__github_ci__download_job_log` to fetch the failure logs. Pass these + logs to the review subagents in Phase 2 so they can identify whether the PR + changes caused the failures. ## Phase 2: Parallel review subagents @@ -84,7 +166,8 @@ jobs: - Code quality, style, and best practices - Potential bugs, issues, incorrect logic - Security implications - - CLAUDE.md - compliance + - CLAUDE.md compliance + - CI failures (if any logs were fetched in Phase 1) For every category, launch subagents to review them in parallel. Group related sections as needed — use 2-4 subagents based on PR size and scope. @@ -92,11 +175,10 @@ jobs: Give each subagent the PR title, description, full patch, and the list of changed files. Each subagent must return a JSON array of issues: - `[{"file": "path", "line": , "severity": "must-fix|suggestion|nit", "title": "...", "body": "..."}]` + `[{"file": "path", "line": , "severity": "must-fix|suggestion|nit", "body": "..."}]` - Subagents must ONLY return the JSON array — they must NOT post comments, - call `gh`, or use `mcp__github_inline_comment__create_inline_comment`. - All posting happens in Phase 3. + `line` must be a line number from the NEW side of the diff (i.e. where the comment + should appear in the changed file after the patch is applied). Each subagent MUST verify its findings before returning them: - For style/convention claims, check at least 3 existing examples in the codebase to confirm @@ -104,55 +186,171 @@ jobs: - For "use X instead of Y" suggestions, confirm X actually exists and works for this case. - If unsure, don't include the issue. - ## Phase 3: Collect and post + ## Phase 3: Collect, deduplicate, and summarize After ALL subagents complete: 1. Collect all issues. Merge duplicates (same file, lines within 3 of each other, same problem). 2. Drop low-confidence findings. - 3. For CLAUDE.md violations that appear in 3+ existing places in the codebase, do NOT post inline comments. - Instead, add them to the 'CLAUDE.md improvements' section of the tracking comment - 4. Check existing inline review comments (fetched in Phase 1). Do NOT post an inline comment if - one already exists on the same file+line about the same problem. - 5. Check for author replies that dismiss or reject a previous comment. Do NOT re-raise an issue - the PR author has already responded to disagreeing with. - 6. Post new inline comments with `mcp__github_inline_comment__create_inline_comment`. - - Prefix ALL comments with "Claude: ". - Link format: https://github.com/${{ github.repository }}/blob/${{ github.event.pull_request.head.sha }}/README.md#L10-L15 - - Then maintain a single top-level "tracking comment" listing ALL issues as checkboxes. - Use a hidden HTML marker to find it: ``. - Look through the top-level comments fetched in Phase 1 for one containing that marker. - - **If no tracking comment exists (first run):** - Create one with `gh pr comment ${{ github.event.pull_request.number }} --body "..."` using this format: - ``` - Claude: review of # () - - - - ### Must fix - - [ ] **title** — `file:line` — short explanation - - ### Suggestions - - [ ] **title** — `file:line` — short explanation - - ### Nits - - [ ] **title** — `file:line` — short explanation - - ### CLAUDE.md improvements - - improvement suggestion - ``` - Omit empty sections. - - **If a tracking comment already exists (subsequent run):** - 1. Parse the existing checkboxes. For each old issue, check if the current patch still has - that problem (re-check the relevant lines in the new diff). If fixed, mark it `- [x]`. - If the author dismissed it, mark it `- [x] ~~title~~ (dismissed)`. - 2. Append any NEW issues found in this run that aren't already listed. - 3. Update the HEAD SHA in the header line. - 4. Edit the comment in-place. - ``` - printf '%s' "$BODY" > pr-review-body.txt - gh api --method PATCH repos/${{ github.repository }}/issues/comments/ -F body=@pr-review-body.txt - ``` + 3. Check the existing inline review comments fetched in Phase 1. Do NOT include a + comment if one already exists on the same file and line about the same problem. + Also check for author replies that dismiss or reject a previous comment — do NOT + re-raise an issue the PR author has already responded to disagreeing with. + 4. Prefix ALL comment bodies with a severity tag: `**must-fix**: `, `**suggestion**: `, + or `**nit**: `. + 5. Write a `summary` field in markdown for a top-level tracking comment. + + **If no existing tracking comment was found (first run):** + Use this format: + + ``` + ## Claude review of PR # () + + + + ### Must fix + - [ ] **short title** — `file:line` — brief explanation + + ### Suggestions + - [ ] **short title** — `file:line` — brief explanation + + ### Nits + - [ ] **short title** — `file:line` — brief explanation + ``` + + Omit empty sections. Each checkbox item must correspond to an entry in `comments`. + If there are no issues at all, write a short message saying the PR looks good. + + **If an existing tracking comment was found (subsequent run):** + Use the existing comment as the starting point. Preserve the order and wording + of all existing items. Then apply these updates: + - Update the HEAD SHA in the header line. + - For each existing item, re-check whether the issue is still present in the + current diff. If it has been fixed, mark it checked: `- [x]`. + - If the PR author replied dismissing an item, mark it: + `- [x] ~~short title~~ (dismissed)`. + - Preserve checkbox state that was already set by previous runs or by hand. + - Append any NEW issues found in this run that aren't already listed, + in the appropriate severity section, after the existing items. + - Do NOT reorder, reword, or remove existing items. + + Return the final JSON object with your `comments` array and `summary` string. + Do NOT attempt to post comments, call `gh`, or use any MCP tools to interact with the PR. + + post: + runs-on: ubuntu-latest + needs: review + if: always() && needs.review.result == 'success' + + permissions: + pull-requests: write + + steps: + - name: Post review comments + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea + env: + STRUCTURED_OUTPUT: ${{ needs.review.outputs.structured_output }} + PR_NUMBER: ${{ needs.review.outputs.pr_number }} + HEAD_SHA: ${{ needs.review.outputs.head_sha }} + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const prNumber = parseInt(process.env.PR_NUMBER, 10); + const headSha = process.env.HEAD_SHA; + + /* Parse Claude's structured output. */ + const raw = process.env.STRUCTURED_OUTPUT; + console.log("Structured output from Claude:"); + console.log(raw || "(empty)"); + + let comments = []; + let summary = ""; + if (raw) { + try { + const review = JSON.parse(raw); + if (Array.isArray(review.comments)) + comments = review.comments; + if (typeof review.summary === "string") + summary = review.summary; + } catch (e) { + console.log(`Failed to parse structured output: ${e.message}`); + } + } + + console.log(`Claude produced ${comments.length} review comment(s).`); + + /* Post each inline comment individually. Deduplication against existing + * comments is handled by Claude in the prompt, so we just post whatever + * it returns. Using individual comments (rather than a review) means + * re-runs only add new comments instead of creating a whole new review. */ + for (const c of comments) { + console.log(` Posting comment on ${c.file}:${c.line}`); + await github.rest.pulls.createReviewComment({ + owner, + repo, + pull_number: prNumber, + commit_id: headSha, + path: c.file, + line: c.line, + body: `Claude: ${c.body}`, + }); + } + + if (comments.length > 0) + console.log(`Posted ${comments.length} inline comment(s).`); + else + console.log("No inline comments to post."); + + /* Create or update the tracking comment. */ + const MARKER = ""; + if (!summary) + summary = "Claude review: no issues found :tada:\n\n" + MARKER; + else if (!summary.includes(MARKER)) + summary = summary.replace(/\n/, `\n${MARKER}\n`); + + /* Find an existing tracking comment. */ + const {data: issueComments} = await github.rest.issues.listComments({ + owner, + repo, + issue_number: prNumber, + per_page: 100, + }); + + const existing = issueComments.find((c) => c.body && c.body.includes(MARKER)); + + if (existing) { + const commentUrl = existing.html_url; + if (existing.body === summary) { + console.log(`Tracking comment ${existing.id} is unchanged.`); + await github.rest.issues.createComment({ + owner, + repo, + issue_number: prNumber, + body: `Claude re-reviewed this PR — no changes to the [tracking comment](${commentUrl}).`, + }); + } else { + console.log(`Updating existing tracking comment ${existing.id}.`); + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: existing.id, + body: summary, + }); + await github.rest.issues.createComment({ + owner, + repo, + issue_number: prNumber, + body: `Claude review updated — see [tracking comment](${commentUrl}).`, + }); + } + } else { + console.log("Creating new tracking comment."); + await github.rest.issues.createComment({ + owner, + repo, + issue_number: prNumber, + body: summary, + }); + } + + console.log("Tracking comment posted successfully."); From 48b3642dd1f021a457b51b36d4a6c0fa7aaf702e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 9 Mar 2026 10:33:20 +0100 Subject: [PATCH 0159/1296] ci: Don't cancel in progress jobs for claude-review workflow This workflow runs on any comment to a github PR. 99% of the time the workflow will be skipped yet it will still cancel any previous ongoing workflows. Let's not cancel in progress workflow but instead queue the workflow so we don't cancel in progress reviews any time a comment is posted on a PR that is being reviewed. --- .github/workflows/claude-review.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 33097f95ad407..a6c133aaa7f99 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -20,7 +20,6 @@ on: concurrency: group: claude-review-${{ github.event.pull_request.number || github.event.issue.number }} - cancel-in-progress: true jobs: review: From 47e98763122364c7bd9828617b08abcd8a35e538 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 9 Mar 2026 11:12:21 +0100 Subject: [PATCH 0160/1296] ci: Update claude action to v1 commit I accidentally picked a random commit instead of the one pointing to the official v1 release, let's fix that. --- .github/workflows/claude-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index a6c133aaa7f99..b36d6dc211d1e 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -70,7 +70,7 @@ jobs: - name: Run Claude Code id: claude - uses: anthropics/claude-code-action@1fc90f3ed982521116d8ff6d85b948c9b12cae3e + uses: anthropics/claude-code-action@26ec041249acb0a944c0a47b6c0c13f05dbc5b44 env: REVIEW_SCHEMA: >- { From fbdc5ded8828fc3cba241834dade90229868095d Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 9 Mar 2026 16:24:20 +0100 Subject: [PATCH 0161/1296] AGENTS.md: Tell agents to use mkosi box for running commands mkosi box will run in the tools tree if there is one which is guaranteed to contain basic tools. --- AGENTS.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index e69b25a076a2e..9befc01a594b4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,6 +12,10 @@ Always consult these files as needed: - `docs/CODING_STYLE.md` — full style guide (must-read before writing code) - `docs/CONTRIBUTING.md` — contribution guidelines and PR workflow +## Running arbitrary commands + +- Always run arbitrary commands with the `mkosi box -- ` wrapper command. This runs in an environment where more tools are available. + ## Build and Test Commands - Never compile individual files or targets. Always run `mkosi -f box -- meson compile -C build` to build From d73300914167d51565158a30fafadc6f98997f9f Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 9 Mar 2026 16:25:05 +0100 Subject: [PATCH 0162/1296] mount-util: Use new mount API in bind_mount_submounts() --- src/shared/mount-util.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index f3eaa7ba97ad9..382992edf0887 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -1830,13 +1830,11 @@ int get_sub_mounts(const char *prefix, SubMount **ret_mounts, size_t *ret_n_moun continue; } - mount_fd = open(path, O_CLOEXEC|O_PATH); - if (mount_fd < 0) { - if (errno == ENOENT) /* The path may be hidden by another over-mount or already unmounted. */ - continue; - - return log_debug_errno(errno, "Failed to open subtree of mounted filesystem '%s': %m", path); - } + mount_fd = RET_NERRNO(open_tree(AT_FDCWD, path, OPEN_TREE_CLONE|OPEN_TREE_CLOEXEC|AT_RECURSIVE)); + if (mount_fd == -ENOENT) /* The path may be hidden by another over-mount or already unmounted. */ + continue; + if (mount_fd < 0) + return log_debug_errno(mount_fd, "Failed to open subtree of mounted filesystem '%s': %m", path); p = strdup(path); if (!p) @@ -1905,9 +1903,7 @@ int bind_mount_submounts( continue; } - r = mount_follow_verbose(LOG_DEBUG, FORMAT_PROC_FD_PATH(m->mount_fd), t, NULL, MS_BIND|MS_REC, NULL); - if (r < 0 && ret == 0) - ret = r; + RET_GATHER(ret, RET_NERRNO(move_mount(m->mount_fd, "", AT_FDCWD, t, MOVE_MOUNT_F_EMPTY_PATH))); } return ret; From 8a19cad8aea41e4678bf1908afdc6bea477b0a0c Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 9 Mar 2026 17:55:33 +0100 Subject: [PATCH 0163/1296] ci: Drop tracking comment update from claude review Too noisy, let's drop it. --- .github/workflows/claude-review.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index b36d6dc211d1e..f4a594770200c 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -335,12 +335,6 @@ jobs: comment_id: existing.id, body: summary, }); - await github.rest.issues.createComment({ - owner, - repo, - issue_number: prNumber, - body: `Claude review updated — see [tracking comment](${commentUrl}).`, - }); } } else { console.log("Creating new tracking comment."); From d3df01c4ebd67b9b5be2679ce47b5127b36af16e Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 9 Mar 2026 13:30:02 +0900 Subject: [PATCH 0164/1296] sd-device: use device_add_property() at one more place --- src/libsystemd/sd-device/sd-device.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c index 526f183b1e9ec..a314f65cb23ae 100644 --- a/src/libsystemd/sd-device/sd-device.c +++ b/src/libsystemd/sd-device/sd-device.c @@ -1628,7 +1628,6 @@ bool device_has_devlink(sd_device *device, const char *devlink) { static int device_add_property_internal_from_string(sd_device *device, const char *str) { _cleanup_free_ char *key = NULL; char *value; - int r; assert(device); assert(str); @@ -1648,11 +1647,7 @@ static int device_add_property_internal_from_string(sd_device *device, const cha /* Add the property to both sd_device::properties and sd_device::properties_db, * as this is called by only handle_db_line(). */ - r = device_add_property_aux(device, key, value, false); - if (r < 0) - return r; - - return device_add_property_aux(device, key, value, true); + return device_add_property(device, key, value); } int device_set_usec_initialized(sd_device *device, usec_t when) { From 460b5808108c33bde8917346b6f1ef90b730ecea Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 10 Mar 2026 03:33:10 +0900 Subject: [PATCH 0165/1296] sd-device: do not register a property with an empty string The function device_add_property() handles property with NULL value as removing the property. Let's also make an empty value is handled as same. Note, ENV{hoge}=="" udev property handles both NULL value and an empty string in the same way. --- src/libsystemd/sd-device/sd-device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c index a314f65cb23ae..e6b05b636a871 100644 --- a/src/libsystemd/sd-device/sd-device.c +++ b/src/libsystemd/sd-device/sd-device.c @@ -92,7 +92,7 @@ int device_add_property_aux(sd_device *device, const char *key, const char *valu else properties = &device->properties; - if (value) { + if (!isempty(value)) { _unused_ _cleanup_free_ char *old_value = NULL; _cleanup_free_ char *new_key = NULL, *new_value = NULL, *old_key = NULL; int r; From e1877e6b064c5c8b158fee86e4df7be21ae1ee8a Mon Sep 17 00:00:00 2001 From: ppkramer-hub Date: Mon, 9 Mar 2026 20:31:53 +0100 Subject: [PATCH 0166/1296] mount: honor --timeout-idle-sec=SEC option (#41010) When using systemd-mount to create a transient .mount/.automount file for removable storage, the option to specify the idle timeout on the commandline using **--timeout-idle-sec=SEC** is not reflected in the generated .automount file. Instead, the idle timeout is always set to 1 second. arg_timeout_idle_set was never set to true when passing the argument, so arg_timeout_idle was always set to 1s. Fixes #41007. Co-authored-by: patrick --- src/mount/mount-tool.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mount/mount-tool.c b/src/mount/mount-tool.c index d2af4688abb27..768ff349e2713 100644 --- a/src/mount/mount-tool.c +++ b/src/mount/mount-tool.c @@ -348,6 +348,7 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Failed to parse timeout: %s", optarg); + arg_timeout_idle_set = true; break; case ARG_AUTOMOUNT_PROPERTY: From a62cd5a153ffe18c27aff02685ed75c5bc4509a2 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 9 Mar 2026 13:24:03 +0900 Subject: [PATCH 0167/1296] sd-device: refuse spurious properties Properties are set through uevent, udev rules, or program output by IMPORT. They may contain spurious characters and udev database parsers may be confused. Let's refuse spurious properties. --- src/libsystemd/sd-device/sd-device.c | 23 +++++++++++ src/libsystemd/sd-device/test-sd-device.c | 48 +++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c index e6b05b636a871..ef67c649d1ebd 100644 --- a/src/libsystemd/sd-device/sd-device.c +++ b/src/libsystemd/sd-device/sd-device.c @@ -30,6 +30,7 @@ #include "string-util.h" #include "strv.h" #include "time-util.h" +#include "utf8.h" int device_new_aux(sd_device **ret) { sd_device *device; @@ -81,12 +82,34 @@ static sd_device* device_free(sd_device *device) { DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_device, sd_device, device_free); +static bool property_is_valid(const char *key, const char *value) { + /* Device properties may be saved to database file, then may be parsed from the file. When if a + * property contains spurious characters, then the parser may be confused. Let's refuse spurious + * properties, even if it is internal, which will not be saved to database file, for consistency. */ + + if (isempty(key) || !in_charset(key, ALPHANUMERICAL "_.")) + return false; + + /* an empty value means unset the property, hence that's fine. */ + if (isempty(value)) + return true; + + /* refuse invalid UTF8 and control characters */ + if (!utf8_is_valid(value) || string_has_cc(value, /* ok= */ NULL)) + return false; + + return true; +} + int device_add_property_aux(sd_device *device, const char *key, const char *value, bool db) { OrderedHashmap **properties; assert(device); assert(key); + if (!property_is_valid(key, value)) + return -EINVAL; + if (db) properties = &device->properties_db; else diff --git a/src/libsystemd/sd-device/test-sd-device.c b/src/libsystemd/sd-device/test-sd-device.c index db7c9420cc605..43b76f46e9baf 100644 --- a/src/libsystemd/sd-device/test-sd-device.c +++ b/src/libsystemd/sd-device/test-sd-device.c @@ -841,6 +841,54 @@ TEST(devname_from_devnum) { } } +TEST(device_add_property) { + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + const char *val; + + ASSERT_OK(sd_device_new_from_syspath(&dev, "/sys/class/net/lo")); + + /* add a property */ + ASSERT_OK(device_add_property(dev, "hoge", "foo")); + ASSERT_OK(sd_device_get_property_value(dev, "hoge", &val)); + ASSERT_STREQ(val, "foo"); + + /* update an existing property */ + ASSERT_OK(device_add_property(dev, "hoge", "bar")); + ASSERT_OK(sd_device_get_property_value(dev, "hoge", &val)); + ASSERT_STREQ(val, "bar"); + + /* remove an existing property */ + ASSERT_OK(device_add_property(dev, "hoge", NULL)); + ASSERT_ERROR(sd_device_get_property_value(dev, "hoge", &val), ENOENT); + + /* add a property again */ + ASSERT_OK(device_add_property(dev, "hoge", "foo")); + ASSERT_OK(sd_device_get_property_value(dev, "hoge", &val)); + ASSERT_STREQ(val, "foo"); + + /* remove it with an empty string */ + ASSERT_OK(device_add_property(dev, "hoge", "")); + ASSERT_ERROR(sd_device_get_property_value(dev, "hoge", &val), ENOENT); + + /* check internal property (starting with dot) */ + ASSERT_OK(device_add_property(dev, ".hoge", "baz")); + ASSERT_OK(sd_device_get_property_value(dev, ".hoge", &val)); + ASSERT_STREQ(val, "baz"); + + /* refuse invalid property names */ + ASSERT_ERROR(device_add_property(dev, "hoge-hoge", "aaa"), EINVAL); + ASSERT_ERROR(device_add_property(dev, "hoge=hoge", "aaa"), EINVAL); + ASSERT_ERROR(device_add_property(dev, "hoge hoge", "aaa"), EINVAL); + ASSERT_ERROR(device_add_property(dev, "hoge\nhoge", "aaa"), EINVAL); + ASSERT_ERROR(device_add_property(dev, "hoge\rhoge", "aaa"), EINVAL); + ASSERT_ERROR(device_add_property(dev, "hoge\thoge", "aaa"), EINVAL); + + /* refuse invalid property values */ + ASSERT_ERROR(device_add_property(dev, "hoge", "aaa\naaa"), EINVAL); + ASSERT_ERROR(device_add_property(dev, "hoge", "aaa\raaa"), EINVAL); + ASSERT_ERROR(device_add_property(dev, "hoge", "aaa\taaa"), EINVAL); +} + static int intro(void) { int r; From b6ab595ee1ad8f6124be09a4e0d1de18dcb15a2c Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 9 Mar 2026 13:50:27 +0900 Subject: [PATCH 0168/1296] udev: improve log message in udev_builtin_add_property() --- src/udev/udev-builtin.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/udev/udev-builtin.c b/src/udev/udev-builtin.c index 121694e688fc5..f3c8936cad810 100644 --- a/src/udev/udev-builtin.c +++ b/src/udev/udev-builtin.c @@ -139,13 +139,20 @@ int udev_builtin_add_property(UdevEvent *event, const char *key, const char *val assert(key); + val = empty_to_null(val); + r = device_add_property(dev, key, val); if (r < 0) - return log_device_debug_errno(dev, r, "Failed to add property '%s%s%s'", + return log_device_debug_errno(dev, r, "Failed to %s property '%s%s%s'", + val ? "add" : "remove", key, val ? "=" : "", strempty(val)); - if (event->event_mode == EVENT_UDEVADM_TEST_BUILTIN) - printf("%s=%s\n", key, strempty(val)); + if (event->event_mode == EVENT_UDEVADM_TEST_BUILTIN) { + if (val) + printf("%s=%s\n", key, val); + else + printf("%s (removed)\n", key); + } return 0; } From e7485799a480ddad6f862521198db6ba66737595 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 10 Mar 2026 09:18:48 +0900 Subject: [PATCH 0169/1296] semaphore: use Ubuntu 24.04 Semaphore CI/CD now emits the following error. ``` OS image 'ubuntu2004' for machine type 'e1-standard-2' is currently in a brownout phase. Please use another OS image. ``` Let's use newer image. --- .semaphore/semaphore.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index 42df0f648f5ec..baa6ecfa4a02d 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -7,7 +7,7 @@ name: Debian autopkgtest (LXC) agent: machine: type: e1-standard-2 - os_image: ubuntu2004 + os_image: ubuntu2404 # Cancel any running or queued job for the same ref auto_cancel: From f2a75354bcbb5010fcb2942567948df278f150f1 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 10 Mar 2026 10:58:27 +0100 Subject: [PATCH 0170/1296] ci: Use github MCP in claude review instead of gh command line tool MCP was specifically made for AI and is available, so we might as well use it. --- .github/workflows/claude-review.yml | 31 +++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index f4a594770200c..ce40dae45f83c 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -123,8 +123,15 @@ jobs: --allowedTools " Read,LS,Grep,Glob,Task, Bash(cat:*),Bash(test:*),Bash(printf:*),Bash(jq:*),Bash(head:*),Bash(tail:*), - Bash(git:*),Bash(gh:*),Bash(grep:*),Bash(find:*),Bash(ls:*),Bash(wc:*), + Bash(git:*),Bash(grep:*),Bash(find:*),Bash(ls:*),Bash(wc:*), Bash(diff:*),Bash(sed:*),Bash(awk:*),Bash(sort:*),Bash(uniq:*), + mcp__github__get_pull_request, + mcp__github__get_pull_request_diff, + mcp__github__get_pull_request_files, + mcp__github__get_pull_request_reviews, + mcp__github__get_pull_request_comments, + mcp__github__get_pull_request_status, + mcp__github__get_issue_comments, mcp__github_ci__get_ci_status, mcp__github_ci__get_workflow_run_details, mcp__github_ci__download_job_log, @@ -142,15 +149,19 @@ jobs: ## Phase 1: Gather context - Fetch the patch, PR title/body, and list of existing comments (top-level, inline, and reviews): - - `gh pr diff ${{ steps.pr.outputs.number }} --patch` - - `gh pr view ${{ steps.pr.outputs.number }} --json title,body` - - `gh api --paginate repos/${{ github.repository }}/issues/${{ steps.pr.outputs.number }}/comments` - - `gh api --paginate repos/${{ github.repository }}/pulls/${{ steps.pr.outputs.number }}/comments` - - `gh api --paginate repos/${{ github.repository }}/pulls/${{ steps.pr.outputs.number }}/reviews` + Use the GitHub MCP server tools to fetch PR data. For all tools, pass + owner `${{ github.repository_owner }}`, repo `${{ github.event.repository.name }}`, + and pullNumber/issue_number ${{ steps.pr.outputs.number }}: + - `mcp__github__get_pull_request_diff` to get the PR diff + - `mcp__github__get_pull_request` to get the PR title, body, and metadata + - `mcp__github__get_pull_request_comments` to get top-level PR comments + - `mcp__github__get_pull_request_reviews` to get PR reviews - Also look for an existing tracking comment (containing ``) - in the top-level issue comments. If one exists, you will use it as the basis for + Also fetch issue comments using `mcp__github__get_issue_comments` with + issue_number ${{ steps.pr.outputs.number }}. + + Look for an existing tracking comment (containing ``) + in the issue comments. If one exists, you will use it as the basis for your `summary` in Phase 3. Check CI status for the PR head commit using `mcp__github_ci__get_ci_status`. @@ -233,7 +244,7 @@ jobs: - Do NOT reorder, reword, or remove existing items. Return the final JSON object with your `comments` array and `summary` string. - Do NOT attempt to post comments, call `gh`, or use any MCP tools to interact with the PR. + Do NOT attempt to post comments or use any MCP tools to modify the PR. post: runs-on: ubuntu-latest From 809cb1b4d803044c0129a9524e1b08ce681aab3f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 10 Mar 2026 08:10:06 +0100 Subject: [PATCH 0171/1296] pcrlock: add an extra assert() --- src/pcrextend/pcrextend.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pcrextend/pcrextend.c b/src/pcrextend/pcrextend.c index c80bf376fdce1..c319ddd0f8847 100644 --- a/src/pcrextend/pcrextend.c +++ b/src/pcrextend/pcrextend.c @@ -327,6 +327,8 @@ static int extend_nvpcr_now( _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; int r; + assert(name); + r = tpm2_context_new_or_warn(arg_tpm2_device, &c); if (r < 0) return r; From 2fb2428296b44b72df50b8c7bce0371bce4b0f4c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 10 Mar 2026 11:54:54 +0100 Subject: [PATCH 0172/1296] pcrlock: do not pass wrong error to log message --- src/pcrlock/pcrlock.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index ab97c1a754e30..acf68d698b90f 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -1713,7 +1713,7 @@ static int event_log_add_component_file(EventLog *el, EventLogComponent *compone } if (!sd_json_variant_is_object(j)) { - log_warning_errno(r, "Component file %s does not contain JSON object, ignoring.", path); + log_warning("Component file %s does not contain JSON object, ignoring.", path); return 0; } From 0306b2c20ec5f59fc8aa7da2911353a42078b623 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 10 Mar 2026 11:54:17 +0100 Subject: [PATCH 0173/1296] pcrlock: add .pcrlock file for recently added NvPCR separator Follow-up for: 867e64737a1761e313c371abfb43ab2c04b9e568 --- src/pcrlock/meson.build | 1 + src/pcrlock/pcrlock.d/770-nvpcr-separator.pcrlock | 1 + 2 files changed, 2 insertions(+) create mode 100644 src/pcrlock/pcrlock.d/770-nvpcr-separator.pcrlock diff --git a/src/pcrlock/meson.build b/src/pcrlock/meson.build index 6533ef3ab8f17..f7fa3d18d9cbb 100644 --- a/src/pcrlock/meson.build +++ b/src/pcrlock/meson.build @@ -27,6 +27,7 @@ install_data('pcrlock.d/500-separator.pcrlock.d/600-0xffffffff.pcrlock', install install_data('pcrlock.d/700-action-efi-exit-boot-services.pcrlock.d/300-present.pcrlock', install_dir : pcrlockdir / '700-action-efi-exit-boot-services.pcrlock.d') install_data('pcrlock.d/700-action-efi-exit-boot-services.pcrlock.d/600-absent.pcrlock', install_dir : pcrlockdir / '700-action-efi-exit-boot-services.pcrlock.d') install_data('pcrlock.d/750-enter-initrd.pcrlock', install_dir : pcrlockdir) +install_data('pcrlock.d/770-nvpcr-separator.pcrlock', install_dir : pcrlockdir) install_data('pcrlock.d/800-leave-initrd.pcrlock', install_dir : pcrlockdir) install_data('pcrlock.d/850-sysinit.pcrlock', install_dir : pcrlockdir) install_data('pcrlock.d/900-ready.pcrlock', install_dir : pcrlockdir) diff --git a/src/pcrlock/pcrlock.d/770-nvpcr-separator.pcrlock b/src/pcrlock/pcrlock.d/770-nvpcr-separator.pcrlock new file mode 100644 index 0000000000000..2cca29b550328 --- /dev/null +++ b/src/pcrlock/pcrlock.d/770-nvpcr-separator.pcrlock @@ -0,0 +1 @@ +{"records":[{"pcr":9,"digests":[{"hashAlg":"sha512","digest":"25d91b8f45d61cab950206c6edd86bd46ecbb1f2369bc1cdc7a8956861b4a0e30792e8b327548a7b31d5013088200f061970a8843dbb2504e400b80d46202642"},{"hashAlg":"sha384","digest":"196288284fffef2010ebff9d05ccc0b527fe0dcfd950c0524cbf36ed039aeecc25c8628abd1511ae77da685b7b5e17b4"},{"hashAlg":"sha256","digest":"97c5f96d4cd3fb14c7e272c47762af208f712c1a47733567e5c233e2b6f6c5cb"},{"hashAlg":"sha1","digest":"9cfca2696175a850a1ed68a6a1f075b38beab7cf"}]}]} From 8e59fb624830962dfe83f0322776b8ac6b392d48 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 10 Mar 2026 14:40:00 +0100 Subject: [PATCH 0174/1296] ci: Use one more variable in claude-review workflow --- .github/workflows/claude-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index ce40dae45f83c..4b2a0dc5d638f 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -142,7 +142,7 @@ jobs: PR NUMBER: ${{ steps.pr.outputs.number }} HEAD SHA: ${{ steps.pr.outputs.head_sha }} - You are a code reviewer for the systemd project. Review this pull request and + You are a code reviewer for the ${{ github.repository }} project. Review this pull request and produce a structured JSON result containing your review comments. Do NOT attempt to post comments yourself — just return the JSON. You are in the upstream repo without the patch applied. Do not apply it. From 128ff3582a3fdd0f403bae2dbe418f9a92f63a2a Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 10 Mar 2026 15:25:47 +0000 Subject: [PATCH 0175/1296] mkosi: update debian commit reference to 56e0eed69a4782eb8e110650d93daebcf1ece49a * 56e0eed69a Update changelog for 260~rc2-1 release * 36e6a6d247 Install new files * 692d0ffde5 Install new files for upstream build * 04e77a9300 Enable getty@ via packaging scriptlets, not static anymore * 65e7898ab5 Remove build-depend on rsync, meson is new enough * 1ab5e82a93 Update changelog for 260~rc1-2 release * 17a8004c53 sd-boot-efi: do not pick up hwids, they are shipped by sd-ukify * b620f379b3 Update changelog for 260~rc1-1 release * 8eb95fc404 Drop unused Lintian overrides * 82a111e7ef Update symbols file for v260~rc1 * 9cb8e0457b Disable remaining deprecated sysv interfaces * 100d97ba82 Install new files for v260~rc1 * b8e9e50f4d initramfs-tools: copy udev link files from /usr/local/lib/systemd/network too --- mkosi/mkosi.pkgenv/mkosi.conf.d/debian-ubuntu.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkosi/mkosi.pkgenv/mkosi.conf.d/debian-ubuntu.conf b/mkosi/mkosi.pkgenv/mkosi.conf.d/debian-ubuntu.conf index 7675471d5738a..173945be111fe 100644 --- a/mkosi/mkosi.pkgenv/mkosi.conf.d/debian-ubuntu.conf +++ b/mkosi/mkosi.pkgenv/mkosi.conf.d/debian-ubuntu.conf @@ -9,5 +9,5 @@ Environment= GIT_URL=https://salsa.debian.org/systemd-team/systemd.git GIT_SUBDIR=debian GIT_BRANCH=debian/master - GIT_COMMIT=89a825b80ee85e58b530cd95438988a6fb3531a3 + GIT_COMMIT=56e0eed69a4782eb8e110650d93daebcf1ece49a PKG_SUBDIR=debian From 7a0d0fe9864a72a1f19c3fdbb272b73382fabff4 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 10 Mar 2026 17:04:22 +0000 Subject: [PATCH 0176/1296] Update hwdb ninja -C build update-hwdb --- hwdb.d/20-OUI.hwdb | 197 +- hwdb.d/20-acpi-vendor.hwdb.patch | 4 +- hwdb.d/20-pci-vendor-model.hwdb | 144 +- hwdb.d/ma-large.txt | 8158 +++++++++++++++--------------- hwdb.d/ma-medium.txt | 1555 +++--- hwdb.d/ma-small.txt | 1186 ++--- hwdb.d/pci.ids | 57 +- 7 files changed, 5983 insertions(+), 5318 deletions(-) diff --git a/hwdb.d/20-OUI.hwdb b/hwdb.d/20-OUI.hwdb index b4b95ef1feb1f..39bc652bc85e6 100644 --- a/hwdb.d/20-OUI.hwdb +++ b/hwdb.d/20-OUI.hwdb @@ -14319,7 +14319,7 @@ OUI:0012BE* ID_OUI_FROM_DATABASE=Astek Corporation OUI:0012BF* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:0012C0* ID_OUI_FROM_DATABASE=HotLava Systems, Inc. @@ -20016,7 +20016,7 @@ OUI:001A29* ID_OUI_FROM_DATABASE=Johnson Outdoors Marine Electronics d/b/a Minnkota OUI:001A2A* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:001A2B* ID_OUI_FROM_DATABASE=Ayecom Technology Co., Ltd. @@ -22869,7 +22869,7 @@ OUI:001D18* ID_OUI_FROM_DATABASE=Power Innovation GmbH OUI:001D19* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:001D1A* ID_OUI_FROM_DATABASE=OvisLink S.A. @@ -27426,7 +27426,7 @@ OUI:002307* ID_OUI_FROM_DATABASE=FUTURE INNOVATION TECH CO.,LTD OUI:002308* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:002309* ID_OUI_FROM_DATABASE=Janam Technologies LLC @@ -28683,7 +28683,7 @@ OUI:0024AD* ID_OUI_FROM_DATABASE=Adolf Thies Gmbh & Co. KG OUI:0024AE* - ID_OUI_FROM_DATABASE=IDEMIA FRANCE SAS + ID_OUI_FROM_DATABASE=IDEMIA PUBLIC SECURITY FRANCE OUI:0024AF* ID_OUI_FROM_DATABASE=Dish Technologies Corp @@ -29913,7 +29913,7 @@ OUI:00264C* ID_OUI_FROM_DATABASE=Shanghai DigiVision Technology Co., Ltd. OUI:00264D* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:00264E* ID_OUI_FROM_DATABASE=r2p GmbH @@ -48579,7 +48579,7 @@ OUI:188331* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd OUI:1883BF* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:188410* ID_OUI_FROM_DATABASE=CoreTrust Inc. @@ -50847,7 +50847,7 @@ OUI:1CC586* ID_OUI_FROM_DATABASE=Absolute Acoustics OUI:1CC63C* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:1CC72D* ID_OUI_FROM_DATABASE=Shenzhen Huapu Digital CO.,Ltd @@ -51587,6 +51587,9 @@ OUI:20415A* OUI:204181* ID_OUI_FROM_DATABASE=ESYSE GmbH Embedded Systems Engineering +OUI:2041BC* + ID_OUI_FROM_DATABASE=ANY Electronics Co., Ltd + OUI:2043A8* ID_OUI_FROM_DATABASE=Espressif Inc. @@ -52115,6 +52118,12 @@ OUI:20B001* OUI:20B0F7* ID_OUI_FROM_DATABASE=Enclustra GmbH +OUI:20B37F2* + ID_OUI_FROM_DATABASE=Aina Computers ,Inc. + +OUI:20B37FB* + ID_OUI_FROM_DATABASE=B810 SPA + OUI:20B399* ID_OUI_FROM_DATABASE=Enterasys @@ -53237,6 +53246,9 @@ OUI:247E12* OUI:247E51* ID_OUI_FROM_DATABASE=zte corporation +OUI:247E7F* + ID_OUI_FROM_DATABASE=D-Fend Solutions A.D Ltd + OUI:247F20* ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS @@ -61214,6 +61226,9 @@ OUI:38A067* OUI:38A28C* ID_OUI_FROM_DATABASE=SHENZHEN RF-LINK TECHNOLOGY CO.,LTD. +OUI:38A3E0* + ID_OUI_FROM_DATABASE=1Finity Inc + OUI:38A44B* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -61343,12 +61358,51 @@ OUI:38AFD7* OUI:38B12D* ID_OUI_FROM_DATABASE=Sonotronic Nagel GmbH +OUI:38B14E0* + ID_OUI_FROM_DATABASE=Shenzhen Tongchuang Mechatronics co,LtD. + +OUI:38B14E1* + ID_OUI_FROM_DATABASE=Shenzhen Mondo Technology Co,.Ltd + OUI:38B14E2* ID_OUI_FROM_DATABASE=Marssun +OUI:38B14E3* + ID_OUI_FROM_DATABASE=QRONOZ CO., Ltd. + +OUI:38B14E4* + ID_OUI_FROM_DATABASE=Noitom Robotics Technology (Beijing) Co.,Ltd. + +OUI:38B14E5* + ID_OUI_FROM_DATABASE=Brookhaven National Laboratory + +OUI:38B14E6* + ID_OUI_FROM_DATABASE=Universal Robots A/S + +OUI:38B14E7* + ID_OUI_FROM_DATABASE=NACE + OUI:38B14E8* ID_OUI_FROM_DATABASE=QNION Co.,Ltd +OUI:38B14E9* + ID_OUI_FROM_DATABASE=DCL COMMUNICATION PTE. LTD. + +OUI:38B14EA* + ID_OUI_FROM_DATABASE=Amissiontech Co., Ltd + +OUI:38B14EB* + ID_OUI_FROM_DATABASE=Huizhou GYXX Technology Co., Ltd + +OUI:38B14EC* + ID_OUI_FROM_DATABASE=Guangzhou Sunrise Technology Co., Ltd. + +OUI:38B14ED* + ID_OUI_FROM_DATABASE=Private + +OUI:38B14EE* + ID_OUI_FROM_DATABASE=Knit Sound Company + OUI:38B19E0* ID_OUI_FROM_DATABASE=Triple Jump Medical @@ -65690,6 +65744,9 @@ OUI:44962B* OUI:44975A* ID_OUI_FROM_DATABASE=SHENZHEN FAST TECHNOLOGIES CO.,LTD +OUI:449A52* + ID_OUI_FROM_DATABASE=zte corporation + OUI:449B78* ID_OUI_FROM_DATABASE=The Now Factory @@ -65855,6 +65912,9 @@ OUI:44AEAB* OUI:44AF28* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:44B176* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:44B295* ID_OUI_FROM_DATABASE=Sichuan AI-Link Technology Co., Ltd. @@ -67238,6 +67298,9 @@ OUI:48A9D2* OUI:48AA5D* ID_OUI_FROM_DATABASE=Store Electronic Systems +OUI:48AABB* + ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS + OUI:48AD08* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -67803,7 +67866,7 @@ OUI:4C09B4* ID_OUI_FROM_DATABASE=zte corporation OUI:4C09D4* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:4C09FA* ID_OUI_FROM_DATABASE=FRONTIER SMART TECHNOLOGIES LTD @@ -70116,7 +70179,7 @@ OUI:507D02* ID_OUI_FROM_DATABASE=BIODIT OUI:507E5D* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:50804A* ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co.,Ltd. @@ -74798,6 +74861,9 @@ OUI:5CA721* OUI:5CA86A* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:5CA931* + ID_OUI_FROM_DATABASE=38436 + OUI:5CA933* ID_OUI_FROM_DATABASE=Luma Home @@ -75036,7 +75102,7 @@ OUI:5CDC49* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd OUI:5CDC96* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:5CDD70* ID_OUI_FROM_DATABASE=Hangzhou H3C Technologies Co., Limited @@ -77879,6 +77945,9 @@ OUI:64C905* OUI:64C944* ID_OUI_FROM_DATABASE=LARK Technologies, Inc +OUI:64CA80* + ID_OUI_FROM_DATABASE=Realme Chongqing Mobile Telecommunications Corp.,Ltd. + OUI:64CB5D* ID_OUI_FROM_DATABASE=SIA TeleSet @@ -78524,6 +78593,9 @@ OUI:684749* OUI:684898* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:6848B4* + ID_OUI_FROM_DATABASE=AltoBeam Inc. + OUI:684983* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -79193,6 +79265,9 @@ OUI:68C63A* OUI:68C6AC* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:68C8C0* + ID_OUI_FROM_DATABASE=GSD VIET NAM TECHNOLOGY COMPANY LIMITED + OUI:68C8EB* ID_OUI_FROM_DATABASE=Rockwell Automation @@ -79418,6 +79493,9 @@ OUI:68EE4B* OUI:68EE88* ID_OUI_FROM_DATABASE=Shenzhen TINNO Mobile Technology Corp. +OUI:68EE8F* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:68EE96* ID_OUI_FROM_DATABASE=Cisco SPVTG @@ -90960,7 +91038,7 @@ OUI:70B3D5B77* ID_OUI_FROM_DATABASE=Motec Pty Ltd OUI:70B3D5B78* - ID_OUI_FROM_DATABASE=HOERMANN GmbH + ID_OUI_FROM_DATABASE=Hörmann Warnsysteme GmbH OUI:70B3D5B79* ID_OUI_FROM_DATABASE=Dadacon GmbH @@ -95178,7 +95256,7 @@ OUI:7430AF* ID_OUI_FROM_DATABASE=Fiberhome Telecommunication Technologies Co.,LTD OUI:743170* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:743174* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -98670,7 +98748,7 @@ OUI:7C4F7D* ID_OUI_FROM_DATABASE=Sawwave OUI:7C4FB5* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:7C5049* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -102147,7 +102225,7 @@ OUI:849CA4* ID_OUI_FROM_DATABASE=Mimosa Networks OUI:849CA6* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:849D4B* ID_OUI_FROM_DATABASE=Shenzhen Boomtech Industrial Corporation @@ -102759,7 +102837,7 @@ OUI:88034C* ID_OUI_FROM_DATABASE=WEIFANG GOERTEK ELECTRONICS CO.,LTD OUI:880355* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:8803E9* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD @@ -102912,7 +102990,7 @@ OUI:882510* ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise OUI:88252C* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:882593* ID_OUI_FROM_DATABASE=TP-LINK TECHNOLOGIES CO.,LTD. @@ -106694,6 +106772,9 @@ OUI:8C1F6446D* OUI:8C1F64470* ID_OUI_FROM_DATABASE=Canfield Scientific Inc +OUI:8C1F64471* + ID_OUI_FROM_DATABASE=Apantac LLC + OUI:8C1F64472* ID_OUI_FROM_DATABASE=Surge Networks, Inc. @@ -107678,6 +107759,9 @@ OUI:8C1F64663* OUI:8C1F64664* ID_OUI_FROM_DATABASE=Thermoeye Inc +OUI:8C1F64668* + ID_OUI_FROM_DATABASE=Johnson and Johnson Medtech + OUI:8C1F6466B* ID_OUI_FROM_DATABASE=Currux Vision LLC @@ -109202,6 +109286,9 @@ OUI:8C1F64972* OUI:8C1F64973* ID_OUI_FROM_DATABASE=Dorsett Technologies Inc +OUI:8C1F64976* + ID_OUI_FROM_DATABASE=JES Electronic Systems Private Limited + OUI:8C1F64978* ID_OUI_FROM_DATABASE=Planet Innovation Products Inc. @@ -109832,6 +109919,9 @@ OUI:8C1F64AC4* OUI:8C1F64AC5* ID_OUI_FROM_DATABASE=Forever Engineering Systems Pvt. Ltd. +OUI:8C1F64AC6* + ID_OUI_FROM_DATABASE=Starts Facility Service Co.,Ltd + OUI:8C1F64AC8* ID_OUI_FROM_DATABASE=Teledatics Incorporated @@ -110615,6 +110705,9 @@ OUI:8C1F64C45* OUI:8C1F64C46* ID_OUI_FROM_DATABASE=Inex Technologies +OUI:8C1F64C48* + ID_OUI_FROM_DATABASE=AK Automation + OUI:8C1F64C4A* ID_OUI_FROM_DATABASE=SGi Technology Group Ltd. @@ -111170,6 +111263,9 @@ OUI:8C1F64D63* OUI:8C1F64D64* ID_OUI_FROM_DATABASE=Potter Electric Signal Co. LLC +OUI:8C1F64D67* + ID_OUI_FROM_DATABASE=Groundtruth Ltd + OUI:8C1F64D69* ID_OUI_FROM_DATABASE=ADiCo Corporation @@ -119199,7 +119295,7 @@ OUI:9C807D* ID_OUI_FROM_DATABASE=SYSCABLE Korea Inc. OUI:9C80DF* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:9C823F* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -119564,6 +119660,9 @@ OUI:9CCD42* OUI:9CCD82* ID_OUI_FROM_DATABASE=CHENG UEI PRECISION INDUSTRY CO.,LTD +OUI:9CCE22* + ID_OUI_FROM_DATABASE=PROMED Soest GmbH + OUI:9CCE88* ID_OUI_FROM_DATABASE=Ruijie Networks Co.,LTD @@ -124137,7 +124236,7 @@ OUI:A8D3C8* ID_OUI_FROM_DATABASE=Wachendorff Automation GmbH & CO.KG OUI:A8D3F7* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation OUI:A8D409* ID_OUI_FROM_DATABASE=USA 111 Inc @@ -128285,6 +128384,9 @@ OUI:B4B5AF* OUI:B4B5B6* ID_OUI_FROM_DATABASE=CHONGQING FUGUI ELECTRONICS CO.,LTD. +OUI:B4B650* + ID_OUI_FROM_DATABASE=Realme Chongqing Mobile Telecommunications Corp.,Ltd. + OUI:B4B676* ID_OUI_FROM_DATABASE=Intel Corporate @@ -129704,6 +129806,9 @@ OUI:B8D526* OUI:B8D56B* ID_OUI_FROM_DATABASE=Mirka Ltd. +OUI:B8D5AD* + ID_OUI_FROM_DATABASE=Nokia + OUI:B8D61A* ID_OUI_FROM_DATABASE=Espressif Inc. @@ -131108,6 +131213,9 @@ OUI:BCC342* OUI:BCC427* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:BCC436* + ID_OUI_FROM_DATABASE=Nokia + OUI:BCC493* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -131999,6 +132107,9 @@ OUI:C06911* OUI:C06B55* ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company +OUI:C06BC7* + ID_OUI_FROM_DATABASE=Gallagher Group Limited + OUI:C06C0C* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -135335,6 +135446,9 @@ OUI:C884A1* OUI:C884CF* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:C88541* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:C88550* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -137276,6 +137390,9 @@ OUI:CCC62B* OUI:CCC760* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:CCC837* + ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co.,Ltd. + OUI:CCC8D7* ID_OUI_FROM_DATABASE=CIAS Elettronica srl @@ -139733,6 +139850,9 @@ OUI:D4482D* OUI:D44867* ID_OUI_FROM_DATABASE=Silicon Laboratories +OUI:D44A85* + ID_OUI_FROM_DATABASE=Silicon Laboratories + OUI:D44B5E* ID_OUI_FROM_DATABASE=TAIYO YUDEN CO., LTD. @@ -139769,6 +139889,9 @@ OUI:D44F68* OUI:D44F80* ID_OUI_FROM_DATABASE=Kemper Digital GmbH +OUI:D45039* + ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS + OUI:D4503F* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD @@ -139952,6 +140075,9 @@ OUI:D464F7* OUI:D46624* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:D46663* + ID_OUI_FROM_DATABASE=Shenzhen Detran Technology Co.,Ltd. + OUI:D466A8* ID_OUI_FROM_DATABASE=Riedo Networks Ltd @@ -141542,6 +141668,9 @@ OUI:D88332* OUI:D88466* ID_OUI_FROM_DATABASE=Extreme Networks Headquarters +OUI:D8855E* + ID_OUI_FROM_DATABASE=zte corporation + OUI:D885AC* ID_OUI_FROM_DATABASE=Espressif Inc. @@ -143180,6 +143309,9 @@ OUI:DCB7FC* OUI:DCB808* ID_OUI_FROM_DATABASE=Extreme Networks Headquarters +OUI:DCB87D* + ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise + OUI:DCBB3D* ID_OUI_FROM_DATABASE=Extreme Networks Headquarters @@ -144764,6 +144896,9 @@ OUI:E0D31A* OUI:E0D362* ID_OUI_FROM_DATABASE=TP-Link Systems Inc. +OUI:E0D38E* + ID_OUI_FROM_DATABASE=Chipsea Technologies (Shenzhen) Crop. + OUI:E0D3B4* ID_OUI_FROM_DATABASE=Cisco Meraki @@ -144998,6 +145133,9 @@ OUI:E408E7* OUI:E40A16* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:E40A75* + ID_OUI_FROM_DATABASE=Silicon Laboratories + OUI:E40CFD* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD @@ -146213,6 +146351,9 @@ OUI:E4FAED* OUI:E4FAFD* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:E4FB1E* + ID_OUI_FROM_DATABASE=Microsoft Corporation + OUI:E4FB5D* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -148298,6 +148439,9 @@ OUI:EC6E79* OUI:EC6F0B* ID_OUI_FROM_DATABASE=FADU, Inc. +OUI:EC6FF9* + ID_OUI_FROM_DATABASE=Pioseed Technology(Chengdu)Co.,Ltd. + OUI:EC7097* ID_OUI_FROM_DATABASE=Commscope @@ -148307,6 +148451,9 @@ OUI:EC71DB* OUI:EC725B* ID_OUI_FROM_DATABASE=zte corporation +OUI:EC72F7* + ID_OUI_FROM_DATABASE=DJI BAIWANG TECHNOLOGY CO LTD + OUI:EC7359* ID_OUI_FROM_DATABASE=Shenzhen Cloudsky Technologies Co., Ltd. @@ -148817,6 +148964,9 @@ OUI:ECB541* OUI:ECB550* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:ECB5AF* + ID_OUI_FROM_DATABASE=RayService a.s. + OUI:ECB5FA* ID_OUI_FROM_DATABASE=Philips Lighting BV @@ -150839,6 +150989,9 @@ OUI:F41A9C* OUI:F41AB0* ID_OUI_FROM_DATABASE=Shenzhen Xingguodu Technology Co., Ltd. +OUI:F41AF7* + ID_OUI_FROM_DATABASE=zte corporation + OUI:F41BA1* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -152405,6 +152558,9 @@ OUI:F81A67* OUI:F81B04* ID_OUI_FROM_DATABASE=Zhong Shan City Richsound Electronic Industrial Ltd +OUI:F81B2E* + ID_OUI_FROM_DATABASE=G.Tech Technology Ltd. + OUI:F81CE5* ID_OUI_FROM_DATABASE=Telefonbau Behnke GmbH @@ -155117,6 +155273,9 @@ OUI:FCE26C* OUI:FCE33C* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:FCE421* + ID_OUI_FROM_DATABASE=zhejiang Dusun Electron Co.,Ltd + OUI:FCE4980* ID_OUI_FROM_DATABASE=NTCSOFT diff --git a/hwdb.d/20-acpi-vendor.hwdb.patch b/hwdb.d/20-acpi-vendor.hwdb.patch index 497ee1243c5ed..13c467e2f5fd4 100644 --- a/hwdb.d/20-acpi-vendor.hwdb.patch +++ b/hwdb.d/20-acpi-vendor.hwdb.patch @@ -1,5 +1,5 @@ ---- 20-acpi-vendor.hwdb.base 2026-03-03 17:42:00.020243611 +0000 -+++ 20-acpi-vendor.hwdb 2026-03-03 17:42:00.024243673 +0000 +--- 20-acpi-vendor.hwdb.base 2026-03-10 17:03:34.662556881 +0000 ++++ 20-acpi-vendor.hwdb 2026-03-10 17:03:34.666557017 +0000 @@ -3,6 +3,8 @@ # Data imported from: # https://uefi.org/uefi-pnp-export diff --git a/hwdb.d/20-pci-vendor-model.hwdb b/hwdb.d/20-pci-vendor-model.hwdb index 074eb5c4312a3..60a78dcfad4a8 100644 --- a/hwdb.d/20-pci-vendor-model.hwdb +++ b/hwdb.d/20-pci-vendor-model.hwdb @@ -893,6 +893,12 @@ pci:v00000E11d0000F150* pci:v00000E55* ID_VENDOR_FROM_DATABASE=HaSoTec GmbH +pci:v00000E8D* + ID_VENDOR_FROM_DATABASE=MediaTek Inc. (Wrong ID) + +pci:v00000E8Dd00000801* + ID_MODEL_FROM_DATABASE=MT7621 PCIe Bridge + pci:v00000EAC* ID_VENDOR_FROM_DATABASE=SHF Communication Technologies AG @@ -12075,7 +12081,7 @@ pci:v00001002d00007550* ID_MODEL_FROM_DATABASE=Navi 48 [Radeon RX 9070/9070 XT/9070 GRE] pci:v00001002d00007550sv0000148Csd00002435* - ID_MODEL_FROM_DATABASE=Navi 48 [Radeon RX 9070/9070 XT/9070 GRE] (Reaper Radeon RX 9070 XT 16GB GDDR6 (RX9070XT 16G-A)) + ID_MODEL_FROM_DATABASE=Navi 48 [Radeon RX 9070/9070 XT/9070 GRE] (Radeon RX 9070 XT 16GB) pci:v00001002d00007550sv00001DA2sd0000E490* ID_MODEL_FROM_DATABASE=Navi 48 [Radeon RX 9070/9070 XT/9070 GRE] (Navi 48 XTX [Sapphire Pulse Radeon RX 9070 XT]) @@ -23006,6 +23012,9 @@ pci:v00001077d00001022* pci:v00001077d00001080* ID_MODEL_FROM_DATABASE=ISP1080 SCSI Host Adapter +pci:v00001077d00001080sv00001077sd00000001* + ID_MODEL_FROM_DATABASE=ISP1080 SCSI Host Adapter (QLA1080) + pci:v00001077d00001216* ID_MODEL_FROM_DATABASE=ISP12160 Dual Channel Ultra3 SCSI Processor @@ -55265,6 +55274,45 @@ pci:v00001344d000051CBsv00001028sd000023A7* pci:v00001344d000051CBsv00001028sd000023A8* ID_MODEL_FROM_DATABASE=6550 ION NVMe SSD (MTFDLAL30T7THL-1BK1JABDA) +pci:v00001344d000051CC* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD + +pci:v00001344d000051CCsv00001028sd00002453* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD (MTFDLBQ122T8QHF-1BQ1JABDA) + +pci:v00001344d000051CCsv00001028sd00002483* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD (MTFDLBQ61T4QHF-1BQ1JABDA) + +pci:v00001344d000051CCsv00001028sd00002484* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD (MTFDLBQ30T7QHF-1BQ1JABDA) + +pci:v00001344d000051CCsv00001028sd00002485* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD (MTFDLBQ122T8QHF-1BQ1DFCDA) + +pci:v00001344d000051CCsv00001028sd00002486* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD (MTFDLBQ61T4QHF-1BQ1DFCDA) + +pci:v00001344d000051CCsv00001028sd00002487* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD (MTFDLBQ30T7QHF-1BQ1DFCDA) + +pci:v00001344d000051CCsv00001028sd00002489* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD (MTFDLAL122T8QHF-1BQ1JABDA) + +pci:v00001344d000051CCsv00001028sd0000248A* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD (MTFDLAL61T4QHF-1BQ1JABDA) + +pci:v00001344d000051CCsv00001028sd0000248B* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD (MTFDLAL30T7QHF-1BQ1JABDA) + +pci:v00001344d000051CCsv00001028sd0000248D* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD (MTFDLAL122T8QHF-1BQ1DFCDA) + +pci:v00001344d000051CCsv00001028sd0000248E* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD (MTFDLAL61T4QHF-1BQ1DFCDA) + +pci:v00001344d000051CCsv00001028sd0000248F* + ID_MODEL_FROM_DATABASE=6600 ION NVMe SSD (MTFDLAL30T7QHF-1BQ1DFCDA) + pci:v00001344d000051CD* ID_MODEL_FROM_DATABASE=9650 PRO NVMe SSD @@ -78440,6 +78488,9 @@ pci:v00001CC1d000033F4* pci:v00001CC1d000033F8* ID_MODEL_FROM_DATABASE=IM2P33F8 series NVMe SSD (DRAM-less) +pci:v00001CC1d0000413D* + ID_MODEL_FROM_DATABASE=SM2P41D3Q NVMe SSD (DRAM-less) + pci:v00001CC1d000041C3* ID_MODEL_FROM_DATABASE=SM2P41C3 NVMe SSD (DRAM-less) @@ -83496,7 +83547,7 @@ pci:v00001E95* ID_VENDOR_FROM_DATABASE=Solid State Storage Technology Corporation pci:v00001E95d00001000* - ID_MODEL_FROM_DATABASE=XA1-311024 NVMe SSD M.2 + ID_MODEL_FROM_DATABASE=XA1 Series NVMe SSD M.2 (DRAM-less) pci:v00001E95d00001001* ID_MODEL_FROM_DATABASE=CA6-8D512 NVMe SSD M.2 @@ -83520,7 +83571,7 @@ pci:v00001E95d00001006* ID_MODEL_FROM_DATABASE=CA8 Series NVMe SSD M.2 pci:v00001E95d00001007* - ID_MODEL_FROM_DATABASE=CL4-8D512 NVMe SSD M.2 (DRAM-less) + ID_MODEL_FROM_DATABASE=CL4 Series NVMe SSD M.2 (DRAM-less) pci:v00001E95d00001008* ID_MODEL_FROM_DATABASE=CL5-8D512 NVMe SSD M.2 (DRAM-less) @@ -85211,6 +85262,9 @@ pci:v00001F31d00004622* pci:v00001F32* ID_VENDOR_FROM_DATABASE=Wuhan YuXin Semiconductor Co., Ltd. +pci:v00001F32d0000ED55* + ID_MODEL_FROM_DATABASE=U800G NVMe SSD + pci:v00001F3F* ID_VENDOR_FROM_DATABASE=3SNIC Ltd @@ -87281,6 +87335,9 @@ pci:v000020F6d00000001* pci:v000020F9* ID_VENDOR_FROM_DATABASE=Shenzhen Silicon Dynamic Networks Co., Ltd. +pci:v00002105* + ID_VENDOR_FROM_DATABASE=Shanghai Timar Integrated Circuit Co., LTD + pci:v00002106* ID_VENDOR_FROM_DATABASE=ZCHL Technology Co., Ltd @@ -118061,6 +118118,60 @@ pci:v00008086d0000D157* pci:v00008086d0000D158* ID_MODEL_FROM_DATABASE=Core Processor Miscellaneous Registers +pci:v00008086d0000D323* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H SPI Controller + +pci:v00008086d0000D325* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H Serial IO UART Controller #0 + +pci:v00008086d0000D326* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H Serial IO UART Controller #1 + +pci:v00008086d0000D327* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H Serial IO SPI Controller #0 + +pci:v00008086d0000D330* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H Serial IO SPI Controller #1 + +pci:v00008086d0000D331* + ID_MODEL_FROM_DATABASE=Nova Lake-H Thunderbolt 5 USB Controller + +pci:v00008086d0000D333* + ID_MODEL_FROM_DATABASE=Nova Lake-H Thunderbolt 5 NHI #0 + +pci:v00008086d0000D347* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H Serial IO SPI Controller #2 + +pci:v00008086d0000D34E* + ID_MODEL_FROM_DATABASE=Nova Lake-H Thunderbolt 5 PCI Express Root Port #0 + +pci:v00008086d0000D34F* + ID_MODEL_FROM_DATABASE=Nova Lake-H Thunderbolt 5 PCI Express Root Port #1 + +pci:v00008086d0000D350* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H Serial IO I2C Controller #4 + +pci:v00008086d0000D351* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H Serial IO I2C Controller #5 + +pci:v00008086d0000D352* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H Serial IO UART Controller #2 + +pci:v00008086d0000D360* + ID_MODEL_FROM_DATABASE=Nova Lake-H Thunderbolt 5 PCI Express Root Port #2 + +pci:v00008086d0000D378* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H Serial IO I2C Controller #0 + +pci:v00008086d0000D379* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H Serial IO I2C Controller #1 + +pci:v00008086d0000D37A* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H Serial IO I2C Controller #2 + +pci:v00008086d0000D37B* + ID_MODEL_FROM_DATABASE=Nova Lake PCD-H Serial IO I2C Controller #3 + pci:v00008086d0000D431* ID_MODEL_FROM_DATABASE=Nova Lake-S Thunderbolt 5 USB Controller @@ -118094,6 +118205,33 @@ pci:v00008086d0000D744* pci:v00008086d0000D745* ID_MODEL_FROM_DATABASE=NVL-HX +pci:v00008086d0000D750* + ID_MODEL_FROM_DATABASE=NVL-P + +pci:v00008086d0000D751* + ID_MODEL_FROM_DATABASE=NVL-P + +pci:v00008086d0000D752* + ID_MODEL_FROM_DATABASE=NVL-P + +pci:v00008086d0000D753* + ID_MODEL_FROM_DATABASE=NVL-P + +pci:v00008086d0000D754* + ID_MODEL_FROM_DATABASE=NVL-P + +pci:v00008086d0000D755* + ID_MODEL_FROM_DATABASE=NVL-P + +pci:v00008086d0000D756* + ID_MODEL_FROM_DATABASE=NVL-P + +pci:v00008086d0000D757* + ID_MODEL_FROM_DATABASE=NVL-P + +pci:v00008086d0000D75F* + ID_MODEL_FROM_DATABASE=NVL-P + pci:v00008086d0000E202* ID_MODEL_FROM_DATABASE=Battlemage G21 [Intel Graphics] diff --git a/hwdb.d/ma-large.txt b/hwdb.d/ma-large.txt index e5b2503eb2ca3..fa7556a22ad96 100644 --- a/hwdb.d/ma-large.txt +++ b/hwdb.d/ma-large.txt @@ -17681,12 +17681,6 @@ DC446D (base 16) Allwinner Technology Co., Ltd Dongguan 523808 CN -A8-D3-F7 (hex) Arcadyan Technology Corporation -A8D3F7 (base 16) Arcadyan Technology Corporation - No.8, Sec.2, Guangfu Rd., - Hsinchu City Hsinchu 30071 - TW - 00-0D-92 (hex) ARIMA Communications Corp. 000D92 (base 16) ARIMA Communications Corp. 16, lane 658, Ying-Tao Road @@ -44360,17 +44354,11 @@ A401DE (base 16) SERCOMM PHILIPPINES INC Singapore 408533 SG -0C-8D-DB (hex) Cisco Meraki -0C8DDB (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 - US - -CC-03-D9 (hex) Cisco Meraki -CC03D9 (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 - US +A8-ED-71 (hex) Analogue Enterprises Limited +A8ED71 (base 16) Analogue Enterprises Limited + 2-6 Foo Ming Street, 2J Po Ming Building + Causeway Bay 999077 + HK 10-D6-57 (hex) Siemens Industrial Automation Products Ltd., Chengdu 10D657 (base 16) Siemens Industrial Automation Products Ltd., Chengdu @@ -44384,11 +44372,11 @@ CC03D9 (base 16) Cisco Meraki shenzhen guangdong 518057 CN -A8-ED-71 (hex) Analogue Enterprises Limited -A8ED71 (base 16) Analogue Enterprises Limited - 2-6 Foo Ming Street, 2J Po Ming Building - Causeway Bay 999077 - HK +48-C3-81 (hex) TP-Link Systems Inc. +48C381 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US 0C-1C-31 (hex) MERCUSYS TECHNOLOGIES CO., LTD. 0C1C31 (base 16) MERCUSYS TECHNOLOGIES CO., LTD. @@ -44402,10 +44390,16 @@ A8ED71 (base 16) Analogue Enterprises Limited Shanghai 200233 CN -48-C3-81 (hex) TP-Link Systems Inc. -48C381 (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 +0C-8D-DB (hex) Cisco Meraki +0C8DDB (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US + +CC-03-D9 (hex) Cisco Meraki +CC03D9 (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 US F0-40-EC (hex) RainX PTE. LTD. @@ -44456,18 +44450,6 @@ A8469D (base 16) Cisco Meraki San Francisco 94158 US -D4-24-DD (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -D424DD (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE - -BC-2E-C3 (hex) Shenzhen Tianruixiang Communication Equipment Co.,Ltd -BC2EC3 (base 16) Shenzhen Tianruixiang Communication Equipment Co.,Ltd - 12/F, Building B,Longhua Digital Innovation Center,Longhua District, Shenzhen,China. - Shenzhen Guangdong 518000 - CN - 38-70-F2 (hex) HUAWEI TECHNOLOGIES CO.,LTD 3870F2 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -44486,11 +44468,11 @@ C4BB03 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -98-9B-CB (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -989BCB (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE +BC-2E-C3 (hex) Shenzhen Tianruixiang Communication Equipment Co.,Ltd +BC2EC3 (base 16) Shenzhen Tianruixiang Communication Equipment Co.,Ltd + 12/F, Building B,Longhua Digital Innovation Center,Longhua District, Shenzhen,China. + Shenzhen Guangdong 518000 + CN D0-12-CB (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH D012CB (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH @@ -44498,44 +44480,44 @@ D012CB (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Berlin Berlin 10559 DE -9C-57-BC (hex) eero inc. -9C57BC (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US +D4-24-DD (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +D424DD (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE -84-70-D7 (hex) eero inc. -8470D7 (base 16) eero inc. +08-9B-F1 (hex) eero inc. +089BF1 (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -78-76-89 (hex) eero inc. -787689 (base 16) eero inc. +30-34-22 (hex) eero inc. +303422 (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -28-EC-22 (hex) eero inc. -28EC22 (base 16) eero inc. +9C-57-BC (hex) eero inc. +9C57BC (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -C8-C6-FE (hex) eero inc. -C8C6FE (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US +98-9B-CB (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +989BCB (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE -08-9B-F1 (hex) eero inc. -089BF1 (base 16) eero inc. +84-70-D7 (hex) eero inc. +8470D7 (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -30-34-22 (hex) eero inc. -303422 (base 16) eero inc. +78-76-89 (hex) eero inc. +787689 (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US @@ -44654,11 +44636,17 @@ DC7CF7 (base 16) China Mobile Group Device Co.,Ltd. Beijing 100053 CN -14-08-08 (hex) Espressif Inc. -140808 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +28-EC-22 (hex) eero inc. +28EC22 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +C8-C6-FE (hex) eero inc. +C8C6FE (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US 30-29-2B (hex) eero inc. 30292B (base 16) eero inc. @@ -44666,6 +44654,24 @@ DC7CF7 (base 16) China Mobile Group Device Co.,Ltd. San Francisco CA 94107 US +14-08-08 (hex) Espressif Inc. +140808 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +8C-E4-DB (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd +8CE4DB (base 16) Shenzhen MiaoMing Intelligent Technology Co.,Ltd + 3F, Building 2, No. 1310, Kukeng Sightseeing Road, Kukeng Community, Guanlan Street, Longhua District + Shenzhen Guangdong 518110 + CN + +30-24-50 (hex) Hangzhou Huacheng Network Technology Co.,Ltd +302450 (base 16) Hangzhou Huacheng Network Technology Co.,Ltd + 13th Floor, Building 3, No. 582, Liye Road, Changhe Street, Binjiang District, Hangzhou, China (Zhejiang) Pilot Free Trade Zone + Hangzhou 311200 + CN + 34-09-C9 (hex) Dongguan Huayin Electronic Technology Co., Ltd. 3409C9 (base 16) Dongguan Huayin Electronic Technology Co., Ltd. Room 101, No.8 Xinglong 3rd Road, Shipai Town @@ -44684,24 +44690,6 @@ BC9D37 (base 16) Telink Micro LLC Santa Clara 95054 US -8C-E4-DB (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd -8CE4DB (base 16) Shenzhen MiaoMing Intelligent Technology Co.,Ltd - 3F, Building 2, No. 1310, Kukeng Sightseeing Road, Kukeng Community, Guanlan Street, Longhua District - Shenzhen Guangdong 518110 - CN - -30-24-50 (hex) Hangzhou Huacheng Network Technology Co.,Ltd -302450 (base 16) Hangzhou Huacheng Network Technology Co.,Ltd - 13th Floor, Building 3, No. 582, Liye Road, Changhe Street, Binjiang District, Hangzhou, China (Zhejiang) Pilot Free Trade Zone - Hangzhou 311200 - CN - -94-8E-6D (hex) Nintendo Co.,Ltd -948E6D (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP - 0C-27-79 (hex) New H3C Technologies Co., Ltd 0C2779 (base 16) New H3C Technologies Co., Ltd 466 Changhe Road, Binjiang District @@ -44768,6 +44756,12 @@ B87029 (base 16) Shenzhen Ruiyuanchuangxin Technology Co.,Ltd. Shanghai Shanghai 201203 CN +94-8E-6D (hex) Nintendo Co.,Ltd +948E6D (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP + C8-08-8B (hex) Arista Networks C8088B (base 16) Arista Networks 5453 Great America Parkway @@ -44816,14 +44810,11 @@ E04934 (base 16) Calix Inc. Bac Ninh 16000 VN -CC-BA-BD (hex) TP-Link Systems Inc. -CCBABD (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 - US - -5C-E7-53 (hex) Private -5CE753 (base 16) Private +58-E6-C5 (hex) Espressif Inc. +58E6C5 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN B4-5B-86 (hex) Funshion Online Technologies Co.,Ltd B45B86 (base 16) Funshion Online Technologies Co.,Ltd @@ -44855,17 +44846,11 @@ AC017A (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD Chengdu Sichuan 611330 CN -58-E6-C5 (hex) Espressif Inc. -58E6C5 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -08-73-6F (hex) EM Microelectronic -08736F (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH +CC-BA-BD (hex) TP-Link Systems Inc. +CCBABD (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US 78-0F-81 (hex) Cisco Meraki 780F81 (base 16) Cisco Meraki @@ -44873,6 +44858,9 @@ AC017A (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD San Francisco 94158 US +5C-E7-53 (hex) Private +5CE753 (base 16) Private + B4-1F-4D (hex) Sony Interactive Entertainment Inc. B41F4D (base 16) Sony Interactive Entertainment Inc. 1-7-1 Konan @@ -44927,6 +44915,12 @@ A02B44 (base 16) WaveGo Tech LLC Cupertino CA 95014 US +08-73-6F (hex) EM Microelectronic +08736F (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH + C8-90-09 (hex) Budderfly Inc. C89009 (base 16) Budderfly Inc. 2 Trap Falls Road @@ -44939,29 +44933,17 @@ F8F7D2 (base 16) Equal Optics, LLC Newport Beach CA 92660 US -90-4C-02 (hex) vivo Mobile Communication Co., Ltd. -904C02 (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 - CN - -04-1F-B8 (hex) vivo Mobile Communication Co., Ltd. -041FB8 (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 - CN - A4-7B-52 (hex) JoulWatt Technology Co., Ltd A47B52 (base 16) JoulWatt Technology Co., Ltd 9th Floor, Chuangye Building, No.99 Huaxing Road, Xihu District, Hangzhou, China Hangzhou Zhejiang 311100 CN -44-09-C6 (hex) HUAWEI TECHNOLOGIES CO.,LTD -4409C6 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +30-C9-CC (hex) Samsung Electronics Co.,Ltd +30C9CC (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 + KR 3C-C5-C7 (hex) HUAWEI TECHNOLOGIES CO.,LTD 3CC5C7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD @@ -44969,11 +44951,11 @@ A47B52 (base 16) JoulWatt Technology Co., Ltd Dongguan 523808 CN -30-C9-CC (hex) Samsung Electronics Co.,Ltd -30C9CC (base 16) Samsung Electronics Co.,Ltd - 129, Samsung-ro, Youngtongl-Gu - Suwon Gyeonggi-Do 16677 - KR +44-09-C6 (hex) HUAWEI TECHNOLOGIES CO.,LTD +4409C6 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN 04-55-B2 (hex) Huaqin Technology Co.,Ltd 0455B2 (base 16) Huaqin Technology Co.,Ltd @@ -44981,18 +44963,6 @@ A47B52 (base 16) JoulWatt Technology Co., Ltd Shanghai 201203 CN -1C-D3-AF (hex) LG Innotek -1CD3AF (base 16) LG Innotek - 26, HANAMSANDAN 5BEON-RO - Gwangju Gwangsan-gu 506-731 - KR - -C8-26-E2 (hex) CHINA DRAGON TECHNOLOGY LIMITED -C826E2 (base 16) CHINA DRAGON TECHNOLOGY LIMITED - B4 Building,No.3 First industrial Zone,Nanpu Road,Lao Community,Xinqian Street,Baoan District,Shenzhen,City - ShenZhen 518100 - CN - FC-4C-EA (hex) Dell Inc. FC4CEA (base 16) Dell Inc. One Dell Way @@ -45005,16 +44975,28 @@ FC4CEA (base 16) Dell Inc. Santa Clara CA 95054 US +1C-D3-AF (hex) LG Innotek +1CD3AF (base 16) LG Innotek + 26, HANAMSANDAN 5BEON-RO + Gwangju Gwangsan-gu 506-731 + KR + F4-4E-B4 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. F44EB4 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China Nanning Guangxi 530007 CN -F4-AB-5C (hex) Quectel Wireless Solutions Co.,Ltd. -F4AB5C (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 +90-4C-02 (hex) vivo Mobile Communication Co., Ltd. +904C02 (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN + +04-1F-B8 (hex) vivo Mobile Communication Co., Ltd. +041FB8 (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 CN 80-AE-3C (hex) Taicang T&W Electronics @@ -45029,30 +45011,6 @@ F06FCE (base 16) Ruckus Wireless Sunnyvale CA 94089 US -A0-1B-9E (hex) Samsung Electronics Co.,Ltd -A01B9E (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -D8-71-54 (hex) Samsung Electronics Co.,Ltd -D87154 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -78-33-C6 (hex) Samsung Electronics Co.,Ltd -7833C6 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -2C-C1-F4 (hex) Nokia Solutions and Networks India Private Limited -2CC1F4 (base 16) Nokia Solutions and Networks India Private Limited - Plot 45, Fathima Nagar, Nemilicherry - Chennai Tamilnadu 600044 - IN - 34-FD-70 (hex) Intel Corporate 34FD70 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 @@ -45071,16 +45029,40 @@ B07C8E (base 16) Brother Industries, LTD. NAGOYA 4678561 JP -F0-7A-55 (hex) zte corporation -F07A55 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +A0-1B-D6 (hex) Nautitech Mining Systems Pty. Ltd. +A01BD6 (base 16) Nautitech Mining Systems Pty. Ltd. + 3/9 Packard AvenueCastle Hill + CASTLE HILL 2154 + AU + +90-F0-05 (hex) Xi'an Molead Technology Co., Ltd +90F005 (base 16) Xi'an Molead Technology Co., Ltd + No.34 Fenghui South Road,High-Tech Zone + Xian Shaanxi 710065 CN -D4-61-95 (hex) zte corporation -D46195 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +A0-1B-9E (hex) Samsung Electronics Co.,Ltd +A01B9E (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +D8-71-54 (hex) Samsung Electronics Co.,Ltd +D87154 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +78-33-C6 (hex) Samsung Electronics Co.,Ltd +7833C6 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +C8-26-E2 (hex) CHINA DRAGON TECHNOLOGY LIMITED +C826E2 (base 16) CHINA DRAGON TECHNOLOGY LIMITED + B4 Building,No.3 First industrial Zone,Nanpu Road,Lao Community,Xinqian Street,Baoan District,Shenzhen,City + ShenZhen 518100 CN E0-D5-5D (hex) Intel Corporate @@ -45101,23 +45083,29 @@ A08527 (base 16) Intel Corporate Kulim Kedah 09000 MY -90-F0-05 (hex) Xi'an Molead Technology Co., Ltd -90F005 (base 16) Xi'an Molead Technology Co., Ltd - No.34 Fenghui South Road,High-Tech Zone - Xian Shaanxi 710065 +F0-7A-55 (hex) zte corporation +F07A55 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -A0-1B-D6 (hex) Nautitech Mining Systems Pty. Ltd. -A01BD6 (base 16) Nautitech Mining Systems Pty. Ltd. - 3/9 Packard AvenueCastle Hill - CASTLE HILL 2154 - AU +D4-61-95 (hex) zte corporation +D46195 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -60-73-C8 (hex) Voyetra Turtle Beach, Inc. -6073C8 (base 16) Voyetra Turtle Beach, Inc. - 15822 Bernardo Center Drive, Suite 105 - San Diego CA 92127 - US +F4-AB-5C (hex) Quectel Wireless Solutions Co.,Ltd. +F4AB5C (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 + CN + +2C-C1-F4 (hex) Nokia Solutions and Networks India Private Limited +2CC1F4 (base 16) Nokia Solutions and Networks India Private Limited + Plot 45, Fathima Nagar, Nemilicherry + Chennai Tamilnadu 600044 + IN 5C-E1-A4 (hex) Pleneo 5CE1A4 (base 16) Pleneo @@ -45131,6 +45119,12 @@ FCE498 (base 16) IEEE Registration Authority Piscataway NJ 08554 US +60-73-C8 (hex) Voyetra Turtle Beach, Inc. +6073C8 (base 16) Voyetra Turtle Beach, Inc. + 15822 Bernardo Center Drive, Suite 105 + San Diego CA 92127 + US + 24-B5-B9 (hex) Motorola Mobility LLC, a Lenovo Company 24B5B9 (base 16) Motorola Mobility LLC, a Lenovo Company 222 West Merchandise Mart Plaza @@ -45173,12 +45167,6 @@ ECBB78 (base 16) Cisco Systems, Inc Rueil Malmaison Cedex hauts de seine 92848 FR -54-9B-24 (hex) Mellanox Technologies, Inc. -549B24 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US - 50-62-45 (hex) Annapurna labs 506245 (base 16) Annapurna labs Matam Scientific Industries Center, Building 8.2 @@ -45218,6 +45206,18 @@ D4532A (base 16) Beijing Xiaomi Mobile Software Co., Ltd Beijing Beijing 100085 CN +F0-57-8D (hex) JetHome LLC +F0578D (base 16) JetHome LLC + Serebristy boulevard, 21, letter A, office 79-N + St. Petersburg 197341 + RU + +78-C8-84 (hex) Huawei Device Co., Ltd. +78C884 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + 98-7E-B5 (hex) Huawei Device Co., Ltd. 987EB5 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone @@ -45230,11 +45230,11 @@ D4532A (base 16) Beijing Xiaomi Mobile Software Co., Ltd Dongguan Guangdong 523808 CN -78-C8-84 (hex) Huawei Device Co., Ltd. -78C884 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +54-9B-24 (hex) Mellanox Technologies, Inc. +549B24 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US 18-95-78 (hex) DENSO CORPORATION 189578 (base 16) DENSO CORPORATION @@ -45272,6 +45272,12 @@ F8D554 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. beijing beijing 100000 CN +00-50-CA (hex) Zhone Technologies, Inc. +0050CA (base 16) Zhone Technologies, Inc. + 680 CENTRAL AVENUE - STE. #301 + DOVER NH 03820 + US + 4C-82-0C (hex) Apple, Inc. 4C820C (base 16) Apple, Inc. 1 Infinite Loop @@ -45290,22 +45296,34 @@ F8D554 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. Cupertino CA 95014 US +F4-06-3C (hex) Texas Instruments +F4063C (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +E0-DE-F2 (hex) Texas Instruments +E0DEF2 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + 74-95-33 (hex) Westala Technologies Inc. 749533 (base 16) Westala Technologies Inc. 3333 Preston Road STE 300 FRISCO TX 75034 US -F0-57-8D (hex) JetHome LLC -F0578D (base 16) JetHome LLC - Serebristy boulevard, 21, letter A, office 79-N - St. Petersburg 197341 - RU +44-8D-D5 (hex) Cisco Systems, Inc +448DD5 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -00-50-CA (hex) Zhone Technologies, Inc. -0050CA (base 16) Zhone Technologies, Inc. - 680 CENTRAL AVENUE - STE. #301 - DOVER NH 03820 +8C-91-A4 (hex) Apple, Inc. +8C91A4 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US 94-97-4F (hex) Liteon Technology Corporation @@ -45326,17 +45344,11 @@ F0578D (base 16) JetHome LLC Irvine CA 92618 US -E0-DE-F2 (hex) Texas Instruments -E0DEF2 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US - -44-8D-D5 (hex) Cisco Systems, Inc -448DD5 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +F4-D7-E4 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. +F4D7E4 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. + No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. + Chongqing China 401120 + CN 20-0A-87 (hex) Guangzhou On-Bright Electronics Co., Ltd. 200A87 (base 16) Guangzhou On-Bright Electronics Co., Ltd. @@ -45344,6 +45356,12 @@ E0DEF2 (base 16) Texas Instruments Guangzhou Guangdong 510520 CN +BC-34-D6 (hex) Extreme Networks Headquarters +BC34D6 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US + E0-8C-FE (hex) Espressif Inc. E08CFE (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area @@ -45356,24 +45374,6 @@ E08CFE (base 16) Espressif Inc. KYOTO KYOTO 601-8501 JP -F4-06-3C (hex) Texas Instruments -F4063C (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US - -8C-91-A4 (hex) Apple, Inc. -8C91A4 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -18-16-28 (hex) SharkNinja Operating LLC -181628 (base 16) SharkNinja Operating LLC - 89 A Street, Suite 100 02494 Needham - Needham MA 02494 - US - 4C-C5-D9 (hex) Dell Inc. 4CC5D9 (base 16) Dell Inc. One Dell Way @@ -45392,18 +45392,6 @@ F4063C (base 16) Texas Instruments Beijing Beijing 100085 CN -BC-34-D6 (hex) Extreme Networks Headquarters -BC34D6 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US - -CC-E4-D1 (hex) Arista Networks -CCE4D1 (base 16) Arista Networks - 5453 Great America Parkway - Santa Clara CA 95054 - US - E8-7E-EF (hex) Xiaomi Communications Co Ltd E87EEF (base 16) Xiaomi Communications Co Ltd #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road @@ -45416,11 +45404,17 @@ E87EEF (base 16) Xiaomi Communications Co Ltd Beijing Haidian District 100085 CN -F4-D7-E4 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. -F4D7E4 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. - No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. - Chongqing China 401120 - CN +18-16-28 (hex) SharkNinja Operating LLC +181628 (base 16) SharkNinja Operating LLC + 89 A Street, Suite 100 02494 Needham + Needham MA 02494 + US + +CC-E4-D1 (hex) Arista Networks +CCE4D1 (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 + US 0C-9A-E6 (hex) SZ DJI TECHNOLOGY CO.,LTD 0C9AE6 (base 16) SZ DJI TECHNOLOGY CO.,LTD @@ -45434,6 +45428,12 @@ F4D7E4 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. Shanghai Shanghai 201203 CN +C0-40-8D (hex) ALPSALPINE CO,.LTD +C0408D (base 16) ALPSALPINE CO,.LTD + nishida 6-1 + Kakuda-City Miyagi-Pref 981-1595 + JP + BC-09-B9 (hex) Hui Zhou Gaoshengda Technology Co.,LTD BC09B9 (base 16) Hui Zhou Gaoshengda Technology Co.,LTD No.2,Jin-da Road,Huinan Industrial Park @@ -45464,17 +45464,23 @@ FC8B1F (base 16) GUTOR Electronic Dongguan 523808 CN +24-FA-D4 (hex) ShenZhen More Star Technology Co.,LTD +24FAD4 (base 16) ShenZhen More Star Technology Co.,LTD + Room 301, 3F, Building 25, Keyuan West, No. 5, Kezhi West Road, Yuehai street + Shenzhen GuangDong 518000 + CN + F4-B0-FF (hex) Shanghai Baud Data Communication Co.,Ltd. F4B0FF (base 16) Shanghai Baud Data Communication Co.,Ltd. NO.123 JULI RD PUDONG ZHANGJIANG HIGH-TECH PARK SHANGHAI 201203 CN -C0-40-8D (hex) ALPSALPINE CO,.LTD -C0408D (base 16) ALPSALPINE CO,.LTD - nishida 6-1 - Kakuda-City Miyagi-Pref 981-1595 - JP +10-2B-1C (hex) Motorola Mobility LLC, a Lenovo Company +102B1C (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US 04-C8-B0 (hex) Google, Inc. 04C8B0 (base 16) Google, Inc. @@ -45488,18 +45494,24 @@ D86DD0 (base 16) InnoCare Optoelectronics Tainan 74144 TW +EC-46-84 (hex) Microsoft Corporation +EC4684 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US + +D4-A7-EA (hex) Solar76 +D4A7EA (base 16) Solar76 + 400 Maple Street + Commerce TX 75428 + US + C4-D4-D0 (hex) SHENZHEN TECNO TECHNOLOGY C4D4D0 (base 16) SHENZHEN TECNO TECHNOLOGY 101,Building 24,Waijing Industrial Park,Fumin Community,Fucheng Street,Longhua District,Shenzhen City,P.R.China Shenzhen guangdong 518000 CN -24-FA-D4 (hex) ShenZhen More Star Technology Co.,LTD -24FAD4 (base 16) ShenZhen More Star Technology Co.,LTD - Room 301, 3F, Building 25, Keyuan West, No. 5, Kezhi West Road, Yuehai street - Shenzhen GuangDong 518000 - CN - 64-F2-FB (hex) Hangzhou Ezviz Software Co.,Ltd. 64F2FB (base 16) Hangzhou Ezviz Software Co.,Ltd. 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District @@ -45512,11 +45524,11 @@ C4D4D0 (base 16) SHENZHEN TECNO TECHNOLOGY Hangzhou Zhejiang 310051 CN -10-2B-1C (hex) Motorola Mobility LLC, a Lenovo Company -102B1C (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 - US +68-E6-D4 (hex) FURUNO SYSTEMS CO.,LTD. +68E6D4 (base 16) FURUNO SYSTEMS CO.,LTD. + 6F, JEI Ryogoku Building, 3-25-5 Ryogoku + Sumida-ku Tokyo 130-0026 + JP DC-D8-3B (hex) Cisco Systems, Inc DCD83B (base 16) Cisco Systems, Inc @@ -45524,54 +45536,30 @@ DCD83B (base 16) Cisco Systems, Inc San Jose CA 94568 US +C8-6C-9A (hex) SNUC System +C86C9A (base 16) SNUC System + 495 Round Rock West Drive + Round Rock TX 78681 + US + 90-FE-E2 (hex) ISIF 90FEE2 (base 16) ISIF Lasnamäe linnaosa, Sepapaja tn 6 Tallinn Harju maakond 15551 EE -EC-46-84 (hex) Microsoft Corporation -EC4684 (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 - US - -D4-A7-EA (hex) Solar76 -D4A7EA (base 16) Solar76 - 400 Maple Street - Commerce TX 75428 - US - 6C-43-29 (hex) COSMIQ EDUSNAP PRIVATE LIMITED 6C4329 (base 16) COSMIQ EDUSNAP PRIVATE LIMITED C-185, 2nd Floor, Naraina Industrial Area, Phase 1 NEW DELHI DELHI 110028 IN -68-E6-D4 (hex) FURUNO SYSTEMS CO.,LTD. -68E6D4 (base 16) FURUNO SYSTEMS CO.,LTD. - 6F, JEI Ryogoku Building, 3-25-5 Ryogoku - Sumida-ku Tokyo 130-0026 - JP - -C8-6C-9A (hex) SNUC System -C86C9A (base 16) SNUC System - 495 Round Rock West Drive - Round Rock TX 78681 - US - 00-0E-72 (hex) Sesami Technologies Srl 000E72 (base 16) Sesami Technologies Srl via Statale 17 Bollengo Torino 10012 IT -44-39-AA (hex) G.Tech Technology Ltd. -4439AA (base 16) G.Tech Technology Ltd. - No.8,Jinyuan 1st Road,Tangjiawan Town, High-tech Zone - Zhuhai Guangdong 519085 - CN - 58-27-45 (hex) Angelbird Technologies GmbH 582745 (base 16) Angelbird Technologies GmbH Steinebach 18 @@ -45584,40 +45572,34 @@ C86C9A (base 16) SNUC System Suzhou 215021 CN -30-F6-5D (hex) Hewlett Packard Enterprise -30F65D (base 16) Hewlett Packard Enterprise - 6280 America Center Dr - San Jose CA 95002 - US - F0-3E-05 (hex) Murata Manufacturing Co., Ltd. F03E05 (base 16) Murata Manufacturing Co., Ltd. 1-10-1, Higashikotari Nagaokakyo-shi Kyoto 617-8555 JP -64-FA-2B (hex) Sagemcom Broadband SAS -64FA2B (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - B0-A6-04 (hex) Espressif Inc. B0A604 (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area Shanghai Shanghai 201203 CN +44-39-AA (hex) G.Tech Technology Ltd. +4439AA (base 16) G.Tech Technology Ltd. + No.8,Jinyuan 1st Road,Tangjiawan Town, High-tech Zone + Zhuhai Guangdong 519085 + CN + C0-2E-DF (hex) AltoBeam Inc. C02EDF (base 16) AltoBeam Inc. B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian Beijing Beijing 100083 CN -70-3E-76 (hex) Arcadyan Corporation -703E76 (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 +8C-3B-4A (hex) Universal Global Scientific Industrial., Ltd +8C3B4A (base 16) Universal Global Scientific Industrial., Ltd + 141, Lane 351, Taiping Road, Sec.1,Tsao Tuen + Nan-Tou Taiwan 54261 TW F4-5C-42 (hex) Huawei Device Co., Ltd. @@ -45644,18 +45626,6 @@ E4AEE4 (base 16) Tuya Smart Inc. Mannheim 68167 DE -80-48-63 (hex) Electralsys Networks -804863 (base 16) Electralsys Networks - 45 Bharat Nagar, New Friends Colony - NEW DELHI DELHI 110025 - IN - -7C-CF-4E (hex) FINE TRIUMPH TECHNOLOGY CORP.,LTD. -7CCF4E (base 16) FINE TRIUMPH TECHNOLOGY CORP.,LTD. - kihitech Industrial Park,DongChen Town, RuGao,jiangsu - RuGao,jiangsu 226571 - CN - 70-F3-95 (hex) Universal Global Scientific Industrial., Ltd 70F395 (base 16) Universal Global Scientific Industrial., Ltd 141, LANE 351,SEC.1, TAIPING RD. @@ -45674,23 +45644,23 @@ E02A82 (base 16) Universal Global Scientific Industrial., Ltd Nan-Tou Taiwan 54261 TW -8C-3B-4A (hex) Universal Global Scientific Industrial., Ltd -8C3B4A (base 16) Universal Global Scientific Industrial., Ltd - 141, Lane 351, Taiping Road, Sec.1,Tsao Tuen - Nan-Tou Taiwan 54261 - TW - -8C-19-52 (hex) Amazon Technologies Inc. -8C1952 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 +30-F6-5D (hex) Hewlett Packard Enterprise +30F65D (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 US -04-72-EF (hex) Apple, Inc. -0472EF (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +64-FA-2B (hex) Sagemcom Broadband SAS +64FA2B (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR + +70-3E-76 (hex) Arcadyan Corporation +703E76 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW D4-FF-1A (hex) Apple, Inc. D4FF1A (base 16) Apple, Inc. @@ -45716,6 +45686,12 @@ F4B599 (base 16) Apple, Inc. Cupertino CA 95014 US +24-6D-10 (hex) Apple, Inc. +246D10 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + A0-F7-C3 (hex) Ficosa Automotive SLU A0F7C3 (base 16) Ficosa Automotive SLU Pol. Ind. Can Mitjans,Vial San Jordi s/n @@ -45728,6 +45704,18 @@ B8FBB3 (base 16) TP-Link Systems Inc. Irvine CA 92618 US +20-46-3A (hex) Apple, Inc. +20463A (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +04-72-EF (hex) Apple, Inc. +0472EF (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + 50-93-CE (hex) HUAWEI TECHNOLOGIES CO.,LTD 5093CE (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -45740,23 +45728,23 @@ B8FBB3 (base 16) TP-Link Systems Inc. Dongguan 523808 CN -24-6D-10 (hex) Apple, Inc. -246D10 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +80-48-63 (hex) Electralsys Networks +804863 (base 16) Electralsys Networks + 45 Bharat Nagar, New Friends Colony + NEW DELHI DELHI 110025 + IN -20-46-3A (hex) Apple, Inc. -20463A (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +7C-CF-4E (hex) FINE TRIUMPH TECHNOLOGY CORP.,LTD. +7CCF4E (base 16) FINE TRIUMPH TECHNOLOGY CORP.,LTD. + kihitech Industrial Park,DongChen Town, RuGao,jiangsu + RuGao,jiangsu 226571 + CN -10-E8-A7 (hex) WNC Corporation -10E8A7 (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW +8C-19-52 (hex) Amazon Technologies Inc. +8C1952 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US AC-91-9B (hex) WNC Corporation AC919B (base 16) WNC Corporation @@ -45782,24 +45770,12 @@ DC4BA1 (base 16) WNC Corporation Hsin-Chu R.O.C. 308 TW -B0-00-73 (hex) WNC Corporation -B00073 (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW - -98-49-14 (hex) WNC Corporation -984914 (base 16) WNC Corporation +74-6F-F7 (hex) WNC Corporation +746FF7 (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park Hsin-Chu R.O.C. 308 TW -30-41-DB (hex) vivo Mobile Communication Co., Ltd. -3041DB (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 - CN - A8-54-B2 (hex) WNC Corporation A854B2 (base 16) WNC Corporation 20 Park Avenue II, Hsin Science Park, Hsinchu 308, Taiwan @@ -45812,23 +45788,29 @@ A854B2 (base 16) WNC Corporation Hsinchu 308 TW -74-6F-F7 (hex) WNC Corporation -746FF7 (base 16) WNC Corporation +B0-00-73 (hex) WNC Corporation +B00073 (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park Hsin-Chu R.O.C. 308 TW -2C-65-8D (hex) Cisco Systems, Inc -2C658D (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +98-49-14 (hex) WNC Corporation +984914 (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW -94-AA-07 (hex) Nokia -94AA07 (base 16) Nokia - 600 March Road - Kanata Ontario K2K 2E6 - CA +10-E8-A7 (hex) WNC Corporation +10E8A7 (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + +30-41-DB (hex) vivo Mobile Communication Co., Ltd. +3041DB (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN 68-A3-4F (hex) Nokia 68A34F (base 16) Nokia @@ -45842,17 +45824,17 @@ A854B2 (base 16) WNC Corporation Nanzi Dist. Kaohsiung 811643 TW -20-CB-CC (hex) GridVisibility, inc. -20CBCC (base 16) GridVisibility, inc. - 1502 Meeker Dr - Longmont CO 80504 - US +EC-79-C0 (hex) zte corporation +EC79C0 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -F4-9A-B1 (hex) Hewlett Packard Enterprise -F49AB1 (base 16) Hewlett Packard Enterprise - 6280 America Center Dr - San Jose CA 95002 - US +6C-11-BA (hex) zte corporation +6C11BA (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN FC-9E-53 (hex) Intel Corporate FC9E53 (base 16) Intel Corporate @@ -45866,30 +45848,6 @@ D494A9 (base 16) Intel Corporate Kulim Kedah 09000 MY -84-08-3A (hex) Intel Corporate -84083A (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -EC-79-C0 (hex) zte corporation -EC79C0 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN - -6C-11-BA (hex) zte corporation -6C11BA (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN - -10-16-B1 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -1016B1 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN - E4-65-66 (hex) Maple IoT Solutions LLC E46566 (base 16) Maple IoT Solutions LLC N8408 MUIRFIELD WAY @@ -45902,47 +45860,41 @@ E46566 (base 16) Maple IoT Solutions LLC Palo Alto CA 94301 US -40-08-77 (hex) Xiaomi Communications Co Ltd -400877 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN - -08-B3-39 (hex) Xiaomi Communications Co Ltd -08B339 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN +2C-65-8D (hex) Cisco Systems, Inc +2C658D (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -B8-53-84 (hex) Xiaomi Communications Co Ltd -B85384 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN +94-AA-07 (hex) Nokia +94AA07 (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA -CC-2D-D2 (hex) Ruckus Wireless -CC2DD2 (base 16) Ruckus Wireless - 350 West Java Drive - Sunnyvale CA 94089 - US +84-08-3A (hex) Intel Corporate +84083A (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -B0-D7-DE (hex) Hangzhou Linovision Co., Ltd. -B0D7DE (base 16) Hangzhou Linovision Co., Ltd. - No. 181 Wuchang Road - Hangzhou Zhejiang 310023 +10-16-B1 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +1016B1 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -98-42-AB (hex) GN Hearing A/S -9842AB (base 16) GN Hearing A/S - Lautrupbjerg 7 - Ballerup 2750 - DK +20-CB-CC (hex) GridVisibility, inc. +20CBCC (base 16) GridVisibility, inc. + 1502 Meeker Dr + Longmont CO 80504 + US -5C-33-B1 (hex) EM Microelectronic -5C33B1 (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH +F4-9A-B1 (hex) Hewlett Packard Enterprise +F49AB1 (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US E0-FA-5B (hex) Arista Networks E0FA5B (base 16) Arista Networks @@ -45956,10 +45908,10 @@ E0FA5B (base 16) Arista Networks Piscataway NJ 08554 US -9C-CD-42 (hex) Dongguan Liesheng Electronic Co., Ltd. -9CCD42 (base 16) Dongguan Liesheng Electronic Co., Ltd. - F5, Building B, North Block, Gaosheng Tech Park, No. 84 Zhongli Road, Nancheng District, Dongguan Ci - dongguan guangdong 523000 +40-08-77 (hex) Xiaomi Communications Co Ltd +400877 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 CN 7C-D4-A8 (hex) Sagemcom Broadband SAS @@ -45974,18 +45926,78 @@ E0FA5B (base 16) Arista Networks SHENZHEN Guangdong Province 518052 CN +CC-2D-D2 (hex) Ruckus Wireless +CC2DD2 (base 16) Ruckus Wireless + 350 West Java Drive + Sunnyvale CA 94089 + US + +B0-D7-DE (hex) Hangzhou Linovision Co., Ltd. +B0D7DE (base 16) Hangzhou Linovision Co., Ltd. + No. 181 Wuchang Road + Hangzhou Zhejiang 310023 + CN + 18-AC-C2 (hex) TCL King Electrical Appliances(Huizhou)Co.,Ltd 18ACC2 (base 16) TCL King Electrical Appliances(Huizhou)Co.,Ltd B Area, 10th floor, TCL multimedia Building, TCL International E City, #1001 Zhonshanyuan road,Shenzhen guangdong China 518058 CN +08-B3-39 (hex) Xiaomi Communications Co Ltd +08B339 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + +B8-53-84 (hex) Xiaomi Communications Co Ltd +B85384 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + 00-BC-99 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. 00BC99 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. No.555 Qianmo Road Hangzhou Zhejiang 310052 CN +5C-33-B1 (hex) EM Microelectronic +5C33B1 (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH + +00-15-EA (hex) Hensoldt South Africa (Pty) Ltd +0015EA (base 16) Hensoldt South Africa (Pty) Ltd + 64/74 White Road + Cape Town Western Province 7945 + ZA + +98-42-AB (hex) GN Hearing A/S +9842AB (base 16) GN Hearing A/S + Lautrupbjerg 7 + Ballerup 2750 + DK + +9C-CD-42 (hex) Dongguan Liesheng Electronic Co., Ltd. +9CCD42 (base 16) Dongguan Liesheng Electronic Co., Ltd. + F5, Building B, North Block, Gaosheng Tech Park, No. 84 Zhongli Road, Nancheng District, Dongguan Ci + dongguan guangdong 523000 + CN + +70-4B-CA (hex) Espressif Inc. +704BCA (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +8C-FD-49 (hex) Espressif Inc. +8CFD49 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + 4C-3C-8F (hex) Shenzhen Jingxun Technology Co., Ltd. 4C3C8F (base 16) Shenzhen Jingxun Technology Co., Ltd. 3/F, A5 Building, Zhiyuan Community, No. 1001, Xueyuan Road, Nanshan District @@ -46004,11 +46016,17 @@ BCD767 (base 16) BAE Systems Sunnyvale CA 94085 US -00-15-EA (hex) Hensoldt South Africa (Pty) Ltd -0015EA (base 16) Hensoldt South Africa (Pty) Ltd - 64/74 White Road - Cape Town Western Province 7945 - ZA +C4-3D-C7 (hex) NETGEAR +C43DC7 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US + +4C-60-DE (hex) NETGEAR +4C60DE (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US F8-10-37 (hex) ENTOUCH Controls F81037 (base 16) ENTOUCH Controls @@ -46022,16 +46040,16 @@ F81037 (base 16) ENTOUCH Controls Piscataway NJ 08554 US -70-4B-CA (hex) Espressif Inc. -704BCA (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +80-8F-97 (hex) Xiaomi Communications Co Ltd +808F97 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 CN -8C-FD-49 (hex) Espressif Inc. -8CFD49 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +4C-E2-0F (hex) Xiaomi Communications Co Ltd +4CE20F (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 CN 10-0C-6B (hex) NETGEAR @@ -46052,12 +46070,30 @@ F81037 (base 16) ENTOUCH Controls San Jose CA 95134 US +30-91-8F (hex) Vantiva Technologies Belgium +30918F (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE + +E0-B9-E5 (hex) Vantiva Technologies Belgium +E0B9E5 (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE + 44-FB-76 (hex) vivo Mobile Communication Co., Ltd. 44FB76 (base 16) vivo Mobile Communication Co., Ltd. No.1, vivo Road, Chang'an Dongguan Guangdong 523860 CN +A0-55-2E (hex) zte corporation +A0552E (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + B0-7F-B9 (hex) NETGEAR B07FB9 (base 16) NETGEAR 3553 N. First Street @@ -46070,17 +46106,11 @@ B07FB9 (base 16) NETGEAR San Jose CA 95134 US -C4-3D-C7 (hex) NETGEAR -C43DC7 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US - -4C-60-DE (hex) NETGEAR -4C60DE (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US +9C-97-26 (hex) Vantiva Technologies Belgium +9C9726 (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE 08-BD-43 (hex) NETGEAR 08BD43 (base 16) NETGEAR @@ -46112,42 +46142,12 @@ C05724 (base 16) Honor Device Co., Ltd. Milan IT20126 IT -80-8F-97 (hex) Xiaomi Communications Co Ltd -808F97 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN - -4C-E2-0F (hex) Xiaomi Communications Co Ltd -4CE20F (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN - C4-CD-50 (hex) Shenzhen C-Data Technology Co., Ltd. C4CD50 (base 16) Shenzhen C-Data Technology Co., Ltd. #201, Building A4, Nanshan Zhiyuan, No.1001, Xueyuan Avenue, Changyuan Community,Taoyuan,Nanshan Shenzhen Guangdong 518055 CN -9C-97-26 (hex) Vantiva Technologies Belgium -9C9726 (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE - -30-91-8F (hex) Vantiva Technologies Belgium -30918F (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE - -E0-B9-E5 (hex) Vantiva Technologies Belgium -E0B9E5 (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE - AC-8A-C7 (hex) HUAWEI TECHNOLOGIES CO.,LTD AC8AC7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -46160,6 +46160,12 @@ AC8AC7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN +DC-04-5A (hex) Nanjing Qinheng Microelectronics Co., Ltd. +DC045A (base 16) Nanjing Qinheng Microelectronics Co., Ltd. + No.18, Ningshuang Road + Nanjing Jiangsu 210012 + CN + 24-DB-94 (hex) Juniper Networks 24DB94 (base 16) Juniper Networks 1133 Innovation Way @@ -46172,11 +46178,11 @@ AC8AC7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Sunnyvale CA 94089 US -A0-55-2E (hex) zte corporation -A0552E (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN +8C-77-79 (hex) Arcadyan Corporation +8C7779 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW 54-AE-BC (hex) CHINA DRAGON TECHNOLOGY LIMITED 54AEBC (base 16) CHINA DRAGON TECHNOLOGY LIMITED @@ -46214,17 +46220,17 @@ C8806D (base 16) Apple, Inc. Cupertino CA 95014 US -DC-04-5A (hex) Nanjing Qinheng Microelectronics Co., Ltd. -DC045A (base 16) Nanjing Qinheng Microelectronics Co., Ltd. - No.18, Ningshuang Road - Nanjing Jiangsu 210012 - CN +98-CF-7D (hex) Apple, Inc. +98CF7D (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -8C-77-79 (hex) Arcadyan Corporation -8C7779 (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW +74-29-59 (hex) Apple, Inc. +742959 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US 04-C9-DE (hex) Qingdao HaierTechnology Co.,Ltd 04C9DE (base 16) Qingdao HaierTechnology Co.,Ltd @@ -46238,17 +46244,17 @@ DC045A (base 16) Nanjing Qinheng Microelectronics Co., Ltd. Redmond WA 98052 US -98-CF-7D (hex) Apple, Inc. -98CF7D (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +80-B8-2A (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. +80B82A (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. + No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. + Chongqing China 401120 + CN -74-29-59 (hex) Apple, Inc. -742959 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +44-25-38 (hex) WNC Corporation +442538 (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW E8-6E-3E (hex) Sichuan Tianyi Comheart Telecom Co.,LTD E86E3E (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD @@ -46262,12 +46268,6 @@ D8D7F3 (base 16) New H3C Technologies Co., Ltd Hangzhou Zhejiang 310052 CN -44-25-38 (hex) WNC Corporation -442538 (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW - 1C-CF-82 (hex) Palo Alto Networks 1CCF82 (base 16) Palo Alto Networks 3000 Tannery Way @@ -46280,18 +46280,42 @@ B0435D (base 16) MechoShade Vista CA 92081 US -80-B8-2A (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. -80B82A (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. - No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. - Chongqing China 401120 +9C-04-B6 (hex) Quectel Wireless Solutions Co.,Ltd. +9C04B6 (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 CN -18-5C-A1 (hex) Jiangxi Risound Electronics Co.,LTD -185CA1 (base 16) Jiangxi Risound Electronics Co.,LTD - No 271,innovation Avenue, Jinggangshan economic and Technological Development Zone - Ji'an Jiangxi 343100 +34-55-E5 (hex) SJIT Co., Ltd. +3455E5 (base 16) SJIT Co., Ltd. + 54-33 Dongtanhana 1-gil + Hwaseong-si Gyeonggi-do 18423 + KR + +BC-AA-82 (hex) Fiberhome Telecommunication Technologies Co.,LTD +BCAA82 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 CN +C8-CC-21 (hex) eero inc. +C8CC21 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +B8-F4-A4 (hex) Google, Inc. +B8F4A4 (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US + +E0-1A-DF (hex) Google, Inc. +E01ADF (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US + 3C-2A-B3 (hex) Telesystem communications Pte Ltd 3C2AB3 (base 16) Telesystem communications Pte Ltd 3F, No.7 Xing Hua Rd., @@ -46310,52 +46334,34 @@ F85B1B (base 16) Espressif Inc. Shanghai Shanghai 201203 CN -9C-04-B6 (hex) Quectel Wireless Solutions Co.,Ltd. -9C04B6 (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 - CN - -34-55-E5 (hex) SJIT Co., Ltd. -3455E5 (base 16) SJIT Co., Ltd. - 54-33 Dongtanhana 1-gil - Hwaseong-si Gyeonggi-do 18423 - KR - 4C-D7-C8 (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. 4CD7C8 (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. 13/F, Building 1, No.13 Bohua 4th Road, Huangpu District Guangzhou Guangdong 510663 CN -BC-AA-82 (hex) Fiberhome Telecommunication Technologies Co.,LTD -BCAA82 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 +18-5C-A1 (hex) Jiangxi Risound Electronics Co.,LTD +185CA1 (base 16) Jiangxi Risound Electronics Co.,LTD + No 271,innovation Avenue, Jinggangshan economic and Technological Development Zone + Ji'an Jiangxi 343100 CN +C8-91-43 (hex) Nintendo Co.,Ltd +C89143 (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP + 44-93-8D (hex) Innolux Corporation 44938D (base 16) Innolux Corporation No. 160, Kexue Rd., Zhunan Township Miaoli County 35053 TW -C8-CC-21 (hex) eero inc. -C8CC21 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -B8-F4-A4 (hex) Google, Inc. -B8F4A4 (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 - US - -E0-1A-DF (hex) Google, Inc. -E01ADF (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 +70-AD-43 (hex) Blink by Amazon +70AD43 (base 16) Blink by Amazon + 100 Riverpark Drive + North Reading MA 01864 US 70-3A-8C (hex) Shenzhen Skyworth Digital Technology CO., Ltd @@ -46370,6 +46376,12 @@ E01ADF (base 16) Google, Inc. Werkendam 4251 LT NL +88-5E-54 (hex) Samsung Electronics Co.,Ltd +885E54 (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 + KR + D0-98-B1 (hex) GScoolink Microelectronics (Beijing) Co.,LTD D098B1 (base 16) GScoolink Microelectronics (Beijing) Co.,LTD Room 101, 3rd Floor, Building 23, No. 8 Dongbeiwang West Road, Haidian District @@ -46388,12 +46400,6 @@ D098B1 (base 16) GScoolink Microelectronics (Beijing) Co.,LTD Dongguan 523808 CN -C8-91-43 (hex) Nintendo Co.,Ltd -C89143 (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP - C8-AF-F0 (hex) CDVI Wireless SpA C8AFF0 (base 16) CDVI Wireless SpA via Piave 23 @@ -46424,11 +46430,29 @@ E4FAE4 (base 16) Shenzhen SDMC Technology CP,.LTD Gumi Gyeongbuk 730-350 KR -88-5E-54 (hex) Samsung Electronics Co.,Ltd -885E54 (base 16) Samsung Electronics Co.,Ltd - 129, Samsung-ro, Youngtongl-Gu - Suwon Gyeonggi-Do 16677 - KR +B8-1E-61 (hex) TELLESCOM INDUSTRIA E COMERCIO EM TELECOMUNICACAO +B81E61 (base 16) TELLESCOM INDUSTRIA E COMERCIO EM TELECOMUNICACAO + Av. Buriti, 1900 – Setor B – Distrito Industrial + Manaus Amazonas 69075-000 + BR + +40-6E-0F (hex) SKYASTAR TECHNOLOGLES(ZHUHAI) LTD +406E0F (base 16) SKYASTAR TECHNOLOGLES(ZHUHAI) LTD + 3F, 5# Building, Maker Town, Jinwan, Zhuhai, Guangdong, 519090 China + ZHUHAI Guangdong 519090 + CN + +EC-B5-0A (hex) Quectel Wireless Solutions Co.,Ltd. +ECB50A (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 + CN + +38-E0-54 (hex) Security Design, Inc. +38E054 (base 16) Security Design, Inc. + Nishiki-cho Trad Square 4F, 3-20 Kanda Nishiki-cho + Chiyoda-ku Tokyo 101-0054 + JP 8C-A3-EC (hex) Samsung Electronics Co.,Ltd 8CA3EC (base 16) Samsung Electronics Co.,Ltd @@ -46460,18 +46484,6 @@ AC3AE2 (base 16) NVIDIA Corporation Santa Clara CA 95050 US -70-AD-43 (hex) Blink by Amazon -70AD43 (base 16) Blink by Amazon - 100 Riverpark Drive - North Reading MA 01864 - US - -D4-00-CA (hex) AUMOVIO Systems Romania S.R.L. -D400CA (base 16) AUMOVIO Systems Romania S.R.L. - Str. Salzburg Nr. 8, 550018 - Sibiu Sibiu 550018 - RO - 40-85-56 (hex) AUMOVIO Technologies Romania S.R.L. 408556 (base 16) AUMOVIO Technologies Romania S.R.L. Str Siemens no.1, 300701 Timisoara, Romania @@ -46496,42 +46508,6 @@ D494FB (base 16) AUMOVIO Systems, Inc. Deer Park IL 60010 US -B8-1E-61 (hex) TELLESCOM INDUSTRIA E COMERCIO EM TELECOMUNICACAO -B81E61 (base 16) TELLESCOM INDUSTRIA E COMERCIO EM TELECOMUNICACAO - Av. Buriti, 1900 – Setor B – Distrito Industrial - Manaus Amazonas 69075-000 - BR - -40-6E-0F (hex) SKYASTAR TECHNOLOGLES(ZHUHAI) LTD -406E0F (base 16) SKYASTAR TECHNOLOGLES(ZHUHAI) LTD - 3F, 5# Building, Maker Town, Jinwan, Zhuhai, Guangdong, 519090 China - ZHUHAI Guangdong 519090 - CN - -EC-B5-0A (hex) Quectel Wireless Solutions Co.,Ltd. -ECB50A (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 - CN - -38-E0-54 (hex) Security Design, Inc. -38E054 (base 16) Security Design, Inc. - Nishiki-cho Trad Square 4F, 3-20 Kanda Nishiki-cho - Chiyoda-ku Tokyo 101-0054 - JP - -44-7C-AC (hex) Invictus-AV -447CAC (base 16) Invictus-AV - 17650 Hillcrest Drive - Meadow Vista CA 95722 - US - -6C-D5-52 (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD -6CD552 (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD - NO.268, Fuqian Rd, Jutang community, Guanlan Town, Longhua New district - shenzhen guangdong 518000 - CN - 44-20-63 (hex) AUMOVIO Germany GmbH 442063 (base 16) AUMOVIO Germany GmbH Siemensstr. 12 @@ -46544,6 +46520,24 @@ E41E33 (base 16) AUMOVIO Germany GmbH Villingen-Schwenningen 78052 DE +6C-D5-52 (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD +6CD552 (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD + NO.268, Fuqian Rd, Jutang community, Guanlan Town, Longhua New district + shenzhen guangdong 518000 + CN + +D4-00-CA (hex) AUMOVIO Systems Romania S.R.L. +D400CA (base 16) AUMOVIO Systems Romania S.R.L. + Str. Salzburg Nr. 8, 550018 + Sibiu Sibiu 550018 + RO + +44-7C-AC (hex) Invictus-AV +447CAC (base 16) Invictus-AV + 17650 Hillcrest Drive + Meadow Vista CA 95722 + US + 00-02-DC (hex) GENERAL Inc. 0002DC (base 16) GENERAL Inc. 3-3-17,Suenaga,Takatsu-ku @@ -46580,6 +46574,24 @@ D02C39 (base 16) Cisco Systems, Inc San Jose CA 94568 US +1C-FF-3F (hex) Cust2mate +1CFF3F (base 16) Cust2mate + 4 Ariel Sharon St + Givatayim 5320047 + IL + +74-83-A0 (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd +7483A0 (base 16) Shenzhen MiaoMing Intelligent Technology Co.,Ltd + 3F, Building 2, No. 1310, Kukeng Sightseeing Road, Kukeng Community, Guanlan Street, Longhua District + Shenzhen Guangdong 518110 + CN + +18-69-45 (hex) TP-Link Systems Inc. +186945 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US + 48-76-96 (hex) Huaan Zhongyun Co., Ltd. 487696 (base 16) Huaan Zhongyun Co., Ltd. Room 201, 2nd Floor, Building A, No. 128 Qiming Road, Yinzhou District, Ningbo City @@ -46592,11 +46604,17 @@ D02C39 (base 16) Cisco Systems, Inc Dongguan 523808 CN -18-69-45 (hex) TP-Link Systems Inc. -186945 (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 - US +20-4B-2E (hex) Pizzato Elettrica S.r.l. +204B2E (base 16) Pizzato Elettrica S.r.l. + Via Torino, 1 + Marostica VI 36063 + IT + +50-61-7E (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd +50617E (base 16) Shenzhen MiaoMing Intelligent Technology Co.,Ltd + 3F, Building 2, No. 1310, Kukeng Sightseeing Road, Kukeng Community, Guanlan Street, Longhua District + Shenzhen Guangdong 518110 + CN 80-BF-21 (hex) vivo Mobile Communication Co., Ltd. 80BF21 (base 16) vivo Mobile Communication Co., Ltd. @@ -46616,36 +46634,6 @@ D0B324 (base 16) Apple, Inc. Cupertino CA 95014 US -1C-FF-3F (hex) Cust2mate -1CFF3F (base 16) Cust2mate - 4 Ariel Sharon St - Givatayim 5320047 - IL - -74-83-A0 (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd -7483A0 (base 16) Shenzhen MiaoMing Intelligent Technology Co.,Ltd - 3F, Building 2, No. 1310, Kukeng Sightseeing Road, Kukeng Community, Guanlan Street, Longhua District - Shenzhen Guangdong 518110 - CN - -60-25-ED (hex) Hewlett Packard Enterprise -6025ED (base 16) Hewlett Packard Enterprise - 6280 America Center Dr - San Jose CA 95002 - US - -20-4B-2E (hex) Pizzato Elettrica S.r.l. -204B2E (base 16) Pizzato Elettrica S.r.l. - Via Torino, 1 - Marostica VI 36063 - IT - -50-61-7E (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd -50617E (base 16) Shenzhen MiaoMing Intelligent Technology Co.,Ltd - 3F, Building 2, No. 1310, Kukeng Sightseeing Road, Kukeng Community, Guanlan Street, Longhua District - Shenzhen Guangdong 518110 - CN - 2C-79-BE (hex) TP-LINK TECHNOLOGIES CO.,LTD. 2C79BE (base 16) TP-LINK TECHNOLOGIES CO.,LTD. Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan @@ -46658,23 +46646,47 @@ D0B324 (base 16) Apple, Inc. Santa Clara CA 95054 US +74-DC-13 (hex) Telink Micro LLC +74DC13 (base 16) Telink Micro LLC + 2975 Scott Blvd #120 + Santa Clara 95054 + US + +60-25-ED (hex) Hewlett Packard Enterprise +6025ED (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US + +00-21-04 (hex) Gigaset Technologies GmbH +002104 (base 16) Gigaset Technologies GmbH + Frankenstrasse 2 + 46395 Bocholt + DE + +AC-D2-0C (hex) Chengdu SingCore Technology Co.,Ltd. +ACD20C (base 16) Chengdu SingCore Technology Co.,Ltd. + Room 4, 16th Floor, Building 10, No. 399 West Fucheng Avenue, Chengdu High-Tech Zone, China (Sichuan) Pilot Free Trade Zone,Chengdu, Sichuan Province, China. + Chengdu Sichuan 610041 + CN + 04-64-FA (hex) Dell Inc. 0464FA (base 16) Dell Inc. One Dell Way Round Rock TX 78682 US -68-78-A8 (hex) Fiberhome Telecommunication Technologies Co.,LTD -6878A8 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 +8C-37-B7 (hex) Hosin Global Electronics Co.,Ltd +8C37B7 (base 16) Hosin Global Electronics Co.,Ltd + Rm 2501, Bldg 2, Shenzhen Next Generation Industrial Park, No.136 Zhongkang Rd, Futian Dist + Shenzhen 518000 CN -74-DC-13 (hex) Telink Micro LLC -74DC13 (base 16) Telink Micro LLC - 2975 Scott Blvd #120 - Santa Clara 95054 - US +F0-6D-93 (hex) EM Microelectronic +F06D93 (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH B8-1E-0B (hex) Extreme Networks Headquarters B81E0B (base 16) Extreme Networks Headquarters @@ -46682,12 +46694,6 @@ B81E0B (base 16) Extreme Networks Headquarters Morrisville NC 27560 US -AC-D2-0C (hex) Chengdu SingCore Technology Co.,Ltd. -ACD20C (base 16) Chengdu SingCore Technology Co.,Ltd. - Room 4, 16th Floor, Building 10, No. 399 West Fucheng Avenue, Chengdu High-Tech Zone, China (Sichuan) Pilot Free Trade Zone,Chengdu, Sichuan Province, China. - Chengdu Sichuan 610041 - CN - 8C-94-DF (hex) Espressif Inc. 8C94DF (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area @@ -46706,46 +46712,10 @@ ACD20C (base 16) Chengdu SingCore Technology Co.,Ltd. Guangzhou 510540 CN -00-21-04 (hex) Gigaset Technologies GmbH -002104 (base 16) Gigaset Technologies GmbH - Frankenstrasse 2 - 46395 Bocholt - DE - -8C-37-B7 (hex) Hosin Global Electronics Co.,Ltd -8C37B7 (base 16) Hosin Global Electronics Co.,Ltd - Rm 2501, Bldg 2, Shenzhen Next Generation Industrial Park, No.136 Zhongkang Rd, Futian Dist - Shenzhen 518000 - CN - -F0-6D-93 (hex) EM Microelectronic -F06D93 (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH - -0C-C9-8A (hex) Intel Corporate -0CC98A (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -EC-F3-3C (hex) Intel Corporate -ECF33C (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -40-EC-BD (hex) Intel Corporate -40ECBD (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -6C-10-41 (hex) Shenzhen Skyworth Digital Technology CO., Ltd -6C1041 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd - 4F,Block A, Skyworth?Building, - Shenzhen Guangdong 518057 +68-78-A8 (hex) Fiberhome Telecommunication Technologies Co.,LTD +6878A8 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 CN 90-0A-75 (hex) New H3C Technologies Co., Ltd @@ -46760,12 +46730,6 @@ ECF33C (base 16) Intel Corporate Dongguan Guangdong 523808 CN -8C-8C-29 (hex) Espressif Inc. -8C8C29 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - E4-06-E0 (hex) HUAWEI TECHNOLOGIES CO.,LTD E406E0 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -46784,20 +46748,14 @@ DCB43F (base 16) eero inc. San Francisco CA 94107 US -14-90-7A (hex) Beijing Xiaomi Mobile Software Co., Ltd -14907A (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 - CN - -1C-8F-57 (hex) Espressif Inc. -1C8F57 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +6C-10-41 (hex) Shenzhen Skyworth Digital Technology CO., Ltd +6C1041 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, + Shenzhen Guangdong 518057 CN -10-BD-A3 (hex) Espressif Inc. -10BDA3 (base 16) Espressif Inc. +8C-8C-29 (hex) Espressif Inc. +8C8C29 (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area Shanghai Shanghai 201203 CN @@ -46832,6 +46790,48 @@ C05BBD (base 16) HUAWEI TECHNOLOGIES CO.,LTD Chengdu Sichuan 611330 CN +EC-F3-3C (hex) Intel Corporate +ECF33C (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +40-EC-BD (hex) Intel Corporate +40ECBD (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +0C-C9-8A (hex) Intel Corporate +0CC98A (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +14-90-7A (hex) Beijing Xiaomi Mobile Software Co., Ltd +14907A (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 + CN + +1C-8F-57 (hex) Espressif Inc. +1C8F57 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +94-AE-E3 (hex) Belden Hirschmann industries (Suzhou) Limited +94AEE3 (base 16) Belden Hirschmann industries (Suzhou) Limited + 333 Yanhu Road, Huaqiao Town + Kunshan Jiangsu 215332 + CN + +94-10-5A (hex) Dell Inc. +94105A (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 + US + 44-83-46 (hex) Texas Instruments 448346 (base 16) Texas Instruments 12500 TI Blvd @@ -46856,17 +46856,17 @@ DCDEE3 (base 16) Texas Instruments ShenZhen 518100 CN -94-AE-E3 (hex) Belden Hirschmann industries (Suzhou) Limited -94AEE3 (base 16) Belden Hirschmann industries (Suzhou) Limited - 333 Yanhu Road, Huaqiao Town - Kunshan Jiangsu 215332 +10-BD-A3 (hex) Espressif Inc. +10BDA3 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 CN -94-10-5A (hex) Dell Inc. -94105A (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US +E4-72-9D (hex) Nokia Shanghai Bell Co., Ltd. +E4729D (base 16) Nokia Shanghai Bell Co., Ltd. + No.388 Ning Qiao Road,Jin Qiao Pudong Shanghai + Shanghai 201206 + CN 7C-CF-0F (hex) LCFC(Hefei) Electronics Technology co., ltd 7CCF0F (base 16) LCFC(Hefei) Electronics Technology co., ltd @@ -46898,10 +46898,10 @@ A02605 (base 16) Belden Hirschmann industries (Suzhou) Limited Suzhou Jiangsu 215332 CN -E4-72-9D (hex) Nokia Shanghai Bell Co., Ltd. -E4729D (base 16) Nokia Shanghai Bell Co., Ltd. - No.388 Ning Qiao Road,Jin Qiao Pudong Shanghai - Shanghai 201206 +C0-2F-CD (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +C02FCD (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN F8-84-75 (hex) i5LED, LLC @@ -46910,29 +46910,11 @@ F88475 (base 16) i5LED, LLC Sacramento CA 95827 US -C0-2F-CD (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -C02FCD (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN - -04-DB-D9 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -04DBD9 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN - -54-01-4A (hex) Guangzhou Shiyuan Electronic Technology Company Limited -54014A (base 16) Guangzhou Shiyuan Electronic Technology Company Limited - No.6, 4th Yunpu Road, Yunpu industry District - Guangzhou Guangdong 510530 - CN - -FC-8F-A4 (hex) NXP Semiconductors Taiwan Ltd. -FC8FA4 (base 16) NXP Semiconductors Taiwan Ltd. - No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan - Nanzi Dist. Kaohsiung 811643 - TW +44-9F-79 (hex) onsemi +449F79 (base 16) onsemi + 5701 N Pima Rd + Scottsdale AZ 85250 + US A4-61-77 (hex) AMOSENSE A46177 (base 16) AMOSENSE @@ -46943,23 +46925,35 @@ A46177 (base 16) AMOSENSE 58-DF-70 (hex) Private 58DF70 (base 16) Private +54-01-4A (hex) Guangzhou Shiyuan Electronic Technology Company Limited +54014A (base 16) Guangzhou Shiyuan Electronic Technology Company Limited + No.6, 4th Yunpu Road, Yunpu industry District + Guangzhou Guangdong 510530 + CN + 50-EE-9B (hex) AltoBeam Inc. 50EE9B (base 16) AltoBeam Inc. B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian Beijing Beijing 100083 CN -90-DF-06 (hex) Ciena Corporation -90DF06 (base 16) Ciena Corporation - 7035 Ridge Road - Hanover MD 21076 - US +EC-73-F6 (hex) Sichuan AI-Link Technology Co., Ltd. +EC73F6 (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou, Industrial Park + Mianyang Sichuan 622650 + CN -44-9F-79 (hex) onsemi -449F79 (base 16) onsemi - 5701 N Pima Rd - Scottsdale AZ 85250 - US +04-DB-D9 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +04DBD9 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN + +FC-8F-A4 (hex) NXP Semiconductors Taiwan Ltd. +FC8FA4 (base 16) NXP Semiconductors Taiwan Ltd. + No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan + Nanzi Dist. Kaohsiung 811643 + TW 2C-DE-F5 (hex) TVS REGZA Corporation 2CDEF5 (base 16) TVS REGZA Corporation @@ -46967,22 +46961,10 @@ A46177 (base 16) AMOSENSE Kawasaki-shi Kanagawa 2120013 JP -EC-73-F6 (hex) Sichuan AI-Link Technology Co., Ltd. -EC73F6 (base 16) Sichuan AI-Link Technology Co., Ltd. - Anzhou, Industrial Park - Mianyang Sichuan 622650 - CN - -00-26-89 (hex) General Dynamics Land Systems Inc. -002689 (base 16) General Dynamics Land Systems Inc. - 38500 Mound Road - Sterling Heights MI 48310-3200 - US - -0C-6F-8B (hex) Apple, Inc. -0C6F8B (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +90-DF-06 (hex) Ciena Corporation +90DF06 (base 16) Ciena Corporation + 7035 Ridge Road + Hanover MD 21076 US 50-EE-87 (hex) HPRO @@ -46991,36 +46973,6 @@ EC73F6 (base 16) Sichuan AI-Link Technology Co., Ltd. Northridge CA 91329 US -10-C1-97 (hex) Xiaomi Communications Co Ltd -10C197 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN - -3C-B9-22 (hex) HUAWEI TECHNOLOGIES CO.,LTD -3CB922 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -AC-45-B0 (hex) Shenzhen Jidao Technology Co Ltd -AC45B0 (base 16) Shenzhen Jidao Technology Co Ltd - Room 605, Building 1, Saiba Technology Building, No. 16, North Keji Er Road - Shenzhen Guangdong 518057 - CN - -70-70-D5 (hex) HUAWEI TECHNOLOGIES CO.,LTD -7070D5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -60-53-55 (hex) HUAWEI TECHNOLOGIES CO.,LTD -605355 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - B0-2B-64 (hex) Cisco Systems, Inc B02B64 (base 16) Cisco Systems, Inc 80 West Tasman Drive @@ -47039,6 +46991,18 @@ FC500C (base 16) Sitehop Ltd Sheffield South Yorkshire S2 5QX GB +00-26-89 (hex) General Dynamics Land Systems Inc. +002689 (base 16) General Dynamics Land Systems Inc. + 38500 Mound Road + Sterling Heights MI 48310-3200 + US + +0C-6F-8B (hex) Apple, Inc. +0C6F8B (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + 58-2A-BD (hex) Espressif Inc. 582ABD (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area @@ -47051,12 +47015,42 @@ C8C873 (base 16) CHIPSEN INC. Gwangmyeong-si Gyeonggi-do 14353 KR +AC-45-B0 (hex) Shenzhen Jidao Technology Co Ltd +AC45B0 (base 16) Shenzhen Jidao Technology Co Ltd + Room 605, Building 1, Saiba Technology Building, No. 16, North Keji Er Road + Shenzhen Guangdong 518057 + CN + +3C-B9-22 (hex) HUAWEI TECHNOLOGIES CO.,LTD +3CB922 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +70-70-D5 (hex) HUAWEI TECHNOLOGIES CO.,LTD +7070D5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +60-53-55 (hex) HUAWEI TECHNOLOGIES CO.,LTD +605355 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + D4-9F-F9 (hex) Earda Technologies co Ltd D49FF9 (base 16) Earda Technologies co Ltd Block A,Lianfeng Creative Park, #2 Jisheng Rd., Nansha District Guangzhou Guangdong 511455 CN +10-C1-97 (hex) Xiaomi Communications Co Ltd +10C197 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + 90-0E-84 (hex) eero inc. 900E84 (base 16) eero inc. 660 3rd Street @@ -47069,6 +47063,42 @@ F4C6D7 (base 16) blackned GmbH Bavaria Heimertingen 87751 DE +48-AA-BB (hex) Sagemcom Broadband SAS +48AABB (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR + +E0-D3-8E (hex) Chipsea Technologies (Shenzhen) Crop. +E0D38E (base 16) Chipsea Technologies (Shenzhen) Crop. + Room 301, Building 1, Shenzhen Bay Innovation and Technology Center, Keyuan Avenue, High-tech Zone Community, Yuehai Subdistrict, Nanshan District, Shenzhen + Shenzhen 518000 + CN + +CC-C8-37 (hex) Quectel Wireless Solutions Co.,Ltd. +CCC837 (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 + CN + +D4-4A-85 (hex) Silicon Laboratories +D44A85 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + +38-A3-E0 (hex) 1Finity Inc +38A3E0 (base 16) 1Finity Inc + 4-1-1 Kamikodanaka, Nakahara-ku, Kawasaki-shi, Kanagawa211-8588, Japan + Kawasaki Kanagawa 211-8588 + JP + +A8-D3-F7 (hex) Arcadyan Corporation +A8D3F7 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd., + Hsinchu City Hsinchu 30071 + TW + 00-01-30 (hex) Extreme Networks Headquarters 000130 (base 16) Extreme Networks Headquarters 2121 RDU Center Drive @@ -64325,18 +64355,6 @@ C0FFD4 (base 16) NETGEAR San Jose CA 95134 US -00-26-4D (hex) Arcadyan Technology Corporation -00264D (base 16) Arcadyan Technology Corporation - 4F., No. 9 , Park Avenue II , - Hsinchu Taiwan 300 - TW - -84-9C-A6 (hex) Arcadyan Technology Corporation -849CA6 (base 16) Arcadyan Technology Corporation - 4F, No. 9, Park Avenue II , - Hsinchu 300 - TW - 00-24-B2 (hex) NETGEAR 0024B2 (base 16) NETGEAR 350 East Plumeria Drive @@ -90896,18 +90914,6 @@ A4AD9E (base 16) NEXAIOT Shenzhen Guangdong 518057 CN -94-EF-50 (hex) TP-Link Systems Inc. -94EF50 (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 - US - -90-3F-C3 (hex) Huawei Device Co., Ltd. -903FC3 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - A8-0C-CA (hex) Shenzhen Sundray Technologies company Limited A80CCA (base 16) Shenzhen Sundray Technologies company Limited 6th Floor,Block A1, Nanshan iPark, No.1001 XueYuan Road, Nanshan District @@ -90926,12 +90932,24 @@ A80CCA (base 16) Shenzhen Sundray Technologies company Limited Shenzhen Guangdong 518057 CN +90-3F-C3 (hex) Huawei Device Co., Ltd. +903FC3 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + C4-49-3E (hex) Motorola Mobility LLC, a Lenovo Company C4493E (base 16) Motorola Mobility LLC, a Lenovo Company 222 West Merchandise Mart Plaza Chicago IL 60654 US +94-EF-50 (hex) TP-Link Systems Inc. +94EF50 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US + FC-A2-DF (hex) IEEE Registration Authority FCA2DF (base 16) IEEE Registration Authority 445 Hoes Lane @@ -90962,36 +90980,12 @@ CC6200 (base 16) Honor Device Co., Ltd. Dongguan 523808 CN -18-CE-DF (hex) Quectel Wireless Solutions Co.,Ltd. -18CEDF (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 - CN - -00-33-7A (hex) Tuya Smart Inc. -00337A (base 16) Tuya Smart Inc. - 160 Greentree Drive, Suite 101 - Dover DE 19904 - US - -C0-54-4D (hex) Nokia Shanghai Bell Co., Ltd. -C0544D (base 16) Nokia Shanghai Bell Co., Ltd. - No.388 Ning Qiao Road,Jin Qiao Pudong Shanghai - Shanghai 201206 +7C-C8-82 (hex) HUAWEI TECHNOLOGIES CO.,LTD +7CC882 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -CC-CE-1E (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -CCCE1E (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE - -7C-FF-4D (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -7CFF4D (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE - 3C-A6-2F (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH 3CA62F (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 @@ -91016,6 +91010,48 @@ CCCE1E (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Berlin Berlin 10559 DE +C0-54-4D (hex) Nokia Shanghai Bell Co., Ltd. +C0544D (base 16) Nokia Shanghai Bell Co., Ltd. + No.388 Ning Qiao Road,Jin Qiao Pudong Shanghai + Shanghai 201206 + CN + +CC-CE-1E (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +CCCE1E (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +7C-FF-4D (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +7CFF4D (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +60-57-7D (hex) eero inc. +60577D (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +AC-EC-85 (hex) eero inc. +ACEC85 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +0C-1C-1A (hex) eero inc. +0C1C1A (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +64-C2-69 (hex) eero inc. +64C269 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + 74-B6-B6 (hex) eero inc. 74B6B6 (base 16) eero inc. 660 3rd Street @@ -91052,12 +91088,18 @@ F8BBBF (base 16) eero inc. San Francisco CA 94107 US -7C-C8-82 (hex) HUAWEI TECHNOLOGIES CO.,LTD -7CC882 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +18-CE-DF (hex) Quectel Wireless Solutions Co.,Ltd. +18CEDF (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 CN +00-33-7A (hex) Tuya Smart Inc. +00337A (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 + US + 48-1F-66 (hex) China Mobile Group Device Co.,Ltd. 481F66 (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District @@ -91118,30 +91160,6 @@ DC152D (base 16) China Mobile Group Device Co.,Ltd. Beijing 100053 CN -60-57-7D (hex) eero inc. -60577D (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -AC-EC-85 (hex) eero inc. -ACEC85 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -0C-1C-1A (hex) eero inc. -0C1C1A (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -64-C2-69 (hex) eero inc. -64C269 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - 70-93-C1 (hex) eero inc. 7093C1 (base 16) eero inc. 660 3rd Street @@ -91166,6 +91184,12 @@ ACEC85 (base 16) eero inc. San Francisco CA 94107 US +54-92-6A (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +54926A (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 + CN + 0C-93-A5 (hex) eero inc. 0C93A5 (base 16) eero inc. 660 3rd Street @@ -91202,18 +91226,18 @@ B4B9E6 (base 16) eero inc. San Francisco CA 94107 US -70-7D-A1 (hex) Sagemcom Broadband SAS -707DA1 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - D8-3E-EF (hex) COOSEA GROUP (HK) COMPANY LIMITED D83EEF (base 16) COOSEA GROUP (HK) COMPANY LIMITED Unit 56 16F Multifield Plaza,37A Part Avenue.Tsim Sha Tsui,KL,Hong Kong,SAR CHINA Hong Kong 999077 CN +70-7D-A1 (hex) Sagemcom Broadband SAS +707DA1 (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR + 04-58-5D (hex) IEEE Registration Authority 04585D (base 16) IEEE Registration Authority 445 Hoes Lane @@ -91232,11 +91256,11 @@ C4864F (base 16) Beijing BitIntelligence Information Technology Co. Ltd. Beijing Beijing 100080 CN -54-92-6A (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -54926A (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 - CN +E0-E6-E3 (hex) TeamF1 Networks Pvt Limited +E0E6E3 (base 16) TeamF1 Networks Pvt Limited + Ascendas IT Park, Capella Block, Floor #2, Plot No: 17, Software Units Layout, Madhapur + Hyderabad Telangana 500081 + IN C8-7F-2B (hex) INGRAM MICRO SERVICES C87F2B (base 16) INGRAM MICRO SERVICES @@ -91268,12 +91292,6 @@ C87F2B (base 16) INGRAM MICRO SERVICES San Francisco 94158 US -E0-E6-E3 (hex) TeamF1 Networks Pvt Limited -E0E6E3 (base 16) TeamF1 Networks Pvt Limited - Ascendas IT Park, Capella Block, Floor #2, Plot No: 17, Software Units Layout, Madhapur - Hyderabad Telangana 500081 - IN - 34-FA-1C (hex) Beijing Xiaomi Mobile Software Co., Ltd 34FA1C (base 16) Beijing Xiaomi Mobile Software Co., Ltd The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District @@ -91286,18 +91304,42 @@ E0E6E3 (base 16) TeamF1 Networks Pvt Limited Beijing 100029 CN +44-35-B9 (hex) NetComm Wireless Pty Ltd +4435B9 (base 16) NetComm Wireless Pty Ltd + Level 1, 18-20 Orion Road + Sydney NSW 2066 + AU + 4C-CF-7C (hex) HP Inc. 4CCF7C (base 16) HP Inc. 10300 Energy Dr Spring TX 77389 US +DC-BB-3D (hex) Extreme Networks Headquarters +DCBB3D (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville 27560 + US + 20-3A-0C (hex) eero inc. 203A0C (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US +FC-B2-14 (hex) Apple, Inc. +FCB214 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +2C-95-20 (hex) Apple, Inc. +2C9520 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + 00-15-FF (hex) Inseego Wireless, Inc 0015FF (base 16) Inseego Wireless, Inc 9710 Scranton Rd., Suite 200 @@ -91340,30 +91382,30 @@ FC9F2A (base 16) Zyxel Communications Corporation Hsichu Taiwan 300 TW -44-35-B9 (hex) NetComm Wireless Pty Ltd -4435B9 (base 16) NetComm Wireless Pty Ltd - Level 1, 18-20 Orion Road - Sydney NSW 2066 - AU - 64-75-DA (hex) Arcadyan Corporation 6475DA (base 16) Arcadyan Corporation No.8, Sec.2, Guangfu Rd. Hsinchu City Hsinchu 30071 TW -DC-BB-3D (hex) Extreme Networks Headquarters -DCBB3D (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville 27560 - US - B0-CC-CE (hex) IEEE Registration Authority B0CCCE (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US +DC-B4-D9 (hex) Espressif Inc. +DCB4D9 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +98-32-68 (hex) Silicon Laboratories +983268 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + B8-9C-13 (hex) Alps Alpine B89C13 (base 16) Alps Alpine 20-1, Yoshima Industrial Park @@ -91394,34 +91436,22 @@ A81F79 (base 16) Yingling Innovations Pte. Ltd. Midview 573970 SG -FC-B2-14 (hex) Apple, Inc. -FCB214 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -2C-95-20 (hex) Apple, Inc. -2C9520 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - 80-23-95 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH 802395 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -98-32-68 (hex) Silicon Laboratories -983268 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US +04-8F-00 (hex) Rong-Paisa Electronics Co., Ltd. +048F00 (base 16) Rong-Paisa Electronics Co., Ltd. + Carrera 43f #14A-112 + Medellin Antioquia 050021 + CO -DC-B4-D9 (hex) Espressif Inc. -DCB4D9 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +8C-5C-53 (hex) AltoBeam Inc. +8C5C53 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 CN EC-7C-BA (hex) Hewlett Packard Enterprise @@ -91436,17 +91466,17 @@ EC7CBA (base 16) Hewlett Packard Enterprise Beckwith Knowle Harrogate HG3 1UF GB -04-8F-00 (hex) Rong-Paisa Electronics Co., Ltd. -048F00 (base 16) Rong-Paisa Electronics Co., Ltd. - Carrera 43f #14A-112 - Medellin Antioquia 050021 - CO +50-2E-91 (hex) AzureWave Technology Inc. +502E91 (base 16) AzureWave Technology Inc. + 8F., No. 94, Baozhong Rd. + New Taipei City Taiwan 231 + TW -8C-5C-53 (hex) AltoBeam Inc. -8C5C53 (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 - CN +E4-9F-7D (hex) Samsung Electronics Co.,Ltd +E49F7D (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR E8-BF-E1 (hex) Intel Corporate E8BFE1 (base 16) Intel Corporate @@ -91490,12 +91520,6 @@ B43A96 (base 16) Arista Networks Fitzroy Victoria 3065 AU -E4-9F-7D (hex) Samsung Electronics Co.,Ltd -E49F7D (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - 60-98-49 (hex) Nokia Solutions and Networks India Private Limited 609849 (base 16) Nokia Solutions and Networks India Private Limited Radiance Ivy terrace, Block 4, 9R, Egattur, Chennai @@ -91508,12 +91532,6 @@ E49F7D (base 16) Samsung Electronics Co.,Ltd Chennai TamilNadu 600130 IN -50-2E-91 (hex) AzureWave Technology Inc. -502E91 (base 16) AzureWave Technology Inc. - 8F., No. 94, Baozhong Rd. - New Taipei City Taiwan 231 - TW - 68-F7-D8 (hex) Microsoft Corporation 68F7D8 (base 16) Microsoft Corporation One Microsoft Way @@ -91532,10 +91550,10 @@ C0CDD6 (base 16) Espressif Inc. Shanghai Shanghai 201203 CN -88-B9-51 (hex) Xiaomi Communications Co Ltd -88B951 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +8C-7A-B3 (hex) Guangzhou Shiyuan Electronic Technology Company Limited +8C7AB3 (base 16) Guangzhou Shiyuan Electronic Technology Company Limited + No.6, 4th Yunpu Road, Yunpu industry District + Guangzhou Guangdong 510530 CN E8-CD-15 (hex) Vantiva USA LLC @@ -91556,10 +91574,10 @@ E8CD15 (base 16) Vantiva USA LLC Shanghai Shanghai 201203 CN -8C-7A-B3 (hex) Guangzhou Shiyuan Electronic Technology Company Limited -8C7AB3 (base 16) Guangzhou Shiyuan Electronic Technology Company Limited - No.6, 4th Yunpu Road, Yunpu industry District - Guangzhou Guangdong 510530 +88-B9-51 (hex) Xiaomi Communications Co Ltd +88B951 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 CN 74-24-CA (hex) Guangzhou Shiyuan Electronic Technology Company Limited @@ -91574,12 +91592,6 @@ E8CD15 (base 16) Vantiva USA LLC Sunnyvale CA 94089 US -EC-31-11 (hex) Sichuan AI-Link Technology Co., Ltd. -EC3111 (base 16) Sichuan AI-Link Technology Co., Ltd. - Anzhou, Industrial Park - Mianyang Sichuan 622650 - CN - 00-6A-5E (hex) IEEE Registration Authority 006A5E (base 16) IEEE Registration Authority 445 Hoes Lane @@ -91604,6 +91616,12 @@ FC50D6 (base 16) Huawei Device Co., Ltd. Dongguan Guangdong 523808 CN +EC-31-11 (hex) Sichuan AI-Link Technology Co., Ltd. +EC3111 (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou, Industrial Park + Mianyang Sichuan 622650 + CN + D0-B1-CA (hex) Shenzhen Skyworth Digital Technology CO., Ltd D0B1CA (base 16) Shenzhen Skyworth Digital Technology CO., Ltd 4F,Block A, Skyworth?Building, @@ -91622,30 +91640,6 @@ D801EB (base 16) Infinity Electronics Ltd Stockholm SE-164 80 SE -28-35-3A (hex) HUAWEI TECHNOLOGIES CO.,LTD -28353A (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -60-30-B3 (hex) HUAWEI TECHNOLOGIES CO.,LTD -6030B3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -E8-7E-1C (hex) HUAWEI TECHNOLOGIES CO.,LTD -E87E1C (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -C0-15-1B (hex) Sony Interactive Entertainment Inc. -C0151B (base 16) Sony Interactive Entertainment Inc. - 1-7-1 Konan - Minato-ku Tokyo 108-0075 - JP - D0-68-27 (hex) eero inc. D06827 (base 16) eero inc. 660 3rd Street @@ -91664,6 +91658,18 @@ BC4529 (base 16) zte corporation shenzhen guangdong 518057 CN +E8-7E-1C (hex) HUAWEI TECHNOLOGIES CO.,LTD +E87E1C (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +C0-15-1B (hex) Sony Interactive Entertainment Inc. +C0151B (base 16) Sony Interactive Entertainment Inc. + 1-7-1 Konan + Minato-ku Tokyo 108-0075 + JP + 9C-65-EE (hex) Zhone Technologies, Inc. 9C65EE (base 16) Zhone Technologies, Inc. DASAN Tower 8F, 49 Daewangpangyo-ro644beon-gil Bundang-gu @@ -91676,24 +91682,24 @@ CC6C52 (base 16) Zhone Technologies, Inc. Plano TX 75024 US +28-35-3A (hex) HUAWEI TECHNOLOGIES CO.,LTD +28353A (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +60-30-B3 (hex) HUAWEI TECHNOLOGIES CO.,LTD +6030B3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + 8C-73-DA (hex) Silicon Laboratories 8C73DA (base 16) Silicon Laboratories 400 West Cesar Chavez Austin TX 78701 US -34-55-06 (hex) GUANGDONG GENIUS TECHNOLOGY CO., LTD. -345506 (base 16) GUANGDONG GENIUS TECHNOLOGY CO., LTD. - No.168, Middle Road Of East Gate - Xiaobian Community Chang'an Town 523851 - CN - -D4-7A-97 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. -D47A97 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. - No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. - Chongqing China 401120 - CN - D4-E9-F4 (hex) Espressif Inc. D4E9F4 (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area @@ -91706,12 +91712,6 @@ D4E9F4 (base 16) Espressif Inc. Shanghai Shanghai 201203 CN -6C-1A-EA (hex) Texas Instruments -6C1AEA (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US - 68-44-06 (hex) Texas Instruments 684406 (base 16) Texas Instruments 12500 TI Blvd @@ -91742,17 +91742,23 @@ E4B16C (base 16) Apple, Inc. Cupertino CA 95014 US +BC-5A-34 (hex) New H3C Technologies Co., Ltd +BC5A34 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN + 28-D5-B1 (hex) Apple, Inc. 28D5B1 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -BC-5A-34 (hex) New H3C Technologies Co., Ltd -BC5A34 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 - CN +80-D1-CE (hex) Apple, Inc. +80D1CE (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US 2C-CC-7A (hex) AltoBeam Inc. 2CCC7A (base 16) AltoBeam Inc. @@ -91760,12 +91766,18 @@ BC5A34 (base 16) New H3C Technologies Co., Ltd Beijing Beijing 100083 CN -80-D1-CE (hex) Apple, Inc. -80D1CE (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +6C-1A-EA (hex) Texas Instruments +6C1AEA (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 US +34-55-06 (hex) GUANGDONG GENIUS TECHNOLOGY CO., LTD. +345506 (base 16) GUANGDONG GENIUS TECHNOLOGY CO., LTD. + No.168, Middle Road Of East Gate + Xiaobian Community Chang'an Town 523851 + CN + F0-68-E3 (hex) AzureWave Technology Inc. F068E3 (base 16) AzureWave Technology Inc. 8F., No. 94, Baozhong Rd. @@ -91778,6 +91790,24 @@ F068E3 (base 16) AzureWave Technology Inc. Cupertino CA 95014 US +14-D5-C6 (hex) slash dev slash agents, inc +14D5C6 (base 16) slash dev slash agents, inc + 334 Brannan St, Floor 2 + San Francisco CA 94107 + US + +D8-85-AC (hex) Espressif Inc. +D885AC (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +D4-7A-97 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. +D47A97 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. + No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. + Chongqing China 401120 + CN + 4C-8E-19 (hex) Xiaomi Communications Co Ltd 4C8E19 (base 16) Xiaomi Communications Co Ltd #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road @@ -91790,36 +91820,12 @@ F068E3 (base 16) AzureWave Technology Inc. Shenzhen 518102 CN -D8-85-AC (hex) Espressif Inc. -D885AC (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -14-D5-C6 (hex) slash dev slash agents, inc -14D5C6 (base 16) slash dev slash agents, inc - 334 Brannan St, Floor 2 - San Francisco CA 94107 - US - 44-38-F3 (hex) EM Microelectronic 4438F3 (base 16) EM Microelectronic Rue des Sors 3 Marin-Epagnier Neuchatel 2074 CH -1C-D1-1A (hex) Fortinet, Inc. -1CD11A (base 16) Fortinet, Inc. - 899 Kifer Road - Sunnyvale 94086 - US - -50-51-4F (hex) Netbeam Technology Limited -50514F (base 16) Netbeam Technology Limited - Hudsun Chambers, P.O.Box 986, Road Town - Tortola VG1110 - VG - F8-D0-0E (hex) Vantiva USA LLC F8D00E (base 16) Vantiva USA LLC 4855 Peachtree Industrial Blvd, Suite 200 @@ -91838,6 +91844,18 @@ E4BD96 (base 16) Chengdu Hurray Data Technology co., Ltd. Chengdu 610000 CN +84-00-55 (hex) VusionGroup +840055 (base 16) VusionGroup + Kalsdorfer Straße 12 + Fernitz-Mellach Steiermark 8072 + AT + +14-B9-03 (hex) HUAWEI TECHNOLOGIES CO.,LTD +14B903 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + 18-69-0A (hex) Silicon Laboratories 18690A (base 16) Silicon Laboratories 400 West Cesar Chavez @@ -91856,16 +91874,22 @@ A46B40 (base 16) CHINA DRAGON TECHNOLOGY LIMITED Zhongshan Guangdong 528400 CN -84-00-55 (hex) VusionGroup -840055 (base 16) VusionGroup - Kalsdorfer Straße 12 - Fernitz-Mellach Steiermark 8072 - AT +1C-D1-1A (hex) Fortinet, Inc. +1CD11A (base 16) Fortinet, Inc. + 899 Kifer Road + Sunnyvale 94086 + US -14-B9-03 (hex) HUAWEI TECHNOLOGIES CO.,LTD -14B903 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +50-51-4F (hex) Netbeam Technology Limited +50514F (base 16) Netbeam Technology Limited + Hudsun Chambers, P.O.Box 986, Road Town + Tortola VG1110 + VG + +60-C4-18 (hex) TPV Display Technology (Xiamen) Co.,Ltd. +60C418 (base 16) TPV Display Technology (Xiamen) Co.,Ltd. + No.1, Xianghai Road, Xiamen Torch Hi-Tech Industrial Development Zone + XM Fujian 361101 CN 50-0B-23 (hex) HUAWEI TECHNOLOGIES CO.,LTD @@ -91880,30 +91904,12 @@ C8B78A (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -60-C4-18 (hex) TPV Display Technology (Xiamen) Co.,Ltd. -60C418 (base 16) TPV Display Technology (Xiamen) Co.,Ltd. - No.1, Xianghai Road, Xiamen Torch Hi-Tech Industrial Development Zone - XM Fujian 361101 - CN - -B0-2E-BA (hex) Earda Technologies co Ltd -B02EBA (base 16) Earda Technologies co Ltd - Block A,Lianfeng Creative Park, #2 Jisheng Rd., Nansha District - Guangzhou Guangdong 511455 - CN - 0C-3D-5E (hex) Nanjing Qinheng Microelectronics Co., Ltd. 0C3D5E (base 16) Nanjing Qinheng Microelectronics Co., Ltd. No.18, Ningshuang Road Nanjing Jiangsu 210012 CN -B8-CE-ED (hex) Broadcom -B8CEED (base 16) Broadcom - 1320 Ridder Park - San Jose CA 95131 - US - CC-0D-CB (hex) Microsoft Corporation CC0DCB (base 16) Microsoft Corporation One Microsoft Way @@ -91916,18 +91922,18 @@ CC0DCB (base 16) Microsoft Corporation Mountain View CA 94043 US -EC-97-E0 (hex) Hangzhou Ezviz Software Co.,Ltd. -EC97E0 (base 16) Hangzhou Ezviz Software Co.,Ltd. - 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District - Hangzhou Zhejiang 310051 - CN - -60-5E-65 (hex) Mellanox Technologies, Inc. -605E65 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 +B8-CE-ED (hex) Broadcom +B8CEED (base 16) Broadcom + 1320 Ridder Park + San Jose CA 95131 US +B0-2E-BA (hex) Earda Technologies co Ltd +B02EBA (base 16) Earda Technologies co Ltd + Block A,Lianfeng Creative Park, #2 Jisheng Rd., Nansha District + Guangzhou Guangdong 511455 + CN + 54-BA-D9 (hex) Intelbras 54BAD9 (base 16) Intelbras BR 101, km 210, S/N° @@ -91952,23 +91958,35 @@ EC96BF (base 16) Kontron eSystems GmbH shenzhen guangdong 518057 CN +A4-F0-0F (hex) Espressif Inc. +A4F00F (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + F0-92-58 (hex) China Electronics Cloud Computing Technology Co., Ltd F09258 (base 16) China Electronics Cloud Computing Technology Co., Ltd N3013,3F,N R&D building, A.I. Technology Park, Economic and Technological Development Zone Wuhan Hubei 430090 CN -A4-F0-0F (hex) Espressif Inc. -A4F00F (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +EC-97-E0 (hex) Hangzhou Ezviz Software Co.,Ltd. +EC97E0 (base 16) Hangzhou Ezviz Software Co.,Ltd. + 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District + Hangzhou Zhejiang 310051 CN -2C-8D-48 (hex) Smart Innovation LLC -2C8D48 (base 16) Smart Innovation LLC - 7F,Tower B,Jianxing - ShenZhen GuangZhou 518055 - CN +60-5E-65 (hex) Mellanox Technologies, Inc. +605E65 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +0C-C5-74 (hex) FRITZ! Technology GmbH +0CC574 (base 16) FRITZ! Technology GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE 38-8C-EF (hex) Samsung Electronics Co.,Ltd 388CEF (base 16) Samsung Electronics Co.,Ltd @@ -91982,34 +92000,22 @@ A4F00F (base 16) Espressif Inc. shenzhen guangdong 518100 CN -0C-C5-74 (hex) FRITZ! Technology GmbH -0CC574 (base 16) FRITZ! Technology GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE - -84-70-03 (hex) Axon Networks Inc. -847003 (base 16) Axon Networks Inc. - 15420 Laguna Canyon rd. - Irvine CA 92618 - US - A0-FF-FD (hex) HMD Global Oy A0FFFD (base 16) HMD Global Oy Bertel Jungin aukio 9 Espoo 02600 FI -30-7A-D2 (hex) Apple, Inc. -307AD2 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +2C-8D-48 (hex) Smart Innovation LLC +2C8D48 (base 16) Smart Innovation LLC + 7F,Tower B,Jianxing + ShenZhen GuangZhou 518055 + CN -D4-2D-CC (hex) Apple, Inc. -D42DCC (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +84-70-03 (hex) Axon Networks Inc. +847003 (base 16) Axon Networks Inc. + 15420 Laguna Canyon rd. + Irvine CA 92618 US 04-2E-C1 (hex) Apple, Inc. @@ -92036,29 +92042,32 @@ B45575 (base 16) Apple, Inc. Cupertino CA 95014 US +A0-E3-90 (hex) Apple, Inc. +A0E390 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + 6C-E4-A4 (hex) Silicon Laboratories 6CE4A4 (base 16) Silicon Laboratories 400 West Cesar Chavez Austin TX 78701 US -90-3F-86 (hex) New H3C Technologies Co., Ltd -903F86 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 - CN - -6C-88-5F (hex) Private -6C885F (base 16) Private - 60-D4-AF (hex) Honor Device Co., Ltd. 60D4AF (base 16) Honor Device Co., Ltd. Suite 3401, Unit A, Building 6, Shum Yip Sky Park, No. 8089, Hongli West Road, Xiangmihu Street, Futian District Shenzhen Guangdong 518040 CN -A0-E3-90 (hex) Apple, Inc. -A0E390 (base 16) Apple, Inc. +30-7A-D2 (hex) Apple, Inc. +307AD2 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +D4-2D-CC (hex) Apple, Inc. +D42DCC (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US @@ -92087,6 +92096,15 @@ A0E390 (base 16) Apple, Inc. Dongguan 523808 CN +90-3F-86 (hex) New H3C Technologies Co., Ltd +903F86 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN + +6C-88-5F (hex) Private +6C885F (base 16) Private + 6C-7F-49 (hex) Huawei Device Co., Ltd. 6C7F49 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone @@ -92105,20 +92123,14 @@ A0E390 (base 16) Apple, Inc. Nan-Tou Taiwan 54261 TW -B8-9F-09 (hex) WNC Corporation -B89F09 (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW - -88-5A-85 (hex) WNC Corporation -885A85 (base 16) WNC Corporation +28-24-FF (hex) WNC Corporation +2824FF (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park Hsin-Chu R.O.C. 308 TW -28-24-FF (hex) WNC Corporation -2824FF (base 16) WNC Corporation +B8-9F-09 (hex) WNC Corporation +B89F09 (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park Hsin-Chu R.O.C. 308 TW @@ -92147,6 +92159,12 @@ A8A092 (base 16) CHINA DRAGON TECHNOLOGY LIMITED Kanata Ontario K2K 2E6 CA +5C-BF-03 (hex) EMOCO +5CBF03 (base 16) EMOCO + Valhallavägen 5 + Lidingö 18151 + SE + EC-9E-68 (hex) Anhui Taoyun Technology Co., Ltd EC9E68 (base 16) Anhui Taoyun Technology Co., Ltd 6/F and 23/F, Scientific Research Building, Building 2, Zone A, China Sound Valley, No. 3333, Xiyou Road, High tech Zone Hefei Anhui @@ -92171,11 +92189,23 @@ B882F2 (base 16) WNC Corporation Hsin-Chu R.O.C. 308 TW -5C-BF-03 (hex) EMOCO -5CBF03 (base 16) EMOCO - Valhallavägen 5 - Lidingö 18151 - SE +88-5A-85 (hex) WNC Corporation +885A85 (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + +80-13-16 (hex) Intel Corporate +801316 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +2C-EA-FC (hex) Intel Corporate +2CEAFC (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY 04-24-05 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD 042405 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD @@ -92195,18 +92225,6 @@ D056F2 (base 16) BUFFALO.INC Nagoya Aichi Pref. 460-8315 JP -80-13-16 (hex) Intel Corporate -801316 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -2C-EA-FC (hex) Intel Corporate -2CEAFC (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - 74-F9-2C (hex) Ubiquiti Inc 74F92C (base 16) Ubiquiti Inc 685 Third Avenue, 27th Floor @@ -92225,6 +92243,12 @@ D056F2 (base 16) BUFFALO.INC Zoetermeer Zoetermeer 2712PN NL +30-4D-1F (hex) Amazon Technologies Inc. +304D1F (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + AC-F9-32 (hex) NXP Semiconductor (Tianjin) LTD. ACF932 (base 16) NXP Semiconductor (Tianjin) LTD. No.15 Xinghua Avenue, Xiqing Economic Development Area @@ -92249,12 +92273,6 @@ ACF932 (base 16) NXP Semiconductor (Tianjin) LTD. Austin TX 78701 US -30-4D-1F (hex) Amazon Technologies Inc. -304D1F (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US - F0-4E-A4 (hex) HP Inc. F04EA4 (base 16) HP Inc. 10300 Energy Dr @@ -92279,29 +92297,35 @@ E072A1 (base 16) Espressif Inc. Shanghai Shanghai 201203 CN -AC-A7-04 (hex) Espressif Inc. -ACA704 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 74-67-5F (hex) COMPAL INFORMATION(KUNSHAN)CO.,LTD. 74675F (base 16) COMPAL INFORMATION(KUNSHAN)CO.,LTD. No.25 , THE 3RD Street KUNSHAN EXPORT PROCESSING ZONE KUNSHAN SUZHOU 215300 CN +AC-A7-04 (hex) Espressif Inc. +ACA704 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + 0C-BF-B4 (hex) IEEE Registration Authority 0CBFB4 (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US -00-24-AE (hex) IDEMIA FRANCE SAS -0024AE (base 16) IDEMIA FRANCE SAS - 2 Place Samuel de Champlain - Courbevoie 92400 - FR +00-1F-33 (hex) NETGEAR +001F33 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US + +00-1B-2F (hex) NETGEAR +001B2F (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US 50-61-3F (hex) eero inc. 50613F (base 16) eero inc. @@ -92399,29 +92423,17 @@ E8FCAF (base 16) NETGEAR Kąty Wrocławskie dolnośląskie 55-080 PL -00-1F-33 (hex) NETGEAR -001F33 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US - -00-1B-2F (hex) NETGEAR -001B2F (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US - -38-33-C5 (hex) Microsoft Corporation -3833C5 (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 - US +A4-2A-26 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +A42A26 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 + CN -90-1C-9E (hex) Alcatel-Lucent Enterprise -901C9E (base 16) Alcatel-Lucent Enterprise - 2000 Corporate Center Dr Suite A - Thousand Oaks 91320 - US +74-08-AA (hex) Ruijie Networks Co.,LTD +7408AA (base 16) Ruijie Networks Co.,LTD + Building 19,Juyuanzhou Industrial Park, No.618 Jinshan Avenue, Cangshan District + Fuzhou 35000 + CN 18-24-39 (hex) YIPPEE ELECTRONICS CP.,LIMITED 182439 (base 16) YIPPEE ELECTRONICS CP.,LIMITED @@ -92435,18 +92447,18 @@ E8FCAF (base 16) NETGEAR Planegg 82152 DE +38-33-C5 (hex) Microsoft Corporation +3833C5 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US + 50-AB-29 (hex) Trackunit ApS 50AB29 (base 16) Trackunit ApS Gasvaerksvej 24, 4. sal Aalborg 9000 DK -A4-2A-26 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -A42A26 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 - CN - EC-A7-8D (hex) Cisco Systems, Inc ECA78D (base 16) Cisco Systems, Inc 80 West Tasman Drive @@ -92471,11 +92483,11 @@ FC7288 (base 16) Cisco Systems, Inc Dongguan Guangdong 523860 CN -74-08-AA (hex) Ruijie Networks Co.,LTD -7408AA (base 16) Ruijie Networks Co.,LTD - Building 19,Juyuanzhou Industrial Park, No.618 Jinshan Avenue, Cangshan District - Fuzhou 35000 - CN +90-1C-9E (hex) Alcatel-Lucent Enterprise +901C9E (base 16) Alcatel-Lucent Enterprise + 2000 Corporate Center Dr Suite A + Thousand Oaks 91320 + US 50-E0-F9 (hex) GE Vernova 50E0F9 (base 16) GE Vernova @@ -92525,16 +92537,22 @@ A0B53C (base 16) Vantiva Technologies Belgium Cedarburg WI 53012 US -0C-88-32 (hex) Nokia Solutions and Networks India Private Limited -0C8832 (base 16) Nokia Solutions and Networks India Private Limited - Plot 45, Fathima NagarNemilicherry,Chrompet - Chennai Taminadu 600044 - IN +24-19-A5 (hex) New H3C Technologies Co., Ltd +2419A5 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN -94-3B-22 (hex) NETGEAR -943B22 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 +6C-AF-AB (hex) UAB Teltonika Telematics +6CAFAB (base 16) UAB Teltonika Telematics + Saltoniskiu str. 9B-1 + Vilnius LT-08105 + LT + +1C-8E-2A (hex) Apple, Inc. +1C8E2A (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US 30-0E-43 (hex) Apple, Inc. @@ -92555,6 +92573,18 @@ A0B53C (base 16) Vantiva Technologies Belgium New Taipei City 238035 TW +0C-88-32 (hex) Nokia Solutions and Networks India Private Limited +0C8832 (base 16) Nokia Solutions and Networks India Private Limited + Plot 45, Fathima NagarNemilicherry,Chrompet + Chennai Taminadu 600044 + IN + +94-3B-22 (hex) NETGEAR +943B22 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US + B8-A7-92 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD B8A792 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County @@ -92567,54 +92597,42 @@ C8E713 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. Nanjing Jiangsu 211800 CN -1C-8E-2A (hex) Apple, Inc. -1C8E2A (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - 58-76-07 (hex) IEEE Registration Authority 587607 (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US -24-19-A5 (hex) New H3C Technologies Co., Ltd -2419A5 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 - CN - -6C-AF-AB (hex) UAB Teltonika Telematics -6CAFAB (base 16) UAB Teltonika Telematics - Saltoniskiu str. 9B-1 - Vilnius LT-08105 - LT - 54-83-BB (hex) Honda Motor Co., Ltd 5483BB (base 16) Honda Motor Co., Ltd Toranomon Alcea Tower, 2-2-3 Toranomon, Minato-ku, Tokyo 105-8404 JP +E0-96-E8 (hex) Fiberhome Telecommunication Technologies Co.,LTD +E096E8 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 + CN + A8-13-78 (hex) Nokia A81378 (base 16) Nokia 600 March Road Kanata Ontario K2K 2E6 CA +B4-64-15 (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. +B46415 (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. + 13/F, Building 1, No.13 Bohua 4th Road, Huangpu District + Guangzhou Guangdong 510663 + CN + 1C-8E-E6 (hex) VTECH TELECOMMUNICATIONS LIMITED 1C8EE6 (base 16) VTECH TELECOMMUNICATIONS LIMITED BLOCK 01 23-24/F TAT PING INDUSTRIAL CENTRE 57 TING KOK ROAD TAI PONT DONG GUAN GUANG ZHOU 52300 CN -E0-96-E8 (hex) Fiberhome Telecommunication Technologies Co.,LTD -E096E8 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 - CN - 84-5B-0C (hex) eFAB P.S.A. 845B0C (base 16) eFAB P.S.A. al. Solidarości 129/131/197VATID: PL5272968735 @@ -92633,24 +92651,6 @@ F0C88B (base 16) Wyze Labs Inc BOTHELL WA 98021 US -34-02-9C (hex) D-Link Corporation -34029C (base 16) D-Link Corporation - No.289, Sinhu 3rd Rd., Neihu District, - Taipei City 114 - TW - -B4-64-15 (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. -B46415 (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. - 13/F, Building 1, No.13 Bohua 4th Road, Huangpu District - Guangzhou Guangdong 510663 - CN - -6C-77-F0 (hex) Huawei Device Co., Ltd. -6C77F0 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - 24-62-C6 (hex) Huawei Device Co., Ltd. 2462C6 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone @@ -92687,6 +92687,18 @@ B472D4 (base 16) zte corporation Guangzhou Guangdong 511455 CN +6C-77-F0 (hex) Huawei Device Co., Ltd. +6C77F0 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +34-02-9C (hex) D-Link Corporation +34029C (base 16) D-Link Corporation + No.289, Sinhu 3rd Rd., Neihu District, + Taipei City 114 + TW + 5C-1B-17 (hex) Bosch Automotive Electronics India Pvt. Ltd. 5C1B17 (base 16) Bosch Automotive Electronics India Pvt. Ltd. Naganathapura @@ -92699,14 +92711,14 @@ B472D4 (base 16) zte corporation SHENZHEN Guangdong Province 518052 CN -78-60-89 (hex) Samsung Electronics Co.,Ltd -786089 (base 16) Samsung Electronics Co.,Ltd +A8-D1-62 (hex) Samsung Electronics Co.,Ltd +A8D162 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR -A8-D1-62 (hex) Samsung Electronics Co.,Ltd -A8D162 (base 16) Samsung Electronics Co.,Ltd +4C-EB-B0 (hex) Samsung Electronics Co.,Ltd +4CEBB0 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR @@ -92717,8 +92729,14 @@ A8D162 (base 16) Samsung Electronics Co.,Ltd Beijing 100190 CN -4C-EB-B0 (hex) Samsung Electronics Co.,Ltd -4CEBB0 (base 16) Samsung Electronics Co.,Ltd +8C-05-28 (hex) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD +8C0528 (base 16) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD + 6-8 Floor, Tower E3, No. 1001, Zhongshanyuan Road, Nanshan District, Shenzhen,China + Shenzhen 518052 + CN + +78-60-89 (hex) Samsung Electronics Co.,Ltd +786089 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR @@ -92735,6 +92753,30 @@ FC5708 (base 16) Broadcom Limited Austin TX 78735 US +9C-28-BF (hex) AUMOVIO Czech Republic s.r.o. +9C28BF (base 16) AUMOVIO Czech Republic s.r.o. + Průmyslová 1851 + Brandýs nad Labem 250 01 + CZ + +18-4C-AE (hex) AUMOVIO France S.A.S. +184CAE (base 16) AUMOVIO France S.A.S. + 1 AVENUE PAUL OURLIAC + TOULOUSE 31100 + FR + +E8-2D-79 (hex) AltoBeam Inc. +E82D79 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN + +AC-D3-FB (hex) Arycs Technologies Inc +ACD3FB (base 16) Arycs Technologies Inc + 718 University Ave Suite 200 + Los Gatos 95032 + US + 34-87-FB (hex) GTAI 3487FB (base 16) GTAI Room 208, Building B11, Yantian Industrial Zone, Yantian Community, Xixiang Street, Bao 'an District, @@ -92759,11 +92801,14 @@ E07291 (base 16) Silicon Laboratories Austin TX 78701 US -AC-D3-FB (hex) Arycs Technologies Inc -ACD3FB (base 16) Arycs Technologies Inc - 718 University Ave Suite 200 - Los Gatos 95032 - US +6C-81-66 (hex) Private +6C8166 (base 16) Private + +D0-EA-11 (hex) Routerboard.com +D0EA11 (base 16) Routerboard.com + Mikrotikls SIA + Riga Riga LV1009 + LV 2C-9D-90 (hex) Mellanox Technologies, Inc. 2C9D90 (base 16) Mellanox Technologies, Inc. @@ -92777,30 +92822,6 @@ E46DAB (base 16) Mellanox Technologies, Inc. Sunnyvale CA 94085 US -8C-05-28 (hex) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD -8C0528 (base 16) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD - 6-8 Floor, Tower E3, No. 1001, Zhongshanyuan Road, Nanshan District, Shenzhen,China - Shenzhen 518052 - CN - -90-74-AE (hex) AzureWave Technology Inc. -9074AE (base 16) AzureWave Technology Inc. - 8F., No. 94, Baozhong Rd. - New Taipei City Taiwan 231 - TW - -E8-2D-79 (hex) AltoBeam Inc. -E82D79 (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 - CN - -18-4C-AE (hex) AUMOVIO France S.A.S. -184CAE (base 16) AUMOVIO France S.A.S. - 1 AVENUE PAUL OURLIAC - TOULOUSE 31100 - FR - 00-54-AF (hex) AUMOVIO Systems, Inc. 0054AF (base 16) AUMOVIO Systems, Inc. 21440 W. Lake Cook Rd. @@ -92813,14 +92834,17 @@ E82D79 (base 16) AltoBeam Inc. Deer Park IL 60010 US -6C-81-66 (hex) Private -6C8166 (base 16) Private +90-74-AE (hex) AzureWave Technology Inc. +9074AE (base 16) AzureWave Technology Inc. + 8F., No. 94, Baozhong Rd. + New Taipei City Taiwan 231 + TW -D0-EA-11 (hex) Routerboard.com -D0EA11 (base 16) Routerboard.com - Mikrotikls SIA - Riga Riga LV1009 - LV +B8-51-1D (hex) TELECHIPS, INC +B8511D (base 16) TELECHIPS, INC + 27, Geumto-ro 80beon-gil, Sujeong-gu, + Seongnam-si, Gyeonggi-do, 13453 + KR D8-FC-92 (hex) Tuya Smart Inc. D8FC92 (base 16) Tuya Smart Inc. @@ -92834,17 +92858,23 @@ B4E25B (base 16) HP Inc. Spring TX 77389 US +F4-70-18 (hex) Hangzhou Ezviz Software Co.,Ltd. +F47018 (base 16) Hangzhou Ezviz Software Co.,Ltd. + 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District + Hangzhou Zhejiang 310051 + CN + DC-74-CE (hex) ITOCHU Techno-Solutions Corporation DC74CE (base 16) ITOCHU Techno-Solutions Corporation Kamiyacho Trust Tower, 4-1-1, Toranomon, Minato-ku, Tokyo Tokyo 105-6950 JP -B8-51-1D (hex) TELECHIPS, INC -B8511D (base 16) TELECHIPS, INC - 27, Geumto-ro 80beon-gil, Sujeong-gu, - Seongnam-si, Gyeonggi-do, 13453 - KR +4C-55-B2 (hex) Xiaomi Communications Co Ltd +4C55B2 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN 10-03-CD (hex) Calix Inc. 1003CD (base 16) Calix Inc. @@ -92852,11 +92882,47 @@ B8511D (base 16) TELECHIPS, INC San Jose CA 95131 US -9C-28-BF (hex) AUMOVIO Czech Republic s.r.o. -9C28BF (base 16) AUMOVIO Czech Republic s.r.o. - Průmyslová 1851 - Brandýs nad Labem 250 01 - CZ +98-2A-FD (hex) HUAWEI TECHNOLOGIES CO.,LTD +982AFD (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +2C-AE-46 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +2CAE46 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. + B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China + Nanning Guangxi 530007 + CN + +6C-77-42 (hex) zte corporation +6C7742 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +10-CD-54 (hex) HUAWEI TECHNOLOGIES CO.,LTD +10CD54 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +94-A2-5D (hex) HUAWEI TECHNOLOGIES CO.,LTD +94A25D (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +B4-C3-D9 (hex) HUAWEI TECHNOLOGIES CO.,LTD +B4C3D9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +54-26-18 (hex) HUAWEI TECHNOLOGIES CO.,LTD +542618 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN 0C-0F-D8 (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED 0C0FD8 (base 16) GSD VIET NAM TECHNOLOGY COMPANY LIMITED @@ -92876,12 +92942,6 @@ D41368 (base 16) Shenzhen Intellirocks Tech. Co. Ltd. Shenzhen Guangdong 518000 CN -F4-70-18 (hex) Hangzhou Ezviz Software Co.,Ltd. -F47018 (base 16) Hangzhou Ezviz Software Co.,Ltd. - 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District - Hangzhou Zhejiang 310051 - CN - E4-53-41 (hex) Apple, Inc. E45341 (base 16) Apple, Inc. 1 Infinite Loop @@ -92900,12 +92960,6 @@ E45341 (base 16) Apple, Inc. Cupertino CA 95014 US -4C-55-B2 (hex) Xiaomi Communications Co Ltd -4C55B2 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN - A4-22-B6 (hex) Motorola Mobility LLC, a Lenovo Company A422B6 (base 16) Motorola Mobility LLC, a Lenovo Company 222 West Merchandise Mart Plaza @@ -92924,46 +92978,16 @@ E0BA78 (base 16) Apple, Inc. Cupertino CA 95014 US -98-2A-FD (hex) HUAWEI TECHNOLOGIES CO.,LTD -982AFD (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -2C-AE-46 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. -2CAE46 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. - B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China - Nanning Guangxi 530007 - CN - -6C-77-42 (hex) zte corporation -6C7742 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN - -10-CD-54 (hex) HUAWEI TECHNOLOGIES CO.,LTD -10CD54 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -94-A2-5D (hex) HUAWEI TECHNOLOGIES CO.,LTD -94A25D (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -B4-C3-D9 (hex) HUAWEI TECHNOLOGIES CO.,LTD -B4C3D9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +90-20-D7 (hex) Microsoft Corporation +9020D7 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US -54-26-18 (hex) HUAWEI TECHNOLOGIES CO.,LTD -542618 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +80-2A-F6 (hex) Honor Device Co., Ltd. +802AF6 (base 16) Honor Device Co., Ltd. + Suite 3401, Unit A, Building 6, Shum Yip Sky Park, No. 8089, Hongli West Road, Xiangmihu Street, Futian District + Shenzhen Guangdong 518040 CN 88-99-86 (hex) TP-LINK TECHNOLOGIES CO.,LTD. @@ -92972,11 +92996,11 @@ B4C3D9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Shenzhen Guangdong 518057 CN -90-20-D7 (hex) Microsoft Corporation -9020D7 (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 - US +60-95-F8 (hex) Arcadyan Corporation +6095F8 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW 28-B2-0B (hex) NXP USA, Inc 28B20B (base 16) NXP USA, Inc @@ -92984,6 +93008,12 @@ B4C3D9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Austin TX 78735 US +00-11-1E (hex) B&R Industrial Automation GmbH +00111E (base 16) B&R Industrial Automation GmbH + B&R Strasse 1 + Eggelsberg       5142 + AT + 80-AF-9F (hex) eero inc. 80AF9F (base 16) eero inc. 660 3rd Street @@ -92996,11 +93026,17 @@ BC9C8D (base 16) Ruckus Wireless Sunnyvale CA 94089 US -64-70-84 (hex) AltoBeam Inc. -647084 (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 - CN +EC-50-A6 (hex) Sagemcom Broadband SAS +EC50A6 (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR + +0C-52-7F (hex) Check Point Software Technologies Ltd. +0C527F (base 16) Check Point Software Technologies Ltd. + 5 Ha'solelim St + Tel Aviv 67897 + IL 00-15-1E (hex) B&R Industrial Automation GmbH 00151E (base 16) B&R Industrial Automation GmbH @@ -93008,42 +93044,12 @@ BC9C8D (base 16) Ruckus Wireless Eggelsberg       5142 AT -80-2A-F6 (hex) Honor Device Co., Ltd. -802AF6 (base 16) Honor Device Co., Ltd. - Suite 3401, Unit A, Building 6, Shum Yip Sky Park, No. 8089, Hongli West Road, Xiangmihu Street, Futian District - Shenzhen Guangdong 518040 - CN - -00-A3-07 (hex) Honor Device Co., Ltd. -00A307 (base 16) Honor Device Co., Ltd. - Suite 3401, Unit A, Building 6, Shum Yip Sky Park, No. 8089, Hongli West Road, Xiangmihu Street, Futian District - Shenzhen Guangdong 518040 - CN - 64-D5-62 (hex) Huawei Device Co., Ltd. 64D562 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -60-95-F8 (hex) Arcadyan Corporation -6095F8 (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW - -00-11-1E (hex) B&R Industrial Automation GmbH -00111E (base 16) B&R Industrial Automation GmbH - B&R Strasse 1 - Eggelsberg       5142 - AT - -DC-15-5C (hex) Anntec (Beijing) Technology Co.,Ltd. -DC155C (base 16) Anntec (Beijing) Technology Co.,Ltd. - F803, Shangdi Third Street, No.9,HaiDian District - Beijing 100080 - CN - 08-94-EC (hex) Huawei Device Co., Ltd. 0894EC (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone @@ -93056,6 +93062,12 @@ CCB775 (base 16) Huawei Device Co., Ltd. Dongguan Guangdong 523808 CN +64-70-84 (hex) AltoBeam Inc. +647084 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN + 98-A3-75 (hex) Huawei Device Co., Ltd. 98A375 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone @@ -93068,6 +93080,18 @@ B8752E (base 16) Huawei Device Co., Ltd. Dongguan Guangdong 523808 CN +00-A3-07 (hex) Honor Device Co., Ltd. +00A307 (base 16) Honor Device Co., Ltd. + Suite 3401, Unit A, Building 6, Shum Yip Sky Park, No. 8089, Hongli West Road, Xiangmihu Street, Futian District + Shenzhen Guangdong 518040 + CN + +DC-15-5C (hex) Anntec (Beijing) Technology Co.,Ltd. +DC155C (base 16) Anntec (Beijing) Technology Co.,Ltd. + F803, Shangdi Third Street, No.9,HaiDian District + Beijing 100080 + CN + 10-A8-79 (hex) Intel Corporate 10A879 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 @@ -93098,24 +93122,18 @@ B8752E (base 16) Huawei Device Co., Ltd. Kulim Kedah 09000 MY -EC-50-A6 (hex) Sagemcom Broadband SAS -EC50A6 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -0C-52-7F (hex) Check Point Software Technologies Ltd. -0C527F (base 16) Check Point Software Technologies Ltd. - 5 Ha'solelim St - Tel Aviv 67897 - IL - 88-FE-B6 (hex) ASKEY COMPUTER CORP 88FEB6 (base 16) ASKEY COMPUTER CORP 10F,No.119,JIANKANG RD,ZHONGHE DIST NEW TAIPEI TAIWAN 23585 TW +EC-9B-75 (hex) Roku, Inc +EC9B75 (base 16) Roku, Inc + 1173 Coleman Ave + San Jose CA 95110 + US + 6C-56-40 (hex) BLU Products Inc 6C5640 (base 16) BLU Products Inc 8600 NW 36th Street Suite 200 @@ -93140,6 +93158,18 @@ EC50A6 (base 16) Sagemcom Broadband SAS Chengdu Sichuan 611330 CN +A4-A6-4E (hex) Mellanox Technologies, Inc. +A4A64E (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +2C-B1-B7 (hex) Mellanox Technologies, Inc. +2CB1B7 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + 9C-47-11 (hex) ACCTON TECHNOLOGY CORPORATION 9C4711 (base 16) ACCTON TECHNOLOGY CORPORATION No.1, Creation Road 3, Hsinchu Science Park, @@ -93152,28 +93182,34 @@ EC50A6 (base 16) Sagemcom Broadband SAS San Jose CA 95131 US +94-9C-BE (hex) HUAWEI TECHNOLOGIES CO.,LTD +949CBE (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +88-BA-74 (hex) Silicon Laboratories +88BA74 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + +88-C0-93 (hex) GIGAMEDIA +88C093 (base 16) GIGAMEDIA + 312 RUE DES HAUTS DE SAIGHIN CRT4 + LESQUIN FRANCE 59811 + FR + E8-EA-7C (hex) Shenzhen Amazwear Holdings Co., Ltd E8EA7C (base 16) Shenzhen Amazwear Holdings Co., Ltd 34th Floor, Chang Jiang Center, Crossroads of Renmin Road and Jianshe Road, Jingxin Community, Longhua Street,Longhua District Shenzhen Guangdong 518000 CN -EC-9B-75 (hex) Roku, Inc -EC9B75 (base 16) Roku, Inc - 1173 Coleman Ave - San Jose CA 95110 - US - -A4-A6-4E (hex) Mellanox Technologies, Inc. -A4A64E (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US - -2C-B1-B7 (hex) Mellanox Technologies, Inc. -2CB1B7 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 +18-5F-27 (hex) Motorola Mobility LLC, a Lenovo Company +185F27 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 US 0C-85-09 (hex) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD @@ -93182,18 +93218,6 @@ A4A64E (base 16) Mellanox Technologies, Inc. Shenzhen 518052 CN -94-9C-BE (hex) HUAWEI TECHNOLOGIES CO.,LTD -949CBE (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -88-BA-74 (hex) Silicon Laboratories -88BA74 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US - 80-79-EF (hex) SUB-ZERO GROUP, INC. 8079EF (base 16) SUB-ZERO GROUP, INC. 2835 Buds Drive @@ -93212,29 +93236,29 @@ A4A64E (base 16) Mellanox Technologies, Inc. Shanghai 200233 CN -18-5F-27 (hex) Motorola Mobility LLC, a Lenovo Company -185F27 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 +B4-C0-C3 (hex) TP-Link Systems Inc. +B4C0C3 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 US +3C-BE-8E (hex) Guangzhou Shiyuan Electronic Technology Company Limited +3CBE8E (base 16) Guangzhou Shiyuan Electronic Technology Company Limited + No.6, 4th Yunpu Road, Yunpu industry District + Guangzhou Guangdong 510530 + CN + 98-F0-4C (hex) Cisco Systems, Inc 98F04C (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -88-C0-93 (hex) GIGAMEDIA -88C093 (base 16) GIGAMEDIA - 312 RUE DES HAUTS DE SAIGHIN CRT4 - LESQUIN FRANCE 59811 - FR - -3C-BE-8E (hex) Guangzhou Shiyuan Electronic Technology Company Limited -3CBE8E (base 16) Guangzhou Shiyuan Electronic Technology Company Limited - No.6, 4th Yunpu Road, Yunpu industry District - Guangzhou Guangdong 510530 - CN +00-05-BA (hex) XK22 Enterprises, LLC +0005BA (base 16) XK22 Enterprises, LLC + 2646 Wooster Rd. + Rocky River OH 44116 + US 34-4A-86 (hex) Honor Device Co., Ltd. 344A86 (base 16) Honor Device Co., Ltd. @@ -93248,10 +93272,22 @@ DC69CC (base 16) LG Innotek Gwangju Gwangsan-gu 506-731 KR -B4-C0-C3 (hex) TP-Link Systems Inc. -B4C0C3 (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 +C0-2D-10 (hex) MOHAN ELECTRONICS AND SYSTEMS +C02D10 (base 16) MOHAN ELECTRONICS AND SYSTEMS + 571, STREET NO. 6, CHANDERLOK, SHAHDARA, DELHI + DELHI DELHI 110093 + IN + +74-98-F4 (hex) BUFFALO.INC +7498F4 (base 16) BUFFALO.INC + AKAMONDORI Bld.,30-20,Ohsu 3-chome,Naka-ku + Nagoya Aichi Pref. 460-8315 + JP + +0C-83-F4 (hex) Canopy Works, Inc. +0C83F4 (base 16) Canopy Works, Inc. + 1875 Mission St, Ste 103 + San Francisco CA 94103 US 20-33-89 (hex) Google, Inc. @@ -93260,17 +93296,17 @@ B4C0C3 (base 16) TP-Link Systems Inc. Mountain View CA 94043 US -00-05-BA (hex) XK22 Enterprises, LLC -0005BA (base 16) XK22 Enterprises, LLC - 2646 Wooster Rd. - Rocky River OH 44116 +D0-C6-BE (hex) HPRO-Video +D0C6BE (base 16) HPRO-Video + 8500 Balboa Blvd + Northridge CA 91329 US -F8-1E-49 (hex) Apple, Inc. -F81E49 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +84-AE-DE (hex) Xiaomi Communications Co Ltd +84AEDE (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN BC-74-EA (hex) Apple, Inc. BC74EA (base 16) Apple, Inc. @@ -93284,12 +93320,6 @@ BC74EA (base 16) Apple, Inc. Cupertino CA 95014 US -C0-2D-10 (hex) MOHAN ELECTRONICS AND SYSTEMS -C02D10 (base 16) MOHAN ELECTRONICS AND SYSTEMS - 571, STREET NO. 6, CHANDERLOK, SHAHDARA, DELHI - DELHI DELHI 110093 - IN - 18-B8-42 (hex) Apple, Inc. 18B842 (base 16) Apple, Inc. 1 Infinite Loop @@ -93302,59 +93332,107 @@ C02D10 (base 16) MOHAN ELECTRONICS AND SYSTEMS Cupertino CA 95014 US -74-98-F4 (hex) BUFFALO.INC -7498F4 (base 16) BUFFALO.INC - AKAMONDORI Bld.,30-20,Ohsu 3-chome,Naka-ku - Nagoya Aichi Pref. 460-8315 - JP +B8-0B-9A (hex) HUAWEI TECHNOLOGIES CO.,LTD +B80B9A (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -0C-83-F4 (hex) Canopy Works, Inc. -0C83F4 (base 16) Canopy Works, Inc. - 1875 Mission St, Ste 103 - San Francisco CA 94103 +F8-1E-49 (hex) Apple, Inc. +F81E49 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -D0-C6-BE (hex) HPRO-Video -D0C6BE (base 16) HPRO-Video - 8500 Balboa Blvd - Northridge CA 91329 +C8-26-91 (hex) Arista Networks, Inc. +C82691 (base 16) Arista Networks, Inc. + 5453 Great America Parkway + Santa Clara 95054 US -84-AE-DE (hex) Xiaomi Communications Co Ltd -84AEDE (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +00-13-AE (hex) Radiance Technologies, Inc. +0013AE (base 16) Radiance Technologies, Inc. + 310 Bob Heath Dr. + Huntsville 35806 + US + +68-C8-C0 (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED +68C8C0 (base 16) GSD VIET NAM TECHNOLOGY COMPANY LIMITED + PART OF FACTORY 2, LOT C2.10, D1 STREET, DONG AN 2 INDUSTRIAL PARK, BINHDUONG WARD + HO CHI MINH CITY HO CHI MINH 820000 + VN + +68-EE-8F (hex) Espressif Inc. +68EE8F (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +FC-E4-21 (hex) zhejiang Dusun Electron Co.,Ltd +FCE421 (base 16) zhejiang Dusun Electron Co.,Ltd + NO.640 FengQing str., + DeQing ZheJiang 313000 CN +20-41-BC (hex) ANY Electronics Co., Ltd +2041BC (base 16) ANY Electronics Co., Ltd + 9, Sanbon-ro 86beon-gil + Gunpo-si Gyeonggi-do 15847 + KR + CC-0C-9C (hex) CIG SHANGHAI CO LTD CC0C9C (base 16) CIG SHANGHAI CO LTD 5th Floor, Building 8 No 2388 Chenhang Road SHANGHAI 201114 CN +D4-66-63 (hex) Shenzhen Detran Technology Co.,Ltd. +D46663 (base 16) Shenzhen Detran Technology Co.,Ltd. + 201, F5 Building, TCL International E City, Zhongshanyuan Rd. Nanshan District + Shenzhen Guangdong 518052 + CN + A4-D7-D6 (hex) Shenzhen Linkoh Network Technology Co;Ltd A4D7D6 (base 16) Shenzhen Linkoh Network Technology Co;Ltd Yangguang Industrial Park, Hangcheng, Bao'an Shenzhen Guangdong 518000 CN -B8-0B-9A (hex) HUAWEI TECHNOLOGIES CO.,LTD -B80B9A (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +5C-A9-31 (hex) 38436 +5CA931 (base 16) 38436 + Flat/RM 1202, 12/F, AT Tower + North Point Hong Kong 180 + HK + +00-24-AE (hex) IDEMIA PUBLIC SECURITY FRANCE +0024AE (base 16) IDEMIA PUBLIC SECURITY FRANCE + 2 Place Samuel de Champlain + Courbevoie 92400 + FR + +B4-B6-50 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. +B4B650 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. + No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. + Chongqing China 401120 CN -C8-26-91 (hex) Arista Networks, Inc. -C82691 (base 16) Arista Networks, Inc. - 5453 Great America Parkway - Santa Clara 95054 - US +84-9C-A6 (hex) Arcadyan Corporation +849CA6 (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II , + Hsinchu 300 + TW -00-13-AE (hex) Radiance Technologies, Inc. -0013AE (base 16) Radiance Technologies, Inc. - 310 Bob Heath Dr. - Huntsville 35806 - US +00-26-4D (hex) Arcadyan Corporation +00264D (base 16) Arcadyan Corporation + 4F., No. 9 , Park Avenue II , + Hsinchu Taiwan 300 + TW + +EC-B5-AF (hex) RayService a.s. +ECB5AF (base 16) RayService a.s. + Huštěnovská 2022 + Staré Město Czech Republic 686 03 + CZ 6C-87-20 (hex) New H3C Technologies Co., Ltd 6C8720 (base 16) New H3C Technologies Co., Ltd @@ -102668,12 +102746,6 @@ AC8D34 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -74-31-70 (hex) Arcadyan Technology Corporation -743170 (base 16) Arcadyan Technology Corporation - 4F. , No. 9 , Park Avenue II, - Hsinchu 300 - TW - 40-11-75 (hex) IEEE Registration Authority 401175 (base 16) IEEE Registration Authority 445 Hoes Lane @@ -110834,24 +110906,6 @@ C4473F (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -5C-DC-96 (hex) Arcadyan Technology Corporation -5CDC96 (base 16) Arcadyan Technology Corporation - No.8, Sec.2, Guangfu Rd., - Hsinchu City 30071, 12345 - TW - -00-1A-2A (hex) Arcadyan Technology Corporation -001A2A (base 16) Arcadyan Technology Corporation - 4F., No. 9 , Park Avenue II, - Hsinchu 300 - TW - -88-25-2C (hex) Arcadyan Technology Corporation -88252C (base 16) Arcadyan Technology Corporation - 4F., NO.9, Park Avenue II , - Hsinchu 300 - TW - 00-E0-63 (hex) Cabletron Systems, Inc. 00E063 (base 16) Cabletron Systems, Inc. 35 INDUSTRIAL WAY @@ -110894,18 +110948,6 @@ D40129 (base 16) Broadcom Tel-aviv 12345 IL -1C-C6-3C (hex) Arcadyan Technology Corporation -1CC63C (base 16) Arcadyan Technology Corporation - 4F, No. 9, Park Avenue II , - Hsinchu 300 - TW - -18-83-BF (hex) Arcadyan Technology Corporation -1883BF (base 16) Arcadyan Technology Corporation - 4F, No. 9, Park Avenue II , - Hsinchu 300 - TW - 68-ED-43 (hex) BlackBerry RTS 68ED43 (base 16) BlackBerry RTS 451 Phillip Street @@ -137408,23 +137450,23 @@ F4F50B (base 16) TP-Link Systems Inc. Irvine CA 92618 US -34-56-FE (hex) Cisco Meraki -3456FE (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 +A4-D5-30 (hex) Avaya LLC +A4D530 (base 16) Avaya LLC + 350 Mt Kimble + Morristown NJ 07960 US -B8-07-56 (hex) Cisco Meraki -B80756 (base 16) Cisco Meraki +34-56-FE (hex) Cisco Meraki +3456FE (base 16) Cisco Meraki 500 Terry A. Francois Blvd San Francisco 94158 US -A4-D5-30 (hex) Avaya LLC -A4D530 (base 16) Avaya LLC - 350 Mt Kimble - Morristown NJ 07960 - US +4C-EF-56 (hex) Shenzhen Sundray Technologies company Limited +4CEF56 (base 16) Shenzhen Sundray Technologies company Limited + 5th Floor, Block A4, Nanshan ipark,NO.1001 Xue Yuan Road, Nanshan District, Shenzhen 518055, P.R. China + Shenzhen Guangdong 518057 + CN 94-14-57 (hex) Shenzhen Sundray Technologies company Limited 941457 (base 16) Shenzhen Sundray Technologies company Limited @@ -137444,29 +137486,17 @@ C8A23B (base 16) Shenzhen Sundray Technologies company Limited Shenzhen Guangdong 518057 CN -7C-15-2D (hex) Renesas Electronics (Penang) Sdn. Bhd. -7C152D (base 16) Renesas Electronics (Penang) Sdn. Bhd. - Phase 3, Bayan Lepas FIZ - Bayan Lepas Penang 11900 - MY - -D8-F1-2E (hex) TP-Link Systems Inc. -D8F12E (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 - US - A0-88-5E (hex) Anhui Xiangyao New Energy Technology Co., Ltd. A0885E (base 16) Anhui Xiangyao New Energy Technology Co., Ltd. No. 2, District 4, Intelligent Industrial Park, South District, Lieshan Economic Development Zone Huaibei City Anhui Province 235065 CN -4C-EF-56 (hex) Shenzhen Sundray Technologies company Limited -4CEF56 (base 16) Shenzhen Sundray Technologies company Limited - 5th Floor, Block A4, Nanshan ipark,NO.1001 Xue Yuan Road, Nanshan District, Shenzhen 518055, P.R. China - Shenzhen Guangdong 518057 - CN +7C-15-2D (hex) Renesas Electronics (Penang) Sdn. Bhd. +7C152D (base 16) Renesas Electronics (Penang) Sdn. Bhd. + Phase 3, Bayan Lepas FIZ + Bayan Lepas Penang 11900 + MY A4-DB-4C (hex) RAI Institute A4DB4C (base 16) RAI Institute @@ -137480,6 +137510,12 @@ A4DB4C (base 16) RAI Institute San Francisco CA 94107 US +B8-07-56 (hex) Cisco Meraki +B80756 (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US + 2C-91-AB (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH 2C91AB (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 @@ -137540,11 +137576,11 @@ E00855 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Berlin Berlin 10559 DE -10-3D-3E (hex) China Mobile Group Device Co.,Ltd. -103D3E (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN +D8-F1-2E (hex) TP-Link Systems Inc. +D8F12E (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US 90-47-3C (hex) China Mobile Group Device Co.,Ltd. 90473C (base 16) China Mobile Group Device Co.,Ltd. @@ -137582,36 +137618,6 @@ CC5CDE (base 16) China Mobile Group Device Co.,Ltd. Beijing 100053 CN -94-BE-09 (hex) China Mobile Group Device Co.,Ltd. -94BE09 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - -BC-9E-2C (hex) China Mobile Group Device Co.,Ltd. -BC9E2C (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - -C8-0C-53 (hex) China Mobile Group Device Co.,Ltd. -C80C53 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - -54-4D-D4 (hex) China Mobile Group Device Co.,Ltd. -544DD4 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - -C0-2D-2E (hex) China Mobile Group Device Co.,Ltd. -C02D2E (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - 70-89-CC (hex) China Mobile Group Device Co.,Ltd. 7089CC (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District @@ -137630,6 +137636,12 @@ AC710C (base 16) China Mobile Group Device Co.,Ltd. Beijing 100053 CN +10-3D-3E (hex) China Mobile Group Device Co.,Ltd. +103D3E (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + D0-CB-DD (hex) eero inc. D0CBDD (base 16) eero inc. 660 3rd Street @@ -137738,6 +137750,36 @@ B0CF0E (base 16) Mellanox Technologies, Inc. Sunnyvale CA 94085 US +94-BE-09 (hex) China Mobile Group Device Co.,Ltd. +94BE09 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +BC-9E-2C (hex) China Mobile Group Device Co.,Ltd. +BC9E2C (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +C8-0C-53 (hex) China Mobile Group Device Co.,Ltd. +C80C53 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +54-4D-D4 (hex) China Mobile Group Device Co.,Ltd. +544DD4 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +C0-2D-2E (hex) China Mobile Group Device Co.,Ltd. +C02D2E (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + 88-57-21 (hex) Espressif Inc. 885721 (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area @@ -137756,12 +137798,6 @@ D4A5B4 (base 16) Hengji Jiaye (Hangzhou) Technology Co., Ltd Hangzhou Zhejiang 310000 CN -A0-3C-20 (hex) Sagemcom Broadband SAS -A03C20 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 20-4D-52 (hex) Mellanox Technologies, Inc. 204D52 (base 16) Mellanox Technologies, Inc. 350 Oakmead Parkway, Suite 100 @@ -137780,30 +137816,6 @@ F02C59 (base 16) Chipsea Technologies (Shenzhen) Crop. Shenzhen 518108 CN -FC-39-5A (hex) SonicWall -FC395A (base 16) SonicWall - 1033 McCarthy Blvd - Milpitas CA 95035 - US - -0C-FE-7B (hex) Vantiva USA LLC -0CFE7B (base 16) Vantiva USA LLC - 4855 Peachtree Industrial Blvd, Suite 200 - Norcross GA 30902 - US - -B0-D5-FB (hex) Google, Inc. -B0D5FB (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 - US - -48-D0-1C (hex) AltoBeam Inc. -48D01C (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 - CN - 58-7A-B1 (hex) Shanghai Lixun Information Technology Co., Ltd. 587AB1 (base 16) Shanghai Lixun Information Technology Co., Ltd. Room 2111-L, No. 89 Yunling East Road @@ -137822,6 +137834,12 @@ B0D5FB (base 16) Google, Inc. Hangzhou Zhejiang 310052 CN +A0-3C-20 (hex) Sagemcom Broadband SAS +A03C20 (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR + C4-9A-89 (hex) Suzhou K-Hiragawa Electronic Technology Co.,Ltd C49A89 (base 16) Suzhou K-Hiragawa Electronic Technology Co.,Ltd No.1 Zhipu Road, Qiandeng Town @@ -137834,6 +137852,30 @@ ACBDF7 (base 16) Cisco Meraki San Francisco 94158 US +FC-39-5A (hex) SonicWall +FC395A (base 16) SonicWall + 1033 McCarthy Blvd + Milpitas CA 95035 + US + +0C-FE-7B (hex) Vantiva USA LLC +0CFE7B (base 16) Vantiva USA LLC + 4855 Peachtree Industrial Blvd, Suite 200 + Norcross GA 30902 + US + +B0-D5-FB (hex) Google, Inc. +B0D5FB (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US + +48-D0-1C (hex) AltoBeam Inc. +48D01C (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN + C8-17-F5 (hex) Nanjing Qinheng Microelectronics Co., Ltd. C817F5 (base 16) Nanjing Qinheng Microelectronics Co., Ltd. No.18, Ningshuang Road @@ -137852,54 +137894,36 @@ D8B2AA (base 16) zte corporation Ashburton Devon TQ13 7UP GB -2C-F8-14 (hex) Cisco Systems, Inc -2CF814 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - F4-15-63 (hex) F5 Inc. F41563 (base 16) F5 Inc. 1322 North Whitman Lane Liberty Lake WA 99019 US -CC-67-D8 (hex) Telin Semiconductor (Wuhan) Co.,Ltd -CC67D8 (base 16) Telin Semiconductor (Wuhan) Co.,Ltd - Room 1003 Buliding 5 , 2377 Shenkun Road ,Minhang District - Shanghai 201106 - CN - -64-AC-2B (hex) Juniper Networks -64AC2B (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US - -CC-22-FE (hex) Apple, Inc. -CC22FE (base 16) Apple, Inc. +34-DA-A1 (hex) Apple, Inc. +34DAA1 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -34-DA-A1 (hex) Apple, Inc. -34DAA1 (base 16) Apple, Inc. +CC-22-FE (hex) Apple, Inc. +CC22FE (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -CC-FA-F1 (hex) Sagemcom Broadband SAS -CCFAF1 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -2C-9D-5A (hex) Flaircomm Microelectronics,Inc. -2C9D5A (base 16) Flaircomm Microelectronics,Inc. - 7F,Guomai Building,Guomai Science and Technology Park,116 Jiangbin East Avenue,Mawei District,Fuzhou City - Fuzhou FUJIAN 350015 +CC-67-D8 (hex) Telin Semiconductor (Wuhan) Co.,Ltd +CC67D8 (base 16) Telin Semiconductor (Wuhan) Co.,Ltd + Room 1003 Buliding 5 , 2377 Shenkun Road ,Minhang District + Shanghai 201106 CN +2C-F8-14 (hex) Cisco Systems, Inc +2CF814 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + 88-DA-04 (hex) HUAWEI TECHNOLOGIES CO.,LTD 88DA04 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -137912,12 +137936,30 @@ CCFAF1 (base 16) Sagemcom Broadband SAS Dongguan 523808 CN +CC-FA-F1 (hex) Sagemcom Broadband SAS +CCFAF1 (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR + +2C-9D-5A (hex) Flaircomm Microelectronics,Inc. +2C9D5A (base 16) Flaircomm Microelectronics,Inc. + 7F,Guomai Building,Guomai Science and Technology Park,116 Jiangbin East Avenue,Mawei District,Fuzhou City + Fuzhou FUJIAN 350015 + CN + 24-34-08 (hex) Edgecore Americas Networking Corporation 243408 (base 16) Edgecore Americas Networking Corporation 20 Mason Irvine CA 92618 US +64-AC-2B (hex) Juniper Networks +64AC2B (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US + 84-D0-DB (hex) Guangdong Juan Intelligent Technology Joint Stock Co., Ltd. 84D0DB (base 16) Guangdong Juan Intelligent Technology Joint Stock Co., Ltd. The first and second floors of Building 2  (Plant No. 2), West Side of Shanxi Village, Dashi Street,Panyu District, Guangzhou @@ -137936,6 +137978,24 @@ A86D04 (base 16) Siemens AG Guangzhou Guangdong 510663 CN +44-38-8C (hex) Sumitomo Electric Industries, Ltd +44388C (base 16) Sumitomo Electric Industries, Ltd + 1-1-3, Shimaya, Konohana-ku + Osaka 554-0024 + JP + +7C-7B-BF (hex) Samsung Electronics Co.,Ltd +7C7BBF (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +8C-2E-72 (hex) Samsung Electronics Co.,Ltd +8C2E72 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + DC-70-35 (hex) Shengzhen Gongjin Electronics DC7035 (base 16) Shengzhen Gongjin Electronics No. 2 Danzi North Road, Kengzi Street, Pingshan District @@ -137966,12 +138026,6 @@ F8CF52 (base 16) Intel Corporate Kulim Kedah 09000 MY -44-38-8C (hex) Sumitomo Electric Industries, Ltd -44388C (base 16) Sumitomo Electric Industries, Ltd - 1-1-3, Shimaya, Konohana-ku - Osaka 554-0024 - JP - BC-51-5F (hex) Nokia Solutions and Networks India Private Limited BC515F (base 16) Nokia Solutions and Networks India Private Limited Plot 45, Fathima NagarNemilicherry,Chrompet @@ -137990,36 +138044,24 @@ D49D9D (base 16) Shenzhen Goodocom lnformation Technology Co.,Ltd. Shenzhen 518000 CN -7C-7B-BF (hex) Samsung Electronics Co.,Ltd -7C7BBF (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -8C-2E-72 (hex) Samsung Electronics Co.,Ltd -8C2E72 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - 80-F1-B2 (hex) Espressif Inc. 80F1B2 (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area Shanghai Shanghai 201203 CN -00-70-07 (hex) Espressif Inc. -007007 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 88-12-7D (hex) Shenzhen Melon Electronics Co.,Ltd 88127D (base 16) Shenzhen Melon Electronics Co.,Ltd 2F, Building E, Digital Silicon Valley, No.89 Hengping Road, Yuanshan Subdistrict, Longgang District, Shenzhen, Guangdong, China Shenzhen Guangdong 518100 CN +00-70-07 (hex) Espressif Inc. +007007 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + 44-CB-AD (hex) Xiaomi Communications Co Ltd 44CBAD (base 16) Xiaomi Communications Co Ltd #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road @@ -138092,30 +138134,24 @@ C4DBAD (base 16) Ring LLC Beijing Beijing 100083 CN -00-4B-0D (hex) Huawei Device Co., Ltd. -004B0D (base 16) Huawei Device Co., Ltd. +40-1C-D4 (hex) Huawei Device Co., Ltd. +401CD4 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -E0-40-27 (hex) Huawei Device Co., Ltd. -E04027 (base 16) Huawei Device Co., Ltd. +00-4B-0D (hex) Huawei Device Co., Ltd. +004B0D (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -40-1C-D4 (hex) Huawei Device Co., Ltd. -401CD4 (base 16) Huawei Device Co., Ltd. +E0-40-27 (hex) Huawei Device Co., Ltd. +E04027 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -D4-7B-6B (hex) Shanghai Cygnus Semiconductor Co., Ltd. -D47B6B (base 16) Shanghai Cygnus Semiconductor Co., Ltd. - Rooms 401 and 402, Building 5, 690 Bibo Road, China (Shanghai) Pilot Free Trade Zone - Shanghai Shanghai 201203 - CN - 00-02-71 (hex) Zhone Technologies, Inc. 000271 (base 16) Zhone Technologies, Inc. 7001 Oakport Street @@ -138128,17 +138164,11 @@ D47B6B (base 16) Shanghai Cygnus Semiconductor Co., Ltd. Oakland CA 94621 US -D0-96-FB (hex) Zhone Technologies, Inc. -D096FB (base 16) Zhone Technologies, Inc. - DASAN Tower 8F, 49 Daewangpangyo-ro644beon-gil Bundang-gu - Seongnam-si Gyeonggi-do 13493 - KR - -30-4F-75 (hex) Zhone Technologies, Inc. -304F75 (base 16) Zhone Technologies, Inc. - DASAN Tower 8F, 49 Daewangpangyo-ro644beon-gil Bundang-gu - Seongnam-si Gyeonggi-do 13493 - KR +F8-83-06 (hex) Beijing Xiaomi Mobile Software Co., Ltd +F88306 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 + CN 7C-C5-18 (hex) vivo Mobile Communication Co., Ltd. 7CC518 (base 16) vivo Mobile Communication Co., Ltd. @@ -138152,16 +138182,22 @@ D096FB (base 16) Zhone Technologies, Inc. shenzhen guangdong 518057 CN -A8-9A-8C (hex) zte corporation -A89A8C (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN +D0-96-FB (hex) Zhone Technologies, Inc. +D096FB (base 16) Zhone Technologies, Inc. + DASAN Tower 8F, 49 Daewangpangyo-ro644beon-gil Bundang-gu + Seongnam-si Gyeonggi-do 13493 + KR -F8-83-06 (hex) Beijing Xiaomi Mobile Software Co., Ltd -F88306 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 +30-4F-75 (hex) Zhone Technologies, Inc. +304F75 (base 16) Zhone Technologies, Inc. + DASAN Tower 8F, 49 Daewangpangyo-ro644beon-gil Bundang-gu + Seongnam-si Gyeonggi-do 13493 + KR + +D4-7B-6B (hex) Shanghai Cygnus Semiconductor Co., Ltd. +D47B6B (base 16) Shanghai Cygnus Semiconductor Co., Ltd. + Rooms 401 and 402, Building 5, 690 Bibo Road, China (Shanghai) Pilot Free Trade Zone + Shanghai Shanghai 201203 CN 98-61-10 (hex) HUAWEI TECHNOLOGIES CO.,LTD @@ -138200,6 +138236,12 @@ AC82F0 (base 16) Apple, Inc. Cupertino CA 95014 US +A8-9A-8C (hex) zte corporation +A89A8C (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + 68-4A-5F (hex) Apple, Inc. 684A5F (base 16) Apple, Inc. 1 Infinite Loop @@ -138218,12 +138260,6 @@ E898EE (base 16) Apple, Inc. Sunnyvale CA 94089 US -D8-1D-13 (hex) Texas Instruments -D81D13 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US - 10-5A-95 (hex) TP-Link Systems Inc. 105A95 (base 16) TP-Link Systems Inc. 10 Mauchly @@ -138254,6 +138290,12 @@ CC5EA5 (base 16) Palo Alto Networks Santa Clara CA 95054 US +D8-1D-13 (hex) Texas Instruments +D81D13 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + 14-75-E5 (hex) ELMAX Srl 1475E5 (base 16) ELMAX Srl Via dei Parietai, 2 @@ -138284,35 +138326,17 @@ E456CA (base 16) Fractal BMS Piscataway NJ 08554 US -B8-FE-90 (hex) Cisco Systems, Inc -B8FE90 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -34-C3-FD (hex) Cisco Systems, Inc -34C3FD (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -64-84-34 (hex) BEMER Int. AG -648434 (base 16) BEMER Int. AG - Austrasse 15 - Triesen 9495 - LI - F0-44-D3 (hex) Silicon Laboratories F044D3 (base 16) Silicon Laboratories 400 West Cesar Chavez Austin TX 78701 US -08-FD-58 (hex) HUAWEI TECHNOLOGIES CO.,LTD -08FD58 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +6C-47-25 (hex) Rochester Network Supply, Inc. +6C4725 (base 16) Rochester Network Supply, Inc. + 1319 Research Forest, + Macedon NY 14502 + US B4-43-89 (hex) HUAWEI TECHNOLOGIES CO.,LTD B44389 (base 16) HUAWEI TECHNOLOGIES CO.,LTD @@ -138320,11 +138344,11 @@ B44389 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -6C-47-25 (hex) Rochester Network Supply, Inc. -6C4725 (base 16) Rochester Network Supply, Inc. - 1319 Research Forest, - Macedon NY 14502 - US +08-FD-58 (hex) HUAWEI TECHNOLOGIES CO.,LTD +08FD58 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN 80-49-BF (hex) Taicang T&W Electronics 8049BF (base 16) Taicang T&W Electronics @@ -138332,18 +138356,24 @@ B44389 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Suzhou Jiangsu 215412 CN -6C-D8-FB (hex) Qorvo Utrecht B.V. -6CD8FB (base 16) Qorvo Utrecht B.V. - Leidseveer 10 - Utrecht Utrecht 3511 SB - NL - B0-97-E6 (hex) FUJIAN FUCAN WECON CO LTD B097E6 (base 16) FUJIAN FUCAN WECON CO LTD Wecon Tech Park, No.58 Jiangbin East Avenue, Mawei, Fuzhou, China FUZHOU FUJIAN 350015 CN +B8-FE-90 (hex) Cisco Systems, Inc +B8FE90 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +34-C3-FD (hex) Cisco Systems, Inc +34C3FD (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + B4-5C-B5 (hex) Mellanox Technologies, Inc. B45CB5 (base 16) Mellanox Technologies, Inc. 350 Oakmead Parkway, Suite 100 @@ -138356,6 +138386,24 @@ E8F60A (base 16) Espressif Inc. Shanghai Shanghai 201203 CN +6C-D8-FB (hex) Qorvo Utrecht B.V. +6CD8FB (base 16) Qorvo Utrecht B.V. + Leidseveer 10 + Utrecht Utrecht 3511 SB + NL + +64-84-34 (hex) BEMER Int. AG +648434 (base 16) BEMER Int. AG + Austrasse 15 + Triesen 9495 + LI + +24-A1-0D (hex) IEEE Registration Authority +24A10D (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + BC-89-F8 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. BC89F8 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. Midea Global Innovation Center,Beijiao Town,Shunde @@ -138368,48 +138416,6 @@ BC89F8 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. Wuxi jiangsu 214174 CN -F8-EF-B1 (hex) Hangzhou Zhongxinghui Intelligent Technology Co., Ltd. -F8EFB1 (base 16) Hangzhou Zhongxinghui Intelligent Technology Co., Ltd. - Room 809, Building B, No. 567 Yueming Road, Xixing Street, - Hangzhou Binjiang Distric 310000 - CN - -C0-2E-5F (hex) Zyxel Communications Corporation -C02E5F (base 16) Zyxel Communications Corporation - No. 6 Innovation Road II, Science Park - Hsichu Taiwan 300 - TW - -24-A1-0D (hex) IEEE Registration Authority -24A10D (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US - -00-27-13 (hex) Universal Global Scientific Industrial., Ltd -002713 (base 16) Universal Global Scientific Industrial., Ltd - 141, LANE 351,SEC.1, TAIPING RD. - TSAOTUEN, NANTOU 54261 - TW - -CC-52-AF (hex) Universal Global Scientific Industrial., Ltd -CC52AF (base 16) Universal Global Scientific Industrial., Ltd - 141, LANE 351, TAIPING RD. - nan tou NAN-TOU 542 - TW - -FC-4D-D4 (hex) Universal Global Scientific Industrial., Ltd -FC4DD4 (base 16) Universal Global Scientific Industrial., Ltd - 141, Lane 351, Taiping Rd. Sec. 1, Tsao Tuen, - Nan-Tou Hsien, 542 - TW - -08-3A-88 (hex) Universal Global Scientific Industrial., Ltd -083A88 (base 16) Universal Global Scientific Industrial., Ltd - 141, Lane 351, Taiping Road, Sec.1,Tsao Tuen - Nan-Tou Taiwan 54261 - TW - F8-F2-F0 (hex) Chipsea Technologies (Shenzhen) Crop. F8F2F0 (base 16) Chipsea Technologies (Shenzhen) Crop. Room 301, Building 1, Shenzhen Bay Innovation and Technology Center, Keyuan Avenue, High-tech Zone Community, Yuehai Subdistrict, Nanshan District, Shenzhen @@ -138422,24 +138428,18 @@ E42F37 (base 16) Apple, Inc. Cupertino CA 95014 US -A4-93-AD (hex) Huawei Device Co., Ltd. -A493AD (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +64-BD-6D (hex) Apple, Inc. +64BD6D (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -2C-3A-B1 (hex) Huawei Device Co., Ltd. -2C3AB1 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +F8-EF-B1 (hex) Hangzhou Zhongxinghui Intelligent Technology Co., Ltd. +F8EFB1 (base 16) Hangzhou Zhongxinghui Intelligent Technology Co., Ltd. + Room 809, Building B, No. 567 Yueming Road, Xixing Street, + Hangzhou Binjiang Distric 310000 CN -20-F1-B2 (hex) Tuya Smart Inc. -20F1B2 (base 16) Tuya Smart Inc. - 160 Greentree Drive, Suite 101 - Dover DE 19904 - US - A8-C0-50 (hex) Quectel Wireless Solutions Co.,Ltd. A8C050 (base 16) Quectel Wireless Solutions Co.,Ltd. 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District @@ -138452,35 +138452,23 @@ A8C050 (base 16) Quectel Wireless Solutions Co.,Ltd. Shanghai Shanghai 201203 CN -58-04-4F (hex) TP-Link Systems Inc. -58044F (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 - US - B4-B8-53 (hex) Honor Device Co., Ltd. B4B853 (base 16) Honor Device Co., Ltd. Suite 3401, Unit A, Building 6, Shum Yip Sky Park, No. 8089, Hongli West Road, Xiangmihu Street, Futian District Shenzhen Guangdong 518040 CN -E0-4F-43 (hex) Universal Global Scientific Industrial., Ltd -E04F43 (base 16) Universal Global Scientific Industrial., Ltd - 141, Lane 351, Taiping Road, Sec.1,Tsao Tuen - Nan-Tou Taiwan 54261 - TW - 08-3B-C1 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. 083BC1 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. No.555 Qianmo Road Hangzhou Zhejiang 310052 CN -64-BD-6D (hex) Apple, Inc. -64BD6D (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +C0-2E-5F (hex) Zyxel Communications Corporation +C02E5F (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW E8-C3-86 (hex) Apple, Inc. E8C386 (base 16) Apple, Inc. @@ -138488,46 +138476,28 @@ E8C386 (base 16) Apple, Inc. Cupertino CA 95014 US -2C-DC-AD (hex) WNC Corporation -2CDCAD (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW - -B8-B7-F1 (hex) WNC Corporation -B8B7F1 (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW - -44-E4-EE (hex) WNC Corporation -44E4EE (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW - -2C-9F-FB (hex) WNC Corporation -2C9FFB (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 +00-27-13 (hex) Universal Global Scientific Industrial., Ltd +002713 (base 16) Universal Global Scientific Industrial., Ltd + 141, LANE 351,SEC.1, TAIPING RD. + TSAOTUEN, NANTOU 54261 TW -1C-D6-BE (hex) WNC Corporation -1CD6BE (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 +CC-52-AF (hex) Universal Global Scientific Industrial., Ltd +CC52AF (base 16) Universal Global Scientific Industrial., Ltd + 141, LANE 351, TAIPING RD. + nan tou NAN-TOU 542 TW -70-61-BE (hex) WNC Corporation -7061BE (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 +FC-4D-D4 (hex) Universal Global Scientific Industrial., Ltd +FC4DD4 (base 16) Universal Global Scientific Industrial., Ltd + 141, Lane 351, Taiping Rd. Sec. 1, Tsao Tuen, + Nan-Tou Hsien, 542 TW -60-E6-F0 (hex) WNC Corporation -60E6F0 (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 +08-3A-88 (hex) Universal Global Scientific Industrial., Ltd +083A88 (base 16) Universal Global Scientific Industrial., Ltd + 141, Lane 351, Taiping Road, Sec.1,Tsao Tuen + Nan-Tou Taiwan 54261 TW 20-0D-3D (hex) Quectel Wireless Solutions Co., Ltd. @@ -138548,6 +138518,12 @@ B0CBD8 (base 16) Espressif Inc. Shanghai Shanghai 201203 CN +8C-57-9B (hex) WNC Corporation +8C579B (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + 80-EA-23 (hex) WNC Corporation 80EA23 (base 16) WNC Corporation 20 Park Avenue II, Hsin Science Park, Hsinchu 308, Taiwan @@ -138572,12 +138548,6 @@ BC307E (base 16) WNC Corporation Hsinchu 30808854 TW -00-1B-B1 (hex) WNC Corporation -001BB1 (base 16) WNC Corporation - No. 10-1, Li-hsin Road I, Hsinchu Science Park, - Hsinchu 300 - TW - E4-8E-C5 (hex) HUAWEI TECHNOLOGIES CO.,LTD E48EC5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -138590,41 +138560,71 @@ E48EC5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -A4-61-85 (hex) Tools for Humanity Corporation -A46185 (base 16) Tools for Humanity Corporation - 650 7th St - San Francisco CA 94103 +A4-93-AD (hex) Huawei Device Co., Ltd. +A493AD (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +2C-3A-B1 (hex) Huawei Device Co., Ltd. +2C3AB1 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +20-F1-B2 (hex) Tuya Smart Inc. +20F1B2 (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 US -8C-57-9B (hex) WNC Corporation -8C579B (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 +58-04-4F (hex) TP-Link Systems Inc. +58044F (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US + +E0-4F-43 (hex) Universal Global Scientific Industrial., Ltd +E04F43 (base 16) Universal Global Scientific Industrial., Ltd + 141, Lane 351, Taiping Road, Sec.1,Tsao Tuen + Nan-Tou Taiwan 54261 TW -7C-A2-36 (hex) Verizon Connect -7CA236 (base 16) Verizon Connect - 5055 North Point Pkwy - Alpharetta GA 30022 +A4-61-85 (hex) Tools for Humanity Corporation +A46185 (base 16) Tools for Humanity Corporation + 650 7th St + San Francisco CA 94103 US -88-A6-8D (hex) Shanghai MXCHIP Information Technology Co., Ltd. -88A68D (base 16) Shanghai MXCHIP Information Technology Co., Ltd. - 9th Floor, No. 5 Building, 2145 Jinshajiang Rd., Putuo District - Shanghai 200333 - CN - D4-4F-14 (hex) Tesla,Inc. D44F14 (base 16) Tesla,Inc. 3500 Deer Creek Rd. PALO ALTO CA 94304 US -34-26-E6 (hex) CIG SHANGHAI CO LTD -3426E6 (base 16) CIG SHANGHAI CO LTD - 5th Floor, Building 8 No 2388 Chenhang Road - SHANGHAI 201114 - CN +2C-9F-FB (hex) WNC Corporation +2C9FFB (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + +1C-D6-BE (hex) WNC Corporation +1CD6BE (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + +70-61-BE (hex) WNC Corporation +7061BE (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + +60-E6-F0 (hex) WNC Corporation +60E6F0 (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW 38-B8-00 (hex) WNC Corporation 38B800 (base 16) WNC Corporation @@ -138638,6 +138638,42 @@ D44F14 (base 16) Tesla,Inc. Hsin-Chu R.O.C. 308 TW +00-1B-B1 (hex) WNC Corporation +001BB1 (base 16) WNC Corporation + No. 10-1, Li-hsin Road I, Hsinchu Science Park, + Hsinchu 300 + TW + +2C-DC-AD (hex) WNC Corporation +2CDCAD (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + +B8-B7-F1 (hex) WNC Corporation +B8B7F1 (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + +44-E4-EE (hex) WNC Corporation +44E4EE (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + +7C-A2-36 (hex) Verizon Connect +7CA236 (base 16) Verizon Connect + 5055 North Point Pkwy + Alpharetta GA 30022 + US + +88-A6-8D (hex) Shanghai MXCHIP Information Technology Co., Ltd. +88A68D (base 16) Shanghai MXCHIP Information Technology Co., Ltd. + 9th Floor, No. 5 Building, 2145 Jinshajiang Rd., Putuo District + Shanghai 200333 + CN + 28-2E-89 (hex) WNC Corporation 282E89 (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park @@ -138674,12 +138710,6 @@ C06E3D (base 16) SHENZHEN TECNO TECHNOLOGY Wuhan Hubei 430074 CN -94-27-0E (hex) Intel Corporate -94270E (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - 7C-01-3E (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED 7C013E (base 16) GSD VIET NAM TECHNOLOGY COMPANY LIMITED PART OF FACTORY 2, LOT C2.10, D1 STREET, DONG AN 2 INDUSTRIAL PARK, BINHDUONG WARD @@ -138698,29 +138728,29 @@ C06E3D (base 16) SHENZHEN TECNO TECHNOLOGY Shimizu Village Shizuoka Prefecture 424-0926 JP +34-26-E6 (hex) CIG SHANGHAI CO LTD +3426E6 (base 16) CIG SHANGHAI CO LTD + 5th Floor, Building 8 No 2388 Chenhang Road + SHANGHAI 201114 + CN + 54-96-CB (hex) AMPAK Technology Inc. 5496CB (base 16) AMPAK Technology Inc. 6F., No23, Huanke 1st Rd. Zhubei City Hsinchu County 302047 TW -90-41-B2 (hex) Ubiquiti Inc -9041B2 (base 16) Ubiquiti Inc - 685 Third Avenue, 27th Floor - New York NY New York NY 10017 - US - D0-17-B7 (hex) Atios AG D017B7 (base 16) Atios AG Obere Postmatte 19 Seedorf Uri 6462 CH -F0-D3-2B (hex) Juniper Networks -F0D32B (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +94-27-0E (hex) Intel Corporate +94270E (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY 00-1E-D1 (hex) TKH Security B.V. 001ED1 (base 16) TKH Security B.V. @@ -138728,29 +138758,11 @@ F0D32B (base 16) Juniper Networks Zoetermeer ZH 2712PN NL -18-FD-00 (hex) Marelli -18FD00 (base 16) Marelli - Avenida da Emancipação, 801 - Hostolândia São Paulo 13184-9074 - BR - -A4-C1-39 (hex) Dongguan Huayin Electronic Technology Co., Ltd. -A4C139 (base 16) Dongguan Huayin Electronic Technology Co., Ltd. - Room 101, No.8 Xinglong 3rd Road, Shipai Town - Dongguan Guangdong 523000 - CN - -60-D8-77 (hex) Phyplus Technology (Shanghai) Co., Ltd -60D877 (base 16) Phyplus Technology (Shanghai) Co., Ltd - 3th Floor, Building 23, 676 Wuxing Road, Pudong New District, Shanghai - Shanghai Shanghai 201204 - CN - -84-FC-14 (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD -84FC14 (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD - NO.268, Fuqian Rd, Jutang community, Guanlan Town, Longhua New district - shenzhen guangdong 518000 - CN +90-41-B2 (hex) Ubiquiti Inc +9041B2 (base 16) Ubiquiti Inc + 685 Third Avenue, 27th Floor + New York NY New York NY 10017 + US 54-39-76 (hex) Groq 543976 (base 16) Groq @@ -138788,23 +138800,35 @@ A4C139 (base 16) Dongguan Huayin Electronic Technology Co., Ltd. Austin TX 78701 US -D8-3A-36 (hex) AltoBeam Inc. -D83A36 (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 +F0-D3-2B (hex) Juniper Networks +F0D32B (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US + +18-FD-00 (hex) Marelli +18FD00 (base 16) Marelli + Avenida da Emancipação, 801 + Hostolândia São Paulo 13184-9074 + BR + +A4-C1-39 (hex) Dongguan Huayin Electronic Technology Co., Ltd. +A4C139 (base 16) Dongguan Huayin Electronic Technology Co., Ltd. + Room 101, No.8 Xinglong 3rd Road, Shipai Town + Dongguan Guangdong 523000 CN -F0-55-82 (hex) Arashi Vision Inc. -F05582 (base 16) Arashi Vision Inc. - Room 1101, 1102, 1103, 11th Floor, Building 2, Jinlitong Financial Center, 1100 Xingye Road, Haiwang Community, Xin'an Street, Bao'an District, Shenzhen, Guangdong, China - Shenzhen 518000 +60-D8-77 (hex) Phyplus Technology (Shanghai) Co., Ltd +60D877 (base 16) Phyplus Technology (Shanghai) Co., Ltd + 3th Floor, Building 23, 676 Wuxing Road, Pudong New District, Shanghai + Shanghai Shanghai 201204 CN -6C-76-F7 (hex) MainStreaming SpA -6C76F7 (base 16) MainStreaming SpA - Viale Sarca, 336/F - Milan MI 20126 - IT +84-FC-14 (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD +84FC14 (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD + NO.268, Fuqian Rd, Jutang community, Guanlan Town, Longhua New district + shenzhen guangdong 518000 + CN 68-AB-A9 (hex) Sagemcom Broadband SAS 68ABA9 (base 16) Sagemcom Broadband SAS @@ -138812,11 +138836,11 @@ F05582 (base 16) Arashi Vision Inc. Rueil Malmaison Cedex hauts de seine 92848 FR -F0-BC-50 (hex) Mellanox Technologies, Inc. -F0BC50 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US +6C-76-F7 (hex) MainStreaming SpA +6C76F7 (base 16) MainStreaming SpA + Viale Sarca, 336/F + Milan MI 20126 + IT 40-A4-4A (hex) Google, Inc. 40A44A (base 16) Google, Inc. @@ -138824,34 +138848,22 @@ F0BC50 (base 16) Mellanox Technologies, Inc. Mountain View CA 94043 US -0C-F2-F5 (hex) Sichuan AI-Link Technology Co., Ltd. -0CF2F5 (base 16) Sichuan AI-Link Technology Co., Ltd. - Anzhou, Industrial Park - Mianyang Sichuan 622650 - CN - -D0-45-8D (hex) Shenzhen Skyworth Digital Technology CO., Ltd -D0458D (base 16) Shenzhen Skyworth Digital Technology CO., Ltd - 4F,Block A, Skyworth?Building, - Shenzhen Guangdong 518057 +D8-3A-36 (hex) AltoBeam Inc. +D83A36 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 CN -9C-24-10 (hex) Bouffalo Lab (Nanjing) Co., Ltd. -9C2410 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. - 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China - Nanjing Jiangsu 211800 +F0-55-82 (hex) Arashi Vision Inc. +F05582 (base 16) Arashi Vision Inc. + Room 1101, 1102, 1103, 11th Floor, Building 2, Jinlitong Financial Center, 1100 Xingye Road, Haiwang Community, Xin'an Street, Bao'an District, Shenzhen, Guangdong, China + Shenzhen 518000 CN -94-18-65 (hex) NETGEAR -941865 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US - -E0-46-EE (hex) NETGEAR -E046EE (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 +F0-BC-50 (hex) Mellanox Technologies, Inc. +F0BC50 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 US 00-18-4D (hex) NETGEAR @@ -138902,41 +138914,35 @@ B03956 (base 16) NETGEAR San Jose CA 95134 US +0C-F2-F5 (hex) Sichuan AI-Link Technology Co., Ltd. +0CF2F5 (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou, Industrial Park + Mianyang Sichuan 622650 + CN + +D0-45-8D (hex) Shenzhen Skyworth Digital Technology CO., Ltd +D0458D (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, + Shenzhen Guangdong 518057 + CN + 3C-1A-CC (hex) Quectel Wireless Solutions Co.,Ltd. 3C1ACC (base 16) Quectel Wireless Solutions Co.,Ltd. 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District Shanghai 200233 CN -38-FB-A0 (hex) Shenzhen Baseus Technology CoLtd -38FBA0 (base 16) Shenzhen Baseus Technology CoLtd - 2nd Floor, Building B, Baseus Intelligence Park, No.2008, Xuegang Rd,Gangtou Community,Bantian Street, Longgang District - Shenzhen 518172 - CN - A4-B2-56 (hex) Shenzhen Incar Technology Co., Ltd. A4B256 (base 16) Shenzhen Incar Technology Co., Ltd. 18th Floor, Zhongxi ECO Building, Shuiku Road, Xixiang Street, Bao'an District, Shenzhen City Guangdong Province 518102 CN -58-98-35 (hex) Vantiva Technologies Belgium -589835 (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE - -C4-EA-1D (hex) Vantiva Technologies Belgium -C4EA1D (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE - -A4-91-B1 (hex) Vantiva Technologies Belgium -A491B1 (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE +9C-24-10 (hex) Bouffalo Lab (Nanjing) Co., Ltd. +9C2410 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. + 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China + Nanjing Jiangsu 211800 + CN D8-E3-74 (hex) Xiaomi Communications Co Ltd D8E374 (base 16) Xiaomi Communications Co Ltd @@ -138944,17 +138950,17 @@ D8E374 (base 16) Xiaomi Communications Co Ltd Beijing Haidian District 100085 CN -D8-78-F0 (hex) Silicon Laboratories -D878F0 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 +94-18-65 (hex) NETGEAR +941865 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US -D4-35-1D (hex) Vantiva Technologies Belgium -D4351D (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE +E0-46-EE (hex) NETGEAR +E046EE (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US 60-3F-FB (hex) Telink Micro LLC 603FFB (base 16) Telink Micro LLC @@ -138962,18 +138968,6 @@ D4351D (base 16) Vantiva Technologies Belgium Santa Clara 95054 US -00-7A-A4 (hex) FRITZ! Technology GmbH -007AA4 (base 16) FRITZ! Technology GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE - -20-B8-3D (hex) UNIONMAN TECHNOLOGY CO.,LTD -20B83D (base 16) UNIONMAN TECHNOLOGY CO.,LTD - No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway - Huizhou Guangdong 516025 - CN - AC-EA-70 (hex) ZUNDA Inc. ACEA70 (base 16) ZUNDA Inc. 3/F Kamon Bldg, Shibuya 2-6-11 @@ -138992,6 +138986,30 @@ ACEA70 (base 16) ZUNDA Inc. Nanzi Dist. Kaohsiung 811643 TW +38-FB-A0 (hex) Shenzhen Baseus Technology CoLtd +38FBA0 (base 16) Shenzhen Baseus Technology CoLtd + 2nd Floor, Building B, Baseus Intelligence Park, No.2008, Xuegang Rd,Gangtou Community,Bantian Street, Longgang District + Shenzhen 518172 + CN + +C4-EA-1D (hex) Vantiva Technologies Belgium +C4EA1D (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE + +A4-91-B1 (hex) Vantiva Technologies Belgium +A491B1 (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE + +D4-35-1D (hex) Vantiva Technologies Belgium +D4351D (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE + EC-5B-71 (hex) Inventec(Chongqing) Corporation EC5B71 (base 16) Inventec(Chongqing) Corporation No.66 West District 2nd Rd, Shapingba District @@ -139010,17 +139028,11 @@ EC5B71 (base 16) Inventec(Chongqing) Corporation Dongguan 523808 CN -B0-FB-15 (hex) Ezurio, LLC -B0FB15 (base 16) Ezurio, LLC - 3F.-1, No.145, Xianzheng 9th Rd., - Zhubei 30251 - TW - -EC-C0-7A (hex) Ezurio, LLC -ECC07A (base 16) Ezurio, LLC - 3F.-1, No.145, Xianzheng 9th Rd., - Zhubei 30251 - TW +D8-78-F0 (hex) Silicon Laboratories +D878F0 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US 2C-2F-F4 (hex) eero inc. 2C2FF4 (base 16) eero inc. @@ -139028,12 +139040,60 @@ ECC07A (base 16) Ezurio, LLC San Francisco CA 94107 US +58-98-35 (hex) Vantiva Technologies Belgium +589835 (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE + +3C-49-FF (hex) UNIONMAN TECHNOLOGY CO.,LTD +3C49FF (base 16) UNIONMAN TECHNOLOGY CO.,LTD + No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway + Huizhou Guangdong 516025 + CN + +00-7A-A4 (hex) FRITZ! Technology GmbH +007AA4 (base 16) FRITZ! Technology GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +20-B8-3D (hex) UNIONMAN TECHNOLOGY CO.,LTD +20B83D (base 16) UNIONMAN TECHNOLOGY CO.,LTD + No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway + Huizhou Guangdong 516025 + CN + +4C-E6-50 (hex) Apple, Inc. +4CE650 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +24-55-9A (hex) Apple, Inc. +24559A (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + 24-53-ED (hex) Dell Inc. 2453ED (base 16) Dell Inc. One Dell Way Round Rock TX 78682 US +B0-FB-15 (hex) Ezurio, LLC +B0FB15 (base 16) Ezurio, LLC + 3F.-1, No.145, Xianzheng 9th Rd., + Zhubei 30251 + TW + +EC-C0-7A (hex) Ezurio, LLC +ECC07A (base 16) Ezurio, LLC + 3F.-1, No.145, Xianzheng 9th Rd., + Zhubei 30251 + TW + A4-CF-03 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. A4CF03 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. @@ -139076,22 +139136,28 @@ B082E2 (base 16) ASUSTek COMPUTER INC. Taipei Taiwan 112 TW -3C-49-FF (hex) UNIONMAN TECHNOLOGY CO.,LTD -3C49FF (base 16) UNIONMAN TECHNOLOGY CO.,LTD - No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway - Huizhou Guangdong 516025 +6C-68-A4 (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. +6C68A4 (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. + 13/F, Building 1, No.13 Bohua 4th Road, Huangpu District + Guangzhou Guangdong 510663 CN -24-55-9A (hex) Apple, Inc. -24559A (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +90-C9-52 (hex) Durin, Inc +90C952 (base 16) Durin, Inc + 440 N Wolfe Rd + Sunnyvale CA 94085 US -4C-E6-50 (hex) Apple, Inc. -4CE650 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +DC-22-6F (hex) HangZhou Nano IC Technologies Co., Ltd +DC226F (base 16) HangZhou Nano IC Technologies Co., Ltd + No. 11, F Building, 1st Floor, Building A, Tiantang Software Park, 3 West Doudimen Road, Xihu District + Hangzhou Zhejiang 310013 + CN + +0C-C7-63 (hex) eero inc. +0CC763 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 US EC-1A-C3 (hex) Ugreen Group Limited @@ -139112,6 +139178,12 @@ BCBCCA (base 16) Huawei Device Co., Ltd. Dongguan Guangdong 523808 CN +B8-64-68 (hex) BBSakura Networks, Inc. +B86468 (base 16) BBSakura Networks, Inc. + Sumitomo Fudosan Nishishinjuku Building, 7-20-1 Nishi-shinjuku + Shinjuku-ku Tokyo 160-0023 + JP + A0-FD-D9 (hex) UNIONMAN TECHNOLOGY CO.,LTD A0FDD9 (base 16) UNIONMAN TECHNOLOGY CO.,LTD No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway @@ -139124,11 +139196,17 @@ BC2B1E (base 16) Cresyn Co., Ltd. Seoul CRESYN B/D, Gangnam-daero 107-gil 06254 KR -6C-68-A4 (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. -6C68A4 (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. - 13/F, Building 1, No.13 Bohua 4th Road, Huangpu District - Guangzhou Guangdong 510663 - CN +08-6A-0B (hex) Cisco Meraki +086A0B (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US + +C8-63-40 (hex) Cisco Meraki +C86340 (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US A4-C0-B0 (hex) Drivenets A4C0B0 (base 16) Drivenets @@ -139136,22 +139214,28 @@ A4C0B0 (base 16) Drivenets Raanana Israel 4366235 IL -90-C9-52 (hex) Durin, Inc -90C952 (base 16) Durin, Inc - 440 N Wolfe Rd - Sunnyvale CA 94085 - US +34-F0-15 (hex) Beijing Xiaomi Mobile Software Co., Ltd +34F015 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 + CN -DC-22-6F (hex) HangZhou Nano IC Technologies Co., Ltd -DC226F (base 16) HangZhou Nano IC Technologies Co., Ltd - No. 11, F Building, 1st Floor, Building A, Tiantang Software Park, 3 West Doudimen Road, Xihu District - Hangzhou Zhejiang 310013 +A0-F2-62 (hex) Espressif Inc. +A0F262 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 CN -0C-C7-63 (hex) eero inc. -0CC763 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +A0-58-66 (hex) Qorvo Utrecht B.V. +A05866 (base 16) Qorvo Utrecht B.V. + Leidseveer 10 + Utrecht Utrecht 3511 SB + NL + +7C-6D-12 (hex) Microsoft Corporation +7C6D12 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 US 98-A9-42 (hex) Tozed Kangwei Tech Co., Ltd @@ -139172,36 +139256,48 @@ ACE011 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -B8-64-68 (hex) BBSakura Networks, Inc. -B86468 (base 16) BBSakura Networks, Inc. - Sumitomo Fudosan Nishishinjuku Building, 7-20-1 Nishi-shinjuku - Shinjuku-ku Tokyo 160-0023 - JP +CC-C4-B2 (hex) Shenzhen Trolink Technology Co.,LTD +CCC4B2 (base 16) Shenzhen Trolink Technology Co.,LTD + 7th Floor, Building 5, Phase 2, Donghua Intelligent Manufacturing Park, Sanwei Community, Hangcheng Street, Bao'an District + Shenzhen Guangdong 518101 + CN -08-6A-0B (hex) Cisco Meraki -086A0B (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 - US +60-B4-A2 (hex) Samsung Electronics Co.,Ltd +60B4A2 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -C8-63-40 (hex) Cisco Meraki -C86340 (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 - US +14-0B-9E (hex) Samsung Electronics Co.,Ltd +140B9E (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -34-F0-15 (hex) Beijing Xiaomi Mobile Software Co., Ltd -34F015 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 +64-CE-0C (hex) Funshion Online Technologies Co.,Ltd +64CE0C (base 16) Funshion Online Technologies Co.,Ltd + 2101, Floor 1-2, Building 9, Anzhen Xili District 3, Chaoyang District, Beijing + Beijing 100029 CN +84-48-80 (hex) Amazon Technologies Inc. +844880 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + FC-26-8C (hex) Signify B.V. FC268C (base 16) Signify B.V. High Tech Campus 7 Eindhoven 5656AE NL +30-F0-3A (hex) UEI Electronics Private Ltd. +30F03A (base 16) UEI Electronics Private Ltd. + #49, 1 st floor, East wing, Khanjabhavan, Racecourse Road. + Bengaluru Karnataka 560001 + IN + E8-3D-C1 (hex) Espressif Inc. E83DC1 (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area @@ -139226,60 +139322,12 @@ B8C924 (base 16) Cisco Systems, Inc San Jose CA 94568 US -A0-F2-62 (hex) Espressif Inc. -A0F262 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -A0-58-66 (hex) Qorvo Utrecht B.V. -A05866 (base 16) Qorvo Utrecht B.V. - Leidseveer 10 - Utrecht Utrecht 3511 SB - NL - -7C-6D-12 (hex) Microsoft Corporation -7C6D12 (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 - US - -CC-C4-B2 (hex) Shenzhen Trolink Technology Co.,LTD -CCC4B2 (base 16) Shenzhen Trolink Technology Co.,LTD - 7th Floor, Building 5, Phase 2, Donghua Intelligent Manufacturing Park, Sanwei Community, Hangcheng Street, Bao'an District - Shenzhen Guangdong 518101 - CN - -60-B4-A2 (hex) Samsung Electronics Co.,Ltd -60B4A2 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -14-0B-9E (hex) Samsung Electronics Co.,Ltd -140B9E (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -64-CE-0C (hex) Funshion Online Technologies Co.,Ltd -64CE0C (base 16) Funshion Online Technologies Co.,Ltd - 2101, Floor 1-2, Building 9, Anzhen Xili District 3, Chaoyang District, Beijing - Beijing 100029 - CN - 88-13-C2 (hex) Tendyron Corporation 8813C2 (base 16) Tendyron Corporation Tendyron Building,Zhongguancun NO.1 Park,Beiqing Road,Haidian District,Beijing,China Beijing 100000 CN -84-48-80 (hex) Amazon Technologies Inc. -844880 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US - 00-12-4F (hex) Chemelex LLC 00124F (base 16) Chemelex LLC 1665 Utica Avenue, Suite 700 @@ -139298,11 +139346,17 @@ B4DDD0 (base 16) AUMOVIO Hungary Kft. Budapest Pest H-1106 HU -30-F0-3A (hex) UEI Electronics Private Ltd. -30F03A (base 16) UEI Electronics Private Ltd. - #49, 1 st floor, East wing, Khanjabhavan, Racecourse Road. - Bengaluru Karnataka 560001 - IN +44-BD-C8 (hex) Xiaomi Communications Co Ltd +44BDC8 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + +30-48-7D (hex) Tuya Smart Inc. +30487D (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 + US 94-2D-3A (hex) PRIZOR VIZTECH LIMITED 942D3A (base 16) PRIZOR VIZTECH LIMITED @@ -139316,24 +139370,30 @@ B4DDD0 (base 16) AUMOVIO Hungary Kft. San Jose CA 95002 US -44-BD-C8 (hex) Xiaomi Communications Co Ltd -44BDC8 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN - -30-48-7D (hex) Tuya Smart Inc. -30487D (base 16) Tuya Smart Inc. - 160 Greentree Drive, Suite 101 - Dover DE 19904 - US - D0-CD-BF (hex) LG Electronics D0CDBF (base 16) LG Electronics 222 LG-ro, JINWI-MYEON Pyeongtaek-si Gyeonggi-do 451-713 KR +A4-F0-1F (hex) CANON INC. +A4F01F (base 16) CANON INC. + 30-2 Shimomaruko 3-chome, + Ohta-ku Tokyo 146-8501 + JP + +90-B9-F9 (hex) Motorola Mobility LLC, a Lenovo Company +90B9F9 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US + +04-6F-00 (hex) LG Electronics +046F00 (base 16) LG Electronics + Lot CN02, Trang Due Industrial Park,An Phong Ward, Hai Phong City, Vietnam + Hai Phong 184956 + VN + 94-EA-E7 (hex) Lynq Technologies 94EAE7 (base 16) Lynq Technologies 11101 West 120th Avenue @@ -139352,23 +139412,23 @@ D0CDBF (base 16) LG Electronics Hawthorne 90250 US -A4-F0-1F (hex) CANON INC. -A4F01F (base 16) CANON INC. - 30-2 Shimomaruko 3-chome, - Ohta-ku Tokyo 146-8501 - JP +D8-3E-EB (hex) AltoBeam Inc. +D83EEB (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN -90-B9-F9 (hex) Motorola Mobility LLC, a Lenovo Company -90B9F9 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 - US +5C-48-79 (hex) HUAWEI TECHNOLOGIES CO.,LTD +5C4879 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -04-6F-00 (hex) LG Electronics -046F00 (base 16) LG Electronics - Lot CN02, Trang Due Industrial Park,An Phong Ward, Hai Phong City, Vietnam - Hai Phong 184956 - VN +DC-57-5C (hex) PR Electronics A/S +DC575C (base 16) PR Electronics A/S + Lerbakken 10 + Følle 8410 + DK 5C-13-AC (hex) Apple, Inc. 5C13AC (base 16) Apple, Inc. @@ -139388,6 +139448,18 @@ A4F01F (base 16) CANON INC. Cupertino CA 95014 US +54-6C-50 (hex) Nanjing Qinheng Microelectronics Co., Ltd. +546C50 (base 16) Nanjing Qinheng Microelectronics Co., Ltd. + No.18, Ningshuang Road + Nanjing Jiangsu 210012 + CN + +E4-85-FB (hex) Quectel Wireless Solutions Co.,Ltd. +E485FB (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 + CN + 24-2A-EA (hex) Apple, Inc. 242AEA (base 16) Apple, Inc. 1 Infinite Loop @@ -139400,24 +139472,18 @@ A4F01F (base 16) CANON INC. Cupertino CA 95014 US -D8-3E-EB (hex) AltoBeam Inc. -D83EEB (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 +B8-BA-66 (hex) Microsoft Corporation +B8BA66 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US + +60-65-F4 (hex) Chipsea Technologies (Shenzhen) Crop. +6065F4 (base 16) Chipsea Technologies (Shenzhen) Crop. + Room 301, Building 1, Shenzhen Bay Innovation and Technology Center, Keyuan Avenue, High-tech Zone Community, Yuehai Subdistrict, Nanshan District, Shenzhen + Shenzhen 518000 CN -5C-48-79 (hex) HUAWEI TECHNOLOGIES CO.,LTD -5C4879 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -DC-57-5C (hex) PR Electronics A/S -DC575C (base 16) PR Electronics A/S - Lerbakken 10 - Følle 8410 - DK - 44-66-90 (hex) TP-LINK TECHNOLOGIES CO.,LTD. 446690 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan @@ -139436,47 +139502,41 @@ DC575C (base 16) PR Electronics A/S Shenzhen Guangdong 518000 CN -54-6C-50 (hex) Nanjing Qinheng Microelectronics Co., Ltd. -546C50 (base 16) Nanjing Qinheng Microelectronics Co., Ltd. - No.18, Ningshuang Road - Nanjing Jiangsu 210012 - CN - -E4-85-FB (hex) Quectel Wireless Solutions Co.,Ltd. -E485FB (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 - CN +58-9E-C6 (hex) Gigaset Technologies GmbH +589EC6 (base 16) Gigaset Technologies GmbH + Frankenstrasse 2 + Bocholt NRW 46395 + DE -B8-BA-66 (hex) Microsoft Corporation -B8BA66 (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 +E8-47-F3 (hex) upscale ai +E847F3 (base 16) upscale ai + 3101 Jay St. + Santa Clara CA 95054 US +B0-7A-16 (hex) ROEHN +B07A16 (base 16) ROEHN + Av. Manuel Bandeira, 291 + Sao Paulo Sp 05317020 + BR + 28-AD-EA (hex) Mallow SAS 28ADEA (base 16) Mallow SAS 229, rue St Honore Paris IDF 75001 FR -60-65-F4 (hex) Chipsea Technologies (Shenzhen) Crop. -6065F4 (base 16) Chipsea Technologies (Shenzhen) Crop. - Room 301, Building 1, Shenzhen Bay Innovation and Technology Center, Keyuan Avenue, High-tech Zone Community, Yuehai Subdistrict, Nanshan District, Shenzhen - Shenzhen 518000 +98-12-23 (hex) Tarmoc Network LTD +981223 (base 16) Tarmoc Network LTD + Taihao Road No.22, 6th Floor, Sandong Town, Huicheng District, Huizhou City + Huizhou City GuangDong 518000 CN -B0-7A-16 (hex) ROEHN -B07A16 (base 16) ROEHN - Av. Manuel Bandeira, 291 - Sao Paulo Sp 05317020 - BR - -58-9E-C6 (hex) Gigaset Technologies GmbH -589EC6 (base 16) Gigaset Technologies GmbH - Frankenstrasse 2 - Bocholt NRW 46395 - DE +84-C6-65 (hex) Taicang T&W Electronics +84C665 (base 16) Taicang T&W Electronics + 89# Jiang Nan RD + Suzhou Jiangsu 215412 + CN B8-61-FC (hex) Juniper Networks B861FC (base 16) Juniper Networks @@ -139484,10 +139544,10 @@ B861FC (base 16) Juniper Networks Sunnyvale CA 94089 US -E8-47-F3 (hex) upscale ai -E847F3 (base 16) upscale ai - 3101 Jay St. - Santa Clara CA 95054 +08-3C-03 (hex) IEEE Registration Authority +083C03 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US 40-68-F9 (hex) Shenzhen SuperElectron Technology Co.,Ltd. @@ -139496,23 +139556,41 @@ E847F3 (base 16) upscale ai Shenzhen Guangdong 518000 CN -98-12-23 (hex) Tarmoc Network LTD -981223 (base 16) Tarmoc Network LTD - Taihao Road No.22, 6th Floor, Sandong Town, Huicheng District, Huizhou City - Huizhou City GuangDong 518000 +14-49-C5 (hex) Huawei Device Co., Ltd. +1449C5 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +B4-54-F2 (hex) Huawei Device Co., Ltd. +B454F2 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN +80-C2-F0 (hex) Xiamen Yeastar Digital Technology Co.,Ltd +80C2F0 (base 16) Xiamen Yeastar Digital Technology Co.,Ltd + Building C09, Software Park Phase III, Xiamen 361024, Fujian, China + XIAMEN FUJIAN 361024 + CN + +FC-66-37 (hex) Sagemcom Broadband SAS +FC6637 (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR + 2C-F8-EC (hex) Quectel Wireless Solutions Co.,Ltd. 2CF8EC (base 16) Quectel Wireless Solutions Co.,Ltd. 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District Shanghai 200233 CN -84-C6-65 (hex) Taicang T&W Electronics -84C665 (base 16) Taicang T&W Electronics - 89# Jiang Nan RD - Suzhou Jiangsu 215412 - CN +68-F9-0F (hex) Intel Corporate +68F90F (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY 6C-91-88 (hex) Nokia 6C9188 (base 16) Nokia @@ -139520,12 +139598,6 @@ E847F3 (base 16) upscale ai Kanata Ontario K2K 2E6 CA -08-3C-03 (hex) IEEE Registration Authority -083C03 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US - 00-09-D8 (hex) Telia Company AB 0009D8 (base 16) Telia Company AB Östermalmsgatan 63A @@ -139538,48 +139610,24 @@ E847F3 (base 16) upscale ai TAIPEI 66.Chung Shan RD, TU-CHENG Industrial , district new TAIPEI 33859 CN -14-49-C5 (hex) Huawei Device Co., Ltd. -1449C5 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +54-A0-AB (hex) Maiyue Future Intelligent Technology (Suzhou) Co.,Ltd. +54A0AB (base 16) Maiyue Future Intelligent Technology (Suzhou) Co.,Ltd. + Room 1283, Building 3, No.288 Jiushenggang Road, Guoxiang Street, Economic Development Zone, Suzhou,China + Suzhou 215000 CN -B4-54-F2 (hex) Huawei Device Co., Ltd. -B454F2 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +DC-7E-F5 (hex) HUAWEI TECHNOLOGIES CO.,LTD +DC7EF5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -68-F9-0F (hex) Intel Corporate -68F90F (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - D0-8E-17 (hex) ACCTON TECHNOLOGY CORPORATION D08E17 (base 16) ACCTON TECHNOLOGY CORPORATION No.1, Creation Road 3, Hsinchu Science Park, Hsinchu 30077 TW -54-A0-AB (hex) Maiyue Future Intelligent Technology (Suzhou) Co.,Ltd. -54A0AB (base 16) Maiyue Future Intelligent Technology (Suzhou) Co.,Ltd. - Room 1283, Building 3, No.288 Jiushenggang Road, Guoxiang Street, Economic Development Zone, Suzhou,China - Suzhou 215000 - CN - -80-C2-F0 (hex) Xiamen Yeastar Digital Technology Co.,Ltd -80C2F0 (base 16) Xiamen Yeastar Digital Technology Co.,Ltd - Building C09, Software Park Phase III, Xiamen 361024, Fujian, China - XIAMEN FUJIAN 361024 - CN - -FC-66-37 (hex) Sagemcom Broadband SAS -FC6637 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - E4-C8-01 (hex) BLU Products Inc E4C801 (base 16) BLU Products Inc 8600 NW 36th Street Suite 200 @@ -139592,42 +139640,12 @@ E4C801 (base 16) BLU Products Inc PARIS IdF 75008 FR -24-64-77 (hex) Beijing Xiaomi Mobile Software Co., Ltd -246477 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 - CN - -7C-B4-0F (hex) Fibocom Wireless Inc. -7CB40F (base 16) Fibocom Wireless Inc. - 1101,Tower A, Building 6, Shenzhen International Innovation Valley, Dashi 1st Rd, Nanshan - Shenzhen Guangdong 518055 - CN - -50-9F-B9 (hex) Shenzhen Skyworth Digital Technology CO., Ltd -509FB9 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd - 4F,Block A, Skyworth?Building, - Shenzhen Guangdong 518057 - CN - -DC-7E-F5 (hex) HUAWEI TECHNOLOGIES CO.,LTD -DC7EF5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - 8C-D0-66 (hex) Texas Instruments 8CD066 (base 16) Texas Instruments 12500 TI Blvd Dallas TX 75243 US -44-63-C2 (hex) Zyxel Communications Corporation -4463C2 (base 16) Zyxel Communications Corporation - No. 6 Innovation Road II, Science Park - Hsichu Taiwan 300 - TW - 34-20-D3 (hex) SHENZHEN IP-COM NETWORKS CO.,LTD. 3420D3 (base 16) SHENZHEN IP-COM NETWORKS CO.,LTD. Room 101, Unit A, First Floor, Tower E3, No. 1001, Zhongshanyuan Road, Nanshan District, Shenzhen,China @@ -139640,53 +139658,47 @@ DC7EF5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Haizhu District Guangzhou 510000 CN +24-64-77 (hex) Beijing Xiaomi Mobile Software Co., Ltd +246477 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 + CN + 64-6B-E7 (hex) Qingdao Intelligent&Precise Electronics Co.,Ltd. 646BE7 (base 16) Qingdao Intelligent&Precise Electronics Co.,Ltd. No.218 Qianwangang Road Qingdao Shangdong 266510 CN +7C-B4-0F (hex) Fibocom Wireless Inc. +7CB40F (base 16) Fibocom Wireless Inc. + 1101,Tower A, Building 6, Shenzhen International Innovation Valley, Dashi 1st Rd, Nanshan + Shenzhen Guangdong 518055 + CN + +50-9F-B9 (hex) Shenzhen Skyworth Digital Technology CO., Ltd +509FB9 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, + Shenzhen Guangdong 518057 + CN + 24-8A-B3 (hex) ICTK Co., Ltd. 248AB3 (base 16) ICTK Co., Ltd. 13F, JACE Tower, 16, Gangnam-daero 84-gil, Gangnam-gu Seoul Select State 06241 KR -7C-62-E7 (hex) Cisco Systems, Inc -7C62E7 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - 10-D8-B1 (hex) AUO Corporation 10D8B1 (base 16) AUO Corporation No. 1, Li-Hsin Rd. 2, Hsinchu Science Park, Hsinchu 300094 TW -BC-00-23 (hex) Honor Device Co., Ltd. -BC0023 (base 16) Honor Device Co., Ltd. - Suite 3401, Unit A, Building 6, Shum Yip Sky Park, No. 8089, Hongli West Road, Xiangmihu Street, Futian District - Shenzhen Guangdong 518040 - CN - -88-7C-C1 (hex) Zebronics India Pvt Ltd -887CC1 (base 16) Zebronics India Pvt Ltd - No 13/7, Smith Road, Royapettah - Chennai Tamil Nadu 600002 - IN - -3C-CB-01 (hex) Beijing Lingji innovations Technology Co., LTD. -3CCB01 (base 16) Beijing Lingji innovations Technology Co., LTD. - Room 106, 1F, A1 Bldg. Zhongguancun Dongsheng Technology Park (Northern Territory), No. 66, Xixiaokou Rd, Haidian Dist., Beijing, China - Beijing Beijing 100192 - CN - -18-14-54 (hex) CIG SHANGHAI CO LTD -181454 (base 16) CIG SHANGHAI CO LTD - 5th Floor, Building 8 No 2388 Chenhang Road - SHANGHAI 201114 - CN +44-63-C2 (hex) Zyxel Communications Corporation +4463C2 (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW 9C-FA-96 (hex) T3 Technology Co., Ltd. 9CFA96 (base 16) T3 Technology Co., Ltd. @@ -139706,30 +139718,60 @@ E4FEE4 (base 16) Ciena Corporation Kyoto 600-8530 JP -48-78-5B (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. -48785B (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. - No.555 Qianmo Road - Hangzhou Zhejiang 310052 - CN - 60-7A-D8 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. 607AD8 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. Midea Global Innovation Center,Beijiao Town,Shunde Foshan Guangdong 528311 CN +48-78-5B (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +48785B (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 + CN + F0-74-C1 (hex) Blink by Amazon F074C1 (base 16) Blink by Amazon 100 Riverpark Drive North Reading MA 01864 US +7C-62-E7 (hex) Cisco Systems, Inc +7C62E7 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +18-14-54 (hex) CIG SHANGHAI CO LTD +181454 (base 16) CIG SHANGHAI CO LTD + 5th Floor, Building 8 No 2388 Chenhang Road + SHANGHAI 201114 + CN + 00-0B-7C (hex) Electro-Voice Dynacord LLC 000B7C (base 16) Electro-Voice Dynacord LLC 130 Perinton Parkway Fairport NY 14450 US +BC-00-23 (hex) Honor Device Co., Ltd. +BC0023 (base 16) Honor Device Co., Ltd. + Suite 3401, Unit A, Building 6, Shum Yip Sky Park, No. 8089, Hongli West Road, Xiangmihu Street, Futian District + Shenzhen Guangdong 518040 + CN + +88-7C-C1 (hex) Zebronics India Pvt Ltd +887CC1 (base 16) Zebronics India Pvt Ltd + No 13/7, Smith Road, Royapettah + Chennai Tamil Nadu 600002 + IN + +3C-CB-01 (hex) Beijing Lingji innovations Technology Co., LTD. +3CCB01 (base 16) Beijing Lingji innovations Technology Co., LTD. + Room 106, 1F, A1 Bldg. Zhongguancun Dongsheng Technology Park (Northern Territory), No. 66, Xixiaokou Rd, Haidian Dist., Beijing, China + Beijing Beijing 100192 + CN + 8C-4E-BB (hex) Amazon Technologies Inc. 8C4EBB (base 16) Amazon Technologies Inc. P.O Box 8102 @@ -139754,12 +139796,30 @@ CCBE61 (base 16) Apple, Inc. Cupertino CA 95014 US +3C-7F-6E (hex) Xiaomi Communications Co Ltd +3C7F6E (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + +90-64-9B (hex) Espressif Inc. +90649B (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + B8-57-D6 (hex) Cisco Systems, Inc B857D6 (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US +00-07-8B (hex) Wegener Communications, Inc. +00078B (base 16) Wegener Communications, Inc. + 930 Interstate Ridge Drive, Ste. A, + Gainesville GA 30501 + US + E4-02-74 (hex) FW Murphy Production Controls E40274 (base 16) FW Murphy Production Controls 4646 S Harvard Ave @@ -139778,41 +139838,83 @@ E40274 (base 16) FW Murphy Production Controls Fuzhou FUJIAN 350015 CN -00-7F-1D (hex) Fantasia Trading LLC -007F1D (base 16) Fantasia Trading LLC - 5350 Ontario Mills Pkwy, Suite 100 - Ontario CA 91764 - US - 58-21-9D (hex) Shanghai Timar Integrated Circuit Co., LTD 58219D (base 16) Shanghai Timar Integrated Circuit Co., LTD Room 1208, No. 999 West Zhongshan Road, Changning District, Shanghai, China shanghai shanghai 200030 CN -3C-7F-6E (hex) Xiaomi Communications Co Ltd -3C7F6E (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +D8-85-5E (hex) zte corporation +D8855E (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -90-64-9B (hex) Espressif Inc. -90649B (base 16) Espressif Inc. +C8-85-41 (hex) Espressif Inc. +C88541 (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area Shanghai Shanghai 201203 CN +00-7F-1D (hex) Fantasia Trading LLC +007F1D (base 16) Fantasia Trading LLC + 5350 Ontario Mills Pkwy, Suite 100 + Ontario CA 91764 + US + +E4-0A-75 (hex) Silicon Laboratories +E40A75 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + 40-79-55 (hex) Datacolor 407955 (base 16) Datacolor 2 Shengpu Road, Suzhou Industrial Park, Export Processing Zone B, Suzhou, Jiangsu, P.R. China Suzhou 215000 CN -00-07-8B (hex) Wegener Communications, Inc. -00078B (base 16) Wegener Communications, Inc. - 930 Interstate Ridge Drive, Ste. A, - Gainesville GA 30501 - US +18-83-BF (hex) Arcadyan Corporation +1883BF (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II , + Hsinchu 300 + TW + +1C-C6-3C (hex) Arcadyan Corporation +1CC63C (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II , + Hsinchu 300 + TW + +88-25-2C (hex) Arcadyan Corporation +88252C (base 16) Arcadyan Corporation + 4F., NO.9, Park Avenue II , + Hsinchu 300 + TW + +00-1A-2A (hex) Arcadyan Corporation +001A2A (base 16) Arcadyan Corporation + 4F., No. 9 , Park Avenue II, + Hsinchu 300 + TW + +5C-DC-96 (hex) Arcadyan Corporation +5CDC96 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd., + Hsinchu City 30071, 12345 + TW + +74-31-70 (hex) Arcadyan Corporation +743170 (base 16) Arcadyan Corporation + 4F. , No. 9 , Park Avenue II, + Hsinchu 300 + TW + +EC-6F-F9 (hex) Pioseed Technology(Chengdu)Co.,Ltd. +EC6FF9 (base 16) Pioseed Technology(Chengdu)Co.,Ltd. + Unit 1,Building 2,177 Tianquan Road,Chengdu High-tech Zone(self-numbered) + Chengdu Sichuan 610097 + CN B0-0C-9D (hex) Quectel Wireless Solutions Co.,Ltd. B00C9D (base 16) Quectel Wireless Solutions Co.,Ltd. @@ -156902,36 +157004,12 @@ D0D04B (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -4C-09-D4 (hex) Arcadyan Technology Corporation -4C09D4 (base 16) Arcadyan Technology Corporation - 4F, No. 9, Park Avenue II , - Hsinchu 300 - TW - C8-FF-28 (hex) Liteon Technology Corporation C8FF28 (base 16) Liteon Technology Corporation 4F, 90, Chien 1 Road New Taipei City Taiwan 23585 TW -9C-80-DF (hex) Arcadyan Technology Corporation -9C80DF (base 16) Arcadyan Technology Corporation - 4F, No. 9, Park Avenue II , - Hsinchu 300 - TW - -00-23-08 (hex) Arcadyan Technology Corporation -002308 (base 16) Arcadyan Technology Corporation - 4F, No. 9, Park Avenue II , - Hsinchu 300 - TW - -88-03-55 (hex) Arcadyan Technology Corporation -880355 (base 16) Arcadyan Technology Corporation - 4F., No.9 , Park Avenue II - Hsinchu 300 - TW - 34-BB-1F (hex) BlackBerry RTS 34BB1F (base 16) BlackBerry RTS 451 Phillip Street @@ -184346,17 +184424,23 @@ E467A6 (base 16) BSH Hausgeräte GmbH Suzhou 215000 CN -30-D5-1F (hex) Prolights -30D51F (base 16) Prolights - Via Adriano Olivetti snc - Minturno Latina 04026 - IT +74-A5-7E (hex) Panasonic Ecology Systems +74A57E (base 16) Panasonic Ecology Systems + 4017, Azashimonakata, Takaki-cho + Kasugai Aichi 4868522 + JP -18-FB-8E (hex) VusionGroup -18FB8E (base 16) VusionGroup - Kalsdorfer Straße 12 - Fernitz-Mellach Steiermark 8072 - AT +7C-E9-13 (hex) Fantasia Trading LLC +7CE913 (base 16) Fantasia Trading LLC + 5350 Ontario Mills Pkwy, Suite 100 + Ontario CA 91764 + US + +E0-CB-BC (hex) Cisco Meraki +E0CBBC (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US 68-3A-1E (hex) Cisco Meraki 683A1E (base 16) Cisco Meraki @@ -184376,35 +184460,23 @@ F89E28 (base 16) Cisco Meraki San Francisco 94158 US -74-A5-7E (hex) Panasonic Ecology Systems -74A57E (base 16) Panasonic Ecology Systems - 4017, Azashimonakata, Takaki-cho - Kasugai Aichi 4868522 - JP - -6C-15-DB (hex) Arcadyan Corporation -6C15DB (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW - -7C-E9-13 (hex) Fantasia Trading LLC -7CE913 (base 16) Fantasia Trading LLC - 5350 Ontario Mills Pkwy, Suite 100 - Ontario CA 91764 - US - 38-2A-8B (hex) nFore Technology Co., Ltd. 382A8B (base 16) nFore Technology Co., Ltd. 5F, No.31, Ln. 258, Ruiguang Rd., Neihu Dist., Taipei city 114 TW -E0-CB-BC (hex) Cisco Meraki -E0CBBC (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 - US +18-FB-8E (hex) VusionGroup +18FB8E (base 16) VusionGroup + Kalsdorfer Straße 12 + Fernitz-Mellach Steiermark 8072 + AT + +30-D5-1F (hex) Prolights +30D51F (base 16) Prolights + Via Adriano Olivetti snc + Minturno Latina 04026 + IT D4-68-BA (hex) Shenzhen Sundray Technologies company Limited D468BA (base 16) Shenzhen Sundray Technologies company Limited @@ -184424,12 +184496,6 @@ D468BA (base 16) Shenzhen Sundray Technologies company Limited Shenzhen Guangdong 518057 CN -08-B3-D6 (hex) Huawei Device Co., Ltd. -08B3D6 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - 78-2B-60 (hex) Huawei Device Co., Ltd. 782B60 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone @@ -184448,24 +184514,36 @@ FC79DD (base 16) Huawei Device Co., Ltd. Dongguan Guangdong 523808 CN -C8-53-E1 (hex) Douyin Vision Co., Ltd -C853E1 (base 16) Douyin Vision Co., Ltd - No.1 Building, Zhonghang Square, West Road of the Northern 3rd Circuit, Haidian Distrct - Beijing Beijing 100098 - CN +F4-14-BF (hex) LG Innotek +F414BF (base 16) LG Innotek + 26, HANAMSANDAN 5BEON-RO + Gwangju Gwangsan-gu 506-731 + KR -DC-39-6F (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -DC396F (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +5C-49-79 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +5C4979 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -5C-49-79 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -5C4979 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +DC-39-6F (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +DC396F (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE +C8-53-E1 (hex) Douyin Vision Co., Ltd +C853E1 (base 16) Douyin Vision Co., Ltd + No.1 Building, Zhonghang Square, West Road of the Northern 3rd Circuit, Haidian Distrct + Beijing Beijing 100098 + CN + +08-B3-D6 (hex) Huawei Device Co., Ltd. +08B3D6 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + 2C-6F-37 (hex) Nokia 2C6F37 (base 16) Nokia 600 March Road @@ -184478,24 +184556,18 @@ DC396F (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Kanata Ontario K2K 2E6 CA +6C-15-DB (hex) Arcadyan Corporation +6C15DB (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW + 58-79-61 (hex) Microsoft Corporation 587961 (base 16) Microsoft Corporation One Microsoft Way REDMOND WA 98052 US -F4-14-BF (hex) LG Innotek -F414BF (base 16) LG Innotek - 26, HANAMSANDAN 5BEON-RO - Gwangju Gwangsan-gu 506-731 - KR - -50-E6-36 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -50E636 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE - 60-B5-8D (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH 60B58D (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 @@ -184508,12 +184580,6 @@ F414BF (base 16) LG Innotek Berlin Berlin 10559 DE -48-DD-0C (hex) eero inc. -48DD0C (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - 3C-5C-F1 (hex) eero inc. 3C5CF1 (base 16) eero inc. 660 3rd Street @@ -184532,6 +184598,12 @@ C4F174 (base 16) eero inc. San Francisco CA 94107 US +64-97-14 (hex) eero inc. +649714 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + 40-47-5E (hex) eero inc. 40475E (base 16) eero inc. 660 3rd Street @@ -184556,54 +184628,54 @@ D88ED4 (base 16) eero inc. San Francisco CA 94107 US -64-97-14 (hex) eero inc. -649714 (base 16) eero inc. +14-22-DB (hex) eero inc. +1422DB (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -20-BE-CD (hex) eero inc. -20BECD (base 16) eero inc. +18-90-88 (hex) eero inc. +189088 (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -C8-B8-2F (hex) eero inc. -C8B82F (base 16) eero inc. +48-DD-0C (hex) eero inc. +48DD0C (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -14-22-DB (hex) eero inc. -1422DB (base 16) eero inc. +50-E6-36 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +50E636 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +E8-D3-EB (hex) eero inc. +E8D3EB (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -18-90-88 (hex) eero inc. -189088 (base 16) eero inc. +C0-6F-98 (hex) eero inc. +C06F98 (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -E8-D3-EB (hex) eero inc. -E8D3EB (base 16) eero inc. +20-BE-CD (hex) eero inc. +20BECD (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -C0-6F-98 (hex) eero inc. -C06F98 (base 16) eero inc. +C8-B8-2F (hex) eero inc. +C8B82F (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -8C-53-D2 (hex) China Mobile Group Device Co.,Ltd. -8C53D2 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - 24-61-5A (hex) China Mobile Group Device Co.,Ltd. 24615A (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District @@ -184664,18 +184736,6 @@ C875F4 (base 16) China Mobile Group Device Co.,Ltd. Beijing 100053 CN -B8-E9-24 (hex) Mellanox Technologies, Inc. -B8E924 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US - -2C-5E-AB (hex) Mellanox Technologies, Inc. -2C5EAB (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US - B8-CE-F6 (hex) Mellanox Technologies, Inc. B8CEF6 (base 16) Mellanox Technologies, Inc. 350 Oakmead Parkway, Suite 100 @@ -184688,6 +184748,12 @@ C470BD (base 16) Mellanox Technologies, Inc. Sunnyvale CA 94085 US +B8-E9-24 (hex) Mellanox Technologies, Inc. +B8E924 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + 50-70-97 (hex) China Mobile Group Device Co.,Ltd. 507097 (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District @@ -184718,10 +184784,16 @@ E83A4B (base 16) China Mobile Group Device Co.,Ltd. Dongguan Guangdong 523860 CN -58-72-C9 (hex) zte corporation -5872C9 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +2C-5E-AB (hex) Mellanox Technologies, Inc. +2C5EAB (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +8C-53-D2 (hex) China Mobile Group Device Co.,Ltd. +8C53D2 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 CN 38-E5-63 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD @@ -184730,10 +184802,10 @@ E83A4B (base 16) China Mobile Group Device Co.,Ltd. DONG GUAN GUANG DONG 523860 CN -58-FC-E3 (hex) Funshion Online Technologies Co.,Ltd -58FCE3 (base 16) Funshion Online Technologies Co.,Ltd - 2101, Floor 1-2, Building 9, Anzhen Xili District 3, Chaoyang District, Beijing - Beijing 100029 +58-72-C9 (hex) zte corporation +5872C9 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN 30-C5-99 (hex) ASUSTek COMPUTER INC. @@ -184748,10 +184820,22 @@ E83A4B (base 16) China Mobile Group Device Co.,Ltd. Qingdao 266101 CN -00-23-E9 (hex) F5 Inc. -0023E9 (base 16) F5 Inc. - 401 Elliott Ave. W. - Seattle WA 98119 +B8-DD-E8 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +B8DDE8 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County + Chengdu Sichuan 611330 + CN + +30-FE-FA (hex) Cisco Systems, Inc +30FEFA (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +6C-4F-A1 (hex) Cisco Systems, Inc +6C4FA1 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US 40-BC-68 (hex) Funshion Online Technologies Co.,Ltd @@ -184766,29 +184850,17 @@ E83A4B (base 16) China Mobile Group Device Co.,Ltd. Gunpo-si Gyeonggi-do 15849 KR -B8-DD-E8 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD -B8DDE8 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD - No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County - Chengdu Sichuan 611330 - CN - 78-A1-D8 (hex) ShenzhenEnjoyTechnologyCo.,Ltd 78A1D8 (base 16) ShenzhenEnjoyTechnologyCo.,Ltd Building A, No.1 Qianwan 1st Road, QianHai Shenzhen HongKong Cooperation Zone, Shenzhen,China shenzhen guangdong 518108 CN -30-FE-FA (hex) Cisco Systems, Inc -30FEFA (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -6C-4F-A1 (hex) Cisco Systems, Inc -6C4FA1 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +58-FC-E3 (hex) Funshion Online Technologies Co.,Ltd +58FCE3 (base 16) Funshion Online Technologies Co.,Ltd + 2101, Floor 1-2, Building 9, Anzhen Xili District 3, Chaoyang District, Beijing + Beijing 100029 + CN 40-95-95 (hex) TP-Link Systems Inc. 409595 (base 16) TP-Link Systems Inc. @@ -184796,6 +184868,12 @@ B8DDE8 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD Irvine CA 92618 US +00-23-E9 (hex) F5 Inc. +0023E9 (base 16) F5 Inc. + 401 Elliott Ave. W. + Seattle WA 98119 + US + 48-CA-68 (hex) Apple, Inc. 48CA68 (base 16) Apple, Inc. 1 Infinite Loop @@ -184814,6 +184892,12 @@ D842F7 (base 16) Tozed Kangwei Tech Co.,Ltd GuangZhou 511466 CN +E0-86-14 (hex) Inseego Wireless, Inc +E08614 (base 16) Inseego Wireless, Inc + 9710 Scranton Rd., Suite 200 + San Diego CA 92121 + US + 18-86-C3 (hex) Nokia 1886C3 (base 16) Nokia 600 March Road @@ -184844,11 +184928,23 @@ E8CA50 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. Sunnyvale CA 94085 US -E0-86-14 (hex) Inseego Wireless, Inc -E08614 (base 16) Inseego Wireless, Inc - 9710 Scranton Rd., Suite 200 - San Diego CA 92121 - US +68-FE-71 (hex) Espressif Inc. +68FE71 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +D8-6B-83 (hex) Nintendo Co.,Ltd +D86B83 (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP + +40-EB-21 (hex) HUAWEI TECHNOLOGIES CO.,LTD +40EB21 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN A8-C4-07 (hex) HUAWEI TECHNOLOGIES CO.,LTD A8C407 (base 16) HUAWEI TECHNOLOGIES CO.,LTD @@ -184868,23 +184964,11 @@ DC121D (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -40-EB-21 (hex) HUAWEI TECHNOLOGIES CO.,LTD -40EB21 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -68-FE-71 (hex) Espressif Inc. -68FE71 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -D8-6B-83 (hex) Nintendo Co.,Ltd -D86B83 (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP +2C-03-69 (hex) ACCTON TECHNOLOGY CORPORATION +2C0369 (base 16) ACCTON TECHNOLOGY CORPORATION + No.1, Creation Road 3, Hsinchu Science Park, + Hsinchu 30077 + TW C0-74-15 (hex) IntelPro Inc. C07415 (base 16) IntelPro Inc. @@ -184910,17 +184994,23 @@ C07415 (base 16) IntelPro Inc. Hangzhou Zhejiang 310052 CN +54-78-F0 (hex) zte corporation +5478F0 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + BC-D2-2C (hex) Intel Corporate BCD22C (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -2C-03-69 (hex) ACCTON TECHNOLOGY CORPORATION -2C0369 (base 16) ACCTON TECHNOLOGY CORPORATION - No.1, Creation Road 3, Hsinchu Science Park, - Hsinchu 30077 - TW +E0-3A-AA (hex) Intel Corporate +E03AAA (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY 50-99-03 (hex) Meta Platforms, Inc. 509903 (base 16) Meta Platforms, Inc. @@ -184928,47 +185018,29 @@ BCD22C (base 16) Intel Corporate Menlo Park CA 94025 US -64-68-1A (hex) DASAN Network Solutions -64681A (base 16) DASAN Network Solutions - 401, 20, Gwacheon-daero 7-gil, - Gwacheon-si Gyeonggi-do 13493 - KR - 40-26-8E (hex) Shenzhen Photon Leap Technology Co., Ltd. 40268E (base 16) Shenzhen Photon Leap Technology Co., Ltd. 15/F, Building 2, Yongxin Times Square, Interchange of Dongbin Road and Nanguang Road Shenzhen 518054 CN -54-78-F0 (hex) zte corporation -5478F0 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN - 74-F4-41 (hex) Samsung Electronics Co.,Ltd 74F441 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR -E0-3A-AA (hex) Intel Corporate -E03AAA (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - 34-39-16 (hex) Google, Inc. 343916 (base 16) Google, Inc. 1600 Amphitheatre Parkway Mountain View CA 94043 US -64-D9-C2 (hex) eero inc. -64D9C2 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US +64-68-1A (hex) DASAN Network Solutions +64681A (base 16) DASAN Network Solutions + 401, 20, Gwacheon-daero 7-gil, + Gwacheon-si Gyeonggi-do 13493 + KR 20-E7-C8 (hex) Espressif Inc. 20E7C8 (base 16) Espressif Inc. @@ -184982,24 +185054,12 @@ E03AAA (base 16) Intel Corporate San Diego CA 92127 US -28-87-61 (hex) LG Innotek -288761 (base 16) LG Innotek - 26, HANAMSANDAN 5BEON-RO - Gwangju Gwangsan-gu 506-731 - KR - -78-0C-71 (hex) Inseego Wireless, Inc -780C71 (base 16) Inseego Wireless, Inc - 9710 Scranton Rd., Suite 200 - San Diego CA 92121 +64-D9-C2 (hex) eero inc. +64D9C2 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 US -A8-2B-DD (hex) LCFC(Hefei) Electronics Technology co., ltd -A82BDD (base 16) LCFC(Hefei) Electronics Technology co., ltd - No. 3188-1 Yungu Road (Comprehensive Bonded Zone), Hefei Economic & Technological Development Area,Anhui - HEFEI ANHUI 230601 - CN - 80-82-FE (hex) Arcadyan Corporation 8082FE (base 16) Arcadyan Corporation No.8, Sec.2, Guangfu Rd. @@ -185012,10 +185072,16 @@ CCCFFE (base 16) Henan Lingyunda Information Technology Co., Ltd Zhengzhou Henan Province 450000 CN -34-B5-F3 (hex) IEEE Registration Authority -34B5F3 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +28-87-61 (hex) LG Innotek +288761 (base 16) LG Innotek + 26, HANAMSANDAN 5BEON-RO + Gwangju Gwangsan-gu 506-731 + KR + +78-0C-71 (hex) Inseego Wireless, Inc +780C71 (base 16) Inseego Wireless, Inc + 9710 Scranton Rd., Suite 200 + San Diego CA 92121 US D4-27-FF (hex) Sagemcom Broadband SAS @@ -185030,24 +185096,18 @@ D427FF (base 16) Sagemcom Broadband SAS San Francisco CA 94107 US -C0-AF-F2 (hex) Dyson Limited -C0AFF2 (base 16) Dyson Limited - Tetbury Hill - Malmesbury Wiltshire SN16 0RP - GB +A8-2B-DD (hex) LCFC(Hefei) Electronics Technology co., ltd +A82BDD (base 16) LCFC(Hefei) Electronics Technology co., ltd + No. 3188-1 Yungu Road (Comprehensive Bonded Zone), Hefei Economic & Technological Development Area,Anhui + HEFEI ANHUI 230601 + CN -20-10-B1 (hex) Amazon Technologies Inc. -2010B1 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 +34-B5-F3 (hex) IEEE Registration Authority +34B5F3 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -84-53-CD (hex) China Mobile Group Device Co.,Ltd. -8453CD (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - F8-55-4B (hex) WirelessMobility Engineering Centre SDN. BHD F8554B (base 16) WirelessMobility Engineering Centre SDN. BHD SummerSkye Square, NO. 1-2-13 & 1-2, 13A, Jalan Sungai Tiram 8, 11900 Bayan Lepas @@ -185072,6 +185132,12 @@ E489CA (base 16) Cisco Systems, Inc San Jose CA 95126 US +C0-AF-F2 (hex) Dyson Limited +C0AFF2 (base 16) Dyson Limited + Tetbury Hill + Malmesbury Wiltshire SN16 0RP + GB + 14-BC-68 (hex) Cisco Systems, Inc 14BC68 (base 16) Cisco Systems, Inc 80 West Tasman Drive @@ -185084,34 +185150,10 @@ E489CA (base 16) Cisco Systems, Inc Shanghai 200000 CN -98-F6-7A (hex) Chipsea Technologies (Shenzhen) Crop. -98F67A (base 16) Chipsea Technologies (Shenzhen) Crop. - Room 301, Building 1, Shenzhen Bay Innovation and Technology Center, Keyuan Avenue, High-tech Zone Community, Yuehai Subdistrict, Nanshan District, Shenzhen - Shenzhen 518000 - CN - -C4-9A-31 (hex) Zyxel Communications Corporation -C49A31 (base 16) Zyxel Communications Corporation - No. 6 Innovation Road II, Science Park - Hsichu Taiwan 300 - TW - -48-0E-13 (hex) ittim -480E13 (base 16) ittim - 1202, No.6, Zhongguancun South Street, Haidian District, - beijing 100080 - CN - -74-34-91 (hex) Shenzhen Kings IoT Co., Ltd -743491 (base 16) Shenzhen Kings IoT Co., Ltd - D102, Yibao Garden, Hangcheng Road, Xixiang, BaoanBaoan district - Shenzhen City 518126 - CN - -08-D9-45 (hex) HUAWEI TECHNOLOGIES CO.,LTD -08D945 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +AC-84-FA (hex) Zhejiang Weilai Jingling Artificial Intelligence Technology Co., Ltd. +AC84FA (base 16) Zhejiang Weilai Jingling Artificial Intelligence Technology Co., Ltd. + B2, 6th Floor (6-7 section), Xixi Campus, Ai Cheng Street, Wuchang Sub-district, Yuhang District, Hangzhou, Zhejiang Province, China + Hangzhou Zhejiang 310024 CN 00-A0-1B (hex) Zhone Technologies, Inc. @@ -185138,24 +185180,36 @@ C49A31 (base 16) Zyxel Communications Corporation Plano TX 75024 US -AC-84-FA (hex) Zhejiang Weilai Jingling Artificial Intelligence Technology Co., Ltd. -AC84FA (base 16) Zhejiang Weilai Jingling Artificial Intelligence Technology Co., Ltd. - B2, 6th Floor (6-7 section), Xixi Campus, Ai Cheng Street, Wuchang Sub-district, Yuhang District, Hangzhou, Zhejiang Province, China - Hangzhou Zhejiang 310024 - CN - 3C-40-15 (hex) 12mm Health Technology (Hainan) Co., Ltd. 3C4015 (base 16) 12mm Health Technology (Hainan) Co., Ltd. Room A20-860, 5th Floor, Building A,Entrepreneurship Incubation Center,No. 266 Nanhai Avenue,National Hi-Tech Industrial Development Zone,Haikou City, Hainan Province, China Haikou Hainan 570100 CN -64-2E-41 (hex) HUAWEI TECHNOLOGIES CO.,LTD -642E41 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +20-10-B1 (hex) Amazon Technologies Inc. +2010B1 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + +84-53-CD (hex) China Mobile Group Device Co.,Ltd. +8453CD (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 CN +98-F6-7A (hex) Chipsea Technologies (Shenzhen) Crop. +98F67A (base 16) Chipsea Technologies (Shenzhen) Crop. + Room 301, Building 1, Shenzhen Bay Innovation and Technology Center, Keyuan Avenue, High-tech Zone Community, Yuehai Subdistrict, Nanshan District, Shenzhen + Shenzhen 518000 + CN + +C4-9A-31 (hex) Zyxel Communications Corporation +C49A31 (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW + 0C-1A-61 (hex) Neox FZCO 0C1A61 (base 16) Neox FZCO S60517 Jebel Ali Freezone @@ -185204,6 +185258,24 @@ F4289D (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. Nanning Guangxi 530007 CN +48-0E-13 (hex) ittim +480E13 (base 16) ittim + 1202, No.6, Zhongguancun South Street, Haidian District, + beijing 100080 + CN + +74-34-91 (hex) Shenzhen Kings IoT Co., Ltd +743491 (base 16) Shenzhen Kings IoT Co., Ltd + D102, Yibao Garden, Hangcheng Road, Xixiang, BaoanBaoan district + Shenzhen City 518126 + CN + +08-D9-45 (hex) HUAWEI TECHNOLOGIES CO.,LTD +08D945 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + 9C-C3-94 (hex) Apple, Inc. 9CC394 (base 16) Apple, Inc. 1 Infinite Loop @@ -185216,11 +185288,11 @@ F4289D (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. Cupertino CA 95014 US -9C-3B-91 (hex) VSSL -9C3B91 (base 16) VSSL - 192 North Old Highway 91, Building 1 - Hurricane UT 84737 - US +64-2E-41 (hex) HUAWEI TECHNOLOGIES CO.,LTD +642E41 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN E0-26-11 (hex) Apple, Inc. E02611 (base 16) Apple, Inc. @@ -185234,24 +185306,6 @@ F4979D (base 16) IEEE Registration Authority Piscataway NJ 08554 US -D8-E0-16 (hex) Extreme Networks Headquarters -D8E016 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US - -B0-F1-AE (hex) eero inc. -B0F1AE (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -0C-58-7B (hex) Quectel Wireless Solutions Co.,Ltd. -0C587B (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 - CN - E8-A5-5A (hex) Juniper Networks E8A55A (base 16) Juniper Networks 1133 Innovation Way @@ -185282,6 +185336,12 @@ B0BC8E (base 16) SkyMirr Melbourne FL 32901 US +9C-3B-91 (hex) VSSL +9C3B91 (base 16) VSSL + 192 North Old Highway 91, Building 1 + Hurricane UT 84737 + US + 88-54-6B (hex) Texas Instruments 88546B (base 16) Texas Instruments 12500 TI Blvd @@ -185300,40 +185360,34 @@ B014DF (base 16) MitraStar Technology Corp. Seongnam-si 13517 KR -AC-F4-66 (hex) HP Inc. -ACF466 (base 16) HP Inc. - 10300 Energy Dr - Spring TX 77389 - US - -74-68-59 (hex) SUNITEC TECHNOLOGY CO.,LIMITED -746859 (base 16) SUNITEC TECHNOLOGY CO.,LIMITED - Floor 1-4, building C, Weixlangtal industrial park, no, 725, Dasan Village、Xingfu community, Fucheng Street, Longhua district - Shenzhen 518110 - CN - -C0-62-F2 (hex) Beijing Cotytech Co.,LTD -C062F2 (base 16) Beijing Cotytech Co.,LTD - Room 702, Building 7, Zone 2, Hanwei International, No. 186, South Fourth Ring Road West, Fengtai District, Beijing - Beijing 100071 - CN - 28-05-A5 (hex) Espressif Inc. 2805A5 (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area Shanghai Shanghai 201203 CN -28-B2-7C (hex) Sailing Northern Technology -28B27C (base 16) Sailing Northern Technology - Unit A4009, 4th floor, BuiIding 1, No. 2 Yongcheng North Road - Beijing 100094 +B0-F1-AE (hex) eero inc. +B0F1AE (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +0C-58-7B (hex) Quectel Wireless Solutions Co.,Ltd. +0C587B (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 CN -24-EE-5D (hex) Vizio, Inc -24EE5D (base 16) Vizio, Inc - 39 Tesla - Irvine CA 92618 +D8-E0-16 (hex) Extreme Networks Headquarters +D8E016 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US + +AC-F4-66 (hex) HP Inc. +ACF466 (base 16) HP Inc. + 10300 Energy Dr + Spring TX 77389 US 40-3E-22 (hex) VusionGroup @@ -185348,28 +185402,46 @@ D4A254 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN +C0-62-F2 (hex) Beijing Cotytech Co.,LTD +C062F2 (base 16) Beijing Cotytech Co.,LTD + Room 702, Building 7, Zone 2, Hanwei International, No. 186, South Fourth Ring Road West, Fengtai District, Beijing + Beijing 100071 + CN + +28-B2-7C (hex) Sailing Northern Technology +28B27C (base 16) Sailing Northern Technology + Unit A4009, 4th floor, BuiIding 1, No. 2 Yongcheng North Road + Beijing 100094 + CN + +74-68-59 (hex) SUNITEC TECHNOLOGY CO.,LIMITED +746859 (base 16) SUNITEC TECHNOLOGY CO.,LIMITED + Floor 1-4, building C, Weixlangtal industrial park, no, 725, Dasan Village、Xingfu community, Fucheng Street, Longhua district + Shenzhen 518110 + CN + EC-81-52 (hex) HUAWEI TECHNOLOGIES CO.,LTD EC8152 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -68-CC-AE (hex) Fortinet, Inc. -68CCAE (base 16) Fortinet, Inc. - 899 Kifer Road - Sunnyvale 94086 - US - 10-BD-43 (hex) Robert Bosch Elektronikai Kft. 10BD43 (base 16) Robert Bosch Elektronikai Kft. Robert Bosch út 1. Hatvan Heves 3000 HU -78-11-9D (hex) Cisco Systems, Inc -78119D (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +24-EE-5D (hex) Vizio, Inc +24EE5D (base 16) Vizio, Inc + 39 Tesla + Irvine CA 92618 + US + +68-CC-AE (hex) Fortinet, Inc. +68CCAE (base 16) Fortinet, Inc. + 899 Kifer Road + Sunnyvale 94086 US 58-8F-CF (hex) Hangzhou Ezviz Software Co.,Ltd. @@ -185396,6 +185468,30 @@ EC8152 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Hangzhou Zhejiang 310051 CN +78-11-9D (hex) Cisco Systems, Inc +78119D (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +00-0B-0F (hex) Bosch Rexroth AG +000B0F (base 16) Bosch Rexroth AG + Bgm.-Dr.Nebel-Str.2 + Lohr am Main 97816 + NL + +A4-05-FD (hex) Bouffalo Lab (Nanjing) Co., Ltd. +A405FD (base 16) Bouffalo Lab (Nanjing) Co., Ltd. + 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China + Nanjing Jiangsu 211800 + CN + +3C-22-7F (hex) Quectel Wireless Solutions Co., Ltd. +3C227F (base 16) Quectel Wireless Solutions Co., Ltd. + Building 5, Shanghai Business Park Phase III (Area B), No.1016 Tianlin Road, Minhang District + Shanghai 200233 + CN + D4-0D-AB (hex) Shenzhen Cudy Technology Co., Ltd. D40DAB (base 16) Shenzhen Cudy Technology Co., Ltd. 7th Floor, West Tower, Lepu building, Nanshan @@ -185408,12 +185504,6 @@ D40DAB (base 16) Shenzhen Cudy Technology Co., Ltd. shenzhen guangdong 518057 CN -00-0B-0F (hex) Bosch Rexroth AG -000B0F (base 16) Bosch Rexroth AG - Bgm.-Dr.Nebel-Str.2 - Lohr am Main 97816 - NL - 84-93-EC (hex) Guangzhou Shiyuan Electronic Technology Company Limited 8493EC (base 16) Guangzhou Shiyuan Electronic Technology Company Limited No.6, 4th Yunpu Road, Yunpu industry District @@ -185426,41 +185516,17 @@ F07084 (base 16) Actiontec Electronics Inc. Santa Clara CA 95054 US -40-44-F7 (hex) Nintendo Co.,Ltd -4044F7 (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP - -A4-05-FD (hex) Bouffalo Lab (Nanjing) Co., Ltd. -A405FD (base 16) Bouffalo Lab (Nanjing) Co., Ltd. - 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China - Nanjing Jiangsu 211800 - CN - -3C-22-7F (hex) Quectel Wireless Solutions Co., Ltd. -3C227F (base 16) Quectel Wireless Solutions Co., Ltd. - Building 5, Shanghai Business Park Phase III (Area B), No.1016 Tianlin Road, Minhang District - Shanghai 200233 - CN - A0-90-B5 (hex) Tiinlab Corporation A090B5 (base 16) Tiinlab Corporation Building A Room 201 Cooperation District between Shenzhen and HongKong,Qianwan Road No.1,Shenzhen City, Business Address:No. 3333, Liuxian AvenueTower A, 35th Floor,Tanglang City, Nanshan District, Shenzhen, China Shenzhen Guangdong 518000 CN -28-83-28 (hex) EMALDO TECHNOLOGY(HK)LIMITED -288328 (base 16) EMALDO TECHNOLOGY(HK)LIMITED - 13/F., Golden Dragon Comm. Bldg., 522 Nathan Road, Yau Ma Tei, Kowloon - HONG KONG 999077 - HK - -B0-1F-F4 (hex) Sagemcom Broadband SAS -B01FF4 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +6C-7A-63 (hex) Arista Networks +6C7A63 (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 + US AC-EB-E6 (hex) Espressif Inc. ACEBE6 (base 16) Espressif Inc. @@ -185468,29 +185534,17 @@ ACEBE6 (base 16) Espressif Inc. Shanghai Shanghai 201203 CN -E8-B3-EE (hex) Pixelent Inc. -E8B3EE (base 16) Pixelent Inc. - #402 HanGuk Mediventure Center - 76, Dongnae-ro, Dong-gu Daegu 41061 - KR - -6C-7A-63 (hex) Arista Networks -6C7A63 (base 16) Arista Networks - 5453 Great America Parkway - Santa Clara CA 95054 - US - -C4-16-8F (hex) Apple, Inc. -C4168F (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +28-83-28 (hex) EMALDO TECHNOLOGY(HK)LIMITED +288328 (base 16) EMALDO TECHNOLOGY(HK)LIMITED + 13/F., Golden Dragon Comm. Bldg., 522 Nathan Road, Yau Ma Tei, Kowloon + HONG KONG 999077 + HK -F8-2A-E2 (hex) Apple, Inc. -F82AE2 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +40-44-F7 (hex) Nintendo Co.,Ltd +4044F7 (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP 84-5C-31 (hex) Dell Inc. 845C31 (base 16) Dell Inc. @@ -185540,18 +185594,6 @@ F82AE2 (base 16) Apple, Inc. TSAOTUEN, NANTOU 54261 TW -1C-7D-51 (hex) HANSHOW TECHNOLOGY CO.,LTD. -1C7D51 (base 16) HANSHOW TECHNOLOGY CO.,LTD. - The 1st Floor Podium and Floor 4 of Building 1, Floor 7 of Building 5, JiaxingPhotovoltaic Technology Innovation Park, No.1288, Kanghe Road, Xiuzhou District,Jiaxing City, Zhejiang Prov,P.R.China - JIAXING 314000 - CN - -3C-0F-02 (hex) Espressif Inc. -3C0F02 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 40-2C-F4 (hex) Universal Global Scientific Industrial., Ltd 402CF4 (base 16) Universal Global Scientific Industrial., Ltd 141, Lane 351, Taiping Rd. Sec. 1, Tsao Tuen, @@ -185570,10 +185612,16 @@ F82AE2 (base 16) Apple, Inc. Nan-Tou Taiwan 54261 TW -50-FB-FF (hex) Franklin Technology Inc. -50FBFF (base 16) Franklin Technology Inc. - 906(Gasan-Dong, JEI Platz), 186, Gasan digital 1-ro, Geumcheon-gu - Seoul 08502 +B0-1F-F4 (hex) Sagemcom Broadband SAS +B01FF4 (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR + +E8-B3-EE (hex) Pixelent Inc. +E8B3EE (base 16) Pixelent Inc. + #402 HanGuk Mediventure Center + 76, Dongnae-ro, Dong-gu Daegu 41061 KR E0-CD-B8 (hex) Huawei Device Co., Ltd. @@ -185606,23 +185654,29 @@ B4E5C5 (base 16) Huawei Device Co., Ltd. Dongguan 523808 CN -88-29-BF (hex) Amazon Technologies Inc. -8829BF (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 +C4-16-8F (hex) Apple, Inc. +C4168F (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -00-1A-B9 (hex) Groupe Carrus -001AB9 (base 16) Groupe Carrus - 56, avenue Raspail - Saint Maur 94100 - FR +F8-2A-E2 (hex) Apple, Inc. +F82AE2 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -C4-67-A1 (hex) Accelight Technologies (Wuhan) Inc. -C467A1 (base 16) Accelight Technologies (Wuhan) Inc. - 777 Guanggu 3rd, Bldg. #16, 5th Floor, - Wuhan Hubei, P. R. 430205 - CN +60-02-B4 (hex) WNC Corporation +6002B4 (base 16) WNC Corporation + No.20 Park Avenue II + Hsinchu 308 + TW + +00-0B-6B (hex) WNC Corporation +000B6B (base 16) WNC Corporation + No. 10-1, Li-Hsin Road I, Science-based + Hsinchu 300 + TW E0-37-BF (hex) WNC Corporation E037BF (base 16) WNC Corporation @@ -185636,6 +185690,12 @@ D86162 (base 16) WNC Corporation Hsin-Chu R.O.C. 308 TW +50-FB-FF (hex) Franklin Technology Inc. +50FBFF (base 16) Franklin Technology Inc. + 906(Gasan-Dong, JEI Platz), 186, Gasan digital 1-ro, Geumcheon-gu + Seoul 08502 + KR + 64-FF-0A (hex) WNC Corporation 64FF0A (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park @@ -185654,29 +185714,35 @@ F46C68 (base 16) WNC Corporation Hsin-Chu R.O.C. 308 TW +1C-7D-51 (hex) HANSHOW TECHNOLOGY CO.,LTD. +1C7D51 (base 16) HANSHOW TECHNOLOGY CO.,LTD. + The 1st Floor Podium and Floor 4 of Building 1, Floor 7 of Building 5, JiaxingPhotovoltaic Technology Innovation Park, No.1288, Kanghe Road, Xiuzhou District,Jiaxing City, Zhejiang Prov,P.R.China + JIAXING 314000 + CN + +3C-0F-02 (hex) Espressif Inc. +3C0F02 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + 58-96-71 (hex) WNC Corporation 589671 (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park Hsin-Chu R.O.C. 308 TW -D8-33-2A (hex) Ruijie Networks Co.,LTD -D8332A (base 16) Ruijie Networks Co.,LTD - Building 19,Juyuanzhou Industrial Park, No.618 Jinshan Avenue, Cangshan District - Fuzhou 35000 - CN - -60-02-B4 (hex) WNC Corporation -6002B4 (base 16) WNC Corporation - No.20 Park Avenue II - Hsinchu 308 - TW +88-29-BF (hex) Amazon Technologies Inc. +8829BF (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US -00-0B-6B (hex) WNC Corporation -000B6B (base 16) WNC Corporation - No. 10-1, Li-Hsin Road I, Science-based - Hsinchu 300 - TW +00-1A-B9 (hex) Groupe Carrus +001AB9 (base 16) Groupe Carrus + 56, avenue Raspail + Saint Maur 94100 + FR 24-D5-3B (hex) Motorola Mobility LLC, a Lenovo Company 24D53B (base 16) Motorola Mobility LLC, a Lenovo Company @@ -185690,42 +185756,24 @@ C834E5 (base 16) Cisco Systems, Inc San Jose CA 94568 US -98-9E-80 (hex) tonies GmbH -989E80 (base 16) tonies GmbH - Oststraße 119 - Düsseldorf NRW 40210 - DE - -24-C3-5D (hex) Duke University -24C35D (base 16) Duke University - 300 Fuller Street Box 104100 - Durham NC 27708 - US - -50-92-6A (hex) Beijing Xiaomi Mobile Software Co., Ltd -50926A (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 - CN - -04-1C-DB (hex) Siba Service -041CDB (base 16) Siba Service - 6F, Kobe Commerce, Industry and Trade Center Building, 5-1-14 Hamabe-dori, Chuo-ku - Kobe-shi Hyogo-ken 6510083 - JP - -98-3F-A4 (hex) zte corporation -983FA4 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN - 80-61-32 (hex) Cisco Systems, Inc 806132 (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US +C4-67-A1 (hex) Accelight Technologies (Wuhan) Inc. +C467A1 (base 16) Accelight Technologies (Wuhan) Inc. + 777 Guanggu 3rd, Bldg. #16, 5th Floor, + Wuhan Hubei, P. R. 430205 + CN + +D8-33-2A (hex) Ruijie Networks Co.,LTD +D8332A (base 16) Ruijie Networks Co.,LTD + Building 19,Juyuanzhou Industrial Park, No.618 Jinshan Avenue, Cangshan District + Fuzhou 35000 + CN + 88-18-F1 (hex) Nokia 8818F1 (base 16) Nokia 600 March Road @@ -185744,24 +185792,18 @@ E41613 (base 16) Extreme Networks Headquarters Morrisville NC 27560 US -F0-FB-7F (hex) Mellanox Technologies, Inc. -F0FB7F (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US - -84-45-A0 (hex) Tube investments of India Limited -8445A0 (base 16) Tube investments of India Limited - Chola crest, 4th floor, No. C 54-55 & Super B4, Thiru Vi Ka Industrial Area, Guindy, Chennai - 600032 - Chennai Other 600032 - IN - -30-BC-4F (hex) Beijing Jianguo Bite Technology Co., Ltd. -30BC4F (base 16) Beijing Jianguo Bite Technology Co., Ltd. - RM1321, Building 2, Taihua Longqi Square, 19 Huangping ROAD, Changping district - Beijing Beijing 100096 +98-3F-A4 (hex) zte corporation +983FA4 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN +E0-C9-32 (hex) Intel Corporate +E0C932 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + 54-36-31 (hex) Intel Corporate 543631 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 @@ -185780,17 +185822,29 @@ F0FB7F (base 16) Mellanox Technologies, Inc. Kulim Kedah 09000 MY -94-53-FF (hex) Intel Corporate -9453FF (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +98-9E-80 (hex) tonies GmbH +989E80 (base 16) tonies GmbH + Oststraße 119 + Düsseldorf NRW 40210 + DE -E0-C9-32 (hex) Intel Corporate -E0C932 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +24-C3-5D (hex) Duke University +24C35D (base 16) Duke University + 300 Fuller Street Box 104100 + Durham NC 27708 + US + +50-92-6A (hex) Beijing Xiaomi Mobile Software Co., Ltd +50926A (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 + CN + +04-1C-DB (hex) Siba Service +041CDB (base 16) Siba Service + 6F, Kobe Commerce, Industry and Trade Center Building, 5-1-14 Hamabe-dori, Chuo-ku + Kobe-shi Hyogo-ken 6510083 + JP A4-3F-A7 (hex) Hewlett Packard Enterprise A43FA7 (base 16) Hewlett Packard Enterprise @@ -185804,17 +185858,11 @@ A43FA7 (base 16) Hewlett Packard Enterprise New York NY New York NY 10017 US -54-C1-D3 (hex) Guangzhou TR Intelligent Manufacturing Technology Co., Ltd -54C1D3 (base 16) Guangzhou TR Intelligent Manufacturing Technology Co., Ltd - Room 3101, TCL Tower, 18 Haizhou Road, Haizhu District, Guangzhou, Guangdong - Guangzhou City Guangdong Province 510000 - CN - -E0-31-5D (hex) EM Microelectronic -E0315D (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH +94-53-FF (hex) Intel Corporate +9453FF (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY 00-12-C1 (hex) Check Point Software Technologies Ltd. 0012C1 (base 16) Check Point Software Technologies Ltd. @@ -185840,10 +185888,46 @@ F0ABFA (base 16) Shenzhen Rayin Technology Co.,Ltd shenzhen guangdong 518000 CN -A4-4A-64 (hex) Maverick Mobile LLC -A44A64 (base 16) Maverick Mobile LLC - 8350 N. Central Expwy #1900 - Dallas TX 75206 +F0-FB-7F (hex) Mellanox Technologies, Inc. +F0FB7F (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +84-45-A0 (hex) Tube investments of India Limited +8445A0 (base 16) Tube investments of India Limited + Chola crest, 4th floor, No. C 54-55 & Super B4, Thiru Vi Ka Industrial Area, Guindy, Chennai - 600032 + Chennai Other 600032 + IN + +30-BC-4F (hex) Beijing Jianguo Bite Technology Co., Ltd. +30BC4F (base 16) Beijing Jianguo Bite Technology Co., Ltd. + RM1321, Building 2, Taihua Longqi Square, 19 Huangping ROAD, Changping district + Beijing Beijing 100096 + CN + +68-9F-D4 (hex) Amazon Technologies Inc. +689FD4 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + +54-C1-D3 (hex) Guangzhou TR Intelligent Manufacturing Technology Co., Ltd +54C1D3 (base 16) Guangzhou TR Intelligent Manufacturing Technology Co., Ltd + Room 3101, TCL Tower, 18 Haizhou Road, Haizhu District, Guangzhou, Guangdong + Guangzhou City Guangdong Province 510000 + CN + +E0-31-5D (hex) EM Microelectronic +E0315D (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH + +50-D0-6D (hex) Bird Buddy +50D06D (base 16) Bird Buddy + 169 Madison Avenue, Suite 15233 + New York NY 10016 US 5C-C4-1D (hex) Stone Devices Sdn. Bhd. @@ -185852,10 +185936,22 @@ A44A64 (base 16) Maverick Mobile LLC SENAI JOHOR 81400 MY -68-9F-D4 (hex) Amazon Technologies Inc. -689FD4 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 +30-76-F5 (hex) Espressif Inc. +3076F5 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +A4-4A-64 (hex) Maverick Mobile LLC +A44A64 (base 16) Maverick Mobile LLC + 8350 N. Central Expwy #1900 + Dallas TX 75206 + US + +AC-E6-BB (hex) Google, Inc. +ACE6BB (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 US DC-44-B1 (hex) Hilti Corporation @@ -185870,12 +185966,6 @@ F4525B (base 16) Antare Technology Ltd London WC2A 2JR GB -50-D0-6D (hex) Bird Buddy -50D06D (base 16) Bird Buddy - 169 Madison Avenue, Suite 15233 - New York NY 10016 - US - 34-EF-8B (hex) NTT DOCOMO BUSINESS, Inc. 34EF8B (base 16) NTT DOCOMO BUSINESS, Inc. NTT DOCOMO BUSINESS Karagasaki Bldg. 1-11-7 Chuo-cho @@ -185894,22 +185984,28 @@ E0A366 (base 16) Motorola Mobility LLC, a Lenovo Company Shenzhen 518052 CN -30-76-F5 (hex) Espressif Inc. -3076F5 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 38-44-BE (hex) Espressif Inc. 3844BE (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area Shanghai Shanghai 201203 CN -AC-E6-BB (hex) Google, Inc. -ACE6BB (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 +30-46-9A (hex) NETGEAR +30469A (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US + +E0-46-9A (hex) NETGEAR +E0469A (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US + +A0-04-60 (hex) NETGEAR +A00460 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US 2C-30-33 (hex) NETGEAR @@ -185918,17 +186014,11 @@ ACE6BB (base 16) Google, Inc. San Jose CA 95134 US -E4-1B-43 (hex) Beijing Xiaomi Mobile Software Co., Ltd -E41B43 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 - CN - -38-0F-E4 (hex) Dedicated Network Partners Oy -380FE4 (base 16) Dedicated Network Partners Oy - Valimotie 13a - Helsinki 00380 - FI +50-4A-6E (hex) NETGEAR +504A6E (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US 54-07-7D (hex) NETGEAR 54077D (base 16) NETGEAR @@ -185972,6 +186062,24 @@ BCA511 (base 16) NETGEAR San Jose CA 95134 US +E4-1B-43 (hex) Beijing Xiaomi Mobile Software Co., Ltd +E41B43 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 + CN + +28-B6-7C (hex) KEBODA Intelligent TECHNOLOGY CO., LTD. +28B67C (base 16) KEBODA Intelligent TECHNOLOGY CO., LTD. + Building 7, Lane 36, Xuelin Road, Pudong New Area + Shanghai Shanghai 200120 + CN + +F0-ED-51 (hex) Qingdao Intelligent&Precise Electronics Co.,Ltd. +F0ED51 (base 16) Qingdao Intelligent&Precise Electronics Co.,Ltd. + No.218 Qianwangang Road + Qingdao Shangdong 266510 + CN + 60-A9-54 (hex) Cisco Systems, Inc 60A954 (base 16) Cisco Systems, Inc 80 West Tasman Drive @@ -185984,34 +186092,16 @@ BCA511 (base 16) NETGEAR San Jose CA 94568 US -30-46-9A (hex) NETGEAR -30469A (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US - -E0-46-9A (hex) NETGEAR -E0469A (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US - -A0-04-60 (hex) NETGEAR -A00460 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US - -50-4A-6E (hex) NETGEAR -504A6E (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US +38-0F-E4 (hex) Dedicated Network Partners Oy +380FE4 (base 16) Dedicated Network Partners Oy + Valimotie 13a + Helsinki 00380 + FI -F0-ED-51 (hex) Qingdao Intelligent&Precise Electronics Co.,Ltd. -F0ED51 (base 16) Qingdao Intelligent&Precise Electronics Co.,Ltd. - No.218 Qianwangang Road - Qingdao Shangdong 266510 +68-2A-DD (hex) zte corporation +682ADD (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN FC-3D-98 (hex) ACCTON TECHNOLOGY CORPORATION @@ -186020,18 +186110,6 @@ FC3D98 (base 16) ACCTON TECHNOLOGY CORPORATION Hsinchu 30077 TW -28-B6-7C (hex) KEBODA Intelligent TECHNOLOGY CO., LTD. -28B67C (base 16) KEBODA Intelligent TECHNOLOGY CO., LTD. - Building 7, Lane 36, Xuelin Road, Pudong New Area - Shanghai Shanghai 200120 - CN - -68-2A-DD (hex) zte corporation -682ADD (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN - 60-29-72 (hex) Arista Networks 602972 (base 16) Arista Networks 5453 Great America Parkway @@ -186062,12 +186140,6 @@ A4B1E9 (base 16) Vantiva Technologies Belgium Toronto Ontario M2N 6L7 CA -48-FC-7C (hex) Shenzhen Huidu Technology Co., Ltd. -48FC7C (base 16) Shenzhen Huidu Technology Co., Ltd. - 18F, No. 196 Tangtou Street, Shiyan Town, Baoan District, Shenzhen - Shenzhen 518000 - CN - 9C-DF-8A (hex) HUAWEI TECHNOLOGIES CO.,LTD 9CDF8A (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -186092,30 +186164,30 @@ FCA27E (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -00-92-35 (hex) Apple, Inc. -009235 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -F0-2F-BA (hex) Apple, Inc. -F02FBA (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -E4-CE-58 (hex) Anhui Realloong Automotive Electronics Co.,Ltd -E4CE58 (base 16) Anhui Realloong Automotive Electronics Co.,Ltd - Anhui Realloong Automotive Electronics Co.,Ltd - Hefei Anhui 230088 +48-FC-7C (hex) Shenzhen Huidu Technology Co., Ltd. +48FC7C (base 16) Shenzhen Huidu Technology Co., Ltd. + 18F, No. 196 Tangtou Street, Shiyan Town, Baoan District, Shenzhen + Shenzhen 518000 CN +F4-64-B6 (hex) Sercomm Corporation. +F464B6 (base 16) Sercomm Corporation. + 3F,No.81,Yu-Yih Rd.,Chu-Nan Chen + Miao-Lih Hsuan 115 + TW + 74-14-D0 (hex) Apple, Inc. 7414D0 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US +C0-EE-40 (hex) Ezurio, LLC +C0EE40 (base 16) Ezurio, LLC + 50 South Main St + Akron 44308 + US + 3C-FB-02 (hex) Apple, Inc. 3CFB02 (base 16) Apple, Inc. 1 Infinite Loop @@ -186140,43 +186212,22 @@ F478AC (base 16) Apple, Inc. Cupertino CA 95014 US -7C-24-6A (hex) Scita Solutions -7C246A (base 16) Scita Solutions - 218, 2nd Cross, ISRO Layout - Bangalore Karnataka 560078 - IN - -F4-64-B6 (hex) Sercomm Corporation. -F464B6 (base 16) Sercomm Corporation. - 3F,No.81,Yu-Yih Rd.,Chu-Nan Chen - Miao-Lih Hsuan 115 - TW - -C0-EE-40 (hex) Ezurio, LLC -C0EE40 (base 16) Ezurio, LLC - 50 South Main St - Akron 44308 +00-92-35 (hex) Apple, Inc. +009235 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -C8-AD-E7 (hex) Shenzhen Shengxi Industrial Co.,Ltd -C8ADE7 (base 16) Shenzhen Shengxi Industrial Co.,Ltd - Unit 402, Building 10, Yuanling New Village, Yuanling Community - Yuanling Street Futian District 518028 - CN - -AC-3D-FA (hex) Hangzhou Huacheng Network Technology Co.,Ltd -AC3DFA (base 16) Hangzhou Huacheng Network Technology Co.,Ltd - 13th Floor, Building 3, No. 582, Liye Road, Changhe Street, Binjiang District, Hangzhou, China (Zhejiang) Pilot Free Trade Zone - Hangzhou 311200 - CN - -8C-A4-54 (hex) Private -8CA454 (base 16) Private +F0-2F-BA (hex) Apple, Inc. +F02FBA (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -C8-74-1B (hex) Fiberhome Telecommunication Technologies Co.,LTD -C8741B (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 +E4-CE-58 (hex) Anhui Realloong Automotive Electronics Co.,Ltd +E4CE58 (base 16) Anhui Realloong Automotive Electronics Co.,Ltd + Anhui Realloong Automotive Electronics Co.,Ltd + Hefei Anhui 230088 CN F4-E2-5D (hex) AltoBeam Inc. @@ -186185,6 +186236,12 @@ F4E25D (base 16) AltoBeam Inc. Beijing Beijing 100083 CN +7C-24-6A (hex) Scita Solutions +7C246A (base 16) Scita Solutions + 218, 2nd Cross, ISRO Layout + Bangalore Karnataka 560078 + IN + CC-36-BB (hex) Silicon Laboratories CC36BB (base 16) Silicon Laboratories 400 West Cesar Chavez @@ -186203,10 +186260,22 @@ CC7645 (base 16) Microsoft Corporation Singapore 068902 SG -F0-16-1D (hex) Espressif Inc. -F0161D (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +C8-AD-E7 (hex) Shenzhen Shengxi Industrial Co.,Ltd +C8ADE7 (base 16) Shenzhen Shengxi Industrial Co.,Ltd + Unit 402, Building 10, Yuanling New Village, Yuanling Community + Yuanling Street Futian District 518028 + CN + +AC-3D-FA (hex) Hangzhou Huacheng Network Technology Co.,Ltd +AC3DFA (base 16) Hangzhou Huacheng Network Technology Co.,Ltd + 13th Floor, Building 3, No. 582, Liye Road, Changhe Street, Binjiang District, Hangzhou, China (Zhejiang) Pilot Free Trade Zone + Hangzhou 311200 + CN + +54-56-18 (hex) Huawei Device Co., Ltd. +545618 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN 8C-5D-54 (hex) Kisi @@ -186215,12 +186284,39 @@ F0161D (base 16) Espressif Inc. Brooklyn NY 11210 US -54-56-18 (hex) Huawei Device Co., Ltd. -545618 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +C8-74-1B (hex) Fiberhome Telecommunication Technologies Co.,LTD +C8741B (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 CN +F0-16-1D (hex) Espressif Inc. +F0161D (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +64-A3-37 (hex) Garmin International +64A337 (base 16) Garmin International + 1200 E. 151st St + Olathe KS 66062 + US + +8C-A4-54 (hex) Private +8CA454 (base 16) Private + +C0-CF-64 (hex) Hangzhou Zenith Electron Co.,Ltd +C0CF64 (base 16) Hangzhou Zenith Electron Co.,Ltd + Room 1702, No.888, Zhongxin Road, Beigan Street. Xiaoshan District, Hangzhou City, Zhejiang + Hangzhou Zhejiang 310000 + CN + +30-77-DF (hex) Terex Corporation +3077DF (base 16) Terex Corporation + 18620 NE 67th Ct + Redmond WA 98052 + US + 58-50-9F (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. 58509F (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China @@ -186245,17 +186341,17 @@ F0161D (base 16) Espressif Inc. Beijing 100029 CN -64-A3-37 (hex) Garmin International -64A337 (base 16) Garmin International - 1200 E. 151st St - Olathe KS 66062 - US +B4-38-36 (hex) HUAWEI TECHNOLOGIES CO.,LTD +B43836 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -5C-51-36 (hex) Samsung Electronics Co.,Ltd -5C5136 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +38-2F-B0 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +382FB0 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 + CN 34-56-ED (hex) Goerdyna Group Co., Ltd 3456ED (base 16) Goerdyna Group Co., Ltd @@ -186263,17 +186359,17 @@ F0161D (base 16) Espressif Inc. Qingdao City Shandong Province 266000 CN -C0-CF-64 (hex) Hangzhou Zenith Electron Co.,Ltd -C0CF64 (base 16) Hangzhou Zenith Electron Co.,Ltd - Room 1702, No.888, Zhongxin Road, Beigan Street. Xiaoshan District, Hangzhou City, Zhejiang - Hangzhou Zhejiang 310000 - CN +BC-AF-6E (hex) Arcadyan Corporation +BCAF6E (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW -30-77-DF (hex) Terex Corporation -3077DF (base 16) Terex Corporation - 18620 NE 67th Ct - Redmond WA 98052 - US +08-9C-74 (hex) UNIONMAN TECHNOLOGY CO.,LTD +089C74 (base 16) UNIONMAN TECHNOLOGY CO.,LTD + No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway + Huizhou Guangdong 516025 + CN 90-1F-09 (hex) Silicon Laboratories 901F09 (base 16) Silicon Laboratories @@ -186293,17 +186389,17 @@ B4BFE9 (base 16) Espressif Inc. Shanghai Shanghai 201203 CN -B4-38-36 (hex) HUAWEI TECHNOLOGIES CO.,LTD -B43836 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +5C-51-36 (hex) Samsung Electronics Co.,Ltd +5C5136 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -B0-42-B7 (hex) HUAWEI TECHNOLOGIES CO.,LTD -B042B7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +BC-27-7A (hex) Samsung Electronics Co.,Ltd +BC277A (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR 80-0D-3F (hex) Samsung Electronics Co.,Ltd 800D3F (base 16) Samsung Electronics Co.,Ltd @@ -186311,10 +186407,10 @@ B042B7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Suwon Gyeonggi-Do 16677 KR -38-2F-B0 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -382FB0 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 +B0-42-B7 (hex) HUAWEI TECHNOLOGIES CO.,LTD +B042B7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN 30-8B-23 (hex) Annapurna labs @@ -186347,23 +186443,11 @@ A49DB8 (base 16) SHENZHEN TECNO TECHNOLOGY Shenzhen guangdong 518000 CN -08-9C-74 (hex) UNIONMAN TECHNOLOGY CO.,LTD -089C74 (base 16) UNIONMAN TECHNOLOGY CO.,LTD - No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway - Huizhou Guangdong 516025 - CN - -BC-27-7A (hex) Samsung Electronics Co.,Ltd -BC277A (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -BC-AF-6E (hex) Arcadyan Corporation -BCAF6E (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW +AC-C3-58 (hex) AUMOVIO Czech Republic s.r.o. +ACC358 (base 16) AUMOVIO Czech Republic s.r.o. + Průmyslová 1851 + Brandýs nad Labem 250 01 + CZ E4-12-26 (hex) AUMOVIO Technologies Romania S.R.L. E41226 (base 16) AUMOVIO Technologies Romania S.R.L. @@ -186371,11 +186455,23 @@ E41226 (base 16) AUMOVIO Technologies Romania S.R.L. Timisoara 300701 RO -AC-C3-58 (hex) AUMOVIO Czech Republic s.r.o. -ACC358 (base 16) AUMOVIO Czech Republic s.r.o. - Průmyslová 1851 - Brandýs nad Labem 250 01 - CZ +A8-57-BA (hex) Shenzhen YOUHUA Technology Co., Ltd +A857BA (base 16) Shenzhen YOUHUA Technology Co., Ltd + Room 407 Shenzhen University-town Business Park,Lishan Road,Taoyuan Street,Nanshan District + Shenzhen Guangdong 518055 + CN + +64-18-DF (hex) Sagemcom Broadband SAS +6418DF (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR + +98-78-00 (hex) TCT mobile ltd +987800 (base 16) TCT mobile ltd + No.86 hechang 7th road, zhongkai, Hi-Tech District + Hui Zhou Guang Dong 516006 + CN 00-05-DB (hex) PSI Software SE, 0005DB (base 16) PSI Software SE, @@ -186383,18 +186479,6 @@ ACC358 (base 16) AUMOVIO Czech Republic s.r.o. Karlsruhe 76131 DE -C0-A4-B9 (hex) Sichuan AI-Link Technology Co., Ltd. -C0A4B9 (base 16) Sichuan AI-Link Technology Co., Ltd. - Anzhou, Industrial Park - Mianyang Sichuan 622650 - CN - -40-5A-DD (hex) Actions Microelectronics -405ADD (base 16) Actions Microelectronics - 201, No. 9 Building, Software Park, KeJiZhongEr Road., GaoXingQu, NanShan - Shenzhen Guangdong 518057 - CN - 80-E6-3C (hex) Xiaomi Communications Co Ltd 80E63C (base 16) Xiaomi Communications Co Ltd #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road @@ -186413,10 +186497,22 @@ C0A4B9 (base 16) Sichuan AI-Link Technology Co., Ltd. San Jose CA 95002 US -A8-57-BA (hex) Shenzhen YOUHUA Technology Co., Ltd -A857BA (base 16) Shenzhen YOUHUA Technology Co., Ltd - Room 407 Shenzhen University-town Business Park,Lishan Road,Taoyuan Street,Nanshan District - Shenzhen Guangdong 518055 +88-45-58 (hex) Amicro Technology Co., Ltd. +884558 (base 16) Amicro Technology Co., Ltd. + 14F Novotown Huixin Office Building,No. 88, Zhishui Road, Hengqin + Zhuhai Guangdong 519000 + CN + +10-CB-33 (hex) NXP Semiconductors Taiwan Ltd. +10CB33 (base 16) NXP Semiconductors Taiwan Ltd. + No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan + Nanzi Dist. Kaohsiung 811643 + TW + +40-5A-DD (hex) Actions Microelectronics +405ADD (base 16) Actions Microelectronics + 201, No. 9 Building, Software Park, KeJiZhongEr Road., GaoXingQu, NanShan + Shenzhen Guangdong 518057 CN 7C-87-67 (hex) Cisco Systems, Inc @@ -186431,29 +186527,29 @@ A857BA (base 16) Shenzhen YOUHUA Technology Co., Ltd San Jose CA 94568 US -64-18-DF (hex) Sagemcom Broadband SAS -6418DF (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +24-A5-FF (hex) Fairbanks Scales +24A5FF (base 16) Fairbanks Scales + 2176 Portland Street + St.Johnsbury VT 05819 + US -98-78-00 (hex) TCT mobile ltd -987800 (base 16) TCT mobile ltd - No.86 hechang 7th road, zhongkai, Hi-Tech District - Hui Zhou Guang Dong 516006 +C0-A4-B9 (hex) Sichuan AI-Link Technology Co., Ltd. +C0A4B9 (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou, Industrial Park + Mianyang Sichuan 622650 CN -88-45-58 (hex) Amicro Technology Co., Ltd. -884558 (base 16) Amicro Technology Co., Ltd. - 14F Novotown Huixin Office Building,No. 88, Zhishui Road, Hengqin - Zhuhai Guangdong 519000 +8C-22-D2 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +8C22D2 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 CN -10-CB-33 (hex) NXP Semiconductors Taiwan Ltd. -10CB33 (base 16) NXP Semiconductors Taiwan Ltd. - No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan - Nanzi Dist. Kaohsiung 811643 - TW +20-9B-DD (hex) HUAWEI TECHNOLOGIES CO.,LTD +209BDD (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN C4-49-1B (hex) Apple, Inc. C4491B (base 16) Apple, Inc. @@ -186473,10 +186569,16 @@ C4491B (base 16) Apple, Inc. Cupertino CA 95014 US -24-A5-FF (hex) Fairbanks Scales -24A5FF (base 16) Fairbanks Scales - 2176 Portland Street - St.Johnsbury VT 05819 +08-02-99 (hex) HC Corporation +080299 (base 16) HC Corporation + 1201, 12th F, Seongnam Woolim Lion’s Valley 1Bldg, 27, Dunchen-Daero 457beon-gil, Jungwon-gu + Seongnam Gyengido 13219 + KR + +80-77-86 (hex) IEEE Registration Authority +807786 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US 74-29-20 (hex) MCX-PRO Kft. @@ -186491,29 +186593,29 @@ C4491B (base 16) Apple, Inc. Cupertino CA 95014 US -60-47-0A (hex) Shenzhen Zenith Intelligent Technology Co., Ltd. -60470A (base 16) Shenzhen Zenith Intelligent Technology Co., Ltd. - Room 1606, Building C3, Nanshan Kexing Science Park, Nanshan District - Shenzhen 518000 +F8-0C-9A (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +F80C9A (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -8C-22-D2 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. -8C22D2 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. - No.555 Qianmo Road - Hangzhou Zhejiang 310052 +54-1F-CD (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +541FCD (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -20-9B-DD (hex) HUAWEI TECHNOLOGIES CO.,LTD -209BDD (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +60-47-0A (hex) Shenzhen Zenith Intelligent Technology Co., Ltd. +60470A (base 16) Shenzhen Zenith Intelligent Technology Co., Ltd. + Room 1606, Building C3, Nanshan Kexing Science Park, Nanshan District + Shenzhen 518000 CN -B8-32-8F (hex) eero inc. -B8328F (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US +94-FC-87 (hex) Hirschmann Automation and Control GmbH +94FC87 (base 16) Hirschmann Automation and Control GmbH + Stuttgarter Straße 45-51 + Neckartenzlingen D-72654 + DE F4-A3-C2 (hex) Shenzhen iComm Semiconductor CO.,LTD F4A3C2 (base 16) Shenzhen iComm Semiconductor CO.,LTD @@ -186527,78 +186629,30 @@ F4A3C2 (base 16) Shenzhen iComm Semiconductor CO.,LTD Shenzhen GuangDong 518000 CN -08-02-99 (hex) HC Corporation -080299 (base 16) HC Corporation - 1201, 12th F, Seongnam Woolim Lion’s Valley 1Bldg, 27, Dunchen-Daero 457beon-gil, Jungwon-gu - Seongnam Gyengido 13219 - KR - -3C-65-D1 (hex) HUAWEI TECHNOLOGIES CO.,LTD -3C65D1 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -80-77-86 (hex) IEEE Registration Authority -807786 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US - -F8-0C-9A (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -F80C9A (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN - -54-1F-CD (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -541FCD (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN - -AC-27-6E (hex) Espressif Inc. -AC276E (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 64-31-36 (hex) Mellanox Technologies, Inc. 643136 (base 16) Mellanox Technologies, Inc. 350 Oakmead Parkway, Suite 100 Sunnyvale CA 94085 US -88-F1-55 (hex) Espressif Inc. -88F155 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +3C-65-D1 (hex) HUAWEI TECHNOLOGIES CO.,LTD +3C65D1 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -94-FC-87 (hex) Hirschmann Automation and Control GmbH -94FC87 (base 16) Hirschmann Automation and Control GmbH - Stuttgarter Straße 45-51 - Neckartenzlingen D-72654 - DE - -E4-79-3F (hex) Juniper Networks -E4793F (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 +B8-32-8F (hex) eero inc. +B8328F (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 US -B8-55-EA (hex) Yantai Jahport Electronic Technology Co., Ltd. -B855EA (base 16) Yantai Jahport Electronic Technology Co., Ltd. - 10th Floor, R & D Center Building, Yantai Photoelectric Sensing Industrial Park, No. 2-1, Guiyang Street, Fushan District, Yantai City. - Yantai Shandong 264000 +E8-F0-94 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +E8F094 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 CN -8C-2A-C1 (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED -8C2AC1 (base 16) GSD VIET NAM TECHNOLOGY COMPANY LIMITED - PART OF FACTORY 2, LOT C2.10, D1 STREET, DONG AN 2 INDUSTRIAL PARK, BINHDUONG WARD - HO CHI MINH CITY HO CHI MINH 820000 - VN - B8-CD-58 (hex) Shenzhen SuperElectron Technology Co.,Ltd. B8CD58 (base 16) Shenzhen SuperElectron Technology Co.,Ltd. 1213-1214, haosheng business center, dongbin road, nanshan street, nanshan district, shenzhen city @@ -186617,17 +186671,29 @@ EC30DD (base 16) eero inc. San Francisco CA 94107 US -E8-F0-94 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -E8F094 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 +B8-55-EA (hex) Yantai Jahport Electronic Technology Co., Ltd. +B855EA (base 16) Yantai Jahport Electronic Technology Co., Ltd. + 10th Floor, R & D Center Building, Yantai Photoelectric Sensing Industrial Park, No. 2-1, Guiyang Street, Fushan District, Yantai City. + Yantai Shandong 264000 CN -3C-F7-5D (hex) Zyxel Communications Corporation -3CF75D (base 16) Zyxel Communications Corporation - No. 6 Innovation Road II, Science Park - Hsichu Taiwan 300 - TW +8C-2A-C1 (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED +8C2AC1 (base 16) GSD VIET NAM TECHNOLOGY COMPANY LIMITED + PART OF FACTORY 2, LOT C2.10, D1 STREET, DONG AN 2 INDUSTRIAL PARK, BINHDUONG WARD + HO CHI MINH CITY HO CHI MINH 820000 + VN + +88-F1-55 (hex) Espressif Inc. +88F155 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +AC-27-6E (hex) Espressif Inc. +AC276E (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN 00-B8-1D (hex) Extreme Networks Headquarters 00B81D (base 16) Extreme Networks Headquarters @@ -186641,12 +186707,24 @@ E8F094 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. Qingdao 266101 CN +E4-79-3F (hex) Juniper Networks +E4793F (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US + CC-58-C7 (hex) Nokia CC58C7 (base 16) Nokia 600 March Road Kanata Ontario K2K 2E6 CA +B0-95-01 (hex) EM Microelectronic +B09501 (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH + 64-1B-85 (hex) Vantiva USA LLC 641B85 (base 16) Vantiva USA LLC 4855 Peachtree Industrial Blvd, Suite 200 @@ -186659,12 +186737,6 @@ CC58C7 (base 16) Nokia Qingdao 266000 CN -B0-95-01 (hex) EM Microelectronic -B09501 (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH - D8-5B-27 (hex) WNC Corporation D85B27 (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park @@ -186677,12 +186749,6 @@ C42C7B (base 16) VIETNAM POST AND TELECOMMUNICATION INDUSTRY TECHNOLOGY JOI Hanoi 100000 VN -F4-A1-57 (hex) Huawei Device Co., Ltd. -F4A157 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - A8-72-4D (hex) Intel Corporate A8724D (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 @@ -186695,6 +186761,18 @@ A8724D (base 16) Intel Corporate Kulim Kedah 09000 MY +3C-F7-5D (hex) Zyxel Communications Corporation +3CF75D (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW + +F4-A1-57 (hex) Huawei Device Co., Ltd. +F4A157 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + 34-D7-F5 (hex) IEEE Registration Authority 34D7F5 (base 16) IEEE Registration Authority 445 Hoes Lane @@ -186719,6 +186797,12 @@ C47BE3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN +DC-5D-31 (hex) ITEL MOBILE LIMITED +DC5D31 (base 16) ITEL MOBILE LIMITED + RM B3 & B4 BLOCK B, KO FAI INDUSTRIAL BUILDING NO.7 KO FAI ROAD, YAU TONG, KLN, H.K + Hong Kong KOWLOON 999077 + HK + 60-72-0B (hex) BLU Products Inc 60720B (base 16) BLU Products Inc 8600 NW 36th Street Suite 200 @@ -186731,24 +186815,24 @@ C47BE3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Miami FL 33166 US -A8-CA-87 (hex) ZHEJIANG DAHUA ZHILIAN CO.,LTD -A8CA87 (base 16) ZHEJIANG DAHUA ZHILIAN CO.,LTD - No.28, Dongqiao Road, Dongzhou Street, Fuyang District, Hangzhou, P.R. China - HANGZHOU 311400 - CN - -A8-61-EC (hex) Texas Instruments -A861EC (base 16) Texas Instruments +74-6A-84 (hex) Texas Instruments +746A84 (base 16) Texas Instruments 12500 TI Blvd Dallas TX 75243 US -74-6A-84 (hex) Texas Instruments -746A84 (base 16) Texas Instruments +A8-61-EC (hex) Texas Instruments +A861EC (base 16) Texas Instruments 12500 TI Blvd Dallas TX 75243 US +A8-CA-87 (hex) ZHEJIANG DAHUA ZHILIAN CO.,LTD +A8CA87 (base 16) ZHEJIANG DAHUA ZHILIAN CO.,LTD + No.28, Dongqiao Road, Dongzhou Street, Fuyang District, Hangzhou, P.R. China + HANGZHOU 311400 + CN + 14-63-93 (hex) Espressif Inc. 146393 (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area @@ -186761,11 +186845,11 @@ B08CB3 (base 16) FN-LINK TECHNOLOGY Ltd. Changsha Hunan 410329 CN -DC-5D-31 (hex) ITEL MOBILE LIMITED -DC5D31 (base 16) ITEL MOBILE LIMITED - RM B3 & B4 BLOCK B, KO FAI INDUSTRIAL BUILDING NO.7 KO FAI ROAD, YAU TONG, KLN, H.K - Hong Kong KOWLOON 999077 - HK +F0-0C-51 (hex) zte corporation +F00C51 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN 80-E8-69 (hex) AltoBeam Inc. 80E869 (base 16) AltoBeam Inc. @@ -186779,6 +186863,18 @@ D489C1 (base 16) Ubiquiti Inc New York NY New York NY 10017 US +24-D6-60 (hex) Silicon Laboratories +24D660 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + +18-C3-E4 (hex) IEEE Registration Authority +18C3E4 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + 8C-0F-7E (hex) TCL King Electrical Appliances(Huizhou)Co.,Ltd 8C0F7E (base 16) TCL King Electrical Appliances(Huizhou)Co.,Ltd B Area, 10th floor, TCL multimedia Building, TCL International E City, #1001 Zhonshanyuan road,Shenzhen @@ -186797,12 +186893,6 @@ D489C1 (base 16) Ubiquiti Inc Nanzi Dist. Kaohsiung 811643 TW -F0-0C-51 (hex) zte corporation -F00C51 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN - 08-95-36 (hex) Actiontec Electronics Inc. 089536 (base 16) Actiontec Electronics Inc. 2445 Augustine Dr #501 @@ -186815,48 +186905,18 @@ F00C51 (base 16) zte corporation San Jose CA 94568 US -90-6F-18 (hex) Afero, Inc. -906F18 (base 16) Afero, Inc. - 4410 El Camino Real, Suite 200 - Los Altos 94022 - US - -24-D6-60 (hex) Silicon Laboratories -24D660 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US - -18-C3-E4 (hex) IEEE Registration Authority -18C3E4 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US - -18-4F-5D (hex) Japan Radio Co., Ltd -184F5D (base 16) Japan Radio Co., Ltd - NAKANO CENTRAL PARK EAST 10-1, Nakano 4-chome - Nakano-ku Tokyo 164-8570 - JP - -6C-28-13 (hex) nFore Technology Co., Ltd. -6C2813 (base 16) nFore Technology Co., Ltd. - 5F, No.31, Ln. 258, Ruiguang Rd., Neihu Dist., - Taipei city 114 - TW - -08-35-7D (hex) Microsoft Corporation -08357D (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 - US - C0-3A-55 (hex) TP-Link Systems Inc. C03A55 (base 16) TP-Link Systems Inc. 10 Mauchly Irvine CA 92618 US +90-6F-18 (hex) Afero, Inc. +906F18 (base 16) Afero, Inc. + 4410 El Camino Real, Suite 200 + Los Altos 94022 + US + B8-87-88 (hex) HP Inc. B88788 (base 16) HP Inc. 10300 Energy Dr @@ -186887,6 +186947,30 @@ F8CB15 (base 16) Apple, Inc. Cupertino CA 95014 US +18-4F-5D (hex) Japan Radio Co., Ltd +184F5D (base 16) Japan Radio Co., Ltd + NAKANO CENTRAL PARK EAST 10-1, Nakano 4-chome + Nakano-ku Tokyo 164-8570 + JP + +6C-28-13 (hex) nFore Technology Co., Ltd. +6C2813 (base 16) nFore Technology Co., Ltd. + 5F, No.31, Ln. 258, Ruiguang Rd., Neihu Dist., + Taipei city 114 + TW + +08-35-7D (hex) Microsoft Corporation +08357D (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US + +74-25-54 (hex) NVIDIA Corporation +742554 (base 16) NVIDIA Corporation + 2701 San Tomas Expressway + Santa Clara CA 95050 + US + 78-45-DC (hex) HUAWEI TECHNOLOGIES CO.,LTD 7845DC (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -186899,6 +186983,12 @@ F8CB15 (base 16) Apple, Inc. Dongguan 523808 CN +50-37-CD (hex) Quectel Wireless Solutions Co., Ltd. +5037CD (base 16) Quectel Wireless Solutions Co., Ltd. + Building 5, Shanghai Business Park Phase III (Area B), No.1016 Tianlin Road, Minhang District + Shanghai 200233 + CN + D4-CE-40 (hex) Apple, Inc. D4CE40 (base 16) Apple, Inc. 1 Infinite Loop @@ -186923,28 +187013,28 @@ F0D018 (base 16) Hewlett Packard Enterprise Bocholt NRW 46397 DE -D8-62-CA (hex) Cisco Systems, Inc -D862CA (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - 44-78-31 (hex) HUAWEI TECHNOLOGIES CO.,LTD 447831 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -50-37-CD (hex) Quectel Wireless Solutions Co., Ltd. -5037CD (base 16) Quectel Wireless Solutions Co., Ltd. - Building 5, Shanghai Business Park Phase III (Area B), No.1016 Tianlin Road, Minhang District - Shanghai 200233 +D8-62-CA (hex) Cisco Systems, Inc +D862CA (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +F8-1B-2E (hex) G.Tech Technology Ltd. +F81B2E (base 16) G.Tech Technology Ltd. + No.8,Jinyuan 1st Road,Tangjiawan Town, High-tech Zone + Zhuhai Guangdong 519085 CN -74-25-54 (hex) NVIDIA Corporation -742554 (base 16) NVIDIA Corporation - 2701 San Tomas Expressway - Santa Clara CA 95050 +E4-FB-1E (hex) Microsoft Corporation +E4FB1E (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 US 54-E6-FD (hex) Sony Interactive Entertainment Inc. @@ -186965,6 +187055,72 @@ D862CA (base 16) Cisco Systems, Inc Beijing 100083 CN +F4-1A-F7 (hex) zte corporation +F41AF7 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +D4-50-39 (hex) Sagemcom Broadband SAS +D45039 (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR + +38-B1-4E (hex) IEEE Registration Authority +38B14E (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +9C-CE-22 (hex) PROMED Soest GmbH +9CCE22 (base 16) PROMED Soest GmbH + Wasserfuhr 5 + Soest 59494 + DE + +68-48-B4 (hex) AltoBeam Inc. +6848B4 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN + +EC-72-F7 (hex) DJI BAIWANG TECHNOLOGY CO LTD +EC72F7 (base 16) DJI BAIWANG TECHNOLOGY CO LTD + Room 101, Building 12, Baiwangxin Industrial Park, 1002 Songbai Road, Sunshine Community, Xili Street + Shenzhen Guangdong 518057 + CN + +B8-D5-AD (hex) Nokia +B8D5AD (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA + +88-03-55 (hex) Arcadyan Corporation +880355 (base 16) Arcadyan Corporation + 4F., No.9 , Park Avenue II + Hsinchu 300 + TW + +00-23-08 (hex) Arcadyan Corporation +002308 (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II , + Hsinchu 300 + TW + +9C-80-DF (hex) Arcadyan Corporation +9C80DF (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II , + Hsinchu 300 + TW + +4C-09-D4 (hex) Arcadyan Corporation +4C09D4 (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II , + Hsinchu 300 + TW + C8-5C-E2 (hex) IEEE Registration Authority C85CE2 (base 16) IEEE Registration Authority 445 Hoes Lane @@ -204470,30 +204626,6 @@ BC620E (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -00-1D-19 (hex) Arcadyan Technology Corporation -001D19 (base 16) Arcadyan Technology Corporation - 4F., No. 9 , Park Avenue II, - Hsinchu 300 - TW - -00-12-BF (hex) Arcadyan Technology Corporation -0012BF (base 16) Arcadyan Technology Corporation - 4F, No. 9, Park Avenue II - Hsinchu 300 - TW - -50-7E-5D (hex) Arcadyan Technology Corporation -507E5D (base 16) Arcadyan Technology Corporation - 4F, No. 9, Park Avenue II , - Hsinchu 300 - TW - -7C-4F-B5 (hex) Arcadyan Technology Corporation -7C4FB5 (base 16) Arcadyan Technology Corporation - 4F, No. 9, Park Avenue II , - Hsinchu 300 - TW - 00-20-D4 (hex) Cabletron Systems, Inc. 0020D4 (base 16) Cabletron Systems, Inc. 35 INDUSTRIAL WAY @@ -231071,6 +231203,18 @@ A083B4 (base 16) Velorum B.V Haps 5443NA NL +8C-05-72 (hex) Huawei Device Co., Ltd. +8C0572 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +9C-7D-C0 (hex) Tech4home, Lda +9C7DC0 (base 16) Tech4home, Lda + Rua de Fundoes N151 + Sao Joao da Madeira Aveiro 3700-121 + PT + 60-0A-8C (hex) Shenzhen Sundray Technologies company Limited 600A8C (base 16) Shenzhen Sundray Technologies company Limited 1st Floor Building A1, Nanshan i Park, No.1001 Xueyuan Road, Nanshan District, Shenzhen, Guangdong Province, P. R. China @@ -231089,23 +231233,17 @@ B00B22 (base 16) Huawei Device Co., Ltd. Dongguan Guangdong 523808 CN -8C-05-72 (hex) Huawei Device Co., Ltd. -8C0572 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - 10-25-CE (hex) ELKA - Torantriebe GmbH u. Co. Betriebs KG 1025CE (base 16) ELKA - Torantriebe GmbH u. Co. Betriebs KG Dithmarscher Straße 9 Tönning 25832 DE -9C-7D-C0 (hex) Tech4home, Lda -9C7DC0 (base 16) Tech4home, Lda - Rua de Fundoes N151 - Sao Joao da Madeira Aveiro 3700-121 - PT +B4-E5-3E (hex) Ruckus Wireless +B4E53E (base 16) Ruckus Wireless + 350 West Java Drive + Sunnyvale CA 94089 + US 20-A7-16 (hex) Silicon Laboratories 20A716 (base 16) Silicon Laboratories @@ -231161,24 +231299,6 @@ F0B014 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Berlin Berlin 10559 DE -48-5D-35 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -485D35 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE - -B4-FC-7D (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -B4FC7D (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE - -98-A9-65 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -98A965 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE - EC-74-27 (hex) eero inc. EC7427 (base 16) eero inc. 660 3rd Street @@ -231209,6 +231329,24 @@ A08E24 (base 16) eero inc. San Francisco CA 94107 US +74-42-7F (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +74427F (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +1C-ED-6F (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +1CED6F (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +48-5D-35 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +485D35 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + B4-20-46 (hex) eero inc. B42046 (base 16) eero inc. 660 3rd Street @@ -231233,14 +231371,38 @@ D405DE (base 16) eero inc. San Francisco CA 94107 US -74-42-7F (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -74427F (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +7C-49-CF (hex) eero inc. +7C49CF (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +24-2D-6C (hex) eero inc. +242D6C (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +30-3A-4A (hex) eero inc. +303A4A (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +DC-69-B5 (hex) eero inc. +DC69B5 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +B4-FC-7D (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +B4FC7D (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -1C-ED-6F (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -1CED6F (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +98-A9-65 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +98A965 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE @@ -231257,12 +231419,6 @@ A8B088 (base 16) eero inc. San Francisco CA 94107 US -B4-E5-3E (hex) Ruckus Wireless -B4E53E (base 16) Ruckus Wireless - 350 West Java Drive - Sunnyvale CA 94089 - US - 88-67-46 (hex) eero inc. 886746 (base 16) eero inc. 660 3rd Street @@ -231299,6 +231455,36 @@ FC3D73 (base 16) eero inc. Beijing 100053 CN +E4-C0-CC (hex) China Mobile Group Device Co.,Ltd. +E4C0CC (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +C0-16-92 (hex) China Mobile Group Device Co.,Ltd. +C01692 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +00-E2-2C (hex) China Mobile Group Device Co.,Ltd. +00E22C (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +E0-E0-C2 (hex) China Mobile Group Device Co.,Ltd. +E0E0C2 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +78-2E-56 (hex) China Mobile Group Device Co.,Ltd. +782E56 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + 00-CF-C0 (hex) China Mobile Group Device Co.,Ltd. 00CFC0 (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District @@ -231323,32 +231509,26 @@ E0456D (base 16) China Mobile Group Device Co.,Ltd. Beijing 100053 CN -E4-C0-CC (hex) China Mobile Group Device Co.,Ltd. -E4C0CC (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - -C0-16-92 (hex) China Mobile Group Device Co.,Ltd. -C01692 (base 16) China Mobile Group Device Co.,Ltd. +5C-75-C6 (hex) China Mobile Group Device Co.,Ltd. +5C75C6 (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District Beijing 100053 CN -00-E2-2C (hex) China Mobile Group Device Co.,Ltd. -00E22C (base 16) China Mobile Group Device Co.,Ltd. +24-12-81 (hex) China Mobile Group Device Co.,Ltd. +241281 (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District Beijing 100053 CN -E0-E0-C2 (hex) China Mobile Group Device Co.,Ltd. -E0E0C2 (base 16) China Mobile Group Device Co.,Ltd. +64-C5-82 (hex) China Mobile Group Device Co.,Ltd. +64C582 (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District Beijing 100053 CN -78-2E-56 (hex) China Mobile Group Device Co.,Ltd. -782E56 (base 16) China Mobile Group Device Co.,Ltd. +44-8E-EC (hex) China Mobile Group Device Co.,Ltd. +448EEC (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District Beijing 100053 CN @@ -231371,48 +231551,6 @@ A088C2 (base 16) Mellanox Technologies, Inc. Sunnyvale CA 94085 US -7C-49-CF (hex) eero inc. -7C49CF (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -24-2D-6C (hex) eero inc. -242D6C (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -30-3A-4A (hex) eero inc. -303A4A (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -DC-69-B5 (hex) eero inc. -DC69B5 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -5C-75-C6 (hex) China Mobile Group Device Co.,Ltd. -5C75C6 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - -24-12-81 (hex) China Mobile Group Device Co.,Ltd. -241281 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - -64-C5-82 (hex) China Mobile Group Device Co.,Ltd. -64C582 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - E0-9D-73 (hex) Mellanox Technologies, Inc. E09D73 (base 16) Mellanox Technologies, Inc. 350 Oakmead Parkway, Suite 100 @@ -231425,12 +231563,6 @@ E09D73 (base 16) Mellanox Technologies, Inc. Sunnyvale CA 94085 US -44-8E-EC (hex) China Mobile Group Device Co.,Ltd. -448EEC (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - 50-CF-56 (hex) China Mobile Group Device Co.,Ltd. 50CF56 (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District @@ -231443,11 +231575,11 @@ C82478 (base 16) Edifier International Hong Kong 070 CN -F8-F2-95 (hex) Annapurna labs -F8F295 (base 16) Annapurna labs - Matam Scientific Industries Center, Building 8.2 - Mail box 15123 Haifa 3508409 - IL +D4-A0-FB (hex) IEEE Registration Authority +D4A0FB (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US E0-42-6D (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD E0426D (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD @@ -231455,18 +231587,18 @@ E0426D (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD DONG GUAN GUANG DONG 523860 CN +F8-F2-95 (hex) Annapurna labs +F8F295 (base 16) Annapurna labs + Matam Scientific Industries Center, Building 8.2 + Mail box 15123 Haifa 3508409 + IL + 80-03-0D (hex) CANON INC. 80030D (base 16) CANON INC. 30-2 Shimomaruko 3-chome, Ohta-ku Tokyo 146-8501 JP -D4-A0-FB (hex) IEEE Registration Authority -D4A0FB (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US - 18-C1-E2 (hex) Qolsys Inc. 18C1E2 (base 16) Qolsys Inc. 1919 S Bascom Ave Suit 600 @@ -231509,6 +231641,18 @@ B0F3E9 (base 16) PATEO CONNECT (Xiamen) Co., Ltd. shenzhen guangdong 518057 CN +00-C8-4E (hex) Hewlett Packard Enterprise +00C84E (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US + +9C-13-9E (hex) Espressif Inc. +9C139E (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + 84-31-A8 (hex) Funshion Online Technologies Co.,Ltd 8431A8 (base 16) Funshion Online Technologies Co.,Ltd 2101, Floor 1-2, Building 9, Anzhen Xili District 3, Chaoyang District, Beijing @@ -231521,40 +231665,28 @@ B0F3E9 (base 16) PATEO CONNECT (Xiamen) Co., Ltd. Beijing 100029 CN -D4-7A-EC (hex) Funshion Online Technologies Co.,Ltd -D47AEC (base 16) Funshion Online Technologies Co.,Ltd - 2101, Floor 1-2, Building 9, Anzhen Xili District 3, Chaoyang District, Beijing - Beijing 100029 - CN - 40-A7-86 (hex) TECNO MOBILE LIMITED 40A786 (base 16) TECNO MOBILE LIMITED ROOMS 05-15, 13A/F., SOUTH TOWER, WORLD FINANCE CENTRE, HARBOUR CITY, 17 CANTON ROAD, TSIM SHA TSUI, KOWLOON, HONG KONG Hong Kong Hong Kong 999077 HK -9C-13-9E (hex) Espressif Inc. -9C139E (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +D4-7A-EC (hex) Funshion Online Technologies Co.,Ltd +D47AEC (base 16) Funshion Online Technologies Co.,Ltd + 2101, Floor 1-2, Building 9, Anzhen Xili District 3, Chaoyang District, Beijing + Beijing 100029 CN -00-C8-4E (hex) Hewlett Packard Enterprise -00C84E (base 16) Hewlett Packard Enterprise - 6280 America Center Dr - San Jose CA 95002 - US - 88-DA-36 (hex) Calix Inc. 88DA36 (base 16) Calix Inc. 2777 Orchard Pkwy San Jose CA 95131 US -98-17-1A (hex) Beijing Xiaomi Mobile Software Co., Ltd -98171A (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 +40-10-ED (hex) G.Tech Technology Ltd. +4010ED (base 16) G.Tech Technology Ltd. + No.8,Jinyuan 1st Road,Tangjiawan Town, High-tech Zone + Zhuhai Guangdong 519085 CN EC-10-55 (hex) Beijing Xiaomi Electronics Co.,Ltd @@ -231569,6 +231701,12 @@ EC1055 (base 16) Beijing Xiaomi Electronics Co.,Ltd Shanghai Shanghai 201203 CN +98-17-1A (hex) Beijing Xiaomi Mobile Software Co., Ltd +98171A (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 + CN + 2C-DC-C1 (hex) EM Microelectronic 2CDCC1 (base 16) EM Microelectronic Rue des Sors 3 @@ -231593,10 +231731,16 @@ D853AD (base 16) Cisco Meraki San Francisco 94158 US -40-10-ED (hex) G.Tech Technology Ltd. -4010ED (base 16) G.Tech Technology Ltd. - No.8,Jinyuan 1st Road,Tangjiawan Town, High-tech Zone - Zhuhai Guangdong 519085 +30-F8-56 (hex) Extreme Networks Headquarters +30F856 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville 27560 + US + +80-40-05 (hex) Guangdong COROS Sports Technology Co.,Ltd +804005 (base 16) Guangdong COROS Sports Technology Co.,Ltd + Room 601 & 701, Bld. 2, No.2, Science and Technology 9 Rd, Songshan Lake Hi-Tech Zone, Dongguan 523808, Guandong, China + Dongguan Guangdong 523808 CN 68-A5-93 (hex) Apple, Inc. @@ -231629,10 +231773,10 @@ B8011F (base 16) Apple, Inc. Hui Zhou Guangdong 516025 CN -80-40-05 (hex) Guangdong COROS Sports Technology Co.,Ltd -804005 (base 16) Guangdong COROS Sports Technology Co.,Ltd - Room 601 & 701, Bld. 2, No.2, Science and Technology 9 Rd, Songshan Lake Hi-Tech Zone, Dongguan 523808, Guandong, China - Dongguan Guangdong 523808 +B0-25-AA (hex) AIstone Global Limited +B025AA (base 16) AIstone Global Limited + 29/F. , One Exchange Square 8 + Connaught Place Centa Hong Kong 999077 CN DC-93-96 (hex) Apple, Inc. @@ -231653,18 +231797,24 @@ CCEA27 (base 16) GE Appliances Louisville KY 40225 US -30-F8-56 (hex) Extreme Networks Headquarters -30F856 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville 27560 - US - 8C-3D-16 (hex) Shenzhen Four Seas Global Link Network Technology Co.,Ltd 8C3D16 (base 16) Shenzhen Four Seas Global Link Network Technology Co.,Ltd 9/F, Block H, South China Digital Valley, No.1 South China Road, Longhua District, Shenzhen ,China Shenzhen 518000 CN +48-F6-EE (hex) Espressif Inc. +48F6EE (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +7C-31-FA (hex) Silicon Laboratories +7C31FA (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + C0-88-40 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. C08840 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. Midea Global Innovation Center,Beijiao Town,Shunde @@ -231683,12 +231833,6 @@ D0C67F (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -B0-25-AA (hex) AIstone Global Limited -B025AA (base 16) AIstone Global Limited - 29/F. , One Exchange Square 8 - Connaught Place Centa Hong Kong 999077 - CN - A0-39-F9 (hex) Sagemcom Broadband SAS A039F9 (base 16) Sagemcom Broadband SAS 250, route de l'Empereur @@ -231701,6 +231845,12 @@ B48931 (base 16) Silicon Laboratories Austin TX 78701 US +10-5E-AE (hex) New H3C Technologies Co., Ltd +105EAE (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN + 4C-AD-DF (hex) Công ty Cổ phần Thiết bị Công nghiệp GEIC 4CADDF (base 16) Công ty Cổ phần Thiết bị Công nghiệp GEIC 52 Lê Đại Hành, phường Lê Đại Hành, quận Hai Bà Trưng @@ -231719,17 +231869,11 @@ B48931 (base 16) Silicon Laboratories Reno NV 89507 US -48-F6-EE (hex) Espressif Inc. -48F6EE (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -7C-31-FA (hex) Silicon Laboratories -7C31FA (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US +08-EB-21 (hex) Intel Corporate +08EB21 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY 3C-A0-70 (hex) Blink by Amazon 3CA070 (base 16) Blink by Amazon @@ -231737,11 +231881,11 @@ B48931 (base 16) Silicon Laboratories North Reading MA 01864 US -10-5E-AE (hex) New H3C Technologies Co., Ltd -105EAE (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 - CN +E8-C9-13 (hex) Samsung Electronics Co.,Ltd +E8C913 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR 24-F4-0A (hex) Samsung Electronics Co.,Ltd 24F40A (base 16) Samsung Electronics Co.,Ltd @@ -231749,26 +231893,20 @@ B48931 (base 16) Silicon Laboratories Gumi Gyeongbuk 730-350 KR +58-E4-EB (hex) FN-LINK TECHNOLOGY Ltd. +58E4EB (base 16) FN-LINK TECHNOLOGY Ltd. + No.8, Litong Road, Liuyang Economic & Technical Development Zone, Changsha, Hunan,China + Changsha Hunan 410329 + CN + 78-C1-1D (hex) Samsung Electronics Co.,Ltd 78C11D (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR -E8-C9-13 (hex) Samsung Electronics Co.,Ltd -E8C913 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -4C-A9-54 (hex) Intel Corporate -4CA954 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -08-EB-21 (hex) Intel Corporate -08EB21 (base 16) Intel Corporate +4C-A9-54 (hex) Intel Corporate +4CA954 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY @@ -231779,11 +231917,11 @@ E8C913 (base 16) Samsung Electronics Co.,Ltd New Taipei City 23845 TW -58-E4-EB (hex) FN-LINK TECHNOLOGY Ltd. -58E4EB (base 16) FN-LINK TECHNOLOGY Ltd. - No.8, Litong Road, Liuyang Economic & Technical Development Zone, Changsha, Hunan,China - Changsha Hunan 410329 - CN +14-05-89 (hex) Motorola Mobility LLC, a Lenovo Company +140589 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US 98-3A-1F (hex) Google, Inc. 983A1F (base 16) Google, Inc. @@ -231815,24 +231953,6 @@ B06B11 (base 16) Hui Zhou Gaoshengda Technology Co.,LTD Beijing Haidian District 100085 CN -14-05-89 (hex) Motorola Mobility LLC, a Lenovo Company -140589 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 - US - -80-6A-34 (hex) Bouffalo Lab (Nanjing) Co., Ltd. -806A34 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. - 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China - Nanjing Jiangsu 211800 - CN - -54-DD-21 (hex) Huawei Device Co., Ltd. -54DD21 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - AC-10-65 (hex) KT Micro, Inc. AC1065 (base 16) KT Micro, Inc. Building 76, National Cybersecurity Industry Park, Beiwucun Road 23, Haidian District, Beijing @@ -231851,6 +231971,18 @@ D4FF26 (base 16) OHSUNG Reno NV 89507 US +80-6A-34 (hex) Bouffalo Lab (Nanjing) Co., Ltd. +806A34 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. + 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China + Nanjing Jiangsu 211800 + CN + +54-DD-21 (hex) Huawei Device Co., Ltd. +54DD21 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + A8-24-50 (hex) Beijing Huadianzhongxin Tech.Co.,Ltd A82450 (base 16) Beijing Huadianzhongxin Tech.Co.,Ltd Room 318,the 3rd Floorl,Xingtianhaiyuan Building,Xianghuangqi East Rd,Nongda South Rd, Haidian District,Beijing,P.R.C @@ -231875,6 +232007,12 @@ C8E31D (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN +E4-56-AC (hex) Silicon Laboratories +E456AC (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + 0C-33-1B (hex) TydenBrooks 0C331B (base 16) TydenBrooks 2727 Paces Ferry Rd, Building 2, Suite 300 @@ -231905,6 +232043,36 @@ E46E8A (base 16) BYD Lithium Battery Co., Ltd. Shen Zhen Guang Dong 518100 CN +C8-C8-3F (hex) Texas Instruments +C8C83F (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +E0-D4-91 (hex) Cisco Systems, Inc +E0D491 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +A4-DC-D5 (hex) Cisco Systems, Inc +A4DCD5 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +D8-52-FA (hex) Texas Instruments +D852FA (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +38-E2-C4 (hex) Texas Instruments +38E2C4 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + 28-57-5D (hex) Apple, Inc. 28575D (base 16) Apple, Inc. 1 Infinite Loop @@ -231917,24 +232085,12 @@ E46E8A (base 16) BYD Lithium Battery Co., Ltd. Cupertino CA 95014 US -E4-56-AC (hex) Silicon Laboratories -E456AC (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US - 54-91-E1 (hex) Vitalacy Inc. 5491E1 (base 16) Vitalacy Inc. 11859 Wilshire Blvd #500 Los Angeles CA 90025 US -D8-52-FA (hex) Texas Instruments -D852FA (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US - F4-33-B7 (hex) Apple, Inc. F433B7 (base 16) Apple, Inc. 1 Infinite Loop @@ -231953,29 +232109,17 @@ D42F4B (base 16) Hon Hai Precision Industry Co.,LTD TAIPEI 66.Chung Shan RD, TU-CHENG Industrial , district new TAIPEI 33859 CN -38-E2-C4 (hex) Texas Instruments -38E2C4 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US - -C8-C8-3F (hex) Texas Instruments -C8C83F (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US - -E0-D4-91 (hex) Cisco Systems, Inc -E0D491 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +90-29-62 (hex) Linkpower Microelectronics Co., Ltd. +902962 (base 16) Linkpower Microelectronics Co., Ltd. + 905, B1, 999-8, Gaolang East Road, Wuxi Economic Development Zone, Jiangsu Province + wuxi jiangsu 214131 + CN -A4-DC-D5 (hex) Cisco Systems, Inc -A4DCD5 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +84-9D-4B (hex) Shenzhen Boomtech Industrial Corporation +849D4B (base 16) Shenzhen Boomtech Industrial Corporation + 905/906,BuildingA, Huizhi R&D Center. Xixiang,Bao'an District + Shenzhen 518100 + CN 54-FB-66 (hex) ASRock Incorporation 54FB66 (base 16) ASRock Incorporation @@ -231983,12 +232127,6 @@ A4DCD5 (base 16) Cisco Systems, Inc Taipei 112 TW -90-29-62 (hex) Linkpower Microelectronics Co., Ltd. -902962 (base 16) Linkpower Microelectronics Co., Ltd. - 905, B1, 999-8, Gaolang East Road, Wuxi Economic Development Zone, Jiangsu Province - wuxi jiangsu 214131 - CN - 2C-15-7E (hex) RADIODATA GmbH 2C157E (base 16) RADIODATA GmbH Newtonstraße 18 @@ -232013,24 +232151,12 @@ A4DCD5 (base 16) Cisco Systems, Inc shenzhen guangdong 518000 CN -A4-3A-39 (hex) AURORA TECHNOLOGIES CO.,LTD. -A43A39 (base 16) AURORA TECHNOLOGIES CO.,LTD. - ROOM 1006, BLOCK B, QIANHAI ECONOMIC AND TRADE CENTER, CHINA MERCHANTS GROUP, NO.151 WEST FREE TRADE STREET, QIANHAI, - SHENZHEN 518000 - CN - 34-E1-D7 (hex) NXP Semiconductors Taiwan Ltd. 34E1D7 (base 16) NXP Semiconductors Taiwan Ltd. No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan Nanzi Dist. Kaohsiung 811643 TW -84-9D-4B (hex) Shenzhen Boomtech Industrial Corporation -849D4B (base 16) Shenzhen Boomtech Industrial Corporation - 905/906,BuildingA, Huizhi R&D Center. Xixiang,Bao'an District - Shenzhen 518100 - CN - 70-A3-A4 (hex) Beijing Guming Communication Technology Co., Ltd. 70A3A4 (base 16) Beijing Guming Communication Technology Co., Ltd. Room 202-6, 2nd Floor, Building 1, No. 8 Courtyard, Yongchang Middle Road, Beijing Economic and Technological Development Area, Beijing @@ -232049,6 +232175,18 @@ A43A39 (base 16) AURORA TECHNOLOGIES CO.,LTD. Hsinchu City Hsinchu 30071 TW +A4-3A-39 (hex) AURORA TECHNOLOGIES CO.,LTD. +A43A39 (base 16) AURORA TECHNOLOGIES CO.,LTD. + ROOM 1006, BLOCK B, QIANHAI ECONOMIC AND TRADE CENTER, CHINA MERCHANTS GROUP, NO.151 WEST FREE TRADE STREET, QIANHAI, + SHENZHEN 518000 + CN + +C0-2E-1D (hex) Nokia Solutions and Networks GmbH & Co. KG +C02E1D (base 16) Nokia Solutions and Networks GmbH & Co. KG + Werinherstrasse 91 + München Bavaria D-81541 + DE + 20-36-D0 (hex) Motorola Mobility LLC, a Lenovo Company 2036D0 (base 16) Motorola Mobility LLC, a Lenovo Company 222 West Merchandise Mart Plaza @@ -232067,17 +232205,11 @@ A43A39 (base 16) AURORA TECHNOLOGIES CO.,LTD. Shenzhen Guangdong 518172 CN -68-4A-6E (hex) Quectel Wireless Solutions Co.,Ltd. -684A6E (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 - CN - -64-24-4D (hex) Hangzhou Ezviz Software Co.,Ltd. -64244D (base 16) Hangzhou Ezviz Software Co.,Ltd. - 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District - Hangzhou Zhejiang 310051 - CN +BC-87-53 (hex) Sera Network Inc. +BC8753 (base 16) Sera Network Inc. + 2F, No.60, 321 Ln., Yangguang St., Neihu Dist., + Taipei Taiwan 114717 + TW 0C-A6-4C (hex) Hangzhou Ezviz Software Co.,Ltd. 0CA64C (base 16) Hangzhou Ezviz Software Co.,Ltd. @@ -232097,17 +232229,17 @@ AC1C26 (base 16) Hangzhou Ezviz Software Co.,Ltd. Hangzhou Zhejiang 310051 CN -C0-2E-1D (hex) Nokia Solutions and Networks GmbH & Co. KG -C02E1D (base 16) Nokia Solutions and Networks GmbH & Co. KG - Werinherstrasse 91 - München Bavaria D-81541 - DE +64-24-4D (hex) Hangzhou Ezviz Software Co.,Ltd. +64244D (base 16) Hangzhou Ezviz Software Co.,Ltd. + 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District + Hangzhou Zhejiang 310051 + CN -BC-87-53 (hex) Sera Network Inc. -BC8753 (base 16) Sera Network Inc. - 2F, No.60, 321 Ln., Yangguang St., Neihu Dist., - Taipei Taiwan 114717 - TW +68-4A-6E (hex) Quectel Wireless Solutions Co.,Ltd. +684A6E (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 + CN 50-FA-CB (hex) IEEE Registration Authority 50FACB (base 16) IEEE Registration Authority @@ -232127,6 +232259,12 @@ B85213 (base 16) zte corporation shenzhen guangdong 518057 CN +2C-27-E4 (hex) Luxshare Precision Industry (Xuancheng) Co.,Ltd. +2C27E4 (base 16) Luxshare Precision Industry (Xuancheng) Co.,Ltd. + Address: No.5 Baishou Road, Xuancheng High-Tech Industrial Development Zone + Xuancheng Anhui 242000 + CN + 9C-6D-92 (hex) Shanghai Kanghai Infomation System CO.,LTD 9C6D92 (base 16) Shanghai Kanghai Infomation System CO.,LTD Room 207, Building 1, 6055 Songze Avenue , Qingpu District, Shanghai @@ -232187,10 +232325,10 @@ AC393D (base 16) eero inc. LAKE FOREST CA 92630 US -2C-27-E4 (hex) Luxshare Precision Industry (Xuancheng) Co.,Ltd. -2C27E4 (base 16) Luxshare Precision Industry (Xuancheng) Co.,Ltd. - Address: No.5 Baishou Road, Xuancheng High-Tech Industrial Development Zone - Xuancheng Anhui 242000 +B4-04-29 (hex) Guangzhou Shiyuan Electronic Technology Company Limited +B40429 (base 16) Guangzhou Shiyuan Electronic Technology Company Limited + No.6, 4th Yunpu Road, Yunpu industry District + Guangzhou Guangdong 510530 CN 4C-D7-4A (hex) Vantiva USA LLC @@ -232211,12 +232349,6 @@ FCCF9F (base 16) EM Microelectronic Marin-Epagnier Neuchatel 2074 CH -B4-04-29 (hex) Guangzhou Shiyuan Electronic Technology Company Limited -B40429 (base 16) Guangzhou Shiyuan Electronic Technology Company Limited - No.6, 4th Yunpu Road, Yunpu industry District - Guangzhou Guangdong 510530 - CN - D4-25-DE (hex) New H3C Technologies Co., Ltd D425DE (base 16) New H3C Technologies Co., Ltd 466 Changhe Road, Binjiang District @@ -232271,12 +232403,6 @@ B0E8E8 (base 16) Silicon Laboratories Hsin-Chu R.O.C. 308 TW -F8-6D-CC (hex) WNC Corporation -F86DCC (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW - 70-EB-A5 (hex) Huawei Device Co., Ltd. 70EBA5 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone @@ -232289,6 +232415,12 @@ C890F7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN +F8-6D-CC (hex) WNC Corporation +F86DCC (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + 20-58-43 (hex) WNC Corporation 205843 (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park @@ -232307,6 +232439,12 @@ F040AF (base 16) IEEE Registration Authority Piscataway NJ 08554 US +E4-7C-1A (hex) mercury corperation +E47C1A (base 16) mercury corperation + 90,gajaeul-ro,seo-gu,incheon + incheon 22830 + KR + 28-B4-46 (hex) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD 28B446 (base 16) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD Unit East Block22-24/F,Skyworth semiconductor design Bldg., Gaoxin Ave.4.S.,Nanshan District,Shenzhen,China @@ -232361,12 +232499,6 @@ C878F7 (base 16) Cisco Systems, Inc Shenzhen Guangdong 518109 CN -E4-7C-1A (hex) mercury corperation -E47C1A (base 16) mercury corperation - 90,gajaeul-ro,seo-gu,incheon - incheon 22830 - KR - 04-5F-A6 (hex) Shenzhen SDMC Technology CP,.LTD 045FA6 (base 16) Shenzhen SDMC Technology CP,.LTD 19/F, Changhong Science &Technology Mansion,No.18, Keji South 12th Road High-tech IndustrialPark Nanshan District,Shenzhen,China @@ -232556,28 +232688,16 @@ C03F0E (base 16) NETGEAR San Jose CA 95134 US -3C-EF-A5 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. -3CEFA5 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. - B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China - Nanning Guangxi 530007 - CN - -04-17-4C (hex) Nanjing SCIYON Wisdom Technology Group Co.,Ltd. -04174C (base 16) Nanjing SCIYON Wisdom Technology Group Co.,Ltd. - No. 1266 Qingshuiting East Road, Jiangning District Nanjing - Nanjing 211800 - CN - CC-03-3D (hex) Beijing Xiaomi Mobile Software Co., Ltd CC033D (base 16) Beijing Xiaomi Mobile Software Co., Ltd The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District Beijing Beijing 100085 CN -50-31-23 (hex) FN-LINK TECHNOLOGY Ltd. -503123 (base 16) FN-LINK TECHNOLOGY Ltd. - No.8, Litong Road, Liuyang Economic & Technical Development Zone, Changsha, Hunan,China - Changsha Hunan 410329 +04-17-4C (hex) Nanjing SCIYON Wisdom Technology Group Co.,Ltd. +04174C (base 16) Nanjing SCIYON Wisdom Technology Group Co.,Ltd. + No. 1266 Qingshuiting East Road, Jiangning District Nanjing + Nanjing 211800 CN E0-C2-50 (hex) NETGEAR @@ -232616,6 +232736,12 @@ A040A0 (base 16) NETGEAR San Jose CA 95134 US +3C-EF-A5 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +3CEFA5 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. + B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China + Nanning Guangxi 530007 + CN + 30-CB-89 (hex) OnLogic Inc 30CB89 (base 16) OnLogic Inc 435 Community Drive @@ -232628,6 +232754,24 @@ E48F09 (base 16) ithinx GmbH Koeln / Cologne 51063 DE +50-31-23 (hex) FN-LINK TECHNOLOGY Ltd. +503123 (base 16) FN-LINK TECHNOLOGY Ltd. + No.8, Litong Road, Liuyang Economic & Technical Development Zone, Changsha, Hunan,China + Changsha Hunan 410329 + CN + +10-13-31 (hex) Vantiva Technologies Belgium +101331 (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE + +D4-92-5E (hex) Vantiva Technologies Belgium +D4925E (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE + BC-D9-FB (hex) China Mobile Group Device Co.,Ltd. BCD9FB (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District @@ -232640,18 +232784,6 @@ BCD9FB (base 16) China Mobile Group Device Co.,Ltd. Regensburg Bayern 93059 DE -D4-92-5E (hex) Vantiva Technologies Belgium -D4925E (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE - -10-13-31 (hex) Vantiva Technologies Belgium -101331 (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE - 20-0E-0F (hex) Panasonic Marketing Middle East & Africa FZE 200E0F (base 16) Panasonic Marketing Middle East & Africa FZE P.O Box 17985 Jebel Ali @@ -232670,26 +232802,20 @@ D8031A (base 16) Ezurio, LLC Zhubei 30251 TW -18-C2-93 (hex) Ezurio, LLC -18C293 (base 16) Ezurio, LLC - 3F.-1, No.145, Xianzheng 9th Rd., - Zhubei 30251 - TW - 88-F9-C0 (hex) KTS Kommunikationstechnik und Systeme GmbH 88F9C0 (base 16) KTS Kommunikationstechnik und Systeme GmbH Schlossstrasse 123 Moenchengladbach NRW 41238 DE -14-5E-BC (hex) HUAWEI TECHNOLOGIES CO.,LTD -145EBC (base 16) HUAWEI TECHNOLOGIES CO.,LTD +10-88-D3 (hex) HUAWEI TECHNOLOGIES CO.,LTD +1088D3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -10-88-D3 (hex) HUAWEI TECHNOLOGIES CO.,LTD -1088D3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +14-5E-BC (hex) HUAWEI TECHNOLOGIES CO.,LTD +145EBC (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN @@ -232706,17 +232832,11 @@ E40177 (base 16) SafeOwl, Inc. Dallas TX 75206 US -28-1D-AA (hex) ASTI India Private Limited -281DAA (base 16) ASTI India Private Limited - Plot No. 75, Ukardi,Japanese Industrial Zone,Ukardi, Taluka-Mandal,Ahmedabad - Ahmedabad Gujarat 382120 - IN - -C0-18-8C (hex) Altus Sistemas de Automação S.A. -C0188C (base 16) Altus Sistemas de Automação S.A. - Av. Theodomiro Porto da Fonseca, 3101 - lote 01 - Cristo Rei - São Leopoldo Rio Grande do Sul 93022-715 - BR +18-C2-93 (hex) Ezurio, LLC +18C293 (base 16) Ezurio, LLC + 3F.-1, No.145, Xianzheng 9th Rd., + Zhubei 30251 + TW 90-7A-BE (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED 907ABE (base 16) GSD VIET NAM TECHNOLOGY COMPANY LIMITED @@ -232736,30 +232856,24 @@ FC8827 (base 16) Apple, Inc. Cupertino CA 95014 US -60-DE-18 (hex) Apple, Inc. -60DE18 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - 30-EC-A3 (hex) Alfatron Electronics INC 30ECA3 (base 16) Alfatron Electronics INC 6518 Old Wake Forest Road STE A Raleigh NC 27616 US -40-38-02 (hex) Silicon Laboratories -403802 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US - 00-89-C9 (hex) Extreme Networks Headquarters 0089C9 (base 16) Extreme Networks Headquarters 2121 RDU Center Drive Morrisville 27560 US +60-DE-18 (hex) Apple, Inc. +60DE18 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + 10-BC-36 (hex) Huawei Device Co., Ltd. 10BC36 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone @@ -232778,11 +232892,17 @@ B4F49B (base 16) Huawei Device Co., Ltd. Mumbai Maharashtra 400104 IN -80-F1-A8 (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. -80F1A8 (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. - 601,Building B2,No.162,Science Avenue,Science City,Guangzhou High-tech Industrial Development Zone,Guangdong Province,China - Guangzhou Guangdong 510663 - CN +28-1D-AA (hex) ASTI India Private Limited +281DAA (base 16) ASTI India Private Limited + Plot No. 75, Ukardi,Japanese Industrial Zone,Ukardi, Taluka-Mandal,Ahmedabad + Ahmedabad Gujarat 382120 + IN + +C0-18-8C (hex) Altus Sistemas de Automação S.A. +C0188C (base 16) Altus Sistemas de Automação S.A. + Av. Theodomiro Porto da Fonseca, 3101 - lote 01 - Cristo Rei + São Leopoldo Rio Grande do Sul 93022-715 + BR 74-24-35 (hex) Huawei Device Co., Ltd. 742435 (base 16) Huawei Device Co., Ltd. @@ -232808,6 +232928,12 @@ E880E7 (base 16) Huawei Device Co., Ltd. REDMOND WA 98052 US +40-38-02 (hex) Silicon Laboratories +403802 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + 5C-5C-75 (hex) IEEE Registration Authority 5C5C75 (base 16) IEEE Registration Authority 445 Hoes Lane @@ -232817,6 +232943,42 @@ E880E7 (base 16) Huawei Device Co., Ltd. A4-F4-CA (hex) Private A4F4CA (base 16) Private +80-F1-A8 (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. +80F1A8 (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. + 601,Building B2,No.162,Science Avenue,Science City,Guangzhou High-tech Industrial Development Zone,Guangdong Province,China + Guangzhou Guangdong 510663 + CN + +F8-91-F5 (hex) Dingtian Technologies Co., Ltd +F891F5 (base 16) Dingtian Technologies Co., Ltd + Rm.3306, Building6, Runyueshan, No.33 Huangzhukeng Rd.,Biling Street,Pingshan District + Shenzhen Guangdong 518100 + CN + +4C-43-F6 (hex) SZ DJI TECHNOLOGY CO.,LTD +4C43F6 (base 16) SZ DJI TECHNOLOGY CO.,LTD + DJI Sky City, No55 Xianyuan Road, Nanshan District + Shenzhen Guangdong 518057 + CN + +7C-A5-3E (hex) Motorola Mobility LLC, a Lenovo Company +7CA53E (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US + +D8-31-39 (hex) zte corporation +D83139 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +A0-59-11 (hex) Cisco Meraki +A05911 (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US + F0-16-53 (hex) YEALINK(XIAMEN) NETWORK TECHNOLOGY CO.,LTD. F01653 (base 16) YEALINK(XIAMEN) NETWORK TECHNOLOGY CO.,LTD. 309, 3th Floor, No.16, Yun Ding North Road, Huli District @@ -232853,34 +233015,28 @@ C46DD1 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -F8-91-F5 (hex) Dingtian Technologies Co., Ltd -F891F5 (base 16) Dingtian Technologies Co., Ltd - Rm.3306, Building6, Runyueshan, No.33 Huangzhukeng Rd.,Biling Street,Pingshan District - Shenzhen Guangdong 518100 +70-97-51 (hex) Beijing Xiaomi Mobile Software Co., Ltd +709751 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN -4C-43-F6 (hex) SZ DJI TECHNOLOGY CO.,LTD -4C43F6 (base 16) SZ DJI TECHNOLOGY CO.,LTD - DJI Sky City, No55 Xianyuan Road, Nanshan District - Shenzhen Guangdong 518057 - CN +5C-D3-3D (hex) Samsung Electronics Co.,Ltd +5CD33D (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -7C-A5-3E (hex) Motorola Mobility LLC, a Lenovo Company -7CA53E (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 +AC-DE-01 (hex) Ruckus Wireless +ACDE01 (base 16) Ruckus Wireless + 350 West Java Drive + Sunnyvale CA 94089 US -D8-31-39 (hex) zte corporation -D83139 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN - -A0-59-11 (hex) Cisco Meraki -A05911 (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 +58-AD-08 (hex) IEEE Registration Authority +58AD08 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US 54-7A-F4 (hex) Bouffalo Lab (Nanjing) Co., Ltd. @@ -232901,22 +233057,28 @@ A05911 (base 16) Cisco Meraki San Jose CA 94568 US -70-97-51 (hex) Beijing Xiaomi Mobile Software Co., Ltd -709751 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 +58-BD-35 (hex) SHANGHAI XIANGCHENG COMMUNICATION TECHNOLOGY CO., LTD +58BD35 (base 16) SHANGHAI XIANGCHENG COMMUNICATION TECHNOLOGY CO., LTD + Room 211-5, Building 1, No. 290 Wankang Road, Minhang District + Shanghai Shanghai 201100 CN -5C-D3-3D (hex) Samsung Electronics Co.,Ltd -5CD33D (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +D4-C1-A8 (hex) KYKXCOM Co., Ltd. +D4C1A8 (base 16) KYKXCOM Co., Ltd. + Building 2, No.8, Yuanhua Road, Xianlin UniversityTown, Xianlin Subdistrict, Qixia District + Nanjing Jiangsu 210033 + CN -AC-DE-01 (hex) Ruckus Wireless -ACDE01 (base 16) Ruckus Wireless - 350 West Java Drive - Sunnyvale CA 94089 +80-99-9B (hex) Murata Manufacturing Co., Ltd. +80999B (base 16) Murata Manufacturing Co., Ltd. + 1-10-1, Higashikotari + Nagaokakyo-shi Kyoto 617-8555 + JP + +B8-58-FF (hex) Arista Networks +B858FF (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 US C0-B5-50 (hex) Broadcom Limited @@ -232931,23 +233093,23 @@ C0B550 (base 16) Broadcom Limited Thalwil Switzerland CH-8800 CH -58-AD-08 (hex) IEEE Registration Authority -58AD08 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US +40-82-56 (hex) AUMOVIO Germany GmbH +408256 (base 16) AUMOVIO Germany GmbH + VDO-Strasse 1 + Babenhausen Garmany 64832 + DE -58-BD-35 (hex) SHANGHAI XIANGCHENG COMMUNICATION TECHNOLOGY CO., LTD -58BD35 (base 16) SHANGHAI XIANGCHENG COMMUNICATION TECHNOLOGY CO., LTD - Room 211-5, Building 1, No. 290 Wankang Road, Minhang District - Shanghai Shanghai 201100 - CN +54-B2-7E (hex) Sagemcom Broadband SAS +54B27E (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR -80-99-9B (hex) Murata Manufacturing Co., Ltd. -80999B (base 16) Murata Manufacturing Co., Ltd. - 1-10-1, Higashikotari - Nagaokakyo-shi Kyoto 617-8555 - JP +40-E7-62 (hex) Calix Inc. +40E762 (base 16) Calix Inc. + 2777 Orchard Pkwy + San Jose CA 95131 + US 00-1E-AE (hex) AUMOVIO Systems, Inc. 001EAE (base 16) AUMOVIO Systems, Inc. @@ -232955,24 +233117,6 @@ C0B550 (base 16) Broadcom Limited Deer Park IL 60010 US -D4-C1-A8 (hex) KYKXCOM Co., Ltd. -D4C1A8 (base 16) KYKXCOM Co., Ltd. - Building 2, No.8, Yuanhua Road, Xianlin UniversityTown, Xianlin Subdistrict, Qixia District - Nanjing Jiangsu 210033 - CN - -B8-58-FF (hex) Arista Networks -B858FF (base 16) Arista Networks - 5453 Great America Parkway - Santa Clara CA 95054 - US - -40-82-56 (hex) AUMOVIO Germany GmbH -408256 (base 16) AUMOVIO Germany GmbH - VDO-Strasse 1 - Babenhausen Garmany 64832 - DE - 18-F7-F6 (hex) Ericsson AB 18F7F6 (base 16) Ericsson AB Torshamnsgatan 36 @@ -233003,60 +233147,12 @@ D89999 (base 16) TECNO MOBILE LIMITED Hong Kong Hong Kong 999077 HK -54-B2-7E (hex) Sagemcom Broadband SAS -54B27E (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 84-C7-E2 (hex) VusionGroup 84C7E2 (base 16) VusionGroup Kalsdorfer Straße 12 Fernitz-Mellach Steiermark 8072 AT -40-E7-62 (hex) Calix Inc. -40E762 (base 16) Calix Inc. - 2777 Orchard Pkwy - San Jose CA 95131 - US - -68-1A-47 (hex) Apple, Inc. -681A47 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -28-49-E9 (hex) Apple, Inc. -2849E9 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -78-96-0D (hex) Apple, Inc. -78960D (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -80-1D-39 (hex) Apple, Inc. -801D39 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -CC-72-2A (hex) Apple, Inc. -CC722A (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -AC-40-1E (hex) vivo Mobile Communication Co., Ltd. -AC401E (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 - CN - 34-17-DD (hex) Sercomm France Sarl 3417DD (base 16) Sercomm France Sarl 2/4 Rue Maurice Hartmann 92370 Issy Les Moulineaux France @@ -233069,6 +233165,12 @@ AC401E (base 16) vivo Mobile Communication Co., Ltd. Piscataway NJ 08554 US +58-D8-12 (hex) TP-Link Systems Inc. +58D812 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US + 74-E6-C7 (hex) LUXSHARE-ICT Co., Ltd. 74E6C7 (base 16) LUXSHARE-ICT Co., Ltd. 1F, No. 22, Lane 35, Jihu Road, Neihu district @@ -233081,10 +233183,10 @@ AC401E (base 16) vivo Mobile Communication Co., Ltd. shenzhen guangdong 518057 CN -D0-F8-15 (hex) HUAWEI TECHNOLOGIES CO.,LTD -D0F815 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +AC-40-1E (hex) vivo Mobile Communication Co., Ltd. +AC401E (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 CN 64-D4-F0 (hex) NETVUE,INC. @@ -233099,34 +233201,40 @@ D0F815 (base 16) HUAWEI TECHNOLOGIES CO.,LTD SINGAPORE SINGAPORE 199591 SG -04-5E-0A (hex) SHENZHEN TRANSCHAN TECHNOLOGY LIMITED -045E0A (base 16) SHENZHEN TRANSCHAN TECHNOLOGY LIMITED - Room 03, 23/F, Unit B Building, No 9, Shenzhen Bay Eco -Technology Park, Yuehai Street, Nanshan District, Shenzhen, China - Shenzhen 518000 - CN +68-1A-47 (hex) Apple, Inc. +681A47 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -A8-6A-CB (hex) EVAR -A86ACB (base 16) EVAR - 42, Changeop-ro, Sujeong-gu, Seongnam-si, Gyeonggi-do, Republic of Korea - Seoul Gyunggi-do 13449 - KR +28-49-E9 (hex) Apple, Inc. +2849E9 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -58-D8-12 (hex) TP-Link Systems Inc. -58D812 (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 +78-96-0D (hex) Apple, Inc. +78960D (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -70-79-2D (hex) Mellanox Technologies, Inc. -70792D (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 +80-1D-39 (hex) Apple, Inc. +801D39 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -A4-43-1B (hex) Dreamtek Intelligent Technology Co., Ltd -A4431B (base 16) Dreamtek Intelligent Technology Co., Ltd - Room 508, Building A2, Area one of Zhongan Chuanggu Science Park, No. 900 of Wangjiang West Road, High-tech Zone, Hefei, Anhui, China - Shanghai 230000 +CC-72-2A (hex) Apple, Inc. +CC722A (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +D0-F8-15 (hex) HUAWEI TECHNOLOGIES CO.,LTD +D0F815 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN B8-84-11 (hex) Shenzhen Shokz Co., Ltd. @@ -233141,6 +233249,18 @@ B88411 (base 16) Shenzhen Shokz Co., Ltd. Shenzhen Guangdong 518057 CN +70-79-2D (hex) Mellanox Technologies, Inc. +70792D (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +A4-43-1B (hex) Dreamtek Intelligent Technology Co., Ltd +A4431B (base 16) Dreamtek Intelligent Technology Co., Ltd + Room 508, Building A2, Area one of Zhongan Chuanggu Science Park, No. 900 of Wangjiang West Road, High-tech Zone, Hefei, Anhui, China + Shanghai 230000 + CN + 94-6A-7C (hex) OnePlus Technology (Shenzhen) Co., Ltd 946A7C (base 16) OnePlus Technology (Shenzhen) Co., Ltd 18C02, 18C03, 18C04 ,18C05,TAIRAN BUILDING, @@ -233153,11 +233273,17 @@ B88411 (base 16) Shenzhen Shokz Co., Ltd. Shanghai Shanghai 201203 CN -0C-88-2F (hex) Frog Innovations Limited -0C882F (base 16) Frog Innovations Limited - C23, Sector 80, Phase-II - Noida Uttar Pradesh 201305 - IN +04-5E-0A (hex) SHENZHEN TRANSCHAN TECHNOLOGY LIMITED +045E0A (base 16) SHENZHEN TRANSCHAN TECHNOLOGY LIMITED + Room 03, 23/F, Unit B Building, No 9, Shenzhen Bay Eco -Technology Park, Yuehai Street, Nanshan District, Shenzhen, China + Shenzhen 518000 + CN + +A8-6A-CB (hex) EVAR +A86ACB (base 16) EVAR + 42, Changeop-ro, Sujeong-gu, Seongnam-si, Gyeonggi-do, Republic of Korea + Seoul Gyunggi-do 13449 + KR F0-4F-E0 (hex) Vizio, Inc F04FE0 (base 16) Vizio, Inc @@ -233171,71 +233297,35 @@ A4CB8F (base 16) Espressif Inc. Shanghai Shanghai 201203 CN -2C-63-A1 (hex) Huawei Device Co., Ltd. -2C63A1 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +14-2E-43 (hex) Hisense broadband multimedia technology Co.,Ltd +142E43 (base 16) Hisense broadband multimedia technology Co.,Ltd + Song ling Road 399 + Qingdao 266000 CN -50-0F-C6 (hex) solum -500FC6 (base 16) solum - 2354, Yonggu-daero, Giheung-gu - Yongin-si Gyeonggi-do 16921 - KR - 04-22-E7 (hex) Fiberhome Telecommunication Technologies Co.,LTD 0422E7 (base 16) Fiberhome Telecommunication Technologies Co.,LTD No.5 DongXin Road Wuhan Hubei 430074 CN +2C-63-A1 (hex) Huawei Device Co., Ltd. +2C63A1 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + 2C-E1-87 (hex) New H3C Technologies Co., Ltd 2CE187 (base 16) New H3C Technologies Co., Ltd 466 Changhe Road, Binjiang District Hangzhou Zhejiang 310052 CN -14-2E-43 (hex) Hisense broadband multimedia technology Co.,Ltd -142E43 (base 16) Hisense broadband multimedia technology Co.,Ltd - Song ling Road 399 - Qingdao 266000 - CN - -48-92-C1 (hex) OHSUNG -4892C1 (base 16) OHSUNG - 335-4,SANHODAERO,GUMI,GYEONG BUK,KOREA - GUMI GYEONG BUK 730-030 - KR - -4C-30-6A (hex) Nintendo Co.,Ltd -4C306A (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP - -2C-2B-DB (hex) eero inc. -2C2BDB (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -DC-F3-4C (hex) PT HAN SUNG ELECTORONICS INDONESIA -DCF34C (base 16) PT HAN SUNG ELECTORONICS INDONESIA - JL.PALEM 1 BLOK DS-6 - KAWASAN INDUSTRI BATIK LIPPO CIKARANG, DESA CIBATU, KECAMATAN CIKARANG SELATAN BEKASI JAWA BARAT 17550 - ID - -B4-91-07 (hex) HUAWEI TECHNOLOGIES CO.,LTD -B49107 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -FC-EF-D7 (hex) HUAWEI TECHNOLOGIES CO.,LTD -FCEFD7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +0C-88-2F (hex) Frog Innovations Limited +0C882F (base 16) Frog Innovations Limited + C23, Sector 80, Phase-II + Noida Uttar Pradesh 201305 + IN EC-58-65 (hex) Shenzhen Xinguodu Technology Co., Ltd EC5865 (base 16) Shenzhen Xinguodu Technology Co., Ltd @@ -233255,6 +233345,12 @@ EC5865 (base 16) Shenzhen Xinguodu Technology Co., Ltd Shanghai Shanghai 201203 CN +50-0F-C6 (hex) solum +500FC6 (base 16) solum + 2354, Yonggu-daero, Giheung-gu + Yongin-si Gyeonggi-do 16921 + KR + 44-61-DF (hex) Skyquad Electronics & Appliances Pvt. Ltd. 4461DF (base 16) Skyquad Electronics & Appliances Pvt. Ltd. 12-50/4/A, Adj to Industrial Estate, MedchalR R District, Hyderabad - 501401, Telangana, India. @@ -233273,12 +233369,42 @@ EC5865 (base 16) Shenzhen Xinguodu Technology Co., Ltd Kulim Kedah 09000 MY -1C-8C-6E (hex) Arista Networks -1C8C6E (base 16) Arista Networks - 5453 Great America Parkway - Santa Clara CA 95054 +48-92-C1 (hex) OHSUNG +4892C1 (base 16) OHSUNG + 335-4,SANHODAERO,GUMI,GYEONG BUK,KOREA + GUMI GYEONG BUK 730-030 + KR + +FC-EF-D7 (hex) HUAWEI TECHNOLOGIES CO.,LTD +FCEFD7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +4C-30-6A (hex) Nintendo Co.,Ltd +4C306A (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP + +2C-2B-DB (hex) eero inc. +2C2BDB (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 US +DC-F3-4C (hex) PT HAN SUNG ELECTORONICS INDONESIA +DCF34C (base 16) PT HAN SUNG ELECTORONICS INDONESIA + JL.PALEM 1 BLOK DS-6 + KAWASAN INDUSTRI BATIK LIPPO CIKARANG, DESA CIBATU, KECAMATAN CIKARANG SELATAN BEKASI JAWA BARAT 17550 + ID + +B4-91-07 (hex) HUAWEI TECHNOLOGIES CO.,LTD +B49107 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + 84-1D-E8 (hex) CJ intelligent technology LTD. 841DE8 (base 16) CJ intelligent technology LTD. 4F, No. 16, Zhongxin St., Shulin Dist. @@ -233291,11 +233417,17 @@ C484C0 (base 16) Motorola Mobility LLC, a Lenovo Company Chicago IL 60654 US -E8-A9-27 (hex) LEAR -E8A927 (base 16) LEAR - Carrer Fuster 54 - Valls Tarragona 43800 - ES +CC-35-D9 (hex) Ubiquiti Inc +CC35D9 (base 16) Ubiquiti Inc + 685 Third Avenue, 27th Floor + New York NY New York NY 10017 + US + +A4-F8-FF (hex) Ubiquiti Inc +A4F8FF (base 16) Ubiquiti Inc + 685 Third Avenue, 27th Floor + New York NY New York NY 10017 + US 64-9B-8F (hex) Texas Instruments 649B8F (base 16) Texas Instruments @@ -233315,16 +233447,10 @@ CCC530 (base 16) AzureWave Technology Inc. New Taipei City Taiwan 231 TW -A4-F8-FF (hex) Ubiquiti Inc -A4F8FF (base 16) Ubiquiti Inc - 685 Third Avenue, 27th Floor - New York NY New York NY 10017 - US - -CC-35-D9 (hex) Ubiquiti Inc -CC35D9 (base 16) Ubiquiti Inc - 685 Third Avenue, 27th Floor - New York NY New York NY 10017 +1C-8C-6E (hex) Arista Networks +1C8C6E (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 US 6C-47-80 (hex) IEEE Registration Authority @@ -233333,6 +233459,36 @@ CC35D9 (base 16) Ubiquiti Inc Piscataway NJ 08554 US +80-C4-29 (hex) Renesas Electronics Operations Services Limited +80C429 (base 16) Renesas Electronics Operations Services Limited + Dukes Meadow, Millboard Raod Bourne End BU + Bourne End BU SL8 5FH + GB + +00-E0-AD (hex) Brandywine Communications UK Ltd. +00E0AD (base 16) Brandywine Communications UK Ltd. + 20a Westside Centre London Road, Stanway, Colchester + ESSEX England CO3 8PH + GB + +50-71-64 (hex) Cisco Systems, Inc +507164 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +E8-A9-27 (hex) LEAR +E8A927 (base 16) LEAR + Carrer Fuster 54 + Valls Tarragona 43800 + ES + +00-1C-44 (hex) Electro Voice Dynacord BV +001C44 (base 16) Electro Voice Dynacord BV + Achtseweg Zuid 173 + 5651 GW Eindhoven Eindhoven 5651 + NL + 50-C3-A2 (hex) nFore Technology Co., Ltd. 50C3A2 (base 16) nFore Technology Co., Ltd. 5F., No.31, Ln. 258, Ruiguang Rd. Neihu Dist., Taipei City 114, Taiwan @@ -233351,23 +233507,35 @@ A40450 (base 16) nFore Technology Co., Ltd. Taipei Neihu District 11491 TW +B0-37-31 (hex) FUJIAN STAR-NET COMMUNICATION CO.,LTD +B03731 (base 16) FUJIAN STAR-NET COMMUNICATION CO.,LTD + 19-22# Building, Star-net Science Plaza, Juyuanzhou, + FUZHOU FUJIAN 350002 + CN + +6C-40-33 (hex) Beijing Megwang Technology Co., Ltd. +6C4033 (base 16) Beijing Megwang Technology Co., Ltd. + Room 1316, 1st Floor, Building 12, Jianzhong Road, Xisanqi Building Materials City, Haidian District, Beijing, China + Beijing 100096 + CN + 44-10-30 (hex) Google, Inc. 441030 (base 16) Google, Inc. 1600 Amphitheatre Parkway Mountain View CA 94043 US -80-C4-29 (hex) Renesas Electronics Operations Services Limited -80C429 (base 16) Renesas Electronics Operations Services Limited - Dukes Meadow, Millboard Raod Bourne End BU - Bourne End BU SL8 5FH - GB +60-A1-FE (hex) HPRO +60A1FE (base 16) HPRO + 8500 Balboa Blvd + Northridge CA 91329 + US -00-E0-AD (hex) Brandywine Communications UK Ltd. -00E0AD (base 16) Brandywine Communications UK Ltd. - 20a Westside Centre London Road, Stanway, Colchester - ESSEX England CO3 8PH - GB +24-99-00 (hex) FRITZ! Technology GmbH +249900 (base 16) FRITZ! Technology GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE 58-8C-CF (hex) Silicon Laboratories 588CCF (base 16) Silicon Laboratories @@ -233375,56 +233543,32 @@ A40450 (base 16) nFore Technology Co., Ltd. Austin TX 78701 US -50-71-64 (hex) Cisco Systems, Inc -507164 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -00-1C-44 (hex) Electro Voice Dynacord BV -001C44 (base 16) Electro Voice Dynacord BV - Achtseweg Zuid 173 - 5651 GW Eindhoven Eindhoven 5651 - NL - -60-A1-FE (hex) HPRO -60A1FE (base 16) HPRO - 8500 Balboa Blvd - Northridge CA 91329 - US - A4-18-94 (hex) IQSIGHT B.V. A41894 (base 16) IQSIGHT B.V. Achtseweg Zuid 173 Eindhoven 5651 GW NL -B0-37-31 (hex) FUJIAN STAR-NET COMMUNICATION CO.,LTD -B03731 (base 16) FUJIAN STAR-NET COMMUNICATION CO.,LTD - 19-22# Building, Star-net Science Plaza, Juyuanzhou, - FUZHOU FUJIAN 350002 - CN - -24-99-00 (hex) FRITZ! Technology GmbH -249900 (base 16) FRITZ! Technology GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE +64-C9-05 (hex) Apple, Inc. +64C905 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -18-A0-84 (hex) Apple, Inc. -18A084 (base 16) Apple, Inc. +3C-BF-D7 (hex) Apple, Inc. +3CBFD7 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -58-2A-93 (hex) Apple, Inc. -582A93 (base 16) Apple, Inc. +88-7E-9B (hex) Apple, Inc. +887E9B (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -64-C9-05 (hex) Apple, Inc. -64C905 (base 16) Apple, Inc. +54-2A-43 (hex) Apple, Inc. +542A43 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US @@ -233435,10 +233579,22 @@ E88F8E (base 16) Hoags Technologies India Private Limited Bangalore KA 560075 IN -6C-40-33 (hex) Beijing Megwang Technology Co., Ltd. -6C4033 (base 16) Beijing Megwang Technology Co., Ltd. - Room 1316, 1st Floor, Building 12, Jianzhong Road, Xisanqi Building Materials City, Haidian District, Beijing, China - Beijing 100096 +18-A0-84 (hex) Apple, Inc. +18A084 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +58-2A-93 (hex) Apple, Inc. +582A93 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +50-63-82 (hex) HUAWEI TECHNOLOGIES CO.,LTD +506382 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN E8-FC-5F (hex) Ruckus Wireless @@ -233447,35 +233603,35 @@ E8FC5F (base 16) Ruckus Wireless Sunnyvale CA 94089 US -50-63-82 (hex) HUAWEI TECHNOLOGIES CO.,LTD -506382 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +E8-68-B1 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +E868B1 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN + +B0-F0-79 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +B0F079 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN +E8-23-FB (hex) Redder +E823FB (base 16) Redder + Via B. Ferracina, 2 + Camisano Vicentino VI 36043 + IT + BC-68-C3 (hex) HUAWEI TECHNOLOGIES CO.,LTD BC68C3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -3C-BF-D7 (hex) Apple, Inc. -3CBFD7 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -88-7E-9B (hex) Apple, Inc. -887E9B (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -54-2A-43 (hex) Apple, Inc. -542A43 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +A4-93-FE (hex) HUAWEI TECHNOLOGIES CO.,LTD +A493FE (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN 9C-CC-01 (hex) Espressif Inc. 9CCC01 (base 16) Espressif Inc. @@ -233483,11 +233639,11 @@ BC68C3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Shanghai Shanghai 201203 CN -A4-93-FE (hex) HUAWEI TECHNOLOGIES CO.,LTD -A493FE (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +40-BA-09 (hex) Dell Inc. +40BA09 (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 + US 00-0C-DE (hex) ABB AG 000CDE (base 16) ABB AG @@ -233495,26 +233651,68 @@ A493FE (base 16) HUAWEI TECHNOLOGIES CO.,LTD Heidelberg Baden-Württemberg 69123 DE -E8-23-FB (hex) Redder -E823FB (base 16) Redder - Via B. Ferracina, 2 - Camisano Vicentino VI 36043 - IT +44-B1-76 (hex) Espressif Inc. +44B176 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN -E8-68-B1 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -E868B1 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 +44-9A-52 (hex) zte corporation +449A52 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -B0-F0-79 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -B0F079 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 +64-CA-80 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. +64CA80 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. + No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. + Chongqing China 401120 CN -40-BA-09 (hex) Dell Inc. -40BA09 (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 +BC-C4-36 (hex) Nokia +BCC436 (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA + +DC-B8-7D (hex) Hewlett Packard Enterprise +DCB87D (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 US + +C0-6B-C7 (hex) Gallagher Group Limited +C06BC7 (base 16) Gallagher Group Limited + 181 Kahikatea Drive + Hamilton Waikato 3204 + NZ + +24-7E-7F (hex) D-Fend Solutions A.D Ltd +247E7F (base 16) D-Fend Solutions A.D Ltd + 13 Zarhin st + Raanana Sharon 4366241 + IL + +7C-4F-B5 (hex) Arcadyan Corporation +7C4FB5 (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II , + Hsinchu 300 + TW + +50-7E-5D (hex) Arcadyan Corporation +507E5D (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II , + Hsinchu 300 + TW + +00-12-BF (hex) Arcadyan Corporation +0012BF (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II + Hsinchu 300 + TW + +00-1D-19 (hex) Arcadyan Corporation +001D19 (base 16) Arcadyan Corporation + 4F., No. 9 , Park Avenue II, + Hsinchu 300 + TW diff --git a/hwdb.d/ma-medium.txt b/hwdb.d/ma-medium.txt index 66da0bc2dbaed..9698465f90298 100644 --- a/hwdb.d/ma-medium.txt +++ b/hwdb.d/ma-medium.txt @@ -7109,6 +7109,18 @@ F0-12-04 (hex) MetaX Shanghai 200000 CN +2C-7A-F4 (hex) Kegao Intelligent Garden Technology(Guangdong) Co.,Ltd. +400000-4FFFFF (base 16) Kegao Intelligent Garden Technology(Guangdong) Co.,Ltd. + 8/F Building D,No.39, East Keji Avenue, Shishan Town Nanhai District + Foshan Guangdong 528225 + CN + +2C-7A-F4 (hex) Shenzhen Yitoa Digital Technology Co., Ltd. +300000-3FFFFF (base 16) Shenzhen Yitoa Digital Technology Co., Ltd. + 7th floor, Building 1, Jiancang Technology Park, Bao'an, Shenzhen, China + Shenzhen GuangDong 518000 + CN + 2C-7A-F4 (hex) ShangYu Auto Technology Co.,Ltd 600000-6FFFFF (base 16) ShangYu Auto Technology Co.,Ltd No. 69 Yuanda Road, Anting Town, Jiading District, Shanghai @@ -7121,18 +7133,6 @@ F0-12-04 (hex) MetaX Xi'An Shaanxi 710000 CN -2C-7A-F4 (hex) Shenzhen Yitoa Digital Technology Co., Ltd. -300000-3FFFFF (base 16) Shenzhen Yitoa Digital Technology Co., Ltd. - 7th floor, Building 1, Jiancang Technology Park, Bao'an, Shenzhen, China - Shenzhen GuangDong 518000 - CN - -2C-7A-F4 (hex) Kegao Intelligent Garden Technology(Guangdong) Co.,Ltd. -400000-4FFFFF (base 16) Kegao Intelligent Garden Technology(Guangdong) Co.,Ltd. - 8/F Building D,No.39, East Keji Avenue, Shishan Town Nanhai District - Foshan Guangdong 528225 - CN - FC-A2-DF (hex) TiGHT AV C00000-CFFFFF (base 16) TiGHT AV Uggledalsvägen 23 @@ -7265,12 +7265,6 @@ A00000-AFFFFF (base 16) ShenZhen Chainway Information Technology Co., Ltd. ShenZhen GuangDong 518102 CN -48-08-EB (hex) Silicon Dynamic Networks -D00000-DFFFFF (base 16) Silicon Dynamic Networks - Floor 2, Building 14, Section C, St. Moritz Garden, Yulong Road, Longhua New District - Shenzhen Guangdong 518131 - CN - E0-23-3B (hex) IOFAC 500000-5FFFFF (base 16) IOFAC Hyundaitera Tower 1628, 8, Ori-ro 651beon-gil @@ -7283,18 +7277,18 @@ E0-23-3B (hex) IOFAC Guangzhou Guangdong 510000 CN +48-08-EB (hex) Silicon Dynamic Networks +D00000-DFFFFF (base 16) Silicon Dynamic Networks + Floor 2, Building 14, Section C, St. Moritz Garden, Yulong Road, Longhua New District + Shenzhen Guangdong 518131 + CN + 50-FA-CB (hex) The Scotts Company C00000-CFFFFF (base 16) The Scotts Company 14111 Scottslawn Marysville OH 43041 US -50-FA-CB (hex) VeriFone Systems(China),Inc -800000-8FFFFF (base 16) VeriFone Systems(China),Inc - 1701 of Building D,Area III of Innovation Park,No.20 of Gaoxin Avenue,Minhou County - Fuzhou Fujian 350000 - CN - 9C-E4-50 (hex) XTX Markets Technologies Limited C00000-CFFFFF (base 16) XTX Markets Technologies Limited R7, 14-18 Handyside Street @@ -7307,14 +7301,26 @@ C00000-CFFFFF (base 16) XTX Markets Technologies Limited Shenzhen Guangdong 518100 CN -8C-AE-49 (hex) Shanghai Kanghai Information System CO.,LTD. -E00000-EFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. +50-FA-CB (hex) VeriFone Systems(China),Inc +800000-8FFFFF (base 16) VeriFone Systems(China),Inc + 1701 of Building D,Area III of Innovation Park,No.20 of Gaoxin Avenue,Minhou County + Fuzhou Fujian 350000 + CN + +F4-97-9D (hex) Smart Access Designs, LLC +800000-8FFFFF (base 16) Smart Access Designs, LLC + 58 Mackenzie Willow Ter + Cheshire CT 06410 + US + +F8-2B-E6 (hex) Shanghai Kanghai Information System CO.,LTD. +B00000-BFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN -6C-93-08 (hex) Shanghai Kanghai Information System CO.,LTD. -500000-5FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. +04-58-5D (hex) Shanghai Kanghai Information System CO.,LTD. +E00000-EFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN @@ -7337,24 +7343,18 @@ E00000-EFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. ShenZhen GuangDong 518000 CN -F8-2B-E6 (hex) Shanghai Kanghai Information System CO.,LTD. -B00000-BFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. +8C-AE-49 (hex) Shanghai Kanghai Information System CO.,LTD. +E00000-EFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN -04-58-5D (hex) Shanghai Kanghai Information System CO.,LTD. -E00000-EFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. +6C-93-08 (hex) Shanghai Kanghai Information System CO.,LTD. +500000-5FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN -F4-97-9D (hex) Smart Access Designs, LLC -800000-8FFFFF (base 16) Smart Access Designs, LLC - 58 Mackenzie Willow Ter - Cheshire CT 06410 - US - 9C-E4-50 (hex) Strato Automation Inc. 400000-4FFFFF (base 16) Strato Automation Inc. 1550-B Rue de Coulomb @@ -7397,6 +7397,18 @@ C00000-CFFFFF (base 16) Rayve Innovation Corp Shawnee KS 66214 US +E8-F6-D7 (hex) CowManager +700000-7FFFFF (base 16) CowManager + Gerverscop 9 + Harmelen UT 3481LT + NL + +74-33-36 (hex) ACTECK TECHNOLOGY Co., Ltd +D00000-DFFFFF (base 16) ACTECK TECHNOLOGY Co., Ltd + 4F-1, No. 13, Sec.2 Beitou Rd., Beitou Dist. + Taipei City Taiwan 112028 + TW + E8-F6-D7 (hex) Emergent Solutions Inc. E00000-EFFFFF (base 16) Emergent Solutions Inc. 3600 Steeles Ave. E, Markham, ON @@ -7427,18 +7439,6 @@ A00000-AFFFFF (base 16) Shenzhen Jooan Technology Co., Ltd Shenzhen Guangdong 518122 CN -E8-F6-D7 (hex) CowManager -700000-7FFFFF (base 16) CowManager - Gerverscop 9 - Harmelen UT 3481LT - NL - -74-33-36 (hex) ACTECK TECHNOLOGY Co., Ltd -D00000-DFFFFF (base 16) ACTECK TECHNOLOGY Co., Ltd - 4F-1, No. 13, Sec.2 Beitou Rd., Beitou Dist. - Taipei City Taiwan 112028 - TW - 0C-BF-B4 (hex) Acula Technology Corp 000000-0FFFFF (base 16) Acula Technology Corp 11 Alley 21 Lane 20 Dashing Rd.,Luchu Dist Taoyuan City 33862, Taiwan @@ -7457,18 +7457,18 @@ D00000-DFFFFF (base 16) ACTECK TECHNOLOGY Co., Ltd Warren NH 03279 US -5C-5C-75 (hex) Bkeen International Corporated -400000-4FFFFF (base 16) Bkeen International Corporated - No.11 xingyung street chungli dist taoyuan city - Taoyuan 320 - TW - 5C-5C-75 (hex) Spectrum FiftyNine BV 900000-9FFFFF (base 16) Spectrum FiftyNine BV Middelweg 8a Molenhoek Limb 6584ah NL +5C-5C-75 (hex) Bkeen International Corporated +400000-4FFFFF (base 16) Bkeen International Corporated + No.11 xingyung street chungli dist taoyuan city + Taoyuan 320 + TW + 5C-5C-75 (hex) Deuta America E00000-EFFFFF (base 16) Deuta America 5547 A1A S, Suite 111 @@ -7505,12 +7505,6 @@ D00000-DFFFFF (base 16) Atlas Tech Inc Winnipeg Manitoba R3S0A1 CA -B4-AB-F3 (hex) Stravik Technologies LLC -800000-8FFFFF (base 16) Stravik Technologies LLC - 447 Sutter St Ste 405 - San Francisco CA 94108 - US - B4-AB-F3 (hex) Shenzhen Unicair Communication Technology Co., Ltd. 200000-2FFFFF (base 16) Shenzhen Unicair Communication Technology Co., Ltd. 8-9/F, Block1, Wutong Island, Shunchang Rd., Xixiang, Bao'an District, @@ -7523,17 +7517,11 @@ B00000-BFFFFF (base 16) Vissonic Electronics Limited Guangzhou 510000 CN -60-15-9F (hex) MaiaSpace -E00000-EFFFFF (base 16) MaiaSpace - BATIMENT A37 ARIANEGROUP, FORET DE VERNON - Vernon 27200 - FR - -60-15-9F (hex) SHENZHEN DAERXIN TECHNOLOGY CO.,LTD -600000-6FFFFF (base 16) SHENZHEN DAERXIN TECHNOLOGY CO.,LTD - 1st Building West 3 /f, Daerxun Technolgy Parks, No.29 pingxin North Road,Pinghu,Longgang - shenzhen guangdong 518111 - CN +B4-AB-F3 (hex) Stravik Technologies LLC +800000-8FFFFF (base 16) Stravik Technologies LLC + 447 Sutter St Ste 405 + San Francisco CA 94108 + US 80-77-86 (hex) Wintec Co., Ltd 200000-2FFFFF (base 16) Wintec Co., Ltd @@ -7547,10 +7535,16 @@ E00000-EFFFFF (base 16) MaiaSpace New York NY 10010 US -08-3C-03 (hex) Dongguan Development Security Intelligent Tech Co., Ltd -C00000-CFFFFF (base 16) Dongguan Development Security Intelligent Tech Co., Ltd - Room 202, No. 17, Sanzhong Xinglong Road, - Qingxi Town Dongguan City 523000 +60-15-9F (hex) MaiaSpace +E00000-EFFFFF (base 16) MaiaSpace + BATIMENT A37 ARIANEGROUP, FORET DE VERNON + Vernon 27200 + FR + +60-15-9F (hex) SHENZHEN DAERXIN TECHNOLOGY CO.,LTD +600000-6FFFFF (base 16) SHENZHEN DAERXIN TECHNOLOGY CO.,LTD + 1st Building West 3 /f, Daerxun Technolgy Parks, No.29 pingxin North Road,Pinghu,Longgang + shenzhen guangdong 518111 CN 80-77-86 (hex) Huizhou Jiemeisi Technology Co.,Ltd. @@ -7565,6 +7559,12 @@ A00000-AFFFFF (base 16) Huizhou Jiemeisi Technology Co.,Ltd. North Kuta Bali 80361 ID +08-3C-03 (hex) Dongguan Development Security Intelligent Tech Co., Ltd +C00000-CFFFFF (base 16) Dongguan Development Security Intelligent Tech Co., Ltd + Room 202, No. 17, Sanzhong Xinglong Road, + Qingxi Town Dongguan City 523000 + CN + 34-D7-F5 (hex) Hefei Panyuan Intelligent Technology Co., Ltd A00000-AFFFFF (base 16) Hefei Panyuan Intelligent Technology Co., Ltd No. 116 Shilian South Road, High-tech District, @@ -7601,42 +7601,60 @@ A00000-AFFFFF (base 16) Hefei Panyuan Intelligent Technology Co., Ltd Largo FL 33773 US -E8-6C-C7 (hex) ebblo Western Europe -000000-0FFFFF (base 16) ebblo Western Europe - Rheinstrasse 36 - Neuhausen am Rheinfall Schaffhausen 8212 - CH - 18-C3-E4 (hex) Trusted Technology Solutions, Inc. 400000-4FFFFF (base 16) Trusted Technology Solutions, Inc. 346 River Street Lemont IL 60439 US +E8-6C-C7 (hex) ebblo Western Europe +000000-0FFFFF (base 16) ebblo Western Europe + Rheinstrasse 36 + Neuhausen am Rheinfall Schaffhausen 8212 + CH + 18-C3-E4 (hex) BRS Sistemas Eletrônicos 700000-7FFFFF (base 16) BRS Sistemas Eletrônicos Rua Capistrano de Abreu, 68 Canoas RS 92120130 BR -C4-82-72 (hex) Digisine Energytech Co., Ltd. -200000-2FFFFF (base 16) Digisine Energytech Co., Ltd. - 2F, No. 196, Sec. 2, Zhongxing Rd., Xindian Dist., - New Taipei City 231 - TW - C4-82-72 (hex) Mantenimiento y paileria 600000-6FFFFF (base 16) Mantenimiento y paileria Avenida 16 de Septiembre 21 Cuautitlán Estado de México 54831 MX +C4-82-72 (hex) Digisine Energytech Co., Ltd. +200000-2FFFFF (base 16) Digisine Energytech Co., Ltd. + 2F, No. 196, Sec. 2, Zhongxing Rd., Xindian Dist., + New Taipei City 231 + TW + C4-82-72 (hex) Satways Ltd 900000-9FFFFF (base 16) Satways Ltd 15 Megalou Konstantinou Street Irakleio, Attica 14122 GR +38-B1-4E (hex) Guangzhou Sunrise Technology Co., Ltd. +C00000-CFFFFF (base 16) Guangzhou Sunrise Technology Co., Ltd. + 503, C2,No.182 Science Avenue,Science City,High-Tech Industrial Development Zone Guangzhou, Guangdong , CN. + Guangzhou Guangdong 510000 + CN + +38-B1-4E (hex) Universal Robots A/S +600000-6FFFFF (base 16) Universal Robots A/S + Energivej 51 + Odense S Odense 5260 + DK + +38-B1-4E (hex) DCL COMMUNICATION PTE. LTD. +900000-9FFFFF (base 16) DCL COMMUNICATION PTE. LTD. + 10 Ubi Crescent #04-18 + Singapore 408564 + SG + B8-4C-87 (hex) Shenzhen Link-all Technology Co., Ltd 300000-3FFFFF (base 16) Shenzhen Link-all Technology Co., Ltd Floor 5th, Block 9th, Sunny Industrial Zone, Xili Town, Nanshan District, Shenzhen, China @@ -14270,12 +14288,6 @@ C00000-CFFFFF (base 16) Faaftech Goiânia Goias 74093020 BR -B0-CC-CE (hex) MICROTEST -E00000-EFFFFF (base 16) MICROTEST - 14 F.-6, No. 79, Sec. 1, Xintai 5th Rd., Xizhi Dist. - New Taipei 221432 - TW - B0-CC-CE (hex) Shenzhen Xtooltech Intelligent Co.,Ltd. 400000-4FFFFF (base 16) Shenzhen Xtooltech Intelligent Co.,Ltd. 17&18/F, A2 Building, Creative City, Liuxian Avenue, Nanshan District, Shenzhen, China @@ -14294,6 +14306,12 @@ A00000-AFFFFF (base 16) Skylight San Francisco CA 94111 US +B0-CC-CE (hex) MICROTEST +E00000-EFFFFF (base 16) MICROTEST + 14 F.-6, No. 79, Sec. 1, Xintai 5th Rd., Xizhi Dist. + New Taipei 221432 + TW + 78-78-35 (hex) EHTech (Beijing)Co., Ltd. 200000-2FFFFF (base 16) EHTech (Beijing)Co., Ltd. 2nd Floor, Building 6 (Block D), No.5 Shengfang Road, Daxing District @@ -14336,12 +14354,6 @@ FC-E4-98 (hex) NTCSOFT Osaka-shi Osaka 530-0047 JP -00-6A-5E (hex) CYBERTEL BRIDGE -C00000-CFFFFF (base 16) CYBERTEL BRIDGE - 9th floor, Hansin IT Tower, 272, Digital-ro,Guro-gu - Seoul 08389 - KR - F4-97-9D (hex) Kaiware (Shenzhen) Technologies Co.,Ltd B00000-BFFFFF (base 16) Kaiware (Shenzhen) Technologies Co.,Ltd B716, Key Laboratory Platform Building, Shenzhen Virtual University Park, No. 1 Yuexing 2nd Road, High-tech Park Community, Yuehai Street, Nanshan District, Shenzhen, Guangdong 518057, China @@ -14354,12 +14366,24 @@ A00000-AFFFFF (base 16) Annapurna labs Mail box 15123 Haifa 3508409 IL +00-6A-5E (hex) CYBERTEL BRIDGE +C00000-CFFFFF (base 16) CYBERTEL BRIDGE + 9th floor, Hansin IT Tower, 272, Digital-ro,Guro-gu + Seoul 08389 + KR + 00-6A-5E (hex) Beijing Lingji Innovations technology Co,LTD. D00000-DFFFFF (base 16) Beijing Lingji Innovations technology Co,LTD. Room 106, 1st Floor, A-1 Building, Zhongguancun Dongsheng Science and Technology Park, No. 66 Xixiaokou Road, Haidian District, Beijing Beijing Beijing 100190 CN +F4-97-9D (hex) Teenage Engineering AB +E00000-EFFFFF (base 16) Teenage Engineering AB + Textilgatan 31 + Stockholm n/a 12030 + SE + F4-97-9D (hex) Warner Technology Corp 500000-5FFFFF (base 16) Warner Technology Corp 421 Shepherds Way @@ -14372,11 +14396,17 @@ A00000-AFFFFF (base 16) MARKT Co., Ltd Seongnam-si Gyeonggi-do 13558 KR -F4-97-9D (hex) Teenage Engineering AB -E00000-EFFFFF (base 16) Teenage Engineering AB - Textilgatan 31 - Stockholm n/a 12030 - SE +48-08-EB (hex) Hangzhou Jianan Technology Co.,Ltd +500000-5FFFFF (base 16) Hangzhou Jianan Technology Co.,Ltd + Room-4606, Building 3, Sijiqing Street, Shangcheng District + Hangzhou Zhejiang Province 310000 + CN + +48-08-EB (hex) ZHEJIANG AIKE INTELLIGENTTECHNOLOGY CO.LTD +C00000-CFFFFF (base 16) ZHEJIANG AIKE INTELLIGENTTECHNOLOGY CO.LTD + No. 18, Chunjiang Road, Ningwei Street, Xiaoshan District, Hangzhou City, Zhejiang + Hangzhou Zhejiang 311200 + CN E0-23-3B (hex) Ugreen Group Limited E00000-EFFFFF (base 16) Ugreen Group Limited @@ -14396,18 +14426,6 @@ D00000-DFFFFF (base 16) Magosys Systems LTD Rehovot 7638517 IL -48-08-EB (hex) Hangzhou Jianan Technology Co.,Ltd -500000-5FFFFF (base 16) Hangzhou Jianan Technology Co.,Ltd - Room-4606, Building 3, Sijiqing Street, Shangcheng District - Hangzhou Zhejiang Province 310000 - CN - -48-08-EB (hex) ZHEJIANG AIKE INTELLIGENTTECHNOLOGY CO.LTD -C00000-CFFFFF (base 16) ZHEJIANG AIKE INTELLIGENTTECHNOLOGY CO.LTD - No. 18, Chunjiang Road, Ningwei Street, Xiaoshan District, Hangzhou City, Zhejiang - Hangzhou Zhejiang 311200 - CN - 48-08-EB (hex) Eruminc Co.,Ltd. B00000-BFFFFF (base 16) Eruminc Co.,Ltd. 59-47, Seouldaehak-ro @@ -14420,10 +14438,10 @@ B00000-BFFFFF (base 16) Eruminc Co.,Ltd. Beijing 100027 CN -50-FA-CB (hex) Shenzhen Hill Technology Co., LTD. -500000-5FFFFF (base 16) Shenzhen Hill Technology Co., LTD. - Room 203, No.118 Xingye 1st Road, Rentian Community, Fuhai Street, Bao’an District - Shenzhen Guangdong 518103 +48-08-EB (hex) Shenzhen Electron Technology Co., LTD. +700000-7FFFFF (base 16) Shenzhen Electron Technology Co., LTD. + Building 2, Yingfeng Industrial Zone, Tantou Community, Songgang Street, Bao'an District + Shenzhen Guangzhou 51800 CN 50-FA-CB (hex) Huaihua Jiannan Electronic Technology Co.,Ltd. @@ -14432,16 +14450,10 @@ B00000-BFFFFF (base 16) Eruminc Co.,Ltd. Huaihua 418000 CN -48-08-EB (hex) Shenzhen Electron Technology Co., LTD. -700000-7FFFFF (base 16) Shenzhen Electron Technology Co., LTD. - Building 2, Yingfeng Industrial Zone, Tantou Community, Songgang Street, Bao'an District - Shenzhen Guangzhou 51800 - CN - -9C-E4-50 (hex) Shenzhen GW Technology Co., LTD -600000-6FFFFF (base 16) Shenzhen GW Technology Co., LTD - 2-1501C, Building T2, Haigu Technology Building, Luozu Community, Shiyan Street, Bao'an District, Shenzhen City, Guangdong Province - Shenzhen Guangdong 518101 +50-FA-CB (hex) Shenzhen Hill Technology Co., LTD. +500000-5FFFFF (base 16) Shenzhen Hill Technology Co., LTD. + Room 203, No.118 Xingye 1st Road, Rentian Community, Fuhai Street, Bao’an District + Shenzhen Guangdong 518103 CN 50-FA-CB (hex) Kyocera AVX Components (Timisoara) SRL @@ -14456,22 +14468,10 @@ E00000-EFFFFF (base 16) Advant sp. z o.o. Gdańsk pomorskie 80-398 PL -9C-E4-50 (hex) AIO SYSTEMS -100000-1FFFFF (base 16) AIO SYSTEMS - 158 Jan smuts drive,Walter streetRosebank quarter - Johannesburg 2196 - ZA - -24-A1-0D (hex) REVUPTECH PRIVATE LIMITED -C00000-CFFFFF (base 16) REVUPTECH PRIVATE LIMITED - G 232, G.B. NAGAR SECTOR 63 NOIDA - NOIDA UTTAR PRADESH 201301 - IN - -9C-E4-50 (hex) Shenzhen Lixun Technology Co., Ltd. -200000-2FFFFF (base 16) Shenzhen Lixun Technology Co., Ltd. - Room 209, Building D, Xinda Creative Park, Qianjin 2nd Road and Baotian 2nd Road, Bao'an District - Shenzhen 518102 +9C-E4-50 (hex) Shenzhen GW Technology Co., LTD +600000-6FFFFF (base 16) Shenzhen GW Technology Co., LTD + 2-1501C, Building T2, Haigu Technology Building, Luozu Community, Shiyan Street, Bao'an District, Shenzhen City, Guangdong Province + Shenzhen Guangdong 518101 CN F4-20-55 (hex) Shanghai Kanghai Information System CO.,LTD. @@ -14492,20 +14492,38 @@ B0-47-5E (hex) Shanghai Kanghai Information System CO.,LTD. ShenZhen GuangDong 518000 CN -FC-E4-98 (hex) Shanghai Kanghai Information System CO.,LTD. -300000-3FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. +38-A8-CD (hex) Shanghai Kanghai Information System CO.,LTD. +700000-7FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN +9C-E4-50 (hex) AIO SYSTEMS +100000-1FFFFF (base 16) AIO SYSTEMS + 158 Jan smuts drive,Walter streetRosebank quarter + Johannesburg 2196 + ZA + +9C-E4-50 (hex) Shenzhen Lixun Technology Co., Ltd. +200000-2FFFFF (base 16) Shenzhen Lixun Technology Co., Ltd. + Room 209, Building D, Xinda Creative Park, Qianjin 2nd Road and Baotian 2nd Road, Bao'an District + Shenzhen 518102 + CN + +24-A1-0D (hex) REVUPTECH PRIVATE LIMITED +C00000-CFFFFF (base 16) REVUPTECH PRIVATE LIMITED + G 232, G.B. NAGAR SECTOR 63 NOIDA + NOIDA UTTAR PRADESH 201301 + IN + 9C-E4-50 (hex) Marelli AL&S ALIT-TZ 300000-3FFFFF (base 16) Marelli AL&S ALIT-TZ Via dell'industria 17 Tolmezzo Italy/Udine 33028 IT -38-A8-CD (hex) Shanghai Kanghai Information System CO.,LTD. -700000-7FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. +FC-E4-98 (hex) Shanghai Kanghai Information System CO.,LTD. +300000-3FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN @@ -14552,11 +14570,11 @@ E8-F6-D7 (hex) Xiphos Systems Corp. Montreal QC H2W 1Y5 CA -74-33-36 (hex) Lyno Dynamics LLC -900000-9FFFFF (base 16) Lyno Dynamics LLC - 2232 dell range blvd - Cheyenne WY 82009 - US +E8-F6-D7 (hex) ZIEHL-ABEGG SE +300000-3FFFFF (base 16) ZIEHL-ABEGG SE + Heinz-Ziehl-Strasse 1 + Kuenzelsau 74653 + DE 74-33-36 (hex) Elide Interfaces Inc 400000-4FFFFF (base 16) Elide Interfaces Inc @@ -14564,18 +14582,18 @@ E8-F6-D7 (hex) Xiphos Systems Corp. Brooklyn NY 11211 US +74-33-36 (hex) Lyno Dynamics LLC +900000-9FFFFF (base 16) Lyno Dynamics LLC + 2232 dell range blvd + Cheyenne WY 82009 + US + E8-F6-D7 (hex) emicrotec 500000-5FFFFF (base 16) emicrotec Münzgrabenstraße 168/102 Graz Styria 8010 AT -E8-F6-D7 (hex) ZIEHL-ABEGG SE -300000-3FFFFF (base 16) ZIEHL-ABEGG SE - Heinz-Ziehl-Strasse 1 - Kuenzelsau 74653 - DE - 0C-BF-B4 (hex) Nanchang si colordisplay Technology Co.,Ltd D00000-DFFFFF (base 16) Nanchang si colordisplay Technology Co.,Ltd No.679,Aixihu North Road, High-tech Zone @@ -14594,18 +14612,18 @@ A00000-AFFFFF (base 16) IRTEYA LLC Hengelo Overijssel 7554PA NL -58-76-07 (hex) Hubcom Techno System LLP -D00000-DFFFFF (base 16) Hubcom Techno System LLP - Level 4 Ceejay House, Dr. Annie Besant Road, Worli, Mumbai City - mumbai Maharashtra 400018 - IN - 58-76-07 (hex) Shade Innovations 600000-6FFFFF (base 16) Shade Innovations 9715 B Burnet Rd. Suite 400 Austin TX 78758 US +58-76-07 (hex) Hubcom Techno System LLP +D00000-DFFFFF (base 16) Hubcom Techno System LLP + Level 4 Ceejay House, Dr. Annie Besant Road, Worli, Mumbai City + mumbai Maharashtra 400018 + IN + 5C-5C-75 (hex) hassoun Gulf Industrial Company 800000-8FFFFF (base 16) hassoun Gulf Industrial Company Building NO:9273Al Shihabi Street3rd Industrial CityJeddah- KSA @@ -14618,23 +14636,17 @@ D00000-DFFFFF (base 16) Hubcom Techno System LLP villepreux 78450 FR -C0-9B-F4 (hex) AUMOVIO Components Malaysia Sdn.Bhd. -E00000-EFFFFF (base 16) AUMOVIO Components Malaysia Sdn.Bhd. - 2455, MK.1, Tingkat Perusahaan 2A, - Prai Industrial Estate, Prai, Penang 13600 - MY - 58-AD-08 (hex) Suzhou Huichuan United Power System Co.,Ltd D00000-DFFFFF (base 16) Suzhou Huichuan United Power System Co.,Ltd Suzhou Huichuan United Power System Co., Ltd Suzhou Jiangsu 215000 CN -B4-AB-F3 (hex) Shenyang Tianwei Technology Co., Ltd -400000-4FFFFF (base 16) Shenyang Tianwei Technology Co., Ltd - 666-1 Nanjing South Street Hunnan District - Shenyang City Liaoning Province 110000 - CN +C0-9B-F4 (hex) AUMOVIO Components Malaysia Sdn.Bhd. +E00000-EFFFFF (base 16) AUMOVIO Components Malaysia Sdn.Bhd. + 2455, MK.1, Tingkat Perusahaan 2A, + Prai Industrial Estate, Prai, Penang 13600 + MY 58-AD-08 (hex) MileOne Technologies Inc E00000-EFFFFF (base 16) MileOne Technologies Inc @@ -14642,6 +14654,12 @@ E00000-EFFFFF (base 16) MileOne Technologies Inc Dover DE 19901 US +B4-AB-F3 (hex) Shenyang Tianwei Technology Co., Ltd +400000-4FFFFF (base 16) Shenyang Tianwei Technology Co., Ltd + 666-1 Nanjing South Street Hunnan District + Shenyang City Liaoning Province 110000 + CN + B4-AB-F3 (hex) RANG DONG LIGHT SOURCE & VACUUM FLASK J.S.C E00000-EFFFFF (base 16) RANG DONG LIGHT SOURCE & VACUUM FLASK J.S.C 87 - 89 Ha Dinh Str, Khuong Dinh Ward, Hanoi , Vietnam @@ -14654,18 +14672,18 @@ B4-AB-F3 (hex) Rugged Video LLC Cedarburg WI 53012 US -08-3C-03 (hex) Luxshare Precision Industry Co., Ltd. -A00000-AFFFFF (base 16) Luxshare Precision Industry Co., Ltd. - 2/F, Block A, Sanyang New Industrial Zone, West Haoyi, Shajing Street, - Baoan District Shenzhen 314117 - CN - 08-3C-03 (hex) Beijing New Matrix Information Technology CO., Ltd 400000-4FFFFF (base 16) Beijing New Matrix Information Technology CO., Ltd Building 2,No.1 Courtyard,Taibai West Road, Fengtai Districtr Beijing Beijing 100071 CN +08-3C-03 (hex) Luxshare Precision Industry Co., Ltd. +A00000-AFFFFF (base 16) Luxshare Precision Industry Co., Ltd. + 2/F, Block A, Sanyang New Industrial Zone, West Haoyi, Shajing Street, + Baoan District Shenzhen 314117 + CN + 34-D7-F5 (hex) ShenZhen C&D Electronics CO.Ltd. 100000-1FFFFF (base 16) ShenZhen C&D Electronics CO.Ltd. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District @@ -14690,18 +14708,6 @@ D00000-DFFFFF (base 16) JEL Corporation FUKUYAMA HIROSHIMA 7200831 JP -18-C3-E4 (hex) CASE Deutschland GmbH -D00000-DFFFFF (base 16) CASE Deutschland GmbH - Gruener Ring 126 - Braunschweig Niedersachsen 38108 - DE - -6C-47-80 (hex) HD HYUNDAI ENERGY SOLUTIONS CO., LTD. -100000-1FFFFF (base 16) HD HYUNDAI ENERGY SOLUTIONS CO., LTD. - 477, Bundangsuseo-ro - Bundang-gu, Seongnam-si Gyeonggi-do 13553 - KR - 6C-47-80 (hex) Prolink Surveillance Technology Co.Ltd A00000-AFFFFF (base 16) Prolink Surveillance Technology Co.Ltd 10F.-7, No.18, Ln. 609, Sec. 5, Chongxin Rd., Sanchong Dist. @@ -14714,23 +14720,59 @@ A00000-AFFFFF (base 16) Prolink Surveillance Technology Co.Ltd Warsaw 02-653 PL +18-C3-E4 (hex) CASE Deutschland GmbH +D00000-DFFFFF (base 16) CASE Deutschland GmbH + Gruener Ring 126 + Braunschweig Niedersachsen 38108 + DE + +6C-47-80 (hex) HD HYUNDAI ENERGY SOLUTIONS CO., LTD. +100000-1FFFFF (base 16) HD HYUNDAI ENERGY SOLUTIONS CO., LTD. + 477, Bundangsuseo-ro + Bundang-gu, Seongnam-si Gyeonggi-do 13553 + KR + 18-C3-E4 (hex) Clicks Technology Ltd 600000-6FFFFF (base 16) Clicks Technology Ltd 2 Stone Buildings London WC2A 3TH GB +6C-47-80 (hex) Alban Giacomo S.p.a. +E00000-EFFFFF (base 16) Alban Giacomo S.p.a. + Via Alcide De Gasperi, 75 + Romano d'Ezzelino Vicenza 36060 + IT + C4-82-72 (hex) MyPlace Australia Pty Ltd B00000-BFFFFF (base 16) MyPlace Australia Pty Ltd 115 Vulcan Rd Canning Vale WA 6155 AU -6C-47-80 (hex) Alban Giacomo S.p.a. -E00000-EFFFFF (base 16) Alban Giacomo S.p.a. - Via Alcide De Gasperi, 75 - Romano d'Ezzelino Vicenza 36060 - IT +38-B1-4E (hex) Noitom Robotics Technology (Beijing) Co.,Ltd. +400000-4FFFFF (base 16) Noitom Robotics Technology (Beijing) Co.,Ltd. + 601–604, 6th Floor, Building 2, NO.16, Xiaoyuehe Dongpan Road, Haidian District + Beijing Beijing 100085 + CN + +38-B1-4E (hex) Shenzhen Mondo Technology Co,.Ltd +100000-1FFFFF (base 16) Shenzhen Mondo Technology Co,.Ltd + East Wing, 4th Floor, Building 1Gemdale Vison Software Technology ParkNanshan District, Shenzhen CityGuangdong Province, P.R. China + Shenzhen Guangdong 518057 + CN + +38-B1-4E (hex) Shenzhen Tongchuang Mechatronics co,LtD. +000000-0FFFFF (base 16) Shenzhen Tongchuang Mechatronics co,LtD. + 1026# Songbai Road, + Shenzhen Guangdong 51800 + CN + +38-B1-4E (hex) QRONOZ CO., Ltd. +300000-3FFFFF (base 16) QRONOZ CO., Ltd. + Rm. 2, 9 F., No. 6, Ln. 180, Sec. 6,Minquan E. Rd., Neihu Dist., + Taipei City 114708 + TW B8-4C-87 (hex) Altronix , Corp A00000-AFFFFF (base 16) Altronix , Corp @@ -21797,24 +21839,30 @@ E00000-EFFFFF (base 16) Orion Power Systems, Inc. Yenimahalle ANKARA 06374 TR -04-58-5D (hex) Rexon Technology -C00000-CFFFFF (base 16) Rexon Technology - No. 261, Renhua Rd., Dali Dist. - Taichung City 412037 - TW - 04-58-5D (hex) Research Laboratory of Design Automation, Ltd. 100000-1FFFFF (base 16) Research Laboratory of Design Automation, Ltd. 8 Birzhevoy Spusk Taganrog 347900 RU +04-58-5D (hex) Rexon Technology +C00000-CFFFFF (base 16) Rexon Technology + No. 261, Renhua Rd., Dali Dist. + Taichung City 412037 + TW + D4-A0-FB (hex) Skyfri Corp 700000-7FFFFF (base 16) Skyfri Corp 800 North State Street Suite 403 City of Dover DE 19901 US +D4-A0-FB (hex) Snap-on Tools +C00000-CFFFFF (base 16) Snap-on Tools + 19220 San Jose Ave. + City of Industry CA 91748 + US + B0-CC-CE (hex) Watermark Systems (India) Private Limited B00000-BFFFFF (base 16) Watermark Systems (India) Private Limited 1010, Maker Chambers 5, Nariman Point, Mumbai @@ -21827,11 +21875,11 @@ B0-CC-CE (hex) Gateview Technologies JACKSONVILLE FL 32226 US -D4-A0-FB (hex) Snap-on Tools -C00000-CFFFFF (base 16) Snap-on Tools - 19220 San Jose Ave. - City of Industry CA 91748 - US +B0-CC-CE (hex) Xiaomi EV Technology Co., Ltd. +D00000-DFFFFF (base 16) Xiaomi EV Technology Co., Ltd. + Room 618, Floor 6, Building 5, Yard 15, Kechuang Tenth Street, Beijing Economic and Technological Development Zone, Beijing + Beijing Beijing 100176 + CN B0-CC-CE (hex) Beijing Viazijing Technology Co., Ltd. 500000-5FFFFF (base 16) Beijing Viazijing Technology Co., Ltd. @@ -21839,11 +21887,11 @@ B0-CC-CE (hex) Beijing Viazijing Technology Co., Ltd. Beijing 100085 CN -B0-CC-CE (hex) Xiaomi EV Technology Co., Ltd. -D00000-DFFFFF (base 16) Xiaomi EV Technology Co., Ltd. - Room 618, Floor 6, Building 5, Yard 15, Kechuang Tenth Street, Beijing Economic and Technological Development Zone, Beijing - Beijing Beijing 100176 - CN +F8-2B-E6 (hex) MaiaEdge, Inc. +C00000-CFFFFF (base 16) MaiaEdge, Inc. + 77 S. Bedford Street + Burlington MA 01803 + US 78-78-35 (hex) Ambient Life Inc. 700000-7FFFFF (base 16) Ambient Life Inc. @@ -21851,18 +21899,18 @@ D00000-DFFFFF (base 16) Xiaomi EV Technology Co., Ltd. Newton MA 02460 US -F8-2B-E6 (hex) MaiaEdge, Inc. -C00000-CFFFFF (base 16) MaiaEdge, Inc. - 77 S. Bedford Street - Burlington MA 01803 - US - FC-E4-98 (hex) Siretta Ltd C00000-CFFFFF (base 16) Siretta Ltd Basingstoke Rd, Spencers Wood, Reading Reading RG7 1PW GB +FC-E4-98 (hex) SM Instruments +500000-5FFFFF (base 16) SM Instruments + 20, Yuseong-daero 1184beon-gil + Daejeon Yuseong-gu 34109 + KR + 78-78-35 (hex) BLOOM VIEW LIMITED 900000-9FFFFF (base 16) BLOOM VIEW LIMITED Room 1502 ,Easey Commercial Building @@ -21881,18 +21929,18 @@ E00000-EFFFFF (base 16) Aplex Technology Inc. Zhonghe District New Taipei City 235 - TW -FC-E4-98 (hex) SM Instruments -500000-5FFFFF (base 16) SM Instruments - 20, Yuseong-daero 1184beon-gil - Daejeon Yuseong-gu 34109 - KR - 34-B5-F3 (hex) WEAD GmbH 300000-3FFFFF (base 16) WEAD GmbH Retzfeld 7 Sankt Georgen an der Gusen 4222 AT +34-B5-F3 (hex) Digicom +D00000-DFFFFF (base 16) Digicom + The 4th floor, Building No.4, Xiangshan South Road 105#, Shijingshan, Beijing, China + BEIJING 100144 + CN + 34-B5-F3 (hex) Shanghai Sigen New Energy Technology Co., Ltd 800000-8FFFFF (base 16) Shanghai Sigen New Energy Technology Co., Ltd Room 514 The 5th Floor, No.175 Weizhan Road China (Shanghai) Plilot Free Trade Zone @@ -21911,12 +21959,6 @@ C00000-CFFFFF (base 16) Shenzhen Mifasuolla Smart Co.,Ltd Shenzhen Guangdong 518052 CN -34-B5-F3 (hex) Digicom -D00000-DFFFFF (base 16) Digicom - The 4th floor, Building No.4, Xiangshan South Road 105#, Shijingshan, Beijing, China - BEIJING 100144 - CN - 34-B5-F3 (hex) Viettel Manufacturing Corporation One Member Limited Liability Company E00000-EFFFFF (base 16) Viettel Manufacturing Corporation One Member Limited Liability Company An Binh Hamlet, An Khanh Commune @@ -21929,18 +21971,18 @@ E00000-EFFFFF (base 16) Viettel Manufacturing Corporation One Member Limite Mughalsarai Uttar Pradesh(UP) 232101 IN -00-6A-5E (hex) Shenzhen yeahmoo Technology Co., Ltd. -300000-3FFFFF (base 16) Shenzhen yeahmoo Technology Co., Ltd. - Room 103, 1st Floor, Building 4, Yunli Intelligent Park, No. 3 Changfa Middle Road,Yangmei Community, Bantian Street, Longgang District, - Shenzhen Guangdong Province 518100 - CN - 04-58-5D (hex) HDS Otomasyon Güvenlik ve Yazılım Teknolojileri Sanayi Ticaret Limited Şirketi D00000-DFFFFF (base 16) HDS Otomasyon Güvenlik ve Yazılım Teknolojileri Sanayi Ticaret Limited Şirketi Merkez Mahallesi Sadabad Cad. Kapı No:20 İstanbul Kağıthane 34406 TR +00-6A-5E (hex) Shenzhen yeahmoo Technology Co., Ltd. +300000-3FFFFF (base 16) Shenzhen yeahmoo Technology Co., Ltd. + Room 103, 1st Floor, Building 4, Yunli Intelligent Park, No. 3 Changfa Middle Road,Yangmei Community, Bantian Street, Longgang District, + Shenzhen Guangdong Province 518100 + CN + F4-97-9D (hex) LUXSHARE - ICT(NGHE AN) LIMITED 700000-7FFFFF (base 16) LUXSHARE - ICT(NGHE AN) LIMITED No. 18, Road No. 03, VSIP Nghe An Industrial Park, Hung Nguyen Commune, Nghe An Province, Vietnam @@ -21965,59 +22007,41 @@ F4-97-9D (hex) MERRY ELECTRONICS CO., LTD. TAICHUNG 408213 TW -48-08-EB (hex) Quanta Storage Inc. -400000-4FFFFF (base 16) Quanta Storage Inc. - 3F. No.188, Wenhua 2nd Rd - Taoyuan City Guishan District 33383 - TW - -E0-23-3B (hex) Rehear Audiology Company LTD. -700000-7FFFFF (base 16) Rehear Audiology Company LTD. - 2F., No.57, Xingzhong Rd., Neihu Dist., - Taipei 114 - TW - -E0-23-3B (hex) PluralFusion INC -200000-2FFFFF (base 16) PluralFusion INC - 1717 E Cary Street - Richmond VA 23223 - US - 48-08-EB (hex) Tianjin Jinmu Intelligent Control Technology Co., Ltd 100000-1FFFFF (base 16) Tianjin Jinmu Intelligent Control Technology Co., Ltd Room 3271, Building 1, Collaborative Development Center, West Ring North Road, Beijing-Tianjin Zhongguancun Science Park, BaoDi District, Tianjin, China. Tianjin 301800 CN -48-08-EB (hex) Technological Application And Production One Member Liability Company (Tecapro Company) -300000-3FFFFF (base 16) Technological Application And Production One Member Liability Company (Tecapro Company) - 18A Cong Hoa Street, Ward Bay Hien - Hochiminh Hochiminh 70000 - VN - 48-08-EB (hex) Yeacode (Xiamen) Inkjet Inc. A00000-AFFFFF (base 16) Yeacode (Xiamen) Inkjet Inc. 2F, No.8826, Lianting Road, Xiang An District Xiamen FUJIAN 361100 CN -50-FA-CB (hex) Darveen Technology Limited -400000-4FFFFF (base 16) Darveen Technology Limited - 3/F, 2nd Building Hui Sheng Da industrial park, Qingcui road, Longhua district, Shenzhen - Shenzhen Guangdong 518000 - CN +48-08-EB (hex) Quanta Storage Inc. +400000-4FFFFF (base 16) Quanta Storage Inc. + 3F. No.188, Wenhua 2nd Rd + Taoyuan City Guishan District 33383 + TW -50-FA-CB (hex) Shenzhen Evertones Quantum Technology Co., Ltd. -100000-1FFFFF (base 16) Shenzhen Evertones Quantum Technology Co., Ltd. - Room 3907, Tower B, Digital Innovation Center, Beizhan Community, Minzhi Sub-district, Longhua District - Shenzhen Guangdong 518131 - CN +E0-23-3B (hex) Rehear Audiology Company LTD. +700000-7FFFFF (base 16) Rehear Audiology Company LTD. + 2F., No.57, Xingzhong Rd., Neihu Dist., + Taipei 114 + TW -C4-FF-BC (hex) Shanghai Kanghai Information System CO.,LTD. -600000-6FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. - 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District - ShenZhen GuangDong 518000 - CN +E0-23-3B (hex) PluralFusion INC +200000-2FFFFF (base 16) PluralFusion INC + 1717 E Cary Street + Richmond VA 23223 + US + +48-08-EB (hex) Technological Application And Production One Member Liability Company (Tecapro Company) +300000-3FFFFF (base 16) Technological Application And Production One Member Liability Company (Tecapro Company) + 18A Cong Hoa Street, Ward Bay Hien + Hochiminh Hochiminh 70000 + VN 64-62-66 (hex) Shanghai Kanghai Information System CO.,LTD. 700000-7FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. @@ -22049,16 +22073,10 @@ D00000-DFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. ShenZhen GuangDong 518000 CN -9C-E4-50 (hex) Shenzhen HQVT TECHNOLOGY Co.,LTD -900000-9FFFFF (base 16) Shenzhen HQVT TECHNOLOGY Co.,LTD - 3/F,Building 8 ,Taihua Wutong Island,Xixiang,Bao'an District - China Guang Dong 518000 - CN - -9C-E4-50 (hex) Shenzhen Coslight Technology Co.,Ltd. -B00000-BFFFFF (base 16) Shenzhen Coslight Technology Co.,Ltd. - Room 101, Factory Building, No. 2 Guangtian Road, Luotian Community, Yanluo Sub-district, Bao'an District, Shenzhen - Shenzhen 518000 +50-FA-CB (hex) Darveen Technology Limited +400000-4FFFFF (base 16) Darveen Technology Limited + 3/F, 2nd Building Hui Sheng Da industrial park, Qingcui road, Longhua district, Shenzhen + Shenzhen Guangdong 518000 CN 48-5E-0E (hex) Shanghai Kanghai Information System CO.,LTD. @@ -22073,16 +22091,16 @@ EC-74-CD (hex) Shanghai Kanghai Information System CO.,LTD. ShenZhen GuangDong 518000 CN -9C-E4-50 (hex) BEIJING TRANSTREAMS TECHNOLOGY CO.,LTD -700000-7FFFFF (base 16) BEIJING TRANSTREAMS TECHNOLOGY CO.,LTD - Room 1401, 14th Floor, Building 8, No. 8 Kegu 1st Street, Beijing Economic and Technological Development Zone - Beijing Beijing 100176 +C4-FF-BC (hex) Shanghai Kanghai Information System CO.,LTD. +600000-6FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. + 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District + ShenZhen GuangDong 518000 CN -9C-E4-50 (hex) Shenzhen Kuki Electric Co., Ltd. -E00000-EFFFFF (base 16) Shenzhen Kuki Electric Co., Ltd. - No.6 Shichang Road,Xinqiao Street Baoan District,Shenzhen,Guangdong - Shenzhen Guangdong 518125 +50-FA-CB (hex) Shenzhen Evertones Quantum Technology Co., Ltd. +100000-1FFFFF (base 16) Shenzhen Evertones Quantum Technology Co., Ltd. + Room 3907, Tower B, Digital Innovation Center, Beizhan Community, Minzhi Sub-district, Longhua District + Shenzhen Guangdong 518131 CN 24-A1-0D (hex) Shenzhen Star Instrument Co., Ltd. @@ -22100,12 +22118,42 @@ B00000-BFFFFF (base 16) Private Hangzhou Binjiang Distric 310000 CN +9C-E4-50 (hex) BEIJING TRANSTREAMS TECHNOLOGY CO.,LTD +700000-7FFFFF (base 16) BEIJING TRANSTREAMS TECHNOLOGY CO.,LTD + Room 1401, 14th Floor, Building 8, No. 8 Kegu 1st Street, Beijing Economic and Technological Development Zone + Beijing Beijing 100176 + CN + +9C-E4-50 (hex) Shenzhen Kuki Electric Co., Ltd. +E00000-EFFFFF (base 16) Shenzhen Kuki Electric Co., Ltd. + No.6 Shichang Road,Xinqiao Street Baoan District,Shenzhen,Guangdong + Shenzhen Guangdong 518125 + CN + +9C-E4-50 (hex) Shenzhen HQVT TECHNOLOGY Co.,LTD +900000-9FFFFF (base 16) Shenzhen HQVT TECHNOLOGY Co.,LTD + 3/F,Building 8 ,Taihua Wutong Island,Xixiang,Bao'an District + China Guang Dong 518000 + CN + +9C-E4-50 (hex) Shenzhen Coslight Technology Co.,Ltd. +B00000-BFFFFF (base 16) Shenzhen Coslight Technology Co.,Ltd. + Room 101, Factory Building, No. 2 Guangtian Road, Luotian Community, Yanluo Sub-district, Bao'an District, Shenzhen + Shenzhen 518000 + CN + 24-A1-0D (hex) Sony Honda Mobility Inc. 200000-2FFFFF (base 16) Sony Honda Mobility Inc. Midtown-East 9th floor, 9-7-2 Akasaka Minato-ku Tokyo 107-0052 JP +F0-40-AF (hex) Shenzhen BitFantasy Technology Co., Ltd +B00000-BFFFFF (base 16) Shenzhen BitFantasy Technology Co., Ltd + Room 507, Building C3, East Industrial Zone, No.12 Wenchang Street, Xiangshan Street Community, Shahe Subdistrict, Nanshan District, Shenzhen, Guangdong, China + Shenzhen 518000 + CN + F0-40-AF (hex) Actia Nordic AB 200000-2FFFFF (base 16) Actia Nordic AB Datalinjen 3A @@ -22136,29 +22184,35 @@ C00000-CFFFFF (base 16) clover Co,.Ltd Uiwang-si Gyeonggi-do 16072 KR -F0-40-AF (hex) Shenzhen BitFantasy Technology Co., Ltd -B00000-BFFFFF (base 16) Shenzhen BitFantasy Technology Co., Ltd - Room 507, Building C3, East Industrial Zone, No.12 Wenchang Street, Xiangshan Street Community, Shahe Subdistrict, Nanshan District, Shenzhen, Guangdong, China - Shenzhen 518000 - CN - E8-F6-D7 (hex) Massive Beams GmbH 600000-6FFFFF (base 16) Massive Beams GmbH Bismarckstr. 10-12 Berlin 10625 DE +74-33-36 (hex) Ramon Space +E00000-EFFFFF (base 16) Ramon Space + HAHARASH 4 + HOD HASHARON 4524078 + IL + 74-33-36 (hex) Moultrie Mobile 800000-8FFFFF (base 16) Moultrie Mobile 5724 Highway 280 East Birmingham AL 35242 US -74-33-36 (hex) Ramon Space -E00000-EFFFFF (base 16) Ramon Space - HAHARASH 4 - HOD HASHARON 4524078 - IL +74-33-36 (hex) Zoller + Fröhlich GmbH +200000-2FFFFF (base 16) Zoller + Fröhlich GmbH + Simoniusstraße 22 + Wangen im Allgäu 88239 + DE + +0C-BF-B4 (hex) ShenZhen XunDun Technology CO.LTD +300000-3FFFFF (base 16) ShenZhen XunDun Technology CO.LTD + 2/F, Building 11, Mabian Industrial Zone (Dezhi High-tech Park), Area 72, Xingdong Community, Xin 'an Street, Bao 'an District, Shenzhen + ShenZhen 518101 + CN 0C-BF-B4 (hex) Shenzhen EN Plus Tech Co.,Ltd. 400000-4FFFFF (base 16) Shenzhen EN Plus Tech Co.,Ltd. @@ -22178,18 +22232,6 @@ E00000-EFFFFF (base 16) Ramon Space Nuremberg Bayern 90441 DE -74-33-36 (hex) Zoller + Fröhlich GmbH -200000-2FFFFF (base 16) Zoller + Fröhlich GmbH - Simoniusstraße 22 - Wangen im Allgäu 88239 - DE - -0C-BF-B4 (hex) ShenZhen XunDun Technology CO.LTD -300000-3FFFFF (base 16) ShenZhen XunDun Technology CO.LTD - 2/F, Building 11, Mabian Industrial Zone (Dezhi High-tech Park), Area 72, Xingdong Community, Xin 'an Street, Bao 'an District, Shenzhen - ShenZhen 518101 - CN - 0C-BF-B4 (hex) ICWiser 500000-5FFFFF (base 16) ICWiser 5th Floor, Building 1, Liandong U Valley, No. 97, Xingguan Road, Industrial Park, Jiading District, @@ -22208,12 +22250,6 @@ C00000-CFFFFF (base 16) EV4 Limited Lokeren 9160 BE -58-76-07 (hex) HARDWARIO a.s. -000000-0FFFFF (base 16) HARDWARIO a.s. - U Jezu 525/4 - Liberec 460 01 - CZ - 20-2B-DA (hex) ZhuoYu Technology E00000-EFFFFF (base 16) ZhuoYu Technology No. 60 Xingke Road, Xili Street @@ -22250,12 +22286,30 @@ B00000-BFFFFF (base 16) Rwaytech Archamps Haute-Savoie 74160 FR +58-76-07 (hex) HARDWARIO a.s. +000000-0FFFFF (base 16) HARDWARIO a.s. + U Jezu 525/4 + Liberec 460 01 + CZ + +5C-5C-75 (hex) Shenzhen Jooan Technology Co., Ltd +D00000-DFFFFF (base 16) Shenzhen Jooan Technology Co., Ltd + Area B, Floor 101-2, Floor 3, Floor 5 and Floor 6 of area B, Building No. 8, Guixiang Community Plaza Road, Guanlan Street, Longhua District, Shenzhen. + Shenzhen Guangdong 518000 + CN + 5C-5C-75 (hex) YingKeSong Pen Industry Technology R&D Center Shenzhen Co Ltd 500000-5FFFFF (base 16) YingKeSong Pen Industry Technology R&D Center Shenzhen Co Ltd R1605 Building 1 HengDaDuHui Plaza, BanTian LongGang Shenzhen Guangdong 518129 CN +5C-5C-75 (hex) UOI TECHNOLOGY CORPORATION +700000-7FFFFF (base 16) UOI TECHNOLOGY CORPORATION + 1F., No. 50, Ln. 148, Lide St. + Zhonghe Dist. New Taipei City 23512 + TW + 5C-5C-75 (hex) Elite Link 000000-0FFFFF (base 16) Elite Link No.1226, F12,Chouyin Building-A, Rd188, Shangcheng Avenue, Financial Services District @@ -22268,24 +22322,6 @@ A00000-AFFFFF (base 16) InoxSmart by Unison Hardware sacramento CA 95829 US -5C-5C-75 (hex) Shenzhen Jooan Technology Co., Ltd -D00000-DFFFFF (base 16) Shenzhen Jooan Technology Co., Ltd - Area B, Floor 101-2, Floor 3, Floor 5 and Floor 6 of area B, Building No. 8, Guixiang Community Plaza Road, Guanlan Street, Longhua District, Shenzhen. - Shenzhen Guangdong 518000 - CN - -5C-5C-75 (hex) UOI TECHNOLOGY CORPORATION -700000-7FFFFF (base 16) UOI TECHNOLOGY CORPORATION - 1F., No. 50, Ln. 148, Lide St. - Zhonghe Dist. New Taipei City 23512 - TW - -58-AD-08 (hex) Fujian Ruihe Technology Co., Ltd. -300000-3FFFFF (base 16) Fujian Ruihe Technology Co., Ltd. - No. 3, Building 6, 3rd Floor, Military Housing Administration Building, 528 Xihong Road, Gulou District, Fuzhou City - Fuzhou Fujian 350000 - CN - 58-AD-08 (hex) Jiangsu Delianda Intelligent Technology Co., Ltd. 700000-7FFFFF (base 16) Jiangsu Delianda Intelligent Technology Co., Ltd. Intelligent Terminal Innovation Park (D), High-tech Zone @@ -22298,6 +22334,18 @@ B00000-BFFFFF (base 16) AUMOVIO Brazil Industry Ltda. Guarulhos São Paulo 07042-020 BR +58-AD-08 (hex) Fujian Ruihe Technology Co., Ltd. +300000-3FFFFF (base 16) Fujian Ruihe Technology Co., Ltd. + No. 3, Building 6, 3rd Floor, Military Housing Administration Building, 528 Xihong Road, Gulou District, Fuzhou City + Fuzhou Fujian 350000 + CN + +B4-AB-F3 (hex) VOOMAX TECHNOLOGY LIMITED +000000-0FFFFF (base 16) VOOMAX TECHNOLOGY LIMITED + 11/F CENTRALTOWER 28 QUEEN'S RD CENTRAL,CENTRAL + HONG KONG 999077 + HK + B4-AB-F3 (hex) SNSYS 600000-6FFFFF (base 16) SNSYS 7f 830, Dongtansunhwan-daero @@ -22310,18 +22358,6 @@ B4-AB-F3 (hex) NubiCubi Karlsruhe Baden Wurttenberg 76187 DE -B4-AB-F3 (hex) VOOMAX TECHNOLOGY LIMITED -000000-0FFFFF (base 16) VOOMAX TECHNOLOGY LIMITED - 11/F CENTRALTOWER 28 QUEEN'S RD CENTRAL,CENTRAL - HONG KONG 999077 - HK - -60-15-9F (hex) HUIZHOU BOHUI CONNECTION TECHNOLOGY CO., LTD -800000-8FFFFF (base 16) HUIZHOU BOHUI CONNECTION TECHNOLOGY CO., LTD - 中国广东省惠州市惠城区东江高新区东兴大道111号泓淋工业园 - 惠州 广东省 516000 - CN - 60-15-9F (hex) QingDao Hiincom Electronics Co., Ltd A00000-AFFFFF (base 16) QingDao Hiincom Electronics Co., Ltd No.1 JinYe Road @@ -22340,11 +22376,11 @@ C00000-CFFFFF (base 16) Terrestar Solutions Inc Montreal Quebec H2V 2L1 CA -80-77-86 (hex) Realtime Biometrics India (P) limited -400000-4FFFFF (base 16) Realtime Biometrics India (P) limited - C-83, Ganesh Nagar, Pandav Nagar, New Delhi, Delhi, 110092 - Delhi Delhi 110092 - IN +60-15-9F (hex) HUIZHOU BOHUI CONNECTION TECHNOLOGY CO., LTD +800000-8FFFFF (base 16) HUIZHOU BOHUI CONNECTION TECHNOLOGY CO., LTD + 中国广东省惠州市惠城区东江高新区东兴大道111号泓淋工业园 + 惠州 广东省 516000 + CN 60-15-9F (hex) Shenzhen NTS Technology Co.,Ltd 900000-9FFFFF (base 16) Shenzhen NTS Technology Co.,Ltd @@ -22352,15 +22388,33 @@ C00000-CFFFFF (base 16) Terrestar Solutions Inc Shenzhen Guangdong 518100 CN +80-77-86 (hex) Realtime Biometrics India (P) limited +400000-4FFFFF (base 16) Realtime Biometrics India (P) limited + C-83, Ganesh Nagar, Pandav Nagar, New Delhi, Delhi, 110092 + Delhi Delhi 110092 + IN + 80-77-86 (hex) Daisy Audio Inc. 000000-0FFFFF (base 16) Daisy Audio Inc. 500 N Central Ave. Suite 600 Glendale CA 91203 US +80-77-86 (hex) YSTen Technology Co., Ltd. +800000-8FFFFF (base 16) YSTen Technology Co., Ltd. + Room 101, Building D (Cygnus), Wuxi Software Park, No. 111 Linghu Avenue, Xinwu District, + Wuxi City Jiangsu Province 214028 + CN + 08-3C-03 (hex) Private B00000-BFFFFF (base 16) Private +08-3C-03 (hex) Wildtech +200000-2FFFFF (base 16) Wildtech + 23 Leinster Road + Christchurch 8014 + NZ + 08-3C-03 (hex) SNM Technology 100000-1FFFFF (base 16) SNM Technology 664, Sosa-ro @@ -22373,18 +22427,6 @@ B00000-BFFFFF (base 16) Private Austin TX 78750 US -80-77-86 (hex) YSTen Technology Co., Ltd. -800000-8FFFFF (base 16) YSTen Technology Co., Ltd. - Room 101, Building D (Cygnus), Wuxi Software Park, No. 111 Linghu Avenue, Xinwu District, - Wuxi City Jiangsu Province 214028 - CN - -08-3C-03 (hex) Wildtech -200000-2FFFFF (base 16) Wildtech - 23 Leinster Road - Christchurch 8014 - NZ - 08-3C-03 (hex) Federal Signal SSG 000000-0FFFFF (base 16) Federal Signal SSG 2645 Federal Signal Drive @@ -22427,26 +22469,17 @@ D00000-DFFFFF (base 16) Schenker Storen AG Ellisville MO 63021 US -18-C3-E4 (hex) 38220 -900000-9FFFFF (base 16) 38220 - C/ Mariano Barbacid, 5. 3ª planta - Rivas Vaciamadrid Madrid 28521 - ES - 18-C3-E4 (hex) iX-tech GmbH 500000-5FFFFF (base 16) iX-tech GmbH Römerstadt 2 Saarbrücken Saarland 66121 DE -C4-82-72 (hex) E2-CAD -C00000-CFFFFF (base 16) E2-CAD - 13-17 Allée Rosa Luxemburg - Eragny sur oise 95610 - FR - -C4-82-72 (hex) Private -100000-1FFFFF (base 16) Private +18-C3-E4 (hex) 38220 +900000-9FFFFF (base 16) 38220 + C/ Mariano Barbacid, 5. 3ª planta + Rivas Vaciamadrid Madrid 28521 + ES C4-82-72 (hex) Mode Sensors AS 700000-7FFFFF (base 16) Mode Sensors AS @@ -22454,18 +22487,6 @@ C4-82-72 (hex) Mode Sensors AS Trondheim 7037 NO -C4-82-72 (hex) Tolt Technologies LLC -A00000-AFFFFF (base 16) Tolt Technologies LLC - 19520 Mountain View Road NE - Duvall WA 98019-8822 - US - -C4-82-72 (hex) Schunk SE & Co. KG -500000-5FFFFF (base 16) Schunk SE & Co. KG - Bahnhofstraße 106-134 - Lauffen am Neckar 74348 - DE - 18-C3-E4 (hex) Fime SAS A00000-AFFFFF (base 16) Fime SAS 8 rue du Commodore JH HALLET @@ -22478,12 +22499,51 @@ A00000-AFFFFF (base 16) Fime SAS Pacé 35740 FR +C4-82-72 (hex) Schunk SE & Co. KG +500000-5FFFFF (base 16) Schunk SE & Co. KG + Bahnhofstraße 106-134 + Lauffen am Neckar 74348 + DE + +C4-82-72 (hex) E2-CAD +C00000-CFFFFF (base 16) E2-CAD + 13-17 Allée Rosa Luxemburg + Eragny sur oise 95610 + FR + +C4-82-72 (hex) Private +100000-1FFFFF (base 16) Private + +C4-82-72 (hex) Tolt Technologies LLC +A00000-AFFFFF (base 16) Tolt Technologies LLC + 19520 Mountain View Road NE + Duvall WA 98019-8822 + US + +38-B1-4E (hex) Huizhou GYXX Technology Co., Ltd +B00000-BFFFFF (base 16) Huizhou GYXX Technology Co., Ltd + Room 01, 4th Floor, Building 2,No. 13, Dahuixi Section, Huizhou Avenue,Shuikou Subdistrict Office, Huicheng District,Huizhou, Guangdong, China + Huizhou 516005 + CN + C4-82-72 (hex) Smart Radar System, Inc E00000-EFFFFF (base 16) Smart Radar System, Inc 7F, Innovalley A, 253 Pangyo-ro Bundang-gu Seongnam-si Gyeonggi-do Korea 13486 KR +38-B1-4E (hex) NACE +700000-7FFFFF (base 16) NACE + 1085 Andrew dr + West Chester PA 19380 + US + +20-B3-7F (hex) B810 SPA +B00000-BFFFFF (base 16) B810 SPA + Via Enzo Lazzaretti, 2/1 + REGGIO EMILIA Reggio Emilia 42122 + IT + D0-14-11 (hex) P.B. Elettronica srl 100000-1FFFFF (base 16) P.B. Elettronica srl Via Santorelli, 8 @@ -29321,23 +29381,17 @@ FC-A2-DF (hex) SpacemiT zhuhai guangdong 519000 CN -D4-A0-FB (hex) Shenzhen Dijiean Technology Co., Ltd -400000-4FFFFF (base 16) Shenzhen Dijiean Technology Co., Ltd - Floor 6,Building B,Tongxie Industrial Zone,No.80 Shilong Road,Shiyan Street,Baoan District - Shenzhen City Guangdong 518000 - CN - 04-58-5D (hex) Wetatronics Limited 000000-0FFFFF (base 16) Wetatronics Limited 45 Bath StreetParnell Auckland Auckland 1052 NZ -D4-A0-FB (hex) M2MD Technologies, Inc. -000000-0FFFFF (base 16) M2MD Technologies, Inc. - 525 Chestnut Rose Ln - Atlanta GA 30327 - US +04-58-5D (hex) JRK VISION +800000-8FFFFF (base 16) JRK VISION + A-1107, 135, Gasan digital 2-ro, Geumcheon-gu + SEOUL 08504 + KR D4-A0-FB (hex) Corelase Oy 500000-5FFFFF (base 16) Corelase Oy @@ -29345,16 +29399,16 @@ D4-A0-FB (hex) Corelase Oy Tampere Pirkanmaa 33720 FI -04-58-5D (hex) JRK VISION -800000-8FFFFF (base 16) JRK VISION - A-1107, 135, Gasan digital 2-ro, Geumcheon-gu - SEOUL 08504 - KR +D4-A0-FB (hex) Shenzhen Dijiean Technology Co., Ltd +400000-4FFFFF (base 16) Shenzhen Dijiean Technology Co., Ltd + Floor 6,Building B,Tongxie Industrial Zone,No.80 Shilong Road,Shiyan Street,Baoan District + Shenzhen City Guangdong 518000 + CN -D4-A0-FB (hex) Spatial Hover Inc -B00000-BFFFFF (base 16) Spatial Hover Inc - 10415 A Westpark Dr. - Houston TX 77042 +D4-A0-FB (hex) M2MD Technologies, Inc. +000000-0FFFFF (base 16) M2MD Technologies, Inc. + 525 Chestnut Rose Ln + Atlanta GA 30327 US D4-A0-FB (hex) NEXXUS NETWORKS INDIA PRIVATE LIMITED @@ -29363,6 +29417,12 @@ D4-A0-FB (hex) NEXXUS NETWORKS INDIA PRIVATE LIMITED GAUTAM BUDDHA NAGAR UTTAR PRADESH 201301 IN +D4-A0-FB (hex) Spatial Hover Inc +B00000-BFFFFF (base 16) Spatial Hover Inc + 10415 A Westpark Dr. + Houston TX 77042 + US + D4-A0-FB (hex) Huizhou Jiemeisi Technology Co.,Ltd. 600000-6FFFFF (base 16) Huizhou Jiemeisi Technology Co.,Ltd. NO.63, HUMEI STREET, DASHULING, XIAOJINKOU HUICHENG @@ -29393,12 +29453,6 @@ B0-CC-CE (hex) Agrisys A/S Seongnam 13590 KR -FC-E4-98 (hex) QuEL, Inc. -100000-1FFFFF (base 16) QuEL, Inc. - ON Build. 5F 4-7-14 Myojincho - Hachioji Tokyo 192-0046 - JP - FC-E4-98 (hex) Infinity Electronics Ltd D00000-DFFFFF (base 16) Infinity Electronics Ltd 167-169 Great Portland Street @@ -29417,6 +29471,12 @@ FC-E4-98 (hex) AVCON Information Technology Co.,Ltd. Shanghai Shanghai 021-55666588 CN +FC-E4-98 (hex) QuEL, Inc. +100000-1FFFFF (base 16) QuEL, Inc. + ON Build. 5F 4-7-14 Myojincho + Hachioji Tokyo 192-0046 + JP + 34-B5-F3 (hex) Hyatta Digital Technology Co., Ltd. 700000-7FFFFF (base 16) Hyatta Digital Technology Co., Ltd. 1405, Building A, Huizhi R&D Center, No. 287 Guangshen Road, Xixiang Street, Bao'an District @@ -29453,11 +29513,11 @@ C00000-CFFFFF (base 16) Lab241 Co.,Ltd. Seoul 08511 KR -F4-97-9D (hex) Huitec printer solution co., -D00000-DFFFFF (base 16) Huitec printer solution co., - 2f#104 Minchuan Rd. Hisdean district - New Taipei Taiwan 23141 - TW +E0-23-3B (hex) Quality Pay Systems S.L. +000000-0FFFFF (base 16) Quality Pay Systems S.L. + 21 Forja Avenue, Cañada de la Fuente Industrial Park + Martos Jaen 23600 + ES F4-97-9D (hex) Beijing Jiaxin Technology Co., Ltd 900000-9FFFFF (base 16) Beijing Jiaxin Technology Co., Ltd @@ -29471,59 +29531,47 @@ F4-97-9D (hex) Equinox Power Burnaby BC V5J 0H1 CA +F4-97-9D (hex) Huitec printer solution co., +D00000-DFFFFF (base 16) Huitec printer solution co., + 2f#104 Minchuan Rd. Hisdean district + New Taipei Taiwan 23141 + TW + +E0-23-3B (hex) 356 Productions +300000-3FFFFF (base 16) 356 Productions + 1881 West Traverse Pkwy + LEHI UT 84043 + US + E0-23-3B (hex) Chengdu ChengFeng Technology co,. Ltd. C00000-CFFFFF (base 16) Chengdu ChengFeng Technology co,. Ltd. High-tech Zone TianfuSoftwarePark,B6-103,CHENGDU, 610000 CHENGDU SICHUAN 610000 CN -E0-23-3B (hex) Quality Pay Systems S.L. -000000-0FFFFF (base 16) Quality Pay Systems S.L. - 21 Forja Avenue, Cañada de la Fuente Industrial Park - Martos Jaen 23600 - ES - E0-23-3B (hex) Kiwimoore(Shanghai) Semiconductor Co.,Ltd 600000-6FFFFF (base 16) Kiwimoore(Shanghai) Semiconductor Co.,Ltd 9F, Block B, No. 800 Naxian Road, Pudong New District Shanghai 201210 CN -E0-23-3B (hex) 356 Productions -300000-3FFFFF (base 16) 356 Productions - 1881 West Traverse Pkwy - LEHI UT 84043 - US - E0-23-3B (hex) Elvys s.r.o 100000-1FFFFF (base 16) Elvys s.r.o Polska 9 Kosice 04011 SK -00-6A-5E (hex) DICOM CORPORATION -600000-6FFFFF (base 16) DICOM CORPORATION - 15TH FL, CENTER BUILDING, NO 1 NGUYEN HUY TUONG STR, THANH XUAN WARD - Hanoi 100000 - VN - 50-FA-CB (hex) Bosch Security Systems 900000-9FFFFF (base 16) Bosch Security Systems Estrada Nacional 109/IC 1 Ovar Aveiro 3880-728 PT -0C-7F-ED (hex) Tango Networks Inc -200000-2FFFFF (base 16) Tango Networks Inc - 2601 Network Blvd, Suite 410 - Frisco TX TX 75034 - US - -50-FA-CB (hex) 1208815047 -600000-6FFFFF (base 16) 1208815047 - 5F, 547, SAMSEONG-RO, GANGNAM-GU,SEOUL, SOUTH KOREA - seoul 06156 - KR +00-6A-5E (hex) DICOM CORPORATION +600000-6FFFFF (base 16) DICOM CORPORATION + 15TH FL, CENTER BUILDING, NO 1 NGUYEN HUY TUONG STR, THANH XUAN WARD + Hanoi 100000 + VN 50-FA-CB (hex) Combined Public Communications, LLC B00000-BFFFFF (base 16) Combined Public Communications, LLC @@ -29531,6 +29579,12 @@ B00000-BFFFFF (base 16) Combined Public Communications, LLC Cold Spring KY 41076 US +0C-7F-ED (hex) Tango Networks Inc +200000-2FFFFF (base 16) Tango Networks Inc + 2601 Network Blvd, Suite 410 + Frisco TX TX 75034 + US + 50-FA-CB (hex) todoc 000000-0FFFFF (base 16) todoc 501ho, 242 Digital-ro @@ -29543,29 +29597,23 @@ D00000-DFFFFF (base 16) Vortex Infotech Private Limited Mumbai Maharashtra 400104 IN -04-EE-E8 (hex) Shanghai Kanghai Information System CO.,LTD. -700000-7FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. - 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District - ShenZhen GuangDong 518000 - CN - -88-A6-EF (hex) Shanghai Kanghai Information System CO.,LTD. -C00000-CFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. +54-A4-93 (hex) Shanghai Kanghai Information System CO.,LTD. +400000-4FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN -C0-48-2F (hex) Shanghai Kanghai Information System CO.,LTD. -B00000-BFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. +04-EE-E8 (hex) Shanghai Kanghai Information System CO.,LTD. +700000-7FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN -54-A4-93 (hex) Shanghai Kanghai Information System CO.,LTD. -400000-4FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. - 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District - ShenZhen GuangDong 518000 - CN +50-FA-CB (hex) 1208815047 +600000-6FFFFF (base 16) 1208815047 + 5F, 547, SAMSEONG-RO, GANGNAM-GU,SEOUL, SOUTH KOREA + seoul 06156 + KR 24-A1-0D (hex) Amina Distribution AS D00000-DFFFFF (base 16) Amina Distribution AS @@ -29573,30 +29621,24 @@ D00000-DFFFFF (base 16) Amina Distribution AS Stavanger 4032 NO -24-A1-0D (hex) Gönnheimer Elektronic GmbH -E00000-EFFFFF (base 16) Gönnheimer Elektronic GmbH - Dr. Julius Leber Str. 2 - Neustadt Rheinland Pfalz 67433 - DE - -F0-40-AF (hex) Proemion GmbH -D00000-DFFFFF (base 16) Proemion GmbH - Donaustraße 14 - Fulda Hessen 36043 - DE - -F0-40-AF (hex) Unionbell Technologies Limited -700000-7FFFFF (base 16) Unionbell Technologies Limited - Crown Court Estate, NO 11 DR Nwachukwu Nwanesi Street - Durumi Abuja 900103 - NG +88-A6-EF (hex) Shanghai Kanghai Information System CO.,LTD. +C00000-CFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. + 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District + ShenZhen GuangDong 518000 + CN -F0-40-AF (hex) Shanghai Kanghai Information System CO.,LTD. -E00000-EFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. +C0-48-2F (hex) Shanghai Kanghai Information System CO.,LTD. +B00000-BFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN +24-A1-0D (hex) Gönnheimer Elektronic GmbH +E00000-EFFFFF (base 16) Gönnheimer Elektronic GmbH + Dr. Julius Leber Str. 2 + Neustadt Rheinland Pfalz 67433 + DE + F0-40-AF (hex) Colorlight Cloud Tech Ltd 000000-0FFFFF (base 16) Colorlight Cloud Tech Ltd 38F, Building A, Building 8, Shenzhen International Innovation Valley, Vanke Cloud City, Nanshan District, Shenzhen @@ -29615,6 +29657,30 @@ F0-40-AF (hex) Nuro.ai Mountain View CA 94070 US +F0-40-AF (hex) Proemion GmbH +D00000-DFFFFF (base 16) Proemion GmbH + Donaustraße 14 + Fulda Hessen 36043 + DE + +F0-40-AF (hex) Shanghai Kanghai Information System CO.,LTD. +E00000-EFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. + 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District + ShenZhen GuangDong 518000 + CN + +F0-40-AF (hex) Unionbell Technologies Limited +700000-7FFFFF (base 16) Unionbell Technologies Limited + Crown Court Estate, NO 11 DR Nwachukwu Nwanesi Street + Durumi Abuja 900103 + NG + +E8-F6-D7 (hex) ZhuoPuCheng (Shenzhen) Technology.Co.,Ltd. +D00000-DFFFFF (base 16) ZhuoPuCheng (Shenzhen) Technology.Co.,Ltd. + Building T3, Gaoxin Industrial Village, No. 011, Gaoxin Nanqi Dao + Shenzhen Guangdong 518057 + CN + E8-F6-D7 (hex) Mono Technologies Inc. 000000-0FFFFF (base 16) Mono Technologies Inc. 600 N Broad Street, Suite 5 # 924 @@ -29627,42 +29693,36 @@ A00000-AFFFFF (base 16) Ivostud GmbH Breckerfeld 58339 DE -E8-F6-D7 (hex) ZhuoPuCheng (Shenzhen) Technology.Co.,Ltd. -D00000-DFFFFF (base 16) ZhuoPuCheng (Shenzhen) Technology.Co.,Ltd. - Building T3, Gaoxin Industrial Village, No. 011, Gaoxin Nanqi Dao - Shenzhen Guangdong 518057 - CN - -74-33-36 (hex) SECLAB FR -500000-5FFFFF (base 16) SECLAB FR - 40 av Theroigne de Mericourt - MONTPELLIER 34000 - FR - 74-33-36 (hex) Shenzhen Handheld-Wireless Technology Co., Ltd. C00000-CFFFFF (base 16) Shenzhen Handheld-Wireless Technology Co., Ltd. 702-1, Building 5, Gonglian Fuji Innovation Park, No. 58 Ping'an Road, Dafu Community, Guanlan Street, Longhua District, Shenzhen GuangDong 518000 CN -74-33-36 (hex) Huzhou Luxshare Precision Industry Co.LTD -000000-0FFFFF (base 16) Huzhou Luxshare Precision Industry Co.LTD - 399 Shengxun Road, Zhili Town, Wuxing District,Huzhou City, Zhejiang Province - Huzhou Zhejiang 313008 - CN - 74-33-36 (hex) Annapurna labs B00000-BFFFFF (base 16) Annapurna labs Matam Scientific Industries Center, Building 8.2 Mail box 15123 Haifa 3508409 IL +74-33-36 (hex) SECLAB FR +500000-5FFFFF (base 16) SECLAB FR + 40 av Theroigne de Mericourt + MONTPELLIER 34000 + FR + 74-33-36 (hex) Venture International Pte Ltd 700000-7FFFFF (base 16) Venture International Pte Ltd 5006, Ang Mo Kio Ave 5, #05-01/12, Techplace II Singapore 569873 SG +74-33-36 (hex) Huzhou Luxshare Precision Industry Co.LTD +000000-0FFFFF (base 16) Huzhou Luxshare Precision Industry Co.LTD + 399 Shengxun Road, Zhili Town, Wuxing District,Huzhou City, Zhejiang Province + Huzhou Zhejiang 313008 + CN + C0-D3-91 (hex) SAMSARA NETWORKS INC E00000-EFFFFF (base 16) SAMSARA NETWORKS INC 1 De Haro St @@ -29681,18 +29741,18 @@ B00000-BFFFFF (base 16) Arvind Limited Pune Maharastra 411060 IN -58-76-07 (hex) Olte Climate sp. z o.o. -800000-8FFFFF (base 16) Olte Climate sp. z o.o. - ul. Rzeczna 8/5NIP: 6772533194 - Krakow malopolska 30-021 - PL - 20-2B-DA (hex) Shenzhen FeiCheng Technology Co.,Ltd 600000-6FFFFF (base 16) Shenzhen FeiCheng Technology Co.,Ltd Room 402, Building B, Huafeng Internet Creative Park, No. 107 Gongye Road, Gonge Community, Xixiang Street, Bao'an District, Shenzhen Shenzhen 518000 CN +58-76-07 (hex) Olte Climate sp. z o.o. +800000-8FFFFF (base 16) Olte Climate sp. z o.o. + ul. Rzeczna 8/5NIP: 6772533194 + Krakow malopolska 30-021 + PL + 20-2B-DA (hex) Industrial Connections & Solutions LLC A00000-AFFFFF (base 16) Industrial Connections & Solutions LLC 6801 Industrial Dr @@ -29711,6 +29771,18 @@ A00000-AFFFFF (base 16) Industrial Connections & Solutions LLC shanghai 200031 CN +58-AD-08 (hex) Wuxi Qinghexiaobei Technology Co., Ltd. +900000-9FFFFF (base 16) Wuxi Qinghexiaobei Technology Co., Ltd. + 801C,Building E, Yingchuang Power, NO.1 Shangdi East Road, Haidian District, Beijing + Beijing Beijing 100085 + CN + +58-AD-08 (hex) Also, Inc. +C00000-CFFFFF (base 16) Also, Inc. + 630 Hansen Way + Palo Alto CA 94306 + US + 5C-5C-75 (hex) Anhui Haima Cloud Technology Co.,Ltd B00000-BFFFFF (base 16) Anhui Haima Cloud Technology Co.,Ltd Wangjiang West Road 900# @@ -29723,17 +29795,11 @@ B00000-BFFFFF (base 16) Anhui Haima Cloud Technology Co.,Ltd Beijing Beijing 100176 CN -58-AD-08 (hex) Also, Inc. -C00000-CFFFFF (base 16) Also, Inc. - 630 Hansen Way - Palo Alto CA 94306 - US - -58-AD-08 (hex) Wuxi Qinghexiaobei Technology Co., Ltd. -900000-9FFFFF (base 16) Wuxi Qinghexiaobei Technology Co., Ltd. - 801C,Building E, Yingchuang Power, NO.1 Shangdi East Road, Haidian District, Beijing - Beijing Beijing 100085 - CN +60-15-9F (hex) Klaric GmbH & Co. KG +100000-1FFFFF (base 16) Klaric GmbH & Co. KG + Kesselstr. 17 + Stuttgart 70327 + DE 58-AD-08 (hex) Shenzhen Yumutek co.,ltd 400000-4FFFFF (base 16) Shenzhen Yumutek co.,ltd @@ -29741,23 +29807,17 @@ C00000-CFFFFF (base 16) Also, Inc. Shenzhen Guangdong 518000 CN -60-15-9F (hex) FF Videosistemas SL -D00000-DFFFFF (base 16) FF Videosistemas SL - Calle Vizcaya, 2 - Las Rozas de Madrid Madrid 28231 - ES - B4-AB-F3 (hex) Annapurna labs C00000-CFFFFF (base 16) Annapurna labs Matam Scientific Industries Center, Building 8.2 Mail box 15123 Haifa 3508409 IL -60-15-9F (hex) Klaric GmbH & Co. KG -100000-1FFFFF (base 16) Klaric GmbH & Co. KG - Kesselstr. 17 - Stuttgart 70327 - DE +60-15-9F (hex) FF Videosistemas SL +D00000-DFFFFF (base 16) FF Videosistemas SL + Calle Vizcaya, 2 + Las Rozas de Madrid Madrid 28231 + ES 60-15-9F (hex) Lens Technology(Xiangtan) Co.,Ltd B00000-BFFFFF (base 16) Lens Technology(Xiangtan) Co.,Ltd @@ -29765,12 +29825,6 @@ B00000-BFFFFF (base 16) Lens Technology(Xiangtan) Co.,Ltd Xiangtan Hunan 411100 CN -80-77-86 (hex) SMW-Autoblok Spannsysteme -900000-9FFFFF (base 16) SMW-Autoblok Spannsysteme - Wiesentalstr. 28 - Meckenbeuren 88074 - DE - 80-77-86 (hex) Applied Energy Technologies Pvt Ltd D00000-DFFFFF (base 16) Applied Energy Technologies Pvt Ltd Plot No:288A,3rd Floor, Udyog Vihar Phase-4, Sector-18,Gurugram Haryana-122015 @@ -29783,23 +29837,35 @@ D00000-DFFFFF (base 16) Applied Energy Technologies Pvt Ltd Pune Maharashtra 411023 IN +80-77-86 (hex) SMW-Autoblok Spannsysteme +900000-9FFFFF (base 16) SMW-Autoblok Spannsysteme + Wiesentalstr. 28 + Meckenbeuren 88074 + DE + 80-77-86 (hex) Mach B00000-BFFFFF (base 16) Mach 2002 Bethel Rd Ste 105 Finksburg MD 21048 US +08-3C-03 (hex) Jiaxing UPhoton Optoelectronics Technology Co., Ltd. +600000-6FFFFF (base 16) Jiaxing UPhoton Optoelectronics Technology Co., Ltd. + Room 102, 1st Floor, Building 10, No. 1156 Gaoqiao Avenue, Gaoqiao Street (Development Zone), Tongxiang City + Jiaxing Zhejiang 314500 + CN + 08-3C-03 (hex) Yinglian Technology Co.,Ltd D00000-DFFFFF (base 16) Yinglian Technology Co.,Ltd Room 802, 8th Floor, Building 5, Wenzhou Runchen Technology Co., Ltd., No. 333 Jiankang Road, Wanquan Town, Pingyang County Wenzhou Zhejiang 325000 CN -08-3C-03 (hex) Jiaxing UPhoton Optoelectronics Technology Co., Ltd. -600000-6FFFFF (base 16) Jiaxing UPhoton Optoelectronics Technology Co., Ltd. - Room 102, 1st Floor, Building 10, No. 1156 Gaoqiao Avenue, Gaoqiao Street (Development Zone), Tongxiang City - Jiaxing Zhejiang 314500 - CN +08-3C-03 (hex) GS Industrie-Elektronik GmbH +800000-8FFFFF (base 16) GS Industrie-Elektronik GmbH + Porschestrasse 11 + Leverkusen 51381 + DE 08-3C-03 (hex) LEADTEK BIOMED INC. 500000-5FFFFF (base 16) LEADTEK BIOMED INC. @@ -29813,12 +29879,6 @@ E00000-EFFFFF (base 16) Prozone jalandhar Punjab 144001 IN -08-3C-03 (hex) GS Industrie-Elektronik GmbH -800000-8FFFFF (base 16) GS Industrie-Elektronik GmbH - Porschestrasse 11 - Leverkusen 51381 - DE - 34-D7-F5 (hex) DREAMTECH 600000-6FFFFF (base 16) DREAMTECH 10F, U-Space 2 A Tower, 670 Daewangpangyo-ro, Bundang-Gu, Seongnam-Si, Gyeonggi-Do, Republic of Korea @@ -29831,8 +29891,11 @@ E00000-EFFFFF (base 16) Prozone ShenZhen Guangdong 518004 CN -6C-47-80 (hex) Private -900000-9FFFFF (base 16) Private +6C-47-80 (hex) KEI SYSTEM Co., Ltd. +000000-0FFFFF (base 16) KEI SYSTEM Co., Ltd. + 2-2-23 Kishinosato, Nishinari Ward + Osaka City Osaka 557-0041 + JP 34-D7-F5 (hex) Catapult Sports Inc E00000-EFFFFF (base 16) Catapult Sports Inc @@ -29840,18 +29903,15 @@ E00000-EFFFFF (base 16) Catapult Sports Inc Boston MA 02109 US -6C-47-80 (hex) KEI SYSTEM Co., Ltd. -000000-0FFFFF (base 16) KEI SYSTEM Co., Ltd. - 2-2-23 Kishinosato, Nishinari Ward - Osaka City Osaka 557-0041 - JP - 34-D7-F5 (hex) Cassel Messtechnik GmbH 300000-3FFFFF (base 16) Cassel Messtechnik GmbH In der Dehne 10 Dransfeld 37127 DE +6C-47-80 (hex) Private +900000-9FFFFF (base 16) Private + 18-C3-E4 (hex) HuiTong intelligence Company 100000-1FFFFF (base 16) HuiTong intelligence Company 8F., No. 51, Ln. 258, Rueiguang Rd., Neihu Dist., Taipei City 114, Taiwan (R.O.C.) @@ -29870,15 +29930,15 @@ E00000-EFFFFF (base 16) SHENZHEN MEGMEET ELECTRICAL CO., LTD ShenZhen 518051 CN +6C-47-80 (hex) Private +800000-8FFFFF (base 16) Private + 18-C3-E4 (hex) Bit Part LLC C00000-CFFFFF (base 16) Bit Part LLC 224 W 35th St, Ste 500 PMB 497 New York NY 10001 US -6C-47-80 (hex) Private -800000-8FFFFF (base 16) Private - C4-82-72 (hex) Gabriel Tecnologia 000000-0FFFFF (base 16) Gabriel Tecnologia Rua Doutor Virgilio de Carvalho Pinto, 142 @@ -29891,6 +29951,15 @@ D00000-DFFFFF (base 16) Posital B.V. Roermond 6041MA NL +38-B1-4E (hex) Private +D00000-DFFFFF (base 16) Private + +38-B1-4E (hex) Knit Sound Company +E00000-EFFFFF (base 16) Knit Sound Company + 496 Ada Dr SE Ste 201 + Ada MI 49301 + US + C8-5C-E2 (hex) Fela Management AG 000000-0FFFFF (base 16) Fela Management AG Basadingerstrasse 18 @@ -36842,11 +36911,11 @@ C00000-CFFFFF (base 16) Reonel Oy ji nan shi shandong 250031 CN -FC-A2-DF (hex) PDI COMMUNICATION SYSTEMS INC. -200000-2FFFFF (base 16) PDI COMMUNICATION SYSTEMS INC. - 40 GREENWOOD LN - SPRINGBORO OH 45066 - US +FC-A2-DF (hex) BPL MEDICAL TECHNOLOGIES PRIVATE LIMITED +A00000-AFFFFF (base 16) BPL MEDICAL TECHNOLOGIES PRIVATE LIMITED + 11KM BANNERGHATTA MAIN ROAD ARAKERE BANGALORE + BANGALORE KARNATAKA 560076 + IN FC-A2-DF (hex) Annapurna labs 500000-5FFFFF (base 16) Annapurna labs @@ -36854,11 +36923,11 @@ FC-A2-DF (hex) Annapurna labs Mail box 15123 Haifa 3508409 IL -FC-A2-DF (hex) BPL MEDICAL TECHNOLOGIES PRIVATE LIMITED -A00000-AFFFFF (base 16) BPL MEDICAL TECHNOLOGIES PRIVATE LIMITED - 11KM BANNERGHATTA MAIN ROAD ARAKERE BANGALORE - BANGALORE KARNATAKA 560076 - IN +FC-A2-DF (hex) PDI COMMUNICATION SYSTEMS INC. +200000-2FFFFF (base 16) PDI COMMUNICATION SYSTEMS INC. + 40 GREENWOOD LN + SPRINGBORO OH 45066 + US FC-A2-DF (hex) MBio Diagnostics, Inc. D00000-DFFFFF (base 16) MBio Diagnostics, Inc. @@ -36866,12 +36935,6 @@ D00000-DFFFFF (base 16) MBio Diagnostics, Inc. Loveland 80538 US -04-58-5D (hex) Foxconn Brasil Industria e Comercio Ltda -200000-2FFFFF (base 16) Foxconn Brasil Industria e Comercio Ltda - Av. Marginal da Rodovia dos Bandeirantes, 800 - Distrito Industrial - Jundiaí Sao Paulo 13213-008 - BR - 04-58-5D (hex) Dron Edge India Private Limited 900000-9FFFFF (base 16) Dron Edge India Private Limited A 93 SECTOR 65 NOIDA 201301 @@ -36890,6 +36953,12 @@ D00000-DFFFFF (base 16) MBio Diagnostics, Inc. Flensburg Schleswig-Holstein 24939 DE +04-58-5D (hex) Foxconn Brasil Industria e Comercio Ltda +200000-2FFFFF (base 16) Foxconn Brasil Industria e Comercio Ltda + Av. Marginal da Rodovia dos Bandeirantes, 800 - Distrito Industrial + Jundiaí Sao Paulo 13213-008 + BR + 04-58-5D (hex) Sercomm Japan Corporation 500000-5FFFFF (base 16) Sercomm Japan Corporation 8F, 3-1, YuanQu St., NanKang, Taipei 115, Taiwan @@ -36902,18 +36971,18 @@ A00000-AFFFFF (base 16) IMPULSE CCTV NETWORKS INDIA PVT. LTD. GREATER NOIDA WEST UTTAR PRADESH 201306 IN -D4-A0-FB (hex) Beijing Lingji Innovations technology Co,LTD. -200000-2FFFFF (base 16) Beijing Lingji Innovations technology Co,LTD. - Room 106, 1st Floor, A-1 Building, Zhongguancun Dongsheng Science and Technology Park, No. 66 Xixiaokou Road, Haidian District, Beijing - Beijing Beijing 100190 - CN - D4-A0-FB (hex) FASTWEL ELECTRONICS INDIA PRIVATE LIMITED D00000-DFFFFF (base 16) FASTWEL ELECTRONICS INDIA PRIVATE LIMITED DORASWANIPALYA , NO 3, ARKER MICOLAYOUT, ARKERE , BENGALURE(BANGLORE) URBAN BENGALURU KARNATAKA 560076 IN +D4-A0-FB (hex) Beijing Lingji Innovations technology Co,LTD. +200000-2FFFFF (base 16) Beijing Lingji Innovations technology Co,LTD. + Room 106, 1st Floor, A-1 Building, Zhongguancun Dongsheng Science and Technology Park, No. 66 Xixiaokou Road, Haidian District, Beijing + Beijing Beijing 100190 + CN + D4-A0-FB (hex) GTEK GLOBAL CO.,LTD E00000-EFFFFF (base 16) GTEK GLOBAL CO.,LTD No3/2/13 Ta Thanh Oai, Thanh Tri district @@ -36926,30 +36995,12 @@ A00000-AFFFFF (base 16) Shenzhen Dangs Science and Technology CO.,Ltd. Shenzhen Guangdong 518063 CN -78-78-35 (hex) Suzhou Chena Information Technology Co., Ltd. -D00000-DFFFFF (base 16) Suzhou Chena Information Technology Co., Ltd. - 3rd Floor, Building B6, No. 8 Yanghua Road, Suzhou Industrial Park - Suzhou Free Trade Zone Jiangsu Province 215000 - CN - B0-CC-CE (hex) Taiv Inc 900000-9FFFFF (base 16) Taiv Inc 400-321 McDermot Ave Winnipeg Manitoba R3A 0A3 CA -78-78-35 (hex) NEOARK Corporation -E00000-EFFFFF (base 16) NEOARK Corporation - Nakano-machi2073-1 - Hachioji Tokyo 1920015 - JP - -78-78-35 (hex) SHENZHEN CHUANGWEI ELECTRONIC APPLIANCE TECH CO., LTD. -300000-3FFFFF (base 16) SHENZHEN CHUANGWEI ELECTRONIC APPLIANCE TECH CO., LTD. - 6F Floor, Overseas Factory, Skyworth Technology Industrial Park, Tangtou Community, Shiyan Street, Bao'an District - Shenzhen Guangdong 518000 - CN - 78-78-35 (hex) ENQT GmbH 100000-1FFFFF (base 16) ENQT GmbH Spaldingstrasse 210 @@ -36962,12 +37013,30 @@ C00000-CFFFFF (base 16) DBG Communications Technology Co.,Ltd. Huizhou Gangdong 516083 CN +78-78-35 (hex) Suzhou Chena Information Technology Co., Ltd. +D00000-DFFFFF (base 16) Suzhou Chena Information Technology Co., Ltd. + 3rd Floor, Building B6, No. 8 Yanghua Road, Suzhou Industrial Park + Suzhou Free Trade Zone Jiangsu Province 215000 + CN + 78-78-35 (hex) Shanghai Intchains Technology Co., Ltd. B00000-BFFFFF (base 16) Shanghai Intchains Technology Co., Ltd. Building 1&2, No.333 Haiyang No.1 Road Lingang Science and Technology Park Pudon shanghai shanghai 200120 CN +78-78-35 (hex) NEOARK Corporation +E00000-EFFFFF (base 16) NEOARK Corporation + Nakano-machi2073-1 + Hachioji Tokyo 1920015 + JP + +78-78-35 (hex) SHENZHEN CHUANGWEI ELECTRONIC APPLIANCE TECH CO., LTD. +300000-3FFFFF (base 16) SHENZHEN CHUANGWEI ELECTRONIC APPLIANCE TECH CO., LTD. + 6F Floor, Overseas Factory, Skyworth Technology Industrial Park, Tangtou Community, Shiyan Street, Bao'an District + Shenzhen Guangdong 518000 + CN + FC-E4-98 (hex) Videonetics Technology Private Limited 600000-6FFFFF (base 16) Videonetics Technology Private Limited Videonetics Technology Private LimitedPlot No. AI/154/1, Action Area - 1A, 4th Floor, Utility Building @@ -36980,11 +37049,11 @@ FC-E4-98 (hex) E Haute Intelligent Technology Co., Ltd Shenzhen Guangdong 518000 CN -00-6A-5E (hex) TRULY ELECTRONICS MFG.,LTD -000000-0FFFFF (base 16) TRULY ELECTRONICS MFG.,LTD - Truly industry city,shanwei guangdong,P.R.C - shanwei guangdong 516600 - CN +34-B5-F3 (hex) LAUMAS Elettronica s.r.l. +400000-4FFFFF (base 16) LAUMAS Elettronica s.r.l. + via I Maggio, 6 - IT01661140341 + Montechiarugolo 43022 + IT 00-6A-5E (hex) BroadMaster Biotech Corp 100000-1FFFFF (base 16) BroadMaster Biotech Corp @@ -36992,35 +37061,23 @@ FC-E4-98 (hex) E Haute Intelligent Technology Co., Ltd Taoyuan Select State 32057 TW -00-6A-5E (hex) Annapurna labs -900000-9FFFFF (base 16) Annapurna labs - Matam Scientific Industries Center, Building 8.2 - Mail box 15123 Haifa 3508409 - IL - -34-B5-F3 (hex) LAUMAS Elettronica s.r.l. -400000-4FFFFF (base 16) LAUMAS Elettronica s.r.l. - via I Maggio, 6 - IT01661140341 - Montechiarugolo 43022 - IT - 34-B5-F3 (hex) Satco Europe GmbH 100000-1FFFFF (base 16) Satco Europe GmbH Waidhauserstr. 3 Vohenstrauß 92546 DE -E0-23-3B (hex) The KIE -400000-4FFFFF (base 16) The KIE - 6F, 619, 42, Changeop-ro, Sujeong-gu, Seongnam-si - Gyeonggi-do 13449 - KR +00-6A-5E (hex) Annapurna labs +900000-9FFFFF (base 16) Annapurna labs + Matam Scientific Industries Center, Building 8.2 + Mail box 15123 Haifa 3508409 + IL -E0-23-3B (hex) HANET TECHNOLOGY -900000-9FFFFF (base 16) HANET TECHNOLOGY - 13th Floor, G-Group Tower Building, No. 5 Nguyen Thi Due, Yen Hoa Ward - HANOI 70000 - VN +00-6A-5E (hex) TRULY ELECTRONICS MFG.,LTD +000000-0FFFFF (base 16) TRULY ELECTRONICS MFG.,LTD + Truly industry city,shanwei guangdong,P.R.C + shanwei guangdong 516600 + CN 48-08-EB (hex) Guangdong Three Link Technology Co., Ltd 200000-2FFFFF (base 16) Guangdong Three Link Technology Co., Ltd @@ -37034,6 +37091,18 @@ E0-23-3B (hex) HANET TECHNOLOGY Velka Lomnica Presov 05952 SK +E0-23-3B (hex) HANET TECHNOLOGY +900000-9FFFFF (base 16) HANET TECHNOLOGY + 13th Floor, G-Group Tower Building, No. 5 Nguyen Thi Due, Yen Hoa Ward + HANOI 70000 + VN + +E0-23-3B (hex) The KIE +400000-4FFFFF (base 16) The KIE + 6F, 619, 42, Changeop-ro, Sujeong-gu, Seongnam-si + Gyeonggi-do 13449 + KR + 50-FA-CB (hex) ZENOPIX TEKNOLOJI SAN VE TIC LTD STI 700000-7FFFFF (base 16) ZENOPIX TEKNOLOJI SAN VE TIC LTD STI AKADEMI MAH. GURBULUT SK. S.U.TEKNOLOJI GELISTIRME BOLGESI KONYA TEKNOKENT NO:67 SELCUKLU @@ -37064,32 +37133,32 @@ A00000-AFFFFF (base 16) AUO DISPLAY PLUS CORPORATION Shenzhen Guangdong 518000 CN -78-13-05 (hex) Shanghai Kanghai Information System CO.,LTD. -900000-9FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. +C4-98-94 (hex) Shanghai Kanghai Information System CO.,LTD. +700000-7FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN -58-47-CA (hex) Shanghai Kanghai Information System CO.,LTD. -600000-6FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. +34-46-63 (hex) Shanghai Kanghai Information System CO.,LTD. +900000-9FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN -C4-98-94 (hex) Shanghai Kanghai Information System CO.,LTD. -700000-7FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. +04-A1-6F (hex) Shanghai Kanghai Information System CO.,LTD. +000000-0FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN -34-46-63 (hex) Shanghai Kanghai Information System CO.,LTD. +78-13-05 (hex) Shanghai Kanghai Information System CO.,LTD. 900000-9FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN -04-A1-6F (hex) Shanghai Kanghai Information System CO.,LTD. -000000-0FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. +58-47-CA (hex) Shanghai Kanghai Information System CO.,LTD. +600000-6FFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District ShenZhen GuangDong 518000 CN @@ -37118,18 +37187,18 @@ B00000-BFFFFF (base 16) Shanghai Kanghai Information System CO.,LTD. Taichung 40768 TW -24-A1-0D (hex) Luxvisions lnnovation TechnologyCorp.Limited -800000-8FFFFF (base 16) Luxvisions lnnovation TechnologyCorp.Limited - No. 69, Yongsheng Road, Huangpu District, Guangzhou - Guangzhou Guangdong Province 510000 - CN - 24-A1-0D (hex) Dongguan Taijie Electronics Technology Co.,Ltd 400000-4FFFFF (base 16) Dongguan Taijie Electronics Technology Co.,Ltd 5F, 6# Building, Sanjia Industrial Park, Dongkeng Town Dongguan Guangdong 523000 CN +24-A1-0D (hex) Luxvisions lnnovation TechnologyCorp.Limited +800000-8FFFFF (base 16) Luxvisions lnnovation TechnologyCorp.Limited + No. 69, Yongsheng Road, Huangpu District, Guangzhou + Guangzhou Guangdong Province 510000 + CN + F0-40-AF (hex) SIEMENS AG A00000-AFFFFF (base 16) SIEMENS AG Oestl. Rheinbrueckenstr.50 @@ -37196,18 +37265,18 @@ B00000-BFFFFF (base 16) 대한전력전자 Colne Lancashire BB8 8LJ GB -20-2B-DA (hex) Chongqing Ruishixing Technology Co., Ltd -700000-7FFFFF (base 16) Chongqing Ruishixing Technology Co., Ltd - No. 1, 5th Floor, Unit 2, Building 1, Jinqian Port Industrial Park, No. 808, Haier Road, Tieshanping Street, - Jiangbei District Chongqing 400000 - CN - 0C-BF-B4 (hex) Shenzhen PengBrain Technology Co.,Ltd E00000-EFFFFF (base 16) Shenzhen PengBrain Technology Co.,Ltd B1014, Building 2, Chuangwei Innovation Valley, No. 8, Tangtou 1st Road, Tangtou Community, Shiyan Street, Bao'an District, Shenzhen Guangdong 518000 CN +20-2B-DA (hex) Chongqing Ruishixing Technology Co., Ltd +700000-7FFFFF (base 16) Chongqing Ruishixing Technology Co., Ltd + No. 1, 5th Floor, Unit 2, Building 1, Jinqian Port Industrial Park, No. 808, Haier Road, Tieshanping Street, + Jiangbei District Chongqing 400000 + CN + 20-2B-DA (hex) BRUSH ELECTRICAL MACHINES LTD 800000-8FFFFF (base 16) BRUSH ELECTRICAL MACHINES LTD Powerhouse, Excelsior Rd @@ -37226,11 +37295,11 @@ E00000-EFFFFF (base 16) Shenzhen PengBrain Technology Co.,Ltd Schenkon LU 6214 CH -20-2B-DA (hex) Plato System Development B.V. -500000-5FFFFF (base 16) Plato System Development B.V. - Amerikalaan 59 - Maastricht-Airport 6199 AE - NL +20-2B-DA (hex) Transit Solutions, LLC. +D00000-DFFFFF (base 16) Transit Solutions, LLC. + 114 West Grandview Avenue + Zelienople PA 16063 + US 20-2B-DA (hex) Teletek Electronics JSC 400000-4FFFFF (base 16) Teletek Electronics JSC @@ -37238,11 +37307,17 @@ E00000-EFFFFF (base 16) Shenzhen PengBrain Technology Co.,Ltd Sofia Sofia 1220 BG -20-2B-DA (hex) Transit Solutions, LLC. -D00000-DFFFFF (base 16) Transit Solutions, LLC. - 114 West Grandview Avenue - Zelienople PA 16063 - US +20-2B-DA (hex) Plato System Development B.V. +500000-5FFFFF (base 16) Plato System Development B.V. + Amerikalaan 59 + Maastricht-Airport 6199 AE + NL + +58-76-07 (hex) BOE Technology Group Co., Ltd. +C00000-CFFFFF (base 16) BOE Technology Group Co., Ltd. + No.12 Xihuanzhong RD, BDA + Beijing Beijing 100176 + CN 5C-5C-75 (hex) youyeetoo 200000-2FFFFF (base 16) youyeetoo @@ -37250,11 +37325,11 @@ D00000-DFFFFF (base 16) Transit Solutions, LLC. Shenzhen Guangdong 518100 CN -58-76-07 (hex) BOE Technology Group Co., Ltd. -C00000-CFFFFF (base 16) BOE Technology Group Co., Ltd. - No.12 Xihuanzhong RD, BDA - Beijing Beijing 100176 - CN +5C-5C-75 (hex) TECTOY S.A +100000-1FFFFF (base 16) TECTOY S.A + Avenida Ministro Mário Andreazza, nº 4120, CEP 69075 - 830 - Manaus / AM – Brasil, CNPJ: 22.770.366/0001-82 + Manaus Manaus 69075 - 830 + BR 58-76-07 (hex) INP Technologies Ltd A00000-AFFFFF (base 16) INP Technologies Ltd @@ -37274,12 +37349,6 @@ C00000-CFFFFF (base 16) Siemens Sensors & Communication Ltd. Dalian Liaoning 116023 CN -5C-5C-75 (hex) TECTOY S.A -100000-1FFFFF (base 16) TECTOY S.A - Avenida Ministro Mário Andreazza, nº 4120, CEP 69075 - 830 - Manaus / AM – Brasil, CNPJ: 22.770.366/0001-82 - Manaus Manaus 69075 - 830 - BR - 5C-5C-75 (hex) Ebet Systems 600000-6FFFFF (base 16) Ebet Systems 150 George St @@ -37298,17 +37367,23 @@ A00000-AFFFFF (base 16) Gateview Technologies Hsinchu County 302 TW +7C-BC-84 (hex) AUMOVIO France S.A.S. +400000-4FFFFF (base 16) AUMOVIO France S.A.S. + 1 AVENUE PAUL OURLIAC + TOULOUSE 31100 + FR + 58-AD-08 (hex) NEOiD 000000-0FFFFF (base 16) NEOiD Rua Germano Torres, 166 - CJ8, Carmo Belo Horizonte MG 30310-040 BR -7C-BC-84 (hex) AUMOVIO France S.A.S. -400000-4FFFFF (base 16) AUMOVIO France S.A.S. - 1 AVENUE PAUL OURLIAC - TOULOUSE 31100 - FR +B4-AB-F3 (hex) FrontGrade Technologies +700000-7FFFFF (base 16) FrontGrade Technologies + 2815 Newby Road SW + Huntsville AL 35805 + US 58-AD-08 (hex) Vanguard Protex Global 500000-5FFFFF (base 16) Vanguard Protex Global @@ -37316,17 +37391,29 @@ A00000-AFFFFF (base 16) Gateview Technologies Oldsmar FL 34677 US +60-15-9F (hex) MICRO-TEX PTE.LTD. +400000-4FFFFF (base 16) MICRO-TEX PTE.LTD. + 131B LORONG 1 TOA PAYOH, #10-544 + TOA PAYOH CREST 312131 + SG + B4-AB-F3 (hex) Shenzhen Quanzhixin Information Technology Co.,Ltd 500000-5FFFFF (base 16) Shenzhen Quanzhixin Information Technology Co.,Ltd Jinhuanyu Building,Xixiang street,Bao'an District Shenzhen Guangdong 518100 CN -B4-AB-F3 (hex) FrontGrade Technologies -700000-7FFFFF (base 16) FrontGrade Technologies - 2815 Newby Road SW - Huntsville AL 35805 - US +60-15-9F (hex) Voxai Technology Co.,Ltd. +200000-2FFFFF (base 16) Voxai Technology Co.,Ltd. + 1211 Dongfangkejidasha + Shenzhen Guangdong 518040 + CN + +60-15-9F (hex) yst +000000-0FFFFF (base 16) yst + Um al ramuol,Al fattan skytower + Dubai 123200 + AE 60-15-9F (hex) Hubei HanRui Jing Automotive Intelligent System Co.,Ltd 300000-3FFFFF (base 16) Hubei HanRui Jing Automotive Intelligent System Co.,Ltd @@ -37340,29 +37427,23 @@ B4-AB-F3 (hex) FrontGrade Technologies Beijing Beijing 101499 CN -60-15-9F (hex) Voxai Technology Co.,Ltd. -200000-2FFFFF (base 16) Voxai Technology Co.,Ltd. - 1211 Dongfangkejidasha - Shenzhen Guangdong 518040 +80-77-86 (hex) Demeas +300000-3FFFFF (base 16) Demeas + 2106, 21st Floor, Building D, Tsinghua Science Park, Keji 2nd Road, Hi-Tech Zone + xi'an shaanxi 710000 CN -60-15-9F (hex) yst -000000-0FFFFF (base 16) yst - Um al ramuol,Al fattan skytower - Dubai 123200 - AE - 60-15-9F (hex) Critical Loop 700000-7FFFFF (base 16) Critical Loop 4150 Donald Douglas Drive Long Beach CA 90808 US -60-15-9F (hex) MICRO-TEX PTE.LTD. -400000-4FFFFF (base 16) MICRO-TEX PTE.LTD. - 131B LORONG 1 TOA PAYOH, #10-544 - TOA PAYOH CREST 312131 - SG +80-77-86 (hex) Arc networks pvt ltd +E00000-EFFFFF (base 16) Arc networks pvt ltd + shop 10, Bhoomi Acres, phase 2, Wagbil road, hiranandani estate, thane west + Thane Maharashtra 400615 + IN 80-77-86 (hex) Cornerstone Technology (Shenzhen) Limited C00000-CFFFFF (base 16) Cornerstone Technology (Shenzhen) Limited @@ -37376,24 +37457,18 @@ C00000-CFFFFF (base 16) Cornerstone Technology (Shenzhen) Limited AHMEDABAD Gujarat 380054 IN -80-77-86 (hex) Demeas -300000-3FFFFF (base 16) Demeas - 2106, 21st Floor, Building D, Tsinghua Science Park, Keji 2nd Road, Hi-Tech Zone - xi'an shaanxi 710000 - CN - -80-77-86 (hex) Arc networks pvt ltd -E00000-EFFFFF (base 16) Arc networks pvt ltd - shop 10, Bhoomi Acres, phase 2, Wagbil road, hiranandani estate, thane west - Thane Maharashtra 400615 - IN - 08-3C-03 (hex) INNOS TECHNOLOGIES INC. 900000-9FFFFF (base 16) INNOS TECHNOLOGIES INC. 4711 Yonge Street, 10th Floor, North York, Canada North York Ontario M2N 6K8 CA +34-D7-F5 (hex) AIoTrust +800000-8FFFFF (base 16) AIoTrust + 66 Bd Niels Bohr + Villeurbanne Rhone 69100 + FR + 34-D7-F5 (hex) Lucy Electric Manufacturing and Technologies India Pvt Ltd C00000-CFFFFF (base 16) Lucy Electric Manufacturing and Technologies India Pvt Ltd Survey Number 26-30, Noorpura, Baska, Ta. HalolDist. Panchamahal @@ -37406,42 +37481,36 @@ C00000-CFFFFF (base 16) Lucy Electric Manufacturing and Technologies India Xiamen Fujian 350000 CN -34-D7-F5 (hex) AIoTrust -800000-8FFFFF (base 16) AIoTrust - 66 Bd Niels Bohr - Villeurbanne Rhone 69100 - FR - 6C-47-80 (hex) ZVK GmbH C00000-CFFFFF (base 16) ZVK GmbH Technologiecampus 2 Teisnach 94244 DE -6C-47-80 (hex) CARDIO SISTEMAS COMERCIAL E INDUSTRIAL LTDA -B00000-BFFFFF (base 16) CARDIO SISTEMAS COMERCIAL E INDUSTRIAL LTDA - AVENIDA PAULISTA, 509 ANDAR 1, 3, 21 E 22, CONJUNTOS 308, 309 E 310 - SAO PAULO SAO PAULO 01311910 - BR - -18-C3-E4 (hex) Duress Pty Ltd -200000-2FFFFF (base 16) Duress Pty Ltd - Floor 8, Unit 1/420 St Kilda Rd - Melbourne Victoria 3004 - AU - 18-C3-E4 (hex) Cascadia Motion LLC B00000-BFFFFF (base 16) Cascadia Motion LLC 7929 SW Burns Way, Ste F WILSONVILLE OR 97070 US +6C-47-80 (hex) CARDIO SISTEMAS COMERCIAL E INDUSTRIAL LTDA +B00000-BFFFFF (base 16) CARDIO SISTEMAS COMERCIAL E INDUSTRIAL LTDA + AVENIDA PAULISTA, 509 ANDAR 1, 3, 21 E 22, CONJUNTOS 308, 309 E 310 + SAO PAULO SAO PAULO 01311910 + BR + 6C-47-80 (hex) Monnit Corporation 500000-5FFFFF (base 16) Monnit Corporation 3400 S West Temple S Salt Lake UT 84115 US +18-C3-E4 (hex) Duress Pty Ltd +200000-2FFFFF (base 16) Duress Pty Ltd + Floor 8, Unit 1/420 St Kilda Rd + Melbourne Victoria 3004 + AU + 18-C3-E4 (hex) Proximus sp. z .o.o. 300000-3FFFFF (base 16) Proximus sp. z .o.o. ul. Piatkowska 163 @@ -37471,3 +37540,21 @@ C4-82-72 (hex) Melecs EWS GmbH GZO-Technologiestrasse 1 Siegendorf 7011 AT + +38-B1-4E (hex) Amissiontech Co., Ltd +A00000-AFFFFF (base 16) Amissiontech Co., Ltd + No.8 Huafu Rd, Shangsha, Chang’an Town + Dongguan Guangdong 523843 + CN + +38-B1-4E (hex) Brookhaven National Laboratory +500000-5FFFFF (base 16) Brookhaven National Laboratory + 741 Brookhaven Ave + Upton NY 11973 + US + +20-B3-7F (hex) Aina Computers ,Inc. +200000-2FFFFF (base 16) Aina Computers ,Inc. + 16192 Coastal Highway + Lewes DE 19958 + US diff --git a/hwdb.d/ma-small.txt b/hwdb.d/ma-small.txt index 8c5e4c6f693a1..e6bc2f4e29501 100644 --- a/hwdb.d/ma-small.txt +++ b/hwdb.d/ma-small.txt @@ -7787,6 +7787,12 @@ DD2000-DD2FFF (base 16) SHIELD-CCTV CO.,LTD. Poway CA 92064 US +8C-1F-64 (hex) Taicang T&W Electronics +FFB000-FFBFFF (base 16) Taicang T&W Electronics + 89# Jiang Nan RD + Suzhou Jiangsu 215412 + CN + 8C-1F-64 (hex) Shenzhen zhushida Technology lnformation Co.,Ltd A5D000-A5DFFF (base 16) Shenzhen zhushida Technology lnformation Co.,Ltd 701, Building D, Zone B, Junxing Industrial Zone, Junxing Industrial Zone, Oyster Road, Zhancheng Community, Fuhai Street, @@ -7799,11 +7805,11 @@ A5D000-A5DFFF (base 16) Shenzhen zhushida Technology lnformation Co.,Ltd SHENZHEN Bao'an District 518000 CN -8C-1F-64 (hex) Taicang T&W Electronics -FFB000-FFBFFF (base 16) Taicang T&W Electronics - 89# Jiang Nan RD - Suzhou Jiangsu 215412 - CN +8C-1F-64 (hex) Oriux +20A000-20AFFF (base 16) Oriux + 5825 N. Sam Houston Pkwy WSuite 220 Houston TX 77086 United S + Houston TX 77086 + US 8C-1F-64 (hex) Breas Medical AB 5A1000-5A1FFF (base 16) Breas Medical AB @@ -7811,36 +7817,18 @@ FFB000-FFBFFF (base 16) Taicang T&W Electronics Mölnlycke SE-435 33 SE -8C-1F-64 (hex) Oriux -20A000-20AFFF (base 16) Oriux - 5825 N. Sam Houston Pkwy WSuite 220 Houston TX 77086 United S - Houston TX 77086 - US - 8C-1F-64 (hex) ENBIK Technology Co., Ltd 85A000-85AFFF (base 16) ENBIK Technology Co., Ltd 2F., No.542, Sec. 1, Minsheng N. Rd., Taoyuan City Taoyuan City 333016 TW -8C-1F-64 (hex) ibg Prüfcomputer GmbH -627000-627FFF (base 16) ibg Prüfcomputer GmbH - Pretzfelder Str. 27 - Ebermannstadt 91320 - DE - 8C-1F-64 (hex) Amazon Robotics MTAC Matrix NPI DC4000-DC4FFF (base 16) Amazon Robotics MTAC Matrix NPI 50 Otis Street Westborough MA 01581 US -8C-1F-64 (hex) Potter Electric Signal Co. LLC -316000-316FFF (base 16) Potter Electric Signal Co. LLC - 1609 Park 370 Place - Hazelwood MO 63042 - US - 8C-1F-64 (hex) Eiden Co.,Ltd. 3D7000-3D7FFF (base 16) Eiden Co.,Ltd. 2-7-1 kurigi,asao-ku,kawasaki-shi @@ -7853,6 +7841,18 @@ C09000-C09FFF (base 16) S.E.I. CO.,LTD. Izunokuni Shizuoka 4102133 JP +8C-1F-64 (hex) ibg Prüfcomputer GmbH +627000-627FFF (base 16) ibg Prüfcomputer GmbH + Pretzfelder Str. 27 + Ebermannstadt 91320 + DE + +8C-1F-64 (hex) Potter Electric Signal Co. LLC +316000-316FFF (base 16) Potter Electric Signal Co. LLC + 1609 Park 370 Place + Hazelwood MO 63042 + US + 8C-1F-64 (hex) Boon Arthur Engineering Pte Ltd D8A000-D8AFFF (base 16) Boon Arthur Engineering Pte Ltd 629 Aljunied Road #06-06 Cititech Industrial Building @@ -7878,31 +7878,31 @@ C74000-C74FFF (base 16) Nippon Techno Lab Inc JP 70-B3-D5 (hex) Aplex Technology Inc. -986000-986FFF (base 16) Aplex Technology Inc. - 15F-1, No.186, Jian Yi Road +9B1000-9B1FFF (base 16) Aplex Technology Inc. + 15F-1, No.186, Jian Yi Road Zhonghe District New Taipei City 235 - TW 70-B3-D5 (hex) Aplex Technology Inc. -605000-605FFF (base 16) Aplex Technology Inc. +F00000-F00FFF (base 16) Aplex Technology Inc. 15F-1, No.186, Jian Yi Road Zhonghe District New Taipei City 235 - TW 70-B3-D5 (hex) Aplex Technology Inc. -F00000-F00FFF (base 16) Aplex Technology Inc. - 15F-1, No.186, Jian Yi Road +986000-986FFF (base 16) Aplex Technology Inc. + 15F-1, No.186, Jian Yi Road Zhonghe District New Taipei City 235 - TW 70-B3-D5 (hex) Aplex Technology Inc. -2EE000-2EEFFF (base 16) Aplex Technology Inc. +605000-605FFF (base 16) Aplex Technology Inc. 15F-1, No.186, Jian Yi Road Zhonghe District New Taipei City 235 - TW 70-B3-D5 (hex) Aplex Technology Inc. -9B1000-9B1FFF (base 16) Aplex Technology Inc. +2EE000-2EEFFF (base 16) Aplex Technology Inc. 15F-1, No.186, Jian Yi Road Zhonghe District New Taipei City 235 - TW @@ -7931,23 +7931,17 @@ CCF000-CCFFFF (base 16) Tiptop Platform P. Ltd Jaipur Rajasthan 302001 IN -8C-1F-64 (hex) Taesung Media -8DB000-8DBFFF (base 16) Taesung Media - Room 20, 306, Dalseo-daero 109-gil - Dalseo-gu Daegu 42709 - KR - 8C-1F-64 (hex) Thermaco Incorporated F20000-F20FFF (base 16) Thermaco Incorporated 646 GREENSBORO ST ASHEBORO NC 27203-4739 US -8C-1F-64 (hex) Kite Rise Technologies GmbH -508000-508FFF (base 16) Kite Rise Technologies GmbH - Kaerntner Strasse 355B/1.OG - Graz 8054 - AT +8C-1F-64 (hex) Taesung Media +8DB000-8DBFFF (base 16) Taesung Media + Room 20, 306, Dalseo-daero 109-gil + Dalseo-gu Daegu 42709 + KR 8C-1F-64 (hex) Omnilink Tecnologia S/A 023000-023FFF (base 16) Omnilink Tecnologia S/A @@ -7955,11 +7949,11 @@ F20000-F20FFF (base 16) Thermaco Incorporated Barueri SP 06455-020 BR -8C-1F-64 (hex) GETQCALL -37A000-37AFFF (base 16) GETQCALL - 23 Lorraine Drive - North York ON M2N6Z6 - CA +8C-1F-64 (hex) Kite Rise Technologies GmbH +508000-508FFF (base 16) Kite Rise Technologies GmbH + Kaerntner Strasse 355B/1.OG + Graz 8054 + AT 8C-1F-64 (hex) Vinfast Trading and Production JSC F80000-F80FFF (base 16) Vinfast Trading and Production JSC @@ -7967,12 +7961,24 @@ F80000-F80FFF (base 16) Vinfast Trading and Production JSC Hai Phong Hai Phong 180000 VN +8C-1F-64 (hex) GETQCALL +37A000-37AFFF (base 16) GETQCALL + 23 Lorraine Drive + North York ON M2N6Z6 + CA + 8C-1F-64 (hex) YDIIT Co., Ltd. 1FA000-1FAFFF (base 16) YDIIT Co., Ltd. #3010, U-Tower, 120, Heungdeokjungang-ro, Giheung-gu, Yongin Gyeonggi 16950 KR +8C-1F-64 (hex) AUREKA SMART LIVING W.L.L +689000-689FFF (base 16) AUREKA SMART LIVING W.L.L + Office 22, Bldg 288C, Avenue 16, Hidd + Hidd 0111 + BH + 8C-1F-64 (hex) Embedded Designs Services India Pvt Ltd CD7000-CD7FFF (base 16) Embedded Designs Services India Pvt Ltd Unit No. 1119-20, 11th Floor, Tower 5, 12th Avenue, RPS Infinia, Faridabad, Haryana @@ -7985,11 +7991,11 @@ CD7000-CD7FFF (base 16) Embedded Designs Services India Pvt Ltd 수원 Gyeonggi-do 16690 KR -8C-1F-64 (hex) AUREKA SMART LIVING W.L.L -689000-689FFF (base 16) AUREKA SMART LIVING W.L.L - Office 22, Bldg 288C, Avenue 16, Hidd - Hidd 0111 - BH +8C-1F-64 (hex) Sichuan ZhikongLingxin Technology Co., Ltd. +17A000-17AFFF (base 16) Sichuan ZhikongLingxin Technology Co., Ltd. + No. 22, 5th Floor, Unit 1, Building 3, No.666 Guandong 1st Street, Chengdu Hightech Zone, China (Sichuan) Pilot Free Trade Zone. + Chengdu Sichuan 610095 + CN 8C-1F-64 (hex) Beijing Dangong Technology Co., Ltd DB8000-DB8FFF (base 16) Beijing Dangong Technology Co., Ltd @@ -8003,11 +8009,11 @@ CC9000-CC9FFF (base 16) Benchmark Electronics BV Almelo Overijssel 7602 EA NL -8C-1F-64 (hex) Sichuan ZhikongLingxin Technology Co., Ltd. -17A000-17AFFF (base 16) Sichuan ZhikongLingxin Technology Co., Ltd. - No. 22, 5th Floor, Unit 1, Building 3, No.666 Guandong 1st Street, Chengdu Hightech Zone, China (Sichuan) Pilot Free Trade Zone. - Chengdu Sichuan 610095 - CN +8C-1F-64 (hex) Raspberry Pi (Trading) Ltd +34A000-34AFFF (base 16) Raspberry Pi (Trading) Ltd + Maurice Wilkes Building, St Johns Innovation Park + Cambridge Cambridgeshire CB4 0DS + GB 8C-1F-64 (hex) WHITEBOX TECHNOLOGY HONG KONG LTD E2A000-E2AFFF (base 16) WHITEBOX TECHNOLOGY HONG KONG LTD @@ -8015,12 +8021,6 @@ E2A000-E2AFFF (base 16) WHITEBOX TECHNOLOGY HONG KONG LTD Wan Chai Hong Kong Hong Kong HK -8C-1F-64 (hex) Raspberry Pi (Trading) Ltd -34A000-34AFFF (base 16) Raspberry Pi (Trading) Ltd - Maurice Wilkes Building, St Johns Innovation Park - Cambridge Cambridgeshire CB4 0DS - GB - 8C-1F-64 (hex) Invader Technologies Pvt Ltd 859000-859FFF (base 16) Invader Technologies Pvt Ltd 4th Floor, Landmark TowerPlot No -2, Ashok Marg, Silokhra, South City Part 1 @@ -8039,36 +8039,36 @@ C70000-C70FFF (base 16) INVIXIUM ACCESS INC Toronto Ontario M2H 3R1 CA -8C-1F-64 (hex) Potter Electric Signal Co. LLC -8C8000-8C8FFF (base 16) Potter Electric Signal Co. LLC - 1609 Park 370 Place - Hazelwood MO 63042 - US - 8C-1F-64 (hex) Televic Rail GmbH 9D1000-9D1FFF (base 16) Televic Rail GmbH Teltowkanalstr.1 Berlin 12247 DE +8C-1F-64 (hex) Potter Electric Signal Co. LLC +8C8000-8C8FFF (base 16) Potter Electric Signal Co. LLC + 1609 Park 370 Place + Hazelwood MO 63042 + US + 8C-1F-64 (hex) Kuntu Technology Limited Liability Compant 7CC000-7CCFFF (base 16) Kuntu Technology Limited Liability Compant Presnensky vet municipal district,Presnenskaya emb., 12,room. 10/45 Moscow Select State 123112 RU -8C-1F-64 (hex) VMA GmbH -783000-783FFF (base 16) VMA GmbH - Graefinauer Strasse 2 - Ilmenau 98693 - DE - 8C-1F-64 (hex) VORTIX NETWORKS 96F000-96FFFF (base 16) VORTIX NETWORKS 3230 E Imperial Hwy, Suite 300 Brea CA 92821 US +8C-1F-64 (hex) VMA GmbH +783000-783FFF (base 16) VMA GmbH + Graefinauer Strasse 2 + Ilmenau 98693 + DE + 8C-1F-64 (hex) 浙江红谱科技有限公司 6DA000-6DAFFF (base 16) 浙江红谱科技有限公司 紫宣路18号西投绿城·浙谷深蓝中心7号楼7楼红谱科技 @@ -8093,11 +8093,11 @@ DB4000-DB4FFF (base 16) MB connect line GmbH Anif Salzburg 5081 AT -8C-1F-64 (hex) Abbott Diagnostics Technologies AS -7F6000-7F6FFF (base 16) Abbott Diagnostics Technologies AS - P. O. Box 6863 Rodeløkka - Oslo Oslo 0504 - NO +8C-1F-64 (hex) TECHTUIT CO.,LTD. +2D6000-2D6FFF (base 16) TECHTUIT CO.,LTD. + 1-4-28,MITA,26F MITA KOKUSAIBLDG, + MINATO-KU TOKYO 108-0073 + JP 8C-1F-64 (hex) SEGRON Automation, s.r.o. DC1000-DC1FFF (base 16) SEGRON Automation, s.r.o. @@ -8105,17 +8105,11 @@ DC1000-DC1FFF (base 16) SEGRON Automation, s.r.o. Bratislava 82101 SK -8C-1F-64 (hex) TECHTUIT CO.,LTD. -2D6000-2D6FFF (base 16) TECHTUIT CO.,LTD. - 1-4-28,MITA,26F MITA KOKUSAIBLDG, - MINATO-KU TOKYO 108-0073 - JP - -8C-1F-64 (hex) Zengar Institute Inc -710000-710FFF (base 16) Zengar Institute Inc - 1007 Fort St, 4th FL - Victoria BC V8V 3K5 - CA +8C-1F-64 (hex) Abbott Diagnostics Technologies AS +7F6000-7F6FFF (base 16) Abbott Diagnostics Technologies AS + P. O. Box 6863 Rodeløkka + Oslo Oslo 0504 + NO 8C-1F-64 (hex) RESMED PTY LTD 3C7000-3C7FFF (base 16) RESMED PTY LTD @@ -8123,6 +8117,12 @@ DC1000-DC1FFF (base 16) SEGRON Automation, s.r.o. NSW 2153 AT +8C-1F-64 (hex) Zengar Institute Inc +710000-710FFF (base 16) Zengar Institute Inc + 1007 Fort St, 4th FL + Victoria BC V8V 3K5 + CA + 8C-1F-64 (hex) Creating Cloud Technology Co.,Ltd.,CT-CLOUD C9D000-C9DFFF (base 16) Creating Cloud Technology Co.,Ltd.,CT-CLOUD Rm. 3, 16F., No. 925, Sec. 4, Taiwan Blvd., Xitun Dist. @@ -8135,12 +8135,6 @@ C9D000-C9DFFF (base 16) Creating Cloud Technology Co.,Ltd.,CT-CLOUD Gent Oost-Vlaanderen 9000 BE -8C-1F-64 (hex) ZKTECO EUROPE -734000-734FFF (base 16) ZKTECO EUROPE - CARRETERA DE FUENCARRAL 44 - ALCOBENDAS MADRID 28108 - ES - 8C-1F-64 (hex) Network Rail 18A000-18AFFF (base 16) Network Rail The Quadrant, Elder Gate @@ -8153,6 +8147,12 @@ C9D000-C9DFFF (base 16) Creating Cloud Technology Co.,Ltd.,CT-CLOUD Neusaess Bayern 85356 DE +8C-1F-64 (hex) ZKTECO EUROPE +734000-734FFF (base 16) ZKTECO EUROPE + CARRETERA DE FUENCARRAL 44 + ALCOBENDAS MADRID 28108 + ES + 8C-1F-64 (hex) inmediQ GmbH 6C4000-6C4FFF (base 16) inmediQ GmbH Gebrüder-Freitag-Str. 1 @@ -8171,23 +8171,17 @@ C9D000-C9DFFF (base 16) Creating Cloud Technology Co.,Ltd.,CT-CLOUD Hanoi 151831 VN -8C-1F-64 (hex) PAL Inc. -60C000-60CFFF (base 16) PAL Inc. - 2217-2 Hayashicho - Takamatsu Kagawa 7610301 - JP - 8C-1F-64 (hex) Watthour Engineering Co., Inc. B0E000-B0EFFF (base 16) Watthour Engineering Co., Inc. 333 Crosspark Dr Pearl MS 39208 US -8C-1F-64 (hex) LaserLinc, Inc. -04D000-04DFFF (base 16) LaserLinc, Inc. - 777 Zapata Drive - Fairborn OH 45324 - US +8C-1F-64 (hex) PAL Inc. +60C000-60CFFF (base 16) PAL Inc. + 2217-2 Hayashicho + Takamatsu Kagawa 7610301 + JP 8C-1F-64 (hex) Xi'an Singularity Energy Co., Ltd. 2AA000-2AAFFF (base 16) Xi'an Singularity Energy Co., Ltd. @@ -8201,24 +8195,30 @@ B0E000-B0EFFF (base 16) Watthour Engineering Co., Inc. Suzhou City Jiangsu 215000 CN +8C-1F-64 (hex) LaserLinc, Inc. +04D000-04DFFF (base 16) LaserLinc, Inc. + 777 Zapata Drive + Fairborn OH 45324 + US + 8C-1F-64 (hex) Meisol Co., Ltd. 827000-827FFF (base 16) Meisol Co., Ltd. Yamato Jisho Building 1006, 74-1 Yamashitacho, Naka-ku Yokohama Kanagawa Prefecture 2310023 JP -8C-1F-64 (hex) RoboCore Tecnologia -8C9000-8C9FFF (base 16) RoboCore Tecnologia - Av Honorio Alvares Penteado, 97 - Galpao 77 - Santana de Parnaiba SP 06543-320 - BR - 8C-1F-64 (hex) Becton Dickinson 597000-597FFF (base 16) Becton Dickinson 7 Loveton Circle Sparks MD 21152 US +8C-1F-64 (hex) RoboCore Tecnologia +8C9000-8C9FFF (base 16) RoboCore Tecnologia + Av Honorio Alvares Penteado, 97 - Galpao 77 + Santana de Parnaiba SP 06543-320 + BR + 8C-1F-64 (hex) Corespan Systems 584000-584FFF (base 16) Corespan Systems 200 Innovative Way Suite 1360 @@ -8237,6 +8237,18 @@ DA0000-DA0FFF (base 16) Sensata Technologies Inc. TAICHUNG 40841 TW +8C-1F-64 (hex) JES Electronic Systems Private Limited +976000-976FFF (base 16) JES Electronic Systems Private Limited + 9/52/5 Kirti Nagar, near industrial area, New Delhi 110015 + New Delhi 110015 + IN + +8C-1F-64 (hex) Starts Facility Service Co.,Ltd +AC6000-AC6FFF (base 16) Starts Facility Service Co.,Ltd + 3-1-8 Nihonbashi + Chuo-ku Tokyo 103-0027 + JP + 8C-1F-64 (hex) Jacobs Technology, Inc. A98000-A98FFF (base 16) Jacobs Technology, Inc. 7765 Old Telegraph Road @@ -15995,18 +16007,18 @@ F03000-F03FFF (base 16) Faust ApS Helsinki 00150 FI -8C-1F-64 (hex) NARI TECH Co., Ltd -888000-888FFF (base 16) NARI TECH Co., Ltd - 947, Hanam-daero - Hanam-si Gyeonggi-do 12982 - KR - 8C-1F-64 (hex) Mitsubishi Electric System & Service Co., Ltd. E05000-E05FFF (base 16) Mitsubishi Electric System & Service Co., Ltd. 1-26-43 Yada, Higashi-ku, Nagoya Aichi 461-0040 JP +8C-1F-64 (hex) NARI TECH Co., Ltd +888000-888FFF (base 16) NARI TECH Co., Ltd + 947, Hanam-daero + Hanam-si Gyeonggi-do 12982 + KR + 8C-1F-64 (hex) NSK Co.,Ltd. 2A3000-2A3FFF (base 16) NSK Co.,Ltd. 1-10-15 Daiko,Higashi-ku @@ -16031,36 +16043,36 @@ B41000-B41FFF (base 16) STATE GRID INTELLIGENCE TECHNOLOGY CO.,LTD. Muenster North Rhine-Westphalia 48163 DE -8C-1F-64 (hex) BK LAB -F8C000-F8CFFF (base 16) BK LAB - #1309, Daeryung Technotown 15, Simin-daero 401, Dongan-gu - Anyang-si Gyonggi-do 14057 - KR - 8C-1F-64 (hex) TECZZ LLC D95000-D95FFF (base 16) TECZZ LLC 17 Forest AvenueSuite 017 Fond Du Lac WI 54935 US +8C-1F-64 (hex) BK LAB +F8C000-F8CFFF (base 16) BK LAB + #1309, Daeryung Technotown 15, Simin-daero 401, Dongan-gu + Anyang-si Gyonggi-do 14057 + KR + 8C-1F-64 (hex) Potter Electric Signal Co. LLC 57B000-57BFFF (base 16) Potter Electric Signal Co. LLC 1609 Park 370 Place Hazelwood MO 63042 US -8C-1F-64 (hex) Potter Electric Signal Co. LLC -442000-442FFF (base 16) Potter Electric Signal Co. LLC - 1609 Park 370 Place - Hazelwood MO 63043 - US - 8C-1F-64 (hex) Potter Electric Signal Co. LLC 8FE000-8FEFFF (base 16) Potter Electric Signal Co. LLC 5757 Phantom Drive Hazelwood MO 63042 US +8C-1F-64 (hex) Potter Electric Signal Co. LLC +442000-442FFF (base 16) Potter Electric Signal Co. LLC + 1609 Park 370 Place + Hazelwood MO 63043 + US + 8C-1F-64 (hex) Eyecloud, Inc 072000-072FFF (base 16) Eyecloud, Inc 171 Branham Ln, Ste 10-243 @@ -16085,15 +16097,15 @@ D95000-D95FFF (base 16) TECZZ LLC Hangzhou 310024 CN +8C-1F-64 (hex) Private +1A8000-1A8FFF (base 16) Private + 8C-1F-64 (hex) Shenzhen Arctec Innovation Technology Co.,Ltd 199000-199FFF (base 16) Shenzhen Arctec Innovation Technology Co.,Ltd Room711-713, Yuefu Square, No.481, Fenghuang Street, Guangming Area Shenzhen Guangdong 518107 CN -8C-1F-64 (hex) Private -1A8000-1A8FFF (base 16) Private - 70-B3-D5 (hex) Aplex Technology Inc. 4B7000-4B7FFF (base 16) Aplex Technology Inc. 15F-1, No.186, Jian Yi Road @@ -16193,11 +16205,11 @@ DA4000-DA4FFF (base 16) Foenix Coding Ltd Chertsey Surrey KT16 0AW GB -8C-1F-64 (hex) Rail Telematics Corp -7C1000-7C1FFF (base 16) Rail Telematics Corp - 494 6th Ave S - Jacksonville Beach 32250 - US +8C-1F-64 (hex) YUYAMA MFG Co.,Ltd +70A000-70AFFF (base 16) YUYAMA MFG Co.,Ltd + 1-4-30 + MEISHINGUCHI,TOYONAKA OSAKA 561-0841 + JP 8C-1F-64 (hex) Vigor Electric Corp. A64000-A64FFF (base 16) Vigor Electric Corp. @@ -16205,11 +16217,11 @@ A64000-A64FFF (base 16) Vigor Electric Corp. Danshui Dist. New Taipei City 25152 TW -8C-1F-64 (hex) YUYAMA MFG Co.,Ltd -70A000-70AFFF (base 16) YUYAMA MFG Co.,Ltd - 1-4-30 - MEISHINGUCHI,TOYONAKA OSAKA 561-0841 - JP +8C-1F-64 (hex) Rail Telematics Corp +7C1000-7C1FFF (base 16) Rail Telematics Corp + 494 6th Ave S + Jacksonville Beach 32250 + US 8C-1F-64 (hex) Sonic Italia 8D2000-8D2FFF (base 16) Sonic Italia @@ -16247,12 +16259,6 @@ B30000-B30FFF (base 16) Fujian ONETHING Technology Co.,Ltd. Antwerp Antwerp 2018 BE -8C-1F-64 (hex) VINGLOOP TECHNOLOGY LTD -DFF000-DFFFFF (base 16) VINGLOOP TECHNOLOGY LTD - UNIT 910.9/F.TOWER 1 CHEUNGSHA WAN PLAZA 833 CHEUNG SHA WAN RD CHEUNG SHA WAN - Hong Kong 000000 - HK - 8C-1F-64 (hex) Inex Technologies C46000-C46FFF (base 16) Inex Technologies 155 Willowbrook Blvd., Suite 130 @@ -16265,6 +16271,12 @@ C46000-C46FFF (base 16) Inex Technologies Troy MI 48083 US +8C-1F-64 (hex) VINGLOOP TECHNOLOGY LTD +DFF000-DFFFFF (base 16) VINGLOOP TECHNOLOGY LTD + UNIT 910.9/F.TOWER 1 CHEUNGSHA WAN PLAZA 833 CHEUNG SHA WAN RD CHEUNG SHA WAN + Hong Kong 000000 + HK + 8C-1F-64 (hex) JMV BHARAT PRIVATE LIMITED 961000-961FFF (base 16) JMV BHARAT PRIVATE LIMITED W 50, SECTOR 11, NOIDA, GAUTAM BUDDHA NAGAR @@ -16289,18 +16301,18 @@ B25000-B25FFF (base 16) Thermo Fisher Scientific (Asheville) LLC Geumcheon-gu, Seoul Select State 08592 KR -8C-1F-64 (hex) TCL OPERATIONS POLSKA SP. Z O.O. -233000-233FFF (base 16) TCL OPERATIONS POLSKA SP. Z O.O. - ul. MICKIEWICZA, 31/41, 96-300, ZYRARDOW, POLAN - ZYRARDOW 96-300 - PL - 8C-1F-64 (hex) Eurotronic Technology GmbH E27000-E27FFF (base 16) Eurotronic Technology GmbH Südweg 1 Steinau 36396 DE +8C-1F-64 (hex) TCL OPERATIONS POLSKA SP. Z O.O. +233000-233FFF (base 16) TCL OPERATIONS POLSKA SP. Z O.O. + ul. MICKIEWICZA, 31/41, 96-300, ZYRARDOW, POLAN + ZYRARDOW 96-300 + PL + 8C-1F-64 (hex) Monnit Corporation A28000-A28FFF (base 16) Monnit Corporation 3400 S West Temple @@ -16337,6 +16349,12 @@ EB8000-EB8FFF (base 16) Power Electronics Espana, S.L. Guildford Surrey GU2 7RQ GB +8C-1F-64 (hex) Sentek Pty Ltd +A95000-A95FFF (base 16) Sentek Pty Ltd + 77 Magill Road + Stepney SA 5069 + AU + 8C-1F-64 (hex) FIBERNET LTD F48000-F48FFF (base 16) FIBERNET LTD 9 Hakidma st. Hi-Tech City Park, @@ -16349,12 +16367,6 @@ F48000-F48FFF (base 16) FIBERNET LTD Shanghai Shanghai 201206 CN -8C-1F-64 (hex) Sentek Pty Ltd -A95000-A95FFF (base 16) Sentek Pty Ltd - 77 Magill Road - Stepney SA 5069 - AU - 8C-1F-64 (hex) Aidhom B1E000-B1EFFF (base 16) Aidhom Avenue de la résistance 188 @@ -16409,22 +16421,22 @@ F21000-F21FFF (base 16) nanoTRONIX Computing Inc. Wilmington DE 19806 US -8C-1F-64 (hex) Fairwinds Technologies -D55000-D55FFF (base 16) Fairwinds Technologies - 6165 Guardian Gateway, Suites A-C - Aberdeen Proving Ground MD 21005 - US - 8C-1F-64 (hex) RADIC Technologies, Inc. E91000-E91FFF (base 16) RADIC Technologies, Inc. 1625 The Alameda, Suite 708 SAN JOSE 95126 US -8C-1F-64 (hex) Potter Electric Signal Co. LLC -9AD000-9ADFFF (base 16) Potter Electric Signal Co. LLC - 1609 Park 370 Place - Hazelwood MO 63043 +8C-1F-64 (hex) DEUTA Werke GmbH +02A000-02AFFF (base 16) DEUTA Werke GmbH + ET + Bergisch Gladbach NRW 51465 + DE + +8C-1F-64 (hex) Fairwinds Technologies +D55000-D55FFF (base 16) Fairwinds Technologies + 6165 Guardian Gateway, Suites A-C + Aberdeen Proving Ground MD 21005 US 8C-1F-64 (hex) Microchip Technologies Inc @@ -16433,11 +16445,11 @@ BEA000-BEAFFF (base 16) Microchip Technologies Inc Chandler AZ 85224-6199 US -8C-1F-64 (hex) DEUTA Werke GmbH -02A000-02AFFF (base 16) DEUTA Werke GmbH - ET - Bergisch Gladbach NRW 51465 - DE +8C-1F-64 (hex) Potter Electric Signal Co. LLC +9AD000-9ADFFF (base 16) Potter Electric Signal Co. LLC + 1609 Park 370 Place + Hazelwood MO 63043 + US 8C-1F-64 (hex) RC Systems 1E9000-1E9FFF (base 16) RC Systems @@ -16451,12 +16463,6 @@ AAC000-AACFFF (base 16) CDR SRL Ginestra Fiorentina Florence/Italy 50055 IT -8C-1F-64 (hex) INVENTIA Sp. z o.o. -E50000-E50FFF (base 16) INVENTIA Sp. z o.o. - Poleczki 23 - Warszawa Mazowieckie 02-822 - PL - 8C-1F-64 (hex) LimeSoft Co., Ltd. 3DF000-3DFFFF (base 16) LimeSoft Co., Ltd. 40 Imi-ro, A-816 @@ -16469,6 +16475,12 @@ E50000-E50FFF (base 16) INVENTIA Sp. z o.o. Broomfield CO 80021 US +8C-1F-64 (hex) INVENTIA Sp. z o.o. +E50000-E50FFF (base 16) INVENTIA Sp. z o.o. + Poleczki 23 + Warszawa Mazowieckie 02-822 + PL + 8C-1F-64 (hex) Engage Technologies F1E000-F1EFFF (base 16) Engage Technologies 7041 Boone Avenue North @@ -16499,18 +16511,18 @@ AD6000-AD6FFF (base 16) INTERNATIONAL SECURITY SYSTEMS W.L.L. Rotterdam 3233 KK NL -8C-1F-64 (hex) Ocarina -6A1000-6A1FFF (base 16) Ocarina - 29 Skelwith Road - London W6 9EX - GB - 8C-1F-64 (hex) SungjinDSP Co., LTD 0BA000-0BAFFF (base 16) SungjinDSP Co., LTD 810, 25 Gasan Digital 1-ro, Geumcheon-gu, Seoul (Gasan-dong, Daeryung Techno Town 17th) Geumcheon-gu Seoul 08594 KR +8C-1F-64 (hex) Ocarina +6A1000-6A1FFF (base 16) Ocarina + 29 Skelwith Road + London W6 9EX + GB + 8C-1F-64 (hex) CyberCube ApS 65C000-65CFFF (base 16) CyberCube ApS Munkehatten 1C @@ -16523,30 +16535,24 @@ AD6000-AD6FFF (base 16) INTERNATIONAL SECURITY SYSTEMS W.L.L. Charlottesville VA 22911 US -8C-1F-64 (hex) YUYAMA MFG Co.,Ltd -EAF000-EAFFFF (base 16) YUYAMA MFG Co.,Ltd - 1-4-30 - MEISHINGUCHI,TOYONAKA OSAKA 561-0841 - JP - 8C-1F-64 (hex) MB connect line GmbH 075000-075FFF (base 16) MB connect line GmbH Winnettener Strasse 6 Dinkelsbuehl Bavaria 91550 DE +8C-1F-64 (hex) YUYAMA MFG Co.,Ltd +EAF000-EAFFFF (base 16) YUYAMA MFG Co.,Ltd + 1-4-30 + MEISHINGUCHI,TOYONAKA OSAKA 561-0841 + JP + 8C-1F-64 (hex) Bright Solutions PTE LTD 6C3000-6C3FFF (base 16) Bright Solutions PTE LTD 51 Goldhill Plaza #07-10/11 Singapore 308900 SG -8C-1F-64 (hex) Sensus -052000-052FFF (base 16) Sensus - Industriestr. 16 - Ludwigshafen 67063 - DE - 8C-1F-64 (hex) AvanTimes 030000-030FFF (base 16) AvanTimes Kuipersweg 2 @@ -16559,12 +16565,21 @@ FBB000-FBBFFF (base 16) Telica Uiwang-si Gyeonggi-do 16006 KR +8C-1F-64 (hex) Sensus +052000-052FFF (base 16) Sensus + Industriestr. 16 + Ludwigshafen 67063 + DE + 8C-1F-64 (hex) vtt systems Inc. A66000-A66FFF (base 16) vtt systems Inc. 8 THE GREEN Dover DE 19901 US +8C-1F-64 (hex) Private +D26000-D26FFF (base 16) Private + 8C-1F-64 (hex) Breas Medical AB 1C5000-1C5FFF (base 16) Breas Medical AB Företagsvägen 1 @@ -16577,27 +16592,24 @@ A66000-A66FFF (base 16) vtt systems Inc. Singapore Singapore 737715 CN -8C-1F-64 (hex) Private -D26000-D26FFF (base 16) Private - 8C-1F-64 (hex) Pneumax Spa 5FE000-5FEFFF (base 16) Pneumax Spa via cascina barbellina, 10 Lurano Bergamo 24050 IT -8C-1F-64 (hex) Erba Lachema s.r.o. -BCF000-BCFFFF (base 16) Erba Lachema s.r.o. - Karasek1d - Brno 62100 - CZ - 8C-1F-64 (hex) HEITEC AG E25000-E25FFF (base 16) HEITEC AG Dr.-Otto-Leich-Str. 16 Eckental Bavaria 90542 DE +8C-1F-64 (hex) Erba Lachema s.r.o. +BCF000-BCFFFF (base 16) Erba Lachema s.r.o. + Karasek1d + Brno 62100 + CZ + 8C-1F-64 (hex) Vision Systems Safety Tech E6F000-E6FFFF (base 16) Vision Systems Safety Tech 5 Chemin de Chiradie @@ -22361,12 +22373,6 @@ D9C000-D9CFFF (base 16) Subinitial LLC South San Francisco CA 94080 US -70-B3-D5 (hex) HOERMANN GmbH -B78000-B78FFF (base 16) HOERMANN GmbH - Hauptstr. 45-47 - Kirchseeon Bavaria 85614 - DE - 70-B3-D5 (hex) Private D0A000-D0AFFF (base 16) Private @@ -24314,6 +24320,12 @@ C36000-C36FFF (base 16) ODTech Co., Ltd. Wanju_gun Jeonbuk-do 55322 KR +8C-1F-64 (hex) Tecsys do Brasil Industrial Ltda +7D0000-7D0FFF (base 16) Tecsys do Brasil Industrial Ltda + Rua Oros, 146 + Sao Jose dos Campos SP 12237150 + BR + 8C-1F-64 (hex) Inspinia Technology s.r.o. 595000-595FFF (base 16) Inspinia Technology s.r.o. Paleckeho 493 @@ -24326,23 +24338,17 @@ CB1000-CB1FFF (base 16) Xi’an Sunway Communication Co., Ltd. Road,High-Tech Zone Xi’an 710000 CN -8C-1F-64 (hex) Tecsys do Brasil Industrial Ltda -7D0000-7D0FFF (base 16) Tecsys do Brasil Industrial Ltda - Rua Oros, 146 - Sao Jose dos Campos SP 12237150 - BR - 8C-1F-64 (hex) Season Electronics Ltd 37D000-37DFFF (base 16) Season Electronics Ltd 600 Nest Business Park Havant Hampshire PO9 5TL GB -8C-1F-64 (hex) SURYA ELECTRONICS -3F2000-3F2FFF (base 16) SURYA ELECTRONICS - Plot no115 ALEAP Industrial Estate Gajularamaram village, Quthubullapur Mandal - HYDERABAD Telangana 500055 - IN +8C-1F-64 (hex) DEUTA Werke GmbH +5ED000-5EDFFF (base 16) DEUTA Werke GmbH + ET + Bergisch Gladbach NRW 51465 + DE 8C-1F-64 (hex) TelecomWadi 1F9000-1F9FFF (base 16) TelecomWadi @@ -24350,24 +24356,30 @@ CB1000-CB1FFF (base 16) Xi’an Sunway Communication Co., Ltd. Giza 3244530 EG +8C-1F-64 (hex) SURYA ELECTRONICS +3F2000-3F2FFF (base 16) SURYA ELECTRONICS + Plot no115 ALEAP Industrial Estate Gajularamaram village, Quthubullapur Mandal + HYDERABAD Telangana 500055 + IN + 8C-1F-64 (hex) Efftronics Systems (P) Ltd 063000-063FFF (base 16) Efftronics Systems (P) Ltd Plot No.4, IT Park, Auto Nagar Mangalagiri Andhra Pradesh 520010 IN +8C-1F-64 (hex) SUS Corporation +F69000-F69FFF (base 16) SUS Corporation + 6F, S-patio Bldg. 14-25 Minami-cho, Suruga-ku, + Shizuoka city, Shizuoka 422-8067 + JP + 8C-1F-64 (hex) Vantageo Private Limited 96B000-96BFFF (base 16) Vantageo Private Limited 617, Lodha Supremus II, Wagle Estate, Thane, Mumbai Maharastra 400604 IN -8C-1F-64 (hex) DEUTA Werke GmbH -5ED000-5EDFFF (base 16) DEUTA Werke GmbH - ET - Bergisch Gladbach NRW 51465 - DE - 8C-1F-64 (hex) Shenzhen Angstrom Excellence Technology Co., Ltd 277000-277FFF (base 16) Shenzhen Angstrom Excellence Technology Co., Ltd Angstrom Excellence Building, No. 1310,Guanguang Road,Longhua District @@ -24386,18 +24398,18 @@ FE0000-FE0FFF (base 16) Potter Electric Signal Co. LLC Hazelwood MO 63042 US -8C-1F-64 (hex) Samwell International Inc -3EB000-3EBFFF (base 16) Samwell International Inc - No. 317-1, Sec.2, An Kang Rd., Hsintien Dist - New Taipei City 231 - TW - 8C-1F-64 (hex) Potter Electric Signal Co. LLC 965000-965FFF (base 16) Potter Electric Signal Co. LLC 5757 Phantom Drive Hazelwood MO 63042 US +8C-1F-64 (hex) Samwell International Inc +3EB000-3EBFFF (base 16) Samwell International Inc + No. 317-1, Sec.2, An Kang Rd., Hsintien Dist + New Taipei City 231 + TW + 8C-1F-64 (hex) Potter Electric Signal Co. LLC EBB000-EBBFFF (base 16) Potter Electric Signal Co. LLC 5757 Phantom Drive @@ -24410,36 +24422,36 @@ EBB000-EBBFFF (base 16) Potter Electric Signal Co. LLC Hazelwood MO 63042 US -8C-1F-64 (hex) SUS Corporation -F69000-F69FFF (base 16) SUS Corporation - 6F, S-patio Bldg. 14-25 Minami-cho, Suruga-ku, - Shizuoka city, Shizuoka 422-8067 - JP - 8C-1F-64 (hex) CS-Tech s.r.o. ED7000-ED7FFF (base 16) CS-Tech s.r.o. Lazenska Usti nad Orlici Czech Republic 56201 CZ +8C-1F-64 (hex) Leap Info Systems Pvt. Ltd. +FEE000-FEEFFF (base 16) Leap Info Systems Pvt. Ltd. + 301 Melinkeri, Plot no.4, Survey No.149/1A, ITI Road,Parihar Chowk, Aundh, Pune – 411007 + Pune Maharashtra 411007 + IN + 8C-1F-64 (hex) SARV WEBS PRIVATE LIMITED CA0000-CA0FFF (base 16) SARV WEBS PRIVATE LIMITED IT-10,EPIP RIICO INDUSTRIAL AREA SITAPURA JAIPUR 302022 JAIPUR RAJASTHAN 302022 IN +8C-1F-64 (hex) CHUGOKU ELECTRICAL INSTRUMENTS Co.,LTD. +DD3000-DD3FFF (base 16) CHUGOKU ELECTRICAL INSTRUMENTS Co.,LTD. + 2-4-6,Tsurue,Fuchu-cho,Aki-gun, + Hiroshima Japan 735-0008 + JP + 8C-1F-64 (hex) Wi-Tronix, LLC D8D000-D8DFFF (base 16) Wi-Tronix, LLC 631 E Boughton Rd, Suite 240 Bolingbrook IL 60440 US -70-B3-D5 (hex) Aplex Technology Inc. -F57000-F57FFF (base 16) Aplex Technology Inc. - 15F-1, No.186, Jian Yi Road - Zhonghe District New Taipei City 235 - - TW - 8C-1F-64 (hex) YUYAMA MFG Co.,Ltd 24E000-24EFFF (base 16) YUYAMA MFG Co.,Ltd 1-4-30 @@ -24459,8 +24471,8 @@ F57000-F57FFF (base 16) Aplex Technology Inc. TW 70-B3-D5 (hex) Aplex Technology Inc. -906000-906FFF (base 16) Aplex Technology Inc. - 15F-1, No.186, Jian Yi Road +F57000-F57FFF (base 16) Aplex Technology Inc. + 15F-1, No.186, Jian Yi Road Zhonghe District New Taipei City 235 - TW @@ -24470,17 +24482,29 @@ B96000-B96FFF (base 16) Observable Space Los Angeles CA 90064 US -8C-1F-64 (hex) CHUGOKU ELECTRICAL INSTRUMENTS Co.,LTD. -DD3000-DD3FFF (base 16) CHUGOKU ELECTRICAL INSTRUMENTS Co.,LTD. - 2-4-6,Tsurue,Fuchu-cho,Aki-gun, - Hiroshima Japan 735-0008 - JP +70-B3-D5 (hex) Aplex Technology Inc. +906000-906FFF (base 16) Aplex Technology Inc. + 15F-1, No.186, Jian Yi Road + Zhonghe District New Taipei City 235 - + TW -8C-1F-64 (hex) Leap Info Systems Pvt. Ltd. -FEE000-FEEFFF (base 16) Leap Info Systems Pvt. Ltd. - 301 Melinkeri, Plot no.4, Survey No.149/1A, ITI Road,Parihar Chowk, Aundh, Pune – 411007 - Pune Maharashtra 411007 - IN +8C-1F-64 (hex) Yu Heng Electric CO. TD +1FC000-1FCFFF (base 16) Yu Heng Electric CO. TD + No. 8, Gongye 2nd Road, Renwu District, + Kaohiung City Taiwan 814 + CN + +8C-1F-64 (hex) REO AG +CD0000-CD0FFF (base 16) REO AG + Brühlerstr. 100 + Solingen 42657 + DE + +8C-1F-64 (hex) Intenseye Inc. +A20000-A20FFF (base 16) Intenseye Inc. + 1250 Broadway Suite 401 + New York NY 10001 + US 8C-1F-64 (hex) BOE Smart IoT Technology Co.,Ltd 761000-761FFF (base 16) BOE Smart IoT Technology Co.,Ltd @@ -24488,12 +24512,6 @@ FEE000-FEEFFF (base 16) Leap Info Systems Pvt. Ltd. Beijing Beijing 100176 CN -8C-1F-64 (hex) Yu Heng Electric CO. TD -1FC000-1FCFFF (base 16) Yu Heng Electric CO. TD - No. 8, Gongye 2nd Road, Renwu District, - Kaohiung City Taiwan 814 - CN - 8C-1F-64 (hex) TRATON AB 741000-741FFF (base 16) TRATON AB Lärlingsvägen 3 @@ -24512,24 +24530,12 @@ ED0000-ED0FFF (base 16) Shanghai Jupper Technology Co.Ltd Hazelwood MO 63042 US -8C-1F-64 (hex) Intenseye Inc. -A20000-A20FFF (base 16) Intenseye Inc. - 1250 Broadway Suite 401 - New York NY 10001 - US - 8C-1F-64 (hex) Smith meter Inc 6D1000-6D1FFF (base 16) Smith meter Inc 1602 Wagner Ave Erie 16510 US -8C-1F-64 (hex) REO AG -CD0000-CD0FFF (base 16) REO AG - Brühlerstr. 100 - Solingen 42657 - DE - 8C-1F-64 (hex) Racelogic Ltd FB6000-FB6FFF (base 16) Racelogic Ltd Unit 10-11 Osier Way,Swan Business Centre @@ -24584,18 +24590,24 @@ FA0000-FA0FFF (base 16) Pneumax Spa Lurano Bergamo 24050 IT -8C-1F-64 (hex) Coral Infratel Pvt Ltd -750000-750FFF (base 16) Coral Infratel Pvt Ltd - First Floor, 144 Subhash Nagar - Rohtak Haryana 124001 - IN - 8C-1F-64 (hex) Fortus 9A3000-9A3FFF (base 16) Fortus 32 Lavery Avenue Dublin Dublin D12 A611 IE +8C-1F-64 (hex) Potter Electric Signal Co. LLC +690000-690FFF (base 16) Potter Electric Signal Co. LLC + 1609 Park 370 Place + Hazelwood MO 63042 + US + +8C-1F-64 (hex) Coral Infratel Pvt Ltd +750000-750FFF (base 16) Coral Infratel Pvt Ltd + First Floor, 144 Subhash Nagar + Rohtak Haryana 124001 + IN + 8C-1F-64 (hex) AMC Europe Kft. A2F000-A2FFFF (base 16) AMC Europe Kft. Csiri utca 13 @@ -24608,12 +24620,6 @@ A2F000-A2FFFF (base 16) AMC Europe Kft. San Antonio TX 78218 US -8C-1F-64 (hex) Potter Electric Signal Co. LLC -690000-690FFF (base 16) Potter Electric Signal Co. LLC - 1609 Park 370 Place - Hazelwood MO 63042 - US - 8C-1F-64 (hex) HARBIN DIGITAL ECONOMY DEVELOPMENT CO.,LTD EC2000-EC2FFF (base 16) HARBIN DIGITAL ECONOMY DEVELOPMENT CO.,LTD No. 22, Binhe Avenue, Pingfang District @@ -24638,17 +24644,23 @@ EC2000-EC2FFF (base 16) HARBIN DIGITAL ECONOMY DEVELOPMENT CO.,LTD HELSINKI 00380 FI +8C-1F-64 (hex) BRS Sistemas Eletrônicos +944000-944FFF (base 16) BRS Sistemas Eletrônicos + Rua Capistrano de Abreu, 68 + Canoas RS 92120130 + BR + 8C-1F-64 (hex) Maven Pet Inc B7E000-B7EFFF (base 16) Maven Pet Inc 800 N King Street Suite 304 2873 Wilmington Wilmington DE 19801 US -8C-1F-64 (hex) BRS Sistemas Eletrônicos -944000-944FFF (base 16) BRS Sistemas Eletrônicos - Rua Capistrano de Abreu, 68 - Canoas RS 92120130 - BR +8C-1F-64 (hex) YONNET BILISIM YAZ. EGT. VE DAN. HIZ. TIC. A.S. +75E000-75EFFF (base 16) YONNET BILISIM YAZ. EGT. VE DAN. HIZ. TIC. A.S. + CUMHURIYET MAH. + ISTANBUL 34870 + TR 8C-1F-64 (hex) FaceLabs.AI DBA PropTech.AI FA9000-FA9FFF (base 16) FaceLabs.AI DBA PropTech.AI @@ -24662,12 +24674,6 @@ EC0000-EC0FFF (base 16) VOOST analytics Riyadh Al Riyadh 11391 SA -8C-1F-64 (hex) YONNET BILISIM YAZ. EGT. VE DAN. HIZ. TIC. A.S. -75E000-75EFFF (base 16) YONNET BILISIM YAZ. EGT. VE DAN. HIZ. TIC. A.S. - CUMHURIYET MAH. - ISTANBUL 34870 - TR - 8C-1F-64 (hex) MobileMustHave 6A7000-6A7FFF (base 16) MobileMustHave 63 Key Road Suite 3-1011 @@ -24680,12 +24686,6 @@ CE9000-CE9FFF (base 16) Landis+Gyr Equipamentos de Medição Ltda Curitiba Paraná 81460-120 BR -8C-1F-64 (hex) TOKYO INTERPHONE CO.,LTD. -652000-652FFF (base 16) TOKYO INTERPHONE CO.,LTD. - 8F, JS Shibuya Building3-8-10 Shibuya, Shibuya-ku - TOKYO 150-0002 - JP - 8C-1F-64 (hex) Förster-Technik GmbH 448000-448FFF (base 16) Förster-Technik GmbH Gerwigstrasse 25 @@ -24698,10 +24698,16 @@ CE9000-CE9FFF (base 16) Landis+Gyr Equipamentos de Medição Ltda Cheyenne WY 82001 US -8C-1F-64 (hex) MAYSUN CORPORATION -784000-784FFF (base 16) MAYSUN CORPORATION - 966-2 Gokanjima - Fuji-shi Shizuoka-ken 416-0946 +8C-1F-64 (hex) TOKYO INTERPHONE CO.,LTD. +652000-652FFF (base 16) TOKYO INTERPHONE CO.,LTD. + 8F, JS Shibuya Building3-8-10 Shibuya, Shibuya-ku + TOKYO 150-0002 + JP + +8C-1F-64 (hex) YUYAMA MFG Co.,Ltd +65A000-65AFFF (base 16) YUYAMA MFG Co.,Ltd + 1-4-30 + MEISHINGUCHI,TOYONAKA OSAKA 561-0841 JP 8C-1F-64 (hex) Pro Design Electronic GmbH @@ -24710,10 +24716,10 @@ CE9000-CE9FFF (base 16) Landis+Gyr Equipamentos de Medição Ltda Bruckmuehl Bavaria 83052 DE -8C-1F-64 (hex) YUYAMA MFG Co.,Ltd -65A000-65AFFF (base 16) YUYAMA MFG Co.,Ltd - 1-4-30 - MEISHINGUCHI,TOYONAKA OSAKA 561-0841 +8C-1F-64 (hex) MAYSUN CORPORATION +784000-784FFF (base 16) MAYSUN CORPORATION + 966-2 Gokanjima + Fuji-shi Shizuoka-ken 416-0946 JP 8C-1F-64 (hex) Buckeye Mountain @@ -24746,24 +24752,18 @@ F37000-F37FFF (base 16) Polarity Inc RANCHO CORDOVA CA 95742-6599 US -8C-1F-64 (hex) Attack do Brasil Ind Com Apar de Som LTDA -178000-178FFF (base 16) Attack do Brasil Ind Com Apar de Som LTDA - AV AYRTON SENNA DA SILVA, 400 – PQ INDL ZONA OESTE - Apucarana Parana 86803-570 - BR - -8C-1F-64 (hex) Grinn Sp. z o.o. -156000-156FFF (base 16) Grinn Sp. z o.o. - Strzegomska 140A - Wrocław 54-429 - PL - 8C-1F-64 (hex) Infosoft Digital Design and Services P L EDC000-EDCFFF (base 16) Infosoft Digital Design and Services P L 484, SECTOR-8 ,IMT MANESER,GURGAONMANESER GURGAON Haryana 122050 IN +8C-1F-64 (hex) Attack do Brasil Ind Com Apar de Som LTDA +178000-178FFF (base 16) Attack do Brasil Ind Com Apar de Som LTDA + AV AYRTON SENNA DA SILVA, 400 – PQ INDL ZONA OESTE + Apucarana Parana 86803-570 + BR + 8C-1F-64 (hex) Guangzhou Beizeng Information Technology Co.,Ltd 39F000-39FFFF (base 16) Guangzhou Beizeng Information Technology Co.,Ltd Room 714, Building D3, No. 197, Shuixi Road, Huangpu District, Guangzhou City, China @@ -24776,11 +24776,11 @@ EDC000-EDCFFF (base 16) Infosoft Digital Design and Services P L Kaohsiung City 81358 TW -8C-1F-64 (hex) Unitron Systems b.v. -1AC000-1ACFFF (base 16) Unitron Systems b.v. - SCHANSESTRAAT 7 - IJzendijke 4515 RN - NL +8C-1F-64 (hex) Grinn Sp. z o.o. +156000-156FFF (base 16) Grinn Sp. z o.o. + Strzegomska 140A + Wrocław 54-429 + PL 8C-1F-64 (hex) ANADOLU TRAFİK KONTROL SİS.TAŞ.SAN.VE TİC. LTD.ŞTİ D14000-D14FFF (base 16) ANADOLU TRAFİK KONTROL SİS.TAŞ.SAN.VE TİC. LTD.ŞTİ @@ -24788,23 +24788,23 @@ D14000-D14FFF (base 16) ANADOLU TRAFİK KONTROL SİS.TAŞ.SAN.VE TİC. LTD. ANKARA ANKARA 06180 TR +8C-1F-64 (hex) Unitron Systems b.v. +1AC000-1ACFFF (base 16) Unitron Systems b.v. + SCHANSESTRAAT 7 + IJzendijke 4515 RN + NL + 8C-1F-64 (hex) Kinemetrics, Inc. B50000-B50FFF (base 16) Kinemetrics, Inc. 222 Vista Avenue Pasadena CA 91107 US -8C-1F-64 (hex) Kneron (Taiwan) Co., Ltd. -1EE000-1EEFFF (base 16) Kneron (Taiwan) Co., Ltd. - 12F-1., No.386, Sec. 6, Nanjing E. Rd., Neihu Dist., - Taipei City 11470 - TW - -8C-1F-64 (hex) NodOn SAS -606000-606FFF (base 16) NodOn SAS - 121 rue des Hêtres - Saint Cyr en Val Loiret 45590 - FR +8C-1F-64 (hex) Tech Mobility Aps +31D000-31DFFF (base 16) Tech Mobility Aps + Lille Frederikslund 2 + Holte 2840 + DK 8C-1F-64 (hex) Nine Fives LLC D22000-D22FFF (base 16) Nine Fives LLC @@ -24812,24 +24812,30 @@ D22000-D22FFF (base 16) Nine Fives LLC Spokane WA 99201 US +8C-1F-64 (hex) Kneron (Taiwan) Co., Ltd. +1EE000-1EEFFF (base 16) Kneron (Taiwan) Co., Ltd. + 12F-1., No.386, Sec. 6, Nanjing E. Rd., Neihu Dist., + Taipei City 11470 + TW + 8C-1F-64 (hex) FemtoTools AG 7A9000-7A9FFF (base 16) FemtoTools AG Furtbachstrasse 4 Buchs Zurich 8107 CH -8C-1F-64 (hex) Tech Mobility Aps -31D000-31DFFF (base 16) Tech Mobility Aps - Lille Frederikslund 2 - Holte 2840 - DK - 8C-1F-64 (hex) AooGee Controls Co., LTD. 458000-458FFF (base 16) AooGee Controls Co., LTD. Siming District office building 14, Fu Lian Xiamen Fujian 361000 CN +8C-1F-64 (hex) NodOn SAS +606000-606FFF (base 16) NodOn SAS + 121 rue des Hêtres + Saint Cyr en Val Loiret 45590 + FR + 8C-1F-64 (hex) Sigmann Elektronik GmbH 49A000-49AFFF (base 16) Sigmann Elektronik GmbH Hauptstrasse 53 @@ -24848,42 +24854,24 @@ C84000-C84FFF (base 16) Luceor Montigny-le-Bretonneux 78180 FR -8C-1F-64 (hex) Currux Vision LLC -66B000-66BFFF (base 16) Currux Vision LLC - 520 Post Oak Boulevard, Suite 260 - Houston TX 77027 - US - 8C-1F-64 (hex) SHODEN Co., Ltd. 259000-259FFF (base 16) SHODEN Co., Ltd. 365, Sannocho Inage-ku Chiba Chiba 2630002 JP -8C-1F-64 (hex) Power Electronics Espana, S.L. -773000-773FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - -8C-1F-64 (hex) Vision Systems Safety Tech -AD9000-AD9FFF (base 16) Vision Systems Safety Tech - 5 Chemin de Chiradie - Brignais 69530 - FR - -8C-1F-64 (hex) Wesync -190000-190FFF (base 16) Wesync - 506Ho, Pyeongchondigitalempire, 16, Heungan-daero 427beon-gil, Dongan-gu - Anyang-si Gyeonggi-do 14059 - KR - 8C-1F-64 (hex) ChamSys 143000-143FFF (base 16) ChamSys Unit 5Adanac Park southampton Hampshire SO16 0BT GB +8C-1F-64 (hex) Currux Vision LLC +66B000-66BFFF (base 16) Currux Vision LLC + 520 Post Oak Boulevard, Suite 260 + Houston TX 77027 + US + 8C-1F-64 (hex) LyconSys GmbH & Co.KG 134000-134FFF (base 16) LyconSys GmbH & Co.KG Hildegardstr. 12A @@ -24896,10 +24884,22 @@ AD9000-AD9FFF (base 16) Vision Systems Safety Tech Ithaca NY 14850 US -70-B3-D5 (hex) ICTK Co., Ltd. -5C9000-5C9FFF (base 16) ICTK Co., Ltd. - 3F Ventureforum B'd, Pangyodae-ro - Seung-nam Si Gyeonggi-Do 13488 +8C-1F-64 (hex) Power Electronics Espana, S.L. +773000-773FFF (base 16) Power Electronics Espana, S.L. + C/ Leonardo Da Vinci, 24-26 + Paterna Valencia 46980 + ES + +8C-1F-64 (hex) Vision Systems Safety Tech +AD9000-AD9FFF (base 16) Vision Systems Safety Tech + 5 Chemin de Chiradie + Brignais 69530 + FR + +8C-1F-64 (hex) Wesync +190000-190FFF (base 16) Wesync + 506Ho, Pyeongchondigitalempire, 16, Heungan-daero 427beon-gil, Dongan-gu + Anyang-si Gyeonggi-do 14059 KR 8C-1F-64 (hex) PASO SPA @@ -24908,11 +24908,17 @@ CF8000-CF8FFF (base 16) PASO SPA Lainate Italy 20045 IT -8C-1F-64 (hex) ASI -B53000-B53FFF (base 16) ASI - 1001 Av. de la République - Marcq-en-Baroeul 59700 - FR +70-B3-D5 (hex) ICTK Co., Ltd. +5C9000-5C9FFF (base 16) ICTK Co., Ltd. + 3F Ventureforum B'd, Pangyodae-ro + Seung-nam Si Gyeonggi-Do 13488 + KR + +8C-1F-64 (hex) Hitachi Energy Australia Pty. Ltd. +505000-505FFF (base 16) Hitachi Energy Australia Pty. Ltd. + 88 Beresford Road + Lilydale 3140 + AU 8C-1F-64 (hex) Potter Electric Signal Co. LLC 75D000-75DFFF (base 16) Potter Electric Signal Co. LLC @@ -24920,11 +24926,17 @@ B53000-B53FFF (base 16) ASI Hazelwood MO 63042 US -8C-1F-64 (hex) Hitachi Energy Australia Pty. Ltd. -505000-505FFF (base 16) Hitachi Energy Australia Pty. Ltd. - 88 Beresford Road - Lilydale 3140 - AU +8C-1F-64 (hex) ASI +B53000-B53FFF (base 16) ASI + 1001 Av. de la République + Marcq-en-Baroeul 59700 + FR + +8C-1F-64 (hex) therlys GmbH +D25000-D25FFF (base 16) therlys GmbH + Heidenkampsweg 40 + Hamburg 20097 + DE 8C-1F-64 (hex) Blackline Systems Corp. BE5000-BE5FFF (base 16) Blackline Systems Corp. @@ -24938,11 +24950,11 @@ BE5000-BE5FFF (base 16) Blackline Systems Corp. St. Gallen 9008 CH -8C-1F-64 (hex) therlys GmbH -D25000-D25FFF (base 16) therlys GmbH - Heidenkampsweg 40 - Hamburg 20097 - DE +8C-1F-64 (hex) Vision Systems Safety Tech +436000-436FFF (base 16) Vision Systems Safety Tech + 5 Chemin de Chiradie + Brignais 69530 + FR 8C-1F-64 (hex) DORLET SAU BC5000-BC5FFF (base 16) DORLET SAU @@ -24956,11 +24968,17 @@ BC5000-BC5FFF (base 16) DORLET SAU Yokneam 2066717 IL -8C-1F-64 (hex) Vision Systems Safety Tech -436000-436FFF (base 16) Vision Systems Safety Tech - 5 Chemin de Chiradie - Brignais 69530 - FR +70-B3-D5 (hex) Hörmann Warnsysteme GmbH +B78000-B78FFF (base 16) Hörmann Warnsysteme GmbH + Hauptstr. 45-47 + Kirchseeon Bavaria 85614 + DE + +8C-1F-64 (hex) Groundtruth Ltd +D67000-D67FFF (base 16) Groundtruth Ltd + 14 Tilley Road + Paekakariki 5034 + NZ 8C-1F-64 (hex) Flow Power 82B000-82BFFF (base 16) Flow Power @@ -32744,18 +32762,18 @@ AB3000-AB3FFF (base 16) VELVU TECHNOLOGIES PRIVATE LIMITED Skovlunde 2740 DK -8C-1F-64 (hex) wincker international enterprise co., ltd -B1F000-B1FFFF (base 16) wincker international enterprise co., ltd - 1FL No. 345 Yen Shou St., Taipei, Taiwan - Taipei 10577 - TW - 8C-1F-64 (hex) Chengdu Xiuwei TechnologyDevelopment Co., Ltd 870000-870FFF (base 16) Chengdu Xiuwei TechnologyDevelopment Co., Ltd 10th Floor, Building 10, No. 8 Guangfu Road, Qingyang District Chengdu City Please Select 610073 CN +8C-1F-64 (hex) wincker international enterprise co., ltd +B1F000-B1FFFF (base 16) wincker international enterprise co., ltd + 1FL No. 345 Yen Shou St., Taipei, Taiwan + Taipei 10577 + TW + 8C-1F-64 (hex) IQ Tools LLC FF5000-FF5FFF (base 16) IQ Tools LLC Zemlyanoy Val, 64, building 2 @@ -32846,18 +32864,18 @@ B33000-B33FFF (base 16) Aplex Technology Inc. Zhonghe District New Taipei City 235 - TW -8C-1F-64 (hex) Boeing India Private Limited -533000-533FFF (base 16) Boeing India Private Limited - Plot No: 55-B,56,57,59 Hitech-Defence and Aerospace park, Aerospace Sector, Unachur Village, Yelahanka Taluk, Bangaloe North - Bengaluru Karnataka 562149 - IN - 8C-1F-64 (hex) Jemac Sweden AB 42D000-42DFFF (base 16) Jemac Sweden AB Trångsundsvägen 20A Kalmar 39356 SE +8C-1F-64 (hex) Boeing India Private Limited +533000-533FFF (base 16) Boeing India Private Limited + Plot No: 55-B,56,57,59 Hitech-Defence and Aerospace park, Aerospace Sector, Unachur Village, Yelahanka Taluk, Bangaloe North + Bengaluru Karnataka 562149 + IN + 70-B3-D5 (hex) Aplex Technology Inc. 3D9000-3D9FFF (base 16) Aplex Technology Inc. 15F-1, No.186, Jian Yi Road @@ -32894,12 +32912,6 @@ C31000-C31FFF (base 16) Ambarella Inc. Santa Clara CA 95054 US -8C-1F-64 (hex) Aegex Technologies LLC Magyarországi Fióktelepe -A9D000-A9DFFF (base 16) Aegex Technologies LLC Magyarországi Fióktelepe - Tildy Zoltán utca - Pécs Baranya 7632 - HU - 8C-1F-64 (hex) Q (Cue), Inc. 6A6000-6A6FFF (base 16) Q (Cue), Inc. Abba Hillel Silver Rd 21 @@ -32912,6 +32924,12 @@ A9D000-A9DFFF (base 16) Aegex Technologies LLC Magyarországi Fióktelepe Beijing Haidian District 100085 CN +8C-1F-64 (hex) Aegex Technologies LLC Magyarországi Fióktelepe +A9D000-A9DFFF (base 16) Aegex Technologies LLC Magyarországi Fióktelepe + Tildy Zoltán utca + Pécs Baranya 7632 + HU + 8C-1F-64 (hex) Automation Displays Inc. 4F2000-4F2FFF (base 16) Automation Displays Inc. 3533 White Ave @@ -32966,12 +32984,6 @@ BF7000-BF7FFF (base 16) Intellicon Private Limited Chuou-ku Tokyo 104-0061 JP -8C-1F-64 (hex) Lumiplan-Duhamel -0D0000-0D0FFF (base 16) Lumiplan-Duhamel - 215 rue Guynemer - Le versoud 38420 - FR - 8C-1F-64 (hex) Private B80000-B80FFF (base 16) Private @@ -32981,6 +32993,12 @@ B80000-B80FFF (base 16) Private Bellingham WA 98225 US +8C-1F-64 (hex) Lumiplan-Duhamel +0D0000-0D0FFF (base 16) Lumiplan-Duhamel + 215 rue Guynemer + Le versoud 38420 + FR + 8C-1F-64 (hex) Breas Medical AB 348000-348FFF (base 16) Breas Medical AB Företagsvägen 1 @@ -32993,18 +33011,18 @@ B80000-B80FFF (base 16) Private Calgary Alberta T3H 5T9 CA -00-1B-C5 (hex) CyanConnode -0C6000-0C6FFF (base 16) CyanConnode - Suite 2, Ground Floor, The Jeffreys Building, Cowley Road - Milton Cambridge CB4 0DS - GB - 8C-1F-64 (hex) Thales Nederland BV 29C000-29CFFF (base 16) Thales Nederland BV Haaksbergerstraat 49 Hengelo Overijssel 7554PA NL +00-1B-C5 (hex) CyanConnode +0C6000-0C6FFF (base 16) CyanConnode + Suite 2, Ground Floor, The Jeffreys Building, Cowley Road + Milton Cambridge CB4 0DS + GB + 8C-1F-64 (hex) SMC Gateway 0B5000-0B5FFF (base 16) SMC Gateway 78 HIGH BEECHES @@ -33035,29 +33053,35 @@ F99000-F99FFF (base 16) Sysinno Technology Inc. Hsinchu 300 TW -8C-1F-64 (hex) Bounce Imaging -1AE000-1AEFFF (base 16) Bounce Imaging - 247 Cayuga Rd., Suite 15e - Cheektowaga NY 14225 - US - 8C-1F-64 (hex) InfoMac Sp. z o.o. Sp.k. 840000-840FFF (base 16) InfoMac Sp. z o.o. Sp.k. UL. WOJSKA POLSKIEGO 6 Szczecinek zachodniopomorskie 78-400 PL +8C-1F-64 (hex) RSC +B31000-B31FFF (base 16) RSC + 36 27th Street, Umm Suqeim 3 + Dubai Dubai 00000 + AE + +8C-1F-64 (hex) Bounce Imaging +1AE000-1AEFFF (base 16) Bounce Imaging + 247 Cayuga Rd., Suite 15e + Cheektowaga NY 14225 + US + 8C-1F-64 (hex) Asteelflash Design Solutions Hamburg GmbH 1EA000-1EAFFF (base 16) Asteelflash Design Solutions Hamburg GmbH Meiendorfer Straße 205c Hamburg 22145 DE -8C-1F-64 (hex) RSC -B31000-B31FFF (base 16) RSC - 36 27th Street, Umm Suqeim 3 - Dubai Dubai 00000 - AE +8C-1F-64 (hex) Chengdu Xinyuandi Technology Co., Ltd. +C34000-C34FFF (base 16) Chengdu Xinyuandi Technology Co., Ltd. + No. 7, Tianxianqiao North Road, Jinjiang District, Chengdu, Sichuan Province, China + Chengdu 610021 + CN 70-B3-D5 (hex) AML Oceanographic 0CD000-0CDFFF (base 16) AML Oceanographic @@ -33065,11 +33089,11 @@ B31000-B31FFF (base 16) RSC DARTMOUTH NS B3B 1S4 CA -8C-1F-64 (hex) Chengdu Xinyuandi Technology Co., Ltd. -C34000-C34FFF (base 16) Chengdu Xinyuandi Technology Co., Ltd. - No. 7, Tianxianqiao North Road, Jinjiang District, Chengdu, Sichuan Province, China - Chengdu 610021 - CN +8C-1F-64 (hex) Produkcija studio C.P.G d.o.o. +A0C000-A0CFFF (base 16) Produkcija studio C.P.G d.o.o. + Svetice 23 + Zagreb Zagreb 10000 + HR 8C-1F-64 (hex) ADETEC SAS 835000-835FFF (base 16) ADETEC SAS @@ -33083,12 +33107,6 @@ C34000-C34FFF (base 16) Chengdu Xinyuandi Technology Co., Ltd. Chengdu SiChuan 610000 CN -8C-1F-64 (hex) Produkcija studio C.P.G d.o.o. -A0C000-A0CFFF (base 16) Produkcija studio C.P.G d.o.o. - Svetice 23 - Zagreb Zagreb 10000 - HR - 8C-1F-64 (hex) Raycon A09000-A09FFF (base 16) Raycon 1115 Broadway, Suite 12 @@ -33119,23 +33137,17 @@ BD0000-BD0FFF (base 16) Mesa Labs, Inc. Lakewood CO 80228 US -8C-1F-64 (hex) Anhui Wenxiang Technology Co.,Ltd. -3CB000-3CBFFF (base 16) Anhui Wenxiang Technology Co.,Ltd. - The intersection of Fengming Avenue and Hanjiang Road, Jiangnan Emerging Industry Concentration Zone - Chizhou Anhui 247100 - CN - 8C-1F-64 (hex) Starview Asia Company 83B000-83BFFF (base 16) Starview Asia Company Level 40, 140 Williams Street Melbourne Victoria 3000 AU -8C-1F-64 (hex) INTERNET PROTOCOLO LOGICA SL -06E000-06EFFF (base 16) INTERNET PROTOCOLO LOGICA SL - Avenida Somosierra 12. Portal A. Planta 1ª. Letra I - San Sebastián de los Reyes Madrid 28703 - ES +8C-1F-64 (hex) Anhui Wenxiang Technology Co.,Ltd. +3CB000-3CBFFF (base 16) Anhui Wenxiang Technology Co.,Ltd. + The intersection of Fengming Avenue and Hanjiang Road, Jiangnan Emerging Industry Concentration Zone + Chizhou Anhui 247100 + CN 8C-1F-64 (hex) Eltvor Instruments B58000-B58FFF (base 16) Eltvor Instruments @@ -33143,6 +33155,12 @@ B58000-B58FFF (base 16) Eltvor Instruments Tabor 39002 CZ +8C-1F-64 (hex) INTERNET PROTOCOLO LOGICA SL +06E000-06EFFF (base 16) INTERNET PROTOCOLO LOGICA SL + Avenida Somosierra 12. Portal A. Planta 1ª. Letra I + San Sebastián de los Reyes Madrid 28703 + ES + 8C-1F-64 (hex) Rudolf Riester GmbH 27A000-27AFFF (base 16) Rudolf Riester GmbH P.O. Box 35 Bruckstrasse 31 @@ -33173,23 +33191,17 @@ A78000-A78FFF (base 16) TAIT Global LLC Lititz PA 17543 US -8C-1F-64 (hex) OES Inc. -578000-578FFF (base 16) OES Inc. - 4056 Blakie Road - London ON N6L1P7 - CA - 8C-1F-64 (hex) netmon 434000-434FFF (base 16) netmon B-1023 TERA Tower#1, 167 SONGPA-DAERO, SONGPA-GU Seoul 05855 KR -8C-1F-64 (hex) inomatic GmbH -96E000-96EFFF (base 16) inomatic GmbH - Karl-Braun-Straße 12-13 - Nordhorn Germany 48531 - DE +8C-1F-64 (hex) OES Inc. +578000-578FFF (base 16) OES Inc. + 4056 Blakie Road + London ON N6L1P7 + CA 8C-1F-64 (hex) Diatech co.,ltd. 26C000-26CFFF (base 16) Diatech co.,ltd. @@ -33197,12 +33209,24 @@ A78000-A78FFF (base 16) TAIT Global LLC Suginami Ku Tokyo 167-0021 JP +8C-1F-64 (hex) inomatic GmbH +96E000-96EFFF (base 16) inomatic GmbH + Karl-Braun-Straße 12-13 + Nordhorn Germany 48531 + DE + 8C-1F-64 (hex) Pneumax Spa 431000-431FFF (base 16) Pneumax Spa via cascina barbellina, 10 Lurano Bergamo 24050 IT +8C-1F-64 (hex) Apantac LLC +471000-471FFF (base 16) Apantac LLC + 7556 SW Bridgeport Road + Durham OR 97224 + US + 8C-1F-64 (hex) Mobileye D63000-D63FFF (base 16) Mobileye 13 Hartom st. @@ -40865,11 +40889,11 @@ C75000-C75FFF (base 16) Abbott Diagnostics Technologies AS Oslo Oslo 0504 NO -8C-1F-64 (hex) Oriental Electronics, Inc. -68E000-68EFFF (base 16) Oriental Electronics, Inc. - 2-4-1 Tanabe-Chuo - Kyo-Tanabe Kyoto 610-0334 - JP +8C-1F-64 (hex) OnAsset Intelligence +415000-415FFF (base 16) OnAsset Intelligence + 8407 Sterling Street + Irving TX 75063 + US 8C-1F-64 (hex) OOO Mig Trading C19000-C19FFF (base 16) OOO Mig Trading @@ -40883,10 +40907,16 @@ C19000-C19FFF (base 16) OOO Mig Trading Dinkelsbuehl Bavaria 91550 DE -8C-1F-64 (hex) OnAsset Intelligence -415000-415FFF (base 16) OnAsset Intelligence - 8407 Sterling Street - Irving TX 75063 +8C-1F-64 (hex) Oriental Electronics, Inc. +68E000-68EFFF (base 16) Oriental Electronics, Inc. + 2-4-1 Tanabe-Chuo + Kyo-Tanabe Kyoto 610-0334 + JP + +8C-1F-64 (hex) Pulcro.io LLC +C22000-C22FFF (base 16) Pulcro.io LLC + 551 S IH 35, Ste 300 + Round Rock TX 78664 US 8C-1F-64 (hex) Digitella Inc. @@ -40895,29 +40925,29 @@ C19000-C19FFF (base 16) OOO Mig Trading Anyang-si Gyeonggi-do 14084 KR -8C-1F-64 (hex) Pulcro.io LLC -C22000-C22FFF (base 16) Pulcro.io LLC - 551 S IH 35, Ste 300 - Round Rock TX 78664 - US - 8C-1F-64 (hex) Melissa Climate Jsc 6CA000-6CAFFF (base 16) Melissa Climate Jsc Gen. Gurko 4 Street Sofia 1000 BG +8C-1F-64 (hex) wonder meditec +B2D000-B2DFFF (base 16) wonder meditec + 2F, 12-11, Seonjam-ro, Seongbuk-gu + Seoul, Korea 02836 + KR + 8C-1F-64 (hex) IGL 8F0000-8F0FFF (base 16) IGL 1, Allée des Chevreuils, Lissieu 69380 FR -8C-1F-64 (hex) wonder meditec -B2D000-B2DFFF (base 16) wonder meditec - 2F, 12-11, Seonjam-ro, Seongbuk-gu - Seoul, Korea 02836 - KR +8C-1F-64 (hex) In-lite Design BV +F4A000-F4AFFF (base 16) In-lite Design BV + Stephensonweg 18 + Gorinchem Zuid-Holland 4207 HB + NL 70-B3-D5 (hex) Teenage Engineering AB 1AF000-1AFFFF (base 16) Teenage Engineering AB @@ -40943,12 +40973,6 @@ D64000-D64FFF (base 16) Potter Electric Signal Co. LLC Hazelwood MO 63042 US -8C-1F-64 (hex) In-lite Design BV -F4A000-F4AFFF (base 16) In-lite Design BV - Stephensonweg 18 - Gorinchem Zuid-Holland 4207 HB - NL - 8C-1F-64 (hex) IDA North America Inc. 275000-275FFF (base 16) IDA North America Inc. 16 16th Street S @@ -40961,30 +40985,30 @@ F4A000-F4AFFF (base 16) In-lite Design BV Zhonghe District New Taipei City 235 - TW -70-B3-D5 (hex) Aplex Technology Inc. -FF3000-FF3FFF (base 16) Aplex Technology Inc. - 15F-1, No.186, Jian Yi Road - Zhonghe District New Taipei City 235 - - TW - -70-B3-D5 (hex) Aplex Technology Inc. -65C000-65CFFF (base 16) Aplex Technology Inc. - 15F-1, No.186, Jian Yi Road - Zhonghe District New Taipei City 235 - - TW - 8C-1F-64 (hex) Shenzhen Broadradio RFID Technology Co., Ltd 057000-057FFF (base 16) Shenzhen Broadradio RFID Technology Co., Ltd B222, 2nd Floor, Building B, Fuhai Technology Industrial Park shenzhen guangdong 5178000 CN +70-B3-D5 (hex) Aplex Technology Inc. +FF3000-FF3FFF (base 16) Aplex Technology Inc. + 15F-1, No.186, Jian Yi Road + Zhonghe District New Taipei City 235 - + TW + 8C-1F-64 (hex) Yu Heng Electric CO. TD 575000-575FFF (base 16) Yu Heng Electric CO. TD No 8 , Gongye 2nd Rd., Renwu Industry Park Kaohsiung Kaohsiung City 814 TW +70-B3-D5 (hex) Aplex Technology Inc. +65C000-65CFFF (base 16) Aplex Technology Inc. + 15F-1, No.186, Jian Yi Road + Zhonghe District New Taipei City 235 - + TW + 8C-1F-64 (hex) SPX Flow Technology BV CDA000-CDAFFF (base 16) SPX Flow Technology BV Munnikenheiweg 41 @@ -41135,18 +41159,18 @@ E2D000-E2DFFF (base 16) BAE Systems Guildford Surrey GU2 7RQ GB -8C-1F-64 (hex) RADA Electronics Industries Ltd. -E37000-E37FFF (base 16) RADA Electronics Industries Ltd. - 7 Gibory Israel St. - Netanya 42504 - IL - 8C-1F-64 (hex) XYZ Digital Private Limited 4B3000-4B3FFF (base 16) XYZ Digital Private Limited KH NO 1126 GROUND FLOOR STREET NO 17 VILLAGE RITHALA LANDMARK HONDA SHOW ROOM, North Delhi Rohini Delhi 110085 IN +8C-1F-64 (hex) RADA Electronics Industries Ltd. +E37000-E37FFF (base 16) RADA Electronics Industries Ltd. + 7 Gibory Israel St. + Netanya 42504 + IL + 8C-1F-64 (hex) Meiji Electric Industry 75B000-75BFFF (base 16) Meiji Electric Industry 48-1 Itabari , Yamayashiki-cho @@ -41156,29 +41180,35 @@ E37000-E37FFF (base 16) RADA Electronics Industries Ltd. 8C-1F-64 (hex) Private D48000-D48FFF (base 16) Private +8C-1F-64 (hex) Fugro Technology B.V. +7CD000-7CDFFF (base 16) Fugro Technology B.V. + Prismastraat 3 + Nootdorp 2631RT + NL + 8C-1F-64 (hex) Hiwin Mikrosystem Corp. A74000-A74FFF (base 16) Hiwin Mikrosystem Corp. NO 6 JINGKE CENTRAL RD TAICHUNG CITY TAIWAN 40841 TAICHUNG 40841 TW +8C-1F-64 (hex) Irmos Technologies AG +DDD000-DDDFFF (base 16) Irmos Technologies AG + Technoparkstrasse 1 + Zürich 8005 + CH + 8C-1F-64 (hex) 37130 81E000-81EFFF (base 16) 37130 Gaildorfer Strasse 6 Backnang 71540 DE -8C-1F-64 (hex) Fugro Technology B.V. -7CD000-7CDFFF (base 16) Fugro Technology B.V. - Prismastraat 3 - Nootdorp 2631RT - NL - -8C-1F-64 (hex) Irmos Technologies AG -DDD000-DDDFFF (base 16) Irmos Technologies AG - Technoparkstrasse 1 - Zürich 8005 - CH +8C-1F-64 (hex) SAEL SRL +60F000-60FFFF (base 16) SAEL SRL + Via Dei Genieri, 31 + Torri di Quartesolo Vicenza 36040 + IT 8C-1F-64 (hex) Kyowakiden Industry Co.,Ltd. 3D6000-3D6FFF (base 16) Kyowakiden Industry Co.,Ltd. @@ -41192,12 +41222,6 @@ DDD000-DDDFFF (base 16) Irmos Technologies AG Shannon Co. Clare V14 V99 IE -8C-1F-64 (hex) SAEL SRL -60F000-60FFFF (base 16) SAEL SRL - Via Dei Genieri, 31 - Torri di Quartesolo Vicenza 36040 - IT - 8C-1F-64 (hex) CEI Ptd Ltd 0FD000-0FDFFF (base 16) CEI Ptd Ltd 2 Ang Mo Kio Ave 12 @@ -41216,18 +41240,18 @@ DDD000-DDDFFF (base 16) Irmos Technologies AG Gramastetten Oberoesterreich 4201 AT -8C-1F-64 (hex) MYIR Electronics Limited -A1D000-A1DFFF (base 16) MYIR Electronics Limited - Room 04, 6th Floor, Building No.2, Fada Road, Yunli Smart Park,Bantian, Longgang District, Shenzhen, Guangdong, China - Shenzhen Guangdong 518129 - CN - 8C-1F-64 (hex) Sicon srl CC8000-CC8FFF (base 16) Sicon srl Via Sila 1/3 Isola Vicentina Vicenza 36033 IT +8C-1F-64 (hex) MYIR Electronics Limited +A1D000-A1DFFF (base 16) MYIR Electronics Limited + Room 04, 6th Floor, Building No.2, Fada Road, Yunli Smart Park,Bantian, Longgang District, Shenzhen, Guangdong, China + Shenzhen Guangdong 518129 + CN + 8C-1F-64 (hex) Nortek(QingDao) Measuring Equipment Co., Ltd 988000-988FFF (base 16) Nortek(QingDao) Measuring Equipment Co., Ltd 18A2, Yingdelong Buliding,No.15 Donghaixi Rd, Qingdao P.R.China @@ -41240,12 +41264,6 @@ CC8000-CC8FFF (base 16) Sicon srl Saint-Laurent Quebec H4T 1W7 CA -8C-1F-64 (hex) SAMSON CO.,LTD. -490000-490FFF (base 16) SAMSON CO.,LTD. - 3-4-15 YAHATA-CHO - Kanonji-City Kagawa 768-8602 - JP - 8C-1F-64 (hex) IRONWOOD ELECTRONICS C26000-C26FFF (base 16) IRONWOOD ELECTRONICS 1335 Eagandale Court @@ -41258,6 +41276,12 @@ A72000-A72FFF (base 16) First Design System Inc. Tokyo Shinjuku-ku 160-0023 JP +8C-1F-64 (hex) SAMSON CO.,LTD. +490000-490FFF (base 16) SAMSON CO.,LTD. + 3-4-15 YAHATA-CHO + Kanonji-City Kagawa 768-8602 + JP + 8C-1F-64 (hex) Innovative Signal Analysis 1BA000-1BAFFF (base 16) Innovative Signal Analysis 3301 E Renner Rd, Ste 200 @@ -41270,24 +41294,12 @@ A72000-A72FFF (base 16) First Design System Inc. Toronto Ontario M2H 3R1 CA -8C-1F-64 (hex) AEviso Video Solution Co., Ltd. -1E4000-1E4FFF (base 16) AEviso Video Solution Co., Ltd. - 15 F.-6, No. 716, Zhongzheng Rd., Zhonghe Dist., - New Taipei City n.a 235603 - TW - 8C-1F-64 (hex) Smart Dynamics SIA 576000-576FFF (base 16) Smart Dynamics SIA Ūdeles Amatciems Cēsu novads LV-4101 LV -8C-1F-64 (hex) Expromo Europe A/S -C39000-C39FFF (base 16) Expromo Europe A/S - Langdyssen 3 - Aarhus N 8200 - DK - 8C-1F-64 (hex) NEBERO SYSTEMS PRIVATE LIMTED 71C000-71CFFF (base 16) NEBERO SYSTEMS PRIVATE LIMTED Plot 691, Sector 82, Industrial Area, SAS Nagar @@ -41300,11 +41312,17 @@ E6B000-E6BFFF (base 16) Terratel Technology s.r.o. Benesov CZ 25601 CZ -8C-1F-64 (hex) SMITEC S.p.A. -E82000-E82FFF (base 16) SMITEC S.p.A. - Via Carlo Ceresa, 10 - San Giovanni Bianco Bergamo 24015 - IT +8C-1F-64 (hex) AEviso Video Solution Co., Ltd. +1E4000-1E4FFF (base 16) AEviso Video Solution Co., Ltd. + 15 F.-6, No. 716, Zhongzheng Rd., Zhonghe Dist., + New Taipei City n.a 235603 + TW + +8C-1F-64 (hex) Expromo Europe A/S +C39000-C39FFF (base 16) Expromo Europe A/S + Langdyssen 3 + Aarhus N 8200 + DK 8C-1F-64 (hex) I2V Systems Pvt. Ltd. 1E0000-1E0FFF (base 16) I2V Systems Pvt. Ltd. @@ -41324,11 +41342,11 @@ E82000-E82FFF (base 16) SMITEC S.p.A. England OL10 4HU GB -8C-1F-64 (hex) Mootek Technologies Private Limited -CEA000-CEAFFF (base 16) Mootek Technologies Private Limited - No.20, First Floor, East Jones Road,SaidapetChennai - Chennai Tamilnadu 600015 - IN +8C-1F-64 (hex) SMITEC S.p.A. +E82000-E82FFF (base 16) SMITEC S.p.A. + Via Carlo Ceresa, 10 + San Giovanni Bianco Bergamo 24015 + IT 8C-1F-64 (hex) Talius Services Pty Ltd 5D2000-5D2FFF (base 16) Talius Services Pty Ltd @@ -41336,6 +41354,18 @@ CEA000-CEAFFF (base 16) Mootek Technologies Private Limited Brisbane QLD 4009 AU +8C-1F-64 (hex) Mootek Technologies Private Limited +CEA000-CEAFFF (base 16) Mootek Technologies Private Limited + No.20, First Floor, East Jones Road,SaidapetChennai + Chennai Tamilnadu 600015 + IN + +8C-1F-64 (hex) Vojensky Technicky Ustav, s.p. +B51000-B51FFF (base 16) Vojensky Technicky Ustav, s.p. + Mladoboleslavska 944, Kbely, Praha 9Reg. n: 24272523VAT n.: CZ24272523 + Praha 19700 + CZ + 8C-1F-64 (hex) Private B94000-B94FFF (base 16) Private @@ -41357,12 +41387,6 @@ BA5000-BA5FFF (base 16) Campus Genevois de Haute Horlogerie Santana de Parnaiba SP 06543-320 BR -8C-1F-64 (hex) Vojensky Technicky Ustav, s.p. -B51000-B51FFF (base 16) Vojensky Technicky Ustav, s.p. - Mladoboleslavska 944, Kbely, Praha 9Reg. n: 24272523VAT n.: CZ24272523 - Praha 19700 - CZ - 8C-1F-64 (hex) BAOLIHDER CO.,LTD. 1CF000-1CFFFF (base 16) BAOLIHDER CO.,LTD. 5 F., No. 46, Ln. 394, Longjiang Rd., Zhongshan Dist., Taipei City 10474, Taiwan (R.O.C.) @@ -41375,8 +41399,20 @@ B51000-B51FFF (base 16) Vojensky Technicky Ustav, s.p. Tabor 39001 CZ +8C-1F-64 (hex) AK Automation +C48000-C48FFF (base 16) AK Automation + 105 /106, New Bharat Industrial Estate,LBS Marg, Lake Road, Bhandup West, Mumbai 400078 + MUMBAI Maharashtra 400078 + IN + 8C-1F-64 (hex) DEUTA Werke GmbH D2C000-D2CFFF (base 16) DEUTA Werke GmbH ET Bergisch Gladbach NRW 51465 DE + +8C-1F-64 (hex) Johnson and Johnson Medtech +668000-668FFF (base 16) Johnson and Johnson Medtech + 5490 Great America Pkwy + Santa Clara CA 95054 + US diff --git a/hwdb.d/pci.ids b/hwdb.d/pci.ids index ddacf4f196d3e..9beedbecb1e8a 100644 --- a/hwdb.d/pci.ids +++ b/hwdb.d/pci.ids @@ -1,8 +1,8 @@ # # List of PCI IDs # -# Version: 2026.03.03 -# Date: 2026-03-03 03:15:02 +# Version: 2026.03.10 +# Date: 2026-03-10 03:15:01 # # Maintained by Albert Pool, Martin Mares, and other volunteers from # the PCI ID Project at https://pci-ids.ucw.cz/. @@ -329,6 +329,9 @@ f130 NetFlex-3/P ThunderLAN 1.0 f150 NetFlex-3/P ThunderLAN 2.3 0e55 HaSoTec GmbH +# Real MediaTek ID is 0x14c3, this actually the USB VID and was used on at least the MT7621 +0e8d MediaTek Inc. (Wrong ID) + 0801 MT7621 PCIe Bridge 0eac SHF Communication Technologies AG 0008 Ethernet Powerlink Managing Node 01 0f62 Acrox Technologies Co., Ltd. @@ -4145,7 +4148,7 @@ 74b9 Aqua Vanjaram [Instinct MI325X VF] 74bd Aqua Vanjaram [Instinct MI300X HF] 7550 Navi 48 [Radeon RX 9070/9070 XT/9070 GRE] - 148c 2435 Reaper Radeon RX 9070 XT 16GB GDDR6 (RX9070XT 16G-A) + 148c 2435 Radeon RX 9070 XT 16GB 1da2 e490 Navi 48 XTX [Sapphire Pulse Radeon RX 9070 XT] 7551 Navi 48 [Radeon AI PRO R9700] 7590 Navi 44 [Radeon RX 9060 XT] @@ -7821,6 +7824,7 @@ 1020 ISP1020/1040 Fast-wide SCSI 1022 ISP1022 Fast-wide SCSI 1080 ISP1080 SCSI Host Adapter + 1077 0001 QLA1080 1216 ISP12160 Dual Channel Ultra3 SCSI Processor 101e 8471 QLA12160 on AMI MegaRAID 101e 8493 QLA12160 on AMI MegaRAID @@ -18692,6 +18696,19 @@ 1028 23a6 MTFDLBQ30T7THL-1BK1JABDA 1028 23a7 MTFDLAL61T4THL-1BK1JABDA 1028 23a8 MTFDLAL30T7THL-1BK1JABDA + 51cc 6600 ION NVMe SSD + 1028 2453 MTFDLBQ122T8QHF-1BQ1JABDA + 1028 2483 MTFDLBQ61T4QHF-1BQ1JABDA + 1028 2484 MTFDLBQ30T7QHF-1BQ1JABDA + 1028 2485 MTFDLBQ122T8QHF-1BQ1DFCDA + 1028 2486 MTFDLBQ61T4QHF-1BQ1DFCDA + 1028 2487 MTFDLBQ30T7QHF-1BQ1DFCDA + 1028 2489 MTFDLAL122T8QHF-1BQ1JABDA + 1028 248a MTFDLAL61T4QHF-1BQ1JABDA + 1028 248b MTFDLAL30T7QHF-1BQ1JABDA + 1028 248d MTFDLAL122T8QHF-1BQ1DFCDA + 1028 248e MTFDLAL61T4QHF-1BQ1DFCDA + 1028 248f MTFDLAL30T7QHF-1BQ1DFCDA 51cd 9650 PRO NVMe SSD 5404 2210 NVMe SSD [Cobain] 5405 2300 NVMe SSD [Santana] @@ -26688,6 +26705,7 @@ 33f3 IM2P33F3 NVMe SSD (DRAM-less) 33f4 IM2P33F4 NVMe SSD (DRAM-less) 33f8 IM2P33F8 series NVMe SSD (DRAM-less) + 413d SM2P41D3Q NVMe SSD (DRAM-less) 41c3 SM2P41C3 NVMe SSD (DRAM-less) 41c8 SM2P41C8 NVMe SSD (DRAM-less) 41d3 SM2P41D3 NVMe SSD (DRAM-less) @@ -28422,7 +28440,7 @@ # aka SED Systems 1e94 Calian SED 1e95 Solid State Storage Technology Corporation - 1000 XA1-311024 NVMe SSD M.2 + 1000 XA1 Series NVMe SSD M.2 (DRAM-less) 1001 CA6-8D512 NVMe SSD M.2 1002 NVMe SSD [3DNAND] 2.5" U.2 (LJ1) 1e95 1101 NVMe SSD [3DNAND] 2.5" U.2 (LJ1) @@ -28430,7 +28448,7 @@ 1003 CLR-8W512 NVMe SSD M.2 (DRAM-less) 1005 PLEXTOR M10P(GN) NVMe SSD M.2 1006 CA8 Series NVMe SSD M.2 - 1007 CL4-8D512 NVMe SSD M.2 (DRAM-less) + 1007 CL4 Series NVMe SSD M.2 (DRAM-less) 1008 CL5-8D512 NVMe SSD M.2 (DRAM-less) 100b XB2-311024 NVMe SSD M.2 (DRAM-less) 100c CL6 Series NVMe SSD M.2 (DRAM-less) @@ -28995,6 +29013,7 @@ 451b NN4LE NVMe SSD (DRAM-less) 4622 NEM-PAC NVMe SSD (DRAM-less) 1f32 Wuhan YuXin Semiconductor Co., Ltd. + ed55 U800G NVMe SSD 1f3f 3SNIC Ltd 2100 SSSHBA SAS/SATA HBA 1f3f 0120 HBA 32 Ports @@ -29695,6 +29714,7 @@ 20f6 Shenzhen Zhishi Network Technology Co., Ltd. 0001 MPU H1 20f9 Shenzhen Silicon Dynamic Networks Co., Ltd. +2105 Shanghai Timar Integrated Circuit Co., LTD 2106 ZCHL Technology Co., Ltd 0001 HL100 Accelerator Controller 2106 0001 HLC100 Accelerator Card @@ -39997,6 +40017,24 @@ d156 Core Processor Semaphore and Scratchpad Registers d157 Core Processor System Control and Status Registers d158 Core Processor Miscellaneous Registers + d323 Nova Lake PCD-H SPI Controller + d325 Nova Lake PCD-H Serial IO UART Controller #0 + d326 Nova Lake PCD-H Serial IO UART Controller #1 + d327 Nova Lake PCD-H Serial IO SPI Controller #0 + d330 Nova Lake PCD-H Serial IO SPI Controller #1 + d331 Nova Lake-H Thunderbolt 5 USB Controller + d333 Nova Lake-H Thunderbolt 5 NHI #0 + d347 Nova Lake PCD-H Serial IO SPI Controller #2 + d34e Nova Lake-H Thunderbolt 5 PCI Express Root Port #0 + d34f Nova Lake-H Thunderbolt 5 PCI Express Root Port #1 + d350 Nova Lake PCD-H Serial IO I2C Controller #4 + d351 Nova Lake PCD-H Serial IO I2C Controller #5 + d352 Nova Lake PCD-H Serial IO UART Controller #2 + d360 Nova Lake-H Thunderbolt 5 PCI Express Root Port #2 + d378 Nova Lake PCD-H Serial IO I2C Controller #0 + d379 Nova Lake PCD-H Serial IO I2C Controller #1 + d37a Nova Lake PCD-H Serial IO I2C Controller #2 + d37b Nova Lake PCD-H Serial IO I2C Controller #3 d431 Nova Lake-S Thunderbolt 5 USB Controller d433 Nova Lake-S Thunderbolt 5 NHI #0 d44e Nova Lake-S Thunderbolt 5 PCI Express Root Port #0 @@ -40008,6 +40046,15 @@ d743 NVL-HX d744 NVL-UL d745 NVL-HX + d750 NVL-P + d751 NVL-P + d752 NVL-P + d753 NVL-P + d754 NVL-P + d755 NVL-P + d756 NVL-P + d757 NVL-P + d75f NVL-P e202 Battlemage G21 [Intel Graphics] e20b Battlemage G21 [Arc B580] e20c Battlemage G21 [Arc B570] From 62e8b0f9b5d29e9706f84cd2c766cf6cd5e86b08 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 10 Mar 2026 17:12:12 +0000 Subject: [PATCH 0177/1296] NEWS: update contributors list --- NEWS | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/NEWS b/NEWS index 83825dd094114..cb7cf855a74f6 100644 --- a/NEWS +++ b/NEWS @@ -487,41 +487,43 @@ CHANGES WITH 260 in spe: * getty@.service gained an [Install] and must now be explicitly enabled to be active. - Contributions from: Adam Williamson, Adrian Vovk, Alessandro Astone, - Alexis-Emmanuel Haeringer, Allison Karlitskaya, Américo Monteiro, - André Paiusco, Anton Tiurin, Antonio Alvarez Feijoo, - Artur Kowalski, AshishKumar Mishra, Baurzhan Muftakhidinov, - Ben Boeckel, Betacentury, Bouke van der Bijl, Carlos Peón Costa, + Contributions from: A S Alam, Adam Williamson, Adrian Vovk, + Alessandro Astone, Alexis-Emmanuel Haeringer, Allison Karlitskaya, + Américo Monteiro, Andrii Zora, André Paiusco, Anton Tiurin, + Antonio Alvarez Feijoo, Arjun-C-S, Artur Kowalski, + AshishKumar Mishra, Baurzhan Muftakhidinov, Ben Boeckel, + Betacentury, Bouke van der Bijl, Carlos Peón Costa, Carolina Jubran, Cathy Hu, Chris Down, Chris Lindee, Christian Brauner, Christian Glombek, Christian Hesse, Christopher Cooper, Christopher Head, Daan De Meyer, Daniel Foster, Daniel Nylander, Daniel Rusek, David Santamaría Rogado, David Tardon, Derek J. Clark, Dirk Su, Dmitry V. Levin, Dmytro Bagrii, Efstathios Iosifidis, - Eisuke Kawashima, Ettore Atalan, Florian Klink, Franck Bui, - Frantisek Sumsal, Govind Venugopal, Graham Reed, Guiorgy, - Han Sol Jin, Hans de Goede, Heran Yang, IntenseWiggling, - Ivan Kruglov, Ivan Shapovalov, James Le Cuirot, Jeff Layton, - Jeremy Kerr, Jian Wen, Jim Spentzos, Julian Sparber, + Eisuke Kawashima, Ettore Atalan, Fergus Dall, Florian Klink, + Franck Bui, Frantisek Sumsal, Govind Venugopal, Graham Reed, + Guiorgy, Han Sol Jin, Hans de Goede, Heran Yang, IntenseWiggling, + Ivan Kruglov, Ivan Shapovalov, James Le Cuirot, Jan Kuparinen, + Jeff Layton, Jeremy Kerr, Jian Wen, Jim Spentzos, Julian Sparber, Jörg Behrmann, Kai Lüke, Lennart Poettering, Louis Stagg, Luca Boccassi, Lucas Werkmeister, Luiz Amaral, Léane GRASSER, - Marc Pervaz Boocha, Mario Limonciello, Mario Limonciello (AMD), - Matt Fleming, Matteo Croce, Matthijs Kooijman, Max Gautier, - Maximilian Bosch, Miao Wang, Michael Vogt, Michal Sekletár, - Mike Gilbert, Mike Yuan, Mikhail Novosyolov, Nandakumar Raghavan, - Nick Rosbrook, Nicolas Dorier, Oblivionsage, - Oleksandr Andrushchenko, Oğuz Ersen, Pablo Fraile Alonso, - Peter Oliver, Philip Withnall, Pontus Lundkvist, Popax21, - Rodrigo Campos, Ronan Pigott, Ryan Zeigler, Skye Soss, - Sriman Achanta, Tabis Kabis, Temuri Doghonadze, Thomas Weißschuh, + Malcolm Frazier, Marc Pervaz Boocha, Mario Limonciello, + Mario Limonciello (AMD), Martin Srebotnjak, Matt Fleming, + Matteo Croce, Matthijs Kooijman, Max Gautier, Maximilian Bosch, + Miao Wang, Michael Vogt, Michal Sekletár, Mike Gilbert, + Mike Yuan, Mikhail Novosyolov, Nandakumar Raghavan, Nick Rosbrook, + Nicolas Dorier, Oblivionsage, Oleksandr Andrushchenko, Oğuz Ersen, + Pablo Fraile Alonso, Peter Oliver, Philip Withnall, + Pontus Lundkvist, Popax21, Rodrigo Campos, Ronan Pigott, + Ryan Zeigler, Salvatore Cocuzza, Skye Soss, Sriman Achanta, + Tabis Kabis, Temuri Doghonadze, The-An0nym, Thomas Weißschuh, Thorsten Kukuk, Tobias Heider, Tobias Stoeckmann, Ulrich Ölmann, Usama Arif, Val Markovic, Vitaly Kuznetsov, Vunny Sodhi, Weixie Cui, Yaping Li, Yaron Shahrabani, Yu Watanabe, Yuri Chornoivan, ZauberNerd, Zbigniew Jędrzejewski-Szmek, Zongyuan He, andre4ik3, calm329, cdown, cyclopentane, francescoza6, gvenugo3, joo es, kiamvdd, lumingzh, naly zzwd, nikstur, novenary, - noxiouz, r-vdp, safforddr, scarlet-storm, sd416, seidlerv, smosia, - tuhaowen, zefr0x + noxiouz, patrick, ppkramer-hub, r-vdp, safforddr, scarlet-storm, sd416, + seidlerv, smosia, tuhaowen, zefr0x — Edinburgh, 2026/03/04 From a790339f8b3a55972f28f335875c415ccb57729f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 10 Mar 2026 18:16:37 +0100 Subject: [PATCH 0178/1296] update TODO --- TODO | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TODO b/TODO index 42506aba8b810..897ba35be9aaa 100644 --- a/TODO +++ b/TODO @@ -121,6 +121,8 @@ Deprecations and removals: Features: +* start making use of the new --graceful switch to util-linux' umount command + * make systemd work nicely without /bin/sh, logins and associated shell tools around - add a small unit that just prints "boot complete" which we can pull in wherever we pull in getty@1.service, but is conditioned on /bin/sh being From 5647a19a583b120c26962d9fef9beb18baf63428 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 9 Mar 2026 11:25:50 +0000 Subject: [PATCH 0179/1296] nsresourced: downgrade benign log message to debug This is very noisy as there's a dozen of these message every time it gets called, and it's not really an error but an expected situation, so downgrade from info to debug --- src/nsresourced/userns-restrict.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nsresourced/userns-restrict.c b/src/nsresourced/userns-restrict.c index c0d7f8a82daec..5012276c8268b 100644 --- a/src/nsresourced/userns-restrict.c +++ b/src/nsresourced/userns-restrict.c @@ -151,7 +151,7 @@ int userns_restrict_install( link = NULL; } else { linked = true; - log_info("userns-restrict BPF-LSM program %s already attached.", ps->name); + log_debug("userns-restrict BPF-LSM program %s already attached.", ps->name); } } From 6d7cb9a6b8361d2b327222bc12872a3676358bc3 Mon Sep 17 00:00:00 2001 From: Nandakumar Raghavan Date: Tue, 10 Mar 2026 06:54:33 +0000 Subject: [PATCH 0180/1296] networkd: fix for networkd crash when client sends Option 82 via SendOption= When a DHCP client uses SendOption=82:string:..., option_append() calls the SD_DHCP_OPTION_RELAY_AGENT_INFORMATION case which was written for the server relay path. It casts optval to sd_dhcp_server* and calls strlen() on its members, but optval is actually raw binary data from the client, causing SIGSEGV. The same is applicable when option 43 and option 77 are passed to SendOption. Fix by checking optlen > 0 and appending the option as a plain TLV, skipping the server-specific relay agent logic. --- src/libsystemd-network/dhcp-option.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/libsystemd-network/dhcp-option.c b/src/libsystemd-network/dhcp-option.c index a840b61462157..c78e3cdad9d72 100644 --- a/src/libsystemd-network/dhcp-option.c +++ b/src/libsystemd-network/dhcp-option.c @@ -59,6 +59,11 @@ static int option_append(uint8_t options[], size_t size, size_t *offset, break; case SD_DHCP_OPTION_USER_CLASS: { + /* When called with raw data (optlen > 0), e.g. from SendOption=, append as a plain TLV. + * The structured handling below expects optval to be a strv. */ + if (optlen > 0) + return dhcp_option_append_tlv(options, size, offset, code, optlen, optval); + size_t total = 0; if (strv_isempty((char **) optval)) @@ -103,6 +108,11 @@ static int option_append(uint8_t options[], size_t size, size_t *offset, break; case SD_DHCP_OPTION_VENDOR_SPECIFIC: { + /* When called with raw data (optlen > 0), e.g. from SendOption=, append as a plain TLV. + * The structured handling below expects optval to be an OrderedSet*. */ + if (optlen > 0) + return dhcp_option_append_tlv(options, size, offset, code, optlen, optval); + OrderedSet *s = (OrderedSet *) optval; struct sd_dhcp_option *p; size_t l = 0; @@ -125,6 +135,11 @@ static int option_append(uint8_t options[], size_t size, size_t *offset, break; } case SD_DHCP_OPTION_RELAY_AGENT_INFORMATION: { + /* When called with raw data (optlen > 0), e.g. from SendOption=, append as a plain TLV. + * The structured handling below expects optval to be an sd_dhcp_server*. */ + if (optlen > 0) + return dhcp_option_append_tlv(options, size, offset, code, optlen, optval); + sd_dhcp_server *server = (sd_dhcp_server *) optval; size_t current_offset = *offset + 2; From 9177786a5ce1fccaf2e00e4ebd0b959a1a2dce92 Mon Sep 17 00:00:00 2001 From: Marcel Leismann Date: Tue, 10 Mar 2026 15:58:28 +0000 Subject: [PATCH 0181/1296] po: Translated using Weblate (German) Currently translated at 99.6% (265 of 266 strings) Co-authored-by: Marcel Leismann Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/de/ Translation: systemd/main --- po/de.po | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/po/de.po b/po/de.po index 0b51c74f41b88..27ddf8e743edc 100644 --- a/po/de.po +++ b/po/de.po @@ -11,13 +11,13 @@ # Jarne Förster , 2024. # Weblate Translation Memory , 2024, 2025. # Anselm Schueler , 2024. -# Marcel Leismann , 2025. +# Marcel Leismann , 2025, 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2026-02-22 23:58+0000\n" -"Last-Translator: Ettore Atalan \n" +"PO-Revision-Date: 2026-03-10 15:58+0000\n" +"Last-Translator: Marcel Leismann \n" "Language-Team: German \n" "Language: de\n" @@ -25,7 +25,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.16\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1098,13 +1098,12 @@ msgid "DHCP server sends force renew message" msgstr "Der DHCP-Server sendet Nachricht zum erzwungenen Erneuern" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy -#| msgid "Authentication is required to send force renew message." msgid "" "Authentication is required to send a force renew message from the DHCP " "server." msgstr "" -"Legitimierung ist zum Versenden einer Zwangserneuerungsnachricht notwendig." +"Legitimierung ist zum Versenden einer Zwangserneuerungsnachricht vom DHCP " +"Server notwendig." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" From 7e0c26e0b2f02c91cbd2ec7c95bba83448a1cb97 Mon Sep 17 00:00:00 2001 From: naly zzwd Date: Tue, 10 Mar 2026 15:58:29 +0000 Subject: [PATCH 0182/1296] po: Translated using Weblate (Catalan) Currently translated at 100.0% (266 of 266 strings) Co-authored-by: naly zzwd Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/ca/ Translation: systemd/main --- po/ca.po | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/po/ca.po b/po/ca.po index 088c11445439c..01e66821c3f78 100644 --- a/po/ca.po +++ b/po/ca.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2026-03-03 08:58+0000\n" +"PO-Revision-Date: 2026-03-10 15:58+0000\n" "Last-Translator: naly zzwd \n" "Language-Team: Catalan \n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.16.1\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1059,12 +1059,12 @@ msgid "DHCP server sends force renew message" msgstr "El servidor DHCP envia un missatge de renovació forçada" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy -#| msgid "Authentication is required to send force renew message." msgid "" "Authentication is required to send a force renew message from the DHCP " "server." -msgstr "Es requereix autenticació per enviar el missatge de renovació forçada." +msgstr "" +"Es requereix autenticació per enviar un missatge de renovació forçada des " +"del servidor DHCP." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" From b15d769b382220c5bf1e1a59224a0711da11c278 Mon Sep 17 00:00:00 2001 From: Jesse Guo Date: Tue, 10 Mar 2026 15:58:29 +0000 Subject: [PATCH 0183/1296] po: Translated using Weblate (Chinese (Simplified) (zh_CN)) Currently translated at 100.0% (266 of 266 strings) Co-authored-by: Jesse Guo Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/zh_CN/ Translation: systemd/main --- po/zh_CN.po | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/po/zh_CN.po b/po/zh_CN.po index 470436279cc9c..ceeb96a72048f 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -10,14 +10,14 @@ # lumingzh , 2024, 2025, 2026. # z z <3397542367@qq.com>, 2025. # Hang Li , 2025. -# Jesse Guo , 2025. +# Jesse Guo , 2025, 2026. # Zongyuan He , 2025. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2026-02-27 16:58+0000\n" -"Last-Translator: lumingzh \n" +"PO-Revision-Date: 2026-03-10 15:58+0000\n" +"Last-Translator: Jesse Guo \n" "Language-Team: Chinese (Simplified) \n" "Language: zh_CN\n" @@ -25,7 +25,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.16.1\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -948,12 +948,10 @@ msgid "DHCP server sends force renew message" msgstr "DHCP 服务器发送强制更新消息" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy -#| msgid "Authentication is required to send force renew message." msgid "" "Authentication is required to send a force renew message from the DHCP " "server." -msgstr "发送强制更新消息需要认证。" +msgstr "从 DHCP 服务器发送强制续约消息需要认证。" #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" From f6857cfaf234ea9e3d5821b5c15cbcdb0605ca1b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 11 Mar 2026 03:23:44 +0900 Subject: [PATCH 0184/1296] fuzz: fix typo Follow-up for be0db50cadadb35fdbc117ed68e133f34604b97b. --- src/fuzz/fuzz-user-record.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fuzz/fuzz-user-record.c b/src/fuzz/fuzz-user-record.c index c520e6a03bb4e..ff08762ce0d45 100644 --- a/src/fuzz/fuzz-user-record.c +++ b/src/fuzz/fuzz-user-record.c @@ -31,7 +31,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { r = user_record_load(ur, v, USER_RECORD_LOAD_FULL|USER_RECORD_PERMISSIVE); if (r >= 0) { - /* We have a valid record, so let's excercise a couple more functions */ + /* We have a valid record, so let's exercise a couple more functions */ _cleanup_(user_record_unrefp) UserRecord *cloned = NULL; (void) user_record_clone(ur, USER_RECORD_LOAD_FULL, &cloned); From 9d15e09d5c5bf74eb9fefe1e9acf6e8422d5d200 Mon Sep 17 00:00:00 2001 From: Cyrus Xi Date: Tue, 10 Mar 2026 11:36:21 -0700 Subject: [PATCH 0185/1296] core/cgroup: fix TasksMaxScale percentage serialization (#41011) bus_cgroup_set_tasks_max_scale() used a hand-rolled percentage format that produced values ~10x too small (e.g., "TasksMax=4.0%" instead of "TasksMax=40.00%"). On daemon-reload, the incorrect value was re-read, silently reducing the effective TasksMax by ~10x and causing fork rejections on systems with high thread counts. Fix by using the existing PERMYRIAD macros, consistent with memory property handlers (MemoryMax, MemoryHigh, MemoryLow, etc.). Fixes: #41009 --- src/core/dbus-cgroup.c | 6 +-- test/units/TEST-19-CGROUP.tasks-max-scale.sh | 50 ++++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) create mode 100755 test/units/TEST-19-CGROUP.tasks-max-scale.sh diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c index dcb27f80f8c6a..0c71017f93a5f 100644 --- a/src/core/dbus-cgroup.c +++ b/src/core/dbus-cgroup.c @@ -1010,9 +1010,9 @@ static int bus_cgroup_set_tasks_max_scale( *p = (CGroupTasksMax) { v, UINT32_MAX }; /* .scale is not 0, so this is interpreted as v/UINT32_MAX. */ unit_invalidate_cgroup(u, CGROUP_MASK_PIDS); - uint32_t scaled = DIV_ROUND_UP((uint64_t) v * 100U, (uint64_t) UINT32_MAX); - unit_write_settingf(u, flags, name, "%s=%" PRIu32 ".%" PRIu32 "%%", "TasksMax", - scaled / 10, scaled % 10); + int scaled = UINT32_SCALE_TO_PERMYRIAD(v); + unit_write_settingf(u, flags, name, "TasksMax=" PERMYRIAD_AS_PERCENT_FORMAT_STR, + PERMYRIAD_AS_PERCENT_FORMAT_VAL(scaled)); } return 1; diff --git a/test/units/TEST-19-CGROUP.tasks-max-scale.sh b/test/units/TEST-19-CGROUP.tasks-max-scale.sh new file mode 100755 index 0000000000000..d9b2793cc046b --- /dev/null +++ b/test/units/TEST-19-CGROUP.tasks-max-scale.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -ex +set -o pipefail + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +testcase_tasks_max_scale_serialize() { + # Regression test for https://github.com/systemd/systemd/issues/41009 + # TasksMaxScale was serialized as 4.0% instead of 40.00%, causing 10x reduction on daemon-reload + + local unit="test-tasks-max.slice" + local unit_file="/run/systemd/system/${unit}" + local dropin_dir="/run/systemd/system.control/${unit}.d" + + # shellcheck disable=SC2329 + cleanup() ( + set +e + rm -rf "${dropin_dir}" + rm -f "${unit_file}" + systemctl daemon-reload + ) + trap cleanup RETURN + + printf '[Slice]\n' >"${unit_file}" + + systemctl daemon-reload + + # Set TasksMax=40% via D-Bus — exercises bus_cgroup_set_tasks_max_scale() + systemctl set-property --runtime "${unit}" TasksMax=40% + + # Verify drop-in file contains correct percentage (40.00%, not 4.0%) + grep -q '^TasksMax=40\.00%$' "${dropin_dir}/50-TasksMaxScale.conf" + + # Capture value before daemon-reload + local tasks_max_before + tasks_max_before=$(systemctl show -P TasksMax "${unit}") + + # Reload and verify value is preserved (the actual bug: value dropped 10x here) + systemctl daemon-reload + + local tasks_max_after + tasks_max_after=$(systemctl show -P TasksMax "${unit}") + assert_eq "${tasks_max_before}" "${tasks_max_after}" +} + +run_testcases From d9cd9b2a1d084963627ab5814cd5eb0734d10912 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 10 Mar 2026 19:56:09 +0100 Subject: [PATCH 0186/1296] ci: Update claude review prompt to insist on valid lines Hopefully fixes the failure in https://github.com/systemd/systemd/actions/runs/22904601370/job/66461528144. --- .github/workflows/claude-review.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 4b2a0dc5d638f..0f1a52a30542b 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -187,8 +187,10 @@ jobs: Each subagent must return a JSON array of issues: `[{"file": "path", "line": , "severity": "must-fix|suggestion|nit", "body": "..."}]` - `line` must be a line number from the NEW side of the diff (i.e. where the comment - should appear in the changed file after the patch is applied). + `line` must be a line number from the NEW side of the diff **that appears inside + a diff hunk** (i.e. a line that is shown in the patch output). GitHub's review + comment API rejects lines outside the diff context, so never reference lines + that are not visible in the patch. Each subagent MUST verify its findings before returning them: - For style/convention claims, check at least 3 existing examples in the codebase to confirm From 1254f4f2d25f40d1b5c7013b28883b3c507669f6 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 10 Mar 2026 19:57:11 +0100 Subject: [PATCH 0187/1296] ci: Don't make a single failed review comment fail the entire job Let's handle failure to post individual review comments gracefully. Reduces the impact of failures like in https://github.com/systemd/systemd/actions/runs/22904601370/job/66461528144. --- .github/workflows/claude-review.yml | 37 ++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 0f1a52a30542b..99095138a4923 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -295,24 +295,36 @@ jobs: * comments is handled by Claude in the prompt, so we just post whatever * it returns. Using individual comments (rather than a review) means * re-runs only add new comments instead of creating a whole new review. */ + let posted = 0; for (const c of comments) { console.log(` Posting comment on ${c.file}:${c.line}`); - await github.rest.pulls.createReviewComment({ - owner, - repo, - pull_number: prNumber, - commit_id: headSha, - path: c.file, - line: c.line, - body: `Claude: ${c.body}`, - }); + try { + await github.rest.pulls.createReviewComment({ + owner, + repo, + pull_number: prNumber, + commit_id: headSha, + path: c.file, + line: c.line, + body: `Claude: ${c.body}`, + }); + posted++; + } catch (e) { + /* GitHub rejects comments on lines outside the diff context. Log + * and continue — the tracking comment still contains all findings. */ + console.log(` Warning: failed to post comment on ${c.file}:${c.line}: ${e.message}`); + } } - if (comments.length > 0) - console.log(`Posted ${comments.length} inline comment(s).`); + if (posted > 0) + console.log(`Posted ${posted}/${comments.length} inline comment(s).`); + else if (comments.length > 0) + console.log(`Could not post any of ${comments.length} inline comment(s) — see warnings above.`); else console.log("No inline comments to post."); + const failed = comments.length > 0 && posted < comments.length; + /* Create or update the tracking comment. */ const MARKER = ""; if (!summary) @@ -360,3 +372,6 @@ jobs: } console.log("Tracking comment posted successfully."); + + if (failed) + core.setFailed(`Failed to post ${comments.length - posted}/${comments.length} inline comment(s).`); From 8f56e54a91ea01c202dd67b48e3367a93aa616bc Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 10 Mar 2026 19:58:17 +0100 Subject: [PATCH 0188/1296] ci: Add workflow url to tracking comment in claude-review workflow Simplifies debugging of failed claude-review workflows. --- .github/workflows/claude-review.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 99095138a4923..b9940edcb1c6e 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -327,10 +327,12 @@ jobs: /* Create or update the tracking comment. */ const MARKER = ""; + const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`; if (!summary) summary = "Claude review: no issues found :tada:\n\n" + MARKER; else if (!summary.includes(MARKER)) summary = summary.replace(/\n/, `\n${MARKER}\n`); + summary += `\n\n[Workflow run](${runUrl})`; /* Find an existing tracking comment. */ const {data: issueComments} = await github.rest.issues.listComments({ @@ -344,7 +346,10 @@ jobs: if (existing) { const commentUrl = existing.html_url; - if (existing.body === summary) { + /* Strip the workflow-run footer before comparing so that a new run + * URL alone doesn't count as a change. */ + const stripRunLink = (s) => s.replace(/\n\n\[Workflow run\]\([^)]*\)$/, ""); + if (stripRunLink(existing.body) === stripRunLink(summary)) { console.log(`Tracking comment ${existing.id} is unchanged.`); await github.rest.issues.createComment({ owner, From 60b3603b2da81b43e983aff201ecb8fb41500220 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 10 Mar 2026 20:19:41 +0100 Subject: [PATCH 0189/1296] ci: Create claude review tracking comment before starting review Let's create a comment to let the user know that the review is in progress and then update that comment with the actual review later. --- .github/workflows/claude-review.yml | 158 +++++++++++++++++----------- 1 file changed, 98 insertions(+), 60 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index b9940edcb1c6e..13bc9edd4fbf6 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -2,9 +2,10 @@ # Mention @claude in any PR comment to request a review. Claude authenticates # via AWS Bedrock using OIDC — no long-lived API keys required. # -# Architecture: The workflow is split into two jobs for least-privilege: -# 1. "review" — runs Claude with read-only permissions, produces structured JSON -# 2. "post" — reads the JSON and posts comments to the PR with write permissions +# Architecture: The workflow is split into three jobs for least-privilege: +# 1. "setup" — posts/updates a "reviewing…" tracking comment (write permissions) +# 2. "review" — runs Claude with read-only permissions, produces structured JSON +# 3. "post" — reads the JSON and posts comments to the PR (write permissions) name: Claude Review @@ -22,7 +23,7 @@ concurrency: group: claude-review-${{ github.event.pull_request.number || github.event.issue.number }} jobs: - review: + setup: runs-on: ubuntu-latest env: PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} @@ -41,26 +42,83 @@ jobs: permissions: contents: read - id-token: write # Authenticate with AWS via OIDC - actions: read + pull-requests: write outputs: - structured_output: ${{ steps.claude.outputs.structured_output }} pr_number: ${{ steps.pr.outputs.number }} head_sha: ${{ steps.pr.outputs.head_sha }} + comment_id: ${{ steps.tracking.outputs.comment_id }} steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - name: Resolve PR metadata id: pr env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | echo "number=$PR_NUMBER" >> "$GITHUB_OUTPUT" - gh pr view "$PR_NUMBER" --json headRefOid --jq '.headRefOid' | \ + gh pr view --repo "${{ github.repository }}" "$PR_NUMBER" --json headRefOid --jq '.headRefOid' | \ xargs -I{} echo "head_sha={}" >> "$GITHUB_OUTPUT" + - name: Create or update tracking comment + id: tracking + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const prNumber = parseInt(process.env.PR_NUMBER, 10); + const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`; + const MARKER = ""; + + const {data: issueComments} = await github.rest.issues.listComments({ + owner, + repo, + issue_number: prNumber, + per_page: 100, + }); + + const existing = issueComments.find((c) => c.body && c.body.includes(MARKER)); + + let commentId; + if (existing) { + console.log(`Updating existing tracking comment ${existing.id}.`); + /* Prepend a re-reviewing banner but keep the previous review visible. */ + const prevBody = existing.body.replace(/\n\n\[Workflow run\]\([^)]*\)$/, ""); + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: existing.id, + body: `> **Claude is re-reviewing this PR…** ([workflow run](${runUrl}))\n\n${prevBody}`, + }); + commentId = existing.id; + } else { + console.log("Creating new tracking comment."); + const {data: created} = await github.rest.issues.createComment({ + owner, + repo, + issue_number: prNumber, + body: `Claude is reviewing this PR… ([workflow run](${runUrl}))\n\n${MARKER}`, + }); + commentId = created.id; + } + + core.setOutput("comment_id", commentId); + + review: + runs-on: ubuntu-latest + needs: setup + + permissions: + contents: read + id-token: write # Authenticate with AWS via OIDC + actions: read + + outputs: + structured_output: ${{ steps.claude.outputs.structured_output }} + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 with: @@ -139,8 +197,8 @@ jobs: --json-schema '${{ env.REVIEW_SCHEMA }}' prompt: | REPO: ${{ github.repository }} - PR NUMBER: ${{ steps.pr.outputs.number }} - HEAD SHA: ${{ steps.pr.outputs.head_sha }} + PR NUMBER: ${{ needs.setup.outputs.pr_number }} + HEAD SHA: ${{ needs.setup.outputs.head_sha }} You are a code reviewer for the ${{ github.repository }} project. Review this pull request and produce a structured JSON result containing your review comments. Do NOT attempt @@ -151,14 +209,14 @@ jobs: Use the GitHub MCP server tools to fetch PR data. For all tools, pass owner `${{ github.repository_owner }}`, repo `${{ github.event.repository.name }}`, - and pullNumber/issue_number ${{ steps.pr.outputs.number }}: + and pullNumber/issue_number ${{ needs.setup.outputs.pr_number }}: - `mcp__github__get_pull_request_diff` to get the PR diff - `mcp__github__get_pull_request` to get the PR title, body, and metadata - `mcp__github__get_pull_request_comments` to get top-level PR comments - `mcp__github__get_pull_request_reviews` to get PR reviews Also fetch issue comments using `mcp__github__get_issue_comments` with - issue_number ${{ steps.pr.outputs.number }}. + issue_number ${{ needs.setup.outputs.pr_number }}. Look for an existing tracking comment (containing ``) in the issue comments. If one exists, you will use it as the basis for @@ -250,8 +308,8 @@ jobs: post: runs-on: ubuntu-latest - needs: review - if: always() && needs.review.result == 'success' + needs: [setup, review] + if: always() && needs.setup.result == 'success' permissions: pull-requests: write @@ -261,14 +319,31 @@ jobs: uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea env: STRUCTURED_OUTPUT: ${{ needs.review.outputs.structured_output }} - PR_NUMBER: ${{ needs.review.outputs.pr_number }} - HEAD_SHA: ${{ needs.review.outputs.head_sha }} + PR_NUMBER: ${{ needs.setup.outputs.pr_number }} + HEAD_SHA: ${{ needs.setup.outputs.head_sha }} + COMMENT_ID: ${{ needs.setup.outputs.comment_id }} with: script: | const owner = context.repo.owner; const repo = context.repo.repo; const prNumber = parseInt(process.env.PR_NUMBER, 10); const headSha = process.env.HEAD_SHA; + const commentId = parseInt(process.env.COMMENT_ID, 10); + const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`; + const MARKER = ""; + + /* If the review job failed or was cancelled, update the tracking + * comment to reflect that and bail out. */ + if ("${{ needs.review.result }}" !== "success") { + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: commentId, + body: `Claude review failed — see [workflow run](${runUrl}) for details.\n\n${MARKER}`, + }); + core.setFailed("Review job did not succeed."); + return; + } /* Parse Claude's structured output. */ const raw = process.env.STRUCTURED_OUTPUT; @@ -325,58 +400,21 @@ jobs: const failed = comments.length > 0 && posted < comments.length; - /* Create or update the tracking comment. */ - const MARKER = ""; - const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`; + /* Update the tracking comment with Claude's summary. */ if (!summary) summary = "Claude review: no issues found :tada:\n\n" + MARKER; else if (!summary.includes(MARKER)) summary = summary.replace(/\n/, `\n${MARKER}\n`); summary += `\n\n[Workflow run](${runUrl})`; - /* Find an existing tracking comment. */ - const {data: issueComments} = await github.rest.issues.listComments({ + await github.rest.issues.updateComment({ owner, repo, - issue_number: prNumber, - per_page: 100, + comment_id: commentId, + body: summary, }); - const existing = issueComments.find((c) => c.body && c.body.includes(MARKER)); - - if (existing) { - const commentUrl = existing.html_url; - /* Strip the workflow-run footer before comparing so that a new run - * URL alone doesn't count as a change. */ - const stripRunLink = (s) => s.replace(/\n\n\[Workflow run\]\([^)]*\)$/, ""); - if (stripRunLink(existing.body) === stripRunLink(summary)) { - console.log(`Tracking comment ${existing.id} is unchanged.`); - await github.rest.issues.createComment({ - owner, - repo, - issue_number: prNumber, - body: `Claude re-reviewed this PR — no changes to the [tracking comment](${commentUrl}).`, - }); - } else { - console.log(`Updating existing tracking comment ${existing.id}.`); - await github.rest.issues.updateComment({ - owner, - repo, - comment_id: existing.id, - body: summary, - }); - } - } else { - console.log("Creating new tracking comment."); - await github.rest.issues.createComment({ - owner, - repo, - issue_number: prNumber, - body: summary, - }); - } - - console.log("Tracking comment posted successfully."); + console.log("Tracking comment updated successfully."); if (failed) core.setFailed(`Failed to post ${comments.length - posted}/${comments.length} inline comment(s).`); From b62b4c697395c9202968a11ee25394c25a832b11 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 10 Mar 2026 20:39:56 +0100 Subject: [PATCH 0190/1296] ci: Give claude review read-only access to issues and pull requests For retrieving previous review comments and extra details from issues linked in the PR. --- .github/workflows/claude-review.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 13bc9edd4fbf6..e1246d3072576 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -110,7 +110,9 @@ jobs: permissions: contents: read - id-token: write # Authenticate with AWS via OIDC + pull-requests: read # Fetch PR comments and reviews + issues: read # Fetch issue comments + id-token: write # Authenticate with AWS via OIDC actions: read outputs: From 55dd233797c42d325ab2f140ef4ea05d6773c366 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 10 Mar 2026 20:43:22 +0100 Subject: [PATCH 0191/1296] ci: Update prompt to include a list of errors To make debugging the review workflow easier, have claude include an overview of errors encountered in the review summary. --- .github/workflows/claude-review.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index e1246d3072576..0f151add14741 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -292,6 +292,13 @@ jobs: Omit empty sections. Each checkbox item must correspond to an entry in `comments`. If there are no issues at all, write a short message saying the PR looks good. + Throughout all phases, track any errors that prevented you from doing + your job fully: permission denials (403, "Resource not accessible by + integration"), tools that were not available, rate limits, or any other + failures that degraded the review quality. If there were any, append a + `### Errors` section listing each failed tool/action and the error + message, so maintainers can fix the workflow configuration. + **If an existing tracking comment was found (subsequent run):** Use the existing comment as the starting point. Preserve the order and wording of all existing items. Then apply these updates: From 45e4e035f7f2dc30e841ec3f218f90bb8f2e55d8 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 10 Mar 2026 20:55:40 +0100 Subject: [PATCH 0192/1296] ci: Only trigger claude review workflow on pr comments The trigger for regular pr and issue comments is the same, so we have to make sure we skip if it's an issue comment and not a pr comment. --- .github/workflows/claude-review.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 0f151add14741..c60bd3fac8e2c 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -31,6 +31,7 @@ jobs: if: | github.repository_owner == 'systemd' && ((github.event_name == 'issue_comment' && + github.event.issue.pull_request && contains(github.event.comment.body, '@claude') && contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association)) || (github.event_name == 'pull_request_review_comment' && From 9a70fdcb741fc62af82427696c05560f4d70e4de Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 10 Mar 2026 21:35:13 +0100 Subject: [PATCH 0193/1296] ci: Add one more mcp tool to claude-review workflow --- .github/workflows/claude-review.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index c60bd3fac8e2c..d0c0632a14e0a 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -191,6 +191,7 @@ jobs: mcp__github__get_pull_request_files, mcp__github__get_pull_request_reviews, mcp__github__get_pull_request_comments, + mcp__github__get_pull_request_review_comments, mcp__github__get_pull_request_status, mcp__github__get_issue_comments, mcp__github_ci__get_ci_status, From 16325b35fa6ecb25f66534a562583ce3b96d52f3 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 6 Mar 2026 19:32:35 +0000 Subject: [PATCH 0194/1296] udev: check for invalid chars in various fields received from the kernel --- src/udev/dmi_memory_id/dmi_memory_id.c | 3 ++- src/udev/scsi_id/scsi_id.c | 5 +++-- src/udev/udev-builtin-net_id.c | 9 +++++++++ src/udev/v4l_id/v4l_id.c | 5 ++++- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/udev/dmi_memory_id/dmi_memory_id.c b/src/udev/dmi_memory_id/dmi_memory_id.c index 9475edcd5418c..a2f2ed726312f 100644 --- a/src/udev/dmi_memory_id/dmi_memory_id.c +++ b/src/udev/dmi_memory_id/dmi_memory_id.c @@ -52,6 +52,7 @@ #include "string-util.h" #include "udev-util.h" #include "unaligned.h" +#include "utf8.h" #define SUPPORTED_SMBIOS_VER 0x030300 @@ -186,7 +187,7 @@ static void dmi_memory_device_string( str = strdupa_safe(dmi_string(h, s)); str = strstrip(str); - if (!isempty(str)) + if (!isempty(str) && utf8_is_valid(str) && !string_has_cc(str, /* ok= */ NULL)) printf("MEMORY_DEVICE_%u_%s=%s\n", slot_num, attr_suffix, str); } diff --git a/src/udev/scsi_id/scsi_id.c b/src/udev/scsi_id/scsi_id.c index 5216455f41d59..b57f31b5935f4 100644 --- a/src/udev/scsi_id/scsi_id.c +++ b/src/udev/scsi_id/scsi_id.c @@ -20,6 +20,7 @@ #include "strv.h" #include "strxcpyx.h" #include "udev-util.h" +#include "utf8.h" static const struct option options[] = { { "device", required_argument, NULL, 'd' }, @@ -441,8 +442,8 @@ static int scsi_id(char *maj_min_dev) { } if (dev_scsi.tgpt_group[0] != '\0') printf("ID_TARGET_PORT=%s\n", dev_scsi.tgpt_group); - if (dev_scsi.unit_serial_number[0] != '\0') - printf("ID_SCSI_SERIAL=%s\n", dev_scsi.unit_serial_number); + if (dev_scsi.unit_serial_number[0] != '\0' && utf8_is_valid(dev_scsi.unit_serial_number) && !string_has_cc(dev_scsi.unit_serial_number, /* ok= */ NULL)) + printf("ID_SCSI_SERIAL=%s\n", serial_str); goto out; } diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c index d93849f332603..f2a69a75d7b76 100644 --- a/src/udev/udev-builtin-net_id.c +++ b/src/udev/udev-builtin-net_id.c @@ -31,6 +31,7 @@ #include "stdio-util.h" #include "string-util.h" #include "udev-builtin.h" +#include "utf8.h" #define ONBOARD_14BIT_INDEX_MAX ((1U << 14) - 1) #define ONBOARD_16BIT_INDEX_MAX ((1U << 16) - 1) @@ -193,6 +194,9 @@ static int get_port_specifier(sd_device *dev, char **ret) { } } + if (!utf8_is_valid(phys_port_name) || string_has_cc(phys_port_name, /* ok= */ NULL)) + return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid phys_port_name"); + /* Otherwise, use phys_port_name as is. */ buf = strjoin("n", phys_port_name); if (!buf) @@ -297,6 +301,9 @@ static int names_pci_onboard_label(UdevEvent *event, sd_device *pci_dev, const c if (r < 0) return log_device_debug_errno(pci_dev, r, "Failed to get PCI onboard label: %m"); + if (!utf8_is_valid(label) || string_has_cc(label, /* ok= */ NULL)) + return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid label"); + char str[ALTIFNAMSIZ]; if (snprintf_ok(str, sizeof str, "%s%s", naming_scheme_has(NAMING_LABEL_NOPREFIX) ? "" : prefix, @@ -1247,6 +1254,8 @@ static int names_netdevsim(UdevEvent *event, const char *prefix) { if (isempty(phys_port_name)) return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EOPNOTSUPP), "The 'phys_port_name' attribute is empty."); + if (!utf8_is_valid(phys_port_name) || string_has_cc(phys_port_name, /* ok= */ NULL)) + return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid phys_port_name"); char str[ALTIFNAMSIZ]; if (snprintf_ok(str, sizeof str, "%si%un%s", prefix, addr, phys_port_name)) diff --git a/src/udev/v4l_id/v4l_id.c b/src/udev/v4l_id/v4l_id.c index 1e374c393c347..dc4d41af2bfab 100644 --- a/src/udev/v4l_id/v4l_id.c +++ b/src/udev/v4l_id/v4l_id.c @@ -14,6 +14,8 @@ #include "fd-util.h" #include "log.h" #include "main-func.h" +#include "string-util.h" +#include "utf8.h" static const char *arg_device = NULL; @@ -72,7 +74,8 @@ static int run(int argc, char *argv[]) { int capabilities; printf("ID_V4L_VERSION=2\n"); - printf("ID_V4L_PRODUCT=%s\n", v2cap.card); + if (utf8_is_valid((char *)v2cap.card) && !string_has_cc((char *)v2cap.card, /* ok= */ NULL)) + printf("ID_V4L_PRODUCT=%s\n", v2cap.card); printf("ID_V4L_CAPABILITIES=:"); if (v2cap.capabilities & V4L2_CAP_DEVICE_CAPS) From 69e4ba69d689748d1d515c5a8d063073df3c5821 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 6 Mar 2026 19:42:16 +0000 Subject: [PATCH 0195/1296] udev: ensure there is space for trailing NUL before calling sprintf sprintf will write 5 characters, as it adds a trailing NUL byte. Reported on yeswehack.com as: YWH-PGM9780-62 Follow-up for 8cfcf9980a3 --- src/basic/device-nodes.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/basic/device-nodes.c b/src/basic/device-nodes.c index 8d4e38ec0638b..37af18fce419c 100644 --- a/src/basic/device-nodes.c +++ b/src/basic/device-nodes.c @@ -6,6 +6,7 @@ #include "device-nodes.h" #include "path-util.h" +#include "stdio-util.h" #include "string-util.h" #include "utf8.h" @@ -38,10 +39,10 @@ int encode_devnode_name(const char *str, char *str_enc, size_t len) { } else if (str[i] == '\\' || !allow_listed_char_for_devnode(str[i], NULL)) { - if (len-j < 4) + if (len-j < 5) return -EINVAL; - sprintf(&str_enc[j], "\\x%02x", (unsigned char) str[i]); + assert_se(snprintf_ok(&str_enc[j], 5, "\\x%02x", (unsigned char) str[i])); j += 4; } else { From 45a200cd751fae382f4145760cf84fd181db1319 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 6 Mar 2026 20:25:05 +0000 Subject: [PATCH 0196/1296] udev: ensure tag parsing stays within bounds This cannot actually happen, but add a safety check nonetheless. Reported on yeswehack.com as: YWH-PGM9780-43 Follow-up for d7867b31836173d1a943ecb1cab6484536126411 --- src/udev/udev-builtin-path_id.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/udev/udev-builtin-path_id.c b/src/udev/udev-builtin-path_id.c index 4db20e4a13f9c..cdd8da3203fea 100644 --- a/src/udev/udev-builtin-path_id.c +++ b/src/udev/udev-builtin-path_id.c @@ -654,7 +654,7 @@ static void add_id_tag(UdevEvent *event, const char *path) { size_t i = 0; /* compose valid udev tag name */ - for (const char *p = path; *p; p++) { + for (const char *p = path; *p && i < sizeof(tag) - 1; p++) { if (ascii_isdigit(*p) || ascii_isalpha(*p) || *p == '-') { From 9a0c83f7e38c8cf9c8157c7d6d9e80d20c0045e9 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 10 Mar 2026 21:51:24 +0100 Subject: [PATCH 0197/1296] sd-boot: fix silly copy/paste mistake This fixes a very silly copy/paste mistake in 3f95881 - sorry for that and thanks to Raul Tambre for reporting. Closes https://github.com/systemd/systemd/issues/40844 --- src/boot/boot.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/boot/boot.c b/src/boot/boot.c index db5cfd989df23..4a7e616faa688 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -1049,9 +1049,9 @@ static EFI_STATUS config_timeout_sec_from_string(const char *value, uint64_t *ds if (streq8(value, "menu-disabled")) *dst = TIMEOUT_MENU_DISABLED; else if (streq8(value, "menu-force")) - *dst = TIMEOUT_MENU_DISABLED; + *dst = TIMEOUT_MENU_FORCE; else if (streq8(value, "menu-hidden")) - *dst = TIMEOUT_MENU_DISABLED; + *dst = TIMEOUT_MENU_HIDDEN; else { uint64_t u; if (!parse_number8(value, &u, NULL) || u > TIMEOUT_TYPE_MAX) From 59e6b68fea4bb6b02f017cff0263cc6a90972498 Mon Sep 17 00:00:00 2001 From: Rodrigo Campos Date: Fri, 6 Mar 2026 16:54:02 +0100 Subject: [PATCH 0198/1296] dissect: Don't bypass blkid *_lookup_value() to decide USAGE After commit "core: reuse existing dm-verity device for single filesystem images pinned by policy" (0bd766553cbf), when I attach a portable image (erofs+verity) and try to start a service, it fails with: Partition root discovered with policy 'unprotected' but 'verity+read-only-on+growfs-off+erofs' was required, refusing. Failed to dissect image: Operation not possible due to RF-kill The image does have verity, in fact the RootImagePolicy= field was added automatically. The inconsistency between what is found at attach vs when starting the service comes from the fact that dissect_image() is called with a different policy as parameter and the recent shortcut added. At attach we do this: dissect_image(policy="*") partition_policy_determine_fstype(policy) partition_policy_flags_to_string(...) // mask is 0, returns 0 -> returns NULL // root_fstype_string is not set if (root_fstype_string) // false sym_blkid_probe_lookup_value()... At start, as we do have the policy set, we do: dissect_image(policy="root=verity+...+erofs:root-verity=...") partition_policy_determine_fstype(policy) partition_policy_flags_to_string(...) // returns 1 -> sets root_fstype_string to "erofs" if (root_fstype_string) // true usage = "filesystem" Then, the service is blocked to start with the aforementioned error. It's correct for partition_policy_determine_fstype() to set erofs in that case, and other callers seem to expect this behavior on similar cases, but what is not correct is to assume that this means it's a filesystem. Usage in this case should still be unset. Let's just always do the lookup, as that gets us the correct answer reliably and we already did the slow part that is the probe. The call to `sym_blkid_do_safeprobe()` is a few lines above. The call to the lookup function isn't very expensive. blkid_probe_lookup_value()[1] calls __blkid_probe_lookup_value(), which searches on a list[2], IIUC in memory and no IO is used. It's a linear search of the property. [1]: https://github.com/util-linux/util-linux/blob/0fd08f19e7a3bc37509491d06a664cfb47be7cd8/libblkid/src/probe.c#L2299 [2]: https://github.com/util-linux/util-linux/blob/0fd08f19e7a3bc37509491d06a664cfb47be7cd8/libblkid/src/probe.c#L2343 --- src/shared/dissect-image.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 9b9c3af2529f5..6c73a12548caf 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -1149,10 +1149,7 @@ static int dissect_image( /* If flags permit this, also allow using non-partitioned single-filesystem images */ - if (root_fstype_string) - usage = encrypted ? "crypto" : "filesystem"; - else - (void) sym_blkid_probe_lookup_value(b, "USAGE", &usage, NULL); + (void) sym_blkid_probe_lookup_value(b, "USAGE", &usage, NULL); if (STRPTR_IN_SET(usage, "filesystem", "crypto")) { _cleanup_free_ char *t = NULL; const char *fstype = NULL; From b27e17d762236ed27f1cac2cf1286edabfa48fdb Mon Sep 17 00:00:00 2001 From: Rodrigo Campos Date: Mon, 9 Mar 2026 14:38:54 +0100 Subject: [PATCH 0199/1296] portable: Test pinning a single fstype on an GPT image with verity This tests a GPT image with a single fstype using verity. This was broken and fixed by the previous commit. --- test/units/TEST-50-DISSECT.dissect.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/units/TEST-50-DISSECT.dissect.sh b/test/units/TEST-50-DISSECT.dissect.sh index 07e0d1a8a0488..7a68e62fb0067 100755 --- a/test/units/TEST-50-DISSECT.dissect.sh +++ b/test/units/TEST-50-DISSECT.dissect.sh @@ -243,6 +243,12 @@ systemd-run --wait -P \ -p RootImagePolicy='root=signed+lol:wut=wat+signed' \ -p MountAPIVFS=yes \ cat /usr/lib/os-release | grep -F "MARKER=1" >/dev/null +# A policy pinning a single fstype on a GPT image should still use verity. +systemd-run --wait -P \ + -p RootImage="$MINIMAL_IMAGE.gpt" \ + -p RootImagePolicy='root=verity+squashfs' \ + -p MountAPIVFS=yes \ + cat /usr/lib/os-release | grep -F "MARKER=1" >/dev/null (! systemd-run --wait -P \ -p RootImage="$MINIMAL_IMAGE.gpt" \ -p RootHash="$MINIMAL_IMAGE_ROOTHASH" \ From 6eaa5ba32231290971823d0ddf73bc712c384bcc Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 9 Mar 2026 15:18:24 +0900 Subject: [PATCH 0200/1296] sd-dhcp-client: several fixlets for sending RELEASE or DECLINE - Extract common logic to client_send_release_or_decline(). - Do not send DECLINE message on BOOTP protocol. - Drop redundant assignment of chaddr, as it is already set by client_message_init() -> dhcp_message_init(). - Do not assign acquired address in ciaddr field of DECLINE message, but use Requested IP Address option. - Broadcast DECLINE message, rather than unicast. - Set server identifier in both cases. Fixes #39299. --- src/libsystemd-network/sd-dhcp-client.c | 128 ++++++++++++++---------- 1 file changed, 73 insertions(+), 55 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 8c373146f6185..4b1cdd0b86352 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -876,7 +876,7 @@ static int client_message_init( MAY contain the Parameter Request List option. */ /* NOTE: in case that there would be an option to do not send * any PRL at all, the size should be checked before sending */ - if (!set_isempty(client->req_opts) && type != DHCP_RELEASE) { + if (!set_isempty(client->req_opts) && IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST)) { _cleanup_free_ uint8_t *opts = NULL; size_t n_opts, i = 0; void *val; @@ -2356,70 +2356,93 @@ int sd_dhcp_client_start(sd_dhcp_client *client) { return r; } -static int client_send_release(sd_dhcp_client *client) { - _cleanup_free_ DHCPPacket *release = NULL; - size_t optoffset, optlen; +static int client_send_release_or_decline(sd_dhcp_client *client, uint8_t type) { int r; - assert(client); + assert(IN_SET(type, DHCP_RELEASE, DHCP_DECLINE)); - if (!client->send_release) - return 0; /* disabled */ + if (!sd_dhcp_client_is_running(client) || !client->lease || client->bootp) + return 0; /* there is nothing to release or decline */ - if (!client->lease || client->bootp) - return 0; /* there is nothing to be released */ + const char *name = type == DHCP_RELEASE ? "RELEASE" : "DECLINE"; - r = client_message_init(client, DHCP_RELEASE, &release, &optlen, &optoffset); + _cleanup_free_ DHCPPacket *packet = NULL; + size_t optoffset, optlen; + r = client_message_init(client, type, &packet, &optlen, &optoffset); if (r < 0) - return r; + return log_dhcp_client_errno(client, r, "Failed to initialize DHCP %s message: %m", name); - /* Fill up release IP and MAC */ - release->dhcp.ciaddr = client->lease->address; - memcpy(&release->dhcp.chaddr, client->hw_addr.bytes, client->hw_addr.length); + /* See RFC 2131, Table 5 */ + switch (type) { + case DHCP_RELEASE: + /* On release, the acquired address must be set in ciaddr. */ + packet->dhcp.ciaddr = client->lease->address; + break; - r = dhcp_option_append(&release->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_END, 0, NULL); + case DHCP_DECLINE: + /* On decline, the acquired address must be set in Requested IP Address option. */ + r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, /* overload= */ 0, + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, + 4, &client->lease->address); + if (r < 0) + return log_dhcp_client_errno( + client, r, + "Failed to append Requested IP Address option to DHCP %s message: %m", + name); + break; + + default: + assert_not_reached(); + } + + /* In both cases, the server identifier must be set. */ + r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, /* overload= */ 0, + SD_DHCP_OPTION_SERVER_IDENTIFIER, + 4, &client->lease->server_address); if (r < 0) - return r; + return log_dhcp_client_errno( + client, r, + "Failed to append Server Identifier option to DHCP %s message: %m", + name); - r = dhcp_network_send_udp_socket(client->fd, - client->lease->server_address, - client->server_port, - &release->dhcp, - sizeof(DHCPMessage) + optoffset); + r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, /* overload= */ 0, + SD_DHCP_OPTION_END, /* optlen= */ 0, /* optval= */ NULL); if (r < 0) - return r; + return log_dhcp_client_errno( + client, r, + "Failed to finalize DHCP %s message: %m", + name); + + switch (type) { + case DHCP_RELEASE: + r = dhcp_network_send_udp_socket( + client->fd, + client->lease->server_address, + client->server_port, + &packet->dhcp, + sizeof(DHCPMessage) + optoffset); + break; + case DHCP_DECLINE: + r = dhcp_client_send_raw(client, packet, sizeof(DHCPPacket) + optoffset); + break; + default: + assert_not_reached(); + } + if (r < 0) + return log_dhcp_client_errno( + client, r, + "Failed to send DHCP %s message: %m", + name); - log_dhcp_client(client, "RELEASE"); - return 0; + log_dhcp_client(client, "%s", name); + return 1; /* sent */ } int sd_dhcp_client_send_decline(sd_dhcp_client *client) { - _cleanup_free_ DHCPPacket *release = NULL; - size_t optoffset, optlen; int r; - if (!sd_dhcp_client_is_running(client) || !client->lease) - return 0; /* do nothing */ - - r = client_message_init(client, DHCP_DECLINE, &release, &optlen, &optoffset); - if (r < 0) - return r; - - release->dhcp.ciaddr = client->lease->address; - memcpy(&release->dhcp.chaddr, client->hw_addr.bytes, client->hw_addr.length); - - r = dhcp_option_append(&release->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_END, 0, NULL); - if (r < 0) - return r; - - r = dhcp_network_send_udp_socket(client->fd, - client->lease->server_address, - client->server_port, - &release->dhcp, - sizeof(DHCPMessage) + optoffset); - if (r < 0) + r = client_send_release_or_decline(client, DHCP_DECLINE); + if (r <= 0) return r; log_dhcp_client(client, "DECLINE"); @@ -2434,20 +2457,15 @@ int sd_dhcp_client_send_decline(sd_dhcp_client *client) { } int sd_dhcp_client_stop(sd_dhcp_client *client) { - int r; - if (!client) return 0; DHCP_CLIENT_DONT_DESTROY(client); - r = client_send_release(client); - if (r < 0) - log_dhcp_client_errno(client, r, - "Failed to send DHCP release message, ignoring: %m"); + if (client->send_release) + (void) client_send_release_or_decline(client, DHCP_RELEASE); client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); - return 0; } From 5188882fbfe910c3e61f8f1a6ead4bc1b4830e7b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 10 Mar 2026 05:07:44 +0900 Subject: [PATCH 0201/1296] test-network: add test case for sending DHCPv4 RELEASE message --- .../conf/25-dhcp-client-simple.network | 7 ++ test/test-network/systemd-networkd-tests.py | 68 ++++++++++++++++++- 2 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 test/test-network/conf/25-dhcp-client-simple.network diff --git a/test/test-network/conf/25-dhcp-client-simple.network b/test/test-network/conf/25-dhcp-client-simple.network new file mode 100644 index 0000000000000..e98bd2620060e --- /dev/null +++ b/test/test-network/conf/25-dhcp-client-simple.network @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=client + +[Network] +DHCP=ipv4 +IPv6AcceptRA=no diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 6f2fe1b9eb65c..1f3a99a040a52 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -869,13 +869,18 @@ class SvcParam(enum.Enum): return data -def start_dnsmasq(*additional_options, interface='veth-peer', ra_mode=None, ipv4_range='192.168.5.10,192.168.5.200', ipv4_router='192.168.5.1', ipv6_range='2600::10,2600::20'): +def start_dnsmasq(*additional_options, namespace=None, interface='veth-peer', ra_mode=None, ipv4_range='192.168.5.10,192.168.5.200', ipv4_router='192.168.5.1', ipv6_range='2600::10,2600::20'): if ra_mode: ra_mode = f',{ra_mode}' else: ra_mode = '' - command = ( + if namespace: + ns_command = ('ip', 'netns', 'exec', namespace) + else: + ns_command = () + + command = ns_command + ( 'dnsmasq', f'--log-facility={dnsmasq_log_file}', '--log-queries=extra', @@ -1104,6 +1109,8 @@ def tear_down_common(): # 3. remove network namespace call_quiet('ip netns del ns99') + call_quiet('ip netns del ns-bridge') + call_quiet('ip netns del ns-server') # 4. remove links flush_l2tp_tunnels() @@ -7988,6 +7995,63 @@ def test_dhcp_client_ipv4_only(self): self.teardown_nftset('addr4', 'network4', 'ifindex') + def test_dhcp_client_send_release(self): + check_output('ip netns add ns-bridge') + check_output('ip netns exec ns-bridge ip link add bridge99 type bridge') + check_output('ip netns exec ns-bridge ip link set bridge99 address 12:34:56:78:90:ab') + check_output('ip netns exec ns-bridge ip link set bridge99 up') + + check_output('ip link add client type veth peer clientp') + check_output('ip link set clientp netns ns-bridge') + check_output('ip netns exec ns-bridge ip link set clientp master bridge99') + check_output('ip netns exec ns-bridge ip link set clientp up') + + check_output('ip link add server type veth peer serverp') + check_output('ip link set serverp netns ns-bridge') + check_output('ip netns exec ns-bridge ip link set serverp master bridge99') + check_output('ip netns exec ns-bridge ip link set serverp up') + + check_output('ip netns add ns-server') + check_output('ip link set server netns ns-server') + check_output('ip netns exec ns-server ip link set server up') + check_output('ip netns exec ns-server ip address add 192.0.2.1/24 dev server') + + start_dnsmasq( + namespace='ns-server', + interface='server', + ipv4_range='192.0.2.100,192.0.2.109', + ipv4_router='192.0.2.1', + ) + + copy_network_unit('25-dhcp-client-simple.network') + start_networkd() + self.wait_online('client:routable') + + print('## ip -4 address show dev client scope global') + output = check_output('ip -4 address show dev client scope global') + print(output) + self.assertRegex(output, r'192.0.2.10[0-9]/24') + + print('## ip -4 route show dev client') + output = check_output('ip -4 route show dev client') + print(output) + self.assertRegex(output, r'default via 192.0.2.1 proto dhcp src 192.0.2.10[0-9]') + self.assertRegex(output, r'192.0.2.0/24 proto kernel scope link src 192.0.2.10[0-9]') + self.assertRegex(output, r'192.0.2.1 proto dhcp scope link src 192.0.2.10[0-9]') + + networkctl('down', 'client') + + print('## dnsmasq log') + for _ in range(20): + time.sleep(0.5) + output = read_dnsmasq_log_file() + if 'DHCPRELEASE' in output: + print(output) + break + else: + print(output) + self.fail('Timed out waiting for DHCPRELEASE in dnsmasq log') + def test_dhcp_client_ipv4_dbus_status(self): copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv4-only.network') start_networkd() From c72860d5f6b2c1a1f0c718d1fbb09247a7cfa41f Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Fri, 6 Mar 2026 10:05:32 -0500 Subject: [PATCH 0202/1296] userdb: mark PII fields as sensitive in user records Mark realName, emailAddress, and location as sensitive in JSON user records so that they are excluded from debug log output. These fields contain personally identifiable information that should not be leaked in logs, which are generally more accessible than the user database itself. --- src/shared/user-record.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/shared/user-record.c b/src/shared/user-record.c index d5e572dc094db..c65bab4ff4684 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -1491,10 +1491,17 @@ int user_group_record_mangle( if (USER_RECORD_STRIP_MASK(load_flags) == _USER_RECORD_MASK_MAX) /* strip everything? */ return json_log(v, json_flags, SYNTHETIC_ERRNO(EINVAL), "Stripping everything from record, refusing."); - /* Extra safety: mark the "secret" part (that contains literal passwords and such) as sensitive, so - * that it is not included in debug output and erased from memory when we are done. We do this for - * any record that passes through here. */ - sd_json_variant_sensitive(sd_json_variant_by_key(v, "secret")); + /* Extra safety: mark sensitive parts of the JSON as such, so that they are not included in debug + * output and erased from memory when we are done. We do this for any record that passes through here. */ + FOREACH_STRING(key, + /* This section contains literal passwords and such in plain text */ + "secret", + + /* Personally Identifiable Information (PII) — avoid leaking in logs */ + "realName", + "location", + "emailAddress") + sd_json_variant_sensitive(sd_json_variant_by_key(v, key)); /* Check if we have the special sections and if they match our flags set */ FOREACH_ELEMENT(i, mask_field) { From 6bb70ef95c5070be7c154dbaedaa09e844a2b16d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 11 Mar 2026 10:31:39 +0100 Subject: [PATCH 0203/1296] update TODO --- TODO | 7 ------- 1 file changed, 7 deletions(-) diff --git a/TODO b/TODO index 897ba35be9aaa..a069429636caf 100644 --- a/TODO +++ b/TODO @@ -2328,8 +2328,6 @@ Features: - timer units should get the ability to trigger when DST changes - Modulate timer frequency based on battery state -* add libsystemd-password or so to query passwords during boot using the password agent logic - * clean up date formatting and parsing so that all absolute/relative timestamps we format can also be parsed * on shutdown: move utmp, wall, audit logic all into PID 1 (or logind?) @@ -2673,11 +2671,6 @@ Features: * systemd-repart: read LUKS encryption key from $CREDENTIALS_DIRECTORY -* systemd-repart: add a switch to factory reset the partition table without - immediately applying the new configuration again. i.e. --factory-reset=leave - or so. (this is useful to factory reset an image, then putting it into - another machine, ensuring that luks key is generated on new machine, not old) - * systemd-repart: support setting up dm-integrity with HMAC * systemd-repart: maybe remove half-initialized image on failure. It fails From b9cb90743da9f66f6a7717cc84b37763ce198214 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 10 Mar 2026 21:07:52 +0000 Subject: [PATCH 0204/1296] boot: impose section limit when loading PE from memory too pe_section_table_from_file already checks with SECTION_TABLE_BYTES_MAX, do the same in pe_section_table_from_base() just in case. Originally reported on yeswehack.com as: YWH-PGM9780-117 Follow-up for f4e081051d950a09ce9331ba55eaf604dac72652 --- src/boot/pe.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/boot/pe.c b/src/boot/pe.c index 4c5dfa0d7af47..5fbf5a42e5386 100644 --- a/src/boot/pe.c +++ b/src/boot/pe.c @@ -570,8 +570,14 @@ EFI_STATUS pe_section_table_from_base( if (!verify_pe(dos, pe, /* allow_compatibility= */ false)) return EFI_LOAD_ERROR; + assert_cc(sizeof(pe->FileHeader.NumberOfSections) == sizeof(uint16_t)); /* multiplication below cannot overflow */ + + size_t n_section_table = pe->FileHeader.NumberOfSections; + if (n_section_table * sizeof(PeSectionHeader) > SECTION_TABLE_BYTES_MAX) + return EFI_OUT_OF_RESOURCES; + *ret_section_table = (const PeSectionHeader*) ((const uint8_t*) base + section_table_offset(dos, pe)); - *ret_n_section_table = pe->FileHeader.NumberOfSections; + *ret_n_section_table = n_section_table; return EFI_SUCCESS; } From e5924c6ecca2d2aaab3f80a82927a392a34cdabb Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 11 Mar 2026 16:45:10 +0100 Subject: [PATCH 0205/1296] dissect-image: don't do path based ops on a non-path Also, better use path_extract_filename() when extracting filenames from paths. Also, why void* for the 'base' parameter? --- src/shared/dissect-image.c | 44 ++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 6c73a12548caf..929a7dcac98d5 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -2951,30 +2951,24 @@ static int decrypted_image_new(DecryptedImage **ret) { return 0; } -static int make_dm_name_and_node(const void *original_node, const char *suffix, char **ret_name, char **ret_node) { - _cleanup_free_ char *name = NULL, *node = NULL; - const char *base; +static int make_dm_name_and_node( + const char *base, + const char *suffix, + char **ret_name, + char **ret_node) { - assert(original_node); + assert(base); assert(suffix); assert(ret_name); assert(ret_node); - base = strrchr(original_node, '/'); - if (!base) - base = original_node; - else - base++; - if (isempty(base)) - return -EINVAL; - - name = strjoin(base, suffix); + _cleanup_free_ char *name = strjoin(base, suffix); if (!name) return -ENOMEM; if (!filename_is_valid(name)) return -EINVAL; - node = path_join(sym_crypt_get_dir(), name); + _cleanup_free_ char *node = path_join(sym_crypt_get_dir(), name); if (!node) return -ENOMEM; @@ -2984,6 +2978,24 @@ static int make_dm_name_and_node(const void *original_node, const char *suffix, return 0; } +static int make_dm_name_and_node_from_node( + const char *original_node, + const char *suffix, + char **ret_name, + char **ret_node) { + + int r; + + assert(original_node); + + _cleanup_free_ char *base = NULL; + r = path_extract_filename(original_node, &base); + if (r < 0) + return r; + + return make_dm_name_and_node(base, suffix, ret_name, ret_node); +} + static int decrypt_partition( DissectedPartition *m, const char *passphrase, @@ -3015,7 +3027,7 @@ static int decrypt_partition( if (r < 0) return r; - r = make_dm_name_and_node(m->node, "-decrypted", &name, &node); + r = make_dm_name_and_node_from_node(m->node, "-decrypted", &name, &node); if (r < 0) return r; @@ -3391,7 +3403,7 @@ static int verity_partition( r = make_dm_name_and_node(root_hash_encoded, "-verity", &name, &node); } else - r = make_dm_name_and_node(m->node, "-verity", &name, &node); + r = make_dm_name_and_node_from_node(m->node, "-verity", &name, &node); if (r < 0) return r; From 15e9fbc769835cef9f5ec842e819ad65bb9b1787 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 11 Mar 2026 16:56:59 +0100 Subject: [PATCH 0206/1296] dissect-image: include diskseq in DM names, to avoid any name clashes --- src/shared/dissect-image.c | 42 ++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 929a7dcac98d5..fa38688411bdd 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -69,6 +69,7 @@ #include "runtime-scope.h" #include "siphash24.h" #include "stat-util.h" +#include "stdio-util.h" #include "string-util.h" #include "strv.h" #include "time-util.h" @@ -2951,8 +2952,15 @@ static int decrypted_image_new(DecryptedImage **ret) { return 0; } +static uint64_t dissected_image_diskseq(const DissectedImage *di) { + assert(di); + + return di->loop ? di->loop->diskseq : 0; +} + static int make_dm_name_and_node( const char *base, + uint64_t diskseq, const char *suffix, char **ret_name, char **ret_node) { @@ -2962,7 +2970,11 @@ static int make_dm_name_and_node( assert(ret_name); assert(ret_node); - _cleanup_free_ char *name = strjoin(base, suffix); + _cleanup_free_ char *name = NULL; + if (diskseq != 0) + name = asprintf_safe("%s-%" PRIu64 "%s", base, diskseq, suffix); + else + name = strjoin(base, suffix); if (!name) return -ENOMEM; if (!filename_is_valid(name)) @@ -2980,6 +2992,7 @@ static int make_dm_name_and_node( static int make_dm_name_and_node_from_node( const char *original_node, + uint64_t diskseq, const char *suffix, char **ret_name, char **ret_node) { @@ -2993,10 +3006,11 @@ static int make_dm_name_and_node_from_node( if (r < 0) return r; - return make_dm_name_and_node(base, suffix, ret_name, ret_node); + return make_dm_name_and_node(base, diskseq, suffix, ret_name, ret_node); } static int decrypt_partition( + DissectedImage *di, DissectedPartition *m, const char *passphrase, DissectImageFlags flags, @@ -3008,6 +3022,7 @@ static int decrypt_partition( _cleanup_close_ int fd = -EBADF; int r; + assert(di); assert(m); assert(d); @@ -3027,7 +3042,7 @@ static int decrypt_partition( if (r < 0) return r; - r = make_dm_name_and_node_from_node(m->node, "-decrypted", &name, &node); + r = make_dm_name_and_node_from_node(m->node, dissected_image_diskseq(di), "-decrypted", &name, &node); if (r < 0) return r; @@ -3351,6 +3366,7 @@ static usec_t verity_timeout(void) { } static int verity_partition( + DissectedImage *di, PartitionDesignator designator, DissectedPartition *m, /* data partition */ DissectedPartition *v, /* verity partition */ @@ -3365,6 +3381,7 @@ static int verity_partition( _cleanup_close_ int mount_node_fd = -EBADF; int r; + assert(di); assert(m); assert(v || (verity && verity->data_path)); @@ -3401,9 +3418,9 @@ static int verity_partition( if (!root_hash_encoded) return -ENOMEM; - r = make_dm_name_and_node(root_hash_encoded, "-verity", &name, &node); + r = make_dm_name_and_node(root_hash_encoded, /* diskseq= */ 0, "-verity", &name, &node); } else - r = make_dm_name_and_node_from_node(m->node, "-verity", &name, &node); + r = make_dm_name_and_node_from_node(m->node, dissected_image_diskseq(di), "-verity", &name, &node); if (r < 0) return r; @@ -3534,7 +3551,16 @@ static int verity_partition( */ sym_crypt_free(cd); cd = NULL; - return verity_partition(designator, m, v, root, verity, flags & ~DISSECT_IMAGE_VERITY_SHARE, policy_flags, d); + return verity_partition( + di, + designator, + m, + v, + root, + verity, + flags & ~DISSECT_IMAGE_VERITY_SHARE, + policy_flags, + d); } return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "All attempts to activate verity device %s failed.", name); @@ -3605,13 +3631,13 @@ int dissected_image_decrypt( PartitionPolicyFlags fl = image_policy_get_exhaustively(policy, i); - r = decrypt_partition(p, passphrase, flags, fl, d); + r = decrypt_partition(m, p, passphrase, flags, fl, d); if (r < 0) return r; k = partition_verity_hash_of(i); if (k >= 0) { - r = verity_partition(i, p, m->partitions + k, root, verity, flags, fl, d); + r = verity_partition(m, i, p, m->partitions + k, root, verity, flags, fl, d); if (r < 0) return r; } From 7c4047957ef58744ecfad6d277f7c45d430f6d70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 11 Mar 2026 11:27:48 +0100 Subject: [PATCH 0207/1296] udev-builtin-net-id: print cescaped bad attributes Follow-up for 16325b35fa6ecb25f66534a562583ce3b96d52f3. Let's log those bad value to make it easier to figure out why things are not working if we reject an attribute. --- src/udev/udev-builtin-net_id.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c index f2a69a75d7b76..4e962d54dec7e 100644 --- a/src/udev/udev-builtin-net_id.c +++ b/src/udev/udev-builtin-net_id.c @@ -22,6 +22,7 @@ #include "device-private.h" #include "device-util.h" #include "dirent-util.h" +#include "escape.h" #include "ether-addr-util.h" #include "fd-util.h" #include "fileio.h" @@ -36,6 +37,12 @@ #define ONBOARD_14BIT_INDEX_MAX ((1U << 14) - 1) #define ONBOARD_16BIT_INDEX_MAX ((1U << 16) - 1) +static int log_invalid_device_attr(sd_device *dev, const char *attr, const char *value) { + _cleanup_free_ char *escaped = cescape(value); + return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), + "Invalid %s value '%s'.", attr, strnull(escaped)); +} + static int device_get_parent_skip_virtio(sd_device *dev, sd_device **ret) { int r; @@ -195,7 +202,7 @@ static int get_port_specifier(sd_device *dev, char **ret) { } if (!utf8_is_valid(phys_port_name) || string_has_cc(phys_port_name, /* ok= */ NULL)) - return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid phys_port_name"); + return log_invalid_device_attr(dev, "phys_port_name", phys_port_name); /* Otherwise, use phys_port_name as is. */ buf = strjoin("n", phys_port_name); @@ -302,7 +309,7 @@ static int names_pci_onboard_label(UdevEvent *event, sd_device *pci_dev, const c return log_device_debug_errno(pci_dev, r, "Failed to get PCI onboard label: %m"); if (!utf8_is_valid(label) || string_has_cc(label, /* ok= */ NULL)) - return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid label"); + return log_invalid_device_attr(dev, "label", label); char str[ALTIFNAMSIZ]; if (snprintf_ok(str, sizeof str, "%s%s", @@ -717,8 +724,7 @@ static int names_vio(UdevEvent *event, const char *prefix) { "VIO bus ID and slot ID have invalid length: %s", s); if (!in_charset(s, HEXDIGITS)) - return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), - "VIO bus ID and slot ID contain invalid characters: %s", s); + return log_invalid_device_attr(dev, "VIO bus ID and slot ID", s); /* Parse only slot ID (the last 4 hexdigits). */ r = safe_atou_full(s + 4, 16, &slotid); @@ -774,8 +780,7 @@ static int names_platform(UdevEvent *event, const char *prefix) { return -EOPNOTSUPP; if (!in_charset(vendor, validchars)) - return log_device_debug_errno(dev, SYNTHETIC_ERRNO(ENOENT), - "Platform vendor contains invalid characters: %s", vendor); + return log_invalid_device_attr(dev, "platform vendor", vendor); ascii_strlower(vendor); @@ -1255,7 +1260,7 @@ static int names_netdevsim(UdevEvent *event, const char *prefix) { return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EOPNOTSUPP), "The 'phys_port_name' attribute is empty."); if (!utf8_is_valid(phys_port_name) || string_has_cc(phys_port_name, /* ok= */ NULL)) - return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid phys_port_name"); + return log_invalid_device_attr(dev, "phys_port_name", phys_port_name); char str[ALTIFNAMSIZ]; if (snprintf_ok(str, sizeof str, "%si%un%s", prefix, addr, phys_port_name)) From 2f5cecd7de511968ab170ef6656f9851ea2b64c4 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sat, 7 Mar 2026 23:04:38 +0100 Subject: [PATCH 0208/1296] dbus-cgroup: Fix copy paste error Let's set the appropriate field for ManagedOOMMemoryPressureDurationUSec= and not a totally different one. --- src/core/dbus-cgroup.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c index 0c71017f93a5f..a7508c96daa11 100644 --- a/src/core/dbus-cgroup.c +++ b/src/core/dbus-cgroup.c @@ -1721,13 +1721,13 @@ int bus_cgroup_set_property( FORMAT_TIMESPAN(t, USEC_PER_SEC)); if (!UNIT_WRITE_FLAGS_NOOP(flags)) { - c->memory_pressure_threshold_usec = t; - if (c->memory_pressure_threshold_usec == USEC_INFINITY) + c->moom_mem_pressure_duration_usec = t; + if (c->moom_mem_pressure_duration_usec == USEC_INFINITY) unit_write_setting(u, flags, name, "ManagedOOMMemoryPressureDurationSec="); else unit_write_settingf(u, flags, name, "ManagedOOMMemoryPressureDurationSec=%s", - FORMAT_TIMESPAN(c->memory_pressure_threshold_usec, 1)); + FORMAT_TIMESPAN(c->moom_mem_pressure_duration_usec, 1)); } if (c->moom_mem_pressure == MANAGED_OOM_KILL) From 0c67b8adcb65dfb92935dc13f94ef7172c844ebc Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 11 Mar 2026 14:20:21 +0000 Subject: [PATCH 0209/1296] core: fix reloading multiple confexts at the same time [] has higher precedence than pointer dereference, hence hilarity ensues as soon as there are multuple images Originally reported on yeswehack.com as: YWH-PGM9780-122 Follow-up for dfdeb0b1cbb05a213f0965eedfe0e7ef06cd39d3 --- src/core/namespace.c | 2 +- test/units/TEST-50-DISSECT.dissect.sh | 49 ++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/core/namespace.c b/src/core/namespace.c index 9727d725eb7df..6e4ec80dc96d4 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -3877,7 +3877,7 @@ static int handle_mount_from_grandchild( if (r < 0) return log_oom_debug(); - *fd_layers[(*n_fd_layers)++] = TAKE_FD(tree_fd); + (*fd_layers)[(*n_fd_layers)++] = TAKE_FD(tree_fd); } m->overlay_layers = strv_free(m->overlay_layers); m->overlay_layers = TAKE_PTR(new_layers); diff --git a/test/units/TEST-50-DISSECT.dissect.sh b/test/units/TEST-50-DISSECT.dissect.sh index 7a68e62fb0067..f87bda82ce295 100755 --- a/test/units/TEST-50-DISSECT.dissect.sh +++ b/test/units/TEST-50-DISSECT.dissect.sh @@ -649,13 +649,20 @@ VDIR="/tmp/${VBASE}.v" mkdir "$VDIR" rm -rf /tmp/markers/ mkdir /tmp/markers/ +CDIR1="/tmp/${VBASE}_confext_a" +CDIR2="/tmp/${VBASE}_confext_b" +mkdir -p "$CDIR1/etc/extension-release.d/" "$CDIR2/etc/extension-release.d/" +echo "ID=_any" >"$CDIR1/etc/extension-release.d/extension-release.${VBASE}_confext_a" +touch "$CDIR1/etc/${VBASE}_confext_a.marker" +echo "ID=_any" >"$CDIR2/etc/extension-release.d/extension-release.${VBASE}_confext_b" +touch "$CDIR2/etc/${VBASE}_confext_b.marker" cat >/run/systemd/system/testservice-50g.service <"$VDIR/${VBASE}_2/etc/extension-release.d/extension-release.${VBASE}_2" touch "$VDIR/${VBASE}_2/etc/${VBASE}_2.marker" systemctl reload testservice-50g.service grep -q -F "${VBASE}_2.marker" /tmp/markers/50g +grep -q -F "${VBASE}_confext_a.marker" /tmp/markers/50g +grep -q -F "${VBASE}_confext_b.marker" /tmp/markers/50g # Do it for a couple more times (to make sure we're tearing down old overlays) for _ in {1..5}; do systemctl reload testservice-50g.service; done systemctl stop testservice-50g.service @@ -690,13 +701,17 @@ rm -f /run/systemd/system/testservice-50g.service # this time) VDIR2="/tmp/${VBASE}.raw.v" mkdir "$VDIR2" +CIMG1="/tmp/${VBASE}_confext_a.raw" +CIMG2="/tmp/${VBASE}_confext_b.raw" +mksquashfs "$CDIR1" "$CIMG1" -noappend +mksquashfs "$CDIR2" "$CIMG2" -noappend cat >/run/systemd/system/testservice-50h.service </run/systemd/system/testservice-50m.service < Date: Wed, 11 Mar 2026 12:03:19 +0100 Subject: [PATCH 0210/1296] core: limit number of LogExtraFields We have two places where those fields can be set: config and the dbus interface. Let's clamp down on the number in both places. But in principle, we could also be upgrading (through serialization/deserialization) from an older systemd which didn't enforce this limit, so also check on deserialization. A user could have a unit with lots and lots of ExtraFields, but not enough to cause the issue in #40916. To handle this gracefully, ignore the extra fields, like we do in the parser. Where the field is used, assert that we are within the expected bounds. Fixes #40916. Reproducer: $ python3 -c 'from pydbus import SystemBus; from gi.repository import GLib; SystemBus().get("org.freedesktop.systemd1", "/org/freedesktop/systemd1").StartTransientUnit("crash.service", "fail", [("ExecStart", GLib.Variant("a(sasb)", [("/bin/true", ["/bin/true"], False)])), ("LogExtraFields", GLib.Variant("aay", [b"F%d=x" % i for i in range(140000)]))], [])' Traceback (most recent call last): File "", line 1, in from pydbus import SystemBus; from gi.repository import GLib; SystemBus().get("org.freedesktop.systemd1", "/org/freedesktop/systemd1").StartTransientUnit("crash.service", "fail", [("ExecStart", GLib.Variant("a(sasb)", [("/bin/true", ["/bin/true"], False)])), ("LogExtraFields", GLib.Variant("aay", [b"F%d=x" % i for i in range(140000)]))], []) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.14/site-packages/pydbus/proxy_method.py", line 102, in __call__ raise error File "/usr/lib/python3.14/site-packages/pydbus/proxy_method.py", line 97, in __call__ result = instance._bus.con.call_sync(*call_args) gi.repository.GLib.GError: g-dbus-error-quark: GDBus.Error:org.freedesktop.DBus.Error.InvalidArgs: Too many extra log fields. (16) --- src/core/dbus-execute.c | 3 +++ src/core/execute-serialize.c | 6 ++++++ src/core/load-fragment.c | 5 +++++ src/core/unit.c | 2 ++ src/core/unit.h | 6 ++++++ 5 files changed, 22 insertions(+) diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 2bd7b1c07eea3..9e6077c7e1fea 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -2806,6 +2806,9 @@ int bus_exec_context_set_transient_property( return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Journal field is not valid UTF-8"); if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + if (c->n_log_extra_fields >= LOG_EXTRA_FIELDS_MAX) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Too many extra log fields."); + if (!GREEDY_REALLOC(c->log_extra_fields, c->n_log_extra_fields + 1)) return -ENOMEM; diff --git a/src/core/execute-serialize.c b/src/core/execute-serialize.c index 560df952874ea..8f9a7ac546402 100644 --- a/src/core/execute-serialize.c +++ b/src/core/execute-serialize.c @@ -30,6 +30,7 @@ #include "string-util.h" #include "strv.h" #include "time-util.h" +#include "unit.h" static int exec_cgroup_context_serialize(const CGroupContext *c, FILE *f) { _cleanup_free_ char *disable_controllers_str = NULL, *delegate_controllers_str = NULL, @@ -3104,6 +3105,11 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { if (r < 0) return r; } else if ((val = startswith(l, "exec-context-log-extra-fields="))) { + if (c->n_log_extra_fields >= LOG_EXTRA_FIELDS_MAX) { + log_warning("Too many extra log fields, ignoring."); + continue; + } + if (!GREEDY_REALLOC(c->log_extra_fields, c->n_log_extra_fields + 1)) return log_oom_debug(); diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index d2bfd20fd43af..d1f74a8fd7e19 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -2941,6 +2941,11 @@ int config_parse_log_extra_fields( continue; } + if (c->n_log_extra_fields >= LOG_EXTRA_FIELDS_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Too many extra log fields, ignoring some."); + return 0; + } + if (!GREEDY_REALLOC(c->log_extra_fields, c->n_log_extra_fields + 1)) return log_oom(); diff --git a/src/core/unit.c b/src/core/unit.c index ab0db25687826..fb6d501ece4e7 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -5843,6 +5843,8 @@ static int unit_export_log_extra_fields(Unit *u, const ExecContext *c) { if (c->n_log_extra_fields <= 0) return 0; + assert(c->n_log_extra_fields <= LOG_EXTRA_FIELDS_MAX); + sizes = newa(le64_t, c->n_log_extra_fields); iovec = newa(struct iovec, c->n_log_extra_fields * 2); diff --git a/src/core/unit.h b/src/core/unit.h index 9c94113239ee0..380ce7fac8c08 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -10,6 +10,7 @@ #include "install.h" #include "iterator.h" #include "job.h" +#include "journal-importer.h" #include "list.h" #include "log.h" #include "log-context.h" @@ -1097,6 +1098,11 @@ int unit_queue_job_check_and_mangle_type(Unit *u, JobType *type, bool reload_if_ int parse_unit_marker(const char *marker, unsigned *settings, unsigned *mask); unsigned unit_normalize_markers(unsigned existing_markers, unsigned new_markers); +/* Trying to log with too many fields is going to fail. We need at least also MESSAGE=, + * but we generally log a few extra in most cases. So let's reserve 10. Anything + * above a few would be very unusual, but let's not be overly strict. */ +#define LOG_EXTRA_FIELDS_MAX (ENTRY_FIELD_COUNT_MAX - 10) + /* Macros which append UNIT= or USER_UNIT= to the message */ #define log_unit_full_errno_zerook(unit, level, error, ...) \ From 3105286f251286a4cc78cde04705e0c3b4013990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 11 Mar 2026 12:50:49 +0100 Subject: [PATCH 0211/1296] core/unit: shorten code I wanted to use _cleanup_(unlink_tempfilep), but the type doesn't match (char ** vs. char (*)[]), so the goto remains. --- src/core/unit.c | 35 ++++++++++++----------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/src/core/unit.c b/src/core/unit.c index fb6d501ece4e7..9af7fb51405e0 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -5829,12 +5829,6 @@ static int unit_export_log_level_max(Unit *u, int log_level_max, bool overwrite) } static int unit_export_log_extra_fields(Unit *u, const ExecContext *c) { - _cleanup_close_ int fd = -EBADF; - struct iovec *iovec; - const char *p; - char *pattern; - le64_t *sizes; - ssize_t n; int r; if (u->exported_log_extra_fields) @@ -5845,8 +5839,8 @@ static int unit_export_log_extra_fields(Unit *u, const ExecContext *c) { assert(c->n_log_extra_fields <= LOG_EXTRA_FIELDS_MAX); - sizes = newa(le64_t, c->n_log_extra_fields); - iovec = newa(struct iovec, c->n_log_extra_fields * 2); + le64_t *sizes = newa(le64_t, c->n_log_extra_fields); + struct iovec *iovec = newa(struct iovec, c->n_log_extra_fields * 2); for (size_t i = 0; i < c->n_log_extra_fields; i++) { sizes[i] = htole64(c->log_extra_fields[i].iov_len); @@ -5855,15 +5849,14 @@ static int unit_export_log_extra_fields(Unit *u, const ExecContext *c) { iovec[i*2+1] = c->log_extra_fields[i]; } - p = strjoina("/run/systemd/units/log-extra-fields:", u->id); - pattern = strjoina(p, ".XXXXXX"); + const char *p = strjoina("/run/systemd/units/log-extra-fields:", u->id); + char *pattern = strjoina(p, ".XXXXXX"); - fd = mkostemp_safe(pattern); + _cleanup_close_ int fd = mkostemp_safe(pattern); if (fd < 0) return log_unit_debug_errno(u, fd, "Failed to create extra fields file %s: %m", p); - n = writev(fd, iovec, c->n_log_extra_fields*2); - if (n < 0) { + if (writev(fd, iovec, c->n_log_extra_fields * 2) < 0) { r = log_unit_debug_errno(u, errno, "Failed to write extra fields: %m"); goto fail; } @@ -5884,8 +5877,6 @@ static int unit_export_log_extra_fields(Unit *u, const ExecContext *c) { } static int unit_export_log_ratelimit_interval(Unit *u, const ExecContext *c) { - _cleanup_free_ char *buf = NULL; - const char *p; int r; assert(u); @@ -5897,10 +5888,10 @@ static int unit_export_log_ratelimit_interval(Unit *u, const ExecContext *c) { if (c->log_ratelimit.interval == 0) return 0; - p = strjoina("/run/systemd/units/log-rate-limit-interval:", u->id); + const char *p = strjoina("/run/systemd/units/log-rate-limit-interval:", u->id); - if (asprintf(&buf, "%" PRIu64, c->log_ratelimit.interval) < 0) - return log_oom(); + char buf[DECIMAL_STR_MAX(c->log_ratelimit.interval)]; + xsprintf(buf, "%" PRIu64, c->log_ratelimit.interval); r = symlink_atomic(buf, p); if (r < 0) @@ -5911,8 +5902,6 @@ static int unit_export_log_ratelimit_interval(Unit *u, const ExecContext *c) { } static int unit_export_log_ratelimit_burst(Unit *u, const ExecContext *c) { - _cleanup_free_ char *buf = NULL; - const char *p; int r; assert(u); @@ -5924,10 +5913,10 @@ static int unit_export_log_ratelimit_burst(Unit *u, const ExecContext *c) { if (c->log_ratelimit.burst == 0) return 0; - p = strjoina("/run/systemd/units/log-rate-limit-burst:", u->id); + const char *p = strjoina("/run/systemd/units/log-rate-limit-burst:", u->id); - if (asprintf(&buf, "%u", c->log_ratelimit.burst) < 0) - return log_oom(); + char buf[DECIMAL_STR_MAX(c->log_ratelimit.burst)]; + xsprintf(buf, "%u", c->log_ratelimit.burst); r = symlink_atomic(buf, p); if (r < 0) From 18031b17f8fa9bd7bd91cab3faab58f0299339dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 11 Mar 2026 13:15:57 +0100 Subject: [PATCH 0212/1296] TEST-07-PID1: add small test for LogExtraFields --- test/units/TEST-07-PID1.issue-40916.sh | 38 ++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100755 test/units/TEST-07-PID1.issue-40916.sh diff --git a/test/units/TEST-07-PID1.issue-40916.sh b/test/units/TEST-07-PID1.issue-40916.sh new file mode 100755 index 0000000000000..c3f6dd6f59ad8 --- /dev/null +++ b/test/units/TEST-07-PID1.issue-40916.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later + +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +# Units with excessive numbers of fields in LogExtraFields=. +# Issue: https://github.com/systemd/systemd/issues/40916 + +UNIT=test-07-pid1-issue-40916.service + +cleanup() { + rm -f /run/systemd/system/"$UNIT" + systemctl daemon-reload +} + +trap cleanup EXIT + +cat >/run/systemd/system/"$UNIT" <>/run/systemd/system/"$UNIT" + +systemctl start --wait "$UNIT" + +systemctl show -p LogExtraFields "$UNIT" | grep FIELD_1000 +(! systemctl show -p LogExtraFields "$UNIT" | grep FIELD_1500) + +# Now try setting the properties dynamically +(! systemd-run --wait -u test-07-pid1-issue-40916-1.service -pLogExtraFields=FD{1..2000}=1 true) +systemd-run --wait -u test-07-pid1-issue-40916-1.service -pLogExtraFields=FD{1..1000}=1 true From 1b364f8b53e8d435a9f1b81a7487e695fb9b61eb Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 11 Mar 2026 16:29:01 +0100 Subject: [PATCH 0213/1296] sd-ndisc: fix address family check Issue reported by zhengg-research --- src/libsystemd-network/ndisc-option.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libsystemd-network/ndisc-option.c b/src/libsystemd-network/ndisc-option.c index 0104515cb0c9e..604ef57cf1111 100644 --- a/src/libsystemd-network/ndisc-option.c +++ b/src/libsystemd-network/ndisc-option.c @@ -1376,7 +1376,7 @@ static int ndisc_option_parse_encrypted_dns(Set **options, size_t offset, size_t union in_addr_union addr; memcpy(&addr.in6, opt + off, sizeof(struct in6_addr)); if (in_addr_is_multicast(AF_INET6, &addr) || - in_addr_is_localhost(AF_INET, &addr)) + in_addr_is_localhost(AF_INET6, &addr)) return -EBADMSG; res.addrs[i] = addr; off += sizeof(struct in6_addr); From 4b551035b4e7d96abd04595f05575d6fc18103ae Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 11 Mar 2026 17:27:12 +0100 Subject: [PATCH 0214/1296] measure-tool: always sign with SHA256 We should not use the bank algorithm for the signing, as we only support validating via SHA256. Fix that. Fixes: #40245 --- src/measure/measure-tool.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/measure/measure-tool.c b/src/measure/measure-tool.c index 515e7588b0706..6392460cf40e8 100644 --- a/src/measure/measure-tool.c +++ b/src/measure/measure-tool.c @@ -935,7 +935,10 @@ static int build_policy_digest(bool sign) { _cleanup_free_ void *sig = NULL; size_t ss = 0; if (privkey) { - r = digest_and_sign(p->md, privkey, pcr_policy_digest.buffer, pcr_policy_digest.size, &sig, &ss); + /* We always use SHA256 for signing currently. Regardless of the bank. */ + const EVP_MD *sha256 = ASSERT_PTR(EVP_get_digestbyname("sha256")); + + r = digest_and_sign(sha256, privkey, pcr_policy_digest.buffer, pcr_policy_digest.size, &sig, &ss); if (r == -EADDRNOTAVAIL) return log_error_errno(r, "Hash algorithm '%s' not available while signing. (Maybe OS security policy disables this algorithm?)", EVP_MD_name(p->md)); if (r < 0) From 43cca8348a90409a3c3b8125ed6356b15fbd1e3b Mon Sep 17 00:00:00 2001 From: Mikhail Novosyolov Date: Wed, 11 Mar 2026 22:27:58 +0300 Subject: [PATCH 0215/1296] hwdb/keyboard: fix Positron vendor location Move lines without changing them. Fixes: 9aad3336f ("hwdb/keyboard: Map FN key on Positron Proxima 15") (https://github.com/systemd/systemd/pull/40929) --- hwdb.d/60-keyboard.hwdb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/hwdb.d/60-keyboard.hwdb b/hwdb.d/60-keyboard.hwdb index bd2e07788fecc..59aeef6b6f857 100644 --- a/hwdb.d/60-keyboard.hwdb +++ b/hwdb.d/60-keyboard.hwdb @@ -1810,6 +1810,14 @@ evdev:input:b0003v258Ap001E* KEYBOARD_KEY_700a6=brightnessup KEYBOARD_KEY_70066=sleep +########################################################### +# Positron +########################################################### + +# Positron Proxima 15 (G1569) +evdev:atkbd:dmi:bvn*:bvr*:bd*:svn*Positron*:pnG1569*:* + KEYBOARD_KEY_6e=fn + ########################################################### # Purism ########################################################### @@ -2135,10 +2143,6 @@ evdev:atkbd:dmi:*:svnTUXEDO:*:rvnNB02:* evdev:atkbd:dmi:*:svnTUXEDO:*:rnDN50Z-140HC-YD:* KEYBOARD_KEY_6e=fn -# Positron Proxima 15 (G1569) -evdev:atkbd:dmi:bvn*:bvr*:bd*:svn*Positron*:pnG1569*:* - KEYBOARD_KEY_6e=fn - ########################################################### # VIA ########################################################### From 6df5f80bd374be1b45c52d740e88f0236da922c7 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sun, 8 Mar 2026 14:30:52 +0000 Subject: [PATCH 0216/1296] machined: reject invalid class types when registering machines Follow-up for fbe550738d03b178bb004a1390e74115e904118a --- src/machine/machine-varlink.c | 3 +++ src/machine/machined-dbus.c | 4 +-- test/units/TEST-13-NSPAWN.unpriv.sh | 40 +++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c index 118d8178f40fb..73edd781b58b5 100644 --- a/src/machine/machine-varlink.c +++ b/src/machine/machine-varlink.c @@ -155,6 +155,9 @@ int vl_method_register(sd_varlink *link, sd_json_variant *parameters, sd_varlink if (r != 0) return r; + if (!IN_SET(machine->class, MACHINE_CONTAINER, MACHINE_VM)) + return sd_varlink_error_invalid_parameter_name(link, "class"); + if (manager->runtime_scope != RUNTIME_SCOPE_USER) { r = varlink_verify_polkit_async( link, diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index ab7ca94fd01bd..87f0c15ee13d0 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -433,7 +433,7 @@ static int method_create_or_register_machine( c = _MACHINE_CLASS_INVALID; else { c = machine_class_from_string(class); - if (c < 0) + if (c < 0 || !IN_SET(c, MACHINE_CONTAINER, MACHINE_VM)) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine class parameter"); } @@ -608,7 +608,7 @@ static int method_create_or_register_machine_ex( c = _MACHINE_CLASS_INVALID; else { c = machine_class_from_string(class); - if (c < 0) + if (c < 0 || !IN_SET(c, MACHINE_CONTAINER, MACHINE_VM)) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine class parameter"); } diff --git a/test/units/TEST-13-NSPAWN.unpriv.sh b/test/units/TEST-13-NSPAWN.unpriv.sh index 25867de707115..75a9c1aac070b 100755 --- a/test/units/TEST-13-NSPAWN.unpriv.sh +++ b/test/units/TEST-13-NSPAWN.unpriv.sh @@ -120,6 +120,46 @@ run0 -u testuser \ /run/systemd/machine/io.systemd.Machine \ io.systemd.Machine.Open \ '{"name":"shouldnotwork3", "mode": "shell", "user":"root","path":"/usr/bin/bash","args":["bash","-c","''touch /shouldnotwork; sleep 20''"]}') +(! varlinkctl \ + call \ + /run/systemd/machine/io.systemd.Machine \ + io.systemd.Machine.Register \ + "{\"name\":\"shouldnotwork4\", \"class\":\"host\", \"leader\": $sleep_pid}") +(! machinectl list | grep shouldnotwork4) +(! run0 -u testuser \ + varlinkctl \ + call \ + /run/systemd/machine/io.systemd.Machine \ + io.systemd.Machine.Register \ + "{\"name\":\"shouldnotwork5\", \"class\":\"host\", \"leader\": $sleep_pid}") +(! machinectl list | grep shouldnotwork5) +(! busctl call \ + org.freedesktop.machine1 \ + /org/freedesktop/machine1 \ + org.freedesktop.machine1.Manager \ + RegisterMachine \ + 'sayssus' \ + shouldnotwork6 \ + 0 \ + "" \ + host \ + 0 \ + "") +(! machinectl list | grep shouldnotwork6) +(! run0 -u testuser \ + busctl call \ + org.freedesktop.machine1 \ + /org/freedesktop/machine1 \ + org.freedesktop.machine1.Manager \ + RegisterMachine \ + 'sayssus' \ + shouldnotwork7 \ + 0 \ + "" \ + host \ + 0 \ + "") +(! machinectl list | grep shouldnotwork7) systemctl --user --machine testuser@ stop sleep.service test ! -f /shouldnotwork From 61bceb1bff4b1f9c126b18dc971ca3e6d8c71c40 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 11 Mar 2026 12:15:26 +0000 Subject: [PATCH 0217/1296] nspawn: apply BindUser/Ephemeral from settings file only if trusted Originally reported on yeswehack.com as: YWH-PGM9780-116 Follow-up for 2f8930449079403b26c9164b8eeac78d5af2c8df Follow-up for a2f577fca0be79b23f61f033229b64884e7d840a --- src/nspawn/nspawn.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 1b3aa7d1ad50a..84e94e845a6b5 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -4783,8 +4783,13 @@ static int merge_settings(Settings *settings, const char *path) { } if ((arg_settings_mask & SETTING_EPHEMERAL) == 0 && - settings->ephemeral >= 0) - arg_ephemeral = settings->ephemeral; + settings->ephemeral >= 0) { + + if (!arg_settings_trusted) + log_warning("Ignoring ephemeral setting, file %s is not trusted.", path); + else + arg_ephemeral = settings->ephemeral; + } if ((arg_settings_mask & SETTING_DIRECTORY) == 0 && settings->root) { @@ -4953,8 +4958,13 @@ static int merge_settings(Settings *settings, const char *path) { } if ((arg_settings_mask & SETTING_BIND_USER) == 0 && - !strv_isempty(settings->bind_user)) - strv_free_and_replace(arg_bind_user, settings->bind_user); + !strv_isempty(settings->bind_user)) { + + if (!arg_settings_trusted) + log_warning("Ignoring bind user setting, file %s is not trusted.", path); + else + strv_free_and_replace(arg_bind_user, settings->bind_user); + } if (!FLAGS_SET(arg_settings_mask, SETTING_BIND_USER_SHELL) && settings->bind_user_shell_set) { From 7b85f5498a958e5bb660c703b8f4a71cceed3373 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 11 Mar 2026 13:27:14 +0000 Subject: [PATCH 0218/1296] nspawn: normalize pivot_root paths Originally reported on yeswehack.com as: YWH-PGM9780-116 Follow-up for b53ede699cdc5233041a22591f18863fb3fe2672 --- src/nspawn/nspawn-mount.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c index 282a29c359f70..1ee01238f31ef 100644 --- a/src/nspawn/nspawn-mount.c +++ b/src/nspawn/nspawn-mount.c @@ -1370,7 +1370,9 @@ int pivot_root_parse(char **pivot_root_new, char **pivot_root_old, const char *s if (!path_is_absolute(root_new)) return -EINVAL; - if (root_old && !path_is_absolute(root_old)) + if (!path_is_normalized(root_new)) + return -EINVAL; + if (root_old && (!path_is_absolute(root_old) || !path_is_normalized(root_old))) return -EINVAL; free_and_replace(*pivot_root_new, root_new); From 4443626b167c4e07bc971afe5bb05ea04bc27bc3 Mon Sep 17 00:00:00 2001 From: Rito Rhymes Date: Wed, 11 Mar 2026 23:34:40 -0400 Subject: [PATCH 0219/1296] docs: allow long links to wrap to prevent overflow on mobile --- docs/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/style.css b/docs/style.css index ee0fc7f754ec6..1711156e68919 100644 --- a/docs/style.css +++ b/docs/style.css @@ -111,6 +111,7 @@ a { text-decoration: none; color: var(--sd-link-color); cursor: pointer; + overflow-wrap: anywhere; } a:hover { text-decoration: underline; From f9d4dce604fd1688a690daaac32c4221a60f4205 Mon Sep 17 00:00:00 2001 From: Rito Rhymes Date: Wed, 11 Mar 2026 23:36:42 -0400 Subject: [PATCH 0220/1296] docs: allow long inline code to wrap to prevent overflow on mobile --- docs/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/style.css b/docs/style.css index 1711156e68919..1f98b6bacf3d5 100644 --- a/docs/style.css +++ b/docs/style.css @@ -568,6 +568,7 @@ tbody td { code.highlighter-rouge { padding: 2px 6px; background-color: var(--sd-highlight-inline-bg); + overflow-wrap: anywhere; } a code.highlighter-rouge { From f18df62e712b65234cd27cca69f83e1e01a572f9 Mon Sep 17 00:00:00 2001 From: Rito Rhymes Date: Wed, 11 Mar 2026 23:54:10 -0400 Subject: [PATCH 0221/1296] docs: wrap bare enum constants in inline code in JOURNAL_FILE_FORMAT --- docs/JOURNAL_FILE_FORMAT.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/JOURNAL_FILE_FORMAT.md b/docs/JOURNAL_FILE_FORMAT.md index ddd4a2de1abeb..9907c622d7347 100644 --- a/docs/JOURNAL_FILE_FORMAT.md +++ b/docs/JOURNAL_FILE_FORMAT.md @@ -202,7 +202,7 @@ also supposed to be updated whenever the file was opened for any form of writing, including when opened to mark it as archived. This behaviour has been deemed problematic since without an associated boot ID the **tail_entry_monotonic** field is useless. To indicate whether the boot ID is -updated only on append the JOURNAL_COMPATIBLE_TAIL_ENTRY_BOOT_ID is set. If it +updated only on append the `JOURNAL_COMPATIBLE_TAIL_ENTRY_BOOT_ID` is set. If it is not set, the **tail_entry_monotonic** field is not usable). The currently used part of the file is the **header_size** plus the @@ -291,27 +291,27 @@ enum { }; ``` -HEADER_INCOMPATIBLE_COMPRESSED_XZ indicates that the file includes DATA objects -that are compressed using XZ. Similarly, HEADER_INCOMPATIBLE_COMPRESSED_LZ4 +`HEADER_INCOMPATIBLE_COMPRESSED_XZ` indicates that the file includes DATA objects +that are compressed using XZ. Similarly, `HEADER_INCOMPATIBLE_COMPRESSED_LZ4` indicates that the file includes DATA objects that are compressed with the LZ4 -algorithm. And HEADER_INCOMPATIBLE_COMPRESSED_ZSTD indicates that there are +algorithm. And `HEADER_INCOMPATIBLE_COMPRESSED_ZSTD` indicates that there are objects compressed with ZSTD. -HEADER_INCOMPATIBLE_KEYED_HASH indicates that instead of the unkeyed Jenkins +`HEADER_INCOMPATIBLE_KEYED_HASH` indicates that instead of the unkeyed Jenkins hash function the keyed siphash24 hash function is used for the two hash tables, see below. -HEADER_INCOMPATIBLE_COMPACT indicates that the journal file uses the new binary +`HEADER_INCOMPATIBLE_COMPACT` indicates that the journal file uses the new binary format that uses less space on disk compared to the original format. -HEADER_COMPATIBLE_SEALED indicates that the file includes TAG objects required +`HEADER_COMPATIBLE_SEALED` indicates that the file includes TAG objects required for Forward Secure Sealing. -HEADER_COMPATIBLE_TAIL_ENTRY_BOOT_ID indicates whether the +`HEADER_COMPATIBLE_TAIL_ENTRY_BOOT_ID` indicates whether the **tail_entry_boot_id** field is strictly updated on initial creation of the file and whenever an entry is updated (in which case the flag is set), or also when the file is archived (in which case it is unset). New files should always -set this flag (and thus not update the **tail_entry_boot_id** except when +set this flag (and thus not update **tail_entry_boot_id** except when creating the file and when appending an entry to it. ## Dirty Detection @@ -406,11 +406,11 @@ _packed_ struct ObjectHeader { ``` The **type** field is one of the object types listed above. The **flags** field -currently knows three flags: OBJECT_COMPRESSED_XZ, OBJECT_COMPRESSED_LZ4 and -OBJECT_COMPRESSED_ZSTD. It is only valid for DATA objects and indicates that +currently knows three flags: `OBJECT_COMPRESSED_XZ`, `OBJECT_COMPRESSED_LZ4` and +`OBJECT_COMPRESSED_ZSTD`. It is only valid for DATA objects and indicates that the data payload is compressed with XZ/LZ4/ZSTD. If one of the -OBJECT_COMPRESSED_* flags is set for an object then the matching -HEADER_INCOMPATIBLE_COMPRESSED_XZ/HEADER_INCOMPATIBLE_COMPRESSED_LZ4/HEADER_INCOMPATIBLE_COMPRESSED_ZSTD +`OBJECT_COMPRESSED_*` flags is set for an object then the matching +`HEADER_INCOMPATIBLE_COMPRESSED_XZ`/`HEADER_INCOMPATIBLE_COMPRESSED_LZ4`/`HEADER_INCOMPATIBLE_COMPRESSED_ZSTD` flag must be set for the file as well. At most one of these three bits may be set. The **size** field encodes the size of the object including all its headers and payload. @@ -465,7 +465,7 @@ number of ENTRY objects that reference this object, i.e. the sum of all ENTRY_ARRAYS chained up from this object, plus 1. The **payload[]** field contains the field name and date unencoded, unless -OBJECT_COMPRESSED_XZ/OBJECT_COMPRESSED_LZ4/OBJECT_COMPRESSED_ZSTD is set in the +`OBJECT_COMPRESSED_XZ`/`OBJECT_COMPRESSED_LZ4`/`OBJECT_COMPRESSED_ZSTD` is set in the `ObjectHeader`, in which case the payload is compressed with the indicated compression algorithm. From e5a6cc3a6f675305f2877a578af106b14930eecb Mon Sep 17 00:00:00 2001 From: Rito Rhymes Date: Wed, 11 Mar 2026 21:55:22 -0400 Subject: [PATCH 0222/1296] docs: contain image sizing and prevent overflow on mobile `max-width: 100%` keeps images from expanding beyond their container and creating horizontal overflow scroll on small screens. `height: auto` ensures the image scales proportionally when width is adjusted. --- docs/style.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/style.css b/docs/style.css index 1f98b6bacf3d5..d5072cca8a15d 100644 --- a/docs/style.css +++ b/docs/style.css @@ -116,6 +116,10 @@ a { a:hover { text-decoration: underline; } +img { + max-width: 100%; + height: auto; +} b { font-weight: 600; } From 6eab41c8138e7704f523f16d933fadaba2ced75f Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Thu, 12 Mar 2026 05:14:40 -0700 Subject: [PATCH 0223/1296] test: use --nogpgcheck instead of --no-gpgchecks in TEST-88-UPGRADE --no-gpgchecks was introduced in 920483872449 but is only available in dnf5. Use --nogpgcheck instead, which is supported by both dnf4 and dnf5 (where it is an alias for --no-gpgchecks). Fixes test failure on distros still using dnf4 (e.g. CentOS/RHEL 9). Co-developed-by: Claude --- test/units/TEST-88-UPGRADE.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/units/TEST-88-UPGRADE.sh b/test/units/TEST-88-UPGRADE.sh index faf3e3748ee8f..5cc1df21aae7b 100755 --- a/test/units/TEST-88-UPGRADE.sh +++ b/test/units/TEST-88-UPGRADE.sh @@ -84,7 +84,7 @@ timer2=$(systemctl show -P NextElapseUSecRealtime upgrade_timer_test.timer) # FIXME: See https://github.com/systemd/systemd/pull/39293 systemctl stop systemd-networkd-resolve-hook.socket || true -dnf downgrade --no-gpgchecks -y --allowerasing --disablerepo '*' "$pkgdir"/distro/*.rpm +dnf downgrade --nogpgcheck -y --allowerasing --disablerepo '*' "$pkgdir"/distro/*.rpm # Some distros don't ship networkd, so the test will always fail if command -v networkctl >/dev/null; then @@ -105,7 +105,7 @@ fi check_sd # Finally test the upgrade -dnf -y upgrade --no-gpgchecks --disablerepo '*' "$pkgdir"/devel/*.rpm +dnf -y upgrade --nogpgcheck --disablerepo '*' "$pkgdir"/devel/*.rpm # TODO: sanity checks check_sd From c248d6a409455cafd49598a86853b9416b57605c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 12 Mar 2026 14:31:42 +0100 Subject: [PATCH 0224/1296] pcrlock: don't accept PCRs > 23 from firmware event log Let's harden ourselves against shitty firmware which might report an invalid PCR. (This is not really a security issue, more a robustness issue, after all firmware generally comes with highest privileges and trust, even though it might just be shit) Fixes an issue found with Claude code review --- src/pcrlock/pcrlock.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index acf68d698b90f..2d3c0862615a4 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -949,6 +949,11 @@ static int event_log_load_firmware(EventLog *el) { continue; } + if (event->pcrIndex >= TPM2_PCRS_MAX) { + log_debug("Skipping event on PCR %" PRIu32 " (out of range).", event->pcrIndex); + continue; + } + r = event_log_add_record(el, &record); if (r < 0) return log_error_errno(r, "Failed to add record to event log: %m"); From bb19b6104978b5ede792fa3f0cfc74272f20bf9c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 12 Mar 2026 14:41:43 +0100 Subject: [PATCH 0225/1296] measure: figure success of measurement correctly Found by Claude Code Review. --- src/boot/measure.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/boot/measure.c b/src/boot/measure.c index 22129cb87d61d..3c51998d1b298 100644 --- a/src/boot/measure.c +++ b/src/boot/measure.c @@ -309,7 +309,7 @@ EFI_STATUS tpm_log_tagged_event( } err = tpm2_measure_to_pcr_and_tagged_event_log(tpm2, pcrindex, buffer, buffer_size, event_id, description); - if (!err) + if (err != EFI_SUCCESS) return err; *ret_measured = true; From 19606b5ce5d686e926908b7040d6507bf4e42ac6 Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Thu, 12 Mar 2026 13:58:30 +0000 Subject: [PATCH 0226/1296] po: Translated using Weblate (Hebrew) Currently translated at 100.0% (266 of 266 strings) Co-authored-by: Yaron Shahrabani Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/he/ Translation: systemd/main --- po/he.po | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/po/he.po b/po/he.po index 69717efe2af1a..d7a92ea6bd0a2 100644 --- a/po/he.po +++ b/po/he.po @@ -6,7 +6,7 @@ msgstr "" "Project-Id-Version: systemd\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2026-02-22 23:58+0000\n" +"PO-Revision-Date: 2026-03-12 13:58+0000\n" "Last-Translator: Yaron Shahrabani \n" "Language-Team: Hebrew \n" @@ -16,7 +16,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n == 1) ? 0 : ((n == 2) ? 1 : ((n > 10 && " "n % 10 == 0) ? 2 : 3));\n" -"X-Generator: Weblate 5.16\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -938,12 +938,10 @@ msgid "DHCP server sends force renew message" msgstr "שרת DHCP שולח הודעת אילוץ חידוש" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy -#| msgid "Authentication is required to send force renew message." msgid "" "Authentication is required to send a force renew message from the DHCP " "server." -msgstr "נדרש אימות כדי לשלוח הודעת אילוץ חידוש." +msgstr "נדרש אימות כדי לשלוח הודעת אילוץ חידוש משרת ה־DHCP." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" @@ -981,11 +979,11 @@ msgstr "נדרש אימות כדי לציין האם אחסון קבוע זמי #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "ניהול קישורי רשת" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" +msgstr "נדרש אימות כדי לנהל קישורי רשת." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" From d6aa1fecc1313e2a421bb6706aa335fa96702785 Mon Sep 17 00:00:00 2001 From: "Sergey A." Date: Thu, 12 Mar 2026 13:58:31 +0000 Subject: [PATCH 0227/1296] po: Translated using Weblate (Russian) Currently translated at 100.0% (266 of 266 strings) Co-authored-by: Sergey A. Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/ru/ Translation: systemd/main --- po/ru.po | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/po/ru.po b/po/ru.po index 934a01741bcac..0f39e3cf2093c 100644 --- a/po/ru.po +++ b/po/ru.po @@ -9,12 +9,12 @@ # Olga Smirnova , 2022. # Andrei Stepanov , 2023. # "Sergey A." , 2023, 2024. -# "Sergey A." , 2024, 2025. +# "Sergey A." , 2024, 2025, 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2025-09-03 09:14+0000\n" +"PO-Revision-Date: 2026-03-12 13:58+0000\n" "Last-Translator: \"Sergey A.\" \n" "Language-Team: Russian \n" @@ -24,7 +24,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" -"X-Generator: Weblate 5.13\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1109,14 +1109,12 @@ msgid "DHCP server sends force renew message" msgstr "Сервер DHCP посылает сообщение о принудительном обновлении" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy -#| msgid "Authentication is required to send force renew message." msgid "" "Authentication is required to send a force renew message from the DHCP " "server." msgstr "" -"Чтобы отправить сообщение о принудительном обновлении, необходимо пройти " -"аутентификацию." +"Чтобы отправить сообщение от сервера DHCP о принудительном обновлении, " +"необходимо пройти аутентификацию." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" @@ -1132,9 +1130,7 @@ msgstr "Перечитать настройки сети" #: src/network/org.freedesktop.network1.policy:166 msgid "Authentication is required to reload network settings." -msgstr "" -"Чтобы заставить systemd перечитать настройки сети, необходимо пройти " -"аутентификацию." +msgstr "Чтобы перечитать настройки сети, необходимо пройти аутентификацию." #: src/network/org.freedesktop.network1.policy:176 msgid "Reconfigure network interface" @@ -1162,13 +1158,11 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Управление сетевыми ссылками" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" -"Чтобы заставить systemd перечитать настройки сети, необходимо пройти " -"аутентификацию." +msgstr "Для управления сетевыми ссылками, необходимо пройти аутентификацию." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" From 9ce905e35f690e7a10cd286be2b50594d0857f5e Mon Sep 17 00:00:00 2001 From: Dan McGregor Date: Wed, 11 Mar 2026 18:26:05 -0600 Subject: [PATCH 0228/1296] meson: use libfido2_cflags dependency Add the libfido2 dependency to cryptenroll and cryptsetup's meson files. If libfido2's not installed in the default path the build wasn't finding its headers correctly. --- src/cryptenroll/meson.build | 1 + src/cryptsetup/cryptsetup-tokens/meson.build | 2 +- src/cryptsetup/meson.build | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cryptenroll/meson.build b/src/cryptenroll/meson.build index 488ceea14d1ef..11265c8b41cc0 100644 --- a/src/cryptenroll/meson.build +++ b/src/cryptenroll/meson.build @@ -23,6 +23,7 @@ executables += [ 'dependencies' : [ libcryptsetup, libdl, + libfido2_cflags, libopenssl, libp11kit_cflags, ], diff --git a/src/cryptsetup/cryptsetup-tokens/meson.build b/src/cryptsetup/cryptsetup-tokens/meson.build index 804e18bc67a2e..0fd6309201bb7 100644 --- a/src/cryptsetup/cryptsetup-tokens/meson.build +++ b/src/cryptsetup/cryptsetup-tokens/meson.build @@ -58,7 +58,7 @@ modules += [ 'sources' : cryptsetup_token_systemd_fido2_sources, 'dependencies' : [ libcryptsetup, - libfido2, + libfido2_cflags, ], }, template + { diff --git a/src/cryptsetup/meson.build b/src/cryptsetup/meson.build index d9778259c2fce..b36354fb0ad0d 100644 --- a/src/cryptsetup/meson.build +++ b/src/cryptsetup/meson.build @@ -19,6 +19,7 @@ executables += [ 'sources' : systemd_cryptsetup_sources, 'dependencies' : [ libcryptsetup, + libfido2_cflags, libmount_cflags, libopenssl, libp11kit_cflags, From cfe93e63413a0f963fee2b1fcd936e745580a1c6 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 12 Mar 2026 16:55:41 +0000 Subject: [PATCH 0229/1296] NEWS: update contributors list --- NEWS | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/NEWS b/NEWS index cb7cf855a74f6..a0168c6bec007 100644 --- a/NEWS +++ b/NEWS @@ -495,26 +495,27 @@ CHANGES WITH 260 in spe: Betacentury, Bouke van der Bijl, Carlos Peón Costa, Carolina Jubran, Cathy Hu, Chris Down, Chris Lindee, Christian Brauner, Christian Glombek, Christian Hesse, - Christopher Cooper, Christopher Head, Daan De Meyer, - Daniel Foster, Daniel Nylander, Daniel Rusek, + Christopher Cooper, Christopher Head, Cyrus Xi, Daan De Meyer, + Dan McGregor, Daniel Foster, Daniel Nylander, Daniel Rusek, David Santamaría Rogado, David Tardon, Derek J. Clark, Dirk Su, - Dmitry V. Levin, Dmytro Bagrii, Efstathios Iosifidis, - Eisuke Kawashima, Ettore Atalan, Fergus Dall, Florian Klink, - Franck Bui, Frantisek Sumsal, Govind Venugopal, Graham Reed, - Guiorgy, Han Sol Jin, Hans de Goede, Heran Yang, IntenseWiggling, - Ivan Kruglov, Ivan Shapovalov, James Le Cuirot, Jan Kuparinen, - Jeff Layton, Jeremy Kerr, Jian Wen, Jim Spentzos, Julian Sparber, - Jörg Behrmann, Kai Lüke, Lennart Poettering, Louis Stagg, - Luca Boccassi, Lucas Werkmeister, Luiz Amaral, Léane GRASSER, - Malcolm Frazier, Marc Pervaz Boocha, Mario Limonciello, - Mario Limonciello (AMD), Martin Srebotnjak, Matt Fleming, - Matteo Croce, Matthijs Kooijman, Max Gautier, Maximilian Bosch, - Miao Wang, Michael Vogt, Michal Sekletár, Mike Gilbert, - Mike Yuan, Mikhail Novosyolov, Nandakumar Raghavan, Nick Rosbrook, - Nicolas Dorier, Oblivionsage, Oleksandr Andrushchenko, Oğuz Ersen, - Pablo Fraile Alonso, Peter Oliver, Philip Withnall, - Pontus Lundkvist, Popax21, Rodrigo Campos, Ronan Pigott, - Ryan Zeigler, Salvatore Cocuzza, Skye Soss, Sriman Achanta, + Dmitry V. Levin, Dmytro Bagrii, Dylan M. Taylor, + Efstathios Iosifidis, Eisuke Kawashima, Ettore Atalan, Fergus Dall, + Florian Klink, Franck Bui, Frantisek Sumsal, Govind Venugopal, + Graham Reed, Guiorgy, Han Sol Jin, Hans de Goede, Heran Yang, + IntenseWiggling, Ivan Kruglov, Ivan Shapovalov, James Le Cuirot, + Jan Kuparinen, Jeff Layton, Jeremy Kerr, Jesse Guo, Jian Wen, + Jim Spentzos, Julian Sparber, Jörg Behrmann, Kai Lüke, + Lennart Poettering, Louis Stagg, Luca Boccassi, Lucas Werkmeister, + Luiz Amaral, Léane GRASSER, Malcolm Frazier, Marc Pervaz Boocha, + Marcel Leismann, Mario Limonciello, Mario Limonciello (AMD), + Martin Srebotnjak, Matt Fleming, Matteo Croce, Matthijs Kooijman, + Max Gautier, Maximilian Bosch, Miao Wang, Michael Vogt, + Michal Sekletár, Mike Gilbert, Mike Yuan, Mikhail Novosyolov, + Nandakumar Raghavan, Nick Rosbrook, Nicolas Dorier, Oblivionsage, + Oleksandr Andrushchenko, Oğuz Ersen, Pablo Fraile Alonso, + Peter Oliver, Philip Withnall, Pontus Lundkvist, Popax21, + Rito Rhymes, Rodrigo Campos, Ronan Pigott, Ryan Zeigler, + Salvatore Cocuzza, Sergey A., Skye Soss, Sriman Achanta, Tabis Kabis, Temuri Doghonadze, The-An0nym, Thomas Weißschuh, Thorsten Kukuk, Tobias Heider, Tobias Stoeckmann, Ulrich Ölmann, Usama Arif, Val Markovic, Vitaly Kuznetsov, Vunny Sodhi, From 2ed82970d0a0f6f2d393b2a1b9c9a547b3384518 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 12 Mar 2026 16:55:49 +0000 Subject: [PATCH 0230/1296] NEWS: finalize place and date --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index a0168c6bec007..4ded869260a4b 100644 --- a/NEWS +++ b/NEWS @@ -526,7 +526,7 @@ CHANGES WITH 260 in spe: noxiouz, patrick, ppkramer-hub, r-vdp, safforddr, scarlet-storm, sd416, seidlerv, smosia, tuhaowen, zefr0x - — Edinburgh, 2026/03/04 + — Edinburgh, 2026/03/12 CHANGES WITH 259: From c1d4d5fd9ae56dc07377ef63417f461a0f4a4346 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 12 Mar 2026 16:58:12 +0000 Subject: [PATCH 0231/1296] meson: bump version to v260~rc3 --- meson.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.version b/meson.version index 4366da0c80afe..636509f058b33 100644 --- a/meson.version +++ b/meson.version @@ -1 +1 @@ -260~rc2 +260~rc3 From 3df88e836cf97010c7161f422382210b56a87562 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 12 Mar 2026 15:08:07 +0100 Subject: [PATCH 0232/1296] man: document explicitly that ProtectHome= has no effect on non-standard homedir locations Fixes: #41045 --- man/systemd.exec.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index e7d5e63c963de..093cd2780b65e 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -1538,6 +1538,9 @@ CapabilityBoundingSet=~CAP_B CAP_C DynamicUser= is set. This setting cannot ensure protection in all cases. In general it has the same limitations as ReadOnlyPaths=, see below. + Note that this setting provides no protection if home directories are placed at a non-standard + location, i.e. outside of the hierarchies listed above. + From 67957b1464ee433f205d8bbba90602a720bd2e05 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 12 Mar 2026 16:15:47 +0900 Subject: [PATCH 0233/1296] test-network: drop duplicated definition of networkd_pid() --- test/test-network/systemd-networkd-tests.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 1f3a99a040a52..835f31656e839 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -987,7 +987,7 @@ def networkd_invocation_id(): return check_output('systemctl show --value -p InvocationID systemd-networkd.service') def networkd_pid(): - return check_output('systemctl show --value -p MainPID systemd-networkd.service') + return int(check_output('systemctl show --value -p MainPID systemd-networkd.service')) def read_networkd_log(invocation_id=None, since=None): if not invocation_id: @@ -1051,9 +1051,6 @@ def restart_networkd(show_logs=True): pid = networkd_pid() print(f'Restarted systemd-networkd.service: PID={pid}, Invocation ID={invocation_id}') -def networkd_pid(): - return int(check_output('systemctl show --value -p MainPID systemd-networkd.service')) - def networkctl(*args): # Do not call networkctl if networkd is in failed state. # Otherwise, networkd may be restarted and we may get wrong results. From bd061abd87987e8233aa8fa91d209d42637df8f8 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 12 Mar 2026 16:48:29 +0900 Subject: [PATCH 0234/1296] test-network: improve reliability of test case of DHCPRELEASE message --- test/test-network/systemd-networkd-tests.py | 77 ++++++++++++++------- 1 file changed, 52 insertions(+), 25 deletions(-) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 835f31656e839..b1754cd80d25b 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -7992,27 +7992,7 @@ def test_dhcp_client_ipv4_only(self): self.teardown_nftset('addr4', 'network4', 'ifindex') - def test_dhcp_client_send_release(self): - check_output('ip netns add ns-bridge') - check_output('ip netns exec ns-bridge ip link add bridge99 type bridge') - check_output('ip netns exec ns-bridge ip link set bridge99 address 12:34:56:78:90:ab') - check_output('ip netns exec ns-bridge ip link set bridge99 up') - - check_output('ip link add client type veth peer clientp') - check_output('ip link set clientp netns ns-bridge') - check_output('ip netns exec ns-bridge ip link set clientp master bridge99') - check_output('ip netns exec ns-bridge ip link set clientp up') - - check_output('ip link add server type veth peer serverp') - check_output('ip link set serverp netns ns-bridge') - check_output('ip netns exec ns-bridge ip link set serverp master bridge99') - check_output('ip netns exec ns-bridge ip link set serverp up') - - check_output('ip netns add ns-server') - check_output('ip link set server netns ns-server') - check_output('ip netns exec ns-server ip link set server up') - check_output('ip netns exec ns-server ip address add 192.0.2.1/24 dev server') - + def _test_dhcp_client_send_release_one(self) -> bool: start_dnsmasq( namespace='ns-server', interface='server', @@ -8020,7 +8000,6 @@ def test_dhcp_client_send_release(self): ipv4_router='192.0.2.1', ) - copy_network_unit('25-dhcp-client-simple.network') start_networkd() self.wait_online('client:routable') @@ -8038,15 +8017,63 @@ def test_dhcp_client_send_release(self): networkctl('down', 'client') - print('## dnsmasq log') + success = False for _ in range(20): time.sleep(0.5) output = read_dnsmasq_log_file() if 'DHCPRELEASE' in output: - print(output) + success = True + break + + print('## dnsmasq log') + print(output) + + return success + + def test_dhcp_client_send_release(self): + check_output('ip netns add ns-bridge') + check_output('ip netns exec ns-bridge ip link add bridge99 type bridge') + check_output('ip netns exec ns-bridge ip link set bridge99 address 12:34:56:78:90:ab') + check_output('ip netns exec ns-bridge ip link set bridge99 up') + + check_output('ip link add client type veth peer clientp') + check_output('ip link set clientp netns ns-bridge') + check_output('ip netns exec ns-bridge ip link set clientp master bridge99') + check_output('ip netns exec ns-bridge ip link set clientp up') + + check_output('ip link add server type veth peer serverp') + check_output('ip link set serverp netns ns-bridge') + check_output('ip netns exec ns-bridge ip link set serverp master bridge99') + check_output('ip netns exec ns-bridge ip link set serverp up') + + check_output('ip netns add ns-server') + check_output('ip link set server netns ns-server') + check_output('ip netns exec ns-server ip link set server up') + check_output('ip netns exec ns-server ip address add 192.0.2.1/24 dev server') + + copy_network_unit('25-dhcp-client-simple.network') + + ''' + Sending DHCPRELEASE is best-effort. Even if send() succeeds, the packet may be dropped later in the + networking stack (e.g. due to unresolved neighbor state or interface teardown). Userspace cannot + reliably determine whether the packet was eventually transmitted or dropped. + + Hence, the test below may be flaky. In most cases, neighbor resolution completes quickly enough and + the packet is transmitted before the interface is brought down. Running the test multiple times + should make it sufficiently reliable. + ''' + + first = True + for _ in range(5): + if not first: + stop_dnsmasq() + stop_networkd(show_logs=False) + + first = False + + if self._test_dhcp_client_send_release_one(): break else: - print(output) self.fail('Timed out waiting for DHCPRELEASE in dnsmasq log') def test_dhcp_client_ipv4_dbus_status(self): From 8e764acf1ea39bd9fa93a061b9e5156e07a79274 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 12 Mar 2026 17:38:37 +0900 Subject: [PATCH 0235/1296] test-network: also check if DHCPRELEASE is sent on stopping networkd --- test/test-network/systemd-networkd-tests.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index b1754cd80d25b..8aee898aab3d0 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -7992,7 +7992,7 @@ def test_dhcp_client_ipv4_only(self): self.teardown_nftset('addr4', 'network4', 'ifindex') - def _test_dhcp_client_send_release_one(self) -> bool: + def _test_dhcp_client_send_release_one(self, stop=True) -> bool: start_dnsmasq( namespace='ns-server', interface='server', @@ -8015,7 +8015,10 @@ def _test_dhcp_client_send_release_one(self) -> bool: self.assertRegex(output, r'192.0.2.0/24 proto kernel scope link src 192.0.2.10[0-9]') self.assertRegex(output, r'192.0.2.1 proto dhcp scope link src 192.0.2.10[0-9]') - networkctl('down', 'client') + if stop: + stop_networkd() + else: + networkctl('down', 'client') success = False for _ in range(20): @@ -8074,7 +8077,16 @@ def test_dhcp_client_send_release(self): if self._test_dhcp_client_send_release_one(): break else: - self.fail('Timed out waiting for DHCPRELEASE in dnsmasq log') + self.fail('Timed out waiting for DHCPRELEASE in dnsmasq log (on stopping networkd)') + + for _ in range(5): + stop_dnsmasq() + stop_networkd(show_logs=False) + + if self._test_dhcp_client_send_release_one(stop=False): + break + else: + self.fail('Timed out waiting for DHCPRELEASE in dnsmasq log (on bringing down interface)') def test_dhcp_client_ipv4_dbus_status(self): copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv4-only.network') From cbea0e34c4932188616632530623247023e55097 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 12 Mar 2026 16:16:30 +0000 Subject: [PATCH 0236/1296] portable: avoid passing through ID/version fields to LogExtraFields= when they contain control characters Found by Claude Code Review. Follow-up for e8114a4f86efa9a176962bbebbba4cb8b5a1c322 --- src/portable/portable.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/portable/portable.c b/src/portable/portable.c index df505bbe2d5d3..a7f3ce9dfd052 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -1382,9 +1382,19 @@ static int append_release_log_fields( /* Find an ID first, in order of preference from more specific to less specific: IMAGE_ID -> ID */ id = strv_find_first_field((char *const *)field_ids[type], fields); + if (id && string_has_cc(id, /* ok= */ NULL)) { + log_debug("os-release file '%s' contains control characters in the ID field, skipping.", + release->name); + id = NULL; + } /* Then the version, same logic, prefer the more specific one */ version = strv_find_first_field((char *const *)field_versions[type], fields); + if (version && string_has_cc(version, /* ok= */ NULL)) { + log_debug("os-release file '%s' contains control characters in the version field, skipping.", + release->name); + version = NULL; + } /* If there's no valid version to be found, simply omit it. */ if (!id && !version) From 651100c946b6839e518936fb34a0ce4e1ae2b2f6 Mon Sep 17 00:00:00 2001 From: Franck Bui Date: Fri, 13 Mar 2026 10:19:15 +0100 Subject: [PATCH 0237/1296] zsh: don't install _sd_machines when machined is disabled --- shell-completion/zsh/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell-completion/zsh/meson.build b/shell-completion/zsh/meson.build index c5ee8d43a90d4..eb5bb4b6a4a2f 100644 --- a/shell-completion/zsh/meson.build +++ b/shell-completion/zsh/meson.build @@ -30,7 +30,7 @@ foreach item : [ ['_run0', ''], ['_sd_bus_address', ''], ['_sd_hosts_or_user_at_host', ''], - ['_sd_machines', ''], + ['_sd_machines', 'ENABLE_MACHINED'], ['_sd_outputmodes', ''], ['_sd_unit_files', ''], ['_systemd', ''], From c6815436809268cf2adc797be85e050a65a7280d Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 12 Mar 2026 20:39:23 +0000 Subject: [PATCH 0238/1296] homed: fix copypasta in openssl calls decrypted_size/encrypted_size are sizes, not pointers to buffers Reported on yeswehack.com as: YWH-PGM9780-134 Follow-up for 70a5db5822c8056b53d9a4a9273ad12cb5f87a92 --- src/home/homework-fscrypt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/home/homework-fscrypt.c b/src/home/homework-fscrypt.c index 1c700999825ba..c2134142ded6c 100644 --- a/src/home/homework-fscrypt.c +++ b/src/home/homework-fscrypt.c @@ -256,7 +256,7 @@ static int fscrypt_slot_try_one( assert((size_t) decrypted_size_out1 <= decrypted_size); - if (EVP_DecryptFinal_ex(context, (uint8_t*) decrypted_size + decrypted_size_out1, &decrypted_size_out2) != 1) + if (EVP_DecryptFinal_ex(context, (uint8_t*) decrypted + decrypted_size_out1, &decrypted_size_out2) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finish decryption of volume key."); assert((size_t) decrypted_size_out1 + (size_t) decrypted_size_out2 < decrypted_size); @@ -520,7 +520,7 @@ static int fscrypt_slot_set( assert((size_t) encrypted_size_out1 <= encrypted_size); - if (EVP_EncryptFinal_ex(context, (uint8_t*) encrypted_size + encrypted_size_out1, &encrypted_size_out2) != 1) + if (EVP_EncryptFinal_ex(context, (uint8_t*) encrypted + encrypted_size_out1, &encrypted_size_out2) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finish encryption of volume key."); assert((size_t) encrypted_size_out1 + (size_t) encrypted_size_out2 < encrypted_size); From e799263ff4160565953ac6b52be3706d91133bd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 13 Mar 2026 12:02:07 +0100 Subject: [PATCH 0239/1296] test-network: handle the case where dnsmasq is slow to start better MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit > read_dnsmasq_log_file() will raise FileNotFoundError if dnsmasq hasn’t created the > log file yet (or if the file was just removed by stop_dnsmasq() before the restart). > This would error the test instead of retrying. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- test/test-network/systemd-networkd-tests.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 8aee898aab3d0..bab725bd23943 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -8023,7 +8023,10 @@ def _test_dhcp_client_send_release_one(self, stop=True) -> bool: success = False for _ in range(20): time.sleep(0.5) - output = read_dnsmasq_log_file() + try: + output = read_dnsmasq_log_file() + except FileNotFoundError: + output = "" if 'DHCPRELEASE' in output: success = True break From 95aff47112cff5607db7e34d78aa9db813818405 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 13 Mar 2026 11:33:25 +0100 Subject: [PATCH 0240/1296] boot: check that `ret_version` is valid in tpm_log_tagged_event In a project I'm working on I recently observed a boot failure with the most recent version of systemd. It seems it is triggered by bb19b61049 which fixed a bug that now leads to the function being excuted differently. The code is missing a check if `*ret_version` is actually valid in the `ret_measured = true` case. --- src/boot/measure.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/boot/measure.c b/src/boot/measure.c index 3c51998d1b298..cf3d4254b86cb 100644 --- a/src/boot/measure.c +++ b/src/boot/measure.c @@ -312,7 +312,8 @@ EFI_STATUS tpm_log_tagged_event( if (err != EFI_SUCCESS) return err; - *ret_measured = true; + if (ret_measured) + *ret_measured = true; return EFI_SUCCESS; } From 1ed967364f9ad538a2152f0a9cf0d98e131e737a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 13 Mar 2026 14:36:51 +0100 Subject: [PATCH 0241/1296] update TODO --- TODO | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TODO b/TODO index a069429636caf..bbe887bde5b08 100644 --- a/TODO +++ b/TODO @@ -121,6 +121,9 @@ Deprecations and removals: Features: +* crypttab/gpt-auto-generator: allow explicit control over which unlock mechs + to permit, and maybe have a global headless kernel cmdline option + * start making use of the new --graceful switch to util-linux' umount command * make systemd work nicely without /bin/sh, logins and associated shell tools around From 60b72a49beff4d87ad2f4bb8b16878217ce26e38 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 13 Mar 2026 14:39:43 +0100 Subject: [PATCH 0242/1296] update TODO --- TODO | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/TODO b/TODO index bbe887bde5b08..a3a92824f6670 100644 --- a/TODO +++ b/TODO @@ -127,12 +127,9 @@ Features: * start making use of the new --graceful switch to util-linux' umount command * make systemd work nicely without /bin/sh, logins and associated shell tools around - - add a small unit that just prints "boot complete" which we can pull in - wherever we pull in getty@1.service, but is conditioned on /bin/sh being - gone. - - get rid of /bin/rm in ExecStart= of system-update-cleanup.service - make sure debug shell service (sushell) has a nice failure mode, prints a message and reboots - varlink interface for "systemctl start" and friends + - https://github.com/util-linux/util-linux/issues/4117 * drop NV_ORDERLY flag from the product uuid nvpcr. Effect of the flag is that it pushes the thing into TPM RAM, but a TPM usually has very little of that, From 54f880b02ecf7362e630ffc885d1466df6ee6820 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 13 Mar 2026 11:10:47 +0000 Subject: [PATCH 0243/1296] udev: fix review mixup The previous version in the PR changed variable and sanitized it in place. The second version switched to skip if CCs are in the string instead, but didn't move back to the original variable. Because it's an existing variable, no CI caught it. Follow-up for 16325b35fa6ecb25f66534a562583ce3b96d52f3 --- src/udev/scsi_id/scsi_id.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/udev/scsi_id/scsi_id.c b/src/udev/scsi_id/scsi_id.c index b57f31b5935f4..e3438897199f9 100644 --- a/src/udev/scsi_id/scsi_id.c +++ b/src/udev/scsi_id/scsi_id.c @@ -443,7 +443,7 @@ static int scsi_id(char *maj_min_dev) { if (dev_scsi.tgpt_group[0] != '\0') printf("ID_TARGET_PORT=%s\n", dev_scsi.tgpt_group); if (dev_scsi.unit_serial_number[0] != '\0' && utf8_is_valid(dev_scsi.unit_serial_number) && !string_has_cc(dev_scsi.unit_serial_number, /* ok= */ NULL)) - printf("ID_SCSI_SERIAL=%s\n", serial_str); + printf("ID_SCSI_SERIAL=%s\n", dev_scsi.unit_serial_number); goto out; } From 334c71eed898152672d408ee8308be80338ae662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 13 Mar 2026 09:52:23 +0100 Subject: [PATCH 0244/1296] shared/tar-util: wrap some long lines, normalize indentation --- src/shared/tar-util.c | 73 ++++++++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/src/shared/tar-util.c b/src/shared/tar-util.c index 17c03e910bb74..d2a991ba135e9 100644 --- a/src/shared/tar-util.c +++ b/src/shared/tar-util.c @@ -472,9 +472,8 @@ static int archive_unpack_special_inode( return log_error_errno(errno, "Failed to fstat() '%s': %m", path); if (((st.st_mode ^ filetype) & S_IFMT) != 0) - return log_error_errno( - SYNTHETIC_ERRNO(ENODEV), - "Special node '%s' we just created is of a wrong type: %m", path); + return log_error_errno(SYNTHETIC_ERRNO(ENODEV), + "Special node '%s' we just created is of a wrong type: %m", path); return TAKE_FD(fd); } @@ -552,7 +551,8 @@ static int archive_entry_read_acl( if (r == ARCHIVE_EOF) break; if (r != ARCHIVE_OK) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unexpected error while iterating through ACLs."); + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Unexpected error while iterating through ACLs."); assert(rtype == type); @@ -781,7 +781,8 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { ar = sym_archive_read_open_fd(a, input_fd, 64 * 1024); if (ar != ARCHIVE_OK) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to initialize archive context: %s", sym_archive_error_string(a)); + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to initialize archive context: %s", sym_archive_error_string(a)); OpenInode *open_inodes = NULL; @@ -811,14 +812,16 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { if (ar == ARCHIVE_EOF) break; if (!IN_SET(ar, ARCHIVE_OK, ARCHIVE_WARN)) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse archive: %s", sym_archive_error_string(a)); + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse archive: %s", + sym_archive_error_string(a)); const char *p = NULL; r = archive_entry_pathname_safe(entry, &p); if (r < 0) return log_error_errno(r, "Invalid path name in entry, refusing."); if (ar == ARCHIVE_WARN) - log_warning("Non-critical error found while parsing '%s' from the archive, ignoring: %s", p ?: ".", sym_archive_error_string(a)); + log_warning("Non-critical error found while parsing '%s' from the archive, ignoring: %s", + p ?: ".", sym_archive_error_string(a)); if (!p) { /* This is the root inode */ @@ -838,7 +841,8 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { if (r < 0) return r; if (open_inodes[0].filetype != S_IFDIR) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Archives root inode is not a directory, refusing."); + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Archives root inode is not a directory, refusing."); continue; } @@ -930,7 +934,9 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { "Invalid hardlink path name '%s' in entry, refusing.", target); _cleanup_close_ int target_fd = -EBADF; - r = chaseat(tree_fd, target, CHASE_PROHIBIT_SYMLINKS|CHASE_AT_RESOLVE_IN_ROOT|CHASE_NOFOLLOW, /* ret_path= */ NULL, &target_fd); + r = chaseat(tree_fd, target, + CHASE_PROHIBIT_SYMLINKS|CHASE_AT_RESOLVE_IN_ROOT|CHASE_NOFOLLOW, + /* ret_path= */ NULL, &target_fd); if (r < 0) return log_error_errno( r, @@ -942,9 +948,9 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { /* Refuse hardlinking directories early. */ if (!inode_type_can_hardlink(verify_st.st_mode)) - return log_error_errno( - SYNTHETIC_ERRNO(EBADF), - "Refusing to hardlink inode '%s' of type '%s': %m", target, inode_type_to_string(verify_st.st_mode)); + return log_error_errno(SYNTHETIC_ERRNO(EBADF), + "Refusing to hardlink inode '%s' of type '%s': %m", + target, inode_type_to_string(verify_st.st_mode)); if (linkat(target_fd, "", parent_fd, e, AT_EMPTY_PATH) < 0) { if (errno != ENOENT) @@ -959,16 +965,16 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { _cleanup_close_ int target_parent_fd = -EBADF; _cleanup_free_ char *target_filename = NULL; - r = chaseat(tree_fd, target, CHASE_PROHIBIT_SYMLINKS|CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_EXTRACT_FILENAME|CHASE_NOFOLLOW, &target_filename, &target_parent_fd); + r = chaseat(tree_fd, target, + CHASE_PROHIBIT_SYMLINKS|CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_EXTRACT_FILENAME|CHASE_NOFOLLOW, + &target_filename, &target_parent_fd); if (r < 0) - return log_error_errno( - r, - "Failed to find inode '%s' which shall be hardlinked as '%s': %m", target, j); + return log_error_errno(r, "Failed to find inode '%s' which shall be hardlinked as '%s': %m", + target, j); if (linkat(target_parent_fd, target_filename, parent_fd, e, /* flags= */ 0) < 0) - return log_error_errno( - errno, - "Failed to hardlink inode '%s' as '%s': %m", target, j); + return log_error_errno(errno, "Failed to hardlink inode '%s' as '%s': %m", + target, j); } continue; @@ -1006,7 +1012,8 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { const char *w = startswith(e, ".wh."); if (w) { if (!filename_is_valid(w)) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Invalid whiteout file entry '%s', refusing.", e); + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Invalid whiteout file entry '%s', refusing.", e); r = archive_unpack_whiteout(a, entry, parent_fd, empty_to_root(parent_path), w, j); if (r < 0) @@ -1044,9 +1051,9 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { break; default: - return log_error_errno( - SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "Unexpected file type %i of '%s', refusing.", (int) filetype, j); + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Unexpected file type %i of '%s', refusing.", + (int) filetype, j); } } else { /* This is some intermediary node in the path that we haven't opened yet. Create it with default attributes */ @@ -1397,7 +1404,8 @@ static int archive_item( sym_archive_entry_set_hardlink(entry, hardlink); if (sym_archive_write_header(d->archive, entry) != ARCHIVE_OK) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive entry header: %s", sym_archive_error_string(d->archive)); + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to write archive entry header: %s", sym_archive_error_string(d->archive)); return RECURSE_DIR_CONTINUE; } @@ -1525,7 +1533,9 @@ static int archive_item( } if (sym_archive_write_header(d->archive, entry) != ARCHIVE_OK) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive entry header: %s", sym_archive_error_string(d->archive)); + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to write archive entry header: %s", + sym_archive_error_string(d->archive)); if (S_ISREG(sx->stx_mode)) { assert(data_fd >= 0); @@ -1543,7 +1553,9 @@ static int archive_item( la_ssize_t k; k = sym_archive_write_data(d->archive, buffer, l); if (k < 0) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive data: %s", sym_archive_error_string(d->archive)); + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to write archive data: %s", + sym_archive_error_string(d->archive)); } } @@ -1574,11 +1586,13 @@ int tar_c(int tree_fd, int output_fd, const char *filename, TarFlags flags) { else r = sym_archive_write_set_format_pax(a); if (r != ARCHIVE_OK) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to set libarchive output format: %s", sym_archive_error_string(a)); + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to set libarchive output format: %s", sym_archive_error_string(a)); r = sym_archive_write_open_fd(a, output_fd); if (r != ARCHIVE_OK) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to set libarchive output file: %s", sym_archive_error_string(a)); + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to set libarchive output file: %s", sym_archive_error_string(a)); _cleanup_(make_archive_data_done) struct make_archive_data data = { .archive = a, @@ -1599,7 +1613,8 @@ int tar_c(int tree_fd, int output_fd, const char *filename, TarFlags flags) { r = sym_archive_write_close(a); if (r != ARCHIVE_OK) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to finish writing archive: %s", sym_archive_error_string(a)); + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Unable to finish writing archive: %s", sym_archive_error_string(a)); return 0; } From aa8cf865b98a23ee65b32c56b1f1d338ac9cf0a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 13 Mar 2026 10:11:22 +0100 Subject: [PATCH 0245/1296] test-tar-extract: fix error value in messages --- src/import/test-tar-extract.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/import/test-tar-extract.c b/src/import/test-tar-extract.c index f7fa06f044f15..0d44196c38e25 100644 --- a/src/import/test-tar-extract.c +++ b/src/import/test-tar-extract.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "errno-util.h" #include "fd-util.h" #include "libarchive-util.h" #include "main-func.h" @@ -19,11 +20,11 @@ static int run(int argc, char **argv) { if (r < 0) return r; - _cleanup_close_ int input_fd = open(argv[1], O_RDONLY | O_CLOEXEC); + _cleanup_close_ int input_fd = RET_NERRNO(open(argv[1], O_RDONLY | O_CLOEXEC)); if (input_fd < 0) return log_error_errno(input_fd, "Cannot open %s: %m", argv[1]); - _cleanup_close_ int output_fd = open(argv[2], O_DIRECTORY | O_CLOEXEC); + _cleanup_close_ int output_fd = RET_NERRNO(open(argv[2], O_DIRECTORY | O_CLOEXEC)); if (output_fd < 0) return log_error_errno(output_fd, "Cannot open %s: %m", argv[2]); From 4f0989d42d8f4f81c41284582937e38b066d5e59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 13 Mar 2026 10:42:46 +0100 Subject: [PATCH 0246/1296] test-tar-extract: rename and add support for creating archives This makes it much easier to test importd code without the surrounding machinery. --- src/import/meson.build | 2 +- src/import/test-tar-extract.c | 38 ----------------------- src/import/test-tar.c | 58 +++++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 39 deletions(-) delete mode 100644 src/import/test-tar-extract.c create mode 100644 src/import/test-tar.c diff --git a/src/import/meson.build b/src/import/meson.build index c0589ddd4ed57..8349202a329ad 100644 --- a/src/import/meson.build +++ b/src/import/meson.build @@ -88,7 +88,7 @@ executables += [ 'sources' : files('import-generator.c'), }, test_template + { - 'sources' : files('test-tar-extract.c'), + 'sources' : files('test-tar.c'), 'type' : 'manual', }, test_template + { diff --git a/src/import/test-tar-extract.c b/src/import/test-tar-extract.c deleted file mode 100644 index 0d44196c38e25..0000000000000 --- a/src/import/test-tar-extract.c +++ /dev/null @@ -1,38 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "errno-util.h" -#include "fd-util.h" -#include "libarchive-util.h" -#include "main-func.h" -#include "tar-util.h" -#include "tests.h" - -static int run(int argc, char **argv) { - int r; - - test_setup_logging(LOG_DEBUG); - - if (argc != 3) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Need two arguments exactly: "); - - r = dlopen_libarchive(); - if (r < 0) - return r; - - _cleanup_close_ int input_fd = RET_NERRNO(open(argv[1], O_RDONLY | O_CLOEXEC)); - if (input_fd < 0) - return log_error_errno(input_fd, "Cannot open %s: %m", argv[1]); - - _cleanup_close_ int output_fd = RET_NERRNO(open(argv[2], O_DIRECTORY | O_CLOEXEC)); - if (output_fd < 0) - return log_error_errno(output_fd, "Cannot open %s: %m", argv[2]); - - r = tar_x(input_fd, output_fd, /* flags= */ TAR_SELINUX); - if (r < 0) - return log_error_errno(r, "tar_x failed: %m"); - - return 0; -} - -DEFINE_MAIN_FUNCTION(run); diff --git a/src/import/test-tar.c b/src/import/test-tar.c new file mode 100644 index 0000000000000..c0e55c3597edc --- /dev/null +++ b/src/import/test-tar.c @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "errno-util.h" +#include "fd-util.h" +#include "libarchive-util.h" +#include "main-func.h" +#include "tar-util.h" +#include "tests.h" + +static int run(int argc, char **argv) { + int r; + + test_setup_logging(LOG_DEBUG); + + if (argc != 4) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Need three arguments exactly: -c | -x "); + + bool create; + if (streq(argv[1], "-c")) + create = true; + else if (streq(argv[1], "-x")) + create = false; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown operation '%s'.", argv[1]); + + r = dlopen_libarchive(); + if (r < 0) + return r; + + int flags = create ? O_CREAT | O_WRONLY | O_TRUNC : O_RDONLY; + _cleanup_close_ int fd1 = RET_NERRNO(open(argv[2], flags | O_CLOEXEC, 0666)); + if (fd1 < 0) + return log_error_errno(fd1, "Cannot open %s: %m", argv[2]); + + if (!create) { + r = RET_NERRNO(mkdir(argv[3], 0777)); + if (r < 0 && r != -EEXIST) + return log_error_errno(r, "Failed to mkdir %s: %m", argv[3]); + } + + _cleanup_close_ int fd2 = RET_NERRNO(open(argv[3], O_DIRECTORY | O_CLOEXEC)); + if (fd2 < 0) + return log_error_errno(fd2, "Cannot open %s: %m", argv[3]); + + if (create) + r = tar_c(fd2, fd1, argv[2], /* flags= */ TAR_SELINUX); + else + r = tar_x(fd1, fd2, /* flags= */ TAR_SELINUX); + if (r < 0) + return log_error_errno(r, "tar %s failed: %m", argv[1]); + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); From 4b4e0947530f3d7f80c44111c7212e16ea188d2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 13 Mar 2026 11:08:07 +0100 Subject: [PATCH 0247/1296] import: skip sockets and fifos when creating archives Fixes #40239. $ SYSTEMD_LOG_LEVEL=debug SYSTEMD_LOG_LOCATION=1 build/test-tar -c /var/tmp/tar1.tar /var/tmp/with-fifo/ src/basic/dlfcn-util.c:66: Loaded shared library 'libarchive.so.13' via dlopen(). src/shared/tar-util.c:1422: Archiving '.'... src/basic/dlfcn-util.c:66: Loaded shared library 'libacl.so.1' via dlopen(). src/shared/tar-util.c:1152: Skipping './fifo' (fifo). src/shared/tar-util.c:1152: Skipping './unix' (sock). --- src/shared/tar-util.c | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/shared/tar-util.c b/src/shared/tar-util.c index d2a991ba135e9..33395e96bcf21 100644 --- a/src/shared/tar-util.c +++ b/src/shared/tar-util.c @@ -1130,6 +1130,36 @@ struct make_archive_data { int have_unique_mount_id; }; +static int filter_item( + int inode_fd, + const struct statx *sx, + const char *path) { + mode_t m; + int r; + + assert(inode_fd >= 0); + assert(sx); + assert(path); + + if (FLAGS_SET(sx->stx_mask, STATX_TYPE)) + m = sx->stx_mode; + else { + struct stat st; + r = RET_NERRNO(fstat(inode_fd, &st)); + if (r < 0) + return log_error_errno(r, "Failed to stat '%s': %m", path); + m = st.st_mode; + } + + /* Filter out sockets, fifos, and weird misc fds such as eventfds() that have no inode type. */ + if (IN_SET(m & S_IFMT, S_IFSOCK, S_IFIFO, 0)) { + log_debug("Skipping '%s' (%s).", path, inode_type_to_string(m) ?: "unknown"); + return false; + } + + return true; +} + static int hardlink_lookup( struct make_archive_data *d, int inode_fd, @@ -1387,7 +1417,13 @@ static int archive_item( assert(inode_fd >= 0); assert(sx); - log_debug("Archiving %s\n", path); + r = filter_item(inode_fd, sx, path); + if (r < 0) + return r; + if (r == 0) + return RECURSE_DIR_CONTINUE; + + log_debug("Archiving '%s'...", path); _cleanup_(archive_entry_freep) struct archive_entry *entry = NULL; entry = sym_archive_entry_new(); From 908dfa11649e5505ca6c444ab01d2c76b6297640 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 13 Mar 2026 23:02:53 +0000 Subject: [PATCH 0248/1296] NEWS: finalize place and date --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 4ded869260a4b..fcdefd2028864 100644 --- a/NEWS +++ b/NEWS @@ -526,7 +526,7 @@ CHANGES WITH 260 in spe: noxiouz, patrick, ppkramer-hub, r-vdp, safforddr, scarlet-storm, sd416, seidlerv, smosia, tuhaowen, zefr0x - — Edinburgh, 2026/03/12 + — Edinburgh, 2026/03/13 CHANGES WITH 259: From 1bbf72d7700a0b93264c9ff105e2aee8b7701541 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 13 Mar 2026 23:03:20 +0000 Subject: [PATCH 0249/1296] meson: bump version to v260~rc4 --- meson.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.version b/meson.version index 636509f058b33..24463c2890d71 100644 --- a/meson.version +++ b/meson.version @@ -1 +1 @@ -260~rc3 +260~rc4 From 6dcabd5f5e8d21a1ef83ea4294539ad9874cd536 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Fri, 13 Mar 2026 17:09:40 +0100 Subject: [PATCH 0250/1296] coccinelle: simplify file exclusions Use Coccinelle's "depends on" directive to exclude files from certain transformations. This should make them a bit simpler and possibly faster, since we don't have to shell out to Python. Unfortunately, this works only for file/directory exclusions. For function and other more complex exclusions we still need to use Python, at least for now. Also, completely drop the file exclusion for man/ in the xsprintf transformation, since we filter out everything under man/ before we even run Coccinelle (in run-coccinelle.sh). --- coccinelle/dup-fcntl.cocci | 5 +-- coccinelle/isempty.cocci | 70 +++++++++++++++---------------- coccinelle/sd_build_pair.cocci | 13 +++--- coccinelle/timestamp-is-set.cocci | 7 ++-- coccinelle/xsprintf.cocci | 3 +- coccinelle/zz-drop-braces.cocci | 9 ++-- 6 files changed, 49 insertions(+), 58 deletions(-) diff --git a/coccinelle/dup-fcntl.cocci b/coccinelle/dup-fcntl.cocci index 2c87f70dc3d4f..434e48d51415a 100644 --- a/coccinelle/dup-fcntl.cocci +++ b/coccinelle/dup-fcntl.cocci @@ -1,8 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -@@ /* We want to stick with dup() in test-fd-util.c */ -position p : script:python() { p[0].file != "src/test/test-fd-util.c" }; +@ depends on !(file in "src/test/test-fd-util.c") @ expression fd; @@ -- dup@p(fd) +- dup(fd) + fcntl(fd, F_DUPFD, 3) diff --git a/coccinelle/isempty.cocci b/coccinelle/isempty.cocci index 2089970886499..4a266c4195329 100644 --- a/coccinelle/isempty.cocci +++ b/coccinelle/isempty.cocci @@ -1,103 +1,99 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -@@ /* Disable this transformation for the test-string-util.c */ -position p : script:python() { p[0].file != "src/test/test-string-util.c" }; +@ depends on !(file in "src/test/test-string-util.c") @ expression s; @@ ( -- strv_length@p(s) == 0 +- strv_length(s) == 0 + strv_isempty(s) | -- strv_length@p(s) <= 0 +- strv_length(s) <= 0 + strv_isempty(s) | -- strv_length@p(s) > 0 +- strv_length(s) > 0 + !strv_isempty(s) | -- strv_length@p(s) != 0 +- strv_length(s) != 0 + !strv_isempty(s) | -- strlen@p(s) == 0 +- strlen(s) == 0 + isempty(s) | -- strlen@p(s) <= 0 +- strlen(s) <= 0 + isempty(s) | -- strlen@p(s) > 0 +- strlen(s) > 0 + !isempty(s) | -- strlen@p(s) != 0 +- strlen(s) != 0 + !isempty(s) | -- strlen_ptr@p(s) == 0 +- strlen_ptr(s) == 0 + isempty(s) | -- strlen_ptr@p(s) <= 0 +- strlen_ptr(s) <= 0 + isempty(s) | -- strlen_ptr@p(s) > 0 +- strlen_ptr(s) > 0 + !isempty(s) | -- strlen_ptr@p(s) != 0 +- strlen_ptr(s) != 0 + !isempty(s) ) -@@ /* Disable this transformation for the hashmap.h, set.h, test-hashmap.c, test-hashmap-plain.c */ -position p : script:python() { - p[0].file != "src/basic/hashmap.h" and - p[0].file != "src/basic/set.h" and - p[0].file != "src/test/test-hashmap.c" and - p[0].file != "src/test/test-hashmap-plain.c" - }; +@ depends on !(file in "src/basic/hashmap.h") + && !(file in "src/basic/set.h") + && !(file in "src/test/test-hashmap.c") + && !(file in "src/test/test-hashmap-plain.c") @ expression s; @@ ( -- hashmap_size@p(s) == 0 +- hashmap_size(s) == 0 + hashmap_isempty(s) | -- hashmap_size@p(s) <= 0 +- hashmap_size(s) <= 0 + hashmap_isempty(s) | -- hashmap_size@p(s) > 0 +- hashmap_size(s) > 0 + !hashmap_isempty(s) | -- hashmap_size@p(s) != 0 +- hashmap_size(s) != 0 + !hashmap_isempty(s) | -- ordered_hashmap_size@p(s) == 0 +- ordered_hashmap_size(s) == 0 + ordered_hashmap_isempty(s) | -- ordered_hashmap_size@p(s) <= 0 +- ordered_hashmap_size(s) <= 0 + ordered_hashmap_isempty(s) | -- ordered_hashmap_size@p(s) > 0 +- ordered_hashmap_size(s) > 0 + !ordered_hashmap_isempty(s) | -- ordered_hashmap_size@p(s) != 0 +- ordered_hashmap_size(s) != 0 + !ordered_hashmap_isempty(s) | -- set_size@p(s) == 0 +- set_size(s) == 0 + set_isempty(s) | -- set_size@p(s) <= 0 +- set_size(s) <= 0 + set_isempty(s) | -- set_size@p(s) > 0 +- set_size(s) > 0 + !set_isempty(s) | -- set_size@p(s) != 0 +- set_size(s) != 0 + !set_isempty(s) | -- ordered_set_size@p(s) == 0 +- ordered_set_size(s) == 0 + ordered_set_isempty(s) | -- ordered_set_size@p(s) <= 0 +- ordered_set_size(s) <= 0 + ordered_set_isempty(s) | -- ordered_set_size@p(s) > 0 +- ordered_set_size(s) > 0 + !ordered_set_isempty(s) | -- ordered_set_size@p(s) != 0 +- ordered_set_size(s) != 0 + !ordered_set_isempty(s) ) @@ diff --git a/coccinelle/sd_build_pair.cocci b/coccinelle/sd_build_pair.cocci index 8c9af38cb2d13..e97c273e71f34 100644 --- a/coccinelle/sd_build_pair.cocci +++ b/coccinelle/sd_build_pair.cocci @@ -1,22 +1,21 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -@@ /* Disable this transformation on test-json.c */ -position p : script:python() { p[0].file != "src/test/test-json.c" }; +@ depends on !(file in "src/test/test-json.c") @ expression key, val; @@ ( -- SD_JSON_BUILD_PAIR@p(key, SD_JSON_BUILD_BOOLEAN(val)) +- SD_JSON_BUILD_PAIR(key, SD_JSON_BUILD_BOOLEAN(val)) + SD_JSON_BUILD_PAIR_BOOLEAN(key, val) | -- SD_JSON_BUILD_PAIR@p(key, SD_JSON_BUILD_INTEGER(val)) +- SD_JSON_BUILD_PAIR(key, SD_JSON_BUILD_INTEGER(val)) + SD_JSON_BUILD_PAIR_INTEGER(key, val) | -- SD_JSON_BUILD_PAIR@p(key, SD_JSON_BUILD_STRING(val)) +- SD_JSON_BUILD_PAIR(key, SD_JSON_BUILD_STRING(val)) + SD_JSON_BUILD_PAIR_STRING(key, val) | -- SD_JSON_BUILD_PAIR@p(key, SD_JSON_BUILD_UNSIGNED(val)) +- SD_JSON_BUILD_PAIR(key, SD_JSON_BUILD_UNSIGNED(val)) + SD_JSON_BUILD_PAIR_UNSIGNED(key, val) | -- SD_JSON_BUILD_PAIR@p(key, SD_JSON_BUILD_VARIANT(val)) +- SD_JSON_BUILD_PAIR(key, SD_JSON_BUILD_VARIANT(val)) + SD_JSON_BUILD_PAIR_VARIANT(key, val) ) diff --git a/coccinelle/timestamp-is-set.cocci b/coccinelle/timestamp-is-set.cocci index 2d251fa2057e9..34f37458f74f3 100644 --- a/coccinelle/timestamp-is-set.cocci +++ b/coccinelle/timestamp-is-set.cocci @@ -1,9 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -@@ +/* We want to stick with the literal expression in the implementation of timestamp_is_set(), i.e. in time-util.h */ +@ depends on !(file in "src/basic/time-util.h") @ expression x; constant USEC_INFINITY = USEC_INFINITY; -/* We want to stick with the literal expression in the implementation of timestamp_is_set(), i.e. in time-util.c */ -position p : script:python() { p[0].file != "src/basic/time-util.h" }; @@ ( - x > 0 && x < USEC_INFINITY @@ -12,7 +11,7 @@ position p : script:python() { p[0].file != "src/basic/time-util.h" }; - x < USEC_INFINITY && x > 0 + timestamp_is_set(x) | -- x@p > 0 && x != USEC_INFINITY +- x > 0 && x != USEC_INFINITY + timestamp_is_set(x) | - x != USEC_INFINITY && x > 0 diff --git a/coccinelle/xsprintf.cocci b/coccinelle/xsprintf.cocci index 3b38090652e79..669734946caa4 100644 --- a/coccinelle/xsprintf.cocci +++ b/coccinelle/xsprintf.cocci @@ -1,8 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ @@ -position p : script:python() { not p[0].file.startswith("man/") }; expression e, fmt; expression list vaargs; @@ -- snprintf@p(e, sizeof(e), fmt, vaargs); +- snprintf(e, sizeof(e), fmt, vaargs); + xsprintf(e, fmt, vaargs); diff --git a/coccinelle/zz-drop-braces.cocci b/coccinelle/zz-drop-braces.cocci index 7a3382c9a7b9e..a1d9f8d4a34d5 100644 --- a/coccinelle/zz-drop-braces.cocci +++ b/coccinelle/zz-drop-braces.cocci @@ -1,13 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -@@ -position p : script:python() { p[0].file != "src/journal/lookup3.c" }; -expression e,e1; +@ depends on !(file in "src/journal/lookup3.c") @ +expression e, e1; @@ - if (e) { + if (e) ( - e1@p; + e1; | - return e1@p; + return e1; ) - } From 876d36f68abf5ff9606f2c0862641a931328c0d0 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 15 Mar 2026 21:47:21 +0100 Subject: [PATCH 0251/1296] ci: Add full output from claude to debug intermittent failures --- .github/workflows/claude-review.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index d0c0632a14e0a..c29028569bf00 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -176,6 +176,7 @@ jobs: # so it cannot post comments or modify the PR. github_token: ${{ secrets.GITHUB_TOKEN }} track_progress: false + show_full_output: "true" additional_permissions: | actions: read claude_args: | From 7178e3829fb7458e95ba6f9fc598226b0e8d1b76 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 15 Mar 2026 21:53:01 +0100 Subject: [PATCH 0252/1296] ci: Fix several robustness issues in claude-review workflow - Use github.paginate() for listComments to handle PRs with 100+ comments - Make line optional in review schema to allow file-level comments - Skip createReviewComment for comments without a line number - Fix failed count to exclude skipped file-level comments - Pass review result via env var instead of expression injection - Use core.warning() instead of console.log() for JSON parse failures - Fix MARKER insertion for single-line summaries that have no newline - Require "@claude review" instead of just "@claude" to trigger Co-developed-by: Claude --- .github/workflows/claude-review.yml | 40 ++++++++++++++++------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index c29028569bf00..ef5afe620eda9 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -32,13 +32,13 @@ jobs: github.repository_owner == 'systemd' && ((github.event_name == 'issue_comment' && github.event.issue.pull_request && - contains(github.event.comment.body, '@claude') && + contains(github.event.comment.body, '@claude review') && contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association)) || (github.event_name == 'pull_request_review_comment' && - contains(github.event.comment.body, '@claude') && + contains(github.event.comment.body, '@claude review') && contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association)) || (github.event_name == 'pull_request_review' && - contains(github.event.review.body, '@claude') && + contains(github.event.review.body, '@claude review') && contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.review.author_association))) permissions: @@ -71,12 +71,10 @@ jobs: const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`; const MARKER = ""; - const {data: issueComments} = await github.rest.issues.listComments({ - owner, - repo, - issue_number: prNumber, - per_page: 100, - }); + const issueComments = await github.paginate( + github.rest.issues.listComments, + { owner, repo, issue_number: prNumber, per_page: 100 }, + ); const existing = issueComments.find((c) => c.body && c.body.includes(MARKER)); @@ -146,7 +144,7 @@ jobs: "type": "array", "items": { "type": "object", - "required": ["file", "line", "severity", "body"], + "required": ["file", "severity", "body"], "properties": { "file": { "type": "string", @@ -331,6 +329,7 @@ jobs: uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea env: STRUCTURED_OUTPUT: ${{ needs.review.outputs.structured_output }} + REVIEW_RESULT: ${{ needs.review.result }} PR_NUMBER: ${{ needs.setup.outputs.pr_number }} HEAD_SHA: ${{ needs.setup.outputs.head_sha }} COMMENT_ID: ${{ needs.setup.outputs.comment_id }} @@ -346,7 +345,7 @@ jobs: /* If the review job failed or was cancelled, update the tracking * comment to reflect that and bail out. */ - if ("${{ needs.review.result }}" !== "success") { + if (process.env.REVIEW_RESULT !== "success") { await github.rest.issues.updateComment({ owner, repo, @@ -372,7 +371,7 @@ jobs: if (typeof review.summary === "string") summary = review.summary; } catch (e) { - console.log(`Failed to parse structured output: ${e.message}`); + core.warning(`Failed to parse structured output: ${e.message}`); } } @@ -382,8 +381,13 @@ jobs: * comments is handled by Claude in the prompt, so we just post whatever * it returns. Using individual comments (rather than a review) means * re-runs only add new comments instead of creating a whole new review. */ + const inlineComments = comments.filter((c) => c.line); + const skipped = comments.length - inlineComments.length; + if (skipped > 0) + console.log(`Skipping ${skipped} file-level comment(s) (no line number).`); + let posted = 0; - for (const c of comments) { + for (const c of inlineComments) { console.log(` Posting comment on ${c.file}:${c.line}`); try { await github.rest.pulls.createReviewComment({ @@ -404,19 +408,19 @@ jobs: } if (posted > 0) - console.log(`Posted ${posted}/${comments.length} inline comment(s).`); - else if (comments.length > 0) - console.log(`Could not post any of ${comments.length} inline comment(s) — see warnings above.`); + console.log(`Posted ${posted}/${inlineComments.length} inline comment(s).`); + else if (inlineComments.length > 0) + console.log(`Could not post any of ${inlineComments.length} inline comment(s) — see warnings above.`); else console.log("No inline comments to post."); - const failed = comments.length > 0 && posted < comments.length; + const failed = inlineComments.length > 0 && posted < inlineComments.length; /* Update the tracking comment with Claude's summary. */ if (!summary) summary = "Claude review: no issues found :tada:\n\n" + MARKER; else if (!summary.includes(MARKER)) - summary = summary.replace(/\n/, `\n${MARKER}\n`); + summary += "\n\n" + MARKER; summary += `\n\n[Workflow run](${runUrl})`; await github.rest.issues.updateComment({ From bbd707a882344d1dc69366146423ccfbd3d253f2 Mon Sep 17 00:00:00 2001 From: davidak Date: Fri, 13 Mar 2026 02:45:41 +0100 Subject: [PATCH 0253/1296] docs: document AI use disclosure consistently The example also adds the model version to have it for reference. --- AGENTS.md | 4 +++- docs/CONTRIBUTING.md | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 9befc01a594b4..418d1705419be 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,4 +37,6 @@ display. This is critical for diagnosing build and test failures. ## AI Contribution Disclosure -Per project policy: if you use AI code generation tools, you **must disclose** this in commit messages by adding e.g. `Co-developed-by: Claude `. All AI-generated output requires thorough human review before submission. +Per project policy: if you use AI code generation tools, you **must disclose** this in commit messages +by adding e.g. `Co-developed-by: Claude Opus 4.6 `. +All AI-generated output requires thorough human review before submission. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 970510a3123b7..0b4214de0ce81 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -53,7 +53,8 @@ See [reporting of security vulnerabilities](https://systemd.io/SECURITY). ## Using AI Code Generators If you use an AI code generator such as ChatGPT, Claude, Copilot, Llama or a similar tool, this must be -disclosed in the commit messages and pull request description. +disclosed in the commit messages by adding e.g. `Co-developed-by: Claude Opus 4.6 ` +and pull request description. The quality bar for contributions to this project is high, and unlikely to be met by an unattended AI tool, without significant manual corrections. Always thoroughly review and correct any such outputs, for example From 9e7a2793ad5447bda6eeedd11ff713986bffb4ed Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 16 Mar 2026 11:12:34 +0100 Subject: [PATCH 0254/1296] ci: Insist on structured output from claude-review workflow In some cases claude is not outputting structured JSON at the end. Let's modify the prompt a bit to hopefully mitigate the issue. --- .github/workflows/claude-review.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index ef5afe620eda9..0a90a89ca4728 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -313,7 +313,17 @@ jobs: in the appropriate severity section, after the existing items. - Do NOT reorder, reword, or remove existing items. - Return the final JSON object with your `comments` array and `summary` string. + ## CRITICAL: Return structured JSON output + + Your FINAL action must be to return a JSON object matching the following + JSON schema — do NOT end with a text summary or narrative. The `--json-schema` + flag is set, so your last response must be the structured JSON result, not a + text message. + + ```json + ${{ env.REVIEW_SCHEMA }} + ``` + Do NOT attempt to post comments or use any MCP tools to modify the PR. post: From b1c446a0b000953532572fdb864c51e43a11f4fe Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 16 Mar 2026 10:36:33 +0100 Subject: [PATCH 0255/1296] ci: Run claude-review workflow automatically on trusted PRs --- .github/workflows/claude-review.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 0a90a89ca4728..861613e65d844 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -10,6 +10,8 @@ name: Claude Review on: + pull_request_target: + types: [opened, synchronize, reopened] # Strangely enough you have to use issue_comment to react to regular comments on PRs. # See https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#pull_request_comment-use-issue_comment. issue_comment: @@ -30,7 +32,10 @@ jobs: if: | github.repository_owner == 'systemd' && - ((github.event_name == 'issue_comment' && + ((github.event_name == 'pull_request_target' && + contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.pull_request.author_association) && + github.event.pull_request.user.login != 'YHNdnzj') || + (github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '@claude review') && contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association)) || From a9ac5cdf1850bc3962646653d01988d2b82a1d85 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 16 Mar 2026 12:01:36 +0100 Subject: [PATCH 0256/1296] ci: Update github-script action version to 8.0.0 in claude-review --- .github/workflows/claude-review.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 861613e65d844..160bab85c4965 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -67,7 +67,7 @@ jobs: - name: Create or update tracking comment id: tracking - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd with: script: | const owner = context.repo.owner; @@ -341,7 +341,7 @@ jobs: steps: - name: Post review comments - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd env: STRUCTURED_OUTPUT: ${{ needs.review.outputs.structured_output }} REVIEW_RESULT: ${{ needs.review.result }} From 3a76c0959fde4430434099f20412ea34445ea566 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 16 Mar 2026 13:49:31 +0100 Subject: [PATCH 0257/1296] ci: Fix several issues in claude-review workflow Address feedback from facebook/bpfilter#472: - Fix setFailed error message counting file-level comments (without line numbers) that are intentionally skipped, use inlineComments.length instead of comments.length - Fix double severity prefix in inline comments: the prompt told Claude to prefix body with **must-fix**/etc but the post job also prepended "Claude: ", producing "Claude: **must-fix**: ...". Now the prompt says not to prefix and the post job adds "Claude **severity**: " using the structured severity field - Move error tracking instructions to a top-level section after all phases so they apply to all runs, not just the first run - Clarify that line is optional: use "should be" instead of "must be" and document that omitting line still surfaces the comment in the tracking comment summary - Distinguish cancelled vs failed in tracking comment message - Add side: "RIGHT" and subject_type: "line" to createReviewComment per GitHub API recommendations - Downgrade partial inline comment posting failures to warnings; only fail the job when no comments at all could be posted Co-developed-by: Claude Opus 4.6 --- .github/workflows/claude-review.yml | 45 +++++++++++++++++------------ 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 160bab85c4965..293aef3b6a2de 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -209,7 +209,7 @@ jobs: HEAD SHA: ${{ needs.setup.outputs.head_sha }} You are a code reviewer for the ${{ github.repository }} project. Review this pull request and - produce a structured JSON result containing your review comments. Do NOT attempt + produce a structured JSON result containing your review. Do NOT attempt to post comments yourself — just return the JSON. You are in the upstream repo without the patch applied. Do not apply it. @@ -251,12 +251,13 @@ jobs: Give each subagent the PR title, description, full patch, and the list of changed files. Each subagent must return a JSON array of issues: - `[{"file": "path", "line": , "severity": "must-fix|suggestion|nit", "body": "..."}]` + `[{"file": "path", "line": (optional), "severity": "must-fix|suggestion|nit", "body": "..."}]` - `line` must be a line number from the NEW side of the diff **that appears inside + `line` should be a line number from the NEW side of the diff **that appears inside a diff hunk** (i.e. a line that is shown in the patch output). GitHub's review comment API rejects lines outside the diff context, so never reference lines - that are not visible in the patch. + that are not visible in the patch. If you cannot determine a valid diff line, + omit `line` — the comment will still appear in the tracking comment summary. Each subagent MUST verify its findings before returning them: - For style/convention claims, check at least 3 existing examples in the codebase to confirm @@ -273,8 +274,9 @@ jobs: comment if one already exists on the same file and line about the same problem. Also check for author replies that dismiss or reject a previous comment — do NOT re-raise an issue the PR author has already responded to disagreeing with. - 4. Prefix ALL comment bodies with a severity tag: `**must-fix**: `, `**suggestion**: `, - or `**nit**: `. + 4. Do NOT prefix `body` with a severity tag — the severity is already + captured in the `severity` field and will be added automatically when + posting inline comments. 5. Write a `summary` field in markdown for a top-level tracking comment. **If no existing tracking comment was found (first run):** @@ -298,13 +300,6 @@ jobs: Omit empty sections. Each checkbox item must correspond to an entry in `comments`. If there are no issues at all, write a short message saying the PR looks good. - Throughout all phases, track any errors that prevented you from doing - your job fully: permission denials (403, "Resource not accessible by - integration"), tools that were not available, rate limits, or any other - failures that degraded the review quality. If there were any, append a - `### Errors` section listing each failed tool/action and the error - message, so maintainers can fix the workflow configuration. - **If an existing tracking comment was found (subsequent run):** Use the existing comment as the starting point. Preserve the order and wording of all existing items. Then apply these updates: @@ -318,6 +313,15 @@ jobs: in the appropriate severity section, after the existing items. - Do NOT reorder, reword, or remove existing items. + ## Error tracking + + Throughout all phases, track any errors that prevented you from doing + your job fully: permission denials (403, "Resource not accessible by + integration"), tools that were not available, rate limits, or any other + failures that degraded the review quality. If there were any, append a + `### Errors` section to the summary listing each failed tool/action and + the error message, so maintainers can fix the workflow configuration. + ## CRITICAL: Return structured JSON output Your FINAL action must be to return a JSON object matching the following @@ -361,11 +365,12 @@ jobs: /* If the review job failed or was cancelled, update the tracking * comment to reflect that and bail out. */ if (process.env.REVIEW_RESULT !== "success") { + const verb = process.env.REVIEW_RESULT === "cancelled" ? "was cancelled" : "failed"; await github.rest.issues.updateComment({ owner, repo, comment_id: commentId, - body: `Claude review failed — see [workflow run](${runUrl}) for details.\n\n${MARKER}`, + body: `Claude review ${verb} — see [workflow run](${runUrl}) for details.\n\n${MARKER}`, }); core.setFailed("Review job did not succeed."); return; @@ -412,7 +417,9 @@ jobs: commit_id: headSha, path: c.file, line: c.line, - body: `Claude: ${c.body}`, + side: "RIGHT", + subject_type: "line", + body: `Claude: **${c.severity}**: ${c.body}`, }); posted++; } catch (e) { @@ -429,8 +436,6 @@ jobs: else console.log("No inline comments to post."); - const failed = inlineComments.length > 0 && posted < inlineComments.length; - /* Update the tracking comment with Claude's summary. */ if (!summary) summary = "Claude review: no issues found :tada:\n\n" + MARKER; @@ -447,5 +452,7 @@ jobs: console.log("Tracking comment updated successfully."); - if (failed) - core.setFailed(`Failed to post ${comments.length - posted}/${comments.length} inline comment(s).`); + if (inlineComments.length > 0 && posted === 0) + core.setFailed(`Could not post any of ${inlineComments.length} inline comment(s) — see warnings above.`); + else if (posted < inlineComments.length) + core.warning(`${inlineComments.length - posted}/${inlineComments.length} inline comment(s) could not be posted.`); From 52c4aca21e14df25851e27cf2b77b5bcf6b59236 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 16 Mar 2026 16:07:55 +0100 Subject: [PATCH 0258/1296] ci: Revert side/subject_type change for claude review workflow This doesn't seem to actually work, so revert the change. --- .github/workflows/claude-review.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 293aef3b6a2de..de31e0b818d9b 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -417,8 +417,6 @@ jobs: commit_id: headSha, path: c.file, line: c.line, - side: "RIGHT", - subject_type: "line", body: `Claude: **${c.severity}**: ${c.body}`, }); posted++; From 94a695d89a8d72d7c9335772fdd423f073a6e90a Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 16 Mar 2026 15:33:11 +0100 Subject: [PATCH 0259/1296] ci: Review PRs per-commit and attach comments to correct commits Switch claude-review from reviewing the entire PR diff at once to reviewing each commit individually via subagents. Each commit review subagent receives the PR context, preceding commit diffs, and its own commit diff, then returns comments tagged with the commit SHA. This ensures review comments are attached to the correct commit via the GitHub API rather than all pointing at HEAD. Also add Bash(gh:*) to allowed tools so subagents can fetch per-commit diffs via `gh api` without needing local git objects, and remove CI analysis (needs to be delayed until CI finishes to be useful). Co-developed-by: Claude Opus 4.6 --- .github/workflows/claude-review.yml | 60 ++++++++++++++++------------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index de31e0b818d9b..6d5bb01724570 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -117,7 +117,6 @@ jobs: pull-requests: read # Fetch PR comments and reviews issues: read # Fetch issue comments id-token: write # Authenticate with AWS via OIDC - actions: read outputs: structured_output: ${{ steps.claude.outputs.structured_output }} @@ -149,7 +148,7 @@ jobs: "type": "array", "items": { "type": "object", - "required": ["file", "severity", "body"], + "required": ["file", "severity", "body", "commit"], "properties": { "file": { "type": "string", @@ -166,6 +165,10 @@ jobs: "body": { "type": "string", "description": "The review comment body in markdown" + }, + "commit": { + "type": "string", + "description": "The SHA of the PR commit that introduced the code being commented on" } } } @@ -180,8 +183,6 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} track_progress: false show_full_output: "true" - additional_permissions: | - actions: read claude_args: | --model us.anthropic.claude-opus-4-6-v1 --max-turns 100 @@ -189,18 +190,15 @@ jobs: Read,LS,Grep,Glob,Task, Bash(cat:*),Bash(test:*),Bash(printf:*),Bash(jq:*),Bash(head:*),Bash(tail:*), Bash(git:*),Bash(grep:*),Bash(find:*),Bash(ls:*),Bash(wc:*), + Bash(gh:api *), Bash(diff:*),Bash(sed:*),Bash(awk:*),Bash(sort:*),Bash(uniq:*), mcp__github__get_pull_request, - mcp__github__get_pull_request_diff, mcp__github__get_pull_request_files, mcp__github__get_pull_request_reviews, mcp__github__get_pull_request_comments, mcp__github__get_pull_request_review_comments, mcp__github__get_pull_request_status, mcp__github__get_issue_comments, - mcp__github_ci__get_ci_status, - mcp__github_ci__get_workflow_run_details, - mcp__github_ci__download_job_log, " --json-schema '${{ env.REVIEW_SCHEMA }}' prompt: | @@ -218,11 +216,13 @@ jobs: Use the GitHub MCP server tools to fetch PR data. For all tools, pass owner `${{ github.repository_owner }}`, repo `${{ github.event.repository.name }}`, and pullNumber/issue_number ${{ needs.setup.outputs.pr_number }}: - - `mcp__github__get_pull_request_diff` to get the PR diff - `mcp__github__get_pull_request` to get the PR title, body, and metadata - `mcp__github__get_pull_request_comments` to get top-level PR comments - `mcp__github__get_pull_request_reviews` to get PR reviews + Fetch the list of commits in the PR using: + `gh api repos/{owner}/{repo}/pulls/{number}/commits --paginate --jq '.[].sha'` + Also fetch issue comments using `mcp__github__get_issue_comments` with issue_number ${{ needs.setup.outputs.pr_number }}. @@ -230,28 +230,33 @@ jobs: in the issue comments. If one exists, you will use it as the basis for your `summary` in Phase 3. - Check CI status for the PR head commit using `mcp__github_ci__get_ci_status`. - If any workflow runs have failed, use `mcp__github_ci__get_workflow_run_details` - and `mcp__github_ci__download_job_log` to fetch the failure logs. Pass these - logs to the review subagents in Phase 2 so they can identify whether the PR - changes caused the failures. + ## Phase 2: Per-commit review with subagents - ## Phase 2: Parallel review subagents + Review each commit in the PR individually, in order (oldest first). For each + commit, fetch its diff using `gh api repos/{owner}/{repo}/commits/{sha} + -H 'Accept: application/vnd.github.diff'` via Bash, then launch a subagent + to review that commit's changes. - Review: + Each commit review subagent receives: + - The PR title and description (for overall context) + - The diffs of all preceding commits in the PR (for context on what was already changed) + - The commit message and SHA of the commit being reviewed + - The commit diff (from `gh api`) + + Each commit review subagent reviews: - Code quality, style, and best practices - Potential bugs, issues, incorrect logic - Security implications - CLAUDE.md compliance - - CI failures (if any logs were fetched in Phase 1) - - For every category, launch subagents to review them in parallel. Group related sections - as needed — use 2-4 subagents based on PR size and scope. - Give each subagent the PR title, description, full patch, and the list of changed files. + Each commit review subagent must return a JSON array of issues: + `[{"file": "path", "line": (optional), "severity": "must-fix|suggestion|nit", "body": "...", "commit": ""}]` - Each subagent must return a JSON array of issues: - `[{"file": "path", "line": (optional), "severity": "must-fix|suggestion|nit", "body": "..."}]` + The `commit` field MUST be set to the SHA of the commit being reviewed. + Each commit review subagent MUST only return comments about changes in the commit it is + reviewing — do NOT comment on code from preceding commits, even if it was + provided for context. If a preceding commit has an issue, it will be caught + by the subagent reviewing that commit. `line` should be a line number from the NEW side of the diff **that appears inside a diff hunk** (i.e. a line that is shown in the patch output). GitHub's review @@ -259,15 +264,18 @@ jobs: that are not visible in the patch. If you cannot determine a valid diff line, omit `line` — the comment will still appear in the tracking comment summary. - Each subagent MUST verify its findings before returning them: + Each commit review subagent MUST verify its findings before returning them: - For style/convention claims, check at least 3 existing examples in the codebase to confirm the pattern actually exists before flagging a violation. - For "use X instead of Y" suggestions, confirm X actually exists and works for this case. - If unsure, don't include the issue. + Launch commit review subagents for all commits in parallel (e.g. if + reviewing a 7-commit PR, launch all 7 commit review subagents at once). + ## Phase 3: Collect, deduplicate, and summarize - After ALL subagents complete: + After ALL commit review subagents complete: 1. Collect all issues. Merge duplicates (same file, lines within 3 of each other, same problem). 2. Drop low-confidence findings. 3. Check the existing inline review comments fetched in Phase 1. Do NOT include a @@ -414,7 +422,7 @@ jobs: owner, repo, pull_number: prNumber, - commit_id: headSha, + commit_id: c.commit, path: c.file, line: c.line, body: `Claude: **${c.severity}**: ${c.body}`, From c751714d8ce357b593e2be15fa3d0bd4e83df961 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 16 Mar 2026 18:45:58 +0000 Subject: [PATCH 0260/1296] man: document that with RuntimeDirecoryPreserve= dirs are under /run/private/ This is not immediately obvious so document it explicitly. Follow-up for 40cd2ecc26b776ef085fd0fd29e8e96f6422a0d3 --- man/systemd.exec.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 093cd2780b65e..48bec7361bde1 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -1773,6 +1773,15 @@ StateDirectory=aaa/bbb ccc tmpfs, then for system services the directories specified in RuntimeDirectory= are removed when the system is rebooted. + If DynamicUser= is used together with + RuntimeDirectoryPreserve= set to values other than , the logic + is slightly altered: the RuntimeDirectory= directories are created below + /run/private/, which is a host directory made inaccessible to unprivileged + users, which ensures that access to these directories cannot be gained through dynamic user ID + recycling. Symbolic links are created to hide this difference in behaviour. Both from the + perspective of the host and from inside the unit, the relevant directories hence always appear + directly below /run/. + From cccb77304b14bd3dcde0eb04f2b261f170b8a7d3 Mon Sep 17 00:00:00 2001 From: dongshengyuan Date: Mon, 16 Mar 2026 14:29:37 +0800 Subject: [PATCH 0261/1296] add-ug-bo-translation --- po/LINGUAS | 2 + po/bo.po | 1198 ++++++++++++++++++++++++++++++++++++++++++++++++++++ po/ug.po | 1195 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 2395 insertions(+) create mode 100644 po/bo.po create mode 100644 po/ug.po diff --git a/po/LINGUAS b/po/LINGUAS index e520dec8b355d..d167d935423ae 100644 --- a/po/LINGUAS +++ b/po/LINGUAS @@ -45,3 +45,5 @@ ar km kw kk +ug +bo diff --git a/po/bo.po b/po/bo.po new file mode 100644 index 0000000000000..853f1828c1679 --- /dev/null +++ b/po/bo.po @@ -0,0 +1,1198 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Tibetan translation for systemd. +# Dongshengyuan , 2026. +msgid "" +msgstr "" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-03-16 14:21+0000\n" +"Last-Translator: Dongshengyuan \n" +"Language-Team: Tibetan\n" +"Language: bo\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: manual\n" + +#: src/core/org.freedesktop.systemd1.policy.in:22 +msgid "Send passphrase back to system" +msgstr "གསང་ཚིག་རྒྱུད་ལམ་ལ་ཕྱིར་སྐྱེལ།" + +#: src/core/org.freedesktop.systemd1.policy.in:23 +msgid "" +"Authentication is required to send the entered passphrase back to the system." +msgstr "ནང་འཇུག་བྱས་པའི་གསང་ཚིག་རྒྱུད་ལམ་ལ་ཕྱིར་སྐྱེལ་བར་ར་སྤྲོད་དགོས།" + +#: src/core/org.freedesktop.systemd1.policy.in:33 +msgid "Manage system services or other units" +msgstr "རྒྱུད་ལམ་ཞབས་ཞུ་དང་སྡེ་ཚན་གཞན་དག་དོ་དམ་བྱེད།" + +#: src/core/org.freedesktop.systemd1.policy.in:34 +msgid "Authentication is required to manage system services or other units." +msgstr "རྒྱུད་ལམ་ཞབས་ཞུ་དང་སྡེ་ཚན་གཞན་དག་དོ་དམ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/core/org.freedesktop.systemd1.policy.in:43 +msgid "Manage system service or unit files" +msgstr "རྒྱུད་ལམ་ཞབས་ཞུའམ་སྡེ་ཚན་ཡིག་ཆ་དོ་དམ་བྱེད།" + +#: src/core/org.freedesktop.systemd1.policy.in:44 +msgid "Authentication is required to manage system service or unit files." +msgstr "རྒྱུད་ལམ་ཞབས་ཞུའམ་སྡེ་ཚན་ཡིག་ཆ་དོ་དམ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/core/org.freedesktop.systemd1.policy.in:54 +msgid "Set or unset system and service manager environment variables" +msgstr "རྒྱུད་ལམ་དང་ཞབས་ཞུ་དོ་དམ་པའི་ཁོར་ཡུག་འགྱུར་ཚད་སྒྲིག་གམ་སུབ།" + +#: src/core/org.freedesktop.systemd1.policy.in:55 +msgid "" +"Authentication is required to set or unset system and service manager " +"environment variables." +msgstr "རྒྱུད་ལམ་དང་ཞབས་ཞུ་དོ་དམ་པའི་ཁོར་ཡུག་འགྱུར་ཚད་སྒྲིག་པའམ་སུབ་པར་ར་སྤྲོད་དགོས།" + +#: src/core/org.freedesktop.systemd1.policy.in:64 +msgid "Reload the systemd state" +msgstr "systemd གནས་ཚུལ་བསྐྱར་འཇུག་བྱེད།" + +#: src/core/org.freedesktop.systemd1.policy.in:65 +msgid "Authentication is required to reload the systemd state." +msgstr "systemd གནས་ཚུལ་བསྐྱར་འཇུག་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/core/org.freedesktop.systemd1.policy.in:74 +msgid "Dump the systemd state without rate limits" +msgstr "མྱུར་ཚད་ཚད་བཀག་མེད་པར systemd གནས་ཚུལ་ཕྱིར་འདོན།" + +#: src/core/org.freedesktop.systemd1.policy.in:75 +msgid "" +"Authentication is required to dump the systemd state without rate limits." +msgstr "མྱུར་ཚད་ཚད་བཀག་མེད་པར systemd གནས་ཚུལ་ཕྱིར་འདོན་པར་ར་སྤྲོད་དགོས།" + +#: src/home/org.freedesktop.home1.policy:13 +msgid "Create a home area" +msgstr "ཁྱིམ་ཁོངས་གསར་བཟོ།" + +#: src/home/org.freedesktop.home1.policy:14 +msgid "Authentication is required to create a user's home area." +msgstr "སྤྱོད་མཁན་གྱི་ཁྱིམ་ཁོངས་གསར་བཟོ་བར་ར་སྤྲོད་དགོས།" + +#: src/home/org.freedesktop.home1.policy:23 +msgid "Remove a home area" +msgstr "ཁྱིམ་ཁོངས་སྤོ་བ།" + +#: src/home/org.freedesktop.home1.policy:24 +msgid "Authentication is required to remove a user's home area." +msgstr "སྤྱོད་མཁན་གྱི་ཁྱིམ་ཁོངས་སྤོ་བར་ར་སྤྲོད་དགོས།" + +#: src/home/org.freedesktop.home1.policy:33 +msgid "Check credentials of a home area" +msgstr "ཁྱིམ་ཁོངས་ཀྱི་དཔང་ཡིག་ཞིབ་བཤེར།" + +#: src/home/org.freedesktop.home1.policy:34 +msgid "" +"Authentication is required to check credentials against a user's home area." +msgstr "སྤྱོད་མཁན་གྱི་ཁྱིམ་ཁོངས་ལ་བསྟུན་ནས་དཔང་ཡིག་ཞིབ་བཤེར་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/home/org.freedesktop.home1.policy:43 +msgid "Update a home area" +msgstr "ཁྱིམ་ཁོངས་གསར་བསྒྱུར།" + +#: src/home/org.freedesktop.home1.policy:44 +msgid "Authentication is required to update a user's home area." +msgstr "སྤྱོད་མཁན་གྱི་ཁྱིམ་ཁོངས་གསར་བསྒྱུར་བར་ར་སྤྲོད་དགོས།" + +#: src/home/org.freedesktop.home1.policy:53 +msgid "Update your home area" +msgstr "ཁྱེད་ཀྱི་ཁྱིམ་ཁོངས་གསར་བསྒྱུར།" + +#: src/home/org.freedesktop.home1.policy:54 +msgid "Authentication is required to update your home area." +msgstr "ཁྱེད་ཀྱི་ཁྱིམ་ཁོངས་གསར་བསྒྱུར་བར་ར་སྤྲོད་དགོས།" + +#: src/home/org.freedesktop.home1.policy:63 +msgid "Resize a home area" +msgstr "ཁྱིམ་ཁོངས་ཆེ་ཆུང་བསྒྱུར།" + +#: src/home/org.freedesktop.home1.policy:64 +msgid "Authentication is required to resize a user's home area." +msgstr "སྤྱོད་མཁན་གྱི་ཁྱིམ་ཁོངས་ཆེ་ཆུང་བསྒྱུར་བར་ར་སྤྲོད་དགོས།" + +#: src/home/org.freedesktop.home1.policy:73 +msgid "Change password of a home area" +msgstr "ཁྱིམ་ཁོངས་ཀྱི་གསང་ཚིག་བསྒྱུར།" + +#: src/home/org.freedesktop.home1.policy:74 +msgid "" +"Authentication is required to change the password of a user's home area." +msgstr "སྤྱོད་མཁན་གྱི་ཁྱིམ་ཁོངས་གསང་ཚིག་བསྒྱུར་བར་ར་སྤྲོད་དགོས།" + +#: src/home/org.freedesktop.home1.policy:83 +msgid "Activate a home area" +msgstr "ཁྱིམ་ཁོངས་སྐུལ་སློང་།" + +#: src/home/org.freedesktop.home1.policy:84 +msgid "Authentication is required to activate a user's home area." +msgstr "སྤྱོད་མཁན་གྱི་ཁྱིམ་ཁོངས་སྐུལ་སློང་བར་ར་སྤྲོད་དགོས།" + +#: src/home/org.freedesktop.home1.policy:93 +msgid "Manage Home Directory Signing Keys" +msgstr "ཁྱིམ་དཀར་ཆག་མིང་རྟགས་ལྡེ་མིག་དོ་དམ།" + +#: src/home/org.freedesktop.home1.policy:94 +msgid "Authentication is required to manage signing keys for home directories." +msgstr "ཁྱིམ་དཀར་ཆག་མིང་རྟགས་ལྡེ་མིག་དོ་དམ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/home/pam_systemd_home.c:330 +#, c-format +msgid "" +"Home of user %s is currently absent, please plug in the necessary storage " +"device or backing file system." +msgstr "སྤྱོད་མཁན %s ཡི་ཁྱིམ་དཀར་ཆག་ད་ལྟ་མེད། དགོས་ངེས་ཀྱི་གསོག་ཉར་སྒྲིག་ཆས་སམ་རྒྱབ་རྟེན་ཡིག་ཚགས་མ་ལག་སྦྲེལ་རོགས།" + +#: src/home/pam_systemd_home.c:335 +#, c-format +msgid "Too frequent login attempts for user %s, try again later." +msgstr "སྤྱོད་མཁན %s ཡི་ནང་འཛུལ་ཚོད་ལྟ་མང་དྲགས་པས་རྗེས་སུ་ཡང་བསྐྱར་ཚོད་ལྟ་བྱེད།" + +#: src/home/pam_systemd_home.c:347 +msgid "Password: " +msgstr "གསང་ཚིག: " + +#: src/home/pam_systemd_home.c:349 +#, c-format +msgid "Password incorrect or not sufficient for authentication of user %s." +msgstr "སྤྱོད་མཁན %s ཡི་གསང་ཚིག་ནོར་འཁྲུལ་ཡིན་པའམ་ར་སྤྲོད་ལ་མི་འདང་།" + +#: src/home/pam_systemd_home.c:350 +msgid "Sorry, try again: " +msgstr "དགོངས་དག ཡང་བསྐྱར་ཚོད་ལྟ: " + +#: src/home/pam_systemd_home.c:372 +msgid "Recovery key: " +msgstr "སླར་གསོ་ལྡེ་མིག: " + +#: src/home/pam_systemd_home.c:374 +#, c-format +msgid "" +"Password/recovery key incorrect or not sufficient for authentication of user " +"%s." +msgstr "སྤྱོད་མཁན %s ཡི་གསང་ཚིག/སླར་གསོ་ལྡེ་མིག་ནོར་འཁྲུལ་ཡིན་པའམ་ར་སྤྲོད་ལ་མི་འདང་།" + +#: src/home/pam_systemd_home.c:375 +msgid "Sorry, reenter recovery key: " +msgstr "དགོངས་དག སླར་གསོ་ལྡེ་མིག་ཡང་བསྐྱར་ནང་འཇུག: " + +#: src/home/pam_systemd_home.c:395 +#, c-format +msgid "Security token of user %s not inserted." +msgstr "སྤྱོད་མཁན %s ཡི་བདེ་འཇགས་རྟགས་མ་བཙུགས་པ།" + +#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 +msgid "Try again with password: " +msgstr "གསང་ཚིག་སྤྱད་ནས་ཡང་བསྐྱར་ཚོད་ལྟ: " + +#: src/home/pam_systemd_home.c:398 +#, c-format +msgid "" +"Password incorrect or not sufficient, and configured security token of user " +"%s not inserted." +msgstr "གསང་ཚིག་ནོར་འཁྲུལ་ཡིན་པའམ་མི་འདང་། སྤྱོད་མཁན %s ཡི་སྒྲིག་བཀོད་བདེ་འཇགས་རྟགས་ཀྱང་མ་བཙུགས།" + +#: src/home/pam_systemd_home.c:418 +msgid "Security token PIN: " +msgstr "བདེ་འཇགས་རྟགས PIN: " + +#: src/home/pam_systemd_home.c:435 +#, c-format +msgid "Please authenticate physically on security token of user %s." +msgstr "སྤྱོད་མཁན %s ཡི་བདེ་འཇགས་རྟགས་སྟེང་ལུས་ངོས་ར་སྤྲོད་གནང་རོགས།" + +#: src/home/pam_systemd_home.c:446 +#, c-format +msgid "Please confirm presence on security token of user %s." +msgstr "སྤྱོད་མཁན %s ཡི་བདེ་འཇགས་རྟགས་སྟེང་ཡོད་པ་ངེས་གཏན་གནང་རོགས།" + +#: src/home/pam_systemd_home.c:457 +#, c-format +msgid "Please verify user on security token of user %s." +msgstr "སྤྱོད་མཁན %s ཡི་བདེ་འཇགས་རྟགས་སྟེང་སྤྱོད་མཁན་ཞིབ་བཤེར་གནང་རོགས།" + +#: src/home/pam_systemd_home.c:466 +msgid "" +"Security token PIN is locked, please unlock it first. (Hint: Removal and re-" +"insertion might suffice.)" +msgstr "བདེ་འཇགས་རྟགས PIN སྒོ་ལྕགས་བཀག་ཟིན། སྔོན་ལ་སྒྲོལ་རོགས། (ཁ་སྣོན: ཕྱིར་བཏོན་ནས་ཡང་བསྐྱར་བཙུགས་ན་འགྲིག་སྲིད།)" + +#: src/home/pam_systemd_home.c:474 +#, c-format +msgid "Security token PIN incorrect for user %s." +msgstr "སྤྱོད་མཁན %s ཡི་བདེ་འཇགས་རྟགས PIN ནོར་འཁྲུལ།" + +#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 +#: src/home/pam_systemd_home.c:513 +msgid "Sorry, retry security token PIN: " +msgstr "དགོངས་དག བདེ་འཇགས་རྟགས PIN ཡང་བསྐྱར་ཚོད་ལྟ: " + +#: src/home/pam_systemd_home.c:493 +#, c-format +msgid "Security token PIN of user %s incorrect (only a few tries left!)" +msgstr "སྤྱོད་མཁན %s ཡི་བདེ་འཇགས་རྟགས PIN ནོར་འཁྲུལ། (ཚོད་ལྟ་ཆེས་ཉུང་ཙམ་ལས་མེད!)" + +#: src/home/pam_systemd_home.c:512 +#, c-format +msgid "Security token PIN of user %s incorrect (only one try left!)" +msgstr "སྤྱོད་མཁན %s ཡི་བདེ་འཇགས་རྟགས PIN ནོར་འཁྲུལ། (ཚོད་ལྟ་གཅིག་ལས་མེད!)" + +#: src/home/pam_systemd_home.c:679 +#, c-format +msgid "Home of user %s is currently not active, please log in locally first." +msgstr "སྤྱོད་མཁན %s ཡི་ཁྱིམ་དཀར་ཆག་ད་ལྟ་སྐུལ་སློང་མེད། སྔོན་ལ་ས་གནས་ནས་ནང་འཛུལ་གནང་རོགས།" + +#: src/home/pam_systemd_home.c:681 +#, c-format +msgid "Home of user %s is currently locked, please unlock locally first." +msgstr "སྤྱོད་མཁན %s ཡི་ཁྱིམ་དཀར་ཆག་ད་ལྟ་སྒོ་ལྕགས་བཀག་ཡོད། སྔོན་ལ་ས་གནས་ནས་སྒྲོལ་རོགས།" + +#: src/home/pam_systemd_home.c:715 +#, c-format +msgid "Too many unsuccessful login attempts for user %s, refusing." +msgstr "སྤྱོད་མཁན %s ཡི་ནང་འཛུལ་མ་ལེགས་པའི་ཚོད་ལྟ་མང་དྲགས་པས་ངོས་ལེན་མི་བྱེད།" + +#: src/home/pam_systemd_home.c:1012 +msgid "User record is blocked, prohibiting access." +msgstr "སྤྱོད་མཁན་ཐོ་བཀག་ཟིན་པས་ལྟ་སྤྱོད་བཀག་ཡོད།" + +#: src/home/pam_systemd_home.c:1016 +msgid "User record is not valid yet, prohibiting access." +msgstr "སྤྱོད་མཁན་ཐོ་ད་དུང་ནུས་ལྡན་མིན་པས་ལྟ་སྤྱོད་བཀག་ཡོད།" + +#: src/home/pam_systemd_home.c:1020 +msgid "User record is not valid anymore, prohibiting access." +msgstr "སྤྱོད་མཁན་ཐོ་ད་ནས་ནུས་མེད་པས་ལྟ་སྤྱོད་བཀག་ཡོད།" + +#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +msgid "User record not valid, prohibiting access." +msgstr "སྤྱོད་མཁན་ཐོ་ནུས་མེད་པས་ལྟ་སྤྱོད་བཀག་ཡོད།" + +#: src/home/pam_systemd_home.c:1035 +#, c-format +msgid "Too many logins, try again in %s." +msgstr "ནང་འཛུལ་མང་དྲགས་པས %s རྗེས་སུ་ཡང་བསྐྱར་ཚོད་ལྟ་བྱེད།" + +#: src/home/pam_systemd_home.c:1046 +msgid "Password change required." +msgstr "གསང་ཚིག་བསྒྱུར་དགོས།" + +#: src/home/pam_systemd_home.c:1050 +msgid "Password expired, change required." +msgstr "གསང་ཚིག་དུས་ཡོལ་ཟིན་པས་བསྒྱུར་དགོས།" + +#: src/home/pam_systemd_home.c:1056 +msgid "Password is expired, but can't change, refusing login." +msgstr "གསང་ཚིག་དུས་ཡོལ་ཟིན་ཡང་བསྒྱུར་མི་ཐུབ་པས་ནང་འཛུལ་ཁས་མི་ལེན།" + +#: src/home/pam_systemd_home.c:1060 +msgid "Password will expire soon, please change." +msgstr "གསང་ཚིག་མི་རིང་བར་དུས་ཡོལ་འགྲོ་གི་ཡོད་པས་བསྒྱུར་རོགས།" + +#: src/hostname/org.freedesktop.hostname1.policy:20 +msgid "Set hostname" +msgstr "གཙོ་འཁོར་མིང་སྒྲིག" + +#: src/hostname/org.freedesktop.hostname1.policy:21 +msgid "Authentication is required to set the local hostname." +msgstr "ས་གནས་གཙོ་འཁོར་མིང་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/hostname/org.freedesktop.hostname1.policy:30 +msgid "Set static hostname" +msgstr "བརྟན་པོའི་གཙོ་འཁོར་མིང་སྒྲིག" + +#: src/hostname/org.freedesktop.hostname1.policy:31 +msgid "" +"Authentication is required to set the statically configured local hostname, " +"as well as the pretty hostname." +msgstr "བརྟན་པོར་སྒྲིག་པའི་ས་གནས་གཙོ་འཁོར་མིང་དང pretty hostname སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/hostname/org.freedesktop.hostname1.policy:41 +msgid "Set machine information" +msgstr "འཕྲུལ་ཆས་ཆ་འཕྲིན་སྒྲིག" + +#: src/hostname/org.freedesktop.hostname1.policy:42 +msgid "Authentication is required to set local machine information." +msgstr "ས་གནས་འཕྲུལ་ཆས་ཆ་འཕྲིན་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/hostname/org.freedesktop.hostname1.policy:51 +msgid "Get product UUID" +msgstr "ཐོན་རྫས UUID ལེན" + +#: src/hostname/org.freedesktop.hostname1.policy:52 +msgid "Authentication is required to get product UUID." +msgstr "ཐོན་རྫས UUID ལེན་པར་ར་སྤྲོད་དགོས།" + +#: src/hostname/org.freedesktop.hostname1.policy:61 +msgid "Get hardware serial number" +msgstr "སྲ་ཆས་རིམ་ཨང་ལེན" + +#: src/hostname/org.freedesktop.hostname1.policy:62 +msgid "Authentication is required to get hardware serial number." +msgstr "སྲ་ཆས་རིམ་ཨང་ལེན་པར་ར་སྤྲོད་དགོས།" + +#: src/hostname/org.freedesktop.hostname1.policy:71 +msgid "Get system description" +msgstr "རྒྱུད་ལམ་འགྲེལ་བཤད་ལེན" + +#: src/hostname/org.freedesktop.hostname1.policy:72 +msgid "Authentication is required to get system description." +msgstr "རྒྱུད་ལམ་འགྲེལ་བཤད་ལེན་པར་ར་སྤྲོད་དགོས།" + +#: src/import/org.freedesktop.import1.policy:22 +msgid "Import a disk image" +msgstr "ཌིསྐ་མཚོན་རིས་ནང་འདྲེན།" + +#: src/import/org.freedesktop.import1.policy:23 +msgid "Authentication is required to import an image." +msgstr "མཚོན་རིས་ནང་འདྲེན་པར་ར་སྤྲོད་དགོས།" + +#: src/import/org.freedesktop.import1.policy:32 +msgid "Export a disk image" +msgstr "ཌིསྐ་མཚོན་རིས་ཕྱིར་འདོན།" + +#: src/import/org.freedesktop.import1.policy:33 +msgid "Authentication is required to export disk image." +msgstr "ཌིསྐ་མཚོན་རིས་ཕྱིར་འདོན་པར་ར་སྤྲོད་དགོས།" + +#: src/import/org.freedesktop.import1.policy:42 +msgid "Download a disk image" +msgstr "ཌིསྐ་མཚོན་རིས་ཕབ་ལེན།" + +#: src/import/org.freedesktop.import1.policy:43 +msgid "Authentication is required to download a disk image." +msgstr "ཌིསྐ་མཚོན་རིས་ཕབ་ལེན་པར་ར་སྤྲོད་དགོས།" + +#: src/import/org.freedesktop.import1.policy:52 +msgid "Cancel transfer of a disk image" +msgstr "ཌིསྐ་མཚོན་རིས་བརྒྱུད་སྐྱེལ་མེད་པར་བཟོ" + +#: src/import/org.freedesktop.import1.policy:53 +msgid "" +"Authentication is required to cancel the ongoing transfer of a disk image." +msgstr "ད་ལྟ་བྱེད་བཞིན་པའི་ཌིསྐ་མཚོན་རིས་བརྒྱུད་སྐྱེལ་མེད་པར་བཟོ་བར་ར་སྤྲོད་དགོས།" + +#: src/locale/org.freedesktop.locale1.policy:22 +msgid "Set system locale" +msgstr "རྒྱུད་ལམ locale སྒྲིག" + +#: src/locale/org.freedesktop.locale1.policy:23 +msgid "Authentication is required to set the system locale." +msgstr "རྒྱུད་ལམ locale སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/locale/org.freedesktop.locale1.policy:33 +msgid "Set system keyboard settings" +msgstr "རྒྱུད་ལམ་མཐེབ་གཞོང་སྒྲིག་འགོད་སྒྲིག" + +#: src/locale/org.freedesktop.locale1.policy:34 +msgid "Authentication is required to set the system keyboard settings." +msgstr "རྒྱུད་ལམ་མཐེབ་གཞོང་སྒྲིག་འགོད་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:22 +msgid "Allow applications to inhibit system shutdown" +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་སྒོ་རྒྱག་འགོག་པར་ཆོག" + +#: src/login/org.freedesktop.login1.policy:23 +msgid "" +"Authentication is required for an application to inhibit system shutdown." +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་སྒོ་རྒྱག་འགོག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:33 +msgid "Allow applications to delay system shutdown" +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་སྒོ་རྒྱག་ཕྱིར་འགྱངས་བྱེད་པར་ཆོག" + +#: src/login/org.freedesktop.login1.policy:34 +msgid "Authentication is required for an application to delay system shutdown." +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་སྒོ་རྒྱག་ཕྱིར་འགྱངས་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:44 +msgid "Allow applications to inhibit system sleep" +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་གཉིད་ཁུག་པ་འགོག་པར་ཆོག" + +#: src/login/org.freedesktop.login1.policy:45 +msgid "Authentication is required for an application to inhibit system sleep." +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་གཉིད་ཁུག་པ་འགོག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:55 +msgid "Allow applications to delay system sleep" +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་གཉིད་ཁུག་པ་ཕྱིར་འགྱངས་བྱེད་པར་ཆོག" + +#: src/login/org.freedesktop.login1.policy:56 +msgid "Authentication is required for an application to delay system sleep." +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་གཉིད་ཁུག་པ་ཕྱིར་འགྱངས་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:65 +msgid "Allow applications to inhibit automatic system suspend" +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་རང་འགུལ་འགེལ་འཇོག་འགོག་པར་ཆོག" + +#: src/login/org.freedesktop.login1.policy:66 +msgid "" +"Authentication is required for an application to inhibit automatic system " +"suspend." +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་རང་འགུལ་འགེལ་འཇོག་འགོག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:75 +msgid "Allow applications to inhibit system handling of the power key" +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་གློག་སྒོ་མཐེབ་ལ་ལག་ལེན་བྱེད་པ་འགོག་པར་ཆོག" + +#: src/login/org.freedesktop.login1.policy:76 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the power key." +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་གློག་སྒོ་མཐེབ་ལ་ལག་ལེན་བྱེད་པ་འགོག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:86 +msgid "Allow applications to inhibit system handling of the suspend key" +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་འགེལ་འཇོག་མཐེབ་ལ་ལག་ལེན་བྱེད་པ་འགོག་པར་ཆོག" + +#: src/login/org.freedesktop.login1.policy:87 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the suspend key." +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་འགེལ་འཇོག་མཐེབ་ལ་ལག་ལེན་བྱེད་པ་འགོག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:97 +msgid "Allow applications to inhibit system handling of the hibernate key" +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་ཉལ་ཉིན་མཐེབ་ལ་ལག་ལེན་བྱེད་པ་འགོག་པར་ཆོག" + +#: src/login/org.freedesktop.login1.policy:98 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the hibernate key." +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་ཉལ་ཉིན་མཐེབ་ལ་ལག་ལེན་བྱེད་པ་འགོག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:107 +msgid "Allow applications to inhibit system handling of the lid switch" +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་ལཔ་ཊོཔ་ཁ་ལེབ་སྒྱུར་མཐེབ་ལ་ལག་ལེན་བྱེད་པ་འགོག་པར་ཆོག" + +#: src/login/org.freedesktop.login1.policy:108 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the lid switch." +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་ལཔ་ཊོཔ་ཁ་ལེབ་སྒྱུར་མཐེབ་ལ་ལག་ལེན་བྱེད་པ་འགོག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:117 +msgid "Allow applications to inhibit system handling of the reboot key" +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་བསྐྱར་འགོ་མཐེབ་ལ་ལག་ལེན་བྱེད་པ་འགོག་པར་ཆོག" + +#: src/login/org.freedesktop.login1.policy:118 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the reboot key." +msgstr "མཉེན་ཆས་ཀྱིས་རྒྱུད་ལམ་བསྐྱར་འགོ་མཐེབ་ལ་ལག་ལེན་བྱེད་པ་འགོག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:128 +msgid "Allow non-logged-in user to run programs" +msgstr "ནང་འཛུལ་མ་བྱས་པའི་སྤྱོད་མཁན་ལ་ལས་རིམ་འགྲོ་བར་ཆོག" + +#: src/login/org.freedesktop.login1.policy:129 +msgid "Explicit request is required to run programs as a non-logged-in user." +msgstr "ནང་འཛུལ་མ་བྱས་པའི་སྤྱོད་མཁན་དབང་གིས་ལས་རིམ་འགྲོ་བར་གསལ་བཤད་ཀྱི་ཞུ་བ་དགོས།" + +#: src/login/org.freedesktop.login1.policy:138 +msgid "Allow non-logged-in users to run programs" +msgstr "ནང་འཛུལ་མ་བྱས་པའི་སྤྱོད་མཁན་ཚོར་ལས་རིམ་འགྲོ་བར་ཆོག" + +#: src/login/org.freedesktop.login1.policy:139 +msgid "Authentication is required to run programs as a non-logged-in user." +msgstr "ནང་འཛུལ་མ་བྱས་པའི་སྤྱོད་མཁན་དབང་གིས་ལས་རིམ་འགྲོ་བར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:148 +msgid "Allow attaching devices to seats" +msgstr "སྒྲིག་ཆས seat ལ་སྦྲེལ་བར་ཆོག" + +# Pay attention to the concept of "seat". +# +# To fully understand the meaning, please refer to session management in old ConsoleKit and new systemd-logind. +#: src/login/org.freedesktop.login1.policy:149 +msgid "Authentication is required to attach a device to a seat." +msgstr "སྒྲིག་ཆས seat ལ་སྦྲེལ་བར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:159 +msgid "Flush device to seat attachments" +msgstr "སྒྲིག་ཆས་དང seat སྦྲེལ་བ་བསྐྱར་སྒྲིག" + +#: src/login/org.freedesktop.login1.policy:160 +msgid "Authentication is required to reset how devices are attached to seats." +msgstr "སྒྲིག་ཆས seat ལ་སྦྲེལ་ཚུལ་བསྐྱར་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:169 +msgid "Power off the system" +msgstr "རྒྱུད་ལམ་སྒོ་རྒྱག" + +#: src/login/org.freedesktop.login1.policy:170 +msgid "Authentication is required to power off the system." +msgstr "རྒྱུད་ལམ་སྒོ་རྒྱག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:180 +msgid "Power off the system while other users are logged in" +msgstr "སྤྱོད་མཁན་གཞན་ནང་འཛུལ་ཡོད་སྐབས་རྒྱུད་ལམ་སྒོ་རྒྱག" + +#: src/login/org.freedesktop.login1.policy:181 +msgid "" +"Authentication is required to power off the system while other users are " +"logged in." +msgstr "སྤྱོད་མཁན་གཞན་ནང་འཛུལ་ཡོད་སྐབས་རྒྱུད་ལམ་སྒོ་རྒྱག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:191 +msgid "Power off the system while an application is inhibiting this" +msgstr "མཉེན་ཆས་ཀྱིས་འགོག་བཞིན་སྐབས་རྒྱུད་ལམ་སྒོ་རྒྱག" + +#: src/login/org.freedesktop.login1.policy:192 +msgid "" +"Authentication is required to power off the system while an application is " +"inhibiting this." +msgstr "མཉེན་ཆས་ཀྱིས་འགོག་བཞིན་སྐབས་རྒྱུད་ལམ་སྒོ་རྒྱག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:202 +msgid "Reboot the system" +msgstr "རྒྱུད་ལམ་བསྐྱར་འགོ" + +#: src/login/org.freedesktop.login1.policy:203 +msgid "Authentication is required to reboot the system." +msgstr "རྒྱུད་ལམ་བསྐྱར་འགོ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:213 +msgid "Reboot the system while other users are logged in" +msgstr "སྤྱོད་མཁན་གཞན་ནང་འཛུལ་ཡོད་སྐབས་རྒྱུད་ལམ་བསྐྱར་འགོ" + +#: src/login/org.freedesktop.login1.policy:214 +msgid "" +"Authentication is required to reboot the system while other users are logged " +"in." +msgstr "སྤྱོད་མཁན་གཞན་ནང་འཛུལ་ཡོད་སྐབས་རྒྱུད་ལམ་བསྐྱར་འགོ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:224 +msgid "Reboot the system while an application is inhibiting this" +msgstr "མཉེན་ཆས་ཀྱིས་འགོག་བཞིན་སྐབས་རྒྱུད་ལམ་བསྐྱར་འགོ" + +#: src/login/org.freedesktop.login1.policy:225 +msgid "" +"Authentication is required to reboot the system while an application is " +"inhibiting this." +msgstr "མཉེན་ཆས་ཀྱིས་འགོག་བཞིན་སྐབས་རྒྱུད་ལམ་བསྐྱར་འགོ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:235 +msgid "Halt the system" +msgstr "རྒྱུད་ལམ་མཚམས་འཇོག" + +#: src/login/org.freedesktop.login1.policy:236 +msgid "Authentication is required to halt the system." +msgstr "རྒྱུད་ལམ་མཚམས་འཇོག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:246 +msgid "Halt the system while other users are logged in" +msgstr "སྤྱོད་མཁན་གཞན་ནང་འཛུལ་ཡོད་སྐབས་རྒྱུད་ལམ་མཚམས་འཇོག" + +#: src/login/org.freedesktop.login1.policy:247 +msgid "" +"Authentication is required to halt the system while other users are logged " +"in." +msgstr "སྤྱོད་མཁན་གཞན་ནང་འཛུལ་ཡོད་སྐབས་རྒྱུད་ལམ་མཚམས་འཇོག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:257 +msgid "Halt the system while an application is inhibiting this" +msgstr "མཉེན་ཆས་ཀྱིས་འགོག་བཞིན་སྐབས་རྒྱུད་ལམ་མཚམས་འཇོག" + +#: src/login/org.freedesktop.login1.policy:258 +msgid "" +"Authentication is required to halt the system while an application is " +"inhibiting this." +msgstr "མཉེན་ཆས་ཀྱིས་འགོག་བཞིན་སྐབས་རྒྱུད་ལམ་མཚམས་འཇོག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:268 +msgid "Suspend the system" +msgstr "རྒྱུད་ལམ་འགེལ་འཇོག" + +#: src/login/org.freedesktop.login1.policy:269 +msgid "Authentication is required to suspend the system." +msgstr "རྒྱུད་ལམ་འགེལ་འཇོག་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:278 +msgid "Suspend the system while other users are logged in" +msgstr "སྤྱོད་མཁན་གཞན་ནང་འཛུལ་ཡོད་སྐབས་རྒྱུད་ལམ་འགེལ་འཇོག" + +#: src/login/org.freedesktop.login1.policy:279 +msgid "" +"Authentication is required to suspend the system while other users are " +"logged in." +msgstr "སྤྱོད་མཁན་གཞན་ནང་འཛུལ་ཡོད་སྐབས་རྒྱུད་ལམ་འགེལ་འཇོག་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:289 +msgid "Suspend the system while an application is inhibiting this" +msgstr "མཉེན་ཆས་ཀྱིས་འགོག་བཞིན་སྐབས་རྒྱུད་ལམ་འགེལ་འཇོག" + +#: src/login/org.freedesktop.login1.policy:290 +msgid "" +"Authentication is required to suspend the system while an application is " +"inhibiting this." +msgstr "མཉེན་ཆས་ཀྱིས་འགོག་བཞིན་སྐབས་རྒྱུད་ལམ་འགེལ་འཇོག་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:300 +msgid "Hibernate the system" +msgstr "རྒྱུད་ལམ་ཉལ་ཉིན" + +#: src/login/org.freedesktop.login1.policy:301 +msgid "Authentication is required to hibernate the system." +msgstr "རྒྱུད་ལམ་ཉལ་ཉིན་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:310 +msgid "Hibernate the system while other users are logged in" +msgstr "སྤྱོད་མཁན་གཞན་ནང་འཛུལ་ཡོད་སྐབས་རྒྱུད་ལམ་ཉལ་ཉིན" + +#: src/login/org.freedesktop.login1.policy:311 +msgid "" +"Authentication is required to hibernate the system while other users are " +"logged in." +msgstr "སྤྱོད་མཁན་གཞན་ནང་འཛུལ་ཡོད་སྐབས་རྒྱུད་ལམ་ཉལ་ཉིན་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:321 +msgid "Hibernate the system while an application is inhibiting this" +msgstr "མཉེན་ཆས་ཀྱིས་འགོག་བཞིན་སྐབས་རྒྱུད་ལམ་ཉལ་ཉིན" + +#: src/login/org.freedesktop.login1.policy:322 +msgid "" +"Authentication is required to hibernate the system while an application is " +"inhibiting this." +msgstr "མཉེན་ཆས་ཀྱིས་འགོག་བཞིན་སྐབས་རྒྱུད་ལམ་ཉལ་ཉིན་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:332 +msgid "Manage active sessions, users and seats" +msgstr "སྐུལ་སློང་ཅན་གྱི་གླེང་མོལ་(session) སྤྱོད་མཁན་དང seat དོ་དམ" + +#: src/login/org.freedesktop.login1.policy:333 +msgid "Authentication is required to manage active sessions, users and seats." +msgstr "སྐུལ་སློང་ཅན་གྱི session སྤྱོད་མཁན་དང seat དོ་དམ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:342 +msgid "Lock or unlock active sessions" +msgstr "སྐུལ་སློང་ཅན་གྱི session སྒོ་ལྕགས་བཀག་གམ་སྒྲོལ" + +#: src/login/org.freedesktop.login1.policy:343 +msgid "Authentication is required to lock or unlock active sessions." +msgstr "སྐུལ་སློང་ཅན་གྱི session སྒོ་ལྕགས་བཀག་གམ་སྒྲོལ་བར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:352 +msgid "Set the reboot \"reason\" in the kernel" +msgstr "kernel ནང་བསྐྱར་འགོའི \"reason\" སྒྲིག" + +#: src/login/org.freedesktop.login1.policy:353 +msgid "Authentication is required to set the reboot \"reason\" in the kernel." +msgstr "kernel ནང་བསྐྱར་འགོའི \"reason\" སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:363 +msgid "Indicate to the firmware to boot to setup interface" +msgstr "firmware ལ་སྒྲིག་འགོད་འཆར་ངོས་སུ་འགོ་ཚུགས་དགོས་ཞེས་བརྡ་སྟོན" + +#: src/login/org.freedesktop.login1.policy:364 +msgid "" +"Authentication is required to indicate to the firmware to boot to setup " +"interface." +msgstr "firmware ལ་སྒྲིག་འགོད་འཆར་ངོས་སུ་འགོ་ཚུགས་དགོས་ཞེས་བརྡ་སྟོན་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:374 +msgid "Indicate to the boot loader to boot to the boot loader menu" +msgstr "boot loader ལ་ boot loader དཀར་ཆག་ཏུ་འགོ་ཚུགས་དགོས་ཞེས་བརྡ་སྟོན" + +#: src/login/org.freedesktop.login1.policy:375 +msgid "" +"Authentication is required to indicate to the boot loader to boot to the " +"boot loader menu." +msgstr "boot loader ལ་ boot loader དཀར་ཆག་ཏུ་འགོ་ཚུགས་དགོས་ཞེས་བརྡ་སྟོན་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:385 +msgid "Indicate to the boot loader to boot a specific entry" +msgstr "boot loader ལ་དམིགས་བསལ་གྱི་འཇུག་ཚན་ཞིག་ཏུ་འགོ་ཚུགས་དགོས་ཞེས་བརྡ་སྟོན" + +#: src/login/org.freedesktop.login1.policy:386 +msgid "" +"Authentication is required to indicate to the boot loader to boot into a " +"specific boot loader entry." +msgstr "boot loader ལ་དམིགས་བསལ་གྱི boot loader འཇུག་ཚན་ཏུ་འགོ་ཚུགས་དགོས་ཞེས་བརྡ་སྟོན་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:396 +msgid "Set a wall message" +msgstr "wall འཕྲིན་སྒྲིག" + +#: src/login/org.freedesktop.login1.policy:397 +msgid "Authentication is required to set a wall message." +msgstr "wall འཕྲིན་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/login/org.freedesktop.login1.policy:406 +msgid "Change Session" +msgstr "Session བསྒྱུར" + +#: src/login/org.freedesktop.login1.policy:407 +msgid "Authentication is required to change the virtual terminal." +msgstr "virtual terminal བསྒྱུར་བར་ར་སྤྲོད་དགོས།" + +#: src/machine/org.freedesktop.machine1.policy:22 +msgid "Log into a local container" +msgstr "ས་གནས container དུ་ནང་འཛུལ" + +#: src/machine/org.freedesktop.machine1.policy:23 +msgid "Authentication is required to log into a local container." +msgstr "ས་གནས container དུ་ནང་འཛུལ་བར་ར་སྤྲོད་དགོས།" + +#: src/machine/org.freedesktop.machine1.policy:32 +msgid "Log into the local host" +msgstr "ས་གནས་གཙོ་འཁོར་ལ་ནང་འཛུལ" + +#: src/machine/org.freedesktop.machine1.policy:33 +msgid "Authentication is required to log into the local host." +msgstr "ས་གནས་གཙོ་འཁོར་ལ་ནང་འཛུལ་བར་ར་སྤྲོད་དགོས།" + +#: src/machine/org.freedesktop.machine1.policy:42 +msgid "Acquire a shell in a local container" +msgstr "ས་གནས container ནང shell ཞིག་ཐོབ" + +#: src/machine/org.freedesktop.machine1.policy:43 +msgid "Authentication is required to acquire a shell in a local container." +msgstr "ས་གནས container ནང shell ཐོབ་པར་ར་སྤྲོད་དགོས།" + +#: src/machine/org.freedesktop.machine1.policy:53 +msgid "Acquire a shell on the local host" +msgstr "ས་གནས་གཙོ་འཁོར་སྟེང shell ཞིག་ཐོབ" + +#: src/machine/org.freedesktop.machine1.policy:54 +msgid "Authentication is required to acquire a shell on the local host." +msgstr "ས་གནས་གཙོ་འཁོར་སྟེང shell ཐོབ་པར་ར་སྤྲོད་དགོས།" + +#: src/machine/org.freedesktop.machine1.policy:64 +msgid "Acquire a pseudo TTY in a local container" +msgstr "ས་གནས container ནང pseudo TTY ཞིག་ཐོབ" + +#: src/machine/org.freedesktop.machine1.policy:65 +msgid "" +"Authentication is required to acquire a pseudo TTY in a local container." +msgstr "ས་གནས container ནང pseudo TTY ཐོབ་པར་ར་སྤྲོད་དགོས།" + +#: src/machine/org.freedesktop.machine1.policy:74 +msgid "Acquire a pseudo TTY on the local host" +msgstr "ས་གནས་གཙོ་འཁོར་སྟེང pseudo TTY ཞིག་ཐོབ" + +#: src/machine/org.freedesktop.machine1.policy:75 +msgid "Authentication is required to acquire a pseudo TTY on the local host." +msgstr "ས་གནས་གཙོ་འཁོར་སྟེང pseudo TTY ཐོབ་པར་ར་སྤྲོད་དགོས།" + +#: src/machine/org.freedesktop.machine1.policy:84 +msgid "Manage local virtual machines and containers" +msgstr "ས་གནས virtual machine དང container དོ་དམ" + +#: src/machine/org.freedesktop.machine1.policy:85 +msgid "" +"Authentication is required to manage local virtual machines and containers." +msgstr "ས་གནས virtual machine དང container དོ་དམ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/machine/org.freedesktop.machine1.policy:95 +msgid "Create a local virtual machine or container" +msgstr "ས་གནས virtual machine ཡང་ན container གསར་བཟོ" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to create a local virtual machine or container." +msgstr "ས་གནས virtual machine ཡང་ན container གསར་བཟོ་བར་ར་སྤྲོད་དགོས།" + +#: src/machine/org.freedesktop.machine1.policy:106 +msgid "Register a local virtual machine or container" +msgstr "ས་གནས virtual machine ཡང་ན container ཐོ་འགོད" + +#: src/machine/org.freedesktop.machine1.policy:107 +msgid "" +"Authentication is required to register a local virtual machine or container." +msgstr "ས་གནས virtual machine ཡང་ན container ཐོ་འགོད་པར་ར་སྤྲོད་དགོས།" + +#: src/machine/org.freedesktop.machine1.policy:116 +msgid "Manage local virtual machine and container images" +msgstr "ས་གནས virtual machine དང container མཚོན་རིས་དོ་དམ" + +#: src/machine/org.freedesktop.machine1.policy:117 +msgid "" +"Authentication is required to manage local virtual machine and container " +"images." +msgstr "ས་གནས virtual machine དང container མཚོན་རིས་དོ་དམ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:22 +msgid "Set NTP servers" +msgstr "NTP ཞབས་ཞུ་ཆས་སྒྲིག" + +#: src/network/org.freedesktop.network1.policy:23 +msgid "Authentication is required to set NTP servers." +msgstr "NTP ཞབས་ཞུ་ཆས་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:33 +#: src/resolve/org.freedesktop.resolve1.policy:44 +msgid "Set DNS servers" +msgstr "DNS ཞབས་ཞུ་ཆས་སྒྲིག" + +#: src/network/org.freedesktop.network1.policy:34 +#: src/resolve/org.freedesktop.resolve1.policy:45 +msgid "Authentication is required to set DNS servers." +msgstr "DNS ཞབས་ཞུ་ཆས་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:44 +#: src/resolve/org.freedesktop.resolve1.policy:55 +msgid "Set domains" +msgstr "domain སྒྲིག" + +#: src/network/org.freedesktop.network1.policy:45 +#: src/resolve/org.freedesktop.resolve1.policy:56 +msgid "Authentication is required to set domains." +msgstr "domain སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:55 +#: src/resolve/org.freedesktop.resolve1.policy:66 +msgid "Set default route" +msgstr "སྔོན་སྒྲིག route སྒྲིག" + +#: src/network/org.freedesktop.network1.policy:56 +#: src/resolve/org.freedesktop.resolve1.policy:67 +msgid "Authentication is required to set default route." +msgstr "སྔོན་སྒྲིག route སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:66 +#: src/resolve/org.freedesktop.resolve1.policy:77 +msgid "Enable/disable LLMNR" +msgstr "LLMNR སྒོ་འབྱེད/ཁ་རྒྱག" + +#: src/network/org.freedesktop.network1.policy:67 +#: src/resolve/org.freedesktop.resolve1.policy:78 +msgid "Authentication is required to enable or disable LLMNR." +msgstr "LLMNR སྒོ་འབྱེད་དམ་ཁ་རྒྱག་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:77 +#: src/resolve/org.freedesktop.resolve1.policy:88 +msgid "Enable/disable multicast DNS" +msgstr "multicast DNS སྒོ་འབྱེད/ཁ་རྒྱག" + +#: src/network/org.freedesktop.network1.policy:78 +#: src/resolve/org.freedesktop.resolve1.policy:89 +msgid "Authentication is required to enable or disable multicast DNS." +msgstr "multicast DNS སྒོ་འབྱེད་དམ་ཁ་རྒྱག་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:88 +#: src/resolve/org.freedesktop.resolve1.policy:99 +msgid "Enable/disable DNS over TLS" +msgstr "DNS over TLS སྒོ་འབྱེད/ཁ་རྒྱག" + +#: src/network/org.freedesktop.network1.policy:89 +#: src/resolve/org.freedesktop.resolve1.policy:100 +msgid "Authentication is required to enable or disable DNS over TLS." +msgstr "DNS over TLS སྒོ་འབྱེད་དམ་ཁ་རྒྱག་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:99 +#: src/resolve/org.freedesktop.resolve1.policy:110 +msgid "Enable/disable DNSSEC" +msgstr "DNSSEC སྒོ་འབྱེད/ཁ་རྒྱག" + +#: src/network/org.freedesktop.network1.policy:100 +#: src/resolve/org.freedesktop.resolve1.policy:111 +msgid "Authentication is required to enable or disable DNSSEC." +msgstr "DNSSEC སྒོ་འབྱེད་དམ་ཁ་རྒྱག་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:110 +#: src/resolve/org.freedesktop.resolve1.policy:121 +msgid "Set DNSSEC Negative Trust Anchors" +msgstr "DNSSEC Negative Trust Anchors སྒྲིག" + +#: src/network/org.freedesktop.network1.policy:111 +#: src/resolve/org.freedesktop.resolve1.policy:122 +msgid "Authentication is required to set DNSSEC Negative Trust Anchors." +msgstr "DNSSEC Negative Trust Anchors སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:121 +msgid "Revert NTP settings" +msgstr "NTP སྒྲིག་འགོད་སླར་གསོ" + +#: src/network/org.freedesktop.network1.policy:122 +msgid "Authentication is required to reset NTP settings." +msgstr "NTP སྒྲིག་འགོད་བསྐྱར་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:132 +msgid "Revert DNS settings" +msgstr "DNS སྒྲིག་འགོད་སླར་གསོ" + +#: src/network/org.freedesktop.network1.policy:133 +msgid "Authentication is required to reset DNS settings." +msgstr "DNS སྒྲིག་འགོད་བསྐྱར་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:143 +msgid "DHCP server sends force renew message" +msgstr "DHCP ཞབས་ཞུ་ཆས་ཀྱིས force renew འཕྲིན་གཏོང" + +#: src/network/org.freedesktop.network1.policy:144 +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "DHCP ཞབས་ཞུ་ཆས་ནས force renew འཕྲིན་གཏོང་བར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:154 +msgid "Renew dynamic addresses" +msgstr "dynamic གནས་ཡུལ་སླར་གསོ" + +#: src/network/org.freedesktop.network1.policy:155 +msgid "Authentication is required to renew dynamic addresses." +msgstr "dynamic གནས་ཡུལ་སླར་གསོ་བར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:165 +msgid "Reload network settings" +msgstr "དྲ་རྒྱའི་སྒྲིག་འགོད་བསྐྱར་འཇུག" + +#: src/network/org.freedesktop.network1.policy:166 +msgid "Authentication is required to reload network settings." +msgstr "དྲ་རྒྱའི་སྒྲིག་འགོད་བསྐྱར་འཇུག་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:176 +msgid "Reconfigure network interface" +msgstr "དྲ་རྒྱའི་མཐུད་ཁ་བསྐྱར་སྒྲིག" + +#: src/network/org.freedesktop.network1.policy:177 +msgid "Authentication is required to reconfigure network interface." +msgstr "དྲ་རྒྱའི་མཐུད་ཁ་བསྐྱར་སྒྲིག་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:187 +msgid "Specify whether persistent storage for systemd-networkd is available" +msgstr "systemd-networkd ཡི་བརྟན་པོའི་གསོག་ཉར་སྤྱོད་ཆོག་མིན་སྟོན" + +#: src/network/org.freedesktop.network1.policy:188 +msgid "" +"Authentication is required to specify whether persistent storage for systemd-" +"networkd is available." +msgstr "systemd-networkd ཡི་བརྟན་པོའི་གསོག་ཉར་སྤྱོད་ཆོག་མིན་སྟོན་པར་ར་སྤྲོད་དགོས།" + +#: src/network/org.freedesktop.network1.policy:198 +msgid "Manage network links" +msgstr "དྲ་རྒྱའི་སྦྲེལ་ལམ་དོ་དམ" + +#: src/network/org.freedesktop.network1.policy:199 +msgid "Authentication is required to manage network links." +msgstr "དྲ་རྒྱའི་སྦྲེལ་ལམ་དོ་དམ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/portable/org.freedesktop.portable1.policy:13 +msgid "Inspect a portable service image" +msgstr "portable service image ཞིབ་བཤེར" + +#: src/portable/org.freedesktop.portable1.policy:14 +msgid "Authentication is required to inspect a portable service image." +msgstr "portable service image ཞིབ་བཤེར་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/portable/org.freedesktop.portable1.policy:23 +msgid "Attach or detach a portable service image" +msgstr "portable service image སྦྲེལ་བའམ་བཀོལ་འཐེན" + +#: src/portable/org.freedesktop.portable1.policy:24 +msgid "" +"Authentication is required to attach or detach a portable service image." +msgstr "portable service image སྦྲེལ་བའམ་བཀོལ་འཐེན་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/portable/org.freedesktop.portable1.policy:34 +msgid "Delete or modify portable service image" +msgstr "portable service image བསུབ་པའམ་བཟོ་བཅོས" + +#: src/portable/org.freedesktop.portable1.policy:35 +msgid "" +"Authentication is required to delete or modify a portable service image." +msgstr "portable service image བསུབ་པའམ་བཟོ་བཅོས་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/resolve/org.freedesktop.resolve1.policy:22 +msgid "Register a DNS-SD service" +msgstr "DNS-SD ཞབས་ཞུ་ཐོ་འགོད" + +#: src/resolve/org.freedesktop.resolve1.policy:23 +msgid "Authentication is required to register a DNS-SD service." +msgstr "DNS-SD ཞབས་ཞུ་ཐོ་འགོད་པར་ར་སྤྲོད་དགོས།" + +#: src/resolve/org.freedesktop.resolve1.policy:33 +msgid "Unregister a DNS-SD service" +msgstr "DNS-SD ཞབས་ཞུ་ཐོ་འགོད་མེད་པར་བཟོ" + +#: src/resolve/org.freedesktop.resolve1.policy:34 +msgid "Authentication is required to unregister a DNS-SD service." +msgstr "DNS-SD ཞབས་ཞུ་ཐོ་འགོད་མེད་པར་བཟོ་བར་ར་སྤྲོད་དགོས།" + +#: src/resolve/org.freedesktop.resolve1.policy:132 +msgid "Revert name resolution settings" +msgstr "མིང་འགྲེལ་སྒྲིག་འགོད་སླར་གསོ" + +#: src/resolve/org.freedesktop.resolve1.policy:133 +msgid "Authentication is required to reset name resolution settings." +msgstr "མིང་འགྲེལ་སྒྲིག་འགོད་བསྐྱར་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/resolve/org.freedesktop.resolve1.policy:143 +msgid "Subscribe query results" +msgstr "འཚོལ་ཞིབ་འབྲས་བུ་མངགས་ཉན" + +#: src/resolve/org.freedesktop.resolve1.policy:144 +msgid "Authentication is required to subscribe query results." +msgstr "འཚོལ་ཞིབ་འབྲས་བུ་མངགས་ཉན་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/resolve/org.freedesktop.resolve1.policy:154 +msgid "Subscribe to DNS configuration" +msgstr "DNS སྒྲིག་འགོད་མངགས་ཉན" + +#: src/resolve/org.freedesktop.resolve1.policy:155 +msgid "Authentication is required to subscribe to DNS configuration." +msgstr "DNS སྒྲིག་འགོད་མངགས་ཉན་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/resolve/org.freedesktop.resolve1.policy:165 +msgid "Dump cache" +msgstr "cache ཕྱིར་འདོན" + +#: src/resolve/org.freedesktop.resolve1.policy:166 +msgid "Authentication is required to dump cache." +msgstr "cache ཕྱིར་འདོན་པར་ར་སྤྲོད་དགོས།" + +#: src/resolve/org.freedesktop.resolve1.policy:176 +msgid "Dump server state" +msgstr "server གནས་ཚུལ་ཕྱིར་འདོན" + +#: src/resolve/org.freedesktop.resolve1.policy:177 +msgid "Authentication is required to dump server state." +msgstr "server གནས་ཚུལ་ཕྱིར་འདོན་པར་ར་སྤྲོད་དགོས།" + +#: src/resolve/org.freedesktop.resolve1.policy:187 +msgid "Dump statistics" +msgstr "གྲངས་ཚད་ཕྱིར་འདོན" + +#: src/resolve/org.freedesktop.resolve1.policy:188 +msgid "Authentication is required to dump statistics." +msgstr "གྲངས་ཚད་ཕྱིར་འདོན་པར་ར་སྤྲོད་དགོས།" + +#: src/resolve/org.freedesktop.resolve1.policy:198 +msgid "Reset statistics" +msgstr "གྲངས་ཚད་བསྐྱར་སྒྲིག" + +#: src/resolve/org.freedesktop.resolve1.policy:199 +msgid "Authentication is required to reset statistics." +msgstr "གྲངས་ཚད་བསྐྱར་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +msgid "Check for system updates" +msgstr "རྒྱུད་ལམ་གསར་སྒྱུར་ཞིབ་བཤེར" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +msgid "Authentication is required to check for system updates." +msgstr "རྒྱུད་ལམ་གསར་སྒྱུར་ཞིབ་བཤེར་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +msgid "Install system updates" +msgstr "རྒྱུད་ལམ་གསར་སྒྱུར་སྒྲིག་འཇུག" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +msgid "Authentication is required to install system updates." +msgstr "རྒྱུད་ལམ་གསར་སྒྱུར་སྒྲིག་འཇུག་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +msgid "Install specific system version" +msgstr "དམིགས་བསལ་རྒྱུད་ལམ་པར་གཞི་སྒྲིག་འཇུག" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +msgid "" +"Authentication is required to update the system to a specific (possibly old) " +"version." +msgstr "རྒྱུད་ལམ་དེ་དམིགས་བསལ་(རྙིང་པ་ཡིན་སྲིད་པའི) པར་གཞིར་གསར་སྒྱུར་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 +msgid "Cleanup old system updates" +msgstr "རྒྱུད་ལམ་གསར་སྒྱུར་རྙིང་པ་གཙང་སེལ" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 +msgid "Authentication is required to cleanup old system updates." +msgstr "རྒྱུད་ལམ་གསར་སྒྱུར་རྙིང་པ་གཙང་སེལ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +msgid "Manage optional features" +msgstr "འདེམས་ཆོག་པའི་ལས་འགན་དོ་དམ" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +msgid "Authentication is required to manage optional features." +msgstr "འདེམས་ཆོག་པའི་ལས་འགན་དོ་དམ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/timedate/org.freedesktop.timedate1.policy:22 +msgid "Set system time" +msgstr "རྒྱུད་ལམ་དུས་ཚོད་སྒྲིག" + +#: src/timedate/org.freedesktop.timedate1.policy:23 +msgid "Authentication is required to set the system time." +msgstr "རྒྱུད་ལམ་དུས་ཚོད་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/timedate/org.freedesktop.timedate1.policy:33 +msgid "Set system timezone" +msgstr "རྒྱུད་ལམ་དུས་ཁུལ་སྒྲིག" + +#: src/timedate/org.freedesktop.timedate1.policy:34 +msgid "Authentication is required to set the system timezone." +msgstr "རྒྱུད་ལམ་དུས་ཁུལ་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/timedate/org.freedesktop.timedate1.policy:43 +msgid "Set RTC to local timezone or UTC" +msgstr "RTC ས་གནས་དུས་ཁུལ་ཡང་ན UTC ལ་སྒྲིག" + +#: src/timedate/org.freedesktop.timedate1.policy:44 +msgid "" +"Authentication is required to control whether the RTC stores the local or " +"UTC time." +msgstr "RTC ཡིས་ས་གནས་དུས་ཚོད་དམ UTC ཉར་ཚགས་བྱེད་མིན་ཚོད་འཛིན་པར་ར་སྤྲོད་དགོས།" + +#: src/timedate/org.freedesktop.timedate1.policy:53 +msgid "Turn network time synchronization on or off" +msgstr "དྲ་རྒྱའི་དུས་ཚོད་མཉམ་བགྲོད་སྒོ་འབྱེད་དམ་ཁ་རྒྱག" + +#: src/timedate/org.freedesktop.timedate1.policy:54 +msgid "" +"Authentication is required to control whether network time synchronization " +"shall be enabled." +msgstr "དྲ་རྒྱའི་དུས་ཚོད་མཉམ་བགྲོད་སྒོ་འབྱེད་མིན་ཚོད་འཛིན་པར་ར་སྤྲོད་དགོས།" + +#: src/core/dbus-unit.c:372 +msgid "Authentication is required to start '$(unit)'." +msgstr "'$(unit)' འགོ་སློང་བར་ར་སྤྲོད་དགོས།" + +#: src/core/dbus-unit.c:373 +msgid "Authentication is required to stop '$(unit)'." +msgstr "'$(unit)' མཚམས་འཇོག་པར་ར་སྤྲོད་དགོས།" + +#: src/core/dbus-unit.c:374 +msgid "Authentication is required to reload '$(unit)'." +msgstr "'$(unit)' བསྐྱར་འཇུག་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/core/dbus-unit.c:375 src/core/dbus-unit.c:376 +msgid "Authentication is required to restart '$(unit)'." +msgstr "'$(unit)' བསྐྱར་འགོ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/core/dbus-unit.c:568 +msgid "" +"Authentication is required to send a UNIX signal to the processes of '$" +"(unit)'." +msgstr "UNIX རྟགས་རྒྱ་དེ '$(unit)' གི་བརྒྱུད་རིམ་ལ་གཏོང་བར་ར་སྤྲོད་དགོས།" + +#: src/core/dbus-unit.c:621 +msgid "" +"Authentication is required to send a UNIX signal to the processes of " +"subgroup of '$(unit)'." +msgstr "UNIX རྟགས་རྒྱ་དེ '$(unit)' གི subgroup བརྒྱུད་རིམ་ལ་གཏོང་བར་ར་སྤྲོད་དགོས།" + +#: src/core/dbus-unit.c:649 +msgid "Authentication is required to reset the \"failed\" state of '$(unit)'." +msgstr "'$(unit)' གི \"failed\" གནས་ཚུལ་བསྐྱར་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/core/dbus-unit.c:679 +msgid "Authentication is required to set properties on '$(unit)'." +msgstr "'$(unit)' སྟེང་གི་གཏོགས་གཤིས་སྒྲིག་པར་ར་སྤྲོད་དགོས།" + +#: src/core/dbus-unit.c:776 +msgid "" +"Authentication is required to delete files and directories associated with '$" +"(unit)'." +msgstr "'$(unit)' དང་འབྲེལ་བའི་ཡིག་ཆ་དང་དཀར་ཆག་བསུབ་པར་ར་སྤྲོད་དགོས།" + +#: src/core/dbus-unit.c:813 +msgid "" +"Authentication is required to freeze or thaw the processes of '$(unit)' unit." +msgstr "'$(unit)' unit གི་བརྒྱུད་རིམ་འཁྱགས་བཅུག་གམ་གྲོལ་བར་ར་སྤྲོད་དགོས།" diff --git a/po/ug.po b/po/ug.po new file mode 100644 index 0000000000000..5d96d103000db --- /dev/null +++ b/po/ug.po @@ -0,0 +1,1195 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Uyghur translation for systemd. +# Dongshengyuan , 2026. +msgid "" +msgstr "" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-03-06 03:46+0900\n" +"PO-Revision-Date: 2026-03-16 14:21+0000\n" +"Last-Translator: Dongshengyuan \n" +"Language-Team: Uyghur\n" +"Language: ug\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: manual\n" + +#: src/core/org.freedesktop.systemd1.policy.in:22 +msgid "Send passphrase back to system" +msgstr "مەخپىي ئىبارىنى سىستېمىغا قايتا يوللا" + +#: src/core/org.freedesktop.systemd1.policy.in:23 +msgid "" +"Authentication is required to send the entered passphrase back to the system." +msgstr "كىرگۈزۈلگەن مەخپىي ئىبارىنى سىستېمىغا قايتا يوللاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/org.freedesktop.systemd1.policy.in:33 +msgid "Manage system services or other units" +msgstr "سىستېما مۇلازىمەتلىرىنى ياكى باشقا بىرىكلەرنى باشقۇر" + +#: src/core/org.freedesktop.systemd1.policy.in:34 +msgid "Authentication is required to manage system services or other units." +msgstr "سىستېما مۇلازىمەتلىرىنى ياكى باشقا بىرىكلەرنى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/org.freedesktop.systemd1.policy.in:43 +msgid "Manage system service or unit files" +msgstr "سىستېما مۇلازىمەت ياكى بىرىك ھۆججەتلىرىنى باشقۇر" + +#: src/core/org.freedesktop.systemd1.policy.in:44 +msgid "Authentication is required to manage system service or unit files." +msgstr "سىستېما مۇلازىمەت ياكى بىرىك ھۆججەتلىرىنى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/org.freedesktop.systemd1.policy.in:54 +msgid "Set or unset system and service manager environment variables" +msgstr "سىستېما ۋە مۇلازىمەت باشقۇرغۇچنىڭ مۇھىت ئۆزگەرگۈچىلىرىنى بەلگىلە ياكى بىكار قىل" + +#: src/core/org.freedesktop.systemd1.policy.in:55 +msgid "" +"Authentication is required to set or unset system and service manager " +"environment variables." +msgstr "سىستېما ۋە مۇلازىمەت باشقۇرغۇچنىڭ مۇھىت ئۆزگەرگۈچىلىرىنى بەلگىلەش ياكى بىكار قىلىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/org.freedesktop.systemd1.policy.in:64 +msgid "Reload the systemd state" +msgstr "systemd ھالىتىنى قايتا يۈكلە" + +#: src/core/org.freedesktop.systemd1.policy.in:65 +msgid "Authentication is required to reload the systemd state." +msgstr "systemd ھالىتىنى قايتا يۈكلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/org.freedesktop.systemd1.policy.in:74 +msgid "Dump the systemd state without rate limits" +msgstr "سۈرئەت چەكلىمىسىز ھالدا systemd ھالىتىنى چىقار" + +#: src/core/org.freedesktop.systemd1.policy.in:75 +msgid "" +"Authentication is required to dump the systemd state without rate limits." +msgstr "سۈرئەت چەكلىمىسىز ھالدا systemd ھالىتىنى چىقىرىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/home/org.freedesktop.home1.policy:13 +msgid "Create a home area" +msgstr "باش مۇندەرىجە رايونىنى قۇر" + +#: src/home/org.freedesktop.home1.policy:14 +msgid "Authentication is required to create a user's home area." +msgstr "ئىشلەتكۈچىنىڭ باش مۇندەرىجە رايونىنى قۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/home/org.freedesktop.home1.policy:23 +msgid "Remove a home area" +msgstr "باش مۇندەرىجە رايونىنى ئۆچۈر" + +#: src/home/org.freedesktop.home1.policy:24 +msgid "Authentication is required to remove a user's home area." +msgstr "ئىشلەتكۈچىنىڭ باش مۇندەرىجە رايونىنى ئۆچۈرۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/home/org.freedesktop.home1.policy:33 +msgid "Check credentials of a home area" +msgstr "باش مۇندەرىجە رايونىنىڭ ئىسپات ئۇچۇرىنى تەكشۈر" + +#: src/home/org.freedesktop.home1.policy:34 +msgid "" +"Authentication is required to check credentials against a user's home area." +msgstr "ئىسپات ئۇچۇرىنى ئىشلەتكۈچىنىڭ باش مۇندەرىجە رايونى بىلەن سېلىشتۇرۇپ تەكشۈرۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/home/org.freedesktop.home1.policy:43 +msgid "Update a home area" +msgstr "باش مۇندەرىجە رايونىنى يېڭىلا" + +#: src/home/org.freedesktop.home1.policy:44 +msgid "Authentication is required to update a user's home area." +msgstr "ئىشلەتكۈچىنىڭ باش مۇندەرىجە رايونىنى يېڭىلاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/home/org.freedesktop.home1.policy:53 +msgid "Update your home area" +msgstr "باش مۇندەرىجە رايونىڭىزنى يېڭىلاڭ" + +#: src/home/org.freedesktop.home1.policy:54 +msgid "Authentication is required to update your home area." +msgstr "باش مۇندەرىجە رايونىڭىزنى يېڭىلاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/home/org.freedesktop.home1.policy:63 +msgid "Resize a home area" +msgstr "باش مۇندەرىجە رايونىنىڭ چوڭ-كىچىكلىكىنى ئۆزگەرت" + +#: src/home/org.freedesktop.home1.policy:64 +msgid "Authentication is required to resize a user's home area." +msgstr "ئىشلەتكۈچىنىڭ باش مۇندەرىجە رايونىنىڭ چوڭ-كىچىكلىكىنى ئۆزگەرتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/home/org.freedesktop.home1.policy:73 +msgid "Change password of a home area" +msgstr "باش مۇندەرىجە رايونىنىڭ پارولىنى ئۆزگەرت" + +#: src/home/org.freedesktop.home1.policy:74 +msgid "" +"Authentication is required to change the password of a user's home area." +msgstr "ئىشلەتكۈچىنىڭ باش مۇندەرىجە رايونى پارولىنى ئۆزگەرتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/home/org.freedesktop.home1.policy:83 +msgid "Activate a home area" +msgstr "باش مۇندەرىجە رايونىنى ئاكتىپلا" + +#: src/home/org.freedesktop.home1.policy:84 +msgid "Authentication is required to activate a user's home area." +msgstr "ئىشلەتكۈچىنىڭ باش مۇندەرىجە رايونىنى ئاكتىپلاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/home/org.freedesktop.home1.policy:93 +msgid "Manage Home Directory Signing Keys" +msgstr "باش مۇندەرىجە ئىمزا ئاچقۇچلىرىنى باشقۇر" + +#: src/home/org.freedesktop.home1.policy:94 +msgid "Authentication is required to manage signing keys for home directories." +msgstr "باش مۇندەرىجىلەرنىڭ ئىمزا ئاچقۇچلىرىنى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/home/pam_systemd_home.c:330 +#, c-format +msgid "" +"Home of user %s is currently absent, please plug in the necessary storage " +"device or backing file system." +msgstr "%s ئىشلەتكۈچىنىڭ باش مۇندەرىجىسى ھازىر مەۋجۇت ئەمەس، زۆرۈر ساقلاش ئۈسكۈنىسى ياكى تۆۋەن قەۋەت ھۆججەت سىستېمىسىنى ئۇلاڭ." + +#: src/home/pam_systemd_home.c:335 +#, c-format +msgid "Too frequent login attempts for user %s, try again later." +msgstr "%s ئىشلەتكۈچىنىڭ كىرىش سىنىقى بەك كۆپ قېتىم بولدى، كېيىن قايتا سىناپ بېقىڭ." + +#: src/home/pam_systemd_home.c:347 +msgid "Password: " +msgstr "پارول: " + +#: src/home/pam_systemd_home.c:349 +#, c-format +msgid "Password incorrect or not sufficient for authentication of user %s." +msgstr "%s ئىشلەتكۈچىنىڭ پارولى خاتا ياكى دەلىللەش ئۈچۈن يېتەرلىك ئەمەس." + +#: src/home/pam_systemd_home.c:350 +msgid "Sorry, try again: " +msgstr "كەچۈرۈڭ، قايتا سىناڭ: " + +#: src/home/pam_systemd_home.c:372 +msgid "Recovery key: " +msgstr "ئەسلىگە كەلتۈرۈش ئاچقۇچى: " + +#: src/home/pam_systemd_home.c:374 +#, c-format +msgid "" +"Password/recovery key incorrect or not sufficient for authentication of user " +"%s." +msgstr "%s ئىشلەتكۈچىنىڭ پارول/ئەسلىگە كەلتۈرۈش ئاچقۇچى خاتا ياكى دەلىللەش ئۈچۈن يېتەرلىك ئەمەس." + +#: src/home/pam_systemd_home.c:375 +msgid "Sorry, reenter recovery key: " +msgstr "كەچۈرۈڭ، ئەسلىگە كەلتۈرۈش ئاچقۇچىنى قايتا كىرگۈزۈڭ: " + +#: src/home/pam_systemd_home.c:395 +#, c-format +msgid "Security token of user %s not inserted." +msgstr "%s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنى چېتىلمىغان." + +#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 +msgid "Try again with password: " +msgstr "پارول بىلەن قايتا سىناڭ: " + +#: src/home/pam_systemd_home.c:398 +#, c-format +msgid "" +"Password incorrect or not sufficient, and configured security token of user " +"%s not inserted." +msgstr "پارول خاتا ياكى يېتەرلىك ئەمەس، شۇنداقلا %s ئىشلەتكۈچىگە سەپلىگەن بىخەتەرلىك توكىنى چېتىلمىغان." + +#: src/home/pam_systemd_home.c:418 +msgid "Security token PIN: " +msgstr "بىخەتەرلىك توكىنى PIN: " + +#: src/home/pam_systemd_home.c:435 +#, c-format +msgid "Please authenticate physically on security token of user %s." +msgstr "ئىلتىماس، %s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنىدا فىزىكىلىق دەلىللەشنى تاماملاڭ." + +#: src/home/pam_systemd_home.c:446 +#, c-format +msgid "Please confirm presence on security token of user %s." +msgstr "ئىلتىماس، %s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنىدا ھازىر ئىكەنلىكىڭىزنى جەزملەڭ." + +#: src/home/pam_systemd_home.c:457 +#, c-format +msgid "Please verify user on security token of user %s." +msgstr "ئىلتىماس، %s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنىدىكى ئىشلەتكۈچىنى تەكشۈرۈپ دەلىللەڭ." + +#: src/home/pam_systemd_home.c:466 +msgid "" +"Security token PIN is locked, please unlock it first. (Hint: Removal and re-" +"insertion might suffice.)" +msgstr "بىخەتەرلىك توكىنى PIN قۇلۇپلانغان، ئالدى بىلەن قۇلۇپنى ئېچىڭ. (ئەسكەرتىش: چېتىپ تۇرغاننى چىقىرىپ قايتا چېتىش كۇپايە بولۇشى مۇمكىن.)" + +#: src/home/pam_systemd_home.c:474 +#, c-format +msgid "Security token PIN incorrect for user %s." +msgstr "%s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنى PIN خاتا." + +#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 +#: src/home/pam_systemd_home.c:513 +msgid "Sorry, retry security token PIN: " +msgstr "كەچۈرۈڭ، بىخەتەرلىك توكىنى PIN نى قايتا سىناڭ: " + +#: src/home/pam_systemd_home.c:493 +#, c-format +msgid "Security token PIN of user %s incorrect (only a few tries left!)" +msgstr "%s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنى PIN خاتا (پەقەت بىر قانچەلا سىناق پۇرسىتى قالدى!)" + +#: src/home/pam_systemd_home.c:512 +#, c-format +msgid "Security token PIN of user %s incorrect (only one try left!)" +msgstr "%s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنى PIN خاتا (پەقەت بىرلا قېتىملىق سىناق پۇرسىتى قالدى!)" + +#: src/home/pam_systemd_home.c:679 +#, c-format +msgid "Home of user %s is currently not active, please log in locally first." +msgstr "%s ئىشلەتكۈچىنىڭ باش مۇندەرىجىسى ھازىر ئاكتىپ ئەمەس، ئالدى بىلەن يەرلىك كىرىڭ." + +#: src/home/pam_systemd_home.c:681 +#, c-format +msgid "Home of user %s is currently locked, please unlock locally first." +msgstr "%s ئىشلەتكۈچىنىڭ باش مۇندەرىجىسى ھازىر قۇلۇپلانغان، ئالدى بىلەن يەرلىكتە قۇلۇپنى ئېچىڭ." + +#: src/home/pam_systemd_home.c:715 +#, c-format +msgid "Too many unsuccessful login attempts for user %s, refusing." +msgstr "%s ئىشلەتكۈچىنىڭ مۇۋەپپەقىيەتسىز كىرىش سىنىقى بەك كۆپ بولدى، رەت قىلىندى." + +#: src/home/pam_systemd_home.c:1012 +msgid "User record is blocked, prohibiting access." +msgstr "ئىشلەتكۈچى خاتىرىسى توسۇلغان، زىيارەت چەكلەندى." + +#: src/home/pam_systemd_home.c:1016 +msgid "User record is not valid yet, prohibiting access." +msgstr "ئىشلەتكۈچى خاتىرىسى تېخى كۈچكە ئىگە ئەمەس، زىيارەت چەكلەندى." + +#: src/home/pam_systemd_home.c:1020 +msgid "User record is not valid anymore, prohibiting access." +msgstr "ئىشلەتكۈچى خاتىرىسى ئەمدى كۈچكە ئىگە ئەمەس، زىيارەت چەكلەندى." + +#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +msgid "User record not valid, prohibiting access." +msgstr "ئىشلەتكۈچى خاتىرىسى كۈچكە ئىگە ئەمەس، زىيارەت چەكلەندى." + +#: src/home/pam_systemd_home.c:1035 +#, c-format +msgid "Too many logins, try again in %s." +msgstr "كىرىش قېتىملىرى بەك كۆپ، %s دىن كېيىن قايتا سىناڭ." + +#: src/home/pam_systemd_home.c:1046 +msgid "Password change required." +msgstr "پارولنى ئۆزگەرتىش زۆرۈر." + +#: src/home/pam_systemd_home.c:1050 +msgid "Password expired, change required." +msgstr "پارولنىڭ مۇددىتى توشتى، ئۆزگەرتىش زۆرۈر." + +#: src/home/pam_systemd_home.c:1056 +msgid "Password is expired, but can't change, refusing login." +msgstr "پارولنىڭ مۇددىتى توشقان، ئەمما ئۆزگەرتكىلى بولمايدۇ، كىرىش رەت قىلىندى." + +#: src/home/pam_systemd_home.c:1060 +msgid "Password will expire soon, please change." +msgstr "پارولنىڭ مۇددىتى تېزلا توشىدۇ، ۋاقتىدا ئۆزگەرتىڭ." + +#: src/hostname/org.freedesktop.hostname1.policy:20 +msgid "Set hostname" +msgstr "ساھىبجامال نامىنى بەلگىلە" + +#: src/hostname/org.freedesktop.hostname1.policy:21 +msgid "Authentication is required to set the local hostname." +msgstr "يەرلىك ساھىبجامال نامىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/hostname/org.freedesktop.hostname1.policy:30 +msgid "Set static hostname" +msgstr "مۇقىم ساھىبجامال نامىنى بەلگىلە" + +#: src/hostname/org.freedesktop.hostname1.policy:31 +msgid "" +"Authentication is required to set the statically configured local hostname, " +"as well as the pretty hostname." +msgstr "مۇقىم تەڭشەلگەن يەرلىك ساھىبجامال نامى ۋە چىرايلىق ساھىبجامال نامىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/hostname/org.freedesktop.hostname1.policy:41 +msgid "Set machine information" +msgstr "ماشىنا ئۇچۇرىنى بەلگىلە" + +#: src/hostname/org.freedesktop.hostname1.policy:42 +msgid "Authentication is required to set local machine information." +msgstr "يەرلىك ماشىنا ئۇچۇرىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/hostname/org.freedesktop.hostname1.policy:51 +msgid "Get product UUID" +msgstr "مەھسۇلات UUID نى ئال" + +#: src/hostname/org.freedesktop.hostname1.policy:52 +msgid "Authentication is required to get product UUID." +msgstr "مەھسۇلات UUID نى ئېلىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/hostname/org.freedesktop.hostname1.policy:61 +msgid "Get hardware serial number" +msgstr "قاتتىق دېتال تەرتىپ نومۇرىنى ئال" + +#: src/hostname/org.freedesktop.hostname1.policy:62 +msgid "Authentication is required to get hardware serial number." +msgstr "قاتتىق دېتال تەرتىپ نومۇرىنى ئېلىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/hostname/org.freedesktop.hostname1.policy:71 +msgid "Get system description" +msgstr "سىستېما چۈشەندۈرۈشىنى ئال" + +#: src/hostname/org.freedesktop.hostname1.policy:72 +msgid "Authentication is required to get system description." +msgstr "سىستېما چۈشەندۈرۈشىنى ئېلىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/import/org.freedesktop.import1.policy:22 +msgid "Import a disk image" +msgstr "دىسكا ئەكسىنى ئەكىر" + +#: src/import/org.freedesktop.import1.policy:23 +msgid "Authentication is required to import an image." +msgstr "ئەكسنى ئەكىرىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/import/org.freedesktop.import1.policy:32 +msgid "Export a disk image" +msgstr "دىسكا ئەكسىنى چىقار" + +#: src/import/org.freedesktop.import1.policy:33 +msgid "Authentication is required to export disk image." +msgstr "دىسكا ئەكسىنى چىقىرىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/import/org.freedesktop.import1.policy:42 +msgid "Download a disk image" +msgstr "دىسكا ئەكسىنى چۈشۈر" + +#: src/import/org.freedesktop.import1.policy:43 +msgid "Authentication is required to download a disk image." +msgstr "دىسكا ئەكسىنى چۈشۈرۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/import/org.freedesktop.import1.policy:52 +msgid "Cancel transfer of a disk image" +msgstr "دىسكا ئەكسىنى يوللاشنى بىكار قىل" + +#: src/import/org.freedesktop.import1.policy:53 +msgid "" +"Authentication is required to cancel the ongoing transfer of a disk image." +msgstr "داۋاملىشىۋاتقان دىسكا ئەكسى يوللاشنى بىكار قىلىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/locale/org.freedesktop.locale1.policy:22 +msgid "Set system locale" +msgstr "سىستېما يەرلىك تەڭشىكىنى بەلگىلە" + +#: src/locale/org.freedesktop.locale1.policy:23 +msgid "Authentication is required to set the system locale." +msgstr "سىستېما يەرلىك تەڭشىكىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/locale/org.freedesktop.locale1.policy:33 +msgid "Set system keyboard settings" +msgstr "سىستېما كۇنۇپكا تاختىسى تەڭشىكىنى بەلگىلە" + +#: src/locale/org.freedesktop.locale1.policy:34 +msgid "Authentication is required to set the system keyboard settings." +msgstr "سىستېما كۇنۇپكا تاختىسى تەڭشىكىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:22 +msgid "Allow applications to inhibit system shutdown" +msgstr "ئەپنىڭ سىستېما تاقاشنى توسۇشىغا يول قوي" + +#: src/login/org.freedesktop.login1.policy:23 +msgid "" +"Authentication is required for an application to inhibit system shutdown." +msgstr "ئەپنىڭ سىستېما تاقاشنى توسۇشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:33 +msgid "Allow applications to delay system shutdown" +msgstr "ئەپنىڭ سىستېما تاقاشنى كېچىكتۈرۈشىگە يول قوي" + +#: src/login/org.freedesktop.login1.policy:34 +msgid "Authentication is required for an application to delay system shutdown." +msgstr "ئەپنىڭ سىستېما تاقاشنى كېچىكتۈرۈشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:44 +msgid "Allow applications to inhibit system sleep" +msgstr "ئەپنىڭ سىستېما ئۇخلاشنى توسۇشىغا يول قوي" + +#: src/login/org.freedesktop.login1.policy:45 +msgid "Authentication is required for an application to inhibit system sleep." +msgstr "ئەپنىڭ سىستېما ئۇخلاشنى توسۇشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:55 +msgid "Allow applications to delay system sleep" +msgstr "ئەپنىڭ سىستېما ئۇخلاشنى كېچىكتۈرۈشىگە يول قوي" + +#: src/login/org.freedesktop.login1.policy:56 +msgid "Authentication is required for an application to delay system sleep." +msgstr "ئەپنىڭ سىستېما ئۇخلاشنى كېچىكتۈرۈشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:65 +msgid "Allow applications to inhibit automatic system suspend" +msgstr "ئەپنىڭ سىستېمىنىڭ ئاپتوماتىك توختىتىلىشىنى توسۇشىغا يول قوي" + +#: src/login/org.freedesktop.login1.policy:66 +msgid "" +"Authentication is required for an application to inhibit automatic system " +"suspend." +msgstr "ئەپنىڭ سىستېمىنىڭ ئاپتوماتىك توختىتىلىشىنى توسۇشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:75 +msgid "Allow applications to inhibit system handling of the power key" +msgstr "ئەپنىڭ سىستېمىنىڭ توك كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشىغا يول قوي" + +#: src/login/org.freedesktop.login1.policy:76 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the power key." +msgstr "ئەپنىڭ سىستېمىنىڭ توك كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:86 +msgid "Allow applications to inhibit system handling of the suspend key" +msgstr "ئەپنىڭ سىستېمىنىڭ توختىتىش كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشىغا يول قوي" + +#: src/login/org.freedesktop.login1.policy:87 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the suspend key." +msgstr "ئەپنىڭ سىستېمىنىڭ توختىتىش كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:97 +msgid "Allow applications to inhibit system handling of the hibernate key" +msgstr "ئەپنىڭ سىستېمىنىڭ ئۇزۇن ئۇخلاش كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشىغا يول قوي" + +#: src/login/org.freedesktop.login1.policy:98 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the hibernate key." +msgstr "ئەپنىڭ سىستېمىنىڭ ئۇزۇن ئۇخلاش كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:107 +msgid "Allow applications to inhibit system handling of the lid switch" +msgstr "ئەپنىڭ سىستېمىنىڭ خاتىرە كومپيۇتېر قاپاق ئالماشتۇرغۇچىنى بىر تەرەپ قىلىشىنى توسۇشىغا يول قوي" + +#: src/login/org.freedesktop.login1.policy:108 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the lid switch." +msgstr "ئەپنىڭ سىستېمىنىڭ خاتىرە كومپيۇتېر قاپاق ئالماشتۇرغۇچىنى بىر تەرەپ قىلىشىنى توسۇشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:117 +msgid "Allow applications to inhibit system handling of the reboot key" +msgstr "ئەپنىڭ سىستېمىنىڭ قايتا قوزغىتىش كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشىغا يول قوي" + +#: src/login/org.freedesktop.login1.policy:118 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the reboot key." +msgstr "ئەپنىڭ سىستېمىنىڭ قايتا قوزغىتىش كۇنۇپكىسىنى بىر تەرەپ قىلىشىنى توسۇشى ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:128 +msgid "Allow non-logged-in user to run programs" +msgstr "تىزىملاتمىغان ئىشلەتكۈچىنىڭ پروگرامما ئىجرا قىلىشىغا يول قوي" + +#: src/login/org.freedesktop.login1.policy:129 +msgid "Explicit request is required to run programs as a non-logged-in user." +msgstr "تىزىملاتمىغان ئىشلەتكۈچى سۈپىتىدە پروگرامما ئىجرا قىلىش ئۈچۈن ئېنىق ئىلتىماس تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:138 +msgid "Allow non-logged-in users to run programs" +msgstr "تىزىملاتمىغان ئىشلەتكۈچىلەرنىڭ پروگرامما ئىجرا قىلىشىغا يول قوي" + +#: src/login/org.freedesktop.login1.policy:139 +msgid "Authentication is required to run programs as a non-logged-in user." +msgstr "تىزىملاتمىغان ئىشلەتكۈچى سۈپىتىدە پروگرامما ئىجرا قىلىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:148 +msgid "Allow attaching devices to seats" +msgstr "ئۈسكۈنىنى seat قا ئۇلاشقا يول قوي" + +#: src/login/org.freedesktop.login1.policy:149 +msgid "Authentication is required to attach a device to a seat." +msgstr "ئۈسكۈنىنى seat قا ئۇلاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:159 +msgid "Flush device to seat attachments" +msgstr "ئۈسكۈنە بىلەن seat ئۇلىنىشىنى يېڭىلا" + +#: src/login/org.freedesktop.login1.policy:160 +msgid "Authentication is required to reset how devices are attached to seats." +msgstr "ئۈسكۈنىلەرنىڭ seat قا قانداق ئۇلىنىدىغانلىقىنى قايتا بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:169 +msgid "Power off the system" +msgstr "سىستېمىنى تاقا" + +#: src/login/org.freedesktop.login1.policy:170 +msgid "Authentication is required to power off the system." +msgstr "سىستېمىنى تاقاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:180 +msgid "Power off the system while other users are logged in" +msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى تاقا" + +#: src/login/org.freedesktop.login1.policy:181 +msgid "" +"Authentication is required to power off the system while other users are " +"logged in." +msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى تاقاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:191 +msgid "Power off the system while an application is inhibiting this" +msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى تاقا" + +#: src/login/org.freedesktop.login1.policy:192 +msgid "" +"Authentication is required to power off the system while an application is " +"inhibiting this." +msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى تاقاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:202 +msgid "Reboot the system" +msgstr "سىستېمىنى قايتا قوزغات" + +#: src/login/org.freedesktop.login1.policy:203 +msgid "Authentication is required to reboot the system." +msgstr "سىستېمىنى قايتا قوزغىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:213 +msgid "Reboot the system while other users are logged in" +msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى قايتا قوزغات" + +#: src/login/org.freedesktop.login1.policy:214 +msgid "" +"Authentication is required to reboot the system while other users are logged " +"in." +msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى قايتا قوزغىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:224 +msgid "Reboot the system while an application is inhibiting this" +msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى قايتا قوزغات" + +#: src/login/org.freedesktop.login1.policy:225 +msgid "" +"Authentication is required to reboot the system while an application is " +"inhibiting this." +msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى قايتا قوزغىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:235 +msgid "Halt the system" +msgstr "سىستېمىنى توختات" + +#: src/login/org.freedesktop.login1.policy:236 +msgid "Authentication is required to halt the system." +msgstr "سىستېمىنى توختىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:246 +msgid "Halt the system while other users are logged in" +msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى توختات" + +#: src/login/org.freedesktop.login1.policy:247 +msgid "" +"Authentication is required to halt the system while other users are logged " +"in." +msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى توختىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:257 +msgid "Halt the system while an application is inhibiting this" +msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى توختات" + +#: src/login/org.freedesktop.login1.policy:258 +msgid "" +"Authentication is required to halt the system while an application is " +"inhibiting this." +msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى توختىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:268 +msgid "Suspend the system" +msgstr "سىستېمىنى ۋاقىتلىق توختات" + +#: src/login/org.freedesktop.login1.policy:269 +msgid "Authentication is required to suspend the system." +msgstr "سىستېمىنى ۋاقىتلىق توختىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:278 +msgid "Suspend the system while other users are logged in" +msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى ۋاقىتلىق توختات" + +#: src/login/org.freedesktop.login1.policy:279 +msgid "" +"Authentication is required to suspend the system while other users are " +"logged in." +msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى ۋاقىتلىق توختىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:289 +msgid "Suspend the system while an application is inhibiting this" +msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى ۋاقىتلىق توختات" + +#: src/login/org.freedesktop.login1.policy:290 +msgid "" +"Authentication is required to suspend the system while an application is " +"inhibiting this." +msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى ۋاقىتلىق توختىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:300 +msgid "Hibernate the system" +msgstr "سىستېمىنى ئۇزۇن ئۇخلاشقا كىرگۈز" + +#: src/login/org.freedesktop.login1.policy:301 +msgid "Authentication is required to hibernate the system." +msgstr "سىستېمىنى ئۇزۇن ئۇخلاشقا كىرگۈزۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:310 +msgid "Hibernate the system while other users are logged in" +msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى ئۇزۇن ئۇخلاشقا كىرگۈز" + +#: src/login/org.freedesktop.login1.policy:311 +msgid "" +"Authentication is required to hibernate the system while other users are " +"logged in." +msgstr "باشقا ئىشلەتكۈچىلەر تىزىملاتقان ھالەتتە سىستېمىنى ئۇزۇن ئۇخلاشقا كىرگۈزۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:321 +msgid "Hibernate the system while an application is inhibiting this" +msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى ئۇزۇن ئۇخلاشقا كىرگۈز" + +#: src/login/org.freedesktop.login1.policy:322 +msgid "" +"Authentication is required to hibernate the system while an application is " +"inhibiting this." +msgstr "ئەپ توسۇۋاتقاندا سىستېمىنى ئۇزۇن ئۇخلاشقا كىرگۈزۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:332 +msgid "Manage active sessions, users and seats" +msgstr "ئاكتىپ سۆھبەتلەر، ئىشلەتكۈچىلەر ۋە seats نى باشقۇر" + +#: src/login/org.freedesktop.login1.policy:333 +msgid "Authentication is required to manage active sessions, users and seats." +msgstr "ئاكتىپ سۆھبەتلەر، ئىشلەتكۈچىلەر ۋە seats نى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:342 +msgid "Lock or unlock active sessions" +msgstr "ئاكتىپ سۆھبەتلەرنى قۇلۇپلا ياكى قۇلۇپنى ئاچ" + +#: src/login/org.freedesktop.login1.policy:343 +msgid "Authentication is required to lock or unlock active sessions." +msgstr "ئاكتىپ سۆھبەتلەرنى قۇلۇپلاش ياكى قۇلۇپنى ئېچىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:352 +msgid "Set the reboot \"reason\" in the kernel" +msgstr "يادرودا قايتا قوزغىتىش «سەۋەبى» نى بەلگىلە" + +#: src/login/org.freedesktop.login1.policy:353 +msgid "Authentication is required to set the reboot \"reason\" in the kernel." +msgstr "يادرودا قايتا قوزغىتىش «سەۋەبى» نى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:363 +msgid "Indicate to the firmware to boot to setup interface" +msgstr "مىكروپروگراممىغا تەڭشەك كۆرۈنمە يۈزىگە قوزغىتىشنى كۆرسەت" + +#: src/login/org.freedesktop.login1.policy:364 +msgid "" +"Authentication is required to indicate to the firmware to boot to setup " +"interface." +msgstr "مىكروپروگراممىغا تەڭشەك كۆرۈنمە يۈزىگە قوزغىتىشنى كۆرسىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:374 +msgid "Indicate to the boot loader to boot to the boot loader menu" +msgstr "قوزغىتىش يۈكلەش پروگراممىسىغا ئۆزىنىڭ تىزىملىكىگە كىرىشنى كۆرسەت" + +#: src/login/org.freedesktop.login1.policy:375 +msgid "" +"Authentication is required to indicate to the boot loader to boot to the " +"boot loader menu." +msgstr "قوزغىتىش يۈكلەش پروگراممىسىغا ئۆزىنىڭ تىزىملىكىگە كىرىشنى كۆرسىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:385 +msgid "Indicate to the boot loader to boot a specific entry" +msgstr "قوزغىتىش يۈكلەش پروگراممىسىغا بەلگىلەنگەن بىر تۈرگە كىرىشنى كۆرسەت" + +#: src/login/org.freedesktop.login1.policy:386 +msgid "" +"Authentication is required to indicate to the boot loader to boot into a " +"specific boot loader entry." +msgstr "قوزغىتىش يۈكلەش پروگراممىسىغا بەلگىلەنگەن بىر تۈرگە كىرىشنى كۆرسىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:396 +msgid "Set a wall message" +msgstr "wall ئۇچۇرىنى بەلگىلە" + +#: src/login/org.freedesktop.login1.policy:397 +msgid "Authentication is required to set a wall message." +msgstr "wall ئۇچۇرىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/login/org.freedesktop.login1.policy:406 +msgid "Change Session" +msgstr "سۆھبەتنى ئۆزگەرت" + +#: src/login/org.freedesktop.login1.policy:407 +msgid "Authentication is required to change the virtual terminal." +msgstr "مەۋھۇم تېرمىنالنى ئۆزگەرتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/machine/org.freedesktop.machine1.policy:22 +msgid "Log into a local container" +msgstr "يەرلىك كونتېينېرغا كىر" + +#: src/machine/org.freedesktop.machine1.policy:23 +msgid "Authentication is required to log into a local container." +msgstr "يەرلىك كونتېينېرغا كىرىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/machine/org.freedesktop.machine1.policy:32 +msgid "Log into the local host" +msgstr "يەرلىك ئاساسىي كومپيۇتېرغا كىر" + +#: src/machine/org.freedesktop.machine1.policy:33 +msgid "Authentication is required to log into the local host." +msgstr "يەرلىك ئاساسىي كومپيۇتېرغا كىرىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/machine/org.freedesktop.machine1.policy:42 +msgid "Acquire a shell in a local container" +msgstr "يەرلىك كونتېينېردا shell غا ئېرىش" + +#: src/machine/org.freedesktop.machine1.policy:43 +msgid "Authentication is required to acquire a shell in a local container." +msgstr "يەرلىك كونتېينېردا shell غا ئېرىشىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/machine/org.freedesktop.machine1.policy:53 +msgid "Acquire a shell on the local host" +msgstr "يەرلىك ئاساسىي كومپيۇتېردا shell غا ئېرىش" + +#: src/machine/org.freedesktop.machine1.policy:54 +msgid "Authentication is required to acquire a shell on the local host." +msgstr "يەرلىك ئاساسىي كومپيۇتېردا shell غا ئېرىشىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/machine/org.freedesktop.machine1.policy:64 +msgid "Acquire a pseudo TTY in a local container" +msgstr "يەرلىك كونتېينېردا pseudo TTY غا ئېرىش" + +#: src/machine/org.freedesktop.machine1.policy:65 +msgid "" +"Authentication is required to acquire a pseudo TTY in a local container." +msgstr "يەرلىك كونتېينېردا pseudo TTY غا ئېرىشىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/machine/org.freedesktop.machine1.policy:74 +msgid "Acquire a pseudo TTY on the local host" +msgstr "يەرلىك ئاساسىي كومپيۇتېردا pseudo TTY غا ئېرىش" + +#: src/machine/org.freedesktop.machine1.policy:75 +msgid "Authentication is required to acquire a pseudo TTY on the local host." +msgstr "يەرلىك ئاساسىي كومپيۇتېردا pseudo TTY غا ئېرىشىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/machine/org.freedesktop.machine1.policy:84 +msgid "Manage local virtual machines and containers" +msgstr "يەرلىك مەۋھۇم ماشىنا ۋە كونتېينېرلارنى باشقۇر" + +#: src/machine/org.freedesktop.machine1.policy:85 +msgid "" +"Authentication is required to manage local virtual machines and containers." +msgstr "يەرلىك مەۋھۇم ماشىنا ۋە كونتېينېرلارنى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/machine/org.freedesktop.machine1.policy:95 +msgid "Create a local virtual machine or container" +msgstr "يەرلىك مەۋھۇم ماشىنا ياكى كونتېينېر قۇر" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to create a local virtual machine or container." +msgstr "يەرلىك مەۋھۇم ماشىنا ياكى كونتېينېر قۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/machine/org.freedesktop.machine1.policy:106 +msgid "Register a local virtual machine or container" +msgstr "يەرلىك مەۋھۇم ماشىنا ياكى كونتېينېرنى تىزىملا" + +#: src/machine/org.freedesktop.machine1.policy:107 +msgid "" +"Authentication is required to register a local virtual machine or container." +msgstr "يەرلىك مەۋھۇم ماشىنا ياكى كونتېينېرنى تىزىملاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/machine/org.freedesktop.machine1.policy:116 +msgid "Manage local virtual machine and container images" +msgstr "يەرلىك مەۋھۇم ماشىنا ۋە كونتېينېر ئەكسلىرىنى باشقۇر" + +#: src/machine/org.freedesktop.machine1.policy:117 +msgid "" +"Authentication is required to manage local virtual machine and container " +"images." +msgstr "يەرلىك مەۋھۇم ماشىنا ۋە كونتېينېر ئەكسلىرىنى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:22 +msgid "Set NTP servers" +msgstr "NTP مۇلازىمېتىرلىرىنى بەلگىلە" + +#: src/network/org.freedesktop.network1.policy:23 +msgid "Authentication is required to set NTP servers." +msgstr "NTP مۇلازىمېتىرلىرىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:33 +#: src/resolve/org.freedesktop.resolve1.policy:44 +msgid "Set DNS servers" +msgstr "DNS مۇلازىمېتىرلىرىنى بەلگىلە" + +#: src/network/org.freedesktop.network1.policy:34 +#: src/resolve/org.freedesktop.resolve1.policy:45 +msgid "Authentication is required to set DNS servers." +msgstr "DNS مۇلازىمېتىرلىرىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:44 +#: src/resolve/org.freedesktop.resolve1.policy:55 +msgid "Set domains" +msgstr "دومېنلارنى بەلگىلە" + +#: src/network/org.freedesktop.network1.policy:45 +#: src/resolve/org.freedesktop.resolve1.policy:56 +msgid "Authentication is required to set domains." +msgstr "دومېنلارنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:55 +#: src/resolve/org.freedesktop.resolve1.policy:66 +msgid "Set default route" +msgstr "كۆڭۈلدىكى route نى بەلگىلە" + +#: src/network/org.freedesktop.network1.policy:56 +#: src/resolve/org.freedesktop.resolve1.policy:67 +msgid "Authentication is required to set default route." +msgstr "كۆڭۈلدىكى route نى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:66 +#: src/resolve/org.freedesktop.resolve1.policy:77 +msgid "Enable/disable LLMNR" +msgstr "LLMNR نى قوزغات/چەكلە" + +#: src/network/org.freedesktop.network1.policy:67 +#: src/resolve/org.freedesktop.resolve1.policy:78 +msgid "Authentication is required to enable or disable LLMNR." +msgstr "LLMNR نى قوزغىتىش ياكى چەكلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:77 +#: src/resolve/org.freedesktop.resolve1.policy:88 +msgid "Enable/disable multicast DNS" +msgstr "multicast DNS نى قوزغات/چەكلە" + +#: src/network/org.freedesktop.network1.policy:78 +#: src/resolve/org.freedesktop.resolve1.policy:89 +msgid "Authentication is required to enable or disable multicast DNS." +msgstr "multicast DNS نى قوزغىتىش ياكى چەكلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:88 +#: src/resolve/org.freedesktop.resolve1.policy:99 +msgid "Enable/disable DNS over TLS" +msgstr "DNS over TLS نى قوزغات/چەكلە" + +#: src/network/org.freedesktop.network1.policy:89 +#: src/resolve/org.freedesktop.resolve1.policy:100 +msgid "Authentication is required to enable or disable DNS over TLS." +msgstr "DNS over TLS نى قوزغىتىش ياكى چەكلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:99 +#: src/resolve/org.freedesktop.resolve1.policy:110 +msgid "Enable/disable DNSSEC" +msgstr "DNSSEC نى قوزغات/چەكلە" + +#: src/network/org.freedesktop.network1.policy:100 +#: src/resolve/org.freedesktop.resolve1.policy:111 +msgid "Authentication is required to enable or disable DNSSEC." +msgstr "DNSSEC نى قوزغىتىش ياكى چەكلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:110 +#: src/resolve/org.freedesktop.resolve1.policy:121 +msgid "Set DNSSEC Negative Trust Anchors" +msgstr "DNSSEC سەلبىي ئىشەنچ لەڭگەرلىرىنى بەلگىلە" + +#: src/network/org.freedesktop.network1.policy:111 +#: src/resolve/org.freedesktop.resolve1.policy:122 +msgid "Authentication is required to set DNSSEC Negative Trust Anchors." +msgstr "DNSSEC سەلبىي ئىشەنچ لەڭگەرلىرىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:121 +msgid "Revert NTP settings" +msgstr "NTP تەڭشەكلىرىنى ئەسلىگە قايتۇر" + +#: src/network/org.freedesktop.network1.policy:122 +msgid "Authentication is required to reset NTP settings." +msgstr "NTP تەڭشەكلىرىنى قايتا بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:132 +msgid "Revert DNS settings" +msgstr "DNS تەڭشەكلىرىنى ئەسلىگە قايتۇر" + +#: src/network/org.freedesktop.network1.policy:133 +msgid "Authentication is required to reset DNS settings." +msgstr "DNS تەڭشەكلىرىنى قايتا بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:143 +msgid "DHCP server sends force renew message" +msgstr "DHCP مۇلازىمېتىرى زورلاپ يېڭىلاش ئۇچۇرىنى ئەۋەتىدۇ" + +#: src/network/org.freedesktop.network1.policy:144 +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "DHCP مۇلازىمېتىرىدىن زورلاپ يېڭىلاش ئۇچۇرى ئەۋەتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:154 +msgid "Renew dynamic addresses" +msgstr "ھەرىكەتچان ئادرېسلارنى يېڭىلا" + +#: src/network/org.freedesktop.network1.policy:155 +msgid "Authentication is required to renew dynamic addresses." +msgstr "ھەرىكەتچان ئادرېسلارنى يېڭىلاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:165 +msgid "Reload network settings" +msgstr "تور تەڭشەكلىرىنى قايتا يۈكلە" + +#: src/network/org.freedesktop.network1.policy:166 +msgid "Authentication is required to reload network settings." +msgstr "تور تەڭشەكلىرىنى قايتا يۈكلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:176 +msgid "Reconfigure network interface" +msgstr "تور ئۇلاش ئېغىزىنى قايتا سەپلە" + +#: src/network/org.freedesktop.network1.policy:177 +msgid "Authentication is required to reconfigure network interface." +msgstr "تور ئۇلاش ئېغىزىنى قايتا سەپلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:187 +msgid "Specify whether persistent storage for systemd-networkd is available" +msgstr "systemd-networkd ئۈچۈن تۇراقلىق ساقلاش ئىشلەتكىلى بولىدىغان-بولمايدىغانلىقىنى بەلگىلە" + +#: src/network/org.freedesktop.network1.policy:188 +msgid "" +"Authentication is required to specify whether persistent storage for systemd-" +"networkd is available." +msgstr "systemd-networkd ئۈچۈن تۇراقلىق ساقلاش ئىشلەتكىلى بولىدىغان-بولمايدىغانلىقىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/network/org.freedesktop.network1.policy:198 +msgid "Manage network links" +msgstr "تور ئۇلىنىشلىرىنى باشقۇر" + +#: src/network/org.freedesktop.network1.policy:199 +msgid "Authentication is required to manage network links." +msgstr "تور ئۇلىنىشلىرىنى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/portable/org.freedesktop.portable1.policy:13 +msgid "Inspect a portable service image" +msgstr "portable service image نى تەكشۈر" + +#: src/portable/org.freedesktop.portable1.policy:14 +msgid "Authentication is required to inspect a portable service image." +msgstr "portable service image نى تەكشۈرۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/portable/org.freedesktop.portable1.policy:23 +msgid "Attach or detach a portable service image" +msgstr "portable service image نى ئۇلا ياكى ئايرى" + +#: src/portable/org.freedesktop.portable1.policy:24 +msgid "" +"Authentication is required to attach or detach a portable service image." +msgstr "portable service image نى ئۇلاش ياكى ئايرىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/portable/org.freedesktop.portable1.policy:34 +msgid "Delete or modify portable service image" +msgstr "portable service image نى ئۆچۈر ياكى ئۆزگەرت" + +#: src/portable/org.freedesktop.portable1.policy:35 +msgid "" +"Authentication is required to delete or modify a portable service image." +msgstr "portable service image نى ئۆچۈرۈش ياكى ئۆزگەرتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/resolve/org.freedesktop.resolve1.policy:22 +msgid "Register a DNS-SD service" +msgstr "DNS-SD مۇلازىمىتىنى تىزىملا" + +#: src/resolve/org.freedesktop.resolve1.policy:23 +msgid "Authentication is required to register a DNS-SD service." +msgstr "DNS-SD مۇلازىمىتىنى تىزىملاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/resolve/org.freedesktop.resolve1.policy:33 +msgid "Unregister a DNS-SD service" +msgstr "DNS-SD مۇلازىمىتىنى تىزىمدىن چىقار" + +#: src/resolve/org.freedesktop.resolve1.policy:34 +msgid "Authentication is required to unregister a DNS-SD service." +msgstr "DNS-SD مۇلازىمىتىنى تىزىمدىن چىقىرىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/resolve/org.freedesktop.resolve1.policy:132 +msgid "Revert name resolution settings" +msgstr "ئات ئېچىش تەڭشەكلىرىنى ئەسلىگە قايتۇر" + +#: src/resolve/org.freedesktop.resolve1.policy:133 +msgid "Authentication is required to reset name resolution settings." +msgstr "ئات ئېچىش تەڭشەكلىرىنى قايتا بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/resolve/org.freedesktop.resolve1.policy:143 +msgid "Subscribe query results" +msgstr "سۈرۈشتۈرۈش نەتىجىلىرىگە مۇشتەرى بول" + +#: src/resolve/org.freedesktop.resolve1.policy:144 +msgid "Authentication is required to subscribe query results." +msgstr "سۈرۈشتۈرۈش نەتىجىلىرىگە مۇشتەرى بولۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/resolve/org.freedesktop.resolve1.policy:154 +msgid "Subscribe to DNS configuration" +msgstr "DNS سەپلىمىسىگە مۇشتەرى بول" + +#: src/resolve/org.freedesktop.resolve1.policy:155 +msgid "Authentication is required to subscribe to DNS configuration." +msgstr "DNS سەپلىمىسىگە مۇشتەرى بولۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/resolve/org.freedesktop.resolve1.policy:165 +msgid "Dump cache" +msgstr "كېشنى چىقار" + +#: src/resolve/org.freedesktop.resolve1.policy:166 +msgid "Authentication is required to dump cache." +msgstr "كېشنى چىقىرىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/resolve/org.freedesktop.resolve1.policy:176 +msgid "Dump server state" +msgstr "مۇلازىمېتىر ھالىتىنى چىقار" + +#: src/resolve/org.freedesktop.resolve1.policy:177 +msgid "Authentication is required to dump server state." +msgstr "مۇلازىمېتىر ھالىتىنى چىقىرىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/resolve/org.freedesktop.resolve1.policy:187 +msgid "Dump statistics" +msgstr "ئىستاتىستىكىنى چىقار" + +#: src/resolve/org.freedesktop.resolve1.policy:188 +msgid "Authentication is required to dump statistics." +msgstr "ئىستاتىستىكىنى چىقىرىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/resolve/org.freedesktop.resolve1.policy:198 +msgid "Reset statistics" +msgstr "ئىستاتىستىكىنى قايتا بەلگىلە" + +#: src/resolve/org.freedesktop.resolve1.policy:199 +msgid "Authentication is required to reset statistics." +msgstr "ئىستاتىستىكىنى قايتا بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +msgid "Check for system updates" +msgstr "سىستېما يېڭىلانمىلىرىنى تەكشۈر" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +msgid "Authentication is required to check for system updates." +msgstr "سىستېما يېڭىلانمىلىرىنى تەكشۈرۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +msgid "Install system updates" +msgstr "سىستېما يېڭىلانمىلىرىنى ئورنات" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +msgid "Authentication is required to install system updates." +msgstr "سىستېما يېڭىلانمىلىرىنى ئورنىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +msgid "Install specific system version" +msgstr "بەلگىلەنگەن سىستېما نەشرىنى ئورنات" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +msgid "" +"Authentication is required to update the system to a specific (possibly old) " +"version." +msgstr "سىستېمىنى بەلگىلەنگەن (بەلكىم كونا) نەشرگە يېڭىلاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 +msgid "Cleanup old system updates" +msgstr "كونا سىستېما يېڭىلانمىلىرىنى تازىلا" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 +msgid "Authentication is required to cleanup old system updates." +msgstr "كونا سىستېما يېڭىلانمىلىرىنى تازىلاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +msgid "Manage optional features" +msgstr "تاللانما ئىقتىدارلارنى باشقۇر" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +msgid "Authentication is required to manage optional features." +msgstr "تاللانما ئىقتىدارلارنى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/timedate/org.freedesktop.timedate1.policy:22 +msgid "Set system time" +msgstr "سىستېما ۋاقتىنى بەلگىلە" + +#: src/timedate/org.freedesktop.timedate1.policy:23 +msgid "Authentication is required to set the system time." +msgstr "سىستېما ۋاقتىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/timedate/org.freedesktop.timedate1.policy:33 +msgid "Set system timezone" +msgstr "سىستېما ۋاقىت رايونىنى بەلگىلە" + +#: src/timedate/org.freedesktop.timedate1.policy:34 +msgid "Authentication is required to set the system timezone." +msgstr "سىستېما ۋاقىت رايونىنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/timedate/org.freedesktop.timedate1.policy:43 +msgid "Set RTC to local timezone or UTC" +msgstr "RTC نى يەرلىك ۋاقىت رايونى ياكى UTC غا بەلگىلە" + +#: src/timedate/org.freedesktop.timedate1.policy:44 +msgid "" +"Authentication is required to control whether the RTC stores the local or " +"UTC time." +msgstr "RTC نىڭ يەرلىك ۋاقىتنى ياكى UTC نى ساقلىشىنى كونترول قىلىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/timedate/org.freedesktop.timedate1.policy:53 +msgid "Turn network time synchronization on or off" +msgstr "تور ۋاقتى ماس قەدەملىشىنى ئېچىش ياكى تاقاش" + +#: src/timedate/org.freedesktop.timedate1.policy:54 +msgid "" +"Authentication is required to control whether network time synchronization " +"shall be enabled." +msgstr "تور ۋاقتى ماس قەدەملىشىنى قوزغىتىش-قوزغاتماسلىقنى كونترول قىلىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/dbus-unit.c:372 +msgid "Authentication is required to start '$(unit)'." +msgstr "'$(unit)' نى قوزغىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/dbus-unit.c:373 +msgid "Authentication is required to stop '$(unit)'." +msgstr "'$(unit)' نى توختىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/dbus-unit.c:374 +msgid "Authentication is required to reload '$(unit)'." +msgstr "'$(unit)' نى قايتا يۈكلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/dbus-unit.c:375 src/core/dbus-unit.c:376 +msgid "Authentication is required to restart '$(unit)'." +msgstr "'$(unit)' نى قايتا قوزغىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/dbus-unit.c:568 +msgid "" +"Authentication is required to send a UNIX signal to the processes of '$" +"(unit)'." +msgstr "UNIX سىگنالىنى '$(unit)' نىڭ جەريانلىرىغا ئەۋەتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/dbus-unit.c:621 +msgid "" +"Authentication is required to send a UNIX signal to the processes of " +"subgroup of '$(unit)'." +msgstr "UNIX سىگنالىنى '$(unit)' نىڭ تارماق گۇرۇپپا جەريانلىرىغا ئەۋەتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/dbus-unit.c:649 +msgid "Authentication is required to reset the \"failed\" state of '$(unit)'." +msgstr "'$(unit)' نىڭ «مەغلۇپ» ھالىتىنى قايتا بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/dbus-unit.c:679 +msgid "Authentication is required to set properties on '$(unit)'." +msgstr "'$(unit)' ئۈستىدىكى خاسلىقلارنى بەلگىلەش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/dbus-unit.c:776 +msgid "" +"Authentication is required to delete files and directories associated with '$" +"(unit)'." +msgstr "'$(unit)' بىلەن مۇناسىۋەتلىك ھۆججەت ۋە مۇندەرىجىلەرنى ئۆچۈرۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/core/dbus-unit.c:813 +msgid "" +"Authentication is required to freeze or thaw the processes of '$(unit)' unit." +msgstr "'$(unit)' بىرىكىنىڭ جەريانلىرىنى توڭلىتىش ياكى ئېچىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." From 385294f7031227f7363e3c47f268fb71833a55d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 10 Mar 2026 22:56:00 +0100 Subject: [PATCH 0262/1296] sysusers: disallow --cat-config with --inline It doesn't work and it doesn't make much sense. --- src/sysusers/sysusers.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index 4e604f9a752a4..d1570eda56fec 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -2184,6 +2184,10 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --replace= is not supported with --cat-config/--tldr."); + if (arg_inline && arg_cat_flags != CAT_CONFIG_OFF) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Option --inline is not supported with --cat-config/--tldr."); + if (arg_replace && optind >= argc) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "When --replace= is given, some configuration items must be specified."); From 55bf0766a1895904094e63f3ee40ac30d1f3d170 Mon Sep 17 00:00:00 2001 From: David Tardon Date: Tue, 17 Mar 2026 09:18:54 +0100 Subject: [PATCH 0263/1296] coccinelle: fix exclusion path This file was moved 5 years ago... Follow-up for commits 99b9f8fddd3f15ca309cc6f068fc3c33caa9fd4e and 6dcabd5f5e8d21a1ef83ea4294539ad9874cd536 . --- coccinelle/zz-drop-braces.cocci | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coccinelle/zz-drop-braces.cocci b/coccinelle/zz-drop-braces.cocci index a1d9f8d4a34d5..5ca2f15681ca8 100644 --- a/coccinelle/zz-drop-braces.cocci +++ b/coccinelle/zz-drop-braces.cocci @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -@ depends on !(file in "src/journal/lookup3.c") @ +@ depends on !(file in "src/libsystemd/sd-journal/lookup3.c") @ expression e, e1; @@ - if (e) { From 58246e64082eadb0df77d0c7c4ca29e7b2b40ed5 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 16 Mar 2026 20:44:28 +0100 Subject: [PATCH 0264/1296] ci: Add automatic review thread resolution to claude-review workflow Claude now identifies which existing review comment threads should be resolved (because the issue was addressed or someone disagreed) and returns their REST API IDs in a new `resolve` array in the structured output. The post job uses GraphQL to map comment IDs to threads and resolve them. Also switches all GitHub data fetching from MCP tools to `gh api` calls, since the MCP tool strips comment IDs during its GraphQL-to-minimal conversion and cannot be used for thread resolution. The thread resolution GraphQL pagination is wrapped in a try/catch so that a failure to fetch threads degrades gracefully instead of aborting the entire post job. Unmatched comment IDs are logged for debuggability. Adds explicit instructions to complete all data fetching before starting review and to cancel background tasks before returning structured output, working around a claude-code-action issue where a late-completing background task triggers a new conversation turn that overwrites the structured JSON result. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/claude-review.yml | 155 +++++++++++++++++++++++----- 1 file changed, 131 insertions(+), 24 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 6d5bb01724570..05c1e3eb5f710 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -172,6 +172,13 @@ jobs: } } } + }, + "resolve": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "REST API IDs of existing review comments whose threads should be resolved because the issue was addressed or the author left a disagreeing reply" } } } @@ -187,18 +194,11 @@ jobs: --model us.anthropic.claude-opus-4-6-v1 --max-turns 100 --allowedTools " - Read,LS,Grep,Glob,Task, + Read,LS,Grep,Glob,Task,TaskStop, Bash(cat:*),Bash(test:*),Bash(printf:*),Bash(jq:*),Bash(head:*),Bash(tail:*), Bash(git:*),Bash(grep:*),Bash(find:*),Bash(ls:*),Bash(wc:*), Bash(gh:api *), Bash(diff:*),Bash(sed:*),Bash(awk:*),Bash(sort:*),Bash(uniq:*), - mcp__github__get_pull_request, - mcp__github__get_pull_request_files, - mcp__github__get_pull_request_reviews, - mcp__github__get_pull_request_comments, - mcp__github__get_pull_request_review_comments, - mcp__github__get_pull_request_status, - mcp__github__get_issue_comments, " --json-schema '${{ env.REVIEW_SCHEMA }}' prompt: | @@ -213,22 +213,27 @@ jobs: ## Phase 1: Gather context - Use the GitHub MCP server tools to fetch PR data. For all tools, pass - owner `${{ github.repository_owner }}`, repo `${{ github.event.repository.name }}`, - and pullNumber/issue_number ${{ needs.setup.outputs.pr_number }}: - - `mcp__github__get_pull_request` to get the PR title, body, and metadata - - `mcp__github__get_pull_request_comments` to get top-level PR comments - - `mcp__github__get_pull_request_reviews` to get PR reviews - - Fetch the list of commits in the PR using: - `gh api repos/{owner}/{repo}/pulls/{number}/commits --paginate --jq '.[].sha'` - - Also fetch issue comments using `mcp__github__get_issue_comments` with - issue_number ${{ needs.setup.outputs.pr_number }}. - - Look for an existing tracking comment (containing ``) - in the issue comments. If one exists, you will use it as the basis for - your `summary` in Phase 3. + Use `gh api` to fetch all PR data. The base path for all endpoints is + `repos/${{ github.repository }}/pulls/${{ needs.setup.outputs.pr_number }}`. + + **IMPORTANT: All data fetching in this phase MUST complete before moving to + Phase 2. Do NOT use `run_in_background` for any commands in this phase. Wait + for all results before proceeding.** + + Fetch the following in parallel: + - `gh api repos/${{ github.repository }}/pulls/${{ needs.setup.outputs.pr_number }}` + — PR title, body, and metadata + - `gh api repos/${{ github.repository }}/pulls/${{ needs.setup.outputs.pr_number }}/reviews --paginate` + — PR reviews + - `gh api repos/${{ github.repository }}/pulls/${{ needs.setup.outputs.pr_number }}/commits --paginate --jq '.[].sha'` + — list of commit SHAs + - `gh api repos/${{ github.repository }}/issues/${{ needs.setup.outputs.pr_number }}/comments --paginate` + — issue comments (look for the tracking comment containing ``; + if one exists, use it as the basis for your `summary` in Phase 3) + - `gh api repos/${{ github.repository }}/pulls/${{ needs.setup.outputs.pr_number }}/comments --paginate` + — inline review comments including each comment's numeric `id`, `path`, + `line`, `body`, `user.login`, and `in_reply_to_id`; you will need the + `id` fields in Phase 3 to populate the `resolve` array ## Phase 2: Per-commit review with subagents @@ -282,6 +287,15 @@ jobs: comment if one already exists on the same file and line about the same problem. Also check for author replies that dismiss or reject a previous comment — do NOT re-raise an issue the PR author has already responded to disagreeing with. + Populate the `resolve` array with the REST API `id` (integer) of existing + review comments whose threads should be resolved. A thread should be resolved if: + - The issue it raised has been addressed in the current PR (i.e. your review + no longer flags it), OR + - The PR author (or another reviewer) left a reply disagreeing with or + dismissing the comment. + Only include the `id` of the **first** comment in each thread (the one that + started the conversation). Do NOT resolve threads for issues that are still + present and unaddressed. 4. Do NOT prefix `body` with a severity tag — the severity is already captured in the `severity` field and will be added automatically when posting inline comments. @@ -332,6 +346,11 @@ jobs: ## CRITICAL: Return structured JSON output + Before returning structured output, cancel ALL running background tasks + using the TaskStop tool. A background task completing after you return + structured output will trigger a new conversation turn that overwrites your + result and causes the workflow to fail. + Your FINAL action must be to return a JSON object matching the following JSON schema — do NOT end with a text summary or narrative. The `--json-schema` flag is set, so your last response must be the structured JSON result, not a @@ -390,12 +409,15 @@ jobs: console.log(raw || "(empty)"); let comments = []; + let resolveIds = []; let summary = ""; if (raw) { try { const review = JSON.parse(raw); if (Array.isArray(review.comments)) comments = review.comments; + if (Array.isArray(review.resolve)) + resolveIds = review.resolve; if (typeof review.summary === "string") summary = review.summary; } catch (e) { @@ -442,6 +464,91 @@ jobs: else console.log("No inline comments to post."); + /* Resolve review threads that Claude identified as addressed or dismissed. */ + if (resolveIds.length > 0) { + const resolveSet = new Set(resolveIds); + + /* Fetch all review threads and map first-comment database IDs to thread IDs. */ + let threads = []; + try { + let threadCursor = null; + do { + const threadQuery = ` + query($owner: String!, $repo: String!, $number: Int!, $cursor: String) { + repository(owner: $owner, name: $repo) { + pullRequest(number: $number) { + reviewThreads(first: 100, after: $cursor) { + pageInfo { hasNextPage endCursor } + nodes { + id + isResolved + comments(first: 1) { + nodes { + databaseId + } + } + } + } + } + } + } + `; + + const threadResult = await github.graphql(threadQuery, { owner, repo, number: prNumber, cursor: threadCursor }); + const page = threadResult.repository.pullRequest.reviewThreads; + threads.push(...page.nodes); + threadCursor = page.pageInfo.hasNextPage ? page.pageInfo.endCursor : null; + } while (threadCursor); + } catch (e) { + console.log(`Warning: failed to fetch review threads, skipping resolution: ${e.message}`); + threads = []; + } + + let resolved = 0; + let alreadyResolved = 0; + const matchedIds = new Set(); + for (const thread of threads) { + const firstCommentId = thread.comments.nodes[0]?.databaseId; + if (!firstCommentId || !resolveSet.has(firstCommentId)) continue; + + matchedIds.add(firstCommentId); + + if (thread.isResolved) { + alreadyResolved++; + continue; + } + + try { + await github.graphql(` + mutation($threadId: ID!) { + resolveReviewThread(input: { threadId: $threadId }) { + thread { id } + } + } + `, { threadId: thread.id }); + resolved++; + console.log(` Resolved thread for comment ${firstCommentId}`); + } catch (e) { + console.log(` Warning: failed to resolve thread for comment ${firstCommentId}: ${e.message}`); + } + } + + const requested = resolveSet.size; + const unmatched = [...resolveSet].filter(id => !matchedIds.has(id)); + if (resolved > 0) + console.log(`Resolved ${resolved}/${requested} review thread(s)${alreadyResolved > 0 ? ` (${alreadyResolved} already resolved)` : ""}.`); + else if (alreadyResolved === requested) + console.log(`All ${requested} review thread(s) were already resolved.`); + else if (alreadyResolved > 0) + console.log(`${alreadyResolved}/${requested} review thread(s) were already resolved; could not resolve the rest — see warnings above.`); + else if (threads.length > 0) + console.log(`Could not resolve any of ${requested} review thread(s) — see warnings above.`); + if (unmatched.length > 0) + console.log(` ${unmatched.length} comment ID(s) not found in any thread: ${unmatched.join(", ")}`); + } else { + console.log("No review threads to resolve."); + } + /* Update the tracking comment with Claude's summary. */ if (!summary) summary = "Claude review: no issues found :tada:\n\n" + MARKER; From ce48f694a0baec7cb1627bdaadf2fbeafdb4c1ad Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 17 Mar 2026 17:38:01 +0000 Subject: [PATCH 0265/1296] Update hwdb ninja -C build update-hwdb --- hwdb.d/20-OUI.hwdb | 203 +++++++++++++++++- hwdb.d/20-acpi-vendor.hwdb.patch | 4 +- hwdb.d/20-pci-vendor-model.hwdb | 33 +++ hwdb.d/ma-large.txt | 354 +++++++++++++++++++++++++++---- hwdb.d/ma-medium.txt | 96 +++++++-- hwdb.d/ma-small.txt | 72 +++++++ hwdb.d/pci.ids | 15 +- 7 files changed, 715 insertions(+), 62 deletions(-) diff --git a/hwdb.d/20-OUI.hwdb b/hwdb.d/20-OUI.hwdb index 39bc652bc85e6..ace393450eda4 100644 --- a/hwdb.d/20-OUI.hwdb +++ b/hwdb.d/20-OUI.hwdb @@ -34214,6 +34214,9 @@ OUI:007686* OUI:0076B1* ID_OUI_FROM_DATABASE=Somfy-Protect By Myfox SAS +OUI:0076B6* + ID_OUI_FROM_DATABASE=Ford Motor Company + OUI:00778D* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -43031,6 +43034,9 @@ OUI:08CD9B* OUI:08CE94* ID_OUI_FROM_DATABASE=EM Microelectronic +OUI:08D01E* + ID_OUI_FROM_DATABASE=Juniper Networks + OUI:08D09F* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -45380,6 +45386,9 @@ OUI:10394E* OUI:1039E9* ID_OUI_FROM_DATABASE=Juniper Networks +OUI:103A5D* + ID_OUI_FROM_DATABASE=Emerson + OUI:103B59* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -49211,6 +49220,9 @@ OUI:18D9EF* OUI:18DBF2* ID_OUI_FROM_DATABASE=Dell Inc. +OUI:18DC12* + ID_OUI_FROM_DATABASE=Silicon Laboratories + OUI:18DC56* ID_OUI_FROM_DATABASE=Yulong Computer Telecommunication Scientific (Shenzhen) Co.,Ltd @@ -49331,6 +49343,9 @@ OUI:18F46A* OUI:18F46B* ID_OUI_FROM_DATABASE=Telenor Connexion AB +OUI:18F58B* + ID_OUI_FROM_DATABASE=GlobalReach Technology EMEA Ltd + OUI:18F643* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -52118,12 +52133,48 @@ OUI:20B001* OUI:20B0F7* ID_OUI_FROM_DATABASE=Enclustra GmbH +OUI:20B37F0* + ID_OUI_FROM_DATABASE=Chelsio Communications Inc + +OUI:20B37F1* + ID_OUI_FROM_DATABASE=TDK-Lambda UK + OUI:20B37F2* ID_OUI_FROM_DATABASE=Aina Computers ,Inc. +OUI:20B37F3* + ID_OUI_FROM_DATABASE=QT medical inc + +OUI:20B37F4* + ID_OUI_FROM_DATABASE=OTP CO.,LTD. + +OUI:20B37F5* + ID_OUI_FROM_DATABASE=Shenzhen HantangFengyun Technology Co.,Ltd + +OUI:20B37F6* + ID_OUI_FROM_DATABASE=Kitchen Armor + +OUI:20B37F7* + ID_OUI_FROM_DATABASE=Luxedo + +OUI:20B37F8* + ID_OUI_FROM_DATABASE=Xconnect LLP + +OUI:20B37F9* + ID_OUI_FROM_DATABASE=Annapurna labs + +OUI:20B37FA* + ID_OUI_FROM_DATABASE=ShenZhen C&D Electronics CO.Ltd. + OUI:20B37FB* ID_OUI_FROM_DATABASE=B810 SPA +OUI:20B37FC* + ID_OUI_FROM_DATABASE=EGSTON Power Electronics GmbH + +OUI:20B37FD* + ID_OUI_FROM_DATABASE=Xunmu Information Technology (Shanghai) Co., Ltd. + OUI:20B399* ID_OUI_FROM_DATABASE=Enterasys @@ -54731,6 +54782,9 @@ OUI:28852D* OUI:2885BB* ID_OUI_FROM_DATABASE=Zen Exim Pvt. Ltd. +OUI:28875F* + ID_OUI_FROM_DATABASE=Annapurna labs + OUI:288761* ID_OUI_FROM_DATABASE=LG Innotek @@ -56684,6 +56738,9 @@ OUI:2CABA4* OUI:2CABEB* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:2CABEE* + ID_OUI_FROM_DATABASE=EM Microelectronic + OUI:2CAC44* ID_OUI_FROM_DATABASE=CONEXTOP @@ -56723,6 +56780,9 @@ OUI:2CB301* OUI:2CB43A* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:2CB471* + ID_OUI_FROM_DATABASE=Tuya Smart Inc. + OUI:2CB68F* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -57197,6 +57257,9 @@ OUI:30074D* OUI:30075C* ID_OUI_FROM_DATABASE=43403 +OUI:30084D* + ID_OUI_FROM_DATABASE=Trumpf Hüttinger + OUI:3009C0* ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company @@ -57374,6 +57437,9 @@ OUI:301ABA* OUI:301B97* ID_OUI_FROM_DATABASE=Lierda Science & Technology Group Co.,Ltd +OUI:301C22* + ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise + OUI:301D49* ID_OUI_FROM_DATABASE=Firmus Technologies Pty Ltd @@ -58163,6 +58229,9 @@ OUI:30A452* OUI:30A612* ID_OUI_FROM_DATABASE=ShenZhen Hugsun Technology Co.,Ltd. +OUI:30A771* + ID_OUI_FROM_DATABASE=Jiang Su Fulian Communication Technology Co.,Ltd + OUI:30A889* ID_OUI_FROM_DATABASE=DECIMATOR DESIGN @@ -60956,6 +61025,9 @@ OUI:386BBB* OUI:386C9B* ID_OUI_FROM_DATABASE=Ivy Biomedical +OUI:386DED* + ID_OUI_FROM_DATABASE=Juniper Networks + OUI:386E21* ID_OUI_FROM_DATABASE=Wasion Group Ltd. @@ -65342,6 +65414,9 @@ OUI:444988* OUI:444A37* ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd +OUI:444A4C* + ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. + OUI:444A65* ID_OUI_FROM_DATABASE=Silverflare Ltd. @@ -65714,6 +65789,9 @@ OUI:448F17* OUI:449046* ID_OUI_FROM_DATABASE=Honor Device Co., Ltd. +OUI:4490BA* + ID_OUI_FROM_DATABASE=CHINA DRAGON TECHNOLOGY LIMITED + OUI:4490BB* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -69983,6 +70061,9 @@ OUI:50617E* OUI:506184* ID_OUI_FROM_DATABASE=Avaya Inc +OUI:506188* + ID_OUI_FROM_DATABASE=PLANET Technology Corporation + OUI:5061BF* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -71162,6 +71243,9 @@ OUI:541310* OUI:541379* ID_OUI_FROM_DATABASE=Hon Hai Precision Ind. Co.,Ltd. +OUI:54138F* + ID_OUI_FROM_DATABASE=GEOIDE Crypto&Com + OUI:5413CA* ID_OUI_FROM_DATABASE=ITEL MOBILE LIMITED @@ -74579,6 +74663,9 @@ OUI:5C80B6* OUI:5C81A7* ID_OUI_FROM_DATABASE=Network Devices Pty Ltd +OUI:5C8217* + ID_OUI_FROM_DATABASE=DSE srl + OUI:5C836C* ID_OUI_FROM_DATABASE=Ruckus Wireless @@ -74948,6 +75035,9 @@ OUI:5CBA2C* OUI:5CBA37* ID_OUI_FROM_DATABASE=Microsoft Corporation +OUI:5CBA75* + ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co., Ltd. + OUI:5CBAEF* ID_OUI_FROM_DATABASE=CHONGQING FUGUI ELECTRONICS CO.,LTD. @@ -76199,6 +76289,9 @@ OUI:609316* OUI:609532* ID_OUI_FROM_DATABASE=Zebra Technologies Inc. +OUI:609578* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:6095BD* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -77804,6 +77897,9 @@ OUI:64A965* OUI:64AC2B* ID_OUI_FROM_DATABASE=Juniper Networks +OUI:64ACE0* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:64AE0C* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -79094,6 +79190,9 @@ OUI:689E19* OUI:689E29* ID_OUI_FROM_DATABASE=zte corporation +OUI:689E67* + ID_OUI_FROM_DATABASE=SHENZHEN FOCUSCOM TECHNOLOGIES CO., LTD + OUI:689E6A* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -80972,6 +81071,9 @@ OUI:6CE01E* OUI:6CE0B0* ID_OUI_FROM_DATABASE=SOUND4 +OUI:6CE20C* + ID_OUI_FROM_DATABASE=Hangzhou SDIC Microelectronics Inc. + OUI:6CE2D3* ID_OUI_FROM_DATABASE=New H3C Technologies Co., Ltd @@ -94970,6 +95072,9 @@ OUI:7412B3* OUI:7412BB* ID_OUI_FROM_DATABASE=Fiberhome Telecommunication Technologies Co.,LTD +OUI:74136A* + ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company + OUI:7413EA* ID_OUI_FROM_DATABASE=Intel Corporate @@ -96578,6 +96683,9 @@ OUI:78078F* OUI:78084D* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:780A57* + ID_OUI_FROM_DATABASE=Shanghai Lightningsemi Technology Co.,Ltd. + OUI:780AC7* ID_OUI_FROM_DATABASE=Baofeng TV Co., Ltd. @@ -98792,6 +98900,9 @@ OUI:7C5A1C* OUI:7C5A67* ID_OUI_FROM_DATABASE=JNC Systems, Inc. +OUI:7C5C8D* + ID_OUI_FROM_DATABASE=EM Microelectronic + OUI:7C5CF8* ID_OUI_FROM_DATABASE=Intel Corporate @@ -104930,6 +105041,9 @@ OUI:8C1F640A0* OUI:8C1F640A2* ID_OUI_FROM_DATABASE=BEST +OUI:8C1F640A3* + ID_OUI_FROM_DATABASE=Fischer & Connectors SA + OUI:8C1F640A4* ID_OUI_FROM_DATABASE=Dynamic Research, Inc. @@ -104939,6 +105053,9 @@ OUI:8C1F640A5* OUI:8C1F640A8* ID_OUI_FROM_DATABASE=SamabaNova Systems +OUI:8C1F640A9* + ID_OUI_FROM_DATABASE=RFT Corp. + OUI:8C1F640AA* ID_OUI_FROM_DATABASE=DI3 INFOTECH LLP @@ -105338,6 +105455,9 @@ OUI:8C1F6417C* OUI:8C1F6417E* ID_OUI_FROM_DATABASE=MI Inc. +OUI:8C1F6417F* + ID_OUI_FROM_DATABASE=BCMTECH + OUI:8C1F64180* ID_OUI_FROM_DATABASE=Structural Integrity Services @@ -106508,6 +106628,9 @@ OUI:8C1F643DF* OUI:8C1F643E0* ID_OUI_FROM_DATABASE=YPP Corporation +OUI:8C1F643E1* + ID_OUI_FROM_DATABASE=CRUXELL Corp. + OUI:8C1F643E2* ID_OUI_FROM_DATABASE=Agrico @@ -106766,6 +106889,9 @@ OUI:8C1F64466* OUI:8C1F6446A* ID_OUI_FROM_DATABASE=Pharsighted LLC +OUI:8C1F6446B* + ID_OUI_FROM_DATABASE=PERSOL EXCEL HR PARTNERS CO., LTD. + OUI:8C1F6446D* ID_OUI_FROM_DATABASE=MB connect line GmbH @@ -107123,6 +107249,9 @@ OUI:8C1F64519* OUI:8C1F6451A* ID_OUI_FROM_DATABASE=TELE Haase Steuergeräte Ges.m.b.H +OUI:8C1F6451E* + ID_OUI_FROM_DATABASE=Owl Home Inc. + OUI:8C1F64521* ID_OUI_FROM_DATABASE=MP-SENSOR GmbH @@ -107339,6 +107468,9 @@ OUI:8C1F6458C* OUI:8C1F6458E* ID_OUI_FROM_DATABASE=Novanta IMS +OUI:8C1F64590* + ID_OUI_FROM_DATABASE=Teledyne Scientific and Imaging + OUI:8C1F64591* ID_OUI_FROM_DATABASE=MB connect line GmbH Fernwartungssysteme @@ -107375,6 +107507,9 @@ OUI:8C1F645A0* OUI:8C1F645A1* ID_OUI_FROM_DATABASE=Breas Medical AB +OUI:8C1F645A2* + ID_OUI_FROM_DATABASE=CMI, Inc. + OUI:8C1F645A4* ID_OUI_FROM_DATABASE=DAVE SRL @@ -109937,6 +110072,9 @@ OUI:8C1F64ACD* OUI:8C1F64ACE* ID_OUI_FROM_DATABASE=Rayhaan Networks +OUI:8C1F64ACF* + ID_OUI_FROM_DATABASE=PROVENRUN + OUI:8C1F64AD0* ID_OUI_FROM_DATABASE=Elektrotechnik & Elektronik Oltmann GmbH @@ -111272,6 +111410,9 @@ OUI:8C1F64D69* OUI:8C1F64D6C* ID_OUI_FROM_DATABASE=Packetalk LLC +OUI:8C1F64D6F* + ID_OUI_FROM_DATABASE=ARKTRON ELECTRONICS + OUI:8C1F64D71* ID_OUI_FROM_DATABASE=Computech International @@ -112031,6 +112172,9 @@ OUI:8C1F64F13* OUI:8C1F64F14* ID_OUI_FROM_DATABASE=Elektrosil GmbH +OUI:8C1F64F16* + ID_OUI_FROM_DATABASE=Schildknecht AG + OUI:8C1F64F18* ID_OUI_FROM_DATABASE=Northern Design (Electronics) Ltd @@ -112448,6 +112592,9 @@ OUI:8C1F64FED* OUI:8C1F64FEE* ID_OUI_FROM_DATABASE=Leap Info Systems Pvt. Ltd. +OUI:8C1F64FF2* + ID_OUI_FROM_DATABASE=MITSUBISHI ELECTRIC INDIA PVT. LTD. + OUI:8C1F64FF3* ID_OUI_FROM_DATABASE=Fuzhou Tucsen Photonics Co.,Ltd @@ -112763,6 +112910,9 @@ OUI:8C5109E* OUI:8C5219* ID_OUI_FROM_DATABASE=SHARP Corporation +OUI:8C5387* + ID_OUI_FROM_DATABASE=Huzhou Luxshare Precision Industry Co.LTD + OUI:8C53C3* ID_OUI_FROM_DATABASE=Beijing Xiaomi Mobile Software Co., Ltd @@ -115988,6 +116138,9 @@ OUI:9453FF* OUI:945493* ID_OUI_FROM_DATABASE=Rigado, LLC +OUI:9454A0* + ID_OUI_FROM_DATABASE=Fosilicon CO., Ltd + OUI:9454C5* ID_OUI_FROM_DATABASE=Espressif Inc. @@ -117329,6 +117482,9 @@ OUI:982A0A* OUI:982AFD* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:982BA6* + ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company + OUI:982CBC* ID_OUI_FROM_DATABASE=Intel Corporate @@ -117404,6 +117560,9 @@ OUI:983B16* OUI:983B67* ID_OUI_FROM_DATABASE=DWnet Technologies(Suzhou) Corporation +OUI:983B8A* + ID_OUI_FROM_DATABASE=Sekisui Jushi CAP-AI System Co.,Ltd. + OUI:983B8F* ID_OUI_FROM_DATABASE=Intel Corporate @@ -125717,6 +125876,9 @@ OUI:ACE4B5* OUI:ACE5F0* ID_OUI_FROM_DATABASE=Doppler Labs +OUI:ACE606* + ID_OUI_FROM_DATABASE=Honor Device Co., Ltd. + OUI:ACE64B* ID_OUI_FROM_DATABASE=Shenzhen Baojia Battery Technology Co., Ltd. @@ -126488,6 +126650,9 @@ OUI:B07994* OUI:B07A16* ID_OUI_FROM_DATABASE=ROEHN +OUI:B07AA4* + ID_OUI_FROM_DATABASE=Guangzhou Punp Electronics Manufacturing Co., Ltd. + OUI:B07ADF* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -128975,6 +129140,9 @@ OUI:B837B2* OUI:B83861* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:B83865* + ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise + OUI:B838CA* ID_OUI_FROM_DATABASE=Kyokko Tsushin System CO.,LTD @@ -129473,6 +129641,9 @@ OUI:B894E7* OUI:B89674* ID_OUI_FROM_DATABASE=AllDSP GmbH & Co. KG +OUI:B89734* + ID_OUI_FROM_DATABASE=Silicon Laboratories + OUI:B8975A* ID_OUI_FROM_DATABASE=BIOSTAR Microtech Int'l Corp. @@ -134658,7 +134829,7 @@ OUI:C4FFBC3* ID_OUI_FROM_DATABASE=SHENZHEN KALIF ELECTRONICS CO.,LTD OUI:C4FFBC4* - ID_OUI_FROM_DATABASE=iMageTech CO.,LTD. + ID_OUI_FROM_DATABASE=HyperNet CO., LTD OUI:C4FFBC5* ID_OUI_FROM_DATABASE=comtime GmbH @@ -136427,6 +136598,9 @@ OUI:CC1E56* OUI:CC1E97* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:CC1EAB* + ID_OUI_FROM_DATABASE=LEDATEL sp. z o.o. i Wspólnicy sp.k + OUI:CC1EFF* ID_OUI_FROM_DATABASE=Metrological Group BV @@ -136562,6 +136736,9 @@ OUI:CC2F71* OUI:CC3080* ID_OUI_FROM_DATABASE=VAIO Corporation +OUI:CC3089* + ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. + OUI:CC312A* ID_OUI_FROM_DATABASE=HUIZHOU TCL COMMUNICATION ELECTRON CO.,LTD @@ -137729,6 +137906,9 @@ OUI:CCFA00* OUI:CCFA66* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. +OUI:CCFA95* + ID_OUI_FROM_DATABASE=Honor Device Co., Ltd. + OUI:CCFAF1* ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS @@ -138614,6 +138794,9 @@ OUI:D0817A* OUI:D081C5* ID_OUI_FROM_DATABASE=Juniper Networks +OUI:D082EB* + ID_OUI_FROM_DATABASE=Tuya Smart Inc. + OUI:D083D4* ID_OUI_FROM_DATABASE=Xtel Wireless ApS @@ -138776,6 +138959,9 @@ OUI:D09686D* OUI:D09686E* ID_OUI_FROM_DATABASE=withnetworks +OUI:D096EA* + ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. + OUI:D096FB* ID_OUI_FROM_DATABASE=Zhone Technologies, Inc. @@ -141473,6 +141659,9 @@ OUI:D85B27* OUI:D85B2A* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:D85C11* + ID_OUI_FROM_DATABASE=Optiview USA + OUI:D85D4C* ID_OUI_FROM_DATABASE=TP-LINK TECHNOLOGIES CO.,LTD. @@ -142955,6 +143144,9 @@ OUI:DC7306* OUI:DC7385* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. +OUI:DC73FC* + ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. + OUI:DC74A8* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -144461,6 +144653,9 @@ OUI:E0806B* OUI:E08177* ID_OUI_FROM_DATABASE=GreenBytes, Inc. +OUI:E0830D* + ID_OUI_FROM_DATABASE=NOTTA PTE. LTD. + OUI:E084F3* ID_OUI_FROM_DATABASE=High Grade Controls Corporation @@ -153803,6 +153998,9 @@ OUI:F8DFA8* OUI:F8DFE1* ID_OUI_FROM_DATABASE=MyLight Systems +OUI:F8E000* + ID_OUI_FROM_DATABASE=FUJI ELECTRIC CO., LTD. + OUI:F8E079* ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company @@ -155075,6 +155273,9 @@ OUI:FCC737* OUI:FCC897* ID_OUI_FROM_DATABASE=zte corporation +OUI:FCCA10* + ID_OUI_FROM_DATABASE=MERCUSYS TECHNOLOGIES CO., LTD. + OUI:FCCAC4* ID_OUI_FROM_DATABASE=LifeHealth, LLC diff --git a/hwdb.d/20-acpi-vendor.hwdb.patch b/hwdb.d/20-acpi-vendor.hwdb.patch index 13c467e2f5fd4..8c6427858ffc2 100644 --- a/hwdb.d/20-acpi-vendor.hwdb.patch +++ b/hwdb.d/20-acpi-vendor.hwdb.patch @@ -1,5 +1,5 @@ ---- 20-acpi-vendor.hwdb.base 2026-03-10 17:03:34.662556881 +0000 -+++ 20-acpi-vendor.hwdb 2026-03-10 17:03:34.666557017 +0000 +--- 20-acpi-vendor.hwdb.base 2026-03-17 17:31:25.705001902 +0000 ++++ 20-acpi-vendor.hwdb 2026-03-17 17:31:25.713002098 +0000 @@ -3,6 +3,8 @@ # Data imported from: # https://uefi.org/uefi-pnp-export diff --git a/hwdb.d/20-pci-vendor-model.hwdb b/hwdb.d/20-pci-vendor-model.hwdb index 60a78dcfad4a8..dc48494c5197f 100644 --- a/hwdb.d/20-pci-vendor-model.hwdb +++ b/hwdb.d/20-pci-vendor-model.hwdb @@ -12083,6 +12083,9 @@ pci:v00001002d00007550* pci:v00001002d00007550sv0000148Csd00002435* ID_MODEL_FROM_DATABASE=Navi 48 [Radeon RX 9070/9070 XT/9070 GRE] (Radeon RX 9070 XT 16GB) +pci:v00001002d00007550sv00001849sd00005403* + ID_MODEL_FROM_DATABASE=Navi 48 [Radeon RX 9070/9070 XT/9070 GRE] (Navi 48 XTX [Steel Legend Radeon RX 9070 XT]) + pci:v00001002d00007550sv00001DA2sd0000E490* ID_MODEL_FROM_DATABASE=Navi 48 [Radeon RX 9070/9070 XT/9070 GRE] (Navi 48 XTX [Sapphire Pulse Radeon RX 9070 XT]) @@ -63857,6 +63860,12 @@ pci:v000014E4d00001760sv000014E4sd00009345* pci:v000014E4d00001760sv000014E4sd0000D125* ID_MODEL_FROM_DATABASE=BCM57608 25Gb/50Gb/100Gb/200Gb/400Gb Ethernet (BCM57608 2x200G PCIe Ethernet NIC) +pci:v000014E4d00001760sv0000193Dsd0000105B* + ID_MODEL_FROM_DATABASE=BCM57608 25Gb/50Gb/100Gb/200Gb/400Gb Ethernet (NIC-ETH2030F-LP-2P 2x200G PCIe Ethernet NIC) + +pci:v000014E4d00001760sv0000193Dsd0000105C* + ID_MODEL_FROM_DATABASE=BCM57608 25Gb/50Gb/100Gb/200Gb/400Gb Ethernet (NIC-ETH4030F-LP-1P 1x400G PCIe Ethernet NIC) + pci:v000014E4d00001800* ID_MODEL_FROM_DATABASE=BCM57502 NetXtreme-E Ethernet Partition @@ -86522,6 +86531,18 @@ pci:v00001FF2d000010A1sv00001FF2sd00000C11* pci:v00001FF2d000010A2* ID_MODEL_FROM_DATABASE=NIC1160 Ethernet Controller Virtual Function Family +pci:v00001FF2d000010B1* + ID_MODEL_FROM_DATABASE=NIC 1260 Ethernet Controller Family + +pci:v00001FF2d000010B2* + ID_MODEL_FROM_DATABASE=NIC 1260 Ethernet Controller Virtual Function Family + +pci:v00001FF2d000010B3* + ID_MODEL_FROM_DATABASE=NIC 1260C Ethernet Controller Family + +pci:v00001FF2d000010B4* + ID_MODEL_FROM_DATABASE=NIC 1260C Ethernet Controller Virtual Function Family + pci:v00001FF2d000020A1* ID_MODEL_FROM_DATABASE=IOC2110 Storage Controller @@ -87317,6 +87338,18 @@ pci:v000020E1d00007103* pci:v000020E1d00007104* ID_MODEL_FROM_DATABASE=LS X710-P +pci:v000020E1d00007180* + ID_MODEL_FROM_DATABASE=LS X718 + +pci:v000020E1d00007211* + ID_MODEL_FROM_DATABASE=LS X721-E + +pci:v000020E1d00007223* + ID_MODEL_FROM_DATABASE=LS X722-M + +pci:v000020E1d00007224* + ID_MODEL_FROM_DATABASE=LS X722-P + pci:v000020E3* ID_VENDOR_FROM_DATABASE=Elix Systems SA diff --git a/hwdb.d/ma-large.txt b/hwdb.d/ma-large.txt index fa7556a22ad96..fbc3faa600c75 100644 --- a/hwdb.d/ma-large.txt +++ b/hwdb.d/ma-large.txt @@ -47087,18 +47087,84 @@ D44A85 (base 16) Silicon Laboratories Austin TX 78701 US +60-95-78 (hex) Samsung Electronics Co.,Ltd +609578 (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 + KR + +78-0A-57 (hex) Shanghai Lightningsemi Technology Co.,Ltd. +780A57 (base 16) Shanghai Lightningsemi Technology Co.,Ltd. + Floor 5, Building 6,No. 9,Lane 1670,XiuYan road,ISPACE Kangqiao Intelligent Manufacturing Industrial Park,Pudong district + SHANGHAI SHANGHAI 201315 + CN + +D0-96-EA (hex) vivo Mobile Communication Co., Ltd. +D096EA (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN + +5C-BA-75 (hex) Quectel Wireless Solutions Co., Ltd. +5CBA75 (base 16) Quectel Wireless Solutions Co., Ltd. + Building 5, Shanghai Business Park Phase III (Area B), No.1016 Tianlin Road, Minhang District + Shanghai 200233 + CN + +74-13-6A (hex) Motorola Mobility LLC, a Lenovo Company +74136A (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US + 38-A3-E0 (hex) 1Finity Inc 38A3E0 (base 16) 1Finity Inc 4-1-1 Kamikodanaka, Nakahara-ku, Kawasaki-shi, Kanagawa211-8588, Japan Kawasaki Kanagawa 211-8588 JP +98-3B-8A (hex) Sekisui Jushi CAP-AI System Co.,Ltd. +983B8A (base 16) Sekisui Jushi CAP-AI System Co.,Ltd. + Mandai Mita Building 2F,3-2-3 Mita,Minato-ku + Tokyo 108-0073 + JP + +08-D0-1E (hex) Juniper Networks +08D01E (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US + A8-D3-F7 (hex) Arcadyan Corporation A8D3F7 (base 16) Arcadyan Corporation No.8, Sec.2, Guangfu Rd., Hsinchu City Hsinchu 30071 TW +B8-38-65 (hex) Hewlett Packard Enterprise +B83865 (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US + +68-9E-67 (hex) SHENZHEN FOCUSCOM TECHNOLOGIES CO., LTD +689E67 (base 16) SHENZHEN FOCUSCOM TECHNOLOGIES CO., LTD + Room 1205, Skyworth Digital Building, Songbai Road, Baoan District, Shenzhen, China + Shenzhen Guangdong 518108 + CN + +8C-53-87 (hex) Huzhou Luxshare Precision Industry Co.LTD +8C5387 (base 16) Huzhou Luxshare Precision Industry Co.LTD + 399 Shengxun Road, Zhili Town, Wuxing District,Huzhou City, Zhejiang Province + Huzhou Zhejiang 313008 + CN + +54-13-8F (hex) GEOIDE Crypto&Com +54138F (base 16) GEOIDE Crypto&Com + 18 Rue Alain Savary + BESANCON 25000 + FR + 00-01-30 (hex) Extreme Networks Headquarters 000130 (base 16) Extreme Networks Headquarters 2121 RDU Center Drive @@ -91946,12 +92012,6 @@ B02EBA (base 16) Earda Technologies co Ltd Sunnyvale CA 94089 US -EC-96-BF (hex) Kontron eSystems GmbH -EC96BF (base 16) Kontron eSystems GmbH - Bahnhofstraße 100 - Wendlingen 73240 - DE - 40-54-93 (hex) zte corporation 405493 (base 16) zte corporation 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China @@ -93416,6 +93476,18 @@ B4B650 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. Chongqing China 401120 CN +DC-73-FC (hex) Mellanox Technologies, Inc. +DC73FC (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +CC-30-89 (hex) Mellanox Technologies, Inc. +CC3089 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + 84-9C-A6 (hex) Arcadyan Corporation 849CA6 (base 16) Arcadyan Corporation 4F, No. 9, Park Avenue II , @@ -93434,6 +93506,54 @@ ECB5AF (base 16) RayService a.s. Staré Město Czech Republic 686 03 CZ +20-B3-7F (hex) IEEE Registration Authority +20B37F (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +18-F5-8B (hex) GlobalReach Technology EMEA Ltd +18F58B (base 16) GlobalReach Technology EMEA Ltd + 51 Eastcheap + London EC3M 1DT + GB + +6C-E2-0C (hex) Hangzhou SDIC Microelectronics Inc. +6CE20C (base 16) Hangzhou SDIC Microelectronics Inc. + 5/F, Bldg 4 Tuosen Technology Park 351 Changhe Road, Binjiang District Hangzhou, Zhejiang, P.R.China + Hangzhou Zhejiang 310052 + CN + +44-90-BA (hex) CHINA DRAGON TECHNOLOGY LIMITED +4490BA (base 16) CHINA DRAGON TECHNOLOGY LIMITED + B4 Building,No.3 First industrial Zone,Nanpu Road,Lao Community,Xinqian Street,Baoan District,Shenzhen,City + ShenZhen 518100 + CN + +44-4A-4C (hex) vivo Mobile Communication Co., Ltd. +444A4C (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN + +00-76-B6 (hex) Ford Motor Company +0076B6 (base 16) Ford Motor Company + 20300 Rotunda Drive + Dearborn MI 48124 + US + +EC-96-BF (hex) Kontron eSystems GmbH +EC96BF (base 16) Kontron eSystems GmbH + Bahnhofstr. 96 + Wendlingen 73240 + DE + +2C-B4-71 (hex) Tuya Smart Inc. +2CB471 (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 + US + 6C-87-20 (hex) New H3C Technologies Co., Ltd 6C8720 (base 16) New H3C Technologies Co., Ltd 466 Changhe Road, Binjiang District @@ -139874,6 +139994,18 @@ E40A75 (base 16) Silicon Laboratories Suzhou 215000 CN +EC-6F-F9 (hex) Pioseed Technology(Chengdu)Co.,Ltd. +EC6FF9 (base 16) Pioseed Technology(Chengdu)Co.,Ltd. + Unit 1,Building 2,177 Tianquan Road,Chengdu High-tech Zone(self-numbered) + Chengdu Sichuan 610097 + CN + +B0-7A-A4 (hex) Guangzhou Punp Electronics Manufacturing Co., Ltd. +B07AA4 (base 16) Guangzhou Punp Electronics Manufacturing Co., Ltd. + No. 20 Qianfeng South Road, Panyu District + Guangzhou Guangdong 511450 + CN + 18-83-BF (hex) Arcadyan Corporation 1883BF (base 16) Arcadyan Corporation 4F, No. 9, Park Avenue II , @@ -139910,11 +140042,35 @@ E40A75 (base 16) Silicon Laboratories Hsinchu 300 TW -EC-6F-F9 (hex) Pioseed Technology(Chengdu)Co.,Ltd. -EC6FF9 (base 16) Pioseed Technology(Chengdu)Co.,Ltd. - Unit 1,Building 2,177 Tianquan Road,Chengdu High-tech Zone(self-numbered) - Chengdu Sichuan 610097 - CN +F8-E0-00 (hex) FUJI ELECTRIC CO., LTD. +F8E000 (base 16) FUJI ELECTRIC CO., LTD. + 1-27, Fuji-cho + Yokkaichi 510-0013 + JP + +50-61-88 (hex) PLANET Technology Corporation +506188 (base 16) PLANET Technology Corporation + 11F., No. 96, Minquan Road, Xindian Dist., + New Taipei City TAIWAN 23141 + TW + +D8-5C-11 (hex) Optiview USA +D85C11 (base 16) Optiview USA + 5211 Fairmont Street + Jacksonville FL 32207 + US + +18-DC-12 (hex) Silicon Laboratories +18DC12 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + +D0-82-EB (hex) Tuya Smart Inc. +D082EB (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 + US B0-0C-9D (hex) Quectel Wireless Solutions Co.,Ltd. B00C9D (base 16) Quectel Wireless Solutions Co.,Ltd. @@ -187091,18 +187247,18 @@ EC72F7 (base 16) DJI BAIWANG TECHNOLOGY CO LTD Shenzhen Guangdong 518057 CN +38-6D-ED (hex) Juniper Networks +386DED (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US + B8-D5-AD (hex) Nokia B8D5AD (base 16) Nokia 600 March Road Kanata Ontario K2K 2E6 CA -88-03-55 (hex) Arcadyan Corporation -880355 (base 16) Arcadyan Corporation - 4F., No.9 , Park Avenue II - Hsinchu 300 - TW - 00-23-08 (hex) Arcadyan Corporation 002308 (base 16) Arcadyan Corporation 4F, No. 9, Park Avenue II , @@ -187115,12 +187271,66 @@ B8D5AD (base 16) Nokia Hsinchu 300 TW +30-08-4D (hex) Trumpf Hüttinger +30084D (base 16) Trumpf Hüttinger + Bötzingerstraße 80 + Freiburg 79111 + DE + +88-03-55 (hex) Arcadyan Corporation +880355 (base 16) Arcadyan Corporation + 4F., No.9 , Park Avenue II + Hsinchu 300 + TW + 4C-09-D4 (hex) Arcadyan Corporation 4C09D4 (base 16) Arcadyan Corporation 4F, No. 9, Park Avenue II , Hsinchu 300 TW +94-54-A0 (hex) Fosilicon CO., Ltd +9454A0 (base 16) Fosilicon CO., Ltd + Room 502A, Building A, Phoenix Wisdom Valley, No. 50, Tiezi Road, Xixiang, Bao'an, Shenzhen + Shenzhen Guangdong 518102 + CN + +E0-83-0D (hex) NOTTA PTE. LTD. +E0830D (base 16) NOTTA PTE. LTD. + 9 RAFFLES PLACE #26-01 REPUBLIC PLAZA + SINGAPORE 048619 + SG + +2C-AB-EE (hex) EM Microelectronic +2CABEE (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH + +28-87-5F (hex) Annapurna labs +28875F (base 16) Annapurna labs + Matam Scientific Industries Center, Building 8.2 + Mail box 15123 Haifa 3508409 + IL + +10-3A-5D (hex) Emerson +103A5D (base 16) Emerson + 6021 Innovation Blvd + Shakopee MN 55379 + US + +30-1C-22 (hex) Hewlett Packard Enterprise +301C22 (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US + +FC-CA-10 (hex) MERCUSYS TECHNOLOGIES CO., LTD. +FCCA10 (base 16) MERCUSYS TECHNOLOGIES CO., LTD. + 3F,Zone B,Building R1,High-Tech Industrial Village,No.023 High-Tech South 4 Road,Nanshan,Shenzhen + Shenzhen Guangdong 518057 + CN + C8-5C-E2 (hex) IEEE Registration Authority C85CE2 (base 16) IEEE Registration Authority 445 Hoes Lane @@ -193628,12 +193838,6 @@ F885F9 (base 16) Calix Inc. Dongguan Guangdong 523808 CN -04-C2-9B (hex) Aura Home, Inc. -04C29B (base 16) Aura Home, Inc. - 50 Eldridge Street, Suite 5D - New York NY 10002 - US - 1C-87-E3 (hex) TECNO MOBILE LIMITED 1C87E3 (base 16) TECNO MOBILE LIMITED ROOMS 05-15, 13A/F., SOUTH TOWER, WORLD FINANCE CENTRE, HARBOUR CITY, 17 CANTON ROAD, TSIM SHA TSUI, KOWLOON, HONG KONG @@ -232247,12 +232451,6 @@ AC1C26 (base 16) Hangzhou Ezviz Software Co.,Ltd. Piscataway NJ 08554 US -68-1D-4C (hex) Kontron eSystems GmbH -681D4C (base 16) Kontron eSystems GmbH - Bahnhofstraße 100 - Wendlingen 73240 - DE - B8-52-13 (hex) zte corporation B85213 (base 16) zte corporation 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China @@ -233651,6 +233849,18 @@ A493FE (base 16) HUAWEI TECHNOLOGIES CO.,LTD Heidelberg Baden-Württemberg 69123 DE +64-CA-80 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. +64CA80 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. + No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. + Chongqing China 401120 + CN + +00-1D-19 (hex) Arcadyan Corporation +001D19 (base 16) Arcadyan Corporation + 4F., No. 9 , Park Avenue II, + Hsinchu 300 + TW + 44-B1-76 (hex) Espressif Inc. 44B176 (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area @@ -233663,11 +233873,11 @@ A493FE (base 16) HUAWEI TECHNOLOGIES CO.,LTD shenzhen guangdong 518057 CN -64-CA-80 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. -64CA80 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. - No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. - Chongqing China 401120 - CN +C0-6B-C7 (hex) Gallagher Group Limited +C06BC7 (base 16) Gallagher Group Limited + 181 Kahikatea Drive + Hamilton Waikato 3204 + NZ BC-C4-36 (hex) Nokia BCC436 (base 16) Nokia @@ -233681,12 +233891,6 @@ DCB87D (base 16) Hewlett Packard Enterprise San Jose CA 95002 US -C0-6B-C7 (hex) Gallagher Group Limited -C06BC7 (base 16) Gallagher Group Limited - 181 Kahikatea Drive - Hamilton Waikato 3204 - NZ - 24-7E-7F (hex) D-Fend Solutions A.D Ltd 247E7F (base 16) D-Fend Solutions A.D Ltd 13 Zarhin st @@ -233711,8 +233915,68 @@ C06BC7 (base 16) Gallagher Group Limited Hsinchu 300 TW -00-1D-19 (hex) Arcadyan Corporation -001D19 (base 16) Arcadyan Corporation - 4F., No. 9 , Park Avenue II, - Hsinchu 300 - TW +98-2B-A6 (hex) Motorola Mobility LLC, a Lenovo Company +982BA6 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US + +5C-82-17 (hex) DSE srl +5C8217 (base 16) DSE srl + Via La Valle 51 + San Mauro Torinese TO 10099 + IT + +AC-E6-06 (hex) Honor Device Co., Ltd. +ACE606 (base 16) Honor Device Co., Ltd. + Suite 3401, Unit A, Building 6, Shum Yip Sky Park, No. 8089, Hongli West Road, Xiangmihu Street, Futian District + Shenzhen Guangdong 518040 + CN + +CC-FA-95 (hex) Honor Device Co., Ltd. +CCFA95 (base 16) Honor Device Co., Ltd. + Suite 3401, Unit A, Building 6, Shum Yip Sky Park, No. 8089, Hongli West Road, Xiangmihu Street, Futian District + Shenzhen Guangdong 518040 + CN + +64-AC-E0 (hex) Samsung Electronics Co.,Ltd +64ACE0 (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 + KR + +CC-1E-AB (hex) LEDATEL sp. z o.o. i Wspólnicy sp.k +CC1EAB (base 16) LEDATEL sp. z o.o. i Wspólnicy sp.k + Terespolska 144 + Nowy Konik 9522033813 05-074 + PL + +7C-5C-8D (hex) EM Microelectronic +7C5C8D (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH + +68-1D-4C (hex) Kontron eSystems GmbH +681D4C (base 16) Kontron eSystems GmbH + Bahnhofstr. 96 + Wendlingen 73240 + DE + +04-C2-9B (hex) Aura Home, Inc. +04C29B (base 16) Aura Home, Inc. + 148 Lafayette Street, Floor 5 + New York NY 10013 + US + +B8-97-34 (hex) Silicon Laboratories +B89734 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + +30-A7-71 (hex) Jiang Su Fulian Communication Technology Co.,Ltd +30A771 (base 16) Jiang Su Fulian Communication Technology Co.,Ltd + Yongan Community, the south of Lanling Road, Danyang Development Distinct + zhenjiang jiangsu 212300 + CN diff --git a/hwdb.d/ma-medium.txt b/hwdb.d/ma-medium.txt index 9698465f90298..72b11878d4d5b 100644 --- a/hwdb.d/ma-medium.txt +++ b/hwdb.d/ma-medium.txt @@ -7655,6 +7655,12 @@ C00000-CFFFFF (base 16) Guangzhou Sunrise Technology Co., Ltd. Singapore 408564 SG +20-B3-7F (hex) EGSTON Power Electronics GmbH +C00000-CFFFFF (base 16) EGSTON Power Electronics GmbH + Grafenbergerstraße 37 + Eggenburg 3730 + AT + B8-4C-87 (hex) Shenzhen Link-all Technology Co., Ltd 300000-3FFFFF (base 16) Shenzhen Link-all Technology Co., Ltd Floor 5th, Block 9th, Sunny Industrial Zone, Xili Town, Nanshan District, Shenzhen, China @@ -14774,6 +14780,36 @@ B00000-BFFFFF (base 16) MyPlace Australia Pty Ltd Taipei City 114708 TW +20-B3-7F (hex) Kitchen Armor +600000-6FFFFF (base 16) Kitchen Armor + 17500 Cartwright Rd + Irvine CA 92614 + US + +20-B3-7F (hex) OTP CO.,LTD. +400000-4FFFFF (base 16) OTP CO.,LTD. + 817 the SOHO, 2-7-4, AOMI, KOTO-KU,TOKYO JAPAN + TOKYO TOKYO 135-0064 + JP + +20-B3-7F (hex) Shenzhen HantangFengyun Technology Co.,Ltd +500000-5FFFFF (base 16) Shenzhen HantangFengyun Technology Co.,Ltd + 741, HUAMEIJU Building 2., 82 of Haiyu Community., Xin'an Street, Bao'an District, Shenzhen + Shenzhen 518000 + CN + +20-B3-7F (hex) Annapurna labs +900000-9FFFFF (base 16) Annapurna labs + Matam Scientific Industries Center, Building 8.2 + Mail box 15123 Haifa 3508409 + IL + +20-B3-7F (hex) ShenZhen C&D Electronics CO.Ltd. +A00000-AFFFFF (base 16) ShenZhen C&D Electronics CO.Ltd. + 9th FIoor, Building 9, No.1 Qingxiang road, BaoNeng Science and TechnoIogy Industrial Park, Longhua New District + ShenZhen GuangDong 518000 + CN + B8-4C-87 (hex) Altronix , Corp A00000-AFFFFF (base 16) Altronix , Corp 140 58th St. Bldg A, Ste 2N @@ -22544,6 +22580,24 @@ B00000-BFFFFF (base 16) B810 SPA REGGIO EMILIA Reggio Emilia 42122 IT +20-B3-7F (hex) Luxedo +700000-7FFFFF (base 16) Luxedo + 1232 Topside Rd. + Louisville TN 37777 + US + +20-B3-7F (hex) Chelsio Communications Inc +000000-0FFFFF (base 16) Chelsio Communications Inc + 735, N Pastoria Av + SUNNYVALE CA 94085 + US + +20-B3-7F (hex) Xunmu Information Technology (Shanghai) Co., Ltd. +D00000-DFFFFF (base 16) Xunmu Information Technology (Shanghai) Co., Ltd. + 15F,New Bund Oriental Plaza 1,No.512,Haiyang West Road, Pudong New Area, Shanghai + Shanghai 200135 + CN + D0-14-11 (hex) P.B. Elettronica srl 100000-1FFFFF (base 16) P.B. Elettronica srl Via Santorelli, 8 @@ -26303,12 +26357,6 @@ B00000-BFFFFF (base 16) JNL Technologies Inc Ixonia WI 53036 US -C4-FF-BC (hex) iMageTech CO.,LTD. -400000-4FFFFF (base 16) iMageTech CO.,LTD. - 5F., No.16, Lane 15, Sec. 6, Mincyuan E. Rd., Neihu District, - TAIPEI 114 - TW - 9C-43-1E (hex) SuZhou Jinruiyang Information Technology CO.,LTD C00000-CFFFFF (base 16) SuZhou Jinruiyang Information Technology CO.,LTD NO.1003 Room A1 Buliding Tengfei Business Park in Suzhou Industrial Park. @@ -29954,12 +30002,30 @@ D00000-DFFFFF (base 16) Posital B.V. 38-B1-4E (hex) Private D00000-DFFFFF (base 16) Private +C4-FF-BC (hex) HyperNet CO., LTD +400000-4FFFFF (base 16) HyperNet CO., LTD + 5F., No.16, Lane 15, Sec. 6, Mincyuan E. Rd., Neihu District, + TAIPEI 114 + TW + +20-B3-7F (hex) QT medical inc +300000-3FFFFF (base 16) QT medical inc + 1370 Valley Vista Dr Ste 266 + Diamond Bar CA 91765 + US + 38-B1-4E (hex) Knit Sound Company E00000-EFFFFF (base 16) Knit Sound Company 496 Ada Dr SE Ste 201 Ada MI 49301 US +20-B3-7F (hex) Xconnect LLP +800000-8FFFFF (base 16) Xconnect LLP + Kurmangazy st 77 + Almaty Almaty 050022 + KZ + C8-5C-E2 (hex) Fela Management AG 000000-0FFFFF (base 16) Fela Management AG Basadingerstrasse 18 @@ -31193,12 +31259,6 @@ E00000-EFFFFF (base 16) Suzhou Sidi Information Technology Co., Ltd. Suzhou 215000 CN -F4-A4-54 (hex) TRI WORKS -200000-2FFFFF (base 16) TRI WORKS - #402 Goto building 4F 2-2-2 Daimyo Chuo-ku - Fukuoka-shi 810-0041 - JP - F4-A4-54 (hex) Chongqing Hengxun Liansheng Industrial Co.,Ltd 300000-3FFFFF (base 16) Chongqing Hengxun Liansheng Industrial Co.,Ltd Shop 42, Area C, Chongqing Yixiang City, No. 12 Jiangnan Avenue, Nan'an District @@ -37558,3 +37618,15 @@ A00000-AFFFFF (base 16) Amissiontech Co., Ltd 16192 Coastal Highway Lewes DE 19958 US + +F4-A4-54 (hex) TRI WORKS +200000-2FFFFF (base 16) TRI WORKS + Kiyokawa place 3F 1-14-18 kiyokawa Chuo-ku + Fukuoka-shi Fukuoka 810-0041 + JP + +20-B3-7F (hex) TDK-Lambda UK +100000-1FFFFF (base 16) TDK-Lambda UK + Kingsley Avenue + Ilfracombe Devon EX348ES + GB diff --git a/hwdb.d/ma-small.txt b/hwdb.d/ma-small.txt index e6bc2f4e29501..324f86bb3e100 100644 --- a/hwdb.d/ma-small.txt +++ b/hwdb.d/ma-small.txt @@ -8249,6 +8249,12 @@ AC6000-AC6FFF (base 16) Starts Facility Service Co.,Ltd Chuo-ku Tokyo 103-0027 JP +8C-1F-64 (hex) ARKTRON ELECTRONICS +D6F000-D6FFFF (base 16) ARKTRON ELECTRONICS + PLOT NO-605,SECTOR-58 + FARIDABAD HARYANA 121004 + IN + 8C-1F-64 (hex) Jacobs Technology, Inc. A98000-A98FFF (base 16) Jacobs Technology, Inc. 7765 Old Telegraph Road @@ -16610,6 +16616,30 @@ BCF000-BCFFFF (base 16) Erba Lachema s.r.o. Brno 62100 CZ +8C-1F-64 (hex) MITSUBISHI ELECTRIC INDIA PVT. LTD. +FF2000-FF2FFF (base 16) MITSUBISHI ELECTRIC INDIA PVT. LTD. + Plot No B-3, Talegaon Industrial Area,Phase-II, Badhalwadi MIDC, Talegoan,, + Pune Maharashtra 410507 + IN + +8C-1F-64 (hex) BCMTECH +17F000-17FFFF (base 16) BCMTECH + A-1605Ho,Anyang-dong 1432,Manan-gu + Anyang-si Gyeonggi-do 14084 + KR + +8C-1F-64 (hex) PERSOL EXCEL HR PARTNERS CO., LTD. +46B000-46BFFF (base 16) PERSOL EXCEL HR PARTNERS CO., LTD. + 1-6-1-B1 Awaza, Nishi-ku + Osaka City Osaka Prefecture 550-0011 + JP + +8C-1F-64 (hex) Owl Home Inc. +51E000-51EFFF (base 16) Owl Home Inc. + SE #82363, 1-1100 Courtneypark Dr E + Mississauga Ontario L5T1L7 + CA + 8C-1F-64 (hex) Vision Systems Safety Tech E6F000-E6FFFF (base 16) Vision Systems Safety Tech 5 Chemin de Chiradie @@ -24980,6 +25010,18 @@ D67000-D67FFF (base 16) Groundtruth Ltd Paekakariki 5034 NZ +8C-1F-64 (hex) CRUXELL Corp. +3E1000-3E1FFF (base 16) CRUXELL Corp. + A-405 Migun techno world II,187 techno 2-ro, Yusong-gu + Daejeon Daejeon 34025 + KR + +8C-1F-64 (hex) RFT Corp. +0A9000-0A9FFF (base 16) RFT Corp. + 516 Kamikocho, Omiya-ku + Saitama-shi Saitama 330-0855 + JP + 8C-1F-64 (hex) Flow Power 82B000-82BFFF (base 16) Flow Power Suite 2, Level 3, 18 - 20 York St @@ -33227,6 +33269,30 @@ A78000-A78FFF (base 16) TAIT Global LLC Durham OR 97224 US +8C-1F-64 (hex) Fischer & Connectors SA +0A3000-0A3FFF (base 16) Fischer & Connectors SA + Chemin du Glapin 20 + Saint-Prex CH-1162 + CH + +8C-1F-64 (hex) Schildknecht AG +F16000-F16FFF (base 16) Schildknecht AG + Haugweg 26 + Murr 71711 + DE + +8C-1F-64 (hex) Teledyne Scientific and Imaging +590000-590FFF (base 16) Teledyne Scientific and Imaging + 1049 Camino Dos Rios + Thousand Oaks CA 91360-2362 + US + +8C-1F-64 (hex) CMI, Inc. +5A2000-5A2FFF (base 16) CMI, Inc. + 316 East 9th Street + Owensboro KY 42303 + US + 8C-1F-64 (hex) Mobileye D63000-D63FFF (base 16) Mobileye 13 Hartom st. @@ -41416,3 +41482,9 @@ D2C000-D2CFFF (base 16) DEUTA Werke GmbH 5490 Great America Pkwy Santa Clara CA 95054 US + +8C-1F-64 (hex) PROVENRUN +ACF000-ACFFFF (base 16) PROVENRUN + 77 avenue Niel + PARIS 75017 + FR diff --git a/hwdb.d/pci.ids b/hwdb.d/pci.ids index 9beedbecb1e8a..5dbb806e2e481 100644 --- a/hwdb.d/pci.ids +++ b/hwdb.d/pci.ids @@ -1,8 +1,8 @@ # # List of PCI IDs # -# Version: 2026.03.10 -# Date: 2026-03-10 03:15:01 +# Version: 2026.03.16 +# Date: 2026-03-16 03:15:01 # # Maintained by Albert Pool, Martin Mares, and other volunteers from # the PCI ID Project at https://pci-ids.ucw.cz/. @@ -4149,6 +4149,7 @@ 74bd Aqua Vanjaram [Instinct MI300X HF] 7550 Navi 48 [Radeon RX 9070/9070 XT/9070 GRE] 148c 2435 Radeon RX 9070 XT 16GB + 1849 5403 Navi 48 XTX [Steel Legend Radeon RX 9070 XT] 1da2 e490 Navi 48 XTX [Sapphire Pulse Radeon RX 9070 XT] 7551 Navi 48 [Radeon AI PRO R9700] 7590 Navi 44 [Radeon RX 9060 XT] @@ -21602,6 +21603,8 @@ 14e4 9340 BCM57608 4x100G OCP Ethernet NIC 14e4 9345 BCM57608 4x25G OCP Ethernet NIC 14e4 d125 BCM57608 2x200G PCIe Ethernet NIC + 193d 105b NIC-ETH2030F-LP-2P 2x200G PCIe Ethernet NIC + 193d 105c NIC-ETH4030F-LP-1P 1x400G PCIe Ethernet NIC 1800 BCM57502 NetXtreme-E Ethernet Partition 1801 BCM57504 NetXtreme-E Ethernet Partition 1590 0420 Ethernet NPAR 6310C Adapter @@ -29443,6 +29446,10 @@ 10a1 NIC1160 Ethernet Controller Family 1ff2 0c11 10GE Ethernet Adapter 1160-2X 10a2 NIC1160 Ethernet Controller Virtual Function Family + 10b1 NIC 1260 Ethernet Controller Family + 10b2 NIC 1260 Ethernet Controller Virtual Function Family + 10b3 NIC 1260C Ethernet Controller Family + 10b4 NIC 1260C Ethernet Controller Virtual Function Family 20a1 IOC2110 Storage Controller 1ff2 0a11 2120-16i SATA3/SAS3 HBA Adapter 1ff2 0a12 2120-8i SATA3/SAS3 HBA Adapter @@ -29708,6 +29715,10 @@ 7101 LS X710-E 7103 LS X710-M 7104 LS X710-P + 7180 LS X718 + 7211 LS X721-E + 7223 LS X722-M + 7224 LS X722-P 20e3 Elix Systems SA 20e7 TOPSSD 20f4 TRENDnet From 166d91ad9110779ec97d2d30b4d24fd51fe1d51e Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 17 Mar 2026 17:28:50 +0000 Subject: [PATCH 0266/1296] NEWS: update contributors list --- NEWS | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index fcdefd2028864..cc914557abae5 100644 --- a/NEWS +++ b/NEWS @@ -495,7 +495,8 @@ CHANGES WITH 260 in spe: Betacentury, Bouke van der Bijl, Carlos Peón Costa, Carolina Jubran, Cathy Hu, Chris Down, Chris Lindee, Christian Brauner, Christian Glombek, Christian Hesse, - Christopher Cooper, Christopher Head, Cyrus Xi, Daan De Meyer, + Christopher Cooper, Christopher Head, + Copilot Autofix powered by AI, Cyrus Xi, Daan De Meyer, Dan McGregor, Daniel Foster, Daniel Nylander, Daniel Rusek, David Santamaría Rogado, David Tardon, Derek J. Clark, Dirk Su, Dmitry V. Levin, Dmytro Bagrii, Dylan M. Taylor, @@ -521,10 +522,10 @@ CHANGES WITH 260 in spe: Usama Arif, Val Markovic, Vitaly Kuznetsov, Vunny Sodhi, Weixie Cui, Yaping Li, Yaron Shahrabani, Yu Watanabe, Yuri Chornoivan, ZauberNerd, Zbigniew Jędrzejewski-Szmek, - Zongyuan He, andre4ik3, calm329, cdown, cyclopentane, francescoza6, - gvenugo3, joo es, kiamvdd, lumingzh, naly zzwd, nikstur, novenary, - noxiouz, patrick, ppkramer-hub, r-vdp, safforddr, scarlet-storm, sd416, - seidlerv, smosia, tuhaowen, zefr0x + Zongyuan He, andre4ik3, calm329, cdown, cyclopentane, davidak, + dongshengyuan, francescoza6, gvenugo3, joo es, kiamvdd, lumingzh, + naly zzwd, nikstur, novenary, noxiouz, patrick, ppkramer-hub, r-vdp, + safforddr, scarlet-storm, sd416, seidlerv, smosia, tuhaowen, zefr0x — Edinburgh, 2026/03/13 From 39ba090035592feb45d768e7e3b8827d5b1f9cea Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 17 Mar 2026 17:41:20 +0000 Subject: [PATCH 0267/1296] NEWS: finalize for v260 --- NEWS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index cc914557abae5..8595a285b86f1 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,6 @@ systemd System and Service Manager -CHANGES WITH 260 in spe: +CHANGES WITH 260: Feature Removals and Incompatible Changes: @@ -527,7 +527,7 @@ CHANGES WITH 260 in spe: naly zzwd, nikstur, novenary, noxiouz, patrick, ppkramer-hub, r-vdp, safforddr, scarlet-storm, sd416, seidlerv, smosia, tuhaowen, zefr0x - — Edinburgh, 2026/03/13 + — Edinburgh, 2026/03/17 CHANGES WITH 259: From fb292e18d8c8ab400569fe8872fc2d1d24661814 Mon Sep 17 00:00:00 2001 From: Antonio Alvarez Feijoo Date: Tue, 17 Mar 2026 15:39:27 +0100 Subject: [PATCH 0268/1296] mkosi: fix typo in UKI profile title --- mkosi/mkosi.uki-profiles/profile1.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkosi/mkosi.uki-profiles/profile1.conf b/mkosi/mkosi.uki-profiles/profile1.conf index 3dc39d2534b4d..e0508a0664356 100644 --- a/mkosi/mkosi.uki-profiles/profile1.conf +++ b/mkosi/mkosi.uki-profiles/profile1.conf @@ -3,5 +3,5 @@ [UKIProfile] Profile= ID=profile1 - TITLE=Profile Two + TITLE=Profile One Cmdline=testprofile1=1 From b54d61e00b4493606c3c2d9ec5cdbb15f07018be Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 17 Mar 2026 19:46:31 +0100 Subject: [PATCH 0269/1296] ci: Fix allowed tools in claude-review Bash(gh:api *) wasn't actually working. Turns out the colon syntax is deprecated and unnecessary. Let's stop using it which also fixes the bug so that gh api calls are allowed now. --- .github/workflows/claude-review.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 05c1e3eb5f710..6860380fd850c 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -195,10 +195,10 @@ jobs: --max-turns 100 --allowedTools " Read,LS,Grep,Glob,Task,TaskStop, - Bash(cat:*),Bash(test:*),Bash(printf:*),Bash(jq:*),Bash(head:*),Bash(tail:*), - Bash(git:*),Bash(grep:*),Bash(find:*),Bash(ls:*),Bash(wc:*), - Bash(gh:api *), - Bash(diff:*),Bash(sed:*),Bash(awk:*),Bash(sort:*),Bash(uniq:*), + Bash(cat *),Bash(test *),Bash(printf *),Bash(jq *),Bash(head *),Bash(tail *), + Bash(git *),Bash(grep *),Bash(find *),Bash(ls *),Bash(wc *), + Bash(gh api *), + Bash(diff *),Bash(sed *),Bash(awk *),Bash(sort *),Bash(uniq *), " --json-schema '${{ env.REVIEW_SCHEMA }}' prompt: | From 6089075265765b43e6666e4d5978292a32501496 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 17 Mar 2026 19:47:35 +0100 Subject: [PATCH 0270/1296] ci: Allow attaching claude-review label to PRs for automatic review - If a pr is labeled with claude-review, review it immediately - If a pr labeled with claude-review is updated, review it regardless of the author - If a pr is opened by a maintainer, review it and add the claude-review label. If the claude-review label is later removed, the pr won't be auto-reviewed anymore. --- .github/workflows/claude-review.yml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 6860380fd850c..736b459b0740d 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -11,7 +11,7 @@ name: Claude Review on: pull_request_target: - types: [opened, synchronize, reopened] + types: [opened, synchronize, reopened, labeled] # Strangely enough you have to use issue_comment to react to regular comments on PRs. # See https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#pull_request_comment-use-issue_comment. issue_comment: @@ -33,8 +33,11 @@ jobs: if: | github.repository_owner == 'systemd' && ((github.event_name == 'pull_request_target' && - contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.pull_request.author_association) && - github.event.pull_request.user.login != 'YHNdnzj') || + (github.event.action == 'labeled' && github.event.label.name == 'claude-review' && github.event.sender.login != 'github-actions[bot]' || + github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'claude-review') || + github.event.action == 'opened' && + contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.pull_request.author_association) && + github.event.pull_request.user.login != 'YHNdnzj')) || (github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '@claude review') && @@ -56,6 +59,12 @@ jobs: comment_id: ${{ steps.tracking.outputs.comment_id }} steps: + - name: Auto-add claude-review label for trusted contributors + if: github.event_name == 'pull_request_target' && github.event.action == 'opened' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh pr edit --repo "${{ github.repository }}" "$PR_NUMBER" --add-label claude-review + - name: Resolve PR metadata id: pr env: From d97896b647690d5e73686ed3f26b739bd27d62cb Mon Sep 17 00:00:00 2001 From: huchangzai Date: Tue, 17 Mar 2026 10:01:42 +0800 Subject: [PATCH 0271/1296] hwdb: fix ABS_PRESSURE axis range for Goodix GXTP5100 touchpad The Goodix GXTP5100 touchpad (HID bus 0x0018, vendor 0x27C6, product 0x01E9), found in the Lenovo ThinkBook 16 G7+ IAH and ThinkPad X9 15 Gen 1, has a kernel driver bug where ABS_PRESSURE (axis 24 / 0x18) is reported with min=0, max=0. This invalid axis range causes libinput to reject the device with: "kernel bug: ABS_PRESSURE has min == max (both 0)" The touchpad hardware itself is functional and reports valid ranges for all other axes: ABS_X: min=0, max=4149, resolution=31 ABS_Y: min=0, max=2147, resolution=27 ABS_MT_POSITION_X/Y: valid ranges Root cause: the kernel hid-multitouch driver applies a "GT7868Q report descriptor fixup" to this device (the HID descriptor is malformed and fails hid-generic probe with EINVAL). The fixup corrects most axes but leaves ABS_PRESSURE with an invalid 0:0 range. This hwdb entry overrides ABS_PRESSURE to a valid 0:255 range, allowing libinput to accept and initialize the device. Kernel version: 6.17.0-19-generic Device path: /sys/bus/hid/drivers/hid-multitouch/0018:27C6:01E9.0001 --- hwdb.d/60-evdev.hwdb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/hwdb.d/60-evdev.hwdb b/hwdb.d/60-evdev.hwdb index 92b43fe1b29d1..767b63f83571d 100644 --- a/hwdb.d/60-evdev.hwdb +++ b/hwdb.d/60-evdev.hwdb @@ -421,6 +421,17 @@ evdev:name:Atmel maXTouch Touch*:dmi:bvn*:bvr*:bd*:svnGOOGLE:pnSamus:* EVDEV_ABS_35=::10 EVDEV_ABS_36=::10 +######################################### +# Goodix +######################################### + +# Goodix GXTP5100 Forcepad (Lenovo ThinkBook 16 G7+ IAH, ThinkPad X9 15 Gen 1) +# The kernel hid-multitouch driver reports ABS_PRESSURE with min==max==0, +# an invalid range that causes libinput to reject the device entirely. +# Override ABS_PRESSURE (axis 0x18=24) to a valid range. +evdev:input:b0018v27C6p01E9* + EVDEV_ABS_18=0:255:0:0 + ######################################### # Granite Devices Simucube wheel bases ######################################### From 3e52279684a53ebf84d1d73d528bf400c5fb3497 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 17 Mar 2026 19:36:34 +0000 Subject: [PATCH 0272/1296] Finalize meson.version for v260 --- meson.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.version b/meson.version index 24463c2890d71..98da127e3c80b 100644 --- a/meson.version +++ b/meson.version @@ -1 +1 @@ -260~rc4 +260 From 0c71d20265200e09e5b4969c6ea3baf2958a2aec Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 17 Mar 2026 20:02:38 +0000 Subject: [PATCH 0273/1296] meson: switch version to 261~devel --- meson.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.version b/meson.version index 98da127e3c80b..ca05aea76d08c 100644 --- a/meson.version +++ b/meson.version @@ -1 +1 @@ -260 +261~devel From fb513a7e1c5aa5f1ac7a274a0ebf9a6ed7fc02d1 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 17 Mar 2026 22:14:15 +0100 Subject: [PATCH 0274/1296] ci: Fetch context for claude-review job in setup job Rather than have claude fetch the context itself, let's fetch the context for it in the setup job. This has the following advantages: - We can reduce the permissions granted to the claude job - claude has less opportunity to mess up trying to fetch the context itself. Specifically, it keeps spawsning a background task to fetch the PR branch which messes up the structured output at the end, causing the review job to fail. By pre-fetching the context it won't have to spawn the background task. Additionally, we limit the git commands it can execute to local ones to ensure it doesn't try to fetch the PR branch. Finally, we fetch the branch ourselves as pr-review so claude can look at it to review the PR. --- .github/workflows/claude-review.yml | 176 +++++++++++++--------------- 1 file changed, 80 insertions(+), 96 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 736b459b0740d..afcae1c9e9777 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -3,7 +3,7 @@ # via AWS Bedrock using OIDC — no long-lived API keys required. # # Architecture: The workflow is split into three jobs for least-privilege: -# 1. "setup" — posts/updates a "reviewing…" tracking comment (write permissions) +# 1. "setup" — fetches PR context, posts/updates tracking comment (write permissions) # 2. "review" — runs Claude with read-only permissions, produces structured JSON # 3. "post" — reads the JSON and posts comments to the PR (write permissions) @@ -54,9 +54,9 @@ jobs: pull-requests: write outputs: - pr_number: ${{ steps.pr.outputs.number }} - head_sha: ${{ steps.pr.outputs.head_sha }} - comment_id: ${{ steps.tracking.outputs.comment_id }} + pr_number: ${{ steps.context.outputs.pr_number }} + comment_id: ${{ steps.context.outputs.comment_id }} + pr_context: ${{ steps.context.outputs.pr_context }} steps: - name: Auto-add claude-review label for trusted contributors @@ -65,17 +65,8 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: gh pr edit --repo "${{ github.repository }}" "$PR_NUMBER" --add-label claude-review - - name: Resolve PR metadata - id: pr - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - echo "number=$PR_NUMBER" >> "$GITHUB_OUTPUT" - gh pr view --repo "${{ github.repository }}" "$PR_NUMBER" --json headRefOid --jq '.headRefOid' | \ - xargs -I{} echo "head_sha={}" >> "$GITHUB_OUTPUT" - - - name: Create or update tracking comment - id: tracking + - name: Fetch PR context and create tracking comment + id: context uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd with: script: | @@ -85,14 +76,19 @@ jobs: const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`; const MARKER = ""; - const issueComments = await github.paginate( - github.rest.issues.listComments, - { owner, repo, issue_number: prNumber, per_page: 100 }, - ); + /* Fetch all PR data in parallel. */ + const [pr, reviews, issueComments, reviewComments] = await Promise.all([ + github.rest.pulls.get({ owner, repo, pull_number: prNumber }), + github.paginate(github.rest.pulls.listReviews, { owner, repo, pull_number: prNumber, per_page: 100 }), + github.paginate(github.rest.issues.listComments, { owner, repo, issue_number: prNumber, per_page: 100 }), + github.paginate(github.rest.pulls.listReviewComments, { owner, repo, pull_number: prNumber, per_page: 100 }), + ]); + /* Find or create tracking comment. */ const existing = issueComments.find((c) => c.body && c.body.includes(MARKER)); - let commentId; + let trackingCommentBody = null; + if (existing) { console.log(`Updating existing tracking comment ${existing.id}.`); /* Prepend a re-reviewing banner but keep the previous review visible. */ @@ -104,6 +100,7 @@ jobs: body: `> **Claude is re-reviewing this PR…** ([workflow run](${runUrl}))\n\n${prevBody}`, }); commentId = existing.id; + trackingCommentBody = prevBody; } else { console.log("Creating new tracking comment."); const {data: created} = await github.rest.issues.createComment({ @@ -115,7 +112,17 @@ jobs: commentId = created.id; } + /* Build context JSON for Claude. */ + const prContext = { + pr: pr.data, + reviews, + tracking_comment: trackingCommentBody, + review_comments: reviewComments, + }; + + core.setOutput("pr_number", prNumber); core.setOutput("comment_id", commentId); + core.setOutput("pr_context", JSON.stringify(prContext)); review: runs-on: ubuntu-latest @@ -123,8 +130,6 @@ jobs: permissions: contents: read - pull-requests: read # Fetch PR comments and reviews - issues: read # Fetch issue comments id-token: write # Authenticate with AWS via OIDC outputs: @@ -132,6 +137,19 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + # Need full history so git diff ~1.. works for all PR commits. + fetch-depth: 0 + + - name: Fetch PR branch + env: + PR_NUMBER: ${{ needs.setup.outputs.pr_number }} + run: git fetch origin "pull/${PR_NUMBER}/head:pr-review" + + - name: Write PR context + env: + PR_CONTEXT: ${{ needs.setup.outputs.pr_context }} + run: printenv PR_CONTEXT > pr-context.json - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 @@ -193,9 +211,8 @@ jobs: } with: use_bedrock: "true" - # We still have to pass GITHUB_TOKEN here because claude-code-action - # requires it, but we restrict Claude's tools to read-only operations - # so it cannot post comments or modify the PR. + # Required by claude-code-action even though Claude itself doesn't + # call the GitHub API — the action uses it for permission checks. github_token: ${{ secrets.GITHUB_TOKEN }} track_progress: false show_full_output: "true" @@ -205,87 +222,59 @@ jobs: --allowedTools " Read,LS,Grep,Glob,Task,TaskStop, Bash(cat *),Bash(test *),Bash(printf *),Bash(jq *),Bash(head *),Bash(tail *), - Bash(git *),Bash(grep *),Bash(find *),Bash(ls *),Bash(wc *), - Bash(gh api *), + Bash(git log *),Bash(git diff *),Bash(git show *),Bash(git rev-parse *), + Bash(git merge-base *),Bash(git blame *),Bash(git branch *),Bash(git status *), + Bash(grep *),Bash(find *),Bash(ls *),Bash(wc *), Bash(diff *),Bash(sed *),Bash(awk *),Bash(sort *),Bash(uniq *), " --json-schema '${{ env.REVIEW_SCHEMA }}' prompt: | REPO: ${{ github.repository }} PR NUMBER: ${{ needs.setup.outputs.pr_number }} - HEAD SHA: ${{ needs.setup.outputs.head_sha }} You are a code reviewer for the ${{ github.repository }} project. Review this pull request and produce a structured JSON result containing your review. Do NOT attempt to post comments yourself — just return the JSON. You are in the upstream repo - without the patch applied. Do not apply it. - - ## Phase 1: Gather context - - Use `gh api` to fetch all PR data. The base path for all endpoints is - `repos/${{ github.repository }}/pulls/${{ needs.setup.outputs.pr_number }}`. - - **IMPORTANT: All data fetching in this phase MUST complete before moving to - Phase 2. Do NOT use `run_in_background` for any commands in this phase. Wait - for all results before proceeding.** - - Fetch the following in parallel: - - `gh api repos/${{ github.repository }}/pulls/${{ needs.setup.outputs.pr_number }}` - — PR title, body, and metadata - - `gh api repos/${{ github.repository }}/pulls/${{ needs.setup.outputs.pr_number }}/reviews --paginate` - — PR reviews - - `gh api repos/${{ github.repository }}/pulls/${{ needs.setup.outputs.pr_number }}/commits --paginate --jq '.[].sha'` - — list of commit SHAs - - `gh api repos/${{ github.repository }}/issues/${{ needs.setup.outputs.pr_number }}/comments --paginate` - — issue comments (look for the tracking comment containing ``; - if one exists, use it as the basis for your `summary` in Phase 3) - - `gh api repos/${{ github.repository }}/pulls/${{ needs.setup.outputs.pr_number }}/comments --paginate` - — inline review comments including each comment's numeric `id`, `path`, - `line`, `body`, `user.login`, and `in_reply_to_id`; you will need the - `id` fields in Phase 3 to populate the `resolve` array + with the PR branch available as `pr-review`. Do not apply or merge the patch. - ## Phase 2: Per-commit review with subagents + ## Phase 1: Read context - Review each commit in the PR individually, in order (oldest first). For each - commit, fetch its diff using `gh api repos/{owner}/{repo}/commits/{sha} - -H 'Accept: application/vnd.github.diff'` via Bash, then launch a subagent - to review that commit's changes. + All PR data has been pre-fetched. Read `pr-context.json` from the repo root. + It contains a JSON object with: + - `pr` — full GitHub PR object (title, body, user, head SHA, etc.) + - `reviews` — array of PR reviews from the GitHub API + - `tracking_comment` — body of the existing tracking comment (null on first run); + if present, use it as the basis for your `summary` in Phase 3 + - `review_comments` — array of inline review comments from the GitHub API; + you will need the `id` fields in Phase 3 to populate the `resolve` array - Each commit review subagent receives: - - The PR title and description (for overall context) - - The diffs of all preceding commits in the PR (for context on what was already changed) - - The commit message and SHA of the commit being reviewed - - The commit diff (from `gh api`) + The PR branch has been fetched locally as `pr-review`. Use + `git log --reverse --format=%H HEAD..pr-review` to list the PR commits, and + `git show ` or `git diff ~1..` to access commit diffs. - Each commit review subagent reviews: - - Code quality, style, and best practices - - Potential bugs, issues, incorrect logic - - Security implications - - CLAUDE.md compliance + ## Phase 2: Per-commit review with subagents - Each commit review subagent must return a JSON array of issues: + Launch a subagent for each commit in the PR, all in parallel. Each subagent + receives only the commit SHA to review. It reads `pr-context.json` for PR + context, uses `git show ` or `git diff ~1..` to fetch the + diff, and reads the codebase to verify its findings. + + Each subagent reviews code quality, style, potential bugs, and security + implications. It must return a JSON array: `[{"file": "path", "line": (optional), "severity": "must-fix|suggestion|nit", "body": "...", "commit": ""}]` - The `commit` field MUST be set to the SHA of the commit being reviewed. - Each commit review subagent MUST only return comments about changes in the commit it is - reviewing — do NOT comment on code from preceding commits, even if it was - provided for context. If a preceding commit has an issue, it will be caught - by the subagent reviewing that commit. - - `line` should be a line number from the NEW side of the diff **that appears inside - a diff hunk** (i.e. a line that is shown in the patch output). GitHub's review - comment API rejects lines outside the diff context, so never reference lines - that are not visible in the patch. If you cannot determine a valid diff line, - omit `line` — the comment will still appear in the tracking comment summary. - - Each commit review subagent MUST verify its findings before returning them: - - For style/convention claims, check at least 3 existing examples in the codebase to confirm - the pattern actually exists before flagging a violation. - - For "use X instead of Y" suggestions, confirm X actually exists and works for this case. - - If unsure, don't include the issue. + The `commit` field MUST be the SHA of the commit being reviewed. Only + comment on changes in that commit — not preceding commits. - Launch commit review subagents for all commits in parallel (e.g. if - reviewing a 7-commit PR, launch all 7 commit review subagents at once). + `line` should be a line number from the NEW side of the diff **that appears + inside a diff hunk**. GitHub rejects lines outside the diff context. If you + cannot determine a valid diff line, omit `line`. + + Each subagent MUST verify findings before returning them: + - For style/convention claims, check at least 3 existing examples in the + codebase to confirm the pattern actually exists before flagging a violation. + - For "use X instead of Y" suggestions, confirm X actually exists and works. + - If unsure, don't include the issue. ## Phase 3: Collect, deduplicate, and summarize @@ -346,12 +335,9 @@ jobs: ## Error tracking - Throughout all phases, track any errors that prevented you from doing - your job fully: permission denials (403, "Resource not accessible by - integration"), tools that were not available, rate limits, or any other - failures that degraded the review quality. If there were any, append a - `### Errors` section to the summary listing each failed tool/action and - the error message, so maintainers can fix the workflow configuration. + If any errors prevented you from doing your job fully (tools that were + not available, git commands that failed, etc.), append a `### Errors` + section to the summary listing each failed action and the error message. ## CRITICAL: Return structured JSON output @@ -386,14 +372,12 @@ jobs: STRUCTURED_OUTPUT: ${{ needs.review.outputs.structured_output }} REVIEW_RESULT: ${{ needs.review.result }} PR_NUMBER: ${{ needs.setup.outputs.pr_number }} - HEAD_SHA: ${{ needs.setup.outputs.head_sha }} COMMENT_ID: ${{ needs.setup.outputs.comment_id }} with: script: | const owner = context.repo.owner; const repo = context.repo.repo; const prNumber = parseInt(process.env.PR_NUMBER, 10); - const headSha = process.env.HEAD_SHA; const commentId = parseInt(process.env.COMMENT_ID, 10); const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`; const MARKER = ""; From f2210fda544fd958ca696deb8f5e7d374af2f492 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 17 Mar 2026 23:10:01 +0100 Subject: [PATCH 0275/1296] ci: Add issue comments to pr context for claude-review as well Follow up for fb513a7e1c5aa5f1ac7a274a0ebf9a6ed7fc02d1. The issue comments are the regular comments left on the pr. --- .github/workflows/claude-review.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index afcae1c9e9777..652dfd61a5b22 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -116,6 +116,7 @@ jobs: const prContext = { pr: pr.data, reviews, + issue_comments: issueComments, tracking_comment: trackingCommentBody, review_comments: reviewComments, }; @@ -243,6 +244,7 @@ jobs: It contains a JSON object with: - `pr` — full GitHub PR object (title, body, user, head SHA, etc.) - `reviews` — array of PR reviews from the GitHub API + - `issue_comments` — array of issue comments on the PR from the GitHub API - `tracking_comment` — body of the existing tracking comment (null on first run); if present, use it as the basis for your `summary` in Phase 3 - `review_comments` — array of inline review comments from the GitHub API; From b29f3bbfa8edfd47a25d1c887cdb6c91b230f4f4 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 18 Mar 2026 00:02:26 +0100 Subject: [PATCH 0276/1296] ci: Use artifacts to pass around pr context The current approach runs into issues on large prs: https://github.com/systemd/systemd/actions/runs/23220105199/job/67490722033 --- .github/workflows/claude-review.yml | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 652dfd61a5b22..344b3d83deaa8 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -56,7 +56,6 @@ jobs: outputs: pr_number: ${{ steps.context.outputs.pr_number }} comment_id: ${{ steps.context.outputs.comment_id }} - pr_context: ${{ steps.context.outputs.pr_context }} steps: - name: Auto-add claude-review label for trusted contributors @@ -123,7 +122,16 @@ jobs: core.setOutput("pr_number", prNumber); core.setOutput("comment_id", commentId); - core.setOutput("pr_context", JSON.stringify(prContext)); + + const fs = require("fs"); + fs.writeFileSync("pr-context.json", JSON.stringify(prContext)); + + - name: Upload PR context + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f + with: + name: pr-context + path: pr-context.json + archive: false review: runs-on: ubuntu-latest @@ -147,10 +155,10 @@ jobs: PR_NUMBER: ${{ needs.setup.outputs.pr_number }} run: git fetch origin "pull/${PR_NUMBER}/head:pr-review" - - name: Write PR context - env: - PR_CONTEXT: ${{ needs.setup.outputs.pr_context }} - run: printenv PR_CONTEXT > pr-context.json + - name: Download PR context + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + with: + name: pr-context - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 From 2cada660de5b9bd3614f3c412e1f1a6db7e9116f Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 18 Mar 2026 09:11:09 +0100 Subject: [PATCH 0277/1296] ci: Fix artifact name in claude-review workflow The name doesn't actually matter, it gets replaced with the name of the file when not archiving. So stop passing a name and pass in the filename as the name when downloading the artifact. --- .github/workflows/claude-review.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 344b3d83deaa8..cf55bf612a400 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -126,10 +126,11 @@ jobs: const fs = require("fs"); fs.writeFileSync("pr-context.json", JSON.stringify(prContext)); + # archive: false makes upload-artifact use the file's basename + # (pr-context.json) as the artifact name, ignoring the name input. - name: Upload PR context uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f with: - name: pr-context path: pr-context.json archive: false @@ -158,7 +159,7 @@ jobs: - name: Download PR context uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with: - name: pr-context + name: pr-context.json - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 From 2967e89597d51db77f4b73be338bc273b65be28b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 18 Mar 2026 08:59:48 +0100 Subject: [PATCH 0278/1296] ci: Enable users without write action to the repo to access claude review The labelling approach introduced in 6089075265765b43e6666e4d5978292a32501496 means contributors can now trigger the workflow on their own when the label is added by a maintainer and they update the PR. Hence we need to allow all users to access the claude code action. This is safe because we already gate the workflow ourselves to only the contributors that we want to allow. Additionally, the claude code job has no permissions anymore except read access to the repository and can execute very limited tools, so this should be safe. --- .github/workflows/claude-review.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index cf55bf612a400..6368140ea0041 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -224,6 +224,11 @@ jobs: # Required by claude-code-action even though Claude itself doesn't # call the GitHub API — the action uses it for permission checks. github_token: ${{ secrets.GITHUB_TOKEN }} + # Safe because the workflow's `if` condition already restricts + # execution to trusted actors (MEMBER/OWNER/COLLABORATOR) or PRs + # that a trusted actor explicitly labeled, and this job only has + # read-only permissions. + allowed_non_write_users: "*" track_progress: false show_full_output: "true" claude_args: | From e87303d511ef0ba0295e7a52778dce1b1fda71f2 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 18 Mar 2026 10:35:28 +0100 Subject: [PATCH 0279/1296] ci: Reduce retention for pr-context JSON file to a week We don't need to keep this around fpr 90 days, let's keep it around for a week. --- .github/workflows/claude-review.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 6368140ea0041..3b2444073a983 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -133,6 +133,7 @@ jobs: with: path: pr-context.json archive: false + retention-days: 7 review: runs-on: ubuntu-latest From 8a2c423870d8c8997471f61846853c0d54e109f3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 13 Mar 2026 11:53:57 +0100 Subject: [PATCH 0280/1296] find-esp: introduce _full() flavour of ESP/XBOOTLDR discovery functions These functions take so many return paramaters, and in many of our cases we don't actually needt them. Hence introduce _full() flavours of the funcs, and hide the params by default. --- src/bless-boot/bless-boot.c | 19 +++++++++++++++++-- src/bootctl/bootctl-install.c | 6 ++---- src/bootctl/bootctl-random-seed.c | 2 +- src/bootctl/bootctl.c | 4 ++-- src/kernel-install/kernel-install.c | 11 ++--------- src/shared/bootspec.c | 19 +++++++++++++++++-- src/shared/creds-util.c | 11 ++--------- src/shared/find-esp.c | 12 ++++++------ src/shared/find-esp.h | 22 ++++++++++++++++++---- src/sysupdate/sysupdate-resource.c | 4 ++-- 10 files changed, 69 insertions(+), 41 deletions(-) diff --git a/src/bless-boot/bless-boot.c b/src/bless-boot/bless-boot.c index ac989630aff0d..1df341cf59baf 100644 --- a/src/bless-boot/bless-boot.c +++ b/src/bless-boot/bless-boot.c @@ -107,11 +107,26 @@ static int acquire_path(void) { if (!strv_isempty(arg_path)) return 0; - r = find_esp_and_warn(NULL, NULL, /* unprivileged_mode= */ false, &esp_path, NULL, NULL, NULL, NULL, &esp_devid); + r = find_esp_and_warn_full( + /* root= */ NULL, + /* path= */ NULL, + /* unprivileged_mode= */ false, + &esp_path, + /* ret_part= */ NULL, + /* ret_pstart= */ NULL, + /* ret_psize= */ NULL, + /* ret_uuid= */ NULL, + &esp_devid); if (r < 0 && r != -ENOKEY) /* ENOKEY means not found, and is the only error the function won't log about on its own */ return r; - r = find_xbootldr_and_warn(NULL, NULL, /* unprivileged_mode= */ false, &xbootldr_path, NULL, &xbootldr_devid); + r = find_xbootldr_and_warn_full( + /* root= */ NULL, + /* path= */ NULL, + /* unprivileged_mode= */ false, + &xbootldr_path, + /* ret_uuid= */ NULL, + &xbootldr_devid); if (r < 0 && r != -ENOKEY) return r; diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index 1a8d5ffb30cb2..4d56c6874e9ca 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -2062,7 +2062,7 @@ int vl_method_install( if (p.context.entry_token_type < 0) p.context.entry_token_type = BOOT_ENTRY_TOKEN_AUTO; - r = find_esp_and_warn_at( + r = find_esp_and_warn_at_full( p.context.root_fd, /* path= */ NULL, /* unprivileged_mode= */ false, @@ -2081,9 +2081,7 @@ int vl_method_install( p.context.root_fd, /* path= */ NULL, /* unprivileged_mode= */ false, - &p.context.xbootldr_path, - /* ret_uuid= */ NULL, - /* ret_devid= */ NULL); + &p.context.xbootldr_path); if (r == -ENOKEY) log_debug_errno(r, "Didn't find an XBOOTLDR partition, using ESP as $BOOT."); else if (r < 0) diff --git a/src/bootctl/bootctl-random-seed.c b/src/bootctl/bootctl-random-seed.c index 62ff8fa07ffe2..6f5249aeeb4c4 100644 --- a/src/bootctl/bootctl-random-seed.c +++ b/src/bootctl/bootctl-random-seed.c @@ -204,7 +204,7 @@ int install_random_seed(const char *esp) { int verb_random_seed(int argc, char *argv[], void *userdata) { int r; - r = find_esp_and_warn(arg_root, arg_esp_path, false, &arg_esp_path, NULL, NULL, NULL, NULL, NULL); + r = find_esp_and_warn(arg_root, arg_esp_path, /* unprivileged_mode= */ false, &arg_esp_path); if (r == -ENOKEY) { /* find_esp_and_warn() doesn't warn about ENOKEY, so let's do that on our own */ if (arg_graceful() == ARG_GRACEFUL_NO) diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index da4592a7240a8..65a5da3358faf 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -117,7 +117,7 @@ int acquire_esp( * we simply eat up the error here, so that --list and --status work too, without noise about * this). */ - r = find_esp_and_warn(arg_root, arg_esp_path, unprivileged_mode, &np, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid); + r = find_esp_and_warn_full(arg_root, arg_esp_path, unprivileged_mode, &np, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid); if (r == -ENOKEY) { if (graceful) return log_full_errno(arg_quiet ? LOG_DEBUG : LOG_INFO, r, @@ -144,7 +144,7 @@ int acquire_xbootldr( char *np; int r; - r = find_xbootldr_and_warn(arg_root, arg_xbootldr_path, unprivileged_mode, &np, ret_uuid, ret_devid); + r = find_xbootldr_and_warn_full(arg_root, arg_xbootldr_path, unprivileged_mode, &np, ret_uuid, ret_devid); if (r == -ENOKEY || path_equal(np, arg_esp_path)) { log_debug("Didn't find an XBOOTLDR partition, using the ESP as $BOOT."); arg_xbootldr_path = mfree(arg_xbootldr_path); diff --git a/src/kernel-install/kernel-install.c b/src/kernel-install/kernel-install.c index a38dcaab8b556..9046e82e921a8 100644 --- a/src/kernel-install/kernel-install.c +++ b/src/kernel-install/kernel-install.c @@ -570,9 +570,7 @@ static int context_acquire_xbootldr(Context *c) { /* rfd= */ c->rfd, /* path= */ arg_xbootldr_path, /* unprivileged_mode= */ -1, - /* ret_path= */ &c->boot_root, - /* ret_uuid= */ NULL, - /* ret_devid= */ NULL); + /* ret_path= */ &c->boot_root); if (r == -ENOKEY) { log_debug_errno(r, "Couldn't find an XBOOTLDR partition."); return 0; @@ -596,12 +594,7 @@ static int context_acquire_esp(Context *c) { /* rfd= */ c->rfd, /* path= */ arg_esp_path, /* unprivileged_mode= */ -1, - /* ret_path= */ &c->boot_root, - /* ret_part= */ NULL, - /* ret_pstart= */ NULL, - /* ret_psize= */ NULL, - /* ret_uuid= */ NULL, - /* ret_devid= */ NULL); + /* ret_path= */ &c->boot_root); if (r == -ENOKEY) { log_debug_errno(r, "Couldn't find EFI system partition, ignoring."); return 0; diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index 5901729e8845d..a341b0729bd1e 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -1587,11 +1587,26 @@ int boot_config_load_auto( "Failed to determine whether /run/boot-loader-entries/ exists: %m"); } - r = find_esp_and_warn(NULL, override_esp_path, /* unprivileged_mode= */ false, &esp_where, NULL, NULL, NULL, NULL, &esp_devid); + r = find_esp_and_warn_full( + /* root= */ NULL, + override_esp_path, + /* unprivileged_mode= */ false, + &esp_where, + /* ret_part= */ NULL, + /* ret_pstart= */ NULL, + /* ret_psize= */ NULL, + /* ret_uuid= */ NULL, + &esp_devid); if (r < 0) /* we don't log about ENOKEY here, but propagate it, leaving it to the caller to log */ return r; - r = find_xbootldr_and_warn(NULL, override_xbootldr_path, /* unprivileged_mode= */ false, &xbootldr_where, NULL, &xbootldr_devid); + r = find_xbootldr_and_warn_full( + /* root= */ NULL, + override_xbootldr_path, + /* unprivileged_mode= */ false, + &xbootldr_where, + /* ret_uuid= */ NULL, + &xbootldr_devid); if (r < 0 && r != -ENOKEY) return r; /* It's fine if the XBOOTLDR partition doesn't exist, hence we ignore ENOKEY here */ diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index 2aac4d253bb76..54ae368fdfb09 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -1689,9 +1689,7 @@ int get_global_boot_credentials_path(char **ret) { /* root= */ NULL, /* path= */ NULL, /* unprivileged_mode= */ false, - &path, - /* ret_uuid= */ NULL, - /* ret_devid= */ NULL); + &path); if (r < 0) { if (r != -ENOKEY) return log_error_errno(r, "Failed to find XBOOTLDR partition: %m"); @@ -1700,12 +1698,7 @@ int get_global_boot_credentials_path(char **ret) { /* root= */ NULL, /* path= */ NULL, /* unprivileged_mode= */ false, - &path, - /* ret_part= */ NULL, - /* ret_pstart= */ NULL, - /* ret_psize= */ NULL, - /* ret_uuid= */ NULL, - /* ret_devid= */ NULL); + &path); if (r < 0) { if (r != -ENOKEY) return log_error_errno(r, "Failed to find ESP partition: %m"); diff --git a/src/shared/find-esp.c b/src/shared/find-esp.c index a2a2093fafb93..3f490ced714cf 100644 --- a/src/shared/find-esp.c +++ b/src/shared/find-esp.c @@ -429,7 +429,7 @@ static int verify_esp( return 0; } -int find_esp_and_warn_at( +int find_esp_and_warn_at_full( int rfd, const char *path, int unprivileged_mode, @@ -509,7 +509,7 @@ int find_esp_and_warn_at( return -ENOKEY; } -int find_esp_and_warn( +int find_esp_and_warn_full( const char *root, const char *path, int unprivileged_mode, @@ -536,7 +536,7 @@ int find_esp_and_warn( return -errno; } - r = find_esp_and_warn_at( + r = find_esp_and_warn_at_full( rfd, path, unprivileged_mode, @@ -792,7 +792,7 @@ static int verify_xbootldr( return 0; } -int find_xbootldr_and_warn_at( +int find_xbootldr_and_warn_at_full( int rfd, const char *path, int unprivileged_mode, @@ -853,7 +853,7 @@ int find_xbootldr_and_warn_at( return 0; } -int find_xbootldr_and_warn( +int find_xbootldr_and_warn_full( const char *root, const char *path, int unprivileged_mode, @@ -875,7 +875,7 @@ int find_xbootldr_and_warn( return -errno; } - r = find_xbootldr_and_warn_at( + r = find_xbootldr_and_warn_at_full( rfd, path, unprivileged_mode, diff --git a/src/shared/find-esp.h b/src/shared/find-esp.h index ac62e6c51e519..30b7c4a76117e 100644 --- a/src/shared/find-esp.h +++ b/src/shared/find-esp.h @@ -4,8 +4,22 @@ #include "shared-forward.h" -int find_esp_and_warn_at(int rfd, const char *path, int unprivileged_mode, char **ret_path, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid); -int find_esp_and_warn(const char *root, const char *path, int unprivileged_mode, char **ret_path, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid); +int find_esp_and_warn_at_full(int rfd, const char *path, int unprivileged_mode, char **ret_path, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid); +int find_esp_and_warn_full(const char *root, const char *path, int unprivileged_mode, char **ret_path, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid); -int find_xbootldr_and_warn_at(int rfd, const char *path, int unprivileged_mode, char **ret_path, sd_id128_t *ret_uuid, dev_t *ret_devid); -int find_xbootldr_and_warn(const char *root, const char *path, int unprivileged_mode, char **ret_path, sd_id128_t *ret_uuid, dev_t *ret_devid); +static inline int find_esp_and_warn_at(int rfd, const char *path, int unprivileged_mode, char **ret_path) { + return find_esp_and_warn_at_full(rfd, path, unprivileged_mode, ret_path, NULL, NULL, NULL, NULL, NULL); +} +static inline int find_esp_and_warn(const char *root, const char *path, int unprivileged_mode, char **ret_path) { + return find_esp_and_warn_full(root, path, unprivileged_mode, ret_path, NULL, NULL, NULL, NULL, NULL); +} + +int find_xbootldr_and_warn_at_full(int rfd, const char *path, int unprivileged_mode, char **ret_path, sd_id128_t *ret_uuid, dev_t *ret_devid); +int find_xbootldr_and_warn_full(const char *root, const char *path, int unprivileged_mode, char **ret_path, sd_id128_t *ret_uuid, dev_t *ret_devid); + +static inline int find_xbootldr_and_warn_at(int rfd, const char *path, int unprivileged_mode, char **ret_path) { + return find_xbootldr_and_warn_at_full(rfd, path, unprivileged_mode, ret_path, NULL, NULL); +} +static inline int find_xbootldr_and_warn(const char *root, const char *path, int unprivileged_mode, char **ret_path) { + return find_xbootldr_and_warn_full(root, path, unprivileged_mode, ret_path, NULL, NULL); +} diff --git a/src/sysupdate/sysupdate-resource.c b/src/sysupdate/sysupdate-resource.c index 3be0943e4c0a3..1cc48201efabe 100644 --- a/src/sysupdate/sysupdate-resource.c +++ b/src/sysupdate/sysupdate-resource.c @@ -852,9 +852,9 @@ int resource_resolve_path( } else { /* boot, esp, or xbootldr */ r = 0; if (IN_SET(rr->path_relative_to, PATH_RELATIVE_TO_BOOT, PATH_RELATIVE_TO_XBOOTLDR)) - r = find_xbootldr_and_warn(root, NULL, /* unprivileged_mode= */ -1, &relative_to, NULL, NULL); + r = find_xbootldr_and_warn(root, /* path= */ NULL, /* unprivileged_mode= */ -1, &relative_to); if (r == -ENOKEY || rr->path_relative_to == PATH_RELATIVE_TO_ESP) - r = find_esp_and_warn(root, NULL, -1, &relative_to, NULL, NULL, NULL, NULL, NULL); + r = find_esp_and_warn(root, /* path= */ NULL, /* unprivileged_mode= */ -1, &relative_to); if (r < 0) return log_error_errno(r, "Failed to resolve $BOOT: %m"); log_debug("Resolved $BOOT to '%s'", relative_to); From 4c7af7d9d1cd40e2f36a87787ce2fab32400bf89 Mon Sep 17 00:00:00 2001 From: noxiouz Date: Fri, 13 Mar 2026 00:36:08 +0000 Subject: [PATCH 0281/1296] coredump: capture crashing thread ID and name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add %I (TID in initial PID namespace) to the core_pattern, so the kernel passes the crashing thread's TID to systemd-coredump. Use it to read the thread's comm name from /proc//comm and log both as new journal fields: COREDUMP_TID= — TID of the crashing thread COREDUMP_THREAD_NAME= — comm name of the crashing thread These fields are also stored as xattrs on external coredump files (user.coredump.tid, user.coredump.thread_name) and displayed by coredumpctl info alongside the PID line. For single-threaded processes the TID equals the PID and thread_name equals comm; for multi-threaded programs with named worker threads (pthread_setname_np / PR_SET_NAME) this identifies which thread crashed without needing to open the coredump file itself. The new fields are optional in the socket forwarding path, so older systemd-coredump senders are handled gracefully. Co-developed-by: Claude --- src/coredump/coredump-context.c | 24 +++++++++++++++++++++ src/coredump/coredump-context.h | 4 ++++ src/coredump/coredump-submit.c | 4 ++++ src/coredump/coredumpctl.c | 12 ++++++++++- sysctl.d/50-coredump.conf.in | 2 +- test/units/TEST-87-AUX-UTILS-VM.coredump.sh | 5 +++++ 6 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/coredump/coredump-context.c b/src/coredump/coredump-context.c index 921cfe5de7650..574d3ecd52928 100644 --- a/src/coredump/coredump-context.c +++ b/src/coredump/coredump-context.c @@ -32,10 +32,12 @@ static const char * const metadata_field_table[_META_MAX] = { [META_ARGV_HOSTNAME] = "COREDUMP_HOSTNAME=", [META_ARGV_DUMPABLE] = "COREDUMP_DUMPABLE=", [META_ARGV_PIDFD] = "COREDUMP_BY_PIDFD=", + [META_ARGV_TID] = "COREDUMP_TID=", [META_COMM] = "COREDUMP_COMM=", [META_EXE] = "COREDUMP_EXE=", [META_UNIT] = "COREDUMP_UNIT=", [META_PROC_AUXV] = "COREDUMP_PROC_AUXV=", + [META_THREAD_NAME] = "COREDUMP_THREAD_NAME=", }; DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(metadata_field, MetadataField); @@ -49,6 +51,7 @@ void coredump_context_done(CoredumpContext *context) { free(context->exe); free(context->unit); free(context->auxv); + free(context->thread_name); safe_close(context->mount_tree_fd); iovw_done_free(&context->iovw); safe_close(context->input_fd); @@ -228,6 +231,12 @@ int coredump_context_build_iovw(CoredumpContext *context) { if (r < 0) return log_error_errno(r, "Failed to add COREDUMP_COMM= field: %m"); + if (context->tid > 0) + (void) iovw_put_string_fieldf(&context->iovw, "COREDUMP_TID=", PID_FMT, context->tid); + + if (context->thread_name) + (void) iovw_put_string_field(&context->iovw, "COREDUMP_THREAD_NAME=", context->thread_name); + if (context->exe) (void) iovw_put_string_field(&context->iovw, "COREDUMP_EXE=", context->exe); @@ -339,6 +348,12 @@ static int coredump_context_parse_from_procfs(CoredumpContext *context) { if (r < 0) return log_error_errno(r, "Failed to get COMM: %m"); + if (context->tid > 0) { + r = pid_get_comm(context->tid, &context->thread_name); + if (r < 0) + log_warning_errno(r, "Failed to get comm for thread "PID_FMT", ignoring: %m", context->tid); + } + r = get_process_exe(pid, &context->exe); if (r < 0) log_warning_errno(r, "Failed to get EXE, ignoring: %m"); @@ -465,6 +480,12 @@ static int context_parse_one(CoredumpContext *context, MetadataField meta, bool context->got_pidfd = 1; return 0; } + case META_ARGV_TID: + r = parse_pid(s, &context->tid); + if (r < 0) + log_warning_errno(r, "Failed to parse TID \"%s\", ignoring: %m", s); + return 0; + case META_COMM: return free_and_strdup_warn(&context->comm, s); @@ -474,6 +495,9 @@ static int context_parse_one(CoredumpContext *context, MetadataField meta, bool case META_UNIT: return free_and_strdup_warn(&context->unit, s); + case META_THREAD_NAME: + return free_and_strdup_warn(&context->thread_name, s); + case META_PROC_AUXV: { char *t = memdup_suffix0(s, size); if (!t) diff --git a/src/coredump/coredump-context.h b/src/coredump/coredump-context.h index f2d44c141c623..7fedfde2adbf7 100644 --- a/src/coredump/coredump-context.h +++ b/src/coredump/coredump-context.h @@ -23,6 +23,7 @@ typedef enum MetadataField { META_ARGV_HOSTNAME = _META_ARGV_REQUIRED, /* %h: hostname */ META_ARGV_DUMPABLE, /* %d: as set by the kernel */ META_ARGV_PIDFD, /* %F: pidfd of the process, since v6.16 */ + META_ARGV_TID, /* %I: TID of the crashing thread, as seen in the initial pid namespace */ /* If new fields are added, they should be added here, to maintain compatibility * with callers which don't know about the new fields. */ _META_ARGV_MAX, @@ -40,6 +41,7 @@ typedef enum MetadataField { META_EXE, META_UNIT, META_PROC_AUXV, + META_THREAD_NAME, _META_MAX, _META_INVALID = -EINVAL, } MetadataField; @@ -53,11 +55,13 @@ struct CoredumpContext { uint64_t rlimit; /* META_ARGV_RLIMIT */ char *hostname; /* META_ARGV_HOSTNAME */ unsigned dumpable; /* META_ARGV_DUMPABLE */ + pid_t tid; /* META_ARGV_TID */ char *comm; /* META_COMM */ char *exe; /* META_EXE */ char *unit; /* META_UNIT */ char *auxv; /* META_PROC_AUXV */ size_t auxv_size; /* META_PROC_AUXV */ + char *thread_name; /* META_THREAD_NAME */ bool got_pidfd; /* META_ARGV_PIDFD */ bool same_pidns; bool forwarded; diff --git a/src/coredump/coredump-submit.c b/src/coredump/coredump-submit.c index 9134697d5b8b7..6cfbda0f46b59 100644 --- a/src/coredump/coredump-submit.c +++ b/src/coredump/coredump-submit.c @@ -201,6 +201,10 @@ static int fix_xattr(int fd, const CoredumpContext *context) { RET_GATHER(r, fix_xattr_one(fd, "user.coredump.hostname", context->hostname)); RET_GATHER(r, fix_xattr_one(fd, "user.coredump.comm", context->comm)); RET_GATHER(r, fix_xattr_one(fd, "user.coredump.exe", context->exe)); + if (context->tid > 0) { + RET_GATHER(r, fix_xattr_format(fd, "user.coredump.tid", PID_FMT, context->tid)); + RET_GATHER(r, fix_xattr_one(fd, "user.coredump.thread_name", context->thread_name)); + } return r; } diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index 64f7ae99a10f0..618a464fcc9bf 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -633,7 +633,8 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) { *slice = NULL, *cgroup = NULL, *owner_uid = NULL, *message = NULL, *timestamp = NULL, *filename = NULL, *truncated = NULL, *coredump = NULL, - *pkgmeta_name = NULL, *pkgmeta_version = NULL, *pkgmeta_json = NULL; + *pkgmeta_name = NULL, *pkgmeta_version = NULL, *pkgmeta_json = NULL, + *tid = NULL, *thread_name = NULL; const void *d; size_t l; bool normal_coredump; @@ -667,6 +668,8 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) { RETRIEVE(d, l, "COREDUMP_PACKAGE_NAME", pkgmeta_name); RETRIEVE(d, l, "COREDUMP_PACKAGE_VERSION", pkgmeta_version); RETRIEVE(d, l, "COREDUMP_PACKAGE_JSON", pkgmeta_json); + RETRIEVE(d, l, "COREDUMP_TID", tid); + RETRIEVE(d, l, "COREDUMP_THREAD_NAME", thread_name); RETRIEVE(d, l, "_BOOT_ID", boot_id); RETRIEVE(d, l, "_MACHINE_ID", machine_id); RETRIEVE(d, l, "MESSAGE", message); @@ -686,6 +689,13 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) { " PID: %s%s%s\n", ansi_highlight(), strna(pid), ansi_normal()); + if (tid) { + if (thread_name) + fprintf(file, " TID: %s (%s)\n", tid, thread_name); + else + fprintf(file, " TID: %s\n", tid); + } + if (uid) { uid_t n; diff --git a/sysctl.d/50-coredump.conf.in b/sysctl.d/50-coredump.conf.in index fe8f7670b0637..0e6f370f47381 100644 --- a/sysctl.d/50-coredump.conf.in +++ b/sysctl.d/50-coredump.conf.in @@ -13,7 +13,7 @@ # the core dump. # # See systemd-coredump(8) and core(5). -kernel.core_pattern=|{{LIBEXECDIR}}/systemd-coredump %P %u %g %s %t %c %h %d %F +kernel.core_pattern=|{{LIBEXECDIR}}/systemd-coredump %P %u %g %s %t %c %h %d %F %I # Allow 16 coredumps to be dispatched in parallel by the kernel. # We collect metadata from /proc/%P/, and thus need to make sure the crashed diff --git a/test/units/TEST-87-AUX-UTILS-VM.coredump.sh b/test/units/TEST-87-AUX-UTILS-VM.coredump.sh index b3f702a2b0d7f..b3a3de76960a3 100755 --- a/test/units/TEST-87-AUX-UTILS-VM.coredump.sh +++ b/test/units/TEST-87-AUX-UTILS-VM.coredump.sh @@ -150,6 +150,11 @@ coredumpctl info foo bar baz "${CORE_TEST_BIN##*/}" coredumpctl info COREDUMP_EXE="$CORE_TEST_BIN" coredumpctl info COREDUMP_EXE=aaaaa COREDUMP_EXE= COREDUMP_EXE="$CORE_TEST_BIN" +# Check that COREDUMP_TID= is present and displayed by coredumpctl info +coredumpctl info "$CORE_TEST_BIN" | grep "TID:" >/dev/null +# Check the field is queryable in the journal +coredumpctl -F COREDUMP_TID + coredumpctl debug --debugger=/bin/true "$CORE_TEST_BIN" SYSTEMD_DEBUGGER=/bin/true coredumpctl debug "$CORE_TEST_BIN" coredumpctl debug --debugger=/bin/true --debugger-arguments="-this --does --not 'do anything' -a -t --all" "${CORE_TEST_BIN##*/}" From e10302b6b65c34d134ff7d55898e4016a3bf18dd Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 11 Mar 2026 15:54:54 +0100 Subject: [PATCH 0282/1296] sd-dlopen: add header-only public API for FDO .note.dlopen ELF metadata Expose ELF note dlopen annotation macros as a public header-only API in sd-dlopen.h. This allows any project to embed .note.dlopen metadata in their ELF binaries by simply including the header - no runtime linkage against libsystemd is required. The header provides SD_ELF_NOTE_DLOPEN() and associated macros/constants implementing the ELF dlopen metadata specification for declaring optional shared library dependencies loaded via dlopen() at runtime. Signed-off-by: Christian Brauner --- src/systemd/meson.build | 1 + src/systemd/sd-dlopen.h | 94 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 src/systemd/sd-dlopen.h diff --git a/src/systemd/meson.build b/src/systemd/meson.build index c3d2c1befb71a..d7335cee558de 100644 --- a/src/systemd/meson.build +++ b/src/systemd/meson.build @@ -6,6 +6,7 @@ _systemd_headers = [ 'sd-bus-vtable.h', 'sd-daemon.h', 'sd-device.h', + 'sd-dlopen.h', 'sd-event.h', 'sd-gpt.h', 'sd-hwdb.h', diff --git a/src/systemd/sd-dlopen.h b/src/systemd/sd-dlopen.h new file mode 100644 index 0000000000000..b3af9b71dcac7 --- /dev/null +++ b/src/systemd/sd-dlopen.h @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosddlopenhfoo +#define foosddlopenhfoo + +/*** + 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _SD_PASTE +# define _SD_PASTE_INNER(a, b) a##b +# define _SD_PASTE(a, b) _SD_PASTE_INNER(a, b) +#endif + +#ifndef _SD_UNIQ +# ifdef __COUNTER__ +# define _SD_UNIQ _SD_PASTE(_sd_uniq_, __COUNTER__) +# else +# define _SD_UNIQ _SD_PASTE(_sd_uniq_, __LINE__) +# endif +#endif + +/* ELF note macros implementing the FDO .note.dlopen standard. + * + * These macros embed metadata in an ELF binary's .note.dlopen section, + * declaring optional shared library dependencies that are loaded via + * dlopen() at runtime. Package managers and build systems can read + * these notes to discover runtime dependencies not visible in ELF + * DT_NEEDED entries. + * + * Usage: + * + * SD_ELF_NOTE_DLOPEN("myfeature", "Feature description", + * SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + * "libfoo.so.1"); + * + * See SD_ELF_NOTE_DLOPEN(3) for details. + */ + +#define SD_ELF_NOTE_DLOPEN_VENDOR "FDO" +#define SD_ELF_NOTE_DLOPEN_TYPE UINT32_C(0x407c0c0a) +#define SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED "required" +#define SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED "recommended" +#define SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED "suggested" + +#define _SD_ELF_NOTE_DLOPEN(json, variable_name) \ + __attribute__((used, section(".note.dlopen"))) _Alignas(sizeof(uint32_t)) static const struct { \ + struct { \ + uint32_t n_namesz, n_descsz, n_type; \ + } nhdr; \ + char name[sizeof(SD_ELF_NOTE_DLOPEN_VENDOR)]; \ + _Alignas(sizeof(uint32_t)) char dlopen_json[sizeof(json)]; \ + } variable_name = { \ + .nhdr = { \ + .n_namesz = sizeof(SD_ELF_NOTE_DLOPEN_VENDOR), \ + .n_descsz = sizeof(json), \ + .n_type = SD_ELF_NOTE_DLOPEN_TYPE, \ + }, \ + .name = SD_ELF_NOTE_DLOPEN_VENDOR, \ + .dlopen_json = json, \ + } + +#define _SD_SONAME_ARRAY1(a) "[\"" a "\"]" +#define _SD_SONAME_ARRAY2(a, b) "[\"" a "\",\"" b "\"]" +#define _SD_SONAME_ARRAY3(a, b, c) "[\"" a "\",\"" b "\",\"" c "\"]" +#define _SD_SONAME_ARRAY4(a, b, c, d) "[\"" a "\",\"" b "\",\"" c "\",\"" d "\"]" +#define _SD_SONAME_ARRAY5(a, b, c, d, e) "[\"" a "\",\"" b "\",\"" c "\",\"" d "\",\"" e "\"]" +#define _SD_SONAME_ARRAY_GET(_1,_2,_3,_4,_5,NAME,...) NAME +#define _SD_SONAME_ARRAY(...) _SD_SONAME_ARRAY_GET(__VA_ARGS__, _SD_SONAME_ARRAY5, _SD_SONAME_ARRAY4, _SD_SONAME_ARRAY3, _SD_SONAME_ARRAY2, _SD_SONAME_ARRAY1)(__VA_ARGS__) + +#define SD_ELF_NOTE_DLOPEN(feature, description, priority, ...) \ + _SD_ELF_NOTE_DLOPEN("[{\"feature\":\"" feature "\",\"description\":\"" description "\",\"priority\":\"" priority "\",\"soname\":" _SD_SONAME_ARRAY(__VA_ARGS__) "}]", _SD_PASTE(_sd_elf_note_dlopen_, _SD_UNIQ)) + +#ifdef __cplusplus +} +#endif + +#endif From 8b12350b75c60468ce832942fd55ff5bbdd7c9e8 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 11 Mar 2026 15:55:02 +0100 Subject: [PATCH 0283/1296] dlfcn-util: migrate to public SD_ELF_NOTE_DLOPEN() API Switch all internal callers from the private ELF_NOTE_DLOPEN() macro to the new public SD_ELF_NOTE_DLOPEN() API from sd-dlopen.h, and remove the now-redundant macro definitions from dlfcn-util.h. Signed-off-by: Christian Brauner --- src/basic/compress.c | 11 ++++-- src/basic/dlfcn-util.h | 41 -------------------- src/basic/gcrypt-util.c | 7 +++- src/locale/xkbcommon-util.c | 7 +++- src/shared/acl-util.c | 7 +++- src/shared/apparmor-util.c | 7 +++- src/shared/blkid-util.c | 6 ++- src/shared/bpf-dlopen.c | 7 +++- src/shared/cryptsetup-util.c | 6 ++- src/shared/elf-util.c | 13 ++++--- src/shared/idn-util.c | 7 +++- src/shared/libarchive-util.c | 7 +++- src/shared/libaudit-util.c | 7 +++- src/shared/libcrypt-util.c | 7 +++- src/shared/libfido2-util.c | 7 +++- src/shared/libmount-util.c | 7 +++- src/shared/module-util.c | 7 +++- src/shared/pam-util.c | 6 ++- src/shared/password-quality-util-passwdqc.c | 7 +++- src/shared/password-quality-util-pwquality.c | 7 +++- src/shared/pcre2-util.c | 7 +++- src/shared/pkcs11-util.c | 7 +++- src/shared/qrcode-util.c | 7 +++- src/shared/seccomp-util.c | 7 +++- src/shared/selinux-util.c | 7 +++- src/shared/tpm2-util.c | 21 ++++++---- 26 files changed, 136 insertions(+), 101 deletions(-) diff --git a/src/basic/compress.c b/src/basic/compress.c index c10448938e071..5c9ca829dfef3 100644 --- a/src/basic/compress.c +++ b/src/basic/compress.c @@ -20,6 +20,8 @@ #include #endif +#include "sd-dlopen.h" + #include "alloc-util.h" #include "bitfield.h" #include "compress.h" @@ -148,7 +150,8 @@ bool compression_supported(Compression c) { int dlopen_lzma(void) { #if HAVE_XZ - ELF_NOTE_DLOPEN("lzma", + SD_ELF_NOTE_DLOPEN( + "lzma", "Support lzma compression in journal and coredump files", COMPRESSION_PRIORITY_XZ, "liblzma.so.5"); @@ -219,7 +222,8 @@ int compress_blob_xz(const void *src, uint64_t src_size, int dlopen_lz4(void) { #if HAVE_LZ4 - ELF_NOTE_DLOPEN("lz4", + SD_ELF_NOTE_DLOPEN( + "lz4", "Support lz4 compression in journal and coredump files", COMPRESSION_PRIORITY_LZ4, "liblz4.so.1"); @@ -286,7 +290,8 @@ int compress_blob_lz4(const void *src, uint64_t src_size, int dlopen_zstd(void) { #if HAVE_ZSTD - ELF_NOTE_DLOPEN("zstd", + SD_ELF_NOTE_DLOPEN( + "zstd", "Support zstd compression in journal and coredump files", COMPRESSION_PRIORITY_ZSTD, "libzstd.so.1"); diff --git a/src/basic/dlfcn-util.h b/src/basic/dlfcn-util.h index 9760b31da4d05..40de1379055f2 100644 --- a/src/basic/dlfcn-util.h +++ b/src/basic/dlfcn-util.h @@ -33,47 +33,6 @@ int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_l #define DLSYM_ARG_FORCE(arg) \ &sym_##arg, STRINGIFY(arg) -#define ELF_NOTE_DLOPEN_VENDOR "FDO" -#define ELF_NOTE_DLOPEN_TYPE UINT32_C(0x407c0c0a) -#define ELF_NOTE_DLOPEN_PRIORITY_REQUIRED "required" -#define ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED "recommended" -#define ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED "suggested" - -/* Add an ".note.dlopen" ELF note to our binary that declares our weak dlopen() dependency. This - * information can be read from an ELF file via "readelf -p .note.dlopen" or an equivalent command. */ -#define _ELF_NOTE_DLOPEN(json, variable_name) \ - __attribute__((used, section(".note.dlopen"))) _Alignas(sizeof(uint32_t)) static const struct { \ - struct { \ - uint32_t n_namesz, n_descsz, n_type; \ - } nhdr; \ - char name[sizeof(ELF_NOTE_DLOPEN_VENDOR)]; \ - _Alignas(sizeof(uint32_t)) char dlopen_json[sizeof(json)]; \ - } variable_name = { \ - .nhdr = { \ - .n_namesz = sizeof(ELF_NOTE_DLOPEN_VENDOR), \ - .n_descsz = sizeof(json), \ - .n_type = ELF_NOTE_DLOPEN_TYPE, \ - }, \ - .name = ELF_NOTE_DLOPEN_VENDOR, \ - .dlopen_json = json, \ - } - -#define _SONAME_ARRAY1(a) "[\""a"\"]" -#define _SONAME_ARRAY2(a, b) "[\""a"\",\""b"\"]" -#define _SONAME_ARRAY3(a, b, c) "[\""a"\",\""b"\",\""c"\"]" -#define _SONAME_ARRAY4(a, b, c, d) "[\""a"\",\""b"\",\""c"\"",\""d"\"]" -#define _SONAME_ARRAY5(a, b, c, d, e) "[\""a"\",\""b"\",\""c"\"",\""d"\",\""e"\"]" -#define _SONAME_ARRAY_GET(_1,_2,_3,_4,_5,NAME,...) NAME -#define _SONAME_ARRAY(...) _SONAME_ARRAY_GET(__VA_ARGS__, _SONAME_ARRAY5, _SONAME_ARRAY4, _SONAME_ARRAY3, _SONAME_ARRAY2, _SONAME_ARRAY1)(__VA_ARGS__) - -/* The 'priority' must be one of 'required', 'recommended' or 'suggested' as per specification, use the - * macro defined above to specify it. - * Multiple sonames can be passed and they will be automatically constructed into a json array (but note that - * due to preprocessor language limitations if more than the limit defined above is used, a new - * _SONAME_ARRAY will need to be added). */ -#define ELF_NOTE_DLOPEN(feature, description, priority, ...) \ - _ELF_NOTE_DLOPEN("[{\"feature\":\"" feature "\",\"description\":\"" description "\",\"priority\":\"" priority "\",\"soname\":" _SONAME_ARRAY(__VA_ARGS__) "}]", UNIQ_T(s, UNIQ)) - /* If called dlopen_many_sym_or_warn() will fail with EPERM. This can be used to block lazy loading of shared * libs, if we transfer a process into a different namespace. Note that this does not work for all calls of * dlopen(), just those through our dlopen_safe() wrapper (which we use comprehensively in our diff --git a/src/basic/gcrypt-util.c b/src/basic/gcrypt-util.c index ebbc3e33bc736..79cab18822ffa 100644 --- a/src/basic/gcrypt-util.c +++ b/src/basic/gcrypt-util.c @@ -2,6 +2,8 @@ #include +#include "sd-dlopen.h" + #include "gcrypt-util.h" #if HAVE_GCRYPT @@ -44,9 +46,10 @@ DLSYM_PROTOTYPE(gcry_strerror) = NULL; int dlopen_gcrypt(void) { #if HAVE_GCRYPT - ELF_NOTE_DLOPEN("gcrypt", + SD_ELF_NOTE_DLOPEN( + "gcrypt", "Support for journald forward-sealing", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libgcrypt.so.20"); return dlopen_many_sym_or_warn( diff --git a/src/locale/xkbcommon-util.c b/src/locale/xkbcommon-util.c index 2334587e88ccd..c0810331a9e1f 100644 --- a/src/locale/xkbcommon-util.c +++ b/src/locale/xkbcommon-util.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-dlopen.h" + #include "dlfcn-util.h" #include "log.h" #include "string-util.h" @@ -15,9 +17,10 @@ DLSYM_PROTOTYPE(xkb_keymap_new_from_names) = NULL; DLSYM_PROTOTYPE(xkb_keymap_unref) = NULL; static int dlopen_xkbcommon(void) { - ELF_NOTE_DLOPEN("xkbcommon", + SD_ELF_NOTE_DLOPEN( + "xkbcommon", "Support for keyboard locale descriptions", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libxkbcommon.so.0"); + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libxkbcommon.so.0"); return dlopen_many_sym_or_warn( &xkbcommon_dl, "libxkbcommon.so.0", LOG_DEBUG, diff --git a/src/shared/acl-util.c b/src/shared/acl-util.c index ae4684414ca8e..07206bdb5f61c 100644 --- a/src/shared/acl-util.c +++ b/src/shared/acl-util.c @@ -3,6 +3,8 @@ #include #include +#include "sd-dlopen.h" + #include "acl-util.h" #include "alloc-util.h" #include "errno-util.h" @@ -44,9 +46,10 @@ DLSYM_PROTOTYPE(acl_set_tag_type); DLSYM_PROTOTYPE(acl_to_any_text); int dlopen_libacl(void) { - ELF_NOTE_DLOPEN("acl", + SD_ELF_NOTE_DLOPEN( + "acl", "Support for file Access Control Lists (ACLs)", - ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, "libacl.so.1"); return dlopen_many_sym_or_warn( diff --git a/src/shared/apparmor-util.c b/src/shared/apparmor-util.c index 10b57c60901d8..e24f4315a0246 100644 --- a/src/shared/apparmor-util.c +++ b/src/shared/apparmor-util.c @@ -2,6 +2,8 @@ #include +#include "sd-dlopen.h" + #include "alloc-util.h" #include "apparmor-util.h" #include "fileio.h" @@ -20,9 +22,10 @@ DLSYM_PROTOTYPE(aa_policy_cache_replace_all) = NULL; DLSYM_PROTOTYPE(aa_policy_cache_unref) = NULL; int dlopen_libapparmor(void) { - ELF_NOTE_DLOPEN("apparmor", + SD_ELF_NOTE_DLOPEN( + "apparmor", "Support for AppArmor policies", - ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, "libapparmor.so.1"); return dlopen_many_sym_or_warn( diff --git a/src/shared/blkid-util.c b/src/shared/blkid-util.c index ae20b47d9ef12..f1b7ccdfeb93f 100644 --- a/src/shared/blkid-util.c +++ b/src/shared/blkid-util.c @@ -2,6 +2,7 @@ #include +#include "sd-dlopen.h" #include "sd-id128.h" #include "blkid-util.h" @@ -49,9 +50,10 @@ DLSYM_PROTOTYPE(blkid_probe_set_superblocks_flags) = NULL; DLSYM_PROTOTYPE(blkid_safe_string) = NULL; int dlopen_libblkid(void) { - ELF_NOTE_DLOPEN("blkid", + SD_ELF_NOTE_DLOPEN( + "blkid", "Support for block device identification", - ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, "libblkid.so.1"); return dlopen_many_sym_or_warn( diff --git a/src/shared/bpf-dlopen.c b/src/shared/bpf-dlopen.c index 940c25a7260af..c8e2e7ce4d812 100644 --- a/src/shared/bpf-dlopen.c +++ b/src/shared/bpf-dlopen.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-dlopen.h" + #include "bpf-dlopen.h" #include "dlfcn-util.h" #include "errno-util.h" @@ -81,9 +83,10 @@ int dlopen_bpf_full(int log_level) { if (cached != 0) return cached; - ELF_NOTE_DLOPEN("bpf", + SD_ELF_NOTE_DLOPEN( + "bpf", "Support firewalling and sandboxing with BPF", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libbpf.so.1", "libbpf.so.0"); DISABLE_WARNING_DEPRECATED_DECLARATIONS; diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c index a766a92b2037f..5058bca3fd83e 100644 --- a/src/shared/cryptsetup-util.c +++ b/src/shared/cryptsetup-util.c @@ -2,6 +2,7 @@ #include +#include "sd-dlopen.h" #include "sd-json.h" #include "alloc-util.h" @@ -270,9 +271,10 @@ int dlopen_cryptsetup(void) { * still available though, and given we want to support 2.2.0 for a while longer, we'll use the old * symbol if the new one is not available. */ - ELF_NOTE_DLOPEN("cryptsetup", + SD_ELF_NOTE_DLOPEN( + "cryptsetup", "Support for disk encryption, integrity, and authentication", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libcryptsetup.so.12"); r = dlopen_many_sym_or_warn( diff --git a/src/shared/elf-util.c b/src/shared/elf-util.c index e199b42c82a24..be9e673a511d9 100644 --- a/src/shared/elf-util.c +++ b/src/shared/elf-util.c @@ -8,6 +8,7 @@ #endif #include +#include "sd-dlopen.h" #include "sd-json.h" #include "alloc-util.h" @@ -94,9 +95,10 @@ int dlopen_dw(void) { #if HAVE_ELFUTILS int r; - ELF_NOTE_DLOPEN("dw", + SD_ELF_NOTE_DLOPEN( + "dw", "Support for backtrace and ELF package metadata decoding from core files", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libdw.so.1"); r = dlopen_many_sym_or_warn( @@ -147,9 +149,10 @@ int dlopen_elf(void) { #if HAVE_ELFUTILS int r; - ELF_NOTE_DLOPEN("elf", + SD_ELF_NOTE_DLOPEN( + "elf", "Support for backtraces and reading ELF package metadata from core files", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libelf.so.1"); r = dlopen_many_sym_or_warn( @@ -406,7 +409,7 @@ static int parse_metadata(const char *name, sd_json_variant *id_json, Elf *elf, /* Package metadata might have different owners, but the * magic ID is always the same. */ - if (!IN_SET(note_header.n_type, ELF_PACKAGE_METADATA_ID, ELF_NOTE_DLOPEN_TYPE)) + if (!IN_SET(note_header.n_type, ELF_PACKAGE_METADATA_ID, SD_ELF_NOTE_DLOPEN_TYPE)) continue; _cleanup_free_ char *payload_0suffixed = NULL; diff --git a/src/shared/idn-util.c b/src/shared/idn-util.c index aad0db1426c53..a1b4a6c49873c 100644 --- a/src/shared/idn-util.c +++ b/src/shared/idn-util.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-dlopen.h" + #include "idn-util.h" #include "log.h" /* IWYU pragma: keep */ @@ -10,9 +12,10 @@ const char *(*sym_idn2_strerror)(int rc) _const_ = NULL; DLSYM_PROTOTYPE(idn2_to_unicode_8z8z) = NULL; int dlopen_idn(void) { - ELF_NOTE_DLOPEN("idn", + SD_ELF_NOTE_DLOPEN( + "idn", "Support for internationalized domain names", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libidn2.so.0"); return dlopen_many_sym_or_warn( diff --git a/src/shared/libarchive-util.c b/src/shared/libarchive-util.c index d2714bd81c183..02b6b36b3c244 100644 --- a/src/shared/libarchive-util.c +++ b/src/shared/libarchive-util.c @@ -2,6 +2,8 @@ #include +#include "sd-dlopen.h" + #include "libarchive-util.h" #include "user-util.h" /* IWYU pragma: keep */ @@ -79,9 +81,10 @@ DLSYM_PROTOTYPE(archive_write_set_format_filter_by_ext) = NULL; DLSYM_PROTOTYPE(archive_write_set_format_pax) = NULL; int dlopen_libarchive(void) { - ELF_NOTE_DLOPEN("archive", + SD_ELF_NOTE_DLOPEN( + "archive", "Support for decompressing archive files", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libarchive.so.13"); return dlopen_many_sym_or_warn( diff --git a/src/shared/libaudit-util.c b/src/shared/libaudit-util.c index 4bdd4c1317edf..bf5791955bbba 100644 --- a/src/shared/libaudit-util.c +++ b/src/shared/libaudit-util.c @@ -4,6 +4,8 @@ #include #include +#include "sd-dlopen.h" + #include "errno-util.h" #include "fd-util.h" #include "iovec-util.h" @@ -23,9 +25,10 @@ static DLSYM_PROTOTYPE(audit_open) = NULL; int dlopen_libaudit(void) { #if HAVE_AUDIT - ELF_NOTE_DLOPEN("audit", + SD_ELF_NOTE_DLOPEN( + "audit", "Support for Audit logging", - ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, "libaudit.so.1"); return dlopen_many_sym_or_warn( diff --git a/src/shared/libcrypt-util.c b/src/shared/libcrypt-util.c index 4760d66c92a93..df3ba146f9ed4 100644 --- a/src/shared/libcrypt-util.c +++ b/src/shared/libcrypt-util.c @@ -4,6 +4,8 @@ # include #endif +#include "sd-dlopen.h" + #include "alloc-util.h" #include "dlfcn-util.h" #include "errno-util.h" @@ -33,9 +35,10 @@ int dlopen_libcrypt(void) { /* Several distributions like Debian/Ubuntu and OpenSUSE provide libxcrypt as libcrypt.so.1 * (libcrypt.so.1.1 on some architectures), while others like Fedora/CentOS and Arch provide it as * libcrypt.so.2. */ - ELF_NOTE_DLOPEN("crypt", + SD_ELF_NOTE_DLOPEN( + "crypt", "Support for hashing passwords", - ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, "libcrypt.so.2", "libcrypt.so.1", "libcrypt.so.1.1"); _cleanup_(dlclosep) void *dl = NULL; diff --git a/src/shared/libfido2-util.c b/src/shared/libfido2-util.c index b4aee235a9923..f4c8ad5c8616e 100644 --- a/src/shared/libfido2-util.c +++ b/src/shared/libfido2-util.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-dlopen.h" + #include "libfido2-util.h" #include "log.h" @@ -82,9 +84,10 @@ int dlopen_libfido2(void) { #if HAVE_LIBFIDO2 int r; - ELF_NOTE_DLOPEN("fido2", + SD_ELF_NOTE_DLOPEN( + "fido2", "Support fido2 for encryption and authentication", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libfido2.so.1"); r = dlopen_many_sym_or_warn( diff --git a/src/shared/libmount-util.c b/src/shared/libmount-util.c index c6c6074c25953..be4dd0712ee4c 100644 --- a/src/shared/libmount-util.c +++ b/src/shared/libmount-util.c @@ -2,6 +2,8 @@ #include +#include "sd-dlopen.h" + #include "fstab-util.h" #include "libmount-util.h" #include "log.h" @@ -41,9 +43,10 @@ DLSYM_PROTOTYPE(mnt_table_parse_swaps) = NULL; DLSYM_PROTOTYPE(mnt_unref_monitor) = NULL; int dlopen_libmount(void) { - ELF_NOTE_DLOPEN("mount", + SD_ELF_NOTE_DLOPEN( + "mount", "Support for mount enumeration", - ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, "libmount.so.1"); return dlopen_many_sym_or_warn( diff --git a/src/shared/module-util.c b/src/shared/module-util.c index a7f9e178e3c2a..8ad7dab180a66 100644 --- a/src/shared/module-util.c +++ b/src/shared/module-util.c @@ -2,6 +2,8 @@ #include +#include "sd-dlopen.h" + #include "log.h" #include "module-util.h" #include "proc-cmdline.h" @@ -26,9 +28,10 @@ DLSYM_PROTOTYPE(kmod_unref) = NULL; DLSYM_PROTOTYPE(kmod_validate_resources) = NULL; int dlopen_libkmod(void) { - ELF_NOTE_DLOPEN("kmod", + SD_ELF_NOTE_DLOPEN( + "kmod", "Support for loading kernel modules", - ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, "libkmod.so.2"); return dlopen_many_sym_or_warn( diff --git a/src/shared/pam-util.c b/src/shared/pam-util.c index d6158ec8caec4..0f04e97377b3e 100644 --- a/src/shared/pam-util.c +++ b/src/shared/pam-util.c @@ -4,6 +4,7 @@ #include #include "sd-bus.h" +#include "sd-dlopen.h" #include "alloc-util.h" #include "bus-internal.h" @@ -35,9 +36,10 @@ DLSYM_PROTOTYPE(pam_syslog) = NULL; DLSYM_PROTOTYPE(pam_vsyslog) = NULL; int dlopen_libpam(void) { - ELF_NOTE_DLOPEN("pam", + SD_ELF_NOTE_DLOPEN( + "pam", "Support for LinuxPAM", - ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, "libpam.so.0"); return dlopen_many_sym_or_warn( diff --git a/src/shared/password-quality-util-passwdqc.c b/src/shared/password-quality-util-passwdqc.c index 5b0b22458bfb3..33c77b01a0cef 100644 --- a/src/shared/password-quality-util-passwdqc.c +++ b/src/shared/password-quality-util-passwdqc.c @@ -6,6 +6,8 @@ #include +#include "sd-dlopen.h" + #include "alloc-util.h" #include "dlfcn-util.h" #include "errno-util.h" @@ -137,9 +139,10 @@ int check_password_quality( int dlopen_passwdqc(void) { #if HAVE_PASSWDQC - ELF_NOTE_DLOPEN("passwdqc", + SD_ELF_NOTE_DLOPEN( + "passwdqc", "Support for password quality checks", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libpasswdqc.so.1"); return dlopen_many_sym_or_warn( diff --git a/src/shared/password-quality-util-pwquality.c b/src/shared/password-quality-util-pwquality.c index 05d85bd69652b..3eba9495b642c 100644 --- a/src/shared/password-quality-util-pwquality.c +++ b/src/shared/password-quality-util-pwquality.c @@ -8,6 +8,8 @@ #include #include +#include "sd-dlopen.h" + #include "alloc-util.h" #include "dlfcn-util.h" #include "errno-util.h" @@ -153,9 +155,10 @@ int check_password_quality(const char *password, const char *old, const char *us int dlopen_pwquality(void) { #if HAVE_PWQUALITY - ELF_NOTE_DLOPEN("pwquality", + SD_ELF_NOTE_DLOPEN( + "pwquality", "Support for password quality checks", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libpwquality.so.1"); return dlopen_many_sym_or_warn( diff --git a/src/shared/pcre2-util.c b/src/shared/pcre2-util.c index 10a767442a4e4..22a7635170bab 100644 --- a/src/shared/pcre2-util.c +++ b/src/shared/pcre2-util.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-dlopen.h" + #include "dlfcn-util.h" #include "hash-funcs.h" #include "log.h" @@ -28,9 +30,10 @@ const struct hash_ops pcre2_code_hash_ops_free = {}; int dlopen_pcre2(void) { #if HAVE_PCRE2 - ELF_NOTE_DLOPEN("pcre2", + SD_ELF_NOTE_DLOPEN( + "pcre2", "Support for regular expressions", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libpcre2-8.so.0"); /* So here's something weird: PCRE2 actually renames the symbols exported by the library via C diff --git a/src/shared/pkcs11-util.c b/src/shared/pkcs11-util.c index 3062bcc554196..1fa0d77d6d004 100644 --- a/src/shared/pkcs11-util.c +++ b/src/shared/pkcs11-util.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-dlopen.h" + #include "alloc-util.h" #include "ask-password-api.h" #include "dlfcn-util.h" @@ -1766,9 +1768,10 @@ static int list_callback( int dlopen_p11kit(void) { #if HAVE_P11KIT - ELF_NOTE_DLOPEN("p11-kit", + SD_ELF_NOTE_DLOPEN( + "p11-kit", "Support for PKCS11 hardware tokens", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libp11-kit.so.0"); return dlopen_many_sym_or_warn( diff --git a/src/shared/qrcode-util.c b/src/shared/qrcode-util.c index 320b2353ff92f..ee6d3c40f93fa 100644 --- a/src/shared/qrcode-util.c +++ b/src/shared/qrcode-util.c @@ -7,6 +7,8 @@ #endif #include +#include "sd-dlopen.h" + #include "ansi-color.h" #include "dlfcn-util.h" #include "locale-util.h" @@ -30,9 +32,10 @@ int dlopen_qrencode(void) { #if HAVE_QRENCODE int r; - ELF_NOTE_DLOPEN("qrencode", + SD_ELF_NOTE_DLOPEN( + "qrencode", "Support for generating QR codes", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libqrencode.so.4", "libqrencode.so.3"); FOREACH_STRING(s, "libqrencode.so.4", "libqrencode.so.3") { diff --git a/src/shared/seccomp-util.c b/src/shared/seccomp-util.c index 55e3c14e443fd..d2f7612a53de5 100644 --- a/src/shared/seccomp-util.c +++ b/src/shared/seccomp-util.c @@ -16,6 +16,8 @@ #include #endif +#include "sd-dlopen.h" + #include "af-list.h" #include "alloc-util.h" #include "env-util.h" @@ -49,9 +51,10 @@ DLSYM_PROTOTYPE(seccomp_syscall_resolve_name) = NULL; DLSYM_PROTOTYPE(seccomp_syscall_resolve_num_arch) = NULL; int dlopen_libseccomp(void) { - ELF_NOTE_DLOPEN("seccomp", + SD_ELF_NOTE_DLOPEN( + "seccomp", "Support for Seccomp Sandboxes", - ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, "libseccomp.so.2"); return dlopen_many_sym_or_warn( diff --git a/src/shared/selinux-util.c b/src/shared/selinux-util.c index 4fca62b66d32c..f980ec83acb4f 100644 --- a/src/shared/selinux-util.c +++ b/src/shared/selinux-util.c @@ -15,6 +15,8 @@ #include #endif +#include "sd-dlopen.h" + #include "alloc-util.h" #include "errno-util.h" #include "fd-util.h" @@ -88,9 +90,10 @@ DLSYM_PROTOTYPE(setsockcreatecon_raw) = NULL; DLSYM_PROTOTYPE(string_to_security_class) = NULL; int dlopen_libselinux(void) { - ELF_NOTE_DLOPEN("selinux", + SD_ELF_NOTE_DLOPEN( + "selinux", "Support for SELinux", - ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, "libselinux.so.1"); return dlopen_many_sym_or_warn( diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 9e05c847edf02..c12ba2d28c778 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -4,6 +4,7 @@ #include #include "sd-device.h" +#include "sd-dlopen.h" #include "alloc-util.h" #include "ansi-color.h" @@ -130,9 +131,10 @@ static DLSYM_PROTOTYPE(Tss2_RC_Decode) = NULL; static int dlopen_tpm2_esys(void) { int r; - ELF_NOTE_DLOPEN("tpm", + SD_ELF_NOTE_DLOPEN( + "tpm", "Support for TPM", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libtss2-esys.so.0"); r = dlopen_many_sym_or_warn( @@ -193,9 +195,10 @@ static int dlopen_tpm2_esys(void) { } static int dlopen_tpm2_rc(void) { - ELF_NOTE_DLOPEN("tpm", + SD_ELF_NOTE_DLOPEN( + "tpm", "Support for TPM", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libtss2-rc.so.0"); return dlopen_many_sym_or_warn( @@ -204,9 +207,10 @@ static int dlopen_tpm2_rc(void) { } static int dlopen_tpm2_mu(void) { - ELF_NOTE_DLOPEN("tpm", + SD_ELF_NOTE_DLOPEN( + "tpm", "Support for TPM", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libtss2-mu.so.0"); return dlopen_many_sym_or_warn( @@ -236,9 +240,10 @@ static int dlopen_tpm2_tcti_device(void) { /* The "device" TCTI is the most relevant one, let's also load it explicitly on dlopen_tpm2(), even * if we don't resolve any symbols here. */ - ELF_NOTE_DLOPEN("tpm", + SD_ELF_NOTE_DLOPEN( + "tpm", "Support for TPM", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libtss2-tcti-device.so.0"); return dlopen_verbose( From f90ee22bcf8465edea38de70cd64875d9b0db343 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 11 Mar 2026 15:55:08 +0100 Subject: [PATCH 0284/1296] man: add sd-dlopen(3) and SD_ELF_NOTE_DLOPEN(3) man pages Document the new public sd-dlopen.h header and SD_ELF_NOTE_DLOPEN() macro with associated constants. Includes usage examples for single and multiple soname annotations. Signed-off-by: Christian Brauner --- man/SD_ELF_NOTE_DLOPEN.xml | 143 +++++++++++++++++++++++++++++++++++++ man/rules/meson.build | 9 +++ man/sd-dlopen.xml | 80 +++++++++++++++++++++ 3 files changed, 232 insertions(+) create mode 100644 man/SD_ELF_NOTE_DLOPEN.xml create mode 100644 man/sd-dlopen.xml diff --git a/man/SD_ELF_NOTE_DLOPEN.xml b/man/SD_ELF_NOTE_DLOPEN.xml new file mode 100644 index 0000000000000..0e3eed84cfd09 --- /dev/null +++ b/man/SD_ELF_NOTE_DLOPEN.xml @@ -0,0 +1,143 @@ + + + + + + + + SD_ELF_NOTE_DLOPEN + systemd + + + + SD_ELF_NOTE_DLOPEN + 3 + + + + SD_ELF_NOTE_DLOPEN + SD_ELF_NOTE_DLOPEN_VENDOR + SD_ELF_NOTE_DLOPEN_TYPE + SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED + + Embed ELF .note.dlopen metadata for shared library dependencies + + + + + #include <systemd/sd-dlopen.h> + + SD_ELF_NOTE_DLOPEN(feature, description, priority, soname...) + + #define SD_ELF_NOTE_DLOPEN_VENDOR "FDO" + #define SD_ELF_NOTE_DLOPEN_TYPE UINT32_C(0x407c0c0a) + #define SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED "required" + #define SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED "recommended" + #define SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED "suggested" + + + + + + Description + + SD_ELF_NOTE_DLOPEN() is a macro that embeds a + .note.dlopen ELF note section in the compiled binary, declaring a weak dependency + on a shared library loaded via dlopen(). This implements the + ELF dlopen + metadata specification, allowing package managers and build systems to discover runtime + dependencies that are not visible through regular ELF DT_NEEDED entries. + + The macro takes the following parameters: + + + + feature + A short string identifying the feature this library provides (e.g. + XKB, PCRE2). + + + + + description + A human-readable description of what the library is used for. + + + + + priority + One of SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED, + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, or + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, indicating how important the + dependency is. + + + + + soname... + One or more shared object names (sonames) for the library, e.g. + libfoo.so.1. Multiple sonames may be specified as separate arguments (up to 5) + for libraries that have changed soname across versions. + + + + + The embedded metadata can be read from a compiled ELF binary using: + systemd-analyze dlopen-metadata binary + + + + + Examples + + + Single soname + #include <systemd/sd-dlopen.h> + +SD_ELF_NOTE_DLOPEN("XKB", "Keyboard layout support", + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + "libxkbcommon.so.0"); + + + + Multiple sonames for different library versions + SD_ELF_NOTE_DLOPEN("crypt", "Support for hashing passwords", + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + "libcrypt.so.2", "libcrypt.so.1", "libcrypt.so.1.1"); + + + + + + Notes + + The macros described here are header-only and do not require runtime linkage against + libsystemd3. + Only the installed header and include path (as provided by + pkg-config --cflags libsystemd) are needed. + + + + History + SD_ELF_NOTE_DLOPEN(), + SD_ELF_NOTE_DLOPEN_VENDOR, + SD_ELF_NOTE_DLOPEN_TYPE, + SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED, + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, and + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED were added in version 261. + + + + See Also + + + systemd1 + sd-dlopen3 + dlopen3 + + + + diff --git a/man/rules/meson.build b/man/rules/meson.build index d69793150be97..d7cbd5b65201b 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -127,6 +127,7 @@ manpages = [ 'SD_WARNING'], ''], ['sd-device', '3', [], ''], + ['sd-dlopen', '3', [], ''], ['sd-event', '3', [], ''], ['sd-hwdb', '3', [], ''], ['sd-id128', @@ -154,6 +155,14 @@ manpages = [ ['sd-login', '3', [], 'HAVE_PAM'], ['sd-path', '3', [], ''], ['sd-varlink', '3', [], ''], + ['SD_ELF_NOTE_DLOPEN', + '3', + ['SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED', + 'SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED', + 'SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED', + 'SD_ELF_NOTE_DLOPEN_TYPE', + 'SD_ELF_NOTE_DLOPEN_VENDOR'], + ''], ['sd_booted', '3', [], ''], ['sd_bus_add_match', '3', diff --git a/man/sd-dlopen.xml b/man/sd-dlopen.xml new file mode 100644 index 0000000000000..ed43e396d69bf --- /dev/null +++ b/man/sd-dlopen.xml @@ -0,0 +1,80 @@ + + + + + + + + sd-dlopen + systemd + + + + sd-dlopen + 3 + + + + sd-dlopen + ELF dlopen metadata annotation macros + + + + + #include <systemd/sd-dlopen.h> + + + + pkg-config --cflags libsystemd + + + + + + Description + + sd-dlopen.h provides macros for embedding + .note.dlopen metadata in ELF binaries, implementing the + ELF dlopen + metadata specification for declaring optional shared library dependencies that are loaded via + dlopen3 + at runtime. + + The header is self-contained and does not require runtime linkage against + libsystemd3. + Projects only need the installed header to use the macros. + + Package managers and build systems can read the embedded ELF notes to discover runtime + dependencies that are not visible in ELF DT_NEEDED entries. + + See + SD_ELF_NOTE_DLOPEN3 + for details on the available macros and constants. + + + + Notes + + The macros described here are header-only and do not require runtime linkage against + libsystemd3. + Only the installed header and include path (as provided by + pkg-config --cflags libsystemd) are needed. + + + + History + SD_ELF_NOTE_DLOPEN() and associated macros and constants were added in + version 261. + + + + See Also + + systemd1 + SD_ELF_NOTE_DLOPEN3 + dlopen3 + + + + From 919b8c7c92b120ba986a28882c151777661e97d6 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 12 Mar 2026 13:51:16 +0100 Subject: [PATCH 0285/1296] sd-dlopen: relicense header to MIT-0 Relicense sd-dlopen.h from LGPL-2.1-or-later to MIT-0 so that downstream projects can copy/paste the macros directly without introducing a build dependency on the systemd headers. Acked-by: Lennart Poettering Acked-by: Luca Boccassi Signed-off-by: Christian Brauner --- LICENSES/README.md | 1 + src/systemd/sd-dlopen.h | 26 +++++++++++++++----------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/LICENSES/README.md b/LICENSES/README.md index 5522a08e10910..9bd26baa0dc67 100644 --- a/LICENSES/README.md +++ b/LICENSES/README.md @@ -57,6 +57,7 @@ The following exceptions apply: * the following sources are licensed under the **MIT-0** license: - all examples under man/ - config files and examples under /network + - src/systemd/sd-dlopen.h * the following sources are under **Public Domain** (LicenseRef-murmurhash2-public-domain): - src/basic/MurmurHash2.c - src/basic/MurmurHash2.h diff --git a/src/systemd/sd-dlopen.h b/src/systemd/sd-dlopen.h index b3af9b71dcac7..a58b90e0ec08c 100644 --- a/src/systemd/sd-dlopen.h +++ b/src/systemd/sd-dlopen.h @@ -1,20 +1,24 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* SPDX-License-Identifier: MIT-0 */ #ifndef foosddlopenhfoo #define foosddlopenhfoo /*** - 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. + Copyright © 2026 The systemd Project - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so. - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. ***/ #include From b16cfc0c16ef7c6cbdcb8dceba84fde5b79906d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 12:26:05 +0100 Subject: [PATCH 0286/1296] homectl: call all verb functions verb_* This series of renaming patches has a few overlapping motivations: - when functions are named uniformly, it code is more obvious - I want to add a parameter to all verb functions - in #40880 uniform naming of verb functions will be necessary too. So let's do this cleanup. Some tools had a mix of functions w/ and w/o "verb_", which looked messy. --- src/home/homectl.c | 90 ++++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index 2b92ab6481eae..21ac8b055be86 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -178,7 +178,7 @@ static int acquire_bus(sd_bus **bus) { return 0; } -static int list_homes(int argc, char *argv[], void *userdata) { +static int verb_list_homes(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -627,7 +627,7 @@ static int acquire_passed_secrets(const char *user_name, UserRecord **ret) { return 0; } -static int activate_home(int argc, char *argv[], void *userdata) { +static int verb_activate_home(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r, ret = 0; @@ -675,7 +675,7 @@ static int activate_home(int argc, char *argv[], void *userdata) { return ret; } -static int deactivate_home(int argc, char *argv[], void *userdata) { +static int verb_deactivate_home(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r, ret = 0; @@ -775,7 +775,7 @@ static int inspect_home(sd_bus *bus, const char *name) { return 0; } -static int inspect_homes(int argc, char *argv[], void *userdata) { +static int verb_inspect_homes(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; @@ -833,7 +833,7 @@ static int authenticate_home(sd_bus *bus, const char *name) { } } -static int authenticate_homes(int argc, char *argv[], void *userdata) { +static int verb_authenticate_homes(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; @@ -1557,7 +1557,7 @@ static int create_home_common(sd_json_variant *input, bool show_enforce_password return 0; } -static int create_home(int argc, char *argv[], void *userdata) { +static int verb_create_home(int argc, char *argv[], void *userdata) { int r; if (argc >= 2) { @@ -1735,7 +1735,7 @@ static int verb_unregister_home(int argc, char *argv[], void *userdata) { return ret; } -static int remove_home(int argc, char *argv[], void *userdata) { +static int verb_remove_home(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r, ret = 0; @@ -1900,7 +1900,7 @@ static int home_record_reset_human_interaction_permission(UserRecord *hr) { return 0; } -static int update_home(int argc, char *argv[], void *userdata) { +static int verb_update_home(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(user_record_unrefp) UserRecord *hr = NULL, *secret = NULL; _cleanup_free_ char *buffer = NULL; @@ -2077,7 +2077,7 @@ static int update_home(int argc, char *argv[], void *userdata) { return 0; } -static int passwd_home(int argc, char *argv[], void *userdata) { +static int verb_passwd_home(int argc, char *argv[], void *userdata) { _cleanup_(user_record_unrefp) UserRecord *old_secret = NULL, *new_secret = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_free_ char *buffer = NULL; @@ -2194,7 +2194,7 @@ static int parse_disk_size(const char *t, uint64_t *ret) { return 0; } -static int resize_home(int argc, char *argv[], void *userdata) { +static int verb_resize_home(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(user_record_unrefp) UserRecord *secret = NULL; uint64_t ds = UINT64_MAX; @@ -2256,7 +2256,7 @@ static int resize_home(int argc, char *argv[], void *userdata) { return 0; } -static int lock_home(int argc, char *argv[], void *userdata) { +static int verb_lock_home(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r, ret = 0; @@ -2287,7 +2287,7 @@ static int lock_home(int argc, char *argv[], void *userdata) { return ret; } -static int unlock_home(int argc, char *argv[], void *userdata) { +static int verb_unlock_home(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r, ret = 0; @@ -2335,7 +2335,7 @@ static int unlock_home(int argc, char *argv[], void *userdata) { return ret; } -static int with_home(int argc, char *argv[], void *userdata) { +static int verb_with_home(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -2458,7 +2458,7 @@ static int with_home(int argc, char *argv[], void *userdata) { return ret; } -static int lock_all_homes(int argc, char *argv[], void *userdata) { +static int verb_lock_all_homes(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -2479,7 +2479,7 @@ static int lock_all_homes(int argc, char *argv[], void *userdata) { return 0; } -static int deactivate_all_homes(int argc, char *argv[], void *userdata) { +static int verb_deactivate_all_homes(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -2500,7 +2500,7 @@ static int deactivate_all_homes(int argc, char *argv[], void *userdata) { return 0; } -static int rebalance(int argc, char *argv[], void *userdata) { +static int verb_rebalance(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -3866,7 +3866,7 @@ static int parse_fido2_device_field(const char *arg) { return 1; } -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -4082,6 +4082,10 @@ static int help(int argc, char *argv[], void *userdata) { return 0; } +static int verb_help(int argc, char *argv[], void *userdata) { + return help(); +} + static int parse_argv(int argc, char *argv[]) { _cleanup_strv_free_ char **arg_languages = NULL; @@ -4316,7 +4320,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - return help(0, NULL, NULL); + return help(); case ARG_VERSION: return version(); @@ -5511,31 +5515,31 @@ static int verb_remove_signing_key(int argc, char *argv[], void *userdata) { static int run(int argc, char *argv[]) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "list", VERB_ANY, 1, VERB_DEFAULT, list_homes }, - { "activate", 2, VERB_ANY, 0, activate_home }, - { "deactivate", 2, VERB_ANY, 0, deactivate_home }, - { "inspect", VERB_ANY, VERB_ANY, 0, inspect_homes }, - { "authenticate", VERB_ANY, VERB_ANY, 0, authenticate_homes }, - { "create", VERB_ANY, 2, 0, create_home }, - { "adopt", VERB_ANY, VERB_ANY, 0, verb_adopt_home }, - { "register", VERB_ANY, VERB_ANY, 0, verb_register_home }, - { "unregister", 2, VERB_ANY, 0, verb_unregister_home }, - { "remove", 2, VERB_ANY, 0, remove_home }, - { "update", VERB_ANY, 2, 0, update_home }, - { "passwd", VERB_ANY, 2, 0, passwd_home }, - { "resize", 2, 3, 0, resize_home }, - { "lock", 2, VERB_ANY, 0, lock_home }, - { "unlock", 2, VERB_ANY, 0, unlock_home }, - { "with", 2, VERB_ANY, 0, with_home }, - { "lock-all", VERB_ANY, 1, 0, lock_all_homes }, - { "deactivate-all", VERB_ANY, 1, 0, deactivate_all_homes }, - { "rebalance", VERB_ANY, 1, 0, rebalance }, - { "firstboot", VERB_ANY, 1, 0, verb_firstboot }, - { "list-signing-keys", VERB_ANY, 1, 0, verb_list_signing_keys }, - { "get-signing-key", VERB_ANY, VERB_ANY, 0, verb_get_signing_key }, - { "add-signing-key", VERB_ANY, VERB_ANY, 0, verb_add_signing_key }, - { "remove-signing-key", 2, VERB_ANY, 0, verb_remove_signing_key }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, + { "list", VERB_ANY, 1, VERB_DEFAULT, verb_list_homes }, + { "activate", 2, VERB_ANY, 0, verb_activate_home }, + { "deactivate", 2, VERB_ANY, 0, verb_deactivate_home }, + { "inspect", VERB_ANY, VERB_ANY, 0, verb_inspect_homes }, + { "authenticate", VERB_ANY, VERB_ANY, 0, verb_authenticate_homes }, + { "create", VERB_ANY, 2, 0, verb_create_home }, + { "adopt", VERB_ANY, VERB_ANY, 0, verb_adopt_home }, + { "register", VERB_ANY, VERB_ANY, 0, verb_register_home }, + { "unregister", 2, VERB_ANY, 0, verb_unregister_home }, + { "remove", 2, VERB_ANY, 0, verb_remove_home }, + { "update", VERB_ANY, 2, 0, verb_update_home }, + { "passwd", VERB_ANY, 2, 0, verb_passwd_home }, + { "resize", 2, 3, 0, verb_resize_home }, + { "lock", 2, VERB_ANY, 0, verb_lock_home }, + { "unlock", 2, VERB_ANY, 0, verb_unlock_home }, + { "with", 2, VERB_ANY, 0, verb_with_home }, + { "lock-all", VERB_ANY, 1, 0, verb_lock_all_homes }, + { "deactivate-all", VERB_ANY, 1, 0, verb_deactivate_all_homes }, + { "rebalance", VERB_ANY, 1, 0, verb_rebalance }, + { "firstboot", VERB_ANY, 1, 0, verb_firstboot }, + { "list-signing-keys", VERB_ANY, 1, 0, verb_list_signing_keys }, + { "get-signing-key", VERB_ANY, VERB_ANY, 0, verb_get_signing_key }, + { "add-signing-key", VERB_ANY, VERB_ANY, 0, verb_add_signing_key }, + { "remove-signing-key", 2, VERB_ANY, 0, verb_remove_signing_key }, {} }; From e6ec16dbe21a34f08bc0464999500ee875c2ecbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 12:33:06 +0100 Subject: [PATCH 0287/1296] busctl: call all verb functions verb_* --- src/busctl/busctl.c | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c index 8c523dc02bae2..2cebd311c0caa 100644 --- a/src/busctl/busctl.c +++ b/src/busctl/busctl.c @@ -175,7 +175,7 @@ static void notify_bus_error(const sd_bus_error *error) { (void) sd_notifyf(/* unset_environment= */ false, "BUSERROR=%s", error->name); } -static int list_bus_names(int argc, char **argv, void *userdata) { +static int verb_list_bus_names(int argc, char **argv, void *userdata) { _cleanup_strv_free_ char **acquired = NULL, **activatable = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_hashmap_free_ Hashmap *names = NULL; @@ -535,7 +535,7 @@ static int tree_one(sd_bus *bus, const char *service) { return r; } -static int tree(int argc, char **argv, void *userdata) { +static int verb_tree(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; @@ -994,7 +994,7 @@ static int members_flags_to_string(const Member *m, char **ret) { return 0; } -static int introspect(int argc, char **argv, void *userdata) { +static int verb_introspect(int argc, char **argv, void *userdata) { static const XMLIntrospectOps ops = { .on_interface = on_interface, .on_method = on_method, @@ -1412,7 +1412,7 @@ static int verb_capture(int argc, char **argv, void *userdata) { return r; } -static int status(int argc, char **argv, void *userdata) { +static int verb_status(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; pid_t pid; @@ -1782,7 +1782,7 @@ static int bus_message_dump(sd_bus_message *m, uint64_t flags) { return 0; } -static int call(int argc, char **argv, void *userdata) { +static int verb_call(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; @@ -1849,7 +1849,7 @@ static int call(int argc, char **argv, void *userdata) { return bus_message_dump(reply, /* flags= */ 0); } -static int emit_signal(int argc, char **argv, void *userdata) { +static int verb_emit_signal(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_fdset_free_ FDSet *passed_fdset = NULL; @@ -1894,7 +1894,7 @@ static int emit_signal(int argc, char **argv, void *userdata) { return 0; } -static int get_property(int argc, char **argv, void *userdata) { +static int verb_get_property(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -1952,7 +1952,7 @@ static int on_bus_signal(sd_bus_message *msg, void *userdata, sd_bus_error *ret_ return 0; } -static int wait_signal(int argc, char **argv, void *userdata) { +static int verb_wait_signal(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_event_unrefp) sd_event *e = NULL; _cleanup_(sd_event_source_unrefp) sd_event_source *timer = NULL; @@ -2000,7 +2000,7 @@ static int wait_signal(int argc, char **argv, void *userdata) { return sd_event_loop(e); } -static int set_property(int argc, char **argv, void *userdata) { +static int verb_set_property(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -2405,18 +2405,18 @@ static int parse_argv(int argc, char *argv[]) { static int busctl_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "list", VERB_ANY, 1, VERB_DEFAULT, list_bus_names }, - { "status", VERB_ANY, 2, 0, status }, - { "monitor", VERB_ANY, VERB_ANY, 0, verb_monitor }, - { "capture", VERB_ANY, VERB_ANY, 0, verb_capture }, - { "tree", VERB_ANY, VERB_ANY, 0, tree }, - { "introspect", 3, 4, 0, introspect }, - { "call", 5, VERB_ANY, 0, call }, - { "emit", 4, VERB_ANY, 0, emit_signal }, - { "wait", 4, 5, 0, wait_signal }, - { "get-property", 5, VERB_ANY, 0, get_property }, - { "set-property", 6, VERB_ANY, 0, set_property }, - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, + { "list", VERB_ANY, 1, VERB_DEFAULT, verb_list_bus_names }, + { "status", VERB_ANY, 2, 0, verb_status }, + { "monitor", VERB_ANY, VERB_ANY, 0, verb_monitor }, + { "capture", VERB_ANY, VERB_ANY, 0, verb_capture }, + { "tree", VERB_ANY, VERB_ANY, 0, verb_tree }, + { "introspect", 3, 4, 0, verb_introspect }, + { "call", 5, VERB_ANY, 0, verb_call }, + { "emit", 4, VERB_ANY, 0, verb_emit_signal }, + { "wait", 4, 5, 0, verb_wait_signal }, + { "get-property", 5, VERB_ANY, 0, verb_get_property }, + { "set-property", 6, VERB_ANY, 0, verb_set_property }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, {} }; From f5f1bc36d09e19cf6d73447f80f0c257542ee7c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 12:45:01 +0100 Subject: [PATCH 0288/1296] coredumpctl: call all verb functions verb_* --- src/coredump/coredumpctl.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index 618a464fcc9bf..2a07935a72f71 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -175,7 +175,7 @@ static int acquire_journal(sd_journal **ret, char **matches) { return 0; } -static int verb_help(int argc, char **argv, void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -223,6 +223,10 @@ static int verb_help(int argc, char **argv, void *userdata) { return 0; } +static int verb_help(int argc, char *argv[], void *userdata) { + return help(); +} + static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, @@ -889,7 +893,7 @@ static int print_entry( return print_info(stdout, j, n_found > 0); } -static int dump_list(int argc, char **argv, void *userdata) { +static int verb_dump_list(int argc, char **argv, void *userdata) { _cleanup_(sd_journal_closep) sd_journal *j = NULL; _cleanup_(table_unrefp) Table *t = NULL; size_t n_found = 0; @@ -1140,7 +1144,7 @@ static int save_core(sd_journal *j, FILE *file, char **path, bool *unlink_temp) return r; } -static int dump_core(int argc, char **argv, void *userdata) { +static int verb_dump_core(int argc, char **argv, void *userdata) { _cleanup_(sd_journal_closep) sd_journal *j = NULL; _cleanup_fclose_ FILE *f = NULL; int r; @@ -1176,7 +1180,7 @@ static int dump_core(int argc, char **argv, void *userdata) { return 0; } -static int run_debug(int argc, char **argv, void *userdata) { +static int verb_run_debug(int argc, char **argv, void *userdata) { static const struct sigaction sa = { .sa_sigaction = sigterm_process_group_handler, .sa_flags = SA_SIGINFO, @@ -1369,12 +1373,12 @@ static int check_units_active(void) { static int coredumpctl_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "list", VERB_ANY, VERB_ANY, VERB_DEFAULT, dump_list }, - { "info", VERB_ANY, VERB_ANY, 0, dump_list }, - { "dump", VERB_ANY, VERB_ANY, 0, dump_core }, - { "debug", VERB_ANY, VERB_ANY, 0, run_debug }, - { "gdb", VERB_ANY, VERB_ANY, 0, run_debug }, - { "help", VERB_ANY, 1, 0, verb_help }, + { "list", VERB_ANY, VERB_ANY, VERB_DEFAULT, verb_dump_list }, + { "info", VERB_ANY, VERB_ANY, 0, verb_dump_list }, + { "dump", VERB_ANY, VERB_ANY, 0, verb_dump_core }, + { "debug", VERB_ANY, VERB_ANY, 0, verb_run_debug }, + { "gdb", VERB_ANY, VERB_ANY, 0, verb_run_debug }, + { "help", VERB_ANY, 1, 0, verb_help }, {} }; From 94a484994716aad7214e43af2e55e8f0adcb344c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 12:47:40 +0100 Subject: [PATCH 0289/1296] loginctl: call all verb functions verb_* --- src/login/loginctl.c | 86 +++++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/src/login/loginctl.c b/src/login/loginctl.c index 1ff650c2adbfd..cea702345ecbc 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -266,7 +266,7 @@ static int list_sessions_table_add_fallback(Table *table, sd_bus_message *reply, return 0; } -static int list_sessions(int argc, char *argv[], void *userdata) { +static int verb_list_sessions(int argc, char *argv[], void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; @@ -308,7 +308,7 @@ static int list_sessions(int argc, char *argv[], void *userdata) { return list_table_print(table, "sessions"); } -static int list_users(int argc, char *argv[], void *userdata) { +static int verb_list_users(int argc, char *argv[], void *userdata) { static const struct bus_properties_map property_map[] = { { "Linger", "b", NULL, offsetof(UserStatusInfo, linger) }, @@ -384,7 +384,7 @@ static int list_users(int argc, char *argv[], void *userdata) { return list_table_print(table, "users"); } -static int list_seats(int argc, char *argv[], void *userdata) { +static int verb_list_seats(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(table_unrefp) Table *table = NULL; @@ -1038,7 +1038,7 @@ static int get_bus_path_by_id( return strdup_to(ret, path); } -static int show_session(int argc, char *argv[], void *userdata) { +static int verb_show_session(int argc, char *argv[], void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); bool properties; int r; @@ -1084,7 +1084,7 @@ static int show_session(int argc, char *argv[], void *userdata) { return 0; } -static int show_user(int argc, char *argv[], void *userdata) { +static int verb_show_user(int argc, char *argv[], void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); bool properties; int r; @@ -1135,7 +1135,7 @@ static int show_user(int argc, char *argv[], void *userdata) { return 0; } -static int show_seat(int argc, char *argv[], void *userdata) { +static int verb_show_seat(int argc, char *argv[], void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); bool properties; int r; @@ -1181,7 +1181,7 @@ static int show_seat(int argc, char *argv[], void *userdata) { return 0; } -static int activate(int argc, char *argv[], void *userdata) { +static int verb_activate(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1224,7 +1224,7 @@ static int activate(int argc, char *argv[], void *userdata) { return 0; } -static int kill_session(int argc, char *argv[], void *userdata) { +static int verb_kill_session(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1250,7 +1250,7 @@ static int kill_session(int argc, char *argv[], void *userdata) { return 0; } -static int enable_linger(int argc, char *argv[], void *userdata) { +static int verb_enable_linger(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); char* short_argv[3]; @@ -1298,7 +1298,7 @@ static int enable_linger(int argc, char *argv[], void *userdata) { return 0; } -static int terminate_user(int argc, char *argv[], void *userdata) { +static int verb_terminate_user(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1328,7 +1328,7 @@ static int terminate_user(int argc, char *argv[], void *userdata) { return 0; } -static int kill_user(int argc, char *argv[], void *userdata) { +static int verb_kill_user(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1366,7 +1366,7 @@ static int kill_user(int argc, char *argv[], void *userdata) { return 0; } -static int attach(int argc, char *argv[], void *userdata) { +static int verb_attach(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1390,7 +1390,7 @@ static int attach(int argc, char *argv[], void *userdata) { return 0; } -static int flush_devices(int argc, char *argv[], void *userdata) { +static int verb_flush_devices(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1406,7 +1406,7 @@ static int flush_devices(int argc, char *argv[], void *userdata) { return 0; } -static int lock_sessions(int argc, char *argv[], void *userdata) { +static int verb_lock_sessions(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1427,7 +1427,7 @@ static int lock_sessions(int argc, char *argv[], void *userdata) { return 0; } -static int terminate_seat(int argc, char *argv[], void *userdata) { +static int verb_terminate_seat(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1446,7 +1446,7 @@ static int terminate_seat(int argc, char *argv[], void *userdata) { return 0; } -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -1519,6 +1519,10 @@ static int help(int argc, char *argv[], void *userdata) { return 0; } +static int verb_help(int argc, char *argv[], void *userdata) { + return help(); +} + static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, @@ -1560,7 +1564,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - return help(0, NULL, NULL); + return help(); case ARG_VERSION: return version(); @@ -1669,30 +1673,30 @@ static int parse_argv(int argc, char *argv[]) { static int loginctl_main(int argc, char *argv[], sd_bus *bus) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "list-sessions", VERB_ANY, 1, VERB_DEFAULT, list_sessions }, - { "session-status", VERB_ANY, VERB_ANY, 0, show_session }, - { "show-session", VERB_ANY, VERB_ANY, 0, show_session }, - { "activate", VERB_ANY, 2, 0, activate }, - { "lock-session", VERB_ANY, VERB_ANY, 0, activate }, - { "unlock-session", VERB_ANY, VERB_ANY, 0, activate }, - { "lock-sessions", VERB_ANY, 1, 0, lock_sessions }, - { "unlock-sessions", VERB_ANY, 1, 0, lock_sessions }, - { "terminate-session", 2, VERB_ANY, 0, activate }, - { "kill-session", 2, VERB_ANY, 0, kill_session }, - { "list-users", VERB_ANY, 1, 0, list_users }, - { "user-status", VERB_ANY, VERB_ANY, 0, show_user }, - { "show-user", VERB_ANY, VERB_ANY, 0, show_user }, - { "enable-linger", VERB_ANY, VERB_ANY, 0, enable_linger }, - { "disable-linger", VERB_ANY, VERB_ANY, 0, enable_linger }, - { "terminate-user", 2, VERB_ANY, 0, terminate_user }, - { "kill-user", 2, VERB_ANY, 0, kill_user }, - { "list-seats", VERB_ANY, 1, 0, list_seats }, - { "seat-status", VERB_ANY, VERB_ANY, 0, show_seat }, - { "show-seat", VERB_ANY, VERB_ANY, 0, show_seat }, - { "attach", 3, VERB_ANY, 0, attach }, - { "flush-devices", VERB_ANY, 1, 0, flush_devices }, - { "terminate-seat", 2, VERB_ANY, 0, terminate_seat }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, + { "list-sessions", VERB_ANY, 1, VERB_DEFAULT, verb_list_sessions }, + { "session-status", VERB_ANY, VERB_ANY, 0, verb_show_session }, + { "show-session", VERB_ANY, VERB_ANY, 0, verb_show_session }, + { "activate", VERB_ANY, 2, 0, verb_activate }, + { "lock-session", VERB_ANY, VERB_ANY, 0, verb_activate }, + { "unlock-session", VERB_ANY, VERB_ANY, 0, verb_activate }, + { "lock-sessions", VERB_ANY, 1, 0, verb_lock_sessions }, + { "unlock-sessions", VERB_ANY, 1, 0, verb_lock_sessions }, + { "terminate-session", 2, VERB_ANY, 0, verb_activate }, + { "kill-session", 2, VERB_ANY, 0, verb_kill_session }, + { "list-users", VERB_ANY, 1, 0, verb_list_users }, + { "user-status", VERB_ANY, VERB_ANY, 0, verb_show_user }, + { "show-user", VERB_ANY, VERB_ANY, 0, verb_show_user }, + { "enable-linger", VERB_ANY, VERB_ANY, 0, verb_enable_linger }, + { "disable-linger", VERB_ANY, VERB_ANY, 0, verb_enable_linger }, + { "terminate-user", 2, VERB_ANY, 0, verb_terminate_user }, + { "kill-user", 2, VERB_ANY, 0, verb_kill_user }, + { "list-seats", VERB_ANY, 1, 0, verb_list_seats }, + { "seat-status", VERB_ANY, VERB_ANY, 0, verb_show_seat }, + { "show-seat", VERB_ANY, VERB_ANY, 0, verb_show_seat }, + { "attach", 3, VERB_ANY, 0, verb_attach }, + { "flush-devices", VERB_ANY, 1, 0, verb_flush_devices }, + { "terminate-seat", 2, VERB_ANY, 0, verb_terminate_seat }, {} }; From 82ac3a24eedb45cd25121035f86dbad596759a3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 12:51:19 +0100 Subject: [PATCH 0290/1296] importctl: call all verb functions verb_* --- src/import/importctl.c | 54 +++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/src/import/importctl.c b/src/import/importctl.c index c2ddd08f27624..712c2a7b64959 100644 --- a/src/import/importctl.c +++ b/src/import/importctl.c @@ -261,7 +261,7 @@ static int transfer_image_common(sd_bus *bus, sd_bus_message *m) { return -r; } -static int import_tar(int argc, char *argv[], void *userdata) { +static int verb_import_tar(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *ll = NULL, *fn = NULL; const char *local = NULL, *path = NULL; @@ -340,7 +340,7 @@ static int import_tar(int argc, char *argv[], void *userdata) { return transfer_image_common(bus, m); } -static int import_raw(int argc, char *argv[], void *userdata) { +static int verb_import_raw(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *ll = NULL, *fn = NULL; const char *local = NULL, *path = NULL; @@ -419,7 +419,7 @@ static int import_raw(int argc, char *argv[], void *userdata) { return transfer_image_common(bus, m); } -static int import_fs(int argc, char *argv[], void *userdata) { +static int verb_import_fs(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; const char *local = NULL, *path = NULL; _cleanup_free_ char *fn = NULL; @@ -506,7 +506,7 @@ static void determine_compression_from_filename(const char *p) { arg_format = "zstd"; } -static int export_tar(int argc, char *argv[], void *userdata) { +static int verb_export_tar(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_close_ int fd = -EBADF; const char *local = NULL, *path = NULL; @@ -565,7 +565,7 @@ static int export_tar(int argc, char *argv[], void *userdata) { return transfer_image_common(bus, m); } -static int export_raw(int argc, char *argv[], void *userdata) { +static int verb_export_raw(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_close_ int fd = -EBADF; const char *local = NULL, *path = NULL; @@ -624,7 +624,7 @@ static int export_raw(int argc, char *argv[], void *userdata) { return transfer_image_common(bus, m); } -static int pull_tar(int argc, char *argv[], void *userdata) { +static int verb_pull_tar(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *l = NULL, *ll = NULL; const char *local, *remote; @@ -697,7 +697,7 @@ static int pull_tar(int argc, char *argv[], void *userdata) { return transfer_image_common(bus, m); } -static int pull_raw(int argc, char *argv[], void *userdata) { +static int verb_pull_raw(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *l = NULL, *ll = NULL; const char *local, *remote; @@ -770,7 +770,7 @@ static int pull_raw(int argc, char *argv[], void *userdata) { return transfer_image_common(bus, m); } -static int pull_oci(int argc, char *argv[], void *userdata) { +static int verb_pull_oci(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *l = NULL; const char *local, *remote; @@ -825,7 +825,7 @@ static int pull_oci(int argc, char *argv[], void *userdata) { return transfer_image_common(bus, m); } -static int list_transfers(int argc, char *argv[], void *userdata) { +static int verb_list_transfers(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(table_unrefp) Table *t = NULL; @@ -929,7 +929,7 @@ static int list_transfers(int argc, char *argv[], void *userdata) { return 0; } -static int cancel_transfer(int argc, char *argv[], void *userdata) { +static int verb_cancel_transfer(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -951,7 +951,7 @@ static int cancel_transfer(int argc, char *argv[], void *userdata) { return 0; } -static int list_images(int argc, char *argv[], void *userdata) { +static int verb_list_images(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(table_unrefp) Table *t = NULL; @@ -1048,7 +1048,7 @@ static int list_images(int argc, char *argv[], void *userdata) { return 0; } -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -1112,6 +1112,10 @@ static int help(int argc, char *argv[], void *userdata) { return 0; } +static int verb_help(int argc, char *argv[], void *userdata) { + return help(); +} + static int parse_argv(int argc, char *argv[]) { enum { @@ -1164,7 +1168,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - return help(0, NULL, NULL); + return help(); case ARG_VERSION: return version(); @@ -1295,18 +1299,18 @@ static int parse_argv(int argc, char *argv[]) { static int importctl_main(int argc, char *argv[], sd_bus *bus) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "import-tar", 2, 3, 0, import_tar }, - { "import-raw", 2, 3, 0, import_raw }, - { "import-fs", 2, 3, 0, import_fs }, - { "export-tar", 2, 3, 0, export_tar }, - { "export-raw", 2, 3, 0, export_raw }, - { "pull-tar", 2, 3, 0, pull_tar }, - { "pull-oci", 2, 3, 0, pull_oci }, - { "pull-raw", 2, 3, 0, pull_raw }, - { "list-transfers", VERB_ANY, 1, VERB_DEFAULT, list_transfers }, - { "cancel-transfer", 2, VERB_ANY, 0, cancel_transfer }, - { "list-images", VERB_ANY, 1, 0, list_images }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, + { "import-tar", 2, 3, 0, verb_import_tar }, + { "import-raw", 2, 3, 0, verb_import_raw }, + { "import-fs", 2, 3, 0, verb_import_fs }, + { "export-tar", 2, 3, 0, verb_export_tar }, + { "export-raw", 2, 3, 0, verb_export_raw }, + { "pull-tar", 2, 3, 0, verb_pull_tar }, + { "pull-oci", 2, 3, 0, verb_pull_oci }, + { "pull-raw", 2, 3, 0, verb_pull_raw }, + { "list-transfers", VERB_ANY, 1, VERB_DEFAULT, verb_list_transfers }, + { "cancel-transfer", 2, VERB_ANY, 0, verb_cancel_transfer }, + { "list-images", VERB_ANY, 1, 0, verb_list_images }, {} }; From 65c10b815b4166c38991d69a393e97da92db57e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 12:56:03 +0100 Subject: [PATCH 0291/1296] localectl: call all verb functions verb_* --- src/locale/localectl.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/locale/localectl.c b/src/locale/localectl.c index 63703007ad527..94c0e8424dd07 100644 --- a/src/locale/localectl.c +++ b/src/locale/localectl.c @@ -151,7 +151,7 @@ static int print_status_info(StatusInfo *i) { return 0; } -static int show_status(int argc, char **argv, void *userdata) { +static int verb_show_status(int argc, char **argv, void *userdata) { _cleanup_(status_info_clear) StatusInfo info = {}; static const struct bus_properties_map map[] = { { "VConsoleKeymap", "s", NULL, offsetof(StatusInfo, vconsole_keymap) }, @@ -183,7 +183,7 @@ static int show_status(int argc, char **argv, void *userdata) { return print_status_info(&info); } -static int set_locale(int argc, char **argv, void *userdata) { +static int verb_set_locale(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); @@ -211,7 +211,7 @@ static int set_locale(int argc, char **argv, void *userdata) { return 0; } -static int list_locales(int argc, char **argv, void *userdata) { +static int verb_list_locales(int argc, char **argv, void *userdata) { _cleanup_strv_free_ char **l = NULL; int r; @@ -225,7 +225,7 @@ static int list_locales(int argc, char **argv, void *userdata) { return 0; } -static int set_vconsole_keymap(int argc, char **argv, void *userdata) { +static int verb_set_vconsole_keymap(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; const char *map, *toggle_map; sd_bus *bus = ASSERT_PTR(userdata); @@ -249,7 +249,7 @@ static int set_vconsole_keymap(int argc, char **argv, void *userdata) { return 0; } -static int list_vconsole_keymaps(int argc, char **argv, void *userdata) { +static int verb_list_vconsole_keymaps(int argc, char **argv, void *userdata) { _cleanup_strv_free_ char **l = NULL; int r; @@ -264,7 +264,7 @@ static int list_vconsole_keymaps(int argc, char **argv, void *userdata) { return 0; } -static int set_x11_keymap(int argc, char **argv, void *userdata) { +static int verb_set_x11_keymap(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; const char *layout, *model, *variant, *options; sd_bus *bus = userdata; @@ -299,7 +299,7 @@ static const char* xkb_directory(void) { return cached; } -static int list_x11_keymaps(int argc, char **argv, void *userdata) { +static int verb_list_x11_keymaps(int argc, char **argv, void *userdata) { _cleanup_fclose_ FILE *f = NULL; _cleanup_strv_free_ char **list = NULL; enum { @@ -526,17 +526,17 @@ static int parse_argv(int argc, char *argv[]) { static int localectl_main(sd_bus *bus, int argc, char *argv[]) { static const Verb verbs[] = { - { "status", VERB_ANY, 1, VERB_DEFAULT, show_status }, - { "set-locale", 2, VERB_ANY, 0, set_locale }, - { "list-locales", VERB_ANY, 1, 0, list_locales }, - { "set-keymap", 2, 3, 0, set_vconsole_keymap }, - { "list-keymaps", VERB_ANY, 1, 0, list_vconsole_keymaps }, - { "set-x11-keymap", 2, 5, 0, set_x11_keymap }, - { "list-x11-keymap-models", VERB_ANY, 1, 0, list_x11_keymaps }, - { "list-x11-keymap-layouts", VERB_ANY, 1, 0, list_x11_keymaps }, - { "list-x11-keymap-variants", VERB_ANY, 2, 0, list_x11_keymaps }, - { "list-x11-keymap-options", VERB_ANY, 1, 0, list_x11_keymaps }, - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, /* Not documented, but supported since it is created. */ + { "status", VERB_ANY, 1, VERB_DEFAULT, verb_show_status }, + { "set-locale", 2, VERB_ANY, 0, verb_set_locale }, + { "list-locales", VERB_ANY, 1, 0, verb_list_locales }, + { "set-keymap", 2, 3, 0, verb_set_vconsole_keymap }, + { "list-keymaps", VERB_ANY, 1, 0, verb_list_vconsole_keymaps }, + { "set-x11-keymap", 2, 5, 0, verb_set_x11_keymap }, + { "list-x11-keymap-models", VERB_ANY, 1, 0, verb_list_x11_keymaps }, + { "list-x11-keymap-layouts", VERB_ANY, 1, 0, verb_list_x11_keymaps }, + { "list-x11-keymap-variants", VERB_ANY, 2, 0, verb_list_x11_keymaps }, + { "list-x11-keymap-options", VERB_ANY, 1, 0, verb_list_x11_keymaps }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, /* Not documented, but supported since it has been created. */ {} }; From 165e36e32039f858b67c026226bd1ebea188d910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 12:56:09 +0100 Subject: [PATCH 0292/1296] hostnamectl: call all verb functions verb_* --- src/hostname/hostnamectl.c | 40 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index ae1af41425703..c9d83031b9204 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -549,7 +549,7 @@ static int get_hostname_based_on_flag(sd_bus *bus) { return get_one_name(bus, attr, NULL); } -static int show_status(int argc, char **argv, void *userdata) { +static int verb_show_status(int argc, char **argv, void *userdata) { sd_bus *bus = userdata; int r; @@ -601,7 +601,7 @@ static int set_simple_string(sd_bus *bus, const char *target, const char *method return set_simple_string_internal(bus, NULL, target, method, value); } -static int set_hostname(int argc, char **argv, void *userdata) { +static int verb_set_hostname(int argc, char **argv, void *userdata) { _cleanup_free_ char *h = NULL; const char *hostname = argv[1]; sd_bus *bus = userdata; @@ -687,27 +687,27 @@ static int set_hostname(int argc, char **argv, void *userdata) { return ret; } -static int get_or_set_hostname(int argc, char **argv, void *userdata) { +static int verb_get_or_set_hostname(int argc, char **argv, void *userdata) { return argc == 1 ? get_hostname_based_on_flag(userdata) : - set_hostname(argc, argv, userdata); + verb_set_hostname(argc, argv, userdata); } -static int get_or_set_icon_name(int argc, char **argv, void *userdata) { +static int verb_get_or_set_icon_name(int argc, char **argv, void *userdata) { return argc == 1 ? get_one_name(userdata, "IconName", NULL) : set_simple_string(userdata, "icon", "SetIconName", argv[1]); } -static int get_or_set_chassis(int argc, char **argv, void *userdata) { +static int verb_get_or_set_chassis(int argc, char **argv, void *userdata) { return argc == 1 ? get_one_name(userdata, "Chassis", NULL) : set_simple_string(userdata, "chassis", "SetChassis", argv[1]); } -static int get_or_set_deployment(int argc, char **argv, void *userdata) { +static int verb_get_or_set_deployment(int argc, char **argv, void *userdata) { return argc == 1 ? get_one_name(userdata, "Deployment", NULL) : set_simple_string(userdata, "deployment", "SetDeployment", argv[1]); } -static int get_or_set_location(int argc, char **argv, void *userdata) { +static int verb_get_or_set_location(int argc, char **argv, void *userdata) { return argc == 1 ? get_one_name(userdata, "Location", NULL) : set_simple_string(userdata, "location", "SetLocation", argv[1]); } @@ -846,18 +846,18 @@ static int parse_argv(int argc, char *argv[]) { static int hostnamectl_main(sd_bus *bus, int argc, char *argv[]) { static const Verb verbs[] = { - { "status", VERB_ANY, 1, VERB_DEFAULT, show_status }, - { "hostname", VERB_ANY, 2, 0, get_or_set_hostname }, - { "set-hostname", 2, 2, 0, get_or_set_hostname }, /* obsolete */ - { "icon-name", VERB_ANY, 2, 0, get_or_set_icon_name }, - { "set-icon-name", 2, 2, 0, get_or_set_icon_name }, /* obsolete */ - { "chassis", VERB_ANY, 2, 0, get_or_set_chassis }, - { "set-chassis", 2, 2, 0, get_or_set_chassis }, /* obsolete */ - { "deployment", VERB_ANY, 2, 0, get_or_set_deployment }, - { "set-deployment", 2, 2, 0, get_or_set_deployment }, /* obsolete */ - { "location", VERB_ANY, 2, 0, get_or_set_location }, - { "set-location", 2, 2, 0, get_or_set_location }, /* obsolete */ - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, /* Not documented, but supported since it is created. */ + { "status", VERB_ANY, 1, VERB_DEFAULT, verb_show_status }, + { "hostname", VERB_ANY, 2, 0, verb_get_or_set_hostname }, + { "set-hostname", 2, 2, 0, verb_get_or_set_hostname }, /* obsolete */ + { "icon-name", VERB_ANY, 2, 0, verb_get_or_set_icon_name }, + { "set-icon-name", 2, 2, 0, verb_get_or_set_icon_name }, /* obsolete */ + { "chassis", VERB_ANY, 2, 0, verb_get_or_set_chassis }, + { "set-chassis", 2, 2, 0, verb_get_or_set_chassis }, /* obsolete */ + { "deployment", VERB_ANY, 2, 0, verb_get_or_set_deployment }, + { "set-deployment", 2, 2, 0, verb_get_or_set_deployment }, /* obsolete */ + { "location", VERB_ANY, 2, 0, verb_get_or_set_location }, + { "set-location", 2, 2, 0, verb_get_or_set_location }, /* obsolete */ + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, /* Not documented, but supported since it is created. */ {} }; From 3d72ffc4151df5ac0e5d975c2b4f038cb4765dca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 13:00:39 +0100 Subject: [PATCH 0293/1296] machinectl: call all verb functions verb_* --- src/machine/machinectl.c | 118 ++++++++++++++++++++------------------- 1 file changed, 61 insertions(+), 57 deletions(-) diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 9b2056918c03c..f71d3deee2ab4 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -277,7 +277,7 @@ static int show_table(Table *table, const char *word) { return 0; } -static int list_machines(int argc, char *argv[], void *userdata) { +static int verb_list_machines(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(table_unrefp) Table *table = NULL; @@ -354,7 +354,7 @@ static int list_machines(int argc, char *argv[], void *userdata) { return show_table(table, "machines"); } -static int list_images(int argc, char *argv[], void *userdata) { +static int verb_list_images(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; @@ -745,7 +745,7 @@ static int show_machine_properties(sd_bus *bus, const char *path, bool *new_line return r; } -static int show_machine(int argc, char *argv[], void *userdata) { +static int verb_show_machine(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; bool properties, new_line = false; sd_bus *bus = ASSERT_PTR(userdata); @@ -1036,7 +1036,7 @@ static int show_image_properties(sd_bus *bus, const char *path, bool *new_line) return r; } -static int show_image(int argc, char *argv[], void *userdata) { +static int verb_show_image(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; bool properties, new_line = false; sd_bus *bus = ASSERT_PTR(userdata); @@ -1080,7 +1080,7 @@ static int show_image(int argc, char *argv[], void *userdata) { return r; } -static int kill_machine(int argc, char *argv[], void *userdata) { +static int verb_kill_machine(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1105,7 +1105,7 @@ static int kill_machine(int argc, char *argv[], void *userdata) { return 0; } -static int reboot_machine(int argc, char *argv[], void *userdata) { +static int verb_reboot_machine(int argc, char *argv[], void *userdata) { if (arg_runner == RUNNER_VMSPAWN) return log_error_errno( SYNTHETIC_ERRNO(EOPNOTSUPP), @@ -1115,17 +1115,17 @@ static int reboot_machine(int argc, char *argv[], void *userdata) { arg_kill_whom = "leader"; arg_signal = SIGINT; /* sysvinit + systemd */ - return kill_machine(argc, argv, userdata); + return verb_kill_machine(argc, argv, userdata); } -static int poweroff_machine(int argc, char *argv[], void *userdata) { +static int verb_poweroff_machine(int argc, char *argv[], void *userdata) { arg_kill_whom = "leader"; arg_signal = SIGRTMIN+4; /* only systemd */ - return kill_machine(argc, argv, userdata); + return verb_kill_machine(argc, argv, userdata); } -static int terminate_machine(int argc, char *argv[], void *userdata) { +static int verb_terminate_machine(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1148,7 +1148,7 @@ static const char *select_copy_method(bool copy_from, bool force) { return copy_from ? "CopyFromMachine" : "CopyToMachine"; } -static int copy_files(int argc, char *argv[], void *userdata) { +static int verb_copy_files(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *abs_host_path = NULL; @@ -1203,7 +1203,7 @@ static int copy_files(int argc, char *argv[], void *userdata) { return 0; } -static int bind_mount(int argc, char *argv[], void *userdata) { +static int verb_bind_mount(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1336,7 +1336,7 @@ static int parse_machine_uid(const char *spec, const char **machine, char **uid) return 0; } -static int login_machine(int argc, char *argv[], void *userdata) { +static int verb_login_machine(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; @@ -1387,7 +1387,7 @@ static int login_machine(int argc, char *argv[], void *userdata) { return process_forward(event, slot, master, PTY_FORWARD_IGNORE_VHANGUP, machine); } -static int shell_machine(int argc, char *argv[], void *userdata) { +static int verb_shell_machine(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; @@ -1515,7 +1515,7 @@ static int get_settings_path(const char *name, char **ret_path) { return -ENOENT; } -static int edit_settings(int argc, char *argv[], void *userdata) { +static int verb_edit_settings(int argc, char *argv[], void *userdata) { _cleanup_(edit_file_context_done) EditFileContext context = {}; int r; @@ -1585,7 +1585,7 @@ static int edit_settings(int argc, char *argv[], void *userdata) { return do_edit_files_and_install(&context); } -static int cat_settings(int argc, char *argv[], void *userdata) { +static int verb_cat_settings(int argc, char *argv[], void *userdata) { int r = 0; if (arg_transport != BUS_TRANSPORT_LOCAL) @@ -1636,7 +1636,7 @@ static int cat_settings(int argc, char *argv[], void *userdata) { return r; } -static int remove_image(int argc, char *argv[], void *userdata) { +static int verb_remove_image(int argc, char *argv[], void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1663,7 +1663,7 @@ static int remove_image(int argc, char *argv[], void *userdata) { return 0; } -static int rename_image(int argc, char *argv[], void *userdata) { +static int verb_rename_image(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1683,7 +1683,7 @@ static int rename_image(int argc, char *argv[], void *userdata) { return 0; } -static int clone_image(int argc, char *argv[], void *userdata) { +static int verb_clone_image(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; sd_bus *bus = ASSERT_PTR(userdata); @@ -1707,7 +1707,7 @@ static int clone_image(int argc, char *argv[], void *userdata) { return 0; } -static int read_only_image(int argc, char *argv[], void *userdata) { +static int verb_read_only_image(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int b = true, r; @@ -1765,7 +1765,7 @@ static int make_service_name(const char *name, char **ret) { return 0; } -static int start_machine(int argc, char *argv[], void *userdata) { +static int verb_start_machine(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; sd_bus *bus = ASSERT_PTR(userdata); @@ -1821,7 +1821,7 @@ static int start_machine(int argc, char *argv[], void *userdata) { return 0; } -static int enable_machine(int argc, char *argv[], void *userdata) { +static int verb_enable_machine(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; const char *method; @@ -1909,15 +1909,15 @@ static int enable_machine(int argc, char *argv[], void *userdata) { return log_oom(); if (enable) - return start_machine(strv_length(new_args), new_args, userdata); + return verb_start_machine(strv_length(new_args), new_args, userdata); - return poweroff_machine(strv_length(new_args), new_args, userdata); + return verb_poweroff_machine(strv_length(new_args), new_args, userdata); } return 0; } -static int set_limit(int argc, char *argv[], void *userdata) { +static int verb_set_limit(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = userdata; uint64_t limit; @@ -1947,7 +1947,7 @@ static int set_limit(int argc, char *argv[], void *userdata) { return 0; } -static int clean_images(int argc, char *argv[], void *userdata) { +static int verb_clean_images(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; uint64_t usage, total = 0; @@ -2048,7 +2048,7 @@ static int chainload_importctl(int argc, char *argv[]) { return log_error_errno(r, "Failed to invoke 'importctl': %m"); } -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -2137,6 +2137,10 @@ static int help(int argc, char *argv[], void *userdata) { return 0; } +static int verb_help(int argc, char *argv[], void *userdata) { + return help(); +} + static int parse_argv(int argc, char *argv[]) { enum { @@ -2257,7 +2261,7 @@ static int parse_argv(int argc, char *argv[]) { break; case 'h': - return help(0, NULL, NULL); + return help(); case ARG_VERSION: return version(); @@ -2444,35 +2448,35 @@ static int parse_argv(int argc, char *argv[]) { static int machinectl_main(int argc, char *argv[], sd_bus *bus) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "list", VERB_ANY, 1, VERB_DEFAULT, list_machines }, - { "list-images", VERB_ANY, 1, 0, list_images }, - { "status", 2, VERB_ANY, 0, show_machine }, - { "image-status", VERB_ANY, VERB_ANY, 0, show_image }, - { "show", VERB_ANY, VERB_ANY, 0, show_machine }, - { "show-image", VERB_ANY, VERB_ANY, 0, show_image }, - { "terminate", 2, VERB_ANY, 0, terminate_machine }, - { "reboot", 2, VERB_ANY, 0, reboot_machine }, - { "restart", 2, VERB_ANY, 0, reboot_machine }, /* Convenience alias */ - { "poweroff", 2, VERB_ANY, 0, poweroff_machine }, - { "stop", 2, VERB_ANY, 0, poweroff_machine }, /* Convenience alias */ - { "kill", 2, VERB_ANY, 0, kill_machine }, - { "login", VERB_ANY, 2, 0, login_machine }, - { "shell", VERB_ANY, VERB_ANY, 0, shell_machine }, - { "bind", 3, 4, 0, bind_mount }, - { "edit", 2, VERB_ANY, 0, edit_settings }, - { "cat", 2, VERB_ANY, 0, cat_settings }, - { "copy-to", 3, 4, 0, copy_files }, - { "copy-from", 3, 4, 0, copy_files }, - { "remove", 2, VERB_ANY, 0, remove_image }, - { "rename", 3, 3, 0, rename_image }, - { "clone", 3, 3, 0, clone_image }, - { "read-only", 2, 3, 0, read_only_image }, - { "start", 2, VERB_ANY, 0, start_machine }, - { "enable", 2, VERB_ANY, 0, enable_machine }, - { "disable", 2, VERB_ANY, 0, enable_machine }, - { "set-limit", 2, 3, 0, set_limit }, - { "clean", VERB_ANY, 1, 0, clean_images }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, + { "list", VERB_ANY, 1, VERB_DEFAULT, verb_list_machines }, + { "list-images", VERB_ANY, 1, 0, verb_list_images }, + { "status", 2, VERB_ANY, 0, verb_show_machine }, + { "image-status", VERB_ANY, VERB_ANY, 0, verb_show_image }, + { "show", VERB_ANY, VERB_ANY, 0, verb_show_machine }, + { "show-image", VERB_ANY, VERB_ANY, 0, verb_show_image }, + { "terminate", 2, VERB_ANY, 0, verb_terminate_machine }, + { "reboot", 2, VERB_ANY, 0, verb_reboot_machine }, + { "restart", 2, VERB_ANY, 0, verb_reboot_machine }, /* Convenience alias */ + { "poweroff", 2, VERB_ANY, 0, verb_poweroff_machine }, + { "stop", 2, VERB_ANY, 0, verb_poweroff_machine }, /* Convenience alias */ + { "kill", 2, VERB_ANY, 0, verb_kill_machine }, + { "login", VERB_ANY, 2, 0, verb_login_machine }, + { "shell", VERB_ANY, VERB_ANY, 0, verb_shell_machine }, + { "bind", 3, 4, 0, verb_bind_mount }, + { "edit", 2, VERB_ANY, 0, verb_edit_settings }, + { "cat", 2, VERB_ANY, 0, verb_cat_settings }, + { "copy-to", 3, 4, 0, verb_copy_files }, + { "copy-from", 3, 4, 0, verb_copy_files }, + { "remove", 2, VERB_ANY, 0, verb_remove_image }, + { "rename", 3, 3, 0, verb_rename_image }, + { "clone", 3, 3, 0, verb_clone_image }, + { "read-only", 2, 3, 0, verb_read_only_image }, + { "start", 2, VERB_ANY, 0, verb_start_machine }, + { "enable", 2, VERB_ANY, 0, verb_enable_machine }, + { "disable", 2, VERB_ANY, 0, verb_enable_machine }, + { "set-limit", 2, 3, 0, verb_set_limit }, + { "clean", VERB_ANY, 1, 0, verb_clean_images }, {} }; From 26216a7e95a4a2f10c7ffe66a2928370392264fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 13:05:09 +0100 Subject: [PATCH 0294/1296] networkctl: call all verb functions verb_* --- src/network/networkctl-address-label.c | 2 +- src/network/networkctl-address-label.h | 2 +- src/network/networkctl-list.c | 2 +- src/network/networkctl-list.h | 2 +- src/network/networkctl-lldp.c | 2 +- src/network/networkctl-lldp.h | 2 +- src/network/networkctl-misc.c | 6 ++--- src/network/networkctl-misc.h | 6 ++--- src/network/networkctl-status-link.c | 2 +- src/network/networkctl-status-link.h | 2 +- src/network/networkctl.c | 32 +++++++++++++------------- 11 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/network/networkctl-address-label.c b/src/network/networkctl-address-label.c index 739c1d2e83964..048159bd2c26c 100644 --- a/src/network/networkctl-address-label.c +++ b/src/network/networkctl-address-label.c @@ -89,7 +89,7 @@ static int dump_address_labels(sd_netlink *rtnl) { return 0; } -int list_address_labels(int argc, char *argv[], void *userdata) { +int verb_list_address_labels(int argc, char *argv[], void *userdata) { _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; int r; diff --git a/src/network/networkctl-address-label.h b/src/network/networkctl-address-label.h index eb3c722744f1e..a5584806fbf3f 100644 --- a/src/network/networkctl-address-label.h +++ b/src/network/networkctl-address-label.h @@ -1,4 +1,4 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int list_address_labels(int argc, char *argv[], void *userdata); +int verb_list_address_labels(int argc, char *argv[], void *userdata); diff --git a/src/network/networkctl-list.c b/src/network/networkctl-list.c index 1ac6ad39a8a7e..a0b7b9a21f2ff 100644 --- a/src/network/networkctl-list.c +++ b/src/network/networkctl-list.c @@ -12,7 +12,7 @@ #include "networkctl-list.h" #include "networkctl-util.h" -int list_links(int argc, char *argv[], void *userdata) { +int verb_list_links(int argc, char *argv[], void *userdata) { _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; _cleanup_(link_info_array_freep) LinkInfo *links = NULL; _cleanup_(table_unrefp) Table *table = NULL; diff --git a/src/network/networkctl-list.h b/src/network/networkctl-list.h index 0ee8dba8941c7..cb418e1cbc7d1 100644 --- a/src/network/networkctl-list.h +++ b/src/network/networkctl-list.h @@ -1,4 +1,4 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int list_links(int argc, char *argv[], void *userdata); +int verb_list_links(int argc, char *argv[], void *userdata); diff --git a/src/network/networkctl-lldp.c b/src/network/networkctl-lldp.c index 300f7b26df975..433a5fc922606 100644 --- a/src/network/networkctl-lldp.c +++ b/src/network/networkctl-lldp.c @@ -219,7 +219,7 @@ static int dump_lldp_neighbors_json(sd_json_variant *reply, char * const *patter return sd_json_variant_dump(v, arg_json_format_flags, NULL, NULL); } -int link_lldp_status(int argc, char *argv[], void *userdata) { +int verb_link_lldp_status(int argc, char *argv[], void *userdata) { _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL; _cleanup_(table_unrefp) Table *table = NULL; sd_json_variant *reply; diff --git a/src/network/networkctl-lldp.h b/src/network/networkctl-lldp.h index 2225fc3abdc53..63f89a6165486 100644 --- a/src/network/networkctl-lldp.h +++ b/src/network/networkctl-lldp.h @@ -4,4 +4,4 @@ #include "shared-forward.h" int dump_lldp_neighbors(sd_varlink *vl, Table *table, int ifindex); -int link_lldp_status(int argc, char *argv[], void *userdata); +int verb_link_lldp_status(int argc, char *argv[], void *userdata); diff --git a/src/network/networkctl-misc.c b/src/network/networkctl-misc.c index 73c79494a073b..47323b1d9faa6 100644 --- a/src/network/networkctl-misc.c +++ b/src/network/networkctl-misc.c @@ -46,7 +46,7 @@ static int parse_interfaces(sd_netlink **rtnl, char *argv[], OrderedSet **ret) { return 0; } -int link_up_down(int argc, char *argv[], void *userdata) { +int verb_link_up_down(int argc, char *argv[], void *userdata) { int r, ret = 0; bool up = streq_ptr(argv[0], "up"); @@ -75,7 +75,7 @@ int link_up_down(int argc, char *argv[], void *userdata) { return ret; } -int link_delete(int argc, char *argv[], void *userdata) { +int verb_link_delete(int argc, char *argv[], void *userdata) { int r, ret = 0; _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; @@ -104,7 +104,7 @@ int link_delete(int argc, char *argv[], void *userdata) { return ret; } -int link_bus_simple_method(int argc, char *argv[], void *userdata) { +int verb_link_bus_simple_method(int argc, char *argv[], void *userdata) { int r, ret = 0; typedef struct LinkBusAction { diff --git a/src/network/networkctl-misc.h b/src/network/networkctl-misc.h index 4860c99ce393c..df7a6a6fc052e 100644 --- a/src/network/networkctl-misc.h +++ b/src/network/networkctl-misc.h @@ -1,8 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int link_up_down(int argc, char *argv[], void *userdata); -int link_delete(int argc, char *argv[], void *userdata); -int link_bus_simple_method(int argc, char *argv[], void *userdata); +int verb_link_up_down(int argc, char *argv[], void *userdata); +int verb_link_delete(int argc, char *argv[], void *userdata); +int verb_link_bus_simple_method(int argc, char *argv[], void *userdata); int verb_reload(int argc, char *argv[], void *userdata); int verb_persistent_storage(int argc, char *argv[], void *userdata); diff --git a/src/network/networkctl-status-link.c b/src/network/networkctl-status-link.c index 62f57c2b215d5..e2db2c7553909 100644 --- a/src/network/networkctl-status-link.c +++ b/src/network/networkctl-status-link.c @@ -939,7 +939,7 @@ static int link_status_one( return show_logs(info->ifindex, info->name); } -int link_status(int argc, char *argv[], void *userdata) { +int verb_link_status(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; diff --git a/src/network/networkctl-status-link.h b/src/network/networkctl-status-link.h index 1c1b4ea75a385..70cbf4f1604d0 100644 --- a/src/network/networkctl-status-link.h +++ b/src/network/networkctl-status-link.h @@ -1,4 +1,4 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int link_status(int argc, char *argv[], void *userdata); +int verb_link_status(int argc, char *argv[], void *userdata); diff --git a/src/network/networkctl.c b/src/network/networkctl.c index 68acdcf60a7a2..91379cc9308f5 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -223,22 +223,22 @@ static int parse_argv(int argc, char *argv[]) { static int networkctl_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "list", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_ONLINE_ONLY, list_links }, - { "status", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, link_status }, - { "lldp", VERB_ANY, VERB_ANY, 0, link_lldp_status }, - { "label", 1, 1, 0, list_address_labels }, - { "delete", 2, VERB_ANY, 0, link_delete }, - { "up", 2, VERB_ANY, 0, link_up_down }, - { "down", 2, VERB_ANY, 0, link_up_down }, - { "renew", 2, VERB_ANY, VERB_ONLINE_ONLY, link_bus_simple_method }, - { "forcerenew", 2, VERB_ANY, VERB_ONLINE_ONLY, link_bus_simple_method }, - { "reconfigure", 2, VERB_ANY, VERB_ONLINE_ONLY, link_bus_simple_method }, - { "reload", 1, 1, VERB_ONLINE_ONLY, verb_reload }, - { "edit", 2, VERB_ANY, 0, verb_edit }, - { "cat", 1, VERB_ANY, 0, verb_cat }, - { "mask", 2, VERB_ANY, 0, verb_mask }, - { "unmask", 2, VERB_ANY, 0, verb_unmask }, - { "persistent-storage", 2, 2, 0, verb_persistent_storage }, + { "list", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_ONLINE_ONLY, verb_list_links }, + { "status", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_link_status }, + { "lldp", VERB_ANY, VERB_ANY, 0, verb_link_lldp_status }, + { "label", 1, 1, 0, verb_list_address_labels }, + { "delete", 2, VERB_ANY, 0, verb_link_delete }, + { "up", 2, VERB_ANY, 0, verb_link_up_down }, + { "down", 2, VERB_ANY, 0, verb_link_up_down }, + { "renew", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_link_bus_simple_method }, + { "forcerenew", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_link_bus_simple_method }, + { "reconfigure", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_link_bus_simple_method }, + { "reload", 1, 1, VERB_ONLINE_ONLY, verb_reload }, + { "edit", 2, VERB_ANY, 0, verb_edit }, + { "cat", 1, VERB_ANY, 0, verb_cat }, + { "mask", 2, VERB_ANY, 0, verb_mask }, + { "unmask", 2, VERB_ANY, 0, verb_unmask }, + { "persistent-storage", 2, 2, 0, verb_persistent_storage }, {} }; From f75a6b7564190ae223953b3228e3b137896ae0a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 13:06:44 +0100 Subject: [PATCH 0295/1296] userdbctl: call all verb functions verb_* --- src/userdb/userdbctl.c | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/userdb/userdbctl.c b/src/userdb/userdbctl.c index 88f738a6061eb..c1e19ee0aa33e 100644 --- a/src/userdb/userdbctl.c +++ b/src/userdb/userdbctl.c @@ -407,7 +407,7 @@ static int table_add_uid_map( return n_added; } -static int display_user(int argc, char *argv[], void *userdata) { +static int verb_display_user(int argc, char *argv[], void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; bool draw_separator = false; int ret = 0, r; @@ -750,7 +750,7 @@ static int add_unavailable_gid(Table *table, uid_t start, uid_t end) { return 2; } -static int display_group(int argc, char *argv[], void *userdata) { +static int verb_display_group(int argc, char *argv[], void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; bool draw_separator = false; int ret = 0, r; @@ -951,7 +951,7 @@ static int show_membership(const char *user, const char *group, Table *table) { return 0; } -static int display_memberships(int argc, char *argv[], void *userdata) { +static int verb_display_memberships(int argc, char *argv[], void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; int ret = 0, r; @@ -1047,7 +1047,7 @@ static int display_memberships(int argc, char *argv[], void *userdata) { return ret; } -static int display_services(int argc, char *argv[], void *userdata) { +static int verb_display_services(int argc, char *argv[], void *userdata) { _cleanup_(table_unrefp) Table *t = NULL; _cleanup_closedir_ DIR *d = NULL; int r; @@ -1114,7 +1114,7 @@ static int display_services(int argc, char *argv[], void *userdata) { return 0; } -static int ssh_authorized_keys(int argc, char *argv[], void *userdata) { +static int verb_ssh_authorized_keys(int argc, char *argv[], void *userdata) { _cleanup_(user_record_unrefp) UserRecord *ur = NULL; char **chain_invocation; int r; @@ -1497,7 +1497,7 @@ static int load_credential_one( return 0; } -static int load_credentials(int argc, char *argv[], void *userdata) { +static int verb_load_credentials(int argc, char *argv[], void *userdata) { int r; _cleanup_close_ int credential_dir_fd = open_credentials_dir(); @@ -1532,7 +1532,7 @@ static int load_credentials(int argc, char *argv[], void *userdata) { return r; } -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -1590,6 +1590,10 @@ static int help(int argc, char *argv[], void *userdata) { return 0; } +static int verb_help(int argc, char *argv[], void *userdata) { + return help(); +} + static int parse_argv(int argc, char *argv[]) { enum { @@ -1670,7 +1674,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - return help(0, NULL, NULL); + return help(); case ARG_VERSION: return version(); @@ -1872,14 +1876,14 @@ static int parse_argv(int argc, char *argv[]) { static int run(int argc, char *argv[]) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "user", VERB_ANY, VERB_ANY, VERB_DEFAULT, display_user }, - { "group", VERB_ANY, VERB_ANY, 0, display_group }, - { "users-in-group", VERB_ANY, VERB_ANY, 0, display_memberships }, - { "groups-of-user", VERB_ANY, VERB_ANY, 0, display_memberships }, - { "services", VERB_ANY, 1, 0, display_services }, - { "ssh-authorized-keys", 2, VERB_ANY, 0, ssh_authorized_keys }, - { "load-credentials", VERB_ANY, 1, 0, load_credentials }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, + { "user", VERB_ANY, VERB_ANY, VERB_DEFAULT, verb_display_user }, + { "group", VERB_ANY, VERB_ANY, 0, verb_display_group }, + { "users-in-group", VERB_ANY, VERB_ANY, 0, verb_display_memberships }, + { "groups-of-user", VERB_ANY, VERB_ANY, 0, verb_display_memberships }, + { "services", VERB_ANY, 1, 0, verb_display_services }, + { "ssh-authorized-keys", 2, VERB_ANY, 0, verb_ssh_authorized_keys }, + { "load-credentials", VERB_ANY, 1, 0, verb_load_credentials }, {} }; From 0950a0c0d102109ca6c061258ad5c6f50e72195e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 13:11:18 +0100 Subject: [PATCH 0296/1296] udevadm: call all verb functions verb_* --- src/udev/udevadm-cat.c | 2 +- src/udev/udevadm-control.c | 2 +- src/udev/udevadm-hwdb.c | 2 +- src/udev/udevadm-info.c | 2 +- src/udev/udevadm-lock.c | 2 +- src/udev/udevadm-monitor.c | 2 +- src/udev/udevadm-settle.c | 2 +- src/udev/udevadm-test-builtin.c | 2 +- src/udev/udevadm-test.c | 2 +- src/udev/udevadm-trigger.c | 2 +- src/udev/udevadm-verify.c | 2 +- src/udev/udevadm-wait.c | 2 +- src/udev/udevadm.c | 32 ++++++++++++++++---------------- src/udev/udevadm.h | 24 ++++++++++++------------ 14 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/udev/udevadm-cat.c b/src/udev/udevadm-cat.c index c77e76d053809..c1c39aedf36d7 100644 --- a/src/udev/udevadm-cat.c +++ b/src/udev/udevadm-cat.c @@ -92,7 +92,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -int cat_main(int argc, char *argv[], void *userdata) { +int verb_cat_main(int argc, char *argv[], void *userdata) { int r; r = parse_argv(argc, argv); diff --git a/src/udev/udevadm-control.c b/src/udev/udevadm-control.c index 492b00f222b0c..a401acfde9101 100644 --- a/src/udev/udevadm-control.c +++ b/src/udev/udevadm-control.c @@ -331,7 +331,7 @@ static int send_control_commands(void) { return 0; } -int control_main(int argc, char *argv[], void *userdata) { +int verb_control_main(int argc, char *argv[], void *userdata) { int r; if (running_in_chroot() > 0) { diff --git a/src/udev/udevadm-hwdb.c b/src/udev/udevadm-hwdb.c index b84ebea162f73..44c04590afa3e 100644 --- a/src/udev/udevadm-hwdb.c +++ b/src/udev/udevadm-hwdb.c @@ -78,7 +78,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -int hwdb_main(int argc, char *argv[], void *userdata) { +int verb_hwdb_main(int argc, char *argv[], void *userdata) { int r; r = parse_argv(argc, argv); diff --git a/src/udev/udevadm-info.c b/src/udev/udevadm-info.c index 1598d8a3210bf..558d80e3af2e5 100644 --- a/src/udev/udevadm-info.c +++ b/src/udev/udevadm-info.c @@ -1275,7 +1275,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -int info_main(int argc, char *argv[], void *userdata) { +int verb_info_main(int argc, char *argv[], void *userdata) { int r; r = parse_argv(argc, argv); diff --git a/src/udev/udevadm-lock.c b/src/udev/udevadm-lock.c index 17b9e2d3e9bcf..df78dd9a844cd 100644 --- a/src/udev/udevadm-lock.c +++ b/src/udev/udevadm-lock.c @@ -228,7 +228,7 @@ static int lock_device( return TAKE_FD(fd); } -int lock_main(int argc, char *argv[], void *userdata) { +int verb_lock_main(int argc, char *argv[], void *userdata) { _cleanup_fdset_free_ FDSet *fds = NULL; _cleanup_free_ dev_t *devnos = NULL; size_t n_devnos = 0; diff --git a/src/udev/udevadm-monitor.c b/src/udev/udevadm-monitor.c index e729a99e95b36..c8a2c3ca22f72 100644 --- a/src/udev/udevadm-monitor.c +++ b/src/udev/udevadm-monitor.c @@ -187,7 +187,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -int monitor_main(int argc, char *argv[], void *userdata) { +int verb_monitor_main(int argc, char *argv[], void *userdata) { _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *kernel_monitor = NULL, *udev_monitor = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; int r; diff --git a/src/udev/udevadm-settle.c b/src/udev/udevadm-settle.c index 19128ec80e77a..74ac4acbefcf7 100644 --- a/src/udev/udevadm-settle.c +++ b/src/udev/udevadm-settle.c @@ -184,7 +184,7 @@ static int on_inotify(sd_event_source *s, const struct inotify_event *event, voi return 0; } -int settle_main(int argc, char *argv[], void *userdata) { +int verb_settle_main(int argc, char *argv[], void *userdata) { _cleanup_(sd_event_unrefp) sd_event *event = NULL; int r; diff --git a/src/udev/udevadm-test-builtin.c b/src/udev/udevadm-test-builtin.c index b25c7ae2fa6a4..24ea039120b60 100644 --- a/src/udev/udevadm-test-builtin.c +++ b/src/udev/udevadm-test-builtin.c @@ -63,7 +63,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -int builtin_main(int argc, char *argv[], void *userdata) { +int verb_builtin_main(int argc, char *argv[], void *userdata) { _cleanup_(udev_event_unrefp) UdevEvent *event = NULL; _cleanup_(sd_device_unrefp) sd_device *dev = NULL; UdevBuiltinCommand cmd; diff --git a/src/udev/udevadm-test.c b/src/udev/udevadm-test.c index 06d9d2cd16f28..98b63aa11c452 100644 --- a/src/udev/udevadm-test.c +++ b/src/udev/udevadm-test.c @@ -129,7 +129,7 @@ static void maybe_insert_empty_line(void) { fputs("\n", stderr); } -int test_main(int argc, char *argv[], void *userdata) { +int verb_test_main(int argc, char *argv[], void *userdata) { _cleanup_(udev_rules_freep) UdevRules *rules = NULL; _cleanup_(udev_event_unrefp) UdevEvent *event = NULL; _cleanup_(sd_device_unrefp) sd_device *dev = NULL; diff --git a/src/udev/udevadm-trigger.c b/src/udev/udevadm-trigger.c index 10f8a15fb17aa..b97ecfa0997eb 100644 --- a/src/udev/udevadm-trigger.c +++ b/src/udev/udevadm-trigger.c @@ -534,7 +534,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -int trigger_main(int argc, char *argv[], void *userdata) { +int verb_trigger_main(int argc, char *argv[], void *userdata) { _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *m = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; diff --git a/src/udev/udevadm-verify.c b/src/udev/udevadm-verify.c index 7918ea2f7e7b2..5d4119399ed20 100644 --- a/src/udev/udevadm-verify.c +++ b/src/udev/udevadm-verify.c @@ -156,7 +156,7 @@ static int verify_rules(UdevRules *rules, ConfFile * const *files, size_t n_file return ret; } -int verify_main(int argc, char *argv[], void *userdata) { +int verb_verify_main(int argc, char *argv[], void *userdata) { _cleanup_(udev_rules_freep) UdevRules *rules = NULL; int r; diff --git a/src/udev/udevadm-wait.c b/src/udev/udevadm-wait.c index 70b14a7e91e91..bfc000e217dab 100644 --- a/src/udev/udevadm-wait.c +++ b/src/udev/udevadm-wait.c @@ -376,7 +376,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; /* work to do */ } -int wait_main(int argc, char *argv[], void *userdata) { +int verb_wait_main(int argc, char *argv[], void *userdata) { _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *udev_monitor = NULL, *kernel_monitor = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; int r; diff --git a/src/udev/udevadm.c b/src/udev/udevadm.c index 3a91d14ef8e93..8ffe068b9d87e 100644 --- a/src/udev/udevadm.c +++ b/src/udev/udevadm.c @@ -91,30 +91,30 @@ int print_version(void) { return 0; } -static int version_main(int argc, char *argv[], void *userdata) { +static int verb_version_main(int argc, char *argv[], void *userdata) { return print_version(); } -static int help_main(int argc, char *argv[], void *userdata) { +static int verb_help_main(int argc, char *argv[], void *userdata) { return help(); } static int udevadm_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "cat", VERB_ANY, VERB_ANY, 0, cat_main }, - { "info", VERB_ANY, VERB_ANY, 0, info_main }, - { "trigger", VERB_ANY, VERB_ANY, 0, trigger_main }, - { "settle", VERB_ANY, VERB_ANY, 0, settle_main }, - { "control", VERB_ANY, VERB_ANY, 0, control_main }, - { "monitor", VERB_ANY, VERB_ANY, 0, monitor_main }, - { "hwdb", VERB_ANY, VERB_ANY, 0, hwdb_main }, - { "test", VERB_ANY, VERB_ANY, 0, test_main }, - { "test-builtin", VERB_ANY, VERB_ANY, 0, builtin_main }, - { "wait", VERB_ANY, VERB_ANY, 0, wait_main }, - { "lock", VERB_ANY, VERB_ANY, 0, lock_main }, - { "verify", VERB_ANY, VERB_ANY, 0, verify_main }, - { "version", VERB_ANY, VERB_ANY, 0, version_main }, - { "help", VERB_ANY, VERB_ANY, 0, help_main }, + { "cat", VERB_ANY, VERB_ANY, 0, verb_cat_main }, + { "info", VERB_ANY, VERB_ANY, 0, verb_info_main }, + { "trigger", VERB_ANY, VERB_ANY, 0, verb_trigger_main }, + { "settle", VERB_ANY, VERB_ANY, 0, verb_settle_main }, + { "control", VERB_ANY, VERB_ANY, 0, verb_control_main }, + { "monitor", VERB_ANY, VERB_ANY, 0, verb_monitor_main }, + { "hwdb", VERB_ANY, VERB_ANY, 0, verb_hwdb_main }, + { "test", VERB_ANY, VERB_ANY, 0, verb_test_main }, + { "test-builtin", VERB_ANY, VERB_ANY, 0, verb_builtin_main }, + { "wait", VERB_ANY, VERB_ANY, 0, verb_wait_main }, + { "lock", VERB_ANY, VERB_ANY, 0, verb_lock_main }, + { "verify", VERB_ANY, VERB_ANY, 0, verb_verify_main }, + { "version", VERB_ANY, VERB_ANY, 0, verb_version_main }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help_main }, {} }; diff --git a/src/udev/udevadm.h b/src/udev/udevadm.h index f75fbb416f8db..b41d091a3479e 100644 --- a/src/udev/udevadm.h +++ b/src/udev/udevadm.h @@ -3,17 +3,17 @@ #include "shared-forward.h" -int cat_main(int argc, char *argv[], void *userdata); -int info_main(int argc, char *argv[], void *userdata); -int trigger_main(int argc, char *argv[], void *userdata); -int settle_main(int argc, char *argv[], void *userdata); -int control_main(int argc, char *argv[], void *userdata); -int monitor_main(int argc, char *argv[], void *userdata); -int hwdb_main(int argc, char *argv[], void *userdata); -int test_main(int argc, char *argv[], void *userdata); -int builtin_main(int argc, char *argv[], void *userdata); -int verify_main(int argc, char *argv[], void *userdata); -int wait_main(int argc, char *argv[], void *userdata); -int lock_main(int argc, char *argv[], void *userdata); +int verb_cat_main(int argc, char *argv[], void *userdata); +int verb_info_main(int argc, char *argv[], void *userdata); +int verb_trigger_main(int argc, char *argv[], void *userdata); +int verb_settle_main(int argc, char *argv[], void *userdata); +int verb_control_main(int argc, char *argv[], void *userdata); +int verb_monitor_main(int argc, char *argv[], void *userdata); +int verb_hwdb_main(int argc, char *argv[], void *userdata); +int verb_test_main(int argc, char *argv[], void *userdata); +int verb_builtin_main(int argc, char *argv[], void *userdata); +int verb_verify_main(int argc, char *argv[], void *userdata); +int verb_wait_main(int argc, char *argv[], void *userdata); +int verb_lock_main(int argc, char *argv[], void *userdata); int print_version(void); From 73a2eafad30d34ec423ebdb9c47da299dd6dd857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 13:13:37 +0100 Subject: [PATCH 0297/1296] timedatectl: call all verb functions verb_* --- src/timedate/timedatectl.c | 42 +++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/timedate/timedatectl.c b/src/timedate/timedatectl.c index 04730de114743..36852b1833224 100644 --- a/src/timedate/timedatectl.c +++ b/src/timedate/timedatectl.c @@ -180,7 +180,7 @@ static int print_status_info(const StatusInfo *i) { return 0; } -static int show_status(int argc, char **argv, void *userdata) { +static int verb_show_status(int argc, char **argv, void *userdata) { StatusInfo info = {}; static const struct bus_properties_map map[] = { { "Timezone", "s", NULL, offsetof(StatusInfo, timezone) }, @@ -212,7 +212,7 @@ static int show_status(int argc, char **argv, void *userdata) { return print_status_info(&info); } -static int show_properties(int argc, char **argv, void *userdata) { +static int verb_show_properties(int argc, char **argv, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -229,7 +229,7 @@ static int show_properties(int argc, char **argv, void *userdata) { return 0; } -static int set_time(int argc, char **argv, void *userdata) { +static int verb_set_time(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = userdata; usec_t t; @@ -254,7 +254,7 @@ static int set_time(int argc, char **argv, void *userdata) { return 0; } -static int set_timezone(int argc, char **argv, void *userdata) { +static int verb_set_timezone(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = userdata; int r; @@ -268,7 +268,7 @@ static int set_timezone(int argc, char **argv, void *userdata) { return 0; } -static int set_local_rtc(int argc, char **argv, void *userdata) { +static int verb_set_local_rtc(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = userdata; int r, b; @@ -299,7 +299,7 @@ static int set_local_rtc(int argc, char **argv, void *userdata) { return 0; } -static int set_ntp(int argc, char **argv, void *userdata) { +static int verb_set_ntp(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = userdata; @@ -327,7 +327,7 @@ static int set_ntp(int argc, char **argv, void *userdata) { return 0; } -static int list_timezones(int argc, char **argv, void *userdata) { +static int verb_list_timezones(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = userdata; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; @@ -688,7 +688,7 @@ static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error return show_timesync_status_once(sd_bus_message_get_bus(m)); } -static int show_timesync_status(int argc, char **argv, void *userdata) { +static int verb_show_timesync_status(int argc, char **argv, void *userdata) { _cleanup_(sd_event_unrefp) sd_event *event = NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -792,7 +792,7 @@ static int print_timesync_property(const char *name, const char *expected_value, return 0; } -static int show_timesync(int argc, char **argv, void *userdata) { +static int verb_show_timesync(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1039,18 +1039,18 @@ static int parse_argv(int argc, char *argv[]) { static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) { static const Verb verbs[] = { - { "status", VERB_ANY, 1, VERB_DEFAULT, show_status }, - { "show", VERB_ANY, 1, 0, show_properties }, - { "set-time", 2, 2, 0, set_time }, - { "set-timezone", 2, 2, 0, set_timezone }, - { "list-timezones", VERB_ANY, 1, 0, list_timezones }, - { "set-local-rtc", 2, 2, 0, set_local_rtc }, - { "set-ntp", 2, 2, 0, set_ntp }, - { "timesync-status", VERB_ANY, 1, 0, show_timesync_status }, - { "show-timesync", VERB_ANY, 1, 0, show_timesync }, - { "ntp-servers", 3, VERB_ANY, 0, verb_ntp_servers }, - { "revert", 2, 2, 0, verb_revert }, - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, /* Not documented, but supported since it is created. */ + { "status", VERB_ANY, 1, VERB_DEFAULT, verb_show_status }, + { "show", VERB_ANY, 1, 0, verb_show_properties }, + { "set-time", 2, 2, 0, verb_set_time }, + { "set-timezone", 2, 2, 0, verb_set_timezone }, + { "list-timezones", VERB_ANY, 1, 0, verb_list_timezones }, + { "set-local-rtc", 2, 2, 0, verb_set_local_rtc }, + { "set-ntp", 2, 2, 0, verb_set_ntp }, + { "timesync-status", VERB_ANY, 1, 0, verb_show_timesync_status }, + { "show-timesync", VERB_ANY, 1, 0, verb_show_timesync }, + { "ntp-servers", 3, VERB_ANY, 0, verb_ntp_servers }, + { "revert", 2, 2, 0, verb_revert }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, /* Not documented, but supported since it has been created. */ {} }; From cdf9951fe089616030c820edffdf6ce6ffc5d60b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 13:15:41 +0100 Subject: [PATCH 0298/1296] resolvectl: call all verb functions verb_* --- src/resolve/resolvectl.c | 54 ++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index 8d887b1fcef06..ac948c64da256 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -1163,7 +1163,7 @@ static int verb_tlsa(int argc, char **argv, void *userdata) { return ret; } -static int show_statistics(int argc, char **argv, void *userdata) { +static int verb_show_statistics(int argc, char **argv, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; sd_json_variant *reply = NULL; _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; @@ -1328,7 +1328,7 @@ static int show_statistics(int argc, char **argv, void *userdata) { return 0; } -static int reset_statistics(int argc, char **argv, void *userdata) { +static int verb_reset_statistics(int argc, char **argv, void *userdata) { sd_json_variant *reply = NULL; _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; int r; @@ -1353,7 +1353,7 @@ static int reset_statistics(int argc, char **argv, void *userdata) { return 0; } -static int flush_caches(int argc, char **argv, void *userdata) { +static int verb_flush_caches(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -1369,7 +1369,7 @@ static int flush_caches(int argc, char **argv, void *userdata) { return 0; } -static int reset_server_features(int argc, char **argv, void *userdata) { +static int verb_reset_server_features(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -3911,29 +3911,29 @@ static int native_parse_argv(int argc, char *argv[]) { static int native_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "status", VERB_ANY, VERB_ANY, VERB_DEFAULT, verb_status }, - { "query", 2, VERB_ANY, 0, verb_query }, - { "service", 2, 4, 0, verb_service }, - { "openpgp", 2, VERB_ANY, 0, verb_openpgp }, - { "tlsa", 2, VERB_ANY, 0, verb_tlsa }, - { "statistics", VERB_ANY, 1, 0, show_statistics }, - { "reset-statistics", VERB_ANY, 1, 0, reset_statistics }, - { "flush-caches", VERB_ANY, 1, 0, flush_caches }, - { "reset-server-features", VERB_ANY, 1, 0, reset_server_features }, - { "dns", VERB_ANY, VERB_ANY, 0, verb_dns }, - { "domain", VERB_ANY, VERB_ANY, 0, verb_domain }, - { "default-route", VERB_ANY, 3, 0, verb_default_route }, - { "llmnr", VERB_ANY, 3, 0, verb_llmnr }, - { "mdns", VERB_ANY, 3, 0, verb_mdns }, - { "dnsovertls", VERB_ANY, 3, 0, verb_dns_over_tls }, - { "dnssec", VERB_ANY, 3, 0, verb_dnssec }, - { "nta", VERB_ANY, VERB_ANY, 0, verb_nta }, - { "revert", VERB_ANY, 2, 0, verb_revert_link }, - { "log-level", VERB_ANY, 2, 0, verb_log_level }, - { "monitor", VERB_ANY, 1, 0, verb_monitor }, - { "show-cache", VERB_ANY, 1, 0, verb_show_cache }, - { "show-server-state", VERB_ANY, 1, 0, verb_show_server_state}, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, + { "status", VERB_ANY, VERB_ANY, VERB_DEFAULT, verb_status }, + { "query", 2, VERB_ANY, 0, verb_query }, + { "service", 2, 4, 0, verb_service }, + { "openpgp", 2, VERB_ANY, 0, verb_openpgp }, + { "tlsa", 2, VERB_ANY, 0, verb_tlsa }, + { "statistics", VERB_ANY, 1, 0, verb_show_statistics }, + { "reset-statistics", VERB_ANY, 1, 0, verb_reset_statistics }, + { "flush-caches", VERB_ANY, 1, 0, verb_flush_caches }, + { "reset-server-features", VERB_ANY, 1, 0, verb_reset_server_features }, + { "dns", VERB_ANY, VERB_ANY, 0, verb_dns }, + { "domain", VERB_ANY, VERB_ANY, 0, verb_domain }, + { "default-route", VERB_ANY, 3, 0, verb_default_route }, + { "llmnr", VERB_ANY, 3, 0, verb_llmnr }, + { "mdns", VERB_ANY, 3, 0, verb_mdns }, + { "dnsovertls", VERB_ANY, 3, 0, verb_dns_over_tls }, + { "dnssec", VERB_ANY, 3, 0, verb_dnssec }, + { "nta", VERB_ANY, VERB_ANY, 0, verb_nta }, + { "revert", VERB_ANY, 2, 0, verb_revert_link }, + { "log-level", VERB_ANY, 2, 0, verb_log_level }, + { "monitor", VERB_ANY, 1, 0, verb_monitor }, + { "show-cache", VERB_ANY, 1, 0, verb_show_cache }, + { "show-server-state", VERB_ANY, 1, 0, verb_show_server_state }, {} }; From 89e3ed81b11e21707b391c2ce862a5ef3d31afa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 13:17:11 +0100 Subject: [PATCH 0299/1296] portablectl: call all verb functions verb_* --- src/portable/portablectl.c | 46 +++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index d277504a81196..cf30361319e5a 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -288,7 +288,7 @@ static int get_image_metadata(sd_bus *bus, const char *image, char **matches, sd return 0; } -static int inspect_image(int argc, char *argv[], void *userdata) { +static int verb_inspect_image(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_strv_free_ char **matches = NULL; @@ -958,15 +958,15 @@ static int attach_reattach_image(int argc, char *argv[], const char *method) { return 0; } -static int attach_image(int argc, char *argv[], void *userdata) { +static int verb_attach_image(int argc, char *argv[], void *userdata) { return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) && !arg_force ? "AttachImage" : "AttachImageWithExtensions"); } -static int reattach_image(int argc, char *argv[], void *userdata) { +static int verb_reattach_image(int argc, char *argv[], void *userdata) { return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) && !arg_force ? "ReattachImage" : "ReattachImageWithExtensions"); } -static int detach_image(int argc, char *argv[], void *userdata) { +static int verb_detach_image(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -1020,7 +1020,7 @@ static int detach_image(int argc, char *argv[], void *userdata) { return 0; } -static int list_images(int argc, char *argv[], void *userdata) { +static int verb_list_images(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -1094,7 +1094,7 @@ static int list_images(int argc, char *argv[], void *userdata) { return 0; } -static int remove_image(int argc, char *argv[], void *userdata) { +static int verb_remove_image(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r, i; @@ -1125,7 +1125,7 @@ static int remove_image(int argc, char *argv[], void *userdata) { return 0; } -static int read_only_image(int argc, char *argv[], void *userdata) { +static int verb_read_only_image(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int b = true, r; @@ -1149,7 +1149,7 @@ static int read_only_image(int argc, char *argv[], void *userdata) { return 0; } -static int set_limit(int argc, char *argv[], void *userdata) { +static int verb_set_limit(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; uint64_t limit; @@ -1182,7 +1182,7 @@ static int set_limit(int argc, char *argv[], void *userdata) { return 0; } -static int is_image_attached(int argc, char *argv[], void *userdata) { +static int verb_is_image_attached(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -1257,7 +1257,7 @@ static int dump_profiles(void) { return 0; } -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -1319,6 +1319,10 @@ static int help(int argc, char *argv[], void *userdata) { return 0; } +static int verb_help(int argc, char *argv[], void *userdata) { + return help(); +} + static int parse_argv(int argc, char *argv[]) { enum { @@ -1375,7 +1379,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - return help(0, NULL, NULL); + return help(); case ARG_VERSION: return version(); @@ -1493,16 +1497,16 @@ static int parse_argv(int argc, char *argv[]) { static int run(int argc, char *argv[]) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "list", VERB_ANY, 1, VERB_DEFAULT, list_images }, - { "attach", 2, VERB_ANY, 0, attach_image }, - { "detach", 2, VERB_ANY, 0, detach_image }, - { "inspect", 2, VERB_ANY, 0, inspect_image }, - { "is-attached", 2, 2, 0, is_image_attached }, - { "read-only", 2, 3, 0, read_only_image }, - { "remove", 2, VERB_ANY, 0, remove_image }, - { "set-limit", 3, 3, 0, set_limit }, - { "reattach", 2, VERB_ANY, 0, reattach_image }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, + { "list", VERB_ANY, 1, VERB_DEFAULT, verb_list_images }, + { "attach", 2, VERB_ANY, 0, verb_attach_image }, + { "detach", 2, VERB_ANY, 0, verb_detach_image }, + { "inspect", 2, VERB_ANY, 0, verb_inspect_image }, + { "is-attached", 2, 2, 0, verb_is_image_attached }, + { "read-only", 2, 3, 0, verb_read_only_image }, + { "remove", 2, VERB_ANY, 0, verb_remove_image }, + { "set-limit", 3, 3, 0, verb_set_limit }, + { "reattach", 2, VERB_ANY, 0, verb_reattach_image }, {} }; From 326824deeb57cb80e8fc3b9aada66998d5822131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 13:18:14 +0100 Subject: [PATCH 0300/1296] oomctl: call all verb functions verb_* --- src/oom/oomctl.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/oom/oomctl.c b/src/oom/oomctl.c index d9098de0ebf1e..1cfde287e9120 100644 --- a/src/oom/oomctl.c +++ b/src/oom/oomctl.c @@ -17,7 +17,7 @@ static PagerFlags arg_pager_flags = 0; -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -46,7 +46,11 @@ static int help(int argc, char *argv[], void *userdata) { return 0; } -static int dump_state(int argc, char *argv[], void *userdata) { +static int verb_help(int argc, char *argv[], void *userdata) { + return help(); +} + +static int verb_dump_state(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -88,7 +92,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - return help(0, NULL, NULL); + return help(); case ARG_VERSION: return version(); @@ -109,8 +113,8 @@ static int parse_argv(int argc, char *argv[]) { static int run(int argc, char* argv[]) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "dump", VERB_ANY, 1, VERB_DEFAULT, dump_state }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, + { "dump", VERB_ANY, 1, VERB_DEFAULT, verb_dump_state }, {} }; From 8fb107aa99d88e2e456b3a3ad3048d2e2ba1847c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 13:27:03 +0100 Subject: [PATCH 0301/1296] export: call all verb functions verb_* --- src/import/export.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/import/export.c b/src/import/export.c index af9e8c15ec959..5f51dbe5176af 100644 --- a/src/import/export.c +++ b/src/import/export.c @@ -58,7 +58,7 @@ static void on_tar_finished(TarExport *export, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int export_tar(int argc, char *argv[], void *userdata) { +static int verb_export_tar(int argc, char *argv[], void *userdata) { _cleanup_(tar_export_unrefp) TarExport *export = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(image_unrefp) Image *image = NULL; @@ -139,7 +139,7 @@ static void on_raw_finished(RawExport *export, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int export_raw(int argc, char *argv[], void *userdata) { +static int verb_export_raw(int argc, char *argv[], void *userdata) { _cleanup_(raw_export_unrefp) RawExport *export = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(image_unrefp) Image *image = NULL; @@ -202,7 +202,7 @@ static int export_raw(int argc, char *argv[], void *userdata) { return -r; } -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { printf("%1$s [OPTIONS...] {COMMAND} ...\n" "\n%4$sExport disk images.%5$s\n" "\n%2$sCommands:%3$s\n" @@ -225,6 +225,10 @@ static int help(int argc, char *argv[], void *userdata) { return 0; } +static int verb_help(int argc, char *argv[], void *userdata) { + return help(); +} + static int parse_argv(int argc, char *argv[]) { enum { @@ -255,7 +259,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - return help(0, NULL, NULL); + return help(); case ARG_VERSION: return version(); @@ -297,9 +301,9 @@ static int parse_argv(int argc, char *argv[]) { static int export_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "tar", 2, 3, 0, export_tar }, - { "raw", 2, 3, 0, export_raw }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, + { "tar", 2, 3, 0, verb_export_tar }, + { "raw", 2, 3, 0, verb_export_raw }, {} }; From b2a55d2fb2864ad2e259ed94030467e3153f057a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 13:27:59 +0100 Subject: [PATCH 0302/1296] import: call all verb functions verb_* --- src/import/import.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/import/import.c b/src/import/import.c index 5276f26977a3c..a7c902555937d 100644 --- a/src/import/import.c +++ b/src/import/import.c @@ -138,7 +138,7 @@ static void on_tar_finished(TarImport *import, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int import_tar(int argc, char *argv[], void *userdata) { +static int verb_import_tar(int argc, char *argv[], void *userdata) { _cleanup_(tar_import_unrefp) TarImport *import = NULL; _cleanup_free_ char *ll = NULL, *normalized = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; @@ -207,7 +207,7 @@ static void on_raw_finished(RawImport *import, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int import_raw(int argc, char *argv[], void *userdata) { +static int verb_import_raw(int argc, char *argv[], void *userdata) { _cleanup_(raw_import_unrefp) RawImport *import = NULL; _cleanup_free_ char *ll = NULL, *normalized = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; @@ -268,8 +268,7 @@ static int import_raw(int argc, char *argv[], void *userdata) { return -r; } -static int help(int argc, char *argv[], void *userdata) { - +static int help(void) { printf("%1$s [OPTIONS...] {COMMAND} ...\n" "\n%4$sImport disk images.%5$s\n" "\n%2$sCommands:%3$s\n" @@ -304,8 +303,11 @@ static int help(int argc, char *argv[], void *userdata) { return 0; } -static int parse_argv(int argc, char *argv[]) { +static int verb_help(int argc, char *argv[], void *userdata) { + return help(); +} +static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, ARG_FORCE, @@ -352,7 +354,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - return help(0, NULL, NULL); + return help(); case ARG_VERSION: return version(); @@ -479,9 +481,9 @@ static int parse_argv(int argc, char *argv[]) { static int import_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "tar", 2, 3, 0, import_tar }, - { "raw", 2, 3, 0, import_raw }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, + { "tar", 2, 3, 0, verb_import_tar }, + { "raw", 2, 3, 0, verb_import_raw }, {} }; From 095368238a107198bad61be20c107a6d167f43ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 13:29:54 +0100 Subject: [PATCH 0303/1296] import-fs: call all verb functions verb_* --- src/import/import-fs.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/import/import-fs.c b/src/import/import-fs.c index 46daf3acc05f8..db25174434117 100644 --- a/src/import/import-fs.c +++ b/src/import/import-fs.c @@ -107,7 +107,7 @@ static int progress_bytes(uint64_t nbytes, uint64_t bps, void *userdata) { return 0; } -static int import_fs(int argc, char *argv[], void *userdata) { +static int verb_import_fs(int argc, char *argv[], void *userdata) { _cleanup_(rm_rf_subvolume_and_freep) char *temp_path = NULL; _cleanup_(progress_info_free) ProgressInfo progress = { .bps = UINT64_MAX }; _cleanup_free_ char *l = NULL, *final_path = NULL; @@ -265,8 +265,7 @@ static int import_fs(int argc, char *argv[], void *userdata) { return 0; } -static int help(int argc, char *argv[], void *userdata) { - +static int help(void) { printf("%1$s [OPTIONS...] {COMMAND} ...\n" "\n%4$sImport container images from a file system directories.%5$s\n" "\n%2$sCommands:%3$s\n" @@ -296,8 +295,11 @@ static int help(int argc, char *argv[], void *userdata) { return 0; } -static int parse_argv(int argc, char *argv[]) { +static int verb_help(int argc, char *argv[], void *userdata) { + return help(); +} +static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, ARG_FORCE, @@ -338,7 +340,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - return help(0, NULL, NULL); + return help(); case ARG_VERSION: return version(); @@ -417,8 +419,8 @@ static int parse_argv(int argc, char *argv[]) { static int import_fs_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "run", 2, 3, 0, import_fs }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, + { "run", 2, 3, 0, verb_import_fs }, {} }; From 5eccdf20ac6ef1c9bdf03fb7c06c55aec99f6b77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 13:32:05 +0100 Subject: [PATCH 0304/1296] pull: call all verb functions verb_* --- src/import/pull.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/import/pull.c b/src/import/pull.c index f8b90ad725a36..e777d41ee6980 100644 --- a/src/import/pull.c +++ b/src/import/pull.c @@ -117,7 +117,7 @@ static void on_tar_finished(TarPull *pull, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int pull_tar(int argc, char *argv[], void *userdata) { +static int verb_pull_tar(int argc, char *argv[], void *userdata) { _cleanup_free_ char *ll = NULL, *normalized = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(tar_pull_unrefp) TarPull *pull = NULL; @@ -187,7 +187,7 @@ static void on_raw_finished(RawPull *pull, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int pull_raw(int argc, char *argv[], void *userdata) { +static int verb_pull_raw(int argc, char *argv[], void *userdata) { _cleanup_free_ char *ll = NULL, *normalized = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(raw_pull_unrefp) RawPull *pull = NULL; @@ -256,7 +256,7 @@ static void on_oci_finished(OciPull *pull, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int pull_oci(int argc, char *argv[], void *userdata) { +static int verb_pull_oci(int argc, char *argv[], void *userdata) { int r; const char *ref = argv[1]; @@ -311,8 +311,7 @@ static int pull_oci(int argc, char *argv[], void *userdata) { return -r; } -static int help(int argc, char *argv[], void *userdata) { - +static int help(void) { printf("%1$s [OPTIONS...] {COMMAND} ...\n" "\n%4$sDownload disk images.%5$s\n" "\n%2$sCommands:%3$s\n" @@ -357,8 +356,11 @@ static int help(int argc, char *argv[], void *userdata) { return 0; } -static int parse_argv(int argc, char *argv[]) { +static int verb_help(int argc, char *argv[], void *userdata) { + return help(); +} +static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, ARG_FORCE, @@ -418,7 +420,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - return help(0, NULL, NULL); + return help(); case ARG_VERSION: return version(); @@ -659,10 +661,10 @@ static void parse_env(void) { static int pull_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "tar", 2, 3, 0, pull_tar }, - { "raw", 2, 3, 0, pull_raw }, - { "oci", 2, 3, 0, pull_oci }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, + { "tar", 2, 3, 0, verb_pull_tar }, + { "raw", 2, 3, 0, verb_pull_raw }, + { "oci", 2, 3, 0, verb_pull_oci }, {} }; From 9e8babc8ab481963e2a53d2901c8606736439f39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 14:03:10 +0100 Subject: [PATCH 0305/1296] update-utmp: call all verb functions verb_* --- src/update-utmp/update-utmp.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/update-utmp/update-utmp.c b/src/update-utmp/update-utmp.c index 5a999806bd5d2..e16520e9849f8 100644 --- a/src/update-utmp/update-utmp.c +++ b/src/update-utmp/update-utmp.c @@ -51,7 +51,7 @@ static int get_startup_monotonic_time(Context *c, usec_t *ret) { return 0; } -static int on_reboot(int argc, char *argv[], void *userdata) { +static int verb_on_reboot(int argc, char *argv[], void *userdata) { Context *c = ASSERT_PTR(userdata); usec_t t = 0, boottime; int r, q = 0; @@ -80,7 +80,7 @@ static int on_reboot(int argc, char *argv[], void *userdata) { return q; } -static int on_shutdown(int argc, char *argv[], void *userdata) { +static int verb_on_shutdown(int argc, char *argv[], void *userdata) { int r, q = 0; /* We started shut-down, so let's write the utmp record and send the audit msg. */ @@ -103,8 +103,8 @@ static int on_shutdown(int argc, char *argv[], void *userdata) { static int run(int argc, char *argv[]) { static const Verb verbs[] = { - { "reboot", 1, 1, 0, on_reboot }, - { "shutdown", 1, 1, 0, on_shutdown }, + { "reboot", 1, 1, 0, verb_on_reboot }, + { "shutdown", 1, 1, 0, verb_on_shutdown }, {} }; From 31ddd87da00c789bc7afcdd6770517518a0e1419 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 14:16:08 +0100 Subject: [PATCH 0306/1296] tree-wide: extend verbs functions with extra per-verb data parameter We often have a pattern where the same verb function is used for multiple actions. This leads to an antipattern where we figure out what action needs to be taken from argv[0] multiple times: often once in parse_argv() to figure out what options are allowed, then once again implicitly in dispatch_verb(), and then again in the action verb itself. Let's allow passing a parameter into the verb to simplify this. This matches a pattern we have in conf-parser.h, where we have both void *userdata (more global) and void *data (per-config item). Here, I opted for uintptr_t userdata. It seems that most of the time we'll want to just pass an enum value. This works OK with no casts. I also tried a void* and union. In both cases, much more boilerplate is needed: either a cast or a macro to help avoid compiler warnings. uintptr_t seems generic enough to cover foreseeable usecases with no fuss. This is a noop refactoring. See next commit for an example. --- src/analyze/analyze-architectures.c | 2 +- src/analyze/analyze-architectures.h | 4 +- src/analyze/analyze-blame.c | 2 +- src/analyze/analyze-blame.h | 4 +- src/analyze/analyze-calendar.c | 2 +- src/analyze/analyze-calendar.h | 4 +- src/analyze/analyze-capability.c | 2 +- src/analyze/analyze-capability.h | 4 +- src/analyze/analyze-cat-config.c | 2 +- src/analyze/analyze-cat-config.h | 4 +- src/analyze/analyze-chid.c | 2 +- src/analyze/analyze-chid.h | 4 +- src/analyze/analyze-compare-versions.c | 2 +- src/analyze/analyze-compare-versions.h | 4 +- src/analyze/analyze-condition.c | 2 +- src/analyze/analyze-condition.h | 4 +- src/analyze/analyze-critical-chain.c | 2 +- src/analyze/analyze-critical-chain.h | 4 +- src/analyze/analyze-dlopen-metadata.c | 2 +- src/analyze/analyze-dlopen-metadata.h | 4 +- src/analyze/analyze-dot.c | 2 +- src/analyze/analyze-dot.h | 4 +- src/analyze/analyze-dump.c | 2 +- src/analyze/analyze-dump.h | 4 +- src/analyze/analyze-exit-status.c | 2 +- src/analyze/analyze-exit-status.h | 4 +- src/analyze/analyze-fdstore.c | 2 +- src/analyze/analyze-fdstore.h | 4 +- src/analyze/analyze-filesystems.c | 2 +- src/analyze/analyze-filesystems.h | 4 +- src/analyze/analyze-has-tpm2.c | 4 +- src/analyze/analyze-has-tpm2.h | 6 +- src/analyze/analyze-image-policy.c | 2 +- src/analyze/analyze-image-policy.h | 4 +- src/analyze/analyze-inspect-elf.c | 2 +- src/analyze/analyze-inspect-elf.h | 4 +- src/analyze/analyze-log-control.c | 2 +- src/analyze/analyze-log-control.h | 4 +- src/analyze/analyze-malloc.c | 2 +- src/analyze/analyze-malloc.h | 5 +- src/analyze/analyze-nvpcrs.c | 2 +- src/analyze/analyze-nvpcrs.h | 4 +- src/analyze/analyze-pcrs.c | 2 +- src/analyze/analyze-pcrs.h | 4 +- src/analyze/analyze-plot.c | 2 +- src/analyze/analyze-plot.h | 4 +- src/analyze/analyze-security.c | 2 +- src/analyze/analyze-security.h | 4 +- src/analyze/analyze-service-watchdogs.c | 2 +- src/analyze/analyze-service-watchdogs.h | 4 +- src/analyze/analyze-smbios11.c | 2 +- src/analyze/analyze-smbios11.h | 4 +- src/analyze/analyze-srk.c | 2 +- src/analyze/analyze-srk.h | 4 +- src/analyze/analyze-syscall-filter.c | 4 +- src/analyze/analyze-syscall-filter.h | 4 +- src/analyze/analyze-time.c | 2 +- src/analyze/analyze-time.h | 4 +- src/analyze/analyze-timespan.c | 2 +- src/analyze/analyze-timespan.h | 4 +- src/analyze/analyze-timestamp.c | 2 +- src/analyze/analyze-timestamp.h | 4 +- src/analyze/analyze-unit-files.c | 2 +- src/analyze/analyze-unit-files.h | 4 +- src/analyze/analyze-unit-gdb.c | 2 +- src/analyze/analyze-unit-gdb.h | 4 +- src/analyze/analyze-unit-paths.c | 2 +- src/analyze/analyze-unit-paths.h | 4 +- src/analyze/analyze-unit-shell.c | 2 +- src/analyze/analyze-unit-shell.h | 4 +- src/analyze/analyze-verify.c | 2 +- src/analyze/analyze-verify.h | 4 +- src/analyze/analyze.c | 12 ++-- src/backlight/backlight.c | 4 +- src/bless-boot/bless-boot.c | 15 +++-- src/bootctl/bootctl-cleanup.c | 2 +- src/bootctl/bootctl-cleanup.h | 2 +- src/bootctl/bootctl-install.c | 6 +- src/bootctl/bootctl-install.h | 6 +- src/bootctl/bootctl-random-seed.c | 2 +- src/bootctl/bootctl-random-seed.h | 4 +- src/bootctl/bootctl-reboot-to-firmware.c | 2 +- src/bootctl/bootctl-reboot-to-firmware.h | 2 +- src/bootctl/bootctl-set-efivar.c | 2 +- src/bootctl/bootctl-set-efivar.h | 4 +- src/bootctl/bootctl-status.c | 4 +- src/bootctl/bootctl-status.h | 4 +- src/bootctl/bootctl-uki.c | 4 +- src/bootctl/bootctl-uki.h | 4 +- src/bootctl/bootctl-unlink.c | 2 +- src/bootctl/bootctl-unlink.h | 2 +- src/bootctl/bootctl.c | 11 ++-- src/busctl/busctl.c | 24 ++++---- src/coredump/coredumpctl.c | 10 ++-- src/creds/creds.c | 34 ++++++----- src/cryptsetup/cryptsetup.c | 4 +- src/factory-reset/factory-reset-tool.c | 8 +-- src/home/homectl.c | 50 ++++++++-------- src/hostname/hostnamectl.c | 18 +++--- src/hwdb/hwdb.c | 4 +- src/id128/id128.c | 14 ++--- src/import/export.c | 6 +- src/import/import-fs.c | 4 +- src/import/import.c | 6 +- src/import/importctl.c | 24 ++++---- src/import/pull.c | 8 +-- src/integritysetup/integritysetup.c | 4 +- src/kernel-install/kernel-install.c | 12 ++-- src/keyutil/keyutil.c | 19 +++--- src/locale/localectl.c | 16 ++--- src/login/loginctl.c | 32 +++++----- src/machine/machinectl.c | 54 ++++++++--------- src/measure/measure-tool.c | 19 +++--- src/network/networkctl-address-label.c | 2 +- src/network/networkctl-address-label.h | 4 +- src/network/networkctl-config-file.c | 8 +-- src/network/networkctl-config-file.h | 10 ++-- src/network/networkctl-list.c | 2 +- src/network/networkctl-list.h | 4 +- src/network/networkctl-lldp.c | 2 +- src/network/networkctl-lldp.h | 2 +- src/network/networkctl-misc.c | 10 ++-- src/network/networkctl-misc.h | 12 ++-- src/network/networkctl-status-link.c | 2 +- src/network/networkctl-status-link.h | 4 +- src/oom/oomctl.c | 4 +- src/pcrlock/pcrlock.c | 65 +++++++++++---------- src/portable/portablectl.c | 20 +++---- src/report/report.c | 12 ++-- src/resolve/resolvectl.c | 46 +++++++-------- src/sbsign/sbsign.c | 15 +++-- src/shared/verbs.c | 4 +- src/shared/verbs.h | 3 +- src/sysext/sysext.c | 19 +++--- src/systemctl/systemctl-add-dependency.c | 2 +- src/systemctl/systemctl-add-dependency.h | 4 +- src/systemctl/systemctl-cancel-job.c | 4 +- src/systemctl/systemctl-cancel-job.h | 4 +- src/systemctl/systemctl-clean-or-freeze.c | 2 +- src/systemctl/systemctl-clean-or-freeze.h | 4 +- src/systemctl/systemctl-compat-halt.c | 2 +- src/systemctl/systemctl-daemon-reload.c | 2 +- src/systemctl/systemctl-daemon-reload.h | 2 +- src/systemctl/systemctl-edit.c | 4 +- src/systemctl/systemctl-edit.h | 6 +- src/systemctl/systemctl-enable.c | 4 +- src/systemctl/systemctl-enable.h | 4 +- src/systemctl/systemctl-is-active.c | 4 +- src/systemctl/systemctl-is-active.h | 6 +- src/systemctl/systemctl-is-enabled.c | 2 +- src/systemctl/systemctl-is-enabled.h | 4 +- src/systemctl/systemctl-is-system-running.c | 2 +- src/systemctl/systemctl-is-system-running.h | 4 +- src/systemctl/systemctl-kill.c | 2 +- src/systemctl/systemctl-kill.h | 4 +- src/systemctl/systemctl-list-dependencies.c | 2 +- src/systemctl/systemctl-list-dependencies.h | 4 +- src/systemctl/systemctl-list-jobs.c | 2 +- src/systemctl/systemctl-list-jobs.h | 4 +- src/systemctl/systemctl-list-machines.c | 2 +- src/systemctl/systemctl-list-machines.h | 2 +- src/systemctl/systemctl-list-unit-files.c | 2 +- src/systemctl/systemctl-list-unit-files.h | 4 +- src/systemctl/systemctl-list-units.c | 10 ++-- src/systemctl/systemctl-list-units.h | 10 ++-- src/systemctl/systemctl-log-setting.c | 4 +- src/systemctl/systemctl-log-setting.h | 6 +- src/systemctl/systemctl-mount.c | 4 +- src/systemctl/systemctl-mount.h | 6 +- src/systemctl/systemctl-preset-all.c | 2 +- src/systemctl/systemctl-preset-all.h | 4 +- src/systemctl/systemctl-reset-failed.c | 4 +- src/systemctl/systemctl-reset-failed.h | 4 +- src/systemctl/systemctl-service-watchdogs.c | 2 +- src/systemctl/systemctl-service-watchdogs.h | 4 +- src/systemctl/systemctl-set-default.c | 4 +- src/systemctl/systemctl-set-default.h | 6 +- src/systemctl/systemctl-set-environment.c | 6 +- src/systemctl/systemctl-set-environment.h | 8 ++- src/systemctl/systemctl-set-property.c | 2 +- src/systemctl/systemctl-set-property.h | 4 +- src/systemctl/systemctl-show.c | 2 +- src/systemctl/systemctl-show.h | 4 +- src/systemctl/systemctl-start-special.c | 10 ++-- src/systemctl/systemctl-start-special.h | 6 +- src/systemctl/systemctl-start-unit.c | 2 +- src/systemctl/systemctl-start-unit.h | 2 +- src/systemctl/systemctl-switch-root.c | 2 +- src/systemctl/systemctl-switch-root.h | 4 +- src/systemctl/systemctl-trivial-method.c | 2 +- src/systemctl/systemctl-trivial-method.h | 4 +- src/systemctl/systemctl-whoami.c | 2 +- src/systemctl/systemctl-whoami.h | 4 +- src/sysupdate/sysupdate.c | 25 ++++---- src/sysupdate/updatectl.c | 12 ++-- src/test/test-verbs.c | 2 +- src/timedate/timedatectl.c | 24 ++++---- src/udev/iocost/iocost.c | 4 +- src/udev/udevadm-cat.c | 2 +- src/udev/udevadm-control.c | 2 +- src/udev/udevadm-hwdb.c | 2 +- src/udev/udevadm-info.c | 2 +- src/udev/udevadm-lock.c | 2 +- src/udev/udevadm-monitor.c | 2 +- src/udev/udevadm-settle.c | 2 +- src/udev/udevadm-test-builtin.c | 2 +- src/udev/udevadm-test.c | 2 +- src/udev/udevadm-trigger.c | 2 +- src/udev/udevadm-verify.c | 2 +- src/udev/udevadm-wait.c | 2 +- src/udev/udevadm.c | 4 +- src/udev/udevadm.h | 24 ++++---- src/update-utmp/update-utmp.c | 4 +- src/userdb/userdbctl.c | 14 ++--- src/varlinkctl/varlinkctl.c | 12 ++-- src/veritysetup/veritysetup.c | 4 +- 216 files changed, 745 insertions(+), 573 deletions(-) diff --git a/src/analyze/analyze-architectures.c b/src/analyze/analyze-architectures.c index de9b899f12f37..7df7f91c2c31f 100644 --- a/src/analyze/analyze-architectures.c +++ b/src/analyze/analyze-architectures.c @@ -42,7 +42,7 @@ static int add_arch(Table *t, Architecture a) { return 0; } -int verb_architectures(int argc, char *argv[], void *userdata) { +int verb_architectures(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; int r; diff --git a/src/analyze/analyze-architectures.h b/src/analyze/analyze-architectures.h index 06b9473784475..d8ba8b82aeeca 100644 --- a/src/analyze/analyze-architectures.h +++ b/src/analyze/analyze-architectures.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_architectures(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_architectures(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-blame.c b/src/analyze/analyze-blame.c index 7476342caa51b..8651f2586a4b9 100644 --- a/src/analyze/analyze-blame.c +++ b/src/analyze/analyze-blame.c @@ -9,7 +9,7 @@ #include "format-table.h" #include "runtime-scope.h" -int verb_blame(int argc, char *argv[], void *userdata) { +int verb_blame(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL; _cleanup_(table_unrefp) Table *table = NULL; diff --git a/src/analyze/analyze-blame.h b/src/analyze/analyze-blame.h index d9aa985c1e611..362f6c9d36242 100644 --- a/src/analyze/analyze-blame.h +++ b/src/analyze/analyze-blame.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_blame(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_blame(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-calendar.c b/src/analyze/analyze-calendar.c index b6e23e0a4449c..ac0b2da7d8286 100644 --- a/src/analyze/analyze-calendar.c +++ b/src/analyze/analyze-calendar.c @@ -122,7 +122,7 @@ static int test_calendar_one(usec_t n, const char *p) { return table_print(table, NULL); } -int verb_calendar(int argc, char *argv[], void *userdata) { +int verb_calendar(int argc, char *argv[], uintptr_t _data, void *userdata) { int r = 0; usec_t n; diff --git a/src/analyze/analyze-calendar.h b/src/analyze/analyze-calendar.h index 3d6eac200ddac..673a6ed61b56f 100644 --- a/src/analyze/analyze-calendar.h +++ b/src/analyze/analyze-calendar.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_calendar(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_calendar(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-capability.c b/src/analyze/analyze-capability.c index 57bb67ace997f..3ddbdb4cc14cc 100644 --- a/src/analyze/analyze-capability.c +++ b/src/analyze/analyze-capability.c @@ -19,7 +19,7 @@ static int table_add_capability(Table *table, int c) { return 0; } -int verb_capabilities(int argc, char *argv[], void *userdata) { +int verb_capabilities(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; unsigned last_cap; int r; diff --git a/src/analyze/analyze-capability.h b/src/analyze/analyze-capability.h index 07ff0887fd948..fa6f5537e125d 100644 --- a/src/analyze/analyze-capability.h +++ b/src/analyze/analyze-capability.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_capabilities(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_capabilities(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-cat-config.c b/src/analyze/analyze-cat-config.c index e8c118a4b2e63..549b1b4f3f3ff 100644 --- a/src/analyze/analyze-cat-config.c +++ b/src/analyze/analyze-cat-config.c @@ -9,7 +9,7 @@ #include "pretty-print.h" #include "strv.h" -int verb_cat_config(int argc, char *argv[], void *userdata) { +int verb_cat_config(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; pager_open(arg_pager_flags); diff --git a/src/analyze/analyze-cat-config.h b/src/analyze/analyze-cat-config.h index 64e87a3a6d4fe..c90d7e82a4464 100644 --- a/src/analyze/analyze-cat-config.h +++ b/src/analyze/analyze-cat-config.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_cat_config(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_cat_config(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-chid.c b/src/analyze/analyze-chid.c index 612465095f8b2..9fe68b2de45da 100644 --- a/src/analyze/analyze-chid.c +++ b/src/analyze/analyze-chid.c @@ -338,7 +338,7 @@ static int edid_search(char16_t **ret_panel) { return -ENOTUNIQ; } -int verb_chid(int argc, char *argv[], void *userdata) { +int verb_chid(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; int r; diff --git a/src/analyze/analyze-chid.h b/src/analyze/analyze-chid.h index a3f40c601341c..64e2bab0e16b2 100644 --- a/src/analyze/analyze-chid.h +++ b/src/analyze/analyze-chid.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_chid(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_chid(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-compare-versions.c b/src/analyze/analyze-compare-versions.c index 2cf9b4a47ce72..5c15fd044d65a 100644 --- a/src/analyze/analyze-compare-versions.c +++ b/src/analyze/analyze-compare-versions.c @@ -7,7 +7,7 @@ #include "log.h" #include "string-util.h" -int verb_compare_versions(int argc, char *argv[], void *userdata) { +int verb_compare_versions(int argc, char *argv[], uintptr_t _data, void *userdata) { const char *v1 = ASSERT_PTR(argv[1]), *v2 = ASSERT_PTR(argv[argc-1]); int r; diff --git a/src/analyze/analyze-compare-versions.h b/src/analyze/analyze-compare-versions.h index 91913039035bc..f907ffff2093a 100644 --- a/src/analyze/analyze-compare-versions.h +++ b/src/analyze/analyze-compare-versions.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_compare_versions(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_compare_versions(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-condition.c b/src/analyze/analyze-condition.c index 17d3126b7ba9e..a928f84ef4e95 100644 --- a/src/analyze/analyze-condition.c +++ b/src/analyze/analyze-condition.c @@ -136,7 +136,7 @@ static int verify_conditions(char **lines, RuntimeScope scope, const char *unit, return r > 0 && q > 0 ? 0 : -EIO; } -int verb_condition(int argc, char *argv[], void *userdata) { +int verb_condition(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; r = verify_conditions(strv_skip(argv, 1), arg_runtime_scope, arg_unit, arg_root); diff --git a/src/analyze/analyze-condition.h b/src/analyze/analyze-condition.h index 28ef51a453373..c385cdfb78182 100644 --- a/src/analyze/analyze-condition.h +++ b/src/analyze/analyze-condition.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_condition(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_condition(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-critical-chain.c b/src/analyze/analyze-critical-chain.c index 887baf8d149a0..ea6d83d417cb6 100644 --- a/src/analyze/analyze-critical-chain.c +++ b/src/analyze/analyze-critical-chain.c @@ -201,7 +201,7 @@ static int list_dependencies(sd_bus *bus, const char *name) { return list_dependencies_one(bus, name, 0, &units, 0); } -int verb_critical_chain(int argc, char *argv[], void *userdata) { +int verb_critical_chain(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL; int n, r; diff --git a/src/analyze/analyze-critical-chain.h b/src/analyze/analyze-critical-chain.h index 844249c911eeb..c4e84b1e1113e 100644 --- a/src/analyze/analyze-critical-chain.h +++ b/src/analyze/analyze-critical-chain.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_critical_chain(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_critical_chain(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-dlopen-metadata.c b/src/analyze/analyze-dlopen-metadata.c index 2d440055fa23a..76c161105f324 100644 --- a/src/analyze/analyze-dlopen-metadata.c +++ b/src/analyze/analyze-dlopen-metadata.c @@ -12,7 +12,7 @@ #include "json-util.h" #include "strv.h" -int verb_dlopen_metadata(int argc, char *argv[], void *userdata) { +int verb_dlopen_metadata(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; _cleanup_free_ char *abspath = NULL; diff --git a/src/analyze/analyze-dlopen-metadata.h b/src/analyze/analyze-dlopen-metadata.h index 3f7355d96bd42..8abb8bb9c41d8 100644 --- a/src/analyze/analyze-dlopen-metadata.h +++ b/src/analyze/analyze-dlopen-metadata.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_dlopen_metadata(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_dlopen_metadata(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-dot.c b/src/analyze/analyze-dot.c index 0a250b7c80ca5..6f1044aa60356 100644 --- a/src/analyze/analyze-dot.c +++ b/src/analyze/analyze-dot.c @@ -146,7 +146,7 @@ static int expand_patterns(sd_bus *bus, char **patterns, char ***ret) { return 0; } -int verb_dot(int argc, char *argv[], void *userdata) { +int verb_dot(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; diff --git a/src/analyze/analyze-dot.h b/src/analyze/analyze-dot.h index 144b43d1ef74f..944e1d7a30ce9 100644 --- a/src/analyze/analyze-dot.h +++ b/src/analyze/analyze-dot.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_dot(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_dot(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-dump.c b/src/analyze/analyze-dump.c index 624403f2a9cc0..7d246535ccb6e 100644 --- a/src/analyze/analyze-dump.c +++ b/src/analyze/analyze-dump.c @@ -112,7 +112,7 @@ static int mangle_patterns(char **args, char ***ret) { return 0; } -int verb_dump(int argc, char *argv[], void *userdata) { +int verb_dump(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_strv_free_ char **patterns = NULL; int r; diff --git a/src/analyze/analyze-dump.h b/src/analyze/analyze-dump.h index 5d6107cb589ac..30c697e2adb31 100644 --- a/src/analyze/analyze-dump.h +++ b/src/analyze/analyze-dump.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_dump(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_dump(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-exit-status.c b/src/analyze/analyze-exit-status.c index 8b50684854672..b291ff0ab7cfb 100644 --- a/src/analyze/analyze-exit-status.c +++ b/src/analyze/analyze-exit-status.c @@ -7,7 +7,7 @@ #include "log.h" #include "strv.h" -int verb_exit_status(int argc, char *argv[], void *userdata) { +int verb_exit_status(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; int r; diff --git a/src/analyze/analyze-exit-status.h b/src/analyze/analyze-exit-status.h index ce14cdbb96de6..63e1601066d3b 100644 --- a/src/analyze/analyze-exit-status.h +++ b/src/analyze/analyze-exit-status.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_exit_status(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_exit_status(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-fdstore.c b/src/analyze/analyze-fdstore.c index 98c3d621463de..f9721119f2e22 100644 --- a/src/analyze/analyze-fdstore.c +++ b/src/analyze/analyze-fdstore.c @@ -101,7 +101,7 @@ static int dump_fdstore(sd_bus *bus, const char *arg) { return EXIT_SUCCESS; } -int verb_fdstore(int argc, char *argv[], void *userdata) { +int verb_fdstore(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; diff --git a/src/analyze/analyze-fdstore.h b/src/analyze/analyze-fdstore.h index d548ad2b4d1dd..b48496a384b9d 100644 --- a/src/analyze/analyze-fdstore.h +++ b/src/analyze/analyze-fdstore.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_fdstore(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_fdstore(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-filesystems.c b/src/analyze/analyze-filesystems.c index a0a2b5afed466..3fc3471d8849c 100644 --- a/src/analyze/analyze-filesystems.c +++ b/src/analyze/analyze-filesystems.c @@ -112,7 +112,7 @@ static void dump_filesystem_set(const FilesystemSet *set) { } } -int verb_filesystems(int argc, char *argv[], void *userdata) { +int verb_filesystems(int argc, char *argv[], uintptr_t _data, void *userdata) { #if ! HAVE_LIBBPF return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Not compiled with libbpf support, sorry."); #endif diff --git a/src/analyze/analyze-filesystems.h b/src/analyze/analyze-filesystems.h index 09045716d0a1c..480e5ae7deaf5 100644 --- a/src/analyze/analyze-filesystems.h +++ b/src/analyze/analyze-filesystems.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_filesystems(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_filesystems(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-has-tpm2.c b/src/analyze/analyze-has-tpm2.c index 2e7890cea148f..c01e686ce8ce5 100644 --- a/src/analyze/analyze-has-tpm2.c +++ b/src/analyze/analyze-has-tpm2.c @@ -8,11 +8,11 @@ #include "time-util.h" #include "tpm2-util.h" -int verb_has_tpm2(int argc, char **argv, void *userdata) { +int verb_has_tpm2(int argc, char *argv[], uintptr_t _data, void *userdata) { return verb_has_tpm2_generic(arg_quiet); } -int verb_identify_tpm2(int argc, char **argv, void *userdata) { +int verb_identify_tpm2(int argc, char **argv, uintptr_t _data, void *userdata) { #if HAVE_TPM2 int r; diff --git a/src/analyze/analyze-has-tpm2.h b/src/analyze/analyze-has-tpm2.h index b7d750090b57b..bb5af66fbbae1 100644 --- a/src/analyze/analyze-has-tpm2.h +++ b/src/analyze/analyze-has-tpm2.h @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_has_tpm2(int argc, char *argv[], void *userdata); -int verb_identify_tpm2(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_has_tpm2(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_identify_tpm2(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-image-policy.c b/src/analyze/analyze-image-policy.c index 65f54505979f8..93777c91a1f56 100644 --- a/src/analyze/analyze-image-policy.c +++ b/src/analyze/analyze-image-policy.c @@ -83,7 +83,7 @@ static int table_add_designator_line(Table *table, PartitionDesignator d, Partit return 0; } -int verb_image_policy(int argc, char *argv[], void *userdata) { +int verb_image_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; for (int i = 1; i < argc; i++) { diff --git a/src/analyze/analyze-image-policy.h b/src/analyze/analyze-image-policy.h index 16c1e966e896b..3e62e1279149a 100644 --- a/src/analyze/analyze-image-policy.h +++ b/src/analyze/analyze-image-policy.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_image_policy(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_image_policy(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-inspect-elf.c b/src/analyze/analyze-inspect-elf.c index d84601dfabe09..41dabd051c822 100644 --- a/src/analyze/analyze-inspect-elf.c +++ b/src/analyze/analyze-inspect-elf.c @@ -127,7 +127,7 @@ static int analyze_elf(char **filenames, sd_json_format_flags_t json_flags) { return 0; } -int verb_elf_inspection(int argc, char *argv[], void *userdata) { +int verb_elf_inspection(int argc, char *argv[], uintptr_t _data, void *userdata) { pager_open(arg_pager_flags); return analyze_elf(strv_skip(argv, 1), arg_json_format_flags); diff --git a/src/analyze/analyze-inspect-elf.h b/src/analyze/analyze-inspect-elf.h index a790eae6bb68e..890572ea00a01 100644 --- a/src/analyze/analyze-inspect-elf.h +++ b/src/analyze/analyze-inspect-elf.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_elf_inspection(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_elf_inspection(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-log-control.c b/src/analyze/analyze-log-control.c index f105d7d0326ab..13a5860436855 100644 --- a/src/analyze/analyze-log-control.c +++ b/src/analyze/analyze-log-control.c @@ -8,7 +8,7 @@ #include "runtime-scope.h" #include "verb-log-control.h" -int verb_log_control(int argc, char *argv[], void *userdata) { +int verb_log_control(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; diff --git a/src/analyze/analyze-log-control.h b/src/analyze/analyze-log-control.h index 350c22861a660..0ad41aeddc170 100644 --- a/src/analyze/analyze-log-control.h +++ b/src/analyze/analyze-log-control.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_log_control(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_log_control(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-malloc.c b/src/analyze/analyze-malloc.c index 35f533e790ae6..c892b37ce312a 100644 --- a/src/analyze/analyze-malloc.c +++ b/src/analyze/analyze-malloc.c @@ -34,7 +34,7 @@ static int dump_malloc_info(sd_bus *bus, char *service) { return bus_message_dump_fd(reply); } -int verb_malloc(int argc, char *argv[], void *userdata) { +int verb_malloc(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; char **services = STRV_MAKE("org.freedesktop.systemd1"); int r; diff --git a/src/analyze/analyze-malloc.h b/src/analyze/analyze-malloc.h index d3feabd757efb..3e30467e77b59 100644 --- a/src/analyze/analyze-malloc.h +++ b/src/analyze/analyze-malloc.h @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ - #pragma once -int verb_malloc(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_malloc(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-nvpcrs.c b/src/analyze/analyze-nvpcrs.c index ddb850fcef5a3..68e7acb33ac3e 100644 --- a/src/analyze/analyze-nvpcrs.c +++ b/src/analyze/analyze-nvpcrs.c @@ -50,7 +50,7 @@ static int add_nvpcr_to_table(Tpm2Context **c, Table *t, const char *name) { } #endif -int verb_nvpcrs(int argc, char *argv[], void *userdata) { +int verb_nvpcrs(int argc, char *argv[], uintptr_t _data, void *userdata) { #if HAVE_TPM2 _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; _cleanup_(table_unrefp) Table *table = NULL; diff --git a/src/analyze/analyze-nvpcrs.h b/src/analyze/analyze-nvpcrs.h index 258005617ea1e..7e287f2298a80 100644 --- a/src/analyze/analyze-nvpcrs.h +++ b/src/analyze/analyze-nvpcrs.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_nvpcrs(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_nvpcrs(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-pcrs.c b/src/analyze/analyze-pcrs.c index 8c1d7f3cfc65a..f98f4a8d50fe9 100644 --- a/src/analyze/analyze-pcrs.c +++ b/src/analyze/analyze-pcrs.c @@ -96,7 +96,7 @@ static int add_pcr_to_table(Table *table, const char *alg, uint32_t pcr) { return 0; } -int verb_pcrs(int argc, char *argv[], void *userdata) { +int verb_pcrs(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; const char *alg = NULL; int r; diff --git a/src/analyze/analyze-pcrs.h b/src/analyze/analyze-pcrs.h index 2a59511885aee..7fa5a8379f251 100644 --- a/src/analyze/analyze-pcrs.h +++ b/src/analyze/analyze-pcrs.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_pcrs(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_pcrs(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-plot.c b/src/analyze/analyze-plot.c index 21a9aa9f1cf08..8460757b8ac89 100644 --- a/src/analyze/analyze-plot.c +++ b/src/analyze/analyze-plot.c @@ -470,7 +470,7 @@ static int produce_plot_as_text(UnitTimes *times, const BootTimes *boot) { return show_table(table, "Units"); } -int verb_plot(int argc, char *argv[], void *userdata) { +int verb_plot(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(free_host_infop) HostInfo *host = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL; diff --git a/src/analyze/analyze-plot.h b/src/analyze/analyze-plot.h index eb2e398b3109a..4854498ce9810 100644 --- a/src/analyze/analyze-plot.h +++ b/src/analyze/analyze-plot.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_plot(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_plot(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-security.c b/src/analyze/analyze-security.c index 3e086a1775d6b..0f763f75e9eba 100644 --- a/src/analyze/analyze-security.c +++ b/src/analyze/analyze-security.c @@ -2904,7 +2904,7 @@ static int analyze_security(sd_bus *bus, return ret; } -int verb_security(int argc, char *argv[], void *userdata) { +int verb_security(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *policy = NULL; int r; diff --git a/src/analyze/analyze-security.h b/src/analyze/analyze-security.h index 82f4c7fdeea7f..cd5fec307cd9b 100644 --- a/src/analyze/analyze-security.h +++ b/src/analyze/analyze-security.h @@ -1,10 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "shared-forward.h" + typedef enum AnalyzeSecurityFlags { ANALYZE_SECURITY_SHORT = 1 << 0, ANALYZE_SECURITY_ONLY_LOADED = 1 << 1, ANALYZE_SECURITY_ONLY_LONG_RUNNING = 1 << 2, } AnalyzeSecurityFlags; -int verb_security(int argc, char *argv[], void *userdata); +int verb_security(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-service-watchdogs.c b/src/analyze/analyze-service-watchdogs.c index 4a2892273c968..4d27fef5330c3 100644 --- a/src/analyze/analyze-service-watchdogs.c +++ b/src/analyze/analyze-service-watchdogs.c @@ -11,7 +11,7 @@ #include "runtime-scope.h" #include "string-util.h" -int verb_service_watchdogs(int argc, char *argv[], void *userdata) { +int verb_service_watchdogs(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int b, r; diff --git a/src/analyze/analyze-service-watchdogs.h b/src/analyze/analyze-service-watchdogs.h index 2f59f5a3f4376..e626a223c35f5 100644 --- a/src/analyze/analyze-service-watchdogs.h +++ b/src/analyze/analyze-service-watchdogs.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_service_watchdogs(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_service_watchdogs(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-smbios11.c b/src/analyze/analyze-smbios11.c index 30c18fef273b7..d76bc106d199a 100644 --- a/src/analyze/analyze-smbios11.c +++ b/src/analyze/analyze-smbios11.c @@ -10,7 +10,7 @@ #include "log.h" #include "smbios11.h" -int verb_smbios11(int argc, char *argv[], void *userdata) { +int verb_smbios11(int argc, char *argv[], uintptr_t _data, void *userdata) { unsigned n = 0; int r; diff --git a/src/analyze/analyze-smbios11.h b/src/analyze/analyze-smbios11.h index 4b1f334dc8fd7..32ae157fa1136 100644 --- a/src/analyze/analyze-smbios11.h +++ b/src/analyze/analyze-smbios11.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_smbios11(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_smbios11(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-srk.c b/src/analyze/analyze-srk.c index 2b81c864a0dac..dcf9459f2dee3 100644 --- a/src/analyze/analyze-srk.c +++ b/src/analyze/analyze-srk.c @@ -10,7 +10,7 @@ #include "terminal-util.h" #include "tpm2-util.h" -int verb_srk(int argc, char *argv[], void *userdata) { +int verb_srk(int argc, char *argv[], uintptr_t _data, void *userdata) { #if HAVE_TPM2 _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; _cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL; diff --git a/src/analyze/analyze-srk.h b/src/analyze/analyze-srk.h index 26028354f86ab..73d3c865ad0e4 100644 --- a/src/analyze/analyze-srk.h +++ b/src/analyze/analyze-srk.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_srk(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_srk(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-syscall-filter.c b/src/analyze/analyze-syscall-filter.c index 11024803fdf3d..cb2a2eab16acd 100644 --- a/src/analyze/analyze-syscall-filter.c +++ b/src/analyze/analyze-syscall-filter.c @@ -106,7 +106,7 @@ static void dump_syscall_filter(const SyscallFilterSet *set) { printf(" %s%s%s\n", syscall[0] == '@' ? ansi_underline() : "", syscall, ansi_normal()); } -int verb_syscall_filters(int argc, char *argv[], void *userdata) { +int verb_syscall_filters(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; pager_open(arg_pager_flags); @@ -195,7 +195,7 @@ int verb_syscall_filters(int argc, char *argv[], void *userdata) { } #else -int verb_syscall_filters(int argc, char *argv[], void *userdata) { +int verb_syscall_filters(int argc, char *argv[], uintptr_t _data, void *userdata) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Not compiled with syscall filters, sorry."); } #endif diff --git a/src/analyze/analyze-syscall-filter.h b/src/analyze/analyze-syscall-filter.h index 3a1af85a69456..a648b3c603b3a 100644 --- a/src/analyze/analyze-syscall-filter.h +++ b/src/analyze/analyze-syscall-filter.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_syscall_filters(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_syscall_filters(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-time.c b/src/analyze/analyze-time.c index da69eebeb4e9d..a4faee977f6aa 100644 --- a/src/analyze/analyze-time.c +++ b/src/analyze/analyze-time.c @@ -9,7 +9,7 @@ #include "bus-util.h" #include "runtime-scope.h" -int verb_time(int argc, char *argv[], void *userdata) { +int verb_time(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_free_ char *buf = NULL; int r; diff --git a/src/analyze/analyze-time.h b/src/analyze/analyze-time.h index a8f8575c3b6f7..23591d8fd4ddf 100644 --- a/src/analyze/analyze-time.h +++ b/src/analyze/analyze-time.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_time(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_time(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-timespan.c b/src/analyze/analyze-timespan.c index cb6e4f00fe121..fb077617d476e 100644 --- a/src/analyze/analyze-timespan.c +++ b/src/analyze/analyze-timespan.c @@ -9,7 +9,7 @@ #include "strv.h" #include "time-util.h" -int verb_timespan(int argc, char *argv[], void *userdata) { +int verb_timespan(int argc, char *argv[], uintptr_t _data, void *userdata) { STRV_FOREACH(input_timespan, strv_skip(argv, 1)) { _cleanup_(table_unrefp) Table *table = NULL; usec_t output_usecs; diff --git a/src/analyze/analyze-timespan.h b/src/analyze/analyze-timespan.h index 46d2295600819..b2f238b413029 100644 --- a/src/analyze/analyze-timespan.h +++ b/src/analyze/analyze-timespan.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_timespan(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_timespan(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-timestamp.c b/src/analyze/analyze-timestamp.c index 2271133268345..e7ba6e1bcc1b9 100644 --- a/src/analyze/analyze-timestamp.c +++ b/src/analyze/analyze-timestamp.c @@ -76,7 +76,7 @@ static int test_timestamp_one(const char *p) { return table_print(table, NULL); } -int verb_timestamp(int argc, char *argv[], void *userdata) { +int verb_timestamp(int argc, char *argv[], uintptr_t _data, void *userdata) { int r = 0; char **args = strv_skip(argv, 1); diff --git a/src/analyze/analyze-timestamp.h b/src/analyze/analyze-timestamp.h index 43e4b57d2a330..e71ab7055414c 100644 --- a/src/analyze/analyze-timestamp.h +++ b/src/analyze/analyze-timestamp.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_timestamp(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_timestamp(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-unit-files.c b/src/analyze/analyze-unit-files.c index 2d59d1808fdcb..a15815ddcc000 100644 --- a/src/analyze/analyze-unit-files.c +++ b/src/analyze/analyze-unit-files.c @@ -21,7 +21,7 @@ static bool strv_fnmatch_strv_or_empty(char* const* patterns, char **strv, int f return false; } -int verb_unit_files(int argc, char *argv[], void *userdata) { +int verb_unit_files(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_hashmap_free_ Hashmap *unit_ids = NULL, *unit_names = NULL; _cleanup_(lookup_paths_done) LookupPaths lp = {}; char **patterns = strv_skip(argv, 1); diff --git a/src/analyze/analyze-unit-files.h b/src/analyze/analyze-unit-files.h index c193fd82746a6..4dc46e7d7b323 100644 --- a/src/analyze/analyze-unit-files.h +++ b/src/analyze/analyze-unit-files.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_unit_files(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_unit_files(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-unit-gdb.c b/src/analyze/analyze-unit-gdb.c index 1d989462cf15d..b208f0b9289fe 100644 --- a/src/analyze/analyze-unit-gdb.c +++ b/src/analyze/analyze-unit-gdb.c @@ -19,7 +19,7 @@ #include "unit-def.h" #include "unit-name.h" -int verb_unit_gdb(int argc, char *argv[], void *userdata) { +int verb_unit_gdb(int argc, char *argv[], uintptr_t _data, void *userdata) { static const struct sigaction sa = { .sa_sigaction = sigterm_process_group_handler, .sa_flags = SA_SIGINFO, diff --git a/src/analyze/analyze-unit-gdb.h b/src/analyze/analyze-unit-gdb.h index a3df6b128f15c..4e62610c5864a 100644 --- a/src/analyze/analyze-unit-gdb.h +++ b/src/analyze/analyze-unit-gdb.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_unit_gdb(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_unit_gdb(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-unit-paths.c b/src/analyze/analyze-unit-paths.c index 500b768299080..3903166bf29f5 100644 --- a/src/analyze/analyze-unit-paths.c +++ b/src/analyze/analyze-unit-paths.c @@ -7,7 +7,7 @@ #include "path-lookup.h" #include "strv.h" -int verb_unit_paths(int argc, char *argv[], void *userdata) { +int verb_unit_paths(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(lookup_paths_done) LookupPaths paths = {}; int r; diff --git a/src/analyze/analyze-unit-paths.h b/src/analyze/analyze-unit-paths.h index b8d46e87d00a7..609c17074f0c4 100644 --- a/src/analyze/analyze-unit-paths.h +++ b/src/analyze/analyze-unit-paths.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_unit_paths(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_unit_paths(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-unit-shell.c b/src/analyze/analyze-unit-shell.c index 8990ffdf1de13..98dc474e6211c 100644 --- a/src/analyze/analyze-unit-shell.c +++ b/src/analyze/analyze-unit-shell.c @@ -20,7 +20,7 @@ #include "unit-def.h" #include "unit-name.h" -int verb_unit_shell(int argc, char *argv[], void *userdata) { +int verb_unit_shell(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; diff --git a/src/analyze/analyze-unit-shell.h b/src/analyze/analyze-unit-shell.h index 7c15e083a8e0f..533cd9a9892f7 100644 --- a/src/analyze/analyze-unit-shell.h +++ b/src/analyze/analyze-unit-shell.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_unit_shell(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_unit_shell(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze-verify.c b/src/analyze/analyze-verify.c index b87fc45767d6d..3369f6c3b96a1 100644 --- a/src/analyze/analyze-verify.c +++ b/src/analyze/analyze-verify.c @@ -59,7 +59,7 @@ static int process_aliases(char *argv[], char *tempdir, char ***ret) { return 0; } -int verb_verify(int argc, char *argv[], void *userdata) { +int verb_verify(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_strv_free_ char **filenames = NULL; _cleanup_(rm_rf_physical_and_freep) char *tempdir = NULL; int r; diff --git a/src/analyze/analyze-verify.h b/src/analyze/analyze-verify.h index 4892c9aa4f532..0108ccf3a1b12 100644 --- a/src/analyze/analyze-verify.h +++ b/src/analyze/analyze-verify.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_verify(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_verify(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index af456314db446..4d078a483851e 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -170,7 +170,7 @@ void time_parsing_hint(const char *p, bool calendar, bool timestamp, bool timesp "Use 'systemd-analyze timespan \"%s\"' instead?", p); } -static int verb_transient_settings(int argc, char *argv[], void *userdata) { +static int verb_transient_settings(int argc, char *argv[], uintptr_t _data, void *userdata) { assert(argc >= 2); pager_open(arg_pager_flags); @@ -193,7 +193,7 @@ static int verb_transient_settings(int argc, char *argv[], void *userdata) { return 0; } -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL, *dot_link = NULL; int r; @@ -326,6 +326,10 @@ static int help(int argc, char *argv[], void *userdata) { return 0; } +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { + return help(); +} + static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, @@ -466,7 +470,7 @@ static int parse_argv(int argc, char *argv[]) { break; case 'h': - return help(0, NULL, NULL); + return help(); case ARG_VERSION: return version(); @@ -773,7 +777,7 @@ static int run(int argc, char *argv[]) { _cleanup_(umount_and_freep) char *mounted_dir = NULL; static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, { "time", VERB_ANY, 1, VERB_DEFAULT, verb_time }, { "blame", VERB_ANY, 1, 0, verb_blame }, { "critical-chain", VERB_ANY, VERB_ANY, 0, verb_critical_chain }, diff --git a/src/backlight/backlight.c b/src/backlight/backlight.c index 027379809d10f..69bd42ebcc828 100644 --- a/src/backlight/backlight.c +++ b/src/backlight/backlight.c @@ -555,7 +555,7 @@ static int device_new_from_arg(const char *s, sd_device **ret) { return 1; /* Found. */ } -static int verb_load(int argc, char *argv[], void *userdata) { +static int verb_load(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_device_unrefp) sd_device *device = NULL; unsigned max_brightness, brightness, percent; bool clamp; @@ -605,7 +605,7 @@ static int verb_load(int argc, char *argv[], void *userdata) { return 0; } -static int verb_save(int argc, char *argv[], void *userdata) { +static int verb_save(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_device_unrefp) sd_device *device = NULL; _cleanup_free_ char *path = NULL; unsigned max_brightness, brightness; diff --git a/src/bless-boot/bless-boot.c b/src/bless-boot/bless-boot.c index 1df341cf59baf..72902e3d5017a 100644 --- a/src/bless-boot/bless-boot.c +++ b/src/bless-boot/bless-boot.c @@ -26,7 +26,7 @@ static char **arg_path = NULL; STATIC_DESTRUCTOR_REGISTER(arg_path, strv_freep); -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -54,6 +54,10 @@ static int help(int argc, char *argv[], void *userdata) { return 0; } +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { + return help(); +} + static int parse_argv(int argc, char *argv[]) { enum { ARG_PATH = 0x100, @@ -76,8 +80,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - help(0, NULL, NULL); - return 0; + return help(); case ARG_VERSION: return version(); @@ -335,7 +338,7 @@ static int make_bad(const char *prefix, uint64_t done, const char *suffix, char return 0; } -static int verb_status(int argc, char *argv[], void *userdata) { +static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL; uint64_t left, done; int r; @@ -441,7 +444,7 @@ static int rename_in_dir_idempotent(int fd, const char *from, const char *to) { return 1; } -static int verb_set(int argc, char *argv[], void *userdata) { +static int verb_set(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL; const char *target, *source1, *source2; uint64_t left, done; @@ -547,7 +550,7 @@ static int verb_set(int argc, char *argv[], void *userdata) { static int run(int argc, char *argv[]) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, { "good", VERB_ANY, 1, 0, verb_set }, { "bad", VERB_ANY, 1, 0, verb_set }, diff --git a/src/bootctl/bootctl-cleanup.c b/src/bootctl/bootctl-cleanup.c index 59f67edb999bc..66e0005605843 100644 --- a/src/bootctl/bootctl-cleanup.c +++ b/src/bootctl/bootctl-cleanup.c @@ -87,7 +87,7 @@ static int cleanup_orphaned_files( return r; } -int verb_cleanup(int argc, char *argv[], void *userdata) { +int verb_cleanup(int argc, char *argv[], uintptr_t _data, void *userdata) { dev_t esp_devid = 0, xbootldr_devid = 0; int r; diff --git a/src/bootctl/bootctl-cleanup.h b/src/bootctl/bootctl-cleanup.h index ffe930b90073d..22d087374eb0b 100644 --- a/src/bootctl/bootctl-cleanup.h +++ b/src/bootctl/bootctl-cleanup.h @@ -3,4 +3,4 @@ #include "shared-forward.h" -int verb_cleanup(int argc, char *argv[], void *userdata); +int verb_cleanup(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index 4d56c6874e9ca..3724a1cfb9402 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -1661,7 +1661,7 @@ static int run_install(InstallContext *c) { return install_variables(c, path); } -int verb_install(int argc, char *argv[], void *userdata) { +int verb_install(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; /* Invoked for both "update" and "install" */ @@ -1882,7 +1882,7 @@ static int remove_loader_variables(void) { return r; } -int verb_remove(int argc, char *argv[], void *userdata) { +int verb_remove(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_id128_t uuid = SD_ID128_NULL; int r; @@ -1956,7 +1956,7 @@ int verb_remove(int argc, char *argv[], void *userdata) { return RET_GATHER(r, remove_loader_variables()); } -int verb_is_installed(int argc, char *argv[], void *userdata) { +int verb_is_installed(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; _cleanup_(install_context_done) InstallContext c = INSTALL_CONTEXT_NULL; diff --git a/src/bootctl/bootctl-install.h b/src/bootctl/bootctl-install.h index f2d7fab5c965e..c9019520cc9e9 100644 --- a/src/bootctl/bootctl-install.h +++ b/src/bootctl/bootctl-install.h @@ -3,8 +3,8 @@ #include "shared-forward.h" -int verb_install(int argc, char *argv[], void *userdata); -int verb_remove(int argc, char *argv[], void *userdata); -int verb_is_installed(int argc, char *argv[], void *userdata); +int verb_install(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_remove(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_is_installed(int argc, char *argv[], uintptr_t _data, void *userdata); int vl_method_install(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/bootctl/bootctl-random-seed.c b/src/bootctl/bootctl-random-seed.c index 6f5249aeeb4c4..2ef491c54f84e 100644 --- a/src/bootctl/bootctl-random-seed.c +++ b/src/bootctl/bootctl-random-seed.c @@ -201,7 +201,7 @@ int install_random_seed(const char *esp) { return set_system_token(); } -int verb_random_seed(int argc, char *argv[], void *userdata) { +int verb_random_seed(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; r = find_esp_and_warn(arg_root, arg_esp_path, /* unprivileged_mode= */ false, &arg_esp_path); diff --git a/src/bootctl/bootctl-random-seed.h b/src/bootctl/bootctl-random-seed.h index 91596d3c818e2..722c511b74808 100644 --- a/src/bootctl/bootctl-random-seed.h +++ b/src/bootctl/bootctl-random-seed.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "shared-forward.h" + int install_random_seed(const char *esp); -int verb_random_seed(int argc, char *argv[], void *userdata); +int verb_random_seed(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/bootctl/bootctl-reboot-to-firmware.c b/src/bootctl/bootctl-reboot-to-firmware.c index be20a4a13227a..aa5f186af9d4a 100644 --- a/src/bootctl/bootctl-reboot-to-firmware.c +++ b/src/bootctl/bootctl-reboot-to-firmware.c @@ -12,7 +12,7 @@ #include "log.h" #include "parse-util.h" -int verb_reboot_to_firmware(int argc, char *argv[], void *userdata) { +int verb_reboot_to_firmware(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; r = verify_touch_variables_allowed(argv[0]); diff --git a/src/bootctl/bootctl-reboot-to-firmware.h b/src/bootctl/bootctl-reboot-to-firmware.h index c8b55004480be..0262c861c4006 100644 --- a/src/bootctl/bootctl-reboot-to-firmware.h +++ b/src/bootctl/bootctl-reboot-to-firmware.h @@ -3,7 +3,7 @@ #include "shared-forward.h" -int verb_reboot_to_firmware(int argc, char *argv[], void *userdata); +int verb_reboot_to_firmware(int argc, char *argv[], uintptr_t _data, void *userdata); int vl_method_set_reboot_to_firmware(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_get_reboot_to_firmware(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/bootctl/bootctl-set-efivar.c b/src/bootctl/bootctl-set-efivar.c index bb853d65afe88..5edcd29f313be 100644 --- a/src/bootctl/bootctl-set-efivar.c +++ b/src/bootctl/bootctl-set-efivar.c @@ -129,7 +129,7 @@ static int parse_loader_entry_target_arg(const char *arg1, char16_t **ret_target return 0; } -int verb_set_efivar(int argc, char *argv[], void *userdata) { +int verb_set_efivar(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; r = verify_touch_variables_allowed(argv[0]); diff --git a/src/bootctl/bootctl-set-efivar.h b/src/bootctl/bootctl-set-efivar.h index 6441681081ae1..ee5e518b440a9 100644 --- a/src/bootctl/bootctl-set-efivar.h +++ b/src/bootctl/bootctl-set-efivar.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_set_efivar(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_set_efivar(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/bootctl/bootctl-status.c b/src/bootctl/bootctl-status.c index 0804070d24dfc..4184e3d4249aa 100644 --- a/src/bootctl/bootctl-status.c +++ b/src/bootctl/bootctl-status.c @@ -326,7 +326,7 @@ static void print_yes_no_line(bool first, bool good, const char *name) { name); } -int verb_status(int argc, char *argv[], void *userdata) { +int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_id128_t esp_uuid = SD_ID128_NULL, xbootldr_uuid = SD_ID128_NULL; dev_t esp_devid = 0, xbootldr_devid = 0; int r, k; @@ -621,7 +621,7 @@ int verb_status(int argc, char *argv[], void *userdata) { return r; } -int verb_list(int argc, char *argv[], void *userdata) { +int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; dev_t esp_devid = 0, xbootldr_devid = 0; int r; diff --git a/src/bootctl/bootctl-status.h b/src/bootctl/bootctl-status.h index 36609fb075b64..941bcdd9aca79 100644 --- a/src/bootctl/bootctl-status.h +++ b/src/bootctl/bootctl-status.h @@ -3,7 +3,7 @@ #include "shared-forward.h" -int verb_status(int argc, char *argv[], void *userdata); -int verb_list(int argc, char *argv[], void *userdata); +int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata); int vl_method_list_boot_entries(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/bootctl/bootctl-uki.c b/src/bootctl/bootctl-uki.c index 2f71ccd36e1de..1b52252210036 100644 --- a/src/bootctl/bootctl-uki.c +++ b/src/bootctl/bootctl-uki.c @@ -6,7 +6,7 @@ #include "bootctl-uki.h" #include "kernel-image.h" -int verb_kernel_identify(int argc, char *argv[], void *userdata) { +int verb_kernel_identify(int argc, char *argv[], uintptr_t _data, void *userdata) { KernelImageType t; int r; @@ -18,7 +18,7 @@ int verb_kernel_identify(int argc, char *argv[], void *userdata) { return 0; } -int verb_kernel_inspect(int argc, char *argv[], void *userdata) { +int verb_kernel_inspect(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ char *cmdline = NULL, *uname = NULL, *pname = NULL; KernelImageType t; int r; diff --git a/src/bootctl/bootctl-uki.h b/src/bootctl/bootctl-uki.h index 99c8ff5c8bf9f..febac7394d354 100644 --- a/src/bootctl/bootctl-uki.h +++ b/src/bootctl/bootctl-uki.h @@ -3,5 +3,5 @@ #include "shared-forward.h" -int verb_kernel_identify(int argc, char *argv[], void *userdata); -int verb_kernel_inspect(int argc, char *argv[], void *userdata); +int verb_kernel_identify(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_kernel_inspect(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/bootctl/bootctl-unlink.c b/src/bootctl/bootctl-unlink.c index 287b2b7904c92..428c751f06279 100644 --- a/src/bootctl/bootctl-unlink.c +++ b/src/bootctl/bootctl-unlink.c @@ -199,7 +199,7 @@ static int unlink_entry(const BootConfig *config, const char *root, const char * return 0; } -int verb_unlink(int argc, char *argv[], void *userdata) { +int verb_unlink(int argc, char *argv[], uintptr_t _data, void *userdata) { dev_t esp_devid = 0, xbootldr_devid = 0; int r; diff --git a/src/bootctl/bootctl-unlink.h b/src/bootctl/bootctl-unlink.h index 5737977ae5ecf..5c33088859437 100644 --- a/src/bootctl/bootctl-unlink.h +++ b/src/bootctl/bootctl-unlink.h @@ -3,6 +3,6 @@ #include "shared-forward.h" -int verb_unlink(int argc, char *argv[], void *userdata); +int verb_unlink(int argc, char *argv[], uintptr_t _data, void *userdata); int boot_config_count_known_files(const BootConfig *config, const char* root, Hashmap **ret_known_files); diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index 65a5da3358faf..68478612ed891 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -244,7 +244,7 @@ GracefulMode arg_graceful(void) { return _arg_graceful; } -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -355,6 +355,10 @@ static int help(int argc, char *argv[], void *userdata) { return 0; } +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { + return help(); +} + static int parse_argv(int argc, char *argv[]) { enum { ARG_ESP_PATH = 0x100, @@ -432,8 +436,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - help(0, NULL, NULL); - return 0; + return help(); case ARG_VERSION: return version(); @@ -672,7 +675,7 @@ static int parse_argv(int argc, char *argv[]) { static int bootctl_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, { "install", VERB_ANY, 1, 0, verb_install }, { "update", VERB_ANY, 1, 0, verb_install }, diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c index 2cebd311c0caa..48635fad64c4c 100644 --- a/src/busctl/busctl.c +++ b/src/busctl/busctl.c @@ -175,7 +175,7 @@ static void notify_bus_error(const sd_bus_error *error) { (void) sd_notifyf(/* unset_environment= */ false, "BUSERROR=%s", error->name); } -static int verb_list_bus_names(int argc, char **argv, void *userdata) { +static int verb_list_bus_names(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_strv_free_ char **acquired = NULL, **activatable = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_hashmap_free_ Hashmap *names = NULL; @@ -535,7 +535,7 @@ static int tree_one(sd_bus *bus, const char *service) { return r; } -static int verb_tree(int argc, char **argv, void *userdata) { +static int verb_tree(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; @@ -994,7 +994,7 @@ static int members_flags_to_string(const Member *m, char **ret) { return 0; } -static int verb_introspect(int argc, char **argv, void *userdata) { +static int verb_introspect(int argc, char *argv[], uintptr_t _data, void *userdata) { static const XMLIntrospectOps ops = { .on_interface = on_interface, .on_method = on_method, @@ -1381,11 +1381,11 @@ static int monitor(int argc, char **argv, int (*dump)(sd_bus_message *m, FILE *f } } -static int verb_monitor(int argc, char **argv, void *userdata) { +static int verb_monitor(int argc, char *argv[], uintptr_t _data, void *userdata) { return monitor(argc, argv, sd_json_format_enabled(arg_json_format_flags) ? message_json : message_dump); } -static int verb_capture(int argc, char **argv, void *userdata) { +static int verb_capture(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ char *osname = NULL; static const char info[] = "busctl (systemd) " PROJECT_VERSION_FULL " (Git " GIT_VERSION ")"; @@ -1412,7 +1412,7 @@ static int verb_capture(int argc, char **argv, void *userdata) { return r; } -static int verb_status(int argc, char **argv, void *userdata) { +static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; pid_t pid; @@ -1782,7 +1782,7 @@ static int bus_message_dump(sd_bus_message *m, uint64_t flags) { return 0; } -static int verb_call(int argc, char **argv, void *userdata) { +static int verb_call(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; @@ -1849,7 +1849,7 @@ static int verb_call(int argc, char **argv, void *userdata) { return bus_message_dump(reply, /* flags= */ 0); } -static int verb_emit_signal(int argc, char **argv, void *userdata) { +static int verb_emit_signal(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_fdset_free_ FDSet *passed_fdset = NULL; @@ -1894,7 +1894,7 @@ static int verb_emit_signal(int argc, char **argv, void *userdata) { return 0; } -static int verb_get_property(int argc, char **argv, void *userdata) { +static int verb_get_property(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -1952,7 +1952,7 @@ static int on_bus_signal(sd_bus_message *msg, void *userdata, sd_bus_error *ret_ return 0; } -static int verb_wait_signal(int argc, char **argv, void *userdata) { +static int verb_wait_signal(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_event_unrefp) sd_event *e = NULL; _cleanup_(sd_event_source_unrefp) sd_event_source *timer = NULL; @@ -2000,7 +2000,7 @@ static int verb_wait_signal(int argc, char **argv, void *userdata) { return sd_event_loop(e); } -static int verb_set_property(int argc, char **argv, void *userdata) { +static int verb_set_property(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -2131,7 +2131,7 @@ static int help(void) { return 0; } -static int verb_help(int argc, char **argv, void *userdata) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { return help(); } diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index 2a07935a72f71..0f655a4694d9b 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -223,7 +223,7 @@ static int help(void) { return 0; } -static int verb_help(int argc, char *argv[], void *userdata) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { return help(); } @@ -272,7 +272,7 @@ static int parse_argv(int argc, char *argv[]) { while ((c = getopt_long(argc, argv, "hA:o:F:1D:rS:U:qn:", options, NULL)) >= 0) switch (c) { case 'h': - return verb_help(0, NULL, NULL); + return help(); case ARG_VERSION: return version(); @@ -893,7 +893,7 @@ static int print_entry( return print_info(stdout, j, n_found > 0); } -static int verb_dump_list(int argc, char **argv, void *userdata) { +static int verb_dump_list(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_journal_closep) sd_journal *j = NULL; _cleanup_(table_unrefp) Table *t = NULL; size_t n_found = 0; @@ -1144,7 +1144,7 @@ static int save_core(sd_journal *j, FILE *file, char **path, bool *unlink_temp) return r; } -static int verb_dump_core(int argc, char **argv, void *userdata) { +static int verb_dump_core(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_journal_closep) sd_journal *j = NULL; _cleanup_fclose_ FILE *f = NULL; int r; @@ -1180,7 +1180,7 @@ static int verb_dump_core(int argc, char **argv, void *userdata) { return 0; } -static int verb_run_debug(int argc, char **argv, void *userdata) { +static int verb_run_debug(int argc, char *argv[], uintptr_t _data, void *userdata) { static const struct sigaction sa = { .sa_sigaction = sigterm_process_group_handler, .sa_flags = SA_SIGINFO, diff --git a/src/creds/creds.c b/src/creds/creds.c index bcdf3d30384b0..5f60f781f6c45 100644 --- a/src/creds/creds.c +++ b/src/creds/creds.c @@ -305,7 +305,7 @@ static int add_credentials_to_table(Table *t, bool encrypted) { return 1; /* Creds dir set */ } -static int verb_list(int argc, char **argv, void *userdata) { +static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *t = NULL; int r, q; @@ -465,14 +465,14 @@ static int write_blob(FILE *f, const void *data, size_t size) { return 0; } -static int verb_cat(int argc, char **argv, void *userdata) { +static int verb_cat(int argc, char *argv[], uintptr_t _data, void *userdata) { usec_t timestamp; int r, ret = 0; timestamp = arg_timestamp != USEC_INFINITY ? arg_timestamp : now(CLOCK_REALTIME); STRV_FOREACH(cn, strv_skip(argv, 1)) { - _cleanup_(erase_and_freep) void *data = NULL; + _cleanup_(erase_and_freep) void *content = NULL; size_t size = 0; int encrypted; @@ -500,7 +500,7 @@ static int verb_cat(int argc, char **argv, void *userdata) { UINT64_MAX, SIZE_MAX, flags, NULL, - (char**) &data, &size); + (char**) &content, &size); if (r == -ENOENT) /* Not found */ continue; if (r >= 0) /* Found */ @@ -522,7 +522,7 @@ static int verb_cat(int argc, char **argv, void *userdata) { *cn, timestamp, uid_is_valid(arg_uid) ? arg_uid : getuid(), - &IOVEC_MAKE(data, size), + &IOVEC_MAKE(content, size), arg_credential_flags | CREDENTIAL_ANY_SCOPE, &plaintext); else @@ -532,18 +532,18 @@ static int verb_cat(int argc, char **argv, void *userdata) { arg_tpm2_device, arg_tpm2_signature, uid_is_valid(arg_uid) ? arg_uid : getuid(), - &IOVEC_MAKE(data, size), + &IOVEC_MAKE(content, size), arg_credential_flags | CREDENTIAL_ANY_SCOPE, &plaintext); if (r < 0) return r; - erase_and_free(data); - data = TAKE_PTR(plaintext.iov_base); + erase_and_free(content); + content = TAKE_PTR(plaintext.iov_base); size = plaintext.iov_len; } - r = write_blob(stdout, data, size); + r = write_blob(stdout, content, size); if (r < 0) return r; } @@ -551,7 +551,7 @@ static int verb_cat(int argc, char **argv, void *userdata) { return ret; } -static int verb_encrypt(int argc, char **argv, void *userdata) { +static int verb_encrypt(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(iovec_done_erase) struct iovec plaintext = {}, output = {}; _cleanup_free_ char *base64_buf = NULL, *fname = NULL; const char *input_path, *output_path, *name; @@ -659,7 +659,7 @@ static int verb_encrypt(int argc, char **argv, void *userdata) { return EXIT_SUCCESS; } -static int verb_decrypt(int argc, char **argv, void *userdata) { +static int verb_decrypt(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(iovec_done_erase) struct iovec input = {}, plaintext = {}; _cleanup_free_ char *fname = NULL; _cleanup_fclose_ FILE *output_file = NULL; @@ -741,7 +741,7 @@ static int verb_decrypt(int argc, char **argv, void *userdata) { return EXIT_SUCCESS; } -static int verb_setup(int argc, char **argv, void *userdata) { +static int verb_setup(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(iovec_done_erase) struct iovec host_key = {}; int r; @@ -754,14 +754,14 @@ static int verb_setup(int argc, char **argv, void *userdata) { return EXIT_SUCCESS; } -static int verb_has_tpm2(int argc, char **argv, void *userdata) { +static int verb_has_tpm2(int argc, char *argv[], uintptr_t _data, void *userdata) { if (!arg_quiet) log_notice("The 'systemd-creds %1$s' command has been replaced by 'systemd-analyze %1$s'. Redirecting invocation.", argv[optind]); return verb_has_tpm2_generic(arg_quiet); } -static int verb_help(int argc, char **argv, void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -825,6 +825,10 @@ static int verb_help(int argc, char **argv, void *userdata) { return 0; } +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { + return help(); +} + static int parse_argv(int argc, char *argv[]) { enum { @@ -889,7 +893,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - return verb_help(0, NULL, NULL); + return help(); case ARG_VERSION: return version(); diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index dbd0b14d7bd1b..bda9a8cc84a97 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -2586,7 +2586,7 @@ static int discover_key(const char *key_file, const char *volume, TokenType toke return r; } -static int verb_attach(int argc, char *argv[], void *userdata) { +static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; _unused_ _cleanup_(remove_and_erasep) const char *destroy_key_file = NULL; crypt_status_info status; @@ -2826,7 +2826,7 @@ static int verb_attach(int argc, char *argv[], void *userdata) { return 0; } -static int verb_detach(int argc, char *argv[], void *userdata) { +static int verb_detach(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; const char *volume = ASSERT_PTR(argv[1]); int r; diff --git a/src/factory-reset/factory-reset-tool.c b/src/factory-reset/factory-reset-tool.c index 2f0fe97ca6f01..76aec0576480d 100644 --- a/src/factory-reset/factory-reset-tool.c +++ b/src/factory-reset/factory-reset-tool.c @@ -109,7 +109,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -static int verb_status(int argc, char *argv[], void *userdata) { +static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { static const int exit_status_table[_FACTORY_RESET_MODE_MAX] = { /* Report current mode also as via exit status, but only return a subset of states */ [FACTORY_RESET_UNSUPPORTED] = EXIT_SUCCESS, @@ -130,7 +130,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { return exit_status_table[f]; } -static int verb_request(int argc, char *argv[], void *userdata) { +static int verb_request(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; FactoryResetMode f = factory_reset_mode(); @@ -197,7 +197,7 @@ static int verb_request(int argc, char *argv[], void *userdata) { return 0; } -static int verb_cancel(int argc, char *argv[], void *userdata) { +static int verb_cancel(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; FactoryResetMode f = factory_reset_mode(); @@ -269,7 +269,7 @@ static int retrigger_block_devices(void) { return 0; } -static int verb_complete(int argc, char *argv[], void *userdata) { +static int verb_complete(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; FactoryResetMode f = factory_reset_mode(); diff --git a/src/home/homectl.c b/src/home/homectl.c index 21ac8b055be86..a8ebf85145a93 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -178,7 +178,7 @@ static int acquire_bus(sd_bus **bus) { return 0; } -static int verb_list_homes(int argc, char *argv[], void *userdata) { +static int verb_list_homes(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -627,7 +627,7 @@ static int acquire_passed_secrets(const char *user_name, UserRecord **ret) { return 0; } -static int verb_activate_home(int argc, char *argv[], void *userdata) { +static int verb_activate_home(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r, ret = 0; @@ -675,7 +675,7 @@ static int verb_activate_home(int argc, char *argv[], void *userdata) { return ret; } -static int verb_deactivate_home(int argc, char *argv[], void *userdata) { +static int verb_deactivate_home(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r, ret = 0; @@ -775,7 +775,7 @@ static int inspect_home(sd_bus *bus, const char *name) { return 0; } -static int verb_inspect_homes(int argc, char *argv[], void *userdata) { +static int verb_inspect_homes(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; @@ -833,7 +833,7 @@ static int authenticate_home(sd_bus *bus, const char *name) { } } -static int verb_authenticate_homes(int argc, char *argv[], void *userdata) { +static int verb_authenticate_homes(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; @@ -1557,7 +1557,7 @@ static int create_home_common(sd_json_variant *input, bool show_enforce_password return 0; } -static int verb_create_home(int argc, char *argv[], void *userdata) { +static int verb_create_home(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; if (argc >= 2) { @@ -1592,7 +1592,7 @@ static int verb_create_home(int argc, char *argv[], void *userdata) { return create_home_common(/* input= */ NULL, /* show_enforce_password_policy_hint= */ true); } -static int verb_adopt_home(int argc, char *argv[], void *userdata) { +static int verb_adopt_home(int argc, char *argv[], uintptr_t _data, void *userdata) { int r, ret = 0; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -1674,7 +1674,7 @@ static int register_home_one(sd_bus *bus, FILE *f, const char *path) { return register_home_common(bus, v); } -static int verb_register_home(int argc, char *argv[], void *userdata) { +static int verb_register_home(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -1705,7 +1705,7 @@ static int verb_register_home(int argc, char *argv[], void *userdata) { return r; } -static int verb_unregister_home(int argc, char *argv[], void *userdata) { +static int verb_unregister_home(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -1735,7 +1735,7 @@ static int verb_unregister_home(int argc, char *argv[], void *userdata) { return ret; } -static int verb_remove_home(int argc, char *argv[], void *userdata) { +static int verb_remove_home(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r, ret = 0; @@ -1900,7 +1900,7 @@ static int home_record_reset_human_interaction_permission(UserRecord *hr) { return 0; } -static int verb_update_home(int argc, char *argv[], void *userdata) { +static int verb_update_home(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(user_record_unrefp) UserRecord *hr = NULL, *secret = NULL; _cleanup_free_ char *buffer = NULL; @@ -2077,7 +2077,7 @@ static int verb_update_home(int argc, char *argv[], void *userdata) { return 0; } -static int verb_passwd_home(int argc, char *argv[], void *userdata) { +static int verb_passwd_home(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(user_record_unrefp) UserRecord *old_secret = NULL, *new_secret = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_free_ char *buffer = NULL; @@ -2194,7 +2194,7 @@ static int parse_disk_size(const char *t, uint64_t *ret) { return 0; } -static int verb_resize_home(int argc, char *argv[], void *userdata) { +static int verb_resize_home(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(user_record_unrefp) UserRecord *secret = NULL; uint64_t ds = UINT64_MAX; @@ -2256,7 +2256,7 @@ static int verb_resize_home(int argc, char *argv[], void *userdata) { return 0; } -static int verb_lock_home(int argc, char *argv[], void *userdata) { +static int verb_lock_home(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r, ret = 0; @@ -2287,7 +2287,7 @@ static int verb_lock_home(int argc, char *argv[], void *userdata) { return ret; } -static int verb_unlock_home(int argc, char *argv[], void *userdata) { +static int verb_unlock_home(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r, ret = 0; @@ -2335,7 +2335,7 @@ static int verb_unlock_home(int argc, char *argv[], void *userdata) { return ret; } -static int verb_with_home(int argc, char *argv[], void *userdata) { +static int verb_with_home(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -2458,7 +2458,7 @@ static int verb_with_home(int argc, char *argv[], void *userdata) { return ret; } -static int verb_lock_all_homes(int argc, char *argv[], void *userdata) { +static int verb_lock_all_homes(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -2479,7 +2479,7 @@ static int verb_lock_all_homes(int argc, char *argv[], void *userdata) { return 0; } -static int verb_deactivate_all_homes(int argc, char *argv[], void *userdata) { +static int verb_deactivate_all_homes(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -2500,7 +2500,7 @@ static int verb_deactivate_all_homes(int argc, char *argv[], void *userdata) { return 0; } -static int verb_rebalance(int argc, char *argv[], void *userdata) { +static int verb_rebalance(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -2983,7 +2983,7 @@ static int create_interactively(void) { static int add_signing_keys_from_credentials(void); -static int verb_firstboot(int argc, char *argv[], void *userdata) { +static int verb_firstboot(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; /* Let's honour the systemd.firstboot kernel command line option, just like the systemd-firstboot @@ -4082,7 +4082,7 @@ static int help(void) { return 0; } -static int verb_help(int argc, char *argv[], void *userdata) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { return help(); } @@ -5249,7 +5249,7 @@ static int fallback_shell(int argc, char *argv[]) { return log_error_errno(errno, "Failed to execute shell '%s': %m", shell); } -static int verb_list_signing_keys(int argc, char *argv[], void *userdata) { +static int verb_list_signing_keys(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -5335,7 +5335,7 @@ static int verb_list_signing_keys(int argc, char *argv[], void *userdata) { return 0; } -static int verb_get_signing_key(int argc, char *argv[], void *userdata) { +static int verb_get_signing_key(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -5391,7 +5391,7 @@ static int add_signing_key_one(sd_bus *bus, const char *fn, FILE *key) { return 0; } -static int verb_add_signing_key(int argc, char *argv[], void *userdata) { +static int verb_add_signing_key(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -5498,7 +5498,7 @@ static int remove_signing_key_one(sd_bus *bus, const char *fn) { return 0; } -static int verb_remove_signing_key(int argc, char *argv[], void *userdata) { +static int verb_remove_signing_key(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index c9d83031b9204..d0ceceb155846 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -549,7 +549,7 @@ static int get_hostname_based_on_flag(sd_bus *bus) { return get_one_name(bus, attr, NULL); } -static int verb_show_status(int argc, char **argv, void *userdata) { +static int verb_show_status(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = userdata; int r; @@ -601,7 +601,7 @@ static int set_simple_string(sd_bus *bus, const char *target, const char *method return set_simple_string_internal(bus, NULL, target, method, value); } -static int verb_set_hostname(int argc, char **argv, void *userdata) { +static int verb_set_hostname(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ char *h = NULL; const char *hostname = argv[1]; sd_bus *bus = userdata; @@ -687,27 +687,27 @@ static int verb_set_hostname(int argc, char **argv, void *userdata) { return ret; } -static int verb_get_or_set_hostname(int argc, char **argv, void *userdata) { +static int verb_get_or_set_hostname(int argc, char *argv[], uintptr_t data, void *userdata) { return argc == 1 ? get_hostname_based_on_flag(userdata) : - verb_set_hostname(argc, argv, userdata); + verb_set_hostname(argc, argv, data, userdata); } -static int verb_get_or_set_icon_name(int argc, char **argv, void *userdata) { +static int verb_get_or_set_icon_name(int argc, char *argv[], uintptr_t _data, void *userdata) { return argc == 1 ? get_one_name(userdata, "IconName", NULL) : set_simple_string(userdata, "icon", "SetIconName", argv[1]); } -static int verb_get_or_set_chassis(int argc, char **argv, void *userdata) { +static int verb_get_or_set_chassis(int argc, char *argv[], uintptr_t _data, void *userdata) { return argc == 1 ? get_one_name(userdata, "Chassis", NULL) : set_simple_string(userdata, "chassis", "SetChassis", argv[1]); } -static int verb_get_or_set_deployment(int argc, char **argv, void *userdata) { +static int verb_get_or_set_deployment(int argc, char *argv[], uintptr_t _data, void *userdata) { return argc == 1 ? get_one_name(userdata, "Deployment", NULL) : set_simple_string(userdata, "deployment", "SetDeployment", argv[1]); } -static int verb_get_or_set_location(int argc, char **argv, void *userdata) { +static int verb_get_or_set_location(int argc, char *argv[], uintptr_t _data, void *userdata) { return argc == 1 ? get_one_name(userdata, "Location", NULL) : set_simple_string(userdata, "location", "SetLocation", argv[1]); } @@ -752,7 +752,7 @@ static int help(void) { return 0; } -static int verb_help(int argc, char **argv, void *userdata) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { return help(); } diff --git a/src/hwdb/hwdb.c b/src/hwdb/hwdb.c index 6391b04133c5e..3b407bb070127 100644 --- a/src/hwdb/hwdb.c +++ b/src/hwdb/hwdb.c @@ -15,11 +15,11 @@ static const char *arg_hwdb_bin_dir = NULL; static const char *arg_root = NULL; static bool arg_strict = false; -static int verb_query(int argc, char *argv[], void *userdata) { +static int verb_query(int argc, char *argv[], uintptr_t _data, void *userdata) { return hwdb_query(argv[1], arg_root); } -static int verb_update(int argc, char *argv[], void *userdata) { +static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata) { if (hwdb_bypass()) return 0; diff --git a/src/id128/id128.c b/src/id128/id128.c index cdb93ac68fab2..fecfeeabab6d1 100644 --- a/src/id128/id128.c +++ b/src/id128/id128.c @@ -24,11 +24,11 @@ static PagerFlags arg_pager_flags = 0; static bool arg_legend = true; static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF; -static int verb_new(int argc, char **argv, void *userdata) { +static int verb_new(int argc, char *argv[], uintptr_t _data, void *userdata) { return id128_print_new(arg_mode); } -static int verb_machine_id(int argc, char **argv, void *userdata) { +static int verb_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_id128_t id; int r; @@ -43,7 +43,7 @@ static int verb_machine_id(int argc, char **argv, void *userdata) { return id128_pretty_print(id, arg_mode); } -static int verb_boot_id(int argc, char **argv, void *userdata) { +static int verb_boot_id(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_id128_t id; int r; @@ -58,7 +58,7 @@ static int verb_boot_id(int argc, char **argv, void *userdata) { return id128_pretty_print(id, arg_mode); } -static int verb_invocation_id(int argc, char **argv, void *userdata) { +static int verb_invocation_id(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_id128_t id; int r; @@ -73,7 +73,7 @@ static int verb_invocation_id(int argc, char **argv, void *userdata) { return id128_pretty_print(id, arg_mode); } -static int verb_var_uuid(int argc, char **argv, void *userdata) { +static int verb_var_uuid(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_id128_t id; int r; @@ -130,7 +130,7 @@ static int show_one(Table **table, const char *name, sd_id128_t uuid, bool first arg_mode == ID128_PRINT_ID128 ? TABLE_ID128 : TABLE_UUID, uuid); } -static int verb_show(int argc, char **argv, void *userdata) { +static int verb_show(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; int r; @@ -223,7 +223,7 @@ static int help(void) { return 0; } -static int verb_help(int argc, char **argv, void *userdata) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { return help(); } diff --git a/src/import/export.c b/src/import/export.c index 5f51dbe5176af..6612a1e70afed 100644 --- a/src/import/export.c +++ b/src/import/export.c @@ -58,7 +58,7 @@ static void on_tar_finished(TarExport *export, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int verb_export_tar(int argc, char *argv[], void *userdata) { +static int verb_export_tar(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(tar_export_unrefp) TarExport *export = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(image_unrefp) Image *image = NULL; @@ -139,7 +139,7 @@ static void on_raw_finished(RawExport *export, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int verb_export_raw(int argc, char *argv[], void *userdata) { +static int verb_export_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(raw_export_unrefp) RawExport *export = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(image_unrefp) Image *image = NULL; @@ -225,7 +225,7 @@ static int help(void) { return 0; } -static int verb_help(int argc, char *argv[], void *userdata) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { return help(); } diff --git a/src/import/import-fs.c b/src/import/import-fs.c index db25174434117..4b2394d4147ba 100644 --- a/src/import/import-fs.c +++ b/src/import/import-fs.c @@ -107,7 +107,7 @@ static int progress_bytes(uint64_t nbytes, uint64_t bps, void *userdata) { return 0; } -static int verb_import_fs(int argc, char *argv[], void *userdata) { +static int verb_import_fs(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(rm_rf_subvolume_and_freep) char *temp_path = NULL; _cleanup_(progress_info_free) ProgressInfo progress = { .bps = UINT64_MAX }; _cleanup_free_ char *l = NULL, *final_path = NULL; @@ -295,7 +295,7 @@ static int help(void) { return 0; } -static int verb_help(int argc, char *argv[], void *userdata) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { return help(); } diff --git a/src/import/import.c b/src/import/import.c index a7c902555937d..3fe6adb8d2d88 100644 --- a/src/import/import.c +++ b/src/import/import.c @@ -138,7 +138,7 @@ static void on_tar_finished(TarImport *import, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int verb_import_tar(int argc, char *argv[], void *userdata) { +static int verb_import_tar(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(tar_import_unrefp) TarImport *import = NULL; _cleanup_free_ char *ll = NULL, *normalized = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; @@ -207,7 +207,7 @@ static void on_raw_finished(RawImport *import, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int verb_import_raw(int argc, char *argv[], void *userdata) { +static int verb_import_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(raw_import_unrefp) RawImport *import = NULL; _cleanup_free_ char *ll = NULL, *normalized = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; @@ -303,7 +303,7 @@ static int help(void) { return 0; } -static int verb_help(int argc, char *argv[], void *userdata) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { return help(); } diff --git a/src/import/importctl.c b/src/import/importctl.c index 712c2a7b64959..2993073cc5c1d 100644 --- a/src/import/importctl.c +++ b/src/import/importctl.c @@ -261,7 +261,7 @@ static int transfer_image_common(sd_bus *bus, sd_bus_message *m) { return -r; } -static int verb_import_tar(int argc, char *argv[], void *userdata) { +static int verb_import_tar(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *ll = NULL, *fn = NULL; const char *local = NULL, *path = NULL; @@ -340,7 +340,7 @@ static int verb_import_tar(int argc, char *argv[], void *userdata) { return transfer_image_common(bus, m); } -static int verb_import_raw(int argc, char *argv[], void *userdata) { +static int verb_import_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *ll = NULL, *fn = NULL; const char *local = NULL, *path = NULL; @@ -419,7 +419,7 @@ static int verb_import_raw(int argc, char *argv[], void *userdata) { return transfer_image_common(bus, m); } -static int verb_import_fs(int argc, char *argv[], void *userdata) { +static int verb_import_fs(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; const char *local = NULL, *path = NULL; _cleanup_free_ char *fn = NULL; @@ -506,7 +506,7 @@ static void determine_compression_from_filename(const char *p) { arg_format = "zstd"; } -static int verb_export_tar(int argc, char *argv[], void *userdata) { +static int verb_export_tar(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_close_ int fd = -EBADF; const char *local = NULL, *path = NULL; @@ -565,7 +565,7 @@ static int verb_export_tar(int argc, char *argv[], void *userdata) { return transfer_image_common(bus, m); } -static int verb_export_raw(int argc, char *argv[], void *userdata) { +static int verb_export_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_close_ int fd = -EBADF; const char *local = NULL, *path = NULL; @@ -624,7 +624,7 @@ static int verb_export_raw(int argc, char *argv[], void *userdata) { return transfer_image_common(bus, m); } -static int verb_pull_tar(int argc, char *argv[], void *userdata) { +static int verb_pull_tar(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *l = NULL, *ll = NULL; const char *local, *remote; @@ -697,7 +697,7 @@ static int verb_pull_tar(int argc, char *argv[], void *userdata) { return transfer_image_common(bus, m); } -static int verb_pull_raw(int argc, char *argv[], void *userdata) { +static int verb_pull_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *l = NULL, *ll = NULL; const char *local, *remote; @@ -770,7 +770,7 @@ static int verb_pull_raw(int argc, char *argv[], void *userdata) { return transfer_image_common(bus, m); } -static int verb_pull_oci(int argc, char *argv[], void *userdata) { +static int verb_pull_oci(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *l = NULL; const char *local, *remote; @@ -825,7 +825,7 @@ static int verb_pull_oci(int argc, char *argv[], void *userdata) { return transfer_image_common(bus, m); } -static int verb_list_transfers(int argc, char *argv[], void *userdata) { +static int verb_list_transfers(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(table_unrefp) Table *t = NULL; @@ -929,7 +929,7 @@ static int verb_list_transfers(int argc, char *argv[], void *userdata) { return 0; } -static int verb_cancel_transfer(int argc, char *argv[], void *userdata) { +static int verb_cancel_transfer(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -951,7 +951,7 @@ static int verb_cancel_transfer(int argc, char *argv[], void *userdata) { return 0; } -static int verb_list_images(int argc, char *argv[], void *userdata) { +static int verb_list_images(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(table_unrefp) Table *t = NULL; @@ -1112,7 +1112,7 @@ static int help(void) { return 0; } -static int verb_help(int argc, char *argv[], void *userdata) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { return help(); } diff --git a/src/import/pull.c b/src/import/pull.c index e777d41ee6980..270f70396cab6 100644 --- a/src/import/pull.c +++ b/src/import/pull.c @@ -117,7 +117,7 @@ static void on_tar_finished(TarPull *pull, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int verb_pull_tar(int argc, char *argv[], void *userdata) { +static int verb_pull_tar(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ char *ll = NULL, *normalized = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(tar_pull_unrefp) TarPull *pull = NULL; @@ -187,7 +187,7 @@ static void on_raw_finished(RawPull *pull, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int verb_pull_raw(int argc, char *argv[], void *userdata) { +static int verb_pull_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ char *ll = NULL, *normalized = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(raw_pull_unrefp) RawPull *pull = NULL; @@ -256,7 +256,7 @@ static void on_oci_finished(OciPull *pull, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int verb_pull_oci(int argc, char *argv[], void *userdata) { +static int verb_pull_oci(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; const char *ref = argv[1]; @@ -356,7 +356,7 @@ static int help(void) { return 0; } -static int verb_help(int argc, char *argv[], void *userdata) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { return help(); } diff --git a/src/integritysetup/integritysetup.c b/src/integritysetup/integritysetup.c index 5eed1b5665d5e..5ca3eee08726a 100644 --- a/src/integritysetup/integritysetup.c +++ b/src/integritysetup/integritysetup.c @@ -89,7 +89,7 @@ static const char *integrity_algorithm_select(const void *key_file_buf) { return "crc32c"; } -static int verb_attach(int argc, char *argv[], void *userdata) { +static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; crypt_status_info status; _cleanup_(erase_and_freep) void *key_buf = NULL; @@ -156,7 +156,7 @@ static int verb_attach(int argc, char *argv[], void *userdata) { return 0; } -static int verb_detach(int argc, char *argv[], void *userdata) { +static int verb_detach(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; int r; diff --git a/src/kernel-install/kernel-install.c b/src/kernel-install/kernel-install.c index 9046e82e921a8..f830bd2bef86e 100644 --- a/src/kernel-install/kernel-install.c +++ b/src/kernel-install/kernel-install.c @@ -1178,7 +1178,7 @@ static int do_add( return context_execute(c); } -static int verb_add(int argc, char *argv[], void *userdata) { +static int verb_add(int argc, char *argv[], uintptr_t _data, void *userdata) { const char *version, *kernel; char **initrds; int r; @@ -1207,7 +1207,7 @@ static int verb_add(int argc, char *argv[], void *userdata) { return do_add(&c, version, kernel, initrds); } -static int verb_add_all(int argc, char *argv[], void *userdata) { +static int verb_add_all(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_close_ int fd = -EBADF; size_t n = 0; int ret = 0, r; @@ -1294,10 +1294,10 @@ static int run_as_installkernel(int argc, char *argv[]) { if (optind + 2 > argc) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "'installkernel' command requires at least two arguments."); - return verb_add(3, STRV_MAKE("add", argv[optind], argv[optind+1]), /* userdata= */ NULL); + return verb_add(3, STRV_MAKE("add", argv[optind], argv[optind+1]), /* data= */ 0, /* userdata= */ NULL); } -static int verb_remove(int argc, char *argv[], void *userdata) { +static int verb_remove(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; assert(argc >= 2); @@ -1333,7 +1333,7 @@ static int verb_remove(int argc, char *argv[], void *userdata) { return context_execute(&c); } -static int verb_inspect(int argc, char *argv[], void *userdata) { +static int verb_inspect(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *t = NULL; _cleanup_free_ char *vmlinuz = NULL; const char *version, *kernel; @@ -1446,7 +1446,7 @@ static int verb_inspect(int argc, char *argv[], void *userdata) { return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, /* show_header= */ false); } -static int verb_list(int argc, char *argv[], void *userdata) { +static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_close_ int fd = -EBADF; int r; diff --git a/src/keyutil/keyutil.c b/src/keyutil/keyutil.c index f26b235d39a0d..516527aa2b5ee 100644 --- a/src/keyutil/keyutil.c +++ b/src/keyutil/keyutil.c @@ -36,7 +36,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_signature, freep); STATIC_DESTRUCTOR_REGISTER(arg_content, freep); STATIC_DESTRUCTOR_REGISTER(arg_output, freep); -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -81,6 +81,10 @@ static int help(int argc, char *argv[], void *userdata) { return 0; } +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { + return help(); +} + static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, @@ -117,8 +121,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - help(0, NULL, NULL); - return 0; + return help(); case ARG_VERSION: return version(); @@ -193,7 +196,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -static int verb_validate(int argc, char *argv[], void *userdata) { +static int verb_validate(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(X509_freep) X509 *certificate = NULL; _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL; _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = NULL; @@ -248,7 +251,7 @@ static int verb_validate(int argc, char *argv[], void *userdata) { return 0; } -static int verb_extract_public(int argc, char *argv[], void *userdata) { +static int verb_extract_public(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(EVP_PKEY_freep) EVP_PKEY *public_key = NULL; int r; @@ -315,7 +318,7 @@ static int verb_extract_public(int argc, char *argv[], void *userdata) { return 0; } -static int verb_extract_certificate(int argc, char *argv[], void *userdata) { +static int verb_extract_certificate(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(X509_freep) X509 *certificate = NULL; int r; @@ -342,7 +345,7 @@ static int verb_extract_certificate(int argc, char *argv[], void *userdata) { return 0; } -static int verb_pkcs7(int argc, char *argv[], void *userdata) { +static int verb_pkcs7(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(X509_freep) X509 *certificate = NULL; _cleanup_free_ char *pkcs1 = NULL; size_t pkcs1_len = 0; @@ -427,7 +430,7 @@ static int verb_pkcs7(int argc, char *argv[], void *userdata) { static int run(int argc, char *argv[]) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, { "validate", VERB_ANY, 1, 0, verb_validate }, { "extract-public", VERB_ANY, 1, 0, verb_extract_public }, { "public", VERB_ANY, 1, 0, verb_extract_public }, /* Deprecated but kept for backwards compat. */ diff --git a/src/locale/localectl.c b/src/locale/localectl.c index 94c0e8424dd07..65756d26f0d30 100644 --- a/src/locale/localectl.c +++ b/src/locale/localectl.c @@ -151,7 +151,7 @@ static int print_status_info(StatusInfo *i) { return 0; } -static int verb_show_status(int argc, char **argv, void *userdata) { +static int verb_show_status(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(status_info_clear) StatusInfo info = {}; static const struct bus_properties_map map[] = { { "VConsoleKeymap", "s", NULL, offsetof(StatusInfo, vconsole_keymap) }, @@ -183,7 +183,7 @@ static int verb_show_status(int argc, char **argv, void *userdata) { return print_status_info(&info); } -static int verb_set_locale(int argc, char **argv, void *userdata) { +static int verb_set_locale(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); @@ -211,7 +211,7 @@ static int verb_set_locale(int argc, char **argv, void *userdata) { return 0; } -static int verb_list_locales(int argc, char **argv, void *userdata) { +static int verb_list_locales(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_strv_free_ char **l = NULL; int r; @@ -225,7 +225,7 @@ static int verb_list_locales(int argc, char **argv, void *userdata) { return 0; } -static int verb_set_vconsole_keymap(int argc, char **argv, void *userdata) { +static int verb_set_vconsole_keymap(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; const char *map, *toggle_map; sd_bus *bus = ASSERT_PTR(userdata); @@ -249,7 +249,7 @@ static int verb_set_vconsole_keymap(int argc, char **argv, void *userdata) { return 0; } -static int verb_list_vconsole_keymaps(int argc, char **argv, void *userdata) { +static int verb_list_vconsole_keymaps(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_strv_free_ char **l = NULL; int r; @@ -264,7 +264,7 @@ static int verb_list_vconsole_keymaps(int argc, char **argv, void *userdata) { return 0; } -static int verb_set_x11_keymap(int argc, char **argv, void *userdata) { +static int verb_set_x11_keymap(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; const char *layout, *model, *variant, *options; sd_bus *bus = userdata; @@ -299,7 +299,7 @@ static const char* xkb_directory(void) { return cached; } -static int verb_list_x11_keymaps(int argc, char **argv, void *userdata) { +static int verb_list_x11_keymaps(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_fclose_ FILE *f = NULL; _cleanup_strv_free_ char **list = NULL; enum { @@ -446,7 +446,7 @@ static int help(void) { return 0; } -static int verb_help(int argc, char **argv, void *userdata) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { return help(); } diff --git a/src/login/loginctl.c b/src/login/loginctl.c index cea702345ecbc..a921a4a6771c9 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -266,7 +266,7 @@ static int list_sessions_table_add_fallback(Table *table, sd_bus_message *reply, return 0; } -static int verb_list_sessions(int argc, char *argv[], void *userdata) { +static int verb_list_sessions(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; @@ -308,7 +308,7 @@ static int verb_list_sessions(int argc, char *argv[], void *userdata) { return list_table_print(table, "sessions"); } -static int verb_list_users(int argc, char *argv[], void *userdata) { +static int verb_list_users(int argc, char *argv[], uintptr_t _data, void *userdata) { static const struct bus_properties_map property_map[] = { { "Linger", "b", NULL, offsetof(UserStatusInfo, linger) }, @@ -384,7 +384,7 @@ static int verb_list_users(int argc, char *argv[], void *userdata) { return list_table_print(table, "users"); } -static int verb_list_seats(int argc, char *argv[], void *userdata) { +static int verb_list_seats(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(table_unrefp) Table *table = NULL; @@ -1038,7 +1038,7 @@ static int get_bus_path_by_id( return strdup_to(ret, path); } -static int verb_show_session(int argc, char *argv[], void *userdata) { +static int verb_show_session(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); bool properties; int r; @@ -1084,7 +1084,7 @@ static int verb_show_session(int argc, char *argv[], void *userdata) { return 0; } -static int verb_show_user(int argc, char *argv[], void *userdata) { +static int verb_show_user(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); bool properties; int r; @@ -1135,7 +1135,7 @@ static int verb_show_user(int argc, char *argv[], void *userdata) { return 0; } -static int verb_show_seat(int argc, char *argv[], void *userdata) { +static int verb_show_seat(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); bool properties; int r; @@ -1181,7 +1181,7 @@ static int verb_show_seat(int argc, char *argv[], void *userdata) { return 0; } -static int verb_activate(int argc, char *argv[], void *userdata) { +static int verb_activate(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1224,7 +1224,7 @@ static int verb_activate(int argc, char *argv[], void *userdata) { return 0; } -static int verb_kill_session(int argc, char *argv[], void *userdata) { +static int verb_kill_session(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1250,7 +1250,7 @@ static int verb_kill_session(int argc, char *argv[], void *userdata) { return 0; } -static int verb_enable_linger(int argc, char *argv[], void *userdata) { +static int verb_enable_linger(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); char* short_argv[3]; @@ -1298,7 +1298,7 @@ static int verb_enable_linger(int argc, char *argv[], void *userdata) { return 0; } -static int verb_terminate_user(int argc, char *argv[], void *userdata) { +static int verb_terminate_user(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1328,7 +1328,7 @@ static int verb_terminate_user(int argc, char *argv[], void *userdata) { return 0; } -static int verb_kill_user(int argc, char *argv[], void *userdata) { +static int verb_kill_user(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1366,7 +1366,7 @@ static int verb_kill_user(int argc, char *argv[], void *userdata) { return 0; } -static int verb_attach(int argc, char *argv[], void *userdata) { +static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1390,7 +1390,7 @@ static int verb_attach(int argc, char *argv[], void *userdata) { return 0; } -static int verb_flush_devices(int argc, char *argv[], void *userdata) { +static int verb_flush_devices(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1406,7 +1406,7 @@ static int verb_flush_devices(int argc, char *argv[], void *userdata) { return 0; } -static int verb_lock_sessions(int argc, char *argv[], void *userdata) { +static int verb_lock_sessions(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1427,7 +1427,7 @@ static int verb_lock_sessions(int argc, char *argv[], void *userdata) { return 0; } -static int verb_terminate_seat(int argc, char *argv[], void *userdata) { +static int verb_terminate_seat(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1519,7 +1519,7 @@ static int help(void) { return 0; } -static int verb_help(int argc, char *argv[], void *userdata) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { return help(); } diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index f71d3deee2ab4..d9ee2fe2bb64c 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -277,7 +277,7 @@ static int show_table(Table *table, const char *word) { return 0; } -static int verb_list_machines(int argc, char *argv[], void *userdata) { +static int verb_list_machines(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(table_unrefp) Table *table = NULL; @@ -354,7 +354,7 @@ static int verb_list_machines(int argc, char *argv[], void *userdata) { return show_table(table, "machines"); } -static int verb_list_images(int argc, char *argv[], void *userdata) { +static int verb_list_images(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; @@ -745,7 +745,7 @@ static int show_machine_properties(sd_bus *bus, const char *path, bool *new_line return r; } -static int verb_show_machine(int argc, char *argv[], void *userdata) { +static int verb_show_machine(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; bool properties, new_line = false; sd_bus *bus = ASSERT_PTR(userdata); @@ -1036,7 +1036,7 @@ static int show_image_properties(sd_bus *bus, const char *path, bool *new_line) return r; } -static int verb_show_image(int argc, char *argv[], void *userdata) { +static int verb_show_image(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; bool properties, new_line = false; sd_bus *bus = ASSERT_PTR(userdata); @@ -1080,7 +1080,7 @@ static int verb_show_image(int argc, char *argv[], void *userdata) { return r; } -static int verb_kill_machine(int argc, char *argv[], void *userdata) { +static int verb_kill_machine(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1105,7 +1105,7 @@ static int verb_kill_machine(int argc, char *argv[], void *userdata) { return 0; } -static int verb_reboot_machine(int argc, char *argv[], void *userdata) { +static int verb_reboot_machine(int argc, char *argv[], uintptr_t data, void *userdata) { if (arg_runner == RUNNER_VMSPAWN) return log_error_errno( SYNTHETIC_ERRNO(EOPNOTSUPP), @@ -1115,17 +1115,17 @@ static int verb_reboot_machine(int argc, char *argv[], void *userdata) { arg_kill_whom = "leader"; arg_signal = SIGINT; /* sysvinit + systemd */ - return verb_kill_machine(argc, argv, userdata); + return verb_kill_machine(argc, argv, data, userdata); } -static int verb_poweroff_machine(int argc, char *argv[], void *userdata) { +static int verb_poweroff_machine(int argc, char *argv[], uintptr_t data, void *userdata) { arg_kill_whom = "leader"; arg_signal = SIGRTMIN+4; /* only systemd */ - return verb_kill_machine(argc, argv, userdata); + return verb_kill_machine(argc, argv, data, userdata); } -static int verb_terminate_machine(int argc, char *argv[], void *userdata) { +static int verb_terminate_machine(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1148,7 +1148,7 @@ static const char *select_copy_method(bool copy_from, bool force) { return copy_from ? "CopyFromMachine" : "CopyToMachine"; } -static int verb_copy_files(int argc, char *argv[], void *userdata) { +static int verb_copy_files(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *abs_host_path = NULL; @@ -1203,7 +1203,7 @@ static int verb_copy_files(int argc, char *argv[], void *userdata) { return 0; } -static int verb_bind_mount(int argc, char *argv[], void *userdata) { +static int verb_bind_mount(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1336,7 +1336,7 @@ static int parse_machine_uid(const char *spec, const char **machine, char **uid) return 0; } -static int verb_login_machine(int argc, char *argv[], void *userdata) { +static int verb_login_machine(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; @@ -1387,7 +1387,7 @@ static int verb_login_machine(int argc, char *argv[], void *userdata) { return process_forward(event, slot, master, PTY_FORWARD_IGNORE_VHANGUP, machine); } -static int verb_shell_machine(int argc, char *argv[], void *userdata) { +static int verb_shell_machine(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; @@ -1515,7 +1515,7 @@ static int get_settings_path(const char *name, char **ret_path) { return -ENOENT; } -static int verb_edit_settings(int argc, char *argv[], void *userdata) { +static int verb_edit_settings(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(edit_file_context_done) EditFileContext context = {}; int r; @@ -1585,7 +1585,7 @@ static int verb_edit_settings(int argc, char *argv[], void *userdata) { return do_edit_files_and_install(&context); } -static int verb_cat_settings(int argc, char *argv[], void *userdata) { +static int verb_cat_settings(int argc, char *argv[], uintptr_t _data, void *userdata) { int r = 0; if (arg_transport != BUS_TRANSPORT_LOCAL) @@ -1636,7 +1636,7 @@ static int verb_cat_settings(int argc, char *argv[], void *userdata) { return r; } -static int verb_remove_image(int argc, char *argv[], void *userdata) { +static int verb_remove_image(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1663,7 +1663,7 @@ static int verb_remove_image(int argc, char *argv[], void *userdata) { return 0; } -static int verb_rename_image(int argc, char *argv[], void *userdata) { +static int verb_rename_image(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -1683,7 +1683,7 @@ static int verb_rename_image(int argc, char *argv[], void *userdata) { return 0; } -static int verb_clone_image(int argc, char *argv[], void *userdata) { +static int verb_clone_image(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; sd_bus *bus = ASSERT_PTR(userdata); @@ -1707,7 +1707,7 @@ static int verb_clone_image(int argc, char *argv[], void *userdata) { return 0; } -static int verb_read_only_image(int argc, char *argv[], void *userdata) { +static int verb_read_only_image(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int b = true, r; @@ -1765,7 +1765,7 @@ static int make_service_name(const char *name, char **ret) { return 0; } -static int verb_start_machine(int argc, char *argv[], void *userdata) { +static int verb_start_machine(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; sd_bus *bus = ASSERT_PTR(userdata); @@ -1821,7 +1821,7 @@ static int verb_start_machine(int argc, char *argv[], void *userdata) { return 0; } -static int verb_enable_machine(int argc, char *argv[], void *userdata) { +static int verb_enable_machine(int argc, char *argv[], uintptr_t data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; const char *method; @@ -1909,15 +1909,15 @@ static int verb_enable_machine(int argc, char *argv[], void *userdata) { return log_oom(); if (enable) - return verb_start_machine(strv_length(new_args), new_args, userdata); + return verb_start_machine(strv_length(new_args), new_args, data, userdata); - return verb_poweroff_machine(strv_length(new_args), new_args, userdata); + return verb_poweroff_machine(strv_length(new_args), new_args, data, userdata); } return 0; } -static int verb_set_limit(int argc, char *argv[], void *userdata) { +static int verb_set_limit(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = userdata; uint64_t limit; @@ -1947,7 +1947,7 @@ static int verb_set_limit(int argc, char *argv[], void *userdata) { return 0; } -static int verb_clean_images(int argc, char *argv[], void *userdata) { +static int verb_clean_images(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; uint64_t usage, total = 0; @@ -2137,7 +2137,7 @@ static int help(void) { return 0; } -static int verb_help(int argc, char *argv[], void *userdata) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { return help(); } diff --git a/src/measure/measure-tool.c b/src/measure/measure-tool.c index 6392460cf40e8..2f37d5838195a 100644 --- a/src/measure/measure-tool.c +++ b/src/measure/measure-tool.c @@ -63,7 +63,7 @@ static void free_sections(char*(*sections)[_UNIFIED_SECTION_MAX]) { STATIC_DESTRUCTOR_REGISTER(arg_sections, free_sections); -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -127,6 +127,10 @@ static int help(int argc, char *argv[], void *userdata) { return 0; } +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { + return help(); +} + static char *normalize_phase(const char *s) { _cleanup_strv_free_ char **l = NULL; @@ -218,8 +222,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - help(0, NULL, NULL); - return 0; + return help(); case ARG_VERSION: return version(); @@ -706,7 +709,7 @@ static void pcr_states_restore(PcrState *pcr_states, size_t n) { } } -static int verb_calculate(int argc, char *argv[], void *userdata) { +static int verb_calculate(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL; _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL; int r; @@ -991,11 +994,11 @@ static int build_policy_digest(bool sign) { return 0; } -static int verb_sign(int argc, char *argv[], void *userdata) { +static int verb_sign(int argc, char *argv[], uintptr_t _data, void *userdata) { return build_policy_digest(/* sign= */ true); } -static int verb_policy_digest(int argc, char *argv[], void *userdata) { +static int verb_policy_digest(int argc, char *argv[], uintptr_t _data, void *userdata) { return build_policy_digest(/* sign= */ false); } @@ -1064,7 +1067,7 @@ static int validate_stub(void) { return 0; } -static int verb_status(int argc, char *argv[], void *userdata) { +static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; int r; @@ -1150,7 +1153,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { static int measure_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, { "calculate", VERB_ANY, 1, 0, verb_calculate }, { "policy-digest", VERB_ANY, 1, 0, verb_policy_digest }, diff --git a/src/network/networkctl-address-label.c b/src/network/networkctl-address-label.c index 048159bd2c26c..f587d0cfbb542 100644 --- a/src/network/networkctl-address-label.c +++ b/src/network/networkctl-address-label.c @@ -89,7 +89,7 @@ static int dump_address_labels(sd_netlink *rtnl) { return 0; } -int verb_list_address_labels(int argc, char *argv[], void *userdata) { +int verb_list_address_labels(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; int r; diff --git a/src/network/networkctl-address-label.h b/src/network/networkctl-address-label.h index a5584806fbf3f..0afbd2b2bc5ce 100644 --- a/src/network/networkctl-address-label.h +++ b/src/network/networkctl-address-label.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_list_address_labels(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_list_address_labels(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/network/networkctl-config-file.c b/src/network/networkctl-config-file.c index 9f0236b5406a6..5674b630a4ba4 100644 --- a/src/network/networkctl-config-file.c +++ b/src/network/networkctl-config-file.c @@ -470,7 +470,7 @@ static int reload_daemons(ReloadFlags flags) { return ret; } -int verb_edit(int argc, char *argv[], void *userdata) { +int verb_edit(int argc, char *argv[], uintptr_t _data, void *userdata) { char **args = ASSERT_PTR(strv_skip(argv, 1)); _cleanup_(edit_file_context_done) EditFileContext context = { .marker_start = DROPIN_MARKER_START, @@ -630,7 +630,7 @@ static int cat_files_by_link_config(const char *link_config, sd_netlink **rtnl, return cat_files_by_link_one(ifname, type, rtnl, /* ignore_missing= */ false, first); } -int verb_cat(int argc, char *argv[], void *userdata) { +int verb_cat(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; char **args = strv_skip(argv, 1); int r, ret = 0; @@ -683,7 +683,7 @@ int verb_cat(int argc, char *argv[], void *userdata) { return ret; } -int verb_mask(int argc, char *argv[], void *userdata) { +int verb_mask(int argc, char *argv[], uintptr_t _data, void *userdata) { ReloadFlags flags = 0; int r; @@ -747,7 +747,7 @@ int verb_mask(int argc, char *argv[], void *userdata) { return reload_daemons(flags); } -int verb_unmask(int argc, char *argv[], void *userdata) { +int verb_unmask(int argc, char *argv[], uintptr_t _data, void *userdata) { ReloadFlags flags = 0; int r; diff --git a/src/network/networkctl-config-file.h b/src/network/networkctl-config-file.h index 38210a8093b32..8d52d58ffcbe9 100644 --- a/src/network/networkctl-config-file.h +++ b/src/network/networkctl-config-file.h @@ -1,8 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_edit(int argc, char *argv[], void *userdata); -int verb_cat(int argc, char *argv[], void *userdata); +#include "shared-forward.h" -int verb_mask(int argc, char *argv[], void *userdata); -int verb_unmask(int argc, char *argv[], void *userdata); +int verb_edit(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_cat(int argc, char *argv[], uintptr_t _data, void *userdata); + +int verb_mask(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_unmask(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/network/networkctl-list.c b/src/network/networkctl-list.c index a0b7b9a21f2ff..1ef38a0b85452 100644 --- a/src/network/networkctl-list.c +++ b/src/network/networkctl-list.c @@ -12,7 +12,7 @@ #include "networkctl-list.h" #include "networkctl-util.h" -int verb_list_links(int argc, char *argv[], void *userdata) { +int verb_list_links(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; _cleanup_(link_info_array_freep) LinkInfo *links = NULL; _cleanup_(table_unrefp) Table *table = NULL; diff --git a/src/network/networkctl-list.h b/src/network/networkctl-list.h index cb418e1cbc7d1..955797ea2f0b7 100644 --- a/src/network/networkctl-list.h +++ b/src/network/networkctl-list.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_list_links(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_list_links(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/network/networkctl-lldp.c b/src/network/networkctl-lldp.c index 433a5fc922606..ddce26e5c4268 100644 --- a/src/network/networkctl-lldp.c +++ b/src/network/networkctl-lldp.c @@ -219,7 +219,7 @@ static int dump_lldp_neighbors_json(sd_json_variant *reply, char * const *patter return sd_json_variant_dump(v, arg_json_format_flags, NULL, NULL); } -int verb_link_lldp_status(int argc, char *argv[], void *userdata) { +int verb_link_lldp_status(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL; _cleanup_(table_unrefp) Table *table = NULL; sd_json_variant *reply; diff --git a/src/network/networkctl-lldp.h b/src/network/networkctl-lldp.h index 63f89a6165486..b1536fc36d7b3 100644 --- a/src/network/networkctl-lldp.h +++ b/src/network/networkctl-lldp.h @@ -4,4 +4,4 @@ #include "shared-forward.h" int dump_lldp_neighbors(sd_varlink *vl, Table *table, int ifindex); -int verb_link_lldp_status(int argc, char *argv[], void *userdata); +int verb_link_lldp_status(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/network/networkctl-misc.c b/src/network/networkctl-misc.c index 47323b1d9faa6..e130cdab4c678 100644 --- a/src/network/networkctl-misc.c +++ b/src/network/networkctl-misc.c @@ -46,7 +46,7 @@ static int parse_interfaces(sd_netlink **rtnl, char *argv[], OrderedSet **ret) { return 0; } -int verb_link_up_down(int argc, char *argv[], void *userdata) { +int verb_link_up_down(int argc, char *argv[], uintptr_t _data, void *userdata) { int r, ret = 0; bool up = streq_ptr(argv[0], "up"); @@ -75,7 +75,7 @@ int verb_link_up_down(int argc, char *argv[], void *userdata) { return ret; } -int verb_link_delete(int argc, char *argv[], void *userdata) { +int verb_link_delete(int argc, char *argv[], uintptr_t _data, void *userdata) { int r, ret = 0; _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; @@ -104,7 +104,7 @@ int verb_link_delete(int argc, char *argv[], void *userdata) { return ret; } -int verb_link_bus_simple_method(int argc, char *argv[], void *userdata) { +int verb_link_bus_simple_method(int argc, char *argv[], uintptr_t _data, void *userdata) { int r, ret = 0; typedef struct LinkBusAction { @@ -159,7 +159,7 @@ int verb_link_bus_simple_method(int argc, char *argv[], void *userdata) { return ret; } -int verb_reload(int argc, char *argv[], void *userdata) { +int verb_reload(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -177,7 +177,7 @@ int verb_reload(int argc, char *argv[], void *userdata) { return 0; } -int verb_persistent_storage(int argc, char *argv[], void *userdata) { +int verb_persistent_storage(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL; bool ready; int r; diff --git a/src/network/networkctl-misc.h b/src/network/networkctl-misc.h index df7a6a6fc052e..092177275d746 100644 --- a/src/network/networkctl-misc.h +++ b/src/network/networkctl-misc.h @@ -1,8 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_link_up_down(int argc, char *argv[], void *userdata); -int verb_link_delete(int argc, char *argv[], void *userdata); -int verb_link_bus_simple_method(int argc, char *argv[], void *userdata); -int verb_reload(int argc, char *argv[], void *userdata); -int verb_persistent_storage(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_link_up_down(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_link_delete(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_link_bus_simple_method(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_reload(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_persistent_storage(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/network/networkctl-status-link.c b/src/network/networkctl-status-link.c index e2db2c7553909..b73fceb91a200 100644 --- a/src/network/networkctl-status-link.c +++ b/src/network/networkctl-status-link.c @@ -939,7 +939,7 @@ static int link_status_one( return show_logs(info->ifindex, info->name); } -int verb_link_status(int argc, char *argv[], void *userdata) { +int verb_link_status(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; diff --git a/src/network/networkctl-status-link.h b/src/network/networkctl-status-link.h index 70cbf4f1604d0..fcc0fa07289dd 100644 --- a/src/network/networkctl-status-link.h +++ b/src/network/networkctl-status-link.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_link_status(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_link_status(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/oom/oomctl.c b/src/oom/oomctl.c index 1cfde287e9120..ed394e42d8c48 100644 --- a/src/oom/oomctl.c +++ b/src/oom/oomctl.c @@ -46,11 +46,11 @@ static int help(void) { return 0; } -static int verb_help(int argc, char *argv[], void *userdata) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { return help(); } -static int verb_dump_state(int argc, char *argv[], void *userdata) { +static int verb_dump_state(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index 2d3c0862615a4..ddae43dc54895 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -2494,7 +2494,7 @@ static int event_log_load_and_process(EventLog **ret) { return 0; } -static int verb_show_log(int argc, char *argv[], void *userdata) { +static int verb_show_log(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *log_table = NULL, *pcr_table = NULL; _cleanup_(event_log_freep) EventLog *el = NULL; bool want_json = sd_json_format_enabled(arg_json_format_flags); @@ -2607,7 +2607,7 @@ static int event_log_record_to_cel(EventLogRecord *record, uint64_t *recnum, sd_ return 0; } -static int verb_show_cel(int argc, char *argv[], void *userdata) { +static int verb_show_cel(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; _cleanup_(event_log_freep) EventLog *el = NULL; uint64_t recnum = 0; @@ -2642,7 +2642,7 @@ static int verb_show_cel(int argc, char *argv[], void *userdata) { return 0; } -static int verb_list_components(int argc, char *argv[], void *userdata) { +static int verb_list_components(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(event_log_freep) EventLog *el = NULL; _cleanup_(table_unrefp) Table *table = NULL; enum { @@ -2962,7 +2962,7 @@ static int unlink_pcrlock(const char *default_pcrlock_path) { return 0; } -static int verb_lock_raw(int argc, char *argv[], void *userdata) { +static int verb_lock_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *records = NULL; _cleanup_fclose_ FILE *f = NULL; int r; @@ -2983,11 +2983,11 @@ static int verb_lock_raw(int argc, char *argv[], void *userdata) { return write_pcrlock(records, NULL); } -static int verb_unlock_simple(int argc, char *argv[], void *userdata) { +static int verb_unlock_simple(int argc, char *argv[], uintptr_t _data, void *userdata) { return unlink_pcrlock(NULL); } -static int verb_lock_secureboot_policy(int argc, char *argv[], void *userdata) { +static int verb_lock_secureboot_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { static const struct { sd_id128_t id; const char *name; @@ -3060,7 +3060,7 @@ static int verb_lock_secureboot_policy(int argc, char *argv[], void *userdata) { return write_pcrlock(array, PCRLOCK_SECUREBOOT_POLICY_PATH); } -static int verb_unlock_secureboot_policy(int argc, char *argv[], void *userdata) { +static int verb_unlock_secureboot_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { return unlink_pcrlock(PCRLOCK_SECUREBOOT_POLICY_PATH); } @@ -3181,7 +3181,7 @@ static int event_log_ensure_secureboot_consistency(EventLog *el) { return 0; } -static int verb_lock_secureboot_authority(int argc, char *argv[], void *userdata) { +static int verb_lock_secureboot_authority(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; _cleanup_(event_log_freep) EventLog *el = NULL; int r; @@ -3260,11 +3260,11 @@ static int verb_lock_secureboot_authority(int argc, char *argv[], void *userdata return write_pcrlock(array, PCRLOCK_SECUREBOOT_AUTHORITY_PATH); } -static int verb_unlock_secureboot_authority(int argc, char *argv[], void *userdata) { +static int verb_unlock_secureboot_authority(int argc, char *argv[], uintptr_t _data, void *userdata) { return unlink_pcrlock(PCRLOCK_SECUREBOOT_AUTHORITY_PATH); } -static int verb_lock_gpt(int argc, char *argv[], void *userdata) { +static int verb_lock_gpt(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL, *record = NULL; _cleanup_(sd_device_unrefp) sd_device *d = NULL; uint8_t h[2 * 4096]; /* space for at least two 4K sectors. GPT header should definitely be in here */ @@ -3377,7 +3377,7 @@ static int verb_lock_gpt(int argc, char *argv[], void *userdata) { return write_pcrlock(array, PCRLOCK_GPT_PATH); } -static int verb_unlock_gpt(int argc, char *argv[], void *userdata) { +static int verb_unlock_gpt(int argc, char *argv[], uintptr_t _data, void *userdata) { return unlink_pcrlock(PCRLOCK_GPT_PATH); } @@ -3436,7 +3436,7 @@ static void enable_json_sse(void) { arg_json_format_flags |= SD_JSON_FORMAT_SSE; } -static int verb_lock_firmware(int argc, char *argv[], void *userdata) { +static int verb_lock_firmware(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *array_early = NULL, *array_late = NULL; _cleanup_(event_log_freep) EventLog *el = NULL; uint32_t always_mask, separator_mask, separator_seen_mask = 0, action_seen_mask = 0; @@ -3555,7 +3555,7 @@ static int verb_lock_firmware(int argc, char *argv[], void *userdata) { return write_pcrlock(array_late, default_pcrlock_late_path); } -static int verb_unlock_firmware(int argc, char *argv[], void *userdata) { +static int verb_unlock_firmware(int argc, char *argv[], uintptr_t _data, void *userdata) { const char *default_pcrlock_early_path, *default_pcrlock_late_path; int r; @@ -3581,7 +3581,7 @@ static int verb_unlock_firmware(int argc, char *argv[], void *userdata) { return 0; } -static int verb_lock_machine_id(int argc, char *argv[], void *userdata) { +static int verb_lock_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL; _cleanup_free_ char *word = NULL; int r; @@ -3601,7 +3601,7 @@ static int verb_lock_machine_id(int argc, char *argv[], void *userdata) { return write_pcrlock(array, PCRLOCK_MACHINE_ID_PATH); } -static int verb_unlock_machine_id(int argc, char *argv[], void *userdata) { +static int verb_unlock_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) { return unlink_pcrlock(PCRLOCK_MACHINE_ID_PATH); } @@ -3632,7 +3632,7 @@ static int pcrlock_file_system_path(const char *normalized_path, char **ret) { return 0; } -static int verb_lock_file_system(int argc, char *argv[], void *userdata) { +static int verb_lock_file_system(int argc, char *argv[], uintptr_t _data, void *userdata) { const char* paths[3] = {}; int r; @@ -3685,7 +3685,7 @@ static int verb_lock_file_system(int argc, char *argv[], void *userdata) { return 0; } -static int verb_unlock_file_system(int argc, char *argv[], void *userdata) { +static int verb_unlock_file_system(int argc, char *argv[], uintptr_t _data, void *userdata) { const char* paths[3] = {}; int r; @@ -3715,7 +3715,7 @@ static int verb_unlock_file_system(int argc, char *argv[], void *userdata) { return 0; } -static int verb_lock_pe(int argc, char *argv[], void *userdata) { +static int verb_lock_pe(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; _cleanup_close_ int fd = -EBADF; int r; @@ -3779,7 +3779,7 @@ static void section_hashes_array_done(SectionHashArray *array) { free((*array)[i]); } -static int verb_lock_uki(int argc, char *argv[], void *userdata) { +static int verb_lock_uki(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL, *pe_digests = NULL; _cleanup_(section_hashes_array_done) SectionHashArray section_hashes = {}; size_t hash_sizes[TPM2_N_HASH_ALGORITHMS]; @@ -3874,7 +3874,7 @@ static int verb_lock_uki(int argc, char *argv[], void *userdata) { return write_pcrlock(array, NULL); } -static int verb_lock_kernel_cmdline(int argc, char *argv[], void *userdata) { +static int verb_lock_kernel_cmdline(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL; _cleanup_free_ char *cmdline = NULL; int r; @@ -3911,11 +3911,11 @@ static int verb_lock_kernel_cmdline(int argc, char *argv[], void *userdata) { return 0; } -static int verb_unlock_kernel_cmdline(int argc, char *argv[], void *userdata) { +static int verb_unlock_kernel_cmdline(int argc, char *argv[], uintptr_t _data, void *userdata) { return unlink_pcrlock(PCRLOCK_KERNEL_CMDLINE_PATH); } -static int verb_lock_kernel_initrd(int argc, char *argv[], void *userdata) { +static int verb_lock_kernel_initrd(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *records = NULL; _cleanup_fclose_ FILE *f = NULL; uint32_t pcr_mask = UINT32_C(1) << TPM2_PCR_KERNEL_INITRD; @@ -3938,7 +3938,7 @@ static int verb_lock_kernel_initrd(int argc, char *argv[], void *userdata) { return 0; } -static int verb_unlock_kernel_initrd(int argc, char *argv[], void *userdata) { +static int verb_unlock_kernel_initrd(int argc, char *argv[], uintptr_t _data, void *userdata) { return unlink_pcrlock(PCRLOCK_KERNEL_INITRD_PATH); } @@ -4322,7 +4322,7 @@ static int tpm2_pcr_prediction_run( return 0; } -static int verb_predict(int argc, char *argv[], void *userdata) { +static int verb_predict(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(tpm2_pcr_prediction_done) Tpm2PCRPrediction context = { arg_pcr_mask != 0 ? arg_pcr_mask : DEFAULT_PCR_MASK, }; @@ -4901,7 +4901,7 @@ static int make_policy(bool force, RecoveryPinMode recovery_pin_mode) { return 1; /* installed new policy */ } -static int verb_make_policy(int argc, char *argv[], void *userdata) { +static int verb_make_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; r = make_policy(arg_force, arg_recovery_pin); @@ -5001,7 +5001,7 @@ static int remove_policy(void) { return ret; } -static int verb_remove_policy(int argc, char *argv[], void *userdata) { +static int verb_remove_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { return remove_policy(); } @@ -5035,7 +5035,7 @@ static int test_tpm2_support_pcrlock(Tpm2Support *ret) { return 0; } -static int verb_is_supported(int argc, char *argv[], void *userdata) { +static int verb_is_supported(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; Tpm2Support s; @@ -5059,7 +5059,7 @@ static int verb_is_supported(int argc, char *argv[], void *userdata) { return ~s & (TPM2_SUPPORT_API|TPM2_SUPPORT_API_PCRLOCK); } -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -5131,6 +5131,10 @@ static int help(int argc, char *argv[], void *userdata) { return 0; } +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { + return help(); +} + static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, @@ -5177,8 +5181,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - help(0, NULL, NULL); - return 0; + return help(); case ARG_VERSION: return version(); @@ -5354,7 +5357,7 @@ static int parse_argv(int argc, char *argv[]) { static int pcrlock_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, { "log", VERB_ANY, 1, VERB_DEFAULT, verb_show_log }, { "cel", VERB_ANY, 1, 0, verb_show_cel }, { "list-components", VERB_ANY, 1, 0, verb_list_components }, diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index cf30361319e5a..ea5836fdabdd7 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -288,7 +288,7 @@ static int get_image_metadata(sd_bus *bus, const char *image, char **matches, sd return 0; } -static int verb_inspect_image(int argc, char *argv[], void *userdata) { +static int verb_inspect_image(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_strv_free_ char **matches = NULL; @@ -958,15 +958,15 @@ static int attach_reattach_image(int argc, char *argv[], const char *method) { return 0; } -static int verb_attach_image(int argc, char *argv[], void *userdata) { +static int verb_attach_image(int argc, char *argv[], uintptr_t _data, void *userdata) { return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) && !arg_force ? "AttachImage" : "AttachImageWithExtensions"); } -static int verb_reattach_image(int argc, char *argv[], void *userdata) { +static int verb_reattach_image(int argc, char *argv[], uintptr_t _data, void *userdata) { return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) && !arg_force ? "ReattachImage" : "ReattachImageWithExtensions"); } -static int verb_detach_image(int argc, char *argv[], void *userdata) { +static int verb_detach_image(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -1020,7 +1020,7 @@ static int verb_detach_image(int argc, char *argv[], void *userdata) { return 0; } -static int verb_list_images(int argc, char *argv[], void *userdata) { +static int verb_list_images(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -1094,7 +1094,7 @@ static int verb_list_images(int argc, char *argv[], void *userdata) { return 0; } -static int verb_remove_image(int argc, char *argv[], void *userdata) { +static int verb_remove_image(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r, i; @@ -1125,7 +1125,7 @@ static int verb_remove_image(int argc, char *argv[], void *userdata) { return 0; } -static int verb_read_only_image(int argc, char *argv[], void *userdata) { +static int verb_read_only_image(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int b = true, r; @@ -1149,7 +1149,7 @@ static int verb_read_only_image(int argc, char *argv[], void *userdata) { return 0; } -static int verb_set_limit(int argc, char *argv[], void *userdata) { +static int verb_set_limit(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; uint64_t limit; @@ -1182,7 +1182,7 @@ static int verb_set_limit(int argc, char *argv[], void *userdata) { return 0; } -static int verb_is_image_attached(int argc, char *argv[], void *userdata) { +static int verb_is_image_attached(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -1319,7 +1319,7 @@ static int help(void) { return 0; } -static int verb_help(int argc, char *argv[], void *userdata) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { return help(); } diff --git a/src/report/report.c b/src/report/report.c index 349d1fb15bd43..ab29cc601f0e6 100644 --- a/src/report/report.c +++ b/src/report/report.c @@ -576,7 +576,7 @@ static int readdir_sources(char **ret_directory, DirectoryEntries **ret) { return m > 0; } -static int verb_metrics(int argc, char *argv[], void *userdata) { +static int verb_metrics(int argc, char *argv[], uintptr_t _data, void *userdata) { Action action; int r; @@ -662,7 +662,7 @@ static int verb_metrics(int argc, char *argv[], void *userdata) { return 0; } -static int verb_list_sources(int argc, char *argv[], void *userdata) { +static int verb_list_sources(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; _cleanup_(table_unrefp) Table *table = table_new("source", "address"); @@ -717,7 +717,7 @@ static int verb_list_sources(int argc, char *argv[], void *userdata) { return 0; } -static int verb_help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -754,6 +754,10 @@ static int verb_help(int argc, char *argv[], void *userdata) { return 0; } +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { + return help(); +} + static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, @@ -783,7 +787,7 @@ static int parse_argv(int argc, char *argv[]) { while ((c = getopt_long(argc, argv, "hj", options, NULL)) >= 0) switch (c) { case 'h': - return verb_help(/* argc= */ 0, /* argv= */ NULL, /* userdata= */ NULL); + return help(); case ARG_VERSION: return version(); diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index ac948c64da256..e4705a6bccc76 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -787,7 +787,7 @@ static int resolve_rfc4501(sd_bus *bus, const char *name) { "Invalid DNS URI: %s", name); } -static int verb_query(int argc, char **argv, void *userdata) { +static int verb_query(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int ret = 0, r; @@ -997,7 +997,7 @@ static int resolve_service(sd_bus *bus, const char *name, const char *type, cons return 0; } -static int verb_service(int argc, char **argv, void *userdata) { +static int verb_service(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; @@ -1079,7 +1079,7 @@ static int resolve_openpgp(sd_bus *bus, const char *address) { } #endif -static int verb_openpgp(int argc, char **argv, void *userdata) { +static int verb_openpgp(int argc, char *argv[], uintptr_t _data, void *userdata) { #if HAVE_OPENSSL _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r, ret = 0; @@ -1136,7 +1136,7 @@ static bool service_family_is_valid(const char *s) { return STR_IN_SET(s, "tcp", "udp", "sctp"); } -static int verb_tlsa(int argc, char **argv, void *userdata) { +static int verb_tlsa(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; const char *family = "tcp"; char **args; @@ -1163,7 +1163,7 @@ static int verb_tlsa(int argc, char **argv, void *userdata) { return ret; } -static int verb_show_statistics(int argc, char **argv, void *userdata) { +static int verb_show_statistics(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; sd_json_variant *reply = NULL; _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; @@ -1328,7 +1328,7 @@ static int verb_show_statistics(int argc, char **argv, void *userdata) { return 0; } -static int verb_reset_statistics(int argc, char **argv, void *userdata) { +static int verb_reset_statistics(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_json_variant *reply = NULL; _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; int r; @@ -1353,7 +1353,7 @@ static int verb_reset_statistics(int argc, char **argv, void *userdata) { return 0; } -static int verb_flush_caches(int argc, char **argv, void *userdata) { +static int verb_flush_caches(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -1369,7 +1369,7 @@ static int verb_flush_caches(int argc, char **argv, void *userdata) { return 0; } -static int verb_reset_server_features(int argc, char **argv, void *userdata) { +static int verb_reset_server_features(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -1985,7 +1985,7 @@ static int status_ifindex(int ifindex, StatusMode mode) { return status_full(mode, STRV_MAKE(ifname)); } -static int verb_status(int argc, char **argv, void *userdata) { +static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { return status_full(STATUS_ALL, strv_skip(argv, 1)); } @@ -2062,7 +2062,7 @@ static int call_dns(sd_bus *bus, char **dns, const BusLocator *locator, sd_bus_e return r; } -static int verb_dns(int argc, char **argv, void *userdata) { +static int verb_dns(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -2147,7 +2147,7 @@ static int call_domain(sd_bus *bus, char **domain, const BusLocator *locator, sd return sd_bus_call(bus, req, 0, error, NULL); } -static int verb_domain(int argc, char **argv, void *userdata) { +static int verb_domain(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -2186,7 +2186,7 @@ static int verb_domain(int argc, char **argv, void *userdata) { return 0; } -static int verb_default_route(int argc, char **argv, void *userdata) { +static int verb_default_route(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r, b; @@ -2230,7 +2230,7 @@ static int verb_default_route(int argc, char **argv, void *userdata) { return 0; } -static int verb_llmnr(int argc, char **argv, void *userdata) { +static int verb_llmnr(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_free_ char *global_llmnr_support_str = NULL; @@ -2288,7 +2288,7 @@ static int verb_llmnr(int argc, char **argv, void *userdata) { return 0; } -static int verb_mdns(int argc, char **argv, void *userdata) { +static int verb_mdns(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_free_ char *global_mdns_support_str = NULL; @@ -2352,7 +2352,7 @@ static int verb_mdns(int argc, char **argv, void *userdata) { return 0; } -static int verb_dns_over_tls(int argc, char **argv, void *userdata) { +static int verb_dns_over_tls(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -2398,7 +2398,7 @@ static int verb_dns_over_tls(int argc, char **argv, void *userdata) { return 0; } -static int verb_dnssec(int argc, char **argv, void *userdata) { +static int verb_dnssec(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -2459,7 +2459,7 @@ static int call_nta(sd_bus *bus, char **nta, const BusLocator *locator, sd_bus_ return sd_bus_call(bus, req, 0, error, NULL); } -static int verb_nta(int argc, char **argv, void *userdata) { +static int verb_nta(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; char **args; @@ -2517,7 +2517,7 @@ static int verb_nta(int argc, char **argv, void *userdata) { return 0; } -static int verb_revert_link(int argc, char **argv, void *userdata) { +static int verb_revert_link(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -2554,7 +2554,7 @@ static int verb_revert_link(int argc, char **argv, void *userdata) { return 0; } -static int verb_log_level(int argc, char *argv[], void *userdata) { +static int verb_log_level(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; @@ -2750,7 +2750,7 @@ static int monitor_reply( return 0; } -static int verb_monitor(int argc, char *argv[], void *userdata) { +static int verb_monitor(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; int r, c; @@ -2924,7 +2924,7 @@ static int dump_cache_scope(sd_json_variant *scope) { return 0; } -static int verb_show_cache(int argc, char *argv[], void *userdata) { +static int verb_show_cache(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_json_variant *reply = NULL, *d = NULL; _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; int r; @@ -3104,7 +3104,7 @@ static int dump_server_state(sd_json_variant *server) { return 0; } -static int verb_show_server_state(int argc, char *argv[], void *userdata) { +static int verb_show_server_state(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_json_variant *reply = NULL, *d = NULL; _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; int r; @@ -3311,7 +3311,7 @@ static int native_help(void) { return 0; } -static int verb_help(int argc, char **argv, void *userdata) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { return native_help(); } diff --git a/src/sbsign/sbsign.c b/src/sbsign/sbsign.c index d13dc5e326a17..ee1c0f77ab906 100644 --- a/src/sbsign/sbsign.c +++ b/src/sbsign/sbsign.c @@ -45,7 +45,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep); STATIC_DESTRUCTOR_REGISTER(arg_signed_data, freep); STATIC_DESTRUCTOR_REGISTER(arg_signed_data_signature, freep); -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -83,6 +83,10 @@ static int help(int argc, char *argv[], void *userdata) { return 0; } +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { + return help(); +} + static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, @@ -119,8 +123,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - help(0, NULL, NULL); - return 0; + return help(); case ARG_VERSION: return version(); @@ -438,7 +441,7 @@ static int pkcs7_add_digest_attribute(PKCS7 *p7, BIO *data, PKCS7_SIGNER_INFO *s return 0; } -static int verb_sign(int argc, char *argv[], void *userdata) { +static int verb_sign(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL; _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = NULL; _cleanup_(X509_freep) X509 *certificate = NULL; @@ -739,8 +742,8 @@ static int verb_sign(int argc, char *argv[], void *userdata) { static int run(int argc, char *argv[]) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "sign", 2, 2, 0, verb_sign }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, + { "sign", 2, 2, 0, verb_sign }, {} }; int r; diff --git a/src/shared/verbs.c b/src/shared/verbs.c index b7b78619fdede..c6d35913b4fc9 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -134,7 +134,7 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { } if (!name) - return verb->dispatch(1, STRV_MAKE(verb->verb), userdata); + return verb->dispatch(1, STRV_MAKE(verb->verb), verb->data, userdata); - return verb->dispatch(left, argv, userdata); + return verb->dispatch(left, argv, verb->data, userdata); } diff --git a/src/shared/verbs.h b/src/shared/verbs.h index 0fb6621af600d..e330156318e96 100644 --- a/src/shared/verbs.h +++ b/src/shared/verbs.h @@ -14,7 +14,8 @@ typedef struct { const char *verb; unsigned min_args, max_args; VerbFlags flags; - int (* const dispatch)(int argc, char *argv[], void *userdata); + int (* const dispatch)(int argc, char *argv[], uintptr_t data, void *userdata); + uintptr_t data; } Verb; bool running_in_chroot_or_offline(void); diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 3661457d26f73..32de5a454c8cb 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -597,7 +597,7 @@ static int unmerge( return 0; } -static int verb_unmerge(int argc, char **argv, void *userdata) { +static int verb_unmerge(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; r = have_effective_cap(CAP_SYS_ADMIN); @@ -690,7 +690,7 @@ static int vl_method_unmerge(sd_varlink *link, sd_json_variant *parameters, sd_v return sd_varlink_reply(link, NULL); } -static int verb_status(int argc, char **argv, void *userdata) { +static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *t = NULL; int r, ret = 0; @@ -2474,7 +2474,7 @@ static int look_for_merged_hierarchies( return 0; } -static int verb_merge(int argc, char **argv, void *userdata) { +static int verb_merge(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_hashmap_free_ Hashmap *images = NULL; const char *which; int r; @@ -2637,7 +2637,7 @@ static int refresh( return r; } -static int verb_refresh(int argc, char **argv, void *userdata) { +static int verb_refresh(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; r = have_effective_cap(CAP_SYS_ADMIN); @@ -2703,7 +2703,7 @@ static int vl_method_refresh(sd_varlink *link, sd_json_variant *parameters, sd_v return sd_varlink_reply(link, NULL); } -static int verb_list(int argc, char **argv, void *userdata) { +static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_hashmap_free_ Hashmap *images = NULL; _cleanup_(table_unrefp) Table *t = NULL; Image *img; @@ -2794,7 +2794,7 @@ static int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varl return 0; } -static int verb_help(int argc, char **argv, void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -2839,8 +2839,11 @@ static int verb_help(int argc, char **argv, void *userdata) { return 0; } -static int parse_argv(int argc, char *argv[]) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { + return help(); +} +static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, ARG_NO_PAGER, @@ -2881,7 +2884,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - return verb_help(argc, argv, NULL); + return help(); case ARG_VERSION: return version(); diff --git a/src/systemctl/systemctl-add-dependency.c b/src/systemctl/systemctl-add-dependency.c index bc1a1f00f69e5..2972c4c63b839 100644 --- a/src/systemctl/systemctl-add-dependency.c +++ b/src/systemctl/systemctl-add-dependency.c @@ -17,7 +17,7 @@ #include "systemctl-util.h" #include "unit-name.h" -int verb_add_dependency(int argc, char *argv[], void *userdata) { +int verb_add_dependency(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_strv_free_ char **names = NULL; _cleanup_free_ char *target = NULL; const char *verb = argv[0]; diff --git a/src/systemctl/systemctl-add-dependency.h b/src/systemctl/systemctl-add-dependency.h index 11e5c82cc99a6..799301170ba6a 100644 --- a/src/systemctl/systemctl-add-dependency.h +++ b/src/systemctl/systemctl-add-dependency.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_add_dependency(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_add_dependency(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-cancel-job.c b/src/systemctl/systemctl-cancel-job.c index 63459820f9698..ed829fb1382ad 100644 --- a/src/systemctl/systemctl-cancel-job.c +++ b/src/systemctl/systemctl-cancel-job.c @@ -12,12 +12,12 @@ #include "systemctl-trivial-method.h" #include "systemctl-util.h" -int verb_cancel(int argc, char *argv[], void *userdata) { +int verb_cancel(int argc, char *argv[], uintptr_t data, void *userdata) { sd_bus *bus; int r; if (argc <= 1) /* Shortcut to trivial_method() if no argument is given */ - return verb_trivial_method(argc, argv, userdata); + return verb_trivial_method(argc, argv, data, userdata); r = acquire_bus(BUS_MANAGER, &bus); if (r < 0) diff --git a/src/systemctl/systemctl-cancel-job.h b/src/systemctl/systemctl-cancel-job.h index 397e5155f3eb2..0c27cb19e5b83 100644 --- a/src/systemctl/systemctl-cancel-job.h +++ b/src/systemctl/systemctl-cancel-job.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_cancel(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_cancel(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-clean-or-freeze.c b/src/systemctl/systemctl-clean-or-freeze.c index 4870074e8c014..dfaff2adbcf35 100644 --- a/src/systemctl/systemctl-clean-or-freeze.c +++ b/src/systemctl/systemctl-clean-or-freeze.c @@ -13,7 +13,7 @@ #include "systemctl-clean-or-freeze.h" #include "systemctl-util.h" -int verb_clean_or_freeze(int argc, char *argv[], void *userdata) { +int verb_clean_or_freeze(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(bus_wait_for_units_freep) BusWaitForUnits *w = NULL; _cleanup_strv_free_ char **names = NULL; int r, ret = EXIT_SUCCESS; diff --git a/src/systemctl/systemctl-clean-or-freeze.h b/src/systemctl/systemctl-clean-or-freeze.h index 5f2bca4a4e56a..aadf15c2975ae 100644 --- a/src/systemctl/systemctl-clean-or-freeze.h +++ b/src/systemctl/systemctl-clean-or-freeze.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_clean_or_freeze(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_clean_or_freeze(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-compat-halt.c b/src/systemctl/systemctl-compat-halt.c index 614216fd08559..cebdfb2413f16 100644 --- a/src/systemctl/systemctl-compat-halt.c +++ b/src/systemctl/systemctl-compat-halt.c @@ -170,7 +170,7 @@ int halt_main(void) { arg_no_block = true; if (!arg_dry_run) - return verb_start(0, NULL, NULL); + return verb_start(0, NULL, /* data= */ 0, NULL); } r = must_be_root(); diff --git a/src/systemctl/systemctl-daemon-reload.c b/src/systemctl/systemctl-daemon-reload.c index 48606dd77f0cb..fd513e69d47b4 100644 --- a/src/systemctl/systemctl-daemon-reload.c +++ b/src/systemctl/systemctl-daemon-reload.c @@ -62,7 +62,7 @@ int daemon_reload(enum action action, bool graceful) { return 1; } -int verb_daemon_reload(int argc, char *argv[], void *userdata) { +int verb_daemon_reload(int argc, char *argv[], uintptr_t _data, void *userdata) { enum action a; int r; diff --git a/src/systemctl/systemctl-daemon-reload.h b/src/systemctl/systemctl-daemon-reload.h index 0265a313f2a33..3a9785a9aaf37 100644 --- a/src/systemctl/systemctl-daemon-reload.h +++ b/src/systemctl/systemctl-daemon-reload.h @@ -5,4 +5,4 @@ int daemon_reload(enum action action, bool graceful); -int verb_daemon_reload(int argc, char *argv[], void *userdata); +int verb_daemon_reload(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-edit.c b/src/systemctl/systemctl-edit.c index a28180922a02d..ea0ca2f2f657e 100644 --- a/src/systemctl/systemctl-edit.c +++ b/src/systemctl/systemctl-edit.c @@ -20,7 +20,7 @@ #include "terminal-util.h" #include "unit-name.h" -int verb_cat(int argc, char *argv[], void *userdata) { +int verb_cat(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_hashmap_free_ Hashmap *cached_id_map = NULL, *cached_name_map = NULL; _cleanup_(lookup_paths_done) LookupPaths lp = {}; _cleanup_strv_free_ char **names = NULL; @@ -324,7 +324,7 @@ static int find_paths_to_edit( return 0; } -int verb_edit(int argc, char *argv[], void *userdata) { +int verb_edit(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(edit_file_context_done) EditFileContext context = { .marker_start = DROPIN_MARKER_START, .marker_end = DROPIN_MARKER_END, diff --git a/src/systemctl/systemctl-edit.h b/src/systemctl/systemctl-edit.h index 10dac5cb2a19d..d847b8c42cde0 100644 --- a/src/systemctl/systemctl-edit.h +++ b/src/systemctl/systemctl-edit.h @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_cat(int argc, char *argv[], void *userdata); -int verb_edit(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_cat(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_edit(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-enable.c b/src/systemctl/systemctl-enable.c index f6277b7287135..68c349a195c67 100644 --- a/src/systemctl/systemctl-enable.c +++ b/src/systemctl/systemctl-enable.c @@ -80,7 +80,7 @@ static int normalize_names(char **names) { return 0; } -int verb_enable(int argc, char *argv[], void *userdata) { +int verb_enable(int argc, char *argv[], uintptr_t data, void *userdata) { const char *verb = ASSERT_PTR(argv[0]); _cleanup_strv_free_ char **names = NULL; int carries_install_info = -1; @@ -402,7 +402,7 @@ int verb_enable(int argc, char *argv[], void *userdata) { return log_oom(); } - return verb_start(strv_length(new_args), new_args, userdata); + return verb_start(strv_length(new_args), new_args, data, userdata); } return 0; diff --git a/src/systemctl/systemctl-enable.h b/src/systemctl/systemctl-enable.h index f04bbcd62a2b2..ec1d911a7eb63 100644 --- a/src/systemctl/systemctl-enable.h +++ b/src/systemctl/systemctl-enable.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_enable(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_enable(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-is-active.c b/src/systemctl/systemctl-is-active.c index 19c24eaf7e577..6f853b5b59c39 100644 --- a/src/systemctl/systemctl-is-active.c +++ b/src/systemctl/systemctl-is-active.c @@ -57,7 +57,7 @@ static int check_unit_generic(int code, const UnitActiveState good_states[], siz return ok ? EXIT_SUCCESS : not_found ? EXIT_PROGRAM_OR_SERVICES_STATUS_UNKNOWN : code; } -int verb_is_active(int argc, char *argv[], void *userdata) { +int verb_is_active(int argc, char *argv[], uintptr_t _data, void *userdata) { static const UnitActiveState states[] = { UNIT_ACTIVE, @@ -69,7 +69,7 @@ int verb_is_active(int argc, char *argv[], void *userdata) { return check_unit_generic(EXIT_PROGRAM_NOT_RUNNING, states, ELEMENTSOF(states), strv_skip(argv, 1)); } -int verb_is_failed(int argc, char *argv[], void *userdata) { +int verb_is_failed(int argc, char *argv[], uintptr_t _data, void *userdata) { static const UnitActiveState states[] = { UNIT_FAILED, diff --git a/src/systemctl/systemctl-is-active.h b/src/systemctl/systemctl-is-active.h index 950f29ac55b62..771739f856478 100644 --- a/src/systemctl/systemctl-is-active.h +++ b/src/systemctl/systemctl-is-active.h @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_is_active(int argc, char *argv[], void *userdata); -int verb_is_failed(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_is_active(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_is_failed(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-is-enabled.c b/src/systemctl/systemctl-is-enabled.c index 77b8cac5f01eb..e42faf2724c7c 100644 --- a/src/systemctl/systemctl-is-enabled.c +++ b/src/systemctl/systemctl-is-enabled.c @@ -65,7 +65,7 @@ static int show_installation_targets(sd_bus *bus, const char *name) { return 0; } -int verb_is_enabled(int argc, char *argv[], void *userdata) { +int verb_is_enabled(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_strv_free_ char **names = NULL; bool not_found = true, enabled = false; int r; diff --git a/src/systemctl/systemctl-is-enabled.h b/src/systemctl/systemctl-is-enabled.h index 96dff95d6f33b..1ce1343327d1c 100644 --- a/src/systemctl/systemctl-is-enabled.h +++ b/src/systemctl/systemctl-is-enabled.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_is_enabled(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_is_enabled(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-is-system-running.c b/src/systemctl/systemctl-is-system-running.c index 943d4aa6d7a77..2270f5ad56fc6 100644 --- a/src/systemctl/systemctl-is-system-running.c +++ b/src/systemctl/systemctl-is-system-running.c @@ -25,7 +25,7 @@ static int match_startup_finished(sd_bus_message *m, void *userdata, sd_bus_erro return 0; } -int verb_is_system_running(int argc, char *argv[], void *userdata) { +int verb_is_system_running(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot_startup_finished = NULL; _cleanup_(sd_event_unrefp) sd_event* event = NULL; diff --git a/src/systemctl/systemctl-is-system-running.h b/src/systemctl/systemctl-is-system-running.h index de86211a912da..ebe80aed9d702 100644 --- a/src/systemctl/systemctl-is-system-running.h +++ b/src/systemctl/systemctl-is-system-running.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_is_system_running(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_is_system_running(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-kill.c b/src/systemctl/systemctl-kill.c index 1452deb5b7c90..575ef1e4193e2 100644 --- a/src/systemctl/systemctl-kill.c +++ b/src/systemctl/systemctl-kill.c @@ -13,7 +13,7 @@ #include "systemctl-kill.h" #include "systemctl-util.h" -int verb_kill(int argc, char *argv[], void *userdata) { +int verb_kill(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(bus_wait_for_units_freep) BusWaitForUnits *w = NULL; _cleanup_strv_free_ char **names = NULL; const char *kill_whom; diff --git a/src/systemctl/systemctl-kill.h b/src/systemctl/systemctl-kill.h index 88b2eae4b29b9..54e8bbe262995 100644 --- a/src/systemctl/systemctl-kill.h +++ b/src/systemctl/systemctl-kill.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_kill(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_kill(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-list-dependencies.c b/src/systemctl/systemctl-list-dependencies.c index 65f12ea473977..8e5736ef3531f 100644 --- a/src/systemctl/systemctl-list-dependencies.c +++ b/src/systemctl/systemctl-list-dependencies.c @@ -167,7 +167,7 @@ static int list_dependencies_one( return 0; } -int verb_list_dependencies(int argc, char *argv[], void *userdata) { +int verb_list_dependencies(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_strv_free_ char **units = NULL, **done = NULL; char **patterns; sd_bus *bus; diff --git a/src/systemctl/systemctl-list-dependencies.h b/src/systemctl/systemctl-list-dependencies.h index 1e68a5f9f05df..53b68085acd63 100644 --- a/src/systemctl/systemctl-list-dependencies.h +++ b/src/systemctl/systemctl-list-dependencies.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_list_dependencies(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_list_dependencies(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-list-jobs.c b/src/systemctl/systemctl-list-jobs.c index 9049873bbe329..5804d32518c6e 100644 --- a/src/systemctl/systemctl-list-jobs.c +++ b/src/systemctl/systemctl-list-jobs.c @@ -133,7 +133,7 @@ static bool output_show_job(struct job_info *job, char **patterns) { return strv_fnmatch_or_empty(patterns, job->name, FNM_NOESCAPE); } -int verb_list_jobs(int argc, char *argv[], void *userdata) { +int verb_list_jobs(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_free_ struct job_info *jobs = NULL; diff --git a/src/systemctl/systemctl-list-jobs.h b/src/systemctl/systemctl-list-jobs.h index b10ec79b3ec98..f8fe9b6302c77 100644 --- a/src/systemctl/systemctl-list-jobs.h +++ b/src/systemctl/systemctl-list-jobs.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_list_jobs(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_list_jobs(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-list-machines.c b/src/systemctl/systemctl-list-machines.c index ea83f43a737fa..a5d0376c27238 100644 --- a/src/systemctl/systemctl-list-machines.c +++ b/src/systemctl/systemctl-list-machines.c @@ -231,7 +231,7 @@ static int output_machines_list(struct machine_info *machine_infos, unsigned n) return 0; } -int verb_list_machines(int argc, char *argv[], void *userdata) { +int verb_list_machines(int argc, char *argv[], uintptr_t _data, void *userdata) { struct machine_info *machine_infos = NULL; sd_bus *bus; int r, rc; diff --git a/src/systemctl/systemctl-list-machines.h b/src/systemctl/systemctl-list-machines.h index f1dd8353e1e51..fd0331604b5a0 100644 --- a/src/systemctl/systemctl-list-machines.h +++ b/src/systemctl/systemctl-list-machines.h @@ -4,7 +4,7 @@ #include "bus-map-properties.h" #include "shared-forward.h" -int verb_list_machines(int argc, char *argv[], void *userdata); +int verb_list_machines(int argc, char *argv[], uintptr_t _data, void *userdata); struct machine_info { bool is_host; diff --git a/src/systemctl/systemctl-list-unit-files.c b/src/systemctl/systemctl-list-unit-files.c index 548b2573fc4ec..e0074974eeb80 100644 --- a/src/systemctl/systemctl-list-unit-files.c +++ b/src/systemctl/systemctl-list-unit-files.c @@ -173,7 +173,7 @@ static int output_unit_file_list(const UnitFileList *units, unsigned c) { return 0; } -int verb_list_unit_files(int argc, char *argv[], void *userdata) { +int verb_list_unit_files(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_hashmap_free_ Hashmap *h = NULL; _cleanup_free_ UnitFileList *units = NULL; diff --git a/src/systemctl/systemctl-list-unit-files.h b/src/systemctl/systemctl-list-unit-files.h index 4819fbd820015..150d392f00ef4 100644 --- a/src/systemctl/systemctl-list-unit-files.h +++ b/src/systemctl/systemctl-list-unit-files.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_list_unit_files(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_list_unit_files(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-list-units.c b/src/systemctl/systemctl-list-units.c index 7e16b4377b4e1..9a8cd89295b5c 100644 --- a/src/systemctl/systemctl-list-units.c +++ b/src/systemctl/systemctl-list-units.c @@ -258,7 +258,7 @@ static int output_units_list(const UnitInfo *unit_infos, size_t c) { return 0; } -int verb_list_units(int argc, char *argv[], void *userdata) { +int verb_list_units(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ UnitInfo *unit_infos = NULL; _cleanup_set_free_ Set *replies = NULL; sd_bus *bus; @@ -490,7 +490,7 @@ static int output_sockets_list(const SocketInfo *sockets, size_t n_sockets) { return 0; } -int verb_list_sockets(int argc, char *argv[], void *userdata) { +int verb_list_sockets(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_set_free_ Set *replies = NULL; _cleanup_strv_free_ char **sockets_with_suffix = NULL; _cleanup_free_ UnitInfo *unit_infos = NULL; @@ -771,7 +771,7 @@ static int add_timer_info( return 0; } -int verb_list_timers(int argc, char *argv[], void *userdata) { +int verb_list_timers(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_set_free_ Set *replies = NULL; _cleanup_strv_free_ char **timers_with_suffix = NULL; _cleanup_free_ UnitInfo *unit_infos = NULL; @@ -970,7 +970,7 @@ static int output_automounts_list(const AutomountInfo *infos, size_t n_infos) { return 0; } -int verb_list_automounts(int argc, char *argv[], void *userdata) { +int verb_list_automounts(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_set_free_ Set *replies = NULL; _cleanup_strv_free_ char **names = NULL; _cleanup_free_ UnitInfo *unit_infos = NULL; @@ -1178,7 +1178,7 @@ static int output_paths_list(const PathInfo *paths, size_t n_paths) { return 0; } -int verb_list_paths(int argc, char *argv[], void *userdata) { +int verb_list_paths(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_set_free_ Set *replies = NULL; _cleanup_strv_free_ char **units = NULL; _cleanup_free_ UnitInfo *unit_infos = NULL; diff --git a/src/systemctl/systemctl-list-units.h b/src/systemctl/systemctl-list-units.h index 74bf9cda166a0..e3c4f24ececb1 100644 --- a/src/systemctl/systemctl-list-units.h +++ b/src/systemctl/systemctl-list-units.h @@ -3,10 +3,10 @@ #include "time-util.h" -int verb_list_units(int argc, char *argv[], void *userdata); -int verb_list_sockets(int argc, char *argv[], void *userdata); -int verb_list_timers(int argc, char *argv[], void *userdata); -int verb_list_automounts(int argc, char *argv[], void *userdata); -int verb_list_paths(int argc, char *argv[], void *userdata); +int verb_list_units(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_list_sockets(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_list_timers(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_list_automounts(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_list_paths(int argc, char *argv[], uintptr_t _data, void *userdata); usec_t calc_next_elapse(const dual_timestamp *nw, const dual_timestamp *next); diff --git a/src/systemctl/systemctl-log-setting.c b/src/systemctl/systemctl-log-setting.c index 1ea3d7abefad4..845ed748c23cb 100644 --- a/src/systemctl/systemctl-log-setting.c +++ b/src/systemctl/systemctl-log-setting.c @@ -26,7 +26,7 @@ static void give_log_control1_hint(const char *name) { " See the %s for details.", link ?: "org.freedesktop.LogControl1(5) man page"); } -int verb_log_setting(int argc, char *argv[], void *userdata) { +int verb_log_setting(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus; int r; @@ -71,7 +71,7 @@ static int service_name_to_dbus(sd_bus *bus, const char *name, char **ret_dbus_n return 0; } -int verb_service_log_setting(int argc, char *argv[], void *userdata) { +int verb_service_log_setting(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus; _cleanup_free_ char *unit = NULL, *dbus_name = NULL; int r; diff --git a/src/systemctl/systemctl-log-setting.h b/src/systemctl/systemctl-log-setting.h index 910d6c8af5c81..bf8bfc3229c12 100644 --- a/src/systemctl/systemctl-log-setting.h +++ b/src/systemctl/systemctl-log-setting.h @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_log_setting(int argc, char *argv[], void *userdata); -int verb_service_log_setting(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_log_setting(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_service_log_setting(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-mount.c b/src/systemctl/systemctl-mount.c index bc4aa92260f83..23720d53fac4d 100644 --- a/src/systemctl/systemctl-mount.c +++ b/src/systemctl/systemctl-mount.c @@ -14,7 +14,7 @@ #include "systemctl-util.h" #include "unit-name.h" -int verb_bind(int argc, char *argv[], void *userdata) { +int verb_bind(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_free_ char *n = NULL; sd_bus *bus; @@ -48,7 +48,7 @@ int verb_bind(int argc, char *argv[], void *userdata) { return 0; } -int verb_mount_image(int argc, char *argv[], void *userdata) { +int verb_mount_image(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; const char *unit = argv[1], *src = argv[2], *dest = argv[3]; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; diff --git a/src/systemctl/systemctl-mount.h b/src/systemctl/systemctl-mount.h index b2d075001693b..a4a9760b3447a 100644 --- a/src/systemctl/systemctl-mount.h +++ b/src/systemctl/systemctl-mount.h @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_bind(int argc, char *argv[], void *userdata); -int verb_mount_image(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_bind(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_mount_image(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-preset-all.c b/src/systemctl/systemctl-preset-all.c index f621d55895614..2687a841d11d2 100644 --- a/src/systemctl/systemctl-preset-all.c +++ b/src/systemctl/systemctl-preset-all.c @@ -13,7 +13,7 @@ #include "systemctl-util.h" #include "verbs.h" -int verb_preset_all(int argc, char *argv[], void *userdata) { +int verb_preset_all(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; if (should_bypass("SYSTEMD_PRESET")) diff --git a/src/systemctl/systemctl-preset-all.h b/src/systemctl/systemctl-preset-all.h index 4631e7ea311fc..178ca31291fc3 100644 --- a/src/systemctl/systemctl-preset-all.h +++ b/src/systemctl/systemctl-preset-all.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_preset_all(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_preset_all(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-reset-failed.c b/src/systemctl/systemctl-reset-failed.c index 18ca190517844..8e14174f5ed6b 100644 --- a/src/systemctl/systemctl-reset-failed.c +++ b/src/systemctl/systemctl-reset-failed.c @@ -10,13 +10,13 @@ #include "systemctl-trivial-method.h" #include "systemctl-util.h" -int verb_reset_failed(int argc, char *argv[], void *userdata) { +int verb_reset_failed(int argc, char *argv[], uintptr_t data, void *userdata) { _cleanup_strv_free_ char **names = NULL; sd_bus *bus; int r, q; if (argc <= 1) /* Shortcut to trivial_method() if no argument is given */ - return verb_trivial_method(argc, argv, userdata); + return verb_trivial_method(argc, argv, data, userdata); r = acquire_bus(BUS_MANAGER, &bus); if (r < 0) diff --git a/src/systemctl/systemctl-reset-failed.h b/src/systemctl/systemctl-reset-failed.h index 5da0659d6ec40..6ad714cf4a7eb 100644 --- a/src/systemctl/systemctl-reset-failed.h +++ b/src/systemctl/systemctl-reset-failed.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_reset_failed(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_reset_failed(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-service-watchdogs.c b/src/systemctl/systemctl-service-watchdogs.c index 632345b405654..7d563dacf5a98 100644 --- a/src/systemctl/systemctl-service-watchdogs.c +++ b/src/systemctl/systemctl-service-watchdogs.c @@ -10,7 +10,7 @@ #include "systemctl-service-watchdogs.h" #include "systemctl-util.h" -int verb_service_watchdogs(int argc, char *argv[], void *userdata) { +int verb_service_watchdogs(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int b, r; diff --git a/src/systemctl/systemctl-service-watchdogs.h b/src/systemctl/systemctl-service-watchdogs.h index 2f59f5a3f4376..e626a223c35f5 100644 --- a/src/systemctl/systemctl-service-watchdogs.h +++ b/src/systemctl/systemctl-service-watchdogs.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_service_watchdogs(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_service_watchdogs(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-set-default.c b/src/systemctl/systemctl-set-default.c index c4faa31b4c17b..ac4e39203780d 100644 --- a/src/systemctl/systemctl-set-default.c +++ b/src/systemctl/systemctl-set-default.c @@ -88,7 +88,7 @@ static int determine_default(char **ret_name) { } } -int verb_get_default(int argc, char *argv[], void *userdata) { +int verb_get_default(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ char *name = NULL; int r; @@ -103,7 +103,7 @@ int verb_get_default(int argc, char *argv[], void *userdata) { return 0; } -int verb_set_default(int argc, char *argv[], void *userdata) { +int verb_set_default(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ char *unit = NULL; int r; diff --git a/src/systemctl/systemctl-set-default.h b/src/systemctl/systemctl-set-default.h index 7873e126780a3..7df9e78c4f020 100644 --- a/src/systemctl/systemctl-set-default.h +++ b/src/systemctl/systemctl-set-default.h @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_get_default(int argc, char *argv[], void *userdata); -int verb_set_default(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_get_default(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_set_default(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-set-environment.c b/src/systemctl/systemctl-set-environment.c index 7e04f5a867337..008608fb8c22c 100644 --- a/src/systemctl/systemctl-set-environment.c +++ b/src/systemctl/systemctl-set-environment.c @@ -70,7 +70,7 @@ static int print_variable(const char *s) { return 0; } -int verb_show_environment(int argc, char *argv[], void *userdata) { +int verb_show_environment(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; const char *text; @@ -122,7 +122,7 @@ static void invalid_callback(const char *p, void *userdata) { log_debug("Ignoring invalid environment assignment \"%s\".", strnull(t)); } -int verb_set_environment(int argc, char *argv[], void *userdata) { +int verb_set_environment(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; const char *method; @@ -157,7 +157,7 @@ int verb_set_environment(int argc, char *argv[], void *userdata) { return 0; } -int verb_import_environment(int argc, char *argv[], void *userdata) { +int verb_import_environment(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; sd_bus *bus; diff --git a/src/systemctl/systemctl-set-environment.h b/src/systemctl/systemctl-set-environment.h index 404258aa43cb3..659afd53c5148 100644 --- a/src/systemctl/systemctl-set-environment.h +++ b/src/systemctl/systemctl-set-environment.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_show_environment(int argc, char *argv[], void *userdata); -int verb_set_environment(int argc, char *argv[], void *userdata); -int verb_import_environment(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_show_environment(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_set_environment(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_import_environment(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-set-property.c b/src/systemctl/systemctl-set-property.c index e84e3d580f0f6..da8bfe3162936 100644 --- a/src/systemctl/systemctl-set-property.c +++ b/src/systemctl/systemctl-set-property.c @@ -51,7 +51,7 @@ static int set_property_one(sd_bus *bus, const char *name, char **properties) { return 0; } -int verb_set_property(int argc, char *argv[], void *userdata) { +int verb_set_property(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus; _cleanup_strv_free_ char **names = NULL; int r; diff --git a/src/systemctl/systemctl-set-property.h b/src/systemctl/systemctl-set-property.h index 0892291d59cd8..e1dc8dc049c91 100644 --- a/src/systemctl/systemctl-set-property.h +++ b/src/systemctl/systemctl-set-property.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_set_property(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_set_property(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-show.c b/src/systemctl/systemctl-show.c index c35db87c45a88..0872f82c2a3f2 100644 --- a/src/systemctl/systemctl-show.c +++ b/src/systemctl/systemctl-show.c @@ -2482,7 +2482,7 @@ static int show_system_status(sd_bus *bus) { return 0; } -int verb_show(int argc, char *argv[], void *userdata) { +int verb_show(int argc, char *argv[], uintptr_t _data, void *userdata) { bool new_line = false, ellipsized = false; SystemctlShowMode show_mode; int r, ret = 0; diff --git a/src/systemctl/systemctl-show.h b/src/systemctl/systemctl-show.h index 5aeed51e5b4a0..4ca15bca74528 100644 --- a/src/systemctl/systemctl-show.h +++ b/src/systemctl/systemctl-show.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_show(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_show(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-start-special.c b/src/systemctl/systemctl-start-special.c index 71927846deebc..d947f1f9e4101 100644 --- a/src/systemctl/systemctl-start-special.c +++ b/src/systemctl/systemctl-start-special.c @@ -127,7 +127,7 @@ static int set_exit_code(uint8_t code) { return 0; } -int verb_start_special(int argc, char *argv[], void *userdata) { +int verb_start_special(int argc, char *argv[], uintptr_t data, void *userdata) { bool termination_action; /* An action that terminates the system, can be performed also by signal. */ enum action a; int r; @@ -198,7 +198,7 @@ int verb_start_special(int argc, char *argv[], void *userdata) { if (arg_force >= 1 && (termination_action || IN_SET(a, ACTION_KEXEC, ACTION_EXIT))) - r = verb_trivial_method(argc, argv, userdata); + r = verb_trivial_method(argc, argv, data, userdata); else { /* First try logind, to allow authentication with polkit */ switch (a) { @@ -255,7 +255,7 @@ int verb_start_special(int argc, char *argv[], void *userdata) { ; } - r = verb_start(argc, argv, userdata); + r = verb_start(argc, argv, data, userdata); } if (termination_action && arg_force < 2 && @@ -265,7 +265,7 @@ int verb_start_special(int argc, char *argv[], void *userdata) { return r; } -int verb_start_system_special(int argc, char *argv[], void *userdata) { +int verb_start_system_special(int argc, char *argv[], uintptr_t data, void *userdata) { /* Like start_special above, but raises an error when running in user mode */ if (arg_runtime_scope != RUNTIME_SCOPE_SYSTEM) @@ -273,5 +273,5 @@ int verb_start_system_special(int argc, char *argv[], void *userdata) { "Bad action for %s mode.", runtime_scope_cmdline_option_to_string(arg_runtime_scope)); - return verb_start_special(argc, argv, userdata); + return verb_start_special(argc, argv, data, userdata); } diff --git a/src/systemctl/systemctl-start-special.h b/src/systemctl/systemctl-start-special.h index 9396321d7064e..93df5f89b7d94 100644 --- a/src/systemctl/systemctl-start-special.h +++ b/src/systemctl/systemctl-start-special.h @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_start_special(int argc, char *argv[], void *userdata); -int verb_start_system_special(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_start_special(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_start_system_special(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-start-unit.c b/src/systemctl/systemctl-start-unit.c index b349483836804..0c8582bdff710 100644 --- a/src/systemctl/systemctl-start-unit.c +++ b/src/systemctl/systemctl-start-unit.c @@ -289,7 +289,7 @@ static const char** make_extra_args(const char *extra_args[static 4]) { return extra_args; } -int verb_start(int argc, char *argv[], void *userdata) { +int verb_start(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(bus_wait_for_units_freep) BusWaitForUnits *wu = NULL; _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; const char *method, *job_type, *mode, *one_name, *suffix = NULL; diff --git a/src/systemctl/systemctl-start-unit.h b/src/systemctl/systemctl-start-unit.h index 28650167731ef..02434a2db6c76 100644 --- a/src/systemctl/systemctl-start-unit.h +++ b/src/systemctl/systemctl-start-unit.h @@ -3,7 +3,7 @@ #include "systemctl.h" -int verb_start(int argc, char *argv[], void *userdata); +int verb_start(int argc, char *argv[], uintptr_t _data, void *userdata); struct action_metadata { const char *target; diff --git a/src/systemctl/systemctl-switch-root.c b/src/systemctl/systemctl-switch-root.c index 62aebe886e611..27fccf7f41748 100644 --- a/src/systemctl/systemctl-switch-root.c +++ b/src/systemctl/systemctl-switch-root.c @@ -38,7 +38,7 @@ static int same_file_in_root( return stat_inode_same(&sta, &stb); } -int verb_switch_root(int argc, char *argv[], void *userdata) { +int verb_switch_root(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_free_ char *cmdline_init = NULL; const char *root, *init; diff --git a/src/systemctl/systemctl-switch-root.h b/src/systemctl/systemctl-switch-root.h index e9ba12baf799f..46d336430641b 100644 --- a/src/systemctl/systemctl-switch-root.h +++ b/src/systemctl/systemctl-switch-root.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_switch_root(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_switch_root(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-trivial-method.c b/src/systemctl/systemctl-trivial-method.c index 3fa1272c665c2..1e94357f4acf2 100644 --- a/src/systemctl/systemctl-trivial-method.c +++ b/src/systemctl/systemctl-trivial-method.c @@ -12,7 +12,7 @@ /* A generic implementation for cases we just need to invoke a simple method call on the Manager object. */ -int verb_trivial_method(int argc, char *argv[], void *userdata) { +int verb_trivial_method(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; const char *method; sd_bus *bus; diff --git a/src/systemctl/systemctl-trivial-method.h b/src/systemctl/systemctl-trivial-method.h index d36b4803d4338..ed901c14a841b 100644 --- a/src/systemctl/systemctl-trivial-method.h +++ b/src/systemctl/systemctl-trivial-method.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_trivial_method(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_trivial_method(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/systemctl/systemctl-whoami.c b/src/systemctl/systemctl-whoami.c index bf38eb2236b14..77ccef8d134ea 100644 --- a/src/systemctl/systemctl-whoami.c +++ b/src/systemctl/systemctl-whoami.c @@ -11,7 +11,7 @@ #include "systemctl-util.h" #include "systemctl-whoami.h" -int verb_whoami(int argc, char *argv[], void *userdata) { +int verb_whoami(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus; int r, ret = 0; diff --git a/src/systemctl/systemctl-whoami.h b/src/systemctl/systemctl-whoami.h index abdd13b34fc1e..9bdefdb14a310 100644 --- a/src/systemctl/systemctl-whoami.h +++ b/src/systemctl/systemctl-whoami.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int verb_whoami(int argc, char *argv[], void *userdata); +#include "shared-forward.h" + +int verb_whoami(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 69848c3fcb7eb..57b2125f92c9e 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -1277,7 +1277,7 @@ static int process_image( return 0; } -static int verb_list(int argc, char **argv, void *userdata) { +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; @@ -1345,7 +1345,7 @@ static int verb_list(int argc, char **argv, void *userdata) { } } -static int verb_features(int argc, char **argv, void *userdata) { +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; @@ -1479,7 +1479,7 @@ static int verb_features(int argc, char **argv, void *userdata) { return 0; } -static int verb_check_new(int argc, char **argv, void *userdata) { +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; @@ -1520,7 +1520,7 @@ static int verb_check_new(int argc, char **argv, void *userdata) { return EXIT_SUCCESS; } -static int verb_vacuum(int argc, char **argv, void *userdata) { +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; @@ -1615,7 +1615,7 @@ static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flag return 0; } -static int verb_update(int argc, char **argv, void *userdata) { +static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata) { UpdateActionFlags flags = UPDATE_ACTION_INSTALL; if (!arg_offline) @@ -1624,11 +1624,11 @@ static int verb_update(int argc, char **argv, void *userdata) { return verb_update_impl(argc, argv, flags); } -static int verb_acquire(int argc, char **argv, void *userdata) { +static int verb_acquire(int argc, char *argv[], uintptr_t _data, void *userdata) { return verb_update_impl(argc, argv, UPDATE_ACTION_ACQUIRE); } -static int verb_pending_or_reboot(int argc, char **argv, void *userdata) { +static int verb_pending_or_reboot(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(context_freep) Context* context = NULL; _cleanup_free_ char *booted_version = NULL; int r; @@ -1702,7 +1702,7 @@ static int component_name_valid(const char *c) { return filename_is_valid(j); } -static int verb_components(int argc, char **argv, void *userdata) { +static int verb_components(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_set_free_ Set *names = NULL; @@ -1792,7 +1792,7 @@ static int verb_components(int argc, char **argv, void *userdata) { return 0; } -static int verb_help(int argc, char **argv, void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -1844,8 +1844,11 @@ static int verb_help(int argc, char **argv, void *userdata) { return 0; } -static int parse_argv(int argc, char *argv[]) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { + return help(); +} +static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, ARG_NO_PAGER, @@ -1892,7 +1895,7 @@ static int parse_argv(int argc, char *argv[]) { switch (c) { case 'h': - return verb_help(0, NULL, NULL); + return help(); case ARG_VERSION: return version(); diff --git a/src/sysupdate/updatectl.c b/src/sysupdate/updatectl.c index 60523181bcdb1..86561cf1c734d 100644 --- a/src/sysupdate/updatectl.c +++ b/src/sysupdate/updatectl.c @@ -641,7 +641,7 @@ static int describe(sd_bus *bus, const char *target_path, const char *version) { return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, arg_legend); } -static int verb_list(int argc, char **argv, void *userdata) { +static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); _cleanup_free_ char *target_path = NULL, *version = NULL; int r; @@ -752,7 +752,7 @@ static int check_finished(sd_bus_message *reply, void *userdata, sd_bus_error *r return 0; } -static int verb_check(int argc, char **argv, void *userdata) { +static int verb_check(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); _cleanup_(table_unrefp) Table *table = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; @@ -1283,7 +1283,7 @@ static int do_update(sd_bus *bus, char **targets) { return did_anything ? 1 : 0; } -static int verb_update(int argc, char **argv, void *userdata) { +static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); _cleanup_strv_free_ char **targets = NULL; bool did_anything = false; @@ -1336,7 +1336,7 @@ static int do_vacuum(sd_bus *bus, const char *target, const char *path) { return count + disabled > 0 ? 1 : 0; } -static int verb_vacuum(int argc, char **argv, void *userdata) { +static int verb_vacuum(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); _cleanup_strv_free_ char **targets = NULL, **target_paths = NULL; size_t n; @@ -1480,7 +1480,7 @@ static int list_features(sd_bus *bus) { return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, arg_legend); } -static int verb_features(int argc, char **argv, void *userdata) { +static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); _cleanup_(table_unrefp) Table *table = NULL; _cleanup_(feature_done) Feature f = {}; @@ -1529,7 +1529,7 @@ static int verb_features(int argc, char **argv, void *userdata) { return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, false); } -static int verb_enable(int argc, char **argv, void *userdata) { +static int verb_enable(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); bool did_anything = false, enable; char **features; diff --git a/src/test/test-verbs.c b/src/test/test-verbs.c index a28fc9b55b274..79c5c27dd94f3 100644 --- a/src/test/test-verbs.c +++ b/src/test/test-verbs.c @@ -6,7 +6,7 @@ #include "tests.h" #include "verbs.h" -static int noop_dispatcher(int argc, char *argv[], void *userdata) { +static int noop_dispatcher(int argc, char *argv[], uintptr_t _data, void *userdata) { return 0; } diff --git a/src/timedate/timedatectl.c b/src/timedate/timedatectl.c index 36852b1833224..cec4363affbbb 100644 --- a/src/timedate/timedatectl.c +++ b/src/timedate/timedatectl.c @@ -180,7 +180,7 @@ static int print_status_info(const StatusInfo *i) { return 0; } -static int verb_show_status(int argc, char **argv, void *userdata) { +static int verb_show_status(int argc, char *argv[], uintptr_t _data, void *userdata) { StatusInfo info = {}; static const struct bus_properties_map map[] = { { "Timezone", "s", NULL, offsetof(StatusInfo, timezone) }, @@ -212,7 +212,7 @@ static int verb_show_status(int argc, char **argv, void *userdata) { return print_status_info(&info); } -static int verb_show_properties(int argc, char **argv, void *userdata) { +static int verb_show_properties(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -229,7 +229,7 @@ static int verb_show_properties(int argc, char **argv, void *userdata) { return 0; } -static int verb_set_time(int argc, char **argv, void *userdata) { +static int verb_set_time(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = userdata; usec_t t; @@ -254,7 +254,7 @@ static int verb_set_time(int argc, char **argv, void *userdata) { return 0; } -static int verb_set_timezone(int argc, char **argv, void *userdata) { +static int verb_set_timezone(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = userdata; int r; @@ -268,7 +268,7 @@ static int verb_set_timezone(int argc, char **argv, void *userdata) { return 0; } -static int verb_set_local_rtc(int argc, char **argv, void *userdata) { +static int verb_set_local_rtc(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = userdata; int r, b; @@ -299,7 +299,7 @@ static int verb_set_local_rtc(int argc, char **argv, void *userdata) { return 0; } -static int verb_set_ntp(int argc, char **argv, void *userdata) { +static int verb_set_ntp(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = userdata; @@ -327,7 +327,7 @@ static int verb_set_ntp(int argc, char **argv, void *userdata) { return 0; } -static int verb_list_timezones(int argc, char **argv, void *userdata) { +static int verb_list_timezones(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = userdata; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; @@ -688,7 +688,7 @@ static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error return show_timesync_status_once(sd_bus_message_get_bus(m)); } -static int verb_show_timesync_status(int argc, char **argv, void *userdata) { +static int verb_show_timesync_status(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_event_unrefp) sd_event *event = NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -792,7 +792,7 @@ static int print_timesync_property(const char *name, const char *expected_value, return 0; } -static int verb_show_timesync(int argc, char **argv, void *userdata) { +static int verb_show_timesync(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -841,7 +841,7 @@ static int parse_ifindex_bus(sd_bus *bus, const char *str) { return i; } -static int verb_ntp_servers(int argc, char **argv, void *userdata) { +static int verb_ntp_servers(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; sd_bus *bus = ASSERT_PTR(userdata); @@ -872,7 +872,7 @@ static int verb_ntp_servers(int argc, char **argv, void *userdata) { return 0; } -static int verb_revert(int argc, char **argv, void *userdata) { +static int verb_revert(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int ifindex, r; @@ -936,7 +936,7 @@ static int help(void) { return 0; } -static int verb_help(int argc, char **argv, void *userdata) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { return help(); } diff --git a/src/udev/iocost/iocost.c b/src/udev/iocost/iocost.c index a1c9e0c9ede64..3d473d469d5a8 100644 --- a/src/udev/iocost/iocost.c +++ b/src/udev/iocost/iocost.c @@ -280,11 +280,11 @@ static int query_solutions_for_path(const char *path) { return 0; } -static int verb_query(int argc, char *argv[], void *userdata) { +static int verb_query(int argc, char *argv[], uintptr_t _data, void *userdata) { return query_solutions_for_path(ASSERT_PTR(argv[1])); } -static int verb_apply(int argc, char *argv[], void *userdata) { +static int verb_apply(int argc, char *argv[], uintptr_t _data, void *userdata) { return apply_solution_for_path( ASSERT_PTR(argv[1]), argc > 2 ? ASSERT_PTR(argv[2]) : NULL); diff --git a/src/udev/udevadm-cat.c b/src/udev/udevadm-cat.c index c1c39aedf36d7..d6c488ff3fab4 100644 --- a/src/udev/udevadm-cat.c +++ b/src/udev/udevadm-cat.c @@ -92,7 +92,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -int verb_cat_main(int argc, char *argv[], void *userdata) { +int verb_cat_main(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; r = parse_argv(argc, argv); diff --git a/src/udev/udevadm-control.c b/src/udev/udevadm-control.c index a401acfde9101..964f721731ceb 100644 --- a/src/udev/udevadm-control.c +++ b/src/udev/udevadm-control.c @@ -331,7 +331,7 @@ static int send_control_commands(void) { return 0; } -int verb_control_main(int argc, char *argv[], void *userdata) { +int verb_control_main(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; if (running_in_chroot() > 0) { diff --git a/src/udev/udevadm-hwdb.c b/src/udev/udevadm-hwdb.c index 44c04590afa3e..5810efefd8ce2 100644 --- a/src/udev/udevadm-hwdb.c +++ b/src/udev/udevadm-hwdb.c @@ -78,7 +78,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -int verb_hwdb_main(int argc, char *argv[], void *userdata) { +int verb_hwdb_main(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; r = parse_argv(argc, argv); diff --git a/src/udev/udevadm-info.c b/src/udev/udevadm-info.c index 558d80e3af2e5..62d7dce4217de 100644 --- a/src/udev/udevadm-info.c +++ b/src/udev/udevadm-info.c @@ -1275,7 +1275,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -int verb_info_main(int argc, char *argv[], void *userdata) { +int verb_info_main(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; r = parse_argv(argc, argv); diff --git a/src/udev/udevadm-lock.c b/src/udev/udevadm-lock.c index df78dd9a844cd..483b64973d401 100644 --- a/src/udev/udevadm-lock.c +++ b/src/udev/udevadm-lock.c @@ -228,7 +228,7 @@ static int lock_device( return TAKE_FD(fd); } -int verb_lock_main(int argc, char *argv[], void *userdata) { +int verb_lock_main(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_fdset_free_ FDSet *fds = NULL; _cleanup_free_ dev_t *devnos = NULL; size_t n_devnos = 0; diff --git a/src/udev/udevadm-monitor.c b/src/udev/udevadm-monitor.c index c8a2c3ca22f72..b7ec2ba1ef281 100644 --- a/src/udev/udevadm-monitor.c +++ b/src/udev/udevadm-monitor.c @@ -187,7 +187,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -int verb_monitor_main(int argc, char *argv[], void *userdata) { +int verb_monitor_main(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *kernel_monitor = NULL, *udev_monitor = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; int r; diff --git a/src/udev/udevadm-settle.c b/src/udev/udevadm-settle.c index 74ac4acbefcf7..b71759dc818e6 100644 --- a/src/udev/udevadm-settle.c +++ b/src/udev/udevadm-settle.c @@ -184,7 +184,7 @@ static int on_inotify(sd_event_source *s, const struct inotify_event *event, voi return 0; } -int verb_settle_main(int argc, char *argv[], void *userdata) { +int verb_settle_main(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_event_unrefp) sd_event *event = NULL; int r; diff --git a/src/udev/udevadm-test-builtin.c b/src/udev/udevadm-test-builtin.c index 24ea039120b60..f17df9a7d51a2 100644 --- a/src/udev/udevadm-test-builtin.c +++ b/src/udev/udevadm-test-builtin.c @@ -63,7 +63,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -int verb_builtin_main(int argc, char *argv[], void *userdata) { +int verb_builtin_main(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(udev_event_unrefp) UdevEvent *event = NULL; _cleanup_(sd_device_unrefp) sd_device *dev = NULL; UdevBuiltinCommand cmd; diff --git a/src/udev/udevadm-test.c b/src/udev/udevadm-test.c index 98b63aa11c452..f3ac39717e946 100644 --- a/src/udev/udevadm-test.c +++ b/src/udev/udevadm-test.c @@ -129,7 +129,7 @@ static void maybe_insert_empty_line(void) { fputs("\n", stderr); } -int verb_test_main(int argc, char *argv[], void *userdata) { +int verb_test_main(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(udev_rules_freep) UdevRules *rules = NULL; _cleanup_(udev_event_unrefp) UdevEvent *event = NULL; _cleanup_(sd_device_unrefp) sd_device *dev = NULL; diff --git a/src/udev/udevadm-trigger.c b/src/udev/udevadm-trigger.c index b97ecfa0997eb..afa6a84262084 100644 --- a/src/udev/udevadm-trigger.c +++ b/src/udev/udevadm-trigger.c @@ -534,7 +534,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -int verb_trigger_main(int argc, char *argv[], void *userdata) { +int verb_trigger_main(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *m = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; diff --git a/src/udev/udevadm-verify.c b/src/udev/udevadm-verify.c index 5d4119399ed20..03fed60d7e63a 100644 --- a/src/udev/udevadm-verify.c +++ b/src/udev/udevadm-verify.c @@ -156,7 +156,7 @@ static int verify_rules(UdevRules *rules, ConfFile * const *files, size_t n_file return ret; } -int verb_verify_main(int argc, char *argv[], void *userdata) { +int verb_verify_main(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(udev_rules_freep) UdevRules *rules = NULL; int r; diff --git a/src/udev/udevadm-wait.c b/src/udev/udevadm-wait.c index bfc000e217dab..0e285fc36b247 100644 --- a/src/udev/udevadm-wait.c +++ b/src/udev/udevadm-wait.c @@ -376,7 +376,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; /* work to do */ } -int verb_wait_main(int argc, char *argv[], void *userdata) { +int verb_wait_main(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *udev_monitor = NULL, *kernel_monitor = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; int r; diff --git a/src/udev/udevadm.c b/src/udev/udevadm.c index 8ffe068b9d87e..70ff213cb9999 100644 --- a/src/udev/udevadm.c +++ b/src/udev/udevadm.c @@ -91,11 +91,11 @@ int print_version(void) { return 0; } -static int verb_version_main(int argc, char *argv[], void *userdata) { +static int verb_version_main(int argc, char *argv[], uintptr_t _data, void *userdata) { return print_version(); } -static int verb_help_main(int argc, char *argv[], void *userdata) { +static int verb_help_main(int argc, char *argv[], uintptr_t _data, void *userdata) { return help(); } diff --git a/src/udev/udevadm.h b/src/udev/udevadm.h index b41d091a3479e..285a474fba277 100644 --- a/src/udev/udevadm.h +++ b/src/udev/udevadm.h @@ -3,17 +3,17 @@ #include "shared-forward.h" -int verb_cat_main(int argc, char *argv[], void *userdata); -int verb_info_main(int argc, char *argv[], void *userdata); -int verb_trigger_main(int argc, char *argv[], void *userdata); -int verb_settle_main(int argc, char *argv[], void *userdata); -int verb_control_main(int argc, char *argv[], void *userdata); -int verb_monitor_main(int argc, char *argv[], void *userdata); -int verb_hwdb_main(int argc, char *argv[], void *userdata); -int verb_test_main(int argc, char *argv[], void *userdata); -int verb_builtin_main(int argc, char *argv[], void *userdata); -int verb_verify_main(int argc, char *argv[], void *userdata); -int verb_wait_main(int argc, char *argv[], void *userdata); -int verb_lock_main(int argc, char *argv[], void *userdata); +int verb_cat_main(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_info_main(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_trigger_main(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_settle_main(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_control_main(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_monitor_main(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_hwdb_main(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_test_main(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_builtin_main(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_verify_main(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_wait_main(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_lock_main(int argc, char *argv[], uintptr_t _data, void *userdata); int print_version(void); diff --git a/src/update-utmp/update-utmp.c b/src/update-utmp/update-utmp.c index e16520e9849f8..875ae9a09bcad 100644 --- a/src/update-utmp/update-utmp.c +++ b/src/update-utmp/update-utmp.c @@ -51,7 +51,7 @@ static int get_startup_monotonic_time(Context *c, usec_t *ret) { return 0; } -static int verb_on_reboot(int argc, char *argv[], void *userdata) { +static int verb_on_reboot(int argc, char *argv[], uintptr_t _data, void *userdata) { Context *c = ASSERT_PTR(userdata); usec_t t = 0, boottime; int r, q = 0; @@ -80,7 +80,7 @@ static int verb_on_reboot(int argc, char *argv[], void *userdata) { return q; } -static int verb_on_shutdown(int argc, char *argv[], void *userdata) { +static int verb_on_shutdown(int argc, char *argv[], uintptr_t _data, void *userdata) { int r, q = 0; /* We started shut-down, so let's write the utmp record and send the audit msg. */ diff --git a/src/userdb/userdbctl.c b/src/userdb/userdbctl.c index c1e19ee0aa33e..b0d7a06941e14 100644 --- a/src/userdb/userdbctl.c +++ b/src/userdb/userdbctl.c @@ -407,7 +407,7 @@ static int table_add_uid_map( return n_added; } -static int verb_display_user(int argc, char *argv[], void *userdata) { +static int verb_display_user(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; bool draw_separator = false; int ret = 0, r; @@ -750,7 +750,7 @@ static int add_unavailable_gid(Table *table, uid_t start, uid_t end) { return 2; } -static int verb_display_group(int argc, char *argv[], void *userdata) { +static int verb_display_group(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; bool draw_separator = false; int ret = 0, r; @@ -951,7 +951,7 @@ static int show_membership(const char *user, const char *group, Table *table) { return 0; } -static int verb_display_memberships(int argc, char *argv[], void *userdata) { +static int verb_display_memberships(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; int ret = 0, r; @@ -1047,7 +1047,7 @@ static int verb_display_memberships(int argc, char *argv[], void *userdata) { return ret; } -static int verb_display_services(int argc, char *argv[], void *userdata) { +static int verb_display_services(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *t = NULL; _cleanup_closedir_ DIR *d = NULL; int r; @@ -1114,7 +1114,7 @@ static int verb_display_services(int argc, char *argv[], void *userdata) { return 0; } -static int verb_ssh_authorized_keys(int argc, char *argv[], void *userdata) { +static int verb_ssh_authorized_keys(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(user_record_unrefp) UserRecord *ur = NULL; char **chain_invocation; int r; @@ -1497,7 +1497,7 @@ static int load_credential_one( return 0; } -static int verb_load_credentials(int argc, char *argv[], void *userdata) { +static int verb_load_credentials(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; _cleanup_close_ int credential_dir_fd = open_credentials_dir(); @@ -1590,7 +1590,7 @@ static int help(void) { return 0; } -static int verb_help(int argc, char *argv[], void *userdata) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { return help(); } diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index 233423935f763..5048f433b1753 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -123,7 +123,7 @@ static int help(void) { return 0; } -static int verb_help(int argc, char **argv, void *userdata) { +static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { return help(); } @@ -366,7 +366,7 @@ static void get_info_data_done(GetInfoData *d) { d->interfaces = strv_free(d->interfaces); } -static int verb_info(int argc, char *argv[], void *userdata) { +static int verb_info(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; const char *url; int r; @@ -463,7 +463,7 @@ typedef struct GetInterfaceDescriptionData { const char *description; } GetInterfaceDescriptionData; -static int verb_introspect(int argc, char *argv[], void *userdata) { +static int verb_introspect(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; _cleanup_strv_free_ char **auto_interfaces = NULL; char **interfaces; @@ -634,7 +634,7 @@ static int reply_callback( return r; } -static int verb_call(int argc, char *argv[], void *userdata) { +static int verb_call(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *jp = NULL; _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; const char *url, *method, *parameter, *source; @@ -946,7 +946,7 @@ static int verb_call(int argc, char *argv[], void *userdata) { return 0; } -static int verb_validate_idl(int argc, char *argv[], void *userdata) { +static int verb_validate_idl(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_varlink_interface_freep) sd_varlink_interface *vi = NULL; _cleanup_free_ char *text = NULL; const char *fname; @@ -995,7 +995,7 @@ static int verb_validate_idl(int argc, char *argv[], void *userdata) { return 0; } -static int verb_list_registry(int argc, char *argv[], void *userdata) { +static int verb_list_registry(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; assert(argc <= 1); diff --git a/src/veritysetup/veritysetup.c b/src/veritysetup/veritysetup.c index e2135562c1f12..b2f6d3b8af726 100644 --- a/src/veritysetup/veritysetup.c +++ b/src/veritysetup/veritysetup.c @@ -316,7 +316,7 @@ static int parse_options(const char *options) { return r; } -static int verb_attach(int argc, char *argv[], void *userdata) { +static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; _cleanup_free_ void *rh = NULL; struct crypt_params_verity p = {}; @@ -450,7 +450,7 @@ static int verb_attach(int argc, char *argv[], void *userdata) { return 0; } -static int verb_detach(int argc, char *argv[], void *userdata) { +static int verb_detach(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; int r; From 1dc35ab944ec26603fe0171e16b8cd5bec89ad65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 17:03:47 +0100 Subject: [PATCH 0307/1296] report: use verb function argument --- src/report/report.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/report/report.c b/src/report/report.c index ab29cc601f0e6..f93b3076c6f81 100644 --- a/src/report/report.c +++ b/src/report/report.c @@ -576,23 +576,17 @@ static int readdir_sources(char **ret_directory, DirectoryEntries **ret) { return m > 0; } -static int verb_metrics(int argc, char *argv[], uintptr_t _data, void *userdata) { - Action action; +static int verb_metrics(int argc, char *argv[], uintptr_t data, void *userdata) { + Action action = data; int r; assert(argc >= 1); assert(argv); + assert(IN_SET(action, ACTION_LIST, ACTION_DESCRIBE)); /* Enable JSON-SEQ mode here, since we'll dump a large series of JSON objects */ arg_json_format_flags |= SD_JSON_FORMAT_SEQ; - if (streq_ptr(argv[0], "metrics")) - action = ACTION_LIST; - else { - assert(streq_ptr(argv[0], "describe-metrics")); - action = ACTION_DESCRIBE; - } - r = parse_metrics_matches(argv + 1); if (r < 0) return r; @@ -830,12 +824,11 @@ static int parse_argv(int argc, char *argv[]) { } static int report_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, 1, 0, verb_help }, - { "metrics", VERB_ANY, VERB_ANY, 0, verb_metrics }, - { "describe-metrics", VERB_ANY, VERB_ANY, 0, verb_metrics }, - { "list-sources", VERB_ANY, 1, 0, verb_list_sources }, + { "help", VERB_ANY, 1, 0, verb_help }, + { "metrics", VERB_ANY, VERB_ANY, 0, verb_metrics, ACTION_LIST }, + { "describe-metrics", VERB_ANY, VERB_ANY, 0, verb_metrics, ACTION_DESCRIBE }, + { "list-sources", VERB_ANY, 1, 0, verb_list_sources }, {} }; From 69544de33e971feab4005543b7d4a5db1397f524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 17:13:58 +0100 Subject: [PATCH 0308/1296] bless-boot: use verb function argument --- src/bless-boot/bless-boot.c | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/bless-boot/bless-boot.c b/src/bless-boot/bless-boot.c index 72902e3d5017a..c4a9eeee76cea 100644 --- a/src/bless-boot/bless-boot.c +++ b/src/bless-boot/bless-boot.c @@ -26,6 +26,12 @@ static char **arg_path = NULL; STATIC_DESTRUCTOR_REGISTER(arg_path, strv_freep); +typedef enum Status { + STATUS_GOOD, + STATUS_BAD, + STATUS_INDETERMINATE, +} Status; + static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -344,7 +350,8 @@ static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) int r; r = acquire_boot_count_path(&path, &prefix, &left, &done, &suffix); - if (r == -EUNATCH) { /* No boot count in place, then let's consider this a "clean" boot, as "good", "bad" or "indeterminate" don't apply. */ + if (r == -EUNATCH) { /* No boot count in place, then let's consider this a "clean" boot, + * since "good", "bad", or "indeterminate" don't apply. */ puts("clean"); return 0; } @@ -444,12 +451,15 @@ static int rename_in_dir_idempotent(int fd, const char *from, const char *to) { return 1; } -static int verb_set(int argc, char *argv[], uintptr_t _data, void *userdata) { +static int verb_set(int argc, char *argv[], uintptr_t data, void *userdata) { _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL; const char *target, *source1, *source2; uint64_t left, done; + Status status = data; int r; + assert(IN_SET(status, STATUS_GOOD, STATUS_BAD, STATUS_INDETERMINATE)); + r = acquire_boot_count_path(&path, &prefix, &left, &done, &suffix); if (r == -EUNATCH) /* acquire_boot_count_path() won't log on its own for this specific error */ return log_error_errno(r, "Not booted with boot counting in effect."); @@ -469,23 +479,25 @@ static int verb_set(int argc, char *argv[], uintptr_t _data, void *userdata) { return log_oom(); /* Figure out what rename to what */ - if (streq(argv[0], "good")) { + switch (status) { + case STATUS_GOOD: target = good; source1 = path; source2 = bad; /* Maybe this boot was previously marked as 'bad'? */ - } else if (streq(argv[0], "bad")) { + break; + case STATUS_BAD: target = bad; source1 = path; source2 = good; /* Maybe this boot was previously marked as 'good'? */ - } else { - assert(streq(argv[0], "indeterminate")); - + break; + case STATUS_INDETERMINATE: if (left == 0) return log_error_errno(r, "Current boot entry was already marked bad in a previous boot, cannot reset to indeterminate."); target = path; source1 = good; source2 = bad; + break; } STRV_FOREACH(p, arg_path) { @@ -550,11 +562,11 @@ static int verb_set(int argc, char *argv[], uintptr_t _data, void *userdata) { static int run(int argc, char *argv[]) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, - { "good", VERB_ANY, 1, 0, verb_set }, - { "bad", VERB_ANY, 1, 0, verb_set }, - { "indeterminate", VERB_ANY, 1, 0, verb_set }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, + { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, + { "good", VERB_ANY, 1, 0, verb_set, STATUS_GOOD }, + { "bad", VERB_ANY, 1, 0, verb_set, STATUS_BAD }, + { "indeterminate", VERB_ANY, 1, 0, verb_set, STATUS_INDETERMINATE }, {} }; From cfc31d9c76375bc20b468f71c4224f07a9b247f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 9 Mar 2026 13:18:22 +0100 Subject: [PATCH 0309/1296] ci: reeanble compilation test with clang -O2, disable -Wmaybe-uninitialized for old gcc In CI we get spurious failures about unitialized variables with gcc versions older then (depending on the case) 12, 13, or 14. Let's only try to do this check with newer gcc which returns more useful results. At the same time, do compile with both gcc and clang at -O2, just disable the warning. The old logic seems to have been confused. We compile with -Wall, at least in some cases, which includes -Wmaybe-unitialized. So if we _don't_ want it, we need to explicitly disable it. --- .github/workflows/build-test.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-test.sh b/.github/workflows/build-test.sh index 3c6c6c50feebe..506479a55845d 100755 --- a/.github/workflows/build-test.sh +++ b/.github/workflows/build-test.sh @@ -12,7 +12,7 @@ success() { echo >&2 -e "\033[32;1m$1\033[0m"; } ARGS=( "--optimization=0 -Dopenssl=disabled -Dtpm=true -Dtpm2=enabled" "--optimization=s -Dutmp=false -Dc_args='-DOPENSSL_NO_UI_CONSOLE=1'" - "--optimization=2 -Dc_args=-Wmaybe-uninitialized -Ddns-over-tls=openssl" + "--optimization=2 -Ddns-over-tls=openssl" "--optimization=3 -Db_lto=true -Ddns-over-tls=false" "--optimization=3 -Db_lto=false -Dtpm2=disabled -Dlibfido2=disabled -Dp11kit=disabled -Defi=false -Dbootloader=disabled" "--optimization=3 -Dfexecve=true -Dstandalone-binaries=true -Dstatic-libsystemd=true -Dstatic-libudev=true" @@ -108,6 +108,11 @@ elif [[ "$COMPILER" == gcc ]]; then CFLAGS="" CXXFLAGS="" + # -Wmaybe-uninitialized works badly in old gcc versions + if [[ "$COMPILER_VERSION" -lt 14 ]]; then + CFLAGS="$CFLAGS -Wno-maybe-uninitialized" + fi + if ! apt-get -y install --dry-run "gcc-$COMPILER_VERSION" >/dev/null; then # Latest gcc stack deb packages provided by # https://launchpad.net/~ubuntu-toolchain-r/+archive/ubuntu/test @@ -162,11 +167,6 @@ ninja --version for args in "${ARGS[@]}"; do SECONDS=0 - if [[ "$COMPILER" == clang && "$args" =~ Wmaybe-uninitialized ]]; then - # -Wmaybe-uninitialized is not implemented in clang - continue - fi - info "Checking build with $args" # shellcheck disable=SC2086 if ! AR="$AR" \ From 3236700a675cf4052bdef9b58ec0dd3d61b29f7c Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 17 Mar 2026 18:26:04 +0000 Subject: [PATCH 0310/1296] docs: update security policy to suggest GH advisories --- docs/SECURITY.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/SECURITY.md b/docs/SECURITY.md index f9f2e91ad681e..0993f85da2bb6 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -8,11 +8,13 @@ SPDX-License-Identifier: LGPL-2.1-or-later # Reporting of Security Vulnerabilities If you discover a security vulnerability, we'd appreciate a non-public disclosure. -systemd developers can be contacted privately on the **[systemd-security@redhat.com](mailto:systemd-security@redhat.com) mailing list**. +systemd developers can be contacted privately by creating a new **[Security Advisory on GitHub](https://github.com/systemd/systemd/security/advisories/new)** +or via the **[systemd-security@redhat.com](mailto:systemd-security@redhat.com) mailing list**. The disclosure will be coordinated with distributions. (The [issue tracker](https://github.com/systemd/systemd/issues) and [systemd-devel mailing list](https://lists.freedesktop.org/mailman/listinfo/systemd-devel) are fully public.) -Subscription to the systemd-security mailing list is open to **regular systemd contributors and people working in the security teams of various distributions**. +Subscription to the Security Advisories and/or systemd-security mailing list is open to **regular systemd contributors and people working in the security teams of various distributions**. Those conditions should be backed by publicly accessible information (ideally, a track of posts and commits from the mail address in question). -If you fall into one of those categories and wish to be subscribed, submit a **[subscription request](https://www.redhat.com/mailman/listinfo/systemd-security)**. +If you fall into one of those categories and wish to be subscribed, +contact the maintainers or submit a **[subscription request](https://www.redhat.com/mailman/listinfo/systemd-security)**. From 36d129a7adae13a95e5ccbe10a5142c268c0882f Mon Sep 17 00:00:00 2001 From: Nandakumar Raghavan Date: Wed, 25 Feb 2026 06:38:31 +0000 Subject: [PATCH 0311/1296] repart: add --grain-size= option for partition alignment Add a --grain-size= CLI option to override the default 4 KiB partition alignment grain. Setting --grain-size=1M matches the alignment used by fdisk/parted and fixes misaligned partitions after small fixed-size partitions like the 16 KiB verity-sig partition. Also fix context_place_partitions() to re-align the start offset after each partition, not just once per free area. Without this, a small partition would cause all subsequent partitions in the same free area to start at an unaligned offset. --- man/systemd-repart.xml | 13 ++++++++++++ src/repart/repart.c | 46 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/man/systemd-repart.xml b/man/systemd-repart.xml index e13dac3cd53c4..dac4759538446 100644 --- a/man/systemd-repart.xml +++ b/man/systemd-repart.xml @@ -535,6 +535,19 @@ + + + + This option controls the partition alignment granularity used when placing + partitions. It takes a power-of-2 value that is at least the sector size. All partition start + offsets will be rounded up to a multiple of this value. Defaults to + MAX(4096, sector_size), matching the conventional 4 KiB alignment. Setting + this to 1M ensures proper alignment for modern storage devices even after + small fixed-size partitions such as a verity signature partition. + + + + diff --git a/src/repart/repart.c b/src/repart/repart.c index f6ca9aca0565a..eb334a7c4013d 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -200,6 +200,7 @@ static size_t arg_n_defer_partitions = 0; static bool arg_defer_partitions_empty = false; static bool arg_defer_partitions_factory_reset = false; static uint64_t arg_sector_size = 0; +static uint64_t arg_grain_size = 0; static ImagePolicy *arg_image_policy = NULL; static Architecture arg_architecture = _ARCHITECTURE_INVALID; static int arg_offline = -1; @@ -617,6 +618,10 @@ static const char *progress_phase_table[_PROGRESS_PHASE_MAX] = { [PROGRESS_REREADING_TABLE] = "rereading-table", }; +static uint64_t determine_grain_size(uint64_t sector_size) { + return MAX(arg_grain_size > 0 ? arg_grain_size : 4096U, sector_size); +} + DEFINE_PRIVATE_STRING_TABLE_LOOKUP(empty_mode, EmptyMode); DEFINE_PRIVATE_STRING_TABLE_LOOKUP(append_mode, AppendMode); DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(encrypt_mode, EncryptMode, ENCRYPT_KEY_FILE); @@ -1701,7 +1706,7 @@ static void context_place_partitions(Context *context) { for (size_t i = 0; i < context->n_free_areas; i++) { FreeArea *a = context->free_areas[i]; - _unused_ uint64_t left; + uint64_t left; uint64_t start; if (a->after) { @@ -1717,6 +1722,8 @@ static void context_place_partitions(Context *context) { left = a->size; LIST_FOREACH(partitions, p, context->partitions) { + uint64_t gap; + if (p->allocated_to_area != a) continue; @@ -1730,6 +1737,21 @@ static void context_place_partitions(Context *context) { assert(left >= p->new_padding); start += p->new_padding; left -= p->new_padding; + + /* Re-align start to the grain after each partition, so that the next + * partition placed into this free area also starts on a grain boundary. + * This matters when the grain is larger than the default (e.g. 1 MiB via + * --grain-size=) and a small partition like verity-sig (16 KiB) precedes + * a larger one: without this, the successor would start at an unaligned + * offset. */ + gap = round_up_size(start, context->grain_size) - start; + if (gap > left) { + log_warning("Not enough space left in free area to re-align partition start to grain size, " + "next partition may start at an unaligned offset."); + gap = 0; + } + start += gap; + left -= gap; } } } @@ -3579,7 +3601,7 @@ static int context_load_fallback_metrics(Context *context) { assert(context); context->sector_size = arg_sector_size > 0 ? arg_sector_size : 512; - context->grain_size = MAX(context->sector_size, 4096U); + context->grain_size = determine_grain_size(context->sector_size); context->default_fs_sector_size = arg_sector_size > 0 ? arg_sector_size : DEFAULT_FILESYSTEM_SECTOR_SIZE; return 1; /* Starting from scratch */ } @@ -3672,7 +3694,7 @@ static int context_load_partition_table(Context *context) { /* Use the fallback values if we have no better idea */ context->sector_size = fdisk_get_sector_size(c); context->default_fs_sector_size = fs_secsz; - context->grain_size = MAX(context->sector_size, 4096U); + context->grain_size = determine_grain_size(context->sector_size); return /* from_scratch= */ true; } @@ -3701,9 +3723,9 @@ static int context_load_partition_table(Context *context) { if (secsz < 512 || !ISPOWEROF2(secsz)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Sector size %lu is not a power of two larger than 512? Refusing.", secsz); - /* Use at least 4K, and ensure it's a multiple of the sector size, regardless if that is smaller or - * larger */ - grainsz = MAX(secsz, 4096U); + /* Determine the grain size: by default at least 4K and a multiple of the sector size, but may be + * overridden via --grain-size=. */ + grainsz = determine_grain_size(secsz); log_debug("Sector size of device is %lu bytes. Using default filesystem sector size of %" PRIu64 " and grain size of %" PRIu64 ".", secsz, fs_secsz, grainsz); @@ -9069,6 +9091,7 @@ static int help(void) { " --offline=BOOL Whether to build the image offline\n" " --discard=BOOL Whether to discard backing blocks for new partitions\n" " --sector-size=SIZE Set the logical sector size for the image\n" + " --grain-size=BYTES Set the grain size for partition alignment\n" " --architecture=ARCH Set the generic architecture for the image\n" " --size=BYTES Grow loopback file to specified size\n" " --seed=UUID 128-bit seed UUID to derive all UUIDs from\n" @@ -9199,6 +9222,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_DEFER_PARTITIONS_EMPTY, ARG_DEFER_PARTITIONS_FACTORY_RESET, ARG_SECTOR_SIZE, + ARG_GRAIN_SIZE, ARG_SKIP_PARTITIONS, ARG_ARCHITECTURE, ARG_OFFLINE, @@ -9248,6 +9272,7 @@ static int parse_argv(int argc, char *argv[]) { { "defer-partitions-empty", required_argument, NULL, ARG_DEFER_PARTITIONS_EMPTY }, { "defer-partitions-factory-reset", required_argument, NULL, ARG_DEFER_PARTITIONS_FACTORY_RESET }, { "sector-size", required_argument, NULL, ARG_SECTOR_SIZE }, + { "grain-size", required_argument, NULL, ARG_GRAIN_SIZE }, { "architecture", required_argument, NULL, ARG_ARCHITECTURE }, { "offline", required_argument, NULL, ARG_OFFLINE }, { "copy-from", required_argument, NULL, ARG_COPY_FROM }, @@ -9570,6 +9595,15 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_GRAIN_SIZE: + r = parse_size(optarg, 1024, &arg_grain_size); + if (r < 0) + return log_error_errno(r, "Failed to parse --grain-size= parameter: %s", optarg); + if (arg_grain_size < 512 || !ISPOWEROF2(arg_grain_size)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Grain size must be a power of 2 >= 512."); + + break; + case ARG_ARCHITECTURE: r = architecture_from_string(optarg); if (r < 0) From eef8f528a39530441c496d2de1a90dd3bb4dc420 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 18 Mar 2026 11:28:55 +0100 Subject: [PATCH 0312/1296] ci: Enable network isolation for claude and allow most tools claude wants to use python to access the JSON context so let's allow it. Since python3 basically allows you to reimplement every other tool, let's just enable all tools except the web related ones but enable network isolation so it can't try to exfiltrate anything via python. --- .github/workflows/claude-review.yml | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 3b2444073a983..dbab77b2e7216 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -162,6 +162,9 @@ jobs: with: name: pr-context.json + - name: Install sandbox dependencies + run: sudo apt-get update && sudo apt-get install -y bubblewrap socat + - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 with: @@ -232,17 +235,20 @@ jobs: allowed_non_write_users: "*" track_progress: false show_full_output: "true" + # Sandbox Bash commands to prevent network access and restrict + # filesystem writes to the working directory. + settings: | + { + "sandbox": { + "enabled": true, + "autoAllowBashIfSandboxed": true, + "allowUnsandboxedCommands": false + } + } claude_args: | --model us.anthropic.claude-opus-4-6-v1 --max-turns 100 - --allowedTools " - Read,LS,Grep,Glob,Task,TaskStop, - Bash(cat *),Bash(test *),Bash(printf *),Bash(jq *),Bash(head *),Bash(tail *), - Bash(git log *),Bash(git diff *),Bash(git show *),Bash(git rev-parse *), - Bash(git merge-base *),Bash(git blame *),Bash(git branch *),Bash(git status *), - Bash(grep *),Bash(find *),Bash(ls *),Bash(wc *), - Bash(diff *),Bash(sed *),Bash(awk *),Bash(sort *),Bash(uniq *), - " + --disallowedTools "WebFetch,WebSearch" --json-schema '${{ env.REVIEW_SCHEMA }}' prompt: | REPO: ${{ github.repository }} @@ -252,6 +258,7 @@ jobs: produce a structured JSON result containing your review. Do NOT attempt to post comments yourself — just return the JSON. You are in the upstream repo with the PR branch available as `pr-review`. Do not apply or merge the patch. + You have no network access — all required context has been pre-fetched locally. ## Phase 1: Read context From e9396ef1653889964d3522342cdb483833c447c3 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 18 Mar 2026 11:46:01 +0100 Subject: [PATCH 0313/1296] ci: Bump number of turns for claude and mention turns in prompt claude keeps failing by its subagents completing after it has already written the review for large prs. It seems to run out of turns, tries to get the subagents to post partial reviews but doesn't seem to stop them. Let's insist that it waits for background tasks to stop but let's also increase the max turns a bit so it doesn't run out as quickly. --- .github/workflows/claude-review.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index dbab77b2e7216..168e658e8a368 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -247,7 +247,7 @@ jobs: } claude_args: | --model us.anthropic.claude-opus-4-6-v1 - --max-turns 100 + --max-turns 200 --disallowedTools "WebFetch,WebSearch" --json-schema '${{ env.REVIEW_SCHEMA }}' prompt: | @@ -302,7 +302,11 @@ jobs: ## Phase 3: Collect, deduplicate, and summarize - After ALL commit review subagents complete: + Wait for all commit review subagents to complete. Monitor your + remaining turns — if you are running low (fewer than 20 turns + left), immediately stop all still-running subagents using + TaskStop and proceed with whatever results you have so far. A + partial review is better than no review. Then: 1. Collect all issues. Merge duplicates (same file, lines within 3 of each other, same problem). 2. Drop low-confidence findings. 3. Check the existing inline review comments fetched in Phase 1. Do NOT include a @@ -365,10 +369,12 @@ jobs: ## CRITICAL: Return structured JSON output - Before returning structured output, cancel ALL running background tasks - using the TaskStop tool. A background task completing after you return - structured output will trigger a new conversation turn that overwrites your - result and causes the workflow to fail. + Before returning structured output, stop ALL running background tasks + using TaskStop and wait for each one to fully terminate. Do NOT + return structured output while any background task is still + running — a background task completing after you return will + trigger a new conversation turn that overwrites your result and + causes the workflow to fail. Your FINAL action must be to return a JSON object matching the following JSON schema — do NOT end with a text summary or narrative. The `--json-schema` From a4aae324784565a838983602ca527df89dfc9316 Mon Sep 17 00:00:00 2001 From: Nandakumar Raghavan Date: Mon, 16 Mar 2026 10:42:08 +0000 Subject: [PATCH 0314/1296] ansi-color: fix SYSTEMD_COLORS=true regression when output is piped The SYSTEMD_COLORS=true/1/yes no longer forced colors when stdout was not a TTY (e.g. piped), because the COLOR_TRUE bypass of the terminal_is_dumb() check was accidentally dropped. Restore the old behavior by guarding the TTY check with `m != COLOR_TRUE`, so an explicit boolean "true" value continues to unconditionally force color output regardless of whether stdout is a TTY or whether $NO_COLOR is set. --- src/basic/ansi-color.c | 24 ++++++++++++++---------- src/test/test-terminal-util.c | 12 ++++++++---- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/basic/ansi-color.c b/src/basic/ansi-color.c index e5c78c93458f6..36cdca727c93d 100644 --- a/src/basic/ansi-color.c +++ b/src/basic/ansi-color.c @@ -58,16 +58,20 @@ static ColorMode get_color_mode_impl(void) { if (m >= 0 && m < _COLOR_MODE_FIXED_MAX) return m; - /* Next, check for the presence of $NO_COLOR; value is ignored. */ - if (m != COLOR_TRUE && getenv("NO_COLOR")) - return COLOR_OFF; - - /* If the above didn't work, we turn colors off unless we are on a TTY. And if we are on a TTY we - * turn it off if $TERM is set to "dumb". There's one special tweak though: if we are PID 1 then we - * do not check whether we are connected to a TTY, because we don't keep /dev/console open - * continuously due to fear of SAK, and hence things are a bit weird. */ - if (getpid_cached() == 1 ? getenv_terminal_is_dumb() : terminal_is_dumb()) - return COLOR_OFF; + /* If SYSTEMD_COLORS=true was set explicitly, skip the environment checks below — the user + * explicitly requested colors, so honor it even when stdout is piped or $NO_COLOR is set. */ + if (m != COLOR_TRUE) { + /* Check for the presence of $NO_COLOR; value is ignored. */ + if (getenv("NO_COLOR")) + return COLOR_OFF; + + /* Turn colors off unless we are on a TTY. And if we are on a TTY we turn it off if $TERM + * is set to "dumb". There's one special tweak though: if we are PID 1 then we do not check + * whether we are connected to a TTY, because we don't keep /dev/console open continuously + * due to fear of SAK, and hence things are a bit weird. */ + if (getpid_cached() == 1 ? getenv_terminal_is_dumb() : terminal_is_dumb()) + return COLOR_OFF; + } /* We failed to figure out any reason to *disable* colors. Let's see how many colors we shall use. */ if (m == COLOR_AUTO_16) diff --git a/src/test/test-terminal-util.c b/src/test/test-terminal-util.c index 4e2b751ad2627..efaed72b10b9b 100644 --- a/src/test/test-terminal-util.c +++ b/src/test/test-terminal-util.c @@ -338,13 +338,17 @@ TEST(get_color_mode) { test_get_color_mode_with_env("SYSTEMD_COLORS", "auto-256", terminal_is_dumb() ? COLOR_OFF : COLOR_256); test_get_color_mode_with_env("SYSTEMD_COLORS", "auto-24bit", terminal_is_dumb() ? COLOR_OFF : COLOR_24BIT); ASSERT_OK_ERRNO(setenv("COLORTERM", "truecolor", true)); - test_get_color_mode_with_env("SYSTEMD_COLORS", "1", terminal_is_dumb() ? COLOR_OFF : COLOR_24BIT); - test_get_color_mode_with_env("SYSTEMD_COLORS", "yes", terminal_is_dumb() ? COLOR_OFF : COLOR_24BIT); + /* SYSTEMD_COLORS=1/yes/true all map to COLOR_TRUE and must force colors on + * even when stdout is not a TTY (piped). With COLORTERM=truecolor, we get 24bit. */ + test_get_color_mode_with_env("SYSTEMD_COLORS", "1", COLOR_24BIT); + test_get_color_mode_with_env("SYSTEMD_COLORS", "yes", COLOR_24BIT); ASSERT_OK_ERRNO(unsetenv("COLORTERM")); - test_get_color_mode_with_env("SYSTEMD_COLORS", "true", terminal_is_dumb() ? COLOR_OFF : COLOR_256); + /* Without COLORTERM, COLOR_TRUE still bypasses the TTY check but autodetects depth. */ + test_get_color_mode_with_env("SYSTEMD_COLORS", "true", COLOR_256); ASSERT_OK_ERRNO(setenv("NO_COLOR", "1", true)); - test_get_color_mode_with_env("SYSTEMD_COLORS", "true", terminal_is_dumb() ? COLOR_OFF : COLOR_256); + /* COLOR_TRUE also bypasses NO_COLOR. */ + test_get_color_mode_with_env("SYSTEMD_COLORS", "true", COLOR_256); test_get_color_mode_with_env("SYSTEMD_COLORS", "auto-16", COLOR_OFF); test_get_color_mode_with_env("SYSTEMD_COLORS", "auto-256", COLOR_OFF); test_get_color_mode_with_env("SYSTEMD_COLORS", "auto-24bit", COLOR_OFF); From 02ab8dfc4f804375266dd8313e6e507a7e36a26b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 18 Mar 2026 12:24:34 +0100 Subject: [PATCH 0315/1296] ci: Enable unpriv user namespaces for claude-review Required for bubblewrap to work properly. --- .github/workflows/claude-review.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 168e658e8a368..926f28dd356af 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -163,7 +163,9 @@ jobs: name: pr-context.json - name: Install sandbox dependencies - run: sudo apt-get update && sudo apt-get install -y bubblewrap socat + run: | + sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 + sudo apt-get update && sudo apt-get install -y bubblewrap socat - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 From 2e676fd636f4f7c1b999fd9a369d6e71b971c30e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 18 Mar 2026 12:30:41 +0100 Subject: [PATCH 0316/1296] ci: Allow all commands in claude-review workflow claude is asking for permissions in the logs, let's grant it access to execute all commands to avoid the permission denials. --- .github/workflows/claude-review.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 926f28dd356af..1933095b0c8ed 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -241,6 +241,9 @@ jobs: # filesystem writes to the working directory. settings: | { + "permissions": { + "allow": ["*"] + }, "sandbox": { "enabled": true, "autoAllowBashIfSandboxed": true, From a06992dd1660336782a2b0ddd95ac7f733225875 Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Fri, 13 Mar 2026 13:02:51 +0100 Subject: [PATCH 0317/1296] measure: make tpm_log_tagged_event() measure CC as well tpm_log_tagged_event() only measures the event to the TPM while tpm_log_ipl_event() measures the event both to the TPM and CC. Fix the inconsistency. Note, this is a potentially breaking change for TDX guests as systemd will now measure more stuff to the MRTD/RTMRs, reference values for attestation may need to be adjusted. Found by Claude Code Review. --- NEWS | 13 +++++++++++++ src/boot/measure.c | 43 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index 8595a285b86f1..85cc7ac2d1d35 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,18 @@ systemd System and Service Manager +CHANGES WITH 261 in spe: + + Feature Removals and Incompatible Changes: + + * It was discovered that systemd-stub does not measure all the events + it measures to the TPM to the hardware CC registers (e.g. Intel TDX + RTMRs) using EFI_CC_MEASUREMENT_PROTOCOL. In particular, devicetree, + initrd, ucode addons and the UKI profile were only measured to the + TPM. The missing measurements got added, however, the expected + register values are now changed. This may need to be reflected in the + attestation environments which use hardware CC registers and not the + TPM quote. + CHANGES WITH 260: Feature Removals and Incompatible Changes: diff --git a/src/boot/measure.c b/src/boot/measure.c index cf3d4254b86cb..085ebde472567 100644 --- a/src/boot/measure.c +++ b/src/boot/measure.c @@ -232,6 +232,33 @@ static EFI_STATUS tcg2_log_ipl_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buf return EFI_SUCCESS; } +static EFI_STATUS tcg2_log_tagged_event( + uint32_t pcrindex, + EFI_PHYSICAL_ADDRESS buffer, + size_t buffer_size, + uint32_t event_id, + const char16_t *description, + bool *ret_measured) { + + EFI_TCG2_PROTOCOL *tpm2; + EFI_STATUS err = EFI_SUCCESS; + + assert(ret_measured); + + tpm2 = tcg2_interface_check(/* ret_version= */ NULL); + if (!tpm2) { + *ret_measured = false; + return EFI_SUCCESS; + } + + err = tpm2_measure_to_pcr_and_tagged_event_log(tpm2, pcrindex, buffer, buffer_size, event_id, description); + if (err != EFI_SUCCESS) + return err; + + *ret_measured = true; + return EFI_SUCCESS; +} + static EFI_STATUS cc_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { EFI_CC_MEASUREMENT_PROTOCOL *cc; EFI_STATUS err = EFI_SUCCESS; @@ -291,8 +318,8 @@ EFI_STATUS tpm_log_tagged_event( const char16_t *description, bool *ret_measured) { - EFI_TCG2_PROTOCOL *tpm2; EFI_STATUS err; + bool tpm_ret_measured, cc_ret_measured; assert(description || pcrindex == UINT32_MAX); assert(event_id > 0); @@ -300,20 +327,26 @@ EFI_STATUS tpm_log_tagged_event( /* If EFI_SUCCESS is returned, will initialize ret_measured to true if we actually measured * something, or false if measurement was turned off. */ - tpm2 = tcg2_interface_check(/* ret_version= */ NULL); - if (!tpm2 || pcrindex == UINT32_MAX) { /* PCR disabled? */ + if (pcrindex == UINT32_MAX) { /* PCR disabled? */ if (ret_measured) *ret_measured = false; return EFI_SUCCESS; } - err = tpm2_measure_to_pcr_and_tagged_event_log(tpm2, pcrindex, buffer, buffer_size, event_id, description); + /* Measure into both CC and TPM if both are available to avoid a problem like CVE-2021-42299. + * The CC protocol has no tagged-event concept, hence use the IPL event log there. */ + err = cc_log_event(pcrindex, buffer, buffer_size, description, &cc_ret_measured); + if (err != EFI_SUCCESS) + return err; + + err = tcg2_log_tagged_event(pcrindex, buffer, buffer_size, event_id, description, &tpm_ret_measured); if (err != EFI_SUCCESS) return err; if (ret_measured) - *ret_measured = true; + *ret_measured = tpm_ret_measured || cc_ret_measured; + return EFI_SUCCESS; } From 1a7678e881334505bfbc76a2099fb52685c7cef2 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 18 Mar 2026 12:55:45 +0100 Subject: [PATCH 0318/1296] ci: Stop using subagents in claude-review workflow As it seems impossible to prevent claude from receiving notifications about subagents finishing after it has produced structured output, which breaks the structured output as it has to be the final reply, let's stop using subagents and background tasks completely to avoid the issue. --- .github/workflows/claude-review.yml | 39 ++++++++++++----------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 1933095b0c8ed..ba680b06201a0 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -250,10 +250,14 @@ jobs: "allowUnsandboxedCommands": false } } + # Agent and TaskCreate are disabled because background task + # notifications race with --json-schema structured output, + # causing a redundant turn that overwrites the result. + # See https://github.com/anthropics/claude-code/issues/33872 claude_args: | --model us.anthropic.claude-opus-4-6-v1 --max-turns 200 - --disallowedTools "WebFetch,WebSearch" + --disallowedTools "WebFetch,WebSearch,Agent,TaskCreate" --json-schema '${{ env.REVIEW_SCHEMA }}' prompt: | REPO: ${{ github.repository }} @@ -263,7 +267,8 @@ jobs: produce a structured JSON result containing your review. Do NOT attempt to post comments yourself — just return the JSON. You are in the upstream repo with the PR branch available as `pr-review`. Do not apply or merge the patch. - You have no network access — all required context has been pre-fetched locally. + You have no network access — all required context has been pre-fetched + locally. You cannot spawn subagents or background tasks. ## Phase 1: Read context @@ -281,16 +286,15 @@ jobs: `git log --reverse --format=%H HEAD..pr-review` to list the PR commits, and `git show ` or `git diff ~1..` to access commit diffs. - ## Phase 2: Per-commit review with subagents + ## Phase 2: Per-commit review - Launch a subagent for each commit in the PR, all in parallel. Each subagent - receives only the commit SHA to review. It reads `pr-context.json` for PR - context, uses `git show ` or `git diff ~1..` to fetch the - diff, and reads the codebase to verify its findings. + Review each commit in the PR sequentially. For each commit, use + `git show ` or `git diff ~1..` to fetch the diff, + and read relevant source files in the codebase to verify findings. - Each subagent reviews code quality, style, potential bugs, and security - implications. It must return a JSON array: - `[{"file": "path", "line": (optional), "severity": "must-fix|suggestion|nit", "body": "...", "commit": ""}]` + For each commit, review code quality, style, potential bugs, and + security implications. Collect issues in the format: + `{"file": "path", "line": (optional), "severity": "must-fix|suggestion|nit", "body": "...", "commit": ""}` The `commit` field MUST be the SHA of the commit being reviewed. Only comment on changes in that commit — not preceding commits. @@ -299,7 +303,7 @@ jobs: inside a diff hunk**. GitHub rejects lines outside the diff context. If you cannot determine a valid diff line, omit `line`. - Each subagent MUST verify findings before returning them: + You MUST verify findings before including them: - For style/convention claims, check at least 3 existing examples in the codebase to confirm the pattern actually exists before flagging a violation. - For "use X instead of Y" suggestions, confirm X actually exists and works. @@ -307,11 +311,7 @@ jobs: ## Phase 3: Collect, deduplicate, and summarize - Wait for all commit review subagents to complete. Monitor your - remaining turns — if you are running low (fewer than 20 turns - left), immediately stop all still-running subagents using - TaskStop and proceed with whatever results you have so far. A - partial review is better than no review. Then: + After reviewing all commits: 1. Collect all issues. Merge duplicates (same file, lines within 3 of each other, same problem). 2. Drop low-confidence findings. 3. Check the existing inline review comments fetched in Phase 1. Do NOT include a @@ -374,13 +374,6 @@ jobs: ## CRITICAL: Return structured JSON output - Before returning structured output, stop ALL running background tasks - using TaskStop and wait for each one to fully terminate. Do NOT - return structured output while any background task is still - running — a background task completing after you return will - trigger a new conversation turn that overwrites your result and - causes the workflow to fail. - Your FINAL action must be to return a JSON object matching the following JSON schema — do NOT end with a text summary or narrative. The `--json-schema` flag is set, so your last response must be the structured JSON result, not a From 9374d7fa06974f227b9278ab63e89ca392d11690 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 18 Mar 2026 13:34:34 +0100 Subject: [PATCH 0319/1296] ci: Allow claude-review access to /tmp and /var/tmp --- .github/workflows/claude-review.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index ba680b06201a0..6ca2193870e97 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -247,7 +247,10 @@ jobs: "sandbox": { "enabled": true, "autoAllowBashIfSandboxed": true, - "allowUnsandboxedCommands": false + "allowUnsandboxedCommands": false, + "filesystem": { + "allowWrite": ["//tmp", "//var/tmp"] + } } } # Agent and TaskCreate are disabled because background task From 20a8f5832a7f0138b70741ac3433220215d59682 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 18 Mar 2026 13:38:17 +0100 Subject: [PATCH 0320/1296] ci: Prettify JSON in pr context file so claude can parse it Currently it's a single line which makes it hard for claude to read what's in it. --- .github/workflows/claude-review.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 6ca2193870e97..4e750b11fcde3 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -162,6 +162,9 @@ jobs: with: name: pr-context.json + - name: Prettify PR context + run: python3 -m json.tool pr-context.json > pr-context-pretty.json && mv pr-context-pretty.json pr-context.json + - name: Install sandbox dependencies run: | sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 From fb2cf9f557e6a23dbf48b39e776e84cdf673a5c1 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 18 Mar 2026 13:40:13 +0100 Subject: [PATCH 0321/1296] ci: Don't read claude settings from the repo Shouldn't be possible, but extra hardening never hurts. --- .github/workflows/claude-review.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 4e750b11fcde3..91f98f695e2a6 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -264,6 +264,7 @@ jobs: --model us.anthropic.claude-opus-4-6-v1 --max-turns 200 --disallowedTools "WebFetch,WebSearch,Agent,TaskCreate" + --setting-sources user --json-schema '${{ env.REVIEW_SCHEMA }}' prompt: | REPO: ${{ github.repository }} From 770958e24a2ee59593aa6833a4a825db0a6abbbc Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Fri, 6 Mar 2026 07:27:10 -0500 Subject: [PATCH 0322/1296] time-util: extract parse_calendar_date() from sysupdate Move the YYYY-MM-DD date parsing and validation logic from sysupdate-resource.c into a shared parse_calendar_date() function in time-util, so it can be reused by other subsystems. --- src/basic/time-util.c | 29 +++++++++++++++++++++++++++++ src/basic/time-util.h | 1 + src/sysupdate/sysupdate-resource.c | 20 +++----------------- src/test/test-time-util.c | 26 ++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 17 deletions(-) diff --git a/src/basic/time-util.c b/src/basic/time-util.c index 5dd00af952d29..4ba5bfafd7161 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -1892,3 +1892,32 @@ TimestampStyle timestamp_style_from_string(const char *s) { return TIMESTAMP_US_UTC; return t; } + +int parse_calendar_date(const char *s, usec_t *ret) { + struct tm parsed_tm = {}, copy_tm; + usec_t usec; + const char *k; + int r; + + assert(s); + + k = strptime(s, "%Y-%m-%d", &parsed_tm); + if (!k || *k) + return -EINVAL; + + copy_tm = parsed_tm; + r = mktime_or_timegm_usec(©_tm, /* utc= */ true, &usec); + if (r < 0) + return r; + + /* Refuse non-normalized dates, e.g. Feb 30 */ + if (copy_tm.tm_mday != parsed_tm.tm_mday || + copy_tm.tm_mon != parsed_tm.tm_mon || + copy_tm.tm_year != parsed_tm.tm_year) + return -EINVAL; + + if (ret) + *ret = usec; + + return 0; +} diff --git a/src/basic/time-util.h b/src/basic/time-util.h index bde0b02d037c4..c39718890131a 100644 --- a/src/basic/time-util.h +++ b/src/basic/time-util.h @@ -180,6 +180,7 @@ const char* etc_localtime(void); int mktime_or_timegm_usec(struct tm *tm, bool utc, usec_t *ret); int localtime_or_gmtime_usec(usec_t t, bool utc, struct tm *ret); +int parse_calendar_date(const char *s, usec_t *ret); uint32_t usec_to_jiffies(usec_t usec); usec_t jiffies_to_usec(uint32_t jiffies); diff --git a/src/sysupdate/sysupdate-resource.c b/src/sysupdate/sysupdate-resource.c index 1cc48201efabe..ba1842b2d6c8e 100644 --- a/src/sysupdate/sysupdate-resource.c +++ b/src/sysupdate/sysupdate-resource.c @@ -433,24 +433,10 @@ static int process_magic_file( if (iovec_memcmp(&IOVEC_MAKE(expected_hash, sizeof(expected_hash)), hash) != 0) log_warning("Hash of best before marker file '%s' has unexpected value, proceeding anyway.", fn); - struct tm parsed_tm = {}; - const char *n = strptime(e, "%Y-%m-%d", &parsed_tm); - if (!n || *n != 0) { - /* Doesn't parse? Then it's not a best-before date */ - log_warning("Found best before marker with an invalid date, ignoring: %s", fn); - return 0; - } - - struct tm copy_tm = parsed_tm; usec_t best_before; - r = mktime_or_timegm_usec(©_tm, /* utc= */ true, &best_before); - if (r < 0) - return log_error_errno(r, "Failed to convert best before time: %m"); - if (copy_tm.tm_mday != parsed_tm.tm_mday || - copy_tm.tm_mon != parsed_tm.tm_mon || - copy_tm.tm_year != parsed_tm.tm_year) { - /* date was not normalized? (e.g. "30th of feb") */ - log_warning("Found best before marker with a non-normalized data, ignoring: %s", fn); + r = parse_calendar_date(e, &best_before); + if (r < 0) { + log_warning_errno(r, "Found best before marker with an invalid date, ignoring: %s", fn); return 0; } diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c index d5d4992f827b4..172056a9bf611 100644 --- a/src/test/test-time-util.c +++ b/src/test/test-time-util.c @@ -1281,4 +1281,30 @@ static int intro(void) { return EXIT_SUCCESS; } +TEST(parse_calendar_date) { + usec_t usec; + + /* Valid dates */ + ASSERT_OK(parse_calendar_date("2000-01-01", &usec)); + ASSERT_OK(parse_calendar_date("1970-01-01", &usec)); + ASSERT_EQ(usec, 0u); /* epoch */ + ASSERT_OK(parse_calendar_date("2000-02-29", &usec)); /* leap year */ + + /* NULL ret is allowed (validation only) */ + ASSERT_OK(parse_calendar_date("2000-06-15", NULL)); + + /* Non-normalized dates */ + ASSERT_ERROR(parse_calendar_date("2023-02-29", &usec), EINVAL); /* not a leap year */ + ASSERT_ERROR(parse_calendar_date("2023-04-31", &usec), EINVAL); /* April has 30 days */ + ASSERT_ERROR(parse_calendar_date("2023-13-01", &usec), EINVAL); /* month 13 */ + ASSERT_ERROR(parse_calendar_date("2023-00-01", &usec), EINVAL); /* month 0 */ + + /* Malformed input */ + ASSERT_ERROR(parse_calendar_date("", &usec), EINVAL); + ASSERT_ERROR(parse_calendar_date("not-a-date", &usec), EINVAL); + ASSERT_ERROR(parse_calendar_date("2023-06-15T00:00:00", &usec), EINVAL); /* trailing time */ + ASSERT_ERROR(parse_calendar_date("2023/06/15", &usec), EINVAL); /* wrong separator */ + ASSERT_ERROR(parse_calendar_date("06-15-2023", &usec), EINVAL); /* wrong order */ +} + DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); From 548816e05829df7547319e7f9eb7dbab039246a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 10 Mar 2026 22:53:03 +0100 Subject: [PATCH 0323/1296] tmpfiles: add --inline This option is exactly like the one in sysusers. (In fact the implementation is copied too.) It is occasionally useful to be able to specify and execute some tmpfiles config not through config files but directly on the command line. This also makes it very easy to test config with: SYSTEMD_LOG_LEVEL=debug systemd-tmpfiles --dry-run --inline ... --- man/systemd-tmpfiles.xml | 8 ++++++++ src/tmpfiles/tmpfiles.c | 34 +++++++++++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/man/systemd-tmpfiles.xml b/man/systemd-tmpfiles.xml index 815dcd88d62ff..c48c0653b0dd2 100644 --- a/man/systemd-tmpfiles.xml +++ b/man/systemd-tmpfiles.xml @@ -210,6 +210,14 @@ + + + Treat each positional argument as a separate configuration + line instead of a file name. + + + + Only apply rules with paths that start with diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 0d62b847b65cb..7885a668fd483 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -199,6 +199,7 @@ typedef enum { static CatFlags arg_cat_flags = CAT_CONFIG_OFF; static bool arg_dry_run = false; +static bool arg_inline = false; static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; static OperationMask arg_operation = 0; static bool arg_boot = false; @@ -3589,6 +3590,7 @@ static int parse_line( assert(fname); assert(line >= 1); assert(buffer); + assert(invalid_config); const Specifier specifier_table[] = { { 'h', specifier_user_home, NULL }, @@ -4157,6 +4159,7 @@ static int help(void) { " --image-policy=POLICY Specify disk image dissection policy\n" " --replace=PATH Treat arguments as replacement for PATH\n" " --dry-run Just print what would be done\n" + " --inline Treat arguments as configuration lines\n" " --no-pager Do not pipe output into a pager\n" "\nSee the %5$s for details.\n", program_invocation_short_name, @@ -4187,6 +4190,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_IMAGE_POLICY, ARG_REPLACE, ARG_DRY_RUN, + ARG_INLINE, ARG_NO_PAGER, }; @@ -4209,6 +4213,7 @@ static int parse_argv(int argc, char *argv[]) { { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, { "replace", required_argument, NULL, ARG_REPLACE }, { "dry-run", no_argument, NULL, ARG_DRY_RUN }, + { "inline", no_argument, NULL, ARG_INLINE }, { "no-pager", no_argument, NULL, ARG_NO_PAGER }, {} }; @@ -4316,6 +4321,10 @@ static int parse_argv(int argc, char *argv[]) { arg_dry_run = true; break; + case ARG_INLINE: + arg_inline = true; + break; + case ARG_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; @@ -4339,6 +4348,10 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --replace= is not supported with --cat-config/--tldr."); + if (arg_inline && arg_cat_flags != CAT_CONFIG_OFF) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Option --inline is not supported with --cat-config/--tldr."); + if (arg_replace && optind >= argc) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "When --replace= is given, some configuration items must be specified."); @@ -4415,14 +4428,29 @@ static int parse_arguments( char **config_dirs, char **args, bool *invalid_config) { + + unsigned pos = 1; int r; assert(c); STRV_FOREACH(arg, args) { - r = read_config_file(c, config_dirs, *arg, false, invalid_config); - if (r < 0) - return r; + if (arg_inline) { + bool invalid_arg = false; + + /* Use (argument):n, where n==1 for the first positional arg */ + r = parse_line("(argument)", pos, *arg, &invalid_arg, c); + if (invalid_arg) + *invalid_config = true; + else if (r < 0) + return r; + } else { + r = read_config_file(c, config_dirs, *arg, /* ignore_enoent= */ false, invalid_config); + if (r < 0) + return r; + } + + pos++; } return 0; From ee97cef1ead7fb97a33bf76bc963c0c4b43260db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 10 Mar 2026 22:59:37 +0100 Subject: [PATCH 0324/1296] units: stop using rm in system-update-cleanup.service If we are running on a system without coreutils, rm might not be available. Let's use our own binary that should be available. --- units/system-update-cleanup.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/units/system-update-cleanup.service b/units/system-update-cleanup.service index a54e74567e1fe..e9ff88c73c87c 100644 --- a/units/system-update-cleanup.service +++ b/units/system-update-cleanup.service @@ -34,4 +34,4 @@ ConditionPathIsSymbolicLink=|/etc/system-update [Service] Type=oneshot -ExecStart=rm -fv /system-update /etc/system-update +ExecStart=systemd-tmpfiles --inline --remove 'r /system-update' 'r /etc/system-update' From 151f803c3e802c6230a4b99936a7dd0a397d1b8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 10 Mar 2026 23:20:15 +0100 Subject: [PATCH 0325/1296] TEST-22-TMPFILES: add simple test for tmpfiles --inline Also add tests for handling of invalid_config. --- test/units/TEST-22-TMPFILES.01.sh | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/units/TEST-22-TMPFILES.01.sh b/test/units/TEST-22-TMPFILES.01.sh index 4159796dae3ce..4062c838dc6d0 100755 --- a/test/units/TEST-22-TMPFILES.01.sh +++ b/test/units/TEST-22-TMPFILES.01.sh @@ -9,5 +9,24 @@ set -o pipefail rm -fr /tmp/test echo "e /tmp/test - root root 1d" | systemd-tmpfiles --create - +test ! -e /tmp/test + +touch /tmp/test +echo "r /tmp/test - - - -" | systemd-tmpfiles --remove - +test ! -e /tmp/test +touch /tmp/test +systemd-tmpfiles --remove --inline 'p /tmp/fifo' 'r /tmp/test' +test ! -e /tmp/fifo test ! -e /tmp/test + +# Test invalid config +systemd-tmpfiles --inline --remove 'garbage' || ret=$? +test "$ret" -eq 65 # EX_DATAERR + +echo 'garbage' >/tmp/config.conf +systemd-tmpfiles --remove /tmp/config.conf || ret=$? +test "$ret" -eq 65 # EX_DATAERR + +systemd-tmpfiles --remove /tmp/config-missing.conf || ret=$? +test "$ret" -eq 1 From d105f4c5a59b96ee34b03277fe07ba41308a3eea Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 18 Mar 2026 14:32:21 +0100 Subject: [PATCH 0326/1296] ci: Add back subagents and stop using --json-schema in claude-review Let's stop using --json-schema and instead have claude write a JSON file in the repo root which we pass around as an artifact similar to how we pass around the input. This works around the bug where claude receives task notifications after producing structured output which breaks the structured output. --- .github/workflows/claude-review.yml | 158 ++++++++++++++-------------- 1 file changed, 77 insertions(+), 81 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 91f98f695e2a6..1aa89bb076b5e 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -143,9 +143,6 @@ jobs: contents: read id-token: write # Authenticate with AWS via OIDC - outputs: - structured_output: ${{ steps.claude.outputs.structured_output }} - steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: @@ -180,54 +177,6 @@ jobs: - name: Run Claude Code id: claude uses: anthropics/claude-code-action@26ec041249acb0a944c0a47b6c0c13f05dbc5b44 - env: - REVIEW_SCHEMA: >- - { - "type": "object", - "required": ["comments", "summary"], - "properties": { - "summary": { - "type": "string", - "description": "A markdown summary of the review to post as a top-level tracking comment" - }, - "comments": { - "type": "array", - "items": { - "type": "object", - "required": ["file", "severity", "body", "commit"], - "properties": { - "file": { - "type": "string", - "description": "Path to the file relative to the repo root" - }, - "line": { - "type": "integer", - "description": "Line number in the diff (new file side) to attach the comment to" - }, - "severity": { - "type": "string", - "enum": ["must-fix", "suggestion", "nit"] - }, - "body": { - "type": "string", - "description": "The review comment body in markdown" - }, - "commit": { - "type": "string", - "description": "The SHA of the PR commit that introduced the code being commented on" - } - } - } - }, - "resolve": { - "type": "array", - "items": { - "type": "integer" - }, - "description": "REST API IDs of existing review comments whose threads should be resolved because the issue was addressed or the author left a disagreeing reply" - } - } - } with: use_bedrock: "true" # Required by claude-code-action even though Claude itself doesn't @@ -256,26 +205,22 @@ jobs: } } } - # Agent and TaskCreate are disabled because background task - # notifications race with --json-schema structured output, - # causing a redundant turn that overwrites the result. - # See https://github.com/anthropics/claude-code/issues/33872 claude_args: | --model us.anthropic.claude-opus-4-6-v1 --max-turns 200 - --disallowedTools "WebFetch,WebSearch,Agent,TaskCreate" + --disallowedTools "WebFetch,WebSearch" --setting-sources user - --json-schema '${{ env.REVIEW_SCHEMA }}' prompt: | REPO: ${{ github.repository }} PR NUMBER: ${{ needs.setup.outputs.pr_number }} You are a code reviewer for the ${{ github.repository }} project. Review this pull request and - produce a structured JSON result containing your review. Do NOT attempt - to post comments yourself — just return the JSON. You are in the upstream repo - with the PR branch available as `pr-review`. Do not apply or merge the patch. - You have no network access — all required context has been pre-fetched - locally. You cannot spawn subagents or background tasks. + produce a JSON result containing your review and write it to + `review-result.json` in the repo root. Do NOT attempt to post + comments yourself. You are in the upstream repo with the PR branch + available as `pr-review`. Do not apply or merge the patch. + You have no network access — all required context has been + pre-fetched locally. ## Phase 1: Read context @@ -293,15 +238,31 @@ jobs: `git log --reverse --format=%H HEAD..pr-review` to list the PR commits, and `git show ` or `git diff ~1..` to access commit diffs. - ## Phase 2: Per-commit review + ## Phase 2: Review commits - Review each commit in the PR sequentially. For each commit, use - `git show ` or `git diff ~1..` to fetch the diff, - and read relevant source files in the codebase to verify findings. + Review every commit in the PR. Use subagents to parallelize the + work — decide how many subagents to spawn and how to divide commits + between them and the main conversation based on the number and size + of commits. Very large commits can be assigned to multiple subagents + for extra thoroughness. Always review some commits yourself so you + have useful work to do while subagents run in the background. For + single-commit PRs with small diffs, just review directly without + subagents. For large single-commit PRs, have multiple subagents + review it independently to maximize coverage. - For each commit, review code quality, style, potential bugs, and - security implications. Collect issues in the format: - `{"file": "path", "line": (optional), "severity": "must-fix|suggestion|nit", "body": "...", "commit": ""}` + IMPORTANT: Always spawn subagents with `isolation: "worktree"` so + each gets its own git worktree. This prevents concurrent git + operations from interfering with each other. Because worktrees + do not include untracked files, first `git add pr-context.json` + so it is available in worktrees. + + Each reviewer (you or a subagent) uses `git show ` or + `git diff ~1..` to fetch diffs, reads `pr-context.json` + for PR context, and reads the codebase to verify findings. + + Each reviewer reviews code quality, style, potential bugs, and security + implications. It must return a JSON array of issues: + `[{"file": "path", "line": (optional), "severity": "must-fix|suggestion|nit", "body": "...", "commit": ""}]` The `commit` field MUST be the SHA of the commit being reviewed. Only comment on changes in that commit — not preceding commits. @@ -310,7 +271,7 @@ jobs: inside a diff hunk**. GitHub rejects lines outside the diff context. If you cannot determine a valid diff line, omit `line`. - You MUST verify findings before including them: + Each reviewer MUST verify findings before returning them: - For style/convention claims, check at least 3 existing examples in the codebase to confirm the pattern actually exists before flagging a violation. - For "use X instead of Y" suggestions, confirm X actually exists and works. @@ -318,7 +279,7 @@ jobs: ## Phase 3: Collect, deduplicate, and summarize - After reviewing all commits: + After all reviews (yours and any subagents') are done: 1. Collect all issues. Merge duplicates (same file, lines within 3 of each other, same problem). 2. Drop low-confidence findings. 3. Check the existing inline review comments fetched in Phase 1. Do NOT include a @@ -379,19 +340,42 @@ jobs: not available, git commands that failed, etc.), append a `### Errors` section to the summary listing each failed action and the error message. - ## CRITICAL: Return structured JSON output + ## CRITICAL: Write review result to file - Your FINAL action must be to return a JSON object matching the following - JSON schema — do NOT end with a text summary or narrative. The `--json-schema` - flag is set, so your last response must be the structured JSON result, not a - text message. + Your FINAL action must be to write `review-result.json` in the repo + root. The file must contain a JSON object with the following schema: ```json - ${{ env.REVIEW_SCHEMA }} + { + "summary": "...", + "comments": [ + { + "file": "path/to/file", + "line": 42, + "severity": "must-fix|suggestion|nit", + "body": "review comment in markdown", + "commit": "abc123" + } + ], + "resolve": [12345] + } ``` + - `summary` (required): markdown summary for the tracking comment + - `comments` (required): array of review comments; `line` is optional + - `resolve` (optional): REST API IDs of review comment threads to resolve + Do NOT attempt to post comments or use any MCP tools to modify the PR. + - name: Upload review result + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f + with: + path: review-result.json + if-no-files-found: ignore + archive: false + retention-days: 7 + post: runs-on: ubuntu-latest needs: [setup, review] @@ -401,15 +385,22 @@ jobs: pull-requests: write steps: + - name: Download review result + if: needs.review.result == 'success' + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + with: + name: review-result.json + - name: Post review comments uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd env: - STRUCTURED_OUTPUT: ${{ needs.review.outputs.structured_output }} REVIEW_RESULT: ${{ needs.review.result }} PR_NUMBER: ${{ needs.setup.outputs.pr_number }} COMMENT_ID: ${{ needs.setup.outputs.comment_id }} with: script: | + const fs = require("fs"); const owner = context.repo.owner; const repo = context.repo.repo; const prNumber = parseInt(process.env.PR_NUMBER, 10); @@ -431,9 +422,14 @@ jobs: return; } - /* Parse Claude's structured output. */ - const raw = process.env.STRUCTURED_OUTPUT; - console.log("Structured output from Claude:"); + /* Parse Claude's review result from the downloaded artifact. */ + let raw = ""; + try { + raw = fs.readFileSync("review-result.json", "utf8"); + } catch (e) { + console.log(`Failed to read review-result.json: ${e.message}`); + } + console.log("Review result from Claude:"); console.log(raw || "(empty)"); let comments = []; From 136d839fec3d183be4cd0edd539d42089fe3800e Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 18 Mar 2026 18:18:39 +0000 Subject: [PATCH 0327/1296] man: switch ostree link to manpage Webpage has been defaced and taken over --- man/systemd-nspawn.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index bf299b0a4afd3..d241ca5c52c5e 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -525,9 +525,9 @@ in the container's file system namespace. This is for containers which have several bootable directories in them; for example, several - OSTree deployments. It emulates the - behavior of the boot loader and the initrd which normally select which directory to mount as the root - and start the container's PID 1 in. + ostree1 + deployments. It emulates the behavior of the boot loader and the initrd which normally select which + directory to mount as the root and start the container's PID 1 in. From a3da9c5cf360aa3c0882f047170d89f801abf9a7 Mon Sep 17 00:00:00 2001 From: Massii Aqvayli Date: Wed, 18 Mar 2026 18:58:45 +0000 Subject: [PATCH 0328/1296] po: Translated using Weblate (Kabyle) Currently translated at 7.5% (20 of 266 strings) Co-authored-by: Massii Aqvayli Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/kab/ Translation: systemd/main --- po/kab.po | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/po/kab.po b/po/kab.po index 17351e01aea13..fea95626b4a72 100644 --- a/po/kab.po +++ b/po/kab.po @@ -4,13 +4,13 @@ # Slimane Selyan Amiri , 2021. # ButterflyOfFire , 2024. # ButterflyOfFire , 2025. +# Massii Aqvayli , 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2025-08-05 01:30+0000\n" -"Last-Translator: ButterflyOfFire " -"\n" +"PO-Revision-Date: 2026-03-18 18:58+0000\n" +"Last-Translator: Massii Aqvayli \n" "Language-Team: Kabyle \n" "Language: kab\n" @@ -18,7 +18,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 5.12.2\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -27,27 +27,27 @@ msgstr "Azen tafyirt n uɛeddi ɣer unagraw" #: src/core/org.freedesktop.systemd1.policy.in:23 msgid "" "Authentication is required to send the entered passphrase back to the system." -msgstr "" +msgstr "Asesteb yettwasra i tuzzna n tefyirt n uɛeddi i teskecmeḍ ɣer unagraw." #: src/core/org.freedesktop.systemd1.policy.in:33 msgid "Manage system services or other units" -msgstr "" +msgstr "Sefrek imeẓla n unagraw neɣ iferdisen-nniḍen" #: src/core/org.freedesktop.systemd1.policy.in:34 msgid "Authentication is required to manage system services or other units." -msgstr "" +msgstr "Asesteb yettwasra i usefrek n imeẓla n unagraw neɣ iferdisen-nniḍen." #: src/core/org.freedesktop.systemd1.policy.in:43 msgid "Manage system service or unit files" -msgstr "" +msgstr "Sefrek ifuyla n umeẓlu neɣ aferdis unagraw" #: src/core/org.freedesktop.systemd1.policy.in:44 msgid "Authentication is required to manage system service or unit files." -msgstr "" +msgstr "Asesteb yettwasra i usefrek n umeẓlu unagraw neɣ ifuyla n uferdis." #: src/core/org.freedesktop.systemd1.policy.in:54 msgid "Set or unset system and service manager environment variables" -msgstr "" +msgstr "Sbadu neɣ kkes imuttiyen n twennaṭ seg umsefrak n unagraw akked imeẓla" #: src/core/org.freedesktop.systemd1.policy.in:55 msgid "" @@ -57,11 +57,11 @@ msgstr "" #: src/core/org.freedesktop.systemd1.policy.in:64 msgid "Reload the systemd state" -msgstr "" +msgstr "Ales asali n waddad n systemd" #: src/core/org.freedesktop.systemd1.policy.in:65 msgid "Authentication is required to reload the systemd state." -msgstr "" +msgstr "Asesteb yettwasra i wallus usali n waddad n unagraw." #: src/core/org.freedesktop.systemd1.policy.in:74 msgid "Dump the systemd state without rate limits" From 38e7ba033c6f6e49013ba9e773aa6ae62b30801b Mon Sep 17 00:00:00 2001 From: Jan Kuparinen Date: Wed, 18 Mar 2026 18:58:46 +0000 Subject: [PATCH 0329/1296] po: Translated using Weblate (Finnish) Currently translated at 100.0% (266 of 266 strings) Co-authored-by: Jan Kuparinen Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/fi/ Translation: systemd/main --- po/fi.po | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/po/fi.po b/po/fi.po index f63090183220d..abc689756959e 100644 --- a/po/fi.po +++ b/po/fi.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2026-03-04 19:58+0000\n" +"PO-Revision-Date: 2026-03-18 18:58+0000\n" "Last-Translator: Jan Kuparinen \n" "Language-Team: Finnish \n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.16.1\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1022,12 +1022,12 @@ msgid "DHCP server sends force renew message" msgstr "DHCP-palvelin lähettää pakota uusiminen-viestin" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy -#| msgid "Authentication is required to send force renew message." msgid "" "Authentication is required to send a force renew message from the DHCP " "server." -msgstr "Todennus vaaditaan pakota uusiminen-viestin lähettämiseksi." +msgstr "" +"Todennus vaaditaan pakotetun uudistamisviestin lähettämiseen DHCP-" +"palvelimelta." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" From 145fcbe711f533dcaef822595633b91c54207a8a Mon Sep 17 00:00:00 2001 From: A S Alam Date: Wed, 18 Mar 2026 18:58:46 +0000 Subject: [PATCH 0330/1296] po: Translated using Weblate (Punjabi) Currently translated at 34.9% (93 of 266 strings) Co-authored-by: A S Alam Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/pa/ Translation: systemd/main --- po/pa.po | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/po/pa.po b/po/pa.po index bd100302fea72..e5511702faf98 100644 --- a/po/pa.po +++ b/po/pa.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2026-03-07 01:58+0000\n" +"PO-Revision-Date: 2026-03-18 18:58+0000\n" "Last-Translator: A S Alam \n" "Language-Team: Punjabi \n" @@ -140,9 +140,9 @@ msgid "Manage Home Directory Signing Keys" msgstr "" #: src/home/org.freedesktop.home1.policy:94 -#, fuzzy msgid "Authentication is required to manage signing keys for home directories." -msgstr "ਸਿਸਟਮ ਸੇਵਾਵਾਂ ਜਾਂ ਹੋਰ ਇਕਾਈਆਂ ਦੇ ਇੰਤਜਾਮ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" +msgstr "" +"ਘਰ ਡਾਇਰੈਕਟਰੀਆਂ ਲਈ ਸਾਈਨ ਕਰਨ ਵਾਲੀਆਂ ਕੁੰਜੀਆਂ ਦੇ ਇੰਤਜ਼ਾਮ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/home/pam_systemd_home.c:330 #, c-format From 59aed18d69c83759a3b05265d3995cff9efe7e84 Mon Sep 17 00:00:00 2001 From: seaeunlee Date: Fri, 6 Mar 2026 00:33:01 +0000 Subject: [PATCH 0331/1296] hostname: add API for getting custom fields from machine-info --- man/org.freedesktop.hostname1.xml | 12 ++++ src/hostname/hostnamed.c | 64 +++++++++++++++++++++ src/libsystemd/sd-bus/bus-common-errors.c | 1 + src/libsystemd/sd-bus/bus-common-errors.h | 1 + test/units/TEST-71-HOSTNAME.sh | 68 +++++++++++++++++++++++ 5 files changed, 146 insertions(+) diff --git a/man/org.freedesktop.hostname1.xml b/man/org.freedesktop.hostname1.xml index c70258459c246..3d98b88ebc177 100644 --- a/man/org.freedesktop.hostname1.xml +++ b/man/org.freedesktop.hostname1.xml @@ -60,6 +60,8 @@ node /org/freedesktop/hostname1 { out ay uuid); GetHardwareSerial(out s serial); Describe(out s json); + GetMachineInfo(in s field, + out s value); properties: readonly s Hostname = '...'; readonly s StaticHostname = '...'; @@ -146,6 +148,8 @@ node /org/freedesktop/hostname1 { + + @@ -377,6 +381,13 @@ node /org/freedesktop/hostname1 { access to unprivileged clients through the polkit framework. Describe() returns a JSON representation of all properties in one. + + GetMachineInfo() returns the value of the given field from + /etc/machine-info. For well-known fields, this method reads values from the same + internal cache used by the corresponding D-Bus property getters, but returns only the raw + /etc/machine-info values (i.e. without property-level fallback logic such as + DMI/chassis-based detection). Custom (non-standard) fields are read directly from the file. + An error is returned if the field name is empty, invalid, or not set in the file. @@ -494,6 +505,7 @@ node /org/freedesktop/hostname1 { OperatingSystemImageVersion, HardwareSKU, and HardwareVersion were added in version 258. OperatingSystemFancyName was added in version 260. + GetMachineInfo() was added in version 261. diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index 04c72e8137bf1..ce7187161834f 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -1652,6 +1652,65 @@ static int method_get_hardware_serial(sd_bus_message *m, void *userdata, sd_bus_ return sd_bus_reply_method_return(m, "s", serial); } +static int method_get_machine_info(sd_bus_message *m, void *userdata, sd_bus_error *error) { + static const struct { + const char *name; + HostProperty prop; + } field_table[] = { + { "PRETTY_HOSTNAME", PROP_PRETTY_HOSTNAME }, + { "ICON_NAME", PROP_ICON_NAME }, + { "CHASSIS", PROP_CHASSIS }, + { "DEPLOYMENT", PROP_DEPLOYMENT }, + { "LOCATION", PROP_LOCATION }, + { "HARDWARE_VENDOR", PROP_HARDWARE_VENDOR }, + { "HARDWARE_MODEL", PROP_HARDWARE_MODEL }, + { "HARDWARE_SKU", PROP_HARDWARE_SKU }, + { "HARDWARE_VERSION", PROP_HARDWARE_VERSION }, + }; + + Context *c = ASSERT_PTR(userdata); + const char *field; + int r; + + assert(m); + + r = sd_bus_message_read(m, "s", &field); + if (r < 0) + return r; + + if (isempty(field)) + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Field name must not be empty."); + + if (!env_name_is_valid(field)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid field name '%s'.", field); + + FOREACH_ELEMENT(e, field_table) + if (streq(field, e->name)) { + /* For fields that are also exposed as D-Bus properties, use the same Context cache as the + * property getters. Note that this returns the raw /etc/machine-info value only: property-level + * fallback logic (e.g. DMI/chassis-based synthesis) is not applied here. For custom/unknown + * fields, fall back to reading the file directly. */ + context_read_machine_info(c); + + if (isempty(c->data[e->prop])) + return sd_bus_error_setf(error, BUS_ERROR_FIELD_NOT_SET, "Field '%s' is not set or empty in /etc/machine-info.", field); + + return sd_bus_reply_method_return(m, "s", c->data[e->prop]); + } + + _cleanup_free_ char *value = NULL; + + r = parse_env_file(NULL, etc_machine_info(), + field, &value); + if (r < 0 && r != -ENOENT) + return sd_bus_error_set_errnof(error, r, "Failed to read /etc/machine-info: %m"); + + if (isempty(value)) + return sd_bus_error_setf(error, BUS_ERROR_FIELD_NOT_SET, "Field '%s' is not set or empty in /etc/machine-info.", field); + + return sd_bus_reply_method_return(m, "s", value); +} + static int build_describe_response(Context *c, bool privileged, sd_json_variant **ret) { _cleanup_free_ char *hn = NULL, *dhn = NULL, *in = NULL, *chassis = NULL, *vendor = NULL, *model = NULL, *serial = NULL, *firmware_version = NULL, @@ -1883,6 +1942,11 @@ static const sd_bus_vtable hostname_vtable[] = { SD_BUS_RESULT("s", json), method_describe, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("GetMachineInfo", + SD_BUS_ARGS("s", field), + SD_BUS_RESULT("s", value), + method_get_machine_info, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END, }; diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c index f0fc09f8a9192..6c2b0fd8814b1 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.c +++ b/src/libsystemd/sd-bus/bus-common-errors.c @@ -111,6 +111,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { SD_BUS_ERROR_MAP(BUS_ERROR_NO_PRODUCT_UUID, EOPNOTSUPP), SD_BUS_ERROR_MAP(BUS_ERROR_NO_HARDWARE_SERIAL, EOPNOTSUPP), + SD_BUS_ERROR_MAP(BUS_ERROR_FIELD_NOT_SET, ENODATA), SD_BUS_ERROR_MAP(BUS_ERROR_FILE_IS_PROTECTED, EACCES), SD_BUS_ERROR_MAP(BUS_ERROR_READ_ONLY_FILESYSTEM, EROFS), diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h index 36676e83db509..b4788433bfce8 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -112,6 +112,7 @@ #define BUS_ERROR_NO_PRODUCT_UUID "org.freedesktop.hostname1.NoProductUUID" #define BUS_ERROR_NO_HARDWARE_SERIAL "org.freedesktop.hostname1.NoHardwareSerial" +#define BUS_ERROR_FIELD_NOT_SET "org.freedesktop.hostname1.FieldNotSet" #define BUS_ERROR_FILE_IS_PROTECTED "org.freedesktop.hostname1.FileIsProtected" #define BUS_ERROR_READ_ONLY_FILESYSTEM "org.freedesktop.hostname1.ReadOnlyFilesystem" diff --git a/test/units/TEST-71-HOSTNAME.sh b/test/units/TEST-71-HOSTNAME.sh index ffb110be68832..9bd743dcaa661 100755 --- a/test/units/TEST-71-HOSTNAME.sh +++ b/test/units/TEST-71-HOSTNAME.sh @@ -320,6 +320,74 @@ EOF assert_in "CHASSIS=watch" "$(cat /run/alternate-path/mymachine-info)" } +testcase_get_machine_info() { + if [[ -f /etc/machine-info ]]; then + cp /etc/machine-info /tmp/machine-info.bak + fi + + trap restore_machine_info RETURN + + # Test all standard cached fields + cat >/etc/machine-info </etc/machine-info < Date: Fri, 6 Mar 2026 07:34:57 -0500 Subject: [PATCH 0332/1296] userdb: add birthDate field to JSON user records Add a birthDate field to the JSON user record, stored internally as a struct tm with INT_MIN/negative sentinels for unset fields. The field is serialized as a YYYY-MM-DD string in JSON and validated via parse_birth_date(), which shares its core logic with parse_calendar_date() through a new parse_calendar_date_full() function. For birth dates, timegm() is called directly (rather than mktime_or_timegm_usec) to support pre-epoch dates. The wday field is used to distinguish timegm() failure from a valid (time_t) -1 return. birthDate is excluded from user_record_self_modifiable_fields(), so only administrators can set or change it via homectl. The field remains in the regular (non-privileged) JSON section, keeping it readable by the user and applications. --- docs/USER_RECORD.md | 3 +++ man/homectl.xml | 10 ++++++++++ src/basic/time-util.c | 33 ++++++++++++++++++++++++++------- src/basic/time-util.h | 19 ++++++++++++++++++- src/home/homectl.c | 19 +++++++++++++++++++ src/shared/user-record-show.c | 2 ++ src/shared/user-record.c | 27 ++++++++++++++++++++++++++- src/shared/user-record.h | 3 +++ src/test/test-time-util.c | 32 ++++++++++++++++++++++++++++++++ src/test/test-user-record.c | 9 +++++++++ 10 files changed, 148 insertions(+), 9 deletions(-) diff --git a/docs/USER_RECORD.md b/docs/USER_RECORD.md index 9d6d8c1d03b88..5335e145b5f71 100644 --- a/docs/USER_RECORD.md +++ b/docs/USER_RECORD.md @@ -273,6 +273,9 @@ This must be a string, and should follow the semantics defined in the It's probably wise to use a location string processable by geo-location subsystems, but this is not enforced nor required. Example: `Berlin, Germany` or `Basement, Room 3a`. +`birthDate` → A string in ISO 8601 calendar date format (`YYYY-MM-DD`) indicating the user's date +of birth. The earliest representable year is 1900. This field is optional. + `disposition` → A string, one of `intrinsic`, `system`, `dynamic`, `regular`, `container`, `foreign`, `reserved`. If specified clarifies the disposition of the user, i.e. the context it is defined in. diff --git a/man/homectl.xml b/man/homectl.xml index a59efd7112ee9..bb827d1e6ba96 100644 --- a/man/homectl.xml +++ b/man/homectl.xml @@ -366,6 +366,16 @@ + + + + Takes a birth date for the user in ISO 8601 calendar date format + (YYYY-MM-DD). The earliest representable year is 1900. If an empty string is + passed the birth date is reset to unset. + + + + diff --git a/src/basic/time-util.c b/src/basic/time-util.c index 4ba5bfafd7161..1e426bb8f988b 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -1893,9 +1893,8 @@ TimestampStyle timestamp_style_from_string(const char *s) { return t; } -int parse_calendar_date(const char *s, usec_t *ret) { +int parse_calendar_date_full(const char *s, bool allow_pre_epoch, usec_t *ret_usec, struct tm *ret_tm) { struct tm parsed_tm = {}, copy_tm; - usec_t usec; const char *k; int r; @@ -1906,9 +1905,22 @@ int parse_calendar_date(const char *s, usec_t *ret) { return -EINVAL; copy_tm = parsed_tm; - r = mktime_or_timegm_usec(©_tm, /* utc= */ true, &usec); - if (r < 0) - return r; + + usec_t usec = USEC_INFINITY; + + if (allow_pre_epoch) { + /* For birth dates we use timegm() directly since we need to accept pre-epoch dates. + * timegm() returns (time_t) -1 both on error and for one second before the epoch. + * Initialize wday to -1 beforehand: if it remains -1 after the call, it's a genuine + * error; if timegm() changed it, the date was successfully normalized. */ + copy_tm.tm_wday = -1; + if (timegm(©_tm) == (time_t) -1 && copy_tm.tm_wday == -1) + return -EINVAL; + } else { + r = mktime_or_timegm_usec(©_tm, /* utc= */ true, &usec); + if (r < 0) + return r; + } /* Refuse non-normalized dates, e.g. Feb 30 */ if (copy_tm.tm_mday != parsed_tm.tm_mday || @@ -1916,8 +1928,15 @@ int parse_calendar_date(const char *s, usec_t *ret) { copy_tm.tm_year != parsed_tm.tm_year) return -EINVAL; - if (ret) - *ret = usec; + if (ret_usec) + *ret_usec = usec; + if (ret_tm) { + /* Reset to unset, then fill in only the date fields we parsed and validated */ + *ret_tm = BIRTH_DATE_UNSET; + ret_tm->tm_mday = parsed_tm.tm_mday; + ret_tm->tm_mon = parsed_tm.tm_mon; + ret_tm->tm_year = parsed_tm.tm_year; + } return 0; } diff --git a/src/basic/time-util.h b/src/basic/time-util.h index c39718890131a..5b6a524c4863e 100644 --- a/src/basic/time-util.h +++ b/src/basic/time-util.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include #include #include "basic-forward.h" @@ -180,7 +181,23 @@ const char* etc_localtime(void); int mktime_or_timegm_usec(struct tm *tm, bool utc, usec_t *ret); int localtime_or_gmtime_usec(usec_t t, bool utc, struct tm *ret); -int parse_calendar_date(const char *s, usec_t *ret); + +int parse_calendar_date_full(const char *s, bool allow_pre_epoch, usec_t *ret_usec, struct tm *ret_tm); + +static inline int parse_calendar_date(const char *s, usec_t *ret) { + return parse_calendar_date_full(s, /* allow_pre_epoch= */ false, ret, NULL); +} + +#define BIRTH_DATE_UNSET \ + (const struct tm) { \ + .tm_year = INT_MIN, \ + } + +#define BIRTH_DATE_IS_SET(tm) ((tm).tm_year != INT_MIN) + +static inline int parse_birth_date(const char *s, struct tm *ret) { + return parse_calendar_date_full(s, /* allow_pre_epoch= */ true, NULL, ret); +} uint32_t usec_to_jiffies(usec_t usec); usec_t jiffies_to_usec(uint32_t jiffies); diff --git a/src/home/homectl.c b/src/home/homectl.c index 2b92ab6481eae..5bb9de1819dfb 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3945,6 +3945,7 @@ static int help(int argc, char *argv[], void *userdata) { " --alias=ALIAS Define alias usernames for this account\n" " --email-address=EMAIL Email address for user\n" " --location=LOCATION Set location of user on earth\n" + " --birth-date=[DATE] Set user birth date (YYYY-MM-DD)\n" " --icon-name=NAME Icon name for user\n" " -d --home-dir=PATH Home directory\n" " -u --uid=UID Numeric UID for user\n" @@ -4109,6 +4110,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_LOCKED, ARG_SSH_AUTHORIZED_KEYS, ARG_LOCATION, + ARG_BIRTH_DATE, ARG_ICON_NAME, ARG_PASSWORD_HINT, ARG_NICE, @@ -4195,6 +4197,7 @@ static int parse_argv(int argc, char *argv[]) { { "alias", required_argument, NULL, ARG_ALIAS }, { "email-address", required_argument, NULL, ARG_EMAIL_ADDRESS }, { "location", required_argument, NULL, ARG_LOCATION }, + { "birth-date", required_argument, NULL, ARG_BIRTH_DATE }, { "password-hint", required_argument, NULL, ARG_PASSWORD_HINT }, { "icon-name", required_argument, NULL, ARG_ICON_NAME }, { "home-dir", required_argument, NULL, 'd' }, /* Compatible with useradd(8) */ @@ -4408,6 +4411,22 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_BIRTH_DATE: + if (isempty(optarg)) { + r = drop_from_identity("birthDate"); + if (r < 0) + return r; + } else { + r = parse_birth_date(optarg, /* ret= */ NULL); + if (r < 0) + return log_error_errno(r, "Invalid birth date (expected YYYY-MM-DD): %s", optarg); + + r = parse_string_field(&arg_identity_extra, "birthDate", optarg); + if (r < 0) + return r; + } + break; + case ARG_CIFS_SERVICE: if (!isempty(optarg)) { r = parse_cifs_service(optarg, /* ret_host= */ NULL, /* ret_service= */ NULL, /* ret_path= */ NULL); diff --git a/src/shared/user-record-show.c b/src/shared/user-record-show.c index a43328c9fe02b..11edf2557c3b5 100644 --- a/src/shared/user-record-show.c +++ b/src/shared/user-record-show.c @@ -342,6 +342,8 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) { printf(" Email: %s\n", hr->email_address); if (hr->location) printf(" Location: %s\n", hr->location); + if (BIRTH_DATE_IS_SET(hr->birth_date)) + printf(" Birth Date: %04d-%02d-%02d\n", hr->birth_date.tm_year + 1900, hr->birth_date.tm_mon + 1, hr->birth_date.tm_mday); if (hr->password_hint) printf(" Passw. Hint: %s\n", hr->password_hint); if (hr->icon_name) diff --git a/src/shared/user-record.c b/src/shared/user-record.c index c65bab4ff4684..4dfb2c72d70f0 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -46,6 +46,7 @@ UserRecord* user_record_new(void) { .nice_level = INT_MAX, .not_before_usec = UINT64_MAX, .not_after_usec = UINT64_MAX, + .birth_date = BIRTH_DATE_UNSET, .locked = -1, .storage = _USER_STORAGE_INVALID, .access_mode = MODE_INVALID, @@ -417,6 +418,28 @@ static int json_dispatch_filename_or_path(const char *name, sd_json_variant *var return 0; } +static int json_dispatch_birth_date(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + struct tm *ret = ASSERT_PTR(userdata); + const char *s; + int r; + + if (sd_json_variant_is_null(variant)) { + *ret = BIRTH_DATE_UNSET; + return 0; + } + + if (!sd_json_variant_is_string(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); + + s = sd_json_variant_string(variant); + + r = parse_birth_date(s, ret); + if (r < 0) + return json_log(variant, flags, r, "JSON field '%s' is not a valid ISO 8601 date (expected YYYY-MM-DD).", strna(name)); + + return 0; +} + static int json_dispatch_home_directory(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { char **s = userdata; const char *n; @@ -1500,7 +1523,8 @@ int user_group_record_mangle( /* Personally Identifiable Information (PII) — avoid leaking in logs */ "realName", "location", - "emailAddress") + "emailAddress", + "birthDate") sd_json_variant_sensitive(sd_json_variant_by_key(v, key)); /* Check if we have the special sections and if they match our flags set */ @@ -1595,6 +1619,7 @@ int user_record_load(UserRecord *h, sd_json_variant *v, UserRecordLoadFlags load { "emailAddress", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(UserRecord, email_address), SD_JSON_STRICT }, { "iconName", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(UserRecord, icon_name), SD_JSON_STRICT }, { "location", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(UserRecord, location), 0 }, + { "birthDate", SD_JSON_VARIANT_STRING, json_dispatch_birth_date, offsetof(UserRecord, birth_date), 0 }, { "disposition", SD_JSON_VARIANT_STRING, json_dispatch_user_disposition, offsetof(UserRecord, disposition), 0 }, { "lastChangeUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(UserRecord, last_change_usec), 0 }, { "lastPasswordChangeUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(UserRecord, last_password_change_usec), 0 }, diff --git a/src/shared/user-record.h b/src/shared/user-record.h index c3d52d2bd71aa..4eb35d43e4487 100644 --- a/src/shared/user-record.h +++ b/src/shared/user-record.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include + #include "sd-id128.h" #include "bitfield.h" @@ -266,6 +268,7 @@ typedef struct UserRecord { char *password_hint; char *icon_name; char *location; + struct tm birth_date; char *blob_directory; Hashmap *blob_manifest; diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c index 172056a9bf611..d3d4fd5f9b2e9 100644 --- a/src/test/test-time-util.c +++ b/src/test/test-time-util.c @@ -1307,4 +1307,36 @@ TEST(parse_calendar_date) { ASSERT_ERROR(parse_calendar_date("06-15-2023", &usec), EINVAL); /* wrong order */ } +TEST(parse_birth_date) { + struct tm tm; + + /* Valid dates */ + ASSERT_OK(parse_birth_date("2000-06-15", &tm)); + ASSERT_EQ(tm.tm_year, 100); /* 2000 - 1900 */ + ASSERT_EQ(tm.tm_mon, 5); /* June, 0-indexed */ + ASSERT_EQ(tm.tm_mday, 15); + + /* Pre-epoch dates */ + ASSERT_OK(parse_birth_date("1960-03-25", &tm)); + ASSERT_EQ(tm.tm_year, 60); + ASSERT_EQ(tm.tm_mon, 2); + ASSERT_EQ(tm.tm_mday, 25); + + /* NULL ret is allowed (validation only) */ + ASSERT_OK(parse_birth_date("2000-01-01", NULL)); + + /* Non-date fields should not be relied upon */ + ASSERT_OK(parse_birth_date("2000-06-15", &tm)); + ASSERT_FALSE(BIRTH_DATE_IS_SET(BIRTH_DATE_UNSET)); + + /* Non-normalized dates */ + ASSERT_ERROR(parse_birth_date("2023-02-29", &tm), EINVAL); + ASSERT_ERROR(parse_birth_date("2023-04-31", &tm), EINVAL); + + /* Malformed input */ + ASSERT_ERROR(parse_birth_date("", &tm), EINVAL); + ASSERT_ERROR(parse_birth_date("not-a-date", &tm), EINVAL); + ASSERT_ERROR(parse_birth_date("2023-06-15T00:00:00", &tm), EINVAL); +} + DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/test/test-user-record.c b/src/test/test-user-record.c index c807ffe83afd6..480a3f33eb2ef 100644 --- a/src/test/test-user-record.c +++ b/src/test/test-user-record.c @@ -96,6 +96,15 @@ TEST(self_changes) { SD_JSON_BUILD_PAIR_OBJECT("privileged", SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 99999))); ASSERT_TRUE(user_record_self_changes_allowed(curr, new)); + + /* birthDate is NOT self-modifiable (admin-only) */ + USER(&curr, + SD_JSON_BUILD_PAIR_STRING("userName", "test"), + SD_JSON_BUILD_PAIR_STRING("birthDate", "1990-01-01")); + USER(&new, + SD_JSON_BUILD_PAIR_STRING("userName", "test"), + SD_JSON_BUILD_PAIR_STRING("birthDate", "1990-06-15")); + ASSERT_FALSE(user_record_self_changes_allowed(curr, new)); } DEFINE_TEST_MAIN(LOG_INFO); From f9ebd7bb0670ee2a8421ecbe2b4cf60dc28901c6 Mon Sep 17 00:00:00 2001 From: noxiouz Date: Sun, 22 Feb 2026 15:09:08 +0000 Subject: [PATCH 0333/1296] network: extract link_get_bit_rates() into networkd-speed-meter.c Move the bit-rate computation out of property_get_bit_rates() in networkd-link-bus.c into a standalone link_get_bit_rates() helper in networkd-speed-meter.c, which already owns the speed meter state. Co-developed-by: Claude Opus 4.6 --- src/network/networkd-link-bus.c | 23 ++------------------- src/network/networkd-speed-meter.c | 32 ++++++++++++++++++++++++++++++ src/network/networkd-speed-meter.h | 1 + 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/network/networkd-link-bus.c b/src/network/networkd-link-bus.c index 1b96fbdbd39af..c411135a33f74 100644 --- a/src/network/networkd-link-bus.c +++ b/src/network/networkd-link-bus.c @@ -17,6 +17,7 @@ #include "networkd-link.h" #include "networkd-link-bus.h" #include "networkd-manager.h" +#include "networkd-speed-meter.h" #include "networkd-state-file.h" #include "ordered-set.h" #include "parse-util.h" @@ -42,32 +43,12 @@ static int property_get_bit_rates( sd_bus_error *error) { Link *link = ASSERT_PTR(userdata); - Manager *manager; - double interval_sec; uint64_t tx, rx; assert(bus); assert(reply); - manager = link->manager; - - if (!manager->use_speed_meter || - manager->speed_meter_usec_old == 0 || - !link->stats_updated) - return sd_bus_message_append(reply, "(tt)", UINT64_MAX, UINT64_MAX); - - assert(manager->speed_meter_usec_new > manager->speed_meter_usec_old); - interval_sec = (double) (manager->speed_meter_usec_new - manager->speed_meter_usec_old) / USEC_PER_SEC; - - if (link->stats_new.tx_bytes > link->stats_old.tx_bytes) - tx = (uint64_t) ((link->stats_new.tx_bytes - link->stats_old.tx_bytes) / interval_sec); - else - tx = (uint64_t) ((UINT64_MAX - (link->stats_old.tx_bytes - link->stats_new.tx_bytes)) / interval_sec); - - if (link->stats_new.rx_bytes > link->stats_old.rx_bytes) - rx = (uint64_t) ((link->stats_new.rx_bytes - link->stats_old.rx_bytes) / interval_sec); - else - rx = (uint64_t) ((UINT64_MAX - (link->stats_old.rx_bytes - link->stats_new.rx_bytes)) / interval_sec); + link_get_bit_rates(link, &tx, &rx); return sd_bus_message_append(reply, "(tt)", tx, rx); } diff --git a/src/network/networkd-speed-meter.c b/src/network/networkd-speed-meter.c index ade6c74331746..f04b11be9cf85 100644 --- a/src/network/networkd-speed-meter.c +++ b/src/network/networkd-speed-meter.c @@ -86,6 +86,38 @@ static int speed_meter_handler(sd_event_source *s, uint64_t usec, void *userdata return 0; } +void link_get_bit_rates(Link *link, uint64_t *ret_tx, uint64_t *ret_rx) { + Manager *manager; + double interval_sec; + + assert(link); + assert(ret_tx); + assert(ret_rx); + + manager = link->manager; + + if (!manager->use_speed_meter || + manager->speed_meter_usec_old == 0 || + !link->stats_updated) { + *ret_tx = UINT64_MAX; + *ret_rx = UINT64_MAX; + return; + } + + assert(manager->speed_meter_usec_new > manager->speed_meter_usec_old); + interval_sec = (double) (manager->speed_meter_usec_new - manager->speed_meter_usec_old) / USEC_PER_SEC; + + if (link->stats_new.tx_bytes > link->stats_old.tx_bytes) + *ret_tx = (uint64_t) ((link->stats_new.tx_bytes - link->stats_old.tx_bytes) / interval_sec); + else + *ret_tx = (uint64_t) ((UINT64_MAX - (link->stats_old.tx_bytes - link->stats_new.tx_bytes)) / interval_sec); + + if (link->stats_new.rx_bytes > link->stats_old.rx_bytes) + *ret_rx = (uint64_t) ((link->stats_new.rx_bytes - link->stats_old.rx_bytes) / interval_sec); + else + *ret_rx = (uint64_t) ((UINT64_MAX - (link->stats_old.rx_bytes - link->stats_new.rx_bytes)) / interval_sec); +} + int manager_start_speed_meter(Manager *manager) { _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; int r; diff --git a/src/network/networkd-speed-meter.h b/src/network/networkd-speed-meter.h index f7ef5a45697dd..8f98005322651 100644 --- a/src/network/networkd-speed-meter.h +++ b/src/network/networkd-speed-meter.h @@ -9,4 +9,5 @@ #define SPEED_METER_DEFAULT_TIME_INTERVAL (10 * USEC_PER_SEC) #define SPEED_METER_MINIMUM_TIME_INTERVAL (100 * USEC_PER_MSEC) +void link_get_bit_rates(Link *link, uint64_t *ret_tx, uint64_t *ret_rx); int manager_start_speed_meter(Manager *m); From 872feaa006c9e6e7fa2e036dcbd09d9858470cfa Mon Sep 17 00:00:00 2001 From: noxiouz Date: Sun, 22 Feb 2026 15:09:56 +0000 Subject: [PATCH 0334/1296] network: add BitRates field to link_build_json() and the Varlink IDL Expose the speed meter transmit/receive rates as a BitRates struct in the per-link JSON output and the io.systemd.Network Varlink IDL. Co-developed-by: Claude Opus 4.6 --- src/network/networkd-json.c | 15 +++++++++++++++ src/shared/varlink-io.systemd.Network.c | 12 +++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/network/networkd-json.c b/src/network/networkd-json.c index de1a8829ee94b..d109fdc473201 100644 --- a/src/network/networkd-json.c +++ b/src/network/networkd-json.c @@ -26,6 +26,7 @@ #include "networkd-route.h" #include "networkd-route-util.h" #include "networkd-routing-policy-rule.h" +#include "networkd-speed-meter.h" #include "networkd-wwan.h" #include "ordered-set.h" #include "set.h" @@ -1639,6 +1640,20 @@ int link_build_json(Link *link, sd_json_variant **ret) { if (r < 0) return r; + /* Append BitRates if speed meter is active */ + uint64_t tx, rx; + link_get_bit_rates(link, &tx, &rx); + if (tx != UINT64_MAX && rx != UINT64_MAX) { + r = sd_json_variant_merge_objectbo( + &v, + SD_JSON_BUILD_PAIR("BitRates", + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_UNSIGNED("TxBitRate", tx), + SD_JSON_BUILD_PAIR_UNSIGNED("RxBitRate", rx)))); + if (r < 0) + return r; + } + *ret = TAKE_PTR(v); return 0; } diff --git a/src/shared/varlink-io.systemd.Network.c b/src/shared/varlink-io.systemd.Network.c index 9ae737714fb61..47004fc959eea 100644 --- a/src/shared/varlink-io.systemd.Network.c +++ b/src/shared/varlink-io.systemd.Network.c @@ -411,6 +411,13 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Static DHCP leases configured for specific clients"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(StaticLeases, DHCPServerLease, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_STRUCT_TYPE( + BitRates, + SD_VARLINK_FIELD_COMMENT("Transmit bitrate in bits per second"), + SD_VARLINK_DEFINE_FIELD(TxBitRate, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("Receive bitrate in bits per second"), + SD_VARLINK_DEFINE_FIELD(RxBitRate, SD_VARLINK_INT, 0)); + static SD_VARLINK_DEFINE_STRUCT_TYPE( Interface, SD_VARLINK_FIELD_COMMENT("Network interface index"), @@ -533,7 +540,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("DHCPv6 client configuration and lease information"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(DHCPv6Client, DHCPv6Client, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("LLDP transmit configuration for this interface"), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(LLDP, LLDPNeighbor, SD_VARLINK_NULLABLE)); + SD_VARLINK_DEFINE_FIELD_BY_TYPE(LLDP, LLDPNeighbor, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Current transmit/receive bitrates from speed meter"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(BitRates, BitRates, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD( Describe, @@ -604,6 +613,7 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_method_GetLLDPNeighbors, &vl_method_SetPersistentStorage, &vl_type_Address, + &vl_type_BitRates, &vl_type_DHCPLease, &vl_type_DHCPServer, &vl_type_DHCPServerLease, From f8722d40335963dace358ef2bc77a5f9a8dc088d Mon Sep 17 00:00:00 2001 From: noxiouz Date: Sun, 22 Feb 2026 15:11:37 +0000 Subject: [PATCH 0335/1296] network: extend link_reconfigure_full() and manager_reload() for Varlink Add an sd_varlink* parameter to both functions so Varlink callers can receive a deferred reply once all async work completes, symmetrically with the existing sd_bus_message* path. Co-developed-by: Claude Opus 4.6 --- src/network/networkd-link-bus.c | 2 +- src/network/networkd-link.c | 35 ++++++++++++++++++++---------- src/network/networkd-link.h | 4 ++-- src/network/networkd-manager-bus.c | 2 +- src/network/networkd-manager.c | 13 +++++++---- src/network/networkd-manager.h | 2 +- 6 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/network/networkd-link-bus.c b/src/network/networkd-link-bus.c index c411135a33f74..a375d0c18b235 100644 --- a/src/network/networkd-link-bus.c +++ b/src/network/networkd-link-bus.c @@ -653,7 +653,7 @@ int bus_link_method_reconfigure(sd_bus_message *message, void *userdata, sd_bus_ if (r == 0) return 1; /* Polkit will call us back */ - r = link_reconfigure_full(l, LINK_RECONFIGURE_UNCONDITIONALLY | LINK_RECONFIGURE_CLEANLY, message, /* counter= */ NULL); + r = link_reconfigure_full(l, LINK_RECONFIGURE_UNCONDITIONALLY | LINK_RECONFIGURE_CLEANLY, message, /* varlink= */ NULL, /* counter= */ NULL); if (r != 0) return r; /* Will reply later when r > 0. */ diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index c77b02b8e0192..57b074e1be235 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -18,6 +18,7 @@ #include "sd-ndisc.h" #include "sd-netlink.h" #include "sd-radv.h" +#include "sd-varlink.h" #include "alloc-util.h" #include "arphrd-util.h" @@ -1509,6 +1510,7 @@ typedef struct LinkReconfigurationData { Link *link; LinkReconfigurationFlag flags; sd_bus_message *message; + sd_varlink *varlink; unsigned *counter; } LinkReconfigurationData; @@ -1518,6 +1520,7 @@ static LinkReconfigurationData* link_reconfiguration_data_free(LinkReconfigurati link_unref(data->link); sd_bus_message_unref(data->message); + sd_varlink_unref(data->varlink); return mfree(data); } @@ -1528,24 +1531,30 @@ static void link_reconfiguration_data_destroy_callback(LinkReconfigurationData * int r; assert(data); + assert(!data->message || !data->varlink); /* D-Bus and Varlink callers are mutually exclusive */ - if (data->message) { - if (data->counter) { - assert(*data->counter > 0); - (*data->counter)--; - } + if (data->counter) { + assert(*data->counter > 0); + (*data->counter)--; + } - if (!data->counter || *data->counter <= 0) { - /* Update the state files before replying the bus method. Otherwise, - * systemd-networkd-wait-online following networkctl reload/reconfigure may read an - * outdated state file and wrongly handle an interface is already in the configured - * state. */ - (void) manager_clean_all(data->manager); + if (!data->counter || *data->counter == 0) { + /* Update the state files before replying. Otherwise, systemd-networkd-wait-online following + * networkctl reload/reconfigure may read an outdated state file and wrongly consider an + * interface already in the configured state. */ + (void) manager_clean_all(data->manager); + if (data->message) { r = sd_bus_reply_method_return(data->message, NULL); if (r < 0) log_warning_errno(r, "Failed to reply for DBus method, ignoring: %m"); } + + if (data->varlink) { + r = sd_varlink_reply(data->varlink, NULL); + if (r < 0) + log_warning_errno(r, "Failed to reply to Varlink request, ignoring: %m"); + } } link_reconfiguration_data_free(data); @@ -1568,7 +1577,7 @@ static int link_reconfigure_handler(sd_netlink *rtnl, sd_netlink_message *m, Lin return r; } -int link_reconfigure_full(Link *link, LinkReconfigurationFlag flags, sd_bus_message *message, unsigned *counter) { +int link_reconfigure_full(Link *link, LinkReconfigurationFlag flags, sd_bus_message *message, sd_varlink *varlink, unsigned *counter) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; _cleanup_(link_reconfiguration_data_freep) LinkReconfigurationData *data = NULL; int r; @@ -1576,6 +1585,7 @@ int link_reconfigure_full(Link *link, LinkReconfigurationFlag flags, sd_bus_mess assert(link); assert(link->manager); assert(link->manager->rtnl); + assert(!message || !varlink); /* D-Bus and Varlink callers are mutually exclusive */ /* When the link is in the pending or initialized state, link_reconfigure_impl() will be called later * by link_initialized() or link_initialized_and_synced(). To prevent the function from being called @@ -1594,6 +1604,7 @@ int link_reconfigure_full(Link *link, LinkReconfigurationFlag flags, sd_bus_mess .link = link_ref(link), .flags = flags, .message = sd_bus_message_ref(message), /* message may be NULL, but _ref() works fine. */ + .varlink = sd_varlink_ref(varlink), /* varlink may be NULL, but _ref() works fine. */ .counter = counter, }; diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 9c641fad39bdb..33c2fd35a5c21 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -243,9 +243,9 @@ DECLARE_STRING_TABLE_LOOKUP(link_state, LinkState); int link_request_stacked_netdevs(Link *link, NetDevLocalAddressType type); int link_reconfigure_impl(Link *link, LinkReconfigurationFlag flags); -int link_reconfigure_full(Link *link, LinkReconfigurationFlag flags, sd_bus_message *message, unsigned *counter); +int link_reconfigure_full(Link *link, LinkReconfigurationFlag flags, sd_bus_message *message, sd_varlink *varlink, unsigned *counter); static inline int link_reconfigure(Link *link, LinkReconfigurationFlag flags) { - return link_reconfigure_full(link, flags, NULL, NULL); + return link_reconfigure_full(link, flags, NULL, NULL, NULL); } int link_check_initialized(Link *link); diff --git a/src/network/networkd-manager-bus.c b/src/network/networkd-manager-bus.c index a98561b25e77a..c335d34ff1c26 100644 --- a/src/network/networkd-manager-bus.c +++ b/src/network/networkd-manager-bus.c @@ -215,7 +215,7 @@ static int bus_method_reload(sd_bus_message *message, void *userdata, sd_bus_err if (r == 0) return 1; /* Polkit will call us back */ - r = manager_reload(manager, message); + r = manager_reload(manager, message, /* varlink= */ NULL); if (r < 0) return r; diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 62e52717585c1..66366fe60d38c 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -533,7 +533,7 @@ static int signal_restart_callback(sd_event_source *s, const struct signalfd_sig static int signal_reload_callback(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { Manager *m = ASSERT_PTR(userdata); - (void) manager_reload(m, /* message= */ NULL); + (void) manager_reload(m, /* message= */ NULL, /* varlink= */ NULL); return 0; } @@ -1257,11 +1257,12 @@ int manager_set_timezone(Manager *m, const char *tz) { return 0; } -int manager_reload(Manager *m, sd_bus_message *message) { +int manager_reload(Manager *m, sd_bus_message *message, sd_varlink *varlink) { Link *link; int r; assert(m); + assert(!message || !varlink); /* D-Bus and Varlink callers are mutually exclusive */ log_debug("Reloading..."); (void) notify_reloading(); @@ -1279,8 +1280,12 @@ int manager_reload(Manager *m, sd_bus_message *message) { } HASHMAP_FOREACH(link, m->links_by_index) - (void) link_reconfigure_full(link, /* flags= */ 0, message, - /* counter= */ message ? &m->reloading : NULL); + (void) link_reconfigure_full( + link, + /* flags= */ 0, + message, + varlink, + /* counter= */ (message || varlink) ? &m->reloading : NULL); log_debug("Reloaded."); r = 0; diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h index 7b014017200dd..bacf3df444474 100644 --- a/src/network/networkd-manager.h +++ b/src/network/networkd-manager.h @@ -151,7 +151,7 @@ int manager_enumerate(Manager *m); int manager_set_hostname(Manager *m, const char *hostname); int manager_set_timezone(Manager *m, const char *tz); -int manager_reload(Manager *m, sd_bus_message *message); +int manager_reload(Manager *m, sd_bus_message *message, sd_varlink *varlink); static inline Hashmap** manager_get_sysctl_shadow(Manager *manager) { #if ENABLE_SYSCTL_BPF From 5e7287c2b51cb1710652cc8d7226f51f67b17e98 Mon Sep 17 00:00:00 2001 From: noxiouz Date: Sun, 22 Feb 2026 15:24:14 +0000 Subject: [PATCH 0336/1296] network: add io.systemd.Network.Link.Renew() Varlink method The handler calls dhcp4_renew() and logs a warning on failure. Co-developed-by: Claude Opus 4.6 --- src/network/networkd-link-varlink.c | 28 ++++++++++++++++++++ src/network/networkd-link-varlink.h | 1 + src/network/networkd-manager-varlink.c | 1 + src/shared/varlink-io.systemd.Network.Link.c | 9 ++++++- 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/network/networkd-link-varlink.c b/src/network/networkd-link-varlink.c index ad7386dabff05..c0abc8ef0647a 100644 --- a/src/network/networkd-link-varlink.c +++ b/src/network/networkd-link-varlink.c @@ -4,6 +4,7 @@ #include "bus-polkit.h" #include "json-util.h" +#include "networkd-dhcp4.h" #include "networkd-link.h" #include "networkd-link-varlink.h" #include "networkd-manager.h" @@ -100,3 +101,30 @@ int vl_method_link_up(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink int vl_method_link_down(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { return vl_method_link_up_or_down(vlink, parameters, userdata, /* up= */ false); } + +int vl_method_link_renew(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + Link *link; + int r; + + assert(vlink); + + r = dispatch_link(vlink, parameters, manager, DISPATCH_LINK_POLKIT | DISPATCH_LINK_MANDATORY, &link); + if (r != 0) + return r; + + r = varlink_verify_polkit_async( + vlink, + manager->bus, + "org.freedesktop.network1.renew", + /* details= */ NULL, + &manager->polkit_registry); + if (r <= 0) + return r; + + r = dhcp4_renew(link); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to renew DHCPv4 lease: %m"); + + return sd_varlink_reply(vlink, NULL); +} diff --git a/src/network/networkd-link-varlink.h b/src/network/networkd-link-varlink.h index 60468519ca1ca..0d0dd3ad25a2d 100644 --- a/src/network/networkd-link-varlink.h +++ b/src/network/networkd-link-varlink.h @@ -12,3 +12,4 @@ int dispatch_link(sd_varlink *vlink, sd_json_variant *parameters, Manager *manag int vl_method_link_up(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_link_down(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_link_renew(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/network/networkd-manager-varlink.c b/src/network/networkd-manager-varlink.c index 49bc82678d090..9adfb0c2a5d0c 100644 --- a/src/network/networkd-manager-varlink.c +++ b/src/network/networkd-manager-varlink.c @@ -273,6 +273,7 @@ int manager_varlink_init(Manager *m, int fd) { "io.systemd.Network.SetPersistentStorage", vl_method_set_persistent_storage, "io.systemd.Network.Link.Up", vl_method_link_up, "io.systemd.Network.Link.Down", vl_method_link_down, + "io.systemd.Network.Link.Renew", vl_method_link_renew, "io.systemd.service.Ping", varlink_method_ping, "io.systemd.service.SetLogLevel", varlink_method_set_log_level, "io.systemd.service.GetEnvironment", varlink_method_get_environment); diff --git a/src/shared/varlink-io.systemd.Network.Link.c b/src/shared/varlink-io.systemd.Network.Link.c index ccbb046ca00ac..338b2be123dfc 100644 --- a/src/shared/varlink-io.systemd.Network.Link.c +++ b/src/shared/varlink-io.systemd.Network.Link.c @@ -19,10 +19,17 @@ static SD_VARLINK_DEFINE_METHOD( VARLINK_NETWORK_INTERFACE_INPUTS, VARLINK_DEFINE_POLKIT_INPUT); +static SD_VARLINK_DEFINE_METHOD( + Renew, + VARLINK_NETWORK_INTERFACE_INPUTS, + VARLINK_DEFINE_POLKIT_INPUT); + SD_VARLINK_DEFINE_INTERFACE( io_systemd_Network_Link, "io.systemd.Network.Link", SD_VARLINK_SYMBOL_COMMENT("Bring the specified link up."), &vl_method_Up, SD_VARLINK_SYMBOL_COMMENT("Bring the specified link down."), - &vl_method_Down); + &vl_method_Down, + SD_VARLINK_SYMBOL_COMMENT("Renew DHCP leases on the specified link."), + &vl_method_Renew); From 982a3fa59f696695651afd9c790029e4db7f7a01 Mon Sep 17 00:00:00 2001 From: noxiouz Date: Sun, 22 Feb 2026 15:25:07 +0000 Subject: [PATCH 0337/1296] network: add io.systemd.Network.Link.ForceRenew() Varlink method The handler calls sd_dhcp_server_forcerenew() if the server is running and logs a warning on failure. Co-developed-by: Claude Opus 4.6 --- src/network/networkd-link-varlink.c | 30 ++++++++++++++++++++ src/network/networkd-link-varlink.h | 1 + src/network/networkd-manager-varlink.c | 1 + src/shared/varlink-io.systemd.Network.Link.c | 9 +++++- 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/network/networkd-link-varlink.c b/src/network/networkd-link-varlink.c index c0abc8ef0647a..edb7f3aa84d86 100644 --- a/src/network/networkd-link-varlink.c +++ b/src/network/networkd-link-varlink.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-dhcp-server.h" #include "sd-varlink.h" #include "bus-polkit.h" @@ -128,3 +129,32 @@ int vl_method_link_renew(sd_varlink *vlink, sd_json_variant *parameters, sd_varl return sd_varlink_reply(vlink, NULL); } + +int vl_method_link_force_renew(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + Link *link; + int r; + + assert(vlink); + + r = dispatch_link(vlink, parameters, manager, DISPATCH_LINK_POLKIT | DISPATCH_LINK_MANDATORY, &link); + if (r != 0) + return r; + + r = varlink_verify_polkit_async( + vlink, + manager->bus, + "org.freedesktop.network1.forcerenew", + /* details= */ NULL, + &manager->polkit_registry); + if (r <= 0) + return r; + + if (sd_dhcp_server_is_running(link->dhcp_server)) { + r = sd_dhcp_server_forcerenew(link->dhcp_server); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to force-renew DHCP server leases: %m"); + } + + return sd_varlink_reply(vlink, NULL); +} diff --git a/src/network/networkd-link-varlink.h b/src/network/networkd-link-varlink.h index 0d0dd3ad25a2d..b318d5950db8e 100644 --- a/src/network/networkd-link-varlink.h +++ b/src/network/networkd-link-varlink.h @@ -13,3 +13,4 @@ int dispatch_link(sd_varlink *vlink, sd_json_variant *parameters, Manager *manag int vl_method_link_up(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_link_down(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_link_renew(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_link_force_renew(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/network/networkd-manager-varlink.c b/src/network/networkd-manager-varlink.c index 9adfb0c2a5d0c..4ee7f8e9a2a9e 100644 --- a/src/network/networkd-manager-varlink.c +++ b/src/network/networkd-manager-varlink.c @@ -274,6 +274,7 @@ int manager_varlink_init(Manager *m, int fd) { "io.systemd.Network.Link.Up", vl_method_link_up, "io.systemd.Network.Link.Down", vl_method_link_down, "io.systemd.Network.Link.Renew", vl_method_link_renew, + "io.systemd.Network.Link.ForceRenew", vl_method_link_force_renew, "io.systemd.service.Ping", varlink_method_ping, "io.systemd.service.SetLogLevel", varlink_method_set_log_level, "io.systemd.service.GetEnvironment", varlink_method_get_environment); diff --git a/src/shared/varlink-io.systemd.Network.Link.c b/src/shared/varlink-io.systemd.Network.Link.c index 338b2be123dfc..a42aab413f302 100644 --- a/src/shared/varlink-io.systemd.Network.Link.c +++ b/src/shared/varlink-io.systemd.Network.Link.c @@ -24,6 +24,11 @@ static SD_VARLINK_DEFINE_METHOD( VARLINK_NETWORK_INTERFACE_INPUTS, VARLINK_DEFINE_POLKIT_INPUT); +static SD_VARLINK_DEFINE_METHOD( + ForceRenew, + VARLINK_NETWORK_INTERFACE_INPUTS, + VARLINK_DEFINE_POLKIT_INPUT); + SD_VARLINK_DEFINE_INTERFACE( io_systemd_Network_Link, "io.systemd.Network.Link", @@ -32,4 +37,6 @@ SD_VARLINK_DEFINE_INTERFACE( SD_VARLINK_SYMBOL_COMMENT("Bring the specified link down."), &vl_method_Down, SD_VARLINK_SYMBOL_COMMENT("Renew DHCP leases on the specified link."), - &vl_method_Renew); + &vl_method_Renew, + SD_VARLINK_SYMBOL_COMMENT("Force-renew DHCP server leases on the specified link."), + &vl_method_ForceRenew); From 506890fa0d537bf7714d1ad07d18b94d9c0f6f3d Mon Sep 17 00:00:00 2001 From: noxiouz Date: Sun, 22 Feb 2026 15:26:28 +0000 Subject: [PATCH 0338/1296] network: add io.systemd.Network.Link.Reconfigure() Varlink method The handler calls link_reconfigure_full() with UNCONDITIONALLY|CLEANLY flags and defers the Varlink reply until reconfiguration completes. Co-developed-by: Claude Opus 4.6 --- src/network/networkd-link-varlink.c | 33 ++++++++++++++++++++ src/network/networkd-link-varlink.h | 1 + src/network/networkd-manager-varlink.c | 1 + src/shared/varlink-io.systemd.Network.Link.c | 9 +++++- 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/network/networkd-link-varlink.c b/src/network/networkd-link-varlink.c index edb7f3aa84d86..2010553ead430 100644 --- a/src/network/networkd-link-varlink.c +++ b/src/network/networkd-link-varlink.c @@ -158,3 +158,36 @@ int vl_method_link_force_renew(sd_varlink *vlink, sd_json_variant *parameters, s return sd_varlink_reply(vlink, NULL); } + +int vl_method_link_reconfigure(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + Link *link; + int r; + + assert(vlink); + + r = dispatch_link(vlink, parameters, manager, DISPATCH_LINK_POLKIT | DISPATCH_LINK_MANDATORY, &link); + if (r != 0) + return r; + + r = varlink_verify_polkit_async( + vlink, + manager->bus, + "org.freedesktop.network1.reconfigure", + /* details= */ NULL, + &manager->polkit_registry); + if (r <= 0) + return r; + + r = link_reconfigure_full(link, + LINK_RECONFIGURE_UNCONDITIONALLY | LINK_RECONFIGURE_CLEANLY, + /* message= */ NULL, + /* varlink= */ vlink, + /* counter= */ NULL); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to reconfigure link: %m"); + if (r > 0) + return 0; /* Reply will be sent asynchronously via vlink */ + + return sd_varlink_reply(vlink, NULL); +} diff --git a/src/network/networkd-link-varlink.h b/src/network/networkd-link-varlink.h index b318d5950db8e..b23b9717e3319 100644 --- a/src/network/networkd-link-varlink.h +++ b/src/network/networkd-link-varlink.h @@ -14,3 +14,4 @@ int vl_method_link_up(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink int vl_method_link_down(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_link_renew(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_link_force_renew(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_link_reconfigure(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/network/networkd-manager-varlink.c b/src/network/networkd-manager-varlink.c index 4ee7f8e9a2a9e..6660f03df1312 100644 --- a/src/network/networkd-manager-varlink.c +++ b/src/network/networkd-manager-varlink.c @@ -275,6 +275,7 @@ int manager_varlink_init(Manager *m, int fd) { "io.systemd.Network.Link.Down", vl_method_link_down, "io.systemd.Network.Link.Renew", vl_method_link_renew, "io.systemd.Network.Link.ForceRenew", vl_method_link_force_renew, + "io.systemd.Network.Link.Reconfigure", vl_method_link_reconfigure, "io.systemd.service.Ping", varlink_method_ping, "io.systemd.service.SetLogLevel", varlink_method_set_log_level, "io.systemd.service.GetEnvironment", varlink_method_get_environment); diff --git a/src/shared/varlink-io.systemd.Network.Link.c b/src/shared/varlink-io.systemd.Network.Link.c index a42aab413f302..43dd640be8ddf 100644 --- a/src/shared/varlink-io.systemd.Network.Link.c +++ b/src/shared/varlink-io.systemd.Network.Link.c @@ -29,6 +29,11 @@ static SD_VARLINK_DEFINE_METHOD( VARLINK_NETWORK_INTERFACE_INPUTS, VARLINK_DEFINE_POLKIT_INPUT); +static SD_VARLINK_DEFINE_METHOD( + Reconfigure, + VARLINK_NETWORK_INTERFACE_INPUTS, + VARLINK_DEFINE_POLKIT_INPUT); + SD_VARLINK_DEFINE_INTERFACE( io_systemd_Network_Link, "io.systemd.Network.Link", @@ -39,4 +44,6 @@ SD_VARLINK_DEFINE_INTERFACE( SD_VARLINK_SYMBOL_COMMENT("Renew DHCP leases on the specified link."), &vl_method_Renew, SD_VARLINK_SYMBOL_COMMENT("Force-renew DHCP server leases on the specified link."), - &vl_method_ForceRenew); + &vl_method_ForceRenew, + SD_VARLINK_SYMBOL_COMMENT("Unconditionally reconfigure the specified link."), + &vl_method_Reconfigure); From dde311ba1496aedba4c43666c35fcec5620818f1 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 24 Feb 2026 21:46:07 +0900 Subject: [PATCH 0339/1296] networkctl: use new varlink methods Co-developed-by: Claude Opus 4.6 --- src/network/networkctl-misc.c | 78 ++++++++++------------------------- src/network/networkctl-misc.h | 3 +- src/network/networkctl.c | 32 +++++++------- 3 files changed, 39 insertions(+), 74 deletions(-) diff --git a/src/network/networkctl-misc.c b/src/network/networkctl-misc.c index e130cdab4c678..2596c144093cc 100644 --- a/src/network/networkctl-misc.c +++ b/src/network/networkctl-misc.c @@ -46,35 +46,6 @@ static int parse_interfaces(sd_netlink **rtnl, char *argv[], OrderedSet **ret) { return 0; } -int verb_link_up_down(int argc, char *argv[], uintptr_t _data, void *userdata) { - int r, ret = 0; - - bool up = streq_ptr(argv[0], "up"); - - _cleanup_ordered_set_free_ OrderedSet *indexes = NULL; - r = parse_interfaces(/* rtnl= */ NULL, argv, &indexes); - if (r < 0) - return r; - - _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL; - r = varlink_connect_networkd(&vl); - if (r < 0) - return r; - - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - - void *p; - ORDERED_SET_FOREACH(p, indexes) - RET_GATHER(ret, varlink_callbo_and_log( - vl, - up ? "io.systemd.Network.Link.Up" : "io.systemd.Network.Link.Down", - /* reply= */ NULL, - SD_JSON_BUILD_PAIR_INTEGER("InterfaceIndex", PTR_TO_INT(p)), - SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password))); - - return ret; -} - int verb_link_delete(int argc, char *argv[], uintptr_t _data, void *userdata) { int r, ret = 0; @@ -104,25 +75,26 @@ int verb_link_delete(int argc, char *argv[], uintptr_t _data, void *userdata) { return ret; } -int verb_link_bus_simple_method(int argc, char *argv[], uintptr_t _data, void *userdata) { +int verb_link_varlink_simple_method(int argc, char *argv[], uintptr_t _data, void *userdata) { int r, ret = 0; - typedef struct LinkBusAction { + typedef struct LinkVarlinkAction { const char *verb; - const char *bus_method; - const char *error_message; - } LinkBusAction; - - static const LinkBusAction link_bus_action_table[] = { - { "renew", "RenewLink", "Failed to renew dynamic configuration of interface" }, - { "forcerenew", "ForceRenewLink", "Failed to forcibly renew dynamic configuration of interface" }, - { "reconfigure", "ReconfigureLink", "Failed to reconfigure network interface" }, + const char *method; + } LinkVarlinkAction; + + static const LinkVarlinkAction link_varlink_action_table[] = { + { "up", "io.systemd.Network.Link.Up", }, + { "down", "io.systemd.Network.Link.Down", }, + { "renew", "io.systemd.Network.Link.Renew", }, + { "forcerenew", "io.systemd.Network.Link.ForceRenew", }, + { "reconfigure", "io.systemd.Network.Link.Reconfigure", }, }; /* Common implementation for 'simple' method calls that just take an ifindex, and nothing else. */ - const LinkBusAction *a = NULL; - FOREACH_ELEMENT(i, link_bus_action_table) + const LinkVarlinkAction *a = NULL; + FOREACH_ELEMENT(i, link_varlink_action_table) if (streq(argv[0], i->verb)) { a = i; break; @@ -134,27 +106,21 @@ int verb_link_bus_simple_method(int argc, char *argv[], uintptr_t _data, void *u if (r < 0) return r; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - r = acquire_bus(&bus); + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL; + r = varlink_connect_networkd(&vl); if (r < 0) return r; (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); void *p; - ORDERED_SET_FOREACH(p, indexes) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int index = PTR_TO_INT(p); - - r = bus_call_method(bus, bus_network_mgr, a->bus_method, &error, /* ret_reply= */ NULL, "i", index); - if (r < 0) { - RET_GATHER(ret, r); - log_error_errno(r, "%s %s: %s", - a->error_message, - FORMAT_IFNAME_FULL(index, FORMAT_IFNAME_IFINDEX), - bus_error_message(&error, r)); - } - } + ORDERED_SET_FOREACH(p, indexes) + RET_GATHER(ret, varlink_callbo_and_log( + vl, + a->method, + /* reply= */ NULL, + SD_JSON_BUILD_PAIR_INTEGER("InterfaceIndex", PTR_TO_INT(p)), + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password))); return ret; } diff --git a/src/network/networkctl-misc.h b/src/network/networkctl-misc.h index 092177275d746..771761d05f268 100644 --- a/src/network/networkctl-misc.h +++ b/src/network/networkctl-misc.h @@ -3,8 +3,7 @@ #include "shared-forward.h" -int verb_link_up_down(int argc, char *argv[], uintptr_t _data, void *userdata); int verb_link_delete(int argc, char *argv[], uintptr_t _data, void *userdata); -int verb_link_bus_simple_method(int argc, char *argv[], uintptr_t _data, void *userdata); +int verb_link_varlink_simple_method(int argc, char *argv[], uintptr_t _data, void *userdata); int verb_reload(int argc, char *argv[], uintptr_t _data, void *userdata); int verb_persistent_storage(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/network/networkctl.c b/src/network/networkctl.c index 91379cc9308f5..7fe34ac1eb778 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -223,22 +223,22 @@ static int parse_argv(int argc, char *argv[]) { static int networkctl_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "list", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_ONLINE_ONLY, verb_list_links }, - { "status", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_link_status }, - { "lldp", VERB_ANY, VERB_ANY, 0, verb_link_lldp_status }, - { "label", 1, 1, 0, verb_list_address_labels }, - { "delete", 2, VERB_ANY, 0, verb_link_delete }, - { "up", 2, VERB_ANY, 0, verb_link_up_down }, - { "down", 2, VERB_ANY, 0, verb_link_up_down }, - { "renew", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_link_bus_simple_method }, - { "forcerenew", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_link_bus_simple_method }, - { "reconfigure", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_link_bus_simple_method }, - { "reload", 1, 1, VERB_ONLINE_ONLY, verb_reload }, - { "edit", 2, VERB_ANY, 0, verb_edit }, - { "cat", 1, VERB_ANY, 0, verb_cat }, - { "mask", 2, VERB_ANY, 0, verb_mask }, - { "unmask", 2, VERB_ANY, 0, verb_unmask }, - { "persistent-storage", 2, 2, 0, verb_persistent_storage }, + { "list", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_ONLINE_ONLY, verb_list_links }, + { "status", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_link_status }, + { "lldp", VERB_ANY, VERB_ANY, 0, verb_link_lldp_status }, + { "label", 1, 1, 0, verb_list_address_labels }, + { "delete", 2, VERB_ANY, 0, verb_link_delete }, + { "up", 2, VERB_ANY, 0, verb_link_varlink_simple_method }, + { "down", 2, VERB_ANY, 0, verb_link_varlink_simple_method }, + { "renew", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_link_varlink_simple_method }, + { "forcerenew", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_link_varlink_simple_method }, + { "reconfigure", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_link_varlink_simple_method }, + { "reload", 1, 1, VERB_ONLINE_ONLY, verb_reload }, + { "edit", 2, VERB_ANY, 0, verb_edit }, + { "cat", 1, VERB_ANY, 0, verb_cat }, + { "mask", 2, VERB_ANY, 0, verb_mask }, + { "unmask", 2, VERB_ANY, 0, verb_unmask }, + { "persistent-storage", 2, 2, 0, verb_persistent_storage }, {} }; From 6627dfe425e18d38c314c393f12ab43004ecce62 Mon Sep 17 00:00:00 2001 From: noxiouz Date: Sun, 22 Feb 2026 15:29:44 +0000 Subject: [PATCH 0340/1296] network: implement io.systemd.service.Reload() in networkd Bind networkd's reload handler to the generic io.systemd.service.Reload method. Co-developed-by: Claude Opus 4.6 --- src/network/networkd-manager-varlink.c | 33 +++++++++++++++++++++++++ src/shared/varlink-io.systemd.Network.c | 2 ++ 2 files changed, 35 insertions(+) diff --git a/src/network/networkd-manager-varlink.c b/src/network/networkd-manager-varlink.c index 6660f03df1312..033dc87fbbae4 100644 --- a/src/network/networkd-manager-varlink.c +++ b/src/network/networkd-manager-varlink.c @@ -236,6 +236,38 @@ static int vl_method_set_persistent_storage(sd_varlink *vlink, sd_json_variant * return sd_varlink_reply(vlink, NULL); } +static int vl_method_reload(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + Manager *m = ASSERT_PTR(userdata); + int r; + + assert(vlink); + + if (m->reloading > 0) + return sd_varlink_error(vlink, "io.systemd.Network.AlreadyReloading", NULL); + + r = sd_varlink_dispatch(vlink, parameters, dispatch_table_polkit_only, /* userdata= */ NULL); + if (r != 0) + return r; + + r = varlink_verify_polkit_async( + vlink, + m->bus, + "org.freedesktop.network1.reload", + /* details= */ NULL, + &m->polkit_registry); + if (r <= 0) + return r; + + r = manager_reload(m, /* message= */ NULL, vlink); + if (r < 0) + return log_error_errno(r, "Failed to reload: %m"); + + if (m->reloading > 0) + return 0; /* Reply will be sent asynchronously. */ + + return sd_varlink_reply(vlink, NULL); +} + int manager_varlink_init(Manager *m, int fd) { _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; _unused_ _cleanup_close_ int fd_close = fd; /* take possession */ @@ -277,6 +309,7 @@ int manager_varlink_init(Manager *m, int fd) { "io.systemd.Network.Link.ForceRenew", vl_method_link_force_renew, "io.systemd.Network.Link.Reconfigure", vl_method_link_reconfigure, "io.systemd.service.Ping", varlink_method_ping, + "io.systemd.service.Reload", vl_method_reload, "io.systemd.service.SetLogLevel", varlink_method_set_log_level, "io.systemd.service.GetEnvironment", varlink_method_get_environment); if (r < 0) diff --git a/src/shared/varlink-io.systemd.Network.c b/src/shared/varlink-io.systemd.Network.c index 47004fc959eea..27b27c055c307 100644 --- a/src/shared/varlink-io.systemd.Network.c +++ b/src/shared/varlink-io.systemd.Network.c @@ -602,6 +602,7 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_FIELD_COMMENT("Whether persistent storage is ready and writable"), SD_VARLINK_DEFINE_INPUT(Ready, SD_VARLINK_BOOL, 0)); +static SD_VARLINK_DEFINE_ERROR(AlreadyReloading); static SD_VARLINK_DEFINE_ERROR(StorageReadOnly); SD_VARLINK_DEFINE_INTERFACE( @@ -641,4 +642,5 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_Route, &vl_type_RoutingPolicyRule, &vl_type_SIP, + &vl_error_AlreadyReloading, &vl_error_StorageReadOnly); From 54e8ea6552fa707dcbe5aca0cf28b422eba0c5ef Mon Sep 17 00:00:00 2001 From: noxiouz Date: Sun, 22 Feb 2026 15:29:44 +0000 Subject: [PATCH 0341/1296] networkctl: use io.systemd.service.Reload() method Co-authored-by: Yu Watanabe Co-developed-by: Claude Opus 4.6 --- src/network/networkctl-config-file.c | 63 +++------------------------- src/network/networkctl-misc.c | 19 +-------- src/network/networkctl-util.c | 36 ++++++++++++++++ src/network/networkctl-util.h | 2 + 4 files changed, 44 insertions(+), 76 deletions(-) diff --git a/src/network/networkctl-config-file.c b/src/network/networkctl-config-file.c index 5674b630a4ba4..43729c37653db 100644 --- a/src/network/networkctl-config-file.c +++ b/src/network/networkctl-config-file.c @@ -2,17 +2,12 @@ #include -#include "sd-bus.h" #include "sd-daemon.h" #include "sd-device.h" #include "sd-netlink.h" #include "sd-network.h" #include "alloc-util.h" -#include "bus-error.h" -#include "bus-locator.h" -#include "bus-util.h" -#include "bus-wait-for-jobs.h" #include "conf-files.h" #include "edit-util.h" #include "errno-util.h" @@ -395,48 +390,8 @@ static int add_config_to_edit( return edit_files_add(context, dropin_path, old_dropin, comment_paths); } -static int udevd_reload(sd_bus *bus) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; - const char *job_path; - int r; - - assert(bus); - - r = bus_wait_for_jobs_new(bus, &w); - if (r < 0) - return log_error_errno(r, "Could not watch jobs: %m"); - - r = bus_call_method(bus, - bus_systemd_mgr, - "ReloadUnit", - &error, - &reply, - "ss", - "systemd-udevd.service", - "replace"); - if (r < 0) - return log_error_errno(r, "Failed to reload systemd-udevd: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "o", &job_path); - if (r < 0) - return bus_log_parse_error(r); - - r = bus_wait_for_jobs_one(w, job_path, /* flags= */ 0, NULL); - if (r == -ENOEXEC) { - log_debug("systemd-udevd is not running, skipping reload."); - return 0; - } - if (r < 0) - return log_error_errno(r, "Failed to reload systemd-udevd: %m"); - - return 1; -} - static int reload_daemons(ReloadFlags flags) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r, ret = 1; + int ret = 1; if (arg_no_reload) return 0; @@ -449,22 +404,14 @@ static int reload_daemons(ReloadFlags flags) { return 0; } - r = sd_bus_open_system(&bus); - if (r < 0) - return log_error_errno(r, "Failed to connect to system bus: %m"); - if (FLAGS_SET(flags, RELOAD_UDEVD)) - RET_GATHER(ret, udevd_reload(bus)); + RET_GATHER(ret, reload_udevd()); if (FLAGS_SET(flags, RELOAD_NETWORKD)) { - if (networkd_is_running()) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - - r = bus_call_method(bus, bus_network_mgr, "Reload", &error, NULL, NULL); - if (r < 0) - RET_GATHER(ret, log_error_errno(r, "Failed to reload systemd-networkd: %s", bus_error_message(&error, r))); - } else + if (!networkd_is_running()) log_debug("systemd-networkd is not running, skipping reload."); + else + RET_GATHER(ret, reload_networkd()); } return ret; diff --git a/src/network/networkctl-misc.c b/src/network/networkctl-misc.c index 2596c144093cc..0436346bc863e 100644 --- a/src/network/networkctl-misc.c +++ b/src/network/networkctl-misc.c @@ -1,10 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "sd-bus.h" #include "sd-netlink.h" -#include "bus-error.h" -#include "bus-locator.h" #include "bus-util.h" #include "errno-util.h" #include "fd-util.h" @@ -126,21 +123,7 @@ int verb_link_varlink_simple_method(int argc, char *argv[], uintptr_t _data, voi } int verb_reload(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - - r = bus_call_method(bus, bus_network_mgr, "Reload", &error, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to reload network settings: %s", bus_error_message(&error, r)); - - return 0; + return reload_networkd(); } int verb_persistent_storage(int argc, char *argv[], uintptr_t _data, void *userdata) { diff --git a/src/network/networkctl-util.c b/src/network/networkctl-util.c index 00996e689f7d1..b180dee0b6f77 100644 --- a/src/network/networkctl-util.c +++ b/src/network/networkctl-util.c @@ -7,9 +7,11 @@ #include "alloc-util.h" #include "ansi-color.h" +#include "bus-util.h" #include "log.h" #include "networkctl.h" #include "networkctl-util.h" +#include "polkit-agent.h" #include "stdio-util.h" #include "string-util.h" #include "strv.h" @@ -62,6 +64,40 @@ int varlink_connect_networkd(sd_varlink **ret_varlink) { return 0; } +int reload_networkd(void) { + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL; + int r; + + r = varlink_connect_networkd(&vl); + if (r < 0) + return r; + + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + + return varlink_callbo_and_log( + vl, + "io.systemd.service.Reload", + /* reply= */ NULL, + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); +} + +int reload_udevd(void) { + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL; + int r; + + r = sd_varlink_connect_address(&vl, "/run/udev/io.systemd.Udev"); + if (r == -ENOENT) { + log_debug("systemd-udevd is not running, skipping reload."); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to connect to udev: %m"); + + (void) sd_varlink_set_description(vl, "udev"); + + return varlink_call_and_log(vl, "io.systemd.service.Reload", /* parameters= */ NULL, /* reply= */ NULL); +} + bool networkd_is_running(void) { static int cached = -1; int r; diff --git a/src/network/networkctl-util.h b/src/network/networkctl-util.h index b2721f5ea2d83..ee05ef6b10476 100644 --- a/src/network/networkctl-util.h +++ b/src/network/networkctl-util.h @@ -4,6 +4,8 @@ #include "shared-forward.h" int varlink_connect_networkd(sd_varlink **ret_varlink); +int reload_networkd(void); +int reload_udevd(void); bool networkd_is_running(void); int acquire_bus(sd_bus **ret); int link_get_property( From e90ea94392b3a76595e8c876ef75618d38a049aa Mon Sep 17 00:00:00 2001 From: noxiouz Date: Sun, 22 Feb 2026 15:17:37 +0000 Subject: [PATCH 0342/1296] network: add io.systemd.Network.Link.Describe() Varlink method The handler returns the JSON produced by link_build_json(). Co-developed-by: Claude Opus 4.6 --- src/network/networkd-link-varlink.c | 22 +++++++++ src/network/networkd-link-varlink.h | 1 + src/network/networkd-manager-varlink.c | 1 + src/shared/varlink-io.systemd.Network.Link.c | 38 +++++++++++++++- src/shared/varlink-io.systemd.Network.c | 48 ++++++++++---------- src/shared/varlink-io.systemd.Network.h | 24 ++++++++++ 6 files changed, 109 insertions(+), 25 deletions(-) diff --git a/src/network/networkd-link-varlink.c b/src/network/networkd-link-varlink.c index 2010553ead430..5864132ef82fc 100644 --- a/src/network/networkd-link-varlink.c +++ b/src/network/networkd-link-varlink.c @@ -6,6 +6,7 @@ #include "bus-polkit.h" #include "json-util.h" #include "networkd-dhcp4.h" +#include "networkd-json.h" #include "networkd-link.h" #include "networkd-link-varlink.h" #include "networkd-manager.h" @@ -67,6 +68,27 @@ int dispatch_link(sd_varlink *vlink, sd_json_variant *parameters, Manager *manag return 0; } +int vl_method_link_describe(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + Manager *manager = ASSERT_PTR(userdata); + Link *link; + int r; + + assert(vlink); + + r = dispatch_link(vlink, parameters, manager, DISPATCH_LINK_MANDATORY, &link); + if (r != 0) + return r; + + r = link_build_json(link, &v); + if (r < 0) + return log_link_error_errno(link, r, "Failed to format JSON data: %m"); + + return sd_varlink_replybo( + vlink, + SD_JSON_BUILD_PAIR_VARIANT("Interface", v)); +} + static int vl_method_link_up_or_down(sd_varlink *vlink, sd_json_variant *parameters, Manager *manager, bool up) { Link *link; int r; diff --git a/src/network/networkd-link-varlink.h b/src/network/networkd-link-varlink.h index b23b9717e3319..7be68fee46582 100644 --- a/src/network/networkd-link-varlink.h +++ b/src/network/networkd-link-varlink.h @@ -10,6 +10,7 @@ typedef enum DispatchLinkFlag { int dispatch_link(sd_varlink *vlink, sd_json_variant *parameters, Manager *manager, DispatchLinkFlag flags, Link **ret); +int vl_method_link_describe(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_link_up(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_link_down(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_link_renew(sd_varlink *vlink, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/network/networkd-manager-varlink.c b/src/network/networkd-manager-varlink.c index 033dc87fbbae4..71ec695339632 100644 --- a/src/network/networkd-manager-varlink.c +++ b/src/network/networkd-manager-varlink.c @@ -303,6 +303,7 @@ int manager_varlink_init(Manager *m, int fd) { "io.systemd.Network.GetNamespaceId", vl_method_get_namespace_id, "io.systemd.Network.GetLLDPNeighbors", vl_method_get_lldp_neighbors, "io.systemd.Network.SetPersistentStorage", vl_method_set_persistent_storage, + "io.systemd.Network.Link.Describe", vl_method_link_describe, "io.systemd.Network.Link.Up", vl_method_link_up, "io.systemd.Network.Link.Down", vl_method_link_down, "io.systemd.Network.Link.Renew", vl_method_link_renew, diff --git a/src/shared/varlink-io.systemd.Network.Link.c b/src/shared/varlink-io.systemd.Network.Link.c index 43dd640be8ddf..5474e5e475393 100644 --- a/src/shared/varlink-io.systemd.Network.Link.c +++ b/src/shared/varlink-io.systemd.Network.Link.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "bus-polkit.h" +#include "varlink-io.systemd.Network.h" #include "varlink-io.systemd.Network.Link.h" #define VARLINK_NETWORK_INTERFACE_INPUTS \ @@ -9,6 +10,12 @@ SD_VARLINK_FIELD_COMMENT("Name of the interface. If specified together with InterfaceIndex, both must reference the same link."), \ SD_VARLINK_DEFINE_INPUT(InterfaceName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE) +static SD_VARLINK_DEFINE_METHOD( + Describe, + VARLINK_NETWORK_INTERFACE_INPUTS, + SD_VARLINK_FIELD_COMMENT("Interface description"), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(Interface, Interface, 0)); + static SD_VARLINK_DEFINE_METHOD( Up, VARLINK_NETWORK_INTERFACE_INPUTS, @@ -46,4 +53,33 @@ SD_VARLINK_DEFINE_INTERFACE( SD_VARLINK_SYMBOL_COMMENT("Force-renew DHCP server leases on the specified link."), &vl_method_ForceRenew, SD_VARLINK_SYMBOL_COMMENT("Unconditionally reconfigure the specified link."), - &vl_method_Reconfigure); + &vl_method_Reconfigure, + SD_VARLINK_SYMBOL_COMMENT("Describe the specified link by index or name."), + &vl_method_Describe, + &vl_type_Address, + &vl_type_BitRates, + &vl_type_DHCPLease, + &vl_type_DHCPServer, + &vl_type_DHCPServerLease, + &vl_type_DHCPv6Client, + &vl_type_DHCPv6ClientPD, + &vl_type_DHCPv6ClientVendorOption, + &vl_type_DNS, + &vl_type_DNSSECNegativeTrustAnchor, + &vl_type_DNSSetting, + &vl_type_Domain, + &vl_type_Interface, + &vl_type_LinkState, + &vl_type_LinkAddressState, + &vl_type_LinkOnlineState, + &vl_type_LinkRequiredAddressFamily, + &vl_type_LLDPNeighbor, + &vl_type_NDisc, + &vl_type_Neighbor, + &vl_type_NextHop, + &vl_type_NextHopGroup, + &vl_type_NTP, + &vl_type_Pref64, + &vl_type_PrivateOption, + &vl_type_Route, + &vl_type_SIP); diff --git a/src/shared/varlink-io.systemd.Network.c b/src/shared/varlink-io.systemd.Network.c index 27b27c055c307..e98d517cc7ec4 100644 --- a/src/shared/varlink-io.systemd.Network.c +++ b/src/shared/varlink-io.systemd.Network.c @@ -9,7 +9,7 @@ SD_VARLINK_FIELD_COMMENT(comment " (human-readable format)"), \ SD_VARLINK_DEFINE_FIELD(field_name##String, SD_VARLINK_STRING, (flags)) -static SD_VARLINK_DEFINE_ENUM_TYPE( +SD_VARLINK_DEFINE_ENUM_TYPE( LinkState, SD_VARLINK_DEFINE_ENUM_VALUE(pending), SD_VARLINK_DEFINE_ENUM_VALUE(initialized), @@ -93,7 +93,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Configuration state of this rule"), SD_VARLINK_DEFINE_FIELD(ConfigState, SD_VARLINK_STRING, 0)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( Route, SD_VARLINK_FIELD_COMMENT("Address family (AF_INET or AF_INET6)"), SD_VARLINK_DEFINE_FIELD(Family, SD_VARLINK_INT, 0), @@ -149,14 +149,14 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("TCP congestion control algorithm for this route"), SD_VARLINK_DEFINE_FIELD(TCPCongestionControlAlgorithm, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( NextHopGroup, SD_VARLINK_FIELD_COMMENT("Next hop identifier in the multipath group"), SD_VARLINK_DEFINE_FIELD(ID, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("Weight for load balancing (higher means more traffic)"), SD_VARLINK_DEFINE_FIELD(Weight, SD_VARLINK_INT, 0)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( NextHop, SD_VARLINK_FIELD_COMMENT("Next hop identifier"), SD_VARLINK_DEFINE_FIELD(ID, SD_VARLINK_INT, 0), @@ -181,7 +181,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Configuration state of this next hop"), SD_VARLINK_DEFINE_FIELD(ConfigState, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( LLDPNeighbor, SD_VARLINK_FIELD_COMMENT("Chassis identifier in human-readable format"), SD_VARLINK_DEFINE_FIELD(ChassisID, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), @@ -204,7 +204,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("VLAN identifier"), SD_VARLINK_DEFINE_FIELD(VlanID, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( DNS, SD_VARLINK_FIELD_COMMENT("Address family (AF_INET or AF_INET6)"), SD_VARLINK_DEFINE_FIELD(Family, SD_VARLINK_INT, 0), @@ -219,7 +219,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(ConfigSource, SD_VARLINK_STRING, 0), VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(ConfigProvider, "Address of the configuration provider", SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( NTP, SD_VARLINK_FIELD_COMMENT("Address family (AF_INET or AF_INET6) for address-based servers"), SD_VARLINK_DEFINE_FIELD(Family, SD_VARLINK_INT, SD_VARLINK_NULLABLE), @@ -230,7 +230,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(ConfigSource, SD_VARLINK_STRING, 0), VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(ConfigProvider, "Address of the configuration provider", SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( SIP, SD_VARLINK_FIELD_COMMENT("Address family (AF_INET or AF_INET6) for address-based servers"), SD_VARLINK_DEFINE_FIELD(Family, SD_VARLINK_INT, SD_VARLINK_NULLABLE), @@ -241,7 +241,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(ConfigSource, SD_VARLINK_STRING, 0), VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(ConfigProvider, "Address of the configuration provider", SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( Domain, SD_VARLINK_FIELD_COMMENT("DNS search or route domain name"), SD_VARLINK_DEFINE_FIELD(Domain, SD_VARLINK_STRING, 0), @@ -249,14 +249,14 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(ConfigSource, SD_VARLINK_STRING, 0), VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(ConfigProvider, "Address of the configuration provider", SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( DNSSECNegativeTrustAnchor, SD_VARLINK_FIELD_COMMENT("Domain name for which DNSSEC validation is disabled"), SD_VARLINK_DEFINE_FIELD(DNSSECNegativeTrustAnchor, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("Configuration source for this negative trust anchor"), SD_VARLINK_DEFINE_FIELD(ConfigSource, SD_VARLINK_STRING, 0)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( DNSSetting, SD_VARLINK_FIELD_COMMENT("Link-Local Multicast Name Resolution setting"), SD_VARLINK_DEFINE_FIELD(LLMNR, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), @@ -269,7 +269,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Configuration source for this DNS setting"), SD_VARLINK_DEFINE_FIELD(ConfigSource, SD_VARLINK_STRING, 0)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( Pref64, SD_VARLINK_FIELD_COMMENT("IPv6 prefix for NAT64/DNS64"), SD_VARLINK_DEFINE_FIELD(Prefix, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), @@ -281,12 +281,12 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(LifetimeUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), VARLINK_DEFINE_IN_ADDR_WITH_STRING_FIELD(ConfigProvider, "Address of router that provided this prefix", SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( NDisc, SD_VARLINK_FIELD_COMMENT("PREF64 (RFC8781) prefixes advertised via IPv6 Router Advertisements"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(PREF64, Pref64, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( Address, SD_VARLINK_FIELD_COMMENT("Address family (AF_INET or AF_INET6)"), SD_VARLINK_DEFINE_FIELD(Family, SD_VARLINK_INT, 0), @@ -319,7 +319,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Configuration state of this address"), SD_VARLINK_DEFINE_FIELD(ConfigState, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( Neighbor, SD_VARLINK_FIELD_COMMENT("Address family (AF_INET or AF_INET6)"), SD_VARLINK_DEFINE_FIELD(Family, SD_VARLINK_INT, 0), @@ -331,7 +331,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Configuration state of this neighbor entry"), SD_VARLINK_DEFINE_FIELD(ConfigState, SD_VARLINK_STRING, 0)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( DHCPLease, SD_VARLINK_FIELD_COMMENT("Timestamp when the lease was acquired in microseconds"), SD_VARLINK_DEFINE_FIELD(LeaseTimestampUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), @@ -342,14 +342,14 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Hostname received from DHCP server"), SD_VARLINK_DEFINE_FIELD(Hostname, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( PrivateOption, SD_VARLINK_FIELD_COMMENT("DHCP option number"), SD_VARLINK_DEFINE_FIELD(Option, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Raw data of the private DHCP option"), SD_VARLINK_DEFINE_FIELD(PrivateOptionData, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( DHCPv6ClientPD, SD_VARLINK_FIELD_COMMENT("Delegated IPv6 prefix"), SD_VARLINK_DEFINE_FIELD(Prefix, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), @@ -362,7 +362,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Valid lifetime of the prefix in microseconds"), SD_VARLINK_DEFINE_FIELD(ValidLifetimeUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( DHCPv6ClientVendorOption, SD_VARLINK_FIELD_COMMENT("IANA enterprise number identifying the vendor"), SD_VARLINK_DEFINE_FIELD(EnterpriseId, SD_VARLINK_INT, 0), @@ -371,7 +371,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Raw data of the vendor-specific sub-option"), SD_VARLINK_DEFINE_FIELD(SubOptionData, SD_VARLINK_STRING, 0)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( DHCPv6Client, SD_VARLINK_FIELD_COMMENT("DHCPv6 lease information including timestamps and timeouts"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Lease, DHCPLease, SD_VARLINK_NULLABLE), @@ -382,7 +382,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("DHCP Unique Identifier (DUID) of the client"), SD_VARLINK_DEFINE_FIELD(DUID, SD_VARLINK_INT, SD_VARLINK_ARRAY)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( DHCPServerLease, SD_VARLINK_FIELD_COMMENT("DHCP client identifier"), SD_VARLINK_DEFINE_FIELD(ClientId, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), @@ -400,7 +400,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Lease expiration time in realtime microseconds"), SD_VARLINK_DEFINE_FIELD(ExpirationRealtimeUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( DHCPServer, SD_VARLINK_FIELD_COMMENT("Offset from the network address for the DHCP address pool"), SD_VARLINK_DEFINE_FIELD(PoolOffset, SD_VARLINK_INT, 0), @@ -411,14 +411,14 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Static DHCP leases configured for specific clients"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(StaticLeases, DHCPServerLease, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( BitRates, SD_VARLINK_FIELD_COMMENT("Transmit bitrate in bits per second"), SD_VARLINK_DEFINE_FIELD(TxBitRate, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("Receive bitrate in bits per second"), SD_VARLINK_DEFINE_FIELD(RxBitRate, SD_VARLINK_INT, 0)); -static SD_VARLINK_DEFINE_STRUCT_TYPE( +SD_VARLINK_DEFINE_STRUCT_TYPE( Interface, SD_VARLINK_FIELD_COMMENT("Network interface index"), SD_VARLINK_DEFINE_FIELD(Index, SD_VARLINK_INT, 0), diff --git a/src/shared/varlink-io.systemd.Network.h b/src/shared/varlink-io.systemd.Network.h index 6d727cd9ee8c7..25d3bc9600f66 100644 --- a/src/shared/varlink-io.systemd.Network.h +++ b/src/shared/varlink-io.systemd.Network.h @@ -7,3 +7,27 @@ extern const sd_varlink_interface vl_interface_io_systemd_Network; extern const sd_varlink_symbol vl_type_LinkAddressState; extern const sd_varlink_symbol vl_type_LinkOnlineState; extern const sd_varlink_symbol vl_type_LinkRequiredAddressFamily; +extern const sd_varlink_symbol vl_type_LinkState; +extern const sd_varlink_symbol vl_type_Route; +extern const sd_varlink_symbol vl_type_NextHopGroup; +extern const sd_varlink_symbol vl_type_NextHop; +extern const sd_varlink_symbol vl_type_LLDPNeighbor; +extern const sd_varlink_symbol vl_type_DNS; +extern const sd_varlink_symbol vl_type_NTP; +extern const sd_varlink_symbol vl_type_SIP; +extern const sd_varlink_symbol vl_type_Domain; +extern const sd_varlink_symbol vl_type_DNSSECNegativeTrustAnchor; +extern const sd_varlink_symbol vl_type_DNSSetting; +extern const sd_varlink_symbol vl_type_Pref64; +extern const sd_varlink_symbol vl_type_NDisc; +extern const sd_varlink_symbol vl_type_Address; +extern const sd_varlink_symbol vl_type_Neighbor; +extern const sd_varlink_symbol vl_type_DHCPLease; +extern const sd_varlink_symbol vl_type_PrivateOption; +extern const sd_varlink_symbol vl_type_DHCPv6ClientPD; +extern const sd_varlink_symbol vl_type_DHCPv6ClientVendorOption; +extern const sd_varlink_symbol vl_type_DHCPv6Client; +extern const sd_varlink_symbol vl_type_DHCPServerLease; +extern const sd_varlink_symbol vl_type_DHCPServer; +extern const sd_varlink_symbol vl_type_BitRates; +extern const sd_varlink_symbol vl_type_Interface; From 689e45a6cd71d3b2b260a473e3e627ae4fa0e012 Mon Sep 17 00:00:00 2001 From: noxiouz Date: Sun, 22 Feb 2026 15:17:37 +0000 Subject: [PATCH 0343/1296] networkctl: use io.systemd.Network.Link.Describe() Varlink method This makes networkctl fetch bit-rate statistics and offered DHCP leases via Link.Describe() method instead of D-Bus. Co-authored-by: Yu Watanabe Co-developed-by: Claude Opus 4.6 --- src/network/networkctl-link-info.c | 52 ++++++------ src/network/networkctl-link-info.h | 3 +- src/network/networkctl-status-link.c | 117 +++++++++------------------ src/network/networkctl-util.c | 37 +++++++++ src/network/networkctl-util.h | 3 + 5 files changed, 106 insertions(+), 106 deletions(-) diff --git a/src/network/networkctl-link-info.c b/src/network/networkctl-link-info.c index 8f072f834ac70..05990bffbc83e 100644 --- a/src/network/networkctl-link-info.c +++ b/src/network/networkctl-link-info.c @@ -2,13 +2,10 @@ #include -#include "sd-bus.h" #include "sd-netlink.h" +#include "sd-json.h" #include "alloc-util.h" -#include "bus-common-errors.h" -#include "bus-error.h" -#include "bus-util.h" #include "device-util.h" #include "fd-util.h" #include "glob-util.h" @@ -28,6 +25,7 @@ LinkInfo* link_info_array_free(LinkInfo *array) { for (unsigned i = 0; array && array[i].needs_freeing; i++) { sd_device_unref(array[i].sd_device); + sd_json_variant_unref(array[i].description); free(array[i].netdev_kind); free(array[i].ssid); free(array[i].qdisc); @@ -280,33 +278,31 @@ static int decode_link( return 1; } -static int acquire_link_bitrates(sd_bus *bus, LinkInfo *link) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; +static int acquire_link_bitrates(LinkInfo *link) { int r; - assert(bus); assert(link); - r = link_get_property(bus, link->ifindex, &error, &reply, "org.freedesktop.network1.Link", "BitRates", "(tt)"); - if (r < 0) { - bool quiet = sd_bus_error_has_names(&error, SD_BUS_ERROR_UNKNOWN_PROPERTY, - BUS_ERROR_SPEED_METER_INACTIVE); - - return log_full_errno(quiet ? LOG_DEBUG : LOG_WARNING, - r, "Failed to query link bit rates: %s", bus_error_message(&error, r)); - } - - r = sd_bus_message_read(reply, "(tt)", &link->tx_bitrate, &link->rx_bitrate); + sd_json_variant *v; + r = json_variant_find_object(link->description, STRV_MAKE("Interface", "BitRates"), &v); + if (r == -ENODATA) + return 0; if (r < 0) - return bus_log_parse_error(r); + return r; - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); + static const sd_json_dispatch_field dispatch_table[] = { + { "TxBitRate", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LinkInfo, tx_bitrate), SD_JSON_MANDATORY }, + { "RxBitRate", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LinkInfo, rx_bitrate), SD_JSON_MANDATORY }, + {} + }; - link->has_bitrates = link->tx_bitrate != UINT64_MAX && link->rx_bitrate != UINT64_MAX; + r = sd_json_dispatch(v, dispatch_table, + SD_JSON_LOG | SD_JSON_WARNING | SD_JSON_ALLOW_EXTENSIONS, + link); + if (r < 0) + return r; + link->has_bitrates = true; return 0; } @@ -356,7 +352,7 @@ static void acquire_wlan_link_info(LinkInfo *link) { link->has_wlan_link_info = r > 0 || k > 0; } -int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char * const *patterns, LinkInfo **ret) { +int acquire_link_info(sd_varlink *vl, sd_netlink *rtnl, char * const *patterns, LinkInfo **ret) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; _cleanup_(link_info_array_freep) LinkInfo *links = NULL; _cleanup_free_ bool *matched_patterns = NULL; @@ -402,6 +398,10 @@ int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char * const *patterns, Lin acquire_ether_link_info(&fd, &links[c]); acquire_wlan_link_info(&links[c]); + if (vl) + (void) acquire_link_description(vl, links[c].ifindex, &links[c].description); + (void) acquire_link_bitrates(&links[c]); + c++; } @@ -421,10 +421,6 @@ int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char * const *patterns, Lin typesafe_qsort(links, c, link_info_compare); - if (bus) - FOREACH_ARRAY(link, links, c) - (void) acquire_link_bitrates(bus, link); - *ret = TAKE_PTR(links); if (patterns && c == 0) diff --git a/src/network/networkctl-link-info.h b/src/network/networkctl-link-info.h index 268798dc7fa1f..ebea57348a30d 100644 --- a/src/network/networkctl-link-info.h +++ b/src/network/networkctl-link-info.h @@ -35,6 +35,7 @@ typedef struct LinkInfo { char name[IFNAMSIZ+1]; char *netdev_kind; sd_device *sd_device; + sd_json_variant *description; int ifindex; unsigned short iftype; struct hw_addr_data hw_address; @@ -133,4 +134,4 @@ typedef struct LinkInfo { LinkInfo* link_info_array_free(LinkInfo *array); DEFINE_TRIVIAL_CLEANUP_FUNC(LinkInfo*, link_info_array_free); -int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char * const *patterns, LinkInfo **ret); +int acquire_link_info(sd_varlink *vl, sd_netlink *rtnl, char * const *patterns, LinkInfo **ret); diff --git a/src/network/networkctl-status-link.c b/src/network/networkctl-status-link.c index b73fceb91a200..f63ee2d4175d7 100644 --- a/src/network/networkctl-status-link.c +++ b/src/network/networkctl-status-link.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "sd-bus.h" #include "sd-device.h" #include "sd-dhcp-client-id.h" #include "sd-dhcp-lease.h" @@ -12,8 +11,6 @@ #include "alloc-util.h" #include "bond-util.h" #include "bridge-util.h" -#include "bus-error.h" -#include "bus-util.h" #include "errno-util.h" #include "escape.h" #include "extract-word.h" @@ -21,7 +18,9 @@ #include "format-util.h" #include "geneve-util.h" #include "glyph-util.h" +#include "iovec-util.h" #include "ipvlan-util.h" +#include "json-util.h" #include "macvlan-util.h" #include "netif-util.h" #include "network-internal.h" @@ -40,87 +39,58 @@ #include "time-util.h" #include "udev-util.h" -static int dump_dhcp_leases(Table *table, const char *prefix, sd_bus *bus, const LinkInfo *link) { +typedef struct LeaseInfo { + const char *address; + struct iovec client_id; +} LeaseInfo; + +static void lease_info_done(LeaseInfo *p) { + assert(p); + + iovec_done(&p->client_id); +} + +static int dump_dhcp_leases(Table *table, const char *prefix, const LinkInfo *link) { _cleanup_strv_free_ char **buf = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; assert(table); assert(prefix); - assert(bus); assert(link); - r = link_get_property(bus, link->ifindex, &error, &reply, "org.freedesktop.network1.DHCPServer", "Leases", "a(uayayayayt)"); - if (r < 0) { - bool quiet = sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_PROPERTY); - - log_full_errno(quiet ? LOG_DEBUG : LOG_WARNING, - r, "Failed to query link DHCP leases: %s", bus_error_message(&error, r)); + sd_json_variant *leases; + r = json_variant_find_object(link->description, STRV_MAKE("Interface", "DHCPServer", "Leases"), &leases); + if (r == -ENODATA) return 0; - } - - r = sd_bus_message_enter_container(reply, 'a', "(uayayayayt)"); if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "uayayayayt")) > 0) { - _cleanup_free_ char *id = NULL, *ip = NULL; - const void *client_id, *addr, *gtw, *hwaddr; - size_t client_id_sz, sz; - uint64_t expiration; - uint32_t family; - - r = sd_bus_message_read(reply, "u", &family); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read_array(reply, 'y', &client_id, &client_id_sz); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read_array(reply, 'y', &addr, &sz); - if (r < 0 || sz != 4) - return bus_log_parse_error(r); - - r = sd_bus_message_read_array(reply, 'y', >w, &sz); - if (r < 0 || sz != 4) - return bus_log_parse_error(r); + return r; - r = sd_bus_message_read_array(reply, 'y', &hwaddr, &sz); - if (r < 0) - return bus_log_parse_error(r); + static const sd_json_dispatch_field dispatch_table[] = { + { "AddressString", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LeaseInfo, address), SD_JSON_MANDATORY }, + { "ClientId", SD_JSON_VARIANT_ARRAY, json_dispatch_byte_array_iovec, offsetof(LeaseInfo, client_id), 0 }, + {} + }; - r = sd_bus_message_read_basic(reply, 't', &expiration); - if (r < 0) - return bus_log_parse_error(r); + sd_json_variant *lease; + JSON_VARIANT_ARRAY_FOREACH(lease, leases) { + _cleanup_(lease_info_done) LeaseInfo info = {}; + _cleanup_free_ char *client_id = NULL; - r = sd_dhcp_client_id_to_string_from_raw(client_id, client_id_sz, &id); + r = sd_json_dispatch(lease, dispatch_table, SD_JSON_LOG | SD_JSON_WARNING | SD_JSON_ALLOW_EXTENSIONS, &info); if (r < 0) - return bus_log_parse_error(r); + continue; - r = in_addr_to_string(family, addr, &ip); - if (r < 0) - return bus_log_parse_error(r); + if (info.client_id.iov_len > 0) + (void) sd_dhcp_client_id_to_string_from_raw(info.client_id.iov_base, info.client_id.iov_len, &client_id); - r = strv_extendf(&buf, "%s (to %s)", ip, id); + r = strv_extendf(&buf, "%s%s%s%s", + info.address, + client_id ? " (to " : "", + strempty(client_id), + client_id ? ")" : ""); if (r < 0) return log_oom(); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); if (strv_isempty(buf)) { r = strv_extendf(&buf, "none"); @@ -259,7 +229,6 @@ static int format_config_files(char ***files, const char *main_config) { } static int link_status_one( - sd_bus *bus, sd_netlink *rtnl, sd_hwdb *hwdb, sd_varlink *vl, @@ -276,7 +245,6 @@ static int link_status_one( _cleanup_(table_unrefp) Table *table = NULL; int r; - assert(bus); assert(rtnl); assert(vl); assert(info); @@ -919,7 +887,7 @@ static int link_status_one( if (r < 0) return r; - r = dump_dhcp_leases(table, "Offered DHCP leases", bus, info); + r = dump_dhcp_leases(table, "Offered DHCP leases", info); if (r < 0) return r; @@ -940,7 +908,6 @@ static int link_status_one( } int verb_link_status(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL; @@ -951,10 +918,6 @@ int verb_link_status(int argc, char *argv[], uintptr_t _data, void *userdata) { if (r != 0) return r; - r = acquire_bus(&bus); - if (r < 0) - return r; - pager_open(arg_pager_flags); r = sd_netlink_open(&rtnl); @@ -970,11 +933,11 @@ int verb_link_status(int argc, char *argv[], uintptr_t _data, void *userdata) { return r; if (arg_all) - c = acquire_link_info(bus, rtnl, NULL, &links); + c = acquire_link_info(vl, rtnl, NULL, &links); else if (argc <= 1) return system_status(rtnl, hwdb); else - c = acquire_link_info(bus, rtnl, argv + 1, &links); + c = acquire_link_info(vl, rtnl, argv + 1, &links); if (c < 0) return c; @@ -985,7 +948,7 @@ int verb_link_status(int argc, char *argv[], uintptr_t _data, void *userdata) { if (!first) putchar('\n'); - RET_GATHER(r, link_status_one(bus, rtnl, hwdb, vl, i)); + RET_GATHER(r, link_status_one(rtnl, hwdb, vl, i)); first = false; } diff --git a/src/network/networkctl-util.c b/src/network/networkctl-util.c index b180dee0b6f77..6c1db351aef9e 100644 --- a/src/network/networkctl-util.c +++ b/src/network/networkctl-util.c @@ -232,3 +232,40 @@ void online_state_to_color(const char *state, const char **on, const char **off) *off = ""; } } + +int acquire_link_description(sd_varlink *vl, int ifindex, sd_json_variant **ret) { + int r; + + assert(vl); + assert(ifindex > 0); + assert(ret); + + sd_json_variant *v; /* borrowed from vl, do not unref */ + r = varlink_callbo_and_log( + vl, + "io.systemd.Network.Link.Describe", + &v, + SD_JSON_BUILD_PAIR_INTEGER("InterfaceIndex", ifindex)); + if (r < 0) + return r; + + *ret = sd_json_variant_ref(v); + return 0; +} + +int json_variant_find_object(sd_json_variant *v, char * const *object_names, sd_json_variant **ret) { + assert(object_names); + assert(ret); + + if (!v || sd_json_variant_is_null(v)) + return -ENODATA; + + STRV_FOREACH(name, object_names) { + v = sd_json_variant_by_key(v, *name); + if (!v || sd_json_variant_is_null(v)) + return -ENODATA; + } + + *ret = v; + return 0; +} diff --git a/src/network/networkctl-util.h b/src/network/networkctl-util.h index ee05ef6b10476..df4ef403472fb 100644 --- a/src/network/networkctl-util.h +++ b/src/network/networkctl-util.h @@ -20,3 +20,6 @@ int link_get_property( void operational_state_to_color(const char *name, const char *state, const char **on, const char **off); void setup_state_to_color(const char *state, const char **on, const char **off); void online_state_to_color(const char *state, const char **on, const char **off); + +int acquire_link_description(sd_varlink *vl, int ifindex, sd_json_variant **ret); +int json_variant_find_object(sd_json_variant *v, char * const *object_names, sd_json_variant **ret); From 1416d3b69c172d83565c4084eca5b8a1fef4ed3d Mon Sep 17 00:00:00 2001 From: noxiouz Date: Tue, 24 Feb 2026 23:04:09 +0900 Subject: [PATCH 0344/1296] networkctl: drop unused functions Co-developed-by: Claude Opus 4.6 --- src/network/networkctl-util.c | 57 ----------------------------------- src/network/networkctl-util.h | 9 ------ 2 files changed, 66 deletions(-) diff --git a/src/network/networkctl-util.c b/src/network/networkctl-util.c index 6c1db351aef9e..590c8a6abc2fb 100644 --- a/src/network/networkctl-util.c +++ b/src/network/networkctl-util.c @@ -3,16 +3,12 @@ #include #include -#include "sd-bus.h" - -#include "alloc-util.h" #include "ansi-color.h" #include "bus-util.h" #include "log.h" #include "networkctl.h" #include "networkctl-util.h" #include "polkit-agent.h" -#include "stdio-util.h" #include "string-util.h" #include "strv.h" #include "varlink-util.h" @@ -117,59 +113,6 @@ bool networkd_is_running(void) { return cached; } -int acquire_bus(sd_bus **ret) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - assert(ret); - - r = sd_bus_open_system(&bus); - if (r < 0) - return log_error_errno(r, "Failed to connect to system bus: %m"); - - (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); - - if (networkd_is_running()) { - r = varlink_connect_networkd(/* ret_varlink= */ NULL); - if (r < 0) - return r; - } else - log_warning("systemd-networkd is not running, output might be incomplete."); - - *ret = TAKE_PTR(bus); - return 0; -} - -int link_get_property( - sd_bus *bus, - int ifindex, - sd_bus_error *error, - sd_bus_message **reply, - const char *iface, - const char *propname, - const char *type) { - - _cleanup_free_ char *path = NULL; - char ifindex_str[DECIMAL_STR_MAX(int)]; - int r; - - assert(bus); - assert(ifindex >= 0); - assert(error); - assert(reply); - assert(iface); - assert(propname); - assert(type); - - xsprintf(ifindex_str, "%i", ifindex); - - r = sd_bus_path_encode("/org/freedesktop/network1/link", ifindex_str, &path); - if (r < 0) - return r; - - return sd_bus_get_property(bus, "org.freedesktop.network1", path, iface, propname, error, reply, type); -} - void operational_state_to_color(const char *name, const char *state, const char **on, const char **off) { if (STRPTR_IN_SET(state, "routable", "enslaved") || (streq_ptr(name, "lo") && streq_ptr(state, "carrier"))) { diff --git a/src/network/networkctl-util.h b/src/network/networkctl-util.h index df4ef403472fb..6067602d1a0f5 100644 --- a/src/network/networkctl-util.h +++ b/src/network/networkctl-util.h @@ -7,15 +7,6 @@ int varlink_connect_networkd(sd_varlink **ret_varlink); int reload_networkd(void); int reload_udevd(void); bool networkd_is_running(void); -int acquire_bus(sd_bus **ret); -int link_get_property( - sd_bus *bus, - int ifindex, - sd_bus_error *error, - sd_bus_message **reply, - const char *iface, - const char *propname, - const char *type); void operational_state_to_color(const char *name, const char *state, const char **on, const char **off); void setup_state_to_color(const char *state, const char **on, const char **off); From 3d153bfeb64d6fd60218418facb1c5f8528b414d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 5 Mar 2026 16:13:34 +0100 Subject: [PATCH 0345/1296] vconsole-setup: add a bunch of assert()s --- src/vconsole/vconsole-setup.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/vconsole/vconsole-setup.c b/src/vconsole/vconsole-setup.c index f1039319fd872..0fb8cf0381ef3 100644 --- a/src/vconsole/vconsole-setup.c +++ b/src/vconsole/vconsole-setup.c @@ -155,6 +155,8 @@ static void context_load_config(Context *c) { } static int verify_vc_device(int fd) { + assert(fd >= 0); + unsigned char data[] = { TIOCL_GETFGCONSOLE, }; @@ -171,8 +173,9 @@ static int verify_vc_allocation(unsigned idx) { } static int verify_vc_allocation_byfd(int fd) { - struct vt_stat vcs = {}; + assert(fd >= 0); + struct vt_stat vcs = {}; if (ioctl(fd, VT_GETSTATE, &vcs) < 0) return -errno; @@ -375,8 +378,9 @@ static void setup_remaining_vcs(int src_fd, unsigned src_idx, bool utf8) { struct unimapdesc unimapd; _cleanup_free_ struct unipair* unipairs = NULL; _cleanup_free_ void *fontbuf = NULL; - int log_level = LOG_WARNING; - int r; + int log_level = LOG_WARNING, r; + + assert(src_fd >= 0); unipairs = new(struct unipair, USHRT_MAX); if (!unipairs) @@ -549,6 +553,8 @@ static int verify_source_vc(char **ret_path, const char *src_vc) { char *path; int r; + assert(ret_path); + fd = open_terminal(src_vc, O_RDWR|O_CLOEXEC|O_NOCTTY); if (fd < 0) return log_error_errno(fd, "Failed to open %s: %m", src_vc); From 3327a411be3ef4a203d23ae86e6c50b30d929d50 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 5 Mar 2026 16:13:39 +0100 Subject: [PATCH 0346/1296] vconsole-setup: handle gracefully if setfont/loadkeys are not available Let's not complain too loudly if these external binaries aren't there. --- src/vconsole/vconsole-setup.c | 54 ++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/src/vconsole/vconsole-setup.c b/src/vconsole/vconsole-setup.c index 0fb8cf0381ef3..e6da288e427eb 100644 --- a/src/vconsole/vconsole-setup.c +++ b/src/vconsole/vconsole-setup.c @@ -271,6 +271,14 @@ static int keyboard_load_and_wait(const char *vc, Context *c, bool utf8) { if (streq(keymap, "@kernel")) return 0; + if (access(KBD_LOADKEYS, X_OK) < 0) { + if (errno != ENOENT) + return log_error_errno(errno, "Failed to check if '" KBD_LOADKEYS "' is available: %m"); + + log_notice("'" KBD_LOADKEYS "' is not available, skipping keyboard mapping setup."); + return 0; /* Report that we skipped this */ + } + args[i++] = KBD_LOADKEYS; args[i++] = "-q"; args[i++] = "-C"; @@ -298,7 +306,13 @@ static int keyboard_load_and_wait(const char *vc, Context *c, bool utf8) { _exit(EXIT_FAILURE); } - return pidref_wait_for_terminate_and_check(KBD_LOADKEYS, &pidref, WAIT_LOG); + r = pidref_wait_for_terminate_and_check(KBD_LOADKEYS, &pidref, WAIT_LOG); + if (r < 0) + return r; + if (r != EXIT_SUCCESS) + return -EPROTO; + + return 1; /* Report that we did something */ } static int font_load_and_wait(const char *vc, Context *c) { @@ -318,6 +332,14 @@ static int font_load_and_wait(const char *vc, Context *c) { if (!font && !font_map && !font_unimap) return 0; + if (access(KBD_SETFONT, X_OK) < 0) { + if (errno != ENOENT) + return log_error_errno(errno, "Failed to check if '" KBD_SETFONT "' is available: %m"); + + log_notice("'" KBD_SETFONT "' is not available, skipping console font setup."); + return 0; /* Report that we skipped this */ + } + args[i++] = KBD_SETFONT; args[i++] = "-C"; args[i++] = vc; @@ -353,12 +375,16 @@ static int font_load_and_wait(const char *vc, Context *c) { * things, but in particular lack of a graphical console. Let's be generous and not treat this as an * error. */ r = pidref_wait_for_terminate_and_check(KBD_SETFONT, &pidref, WAIT_LOG_ABNORMAL); - if (r == EX_OSERR) + if (r < 0) + return r; /* WAIT_LOG_ABNORMAL means we already have logged about these kinds of errors */ + if (r == EX_OSERR) { log_notice(KBD_SETFONT " failed with a \"system error\" (EX_OSERR), ignoring."); - else if (r >= 0 && r != EXIT_SUCCESS) - log_error(KBD_SETFONT " failed with exit status %i.", r); + return 0; /* Report that we skipped this */ + } + if (r != EXIT_SUCCESS) + return log_error_errno(SYNTHETIC_ERRNO(EPROTO), KBD_SETFONT " failed with exit status %i.", r); - return r; + return 1; /* Report that we did something */ } /* @@ -590,9 +616,8 @@ static int run(int argc, char **argv) { _cleanup_(context_done) Context c = {}; _cleanup_free_ char *vc = NULL; _cleanup_close_ int fd = -EBADF, lock_fd = -EBADF; - bool utf8, keyboard_ok; + bool utf8; unsigned idx = 0; - int r; log_setup(); @@ -631,18 +656,19 @@ static int run(int argc, char **argv) { (void) toggle_utf8_vc(vc, fd, utf8); - r = font_load_and_wait(vc, &c); - keyboard_ok = keyboard_load_and_wait(vc, &c, utf8) == 0; + int setfont_status = font_load_and_wait(vc, &c); + int loadkeys_status = keyboard_load_and_wait(vc, &c, utf8); if (idx > 0) { - if (r == 0) - setup_remaining_vcs(fd, idx, utf8); + if (setfont_status == 0) + log_notice("Configuration of first virtual console was skipped, ignoring remaining ones."); + else if (setfont_status < 0) + log_warning("Configuration of first virtual console failed, ignoring remaining ones."); else - log_full(r == EX_OSERR ? LOG_NOTICE : LOG_WARNING, - "Configuration of first virtual console failed, ignoring remaining ones."); + setup_remaining_vcs(fd, idx, utf8); } - return IN_SET(r, 0, EX_OSERR) && keyboard_ok ? EXIT_SUCCESS : EXIT_FAILURE; + return (setfont_status >= 0 && loadkeys_status >= 0) ? EXIT_SUCCESS : EXIT_FAILURE; } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); From 1c8b460b16a18e842edc06d8bc5fb2af5966f323 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 19 Mar 2026 10:34:04 +0100 Subject: [PATCH 0347/1296] ci: Have claude spend more effort on reviews Let's give this a try and see how it impacts reviews (and cost). --- .github/workflows/claude-review.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 1aa89bb076b5e..9ac8de9a4d337 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -207,6 +207,7 @@ jobs: } claude_args: | --model us.anthropic.claude-opus-4-6-v1 + --effort max --max-turns 200 --disallowedTools "WebFetch,WebSearch" --setting-sources user From 2c5e0cfb1dcc320a7d58aded55c2c8265d342562 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 19 Mar 2026 11:12:37 +0100 Subject: [PATCH 0348/1296] ci: Instruct claude to not do any escaping for review comments Should hopefully fix cases like https://github.com/systemd/systemd/pull/40780#discussion_r2956841573. --- .github/workflows/claude-review.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 9ac8de9a4d337..647a6776c459a 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -268,6 +268,9 @@ jobs: The `commit` field MUST be the SHA of the commit being reviewed. Only comment on changes in that commit — not preceding commits. + Do NOT escape characters in `body`. Write plain markdown — no backslash + escaping of `!` or other characters. + `line` should be a line number from the NEW side of the diff **that appears inside a diff hunk**. GitHub rejects lines outside the diff context. If you cannot determine a valid diff line, omit `line`. From 2c018188164cdf14b9deb4e1da08714d7e8a387d Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 19 Mar 2026 11:34:25 +0100 Subject: [PATCH 0349/1296] ci: Update prompt to reduce time spent re-checking comments I noticed looking at the logs that claude spends a lot of time re-checking existing comments, so let's update the prompt to hopefully reduce the amount of comments that it re-checks. --- .github/workflows/claude-review.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 647a6776c459a..88d74295c2bd3 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -232,8 +232,15 @@ jobs: - `issue_comments` — array of issue comments on the PR from the GitHub API - `tracking_comment` — body of the existing tracking comment (null on first run); if present, use it as the basis for your `summary` in Phase 3 - - `review_comments` — array of inline review comments from the GitHub API; - you will need the `id` fields in Phase 3 to populate the `resolve` array + - `review_comments` — array of ALL inline review comments on the PR from the + GitHub API. Use these as context, but observe the following rules: + - Only re-check your own comments (user.login == "github-actions[bot]" and + body starts with "Claude: "). Do NOT validate, re-raise, respond to, or + duplicate comments from other authors. + - Items checked off in the tracking comment (`- [x]`) are resolved. Do NOT + re-check or re-raise review comments that correspond to resolved items. + - You will need the `id` fields of your own unresolved comments in Phase 3 + to populate the `resolve` array. The PR branch has been fetched locally as `pr-review`. Use `git log --reverse --format=%H HEAD..pr-review` to list the PR commits, and From a0eae2ab7b45d22d67487279564f604206149143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 19 Mar 2026 11:35:00 +0100 Subject: [PATCH 0350/1296] Stop disabling -Wattributes In one of the reviews one of the LLMs noticed that the pragma is set but never unset, so it remains in effect for the rest of the translation unit. From the comment, it's not clear how old those "old compilers" were, so let's try if things work without this workaround. --- src/basic/static-destruct.h | 2 -- src/libsystemd/sd-bus/bus-error.h | 4 +--- src/shared/tests.h | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/basic/static-destruct.h b/src/basic/static-destruct.h index 5772d24240f88..00087ad779e0a 100644 --- a/src/basic/static-destruct.h +++ b/src/basic/static-destruct.h @@ -12,8 +12,6 @@ typedef void (*free_func_t)(void *p); * variables declared in .so's, as the list is private to the same linking unit. But maybe that's a good thing. */ #define _common_static_destruct_attrs_ \ - /* Older compilers don't know "retain" attribute. */ \ - _Pragma("GCC diagnostic ignored \"-Wattributes\"") \ /* The actual destructor structure we place in a special section to find it. */ \ _section_("SYSTEMD_STATIC_DESTRUCT") \ /* Use pointer alignment, since that is apparently what gcc does for static variables. */ \ diff --git a/src/libsystemd/sd-bus/bus-error.h b/src/libsystemd/sd-bus/bus-error.h index ac3c90c0d317e..5aca67f006578 100644 --- a/src/libsystemd/sd-bus/bus-error.h +++ b/src/libsystemd/sd-bus/bus-error.h @@ -31,12 +31,10 @@ const char* _bus_error_message(const sd_bus_error *e, int error, char buf[static * the error map is really added to the final binary. * * In addition, set the retain attribute so that the section cannot be - * discarded by ld --gc-sections -z start-stop-gc. Older compilers would - * warn for the unknown attribute, so just disable -Wattributes. + * discarded by ld --gc-sections -z start-stop-gc. */ #define BUS_ERROR_MAP_ELF_REGISTER \ - _Pragma("GCC diagnostic ignored \"-Wattributes\"") \ _section_("SYSTEMD_BUS_ERROR_MAP") \ _used_ \ _retain_ \ diff --git a/src/shared/tests.h b/src/shared/tests.h index ae57cab3863c5..4e1bfad86d350 100644 --- a/src/shared/tests.h +++ b/src/shared/tests.h @@ -101,7 +101,6 @@ typedef struct TestFunc { /* See static-destruct.h for an explanation of how this works. */ #define REGISTER_TEST(func, ...) \ - _Pragma("GCC diagnostic ignored \"-Wattributes\"") \ _section_("SYSTEMD_TEST_TABLE") _alignptr_ _used_ _retain_ _variable_no_sanitize_address_ \ static const TestFunc UNIQ_T(static_test_table_entry, UNIQ) = { \ .f = (union f) &(func), \ From 7d5ec30862b3e9c8c0123520cdb495a0762fe741 Mon Sep 17 00:00:00 2001 From: noxiouz Date: Thu, 19 Mar 2026 11:50:26 +0000 Subject: [PATCH 0351/1296] network: add unmanaged interface checks to Link.Renew and Link.ForceRenew Varlink methods The D-Bus counterparts (bus_link_method_renew, bus_link_method_force_renew) reject calls on unmanaged interfaces with BUS_ERROR_UNMANAGED_INTERFACE, but the Varlink methods silently succeed. Add the same guard to both Varlink methods, returning io.systemd.Network.Link.InterfaceUnmanaged, and declare the error in the IDL. Co-Authored-By: Claude Opus 4.6 --- src/network/networkctl-link-info.c | 2 +- src/network/networkd-link-varlink.c | 6 ++++++ src/shared/varlink-io.systemd.Network.Link.c | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/network/networkctl-link-info.c b/src/network/networkctl-link-info.c index 05990bffbc83e..0b40b442537ac 100644 --- a/src/network/networkctl-link-info.c +++ b/src/network/networkctl-link-info.c @@ -2,8 +2,8 @@ #include -#include "sd-netlink.h" #include "sd-json.h" +#include "sd-netlink.h" #include "alloc-util.h" #include "device-util.h" diff --git a/src/network/networkd-link-varlink.c b/src/network/networkd-link-varlink.c index 5864132ef82fc..c802c7fb43f68 100644 --- a/src/network/networkd-link-varlink.c +++ b/src/network/networkd-link-varlink.c @@ -136,6 +136,9 @@ int vl_method_link_renew(sd_varlink *vlink, sd_json_variant *parameters, sd_varl if (r != 0) return r; + if (!link->network) + return sd_varlink_error(vlink, "io.systemd.Network.Link.InterfaceUnmanaged", NULL); + r = varlink_verify_polkit_async( vlink, manager->bus, @@ -163,6 +166,9 @@ int vl_method_link_force_renew(sd_varlink *vlink, sd_json_variant *parameters, s if (r != 0) return r; + if (!link->network) + return sd_varlink_error(vlink, "io.systemd.Network.Link.InterfaceUnmanaged", NULL); + r = varlink_verify_polkit_async( vlink, manager->bus, diff --git a/src/shared/varlink-io.systemd.Network.Link.c b/src/shared/varlink-io.systemd.Network.Link.c index 5474e5e475393..82807939e3229 100644 --- a/src/shared/varlink-io.systemd.Network.Link.c +++ b/src/shared/varlink-io.systemd.Network.Link.c @@ -41,6 +41,8 @@ static SD_VARLINK_DEFINE_METHOD( VARLINK_NETWORK_INTERFACE_INPUTS, VARLINK_DEFINE_POLKIT_INPUT); +static SD_VARLINK_DEFINE_ERROR(InterfaceUnmanaged); + SD_VARLINK_DEFINE_INTERFACE( io_systemd_Network_Link, "io.systemd.Network.Link", @@ -56,6 +58,8 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_method_Reconfigure, SD_VARLINK_SYMBOL_COMMENT("Describe the specified link by index or name."), &vl_method_Describe, + SD_VARLINK_SYMBOL_COMMENT("The specified interface is not managed by systemd-networkd."), + &vl_error_InterfaceUnmanaged, &vl_type_Address, &vl_type_BitRates, &vl_type_DHCPLease, From 8a66ad0c66f6912e81d400f4c19baaa0821b190f Mon Sep 17 00:00:00 2001 From: Chris Down Date: Thu, 19 Mar 2026 21:01:54 +0800 Subject: [PATCH 0352/1296] dissect-image: Fix wrong UUID logged on usr verity partition mismatch When there's a partition mismatch the USR_VERITY branch logs usr_uuid in the mismatch message, but the check is actually against usr_verity_uuid. --- src/shared/dissect-image.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index fa38688411bdd..f81d1f8beca4a 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -1488,7 +1488,7 @@ static int dissect_image( if (!sd_id128_is_null(usr_verity_uuid) && !sd_id128_equal(usr_verity_uuid, id)) { log_debug("Partition UUID '%s' does not match expected UUID '%s' derived from usr verity hash, ignoring.", SD_ID128_TO_UUID_STRING(id), - SD_ID128_TO_UUID_STRING(usr_uuid)); + SD_ID128_TO_UUID_STRING(usr_verity_uuid)); continue; } From 779e70a8e8f1ef7ae21df39f81eb762e89012478 Mon Sep 17 00:00:00 2001 From: Chris Down Date: Thu, 19 Mar 2026 21:10:21 +0800 Subject: [PATCH 0353/1296] dissect-image: Merge partition handler code dissect-image has six(!) different branches with basically the same code. Let's avoid that and reduce the spaces for bugs or differing behaviour to subtly creep in. --- src/shared/dissect-image.c | 92 ++++++-------------------------------- 1 file changed, 14 insertions(+), 78 deletions(-) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index f81d1f8beca4a..f4d45cfd20eaa 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -1395,41 +1395,43 @@ static int dissect_image( if (!fstype) fstype = "vfat"; - } else if (type.designator == PARTITION_ROOT) { + } else if (IN_SET(type.designator, PARTITION_ROOT, PARTITION_USR)) { + sd_id128_t expected_uuid = type.designator == PARTITION_ROOT ? root_uuid : usr_uuid; check_partition_flags(node, pflags, SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY | SD_GPT_FLAG_GROWFS); - /* If a root ID is specified, ignore everything but the root id */ - if (!sd_id128_is_null(root_uuid) && !sd_id128_equal(root_uuid, id)) { - log_debug("Partition UUID '%s' does not match expected UUID '%s' derived from root verity hash, ignoring.", + if (!sd_id128_is_null(expected_uuid) && !sd_id128_equal(expected_uuid, id)) { + log_debug("Partition UUID '%s' does not match expected UUID '%s' derived from %s verity hash, ignoring.", SD_ID128_TO_UUID_STRING(id), - SD_ID128_TO_UUID_STRING(root_uuid)); + SD_ID128_TO_UUID_STRING(expected_uuid), + partition_designator_to_string(type.designator)); continue; } rw = !(pflags & SD_GPT_FLAG_READ_ONLY); growfs = FLAGS_SET(pflags, SD_GPT_FLAG_GROWFS); - } else if (type.designator == PARTITION_ROOT_VERITY) { + } else if (IN_SET(type.designator, PARTITION_ROOT_VERITY, PARTITION_USR_VERITY)) { + sd_id128_t expected_uuid = type.designator == PARTITION_ROOT_VERITY ? root_verity_uuid : usr_verity_uuid; check_partition_flags(node, pflags, SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY); m->has_verity = true; - /* If root hash is specified, then ignore everything but the root id */ - if (!sd_id128_is_null(root_verity_uuid) && !sd_id128_equal(root_verity_uuid, id)) { - log_debug("Partition UUID '%s' does not match expected UUID '%s' derived from root verity hash, ignoring.", + if (!sd_id128_is_null(expected_uuid) && !sd_id128_equal(expected_uuid, id)) { + log_debug("Partition UUID '%s' does not match expected UUID '%s' derived from %s verity hash, ignoring.", SD_ID128_TO_UUID_STRING(id), - SD_ID128_TO_UUID_STRING(root_verity_uuid)); + SD_ID128_TO_UUID_STRING(expected_uuid), + partition_designator_to_string(partition_verity_to_data(type.designator))); continue; } fstype = "DM_verity_hash"; rw = false; - } else if (type.designator == PARTITION_ROOT_VERITY_SIG) { + } else if (IN_SET(type.designator, PARTITION_ROOT_VERITY_SIG, PARTITION_USR_VERITY_SIG)) { if (verity && iovec_is_set(&verity->root_hash)) { _cleanup_(iovec_done) struct iovec root_hash = {}; @@ -1448,73 +1450,7 @@ static int dissect_image( found = hexmem(root_hash.iov_base, root_hash.iov_len); expected = hexmem(verity->root_hash.iov_base, verity->root_hash.iov_len); - log_debug("Root hash in signature JSON data (%s) doesn't match configured hash (%s).", strna(found), strna(expected)); - } - continue; - } - } - - check_partition_flags(node, pflags, - SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY); - - m->has_verity_sig = true; - fstype = "verity_hash_signature"; - rw = false; - - } else if (type.designator == PARTITION_USR) { - - check_partition_flags(node, pflags, - SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY | SD_GPT_FLAG_GROWFS); - - /* If a usr ID is specified, ignore everything but the usr id */ - if (!sd_id128_is_null(usr_uuid) && !sd_id128_equal(usr_uuid, id)) { - log_debug("Partition UUID '%s' does not match expected UUID '%s' derived from usr verity hash, ignoring.", - SD_ID128_TO_UUID_STRING(id), - SD_ID128_TO_UUID_STRING(usr_uuid)); - continue; - } - - rw = !(pflags & SD_GPT_FLAG_READ_ONLY); - growfs = FLAGS_SET(pflags, SD_GPT_FLAG_GROWFS); - - } else if (type.designator == PARTITION_USR_VERITY) { - - check_partition_flags(node, pflags, - SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY); - - m->has_verity = true; - - /* If usr hash is specified, then ignore everything but the usr id */ - if (!sd_id128_is_null(usr_verity_uuid) && !sd_id128_equal(usr_verity_uuid, id)) { - log_debug("Partition UUID '%s' does not match expected UUID '%s' derived from usr verity hash, ignoring.", - SD_ID128_TO_UUID_STRING(id), - SD_ID128_TO_UUID_STRING(usr_verity_uuid)); - continue; - } - - fstype = "DM_verity_hash"; - rw = false; - - } else if (type.designator == PARTITION_USR_VERITY_SIG) { - if (verity && iovec_is_set(&verity->root_hash)) { - _cleanup_(iovec_done) struct iovec root_hash = {}; - - r = acquire_sig_for_roothash( - fd, - start * 512, - size * 512, - &root_hash, - /* ret_root_hash_sig= */ NULL); - if (r < 0) - return r; - if (iovec_memcmp(&verity->root_hash, &root_hash) != 0) { - if (DEBUG_LOGGING) { - _cleanup_free_ char *found = NULL, *expected = NULL; - - found = hexmem(root_hash.iov_base, root_hash.iov_len); - expected = hexmem(verity->root_hash.iov_base, verity->root_hash.iov_len); - - log_debug("Root hash in signature JSON data (%s) doesn't match configured hash (%s).", strna(found), strna(expected)); + log_debug("Verity root hash in signature JSON data (%s) doesn't match configured hash (%s).", strna(found), strna(expected)); } continue; } From 91578e529395a0299a1e5eaa6da08e73db6eeacd Mon Sep 17 00:00:00 2001 From: Chris Down Date: Thu, 19 Mar 2026 21:15:44 +0800 Subject: [PATCH 0354/1296] dissect-image: Consolidate verity validation and setup The verity consistency checks and verity setup code also have parallel blocks for root and usr that do basically identical work. Let's consolidate them and reduce the footprint for bugs or deviance to manifest. --- src/shared/dissect-image.c | 84 ++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 49 deletions(-) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index f4d45cfd20eaa..d68ea0bc9742a 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -1698,31 +1698,25 @@ static int dissect_image( } } - /* Verity found but no matching rootfs? Something is off, refuse. */ - if (!m->partitions[PARTITION_ROOT].found && - (m->partitions[PARTITION_ROOT_VERITY].found || - m->partitions[PARTITION_ROOT_VERITY_SIG].found)) + /* Verity found but no matching data partition? Something is off, refuse. */ + FOREACH_ELEMENT(dd, ((const PartitionDesignator[]) { PARTITION_ROOT, PARTITION_USR })) { + PartitionDesignator dv = partition_verity_hash_of(*dd); + PartitionDesignator ds = partition_verity_sig_of(*dd); + + if (!m->partitions[*dd].found && (m->partitions[dv].found || m->partitions[ds].found)) return log_debug_errno( SYNTHETIC_ERRNO(EADDRNOTAVAIL), - "Found root verity hash partition without matching root data partition."); - - /* Hmm, we found a signature partition but no Verity data? Something is off. */ - if (m->partitions[PARTITION_ROOT_VERITY_SIG].found && !m->partitions[PARTITION_ROOT_VERITY].found) - return log_debug_errno(SYNTHETIC_ERRNO(EADDRNOTAVAIL), - "Found root verity signature partition without matching root verity hash partition."); + "Found %s verity hash partition without matching %s data partition.", + partition_designator_to_string(*dd), + partition_designator_to_string(*dd)); - /* as above */ - if (!m->partitions[PARTITION_USR].found && - (m->partitions[PARTITION_USR_VERITY].found || - m->partitions[PARTITION_USR_VERITY_SIG].found)) + if (m->partitions[ds].found && !m->partitions[dv].found) return log_debug_errno( SYNTHETIC_ERRNO(EADDRNOTAVAIL), - "Found usr verity hash partition without matching usr data partition."); - - /* as above */ - if (m->partitions[PARTITION_USR_VERITY_SIG].found && !m->partitions[PARTITION_USR_VERITY].found) - return log_debug_errno(SYNTHETIC_ERRNO(EADDRNOTAVAIL), - "Found usr verity signature partition without matching usr verity hash partition."); + "Found %s verity signature partition without matching %s verity hash partition.", + partition_designator_to_string(*dd), + partition_designator_to_string(*dd)); + } /* If root and /usr are combined then insist that the architecture matches */ if (m->partitions[PARTITION_ROOT].found && @@ -1827,35 +1821,27 @@ static int dissect_image( /* If we have an explicit root hash and found the partitions for it, then we are ready to use * Verity, set things up for it */ - if (verity->designator < 0 || verity->designator == PARTITION_ROOT) { - if (!m->partitions[PARTITION_ROOT].found) - return log_debug_errno( - SYNTHETIC_ERRNO(EADDRNOTAVAIL), - "Verity enabled root partition was requested but did not find a root data partition."); - - if (!m->partitions[PARTITION_ROOT_VERITY].found) - return log_debug_errno( - SYNTHETIC_ERRNO(EADDRNOTAVAIL), - "Verity enabled root partition was requested but did not find a root verity hash partition."); - - /* If we found a verity setup, then the root partition is necessarily read-only. */ - m->partitions[PARTITION_ROOT].rw = false; - } else { - assert(verity->designator == PARTITION_USR); - - if (!m->partitions[PARTITION_USR].found) - return log_debug_errno( - SYNTHETIC_ERRNO(EADDRNOTAVAIL), - "Verity enabled usr partition was requested but did not find a usr data partition."); - - if (!m->partitions[PARTITION_USR_VERITY].found) - return log_debug_errno( - SYNTHETIC_ERRNO(EADDRNOTAVAIL), - "Verity enabled usr partition was requested but did not find a usr verity hash partition."); - - - m->partitions[PARTITION_USR].rw = false; - } + PartitionDesignator d = verity->designator < 0 || verity->designator == PARTITION_ROOT + ? PARTITION_ROOT : PARTITION_USR; + PartitionDesignator dv = partition_verity_hash_of(d); + assert(dv >= 0); + + if (!m->partitions[d].found) + return log_debug_errno( + SYNTHETIC_ERRNO(EADDRNOTAVAIL), + "Verity enabled %s partition was requested but did not find a %s data partition.", + partition_designator_to_string(d), + partition_designator_to_string(d)); + + if (!m->partitions[dv].found) + return log_debug_errno( + SYNTHETIC_ERRNO(EADDRNOTAVAIL), + "Verity enabled %s partition was requested but did not find a %s verity hash partition.", + partition_designator_to_string(d), + partition_designator_to_string(d)); + + /* If we found a verity setup, then the data partition is necessarily read-only. */ + m->partitions[d].rw = false; m->verity_ready = true; From bcb65f4b57bdf80618a5b022857a5b7d8a63751f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 19 Mar 2026 12:39:17 +0100 Subject: [PATCH 0355/1296] test-time-util: restore relaxation of check is special timezones MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixup for 514fa9d39ae9935ef1e014a3dd48dd5856007df2. We are now getting failures in CI i386 builds in Fedora rawhide: TZ=Europe/Lisbon, tzname[0]=WET, tzname[1]=WEST @212545617716594 → Sun 1976-09-26 00:26:57 WET → @212542017000000 → Sun 1976-09-26 00:26:57 CET src/test/test-time-util.c:450: Assertion failed: Expected "ignore" to be true Restore the conditionalization for CAT, EAT, WET that was removed in the refactoring. --- src/test/test-time-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c index d3d4fd5f9b2e9..04da9891cb73a 100644 --- a/src/test/test-time-util.c +++ b/src/test/test-time-util.c @@ -439,7 +439,7 @@ static void test_format_timestamp_impl(usec_t x) { * Also, the same may happen on MSK timezone (e.g. Europe/Volgograd or Europe/Kirov). */ bool ignore = (streq_ptr(getenv("TZ"), "Africa/Windhoek") || - streq_ptr(get_tzname(/* dst= */ false), "MSK")) && + STRPTR_IN_SET(get_tzname(/* dst= */ false), "CAT", "EAT", "MSK", "WET")) && (x_sec > y_sec ? x_sec - y_sec : y_sec - x_sec) == 3600; log_full(ignore ? LOG_WARNING : LOG_ERR, From 15a2fdcd961a5c34e94a33b20540868c143dfd3a Mon Sep 17 00:00:00 2001 From: Chris Down Date: Thu, 19 Mar 2026 21:35:46 +0800 Subject: [PATCH 0356/1296] dissect-image: Add usr verity partition coverage --- test/units/TEST-50-DISSECT.dissect.sh | 22 +++++++++ test/units/TEST-50-DISSECT.sh | 69 +++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/test/units/TEST-50-DISSECT.dissect.sh b/test/units/TEST-50-DISSECT.dissect.sh index f87bda82ce295..d82f8c40fdc12 100755 --- a/test/units/TEST-50-DISSECT.dissect.sh +++ b/test/units/TEST-50-DISSECT.dissect.sh @@ -160,6 +160,8 @@ mv "$MINIMAL_IMAGE.foohash" "$MINIMAL_IMAGE.roothash" # Derive partition UUIDs from root hash, in UUID syntax ROOT_UUID="$(systemd-id128 -u show "$(head -c 32 "$MINIMAL_IMAGE.roothash")" -u | tail -n 1 | cut -b 6-)" VERITY_UUID="$(systemd-id128 -u show "$(tail -c 32 "$MINIMAL_IMAGE.roothash")" -u | tail -n 1 | cut -b 6-)" +USR_UUID="$ROOT_UUID" +USR_VERITY_UUID="$VERITY_UUID" systemd-dissect --json=short \ --root-hash "$MINIMAL_IMAGE_ROOTHASH" \ @@ -177,6 +179,20 @@ if [[ -n "${OPENSSL_CONFIG:-}" ]]; then fi systemd-dissect --root-hash "$MINIMAL_IMAGE_ROOTHASH" "$MINIMAL_IMAGE.gpt" | grep -F "MARKER=1" >/dev/null systemd-dissect --root-hash "$MINIMAL_IMAGE_ROOTHASH" "$MINIMAL_IMAGE.gpt" | grep -F -f <(sed 's/"//g' "$OS_RELEASE") >/dev/null +systemd-dissect --json=short \ + --usr-hash "$MINIMAL_IMAGE_ROOTHASH" \ + "$MINIMAL_IMAGE.usr.gpt" | \ + grep '{"rw":"ro","designator":"usr","partition_uuid":"'"$USR_UUID"'","partition_label":"Usr Partition","fstype":"squashfs","architecture":"'"$ARCHITECTURE"'","verity":"signed",' >/dev/null +systemd-dissect --json=short \ + --usr-hash "$MINIMAL_IMAGE_ROOTHASH" \ + "$MINIMAL_IMAGE.usr.gpt" | \ + grep '{"rw":"ro","designator":"usr-verity","partition_uuid":"'"$USR_VERITY_UUID"'","partition_label":"Usr Verity Partition","fstype":"DM_verity_hash","architecture":"'"$ARCHITECTURE"'","verity":null,' >/dev/null +if [[ -n "${OPENSSL_CONFIG:-}" ]]; then + systemd-dissect --json=short \ + --usr-hash "$MINIMAL_IMAGE_ROOTHASH" \ + "$MINIMAL_IMAGE.usr.gpt" | \ + grep -E '{"rw":"ro","designator":"usr-verity-sig","partition_uuid":"'".*"'","partition_label":"Usr Signature Partition","fstype":"verity_hash_signature","architecture":"'"$ARCHITECTURE"'","verity":null,' >/dev/null +fi # Test image policies systemd-dissect --validate "$MINIMAL_IMAGE.gpt" @@ -194,6 +210,12 @@ systemd-dissect --validate "$MINIMAL_IMAGE.gpt" --image-policy=root=verity:swap= systemd-dissect --validate "$MINIMAL_IMAGE.gpt" --image-policy=root=signed (! systemd-dissect --validate "$MINIMAL_IMAGE.gpt" --image-policy=root=signed:root-verity-sig=unused+absent) (! systemd-dissect --validate "$MINIMAL_IMAGE.gpt" --image-policy=root=signed:root-verity=unused+absent) +systemd-dissect --validate "$MINIMAL_IMAGE.usr.gpt" --image-policy=usr=verity +systemd-dissect --validate "$MINIMAL_IMAGE.usr.gpt" --image-policy=usr=verity:usr-verity-sig=unused+absent +(! systemd-dissect --validate "$MINIMAL_IMAGE.usr.gpt" --image-policy=usr=verity:usr-verity=unused+absent) +systemd-dissect --validate "$MINIMAL_IMAGE.usr.gpt" --image-policy=usr=signed +(! systemd-dissect --validate "$MINIMAL_IMAGE.usr.gpt" --image-policy=usr=signed:usr-verity-sig=unused+absent) +(! systemd-dissect --validate "$MINIMAL_IMAGE.usr.gpt" --image-policy=usr=signed:usr-verity=unused+absent) # Test RootImagePolicy= unit file setting systemd-run --wait -P \ diff --git a/test/units/TEST-50-DISSECT.sh b/test/units/TEST-50-DISSECT.sh index 99e4991402393..973f18483789d 100755 --- a/test/units/TEST-50-DISSECT.sh +++ b/test/units/TEST-50-DISSECT.sh @@ -55,6 +55,9 @@ export OPENSSL_CONFIG export OS_RELEASE export ROOT_GUID export SIGNATURE_GUID +export USR_GUID +export USR_SIGNATURE_GUID +export USR_VERITY_GUID export VERITY_GUID machine="$(uname -m)" @@ -62,51 +65,81 @@ if [[ "$machine" == "x86_64" ]]; then ROOT_GUID=4f68bce3-e8cd-4db1-96e7-fbcaf984b709 VERITY_GUID=2c7357ed-ebd2-46d9-aec1-23d437ec2bf5 SIGNATURE_GUID=41092b05-9fc8-4523-994f-2def0408b176 + USR_GUID=8484680c-9521-48c6-9c11-b0720656f69e + USR_VERITY_GUID=77ff5f63-e7b6-4633-acf4-1565b864c0e6 + USR_SIGNATURE_GUID=e7bb33fb-06cf-4e81-8273-e543b413e2e2 ARCHITECTURE="x86-64" elif [[ "$machine" =~ ^(i386|i686|x86)$ ]]; then ROOT_GUID=44479540-f297-41b2-9af7-d131d5f0458a VERITY_GUID=d13c5d3b-b5d1-422a-b29f-9454fdc89d76 SIGNATURE_GUID=5996fc05-109c-48de-808b-23fa0830b676 + USR_GUID=75250d76-8cc6-458e-bd66-bd47cc81a812 + USR_VERITY_GUID=8f461b0d-14ee-4e81-9aa9-049b6fb97abd + USR_SIGNATURE_GUID=974a71c0-de41-43c3-be5d-5c5ccd1ad2c0 ARCHITECTURE="x86" elif [[ "$machine" =~ ^(aarch64|aarch64_be|armv8b|armv8l)$ ]]; then ROOT_GUID=b921b045-1df0-41c3-af44-4c6f280d3fae VERITY_GUID=df3300ce-d69f-4c92-978c-9bfb0f38d820 SIGNATURE_GUID=6db69de6-29f4-4758-a7a5-962190f00ce3 + USR_GUID=b0e01050-ee5f-4390-949a-9101b17104e9 + USR_VERITY_GUID=6e11a4e7-fbca-4ded-b9e9-e1a512bb664e + USR_SIGNATURE_GUID=c23ce4ff-44bd-4b00-b2d4-b41b3419e02a ARCHITECTURE="arm64" elif [[ "$machine" == "arm" ]]; then ROOT_GUID=69dad710-2ce4-4e3c-b16c-21a1d49abed3 VERITY_GUID=7386cdf2-203c-47a9-a498-f2ecce45a2d6 SIGNATURE_GUID=42b0455f-eb11-491d-98d3-56145ba9d037 + USR_GUID=7d0359a3-02b3-4f0a-865c-654403e70625 + USR_VERITY_GUID=c215d751-7bcd-4649-be90-6627490a4c05 + USR_SIGNATURE_GUID=d7ff812f-37d1-4902-a810-d76ba57b975a ARCHITECTURE="arm" elif [[ "$machine" == "ia64" ]]; then ROOT_GUID=993d8d3d-f80e-4225-855a-9daf8ed7ea97 VERITY_GUID=86ed10d5-b607-45bb-8957-d350f23d0571 SIGNATURE_GUID=e98b36ee-32ba-4882-9b12-0ce14655f46a + USR_GUID=4301d2a6-4e3b-4b2a-bb94-9e0b2c4225ea + USR_VERITY_GUID=6a491e03-3be7-4545-8e38-83320e0ea880 + USR_SIGNATURE_GUID=8de58bc2-2a43-460d-b14e-a76e4a17b47f ARCHITECTURE="ia64" elif [[ "$machine" == "loongarch64" ]]; then ROOT_GUID=77055800-792c-4f94-b39a-98c91b762bb6 VERITY_GUID=f3393b22-e9af-4613-a948-9d3bfbd0c535 SIGNATURE_GUID=5afb67eb-ecc8-4f85-ae8e-ac1e7c50e7d0 + USR_GUID=e611c702-575c-4cbe-9a46-434fa0bf7e3f + USR_VERITY_GUID=f46b2c26-59ae-48f0-9106-c50ed47f673d + USR_SIGNATURE_GUID=b024f315-d330-444c-8461-44bbde524e99 ARCHITECTURE="loongarch64" elif [[ "$machine" == "s390x" ]]; then ROOT_GUID=5eead9a9-fe09-4a1e-a1d7-520d00531306 VERITY_GUID=b325bfbe-c7be-4ab8-8357-139e652d2f6b SIGNATURE_GUID=c80187a5-73a3-491a-901a-017c3fa953e9 + USR_GUID=8a4f5770-50aa-4ed3-874a-99b710db6fea + USR_VERITY_GUID=31741cc4-1a2a-4111-a581-e00b447d2d06 + USR_SIGNATURE_GUID=3f324816-667b-46ae-86ee-9b0c0c6c11b4 ARCHITECTURE="s390x" elif [[ "$machine" == "ppc64le" ]]; then ROOT_GUID=c31c45e6-3f39-412e-80fb-4809c4980599 VERITY_GUID=906bd944-4589-4aae-a4e4-dd983917446a SIGNATURE_GUID=d4a236e7-e873-4c07-bf1d-bf6cf7f1c3c6 + USR_GUID=15bb03af-77e7-4d4a-b12b-c0d084f7491c + USR_VERITY_GUID=ee2b9983-21e8-4153-86d9-b6901a54d1ce + USR_SIGNATURE_GUID=c8bfbd1e-268e-4521-8bba-bf314c399557 ARCHITECTURE="ppc64-le" elif [[ "$machine" == "riscv64" ]]; then ROOT_GUID=72ec70a6-cf74-40e6-bd49-4bda08e8f224 VERITY_GUID=b6ed5582-440b-4209-b8da-5ff7c419ea3d SIGNATURE_GUID=efe0f087-ea8d-4469-821a-4c2a96a8386a + USR_GUID=beaec34b-8442-439b-a40b-984381ed097d + USR_VERITY_GUID=8f1056be-9b05-47c4-81d6-be53128e5b54 + USR_SIGNATURE_GUID=d2f9000a-7a18-453f-b5cd-4d32f77a7b32 ARCHITECTURE="riscv64" elif [[ "$machine" == "riscv32" ]]; then ROOT_GUID=60d5a7fe-8e7d-435c-b714-3dd8162144e1 VERITY_GUID=ae0253be-1167-4007-ac68-43926c14c5de SIGNATURE_GUID=3a112a75-8729-4380-b4cf-764d79934448 + USR_GUID=b933fb22-5c3f-4f91-af90-e2bb0fa50702 + USR_VERITY_GUID=cb1ee4e3-8cd0-4136-a0a4-aa61a32e8730 + USR_SIGNATURE_GUID=c3836a13-3137-45ba-b583-b16c50fe5eb4 ARCHITECTURE="riscv32" else echo "Unexpected uname -m: $machine in TEST-50-DISSECT.sh, please fix me" @@ -197,6 +230,42 @@ udevadm lock --timeout=60 --device="${loop}p3" dd if="$MINIMAL_IMAGE.verity-sig" losetup -d "$loop" udevadm settle --timeout=60 +# Construct the same image with /usr verity partitions to exercise the usr-specific code paths. +usr_size="$(du --apparent-size -k "$MINIMAL_IMAGE.raw" | cut -f1)" +usr_verity_size="$(du --apparent-size -k "$MINIMAL_IMAGE.verity" | cut -f1)" +usr_signature_size=4 +truncate -s $(((8192+usr_size*2+usr_verity_size*2+usr_signature_size*2)*512)) "$MINIMAL_IMAGE.usr.gpt" +if [[ "$usr_size" -ge 1024 ]]; then + usr_size="$((usr_size/1024 + 1))MiB" +else + usr_size="${usr_size}KiB" +fi +usr_verity_size="$((usr_verity_size * 2))KiB" +usr_signature_size="$((usr_signature_size * 2))KiB" +uuid="$(head -c 32 "$MINIMAL_IMAGE.roothash" | sed -r 's/(.{8})(.{4})(.{4})(.{4})(.+)/\1-\2-\3-\4-\5/')" +echo -e "label: gpt\nsize=$usr_size, type=$USR_GUID, uuid=$uuid" | sfdisk "$MINIMAL_IMAGE.usr.gpt" +uuid="$(tail -c 32 "$MINIMAL_IMAGE.roothash" | sed -r 's/(.{8})(.{4})(.{4})(.{4})(.+)/\1-\2-\3-\4-\5/')" +echo -e "size=$usr_verity_size, type=$USR_VERITY_GUID, uuid=$uuid" | sfdisk "$MINIMAL_IMAGE.usr.gpt" --append +echo -e "size=$usr_signature_size, type=$USR_SIGNATURE_GUID" | sfdisk "$MINIMAL_IMAGE.usr.gpt" --append + +sfdisk --part-label "$MINIMAL_IMAGE.usr.gpt" 1 "Usr Partition" +sfdisk --part-label "$MINIMAL_IMAGE.usr.gpt" 2 "Usr Verity Partition" +sfdisk --part-label "$MINIMAL_IMAGE.usr.gpt" 3 "Usr Signature Partition" +loop="$(losetup --show -P -f "$MINIMAL_IMAGE.usr.gpt")" +partitions=( + "${loop:?}p1" + "${loop:?}p2" + "${loop:?}p3" +) +# The kernel sometimes(?) does not emit "add" uevent for loop block partition devices. +# Let's not expect the devices to be initialized. +udevadm wait --timeout=60 --settle --initialized=no "${partitions[@]}" +udevadm lock --timeout=60 --device="${loop}p1" dd if="$MINIMAL_IMAGE.raw" of="${loop}p1" +udevadm lock --timeout=60 --device="${loop}p2" dd if="$MINIMAL_IMAGE.verity" of="${loop}p2" +udevadm lock --timeout=60 --device="${loop}p3" dd if="$MINIMAL_IMAGE.verity-sig" of="${loop}p3" +losetup -d "$loop" +udevadm settle --timeout=60 + : "Run subtests" run_subtests From f8e31900cec4a53e0f27a9f111d618e089b3434a Mon Sep 17 00:00:00 2001 From: Artem Proskurnev Date: Thu, 19 Mar 2026 18:39:21 +0300 Subject: [PATCH 0357/1296] hwdb/keyboard: Map FN key on Wareus B15 After kernel commit 907bc9268a ("Input: atkbd - map F23 key to support default copilot shortcut") Fn+F5 combination (switch touchpad on/off) stopped working correctly. Fn produces F23, it is probably a bug in BIOS, ther eis no "Copilot" key. It was ignored before that commit, but now we have to remap it here in hwdb. This workaround is similar to systemd commit d2502f5 ("hwdb/keyboard: Map FN key on TUXEDO InfinityFlex 14 Gen1") Hardware probe of this notebook: https://linux-hardware.org/?probe=2d5266f5c6 --- hwdb.d/60-keyboard.hwdb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hwdb.d/60-keyboard.hwdb b/hwdb.d/60-keyboard.hwdb index 59aeef6b6f857..936b7f7392654 100644 --- a/hwdb.d/60-keyboard.hwdb +++ b/hwdb.d/60-keyboard.hwdb @@ -2157,6 +2157,14 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnVIA:pnK8N800:* evdev:name:SIPODEV USB Composite Device:dmi:bvn*:bvr*:bd*:svnVIOS:pnLTH17:* KEYBOARD_KEY_70073=touchpad_toggle # Touchpad toggle +########################################################### +# Wareus +########################################################### + +# Wareus B15 (8AD5A) +evdev:atkbd:dmi:bvn*:bvr*:bd*:svnWareus*:pnB15*:* + KEYBOARD_KEY_55=fn + ########################################################### # WeiHeng ########################################################### From b3388e7cd652fd357d67313e640bc41a862c8076 Mon Sep 17 00:00:00 2001 From: Massii Aqvayli Date: Thu, 19 Mar 2026 20:58:44 +0000 Subject: [PATCH 0358/1296] po: Translated using Weblate (Kabyle) Currently translated at 15.0% (40 of 266 strings) Co-authored-by: Massii Aqvayli Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/kab/ Translation: systemd/main --- po/kab.po | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/po/kab.po b/po/kab.po index fea95626b4a72..07954f69bbbe7 100644 --- a/po/kab.po +++ b/po/kab.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2026-03-18 18:58+0000\n" +"PO-Revision-Date: 2026-03-19 20:58+0000\n" "Last-Translator: Massii Aqvayli \n" "Language-Team: Kabyle \n" @@ -54,6 +54,8 @@ msgid "" "Authentication is required to set or unset system and service manager " "environment variables." msgstr "" +"Ilaq usesteb i usbadu neɣ tukksa n yimuttiyen n twennaṭ seg umsefrak n " +"unagraw akked imeẓla." #: src/core/org.freedesktop.systemd1.policy.in:64 msgid "Reload the systemd state" @@ -65,12 +67,12 @@ msgstr "Asesteb yettwasra i wallus usali n waddad n unagraw." #: src/core/org.freedesktop.systemd1.policy.in:74 msgid "Dump the systemd state without rate limits" -msgstr "" +msgstr "Silem addad n systemd war tilisa n watug" #: src/core/org.freedesktop.systemd1.policy.in:75 msgid "" "Authentication is required to dump the systemd state without rate limits." -msgstr "" +msgstr "Asesteb yesra i usillem n waddad n systemd war tilisa n watug." #: src/home/org.freedesktop.home1.policy:13 msgid "Create a home area" @@ -78,32 +80,34 @@ msgstr "Rnu tmennaḍt agejdan" #: src/home/org.freedesktop.home1.policy:14 msgid "Authentication is required to create a user's home area." -msgstr "" +msgstr "Asesteb yettwasra i tmerna n temnaḍt tagejdant n useqdac." #: src/home/org.freedesktop.home1.policy:23 msgid "Remove a home area" -msgstr "" +msgstr "Kkes tamnaḍt tagejdant" #: src/home/org.freedesktop.home1.policy:24 msgid "Authentication is required to remove a user's home area." -msgstr "" +msgstr "Asesteb yettwasra i tukksa n temnaḍt tagejdant n useqdac." #: src/home/org.freedesktop.home1.policy:33 msgid "Check credentials of a home area" -msgstr "" +msgstr "Selken talɣut n usesteb n temnaḍt tagejdant" #: src/home/org.freedesktop.home1.policy:34 msgid "" "Authentication is required to check credentials against a user's home area." msgstr "" +"Asesteb yettwasra i uselken n talɣut n usesteb deg temnaḍt tagejdant n " +"useqdac." #: src/home/org.freedesktop.home1.policy:43 msgid "Update a home area" -msgstr "" +msgstr "Leqqem tamnaḍt tagejdant" #: src/home/org.freedesktop.home1.policy:44 msgid "Authentication is required to update a user's home area." -msgstr "" +msgstr "Asesteb yettwasera i uleqqem n tamnaḍt tagejdant n useqdac." #: src/home/org.freedesktop.home1.policy:53 msgid "Update your home area" @@ -111,24 +115,25 @@ msgstr "Mucced tamnaḍt-ik·im tagejdant" #: src/home/org.freedesktop.home1.policy:54 msgid "Authentication is required to update your home area." -msgstr "" +msgstr "Asesteb yettwasera i uleqqem n tamnaḍt tagejdant." #: src/home/org.freedesktop.home1.policy:63 msgid "Resize a home area" -msgstr "" +msgstr "Ales tiddi n temnaḍt n tagejdant" #: src/home/org.freedesktop.home1.policy:64 msgid "Authentication is required to resize a user's home area." -msgstr "" +msgstr "Asesteb yettwasera i wales tiddi n temnaḍt tagejdant n useqdac." #: src/home/org.freedesktop.home1.policy:73 msgid "Change password of a home area" -msgstr "" +msgstr "Snifel awal n uɛeddi n temnaḍt tagejdant" #: src/home/org.freedesktop.home1.policy:74 msgid "" "Authentication is required to change the password of a user's home area." msgstr "" +"Asesteb yettwasera i usnifel n wawal n uɛeddi n temnaḍt tagejdant n useqdac." #: src/home/org.freedesktop.home1.policy:83 msgid "Activate a home area" @@ -136,15 +141,15 @@ msgstr "Rmed tamnaḍṭ-ik·im tagejdant" #: src/home/org.freedesktop.home1.policy:84 msgid "Authentication is required to activate a user's home area." -msgstr "" +msgstr "Asesteb yettwasera i wesermed n temnaḍt tagejdant n useqdac." #: src/home/org.freedesktop.home1.policy:93 msgid "Manage Home Directory Signing Keys" -msgstr "" +msgstr "Sefrek tisura uzmul n ukaram agejdan" #: src/home/org.freedesktop.home1.policy:94 msgid "Authentication is required to manage signing keys for home directories." -msgstr "" +msgstr "Asesteb yettwasera i usefrek n tisura uzmul n ikaramen igejdanen." #: src/home/pam_systemd_home.c:330 #, c-format @@ -152,11 +157,13 @@ msgid "" "Home of user %s is currently absent, please plug in the necessary storage " "device or backing file system." msgstr "" +"Agejdan n useqdac %s ulac-it akka tura, ttxil-k·m, qqen ibenk n usekles " +"ilaqen neɣ anagraw n ufaylu i yellan deg-s." #: src/home/pam_systemd_home.c:335 #, c-format msgid "Too frequent login attempts for user %s, try again later." -msgstr "" +msgstr "Ddeqs n uɛraḍ n tuqqna ɣer useqdac %s, ɛreḍ tikelt nniḍen ticki." #: src/home/pam_systemd_home.c:347 msgid "Password: " From 541622dfc64ba5b887a7ad099982b943c3883375 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 18 Mar 2026 13:23:20 +0100 Subject: [PATCH 0359/1296] test: add basic TEST-74-AUX-UTILS.socket-proxyd.sh With the planned extraction of the socket-forward code its useful to have a basic way to validate the functionality. So add a basic test that ensures at least base functionality is intact. --- mkosi/mkosi.sanitizers/mkosi.postinst | 1 + .../TEST-74-AUX-UTILS.units/proxy-echo.py | 18 ++++++ test/meson.build | 1 + test/units/TEST-74-AUX-UTILS.socket-proxyd.sh | 58 +++++++++++++++++++ 4 files changed, 78 insertions(+) create mode 100755 test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/proxy-echo.py create mode 100755 test/units/TEST-74-AUX-UTILS.socket-proxyd.sh diff --git a/mkosi/mkosi.sanitizers/mkosi.postinst b/mkosi/mkosi.sanitizers/mkosi.postinst index d4d00907ed07f..229a5368b92f4 100755 --- a/mkosi/mkosi.sanitizers/mkosi.postinst +++ b/mkosi/mkosi.sanitizers/mkosi.postinst @@ -43,6 +43,7 @@ fi wrap=( /usr/lib/polkit-1/polkitd /usr/libexec/polkit-1/polkitd + /usr/lib/systemd/tests/testdata/TEST-74-AUX-UTILS.units/proxy-echo.py agetty btrfs capsh diff --git a/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/proxy-echo.py b/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/proxy-echo.py new file mode 100755 index 0000000000000..827ce6af0670f --- /dev/null +++ b/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/proxy-echo.py @@ -0,0 +1,18 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: LGPL-2.1-or-later + +import socket +import sys + +data = sys.stdin.buffer.read() +s = socket.create_connection(("localhost", 12345), timeout=15) +s.settimeout(15) +s.sendall(data) +received = b"" +while len(received) < len(data): + chunk = s.recv(65536) + if not chunk: + break + received += chunk +sys.stdout.buffer.write(received) +s.close() diff --git a/test/meson.build b/test/meson.build index 7bf557cc19335..b64f971126df6 100644 --- a/test/meson.build +++ b/test/meson.build @@ -357,6 +357,7 @@ if install_tests 'integration-tests/TEST-63-PATH/TEST-63-PATH.units', 'integration-tests/TEST-65-ANALYZE/TEST-65-ANALYZE.units', 'integration-tests/TEST-66-DEVICE-ISOLATION/TEST-66-DEVICE-ISOLATION.units', + 'integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units', 'integration-tests/TEST-80-NOTIFYACCESS/TEST-80-NOTIFYACCESS.units', 'units', ] diff --git a/test/units/TEST-74-AUX-UTILS.socket-proxyd.sh b/test/units/TEST-74-AUX-UTILS.socket-proxyd.sh new file mode 100755 index 0000000000000..c028747ec02cb --- /dev/null +++ b/test/units/TEST-74-AUX-UTILS.socket-proxyd.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +# Test systemd-socket-proxyd by setting up a backend server, a proxy in front of it, +# and verifying that data passes through correctly. + +BACKEND_SOCK="/tmp/test-proxyd-backend.sock" + +at_exit() { + set +e + systemctl stop test-proxyd-backend.service 2>/dev/null + systemctl stop test-proxyd.socket 2>/dev/null + systemctl stop test-proxyd.service 2>/dev/null + rm -f "$BACKEND_SOCK" + rm -f /run/systemd/system/test-proxyd.socket /run/systemd/system/test-proxyd.service + systemctl daemon-reload 2>/dev/null +} +trap at_exit EXIT + +# Start a backend echo server via systemd-run +systemd-run --unit=test-proxyd-backend --service-type=simple \ + socat UNIX-LISTEN:"$BACKEND_SOCK",fork EXEC:cat + +# Ensure socket is ready +timeout 5 bash -c "until [[ -S $BACKEND_SOCK ]]; do sleep 0.1; done" + +# Create a socket unit for the proxy +cat >/run/systemd/system/test-proxyd.socket </run/systemd/system/test-proxyd.service < Date: Thu, 19 Mar 2026 18:57:11 +0100 Subject: [PATCH 0360/1296] timesyncd: drop obsolete privilege dropping code systemd-timesyncd always runs as an unprivileged user via the service file, so the code to resolve the systemd-timesync user, drop privileges adjust file ownership/permissions, or even create the directory cannot do anything useful and is unnecessary. Follow-up for 00a415fc8f9e3469549a56d29f448b8cf14b0598, which made running under an unprivileged user unconditional. --- src/timesync/timesyncd.c | 47 +++++----------------------------------- 1 file changed, 6 insertions(+), 41 deletions(-) diff --git a/src/timesync/timesyncd.c b/src/timesync/timesyncd.c index 96d0dd5c2ba2b..5e0d13023aa90 100644 --- a/src/timesync/timesyncd.c +++ b/src/timesync/timesyncd.c @@ -8,7 +8,6 @@ #include "bus-log-control-api.h" #include "bus-object.h" -#include "capability-util.h" #include "clock-util.h" #include "daemon-util.h" #include "errno-util.h" @@ -17,14 +16,12 @@ #include "fs-util.h" #include "log.h" #include "main-func.h" -#include "mkdir-label.h" #include "network-util.h" #include "process-util.h" #include "service-util.h" #include "timesyncd-bus.h" #include "timesyncd-conf.h" #include "timesyncd-manager.h" -#include "user-util.h" static int advance_tstamp(int fd, usec_t epoch) { assert(fd >= 0); @@ -72,7 +69,7 @@ static int advance_tstamp(int fd, usec_t epoch) { return 0; } -static int load_clock_timestamp(uid_t uid, gid_t gid) { +static int load_clock_timestamp(void) { usec_t epoch = TIME_EPOCH * USEC_PER_SEC, ct; _cleanup_close_ int fd = -EBADF; int r; @@ -82,18 +79,13 @@ static int load_clock_timestamp(uid_t uid, gid_t gid) { * is particularly helpful on systems lacking a battery backed RTC. We also will adjust the time to * at least the build time of systemd. */ - fd = open(TIMESYNCD_CLOCK_FILE, O_RDWR|O_CLOEXEC, 0644); + fd = RET_NERRNO(open(TIMESYNCD_CLOCK_FILE, O_RDWR|O_CLOEXEC, 0644)); if (fd < 0) { - if (errno != ENOENT) - log_debug_errno(errno, "Unable to open timestamp file "TIMESYNCD_CLOCK_FILE", ignoring: %m"); - - r = mkdir_safe_label(TIMESYNCD_CLOCK_FILE_DIR, 0755, uid, gid, - MKDIR_FOLLOW_SYMLINK | MKDIR_WARN_MODE); - if (r < 0) - log_debug_errno(r, "Failed to create "TIMESYNCD_CLOCK_FILE_DIR", ignoring: %m"); + if (fd != -ENOENT) + log_warning_errno(fd, "Unable to open timestamp file "TIMESYNCD_CLOCK_FILE", ignoring: %m"); /* Create stamp file with the compiled-in date */ - r = touch_file(TIMESYNCD_CLOCK_FILE, /* parents= */ false, epoch, uid, gid, 0644); + r = touch_file(TIMESYNCD_CLOCK_FILE, /* parents= */ false, epoch, UID_INVALID, GID_INVALID, MODE_INVALID); if (r < 0) log_debug_errno(r, "Failed to create %s, ignoring: %m", TIMESYNCD_CLOCK_FILE); } else { @@ -103,13 +95,6 @@ static int load_clock_timestamp(uid_t uid, gid_t gid) { if (fstat(fd, &st) < 0) return log_error_errno(errno, "Unable to stat timestamp file "TIMESYNCD_CLOCK_FILE": %m"); - /* Try to fix the access mode, so that we can still touch the file after dropping - * privileges */ - r = fchmod_and_chown(fd, 0644, uid, gid); - if (r < 0) - log_full_errno(ERRNO_IS_PRIVILEGE(r) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to chmod or chown %s, ignoring: %m", TIMESYNCD_CLOCK_FILE); - epoch = MAX(epoch, timespec_load(&st.st_mtim)); (void) advance_tstamp(fd, epoch); @@ -140,9 +125,6 @@ static int load_clock_timestamp(uid_t uid, gid_t gid) { static int run(int argc, char *argv[]) { _cleanup_(manager_freep) Manager *m = NULL; _unused_ _cleanup_(notify_on_cleanup) const char *notify_message = NULL; - const char *user = "systemd-timesync"; - uid_t uid, uid_current; - gid_t gid; int r; log_set_facility(LOG_CRON); @@ -161,27 +143,10 @@ static int run(int argc, char *argv[]) { if (argc != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program does not take arguments."); - uid = uid_current = geteuid(); - gid = getegid(); - - if (uid_current == 0) { - r = get_user_creds(&user, &uid, &gid, NULL, NULL, 0); - if (r < 0) - return log_error_errno(r, "Cannot resolve user name %s: %m", user); - } - - r = load_clock_timestamp(uid, gid); + r = load_clock_timestamp(); if (r < 0) return r; - /* Drop privileges, but only if we have been started as root. If we are not running as root we assume all - * privileges are already dropped. */ - if (uid_current == 0) { - r = drop_privileges(uid, gid, (1ULL << CAP_SYS_TIME)); - if (r < 0) - return log_error_errno(r, "Failed to drop privileges: %m"); - } - r = manager_new(&m); if (r < 0) return log_error_errno(r, "Failed to allocate manager: %m"); From 4268e95fb98182137d6acf5c0e3ecb606e52d267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 19 Mar 2026 11:43:08 +0100 Subject: [PATCH 0361/1296] test-bpf-token: convert "intro" to a test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This file was a bit strange… It was shoehorning a manual test into the intro block and not using the rest of the TEST machinery. Let's convert it into a normal executable with a run function as we do in other similar cases. --- src/test/test-bpf-token.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/test/test-bpf-token.c b/src/test/test-bpf-token.c index f0ba50e715f84..7e83a048e52f9 100644 --- a/src/test/test-bpf-token.c +++ b/src/test/test-bpf-token.c @@ -4,9 +4,10 @@ #include #include "fd-util.h" -#include "tests.h" +#include "main-func.h" +#include "tests.h" /* NOLINT(misc-include-cleaner): this is needed conditionally */ -static int intro(void) { +static int run(int argc, char *argv[]) { #if defined(LIBBPF_MAJOR_VERSION) && (LIBBPF_MAJOR_VERSION > 1 || (LIBBPF_MAJOR_VERSION == 1 && LIBBPF_MINOR_VERSION >= 5)) _cleanup_close_ int bpffs_fd = open("/sys/fs/bpf", O_RDONLY); if (bpffs_fd < 0) @@ -16,10 +17,11 @@ static int intro(void) { if (token_fd < 0) return log_error_errno(errno, "Failed to create bpf token: %m"); - return EXIT_SUCCESS; + log_info("Successfully created token fd."); + return 0; #else return log_tests_skipped("libbpf is older than v1.5"); #endif } -DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); +DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); From abb03c7c9d32bebbb9ea67eb2c35a2ccb2088486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 19 Mar 2026 11:44:40 +0100 Subject: [PATCH 0362/1296] tests: drop _weak_ from the SYSTEMD_TEST_TABLE definition This will cause test binaries that reference SYSTEMD_TEST_TABLE, e.g. by trying to iterate over the test list, to fail if no tests are defined. I think this is the correct thing to do, as the lack of tests indicates some kind of mistake. --- src/shared/tests.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/tests.h b/src/shared/tests.h index ae57cab3863c5..607d62fe6aa0e 100644 --- a/src/shared/tests.h +++ b/src/shared/tests.h @@ -110,8 +110,8 @@ typedef struct TestFunc { ##__VA_ARGS__ \ } -extern const TestFunc _weak_ __start_SYSTEMD_TEST_TABLE[]; -extern const TestFunc _weak_ __stop_SYSTEMD_TEST_TABLE[]; +extern const TestFunc __start_SYSTEMD_TEST_TABLE[]; +extern const TestFunc __stop_SYSTEMD_TEST_TABLE[]; #define TEST(name, ...) \ static void test_##name(void); \ From 55356a78219e5c55bd90d83813630c6dc422afee Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Thu, 19 Mar 2026 16:05:52 +0100 Subject: [PATCH 0363/1296] units: allow io.systemd.Hostname to be available earlier Currently the varlink interface for hostname is only available after sysinit. This means it is not available until systemd-firstboot is finished. But there is information like the boot-id in there that is useful to get early. My use-case is to query the system early via the varlink-http-bridge and currently I can't get data from io.systemd.Hostname until systemd-firstboot is completed which is a bit limiting. So to fix it this commit sets DefaultDependencies=no on both the socket and service units. It also changes hostnamed.c to use bus_open_system_watch_bind_with_description() which means we will reconnect once dbus is available. This mimics what resolved-bus.c is doing (and which was originally introduced in d7afd945b). Thanks to Lennart for pointing this out. --- src/hostname/hostnamed.c | 2 +- units/systemd-hostnamed.service.in | 3 +++ units/systemd-hostnamed.socket | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index ce7187161834f..dcd1264534970 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -1964,7 +1964,7 @@ static int connect_bus(Context *c) { assert(c->event); assert(!c->bus); - r = sd_bus_default_system(&c->bus); + r = bus_open_system_watch_bind_with_description(&c->bus, "bus-api-hostname"); if (r < 0) return log_error_errno(r, "Failed to get system bus connection: %m"); diff --git a/units/systemd-hostnamed.service.in b/units/systemd-hostnamed.service.in index ab00c24b53b27..9bc58f4c13437 100644 --- a/units/systemd-hostnamed.service.in +++ b/units/systemd-hostnamed.service.in @@ -13,6 +13,9 @@ Documentation=man:systemd-hostnamed.service(8) Documentation=man:hostname(5) Documentation=man:machine-info(5) Documentation=man:org.freedesktop.hostname1(5) +DefaultDependencies=no +Conflicts=shutdown.target +Before=shutdown.target [Service] Type=notify diff --git a/units/systemd-hostnamed.socket b/units/systemd-hostnamed.socket index 288e736b47134..f84853ade8af2 100644 --- a/units/systemd-hostnamed.socket +++ b/units/systemd-hostnamed.socket @@ -12,6 +12,9 @@ Description=Hostname Service Socket Documentation=man:systemd-hostnamed.service(8) Documentation=man:hostname(5) Documentation=man:machine-info(5) +DefaultDependencies=no +Conflicts=shutdown.target +Before=shutdown.target sockets.target [Socket] ListenStream=/run/systemd/io.systemd.Hostname From 9a708c5115d8b10f4ea21b9e16ef47c008ddcdc9 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 18 Mar 2026 11:38:48 +0100 Subject: [PATCH 0364/1296] shared: extract `socket_forward_new()` helper from socket-proxyd This commit extracts the socket forwarding code from the existing socket-proxyd into a new shared helper that will be used by the varlinkctl protocol upgrade support code and is used as is in the socket-proxyd.c. It tries to keep the changes as small as possible, its mostly renaming like: * connection_create_pipes -> socket_forward_create_pipes * connection_shovel -> socket_forward_shovel * connection_enable_event_sources -> socket_forward_enable_event_sources * traffic_cb -> socket_forward_traffic_cb and a new socket_forward_new() that creates/starts the forwarding. All log_error_errno() got downgraded to log_debug_errno(). --- mkosi/mkosi.sanitizers/mkosi.postinst | 2 +- src/shared/meson.build | 1 + src/shared/socket-forward.c | 256 ++++++++++++++++++++++++++ src/shared/socket-forward.h | 29 +++ src/socket-proxy/socket-proxyd.c | 202 ++------------------ 5 files changed, 305 insertions(+), 185 deletions(-) create mode 100644 src/shared/socket-forward.c create mode 100644 src/shared/socket-forward.h diff --git a/mkosi/mkosi.sanitizers/mkosi.postinst b/mkosi/mkosi.sanitizers/mkosi.postinst index 229a5368b92f4..72356005e9337 100755 --- a/mkosi/mkosi.sanitizers/mkosi.postinst +++ b/mkosi/mkosi.sanitizers/mkosi.postinst @@ -42,8 +42,8 @@ fi wrap=( /usr/lib/polkit-1/polkitd - /usr/libexec/polkit-1/polkitd /usr/lib/systemd/tests/testdata/TEST-74-AUX-UTILS.units/proxy-echo.py + /usr/libexec/polkit-1/polkitd agetty btrfs capsh diff --git a/src/shared/meson.build b/src/shared/meson.build index bbc0307999324..e8a86b11b0659 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -181,6 +181,7 @@ shared_sources = files( 'smack-util.c', 'smbios11.c', 'snapshot-util.c', + 'socket-forward.c', 'socket-label.c', 'socket-netlink.c', 'specifier.c', diff --git a/src/shared/socket-forward.c b/src/shared/socket-forward.c new file mode 100644 index 0000000000000..2601b25e6daab --- /dev/null +++ b/src/shared/socket-forward.c @@ -0,0 +1,256 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-event.h" + +#include "alloc-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "log.h" +#include "socket-forward.h" + +#define SOCKET_FORWARD_BUFFER_SIZE (256 * 1024) + +struct SocketForward { + sd_event *event; + + int server_fd, client_fd; + + int server_to_client_buffer[2]; /* a pipe */ + int client_to_server_buffer[2]; /* a pipe */ + + size_t server_to_client_buffer_full, client_to_server_buffer_full; + size_t server_to_client_buffer_size, client_to_server_buffer_size; + + sd_event_source *server_event_source, *client_event_source; + + socket_forward_done_t on_done; + void *userdata; +}; + +SocketForward* socket_forward_free(SocketForward *sf) { + if (!sf) + return NULL; + + sd_event_source_unref(sf->server_event_source); + sd_event_source_unref(sf->client_event_source); + + safe_close(sf->server_fd); + safe_close(sf->client_fd); + + safe_close_pair(sf->server_to_client_buffer); + safe_close_pair(sf->client_to_server_buffer); + + sd_event_unref(sf->event); + + return mfree(sf); +} + +static int socket_forward_create_pipes(int buffer[static 2], size_t *ret_size) { + int r; + + assert(buffer); + assert(ret_size); + + if (buffer[0] >= 0) + return 0; + + r = pipe2(buffer, O_CLOEXEC|O_NONBLOCK); + if (r < 0) + return log_debug_errno(errno, "Failed to allocate pipe buffer: %m"); + + (void) fcntl(buffer[0], F_SETPIPE_SZ, SOCKET_FORWARD_BUFFER_SIZE); + + r = fcntl(buffer[0], F_GETPIPE_SZ); + if (r < 0) + return log_debug_errno(errno, "Failed to get pipe buffer size: %m"); + + assert(r > 0); + *ret_size = r; + + return 0; +} + +static int socket_forward_shovel( + int *from, int buffer[2], int *to, + size_t *full, size_t *sz, + sd_event_source **from_source, sd_event_source **to_source) { + + bool shoveled; + + assert(from); + assert(buffer); + assert(buffer[0] >= 0); + assert(buffer[1] >= 0); + assert(to); + assert(full); + assert(sz); + assert(from_source); + assert(to_source); + + do { + ssize_t z; + + shoveled = false; + + if (*full < *sz && *from >= 0 && *to >= 0) { + z = splice(*from, NULL, buffer[1], NULL, *sz - *full, SPLICE_F_MOVE|SPLICE_F_NONBLOCK); + if (z > 0) { + *full += z; + shoveled = true; + } else if (z == 0 || ERRNO_IS_DISCONNECT(errno)) { + *from_source = sd_event_source_unref(*from_source); + *from = safe_close(*from); + } else if (!ERRNO_IS_TRANSIENT(errno)) + return log_debug_errno(errno, "Failed to splice: %m"); + } + + if (*full > 0 && *to >= 0) { + z = splice(buffer[0], NULL, *to, NULL, *full, SPLICE_F_MOVE|SPLICE_F_NONBLOCK); + if (z > 0) { + *full -= z; + shoveled = true; + } else if (z == 0 || ERRNO_IS_DISCONNECT(errno)) { + *to_source = sd_event_source_unref(*to_source); + *to = safe_close(*to); + } else if (!ERRNO_IS_TRANSIENT(errno)) + return log_debug_errno(errno, "Failed to splice: %m"); + } + } while (shoveled); + + return 0; +} + +static int socket_forward_enable_event_sources(SocketForward *sf); + +static int socket_forward_traffic_cb(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + SocketForward *sf = ASSERT_PTR(userdata); + int r; + + assert(s); + assert(fd >= 0); + + r = socket_forward_shovel( + &sf->server_fd, sf->server_to_client_buffer, &sf->client_fd, + &sf->server_to_client_buffer_full, &sf->server_to_client_buffer_size, + &sf->server_event_source, &sf->client_event_source); + if (r < 0) + goto quit; + + r = socket_forward_shovel( + &sf->client_fd, sf->client_to_server_buffer, &sf->server_fd, + &sf->client_to_server_buffer_full, &sf->client_to_server_buffer_size, + &sf->client_event_source, &sf->server_event_source); + if (r < 0) + goto quit; + + /* EOF on both sides? */ + if (sf->server_fd < 0 && sf->client_fd < 0) + goto quit; + + /* Server closed, and all data written to client? */ + if (sf->server_fd < 0 && sf->server_to_client_buffer_full <= 0) + goto quit; + + /* Client closed, and all data written to server? */ + if (sf->client_fd < 0 && sf->client_to_server_buffer_full <= 0) + goto quit; + + r = socket_forward_enable_event_sources(sf); + if (r < 0) + goto quit; + + return 1; + +quit: + return sf->on_done(sf, r, sf->userdata); +} + +static int socket_forward_enable_event_sources(SocketForward *sf) { + uint32_t a = 0, b = 0; + int r; + + assert(sf); + + if (sf->server_to_client_buffer_full > 0) + b |= EPOLLOUT; + if (sf->server_to_client_buffer_full < sf->server_to_client_buffer_size) + a |= EPOLLIN; + + if (sf->client_to_server_buffer_full > 0) + a |= EPOLLOUT; + if (sf->client_to_server_buffer_full < sf->client_to_server_buffer_size) + b |= EPOLLIN; + + if (sf->server_event_source) + r = sd_event_source_set_io_events(sf->server_event_source, a); + else if (sf->server_fd >= 0) + r = sd_event_add_io(sf->event, &sf->server_event_source, sf->server_fd, a, socket_forward_traffic_cb, sf); + else + r = 0; + if (r < 0) + return log_debug_errno(r, "Failed to set up server event source: %m"); + + if (sf->client_event_source) + r = sd_event_source_set_io_events(sf->client_event_source, b); + else if (sf->client_fd >= 0) + r = sd_event_add_io(sf->event, &sf->client_event_source, sf->client_fd, b, socket_forward_traffic_cb, sf); + else + r = 0; + if (r < 0) + return log_debug_errno(r, "Failed to set up client event source: %m"); + + return 0; +} + +int socket_forward_new( + sd_event *event, + int server_fd, + int client_fd, + socket_forward_done_t on_done, + void *userdata, + SocketForward **ret) { + + _cleanup_(socket_forward_freep) SocketForward *sf = NULL; + int r; + + assert(event); + assert(server_fd >= 0); + assert(client_fd >= 0); + assert(on_done); + assert(ret); + + sf = new(SocketForward, 1); + if (!sf) { + safe_close(server_fd); + safe_close(client_fd); + return log_oom_debug(); + } + + *sf = (SocketForward) { + .event = sd_event_ref(event), + .server_fd = server_fd, + .client_fd = client_fd, + .server_to_client_buffer = EBADF_PAIR, + .client_to_server_buffer = EBADF_PAIR, + .on_done = on_done, + .userdata = userdata, + }; + + r = socket_forward_create_pipes(sf->server_to_client_buffer, &sf->server_to_client_buffer_size); + if (r < 0) + return r; + + r = socket_forward_create_pipes(sf->client_to_server_buffer, &sf->client_to_server_buffer_size); + if (r < 0) + return r; + + r = socket_forward_enable_event_sources(sf); + if (r < 0) + return r; + + *ret = TAKE_PTR(sf); + return 0; +} diff --git a/src/shared/socket-forward.h b/src/shared/socket-forward.h new file mode 100644 index 0000000000000..a2d34da38c6b9 --- /dev/null +++ b/src/shared/socket-forward.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +/* Bidirectional socket forwarder using splice(). + * + * Forwards data between two bidirectional sockets ("server" and "client") via kernel pipe buffers, + * avoiding userspace copies. + * + * When forwarding completes (both directions reach EOF or error), the completion callback is invoked. + * + * The SocketForward takes ownership of both fds - they are closed when the SocketForward is freed + * (or earlier, during normal forwarding when EOF/disconnect is detected). */ + +typedef struct SocketForward SocketForward; + +typedef int (*socket_forward_done_t)(SocketForward *sf, int error, void *userdata); + +int socket_forward_new( + sd_event *event, + int server_fd, + int client_fd, + socket_forward_done_t on_done, + void *userdata, + SocketForward **ret); + +SocketForward* socket_forward_free(SocketForward *sf); +DEFINE_TRIVIAL_CLEANUP_FUNC(SocketForward*, socket_forward_free); diff --git a/src/socket-proxy/socket-proxyd.c b/src/socket-proxy/socket-proxyd.c index 71172326da125..e1eec1dd41c82 100644 --- a/src/socket-proxy/socket-proxyd.c +++ b/src/socket-proxy/socket-proxyd.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -22,12 +21,11 @@ #include "pretty-print.h" #include "resolve-private.h" #include "set.h" +#include "socket-forward.h" #include "socket-util.h" #include "string-util.h" #include "time-util.h" -#define BUFFER_SIZE (256 * 1024) - static unsigned arg_connections_max = 256; static const char *arg_remote_host = NULL; static usec_t arg_exit_idle_time = USEC_INFINITY; @@ -45,13 +43,10 @@ typedef struct Connection { Context *context; int server_fd, client_fd; - int server_to_client_buffer[2]; /* a pipe */ - int client_to_server_buffer[2]; /* a pipe */ - size_t server_to_client_buffer_full, client_to_server_buffer_full; - size_t server_to_client_buffer_size, client_to_server_buffer_size; + sd_event_source *connect_event_source; - sd_event_source *server_event_source, *client_event_source; + SocketForward *forward; sd_resolve_query *resolve_query; } Connection; @@ -63,15 +58,12 @@ static Connection* connection_free(Connection *c) { if (c->context) set_remove(c->context->connections, c); - sd_event_source_unref(c->server_event_source); - sd_event_source_unref(c->client_event_source); + sd_event_source_unref(c->connect_event_source); + socket_forward_free(c->forward); safe_close(c->server_fd); safe_close(c->client_fd); - safe_close_pair(c->server_to_client_buffer); - safe_close_pair(c->client_to_server_buffer); - sd_resolve_query_unref(c->resolve_query); return mfree(c); @@ -134,185 +126,29 @@ static void connection_release(Connection *c) { context_reset_timer(context); } -static int connection_create_pipes(Connection *c, int buffer[static 2], size_t *sz) { - int r; - - assert(c); - assert(buffer); - assert(sz); - - if (buffer[0] >= 0) - return 0; - - r = pipe2(buffer, O_CLOEXEC|O_NONBLOCK); - if (r < 0) - return log_error_errno(errno, "Failed to allocate pipe buffer: %m"); - - (void) fcntl(buffer[0], F_SETPIPE_SZ, BUFFER_SIZE); - - r = fcntl(buffer[0], F_GETPIPE_SZ); - if (r < 0) - return log_error_errno(errno, "Failed to get pipe buffer size: %m"); - - assert(r > 0); - *sz = r; - - return 0; -} - -static int connection_shovel( - Connection *c, - int *from, int buffer[2], int *to, - size_t *full, size_t *sz, - sd_event_source **from_source, sd_event_source **to_source) { - - bool shoveled; - - assert(c); - assert(from); - assert(buffer); - assert(buffer[0] >= 0); - assert(buffer[1] >= 0); - assert(to); - assert(full); - assert(sz); - assert(from_source); - assert(to_source); - - do { - ssize_t z; - - shoveled = false; - - if (*full < *sz && *from >= 0 && *to >= 0) { - z = splice(*from, NULL, buffer[1], NULL, *sz - *full, SPLICE_F_MOVE|SPLICE_F_NONBLOCK); - if (z > 0) { - *full += z; - shoveled = true; - } else if (z == 0 || ERRNO_IS_DISCONNECT(errno)) { - *from_source = sd_event_source_unref(*from_source); - *from = safe_close(*from); - } else if (!ERRNO_IS_TRANSIENT(errno)) - return log_error_errno(errno, "Failed to splice: %m"); - } - - if (*full > 0 && *to >= 0) { - z = splice(buffer[0], NULL, *to, NULL, *full, SPLICE_F_MOVE|SPLICE_F_NONBLOCK); - if (z > 0) { - *full -= z; - shoveled = true; - } else if (z == 0 || ERRNO_IS_DISCONNECT(errno)) { - *to_source = sd_event_source_unref(*to_source); - *to = safe_close(*to); - } else if (!ERRNO_IS_TRANSIENT(errno)) - return log_error_errno(errno, "Failed to splice: %m"); - } - } while (shoveled); - - return 0; -} - -static int connection_enable_event_sources(Connection *c); - -static int traffic_cb(sd_event_source *s, int fd, uint32_t revents, void *userdata) { +static int connection_forward_done(SocketForward *sf, int error, void *userdata) { Connection *c = ASSERT_PTR(userdata); - int r; - - assert(s); - assert(fd >= 0); - r = connection_shovel(c, - &c->server_fd, c->server_to_client_buffer, &c->client_fd, - &c->server_to_client_buffer_full, &c->server_to_client_buffer_size, - &c->server_event_source, &c->client_event_source); - if (r < 0) - goto quit; - - r = connection_shovel(c, - &c->client_fd, c->client_to_server_buffer, &c->server_fd, - &c->client_to_server_buffer_full, &c->client_to_server_buffer_size, - &c->client_event_source, &c->server_event_source); - if (r < 0) - goto quit; - - /* EOF on both sides? */ - if (c->server_fd < 0 && c->client_fd < 0) - goto quit; - - /* Server closed, and all data written to client? */ - if (c->server_fd < 0 && c->server_to_client_buffer_full <= 0) - goto quit; - - /* Client closed, and all data written to server? */ - if (c->client_fd < 0 && c->client_to_server_buffer_full <= 0) - goto quit; - - r = connection_enable_event_sources(c); - if (r < 0) - goto quit; + if (error < 0) + log_error_errno(error, "Forwarding failed: %m"); - return 1; - -quit: connection_release(c); return 0; /* ignore errors, continue serving */ } -static int connection_enable_event_sources(Connection *c) { - uint32_t a = 0, b = 0; - int r; - - assert(c); - - if (c->server_to_client_buffer_full > 0) - b |= EPOLLOUT; - if (c->server_to_client_buffer_full < c->server_to_client_buffer_size) - a |= EPOLLIN; - - if (c->client_to_server_buffer_full > 0) - a |= EPOLLOUT; - if (c->client_to_server_buffer_full < c->client_to_server_buffer_size) - b |= EPOLLIN; - - if (c->server_event_source) - r = sd_event_source_set_io_events(c->server_event_source, a); - else if (c->server_fd >= 0) - r = sd_event_add_io(c->context->event, &c->server_event_source, c->server_fd, a, traffic_cb, c); - else - r = 0; - - if (r < 0) - return log_error_errno(r, "Failed to set up server event source: %m"); - - if (c->client_event_source) - r = sd_event_source_set_io_events(c->client_event_source, b); - else if (c->client_fd >= 0) - r = sd_event_add_io(c->context->event, &c->client_event_source, c->client_fd, b, traffic_cb, c); - else - r = 0; - - if (r < 0) - return log_error_errno(r, "Failed to set up client event source: %m"); - - return 0; -} - static int connection_complete(Connection *c) { int r; assert(c); - r = connection_create_pipes(c, c->server_to_client_buffer, &c->server_to_client_buffer_size); - if (r < 0) - return r; - - r = connection_create_pipes(c, c->client_to_server_buffer, &c->client_to_server_buffer_size); + r = socket_forward_new( + c->context->event, + TAKE_FD(c->server_fd), + TAKE_FD(c->client_fd), + connection_forward_done, c, + &c->forward); if (r < 0) - return r; - - r = connection_enable_event_sources(c); - if (r < 0) - return r; + return log_error_errno(r, "Failed to set up forwarding: %m"); return 0; } @@ -336,7 +172,7 @@ static int connect_cb(sd_event_source *s, int fd, uint32_t revents, void *userda goto fail; } - c->client_event_source = sd_event_source_unref(c->client_event_source); + c->connect_event_source = sd_event_source_unref(c->connect_event_source); if (connection_complete(c) < 0) goto fail; @@ -364,11 +200,11 @@ static int connection_start(Connection *c, struct sockaddr *sa, socklen_t salen) if (errno != EINPROGRESS) return log_error_errno(errno, "Failed to connect to remote host: %m"); - r = sd_event_add_io(c->context->event, &c->client_event_source, c->client_fd, EPOLLOUT, connect_cb, c); + r = sd_event_add_io(c->context->event, &c->connect_event_source, c->client_fd, EPOLLOUT, connect_cb, c); if (r < 0) return log_error_errno(r, "Failed to add connection socket: %m"); - r = sd_event_source_set_enabled(c->client_event_source, SD_EVENT_ONESHOT); + r = sd_event_source_set_enabled(c->connect_event_source, SD_EVENT_ONESHOT); if (r < 0) return log_error_errno(r, "Failed to enable oneshot event source: %m"); @@ -472,8 +308,6 @@ static int context_add_connection(Context *context, int fd) { *c = (Connection) { .server_fd = TAKE_FD(nfd), .client_fd = -EBADF, - .server_to_client_buffer = EBADF_PAIR, - .client_to_server_buffer = EBADF_PAIR, }; r = set_ensure_put(&context->connections, &connection_hash_ops, c); From 67387626884afec7dbb64cb78a39a3676b7ff663 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 27 Feb 2026 10:05:16 +0100 Subject: [PATCH 0365/1296] fileio: introduce write_data_file_atomic_at() helper This is very similar to write_string_file_atomic(), but is intentionally kept separate (after long consideration). It focusses on arbitrary struct iovec data, not just strings, and hence also doesn't do stdio at all. It's hence a lot more low-level. We might want to consider moving write_string_file*() on top of write_data_file_atomic_at(), but for now don't. --- src/basic/fileio.c | 62 ++++++++++++++++++++++++++++++++++++++++++ src/basic/fileio.h | 6 ++++ src/test/test-fileio.c | 52 +++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+) diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 90436f6ecf820..66d06484dc981 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -7,12 +7,15 @@ #include #include "alloc-util.h" +#include "chase.h" #include "errno-util.h" #include "extract-word.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" #include "hexdecoct.h" +#include "io-util.h" +#include "iovec-util.h" #include "label.h" #include "log.h" #include "mkdir.h" @@ -1655,3 +1658,62 @@ int warn_file_is_world_accessible(const char *filename, struct stat *st, const c filename, st->st_mode & 07777); return 0; } + +int write_data_file_atomic_at( + int dir_fd, + const char *path, + const struct iovec *iovec, + WriteDataFileFlags flags) { + + int r; + + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); + + /* This is a cousin of write_string_file_atomic(), but operates with arbitrary struct iovec binary + * data (rather than strings), works without FILE* streams, and does direct syscalls instead. */ + + _cleanup_free_ char *dn = NULL, *fn = NULL; + r = path_split_prefix_filename(path, &dn, &fn); + if (IN_SET(r, -EADDRNOTAVAIL, O_DIRECTORY)) + return -EISDIR; /* path refers to "." or "/" (which are dirs, which we cannot write), or is suffixed with "/" */ + if (r < 0) + return r; + + _cleanup_close_ int mfd = -EBADF; + if (dn) { + /* If there's a directory component, readjust our position */ + r = chaseat(dir_fd, + dn, + FLAGS_SET(flags, WRITE_DATA_FILE_MKDIR_0755) ? CHASE_MKDIR_0755 : 0, + /* ret_path= */ NULL, + &mfd); + if (r < 0) + return r; + + dir_fd = mfd; + } + + _cleanup_free_ char *t = NULL; + _cleanup_close_ int fd = open_tmpfile_linkable_at(dir_fd, fn, O_WRONLY|O_CLOEXEC, &t); + if (fd < 0) + return fd; + + CLEANUP_TMPFILE_AT(dir_fd, t); + + if (iovec_is_set(iovec)) { + r = loop_write(fd, iovec->iov_base, iovec->iov_len); + if (r < 0) + return r; + } + + r = fchmod_umask(fd, 0644); + if (r < 0) + return r; + + r = link_tmpfile_at(fd, dir_fd, t, fn, LINK_TMPFILE_REPLACE); + if (r < 0) + return r; + + t = mfree(t); /* disarm CLEANUP_TMPFILE_AT */ + return 0; +} diff --git a/src/basic/fileio.h b/src/basic/fileio.h index 578c16c0ee394..3e2372c4dddbc 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -163,3 +163,9 @@ int safe_fgetc(FILE *f, char *ret); int warn_file_is_world_accessible(const char *filename, struct stat *st, const char *unit, unsigned line); int fopen_mode_to_flags(const char *mode); + +typedef enum WriteDataFileFlags { + WRITE_DATA_FILE_MKDIR_0755 = 1 << 0, +} WriteDataFileFlags; + +int write_data_file_atomic_at(int dir_fd, const char *path, const struct iovec *iovec, WriteDataFileFlags flags); diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index 38d92299467a7..575e2c52ed7df 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -9,6 +9,7 @@ #include "fd-util.h" #include "fileio.h" #include "fs-util.h" +#include "iovec-util.h" #include "memfd-util.h" #include "parse-util.h" #include "path-util.h" @@ -695,4 +696,55 @@ TEST(fdopen_independent) { f = safe_fclose(f); } +TEST(write_data_file_atomic_at) { + struct iovec a = IOVEC_MAKE_STRING("hallo"); + ASSERT_OK(write_data_file_atomic_at(AT_FDCWD, "/tmp/wdfa", &a, /* flags= */ 0)); + + _cleanup_(iovec_done) struct iovec ra = {}; + ASSERT_OK(read_full_file("/tmp/wdfa", (char**) &ra.iov_base, &ra.iov_len)); + ASSERT_EQ(iovec_memcmp(&a, &ra), 0); + ASSERT_OK_ERRNO(unlink("/tmp/wdfa")); + + ASSERT_OK(write_data_file_atomic_at(XAT_FDROOT, "tmp/wdfa", &a, /* flags= */ 0)); + iovec_done(&ra); + ASSERT_OK(read_full_file("/tmp/wdfa", (char**) &ra.iov_base, &ra.iov_len)); + ASSERT_EQ(iovec_memcmp(&a, &ra), 0); + ASSERT_OK_ERRNO(unlink("/tmp/wdfa")); + + ASSERT_ERROR(write_data_file_atomic_at(AT_FDCWD, NULL, &a, /* flags= */ 0), EINVAL); + ASSERT_ERROR(write_data_file_atomic_at(AT_FDCWD, "", &a, /* flags= */ 0), EINVAL); + ASSERT_ERROR(write_data_file_atomic_at(AT_FDCWD, "/", &a, /* flags= */ 0), EISDIR); + ASSERT_ERROR(write_data_file_atomic_at(AT_FDCWD, ".", &a, /* flags= */ 0), EISDIR); + ASSERT_ERROR(write_data_file_atomic_at(AT_FDCWD, "/tmp/", &a, /* flags= */ 0), EISDIR); + + _cleanup_free_ char *cwd = NULL; + ASSERT_OK(safe_getcwd(&cwd)); + ASSERT_OK_ERRNO(chdir("/tmp")); + + ASSERT_OK(write_data_file_atomic_at(AT_FDCWD, "wdfa", &a, /* flags= */ 0)); + iovec_done(&ra); + ASSERT_OK(read_full_file("/tmp/wdfa", (char**) &ra.iov_base, &ra.iov_len)); + ASSERT_EQ(iovec_memcmp(&a, &ra), 0); + ASSERT_OK_ERRNO(unlink("/tmp/wdfa")); + + ASSERT_OK(write_data_file_atomic_at(XAT_FDROOT, "tmp/wdfa", &a, /* flags= */ 0)); + iovec_done(&ra); + ASSERT_OK(read_full_file("/tmp/wdfa", (char**) &ra.iov_base, &ra.iov_len)); + ASSERT_EQ(iovec_memcmp(&a, &ra), 0); + ASSERT_OK_ERRNO(unlink("/tmp/wdfa")); + + ASSERT_OK_ERRNO(chdir(cwd)); + + ASSERT_ERROR(write_data_file_atomic_at(XAT_FDROOT, "tmp/zzz/wdfa", &a, /* flags= */ 0), ENOENT); + ASSERT_OK(write_data_file_atomic_at(XAT_FDROOT, "tmp/zzz/wdfa", &a, WRITE_DATA_FILE_MKDIR_0755)); + iovec_done(&ra); + ASSERT_OK(read_full_file("/tmp/zzz/wdfa", (char**) &ra.iov_base, &ra.iov_len)); + ASSERT_EQ(iovec_memcmp(&a, &ra), 0); + ASSERT_OK_ERRNO(unlink("/tmp/zzz/wdfa")); + + ASSERT_ERROR(write_data_file_atomic_at(AT_FDCWD, "/tmp/zzz", &a, /* flags= */ 0), EEXIST); + + ASSERT_OK_ERRNO(rmdir("/tmp/zzz")); +} + DEFINE_TEST_MAIN(LOG_DEBUG); From 56356c9deca6bc2142b8f69c94aea91ca47d81e9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 26 Feb 2026 11:24:21 +0100 Subject: [PATCH 0366/1296] udev: tag DMI id device with "systemd", so that we can order units after it For various usecases it is useful to read relevant data from the DMI udev device, but this means we need a way to wait for it for this to be probed to be race-free. Hence tag it with "systemd", so that sys-devices-virtual-dmi-id.device can be used as synchronization point. --- rules.d/60-dmi-id.rules | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/rules.d/60-dmi-id.rules b/rules.d/60-dmi-id.rules index 10b1fe000ca18..ecea74ec60d1c 100644 --- a/rules.d/60-dmi-id.rules +++ b/rules.d/60-dmi-id.rules @@ -2,24 +2,28 @@ ACTION=="remove", GOTO="dmi_end" SUBSYSTEM!="dmi", GOTO="dmi_end" +KERNEL!="id", GOTO="dmi_end" ENV{ID_SYS_VENDOR_IS_RUBBISH}!="1", ENV{ID_VENDOR}="$attr{sys_vendor}" ENV{ID_SYSFS_ATTRIBUTE_MODEL}=="", ENV{ID_PRODUCT_NAME_IS_RUBBISH}!="1", ENV{ID_MODEL}="$attr{product_name}" ENV{ID_SYSFS_ATTRIBUTE_MODEL}=="product_name", ENV{ID_MODEL}="$attr{product_name}" ENV{ID_SYSFS_ATTRIBUTE_MODEL}=="product_version", ENV{ID_MODEL}="$attr{product_version}" -# fallback to board information +# Fallback to board information ENV{ID_VENDOR}=="", ENV{ID_VENDOR}="$attr{board_vendor}" ENV{ID_MODEL}=="", ENV{ID_MODEL}="$attr{board_name}" -# stock keeping unit +# Stock keeping unit ENV{ID_PRODUCT_SKU_IS_RUBBISH}!="1", ENV{ID_SKU}="$attr{product_sku}" -# hardware version +# Hardware version ENV{ID_PRODUCT_VERSION_IS_RUBBISH}!="1", ENV{ID_HARDWARE_VERSION}="$attr{product_version}" ENV{ID_HARDWARE_VERSION}=="", ENV{ID_BOARD_VERSION_IS_RUBBISH}!="1", ENV{ID_HARDWARE_VERSION}="$attr{board_version}" -# chassis asset tag +# Chassis asset tag ENV{MODALIAS}!="", ATTR{chassis_asset_tag}!="", IMPORT{builtin}="hwdb '$attr{modalias}cat$attr{chassis_asset_tag}:'" ENV{ID_CHASSIS_ASSET_TAG_IS_RUBBISH}!="1", ENV{ID_CHASSIS_ASSET_TAG}="$attr{chassis_asset_tag}" +# Allow units to be ordered after the DMI device +TAG+="systemd" + LABEL="dmi_end" From 56f3ae9292742fff1cac6dbe4883a1715e56e874 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 26 Feb 2026 10:10:53 +0100 Subject: [PATCH 0367/1296] iovec-util: introduce IOVEC_MAKE_BYTE() helper --- src/basic/iovec-util.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/basic/iovec-util.h b/src/basic/iovec-util.h index 0d1d4a7a94d86..00cbb89a7790b 100644 --- a/src/basic/iovec-util.h +++ b/src/basic/iovec-util.h @@ -24,6 +24,12 @@ struct iovec* iovec_make_string(struct iovec *iovec, const char *s); .iov_len = STRLEN(s), \ } +#define IOVEC_MAKE_BYTE(c) \ + (const struct iovec) { \ + .iov_base = (char*) ((const char[]) { c }), \ + .iov_len = 1, \ + } + void iovec_done_erase(struct iovec *iovec); char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value); From 4460a4ba2155619a2f1abb1c8de577292b260162 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 11 Mar 2026 11:15:27 +0100 Subject: [PATCH 0368/1296] firstboot: harden credential handling a bit Credentials are highly privileged things, but still, let's do some validation, because we can. --- src/firstboot/firstboot.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 38e3adaed6eca..ae1899593cdd9 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -412,11 +412,15 @@ static int prompt_keymap(int rfd, sd_varlink **mute_console_link) { if (arg_keymap) return 0; - r = read_credential("firstboot.keymap", (void**) &arg_keymap, NULL); + _cleanup_free_ char *km = NULL; + r = read_credential("firstboot.keymap", (void**) &km, NULL); if (r < 0) log_debug_errno(r, "Failed to read credential firstboot.keymap, ignoring: %m"); + else if (!keymap_is_valid(km)) + log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Keymap '%s' supplied via credential is not valid, ignoring.", km); else { log_debug("Acquired keymap from credential."); + arg_keymap = TAKE_PTR(km); return 0; } @@ -540,11 +544,15 @@ static int prompt_timezone(int rfd, sd_varlink **mute_console_link) { if (arg_timezone) return 0; - r = read_credential("firstboot.timezone", (void**) &arg_timezone, NULL); + _cleanup_free_ char *tz = NULL; + r = read_credential("firstboot.timezone", (void**) &tz, NULL); if (r < 0) log_debug_errno(r, "Failed to read credential firstboot.timezone, ignoring: %m"); + else if (!timezone_is_valid(tz, LOG_DEBUG)) + log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Timezone '%s' supplied via credential is not valid, ignoring.", tz); else { log_debug("Acquired timezone from credential."); + arg_timezone = TAKE_PTR(tz); return 0; } From 4c49e864b0b8d3685c8e53d415289806278955ea Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 5 Mar 2026 10:39:28 +0100 Subject: [PATCH 0369/1296] firstboot: permit setting the static hostname via a system credential For the IMDS case there's value in being able to set the static hostname, instead of just the transient one. Let's introduce firstboot.hostname, which only applies to first boot, and write the static hostname. This is different from system.hostname which applies to any boot, and writes the transient hostname. --- man/systemd-firstboot.xml | 11 +++++++++++ man/systemd.system-credentials.xml | 23 +++++++++++++++++++---- src/firstboot/firstboot.c | 13 +++++++++++++ 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/man/systemd-firstboot.xml b/man/systemd-firstboot.xml index 86a85f0bf2855..db6f2569a8d1f 100644 --- a/man/systemd-firstboot.xml +++ b/man/systemd-firstboot.xml @@ -447,6 +447,17 @@ + + + firstboot.hostname + + This credential specifies the static system hostname to set during first boot. The + user will not be prompted for the hostname. Note that this controls the static hostname, not the + transient hostname, and only has an effect on first boot, unlike + system.hostname. + + + Note that by default the systemd-firstboot.service unit file is set up to diff --git a/man/systemd.system-credentials.xml b/man/systemd.system-credentials.xml index e3e2887207784..a302be236d40d 100644 --- a/man/systemd.system-credentials.xml +++ b/man/systemd.system-credentials.xml @@ -44,7 +44,7 @@ firstboot.keymap - The console key mapping to set (e.g. de). Read by + The console key mapping to set (e.g. de). Read by systemd-firstboot1, and only honoured if no console keymap has been configured before. @@ -52,6 +52,20 @@ + + firstboot.hostname + + This credential specifies the static system hostname to set during first boot. The + user will not be prompted for the hostname. Note that this controls the static hostname, not the transient + hostname, and only has an effect on first boot, unlike system.hostname (see + below). Read by + systemd-firstboot1 + and only honoured if no static hostname has been configured before. + + + + + firstboot.locale firstboot.locale-messages @@ -398,9 +412,10 @@ system.hostname Accepts a (transient) hostname to configure during early boot. The static hostname specified - in /etc/hostname, if configured, takes precedence over this setting. - Interpreted by the service manager (PID 1). For details see - systemd1. + in /etc/hostname, if configured, takes precedence over this setting. + Interpreted by the service manager (PID 1). For details see + systemd1. Also + see firstboot.hostname above. diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index ae1899593cdd9..8cb81e7f06e70 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -655,6 +655,19 @@ static int prompt_hostname(int rfd, sd_varlink **mute_console_link) { if (arg_hostname) return 0; + _cleanup_free_ char *hn = NULL; + r = read_credential("firstboot.hostname", (void**) &hn, NULL); + if (r < 0) + log_debug_errno(r, "Failed to read credential firstboot.hostname, ignoring: %m"); + else if (!hostname_is_valid(hn, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK)) + log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Hostname '%s' supplied via credential is not valid, ignoring.", hn); + else { + log_debug("Acquired hostname from credentials."); + arg_hostname = TAKE_PTR(hn); + hostname_cleanup(arg_hostname); + return 0; + } + if (!arg_prompt_hostname) { log_debug("Prompting for hostname was not requested."); return 0; From dae21304ff6131b0c98764eccb55c055cbe42266 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sat, 7 Mar 2026 23:45:25 +0100 Subject: [PATCH 0370/1296] stub: make debug logging controllable via smbios11 work in the stub too, not just the boot menu Follow-up for: 0ce83b8a578f3076d9ecff6b1d59613ff4afa3b5 --- man/smbios-type-11.xml | 7 ++++--- man/systemd-stub.xml | 10 ++++++++++ src/boot/stub.c | 2 ++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/man/smbios-type-11.xml b/man/smbios-type-11.xml index 95754333a8818..c881592722850 100644 --- a/man/smbios-type-11.xml +++ b/man/smbios-type-11.xml @@ -88,9 +88,10 @@ io.systemd.boot.loglevel=LEVEL - This allows configuration of the log level, and is read by systemd-boot. - For details see - systemd-boot7. + This allows configuration of the log level, and is read by + systemd-boot and systemd-stub. For details see + systemd-boot7 and + systemd-stub7. diff --git a/man/systemd-stub.xml b/man/systemd-stub.xml index 2b40c1e561071..251d79ea6e14e 100644 --- a/man/systemd-stub.xml +++ b/man/systemd-stub.xml @@ -789,6 +789,16 @@ + + + io.systemd.boot.loglevel + If set, the value of this string is used as log level. Valid values (from most to + least critical) are emerg, alert, crit, + err, warning, notice, info, + and debug. + + + diff --git a/src/boot/stub.c b/src/boot/stub.c index 90f28a8ae32f7..7ef5a43a04ca5 100644 --- a/src/boot/stub.c +++ b/src/boot/stub.c @@ -1232,6 +1232,8 @@ static EFI_STATUS run(EFI_HANDLE image) { unsigned profile = 0; EFI_STATUS err; + log_set_max_level_from_smbios(); + err = BS->HandleProtocol(image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &loaded_image); if (err != EFI_SUCCESS) return log_error_status(err, "Error getting a LoadedImageProtocol handle: %m"); From 55e7dc5ce4999ba9f01499dccdeba0235a86aaa4 Mon Sep 17 00:00:00 2001 From: Robin Ebert Date: Fri, 20 Mar 2026 13:32:04 +0100 Subject: [PATCH 0371/1296] kernel-install: fix assert in context_copy --- src/kernel-install/kernel-install.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/kernel-install/kernel-install.c b/src/kernel-install/kernel-install.c index f830bd2bef86e..001e9e20e2f8a 100644 --- a/src/kernel-install/kernel-install.c +++ b/src/kernel-install/kernel-install.c @@ -152,10 +152,10 @@ static int context_copy(const Context *source, Context *ret) { assert(source); assert(ret); - assert(source->rfd >= 0 || source->rfd == AT_FDCWD); + assert(source->rfd >= 0 || source->rfd == AT_FDCWD || source->rfd == XAT_FDROOT); _cleanup_(context_done) Context copy = (Context) { - .rfd = AT_FDCWD, + .rfd = source->rfd, .action = source->action, .machine_id = source->machine_id, .machine_id_is_random = source->machine_id_is_random, From 69c087bade610f85cb8f9ebeae587e8a4aaa5007 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 20 Mar 2026 00:43:26 +0000 Subject: [PATCH 0372/1296] test: skip D-Bus FD truncation test with dbus-daemon dbus-daemon intentionally disconnects peers when FDs get truncated. Detect it and skip it in that case, as the purpose of the test is not to exercise the D-Bus implementation, but our library. When running with dbus-broker (Fedora, etc) we'll get full coverage. Fixes https://github.com/systemd/systemd/issues/41150 Follow-up for 744d589632c545e90ae76853abbfbc90cb530e24 --- src/libsystemd/sd-bus/test-bus-chat.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/libsystemd/sd-bus/test-bus-chat.c b/src/libsystemd/sd-bus/test-bus-chat.c index 3544b4580b7ba..1f358ccd3396e 100644 --- a/src/libsystemd/sd-bus/test-bus-chat.c +++ b/src/libsystemd/sd-bus/test-bus-chat.c @@ -567,11 +567,17 @@ TEST(ctrunc) { /* The very first message should be the one we expect */ ASSERT_OK(get_one_message(bus, &recvd)); - ASSERT_TRUE(sd_bus_message_is_method_call(recvd, "org.freedesktop.systemd.test", "SendFds")); /* This needs to succeed or the following tests are going to be unhappy... */ ASSERT_EQ(setrlimit(RLIMIT_NOFILE, &orig_rl), 0); + /* dbus-daemon disconnects peers when FDs get truncated + * https://github.com/systemd/systemd/issues/41150 */ + if (sd_bus_message_is_signal(recvd, "org.freedesktop.DBus.Local", "Disconnected") > 0) + return (void) log_tests_skipped("Running with dbus-daemon, which doesn't support fd passing with truncation"); + + ASSERT_TRUE(sd_bus_message_is_method_call(recvd, "org.freedesktop.systemd.test", "SendFds")); + /* Try to read all the fds. We expect at least one to fail with -EBADMSG due to * truncation, and all subsequent reads must also fail with -EBADMSG. */ int i; From fabc22f5998e610eb7ba70a963cab9f94dca5c0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 19 Mar 2026 17:06:17 +0100 Subject: [PATCH 0373/1296] meson: disable __attribute__((__retain__)) on old compilers This attribute was introduced in gcc 11, and our baseline is currently 8.4. So let's allow using _retain_ everywhere, but make it into a noop if not supported. Using __has_attribute was suggested, but with gcc-11.5.0-14.el9.x86_64, __has__attribute(__retain__) is true, but we get a warning when the attribute is actually used. --- meson.build | 6 ++++++ src/boot/meson.build | 7 +++++-- src/fundamental/macro-fundamental.h | 7 ++++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/meson.build b/meson.build index 3672005d75b17..c52f8e17c2bbe 100644 --- a/meson.build +++ b/meson.build @@ -524,6 +524,12 @@ if cc.compiles(''' add_project_arguments('-Werror=shadow', language : 'c') endif +have = cc.compiles( + '__attribute__((__retain__)) int x;', + args : '-Werror=attributes', + name : '__attribute__((__retain__))') +conf.set10('HAVE_ATTRIBUTE_RETAIN', have) + if cxx_cmd != '' add_project_arguments(cxx.get_supported_arguments(basic_disabled_warnings), language : 'cpp') endif diff --git a/src/boot/meson.build b/src/boot/meson.build index 06c8146a9ebcb..c51510e96f4a1 100644 --- a/src/boot/meson.build +++ b/src/boot/meson.build @@ -80,8 +80,11 @@ endif efi_conf = configuration_data() # import several configs from userspace -foreach name : ['HAVE_WARNING_ZERO_LENGTH_BOUNDS', 'HAVE_WARNING_ZERO_AS_NULL_POINTER_CONSTANT'] - efi_conf.set10(name, conf.get(name) == 1) +foreach name : ['HAVE_ATTRIBUTE_RETAIN', + 'HAVE_WARNING_ZERO_AS_NULL_POINTER_CONSTANT', + 'HAVE_WARNING_ZERO_LENGTH_BOUNDS', + ] + efi_conf.set(name, conf.get(name)) endforeach efi_conf.set10('ENABLE_TPM', get_option('tpm')) diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro-fundamental.h index e8757b1fc37a4..39004183d90f2 100644 --- a/src/fundamental/macro-fundamental.h +++ b/src/fundamental/macro-fundamental.h @@ -88,7 +88,6 @@ #define _printf_(a, b) __attribute__((__format__(printf, a, b))) #define _public_ __attribute__((__visibility__("default"))) #define _pure_ __attribute__((__pure__)) -#define _retain_ __attribute__((__retain__)) #define _returns_nonnull_ __attribute__((__returns_nonnull__)) #define _section_(x) __attribute__((__section__(x))) #define _sentinel_ __attribute__((__sentinel__)) @@ -99,6 +98,12 @@ #define _weak_ __attribute__((__weak__)) #define _weakref_(x) __attribute__((__weakref__(#x))) +#if HAVE_ATTRIBUTE_RETAIN +# define _retain_ __attribute__((__retain__)) +#else +# define _retain_ +#endif + #ifdef __clang__ # define _alloc_(...) #else From bc6380ae0765ebc6f09fc2afd27070cfb3acc0b6 Mon Sep 17 00:00:00 2001 From: Clayton Craft Date: Thu, 19 Mar 2026 17:08:31 -0700 Subject: [PATCH 0374/1296] nsresourced: fix BPF loading when using kernel compiled with Clang This fixes an issue where nsresourced fails to load BPF on kernels compiled with Clang (this output was from v259): $ sudo env SYSTEMD_LOG_LEVEL=debug /usr/lib/systemd/systemd-nsresourced ; int BPF_PROG(userns_restrict_path_chown, struct path *path, void* uid, void *gid, int ret) { @ userns-restrict.bpf.c:134 ... ; return validate_path(path, ret); @ userns-restrict.bpf.c:135 ... ; static int validate_path(const struct path *path, int ret) { @ userns-restrict.bpf.c:120 ... ; task = (struct task_struct*) bpf_get_current_task_btf(); @ userns-restrict.bpf.c:84 ... ; task_userns = task->cred->user_ns; @ userns-restrict.bpf.c:85 ... R2 invalid mem access 'rcu_ptr_or_null_' When Clang is used (which sets CONFIG_PAHOLE_HAS_BTF_TAG), btf_type_tag support is enabled. As a result, an rcu type tag is added to task_struct::cred: $ bpftool btf dump file /sys/kernel/btf/vmlinux | grep "STRUCT 'task_struct'" [459] STRUCT 'task_struct' size=4672 vlen=242 $ bpftool btf dump file /sys/kernel/btf/vmlinux | grep -A200 "^\[459\] STRUCT 'task_struct'" | grep cred 'ptracer_cred' type_id=802 bits_offset=14528 'real_cred' type_id=802 bits_offset=14592 'cred' type_id=802 bits_offset=14656 $ bpftool btf dump file /sys/kernel/btf/vmlinux | grep '^\[802\]' [802] PTR '(anon)' type_id=801 $ bpftool btf dump file /sys/kernel/btf/vmlinux | grep '^\[801\]' [801] TYPE_TAG 'rcu' type_id=803 Since the struct ptr *could* be null, we have to add a null pointer check to satisfy the bpf verifier. --- .../bpf/userns-restrict/userns-restrict.bpf.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/nsresourced/bpf/userns-restrict/userns-restrict.bpf.c b/src/nsresourced/bpf/userns-restrict/userns-restrict.bpf.c index 25d609bf38fc8..d70493fe7af5f 100644 --- a/src/nsresourced/bpf/userns-restrict/userns-restrict.bpf.c +++ b/src/nsresourced/bpf/userns-restrict/userns-restrict.bpf.c @@ -119,6 +119,7 @@ static int userns_owns_mount(struct user_namespace *userns, struct vfsmount *v) static int validate_mount(struct vfsmount *v, int ret) { struct user_namespace *task_userns; unsigned task_userns_inode; + const struct cred *cred; struct task_struct *task; void *mnt_id_map; struct mount *m; @@ -129,7 +130,10 @@ static int validate_mount(struct vfsmount *v, int ret) { /* Get user namespace from task */ task = (struct task_struct*) bpf_get_current_task_btf(); - task_userns = task->cred->user_ns; + cred = task->cred; + if (!cred) + return -EPERM; + task_userns = cred->user_ns; /* fsuid/fsgid are the UID/GID in the initial user namespace, before any idmapped mounts have been * applied. There is no way (yet) to figure out what the UID/GID that will be written to disk will be @@ -138,7 +142,7 @@ static int validate_mount(struct vfsmount *v, int ret) { * translate the transient UID range to something else. For other UIDs/GIDs, there's no need to do * these checks as we don't insist on idmapped mounts or such for UIDs/GIDs outside the transient * ranges. */ - if (!uid_is_transient(task->cred->fsuid.val) && !uid_is_transient((uid_t) task->cred->fsgid.val)) + if (!uid_is_transient(cred->fsuid.val) && !uid_is_transient((uid_t) cred->fsgid.val)) return 0; r = userns_owns_mount(task_userns, v); @@ -170,6 +174,7 @@ SEC("lsm/path_chown") int BPF_PROG(userns_restrict_path_chown, struct path *path, unsigned long long uid, unsigned long long gid, int ret) { struct user_namespace *task_userns; unsigned task_userns_inode; + const struct cred *cred; struct task_struct *task; struct vfsmount *v; void *mnt_id_map; @@ -180,7 +185,10 @@ int BPF_PROG(userns_restrict_path_chown, struct path *path, unsigned long long u /* Get user namespace from task */ task = (struct task_struct*) bpf_get_current_task_btf(); - task_userns = task->cred->user_ns; + cred = task->cred; + if (!cred) + return -EPERM; + task_userns = cred->user_ns; v = path->mnt; r = userns_owns_mount(task_userns, v); From 7e14d3b979d3ec51d42b10ffa0561a3c4b3e7dd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 20 Mar 2026 09:50:27 +0100 Subject: [PATCH 0375/1296] boot: inline a single-use variable Also, in general we prefer variables that are always defined over checking with #ifdef, so #if defined(HAVE_NO_STACK_PROTECTOR_ATTRIBUTE) is something that we want to avoid. --- src/boot/efi-log.h | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/boot/efi-log.h b/src/boot/efi-log.h index 5458f90109a0b..5dbcb425164ed 100644 --- a/src/boot/efi-log.h +++ b/src/boot/efi-log.h @@ -5,15 +5,12 @@ #include "efi-string.h" #include "proto/simple-text-io.h" /* IWYU pragma: keep */ -#if defined __has_attribute -# if __has_attribute(no_stack_protector) -# define HAVE_NO_STACK_PROTECTOR_ATTRIBUTE -# endif -#endif - -#if defined(HAVE_NO_STACK_PROTECTOR_ATTRIBUTE) && \ - (defined(__SSP__) || defined(__SSP_ALL__) || \ - defined(__SSP_STRONG__) || defined(__SSP_EXPLICIT__)) +#if defined(__has_attribute) && \ + __has_attribute(no_stack_protector) && \ + (defined(__SSP__) || \ + defined(__SSP_ALL__) || \ + defined(__SSP_STRONG__) || \ + defined(__SSP_EXPLICIT__)) # define STACK_PROTECTOR_RANDOM 1 __attribute__((no_stack_protector, noinline)) void __stack_chk_guard_init(void); #else From 2c74a91cb87a8f14975c899d52d32309974e846b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 19 Mar 2026 11:23:45 +0100 Subject: [PATCH 0376/1296] sd-json: when parsing optionally insist top-level variant is object or array Typically, the top-level JSON object has to be an object, in any json document we parse, hence let's add a simple way to enforce that. Make use of this in various places. (Note, various other JSON parsers insist on this logic right from the beginning, but I actually thinking making this insisting optional like this patch does it is the cleaner approach) --- man/rules/meson.build | 11 + man/sd_json_parse.xml | 248 ++++++++++++++++++ src/analyze/analyze-security.c | 5 +- src/coredump/coredumpctl.c | 2 +- .../cryptsetup-token-systemd-fido2.c | 2 +- .../cryptsetup-token-systemd-pkcs11.c | 2 +- .../cryptsetup-token-systemd-tpm2.c | 6 +- .../cryptsetup-tokens/luks2-fido2.c | 2 +- .../cryptsetup-tokens/luks2-pkcs11.c | 2 +- src/home/homectl.c | 27 +- src/home/homed-bus.c | 4 +- src/home/homed-home.c | 2 +- src/home/homed-manager.c | 4 +- src/home/homework-luks.c | 4 +- src/home/homework.c | 4 +- src/home/pam_systemd_home.c | 2 +- src/hostname/hostnamectl.c | 2 +- src/import/pull-oci.c | 12 +- src/libsystemd-network/sd-dhcp-server-lease.c | 2 +- src/libsystemd/sd-json/sd-json.c | 45 +++- src/libsystemd/sd-varlink/sd-varlink.c | 4 +- src/login/pam_systemd.c | 2 +- src/measure/measure-tool.c | 8 +- src/systemd/sd-json.h | 4 +- src/test/test-json.c | 42 +++ src/userdb/userdbctl.c | 4 +- src/varlinkctl/varlinkctl.c | 4 +- src/vmspawn/vmspawn-util.c | 2 +- 28 files changed, 405 insertions(+), 53 deletions(-) create mode 100644 man/sd_json_parse.xml diff --git a/man/rules/meson.build b/man/rules/meson.build index d7cbd5b65201b..d2d26abe5da31 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -861,6 +861,17 @@ manpages = [ 'sd_json_dispatch_variant', 'sd_json_dispatch_variant_noref'], ''], + ['sd_json_parse', + '3', + ['SD_JSON_PARSE_MUST_BE_ARRAY', + 'SD_JSON_PARSE_MUST_BE_OBJECT', + 'SD_JSON_PARSE_SENSITIVE', + 'sd_json_parse_continue', + 'sd_json_parse_file', + 'sd_json_parse_file_at', + 'sd_json_parse_with_source', + 'sd_json_parse_with_source_continue'], + ''], ['sd_listen_fds', '3', ['SD_LISTEN_FDS_START', 'sd_listen_fds_with_names'], diff --git a/man/sd_json_parse.xml b/man/sd_json_parse.xml new file mode 100644 index 0000000000000..c5234734dab1a --- /dev/null +++ b/man/sd_json_parse.xml @@ -0,0 +1,248 @@ + + + + + + + + sd_json_parse + systemd + + + + sd_json_parse + 3 + + + + sd_json_parse + sd_json_parse_continue + sd_json_parse_with_source + sd_json_parse_with_source_continue + sd_json_parse_file + sd_json_parse_file_at + SD_JSON_PARSE_SENSITIVE + SD_JSON_PARSE_MUST_BE_OBJECT + SD_JSON_PARSE_MUST_BE_ARRAY + + Parse JSON strings and files into JSON variant objects + + + + + #include <systemd/sd-json.h> + + + int sd_json_parse + const char *string + sd_json_parse_flags_t flags + sd_json_variant **ret + unsigned *reterr_line + unsigned *reterr_column + + + + int sd_json_parse_continue + const char **p + sd_json_parse_flags_t flags + sd_json_variant **ret + unsigned *reterr_line + unsigned *reterr_column + + + + int sd_json_parse_with_source + const char *string + const char *source + sd_json_parse_flags_t flags + sd_json_variant **ret + unsigned *reterr_line + unsigned *reterr_column + + + + int sd_json_parse_with_source_continue + const char **p + const char *source + sd_json_parse_flags_t flags + sd_json_variant **ret + unsigned *reterr_line + unsigned *reterr_column + + + + int sd_json_parse_file + FILE *f + const char *path + sd_json_parse_flags_t flags + sd_json_variant **ret + unsigned *reterr_line + unsigned *reterr_column + + + + int sd_json_parse_file_at + FILE *f + int dir_fd + const char *path + sd_json_parse_flags_t flags + sd_json_variant **ret + unsigned *reterr_line + unsigned *reterr_column + + + + + + Description + + sd_json_parse() parses the JSON string in string and + returns the resulting JSON variant object in ret. The input must contain exactly + one JSON value (object, array, string, number, boolean, or null); any trailing non-whitespace content + after the first parsed value is considered an error. + + If parsing fails, the reterr_line and reterr_column + arguments are set to the line and column (both one-based) where the parse error occurred. One or both + may be passed as NULL if the caller is not interested in error location + information. On success, the return value is non-negative and ret is set to a + newly allocated JSON variant object (which must be freed with + sd_json_variant_unref3 + when no longer needed). ret may be passed as NULL, in which + case the input is validated but no object is returned. + + sd_json_parse_continue() is similar, but is intended for parsing a sequence of + concatenated JSON values from a single input string. Instead of taking a const char * string + directly, it takes a pointer to a const char * pointer. After each successful parse, the + pointer is advanced past the consumed input, so that subsequent calls will parse the next JSON + value. This is useful for parsing newline-delimited JSON (NDJSON) streams or similar concatenated JSON + formats. Unlike sd_json_parse(), trailing content after the first JSON value is not + considered an error — it is expected to be the beginning of the next value. + + sd_json_parse_with_source() and + sd_json_parse_with_source_continue() are similar to + sd_json_parse() and sd_json_parse_continue(), respectively, but + take an additional source argument. This is a human-readable string (typically a + file name or other origin identifier) that is attached to the parsed JSON variant object and can later be + retrieved via + sd_json_variant_get_source3. If + source is NULL, no source information is + attached. sd_json_parse() and sd_json_parse_continue() are + equivalent to calling their _with_source counterparts with + source set to NULL. + + sd_json_parse_file() reads and parses a JSON value from a file. If the + f argument is non-NULL, the JSON text is read from the + specified FILE stream. If f is NULL, the file + indicated by path is opened and read instead. The path + argument serves a dual purpose: it is both used for opening the file (if f is + NULL) and recorded as source information in the resulting JSON variant (see + above). + + sd_json_parse_file_at() is similar to + sd_json_parse_file(), but takes an additional dir_fd argument + which specifies a file descriptor referring to the directory to resolve relative paths specified in + path against. If set to AT_FDCWD, relative paths are resolved + against the current working directory, which is the default behaviour of + sd_json_parse_file(). + + The flags argument is a bitmask of zero or more of the following + flags: + + + + SD_JSON_PARSE_SENSITIVE + + Marks the resulting JSON variant as "sensitive", indicating that it contains secret + key material or similar confidential data. Sensitive variants are erased from memory when freed and + are excluded from certain debug logging and introspection operations. See + sd_json_variant_sensitive3 + for details. + + + + SD_JSON_PARSE_MUST_BE_OBJECT + + Requires that the top-level JSON value be a JSON object (i.e. {…}). + If the top-level value is an array, string, number, boolean, or null, parsing fails with + -EINVAL. + + + + + + SD_JSON_PARSE_MUST_BE_ARRAY + + Requires that the top-level JSON value be a JSON array (i.e. […]). + If the top-level value is an object, string, number, boolean, or null, parsing fails with + -EINVAL. + + + + + + If both SD_JSON_PARSE_MUST_BE_OBJECT and + SD_JSON_PARSE_MUST_BE_ARRAY are set, both objects and arrays are accepted, but + non-container values (strings, numbers, booleans, null) are still refused. + + + + Return Value + + On success, these functions return 0. On failure, they return a negative errno-style error + code. + + + Errors + + Returned errors may indicate the following problems: + + + + -EINVAL + + The input is not valid JSON, the input contains trailing content after the parsed + value (only for non-_continue variants), or a top-level type constraint + specified via SD_JSON_PARSE_MUST_BE_OBJECT or + SD_JSON_PARSE_MUST_BE_ARRAY was violated. + + + + -ENODATA + + The input string is empty or NULL. + + + + -ENOMEM + + Memory allocation failed. + + + + + + + + + History + sd_json_parse(), + sd_json_parse_continue(), + sd_json_parse_with_source(), + sd_json_parse_with_source_continue(), + sd_json_parse_file(), and + sd_json_parse_file_at() were added in version 257. + + + + See Also + + + systemd1 + sd-json3 + sd_json_variant_unref3 + sd_json_variant_get_source3 + sd_json_dispatch3 + + + diff --git a/src/analyze/analyze-security.c b/src/analyze/analyze-security.c index 0f763f75e9eba..bdbd44910bfba 100644 --- a/src/analyze/analyze-security.c +++ b/src/analyze/analyze-security.c @@ -3,6 +3,7 @@ #include #include "sd-bus.h" +#include "sd-json.h" #include "alloc-util.h" #include "analyze-verify-util.h" @@ -2919,7 +2920,7 @@ int verb_security(int argc, char *argv[], uintptr_t _data, void *userdata) { unsigned line = 0, column = 0; if (arg_security_policy) { - r = sd_json_parse_file(/* f= */ NULL, arg_security_policy, /* flags= */ 0, &policy, &line, &column); + r = sd_json_parse_file(/* f= */ NULL, arg_security_policy, SD_JSON_PARSE_MUST_BE_OBJECT, &policy, &line, &column); if (r < 0) return log_error_errno(r, "Failed to parse '%s' at %u:%u: %m", arg_security_policy, line, column); } else { @@ -2931,7 +2932,7 @@ int verb_security(int argc, char *argv[], uintptr_t _data, void *userdata) { return r; if (f) { - r = sd_json_parse_file(f, pp, /* flags= */ 0, &policy, &line, &column); + r = sd_json_parse_file(f, pp, SD_JSON_PARSE_MUST_BE_OBJECT, &policy, &line, &column); if (r < 0) return log_error_errno(r, "[%s:%u:%u] Failed to parse JSON policy: %m", pp, line, column); } diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index 0f655a4694d9b..f41d6fef310f5 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -829,7 +829,7 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) { if (exe && pkgmeta_json) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - r = sd_json_parse(pkgmeta_json, 0, &v, NULL, NULL); + r = sd_json_parse(pkgmeta_json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) { _cleanup_free_ char *esc = cescape(pkgmeta_json); log_warning_errno(r, "json_parse on \"%s\" failed, ignoring: %m", strnull(esc)); diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c index 02ed4dd273c6f..a2804e033a1de 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c @@ -162,7 +162,7 @@ _public_ int cryptsetup_token_validate( assert(json); - r = sd_json_parse(json, 0, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return crypt_log_debug_errno(cd, r, "Could not parse " TOKEN_NAME " json object: %m."); diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c index 4c6e28500a396..16cf910fe6d89 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c @@ -115,7 +115,7 @@ _public_ int cryptsetup_token_validate( sd_json_variant *w; _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - r = sd_json_parse(json, 0, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return crypt_log_debug_errno(cd, r, "Could not parse " TOKEN_NAME " json object: %m."); diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c index 933d18e2fd7a9..58dc37c5bfb74 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c @@ -72,7 +72,7 @@ _public_ int cryptsetup_token_open_pin( if (usrptr) params = *(systemd_tpm2_plugin_params *)usrptr; - r = sd_json_parse(json, 0, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return crypt_log_debug_errno(cd, r, "Failed to parse token JSON data: %m"); @@ -186,7 +186,7 @@ _public_ void cryptsetup_token_dump( assert(json); - r = sd_json_parse(json, 0, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON object: %m"); @@ -275,7 +275,7 @@ _public_ int cryptsetup_token_validate( assert(json); - r = sd_json_parse(json, 0, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return crypt_log_debug_errno(cd, r, "Could not parse " TOKEN_NAME " json object: %m"); diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c index 18b0e4f37f93f..c6cfdcf6efeb8 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c +++ b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c @@ -97,7 +97,7 @@ int parse_luks2_fido2_data( assert(ret_cid_size); assert(ret_required); - r = sd_json_parse(json, 0, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return crypt_log_error_errno(cd, r, "Failed to parse JSON token data: %m"); diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c index 9f11f81c4ac7b..723265479cc1b 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c +++ b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c @@ -246,7 +246,7 @@ int parse_luks2_pkcs11_data( assert(ret_encrypted_key); assert(ret_encrypted_key_size); - r = sd_json_parse(json, 0, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return r; diff --git a/src/home/homectl.c b/src/home/homectl.c index 507860cde62e4..db4e6639d5d9a 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -758,7 +758,7 @@ static int inspect_home(sd_bus *bus, const char *name) { if (r < 0) return bus_log_parse_error(r); - r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE|SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return log_error_errno(r, "Failed to parse JSON identity: %m"); @@ -1159,7 +1159,11 @@ static int acquire_new_home_record(sd_json_variant *input, UserRecord **ret) { r = sd_json_parse_file( streq(arg_identity, "-") ? stdin : NULL, - streq(arg_identity, "-") ? "" : arg_identity, SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + streq(arg_identity, "-") ? "" : arg_identity, + SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, + &v, + &line, + &column); if (r < 0) return log_error_errno(r, "Failed to parse identity at %u:%u: %m", line, column); } else @@ -1667,7 +1671,7 @@ static int register_home_one(sd_bus *bus, FILE *f, const char *path) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; unsigned line = 0, column = 0; - r = sd_json_parse_file(f, path, SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + r = sd_json_parse_file(f, path, SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, &line, &column); if (r < 0) return log_error_errno(r, "[%s:%u:%u] Failed to parse user record: %m", path, line, column); @@ -1785,7 +1789,11 @@ static int acquire_updated_home_record( r = sd_json_parse_file( streq(arg_identity, "-") ? stdin : NULL, - streq(arg_identity, "-") ? "" : arg_identity, SD_JSON_PARSE_SENSITIVE, &json, &line, &column); + streq(arg_identity, "-") ? "" : arg_identity, + SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, + &json, + &line, + &column); if (r < 0) return log_error_errno(r, "Failed to parse identity at %u:%u: %m", line, column); @@ -1822,7 +1830,12 @@ static int acquire_updated_home_record( if (incomplete) return log_error_errno(SYNTHETIC_ERRNO(EACCES), "Lacking rights to acquire user record including privileged metadata, can't update record."); - r = sd_json_parse(text, SD_JSON_PARSE_SENSITIVE, &json, NULL, NULL); + r = sd_json_parse( + text, + SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, + &json, + /* reterr_line= */ NULL, + /* reterr_column= */ NULL); if (r < 0) return log_error_errno(r, "Failed to parse JSON identity: %m"); @@ -2569,7 +2582,7 @@ static int create_or_register_from_credentials(void) { /* f= */ NULL, fd, de->d_name, - /* flags= */ 0, + /* flags= */ SD_JSON_PARSE_MUST_BE_OBJECT, &identity, &line, &column); @@ -5135,7 +5148,7 @@ static int fallback_shell(int argc, char *argv[]) { if (r < 0) return bus_log_parse_error(r); - r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE|SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return log_error_errno(r, "Failed to parse JSON identity: %m"); diff --git a/src/home/homed-bus.c b/src/home/homed-bus.c index c96ecf662059b..f185e87295537 100644 --- a/src/home/homed-bus.c +++ b/src/home/homed-bus.c @@ -24,7 +24,7 @@ int bus_message_read_secret(sd_bus_message *m, UserRecord **ret, sd_bus_error *e if (r < 0) return r; - r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE|SD_JSON_PARSE_MUST_BE_OBJECT, &v, &line, &column); if (r < 0) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Failed to parse JSON secret record at %u:%u: %m", line, column); @@ -57,7 +57,7 @@ int bus_message_read_home_record(sd_bus_message *m, UserRecordLoadFlags flags, U if (r < 0) return r; - r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE|SD_JSON_PARSE_MUST_BE_OBJECT, &v, &line, &column); if (r < 0) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Failed to parse JSON identity record at %u:%u: %m", line, column); diff --git a/src/home/homed-home.c b/src/home/homed-home.c index 00b2e72f9fb99..12e0eed2dae32 100644 --- a/src/home/homed-home.c +++ b/src/home/homed-home.c @@ -589,7 +589,7 @@ static int home_parse_worker_stdout(int _fd, UserRecord **ret) { } unsigned line = 0, column = 0; - r = sd_json_parse_file(f, "stdout", SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + r = sd_json_parse_file(f, "stdout", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, &line, &column); if (r < 0) return log_error_errno(r, "Failed to parse identity at %u:%u: %m", line, column); diff --git a/src/home/homed-manager.c b/src/home/homed-manager.c index 85c92192f483d..c9d43982d01f8 100644 --- a/src/home/homed-manager.c +++ b/src/home/homed-manager.c @@ -406,9 +406,9 @@ static int manager_add_home_by_record( goto unlink_this_file; unsigned line = 0, column = 0; - r = sd_json_parse_file_at(NULL, dir_fd, fname, SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + r = sd_json_parse_file_at(/* f= */ NULL, dir_fd, fname, SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, &line, &column); if (r < 0) - return log_error_errno(r, "Failed to parse identity record at %s:%u%u: %m", fname, line, column); + return log_error_errno(r, "Failed to parse identity record at %s:%u:%u: %m", fname, line, column); if (sd_json_variant_is_blank_object(v)) goto unlink_this_file; diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index 9f7af06a4e780..caa05db26f491 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -887,7 +887,7 @@ static int luks_validate_home_record( return log_error_errno(r, "Failed to read LUKS token %i: %m", token); unsigned line = 0, column = 0; - r = sd_json_parse(text, SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + r = sd_json_parse(text, SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, &line, &column); if (r < 0) return log_error_errno(r, "Failed to parse LUKS token JSON data %u:%u: %m", line, column); @@ -940,7 +940,7 @@ static int luks_validate_home_record( decrypted[decrypted_size] = 0; - r = sd_json_parse(decrypted, SD_JSON_PARSE_SENSITIVE, &rr, NULL, NULL); + r = sd_json_parse(decrypted, SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &rr, NULL, NULL); if (r < 0) return log_error_errno(r, "Failed to parse decrypted JSON record, refusing."); diff --git a/src/home/homework.c b/src/home/homework.c index e796f125fb56e..2efd3ddb608fa 100644 --- a/src/home/homework.c +++ b/src/home/homework.c @@ -571,7 +571,7 @@ static int read_identity_file(int root_fd, sd_json_variant **ret) { return log_oom(); unsigned line = 0, column = 0; - r = sd_json_parse_file(identity_file, ".identity", SD_JSON_PARSE_SENSITIVE, ret, &line, &column); + r = sd_json_parse_file(identity_file, ".identity", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, ret, &line, &column); if (r < 0) return log_error_errno(r, "[.identity:%u:%u] Failed to parse JSON data: %m", line, column); @@ -2025,7 +2025,7 @@ static int run(int argc, char *argv[]) { } unsigned line = 0, column = 0; - r = sd_json_parse_file(json_file, json_path, SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + r = sd_json_parse_file(json_file, json_path, SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, &line, &column); if (r < 0) return log_error_errno(r, "[%s:%u:%u] Failed to parse JSON data: %m", json_path, line, column); diff --git a/src/home/pam_systemd_home.c b/src/home/pam_systemd_home.c index c58a3433760be..e8d7282cbf82f 100644 --- a/src/home/pam_systemd_home.c +++ b/src/home/pam_systemd_home.c @@ -194,7 +194,7 @@ static int acquire_user_record( fresh_data = true; } - r = sd_json_parse(json, /* flags= */ 0, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return pam_syslog_errno(pamh, LOG_ERR, r, "Failed to parse JSON user record: %m"); diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index d0ceceb155846..75a17a13aeeac 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -567,7 +567,7 @@ static int verb_show_status(int argc, char *argv[], uintptr_t _data, void *userd if (r < 0) return bus_log_parse_error(r); - r = sd_json_parse(text, 0, &v, NULL, NULL); + r = sd_json_parse(text, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return log_error_errno(r, "Failed to parse JSON: %m"); diff --git a/src/import/pull-oci.c b/src/import/pull-oci.c index cbbad44eb1e06..0f16c65630a7e 100644 --- a/src/import/pull-oci.c +++ b/src/import/pull-oci.c @@ -200,7 +200,7 @@ int oci_pull_new( return 0; } -static int pull_job_payload_as_json(PullJob *j, sd_json_variant **ret) { +static int pull_job_payload_as_json_object(PullJob *j, sd_json_variant **ret) { int r; assert(j); @@ -214,7 +214,7 @@ static int pull_job_payload_as_json(PullJob *j, sd_json_variant **ret) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; unsigned line = 0, column = 0; - r = sd_json_parse((char*) j->payload.iov_base, /* flags= */ 0, &v, &line, &column); + r = sd_json_parse((char*) j->payload.iov_base, SD_JSON_PARSE_MUST_BE_OBJECT, &v, &line, &column); if (r < 0) return log_error_errno(r, "Failed to parse JSON at position %u:%u: %m", line, column); @@ -391,7 +391,7 @@ static int oci_pull_process_index(OciPull *i, PullJob *j) { * https://github.com/opencontainers/image-spec/blob/main/image-index.md */ _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - r = pull_job_payload_as_json(j, &v); + r = pull_job_payload_as_json_object(j, &v); if (r < 0) return r; @@ -783,7 +783,7 @@ static int oci_pull_process_manifest(OciPull *i, PullJob *j) { * https://github.com/opencontainers/image-spec/blob/main/manifest.md */ _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - r = pull_job_payload_as_json(j, &v); + r = pull_job_payload_as_json_object(j, &v); if (r < 0) return r; @@ -958,7 +958,7 @@ static int oci_pull_save_nspawn_settings(OciPull *i) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; unsigned line = 0, column = 0; - r = sd_json_parse((char*) i->config.iov_base, /* flags= */ 0, &v, &line, &column); + r = sd_json_parse((char*) i->config.iov_base, SD_JSON_PARSE_MUST_BE_OBJECT, &v, &line, &column); if (r < 0) return log_error_errno(r, "Failed to parse JSON config data at position %u:%u: %m", line, column); @@ -1340,7 +1340,7 @@ static void oci_pull_job_on_finished_bearer_token(PullJob *j) { goto finish; } - r = pull_job_payload_as_json(j, &v); + r = pull_job_payload_as_json_object(j, &v); if (r < 0) goto finish; diff --git a/src/libsystemd-network/sd-dhcp-server-lease.c b/src/libsystemd-network/sd-dhcp-server-lease.c index 5c24de4084fb8..0268bbf2c29af 100644 --- a/src/libsystemd-network/sd-dhcp-server-lease.c +++ b/src/libsystemd-network/sd-dhcp-server-lease.c @@ -490,7 +490,7 @@ static int load_leases_file(int dir_fd, const char *path, SavedInfo *ret) { /* f= */ NULL, dir_fd, path, - /* flags= */ 0, + /* flags= */ SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* ret_column= */ NULL); diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index 7829e11880643..b959fe16286ac 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -3039,7 +3039,6 @@ static int json_parse_internal( int r; assert_return(input, -EINVAL); - assert_return(ret, -EINVAL); p = *input; @@ -3111,12 +3110,16 @@ static int json_parse_internal( break; case JSON_TOKEN_OBJECT_OPEN: - if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) { r = -EINVAL; goto finish; } + if (n_stack == 1 && FLAGS_SET(flags, SD_JSON_PARSE_MUST_BE_ARRAY) && !FLAGS_SET(flags, SD_JSON_PARSE_MUST_BE_OBJECT)) { + r = -EINVAL; + goto finish; + } + if (!GREEDY_REALLOC(stack, n_stack+1)) { r = -ENOMEM; goto finish; @@ -3168,6 +3171,11 @@ static int json_parse_internal( goto finish; } + if (n_stack == 1 && !FLAGS_SET(flags, SD_JSON_PARSE_MUST_BE_ARRAY) && FLAGS_SET(flags, SD_JSON_PARSE_MUST_BE_OBJECT)) { + r = -EINVAL; + goto finish; + } + if (!GREEDY_REALLOC(stack, n_stack+1)) { r = -ENOMEM; goto finish; @@ -3217,6 +3225,11 @@ static int json_parse_internal( goto finish; } + if (n_stack == 1 && (flags & (SD_JSON_PARSE_MUST_BE_ARRAY|SD_JSON_PARSE_MUST_BE_OBJECT)) != 0) { + r = -EINVAL; + goto finish; + } + r = sd_json_variant_new_string(&add, string); if (r < 0) goto finish; @@ -3240,6 +3253,11 @@ static int json_parse_internal( goto finish; } + if (n_stack == 1 && (flags & (SD_JSON_PARSE_MUST_BE_ARRAY|SD_JSON_PARSE_MUST_BE_OBJECT)) != 0) { + r = -EINVAL; + goto finish; + } + r = sd_json_variant_new_real(&add, value.real); if (r < 0) goto finish; @@ -3261,6 +3279,11 @@ static int json_parse_internal( goto finish; } + if (n_stack == 1 && (flags & (SD_JSON_PARSE_MUST_BE_ARRAY|SD_JSON_PARSE_MUST_BE_OBJECT)) != 0) { + r = -EINVAL; + goto finish; + } + r = sd_json_variant_new_integer(&add, value.integer); if (r < 0) goto finish; @@ -3282,6 +3305,11 @@ static int json_parse_internal( goto finish; } + if (n_stack == 1 && (flags & (SD_JSON_PARSE_MUST_BE_ARRAY|SD_JSON_PARSE_MUST_BE_OBJECT)) != 0) { + r = -EINVAL; + goto finish; + } + r = sd_json_variant_new_unsigned(&add, value.unsig); if (r < 0) goto finish; @@ -3303,6 +3331,11 @@ static int json_parse_internal( goto finish; } + if (n_stack == 1 && (flags & (SD_JSON_PARSE_MUST_BE_ARRAY|SD_JSON_PARSE_MUST_BE_OBJECT)) != 0) { + r = -EINVAL; + goto finish; + } + r = sd_json_variant_new_boolean(&add, value.boolean); if (r < 0) goto finish; @@ -3324,6 +3357,11 @@ static int json_parse_internal( goto finish; } + if (n_stack == 1 && (flags & (SD_JSON_PARSE_MUST_BE_ARRAY|SD_JSON_PARSE_MUST_BE_OBJECT)) != 0) { + r = -EINVAL; + goto finish; + } + r = sd_json_variant_new_null(&add); if (r < 0) goto finish; @@ -3365,7 +3403,8 @@ static int json_parse_internal( assert(n_stack == 1); assert(stack[0].n_elements == 1); - *ret = sd_json_variant_ref(stack[0].elements[0]); + if (ret) + *ret = sd_json_variant_ref(stack[0].elements[0]); *input = p; r = 0; diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index 7ee85e9789542..c1ffaedfc8077 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -1009,14 +1009,14 @@ static int varlink_parse_message(sd_varlink *v) { sz = e - begin + 1; - r = sd_json_parse(begin, 0, &v->current, NULL, NULL); + r = sd_json_parse(begin, SD_JSON_PARSE_MUST_BE_OBJECT, &v->current, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (v->input_sensitive) explicit_bzero_safe(begin, sz); if (r < 0) { /* If we encounter a parse failure flush all data. We cannot possibly recover from this, * hence drop all buffered data now. */ v->input_buffer_index = v->input_buffer_size = v->input_buffer_unscanned = 0; - return varlink_log_errno(v, r, "Failed to parse JSON: %m"); + return varlink_log_errno(v, r, "Failed to parse JSON object: %m"); } if (v->input_sensitive) { diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index f7aa6f9b8f626..ec862e7d4fd21 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -205,7 +205,7 @@ static int acquire_user_record(pam_handle_t *pamh, UserRecord **ret_record) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; /* Parse cached record */ - r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return pam_syslog_errno(pamh, LOG_ERR, r, "Failed to parse JSON user record: %m"); diff --git a/src/measure/measure-tool.c b/src/measure/measure-tool.c index 2f37d5838195a..d632bb62f5547 100644 --- a/src/measure/measure-tool.c +++ b/src/measure/measure-tool.c @@ -820,13 +820,9 @@ static int build_policy_digest(bool sign) { assert(!strv_isempty(arg_phase)); if (arg_append) { - r = sd_json_parse_file(NULL, arg_append, 0, &v, NULL, NULL); + r = sd_json_parse_file(/* f= */ NULL, arg_append, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) - return log_error_errno(r, "Failed to parse '%s': %m", arg_append); - - if (!sd_json_variant_is_object(v)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "File '%s' is not a valid JSON object, refusing.", arg_append); + return log_error_errno(r, "Failed to parse JSON object '%s': %m", arg_append); } /* When signing/building digest we only support JSON output */ diff --git a/src/systemd/sd-json.h b/src/systemd/sd-json.h index 359149ef9e0e2..6a1977549098f 100644 --- a/src/systemd/sd-json.h +++ b/src/systemd/sd-json.h @@ -181,7 +181,9 @@ int sd_json_variant_sort(sd_json_variant **v); int sd_json_variant_normalize(sd_json_variant **v); __extension__ typedef enum _SD_ENUM_TYPE_S64(sd_json_parse_flags_t) { - SD_JSON_PARSE_SENSITIVE = 1 << 0, /* mark variant as "sensitive", i.e. something containing secret key material or such */ + SD_JSON_PARSE_SENSITIVE = 1 << 0, /* mark variant as "sensitive", i.e. something containing secret key material or such */ + SD_JSON_PARSE_MUST_BE_OBJECT = 1 << 1, /* refuse parsing if top-level is not an object */ + SD_JSON_PARSE_MUST_BE_ARRAY = 1 << 2, /* refuse parsing if top-level is not an array */ _SD_ENUM_FORCE_S64(JSON_PARSE_FLAGS) } sd_json_parse_flags_t; diff --git a/src/test/test-json.c b/src/test/test-json.c index 679152fd955a8..4994d42abc43c 100644 --- a/src/test/test-json.c +++ b/src/test/test-json.c @@ -1588,4 +1588,46 @@ TEST(json_variant_compare) { test_json_variant_compare_one("{\"a\":\"b\",\"b\":\"c\"}", "{\"a\":\"b\"}", 1); } +TEST(must_be) { + ASSERT_OK(sd_json_parse("null", /* flags= */ 0, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(sd_json_parse("null", SD_JSON_PARSE_MUST_BE_OBJECT, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("null", SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("null", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + + ASSERT_OK(sd_json_parse("true", /* flags= */ 0, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(sd_json_parse("true", SD_JSON_PARSE_MUST_BE_OBJECT, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("true", SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("true", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + + ASSERT_OK(sd_json_parse("\"foo\"", /* flags= */ 0, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(sd_json_parse("\"foo\"", SD_JSON_PARSE_MUST_BE_OBJECT, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("\"foo\"", SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("\"foo\"", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + + ASSERT_OK(sd_json_parse("4711", /* flags= */ 0, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(sd_json_parse("4711", SD_JSON_PARSE_MUST_BE_OBJECT, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("4711", SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("4711", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + + ASSERT_OK(sd_json_parse("-4711", /* flags= */ 0, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(sd_json_parse("-4711", SD_JSON_PARSE_MUST_BE_OBJECT, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("-4711", SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("-4711", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + + ASSERT_OK(sd_json_parse("-4.5", /* flags= */ 0, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(sd_json_parse("-4.5", SD_JSON_PARSE_MUST_BE_OBJECT, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("-4.5", SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("-4.5", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + + ASSERT_OK(sd_json_parse("{}", /* flags= */ 0, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_OK(sd_json_parse("{}", SD_JSON_PARSE_MUST_BE_OBJECT, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(sd_json_parse("{}", SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_OK(sd_json_parse("{}", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + + ASSERT_OK(sd_json_parse("[]", /* flags= */ 0, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(sd_json_parse("[]", SD_JSON_PARSE_MUST_BE_OBJECT, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_OK(sd_json_parse("[]", SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_OK(sd_json_parse("[]", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/userdb/userdbctl.c b/src/userdb/userdbctl.c index b0d7a06941e14..8da35172c54f9 100644 --- a/src/userdb/userdbctl.c +++ b/src/userdb/userdbctl.c @@ -1260,7 +1260,7 @@ static int load_credential_one( _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; unsigned line = 0, column = 0; - r = sd_json_parse_file_at(NULL, credential_dir_fd, name, SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + r = sd_json_parse_file_at(/* f= */ NULL, credential_dir_fd, name, SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, &line, &column); if (r < 0) return log_error_errno(r, "Failed to parse credential '%s' as JSON at %u:%u: %m", name, line, column); @@ -1844,7 +1844,7 @@ static int parse_argv(int argc, char *argv[]) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; const char *fn = streq(optarg, "-") ? NULL : optarg; unsigned line = 0; - r = sd_json_parse_file(fn ? NULL : stdin, fn ?: "", SD_JSON_PARSE_SENSITIVE, &v, &line, /* reterr_column= */ NULL); + r = sd_json_parse_file(fn ? NULL : stdin, fn ?: "", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, &line, /* reterr_column= */ NULL); if (r < 0) return log_syntax(/* unit= */ NULL, LOG_ERR, fn ?: "", line, r, "JSON parse failure."); diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index 5048f433b1753..775a481ae8e6d 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -682,14 +682,14 @@ static int verb_call(int argc, char *argv[], uintptr_t _data, void *userdata) { source = ""; /* is correct, as dispatch_verb() shifts arguments by one for the verb. */ - r = sd_json_parse_with_source(parameter, source, 0, &jp, &line, &column); + r = sd_json_parse_with_source(parameter, source, SD_JSON_PARSE_MUST_BE_OBJECT, &jp, &line, &column); } else { if (isatty_safe(STDIN_FILENO) && !arg_quiet) log_notice("Expecting method call parameter JSON object on standard input. (Provide empty string or {} for no parameters.)"); source = ""; - r = sd_json_parse_file_at(stdin, AT_FDCWD, source, 0, &jp, &line, &column); + r = sd_json_parse_file_at(stdin, AT_FDCWD, source, SD_JSON_PARSE_MUST_BE_OBJECT, &jp, &line, &column); } if (r < 0 && r != -ENODATA) return log_error_errno(r, "Failed to parse parameters at %s:%u:%u: %m", source, line, column); diff --git a/src/vmspawn/vmspawn-util.c b/src/vmspawn/vmspawn-util.c index 3f65fab2cc52b..c6e258c50af87 100644 --- a/src/vmspawn/vmspawn-util.c +++ b/src/vmspawn/vmspawn-util.c @@ -253,7 +253,7 @@ static int load_firmware_data(const char *path, FirmwareData **ret) { r = sd_json_parse_file( /* f= */ NULL, path, - /* flags= */ 0, + /* flags= */ SD_JSON_PARSE_MUST_BE_OBJECT, &json, /* reterr_line= */ NULL, /* ret_column= */ NULL); From afc418daa65d31f4b35b07d4dc4200dd8b28c488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Santamar=C3=ADa=20Rogado?= Date: Thu, 19 Mar 2026 04:07:07 +0100 Subject: [PATCH 0377/1296] sensor: gpd fix matches Actually for example the Win Max 2 match is affecting devices that even didn't exist when the matrix was added. --- hwdb.d/60-sensor.hwdb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hwdb.d/60-sensor.hwdb b/hwdb.d/60-sensor.hwdb index 75fc9428a15ba..189c19588a370 100644 --- a/hwdb.d/60-sensor.hwdb +++ b/hwdb.d/60-sensor.hwdb @@ -519,10 +519,10 @@ sensor:modalias:acpi:MXC6655:*:dmi:*:svnGPD:pnG1621-02:* # Pocket 3 sensor:modalias:acpi:MXC6655:*:dmi:*:svnGPD:pnG1628-04:* # Pocket 4 ACCEL_MOUNT_MATRIX=-1, 0, 0; 0, 1, 0; 0, 0, 1 -sensor:modalias:acpi:BMI0160:*:dmi:*:svnGPD:pnG1619*:* # WinMax2 +sensor:modalias:acpi:BMI0160:*:dmi:*:svnGPD:pnG1619-04:* # Win Max 2 ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, 1 -sensor:modalias:acpi:MXC6655:*:dmi:*:svnGPD:pnG1688-*:* # MicroPC 2 +sensor:modalias:acpi:MXC6655:*:dmi:*:svnGPD:pnG1688-08:* # MicroPC 2 ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, -1 ######################################### From de0314a11f2427e219fe336b6ee19210fddcfe47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Santamar=C3=ADa=20Rogado?= Date: Thu, 19 Mar 2026 02:38:06 +0100 Subject: [PATCH 0378/1296] hwdb: sensor: fix bncf newbook 11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Actually was found that this device has panel mount at -90º. This fixes the matrix to follow panel orientation. More info in the previous PR comments: https://github.com/systemd/systemd/pull/40773 Fixes: 774e8059590fac45614a135161dee4669945e342 --- hwdb.d/60-sensor.hwdb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hwdb.d/60-sensor.hwdb b/hwdb.d/60-sensor.hwdb index 189c19588a370..480dab4cd2b2c 100644 --- a/hwdb.d/60-sensor.hwdb +++ b/hwdb.d/60-sensor.hwdb @@ -221,8 +221,8 @@ sensor:modalias:acpi:KIOX010A:*:dmi:*:svnAMI:*:skuH8Y6:* # MaxBook Y14 # BNCF ######################################### -sensor:modalias:acpi:NSA2513:*:dmi:*:svnBNCF:pnNewBook11:* # NewBook 11 2-in-1 - ACCEL_MOUNT_MATRIX=0, 1, 0; -1, 0, 0; 0, 0, -1 +sensor:modalias:acpi:NSA2513:*:dmi:*:svnBNCF:pnNewBook11:* # NewBook 11 2-in-1: Panel at -90 degrees. No ACPI in_mount_matrix. + ACCEL_MOUNT_MATRIX=0, 1, 0; -1, 0, 0; 0, 0, 1 ######################################### # BUSH From 7876bd5cfc821cf0bb97c73db0b61324d7f51c78 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 13 Mar 2026 22:51:42 +0100 Subject: [PATCH 0379/1296] env-file: add parse_env_data() helper --- src/basic/env-file.c | 34 ++++++++++++++++++++ src/basic/env-file.h | 5 +++ src/portable/portablectl.c | 66 +++++++++++++++++--------------------- src/shared/bootspec.c | 19 +++-------- src/shared/kernel-image.c | 9 ++---- 5 files changed, 75 insertions(+), 58 deletions(-) diff --git a/src/basic/env-file.c b/src/basic/env-file.c index 2e15e7eeb7d57..587618614e66d 100644 --- a/src/basic/env-file.c +++ b/src/basic/env-file.c @@ -420,6 +420,40 @@ int parse_env_file_fd_sentinel( return r; } +int parse_env_datav( + const char *data, + size_t size, + const char *fname, /* only used for logging */ + va_list ap) { + + assert(data); + + if (size == SIZE_MAX) + size = strlen_ptr(data); + + _cleanup_fclose_ FILE *f = fmemopen_unlocked((void*) data, size, "r"); + if (!f) + return -ENOMEM; + + return parse_env_filev(f, fname, ap); +} + +int parse_env_data_sentinel( + const char *data, + size_t size, + const char *fname, /* only used for logging */ + ...) { + + va_list ap; + int r; + + va_start(ap, fname); + r = parse_env_datav(data, size, fname, ap); + va_end(ap); + + return r; +} + static int load_env_file_push( const char *filename, unsigned line, const char *key, char *value, diff --git a/src/basic/env-file.h b/src/basic/env-file.h index 78d47f4980857..4d74245915bd7 100644 --- a/src/basic/env-file.h +++ b/src/basic/env-file.h @@ -9,6 +9,11 @@ int parse_env_file_sentinel(FILE *f, const char *fname, ...) _sentinel_; #define parse_env_file(f, fname, ...) parse_env_file_sentinel(f, fname, __VA_ARGS__, NULL) int parse_env_file_fd_sentinel(int fd, const char *fname, ...) _sentinel_; #define parse_env_file_fd(fd, fname, ...) parse_env_file_fd_sentinel(fd, fname, __VA_ARGS__, NULL) + +int parse_env_datav(const char *data, size_t size, const char *fname, va_list ap); +int parse_env_data_sentinel(const char *data, size_t size, const char *fname, ...) _sentinel_; +#define parse_env_data(text, size, fname, ...) parse_env_data_sentinel(text, size, fname, __VA_ARGS__, NULL) + int load_env_file(FILE *f, const char *fname, char ***ret); int load_env_file_pairs(FILE *f, const char *fname, char ***ret); int load_env_file_pairs_fd(int fd, const char *fname, char ***ret); diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index ea5836fdabdd7..98a1657a720a8 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -13,8 +13,6 @@ #include "bus-wait-for-jobs.h" #include "chase.h" #include "env-file.h" -#include "fd-util.h" -#include "fileio.h" #include "format-table.h" #include "fs-util.h" #include "install.h" @@ -332,15 +330,12 @@ static int verb_inspect_image(int argc, char *argv[], uintptr_t _data, void *use nl = true; } else { _cleanup_free_ char *pretty_portable = NULL, *pretty_os = NULL; - _cleanup_fclose_ FILE *f = NULL; - f = fmemopen_unlocked((void*) data, sz, "r"); - if (!f) - return log_error_errno(errno, "Failed to open /etc/os-release buffer: %m"); - - r = parse_env_file(f, "/etc/os-release", - "PORTABLE_PRETTY_NAME", &pretty_portable, - "PRETTY_NAME", &pretty_os); + r = parse_env_data( + data, sz, + "/etc/os-release", + "PORTABLE_PRETTY_NAME", &pretty_portable, + "PRETTY_NAME", &pretty_os); if (r < 0) return log_error_errno(r, "Failed to parse /etc/os-release: %m"); @@ -396,33 +391,30 @@ static int verb_inspect_image(int argc, char *argv[], uintptr_t _data, void *use *confext_version_id = NULL, *confext_scope = NULL, *confext_image_id = NULL, *confext_image_version = NULL, *confext_build_id = NULL; - _cleanup_fclose_ FILE *f = NULL; - - f = fmemopen_unlocked((void*) data, sz, "r"); - if (!f) - return log_error_errno(errno, "Failed to open extension-release buffer: %m"); - - r = parse_env_file(f, name, - "SYSEXT_ID", &sysext_id, - "SYSEXT_VERSION_ID", &sysext_version_id, - "SYSEXT_BUILD_ID", &sysext_build_id, - "SYSEXT_IMAGE_ID", &sysext_image_id, - "SYSEXT_IMAGE_VERSION", &sysext_image_version, - "SYSEXT_SCOPE", &sysext_scope, - "SYSEXT_LEVEL", &sysext_level, - "SYSEXT_PRETTY_NAME", &sysext_pretty_os, - "CONFEXT_ID", &confext_id, - "CONFEXT_VERSION_ID", &confext_version_id, - "CONFEXT_BUILD_ID", &confext_build_id, - "CONFEXT_IMAGE_ID", &confext_image_id, - "CONFEXT_IMAGE_VERSION", &confext_image_version, - "CONFEXT_SCOPE", &confext_scope, - "CONFEXT_LEVEL", &confext_level, - "CONFEXT_PRETTY_NAME", &confext_pretty_os, - "ID", &id, - "VERSION_ID", &version_id, - "PORTABLE_PRETTY_NAME", &pretty_portable, - "PORTABLE_PREFIXES", &portable_prefixes); + + r = parse_env_data( + data, sz, + name, + "SYSEXT_ID", &sysext_id, + "SYSEXT_VERSION_ID", &sysext_version_id, + "SYSEXT_BUILD_ID", &sysext_build_id, + "SYSEXT_IMAGE_ID", &sysext_image_id, + "SYSEXT_IMAGE_VERSION", &sysext_image_version, + "SYSEXT_SCOPE", &sysext_scope, + "SYSEXT_LEVEL", &sysext_level, + "SYSEXT_PRETTY_NAME", &sysext_pretty_os, + "CONFEXT_ID", &confext_id, + "CONFEXT_VERSION_ID", &confext_version_id, + "CONFEXT_BUILD_ID", &confext_build_id, + "CONFEXT_IMAGE_ID", &confext_image_id, + "CONFEXT_IMAGE_VERSION", &confext_image_version, + "CONFEXT_SCOPE", &confext_scope, + "CONFEXT_LEVEL", &confext_level, + "CONFEXT_PRETTY_NAME", &confext_pretty_os, + "ID", &id, + "VERSION_ID", &version_id, + "PORTABLE_PRETTY_NAME", &pretty_portable, + "PORTABLE_PREFIXES", &portable_prefixes); if (r < 0) return log_error_errno(r, "Failed to parse extension release from '%s': %m", name); diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index a341b0729bd1e..2a898067f81ac 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -711,7 +711,6 @@ static int boot_entry_load_unified( _cleanup_free_ char *fname = NULL, *os_pretty_name = NULL, *os_image_id = NULL, *os_name = NULL, *os_id = NULL, *os_image_version = NULL, *os_version = NULL, *os_version_id = NULL, *os_build_id = NULL; const char *k, *good_name, *good_version, *good_sort_key; - _cleanup_fclose_ FILE *f = NULL; int r; assert(root); @@ -723,11 +722,8 @@ static int boot_entry_load_unified( if (!k) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path is not below root: %s", path); - f = fmemopen_unlocked((void*) osrelease_text, strlen(osrelease_text), "r"); - if (!f) - return log_oom(); - - r = parse_env_file(f, "os-release", + r = parse_env_data(osrelease_text, /* size= */ SIZE_MAX, + ".osrel", "PRETTY_NAME", &os_pretty_name, "IMAGE_ID", &os_image_id, "NAME", &os_name, @@ -755,14 +751,9 @@ static int boot_entry_load_unified( _cleanup_free_ char *profile_id = NULL, *profile_title = NULL; if (profile_text) { - fclose(f); - - f = fmemopen_unlocked((void*) profile_text, strlen(profile_text), "r"); - if (!f) - return log_oom(); - - r = parse_env_file( - f, "profile", + r = parse_env_data( + profile_text, /* size= */ SIZE_MAX, + ".profile", "ID", &profile_id, "TITLE", &profile_title); if (r < 0) diff --git a/src/shared/kernel-image.c b/src/shared/kernel-image.c index d3e18d19f41a8..0f2a646da5eb1 100644 --- a/src/shared/kernel-image.c +++ b/src/shared/kernel-image.c @@ -28,7 +28,6 @@ static int uki_read_pretty_name( char **ret) { _cleanup_free_ char *pname = NULL, *name = NULL; - _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ void *osrel = NULL; size_t osrel_size; int r; @@ -51,12 +50,8 @@ static int uki_read_pretty_name( return 0; } - f = fmemopen(osrel, osrel_size, "r"); - if (!f) - return log_error_errno(errno, "Failed to open embedded os-release file: %m"); - - r = parse_env_file( - f, NULL, + r = parse_env_data( + osrel, osrel_size, ".osrel", "PRETTY_NAME", &pname, "NAME", &name); if (r < 0) From 4341ba091dd1074d3d8c41e2a4e6f155d0a7b79f Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Fri, 20 Mar 2026 11:13:28 -0400 Subject: [PATCH 0380/1296] ssh-proxy: return an error if user supplies VMADDR_CID_ANY Right now, if a user tries to pass VMADDR_CID_ANY to systemd-ssh-proxy, an assert is triggered: $ ssh vsock%4294967295 Assertion 'cid != VMADDR_CID_ANY' failed at src/ssh-generator/ssh-proxy.c:21, function process_vsock_cid(). Aborting. mm_receive_fd: recvmsg: expected received 1 got 0 proxy dialer did not pass back a connection This is becauase the value returned from vsock_parse_cid is not checked before being passed to process_vsock_string. Add a check to prevent that. --- src/ssh-generator/ssh-proxy.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ssh-generator/ssh-proxy.c b/src/ssh-generator/ssh-proxy.c index 337153787ec1d..bfb91b5867c4e 100644 --- a/src/ssh-generator/ssh-proxy.c +++ b/src/ssh-generator/ssh-proxy.c @@ -62,6 +62,9 @@ static int process_vsock_string(const char *host, const char *port) { if (r < 0) return log_error_errno(r, "Failed to parse vsock cid: %s", host); + if (cid == VMADDR_CID_ANY) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use VMADDR_CID_ANY to connect to a remote host."); + return process_vsock_cid(cid, port); } From 83359c4da02a82d2972cf957d9855ea957359287 Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Fri, 20 Mar 2026 11:23:39 -0400 Subject: [PATCH 0381/1296] socket-util: filter out VMADDR_CID_ANY in vsock_get_local_cid() It has been observed on some systems[1] that ssh-issue may print out: Try contacting this VM's SSH server via 'ssh vsock%4294967295' from host. i.e. it suggests connecting with VMADDR_CID_ANY, which is not valid. It seems that IOCTL_VM_SOCKETS_GET_LOCAL_CID may return VMADDR_CID_ANY in some cases, e.g. when vsock is not full initialized or so. Treat VMADDR_CID_ANY as special in vsock_get_local_cid(), the same as VMADDR_CID_LOCAL and VMADDR_CID_HOST, and return an error. [1] https://launchpad.net/bugs/2145027 --- src/basic/socket-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c index 159486f7f1e52..eab09786b67fa 100644 --- a/src/basic/socket-util.c +++ b/src/basic/socket-util.c @@ -1794,7 +1794,7 @@ int vsock_get_local_cid(unsigned *ret) { /* If ret == NULL, we're just want to check if AF_VSOCK is available, so accept * any address. Otherwise, filter out special addresses that are cannot be used * to identify _this_ machine from the outside. */ - if (ret && IN_SET(tmp, VMADDR_CID_LOCAL, VMADDR_CID_HOST)) + if (ret && IN_SET(tmp, VMADDR_CID_LOCAL, VMADDR_CID_HOST, VMADDR_CID_ANY)) return log_debug_errno(SYNTHETIC_ERRNO(EADDRNOTAVAIL), "IOCTL_VM_SOCKETS_GET_LOCAL_CID returned special value (%u), ignoring.", tmp); From 5d0488ab0f33f95fcfb2f88a538dcfa6ba1a8456 Mon Sep 17 00:00:00 2001 From: David Tardon Date: Fri, 27 Feb 2026 13:29:44 +0100 Subject: [PATCH 0382/1296] integritysetup: regularize conversion of integrity alg. The number of integrity algorithms we handle whose names differ between integritysetup and dm-integrity continually increases, so let's drop the ad hoc conversion and use string tables. --- src/integritysetup/integrity-util.c | 37 ++++++++++++++++----------- src/integritysetup/integrity-util.h | 20 +++++++++++---- src/integritysetup/integritysetup.c | 39 ++++++++++++++++------------- 3 files changed, 59 insertions(+), 37 deletions(-) diff --git a/src/integritysetup/integrity-util.c b/src/integritysetup/integrity-util.c index 7e52f5c0dcba4..88236f6323638 100644 --- a/src/integritysetup/integrity-util.c +++ b/src/integritysetup/integrity-util.c @@ -6,15 +6,24 @@ #include "integrity-util.h" #include "log.h" #include "percent-util.h" +#include "string-table.h" #include "string-util.h" -#include "strv.h" #include "time-util.h" -static int supported_integrity_algorithm(char *user_supplied) { - if (!STR_IN_SET(user_supplied, "crc32", "crc32c", "xxhash64", "sha1", "sha256", "hmac-sha256", "hmac-sha512", "phmac-sha256", "phmac-sha512")) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unsupported integrity algorithm (%s)", user_supplied); - return 0; -} +/* Integrity algorithm names used by integritysetup/integritytab */ +static const char* const integrity_algorithm_table[_INTEGRITY_ALGORITHM_MAX] = { + [INTEGRITY_ALGORITHM_CRC32] = "crc32", + [INTEGRITY_ALGORITHM_CRC32C] = "crc32c", + [INTEGRITY_ALGORITHM_XXHASH64] = "xxhash64", + [INTEGRITY_ALGORITHM_SHA1] = "sha1", + [INTEGRITY_ALGORITHM_SHA256] = "sha256", + [INTEGRITY_ALGORITHM_HMAC_SHA256] = "hmac-sha256", + [INTEGRITY_ALGORITHM_HMAC_SHA512] = "hmac-sha512", + [INTEGRITY_ALGORITHM_PHMAC_SHA256] = "phmac-sha256", + [INTEGRITY_ALGORITHM_PHMAC_SHA512] = "phmac-sha512", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(integrity_algorithm, IntegrityAlgorithm); int parse_integrity_options( const char *options, @@ -22,7 +31,7 @@ int parse_integrity_options( int *ret_percent, usec_t *ret_commit_time, char **ret_data_device, - char **ret_integrity_alg) { + IntegrityAlgorithm *ret_integrity_alg) { int r; for (;;) { @@ -73,14 +82,12 @@ int parse_integrity_options( return log_oom(); } } else if ((val = startswith(word, "integrity-algorithm="))) { - r = supported_integrity_algorithm(val); - if (r < 0) - return r; - if (ret_integrity_alg) { - r = free_and_strdup(ret_integrity_alg, val); - if (r < 0) - return log_oom(); - } + IntegrityAlgorithm a = integrity_algorithm_from_string(val); + if (a < 0) + return log_error_errno(a, "Unsupported integrity algorithm (%s)", val); + + if (ret_integrity_alg) + *ret_integrity_alg = a; } else log_warning("Encountered unknown option '%s', ignoring.", word); } diff --git a/src/integritysetup/integrity-util.h b/src/integritysetup/integrity-util.h index 5cc7e42de9270..b1fd98c849b6b 100644 --- a/src/integritysetup/integrity-util.h +++ b/src/integritysetup/integrity-util.h @@ -3,16 +3,26 @@ #include "shared-forward.h" +typedef enum { + INTEGRITY_ALGORITHM_CRC32, + INTEGRITY_ALGORITHM_CRC32C, + INTEGRITY_ALGORITHM_XXHASH64, + INTEGRITY_ALGORITHM_SHA1, + INTEGRITY_ALGORITHM_SHA256, + INTEGRITY_ALGORITHM_HMAC_SHA256, + INTEGRITY_ALGORITHM_HMAC_SHA512, + INTEGRITY_ALGORITHM_PHMAC_SHA256, + INTEGRITY_ALGORITHM_PHMAC_SHA512, + _INTEGRITY_ALGORITHM_MAX, + _INTEGRITY_ALGORITHM_INVALID = -EINVAL, +} IntegrityAlgorithm; + int parse_integrity_options( const char *options, uint32_t *ret_activate_flags, int *ret_percent, usec_t *ret_commit_time, char **ret_data_device, - char **ret_integrity_alg); + IntegrityAlgorithm *ret_integrity_alg); -#define DM_HMAC_256 "hmac(sha256)" -#define DM_HMAC_512 "hmac(sha512)" -#define DM_PHMAC_256 "phmac(sha256)" -#define DM_PHMAC_512 "phmac(sha512)" #define DM_MAX_KEY_SIZE 4096 /* Maximum size of key allowed for dm-integrity */ diff --git a/src/integritysetup/integritysetup.c b/src/integritysetup/integritysetup.c index 5ca3eee08726a..29581a0522647 100644 --- a/src/integritysetup/integritysetup.c +++ b/src/integritysetup/integritysetup.c @@ -12,6 +12,7 @@ #include "main-func.h" #include "path-util.h" #include "pretty-print.h" +#include "string-table.h" #include "string-util.h" #include "time-util.h" #include "verbs.h" @@ -20,10 +21,24 @@ static uint32_t arg_activate_flags; static int arg_percent; static usec_t arg_commit_time; static char *arg_existing_data_device; -static char *arg_integrity_algorithm; +static IntegrityAlgorithm arg_integrity_algorithm = _INTEGRITY_ALGORITHM_INVALID; STATIC_DESTRUCTOR_REGISTER(arg_existing_data_device, freep); -STATIC_DESTRUCTOR_REGISTER(arg_integrity_algorithm, freep); + +/* Integrity algorithm names used by dm-integrity */ +static const char* const dm_integrity_algorithm_table[_INTEGRITY_ALGORITHM_MAX] = { + [INTEGRITY_ALGORITHM_CRC32] = "crc32", + [INTEGRITY_ALGORITHM_CRC32C] = "crc32c", + [INTEGRITY_ALGORITHM_XXHASH64] = "xxhash64", + [INTEGRITY_ALGORITHM_SHA1] = "sha1", + [INTEGRITY_ALGORITHM_SHA256] = "sha256", + [INTEGRITY_ALGORITHM_HMAC_SHA256] = "hmac(sha256)", + [INTEGRITY_ALGORITHM_HMAC_SHA512] = "hmac(sha512)", + [INTEGRITY_ALGORITHM_PHMAC_SHA256] = "phmac(sha256)", + [INTEGRITY_ALGORITHM_PHMAC_SHA512] = "phmac(sha512)", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(dm_integrity_algorithm, IntegrityAlgorithm); static int help(void) { _cleanup_free_ char *link = NULL; @@ -72,21 +87,11 @@ static int load_key_file( } static const char *integrity_algorithm_select(const void *key_file_buf) { - /* To keep a bit of sanity for end users, the subset of integrity - * algorithms we support will match what is used in integritysetup */ - if (arg_integrity_algorithm) { - if (streq(arg_integrity_algorithm, "hmac-sha256")) - return DM_HMAC_256; - if (streq(arg_integrity_algorithm, "hmac-sha512")) - return DM_HMAC_512; - if (streq(arg_integrity_algorithm, "phmac-sha256")) - return DM_PHMAC_256; - if (streq(arg_integrity_algorithm, "phmac-sha512")) - return DM_PHMAC_512; - return arg_integrity_algorithm; - } else if (key_file_buf) - return DM_HMAC_256; - return "crc32c"; + IntegrityAlgorithm a = arg_integrity_algorithm >= 0 + ? arg_integrity_algorithm + : (key_file_buf ? INTEGRITY_ALGORITHM_HMAC_SHA256 : INTEGRITY_ALGORITHM_CRC32C); + + return dm_integrity_algorithm_to_string(a); } static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata) { From 93f1546b930d7d20946c77ab0d88717207570267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Santamar=C3=ADa=20Rogado?= Date: Thu, 19 Mar 2026 10:07:55 +0100 Subject: [PATCH 0383/1296] hwdb: keyboard: erase entry that will never match The match in "AYA NEO" will never happen as dmi modalias will wipe blank spaces. Even more the intended match was covered before by "AYANEO". Actually there are contributions that rely on someone giving some data to other someone with no test at all. Should be consider to enforce the full udevadm info --export-db as mandatory requirement fot this kind of contributions. --- hwdb.d/60-keyboard.hwdb | 1 - 1 file changed, 1 deletion(-) diff --git a/hwdb.d/60-keyboard.hwdb b/hwdb.d/60-keyboard.hwdb index 936b7f7392654..fcc4b063fbbef 100644 --- a/hwdb.d/60-keyboard.hwdb +++ b/hwdb.d/60-keyboard.hwdb @@ -320,7 +320,6 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnAYANEO:pnKUN:pvr* # multi-scancode sequence. The specific preceding codes # depend on the model, but the final scancode is always the # same. -evdev:name:AT Translated Set 2 keyboard:dmi:*:svnAYA NEO:* evdev:name:AT Translated Set 2 keyboard:dmi:*:svnAYADEVICE:* evdev:name:AT Translated Set 2 keyboard:dmi:*:svnAYANEO:* KEYBOARD_KEY_66=f15 # LC (All models) From eafd3e6a7d1b5c4bd46266a7991ce83d8995fb81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 24 Feb 2026 16:13:06 +0100 Subject: [PATCH 0384/1296] Add "option parser" infrastracture that helps with cmdline option parsing The basic idea is that we'll have "one source of truth" for the list of options. Currently, this is split between: 1. struct option options[] array for long options 2. the short option parameter to getopt_long() 3. --help so it is easy to forget to add or update one of those places where appropriate. An option is defined through a macro that includes the option short and long codes, and also the metavar and help. Those four items can be used to generate the help string automatically. The code is easier to read when various parts are written in the same order. We can define common options through a macro in the header file, reducing boilerplate repeated in different files. Over time, if we discover that the same pattern is used in multiple files, we can add another "common option". The macro is defined in a way that the editor can indent it like a normal case statement. The error message for ambiguous options is formatted a bit differently: $ systemd-id128 --no- systemd-id128: option '--no-' is ambiguous; possibilities: '--no-pager' '--no-legend' $ build/systemd-id128 --no- option '--no-' is ambiguous; possibilities: --no-pager, --no-legend I think the formatting without commas is ugly, but OTOH, the quotes around option names are superfluous, real option names are easy to distinguish. --- src/shared/meson.build | 1 + src/shared/options.c | 319 +++++++++++++++++++++++++++++++++++++++++ src/shared/options.h | 101 +++++++++++++ src/shared/verbs.c | 37 ++--- src/shared/verbs.h | 1 + 5 files changed, 442 insertions(+), 17 deletions(-) create mode 100644 src/shared/options.c create mode 100644 src/shared/options.h diff --git a/src/shared/meson.build b/src/shared/meson.build index bbc0307999324..e25f855c712f8 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -143,6 +143,7 @@ shared_sources = files( 'numa-util.c', 'open-file.c', 'openssl-util.c', + 'options.c', 'osc-context.c', 'output-mode.c', 'pager.c', diff --git a/src/shared/options.c b/src/shared/options.c new file mode 100644 index 0000000000000..c5e7057bf85ae --- /dev/null +++ b/src/shared/options.c @@ -0,0 +1,319 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "format-table.h" +#include "log.h" +#include "options.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" + +static bool option_takes_arg(const Option *opt) { + return ASSERT_PTR(opt)->metavar; +} + +static bool option_arg_optional(const Option *opt) { + return option_takes_arg(opt) && FLAGS_SET(opt->flags, OPTION_OPTIONAL_ARG); +} + +static bool option_arg_required(const Option *opt) { + return option_takes_arg(opt) && !FLAGS_SET(opt->flags, OPTION_OPTIONAL_ARG); +} + +static bool option_is_metadata(const Option *opt) { + /* A metadata entry that is not a real option, like the group marker */ + return FLAGS_SET(ASSERT_PTR(opt)->flags, OPTION_GROUP_MARKER); +} + +static void kill_arg(char* argv[], int argc, int index) { + assert(index < argc); + assert(!argv[argc]); + + /* Eliminate argv[index] */ + memmove(argv + index, argv + index + 1, (argc - index) * sizeof(char*)); +} + +static void shift_arg(char* argv[], int target, int source) { + assert(argv); + assert(target <= source); + + /* Move argv[source] before argv[target], shifting arguments inbetween */ + char *saved = argv[source]; + memmove(argv + target + 1, argv + target, (source - target) * sizeof(char*)); + argv[target] = saved; +} + +static int partial_match_error( + const Option options[], + const Option options_end[], + const char *optname, + unsigned n_partial_matches) { + int r; + + assert(startswith(ASSERT_PTR(optname), "--")); + assert(n_partial_matches >= 2); + + /* Find options that match the prefix */ + _cleanup_strv_free_ char **s = NULL; + for (const Option* option = options; option < options_end; option++) + if (!option_is_metadata(option) && + option->long_code && + startswith(option->long_code, optname + 2)) { + + r = strv_extendf(&s, "--%s", option->long_code); + if (r < 0) + return log_error_errno(r, "Failed to format message: %m"); + } + + assert(strv_length(s) == n_partial_matches); + + _cleanup_free_ char *p = strv_join_full(s, ", ", /* prefix= */ NULL, /* escape_separator= */ false); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: option '%s' is ambiguous; possibilities: %s", + program_invocation_short_name, optname, strnull(p)); +} + +int option_parse( + const Option options[], + const Option options_end[], + OptionParser *state, + int argc, char *argv[], + const Option **ret_option, + const char **ret_arg) { + + assert(ret_arg); + + /* Check and initialize */ + if (state->optind == 0) { + if (argc < 1 || strv_isempty(argv)) + return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "argv cannot be empty"); + + *state = (OptionParser) { + .optind = 1, + .positional_offset = 1, + }; + } + + /* Look for the next option */ + + const Option *option; + const char *optname = NULL, *optval = NULL; + _cleanup_free_ char *_optname = NULL; /* allocated option name */ + bool separate_optval = false; + + if (state->short_option_offset == 0) { + /* Skip over non-option parameters */ + for (;;) { + if (state->optind == argc) + return 0; + + if (streq(argv[state->optind], "--")) { + /* No more options. Eliminate "--" so that the list of positional args is clean. */ + kill_arg(argv, argc, state->optind); + return 0; + } + + if (!state->parsing_stopped && + argv[state->optind][0] == '-' && + argv[state->optind][1] != '\0') + /* Looks like we found an option parameter */ + break; + + state->optind++; + } + + /* Find matching option entry. + * First, figure out if we have a long option or a short option. */ + assert(argv[state->optind][0] == '-'); + + if (argv[state->optind][1] == '-') { + /* We have a long option. */ + char *eq = strchr(argv[state->optind], '='); + if (eq) { + optname = _optname = strndup(argv[state->optind], eq - argv[state->optind]); + if (!_optname) + return log_oom(); + + /* joined argument */ + optval = eq + 1; + } else + /* argument (if any) is separate */ + optname = argv[state->optind]; + + const Option *last_partial = NULL; + unsigned n_partial_matches = 0; /* The commandline option matches a defined prefix. */ + + for (option = options;; option++) { + if (option >= options_end) { + if (n_partial_matches == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: unrecognized option '%s'", + program_invocation_short_name, optname); + if (n_partial_matches > 1) + return partial_match_error(options, options_end, optname, n_partial_matches); + + /* just one partial — good */ + option = last_partial; + break; + } + + if (option_is_metadata(option) || !option->long_code) + continue; + + /* Check if the parameter forms a prefix of the option name */ + const char *rest = startswith(option->long_code, optname + 2); + if (!rest) + continue; + if (isempty(rest)) + /* exact match */ + break; + /* partial match */ + last_partial = option; + n_partial_matches++; + } + } else + /* We have a short option */ + state->short_option_offset = 1; + } + + if (state->short_option_offset > 0) { + char optchar = argv[state->optind][state->short_option_offset]; + + if (asprintf(&_optname, "-%c", optchar) < 0) + return log_oom(); + optname = _optname; + + for (option = options;; option++) { + if (option >= options_end) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: unrecognized option '%s'", + program_invocation_short_name, optname); + + if (option_is_metadata(option) || optchar != option->short_code) + continue; + + const char *rest = argv[state->optind] + state->short_option_offset + 1; + + if (option_takes_arg(option) && !isempty(rest)) { + /* The rest of this parameter is the value. */ + optval = rest; + state->short_option_offset = 0; + } else if (isempty(rest)) + state->short_option_offset = 0; + else + state->short_option_offset++; + + break; + } + } + + if (optval && !option_takes_arg(option)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: option '%s' doesn't allow an argument", + program_invocation_short_name, optname); + if (!optval && option_arg_required(option)) { + if (!argv[state->optind + 1]) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: option '%s' requires an argument", + program_invocation_short_name, optname); + optval = argv[state->optind + 1]; + separate_optval = true; + } + + if (state->short_option_offset == 0) { + /* We're done with this option. Adjust the array and position. */ + shift_arg(argv, state->positional_offset++, state->optind++); + if (separate_optval) + shift_arg(argv, state->positional_offset++, state->optind++); + } + + if (FLAGS_SET(option->flags, OPTION_STOPS_PARSING)) + state->parsing_stopped = true; + + if (ret_option) + /* Return the matched Option structure to allow the caller to "know" what was matched */ + *ret_option = option; + *ret_arg = optval; + return option->id; +} + +char** option_parser_get_args(OptionParser *state, int argc, char *argv[]) { + /* Returns positional args as a strv. + * If "--" was found, it has been removed. */ + + assert(state->optind > 0); + return argv + state->positional_offset; +} + +int _option_parser_get_help_table( + const Option options[], + const Option options_end[], + const char *group, + Table **ret) { + int r; + + assert(ret); + + _cleanup_(table_unrefp) Table *table = table_new("names", "help"); + if (!table) + return log_oom(); + + bool in_group = group == NULL; /* Are we currently in the section on the array that forms + * group ? The first part is the default group, so + * the group was not specified, we are in. */ + + for (const Option *opt = options; opt < options_end; opt++) { + bool group_marker = FLAGS_SET(opt->flags, OPTION_GROUP_MARKER); + if (!in_group) { + in_group = group_marker && streq(group, opt->long_code); + continue; + } + if (group_marker) + break; /* End of group */ + + assert(!option_is_metadata(opt)); + + if (!opt->help) + /* No help string — we do not show the option */ + continue; + + char sc[3] = " "; + if (opt->short_code != 0) + xsprintf(sc, "-%c", opt->short_code); + + /* We indent the option string by two spaces. We could set the minimum cell width and + * right-align for a similar result, but that'd be more work. This is only used for + * display. + * + * "=" is shown only when a long option is defined: -l --long=ARG, --long=ARG, -s ARG. + */ + bool need_eq = option_takes_arg(opt) && opt->long_code; + _cleanup_free_ char *s = strjoin( + " ", + sc, + " ", + opt->long_code ? "--" : "", + strempty(opt->long_code), + option_arg_optional(opt) ? "[" : "", + need_eq ? "=" : "", + strempty(opt->metavar), + option_arg_optional(opt) ? "]" : ""); + if (!s) + return log_oom(); + + r = table_add_many(table, TABLE_STRING, s); + if (r < 0) + return table_log_add_error(r); + + _cleanup_strv_free_ char **t = strv_split(opt->help, /* separators= */ NULL); + if (!t) + return log_oom(); + + r = table_add_many(table, TABLE_STRV_WRAPPED, t); + if (r < 0) + return table_log_add_error(r); + } + + table_set_header(table, false); + *ret = TAKE_PTR(table); + return 0; +} diff --git a/src/shared/options.h b/src/shared/options.h new file mode 100644 index 0000000000000..f548538bd048a --- /dev/null +++ b/src/shared/options.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +typedef enum OptionFlags { + OPTION_OPTIONAL_ARG = 1U << 0, /* Same as optional_argument in getopt */ + OPTION_STOPS_PARSING = 1U << 1, /* This option acts like "--" */ + OPTION_GROUP_MARKER = 1U << 2, /* Fake option entry to separate groups */ +} OptionFlags; + +typedef struct Option { + int id; + OptionFlags flags; + char short_code; + const char *long_code; + const char *metavar; + const char *help; +} Option; + +#define _OPTION(counter, fl, sc, lc, mv, h) \ + _section_("SYSTEMD_OPTIONS") \ + _alignptr_ \ + _used_ \ + _retain_ \ + _variable_no_sanitize_address_ \ + static const Option CONCATENATE(option, counter) = { \ + .id = 0x100 + counter, \ + .flags = fl, \ + .short_code = sc, \ + .long_code = lc, \ + .metavar = mv, \ + .help = h, \ + }; \ + case (0x100 + counter) + +/* Magic entry in the table (which will not be returned) that designates the start of the group . + * The define is structured as 'case' so that it can be followed by ':' and indented appropriately. + */ +#define OPTION_GROUP(gr) \ + _OPTION(__COUNTER__, OPTION_GROUP_MARKER, /* sc= */ 0, /* lc= */ gr, /* mv= */ NULL, /* h= */ NULL) + +#define OPTION_FULL(fl, sc, lc, mv, h) _OPTION(__COUNTER__, fl, sc, lc, mv, h) +#define OPTION(sc, lc, mv, h) OPTION_FULL(/* fl= */ 0, sc, lc, mv, h) +#define OPTION_LONG(lc, mv, h) OPTION(/* sc= */ 0, lc, mv, h) +#define OPTION_SHORT(sc, mv, h) OPTION(sc, /* lc= */ NULL, mv, h) + +#define OPTION_COMMON_HELP \ + OPTION('h', "help", NULL, "Show this help") +#define OPTION_COMMON_VERSION \ + OPTION_LONG("version", NULL, "Show package version") +#define OPTION_COMMON_NO_PAGER \ + OPTION_LONG("no-pager", NULL, "Do not start a pager") +#define OPTION_COMMON_NO_LEGEND \ + OPTION_LONG("no-legend", NULL, "Do not show headers and footers") +#define OPTION_COMMON_JSON \ + OPTION_LONG("json", "FORMAT", "Generate JSON output (pretty, short, or off)") + +/* This is magically mapped to the beginning and end of the section */ +extern const Option __start_SYSTEMD_OPTIONS[]; +extern const Option __stop_SYSTEMD_OPTIONS[]; + +typedef struct OptionParser { + int optind; /* Position of the parameter being handled. + * 0 → option parsing hasn't been started yet. */ + int short_option_offset; /* Set when we're parsing an argument with one or more short options. + * 0 → we're not parsing short options. */ + int positional_offset; /* Offset to where positional parameters are. After processing has been + * finished, all options and their args are to the left of this offset. */ + bool parsing_stopped; /* We processed "--" or an option that terminates option parsing. */ +} OptionParser; + +int option_parse( + const Option options[], + const Option options_end[], + OptionParser *state, + int argc, char *argv[], + const Option **ret_option, + const char **ret_arg); + +/* Iterate over options. */ +#define FOREACH_OPTION_FULL(parser, opt, argc, argv, ret_o, ret_a, on_error) \ + for (int opt; (opt = option_parse(ALIGN_PTR(__start_SYSTEMD_OPTIONS), __stop_SYSTEMD_OPTIONS, parser, argc, argv, ret_o, ret_a)) != 0; ) \ + if (opt < 0) { \ + on_error; \ + break; \ + } else + +#define FOREACH_OPTION(parser, opt, argc, argv, ret_a, on_error) \ + FOREACH_OPTION_FULL(parser, opt, argc, argv, /* ret_o= */ NULL, ret_a, on_error) + +char** option_parser_get_args(OptionParser *state, int argc, char *argv[]); +int _option_parser_get_help_table( + const Option options[], + const Option options_end[], + const char *group, + Table **ret); +#define option_parser_get_help_table_group(group, ret) \ + _option_parser_get_help_table(ALIGN_PTR(__start_SYSTEMD_OPTIONS), __stop_SYSTEMD_OPTIONS, group, ret) +#define option_parser_get_help_table(ret) \ + option_parser_get_help_table_group(/* group= */ NULL, ret) diff --git a/src/shared/verbs.c b/src/shared/verbs.c index c6d35913b4fc9..8c174a6c88de4 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -68,24 +68,17 @@ const Verb* verbs_find_verb(const char *name, const Verb verbs[]) { return NULL; } -int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { - const Verb *verb; - const char *name; - int r, left; +int dispatch_verb_with_args(char **args, const Verb verbs[], void *userdata) { + int r; assert(verbs); assert(verbs[0].dispatch); assert(verbs[0].verb); - assert(argc >= 0); - assert(argv); - assert(argc >= optind); - left = argc - optind; - argv += optind; - optind = 0; - name = argv[0]; + const char *name = args ? args[0] : NULL; + size_t left = strv_length(args); - verb = verbs_find_verb(name, verbs); + const Verb *verb = verbs_find_verb(name, verbs); if (!verb) { _cleanup_strv_free_ char **verb_strv = NULL; @@ -120,12 +113,10 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { if (!name) left = 1; - if (verb->min_args != VERB_ANY && - (unsigned) left < verb->min_args) + if (verb->min_args != VERB_ANY && left < verb->min_args) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too few arguments."); - if (verb->max_args != VERB_ANY && - (unsigned) left > verb->max_args) + if (verb->max_args != VERB_ANY && left > verb->max_args) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments."); if ((verb->flags & VERB_ONLINE_ONLY) && running_in_chroot_or_offline()) { @@ -136,5 +127,17 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { if (!name) return verb->dispatch(1, STRV_MAKE(verb->verb), verb->data, userdata); - return verb->dispatch(left, argv, verb->data, userdata); + assert(left < INT_MAX); /* args are derived from argc+argv, so their size must fit in an int. */ + return verb->dispatch(left, args, verb->data, userdata); +} + +int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { + /* getopt wrapper for _dispatch_verb_with_args. + * TBD: remove this function when all programs with verbs have been converted. */ + + assert(argc >= 0); + assert(argv); + assert(argc >= optind); + + return dispatch_verb_with_args(strv_skip(argv, optind), verbs, userdata); } diff --git a/src/shared/verbs.h b/src/shared/verbs.h index e330156318e96..febb8ccfc24fd 100644 --- a/src/shared/verbs.h +++ b/src/shared/verbs.h @@ -23,4 +23,5 @@ bool running_in_chroot_or_offline(void); bool should_bypass(const char *env_prefix); const Verb* verbs_find_verb(const char *name, const Verb verbs[]); +int dispatch_verb_with_args(char **args, const Verb verbs[], void *userdata); int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata); From b6bc0948cd2f5962b77008d4337d88630a9eabd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sun, 1 Mar 2026 15:37:24 +0100 Subject: [PATCH 0385/1296] =?UTF-8?q?options:=20add=20workaround=20for=20s?= =?UTF-8?q?purious=20warning=20with=20gcc=2011=E2=80=9313?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gcc 14+ doesn't need this. --- src/shared/options.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/shared/options.c b/src/shared/options.c index c5e7057bf85ae..09b677f813312 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -95,7 +95,7 @@ int option_parse( /* Look for the next option */ - const Option *option; + const Option *option = NULL; /* initialization to appease gcc 13 */ const char *optname = NULL, *optval = NULL; _cleanup_free_ char *_optname = NULL; /* allocated option name */ bool separate_optval = false; @@ -206,6 +206,8 @@ int option_parse( } } + assert(option); + if (optval && !option_takes_arg(option)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s: option '%s' doesn't allow an argument", From cc07d8ab472be24843a3a6f7c8648af8dbbcaa22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 27 Feb 2026 19:10:56 +0100 Subject: [PATCH 0386/1296] test-option-parser: "translate" test-getopt for the new parser The test cases are the same in both files. To make the test more through, add case where "--" is used more than once and also when options are present after "--". --- src/test/meson.build | 1 + src/test/test-getopt.c | 12 +- src/test/test-options.c | 375 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 382 insertions(+), 6 deletions(-) create mode 100644 src/test/test-options.c diff --git a/src/test/meson.build b/src/test/meson.build index adbcd3c0d4dcf..d11d853f1d46f 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -156,6 +156,7 @@ simple_tests += files( 'test-nsresource.c', 'test-nulstr-util.c', 'test-open-file.c', + 'test-options.c', 'test-ordered-set.c', 'test-os-util.c', 'test-osc-context.c', diff --git a/src/test/test-getopt.c b/src/test/test-getopt.c index e17621af6a944..0ba1c678f241d 100644 --- a/src/test/test-getopt.c +++ b/src/test/test-getopt.c @@ -90,27 +90,27 @@ TEST(getopt_long) { test_getopt_long_one(STRV_MAKE("arg0", "--", "string1", - "string2", - "string3", + "--help", + "-h", "string4"), "hr:o::", options, NULL, STRV_MAKE("string1", - "string2", - "string3", + "--help", + "-h", "string4")); test_getopt_long_one(STRV_MAKE("arg0", "string1", "string2", "--", - "string3", + "--", "string4"), "hr:o::", options, NULL, STRV_MAKE("string1", "string2", - "string3", + "--", "string4")); test_getopt_long_one(STRV_MAKE("arg0", diff --git a/src/test/test-options.c b/src/test/test-options.c new file mode 100644 index 0000000000000..56e8dd1d61390 --- /dev/null +++ b/src/test/test-options.c @@ -0,0 +1,375 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "options.h" +#include "strv.h" +#include "tests.h" + +typedef struct Entry { + const char *long_code; + const char *argument; +} Entry; + +static void test_option_parse_one( + char **argv, + const Option options[], + const Entry *entries, + char **remaining) { + + _cleanup_free_ char *joined = strv_join(argv, ", "); + log_debug("/* %s(%s) */", __func__, joined); + + _cleanup_free_ char *saved_argv0 = NULL; + ASSERT_NOT_NULL(saved_argv0 = strdup(argv[0])); + + int argc = strv_length(argv); + size_t i = 0, n_options = 0, n_entries = 0; + + for (const Option *o = options; o->short_code != 0 || o->long_code; o++) + n_options++; + + for (const Entry *e = entries; e && e->long_code; e++) + n_entries++; + + OptionParser state = {}; + const Option *opt; + const char *arg; + for (int c; (c = option_parse(options, options + n_options, &state, argc, argv, &opt, &arg)) != 0; ) { + ASSERT_OK(c); + ASSERT_NOT_NULL(opt); + + log_debug("%c %s: %s=%s", + opt->short_code != 0 ? opt->short_code : ' ', + opt->long_code ?: "", + strnull(opt->metavar), strnull(arg)); + + ASSERT_LT(i, n_entries); + ASSERT_TRUE(streq_ptr(opt->long_code, entries[i].long_code)); + ASSERT_TRUE(streq_ptr(arg, entries[i].argument)); + i++; + } + + ASSERT_EQ(i, n_entries); + + char **args = option_parser_get_args(&state, argc, argv); + ASSERT_TRUE(strv_equal(args, remaining)); + ASSERT_STREQ(argv[0], saved_argv0); +} + +TEST(option_parse) { + static const Option options[] = { + { 1, .short_code = 'h', .long_code = "help" }, + { 2, .long_code = "version" }, + { 3, .short_code = 'r', .long_code = "required1", .metavar = "ARG" }, + { 4, .long_code = "required2", .metavar = "ARG" }, + { 5, .short_code = 'o', .long_code = "optional1", .metavar = "ARG", .flags = OPTION_OPTIONAL_ARG }, + { 6, .long_code = "optional2", .metavar = "ARG", .flags = OPTION_OPTIONAL_ARG }, + {} + }; + + test_option_parse_one(STRV_MAKE("arg0"), + options, + NULL, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "string3", + "string4"), + options, + NULL, + STRV_MAKE("string1", + "string2", + "string3", + "string4")); + + test_option_parse_one(STRV_MAKE("arg0", + "--", + "string1", + "--help", + "-h", + "string4"), + options, + NULL, + STRV_MAKE("string1", + "--help", + "-h", + "string4")); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "--", + "--", + "string4"), + options, + NULL, + STRV_MAKE("string1", + "string2", + "--", + "string4")); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "string3", + "string4", + "--"), + options, + NULL, + STRV_MAKE("string1", + "string2", + "string3", + "string4")); + + test_option_parse_one(STRV_MAKE("arg0", + "--help"), + options, + (Entry[]) { + { "help" }, + {} + }, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "-h"), + options, + (Entry[]) { + { "help" }, + {} + }, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "--help", + "string1", + "string2", + "string3", + "string4"), + options, + (Entry[]) { + { "help" }, + {} + }, + STRV_MAKE("string1", + "string2", + "string3", + "string4")); + + test_option_parse_one(STRV_MAKE("arg0", + "-h", + "string1", + "string2", + "string3", + "string4"), + options, + (Entry[]) { + { "help" }, + {} + }, + STRV_MAKE("string1", + "string2", + "string3", + "string4")); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "--help", + "string3", + "string4"), + options, + (Entry[]) { + { "help" }, + {} + }, + STRV_MAKE("string1", + "string2", + "string3", + "string4")); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "-h", + "string3", + "string4"), + options, + (Entry[]) { + { "help" }, + {} + }, + STRV_MAKE("string1", + "string2", + "string3", + "string4")); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "string3", + "string4", + "--help"), + options, + (Entry[]) { + { "help" }, + {} + }, + STRV_MAKE("string1", + "string2", + "string3", + "string4")); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "string3", + "string4", + "-h"), + options, + (Entry[]) { + { "help" }, + {} + }, + STRV_MAKE("string1", + "string2", + "string3", + "string4")); + + test_option_parse_one(STRV_MAKE("arg0", + "--required1", "reqarg1"), + options, + (Entry[]) { + { "required1", "reqarg1" }, + {} + }, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "-r", "reqarg1"), + options, + (Entry[]) { + { "required1", "reqarg1" }, + {} + }, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "-r", "reqarg1"), + options, + (Entry[]) { + { "required1", "reqarg1" }, + {} + }, + STRV_MAKE("string1", + "string2")); + + test_option_parse_one(STRV_MAKE("arg0", + "--optional1=optarg1"), + options, + (Entry[]) { + { "optional1", "optarg1" }, + {} + }, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "--optional1", "string1"), + options, + (Entry[]) { + { "optional1", NULL }, + {} + }, + STRV_MAKE("string1")); + + test_option_parse_one(STRV_MAKE("arg0", + "-ooptarg1"), + options, + (Entry[]) { + { "optional1", "optarg1" }, + {} + }, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "-o", "string1"), + options, + (Entry[]) { + { "optional1", NULL }, + {} + }, + STRV_MAKE("string1")); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "--help", + "--version", + "string2", + "--required1", "reqarg1", + "--required2", "reqarg2", + "--required1=reqarg3", + "--required2=reqarg4", + "string3", + "--optional1", "string4", + "--optional2", "string5", + "--optional1=optarg1", + "--optional2=optarg2", + "-h", + "-r", "reqarg5", + "-rreqarg6", + "-ooptarg3", + "-o", + "string6", + "-o", + "-h", + "-o", + "--help", + "string7", + "-hooptarg4", + "-hrreqarg6", + "--", + "--help", + "--required1", + "--optional1"), + options, + (Entry[]) { + { "help" }, + { "version" }, + { "required1", "reqarg1" }, + { "required2", "reqarg2" }, + { "required1", "reqarg3" }, + { "required2", "reqarg4" }, + { "optional1", NULL }, + { "optional2", NULL, }, + { "optional1", "optarg1" }, + { "optional2", "optarg2" }, + { "help" }, + { "required1", "reqarg5" }, + { "required1", "reqarg6" }, + { "optional1", "optarg3" }, + { "optional1", NULL }, + { "optional1", NULL }, + { "help" }, + { "optional1", NULL }, + { "help" }, + { "help" }, + { "optional1", "optarg4" }, + { "help" }, + { "required1", "reqarg6" }, + {} + }, + STRV_MAKE("string1", + "string2", + "string3", + "string4", + "string5", + "string6", + "string7", + "--help", + "--required1", + "--optional1")); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); From d03089ff4f445437022cf312d4062e6a9a98dce2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 27 Feb 2026 11:38:29 +0100 Subject: [PATCH 0387/1296] verbs: add a section to list verbs similarly to options --- src/shared/verbs.c | 61 +++++++++++++++++++++++++++++----- src/shared/verbs.h | 50 ++++++++++++++++++++++++++-- src/systemctl/systemctl-main.c | 2 +- 3 files changed, 101 insertions(+), 12 deletions(-) diff --git a/src/shared/verbs.c b/src/shared/verbs.c index 8c174a6c88de4..bf2d440bb0ec8 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -3,6 +3,7 @@ #include #include "env-util.h" +#include "format-table.h" #include "log.h" #include "string-util.h" #include "strv.h" @@ -57,33 +58,34 @@ bool should_bypass(const char *env_prefix) { return true; } -const Verb* verbs_find_verb(const char *name, const Verb verbs[]) { +const Verb* verbs_find_verb(const char *name, const Verb verbs[], const Verb verbs_end[]) { assert(verbs); - for (size_t i = 0; verbs[i].dispatch; i++) - if (name ? streq(name, verbs[i].verb) : FLAGS_SET(verbs[i].flags, VERB_DEFAULT)) - return verbs + i; + for (const Verb *verb = verbs; verb < verbs_end; verb++) + if (name ? streq(name, verb->verb) : FLAGS_SET(verb->flags, VERB_DEFAULT)) + return verb; /* At the end of the list? */ return NULL; } -int dispatch_verb_with_args(char **args, const Verb verbs[], void *userdata) { +int _dispatch_verb_with_args(char **args, const Verb verbs[], const Verb verbs_end[], void *userdata) { int r; assert(verbs); + assert(verbs_end > verbs); assert(verbs[0].dispatch); assert(verbs[0].verb); const char *name = args ? args[0] : NULL; size_t left = strv_length(args); - const Verb *verb = verbs_find_verb(name, verbs); + const Verb *verb = verbs_find_verb(name, verbs, verbs_end); if (!verb) { _cleanup_strv_free_ char **verb_strv = NULL; - for (size_t i = 0; verbs[i].dispatch; i++) { - r = strv_extend(&verb_strv, verbs[i].verb); + for (verb = verbs; verb < verbs_end; verb++) { + r = strv_extend(&verb_strv, verb->verb); if (r < 0) return log_oom(); } @@ -139,5 +141,46 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { assert(argv); assert(argc >= optind); - return dispatch_verb_with_args(strv_skip(argv, optind), verbs, userdata); + size_t n = 0; + while (verbs[n].dispatch) + n++; + + return _dispatch_verb_with_args(strv_skip(argv, optind), verbs, verbs + n, userdata); +} + +int _verbs_get_help_table(const Verb verbs[], const Verb verbs_end[], Table **ret) { + int r; + + assert(ret); + + _cleanup_(table_unrefp) Table *table = table_new("verb", "help"); + if (!table) + return log_oom(); + + for (const Verb *verb = verbs; verb < verbs_end; verb++) { + assert(verb->dispatch); + + /* We indent the option string by two spaces. We could set the minimum cell width and + * right-align for a similar result, but that'd be more work. This is only used for + * display. */ + r = table_add_cell_stringf(table, NULL, " %s%s%s", + verb->verb, + verb->argspec ? " " : "", + strempty(verb->argspec)); + if (r < 0) + return table_log_add_error(r); + + const char *help = verb->help ?: "FIXME"; + _cleanup_strv_free_ char **s = strv_split(help, /* separators= */ NULL); + if (!s) + return log_oom(); + + r = table_add_many(table, TABLE_STRV_WRAPPED, s); + if (r < 0) + return table_log_add_error(r); + } + + table_set_header(table, false); + *ret = TAKE_PTR(table); + return 0; } diff --git a/src/shared/verbs.h b/src/shared/verbs.h index febb8ccfc24fd..062e49466f9ea 100644 --- a/src/shared/verbs.h +++ b/src/shared/verbs.h @@ -16,12 +16,58 @@ typedef struct { VerbFlags flags; int (* const dispatch)(int argc, char *argv[], uintptr_t data, void *userdata); uintptr_t data; + const char *argspec; + const char *help; } Verb; +#define VERB_FULL(d, v, a, amin, amax, f, dat, h) \ + static int d(int, char**, uintptr_t, void*); \ + _section_("SYSTEMD_VERBS") \ + _alignptr_ \ + _used_ \ + _retain_ \ + _variable_no_sanitize_address_ \ + static const Verb CONCATENATE(d, _data) = { \ + .verb = v, \ + .min_args = amin, \ + .max_args = amax, \ + .flags = f, \ + .dispatch = d, \ + .data = dat, \ + .argspec = a, \ + .help = h, \ + } + +/* The same as VERB_FULL, but without the data argument */ +#define VERB(d, v, a, amin, amax, f, h) \ + VERB_FULL(d, v, a, amin, amax, f, /* dat= */ 0, h) + +/* Simplified VERB for parameters that take no argument */ +#define VERB_NOARG(d, v, h) \ + VERB(d, v, /* a= */ NULL, /* amin= */ VERB_ANY, /* amax= */ 1, /* f= */ 0, h) + +/* This is magically mapped to the beginning and end of the section */ +extern const Verb __start_SYSTEMD_VERBS[]; +extern const Verb __stop_SYSTEMD_VERBS[]; + bool running_in_chroot_or_offline(void); bool should_bypass(const char *env_prefix); -const Verb* verbs_find_verb(const char *name, const Verb verbs[]); -int dispatch_verb_with_args(char **args, const Verb verbs[], void *userdata); +const Verb* verbs_find_verb(const char *name, const Verb verbs[], const Verb verbs_end[]); + +int _dispatch_verb_with_args(char **args, const Verb verbs[], const Verb verbs_end[], void *userdata); +#define dispatch_verb_with_args(args, userdata) \ + _dispatch_verb_with_args(args, ALIGN_PTR(__start_SYSTEMD_VERBS), __stop_SYSTEMD_VERBS, userdata) + int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata); + +int _verbs_get_help_table(const Verb verbs[], const Verb verbs_end[], Table **ret); +#define verbs_get_help_table(ret) \ + _verbs_get_help_table(ALIGN_PTR(__start_SYSTEMD_VERBS), __stop_SYSTEMD_VERBS, ret) + +#define VERB_COMMON_HELP(impl) \ + VERB(verb_help, "help", NULL, VERB_ANY, VERB_ANY, 0, "Show this help"); \ + static int verb_help(int argc, char **argv, uintptr_t data, void *userdata) { \ + return impl(); \ + } diff --git a/src/systemctl/systemctl-main.c b/src/systemctl/systemctl-main.c index 565b3a8878adb..4d1830071f47e 100644 --- a/src/systemctl/systemctl-main.c +++ b/src/systemctl/systemctl-main.c @@ -135,7 +135,7 @@ static int systemctl_main(int argc, char *argv[]) { {} }; - const Verb *verb = verbs_find_verb(argv[optind], verbs); + const Verb *verb = verbs_find_verb(argv[optind], verbs, verbs + ELEMENTSOF(verbs) - 1); if (verb && (verb->flags & VERB_ONLINE_ONLY) && arg_root) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Verb '%s' cannot be used with --root= or --image=.", From bb7486db618f4cf5109abdfef797ee70c47223c0 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 20 Mar 2026 14:14:28 +0100 Subject: [PATCH 0388/1296] mountfsd: Add CAP_SYS_PTRACE and CAP_SYS_CHROOT CAP_SYS_PTRACE for making sure we can open mount namespaces of peers via /proc//ns and CAP_SYS_CHROOT for making sure we can join those mount namespaces. --- units/systemd-mountfsd.service.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/units/systemd-mountfsd.service.in b/units/systemd-mountfsd.service.in index 73105007f925f..1e996a0def832 100644 --- a/units/systemd-mountfsd.service.in +++ b/units/systemd-mountfsd.service.in @@ -18,7 +18,7 @@ Before=sysinit.target shutdown.target DefaultDependencies=no [Service] -CapabilityBoundingSet=CAP_DAC_READ_SEARCH CAP_SYS_RESOURCE CAP_BPF CAP_PERFMON CAP_SETGID CAP_SETUID CAP_DAC_OVERRIDE CAP_CHOWN CAP_SYS_ADMIN +CapabilityBoundingSet=CAP_DAC_READ_SEARCH CAP_SYS_RESOURCE CAP_BPF CAP_PERFMON CAP_SETGID CAP_SETUID CAP_DAC_OVERRIDE CAP_CHOWN CAP_SYS_ADMIN CAP_SYS_PTRACE CAP_SYS_CHROOT ExecStart={{LIBEXECDIR}}/systemd-mountfsd IPAddressDeny=any LimitNOFILE={{HIGH_RLIMIT_NOFILE}} From 7c1075fb8ff2d3b87fa463d542e2e00ac086cbd3 Mon Sep 17 00:00:00 2001 From: vlefebvre Date: Fri, 20 Mar 2026 15:25:09 +0100 Subject: [PATCH 0389/1296] kmod-setup: load vsock_loopback alongside vsock Loading vmw_vsock_virtio_transport early at boot causes vsock to be resident before any application opens an AF_VSOCK socket. Because the kernel skips autoloading when the vsock module is already present, vsock_loopback never gets loaded automatically, and any subsequent bind() to VMADDR_CID_LOCAL fails with EADDRNOTAVAIL. Fix this by explicitly loading vsock_loopback on virtio or VMWare machines via the new may_have_vsock_looopback() helper, wich covers both vmw_vsock_virtio_transport and vmware_vsock_vmci_transport case. vsock_loopback is the only module that registers a transport for VMADDR_CID_LOCAL (CID 1) and has no hard dependency from any of the vsock transport modules. Fixes: #41100 Follow-up for 381c78db491a7c5fad8697543dd36ebe9b848718 --- src/core/kmod-setup.c | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/core/kmod-setup.c b/src/core/kmod-setup.c index 499e09443ff65..7d0d1d9b67373 100644 --- a/src/core/kmod-setup.c +++ b/src/core/kmod-setup.c @@ -94,6 +94,10 @@ static bool in_vmware(void) { static bool in_hyperv(void) { return detect_vm() == VIRTUALIZATION_MICROSOFT; } + +static bool may_have_vsock_loopback(void) { + return may_have_virtio() || in_vmware(); +} #endif int kmod_setup(void) { @@ -107,23 +111,25 @@ int kmod_setup(void) { } kmod_table[] = { /* This one we need to load explicitly, since auto-loading on use doesn't work * before udev created the ghost device nodes, and we need it earlier than that. */ - { "autofs4", "/sys/class/misc/autofs", true, false, NULL }, + { "autofs4", "/sys/class/misc/autofs", true, false, NULL }, /* This one we need to load explicitly, since auto-loading of IPv6 is not done when * we try to configure ::1 on the loopback device. */ - { "ipv6", "/sys/module/ipv6", false, true, NULL }, + { "ipv6", "/sys/module/ipv6", false, true, NULL }, /* virtio_rng would be loaded by udev later, but real entropy might be needed very early */ - { "virtio_rng", NULL, false, false, has_virtio_rng }, + { "virtio_rng", NULL, false, false, has_virtio_rng }, /* we want early logging to hvc consoles if possible, and make sure systemd-getty-generator * can rely on all consoles being probed already. */ - { "virtio_console", NULL, false, false, may_have_virtio }, + { "virtio_console", NULL, false, false, may_have_virtio }, /* Make sure we can send sd-notify messages over vsock as early as possible. */ - { "vmw_vsock_virtio_transport", NULL, false, false, may_have_virtio }, - { "vmw_vsock_vmci_transport", NULL, false, false, in_vmware }, - { "hv_sock", NULL, false, false, in_hyperv }, + { "vmw_vsock_virtio_transport", NULL, false, false, may_have_virtio }, + /* vsock_loopback provides VMADDR_CID_LOCAL and is not a hard dep of any transport module */ + { "vsock_loopback", "/sys/module/vsock_loopback", false, false, may_have_vsock_loopback }, + { "vmw_vsock_vmci_transport", NULL, false, false, in_vmware }, + { "hv_sock", NULL, false, false, in_hyperv }, /* We can't wait for specific virtiofs tags to show up as device nodes so we have to load the * virtiofs and virtio_pci modules early to make sure the virtiofs tags are found when @@ -131,18 +137,18 @@ int kmod_setup(void) { * * TODO: Remove these again once https://gitlab.com/virtio-fs/virtiofsd/-/issues/128 is * resolved and the kernel fix is widely available. */ - { "virtiofs", "/sys/module/virtiofs", false, false, may_have_virtio }, - { "virtio_pci", "/sys/module/virtio_pci", false, false, has_virtio_pci }, + { "virtiofs", "/sys/module/virtiofs", false, false, may_have_virtio }, + { "virtio_pci", "/sys/module/virtio_pci", false, false, has_virtio_pci }, /* qemu_fw_cfg would be loaded by udev later, but we want to import credentials from it super early */ - { "qemu_fw_cfg", "/sys/firmware/qemu_fw_cfg", false, false, in_qemu }, + { "qemu_fw_cfg", "/sys/firmware/qemu_fw_cfg", false, false, in_qemu }, /* dmi-sysfs is needed to import credentials from it super early */ - { "dmi-sysfs", "/sys/firmware/dmi/entries", false, false, NULL }, + { "dmi-sysfs", "/sys/firmware/dmi/entries", false, false, NULL }, #if HAVE_TPM2 /* Make sure the tpm subsystem is available which ConditionSecurity=tpm2 depends on. */ - { "tpm", "/sys/class/tpmrm", false, false, efi_has_tpm2 }, + { "tpm", "/sys/class/tpmrm", false, false, efi_has_tpm2 }, #endif }; From fce03832fa55083cb7b121a04e33f69258aefc82 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 21 Mar 2026 02:11:28 +0900 Subject: [PATCH 0390/1296] test-dhcp-client: fix packet length and checksum in IP header --- src/libsystemd-network/test-dhcp-client.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index 7a8b149e20363..95a7d06592289 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -540,8 +540,8 @@ static void test_addr_acq(sd_event *e) { } static uint8_t test_addr_bootp_reply[] = { - 0x45, 0x00, 0x01, 0x48, 0x00, 0x00, 0x40, 0x00, - 0xff, 0x11, 0x70, 0xa3, 0x0a, 0x00, 0x00, 0x02, + 0x45, 0x00, 0x01, 0x40, 0x00, 0x00, 0x40, 0x00, + 0xff, 0x11, 0x70, 0xab, 0x0a, 0x00, 0x00, 0x02, 0xff, 0xff, 0xff, 0xff, 0x00, 0x43, 0x00, 0x44, 0x01, 0x2c, 0x2b, 0x91, 0x02, 0x01, 0x06, 0x00, 0x69, 0xd3, 0x79, 0x11, 0x17, 0x00, 0x80, 0x00, From 0b2432438cbdfb884d94ab4da7eb9b9a44b4d483 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 20 Mar 2026 21:38:08 +0100 Subject: [PATCH 0391/1296] core: Only build selinux-setup if we have selinux --- src/core/meson.build | 5 ++++- src/core/selinux-setup.c | 6 ++---- src/core/selinux-setup.h | 6 ++++++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/core/meson.build b/src/core/meson.build index e703cc3728970..391dc45a6b294 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -170,11 +170,14 @@ systemd_sources = files( 'apparmor-setup.c', 'ima-setup.c', 'ipe-setup.c', - 'selinux-setup.c', 'smack-setup.c', 'efi-random.c', ) +if conf.get('HAVE_SELINUX') == 1 + systemd_sources += files('selinux-setup.c') +endif + systemd_executor_sources = files( 'executor.c', 'exec-invoke.c', diff --git a/src/core/selinux-setup.c b/src/core/selinux-setup.c index 6f78346036d7c..17905de2c7f6a 100644 --- a/src/core/selinux-setup.c +++ b/src/core/selinux-setup.c @@ -13,11 +13,10 @@ #include "time-util.h" int mac_selinux_setup(bool *loaded_policy) { - assert(loaded_policy); - -#if HAVE_SELINUX int r; + assert(loaded_policy); + r = dlopen_libselinux(); if (r < 0) { log_debug_errno(r, "No SELinux library available, skipping setup."); @@ -92,7 +91,6 @@ int mac_selinux_setup(bool *loaded_policy) { } else log_debug("Unable to load SELinux policy. Ignoring."); } -#endif return 0; } diff --git a/src/core/selinux-setup.h b/src/core/selinux-setup.h index 3dad97bbf6977..dd961b03709d2 100644 --- a/src/core/selinux-setup.h +++ b/src/core/selinux-setup.h @@ -3,4 +3,10 @@ #include "core-forward.h" +#if HAVE_SELINUX int mac_selinux_setup(bool *loaded_policy); +#else +static inline int mac_selinux_setup(bool *loaded_policy) { + return 0; +} +#endif From 5ae78f0ca92a0843bf67616c2e7d6c6c8f4818d7 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 20 Mar 2026 21:38:27 +0100 Subject: [PATCH 0392/1296] selinux-util: Make clang-tidy happy if selinux is not available Most of our libraries are available on all distributions so we don't bother with making clang-tidy happy if the library is not available. The one exception is selinux which isn't available on Arch. Let's conditionalize the includes in selinux-util.c so that clang-tidy is still happy on Arch where we can't install libselinux. --- src/shared/selinux-util.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/shared/selinux-util.c b/src/shared/selinux-util.c index f980ec83acb4f..1049044f87bbd 100644 --- a/src/shared/selinux-util.c +++ b/src/shared/selinux-util.c @@ -1,32 +1,34 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include #include #include -#include #include #if HAVE_SELINUX +#include +#include +#include + #include #include #include #include -#endif #include "sd-dlopen.h" #include "alloc-util.h" -#include "errno-util.h" #include "fd-util.h" #include "label.h" -#include "label-util.h" #include "log.h" #include "path-util.h" -#include "selinux-util.h" #include "string-util.h" #include "time-util.h" +#endif + +#include "errno-util.h" +#include "label-util.h" +#include "selinux-util.h" #if HAVE_SELINUX DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(context_t, sym_context_free, context_freep, NULL); From 52277670215227271297c0cbca5b847416cfb819 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 20 Mar 2026 21:50:10 +0100 Subject: [PATCH 0393/1296] core: Add two more IWYU pragmas If selinux isn't enabled, these are reported as unused, so let's add pragmas to tell clang-tidy to keep these. --- src/core/dbus.c | 2 +- src/core/load-fragment.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/dbus.c b/src/core/dbus.c index 30d265ec6cef1..f5a117d2bde96 100644 --- a/src/core/dbus.c +++ b/src/core/dbus.c @@ -39,7 +39,7 @@ #include "path-util.h" #include "pidref.h" #include "process-util.h" -#include "selinux-access.h" +#include "selinux-access.h" /* IWYU pragma: keep */ #include "serialize.h" #include "set.h" #include "special.h" diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index d1f74a8fd7e19..cef01ab776365 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -57,7 +57,7 @@ #include "reboot-util.h" #include "seccomp-util.h" #include "securebits-util.h" -#include "selinux-util.h" +#include "selinux-util.h" /* IWYU pragma: keep */ #include "set.h" #include "show-status.h" #include "signal-util.h" From 09bee40212006fab0b85a5ff2382194d3d802d3a Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 20 Mar 2026 21:52:00 +0100 Subject: [PATCH 0394/1296] reboot-util: Make clang-tidy happy if xenctrl is not installed xenctrl is another library that's not widely available across distributions. Let's make sure clang-tidy is happy with reboot-util.c if it is not available. --- src/shared/reboot-util.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/shared/reboot-util.c b/src/shared/reboot-util.c index 948e15631d8ab..55ec6c0f0aac6 100644 --- a/src/shared/reboot-util.c +++ b/src/shared/reboot-util.c @@ -1,21 +1,23 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include #include #include #if HAVE_XENCTRL +#include +#include + #define __XEN_INTERFACE_VERSION__ 0x00040900 #include #include #include -#endif -#include "alloc-util.h" #include "errno-util.h" #include "fd-util.h" +#endif + +#include "alloc-util.h" #include "fileio.h" #include "log.h" #include "proc-cmdline.h" From ea733800bd64266ae5e9b1f49cce529a239602ec Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 20 Mar 2026 22:05:08 +0100 Subject: [PATCH 0395/1296] test-resolved-stream: Use accept4() instead of accept() --- src/resolve/test-resolved-stream.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resolve/test-resolved-stream.c b/src/resolve/test-resolved-stream.c index 9e6e7bc05081f..75c2868285f37 100644 --- a/src/resolve/test-resolved-stream.c +++ b/src/resolve/test-resolved-stream.c @@ -111,7 +111,7 @@ static void *tcp_dns_server(void *p) { assert_se(setsockopt(bindfd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) >= 0); assert_se(bind(bindfd, &server_address.sa, sockaddr_len(&server_address)) >= 0); assert_se(listen(bindfd, 1) >= 0); - assert_se((acceptfd = accept(bindfd, NULL, NULL)) >= 0); + assert_se((acceptfd = accept4(bindfd, NULL, NULL, SOCK_CLOEXEC)) >= 0); server_handle(acceptfd); return NULL; } From 84d51e252db0390b448930cfab49d47ec989d8ef Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 20 Mar 2026 22:11:15 +0100 Subject: [PATCH 0396/1296] test-fd-util: Replace dup() with fcntl() Last remaining use of dup() in the codebase, let's get rid of it. --- src/test/test-fd-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/test-fd-util.c b/src/test/test-fd-util.c index e99d7d04726b4..338bbc29d07bc 100644 --- a/src/test/test-fd-util.c +++ b/src/test/test-fd-util.c @@ -151,7 +151,7 @@ TEST(fd_move_above_stdio) { new_fd = fd_move_above_stdio(new_fd); assert_se(new_fd >= 3); - assert_se(dup(original_stdin) == 0); + assert_se(fcntl(original_stdin, F_DUPFD, 0) == 0); assert_se(close_nointr(original_stdin) != EBADF); assert_se(close_nointr(new_fd) != EBADF); } From e6f1d9a5e5eb892aab8f40b1ed0c2825cff3e5d1 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 20 Mar 2026 22:14:42 +0100 Subject: [PATCH 0397/1296] ci: Replace codeql PotentiallyDangerousFunction query with clang-tidy The strerror() calls in test-errno-util.c are intentional so silence clang-tidy there. --- .clang-tidy | 18 +++++ .../PotentiallyDangerousFunction.ql | 68 ------------------- src/test/test-errno-util.c | 7 +- 3 files changed, 22 insertions(+), 71 deletions(-) delete mode 100644 .github/codeql-queries/PotentiallyDangerousFunction.ql diff --git a/.clang-tidy b/.clang-tidy index 56b7e40a28916..82681fda39923 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -15,6 +15,7 @@ Checks: ' bugprone-suspicious-string-compare, bugprone-swapped-arguments, bugprone-tautological-type-limits, + bugprone-unsafe-functions, bugprone-unused-return-value, misc-header-include-cycle, misc-include-cleaner, @@ -50,6 +51,23 @@ CheckOptions: varlink-io\.systemd\..*; varlink-idl-common\.h; unistd\.h +' + bugprone-unsafe-functions.ReportDefaultFunctions: false + bugprone-unsafe-functions.CustomFunctions: ' + ^fgets$,read_line(),is potentially dangerous; + ^strtok$,extract_first_word(),is potentially dangerous; + ^strsep$,extract_first_word(),is potentially dangerous; + ^dup$,fcntl() with F_DUPFD_CLOEXEC,is potentially dangerous; + ^htonl$,htobe32(),is confusing; + ^htons$,htobe16(),is confusing; + ^ntohl$,be32toh(),is confusing; + ^ntohs$,be16toh(),is confusing; + ^strerror$,STRERROR() or printf %m,is not thread-safe; + ^accept$,accept4(),is not O_CLOEXEC-safe; + ^dirname$,path_extract_directory(),is icky; + ^basename$,path_extract_filename(),is icky; + ^setmntent$,libmount_parse_fstab(),libmount parser should be used instead; + ^getmntent$,mnt_table_next_fs(),libmount parser should be used instead ' misc-header-include-cycle.IgnoredFilesList: 'glib-2.0' WarningsAsErrors: '*' diff --git a/.github/codeql-queries/PotentiallyDangerousFunction.ql b/.github/codeql-queries/PotentiallyDangerousFunction.ql deleted file mode 100644 index abd3f87a3425b..0000000000000 --- a/.github/codeql-queries/PotentiallyDangerousFunction.ql +++ /dev/null @@ -1,68 +0,0 @@ -/** - * vi: sw=2 ts=2 et syntax=ql: - * - * Borrowed from - * https://github.com/Semmle/ql/blob/master/cpp/ql/src/Security/CWE/CWE-676/PotentiallyDangerousFunction.ql - * - * @name Use of potentially dangerous function - * @description Certain standard library functions are dangerous to call. - * @id cpp/potentially-dangerous-function - * @kind problem - * @problem.severity error - * @precision high - * @tags reliability - * security - */ -import cpp - -predicate potentiallyDangerousFunction(Function f, string message) { - ( - f.getQualifiedName() = "fgets" and - message = "Call to fgets() is potentially dangerous. Use read_line() instead." - ) or ( - f.getQualifiedName() = "strtok" and - message = "Call to strtok() is potentially dangerous. Use extract_first_word() instead." - ) or ( - f.getQualifiedName() = "strsep" and - message = "Call to strsep() is potentially dangerous. Use extract_first_word() instead." - ) or ( - f.getQualifiedName() = "dup" and - message = "Call to dup() is potentially dangerous. Use fcntl(fd, FD_DUPFD_CLOEXEC, 3) instead." - ) or ( - f.getQualifiedName() = "htonl" and - message = "Call to htonl() is confusing. Use htobe32() instead." - ) or ( - f.getQualifiedName() = "htons" and - message = "Call to htons() is confusing. Use htobe16() instead." - ) or ( - f.getQualifiedName() = "ntohl" and - message = "Call to ntohl() is confusing. Use be32toh() instead." - ) or ( - f.getQualifiedName() = "ntohs" and - message = "Call to ntohs() is confusing. Use be16toh() instead." - ) or ( - f.getQualifiedName() = "strerror" and - message = "Call to strerror() is not thread-safe. Use printf()'s %m format string or STRERROR() instead." - ) or ( - f.getQualifiedName() = "accept" and - message = "Call to accept() is not O_CLOEXEC-safe. Use accept4() instead." - ) or ( - f.getQualifiedName() = "dirname" and - message = "Call dirname() is icky. Use path_extract_directory() instead." - ) or ( - f.getQualifiedName() = "basename" and - message = "Call basename() is icky. Use path_extract_filename() instead." - ) or ( - f.getQualifiedName() = "setmntent" and - message = "Libmount parser is used instead, specifically libmount_parse_fstab()." - ) or ( - f.getQualifiedName() = "getmntent" and - message = "Libmount parser is used instead, specifically mnt_table_next_fs()." - ) -} - -from FunctionCall call, Function target, string message -where - call.getTarget() = target and - potentiallyDangerousFunction(target, message) -select call, message diff --git a/src/test/test-errno-util.c b/src/test/test-errno-util.c index 9eb729c2e4ba3..30a47cbcdeef4 100644 --- a/src/test/test-errno-util.c +++ b/src/test/test-errno-util.c @@ -6,10 +6,11 @@ TEST(strerror_not_threadsafe) { /* Just check that strerror really is not thread-safe. */ - log_info("strerror(%d) → %s", 200, strerror(200)); - log_info("strerror(%d) → %s", 201, strerror(201)); - log_info("strerror(%d) → %s", INT_MAX, strerror(INT_MAX)); + log_info("strerror(%d) → %s", 200, strerror(200)); /* NOLINT(bugprone-unsafe-functions) */ + log_info("strerror(%d) → %s", 201, strerror(201)); /* NOLINT(bugprone-unsafe-functions) */ + log_info("strerror(%d) → %s", INT_MAX, strerror(INT_MAX)); /* NOLINT(bugprone-unsafe-functions) */ + /* NOLINTNEXTLINE(bugprone-unsafe-functions) */ log_info("strerror(%d), strerror(%d) → %p, %p", 200, 201, strerror(200), strerror(201)); /* This call is not allowed, because the first returned string becomes invalid when From 795948170b17fa2ba5c13b53398714d48acbfa5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Santamar=C3=ADa=20Rogado?= Date: Sat, 21 Mar 2026 02:32:15 +0100 Subject: [PATCH 0398/1296] sensor: gpd remove pocket 3 and 4 Both devices have -90 degrees mounted panels but they don't have the quirk in kernel. The Pocket 4 has been researched and it has an acpi accel matrix that works when setting panel orientation at boot parameter. The Pocket 3 hasn't been tested, but given it didn't had panel orientation quirk is for sure that matrix is wrong for it. Actually is pending the quirks for both devices in kernel but eventually they will get merged. Till that happens is encourage that owners of these devices set panel orientation boot parameter to right-up. Fixes: #41036. --- hwdb.d/60-sensor.hwdb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/hwdb.d/60-sensor.hwdb b/hwdb.d/60-sensor.hwdb index 480dab4cd2b2c..e744bd8713d01 100644 --- a/hwdb.d/60-sensor.hwdb +++ b/hwdb.d/60-sensor.hwdb @@ -515,10 +515,6 @@ sensor:modalias:acpi:KIOX000A:*:dmi:bvnAmericanMegatrendsInc.:bvr5.11:bd03/20/20 sensor:modalias:acpi:KIOX000A:*:dmi:bvnAmericanMegatrendsInc.:bvr5.11:bd05/25/2017:*:svnDefaultstring:pnDefaultstring:pvrDefaultstring:rvnAMICorporation:rnDefaultstring:rvrDefaultstring:cvnDefaultstring:ct3:cvrDefaultstring:* ACCEL_LOCATION=base -sensor:modalias:acpi:MXC6655:*:dmi:*:svnGPD:pnG1621-02:* # Pocket 3 -sensor:modalias:acpi:MXC6655:*:dmi:*:svnGPD:pnG1628-04:* # Pocket 4 - ACCEL_MOUNT_MATRIX=-1, 0, 0; 0, 1, 0; 0, 0, 1 - sensor:modalias:acpi:BMI0160:*:dmi:*:svnGPD:pnG1619-04:* # Win Max 2 ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, 1 From f0387aa666559827fa10959b832d217d78e0a533 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 13 Mar 2026 07:11:06 +0900 Subject: [PATCH 0399/1296] test-dhcp-client: modernize test code --- src/libsystemd-network/test-dhcp-client.c | 482 ++++++++-------------- 1 file changed, 175 insertions(+), 307 deletions(-) diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index 95a7d06592289..97802e2c164e4 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -8,9 +8,6 @@ #include #include #include -#if HAVE_VALGRIND_VALGRIND_H -# include -#endif #include "sd-dhcp-client.h" #include "sd-dhcp-lease.h" @@ -33,7 +30,7 @@ static struct hw_addr_data hw_addr = { .length = ETH_ALEN, .ether = {{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }}, }; -typedef int (*test_callback_recv_t)(size_t size, DHCPMessage *dhcp); +typedef void (*test_callback_recv_t)(size_t size, DHCPMessage *dhcp); struct bootp_addr_data { uint8_t *offer_buf; @@ -43,129 +40,97 @@ struct bootp_addr_data { }; static struct bootp_addr_data *bootp_test_context; -static bool verbose = true; static int test_fd[2]; static test_callback_recv_t callback_recv; static be32_t xid; -static void test_request_basic(sd_event *e) { - int r; - - sd_dhcp_client *client; - - if (verbose) - log_info("* %s", __func__); - +TEST(dhcp_client_setters) { /* Initialize client without Anonymize settings. */ - r = sd_dhcp_client_new(&client, false); - - assert_se(r >= 0); - assert_se(client); - - r = sd_dhcp_client_attach_event(client, e, 0); - assert_se(r >= 0); + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + ASSERT_OK(sd_dhcp_client_new(&client, /* anonymize= */ false)); + ASSERT_NOT_NULL(client); ASSERT_RETURN_EXPECTED_SE(sd_dhcp_client_set_request_option(NULL, 0) == -EINVAL); ASSERT_RETURN_EXPECTED_SE(sd_dhcp_client_set_request_address(NULL, NULL) == -EINVAL); ASSERT_RETURN_EXPECTED_SE(sd_dhcp_client_set_ifindex(NULL, 0) == -EINVAL); - assert_se(sd_dhcp_client_set_ifindex(client, 15) == 0); + ASSERT_OK(sd_dhcp_client_set_ifindex(client, 15)); ASSERT_RETURN_EXPECTED_SE(sd_dhcp_client_set_ifindex(client, -42) == -EINVAL); ASSERT_RETURN_EXPECTED_SE(sd_dhcp_client_set_ifindex(client, -1) == -EINVAL); ASSERT_RETURN_EXPECTED_SE(sd_dhcp_client_set_ifindex(client, 0) == -EINVAL); - assert_se(sd_dhcp_client_set_ifindex(client, 1) == 0); - - assert_se(sd_dhcp_client_set_hostname(client, "host") == 1); - assert_se(sd_dhcp_client_set_hostname(client, "host.domain") == 1); - assert_se(sd_dhcp_client_set_hostname(client, NULL) == 1); - assert_se(sd_dhcp_client_set_hostname(client, "~host") == -EINVAL); - assert_se(sd_dhcp_client_set_hostname(client, "~host.domain") == -EINVAL); - - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_SUBNET_MASK) == 0); - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_ROUTER) == 0); - /* This PRL option is not set when using Anonymize, but in this test - * Anonymize settings are not being used. */ - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_HOST_NAME) == 0); - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_DOMAIN_NAME) == 0); - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_DOMAIN_NAME_SERVER) == 0); - - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_PAD) == -EINVAL); - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_END) == -EINVAL); - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_MESSAGE_TYPE) == -EINVAL); - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_OVERLOAD) == -EINVAL); - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_PARAMETER_REQUEST_LIST) == -EINVAL); + ASSERT_OK(sd_dhcp_client_set_ifindex(client, 1)); + + ASSERT_OK_POSITIVE(sd_dhcp_client_set_hostname(client, "host")); + ASSERT_OK_ZERO(sd_dhcp_client_set_hostname(client, "host")); + ASSERT_OK_POSITIVE(sd_dhcp_client_set_hostname(client, "host.domain")); + ASSERT_OK_POSITIVE(sd_dhcp_client_set_hostname(client, NULL)); + ASSERT_ERROR(sd_dhcp_client_set_hostname(client, "~host"), EINVAL); + ASSERT_ERROR(sd_dhcp_client_set_hostname(client, "~host.domain"), EINVAL); + + ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_SUBNET_MASK)); + ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_ROUTER)); + ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_HOST_NAME)); + ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_DOMAIN_NAME)); + ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_DOMAIN_NAME_SERVER)); + + ASSERT_ERROR(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_PAD), EINVAL); + ASSERT_ERROR(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_END), EINVAL); + ASSERT_ERROR(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_MESSAGE_TYPE), EINVAL); + ASSERT_ERROR(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_OVERLOAD), EINVAL); + ASSERT_ERROR(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_PARAMETER_REQUEST_LIST), EINVAL); /* RFC7844: option 33 (SD_DHCP_OPTION_STATIC_ROUTE) is set in the * default PRL when using Anonymize, so it is changed to other option * that is not set by default, to check that it was set successfully. * Options not set by default (using or not anonymize) are option 17 * (SD_DHCP_OPTION_ROOT_PATH) and 42 (SD_DHCP_OPTION_NTP_SERVER) */ - assert_se(sd_dhcp_client_set_request_option(client, 17) == 1); - assert_se(sd_dhcp_client_set_request_option(client, 17) == 0); - assert_se(sd_dhcp_client_set_request_option(client, 42) == 1); - assert_se(sd_dhcp_client_set_request_option(client, 17) == 0); - - sd_dhcp_client_unref(client); + ASSERT_OK_POSITIVE(sd_dhcp_client_set_request_option(client, 17)); + ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, 17)); + ASSERT_OK_POSITIVE(sd_dhcp_client_set_request_option(client, 42)); + ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, 17)); } -static void test_request_anonymize(sd_event *e) { - int r; - - sd_dhcp_client *client; - - if (verbose) - log_info("* %s", __func__); - +TEST(dhcp_client_anonymize) { /* Initialize client with Anonymize settings. */ - r = sd_dhcp_client_new(&client, true); - - assert_se(r >= 0); - assert_se(client); - - r = sd_dhcp_client_attach_event(client, e, 0); - assert_se(r >= 0); + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + ASSERT_OK(sd_dhcp_client_new(&client, /* anonymize= */ true)); + ASSERT_NOT_NULL(client); - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_NETBIOS_NAME_SERVER) == 0); + ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_NETBIOS_NAME_SERVER)); /* This PRL option is not set when using Anonymize */ - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_HOST_NAME) == 1); - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_PARAMETER_REQUEST_LIST) == -EINVAL); + ASSERT_OK_POSITIVE(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_HOST_NAME)); + ASSERT_ERROR(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_PARAMETER_REQUEST_LIST), EINVAL); /* RFC7844: option 101 (SD_DHCP_OPTION_NEW_TZDB_TIMEZONE) is not set in the * default PRL when using Anonymize, */ - assert_se(sd_dhcp_client_set_request_option(client, 101) == 1); - assert_se(sd_dhcp_client_set_request_option(client, 101) == 0); - - sd_dhcp_client_unref(client); + ASSERT_OK_POSITIVE(sd_dhcp_client_set_request_option(client, 101)); + ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, 101)); } -static void test_checksum(void) { +TEST(dhcp_packet_checksum) { uint8_t buf[20] = { 0x45, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x40, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff }; - if (verbose) - log_info("* %s", __func__); - - assert_se(dhcp_packet_checksum((uint8_t*)&buf, 20) == be16toh(0x78ae)); + ASSERT_EQ(dhcp_packet_checksum(buf, 20), be16toh(0x78ae)); } -static void test_dhcp_identifier_set_iaid(void) { +TEST(dhcp_identifier_set_iaid) { uint32_t iaid_legacy; be32_t iaid; - assert_se(dhcp_identifier_set_iaid(NULL, &hw_addr, /* legacy_unstable_byteorder= */ true, &iaid_legacy) >= 0); - assert_se(dhcp_identifier_set_iaid(NULL, &hw_addr, /* legacy_unstable_byteorder= */ false, &iaid) >= 0); + ASSERT_OK(dhcp_identifier_set_iaid(NULL, &hw_addr, /* legacy_unstable_byteorder= */ true, &iaid_legacy)); + ASSERT_OK(dhcp_identifier_set_iaid(NULL, &hw_addr, /* legacy_unstable_byteorder= */ false, &iaid)); - /* we expect, that the MAC address was hashed. The legacy value is in native - * endianness. */ - assert_se(iaid_legacy == 0x8dde4ba8u); - assert_se(iaid == htole32(0x8dde4ba8u)); + /* we expect, that the MAC address was hashed. The legacy value is in native endianness. */ + ASSERT_EQ(iaid_legacy, 0x8dde4ba8u); + ASSERT_EQ(iaid, htole32(0x8dde4ba8u)); #if __BYTE_ORDER == __LITTLE_ENDIAN - assert_se(iaid == iaid_legacy); + ASSERT_EQ(iaid, iaid_legacy); #else - assert_se(iaid == bswap_32(iaid_legacy)); + ASSERT_EQ(iaid, bswap_32(iaid_legacy)); #endif } @@ -175,15 +140,15 @@ static int check_options(uint8_t code, uint8_t len, const void *option, void *us sd_dhcp_duid duid; uint32_t iaid; - assert_se(sd_dhcp_duid_set_en(&duid) >= 0); - assert_se(dhcp_identifier_set_iaid(NULL, &hw_addr, /* legacy_unstable_byteorder= */ true, &iaid) >= 0); + ASSERT_OK(sd_dhcp_duid_set_en(&duid)); + ASSERT_OK(dhcp_identifier_set_iaid(NULL, &hw_addr, /* legacy_unstable_byteorder= */ true, &iaid)); - assert_se(len == sizeof(uint8_t) + sizeof(uint32_t) + duid.size); - assert_se(len == 19); - assert_se(((uint8_t*) option)[0] == 0xff); + ASSERT_EQ(len, 19u); + ASSERT_EQ(len, sizeof(uint8_t) + sizeof(uint32_t) + duid.size); + ASSERT_EQ(((uint8_t*) option)[0], 0xff); - assert_se(memcmp((uint8_t*) option + 1, &iaid, sizeof(iaid)) == 0); - assert_se(memcmp((uint8_t*) option + 5, &duid.duid, duid.size) == 0); + ASSERT_EQ(memcmp((uint8_t*) option + 1, &iaid, sizeof(iaid)), 0); + ASSERT_EQ(memcmp((uint8_t*) option + 5, &duid.duid, duid.size), 0); break; } @@ -195,24 +160,21 @@ static int check_options(uint8_t code, uint8_t len, const void *option, void *us } int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, const void *packet, size_t len) { - size_t size; - _cleanup_free_ DHCPPacket *discover = NULL; uint16_t ip_check, udp_check; - assert_se(s >= 0); - assert_se(packet); + ASSERT_OK(s); + ASSERT_NOT_NULL(packet); - size = sizeof(DHCPPacket); - assert_se(len > size); + ASSERT_GT(len, sizeof(DHCPPacket)); - discover = memdup(packet, len); + _cleanup_free_ DHCPPacket *discover = ASSERT_NOT_NULL(memdup(packet, len)); - assert_se(discover->ip.ttl == IPDEFTTL); - assert_se(discover->ip.protocol == IPPROTO_UDP); - assert_se(discover->ip.saddr == INADDR_ANY); - assert_se(discover->ip.daddr == INADDR_BROADCAST); - assert_se(discover->udp.source == be16toh(DHCP_PORT_CLIENT)); - assert_se(discover->udp.dest == be16toh(DHCP_PORT_SERVER)); + ASSERT_EQ(discover->ip.ttl, IPDEFTTL); + ASSERT_EQ(discover->ip.protocol, IPPROTO_UDP); + ASSERT_EQ(discover->ip.saddr, INADDR_ANY); + ASSERT_EQ(discover->ip.daddr, INADDR_BROADCAST); + ASSERT_EQ(discover->udp.source, be16toh(DHCP_PORT_CLIENT)); + ASSERT_EQ(discover->udp.dest, be16toh(DHCP_PORT_SERVER)); ip_check = discover->ip.check; @@ -220,23 +182,21 @@ int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, const discover->ip.check = discover->udp.len; udp_check = ~dhcp_packet_checksum(&discover->ip.ttl, len - 8); - assert_se(udp_check == 0xffff); + ASSERT_EQ(udp_check, 0xffff); discover->ip.ttl = IPDEFTTL; discover->ip.check = ip_check; - ip_check = ~dhcp_packet_checksum((uint8_t*)&discover->ip, sizeof(discover->ip)); - assert_se(ip_check == 0xffff); - - assert_se(discover->dhcp.xid); - assert_se(memcmp(discover->dhcp.chaddr, hw_addr.bytes, hw_addr.length) == 0); + ip_check = ~dhcp_packet_checksum((uint8_t*) &discover->ip, sizeof(discover->ip)); + ASSERT_EQ(ip_check, 0xffff); - size = len - sizeof(struct iphdr) - sizeof(struct udphdr); + ASSERT_NE(discover->dhcp.xid, 0u); + ASSERT_EQ(memcmp(discover->dhcp.chaddr, hw_addr.bytes, hw_addr.length), 0); - assert_se(callback_recv); - callback_recv(size, &discover->dhcp); + ASSERT_NOT_NULL(callback_recv); + callback_recv(len - sizeof(struct iphdr) - sizeof(struct udphdr), &discover->dhcp); - return 575; + return 0; } int dhcp_network_bind_raw_socket( @@ -250,70 +210,47 @@ int dhcp_network_bind_raw_socket( bool so_priority_set, int so_priority) { - if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) < 0) - return -errno; - + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd)); return test_fd[0]; } int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type) { - int fd; - - fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); - if (fd < 0) - return -errno; - - return fd; + return ASSERT_OK_ERRNO(socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); } int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port, const void *packet, size_t len) { return 0; } -static int test_discover_message_verify(size_t size, struct DHCPMessage *dhcp) { - int res; - - res = dhcp_option_parse(dhcp, size, check_options, NULL, NULL); - assert_se(res == DHCP_DISCOVER); - - if (verbose) - log_info(" recv DHCP Discover 0x%08x", be32toh(dhcp->xid)); - - return 0; +static void test_discover_message_verify(size_t size, struct DHCPMessage *dhcp) { + ASSERT_OK_EQ(dhcp_option_parse(dhcp, size, check_options, NULL, NULL), DHCP_DISCOVER); + log_debug(" recv DHCP Discover 0x%08x", be32toh(dhcp->xid)); } -static void test_discover_message(sd_event *e) { - sd_dhcp_client *client; - int res, r; +TEST(discover_message) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_NOT_NULL(e); - if (verbose) - log_info("* %s", __func__); - - r = sd_dhcp_client_new(&client, false); - assert_se(r >= 0); - assert_se(client); + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + ASSERT_OK(sd_dhcp_client_new(&client, /* anonymize= */ false)); + ASSERT_NOT_NULL(client); - r = sd_dhcp_client_attach_event(client, e, 0); - assert_se(r >= 0); + ASSERT_OK(sd_dhcp_client_attach_event(client, e, /* priority= */ 0)); - assert_se(sd_dhcp_client_set_ifindex(client, 42) >= 0); - assert_se(sd_dhcp_client_set_mac(client, hw_addr.bytes, bcast_addr.bytes, hw_addr.length, ARPHRD_ETHER) >= 0); + ASSERT_OK(sd_dhcp_client_set_ifindex(client, 42)); + ASSERT_OK(sd_dhcp_client_set_mac(client, hw_addr.bytes, bcast_addr.bytes, hw_addr.length, ARPHRD_ETHER)); - assert_se(sd_dhcp_client_set_request_option(client, 248) >= 0); + ASSERT_OK(sd_dhcp_client_set_request_option(client, 248)); callback_recv = test_discover_message_verify; - res = sd_dhcp_client_start(client); - - assert_se(IN_SET(res, 0, -EINPROGRESS)); - - sd_event_run(e, UINT64_MAX); - - sd_dhcp_client_stop(client); - sd_dhcp_client_unref(client); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_run(e, /* timeout= */ UINT64_MAX)); + ASSERT_OK(sd_dhcp_client_stop(client)); + ASSERT_NULL(client = sd_dhcp_client_unref(client)); test_fd[1] = safe_close(test_fd[1]); - callback_recv = NULL; } @@ -405,52 +342,41 @@ static uint8_t test_addr_acq_ack[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; -static int test_addr_acq_acquired(sd_dhcp_client *client, int event, - void *userdata) { - sd_event *e = userdata; - sd_dhcp_lease *lease; - struct in_addr addr; - const struct in_addr *addrs; - - assert_se(client); - assert_se(IN_SET(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE, SD_DHCP_CLIENT_EVENT_SELECTING)); - - assert_se(sd_dhcp_client_get_lease(client, &lease) >= 0); - assert_se(lease); +static int test_addr_acq_acquired(sd_dhcp_client *client, int event, void *userdata) { + ASSERT_NOT_NULL(client); + ASSERT_TRUE(IN_SET(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE, SD_DHCP_CLIENT_EVENT_SELECTING)); - assert_se(sd_dhcp_lease_get_address(lease, &addr) >= 0); - assert_se(memcmp(&addr.s_addr, &test_addr_acq_ack[44], - sizeof(addr.s_addr)) == 0); + sd_dhcp_lease *lease; + ASSERT_OK(sd_dhcp_client_get_lease(client, &lease)); + ASSERT_NOT_NULL(lease); - assert_se(sd_dhcp_lease_get_netmask(lease, &addr) >= 0); - assert_se(memcmp(&addr.s_addr, &test_addr_acq_ack[285], - sizeof(addr.s_addr)) == 0); + struct in_addr addr; + ASSERT_OK(sd_dhcp_lease_get_address(lease, &addr)); + ASSERT_EQ(memcmp(&addr.s_addr, &test_addr_acq_ack[44], sizeof(addr.s_addr)), 0); - assert_se(sd_dhcp_lease_get_router(lease, &addrs) == 1); - assert_se(memcmp(&addrs[0].s_addr, &test_addr_acq_ack[308], - sizeof(addrs[0].s_addr)) == 0); + ASSERT_OK(sd_dhcp_lease_get_netmask(lease, &addr)); + ASSERT_EQ(memcmp(&addr.s_addr, &test_addr_acq_ack[285], sizeof(addr.s_addr)), 0); - if (verbose) - log_info(" DHCP address acquired"); + const struct in_addr *addrs; + ASSERT_OK_EQ(sd_dhcp_lease_get_router(lease, &addrs), 1); + ASSERT_EQ(memcmp(&addrs[0].s_addr, &test_addr_acq_ack[308], sizeof(addrs[0].s_addr)), 0); - sd_event_exit(e, 0); + log_info(" DHCP address acquired"); - return 0; + sd_event *e = ASSERT_NOT_NULL(sd_dhcp_client_get_event(client)); + return ASSERT_OK(sd_event_exit(e, 0)); } -static int test_addr_acq_recv_request(size_t size, DHCPMessage *request) { +static void test_addr_acq_recv_request(size_t size, DHCPMessage *request) { uint16_t udp_check = 0; uint8_t *msg_bytes = (uint8_t *)request; - int res; - res = dhcp_option_parse(request, size, check_options, NULL, NULL); - assert_se(res == DHCP_REQUEST); - assert_se(xid == request->xid); + ASSERT_OK_EQ(dhcp_option_parse(request, size, check_options, NULL, NULL), DHCP_REQUEST); + ASSERT_EQ(request->xid, xid); - assert_se(msg_bytes[size - 1] == SD_DHCP_OPTION_END); + ASSERT_EQ(msg_bytes[size - 1], SD_DHCP_OPTION_END); - if (verbose) - log_info(" recv DHCP Request 0x%08x", be32toh(xid)); + log_info(" recv DHCP Request 0x%08x", be32toh(xid)); memcpy(&test_addr_acq_ack[26], &udp_check, sizeof(udp_check)); memcpy(&test_addr_acq_ack[32], &xid, sizeof(xid)); @@ -458,30 +384,23 @@ static int test_addr_acq_recv_request(size_t size, DHCPMessage *request) { callback_recv = NULL; - res = write(test_fd[1], test_addr_acq_ack, - sizeof(test_addr_acq_ack)); - assert_se(res == sizeof(test_addr_acq_ack)); - - if (verbose) - log_info(" send DHCP Ack"); + ASSERT_OK_EQ_ERRNO(write(test_fd[1], test_addr_acq_ack, sizeof(test_addr_acq_ack)), + (ssize_t) sizeof(test_addr_acq_ack)); - return 0; + log_info(" send DHCP Ack"); }; -static int test_addr_acq_recv_discover(size_t size, DHCPMessage *discover) { +static void test_addr_acq_recv_discover(size_t size, DHCPMessage *discover) { uint16_t udp_check = 0; uint8_t *msg_bytes = (uint8_t *)discover; - int res; - res = dhcp_option_parse(discover, size, check_options, NULL, NULL); - assert_se(res == DHCP_DISCOVER); + ASSERT_OK_EQ(dhcp_option_parse(discover, size, check_options, NULL, NULL), DHCP_DISCOVER); - assert_se(msg_bytes[size - 1] == SD_DHCP_OPTION_END); + ASSERT_EQ(msg_bytes[size - 1], SD_DHCP_OPTION_END); xid = discover->xid; - if (verbose) - log_info(" recv DHCP Discover 0x%08x", be32toh(xid)); + log_info(" recv DHCP Discover 0x%08x", be32toh(xid)); memcpy(&test_addr_acq_offer[26], &udp_check, sizeof(udp_check)); memcpy(&test_addr_acq_offer[32], &xid, sizeof(xid)); @@ -489,52 +408,41 @@ static int test_addr_acq_recv_discover(size_t size, DHCPMessage *discover) { callback_recv = test_addr_acq_recv_request; - res = write(test_fd[1], test_addr_acq_offer, - sizeof(test_addr_acq_offer)); - assert_se(res == sizeof(test_addr_acq_offer)); - - if (verbose) - log_info(" sent DHCP Offer"); + ASSERT_OK_EQ_ERRNO(write(test_fd[1], test_addr_acq_offer, sizeof(test_addr_acq_offer)), + (ssize_t) sizeof(test_addr_acq_offer)); - return 0; + log_info(" sent DHCP Offer"); } -static void test_addr_acq(sd_event *e) { - sd_dhcp_client *client; - int res, r; +TEST(addr_acq) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_NOT_NULL(e); - if (verbose) - log_info("* %s", __func__); - - r = sd_dhcp_client_new(&client, false); - assert_se(r >= 0); - assert_se(client); + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + ASSERT_OK(sd_dhcp_client_new(&client, /* anonymize= */ false)); + ASSERT_NOT_NULL(client); - r = sd_dhcp_client_attach_event(client, e, 0); - assert_se(r >= 0); + ASSERT_OK(sd_dhcp_client_attach_event(client, e, /* priority= */ 0)); - assert_se(sd_dhcp_client_set_ifindex(client, 42) >= 0); - assert_se(sd_dhcp_client_set_mac(client, hw_addr.bytes, bcast_addr.bytes, hw_addr.length, ARPHRD_ETHER) >= 0); + ASSERT_OK(sd_dhcp_client_set_ifindex(client, 42)); + ASSERT_OK(sd_dhcp_client_set_mac(client, hw_addr.bytes, bcast_addr.bytes, hw_addr.length, ARPHRD_ETHER)); - assert_se(sd_dhcp_client_set_callback(client, test_addr_acq_acquired, e) >= 0); + ASSERT_OK(sd_dhcp_client_set_callback(client, test_addr_acq_acquired, NULL)); callback_recv = test_addr_acq_recv_discover; - assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME, + ASSERT_OK(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME, 30 * USEC_PER_SEC, 0, - NULL, INT_TO_PTR(-ETIMEDOUT)) >= 0); - - res = sd_dhcp_client_start(client); - assert_se(IN_SET(res, 0, -EINPROGRESS)); - - assert_se(sd_event_loop(e) >= 0); + NULL, INT_TO_PTR(-ETIMEDOUT))); - assert_se(sd_dhcp_client_set_callback(client, NULL, NULL) >= 0); - assert_se(sd_dhcp_client_stop(client) >= 0); - sd_dhcp_client_unref(client); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_dhcp_client_set_callback(client, NULL, NULL)); + ASSERT_OK(sd_dhcp_client_stop(client)); + ASSERT_NULL(client = sd_dhcp_client_unref(client)); test_fd[1] = safe_close(test_fd[1]); - callback_recv = NULL; xid = 0; } @@ -641,42 +549,33 @@ static struct bootp_addr_data bootp_addr_data[] = { }, }; -static int test_bootp_acquired(sd_dhcp_client *client, int event, - void *userdata) { - sd_dhcp_lease *lease = NULL; - sd_event *e = userdata; - struct in_addr addr; - +static int test_bootp_acquired(sd_dhcp_client *client, int event, void *userdata) { ASSERT_NOT_NULL(client); - assert_se(IN_SET(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE, SD_DHCP_CLIENT_EVENT_SELECTING)); + ASSERT_TRUE(IN_SET(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE, SD_DHCP_CLIENT_EVENT_SELECTING)); + sd_dhcp_lease *lease; ASSERT_OK(sd_dhcp_client_get_lease(client, &lease)); ASSERT_NOT_NULL(lease); + struct in_addr addr; ASSERT_OK(sd_dhcp_lease_get_address(lease, &addr)); - ASSERT_EQ(memcmp(&addr.s_addr, &bootp_test_context->offer_buf[bootp_test_context->ip_offset], - sizeof(addr.s_addr)), 0); + ASSERT_EQ(memcmp(&addr.s_addr, &bootp_test_context->offer_buf[bootp_test_context->ip_offset], sizeof(addr.s_addr)), 0); ASSERT_OK(sd_dhcp_lease_get_netmask(lease, &addr)); - ASSERT_EQ(memcmp(&addr.s_addr, &bootp_test_context->offer_buf[bootp_test_context->netmask_offset], - sizeof(addr.s_addr)), 0); + ASSERT_EQ(memcmp(&addr.s_addr, &bootp_test_context->offer_buf[bootp_test_context->netmask_offset], sizeof(addr.s_addr)), 0); - if (verbose) - log_info(" BOOTP address acquired"); + log_info(" BOOTP address acquired"); - sd_event_exit(e, 0); - - return 0; + sd_event *e = ASSERT_NOT_NULL(sd_dhcp_client_get_event(client)); + return ASSERT_OK(sd_event_exit(e, 0)); } -static int test_bootp_recv_request(size_t size, DHCPMessage *request) { +static void test_bootp_recv_request(size_t size, DHCPMessage *request) { uint16_t udp_check = 0; - size_t res; xid = request->xid; - if (verbose) - log_info(" recv BOOTP Request 0x%08x", be32toh(xid)); + log_info(" recv BOOTP Request 0x%08x", be32toh(xid)); callback_recv = NULL; @@ -684,34 +583,29 @@ static int test_bootp_recv_request(size_t size, DHCPMessage *request) { memcpy(&bootp_test_context->offer_buf[32], &xid, sizeof(xid)); memcpy(&bootp_test_context->offer_buf[56], hw_addr.bytes, hw_addr.length); - res = write(test_fd[1], bootp_test_context->offer_buf, - bootp_test_context->offer_len); - ASSERT_EQ(res, bootp_test_context->offer_len); - - if (verbose) - log_info(" sent BOOTP Reply"); + ASSERT_OK_EQ_ERRNO(write(test_fd[1], bootp_test_context->offer_buf, bootp_test_context->offer_len), + (ssize_t) bootp_test_context->offer_len); - return 0; + log_info(" sent BOOTP Reply"); }; -static void test_acquire_bootp(sd_event *e) { - sd_dhcp_client *client = NULL; - int res; - - if (verbose) - log_info("* %s", __func__); +static void test_bootp_one(void) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_NOT_NULL(e); - ASSERT_OK(sd_dhcp_client_new(&client, false)); + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + ASSERT_OK(sd_dhcp_client_new(&client, /* anonymize= */ false)); ASSERT_NOT_NULL(client); - ASSERT_OK(sd_dhcp_client_attach_event(client, e, 0)); + ASSERT_OK(sd_dhcp_client_attach_event(client, e, /* priority= */ 0)); ASSERT_OK(sd_dhcp_client_set_bootp(client, true)); ASSERT_OK(sd_dhcp_client_set_ifindex(client, 42)); ASSERT_OK(sd_dhcp_client_set_mac(client, hw_addr.bytes, bcast_addr.bytes, hw_addr.length, ARPHRD_ETHER)); - ASSERT_OK(sd_dhcp_client_set_callback(client, test_bootp_acquired, e)); + ASSERT_OK(sd_dhcp_client_set_callback(client, test_bootp_acquired, NULL)); callback_recv = test_bootp_recv_request; @@ -719,53 +613,27 @@ static void test_acquire_bootp(sd_event *e) { 30 * USEC_PER_SEC, 0, NULL, INT_TO_PTR(-ETIMEDOUT))); - res = sd_dhcp_client_start(client); - assert_se(IN_SET(res, 0, -EINPROGRESS)); - + ASSERT_OK(sd_dhcp_client_start(client)); ASSERT_OK(sd_event_loop(e)); - ASSERT_OK(sd_dhcp_client_set_callback(client, NULL, NULL)); ASSERT_OK(sd_dhcp_client_stop(client)); - client = sd_dhcp_client_unref(client); - ASSERT_NULL(client); + ASSERT_NULL(client = sd_dhcp_client_unref(client)); test_fd[1] = safe_close(test_fd[1]); - callback_recv = NULL; xid = 0; } -int main(int argc, char *argv[]) { - _cleanup_(sd_event_unrefp) sd_event *e; - - assert_se(setenv("SYSTEMD_NETWORK_TEST_MODE", "1", 1) >= 0); - - test_setup_logging(LOG_DEBUG); - - assert_se(sd_event_new(&e) >= 0); - - test_request_basic(e); - test_request_anonymize(e); - test_checksum(); - test_dhcp_identifier_set_iaid(); - - test_discover_message(e); - test_addr_acq(e); - +TEST(bootp) { FOREACH_ELEMENT(i, bootp_addr_data) { - sd_event_unref(e); - ASSERT_OK(sd_event_new(&e)); bootp_test_context = i; - test_acquire_bootp(e); + test_bootp_one(); } +} -#if HAVE_VALGRIND_VALGRIND_H - /* Make sure the async_close thread has finished. - * valgrind would report some of the phread_* structures - * as not cleaned up properly. */ - if (RUNNING_ON_VALGRIND) - sleep(1); -#endif - +static int intro(void) { + ASSERT_OK_ERRNO(setenv("SYSTEMD_NETWORK_TEST_MODE", "1", /* overwrite= */ true)); return 0; } + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); From bd1d1e61eb680b05e5aa75202a17f96b9367afe5 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 13 Mar 2026 08:11:18 +0900 Subject: [PATCH 0400/1296] fuzz-dhcp-client: modernize test code --- src/libsystemd-network/fuzz-dhcp-client.c | 72 ++++++++--------------- 1 file changed, 26 insertions(+), 46 deletions(-) diff --git a/src/libsystemd-network/fuzz-dhcp-client.c b/src/libsystemd-network/fuzz-dhcp-client.c index 23471f89fcedd..7a59faff6312b 100644 --- a/src/libsystemd-network/fuzz-dhcp-client.c +++ b/src/libsystemd-network/fuzz-dhcp-client.c @@ -6,6 +6,7 @@ #include "fuzz.h" #include "network-internal.h" #include "sd-dhcp-client.c" +#include "tests.h" #include "tmpfile-util.h" int dhcp_network_bind_raw_socket( @@ -19,77 +20,56 @@ int dhcp_network_bind_raw_socket( bool so_priority_set, int so_priority) { - int fd; - fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); - if (fd < 0) - return -errno; - - return fd; + return ASSERT_OK_ERRNO(socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); } int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, const void *packet, size_t len) { - return len; + return 0; } int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type) { - int fd; - - fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); - if (fd < 0) - return -errno; - - return fd; + return ASSERT_OK_ERRNO(socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); } int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port, const void *packet, size_t len) { - return len; + return 0; } int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - uint8_t mac_addr[] = {'A', 'B', 'C', '1', '2', '3'}; - uint8_t bcast_addr[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; - _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; - _cleanup_(sd_event_unrefp) sd_event *e = NULL; - _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; - _cleanup_(unlink_tempfilep) char lease_file[] = "/tmp/fuzz-dhcp-client.XXXXXX"; - _cleanup_close_ int fd = -1; - int res, r; + static const uint8_t mac_addr[] = {'A', 'B', 'C', '1', '2', '3'}; + static const uint8_t bcast_addr[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; - assert_se(setenv("SYSTEMD_NETWORK_TEST_MODE", "1", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_NETWORK_TEST_MODE", "1", /* overwrite= */ true)); fuzz_setup_logging(); - r = sd_dhcp_client_new(&client, false); - assert_se(r >= 0); - assert_se(client); + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_NOT_NULL(e); - assert_se(sd_event_new(&e) >= 0); + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + ASSERT_OK(sd_dhcp_client_new(&client, /* anonymize= */ false)); + ASSERT_NOT_NULL(client); - r = sd_dhcp_client_attach_event(client, e, 0); - assert_se(r >= 0); + ASSERT_OK(sd_dhcp_client_attach_event(client, e, /* priority= */ 0)); - assert_se(sd_dhcp_client_set_ifindex(client, 42) >= 0); - assert_se(sd_dhcp_client_set_mac(client, mac_addr, bcast_addr, ETH_ALEN, ARPHRD_ETHER) >= 0); + ASSERT_OK(sd_dhcp_client_set_ifindex(client, 42)); + ASSERT_OK(sd_dhcp_client_set_mac(client, mac_addr, bcast_addr, ETH_ALEN, ARPHRD_ETHER)); - res = sd_dhcp_client_start(client); - assert_se(IN_SET(res, 0, -EINPROGRESS)); + ASSERT_OK(sd_dhcp_client_start(client)); client->xid = 2; client->state = DHCP_STATE_SELECTING; - if (client_handle_offer_or_rapid_ack(client, (DHCPMessage*) data, size, NULL) < 0) - goto end; - - fd = mkostemp_safe(lease_file); - assert_se(fd >= 0); - - r = dhcp_lease_save(client->lease, lease_file); - assert_se(r >= 0); + if (client_handle_offer_or_rapid_ack(client, (DHCPMessage*) data, size, NULL) >= 0) { + _cleanup_(unlink_tempfilep) char lease_file[] = "/tmp/fuzz-dhcp-client.XXXXXX"; + _unused_ _cleanup_close_ int fd = ASSERT_OK(mkostemp_safe(lease_file)); - r = dhcp_lease_load(&lease, lease_file); - assert_se(r >= 0); + ASSERT_OK(dhcp_lease_save(client->lease, lease_file)); -end: - assert_se(sd_dhcp_client_stop(client) >= 0); + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + ASSERT_OK(dhcp_lease_load(&lease, lease_file)); + } + ASSERT_OK(sd_dhcp_client_stop(client)); return 0; } From ced607506d3ef365f3eadb9fad0125e66a5fdd22 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 13 Mar 2026 05:31:45 +0900 Subject: [PATCH 0401/1296] sd-dhcp-client: coding style fix --- src/libsystemd-network/sd-dhcp-client.c | 4 ++-- src/systemd/sd-dhcp-client.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 4b1cdd0b86352..bb9ea3594347e 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -2524,7 +2524,7 @@ int sd_dhcp_client_detach_event(sd_dhcp_client *client) { return 0; } -sd_event *sd_dhcp_client_get_event(sd_dhcp_client *client) { +sd_event* sd_dhcp_client_get_event(sd_dhcp_client *client) { assert_return(client, NULL); return client->event; @@ -2536,7 +2536,7 @@ int sd_dhcp_client_attach_device(sd_dhcp_client *client, sd_device *dev) { return device_unref_and_replace(client->dev, dev); } -static sd_dhcp_client *dhcp_client_free(sd_dhcp_client *client) { +static sd_dhcp_client* dhcp_client_free(sd_dhcp_client *client) { if (!client) return NULL; diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h index b2995a961f320..033d5ad894ec3 100644 --- a/src/systemd/sd-dhcp-client.h +++ b/src/systemd/sd-dhcp-client.h @@ -172,7 +172,7 @@ int sd_dhcp_client_attach_event( sd_event *event, int64_t priority); int sd_dhcp_client_detach_event(sd_dhcp_client *client); -sd_event *sd_dhcp_client_get_event(sd_dhcp_client *client); +sd_event* sd_dhcp_client_get_event(sd_dhcp_client *client); int sd_dhcp_client_attach_device(sd_dhcp_client *client, sd_device *dev); _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_client, sd_dhcp_client_unref); From 0c68816c5648ab42c2ced1682c487c51d1865d56 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 14 Mar 2026 01:31:41 +0900 Subject: [PATCH 0402/1296] sd-dhcp-client: add missing assertion --- src/libsystemd-network/sd-dhcp-client.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index bb9ea3594347e..d126aa9e838d9 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -1490,6 +1490,8 @@ static int client_start_delayed(sd_dhcp_client *client) { } static int client_start(sd_dhcp_client *client) { + assert(client); + client->start_delay = 0; return client_start_delayed(client); } From ddded65d41f691afc4d248343d987775655bcfb7 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 13 Mar 2026 05:19:06 +0900 Subject: [PATCH 0403/1296] sd-dhcp-client: add missing error checks --- src/libsystemd-network/sd-dhcp-client.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index d126aa9e838d9..55c7c741b5abb 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -1386,8 +1386,9 @@ static int client_timeout_resend( the client reverts to INIT state and restarts the initialization process */ if (client->request_attempt >= client->max_request_attempts) { log_dhcp_client(client, "Max REQUEST attempts reached. Restarting..."); - client_restart(client); - return 0; + r = client_restart(client); + if (r >= 0) + return 0; } client_stop(client, r); @@ -1499,6 +1500,7 @@ static int client_start(sd_dhcp_client *client) { static int client_timeout_expire(sd_event_source *s, uint64_t usec, void *userdata) { sd_dhcp_client *client = userdata; DHCP_CLIENT_DONT_DESTROY(client); + int r; log_dhcp_client(client, "EXPIRED"); @@ -1507,7 +1509,12 @@ static int client_timeout_expire(sd_event_source *s, uint64_t usec, void *userda /* lease was lost, start over if not freed or stopped in callback */ if (client->state != DHCP_STATE_STOPPED) { client_initialize(client); - client_start(client); + + r = client_start(client); + if (r < 0) { + client_stop(client, r); + return 0; + } } return 0; From 2fb192d237f91b8f2f9bb42adc7a3ad010641876 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 11 Mar 2026 08:50:24 +0900 Subject: [PATCH 0404/1296] sd-dhcp-client: drop disabled FORCERENEW message support FORCERENEW message support has been disabled so long time for security concern. Most other implementations of DHCP server/client neither support FORCERENEW. Let's completely drop relevant code. --- src/libsystemd-network/sd-dhcp-client.c | 33 +++---------------------- 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 55c7c741b5abb..903add49fd966 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -1802,25 +1802,6 @@ static int client_enter_requesting(sd_dhcp_client *client) { return client_enter_requesting_now(client); } -static int client_handle_forcerenew(sd_dhcp_client *client, DHCPMessage *force, size_t len) { - int r; - - r = dhcp_option_parse(force, len, NULL, NULL, NULL); - if (r != DHCP_FORCERENEW) - return -ENOMSG; - -#if 0 - log_dhcp_client(client, "FORCERENEW"); - return 0; -#else - /* FIXME: Ignore FORCERENEW requests until we implement RFC3118 (Authentication for DHCP - * Messages) and/or RFC6704 (Forcerenew Nonce Authentication), as unauthenticated FORCERENEW - * requests causes a security issue (TALOS-2020-1142, CVE-2020-13529). */ - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), - "Received FORCERENEW, ignoring."); -#endif -} - static bool lease_equal(const sd_dhcp_lease *a, const sd_dhcp_lease *b) { if (a->address != b->address) return false; @@ -2113,10 +2094,7 @@ static int client_verify_message_header(sd_dhcp_client *client, DHCPMessage *mes return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), "Received chaddr does not match expected, ignoring."); - if (client->state != DHCP_STATE_BOUND && - be32toh(message->xid) != client->xid) - /* in BOUND state, we may receive FORCERENEW with xid set by server, - so ignore the xid in this case */ + if (be32toh(message->xid) != client->xid) return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), "Received xid (%u) does not match expected (%u), ignoring.", be32toh(message->xid), client->xid); @@ -2170,13 +2148,8 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, s return client_enter_bound(client, r); case DHCP_STATE_BOUND: - r = client_handle_forcerenew(client, message, len); - if (ERRNO_IS_NEG_RESOURCE(r)) - return r; - if (r < 0) - return 0; /* invalid message, let's ignore it */ - - return client_timeout_t1(NULL, 0, client); + log_dhcp_client(client, "Unexpected DHCP message received in BOUND state, ignoring."); + return 0; case DHCP_STATE_INIT: case DHCP_STATE_INIT_REBOOT: From ad4d2ae619dc16abc708c8aeb54148973712a46e Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 13 Mar 2026 05:16:16 +0900 Subject: [PATCH 0405/1296] sd-dhcp-client: voidify client_initialize() It never fails. --- src/libsystemd-network/sd-dhcp-client.c | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 903add49fd966..683ee3d5090c2 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -702,8 +702,8 @@ static int client_notify(sd_dhcp_client *client, int event) { return 0; } -static int client_initialize(sd_dhcp_client *client) { - assert_return(client, -EINVAL); +static void client_initialize(sd_dhcp_client *client) { + assert(client); client->receive_message = sd_event_source_disable_unref(client->receive_message); @@ -722,8 +722,6 @@ static int client_initialize(sd_dhcp_client *client) { client->xid = 0; client->lease = sd_dhcp_lease_unref(client->lease); - - return 0; } static void client_stop(sd_dhcp_client *client, int error) { @@ -1286,9 +1284,7 @@ static int client_timeout_resend( case DHCP_STATE_REBOOTING: /* start over as we did not receive a timely ack or nak */ - r = client_initialize(client); - if (r < 0) - goto error; + client_initialize(client); r = client_start(client); if (r < 0) @@ -2042,9 +2038,7 @@ static int client_restart(sd_dhcp_client *client) { client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED); - r = client_initialize(client); - if (r < 0) - return r; + client_initialize(client); r = client_start_delayed(client); if (r < 0) @@ -2307,9 +2301,7 @@ int sd_dhcp_client_start(sd_dhcp_client *client) { /* Note, do not reset the flag in client_initialize(), as it is also called on expire. */ client->ipv6_acquired = false; - r = client_initialize(client); - if (r < 0) - return r; + client_initialize(client); /* If no client identifier exists, construct an RFC 4361-compliant one */ if (!sd_dhcp_client_id_is_set(&client->client_id)) { From 26f769bea390eb06bef0d709beb47a5a75f9ff3f Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 14 Mar 2026 21:59:42 +0900 Subject: [PATCH 0406/1296] sd-dhcp-client: propagate errors in client_initialize_{io,time}_events() Call client_stop() on error and return 0 only on callback. Normal non-callback functions should propagate errors. This also makes client_initialize_time_events() use event_reset_time_relative(). --- src/libsystemd-network/sd-dhcp-client.c | 84 +++++++++++++------------ 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 683ee3d5090c2..9dcd9859a3fd7 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -1401,59 +1401,56 @@ static int client_initialize_io_events( assert(client); assert(client->event); + assert(io_callback); - r = sd_event_add_io(client->event, &client->receive_message, - client->fd, EPOLLIN, io_callback, - client); + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + r = sd_event_add_io(client->event, &s, client->fd, EPOLLIN, io_callback, client); if (r < 0) - goto error; - - r = sd_event_source_set_priority(client->receive_message, - client->event_priority); - if (r < 0) - goto error; + return r; - r = sd_event_source_set_description(client->receive_message, "dhcp4-receive-message"); + r = sd_event_source_set_priority(s, client->event_priority); if (r < 0) - goto error; + return r; -error: + r = sd_event_source_set_description(s, "dhcp4-receive-message"); if (r < 0) - client_stop(client, r); + return r; + sd_event_source_disable_unref(client->receive_message); + client->receive_message = TAKE_PTR(s); return 0; } static int client_initialize_time_events(sd_dhcp_client *client) { - usec_t usec = 0; - int r; - assert(client); assert(client->event); (void) event_source_disable(client->timeout_ipv6_only_mode); - if (client->start_delay > 0) { - assert_se(sd_event_now(client->event, CLOCK_BOOTTIME, &usec) >= 0); - usec = usec_add(usec, client->start_delay); - } - - r = event_reset_time(client->event, &client->timeout_resend, - CLOCK_BOOTTIME, - usec, 0, - client_timeout_resend, client, - client->event_priority, "dhcp4-resend-timer", true); - if (r < 0) - client_stop(client, r); - - return 0; + return event_reset_time_relative( + client->event, + &client->timeout_resend, + CLOCK_BOOTTIME, + client->start_delay, + /* accuracy= */ 0, + client_timeout_resend, + client, + client->event_priority, + "dhcp4-resend-timer", + /* force_reset= */ true); } static int client_initialize_events(sd_dhcp_client *client, sd_event_io_handler_t io_callback) { - client_initialize_io_events(client, io_callback); - client_initialize_time_events(client); + int r; - return 0; + assert(client); + assert(io_callback); + + r = client_initialize_io_events(client, io_callback); + if (r < 0) + return r; + + return client_initialize_time_events(client); } static int client_start_delayed(sd_dhcp_client *client) { @@ -1472,10 +1469,8 @@ static int client_start_delayed(sd_dhcp_client *client) { &client->hw_addr, &client->bcast_addr, client->arp_type, client->port, client->socket_priority_set, client->socket_priority); - if (r < 0) { - client_stop(client, r); + if (r < 0) return r; - } client->fd = r; client->start_time = now(CLOCK_BOOTTIME); @@ -1538,12 +1533,17 @@ static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) } client->fd = r; - return client_initialize_events(client, client_receive_message_raw); + r = client_initialize_events(client, client_receive_message_raw); + if (r < 0) + client_stop(client, r); + + return 0; } static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) { sd_dhcp_client *client = userdata; DHCP_CLIENT_DONT_DESTROY(client); + int r; if (client->lease) client_set_state(client, DHCP_STATE_RENEWING); @@ -1552,7 +1552,11 @@ static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) client->discover_attempt = 0; client->request_attempt = 0; - return client_initialize_time_events(client); + r = client_initialize_time_events(client); + if (r < 0) + client_stop(client, r); + + return 0; } static int dhcp_option_parse_and_verify( @@ -1976,7 +1980,9 @@ static int client_enter_bound_now(sd_dhcp_client *client, int notify_event) { client->receive_message = sd_event_source_disable_unref(client->receive_message); close_and_replace(client->fd, r); - client_initialize_io_events(client, client_receive_message_udp); + r = client_initialize_io_events(client, client_receive_message_udp); + if (r < 0) + return r; } client_notify(client, notify_event); From 494c65236b19e160ade48315edfa0f089f3d4154 Mon Sep 17 00:00:00 2001 From: Oblivionsage Date: Sat, 21 Mar 2026 17:43:50 +0100 Subject: [PATCH 0407/1296] dns-packet: move p->more unref into the free path dns_packet_unref() unconditionally unrefs p->more on every call, even when n_ref > 1. But dns_packet_ref() doesn't ref p->more. This means if a packet with a ->more chain gets ref'd and unref'd multiple times, the chain gets freed too early while the parent still holds a dangling pointer. Move the p->more unref into the n_ref == 1 block so the chain only gets cleaned up when the packet is actually being freed. --- src/shared/dns-packet.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/shared/dns-packet.c b/src/shared/dns-packet.c index 04178e5df2b5c..cdd56d513faba 100644 --- a/src/shared/dns-packet.c +++ b/src/shared/dns-packet.c @@ -284,11 +284,10 @@ DnsPacket *dns_packet_unref(DnsPacket *p) { assert(p->n_ref > 0); - dns_packet_unref(p->more); - - if (p->n_ref == 1) + if (p->n_ref == 1) { + dns_packet_unref(p->more); dns_packet_free(p); - else + } else p->n_ref--; return NULL; From 621514762443bc536cd489a221a8669fdfd54241 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 20 Mar 2026 16:25:42 +0100 Subject: [PATCH 0408/1296] core: add `io.systemd.Manager.{PowerOff,Reboot,SoftReboot,Halt,Kexec}` This adds the low-level io.systemd.Manager shutdown support. This is (much) simpler than the logind one. It mimics dbus but uses a shared helper for the simple cases. Note that this is more restrictive than the dbus version. The dbus version uses SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT) but the varlink version uses varlink_check_privileged_peer(link). This is mostly because I'm not sure how to do the equivalent in a race-free way. Thanks to Daan for suggesting this. --- src/core/dbus-manager.c | 1 + src/core/varlink-manager.c | 82 +++++++++++++++++++++++++ src/core/varlink-manager.h | 5 ++ src/core/varlink.c | 5 ++ src/shared/varlink-io.systemd.Manager.c | 19 ++++++ 5 files changed, 112 insertions(+) diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 5e02d189072e2..fec53341caecf 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -1690,6 +1690,7 @@ static int method_soft_reboot(sd_bus_message *message, void *userdata, sd_bus_er return sd_bus_error_set(reterr_error, SD_BUS_ERROR_NOT_SUPPORTED, "Soft reboot is only supported by system manager."); + /* Keep the checks in sync with varlink-manager.c:vl_method_soft_reboot_manager() */ r = mac_selinux_access_check(message, "reboot", reterr_error); if (r < 0) return r; diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index d00f7e5a248a7..53db0a5a2e6fd 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -16,6 +16,7 @@ #include "glyph-util.h" #include "json-util.h" #include "manager.h" +#include "path-util.h" #include "pidref.h" #include "selinux-access.h" #include "set.h" @@ -398,3 +399,84 @@ int vl_method_enqueue_marked_jobs_manager(sd_varlink *link, sd_json_variant *par return ret; } + +static int manager_do_set_objective(sd_varlink *link, sd_json_variant *parameters, ManagerObjective objective, const char *selinux_permission, bool can_do_root) { + Manager *m = ASSERT_PTR(sd_varlink_get_userdata(link)); + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + _cleanup_free_ char *rt = NULL; + const char *root = NULL; + int r; + + assert(link); + assert(parameters); + + if (!MANAGER_IS_SYSTEM(m)) + return sd_varlink_error(link, SD_VARLINK_ERROR_METHOD_NOT_IMPLEMENTED, NULL); + + if (can_do_root) { + static const sd_json_dispatch_field dispatch_table[] = { + { "root", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, 0 }, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &root); + } else + r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL); + if (r != 0) + return r; + + r = mac_selinux_access_check_varlink(link, selinux_permission); + if (r < 0) + return r; + + /* dbus uses SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT) in its checking. We cannot do the same + * because reading capabilities from /proc is racy (TOCTOU). So we use the stricter check + * TODO: figure out a way to check for CAP_SYS_BOOT */ + r = varlink_check_privileged_peer(link); + if (r < 0) + return r; + + if (!isempty(root)) { + if (!path_is_valid(root)) + return sd_varlink_error_invalid_parameter_name(link, "root"); + if (!path_is_absolute(root)) + return sd_varlink_error_invalid_parameter_name(link, "root"); + + r = path_simplify_alloc(root, &rt); + if (r < 0) + return r; + } + + /* We need at least the pidref, otherwise there's nothing to log about. */ + r = varlink_get_peer_pidref(link, &pidref); + if (r < 0) + log_debug_errno(r, "Failed to get peer pidref, ignoring: %m"); + else + manager_log_caller(m, &pidref, manager_objective_to_string(objective)); + + if (can_do_root) + free_and_replace(m->switch_root, rt); + m->objective = objective; + + return sd_varlink_reply(link, NULL); +} + +int vl_method_poweroff_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_set_objective(link, parameters, MANAGER_POWEROFF, "halt", /* can_do_root= */ false); +} + +int vl_method_reboot_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_set_objective(link, parameters, MANAGER_REBOOT, "reboot", /* can_do_root= */ false); +} + +int vl_method_halt_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_set_objective(link, parameters, MANAGER_HALT, "halt", /* can_do_root= */ false); +} + +int vl_method_kexec_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_set_objective(link, parameters, MANAGER_KEXEC, "reboot", /* can_do_root= */ false); +} + +int vl_method_soft_reboot_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_set_objective(link, parameters, MANAGER_SOFT_REBOOT, "reboot", /* can_do_root= */ true); +} diff --git a/src/core/varlink-manager.h b/src/core/varlink-manager.h index e5111eb58dc7a..0e477e761b0d9 100644 --- a/src/core/varlink-manager.h +++ b/src/core/varlink-manager.h @@ -9,3 +9,8 @@ int vl_method_describe_manager(sd_varlink *link, sd_json_variant *parameters, sd int vl_method_reexecute_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_reload_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_enqueue_marked_jobs_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_poweroff_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_reboot_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_halt_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_kexec_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_soft_reboot_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/core/varlink.c b/src/core/varlink.c index ec4f8abad95ae..77b2d3bd82997 100644 --- a/src/core/varlink.c +++ b/src/core/varlink.c @@ -390,6 +390,11 @@ int manager_setup_varlink_server(Manager *m) { "io.systemd.Manager.Reexecute", vl_method_reexecute_manager, "io.systemd.Manager.Reload", vl_method_reload_manager, "io.systemd.Manager.EnqueueMarkedJobs", vl_method_enqueue_marked_jobs_manager, + "io.systemd.Manager.PowerOff", vl_method_poweroff_manager, + "io.systemd.Manager.Reboot", vl_method_reboot_manager, + "io.systemd.Manager.Halt", vl_method_halt_manager, + "io.systemd.Manager.KExec", vl_method_kexec_manager, + "io.systemd.Manager.SoftReboot", vl_method_soft_reboot_manager, "io.systemd.Unit.List", vl_method_list_units, "io.systemd.Unit.SetProperties", vl_method_set_unit_properties, "io.systemd.service.Ping", varlink_method_ping, diff --git a/src/shared/varlink-io.systemd.Manager.c b/src/shared/varlink-io.systemd.Manager.c index cb304f2295029..f33cab34b3de9 100644 --- a/src/shared/varlink-io.systemd.Manager.c +++ b/src/shared/varlink-io.systemd.Manager.c @@ -193,6 +193,15 @@ static SD_VARLINK_DEFINE_METHOD_FULL( SD_VARLINK_FIELD_COMMENT("Job enqueue error message (on failure)"), SD_VARLINK_DEFINE_OUTPUT(errorMessage, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_METHOD(PowerOff); +static SD_VARLINK_DEFINE_METHOD(Reboot); +static SD_VARLINK_DEFINE_METHOD(Halt); +static SD_VARLINK_DEFINE_METHOD(KExec); +static SD_VARLINK_DEFINE_METHOD( + SoftReboot, + SD_VARLINK_FIELD_COMMENT("New root directory for the soft reboot"), + SD_VARLINK_DEFINE_INPUT(root, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + static SD_VARLINK_DEFINE_ERROR(RateLimitReached); SD_VARLINK_DEFINE_INTERFACE( @@ -205,6 +214,16 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_method_Reload, SD_VARLINK_SYMBOL_COMMENT("Enqueue all marked jobs"), &vl_method_EnqueueMarkedJobs, + SD_VARLINK_SYMBOL_COMMENT("Power off the system"), + &vl_method_PowerOff, + SD_VARLINK_SYMBOL_COMMENT("Reboot the system"), + &vl_method_Reboot, + SD_VARLINK_SYMBOL_COMMENT("Halt the system"), + &vl_method_Halt, + SD_VARLINK_SYMBOL_COMMENT("Reboot the system via kexec"), + &vl_method_KExec, + SD_VARLINK_SYMBOL_COMMENT("Soft-reboot the userspace"), + &vl_method_SoftReboot, &vl_error_RateLimitReached, &vl_type_ManagerContext, &vl_type_ManagerRuntime, From 955115546c2bd23cb5ae19ca04ebc4b4bfa235ec Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Sat, 21 Mar 2026 22:12:02 +0100 Subject: [PATCH 0409/1296] core: extract varlink_log_caller() helper Extract a common helper varlink_log_caller() and use in the varlink code when logging the caller of a method. It also logs the method now that was tried (but failed) to be logged with log_notice just like manager_log_caller() would do. I was looking into modifying `manager_log_caller` instead and accept a NULL pidref but could not log more than the method without pidref and would make the manager_log_caller slightly less nice. Thanks to keszybz for suggesting this. --- src/core/varlink-manager.c | 41 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index 53db0a5a2e6fd..a12f14e121897 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -212,8 +212,24 @@ int vl_method_describe_manager(sd_varlink *link, sd_json_variant *parameters, sd return sd_varlink_reply(link, v); } -int vl_method_reload_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { +static void varlink_log_caller(sd_varlink *link, Manager *manager, const char *method) { _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + int r; + + assert(link); + assert(manager); + assert(method); + + /* We need at least the pidref, otherwise there's nothing to log about. */ + r = varlink_get_peer_pidref(link, &pidref); + if (r < 0) + /* We use log_notice here just as manager_log_caller would */ + log_notice_errno(r, "Failed to get peer pidref when trying to log caller for %s, ignoring: %m", method); + else + manager_log_caller(manager, &pidref, method); +} + +int vl_method_reload_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { Manager *manager = ASSERT_PTR(userdata); int r; @@ -237,12 +253,7 @@ int vl_method_reload_manager(sd_varlink *link, sd_json_variant *parameters, sd_v if (r <= 0) return r; - /* We need at least the pidref, otherwise there's nothing to log about. */ - r = varlink_get_peer_pidref(link, &pidref); - if (r < 0) - log_debug_errno(r, "Failed to get peer pidref, ignoring: %m"); - else - manager_log_caller(manager, &pidref, "Reload"); + varlink_log_caller(link, manager, "Reload"); /* Check the rate limit after the authorization succeeds, to avoid denial-of-service issues. */ if (!ratelimit_below(&manager->reload_reexec_ratelimit)) { @@ -263,7 +274,6 @@ int vl_method_reload_manager(sd_varlink *link, sd_json_variant *parameters, sd_v } int vl_method_reexecute_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { - _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; Manager *manager = ASSERT_PTR(userdata); int r; @@ -287,12 +297,7 @@ int vl_method_reexecute_manager(sd_varlink *link, sd_json_variant *parameters, s if (r <= 0) return r; - /* We need at least the pidref, otherwise there's nothing to log about. */ - r = varlink_get_peer_pidref(link, &pidref); - if (r < 0) - log_debug_errno(r, "Failed to get peer pidref, ignoring: %m"); - else - manager_log_caller(manager, &pidref, "Reexecute"); + varlink_log_caller(link, manager, "Reexecute"); /* Check the rate limit after the authorization succeeds, to avoid denial-of-service issues. */ if (!ratelimit_below(&manager->reload_reexec_ratelimit)) { @@ -402,7 +407,6 @@ int vl_method_enqueue_marked_jobs_manager(sd_varlink *link, sd_json_variant *par static int manager_do_set_objective(sd_varlink *link, sd_json_variant *parameters, ManagerObjective objective, const char *selinux_permission, bool can_do_root) { Manager *m = ASSERT_PTR(sd_varlink_get_userdata(link)); - _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; _cleanup_free_ char *rt = NULL; const char *root = NULL; int r; @@ -447,12 +451,7 @@ static int manager_do_set_objective(sd_varlink *link, sd_json_variant *parameter return r; } - /* We need at least the pidref, otherwise there's nothing to log about. */ - r = varlink_get_peer_pidref(link, &pidref); - if (r < 0) - log_debug_errno(r, "Failed to get peer pidref, ignoring: %m"); - else - manager_log_caller(m, &pidref, manager_objective_to_string(objective)); + varlink_log_caller(link, m, manager_objective_to_string(objective)); if (can_do_root) free_and_replace(m->switch_root, rt); From 114c89cc9437bf9793295cf0beee92148844c845 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Sat, 21 Mar 2026 22:36:20 +0100 Subject: [PATCH 0410/1296] core: allow unset pidref in manager_log_caller This commit allows unset pidref when calling manager_log_caller(). With that we can log manager calls even if we cannot resolve the caller. Currently when we cannot resolve the caller we are just not logging anything. With this commit we at least log the call (even though we don't know what caller it was). Thanks to keszybz for the suggestion. --- src/core/manager.c | 6 +++++- src/core/varlink-manager.c | 8 +++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/core/manager.c b/src/core/manager.c index 79fa19d976eb3..e8c5f00895847 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -5220,9 +5220,13 @@ void manager_log_caller(Manager *manager, PidRef *caller, const char *method) { _cleanup_free_ char *comm = NULL; assert(manager); - assert(pidref_is_set(caller)); assert(method); + if (!pidref_is_set(caller)) { + log_notice("%s requested from unknown client PID...", method); + return; + } + (void) pidref_get_comm(caller, &comm); Unit *caller_unit = manager_get_unit_by_pidref(manager, caller); diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index a12f14e121897..bad37206328dd 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -220,13 +220,11 @@ static void varlink_log_caller(sd_varlink *link, Manager *manager, const char *m assert(manager); assert(method); - /* We need at least the pidref, otherwise there's nothing to log about. */ r = varlink_get_peer_pidref(link, &pidref); if (r < 0) - /* We use log_notice here just as manager_log_caller would */ - log_notice_errno(r, "Failed to get peer pidref when trying to log caller for %s, ignoring: %m", method); - else - manager_log_caller(manager, &pidref, method); + log_debug_errno(r, "Failed to get peer pidref, ignoring: %m"); + + manager_log_caller(manager, &pidref, method); } int vl_method_reload_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { From 4bf73e6980fb8f440fbf38253041ca08ff2e0e1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 19 Mar 2026 17:45:17 +0100 Subject: [PATCH 0411/1296] test-options: add tests for option macros and flags Add tests for OPTION_STOPS_PARSING, OPTION_GROUP_MARKER, and OPTION_OPTIONAL_ARG flags with manual Option arrays, and a separate test exercising the OPTION, OPTION_LONG, OPTION_SHORT, OPTION_FULL, and OPTION_GROUP macros via FOREACH_OPTION_FULL in a switch statement, as they would be used in real code. Co-developed-by: Claude Opus 4.6 --- src/test/test-options.c | 624 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 622 insertions(+), 2 deletions(-) diff --git a/src/test/test-options.c b/src/test/test-options.c index 56e8dd1d61390..6ca29fb849ab9 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -7,6 +7,7 @@ typedef struct Entry { const char *long_code; const char *argument; + char short_code; } Entry; static void test_option_parse_one( @@ -27,7 +28,7 @@ static void test_option_parse_one( for (const Option *o = options; o->short_code != 0 || o->long_code; o++) n_options++; - for (const Entry *e = entries; e && e->long_code; e++) + for (const Entry *e = entries; e && (e->long_code || e->short_code != 0); e++) n_entries++; OptionParser state = {}; @@ -43,7 +44,10 @@ static void test_option_parse_one( strnull(opt->metavar), strnull(arg)); ASSERT_LT(i, n_entries); - ASSERT_TRUE(streq_ptr(opt->long_code, entries[i].long_code)); + if (entries[i].long_code) + ASSERT_TRUE(streq_ptr(opt->long_code, entries[i].long_code)); + if (entries[i].short_code != 0) + ASSERT_EQ(opt->short_code, entries[i].short_code); ASSERT_TRUE(streq_ptr(arg, entries[i].argument)); i++; } @@ -55,6 +59,30 @@ static void test_option_parse_one( ASSERT_STREQ(argv[0], saved_argv0); } +static void test_option_invalid_one( + char **argv, + const Option options[static 1]) { + + _cleanup_free_ char *joined = strv_join(argv, ", "); + log_debug("/* %s(%s) */", __func__, joined); + + _cleanup_free_ char *saved_argv0 = NULL; + ASSERT_NOT_NULL(saved_argv0 = strdup(argv[0])); + + int argc = strv_length(argv); + + size_t n_options = 0; + for (const Option *o = options; o->short_code != 0 || o->long_code; o++) + n_options++; + + OptionParser state = {}; + const Option *opt; + const char *arg; + + int c = option_parse(options, options + n_options, &state, argc, argv, &opt, &arg); + ASSERT_ERROR(c, EINVAL); +} + TEST(option_parse) { static const Option options[] = { { 1, .short_code = 'h', .long_code = "help" }, @@ -372,4 +400,596 @@ TEST(option_parse) { "--optional1")); } +TEST(option_stops_parsing) { + static const Option options[] = { + { 1, .short_code = 'h', .long_code = "help" }, + { 2, .long_code = "version" }, + { 3, .short_code = 'r', .long_code = "required", .metavar = "ARG" }, + { 4, .long_code = "exec", .flags = OPTION_STOPS_PARSING }, + {} + }; + + /* --exec stops parsing, subsequent --help is positional */ + test_option_parse_one(STRV_MAKE("arg0", + "--exec", + "--help", + "foo"), + options, + (Entry[]) { + { "exec" }, + {} + }, + STRV_MAKE("--help", + "foo")); + + /* Options before --exec are still parsed */ + test_option_parse_one(STRV_MAKE("arg0", + "--help", + "--exec", + "--version", + "bar"), + options, + (Entry[]) { + { "help" }, + { "exec" }, + {} + }, + STRV_MAKE("--version", + "bar")); + + /* --exec with no trailing args */ + test_option_parse_one(STRV_MAKE("arg0", + "--exec"), + options, + (Entry[]) { + { "exec" }, + {} + }, + NULL); + + /* --exec after positional args */ + test_option_parse_one(STRV_MAKE("arg0", + "pos1", + "--exec", + "--help", + "--required", "val"), + options, + (Entry[]) { + { "exec" }, + {} + }, + STRV_MAKE("pos1", + "--help", + "--required", + "val")); + + /* "--" after --exec: "--" is still consumed as end-of-options marker. This is needed for + * backwards compatibility, systemd-dissect implemented this behaviour. But also, it makes + * sense: we're unlikely to ever want to specify "--" as the first argument of whatever + * sequence, but the user may want to specify it for clarity. */ + test_option_parse_one(STRV_MAKE("arg0", + "--exec", + "--", + "--help"), + options, + (Entry[]) { + { "exec" }, + {} + }, + STRV_MAKE("--help")); + + /* "--" before --exec: "--" terminates first, --exec is positional */ + test_option_parse_one(STRV_MAKE("arg0", + "--", + "--exec", + "--help"), + options, + NULL, + STRV_MAKE("--exec", + "--help")); + + /* Multiple options then --exec then more option-like args */ + test_option_parse_one(STRV_MAKE("arg0", + "--help", + "-r", "val1", + "--exec", + "-h", + "--required", "val2"), + options, + (Entry[]) { + { "help" }, + { "required", "val1" }, + { "exec" }, + {} + }, + STRV_MAKE("-h", + "--required", + "val2")); +} + +TEST(option_group_marker) { + static const Option options[] = { + { 1, .short_code = 'h', .long_code = "help" }, + { 2, .long_code = "version" }, + { 0, .long_code = "AdvancedGroup", .flags = OPTION_GROUP_MARKER }, + { 3, .long_code = "debug" }, + { 4, .long_code = "Advance" }, /* prefix match with the group */ + { 5, .long_code = "defilbrilate" }, + {} + }; + + /* Group markers are skipped by the parser — only real options are returned */ + test_option_parse_one(STRV_MAKE("arg0", + "--help", + "--debug"), + options, + (Entry[]) { + { "help" }, + { "debug" }, + {} + }, + NULL); + + /* Check that group marker name is ignored */ + test_option_parse_one(STRV_MAKE("arg0", + "--debug", + "--version"), + options, + (Entry[]) { + { "debug" }, + { "version" }, + {} + }, + NULL); + + /* Verify that the group marker is not mistaken for an option */ + test_option_invalid_one(STRV_MAKE("arg0", + "--AdvancedGroup"), + options); + + /* Verify that the group marker is not mistaken for an option */ + test_option_invalid_one(STRV_MAKE("arg0", + "--AdvancedGroup=2"), + options); + + /* Verify that the group marker is not mistaken for an option, prefix match */ + test_option_invalid_one(STRV_MAKE("arg0", + "--Advanced"), + options); + + /* Check that group marker name is ignored */ + test_option_parse_one(STRV_MAKE("arg0", + "--Advance", + "--Advan"), /* prefix match with unique prefix */ + options, + (Entry[]) { + { "Advance" }, + { "Advance" }, + {} + }, + NULL); + + /* Partial match with multiple candidates */ + test_option_invalid_one(STRV_MAKE("arg0", + "--de"), + options); +} + +TEST(option_optional_arg) { + static const Option options[] = { + { 1, .short_code = 'o', .long_code = "output", .metavar = "FILE", .flags = OPTION_OPTIONAL_ARG }, + { 2, .short_code = 'h', .long_code = "help" }, + {} + }; + + /* Long option with = gets the argument */ + test_option_parse_one(STRV_MAKE("arg0", + "--output=foo.txt"), + options, + (Entry[]) { + { "output", "foo.txt" }, + {} + }, + NULL); + + /* Long option without = does NOT consume the next arg */ + test_option_parse_one(STRV_MAKE("arg0", + "--output", "foo.txt"), + options, + (Entry[]) { + { "output", NULL }, + {} + }, + STRV_MAKE("foo.txt")); + + /* Short option with inline arg */ + test_option_parse_one(STRV_MAKE("arg0", + "-ofoo.txt"), + options, + (Entry[]) { + { "output", "foo.txt" }, + {} + }, + NULL); + + /* Short option without inline arg does NOT consume the next arg */ + test_option_parse_one(STRV_MAKE("arg0", + "-o", "foo.txt"), + options, + (Entry[]) { + { "output", NULL }, + {} + }, + STRV_MAKE("foo.txt")); + + /* Optional arg option at end of argv */ + test_option_parse_one(STRV_MAKE("arg0", + "--output"), + options, + (Entry[]) { + { "output", NULL }, + {} + }, + NULL); + + /* Mixed: optional arg with other options */ + test_option_parse_one(STRV_MAKE("arg0", + "--help", + "--output=bar", + "--help"), + options, + (Entry[]) { + { "help" }, + { "output", "bar" }, + { "help" }, + {} + }, + NULL); + + /* Short combo: -ho (h then o with no arg) */ + test_option_parse_one(STRV_MAKE("arg0", + "-ho", "pos1"), + options, + (Entry[]) { + { "help" }, + { "output", NULL }, + {} + }, + STRV_MAKE("pos1")); + + /* Short combo: -hobar (h then o with inline arg "bar") */ + test_option_parse_one(STRV_MAKE("arg0", + "-hobar"), + options, + (Entry[]) { + { "help" }, + { "output", "bar" }, + {} + }, + NULL); +} + +/* Test the OPTION, OPTION_LONG, OPTION_SHORT, OPTION_FULL, OPTION_GROUP macros + * by using them in a FOREACH_OPTION_FULL switch, as they would be used in real code. */ + +static void test_macros_parse_one( + char **argv, + const Entry *entries, + char **remaining) { + + _cleanup_free_ char *joined = strv_join(argv, ", "); + log_debug("/* %s(%s) */", __func__, joined); + + _cleanup_free_ char *saved_argv0 = NULL; + ASSERT_NOT_NULL(saved_argv0 = strdup(argv[0])); + + int argc = strv_length(argv); + size_t i = 0, n_entries = 0; + + for (const Entry *e = entries; e && (e->long_code || e->short_code != 0); e++) + n_entries++; + + OptionParser state = {}; + const Option *opt; + const char *arg; + + FOREACH_OPTION_FULL(&state, c, argc, argv, &opt, &arg, ASSERT_TRUE(false)) { + log_debug("%c %s: %s=%s", + opt->short_code != 0 ? opt->short_code : ' ', + opt->long_code ?: "", + strnull(opt->metavar), strnull(arg)); + + ASSERT_LT(i, n_entries); + if (entries[i].long_code) + ASSERT_TRUE(streq_ptr(opt->long_code, entries[i].long_code)); + if (entries[i].short_code != 0) + ASSERT_EQ(opt->short_code, entries[i].short_code); + ASSERT_TRUE(streq_ptr(arg, entries[i].argument)); + i++; + + switch (c) { + + /* OPTION: short + long, no arg */ + OPTION('h', "help", NULL, "Show this help"): + break; + + /* OPTION_LONG: long only, no arg */ + OPTION_LONG("version", NULL, "Show package version"): + break; + + /* OPTION_SHORT: short only, no arg */ + OPTION_SHORT('v', NULL, "Enable verbose mode"): + break; + + /* OPTION: short + long, required arg */ + OPTION('r', "required", "ARG", "Required arg option"): + break; + + /* OPTION_FULL: optional arg */ + OPTION_FULL(OPTION_OPTIONAL_ARG, 'o', "optional", "ARG", "Optional arg option"): + break; + + /* OPTION_FULL: stops parsing */ + OPTION_FULL(OPTION_STOPS_PARSING, 0, "exec", NULL, "Stop parsing after this"): + break; + + /* OPTION_GROUP: group marker (never returned by parser) */ + OPTION_GROUP("Advanced"): + break; + + /* OPTION_LONG: long only, in the "Advanced" group */ + OPTION_LONG("debug", NULL, "Enable debug mode"): + break; + + default: + log_error("Unexpected option id: %d", c); + ASSERT_TRUE(false); + } + } + + ASSERT_EQ(i, n_entries); + + char **args = option_parser_get_args(&state, argc, argv); + ASSERT_TRUE(strv_equal(args, remaining)); + ASSERT_STREQ(argv[0], saved_argv0); +} + +TEST(option_macros) { + /* OPTION: long form */ + test_macros_parse_one(STRV_MAKE("arg0", + "--help"), + (Entry[]) { + { "help" }, + {} + }, + NULL); + + /* OPTION: short form */ + test_macros_parse_one(STRV_MAKE("arg0", + "-h"), + (Entry[]) { + { "help" }, + {} + }, + NULL); + + /* OPTION_LONG: only accessible via long form */ + test_macros_parse_one(STRV_MAKE("arg0", + "--version"), + (Entry[]) { + { "version" }, + {} + }, + NULL); + + /* OPTION_SHORT: only accessible via short form */ + test_macros_parse_one(STRV_MAKE("arg0", + "-v"), + (Entry[]) { + { .short_code = 'v' }, + {} + }, + NULL); + + /* OPTION with required arg: long --required=ARG */ + test_macros_parse_one(STRV_MAKE("arg0", + "--required=val1"), + (Entry[]) { + { "required", "val1" }, + {} + }, + NULL); + + /* OPTION with required arg: long --required ARG */ + test_macros_parse_one(STRV_MAKE("arg0", + "--required", "val1"), + (Entry[]) { + { "required", "val1" }, + {} + }, + NULL); + + /* OPTION with required arg: short -r ARG */ + test_macros_parse_one(STRV_MAKE("arg0", + "-r", "val1"), + (Entry[]) { + { "required", "val1" }, + {} + }, + NULL); + + /* OPTION with required arg: short -rARG */ + test_macros_parse_one(STRV_MAKE("arg0", + "-rval1"), + (Entry[]) { + { "required", "val1" }, + {} + }, + NULL); + + /* OPTION_FULL with OPTION_OPTIONAL_ARG: long with = */ + test_macros_parse_one(STRV_MAKE("arg0", + "--optional=val1"), + (Entry[]) { + { "optional", "val1" }, + {} + }, + NULL); + + /* OPTION_FULL with OPTION_OPTIONAL_ARG: long without = doesn't consume next */ + test_macros_parse_one(STRV_MAKE("arg0", + "--optional", "pos1"), + (Entry[]) { + { "optional", NULL }, + {} + }, + STRV_MAKE("pos1")); + + /* OPTION_FULL with OPTION_OPTIONAL_ARG: short inline */ + test_macros_parse_one(STRV_MAKE("arg0", + "-oval1"), + (Entry[]) { + { "optional", "val1" }, + {} + }, + NULL); + + /* OPTION_FULL with OPTION_OPTIONAL_ARG: short without inline */ + test_macros_parse_one(STRV_MAKE("arg0", + "-o", "pos1"), + (Entry[]) { + { "optional", NULL }, + {} + }, + STRV_MAKE("pos1")); + + /* OPTION_FULL with OPTION_STOPS_PARSING: stops further option parsing */ + test_macros_parse_one(STRV_MAKE("arg0", + "--exec", + "--help", + "--version"), + (Entry[]) { + { "exec" }, + {} + }, + STRV_MAKE("--help", + "--version")); + + /* OPTION_STOPS_PARSING: options before are still parsed */ + test_macros_parse_one(STRV_MAKE("arg0", + "--help", + "--exec", + "-h", + "--debug"), + (Entry[]) { + { "help" }, + { "exec" }, + {} + }, + STRV_MAKE("-h", + "--debug")); + + /* OPTION_STOPS_PARSING with "--": "--" after exec is still consumed */ + test_macros_parse_one(STRV_MAKE("arg0", + "--exec", + "--", + "--help"), + (Entry[]) { + { "exec" }, + {} + }, + STRV_MAKE("--help")); + + /* OPTION_STOPS_PARSING with "--": "--" before exec takes precedence */ + test_macros_parse_one(STRV_MAKE("arg0", + "--", + "--exec", + "--help"), + (Entry[]) { + {} + }, + STRV_MAKE("--exec", + "--help")); + + /* OPTION_GROUP: group marker is transparent to parsing, --debug in Advanced group works */ + test_macros_parse_one(STRV_MAKE("arg0", + "--debug"), + (Entry[]) { + { "debug" }, + {} + }, + NULL); + + /* Mixed: all macro types together */ + test_macros_parse_one(STRV_MAKE("arg0", + "pos1", + "-h", + "--version", + "-v", + "--required=rval", + "--optional=oval", + "--debug", + "pos2", + "-o", + "--help"), + (Entry[]) { + { "help" }, + { "version" }, + { .short_code = 'v' }, + { "required", "rval" }, + { "optional", "oval" }, + { "debug" }, + { "optional", NULL }, + { "help" }, + {} + }, + STRV_MAKE("pos1", + "pos2")); + + /* Short option combos with macros: -hv (help + verbose) */ + test_macros_parse_one(STRV_MAKE("arg0", + "-hv"), + (Entry[]) { + { "help" }, + { .short_code = 'v' }, + {} + }, + NULL); + + /* Short option combo with required arg: -hrval (help + required with arg "val") */ + test_macros_parse_one(STRV_MAKE("arg0", + "-hrval"), + (Entry[]) { + { "help" }, + { "required", "val" }, + {} + }, + NULL); + + /* Short option combo with optional arg: -hoval (help + optional with arg "val") */ + test_macros_parse_one(STRV_MAKE("arg0", + "-hoval"), + (Entry[]) { + { "help" }, + { "optional", "val" }, + {} + }, + NULL); + + /* OPTION_STOPS_PARSING then "--": "--" is still consumed after exec */ + test_macros_parse_one(STRV_MAKE("arg0", + "--help", + "--exec", + "--version", + "--", + "-h"), + (Entry[]) { + { "help" }, + { "exec" }, + {} + }, + STRV_MAKE("--version", + "-h")); +} + DEFINE_TEST_MAIN(LOG_DEBUG); From e13b7872e047b15c0cf3092f1317ed019d17c990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 19 Mar 2026 18:13:41 +0100 Subject: [PATCH 0412/1296] options: only consume "--" immediately after an option that stops parsing The behaviour that was implemented in systemd-dissect was that both '--exec -- cmd' and '--exec cmd' result in 'cmd' as the command, and '--' anywhere later is as a positional argument, so nesting is possible, e.g.: --exec -- cmd --opt -- another-cmd --another-opt This is not obvious, so add some tests for this and keep it as a separate commit. --- src/shared/options.c | 6 ++++-- src/test/test-options.c | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/shared/options.c b/src/shared/options.c index 09b677f813312..903ed62440fd1 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -112,8 +112,10 @@ int option_parse( return 0; } - if (!state->parsing_stopped && - argv[state->optind][0] == '-' && + if (state->parsing_stopped) + return 0; + + if (argv[state->optind][0] == '-' && argv[state->optind][1] != '\0') /* Looks like we found an option parameter */ break; diff --git a/src/test/test-options.c b/src/test/test-options.c index 6ca29fb849ab9..ab3a42936958c 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -977,6 +977,21 @@ TEST(option_macros) { NULL); /* OPTION_STOPS_PARSING then "--": "--" is still consumed after exec */ + test_macros_parse_one(STRV_MAKE("arg0", + "--help", + "--exec", + "--", + "--version", + "-h"), + (Entry[]) { + { "help" }, + { "exec" }, + {} + }, + STRV_MAKE("--version", + "-h")); + + /* OPTION_STOPS_PARSING then later "--": "--" is not consumed */ test_macros_parse_one(STRV_MAKE("arg0", "--help", "--exec", @@ -989,6 +1004,24 @@ TEST(option_macros) { {} }, STRV_MAKE("--version", + "--", + "-h")); + + /* OPTION_STOPS_PARSING then "--" twice: second "--" is not consumed */ + test_macros_parse_one(STRV_MAKE("arg0", + "--help", + "--exec", + "--", + "--", + "--version", + "-h"), + (Entry[]) { + { "help" }, + { "exec" }, + {} + }, + STRV_MAKE("--", + "--version", "-h")); } From cefec0b67d93eb291cbd6210731ae9af84715aaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 18 Mar 2026 17:38:47 +0100 Subject: [PATCH 0413/1296] id128: use the new option parser --version was not documented. Fixup for 0d1d512f7f42071595f0c950f911f3557fda09ea. --- src/id128/id128.c | 142 +++++++++++++++++----------------------------- 1 file changed, 52 insertions(+), 90 deletions(-) diff --git a/src/id128/id128.c b/src/id128/id128.c index fecfeeabab6d1..23a028b28ca7c 100644 --- a/src/id128/id128.c +++ b/src/id128/id128.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" @@ -11,6 +10,7 @@ #include "id128-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" #include "string-util.h" @@ -24,10 +24,12 @@ static PagerFlags arg_pager_flags = 0; static bool arg_legend = true; static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF; +VERB_NOARG(verb_new, "new", "Generate a new ID"); static int verb_new(int argc, char *argv[], uintptr_t _data, void *userdata) { return id128_print_new(arg_mode); } +VERB_NOARG(verb_machine_id, "machine-id", "Print the ID of current machine"); static int verb_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_id128_t id; int r; @@ -43,6 +45,7 @@ static int verb_machine_id(int argc, char *argv[], uintptr_t _data, void *userda return id128_pretty_print(id, arg_mode); } +VERB_NOARG(verb_boot_id, "boot-id", "Print the ID of current boot"); static int verb_boot_id(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_id128_t id; int r; @@ -58,6 +61,7 @@ static int verb_boot_id(int argc, char *argv[], uintptr_t _data, void *userdata) return id128_pretty_print(id, arg_mode); } +VERB_NOARG(verb_invocation_id, "invocation-id", "Print the ID of current invocation"); static int verb_invocation_id(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_id128_t id; int r; @@ -73,6 +77,7 @@ static int verb_invocation_id(int argc, char *argv[], uintptr_t _data, void *use return id128_pretty_print(id, arg_mode); } +VERB_NOARG(verb_var_uuid, "var-partition-uuid", "Print the UUID for the /var/ partition"); static int verb_var_uuid(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_id128_t id; int r; @@ -130,6 +135,7 @@ static int show_one(Table **table, const char *name, sd_id128_t uuid, bool first arg_mode == ID128_PRINT_ID128 ? TABLE_ID128 : TABLE_UUID, uuid); } +VERB(verb_show, "show", "[NAME|UUID]", VERB_ANY, VERB_ANY, 0, "Print one or more UUIDs"); static int verb_show(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; int r; @@ -186,158 +192,114 @@ static int verb_show(int argc, char *argv[], uintptr_t _data, void *userdata) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-id128", "1", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + printf("%s [OPTIONS...] COMMAND\n\n" "%sGenerate and print 128-bit identifiers.%s\n" - "\nCommands:\n" - " new Generate a new ID\n" - " machine-id Print the ID of current machine\n" - " boot-id Print the ID of current boot\n" - " invocation-id Print the ID of current invocation\n" - " var-partition-uuid Print the UUID for the /var/ partition\n" - " show [NAME|UUID] Print one or more UUIDs\n" - " help Show this help\n" - "\nOptions:\n" - " -h --help Show this help\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " --json=FORMAT Output inspection data in JSON (takes one of\n" - " pretty, short, off)\n" - " -j Equivalent to --json=pretty (on TTY) or\n" - " --json=short (otherwise)\n" - " -p --pretty Generate samples of program code\n" - " -P --value Only print the value\n" - " -a --app-specific=ID Generate app-specific IDs\n" - " -u --uuid Output in UUID format\n" - "\nSee the %s for details.\n", + "\nCommands:\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + table_print(verbs, stdout); + printf("\nOptions:\n"); + table_print(options, stdout); + + printf("\nSee the %s for details.\n", link); return 0; } -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} +VERB_COMMON_HELP(help); -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_JSON, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "json", required_argument, NULL, ARG_JSON }, - { "pretty", no_argument, NULL, 'p' }, - { "value", no_argument, NULL, 'P' }, - { "app-specific", required_argument, NULL, 'a' }, - { "uuid", no_argument, NULL, 'u' }, - {}, - }; - - int c, r; +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hpa:uPj", options, NULL)) >= 0) - switch (c) { + OptionParser state = {}; + const char *arg; - case 'h': + FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) + switch (c) { + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_NO_LEGEND: + OPTION_COMMON_NO_LEGEND: arg_legend = false; break; - case 'j': - arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; - break; - - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_LONG("json", "FORMAT", + "Output inspection data in JSON (takes one of pretty, short, off)"): + r = parse_json_argument(arg, &arg_json_format_flags); if (r <= 0) return r; + break; + OPTION_SHORT('j', NULL, + "Equivalent to --json=pretty (on TTY) or --json=short (otherwise)"): + arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; break; - case 'p': + + OPTION('p', "pretty", NULL, "Generate samples of program code"): arg_mode = ID128_PRINT_PRETTY; arg_value = false; break; - case 'P': + OPTION('P', "value", NULL, "Only print the value"): arg_value = true; if (arg_mode == ID128_PRINT_PRETTY) arg_mode = ID128_PRINT_ID128; break; - case 'a': - r = id128_from_string_nonzero(optarg, &arg_app); + OPTION('a', "app-specific", "ID", "Generate app-specific IDs"): + r = id128_from_string_nonzero(arg, &arg_app); if (r == -ENXIO) return log_error_errno(r, "Application ID cannot be all zeros."); if (r < 0) - return log_error_errno(r, "Failed to parse \"%s\" as application-ID: %m", optarg); + return log_error_errno(r, "Failed to parse \"%s\" as application ID: %m", arg); break; - case 'u': + OPTION('u', "uuid", NULL, "Output in UUID format"): arg_mode = ID128_PRINT_UUID; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state, argc, argv); return 1; } -static int id128_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "new", VERB_ANY, 1, 0, verb_new }, - { "machine-id", VERB_ANY, 1, 0, verb_machine_id }, - { "boot-id", VERB_ANY, 1, 0, verb_boot_id }, - { "invocation-id", VERB_ANY, 1, 0, verb_invocation_id }, - { "var-partition-uuid", VERB_ANY, 1, 0, verb_var_uuid }, - { "show", VERB_ANY, VERB_ANY, 0, verb_show }, - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { int r; log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; /* unnecessary initialization to appease gcc <= 13 */ + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return id128_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); From 34fba9e561d78307e7452303dda33a1fd2b36974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 27 Feb 2026 11:35:38 +0100 Subject: [PATCH 0414/1296] dissect: use the new option parser Some cosmetic differences in descriptions are made. --quiet was miscategorized as a command (172fadda65a788c6128e5c93e0c530244c6a42e2), --usr-hash and --usr-hash-sig were not listed (10b8d65f3f37f169fdc3999ff86e5fdbdbd0e3a5). --- src/dissect/dissect.c | 574 +++++++++++++++--------------------------- 1 file changed, 208 insertions(+), 366 deletions(-) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index aafbd872ae713..8ede2f61a96d6 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -42,6 +42,7 @@ #include "mountpoint-util.h" #include "namespace-util.h" #include "nsresource.h" +#include "options.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" @@ -122,6 +123,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_image_filter, image_filter_freep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *commands = NULL; int r; pager_open(arg_pager_flags); @@ -130,6 +132,14 @@ static int help(void) { if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + r = option_parser_get_help_table_group("Commands", &commands); + if (r < 0) + return r; + printf("%1$s [OPTIONS...] IMAGE\n" "%1$s [OPTIONS...] --mount IMAGE PATH\n" "%1$s [OPTIONS...] --umount PATH\n" @@ -144,110 +154,22 @@ static int help(void) { "%1$s [OPTIONS...] --discover\n" "%1$s [OPTIONS...] --validate IMAGE\n" "%1$s [OPTIONS...] --shift IMAGE UIDBASE\n" - "\n%5$sDissect a Discoverable Disk Image (DDI).%6$s\n\n" - "%3$sOptions:%4$s\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " -r --read-only Mount read-only\n" - " --fsck=BOOL Run fsck before mounting\n" - " --growfs=BOOL Grow file system to partition size, if marked\n" - " --mkdir Make mount directory before mounting, if missing\n" - " --rmdir Remove mount directory after unmounting\n" - " --discard=MODE Choose 'discard' mode (disabled, loop, all, crypto)\n" - " --in-memory Copy image into memory\n" - " --root-hash=HASH Specify root hash for verity\n" - " --root-hash-sig=SIG Specify pkcs7 signature of root hash for verity\n" - " as a DER encoded PKCS7, either as a path to a file\n" - " or as an ASCII base64 encoded string prefixed by\n" - " 'base64:'\n" - " --verity-data=PATH Specify data file with hash tree for verity if it is\n" - " not embedded in IMAGE\n" - " --image-policy=POLICY\n" - " Specify image dissection policy\n" - " --image-filter=FILTER\n" - " Specify image dissection filter\n" - " --json=pretty|short|off\n" - " Generate JSON output\n" - " --loop-ref=NAME Set reference string for loopback device\n" - " --loop-ref-auto Derive reference string from image file name\n" - " --mtree-hash=BOOL Whether to include SHA256 hash in the mtree output\n" - " --copy-ownership=BOOL\n" - " Whether to copy ownership when copying files\n" - " --user Discover user images\n" - " --system Discover system images\n" - " --all Show hidden images too\n" - "\n%3$sCommands:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -m --mount Mount the image to the specified directory\n" - " -M Shortcut for --mount --mkdir\n" - " -u --umount Unmount the image from the specified directory\n" - " -U Shortcut for --umount --rmdir\n" - " --attach Attach the disk image to a loopback block device\n" - " --detach Detach a loopback block device again\n" - " -l --list List all the files and directories of the specified\n" - " OS image\n" - " --mtree Show BSD mtree manifest of OS image\n" - " --with Mount, run command, unmount\n" - " -x --copy-from Copy files from image to host\n" - " -a --copy-to Copy files from host to image\n" - " --make-archive Convert the DDI to an archive file\n" - " --discover Discover DDIs in well known directories\n" - " --validate Validate image and image policy\n" - " --shift Shift UID range to selected base\n" - " -q --quiet Suppress output of chosen loopback block device\n" - "\nSee the %2$s for details.\n", + "\n%2$sDissect a Discoverable Disk Image (DDI).%3$s\n" + "\n%4$sOptions:%5$s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); - return 0; -} - -static int patch_argv(int *argc, char ***argv, char ***buf) { - _cleanup_free_ char **l = NULL; - char **e; - - assert(argc); - assert(*argc >= 0); - assert(argv); - assert(*argv); - assert(buf); - - /* Ugly hack: if --with is included in command line, also insert "--" immediately after it, to make - * getopt_long() stop processing switches */ - - for (e = *argv + 1; e < *argv + *argc; e++) { - assert(*e); - - if (streq(*e, "--with")) - break; - } - - if (e >= *argv + *argc || streq_ptr(e[1], "--")) { - /* No --with used? Or already followed by "--"? Then don't do anything */ - *buf = NULL; - return 0; - } - - /* Insert the extra "--" right after the --with */ - l = new(char*, *argc + 2); - if (!l) - return log_oom(); + table_print(options, stdout); - size_t idx = e - *argv + 1; - memcpy(l, *argv, sizeof(char*) * idx); /* copy everything up to and including the --with */ - l[idx] = (char*) "--"; /* insert "--" */ - memcpy(l + idx + 1, e + 1, sizeof(char*) * (*argc - idx + 1)); /* copy the rest, including trailing NULL entry */ + printf("\n%sCommands:%s\n", ansi_underline(), ansi_normal()); - (*argc)++; - (*argv) = l; + table_print(commands, stdout); - *buf = TAKE_PTR(l); - return 1; + printf("\nSee the %s for details.\n", link); + return 0; } static int parse_image_path_argument(const char *path, char **ret_root, char **ret_image) { @@ -276,190 +198,51 @@ static int parse_image_path_argument(const char *path, char **ret_root, char **r } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_WITH, - ARG_DISCARD, - ARG_FSCK, - ARG_GROWFS, - ARG_ROOT_HASH, - ARG_ROOT_HASH_SIG, - ARG_USR_HASH, - ARG_USR_HASH_SIG, - ARG_VERITY_DATA, - ARG_MKDIR, - ARG_RMDIR, - ARG_IN_MEMORY, - ARG_JSON, - ARG_MTREE, - ARG_DISCOVER, - ARG_ATTACH, - ARG_DETACH, - ARG_LOOP_REF, - ARG_LOOP_REF_AUTO, - ARG_IMAGE_POLICY, - ARG_VALIDATE, - ARG_MTREE_HASH, - ARG_MAKE_ARCHIVE, - ARG_SHIFT, - ARG_SYSTEM, - ARG_USER, - ARG_ALL, - ARG_IMAGE_FILTER, - ARG_COPY_OWNERSHIP, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "mount", no_argument, NULL, 'm' }, - { "umount", no_argument, NULL, 'u' }, - { "attach", no_argument, NULL, ARG_ATTACH }, - { "detach", no_argument, NULL, ARG_DETACH }, - { "with", no_argument, NULL, ARG_WITH }, - { "read-only", no_argument, NULL, 'r' }, - { "discard", required_argument, NULL, ARG_DISCARD }, - { "fsck", required_argument, NULL, ARG_FSCK }, - { "growfs", required_argument, NULL, ARG_GROWFS }, - { "root-hash", required_argument, NULL, ARG_ROOT_HASH }, - { "root-hash-sig", required_argument, NULL, ARG_ROOT_HASH_SIG }, - { "usr-hash", required_argument, NULL, ARG_USR_HASH }, - { "usr-hash-sig", required_argument, NULL, ARG_USR_HASH_SIG }, - { "verity-data", required_argument, NULL, ARG_VERITY_DATA }, - { "mkdir", no_argument, NULL, ARG_MKDIR }, - { "rmdir", no_argument, NULL, ARG_RMDIR }, - { "in-memory", no_argument, NULL, ARG_IN_MEMORY }, - { "list", no_argument, NULL, 'l' }, - { "mtree", no_argument, NULL, ARG_MTREE }, - { "copy-from", no_argument, NULL, 'x' }, - { "copy-to", no_argument, NULL, 'a' }, - { "json", required_argument, NULL, ARG_JSON }, - { "discover", no_argument, NULL, ARG_DISCOVER }, - { "loop-ref", required_argument, NULL, ARG_LOOP_REF }, - { "loop-ref-auto", no_argument, NULL, ARG_LOOP_REF_AUTO }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "validate", no_argument, NULL, ARG_VALIDATE }, - { "mtree-hash", required_argument, NULL, ARG_MTREE_HASH }, - { "make-archive", no_argument, NULL, ARG_MAKE_ARCHIVE }, - { "shift", no_argument, NULL, ARG_SHIFT }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - { "all", no_argument, NULL, ARG_ALL }, - { "quiet", no_argument, NULL, 'q' }, - { "image-filter", required_argument, NULL, ARG_IMAGE_FILTER }, - { "copy-ownership", required_argument, NULL, ARG_COPY_OWNERSHIP }, - {} - }; - - _cleanup_free_ char **buf = NULL; /* we use free(), not strv_free() here, as we don't copy the strings here */ bool system_scope_requested = false, user_scope_requested = false; - int c, r; + int r; assert(argc >= 0); assert(argv); - r = patch_argv(&argc, &argv, &buf); - if (r < 0) - return r; - - while ((c = getopt_long(argc, argv, "hmurMUlxaq", options, NULL)) >= 0) { + OptionParser state = {}; + const Option *current; + const char *arg; + FOREACH_OPTION_FULL(&state, c, argc, argv, ¤t, &arg, /* on_error= */ return c) switch (c) { - case 'h': - return help(); - - case ARG_VERSION: - return version(); - - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_NO_LEGEND: + OPTION_COMMON_NO_LEGEND: arg_legend = false; break; - case 'm': - arg_action = ACTION_MOUNT; - break; - - case ARG_MKDIR: - arg_flags |= DISSECT_IMAGE_MKDIR; - break; - - case 'M': - /* Shortcut combination of the above two */ - arg_action = ACTION_MOUNT; + OPTION_LONG("mkdir", NULL, "Make mount directory before mounting, if missing"): arg_flags |= DISSECT_IMAGE_MKDIR; break; - case 'u': - arg_action = ACTION_UMOUNT; - break; - - case ARG_RMDIR: - arg_rmdir = true; - break; - - case 'U': - /* Shortcut combination of the above two */ - arg_action = ACTION_UMOUNT; + OPTION_LONG("rmdir", NULL, "Remove mount directory after unmounting"): arg_rmdir = true; break; - case ARG_ATTACH: - arg_action = ACTION_ATTACH; - break; - - case ARG_DETACH: - arg_action = ACTION_DETACH; - break; - - case 'l': - arg_action = ACTION_LIST; - arg_flags |= DISSECT_IMAGE_READ_ONLY; - break; - - case ARG_MTREE: - arg_action = ACTION_MTREE; - arg_flags |= DISSECT_IMAGE_READ_ONLY; - break; - - case ARG_WITH: - arg_action = ACTION_WITH; - break; - - case 'x': - arg_action = ACTION_COPY_FROM; - arg_flags |= DISSECT_IMAGE_READ_ONLY; - break; - - case 'a': - arg_action = ACTION_COPY_TO; - break; - - case 'r': + OPTION('r', "read-only", NULL, "Mount read-only"): arg_flags |= DISSECT_IMAGE_READ_ONLY; break; - case ARG_DISCARD: { + OPTION_LONG("discard", "MODE", "Choose discard mode (disabled, loop, all, crypto)"): { DissectImageFlags flags; - if (streq(optarg, "disabled")) + if (streq(arg, "disabled")) flags = 0; - else if (streq(optarg, "loop")) + else if (streq(arg, "loop")) flags = DISSECT_IMAGE_DISCARD_ON_LOOP; - else if (streq(optarg, "all")) + else if (streq(arg, "all")) flags = DISSECT_IMAGE_DISCARD_ON_LOOP | DISSECT_IMAGE_DISCARD; - else if (streq(optarg, "crypt")) + else if (streq(arg, "crypt")) flags = DISSECT_IMAGE_DISCARD_ANY; - else if (streq(optarg, "list")) { + else if (streq(arg, "list")) { puts("disabled\n" "all\n" "crypt\n" @@ -467,32 +250,32 @@ static int parse_argv(int argc, char *argv[]) { return 0; } else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown --discard= parameter: %s", - optarg); + "Unknown --discard= parameter: %s", arg); arg_flags = (arg_flags & ~DISSECT_IMAGE_DISCARD_ANY) | flags; break; } - case ARG_IN_MEMORY: + OPTION_LONG("in-memory", NULL, "Copy image into memory"): arg_in_memory = true; break; - case ARG_ROOT_HASH: - case ARG_USR_HASH: { + OPTION_LONG("root-hash", "HASH", "Specify root hash for verity"): + OPTION_LONG("usr-hash", "HASH", "Same, but for the usr partition"): { _cleanup_(iovec_done) struct iovec roothash = {}; - PartitionDesignator d = c == ARG_USR_HASH ? PARTITION_USR : PARTITION_ROOT; + PartitionDesignator d = streq(current->long_code, "root-hash") ? PARTITION_ROOT : PARTITION_USR; if (arg_verity_settings.designator >= 0 && arg_verity_settings.designator != d) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot combine --root-hash=/--root-hash-sig= and --usr-hash=/--usr-hash-sig= options."); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Cannot combine --root-hash=/--root-hash-sig= and --usr-hash=/--usr-hash-sig= options."); - r = unhexmem(optarg, &roothash.iov_base, &roothash.iov_len); + r = unhexmem(arg, &roothash.iov_base, &roothash.iov_len); if (r < 0) - return log_error_errno(r, "Failed to parse root hash '%s': %m", optarg); + return log_error_errno(r, "Failed to parse root hash '%s': %m", arg); if (roothash.iov_len < sizeof(sd_id128_t)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Root hash must be at least 128-bit long: %s", optarg); + "Root hash must be at least 128-bit long: %s", arg); iovec_done(&arg_verity_settings.root_hash); arg_verity_settings.root_hash = TAKE_STRUCT(roothash); @@ -500,24 +283,28 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_ROOT_HASH_SIG: - case ARG_USR_HASH_SIG: { - char *value; + OPTION_LONG("root-hash-sig", "SIG", + "Specify signature of root hash for verity as DER-encoded PKCS7, " + "either as a path to a file or as an ASCII base64-encoded string " + "prefixed by 'base64:'"): + OPTION_LONG("usr-hash-sig", "SIG", "Same, but for the usr partition"): { + const char *value; _cleanup_(iovec_done) struct iovec sig = {}; - PartitionDesignator d = c == ARG_USR_HASH_SIG ? PARTITION_USR : PARTITION_ROOT; + PartitionDesignator d = streq(current->long_code, "root-hash-sig") ? PARTITION_ROOT : PARTITION_USR; if (arg_verity_settings.designator >= 0 && arg_verity_settings.designator != d) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot combine --root-hash=/--root-hash-sig= and --usr-hash=/--usr-hash-sig= options."); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Cannot combine --root-hash=/--root-hash-sig= and --usr-hash=/--usr-hash-sig= options."); - if ((value = startswith(optarg, "base64:"))) { + if ((value = startswith(arg, "base64:"))) { r = unbase64mem(value, &sig.iov_base, &sig.iov_len); if (r < 0) - return log_error_errno(r, "Failed to parse root hash signature '%s': %m", optarg); + return log_error_errno(r, "Failed to parse root hash signature '%s': %m", arg); } else { - r = read_full_file(optarg, (char**) &sig.iov_base, &sig.iov_len); + r = read_full_file(arg, (char**) &sig.iov_base, &sig.iov_len); if (r < 0) - return log_error_errno(r, "Failed to read root hash signature file '%s': %m", optarg); + return log_error_errno(r, "Failed to read root hash signature file '%s': %m", arg); } iovec_done(&arg_verity_settings.root_hash_sig); @@ -526,142 +313,195 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_VERITY_DATA: - r = parse_path_argument(optarg, false, &arg_verity_settings.data_path); + OPTION_LONG("verity-data", "PATH", + "Specify data file with hash tree for verity if it is not embedded in IMAGE"): + r = parse_path_argument(arg, false, &arg_verity_settings.data_path); if (r < 0) return r; break; - case ARG_FSCK: - r = parse_boolean(optarg); + OPTION_LONG("fsck", "BOOL", "Run fsck before mounting"): + r = parse_boolean(arg); if (r < 0) - return log_error_errno(r, "Failed to parse --fsck= parameter: %s", optarg); + return log_error_errno(r, "Failed to parse --fsck= parameter: %s", arg); SET_FLAG(arg_flags, DISSECT_IMAGE_FSCK, r); break; - case ARG_GROWFS: - r = parse_boolean(optarg); + OPTION_LONG("growfs", "BOOL", "Grow file system to partition size, if marked"): + r = parse_boolean(arg); if (r < 0) - return log_error_errno(r, "Failed to parse --growfs= parameter: %s", optarg); + return log_error_errno(r, "Failed to parse --growfs= parameter: %s", arg); SET_FLAG(arg_flags, DISSECT_IMAGE_GROWFS, r); break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); if (r <= 0) return r; - break; - case ARG_DISCOVER: - arg_action = ACTION_DISCOVER; - break; - - case ARG_LOOP_REF: - if (isempty(optarg)) { + OPTION_LONG("loop-ref", "NAME", "Set reference string for loopback device"): + if (isempty(arg)) { arg_loop_ref = mfree(arg_loop_ref); arg_loop_ref_auto = false; break; } - if (strlen(optarg) >= sizeof_field(struct loop_info64, lo_file_name)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Loop device ref string '%s' is too long.", optarg); + if (strlen(arg) >= sizeof_field(struct loop_info64, lo_file_name)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Loop device ref string '%s' is too long.", arg); - r = free_and_strdup_warn(&arg_loop_ref, optarg); + r = free_and_strdup_warn(&arg_loop_ref, arg); if (r < 0) return r; arg_loop_ref_auto = false; break; - case ARG_LOOP_REF_AUTO: + OPTION_LONG("loop-ref-auto", NULL, "Derive reference string from image file name"): arg_loop_ref = mfree(arg_loop_ref); arg_loop_ref_auto = true; break; - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); + OPTION_LONG("image-policy", "POLICY", "Specify image dissection policy"): + r = parse_image_policy_argument(arg, &arg_image_policy); if (r < 0) return r; break; - case ARG_VALIDATE: - arg_action = ACTION_VALIDATE; - break; - - case ARG_MTREE_HASH: - r = parse_boolean_argument("--mtree-hash=", optarg, &arg_mtree_hash); + OPTION_LONG("mtree-hash", "BOOL", "Whether to include SHA256 hash in the mtree output"): + r = parse_boolean_argument("--mtree-hash=", arg, &arg_mtree_hash); if (r < 0) return r; break; - case ARG_MAKE_ARCHIVE: - r = dlopen_libarchive(); - if (r < 0) - return log_error_errno(r, "Archive support not available (compiled without libarchive, or libarchive not installed?)."); - - arg_action = ACTION_MAKE_ARCHIVE; - break; - - case ARG_SHIFT: - arg_action = ACTION_SHIFT; - break; - - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Discover system images"): system_scope_requested = true; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Discover user images"): user_scope_requested = true; break; - case ARG_ALL: + OPTION_LONG("all", NULL, "Show hidden images too"): arg_all = true; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress output of chosen loopback block device"): arg_quiet = true; break; - case ARG_IMAGE_FILTER: { + OPTION_LONG("image-filter", "FILTER", "Specify image dissection filter"): { _cleanup_(image_filter_freep) ImageFilter *f = NULL; - r = image_filter_parse(optarg, &f); + r = image_filter_parse(arg, &f); if (r < 0) - return log_error_errno(r, "Failed to parse image filter expression: %s", optarg); + return log_error_errno(r, "Failed to parse image filter expression: %s", arg); image_filter_free(arg_image_filter); arg_image_filter = TAKE_PTR(f); break; } - case ARG_COPY_OWNERSHIP: - r = parse_tristate_argument_with_auto("--copy-ownership=", optarg, &arg_copy_ownership); + OPTION_LONG("copy-ownership", "BOOL", "Whether to copy ownership when copying files"): + r = parse_tristate_argument_with_auto("--copy-ownership=", arg, &arg_copy_ownership); if (r < 0) return r; break; - case '?': - return -EINVAL; + /************************************ Commands ***************************************/ + OPTION_GROUP("Commands"): - default: - assert_not_reached(); + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION('m', "mount", NULL, "Mount the image to the specified directory"): + arg_action = ACTION_MOUNT; + break; + + OPTION_SHORT('M', NULL, "Shortcut for --mount --mkdir"): + arg_action = ACTION_MOUNT; + arg_flags |= DISSECT_IMAGE_MKDIR; + break; + + OPTION('u', "umount", NULL, "Unmount the image from the specified directory"): + arg_action = ACTION_UMOUNT; + break; + + OPTION_SHORT('U', NULL, "Shortcut for --umount --rmdir"): + arg_action = ACTION_UMOUNT; + arg_rmdir = true; + break; + + OPTION_LONG("attach", NULL, "Attach the disk image to a loopback block device"): + arg_action = ACTION_ATTACH; + break; + + OPTION_LONG("detach", NULL, "Detach a loopback block device again"): + arg_action = ACTION_DETACH; + break; + + OPTION('l', "list", NULL, "List all the files and directories of the specified OS image"): + arg_action = ACTION_LIST; + arg_flags |= DISSECT_IMAGE_READ_ONLY; + break; + + OPTION_LONG("mtree", NULL, "Show BSD mtree manifest of OS image"): + arg_action = ACTION_MTREE; + arg_flags |= DISSECT_IMAGE_READ_ONLY; + break; + + OPTION_FULL(OPTION_STOPS_PARSING, /* sc= */ 0, "with", NULL, "Mount, run command, unmount"): + arg_action = ACTION_WITH; + break; + + OPTION('x', "copy-from", NULL, "Copy files from image to host"): + arg_action = ACTION_COPY_FROM; + arg_flags |= DISSECT_IMAGE_READ_ONLY; + break; + + OPTION('a', "copy-to", NULL, "Copy files from host to image"): + arg_action = ACTION_COPY_TO; + break; + + OPTION_LONG("make-archive", NULL, "Convert the DDI to an archive file"): + r = dlopen_libarchive(); + if (r < 0) + return log_error_errno(r, "Archive support not available (compiled without libarchive, or libarchive not installed?)."); + + arg_action = ACTION_MAKE_ARCHIVE; + break; + + OPTION_LONG("discover", NULL, "Discover DDIs in well known directories"): + arg_action = ACTION_DISCOVER; + break; + + OPTION_LONG("validate", NULL, "Validate image and image policy"): + arg_action = ACTION_VALIDATE; + break; + + OPTION_LONG("shift", NULL, "Shift UID range to selected base"): + arg_action = ACTION_SHIFT; + break; } - } if (system_scope_requested || user_scope_requested) arg_runtime_scope = system_scope_requested && user_scope_requested ? _RUNTIME_SCOPE_INVALID : system_scope_requested ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER; + char **args = option_parser_get_args(&state, argc, argv); + switch (arg_action) { case ACTION_DISSECT: - if (optind + 1 != argc) + if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file path as only argument."); - r = parse_image_path_argument(argv[optind], NULL, &arg_image); + r = parse_image_path_argument(args[0], NULL, &arg_image); if (r < 0) return r; @@ -670,15 +510,15 @@ static int parse_argv(int argc, char *argv[]) { break; case ACTION_MOUNT: - if (optind + 2 != argc) + if (strv_length(args) != 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file path and mount point path as only arguments."); - r = parse_image_path_argument(argv[optind], NULL, &arg_image); + r = parse_image_path_argument(args[0], NULL, &arg_image); if (r < 0) return r; - r = parse_path_argument(argv[optind+1], /* suppress_root= */ false, &arg_path); + r = parse_path_argument(args[1], /* suppress_root= */ false, &arg_path); if (r < 0) return r; @@ -686,42 +526,42 @@ static int parse_argv(int argc, char *argv[]) { break; case ACTION_UMOUNT: - if (optind + 1 != argc) + if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a mount point path as only argument."); - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_path); + r = parse_path_argument(args[0], /* suppress_root= */ false, &arg_path); if (r < 0) return r; break; case ACTION_ATTACH: - if (optind + 1 != argc) + if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file path as only argument."); - r = parse_image_path_argument(argv[optind], NULL, &arg_image); + r = parse_image_path_argument(args[0], NULL, &arg_image); if (r < 0) return r; break; case ACTION_DETACH: - if (optind + 1 != argc) + if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file path or loopback device as only argument."); - r = parse_image_path_argument(argv[optind], NULL, &arg_image); + r = parse_image_path_argument(args[0], NULL, &arg_image); if (r < 0) return r; break; case ACTION_LIST: case ACTION_MTREE: - if (optind + 1 != argc) + if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file or directory path as only argument."); - r = parse_image_path_argument(argv[optind], &arg_root, &arg_image); + r = parse_image_path_argument(args[0], &arg_root, &arg_image); if (r < 0) return r; @@ -729,63 +569,63 @@ static int parse_argv(int argc, char *argv[]) { break; case ACTION_MAKE_ARCHIVE: - if (argc < optind + 1 || argc > optind + 2) + if (!IN_SET(strv_length(args), 1, 2)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file, and an optional target path as only arguments."); - r = parse_image_path_argument(argv[optind], &arg_root, &arg_image); + r = parse_image_path_argument(args[0], &arg_root, &arg_image); if (r < 0) return r; - arg_target = argc > optind + 1 ? empty_or_dash_to_null(argv[optind + 1]) : NULL; + arg_target = empty_or_dash_to_null(args[1]); arg_flags |= DISSECT_IMAGE_READ_ONLY | DISSECT_IMAGE_REQUIRE_ROOT; break; case ACTION_COPY_FROM: - if (argc < optind + 2 || argc > optind + 3) + if (!IN_SET(strv_length(args), 2, 3)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file or directory path, a source path and an optional destination path as only arguments."); - r = parse_image_path_argument(argv[optind], &arg_root, &arg_image); + r = parse_image_path_argument(args[0], &arg_root, &arg_image); if (r < 0) return r; - arg_source = argv[optind + 1]; - arg_target = argc > optind + 2 ? argv[optind + 2] : "-" /* this means stdout */ ; + arg_source = args[1]; + arg_target = args[2] ?: "-"; /* this means stdout */ ; arg_flags |= DISSECT_IMAGE_READ_ONLY | DISSECT_IMAGE_REQUIRE_ROOT; break; case ACTION_COPY_TO: - if (argc < optind + 2 || argc > optind + 3) + if (!IN_SET(strv_length(args), 2, 3)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file or directory path, an optional source path and a destination path as only arguments."); - r = parse_image_path_argument(argv[optind], &arg_root, &arg_image); + r = parse_image_path_argument(args[0], &arg_root, &arg_image); if (r < 0) return r; - if (argc > optind + 2) { - arg_source = argv[optind + 1]; - arg_target = argv[optind + 2]; + if (args[2]) { + arg_source = args[1]; + arg_target = args[2]; } else { arg_source = "-"; /* this means stdin */ - arg_target = argv[optind + 1]; + arg_target = args[1]; } arg_flags |= DISSECT_IMAGE_REQUIRE_ROOT; break; case ACTION_WITH: - if (optind >= argc) + if (strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file path and an optional command line."); - r = parse_image_path_argument(argv[optind], NULL, &arg_image); + r = parse_image_path_argument(args[0], NULL, &arg_image); if (r < 0) return r; - if (argc > optind + 1) { - arg_argv = strv_copy(argv + optind + 1); + if (args[1]) { + arg_argv = strv_copy(args + 1); if (!arg_argv) return log_oom(); } @@ -793,17 +633,17 @@ static int parse_argv(int argc, char *argv[]) { break; case ACTION_DISCOVER: - if (optind != argc) + if (!strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument."); break; case ACTION_VALIDATE: - if (optind + 1 != argc) + if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file path as only argument."); - r = parse_image_path_argument(argv[optind], NULL, &arg_image); + r = parse_image_path_argument(args[0], NULL, &arg_image); if (r < 0) return r; @@ -812,27 +652,29 @@ static int parse_argv(int argc, char *argv[]) { break; case ACTION_SHIFT: - if (optind + 2 != argc) + if (strv_length(args) != 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image path and a UID base as only argument."); - r = parse_image_path_argument(argv[optind], &arg_root, &arg_image); + r = parse_image_path_argument(args[0], &arg_root, &arg_image); if (r < 0) return r; - if (streq(argv[optind + 1], "foreign")) + if (streq(args[1], "foreign")) arg_uid_base = FOREIGN_UID_BASE; else { - r = parse_uid(argv[optind + 1], &arg_uid_base); + r = parse_uid(args[1], &arg_uid_base); if (r < 0) - return log_error_errno(r, "Failed to parse UID base: %s", argv[optind + 1]); + return log_error_errno(r, "Failed to parse UID base: %s", args[1]); if ((arg_uid_base & 0xFFFF) != 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected UID base not a multiple of 64K: " UID_FMT, arg_uid_base); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Selected UID base not a multiple of 64K: " UID_FMT, arg_uid_base); if (arg_uid_base != 0 && !uid_is_container(arg_uid_base) && !uid_is_foreign(arg_uid_base)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected UID range is not in the container range, nor the foreign one, refusing."); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Selected UID range is not in the container range, nor the foreign one, refusing."); } arg_flags |= DISSECT_IMAGE_REQUIRE_ROOT; From 08f7a4865e361bfcac3c480152cbbf5e10829340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 27 Feb 2026 21:16:37 +0100 Subject: [PATCH 0415/1296] dissect: restore compat with clang clang says: src/dissect/dissect.c:292:17: warning: label followed by a declaration is a C23 extension [-Wc23-extensions] Another option would be to raise the C standard to C23. I think that'd make sense, but it's better to do it separately. --- src/dissect/dissect.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 8ede2f61a96d6..76c3f90c7e5a1 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -260,7 +260,7 @@ static int parse_argv(int argc, char *argv[]) { arg_in_memory = true; break; - OPTION_LONG("root-hash", "HASH", "Specify root hash for verity"): + OPTION_LONG("root-hash", "HASH", "Specify root hash for verity"): {} OPTION_LONG("usr-hash", "HASH", "Same, but for the usr partition"): { _cleanup_(iovec_done) struct iovec roothash = {}; @@ -286,7 +286,7 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("root-hash-sig", "SIG", "Specify signature of root hash for verity as DER-encoded PKCS7, " "either as a path to a file or as an ASCII base64-encoded string " - "prefixed by 'base64:'"): + "prefixed by 'base64:'"): {} OPTION_LONG("usr-hash-sig", "SIG", "Same, but for the usr partition"): { const char *value; _cleanup_(iovec_done) struct iovec sig = {}; @@ -410,7 +410,7 @@ static int parse_argv(int argc, char *argv[]) { break; /************************************ Commands ***************************************/ - OPTION_GROUP("Commands"): + OPTION_GROUP("Commands"): {} OPTION_COMMON_HELP: return help(); From 0e4459eb19eca4dee60513dad6ae49d4f36e83c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 27 Feb 2026 20:20:53 +0100 Subject: [PATCH 0416/1296] notify: use the new option parser Cosmetic adjustments to one help string. --- src/notify/notify.c | 200 +++++++++++++++++--------------------------- 1 file changed, 76 insertions(+), 124 deletions(-) diff --git a/src/notify/notify.c b/src/notify/notify.c index 6a39147f99e1c..cdca928f90af9 100644 --- a/src/notify/notify.c +++ b/src/notify/notify.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -16,10 +15,12 @@ #include "exit-status.h" #include "fd-util.h" #include "fdset.h" +#include "format-table.h" #include "format-util.h" #include "log.h" #include "main-func.h" #include "notify-recv.h" +#include "options.h" #include "parse-util.h" #include "pidref.h" #include "pretty-print.h" @@ -57,40 +58,28 @@ STATIC_DESTRUCTOR_REGISTER(arg_fdname, freep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-notify", "1", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...] [VARIABLE=VALUE...]\n" - "%s [OPTIONS...] --exec [VARIABLE=VALUE...] ; -- CMDLINE...\n" - "%s [OPTIONS...] --fork -- CMDLINE...\n" - "\n%sNotify the init system about service status updates.%s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --ready Inform the service manager about service start-up/reload\n" - " completion\n" - " --reloading Inform the service manager about configuration reloading\n" - " --stopping Inform the service manager about service shutdown\n" - " --pid[=PID] Set main PID of daemon\n" - " --uid=USER Set user to send from\n" - " --status=TEXT Set status text\n" - " --booted Check if the system was booted up with systemd\n" - " --no-block Do not wait until operation finished\n" - " --exec Execute command line separated by ';' once done\n" - " --fd=FD Pass specified file descriptor with along with message\n" - " --fdname=NAME Name to assign to passed file descriptor(s)\n" - " --fork Receive notifications from child rather than sending them\n" - " -q --quiet Do not show PID of child when forking\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - program_invocation_short_name, + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%1$s [OPTIONS...] [VARIABLE=VALUE...]\n" + "%1$s [OPTIONS...] --exec [VARIABLE=VALUE...] ; -- CMDLINE...\n" + "%1$s [OPTIONS...] --fork -- CMDLINE...\n" + "\n%2$sNotify the init system about service status updates.%3$s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + + table_print(options, stdout); + printf("\nSee the %s for details.\n", link); return 0; } @@ -161,123 +150,87 @@ static int pidref_parent_if_applicable(PidRef *ret) { return pidref_set_self(ret); } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_READY = 0x100, - ARG_RELOADING, - ARG_STOPPING, - ARG_VERSION, - ARG_PID, - ARG_STATUS, - ARG_BOOTED, - ARG_UID, - ARG_NO_BLOCK, - ARG_EXEC, - ARG_FD, - ARG_FDNAME, - ARG_FORK, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "ready", no_argument, NULL, ARG_READY }, - { "reloading", no_argument, NULL, ARG_RELOADING }, - { "stopping", no_argument, NULL, ARG_STOPPING }, - { "pid", optional_argument, NULL, ARG_PID }, - { "status", required_argument, NULL, ARG_STATUS }, - { "booted", no_argument, NULL, ARG_BOOTED }, - { "uid", required_argument, NULL, ARG_UID }, - { "no-block", no_argument, NULL, ARG_NO_BLOCK }, - { "exec", no_argument, NULL, ARG_EXEC }, - { "fd", required_argument, NULL, ARG_FD }, - { "fdname", required_argument, NULL, ARG_FDNAME }, - { "fork", no_argument, NULL, ARG_FORK }, - { "quiet", no_argument, NULL, 'q' }, - {} - }; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { _cleanup_fdset_free_ FDSet *passed = NULL; bool do_exec = false; - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hq", options, NULL)) >= 0) { + OptionParser state = {}; + const char *arg; + FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_READY: + OPTION_LONG("ready", NULL, + "Inform the service manager about service start-up/reload completion"): arg_ready = true; break; - case ARG_RELOADING: + OPTION_LONG("reloading", NULL, + "Inform the service manager about configuration reloading"): arg_reloading = true; break; - case ARG_STOPPING: + OPTION_LONG("stopping", NULL, + "Inform the service manager about service shutdown"): arg_stopping = true; break; - case ARG_PID: + OPTION_FULL(OPTION_OPTIONAL_ARG, /* sc= */ 0, "pid", "PID", + "Set main PID of daemon"): pidref_done(&arg_pid); - if (isempty(optarg) || streq(optarg, "auto")) + if (isempty(arg) || streq(arg, "auto")) r = pidref_parent_if_applicable(&arg_pid); - else if (streq(optarg, "parent")) + else if (streq(arg, "parent")) r = pidref_set_parent(&arg_pid); - else if (streq(optarg, "self")) + else if (streq(arg, "self")) r = pidref_set_self(&arg_pid); else - r = pidref_set_pidstr(&arg_pid, optarg); + r = pidref_set_pidstr(&arg_pid, arg); if (r < 0) - return log_error_errno(r, "Failed to refer to --pid='%s': %m", optarg); - + return log_error_errno(r, "Failed to refer to --pid='%s': %m", arg); break; - case ARG_STATUS: - arg_status = optarg; + OPTION_LONG("uid", "USER", "Set user to send from"): + r = get_user_creds(&arg, &arg_uid, &arg_gid, NULL, NULL, 0); + if (r == -ESRCH) /* If the user doesn't exist, then accept it anyway as numeric */ + r = parse_uid(arg, &arg_uid); + if (r < 0) + return log_error_errno(r, "Can't resolve user %s: %m", arg); break; - case ARG_BOOTED: - arg_action = ACTION_BOOTED; + OPTION_LONG("status", "TEXT", "Set status text"): + arg_status = arg; break; - case ARG_UID: { - const char *u = optarg; - - r = get_user_creds(&u, &arg_uid, &arg_gid, NULL, NULL, 0); - if (r == -ESRCH) /* If the user doesn't exist, then accept it anyway as numeric */ - r = parse_uid(u, &arg_uid); - if (r < 0) - return log_error_errno(r, "Can't resolve user %s: %m", optarg); - + OPTION_LONG("booted", NULL, "Check if the system was booted up with systemd"): + arg_action = ACTION_BOOTED; break; - } - case ARG_NO_BLOCK: + OPTION_LONG("no-block", NULL, "Do not wait until operation finished"): arg_no_block = true; break; - case ARG_EXEC: + OPTION_LONG("exec", NULL, "Execute command line separated by ';' once done"): do_exec = true; break; - case ARG_FD: { + OPTION_LONG("fd", "FD", "Pass specified file descriptor along with the message"): { _cleanup_close_ int owned_fd = -EBADF; - int fdnr; - fdnr = parse_fd(optarg); + int fdnr = parse_fd(arg); if (fdnr < 0) - return log_error_errno(fdnr, "Failed to parse file descriptor: %s", optarg); + return log_error_errno(fdnr, "Failed to parse file descriptor: %s", arg); if (!passed) { /* Take possession of all passed fds */ @@ -310,33 +263,28 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_FDNAME: - if (!fdname_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File descriptor name invalid: %s", optarg); + OPTION_LONG("fdname", "NAME", "Name to assign to passed file descriptors"): + if (!fdname_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File descriptor name invalid: %s", arg); - if (free_and_strdup(&arg_fdname, optarg) < 0) + if (free_and_strdup(&arg_fdname, arg) < 0) return log_oom(); break; - case ARG_FORK: + OPTION_LONG("fork", NULL, "Receive notifications from child rather than sending them"): arg_action = ACTION_FORK; break; - case 'q': + OPTION('q', "quiet", NULL, "Do not show PID of child when forking"): arg_quiet = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } bool have_env = arg_ready || arg_stopping || arg_reloading || arg_status || pidref_is_set(&arg_pid) || !fdset_isempty(arg_fds); + char **args = option_parser_get_args(&state, argc, argv); + switch (arg_action) { case ACTION_NOTIFY: { @@ -348,22 +296,22 @@ static int parse_argv(int argc, char *argv[]) { if (do_exec) { int i; - for (i = optind; i < argc; i++) - if (streq(argv[i], ";")) + for (i = 0; args[i]; i++) + if (streq(args[i], ";")) break; - if (i >= argc) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "If --exec is used argument list must contain ';' separator, refusing."); - if (i+1 == argc) + if (!args[i]) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "If --exec is used, argument list must contain ';' separator, refusing."); + if (!args[i + 1]) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty command line specified after ';' separator, refusing."); - arg_exec = strv_copy_n(argv + i + 1, argc - i - 1); + arg_exec = strv_copy(args + i + 1); if (!arg_exec) return log_oom(); - n_arg_env = i - optind; + n_arg_env = i; } else - n_arg_env = argc - optind; + n_arg_env = strv_length(args); have_env = have_env || n_arg_env > 0; if (!have_env) { @@ -376,7 +324,7 @@ static int parse_argv(int argc, char *argv[]) { } if (n_arg_env > 0) { - arg_env = strv_copy_n(argv + optind, n_arg_env); + arg_env = strv_copy_n(args, n_arg_env); if (!arg_env) return log_oom(); } @@ -388,13 +336,13 @@ static int parse_argv(int argc, char *argv[]) { } case ACTION_BOOTED: - if (argc > optind) + if (!strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--booted takes no parameters, refusing."); break; case ACTION_FORK: - if (optind >= argc) + if (strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--fork requires a command to be specified, refusing."); break; @@ -404,7 +352,10 @@ static int parse_argv(int argc, char *argv[]) { } if (have_env && arg_action != ACTION_NOTIFY) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--ready, --reloading, --stopping, --pid=, --status=, --fd= may not be combined with --fork or --booted, refusing."); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--ready, --reloading, --stopping, --pid=, --status=, --fd= may not be combined with --fork or --booted, refusing."); + + *ret_args = args; return 1; } @@ -577,16 +528,17 @@ static int run(int argc, char* argv[]) { _cleanup_strv_free_ char **final_env = NULL; const char *our_env[10]; size_t i = 0; + char **args; int r; log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; if (arg_action == ACTION_FORK) - return action_fork(argv + optind); + return action_fork(args); if (arg_action == ACTION_BOOTED) { r = sd_booted(); From 95ee4fd2cc4c3148033c802df9d4ee7d6c444308 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 27 Feb 2026 20:22:04 +0100 Subject: [PATCH 0417/1296] notify: add one more assert We tend to get those preallocated array sizes wrong. In this case, the count was correct, but add the usual assert. --- src/notify/notify.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/notify/notify.c b/src/notify/notify.c index cdca928f90af9..d17df1eafa891 100644 --- a/src/notify/notify.c +++ b/src/notify/notify.c @@ -604,6 +604,7 @@ static int run(int argc, char* argv[]) { } our_env[i++] = NULL; + assert(i <= ELEMENTSOF(our_env)); final_env = strv_env_merge((char**) our_env, arg_env); if (!final_env) From 57a1f1ab96a309f4e426db1ecfda4effb6078606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sun, 1 Mar 2026 11:16:02 +0100 Subject: [PATCH 0418/1296] bus-error: fix typo --- src/libsystemd/sd-bus/bus-error.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libsystemd/sd-bus/bus-error.h b/src/libsystemd/sd-bus/bus-error.h index 5aca67f006578..32d29b3c8ccce 100644 --- a/src/libsystemd/sd-bus/bus-error.h +++ b/src/libsystemd/sd-bus/bus-error.h @@ -47,7 +47,7 @@ const char* _bus_error_message(const sd_bus_error *e, int error, char buf[static static const sd_bus_error_map * const CONCATENATE(errors ## _copy_, __COUNTER__) = errors; /* We use something exotic as end marker, to ensure people build the - * maps using the macsd-ros. */ + * maps using the macros. */ #define BUS_ERROR_MAP_END_MARKER -'x' BUS_ERROR_MAP_ELF_USE(bus_standard_errors); From 924f5a3461512ac8126fe4423329e3ca802b4d20 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Fri, 20 Mar 2026 20:27:07 +0100 Subject: [PATCH 0419/1296] sd-varlink: gracefully reject arrays/maps with a null element Follow-up for 799392286ec0797c0a2a1260c444360b47ef36fc. --- src/libsystemd/sd-varlink/sd-varlink-idl.c | 9 +++-- src/test/test-varlink-idl.c | 38 ++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/libsystemd/sd-varlink/sd-varlink-idl.c b/src/libsystemd/sd-varlink/sd-varlink-idl.c index a70cbe529ce29..144450b702d26 100644 --- a/src/libsystemd/sd-varlink/sd-varlink-idl.c +++ b/src/libsystemd/sd-varlink/sd-varlink-idl.c @@ -1705,7 +1705,12 @@ static int varlink_idl_validate_symbol(const sd_varlink_symbol *symbol, sd_json_ static int varlink_idl_validate_field_element_type(const sd_varlink_field *field, sd_json_variant *v) { assert(field); assert(v); - assert(!sd_json_variant_is_null(v)); + + if (sd_json_variant_is_null(v)) + return varlink_idl_log( + SYNTHETIC_ERRNO(EMEDIUMTYPE), + "Field '%s' element is null, refusing.", + strna(field->name)); switch (field->field_type) { @@ -1767,7 +1772,7 @@ static int varlink_idl_validate_field_element_type(const sd_varlink_field *field case SD_VARLINK_ANY: /* The any type accepts any non-null JSON value, no validation needed. (Note that null is - * already handled by the caller.) */ + * already gracefully rejected at the start of this function.) */ break; case _SD_VARLINK_FIELD_COMMENT: diff --git a/src/test/test-varlink-idl.c b/src/test/test-varlink-idl.c index 039d36a85e42d..469bf8c2fe08e 100644 --- a/src/test/test-varlink-idl.c +++ b/src/test/test-varlink-idl.c @@ -581,4 +581,42 @@ TEST(any) { ASSERT_NULL(bad_field); } +static SD_VARLINK_DEFINE_METHOD( + ArrayTest, + SD_VARLINK_DEFINE_INPUT(arr, SD_VARLINK_INT, SD_VARLINK_ARRAY)); + +static SD_VARLINK_DEFINE_METHOD( + MapTest, + SD_VARLINK_DEFINE_INPUT(m, SD_VARLINK_STRING, SD_VARLINK_MAP)); + +TEST(null_array_element) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + /* Build an array with a null element - this should be rejected gracefully, not crash */ + ASSERT_OK(sd_json_buildo(&v, + SD_JSON_BUILD_PAIR("arr", SD_JSON_BUILD_ARRAY( + SD_JSON_BUILD_INTEGER(1), + SD_JSON_BUILD_NULL, + SD_JSON_BUILD_INTEGER(3))))); + + const char *bad_field = NULL; + ASSERT_ERROR(varlink_idl_validate_method_call(&vl_method_ArrayTest, v, /* flags= */ 0, &bad_field), EMEDIUMTYPE); + ASSERT_STREQ(bad_field, "arr"); +} + +TEST(null_map_element) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + /* Build a map with a null value - this should be rejected gracefully, not crash */ + ASSERT_OK(sd_json_buildo(&v, + SD_JSON_BUILD_PAIR("m", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("key1", "value1"), + SD_JSON_BUILD_PAIR_NULL("key2"), + SD_JSON_BUILD_PAIR_STRING("key3", "value3"))))); + + const char *bad_field = NULL; + ASSERT_ERROR(varlink_idl_validate_method_call(&vl_method_MapTest, v, /* flags= */ 0, &bad_field), EMEDIUMTYPE); + ASSERT_STREQ(bad_field, "m"); +} + DEFINE_TEST_MAIN(LOG_DEBUG); From 55f2fdd508dfe430cc36b9961b09d9eb649c6a83 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 22 Mar 2026 23:39:38 +0900 Subject: [PATCH 0420/1296] dhcp: fix user class and vendor specific option assignment The commit 6d7cb9a6b8361d2b327222bc12872a3676358bc3 fixes the assignment of the these options when specified through SendOption=. However, it breaks when specified through UserClass= or SendVendorOption=. When UserClass= or SendVendorOption= is specified, the option length is calculated from the sd_dhcp_client.user_class or .vendor_options. Hence, we can use 0 for the length in that case. Follow-up for 6d7cb9a6b8361d2b327222bc12872a3676358bc3. --- src/libsystemd-network/sd-dhcp-client.c | 5 ++--- src/libsystemd-network/sd-dhcp-server.c | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 4b1cdd0b86352..6b6a5ded42e74 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -1020,8 +1020,7 @@ static int client_append_common_discover_request_options(sd_dhcp_client *client, if (client->user_class) { r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, SD_DHCP_OPTION_USER_CLASS, - strv_length(client->user_class), - client->user_class); + /* optlen= */ 0, client->user_class); if (r < 0) return r; } @@ -1037,7 +1036,7 @@ static int client_append_common_discover_request_options(sd_dhcp_client *client, r = dhcp_option_append( &packet->dhcp, optlen, optoffset, 0, SD_DHCP_OPTION_VENDOR_SPECIFIC, - ordered_hashmap_size(client->vendor_options), client->vendor_options); + /* optlen= */ 0, client->vendor_options); if (r < 0) return r; } diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index d1fe0e227323b..d88480a3464c5 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -719,7 +719,7 @@ static int server_send_offer_or_ack( r = dhcp_option_append( &packet->dhcp, req->max_optlen, &offset, 0, SD_DHCP_OPTION_VENDOR_SPECIFIC, - ordered_set_size(server->vendor_options), server->vendor_options); + /* optlen= */ 0, server->vendor_options); if (r < 0) return r; } From beef155b12cc2c196d7af2e182458de006eb2cf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20K=2E=20H=C3=BCttel?= Date: Fri, 20 Mar 2026 13:52:17 +0100 Subject: [PATCH 0421/1296] mips: Fix conditional inclusion of MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit systemd now has a system call wrapper that does a long series of #ifdef's to differentiate between architectures and ABIs. This wrapper has two problems. 1. On mips, it needs to differentiate between O32, N32, N64 ABI. It does that via a code block in src/include/override/sys/generate-syscall.py (and derived files): 76 # elif defined(_MIPS_SIM) 77 # if _MIPS_SIM == _MIPS_SIM_ABI32 78 # define systemd_NR_{syscall} {nr_mipso32} 79 # elif _MIPS_SIM == _MIPS_SIM_NABI32 80 # define systemd_NR_{syscall} {nr_mips64n32} 81 # elif _MIPS_SIM == _MIPS_SIM_ABI64 82 # define systemd_NR_{syscall} {nr_mips64} 83 # else 84 # error "Unknown MIPS ABI" 85 # endif 86 # elif defined(__hppa__) Now the _MIPS_SIM* constants stem from a vendor-specific header file sgidefs.h, which is included with glibc, but not with musl. It is however always present in the Linux kernel headers as asm/sgidefs.h ... 2. To work around this, the syscall wrapper already has a block 47 #ifdef ARCH_MIPS 48 #include 49 #endif Turns out, ARCH_MIPS is defined nowhere in Gentoo, neither on glibc nor on musl. As a result the code (by accident, probably sgidefs.h is included transitively somehow) works on glibc, but not on musl. The simplest fix is to replace line 47 in the generator and the derived file with 47 #ifdef __mips__ Two other source code files require a similar fix since they rely on the constants. Bug: https://github.com/systemd/systemd/issues/41239 Bug: https://bugs.gentoo.org/971376 Signed-off-by: Andreas K. Hüttel --- src/include/override/sys/generate-syscall.py | 2 +- src/include/override/sys/syscall.h | 2 +- src/shared/base-filesystem.c | 2 +- src/shared/seccomp-util.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/include/override/sys/generate-syscall.py b/src/include/override/sys/generate-syscall.py index 6f449f9dc1330..1c90ad0e38402 100755 --- a/src/include/override/sys/generate-syscall.py +++ b/src/include/override/sys/generate-syscall.py @@ -44,7 +44,7 @@ def parse_syscall_tables(filenames): #include_next /* IWYU pragma: export */ -#ifdef ARCH_MIPS +#ifdef __mips__ #include #endif diff --git a/src/include/override/sys/syscall.h b/src/include/override/sys/syscall.h index da2f780bed39c..0233f254b421c 100644 --- a/src/include/override/sys/syscall.h +++ b/src/include/override/sys/syscall.h @@ -11,7 +11,7 @@ #include_next /* IWYU pragma: export */ -#ifdef ARCH_MIPS +#ifdef __mips__ #include #endif diff --git a/src/shared/base-filesystem.c b/src/shared/base-filesystem.c index bad3b46f3ad3a..9e8856ba48ce6 100644 --- a/src/shared/base-filesystem.c +++ b/src/shared/base-filesystem.c @@ -5,7 +5,7 @@ #include #include -#ifdef ARCH_MIPS +#ifdef __mips__ #include #endif diff --git a/src/shared/seccomp-util.c b/src/shared/seccomp-util.c index d2f7612a53de5..9785fc45d78f3 100644 --- a/src/shared/seccomp-util.c +++ b/src/shared/seccomp-util.c @@ -12,7 +12,7 @@ #include #include -#ifdef ARCH_MIPS +#ifdef __mips__ #include #endif From aaac2d3edaeb326f7a1b1140ae05b459922bc7e9 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 23 Mar 2026 00:25:46 +0000 Subject: [PATCH 0422/1296] core: also set iov_len when deserializing LogExtraFields= This is not actually used so it doesn't really matter in practice and the fields are used anyway, but for cleanliness fix it Reported on yeswehack.com as YWH-PGM9780-165 Follow-up for 5699a1689b7e49702e4e60d08ab3fe386ba8d4df --- src/core/execute-serialize.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/execute-serialize.c b/src/core/execute-serialize.c index 8f9a7ac546402..c8f802687ee5b 100644 --- a/src/core/execute-serialize.c +++ b/src/core/execute-serialize.c @@ -3113,9 +3113,11 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { if (!GREEDY_REALLOC(c->log_extra_fields, c->n_log_extra_fields + 1)) return log_oom_debug(); - c->log_extra_fields[c->n_log_extra_fields++].iov_base = strdup(val); - if (!c->log_extra_fields[c->n_log_extra_fields-1].iov_base) + char *field = strdup(val); + if (!field) return log_oom_debug(); + + c->log_extra_fields[c->n_log_extra_fields++] = IOVEC_MAKE_STRING(field); } else if ((val = startswith(l, "exec-context-log-namespace="))) { r = free_and_strdup(&c->log_namespace, val); if (r < 0) From 7b148d7b78ab523bc99ef81c0a78889912f875e0 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 23 Mar 2026 00:51:29 +0000 Subject: [PATCH 0423/1296] mountfsd: fix readOnly flag inversion mountfsd applies R/O when the varlink readOnly flag is set to false Reported on yeswehack.com as YWH-PGM9780-164 Follow-up for 702a52f4b5d49cce11e2adbc740deb3b644e2de0 --- src/mountfsd/mountwork.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mountfsd/mountwork.c b/src/mountfsd/mountwork.c index 922bcb1b18995..f3f80b1c27428 100644 --- a/src/mountfsd/mountwork.c +++ b/src/mountfsd/mountwork.c @@ -521,7 +521,7 @@ static int vl_method_mount_image( r = loop_device_make( image_fd, - p.read_only == 0 ? O_RDONLY : O_RDWR, + p.read_only > 0 ? O_RDONLY : O_RDWR, 0, UINT64_MAX, UINT32_MAX, @@ -532,7 +532,7 @@ static int vl_method_mount_image( return r; DissectImageFlags dissect_flags = - (p.read_only == 0 ? DISSECT_IMAGE_READ_ONLY : 0) | + (p.read_only > 0 ? DISSECT_IMAGE_READ_ONLY : 0) | (p.growfs != 0 ? DISSECT_IMAGE_GROWFS : 0) | DISSECT_IMAGE_DISCARD_ANY | DISSECT_IMAGE_FSCK | From 80706896baf9a5f5ad7d0d493a94be3e1d765132 Mon Sep 17 00:00:00 2001 From: Arif Budiman Date: Mon, 23 Mar 2026 06:58:47 +0000 Subject: [PATCH 0424/1296] po: Translated using Weblate (Indonesian) Currently translated at 100.0% (266 of 266 strings) Co-authored-by: Arif Budiman Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/id/ Translation: systemd/main --- po/id.po | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/po/id.po b/po/id.po index 7109436cde4b6..cb53019920f29 100644 --- a/po/id.po +++ b/po/id.po @@ -2,12 +2,13 @@ # # Indonesian translation for systemd. # Andika Triwidada , 2014, 2021, 2022, 2024, 2025. +# Arif Budiman , 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2025-07-28 17:25+0000\n" -"Last-Translator: Andika Triwidada \n" +"PO-Revision-Date: 2026-03-23 06:58+0000\n" +"Last-Translator: Arif Budiman \n" "Language-Team: Indonesian \n" "Language: id\n" @@ -15,7 +16,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.12.2\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1014,12 +1015,12 @@ msgid "DHCP server sends force renew message" msgstr "Server HDCP mengirim pesan paksa pembaruan ulang" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy -#| msgid "Authentication is required to send force renew message." msgid "" "Authentication is required to send a force renew message from the DHCP " "server." -msgstr "Otentikasi diperlukan untuk mengirim pesan paksa pembaruan ulang." +msgstr "" +"Otentikasi diperlukan untuk mengirim pesan paksa pembaruan ulang dari server " +"DHCP." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" @@ -1059,11 +1060,11 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Kelola koneksi jaringan" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" +msgstr "Otentikasi diperlukan untuk mengelola koneksi jaringan." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" From d7a759569e391361fd3e881888e54ec4747fbc09 Mon Sep 17 00:00:00 2001 From: vlefebvre Date: Fri, 20 Mar 2026 15:55:31 +0100 Subject: [PATCH 0425/1296] mkosi-tool/opensuse: add libtss2-tcti-device0 package libtss2-tcti-device0 is not installed by default in the openSUSE image, but is now required when building the test image. Without it, the build fails with ``` Shared library 'libtss2-tcti-device.so.0' is not available: libtss2-tcti-device.so.0: cannot open shared object file: No such file or directory ``` Follow-up for 5f85409f932dfdc123d0e8ded8e8a9a6f9443119 --- mkosi/mkosi.tools.conf/mkosi.conf.d/opensuse.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/mkosi/mkosi.tools.conf/mkosi.conf.d/opensuse.conf b/mkosi/mkosi.tools.conf/mkosi.conf.d/opensuse.conf index b698094618733..8a6711f901ce6 100644 --- a/mkosi/mkosi.tools.conf/mkosi.conf.d/opensuse.conf +++ b/mkosi/mkosi.tools.conf/mkosi.conf.d/opensuse.conf @@ -9,6 +9,7 @@ Packages= clang-tools gh lcov + libtss2-tcti-device0 mypy python3-ruff rpm-build From bcaa5acf81b239f3912da91bff869cefb318820e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 2 Mar 2026 15:35:41 +0100 Subject: [PATCH 0426/1296] shared/format-table: add helpers to query and set column width --- src/shared/format-table.c | 69 +++++++++++++++++++++++++++++++++++++++ src/shared/format-table.h | 5 +++ 2 files changed, 74 insertions(+) diff --git a/src/shared/format-table.c b/src/shared/format-table.c index f400602b343d5..279e7fda68e7f 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -2139,6 +2139,75 @@ static const char* table_data_rgap_underline(const TableData *d) { return NULL; } +int table_data_requested_width(Table *table, size_t column, size_t *ret) { + size_t width = 0; + int r; + + assert(table); + assert(ret); + + for (size_t row = 0; row < table_get_rows(table); row++) { + TableCell *cell = table_get_cell(table, row, column); + if (!cell) + continue; + + TableData *data = table_get_data(table, cell); + if (!data) + continue; + + size_t w; + + r = table_data_requested_width_height( + table, data, SIZE_MAX, &w, /* ret_height= */ NULL, /* have_soft= */ NULL); + if (r < 0) + return r; + + width = MAX(width, w); + } + + *ret = width; + return 0; +} + +int table_set_column_width(Table *t, size_t column, size_t width) { + int r = 0; + + assert(t); + + for (size_t row = 0; row < table_get_rows(t); row++) { + TableCell *cell = table_get_cell(t, row, column); + if (!cell) + continue; + + RET_GATHER(r, table_set_minimum_width(t, cell, width)); + } + + return r; +} + +int table_sync_column_width(Table *a, size_t column_a, Table *b, size_t column_b) { + size_t w1, w2; + int r; + + assert(a); + assert(b); + + /* Make both tables have specified columns of same width */ + + r = table_data_requested_width(a, column_a, &w1); + if (r < 0) + return log_error_errno(r, "Failed to query table column width: %m"); + + r = table_data_requested_width(b, column_b, &w2); + if (r < 0) + return log_error_errno(r, "Failed to query table column width: %m"); + + r = 0; + RET_GATHER(r, table_set_column_width(a, column_a, MAX(w1, w2))); + RET_GATHER(r, table_set_column_width(b, column_b, MAX(w1, w2))); + return r; +} + int table_print(Table *t, FILE *f) { size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width, table_minimum_width, table_maximum_width, table_requested_width, table_effective_width, diff --git a/src/shared/format-table.h b/src/shared/format-table.h index 997ac20eb6882..9a11fb7c30cef 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -141,6 +141,11 @@ int table_set_reverse(Table *t, size_t column, bool b); int table_hide_column_from_display_internal(Table *t, ...); #define table_hide_column_from_display(t, ...) table_hide_column_from_display_internal(t, __VA_ARGS__, SIZE_MAX) +int table_data_requested_width(Table *table, size_t column, size_t *ret); + +int table_set_column_width(Table *t, size_t column, size_t width); +int table_sync_column_width(Table *a, size_t column_a, Table *b, size_t column_b); + int table_print(Table *t, FILE *f); int table_format(Table *t, char **ret); From 8b89c6a64b21a954719b5026b87fce0bd6cca306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 2 Mar 2026 23:40:51 +0100 Subject: [PATCH 0427/1296] Apply the same column width for different option groups This feel a bit like a hack, but it works OK. The width of the first column of verbs or options in different sections is measured and applied to the other tables. This makes the second column aligned. --- src/dissect/dissect.c | 3 +++ src/id128/id128.c | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 76c3f90c7e5a1..d36f31e5c717f 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -140,6 +140,9 @@ static int help(void) { if (r < 0) return r; + /* Make the 1st column same width in both tables */ + (void) table_sync_column_width(options, 0, commands, 0); + printf("%1$s [OPTIONS...] IMAGE\n" "%1$s [OPTIONS...] --mount IMAGE PATH\n" "%1$s [OPTIONS...] --umount PATH\n" diff --git a/src/id128/id128.c b/src/id128/id128.c index 23a028b28ca7c..ebed02913ff6e 100644 --- a/src/id128/id128.c +++ b/src/id128/id128.c @@ -207,6 +207,9 @@ static int help(void) { if (r < 0) return r; + /* Make the 1st column same width in both tables */ + (void) table_sync_column_width(options, 0, verbs, 0); + printf("%s [OPTIONS...] COMMAND\n\n" "%sGenerate and print 128-bit identifiers.%s\n" "\nCommands:\n", From 1ddc5ae3e49f6db7d4014600fb9713696d6e0f52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 3 Mar 2026 00:18:09 +0100 Subject: [PATCH 0428/1296] notify: add one more compiler suppression MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Old gcc (<14) says: 2026-03-02T23:11:28.5676353Z In file included from ../src/notify/notify.c:30: 2026-03-02T23:11:28.5694607Z In function ‘strv_isempty’, 2026-03-02T23:11:28.5695481Z inlined from ‘action_fork’ at ../src/notify/notify.c:440:9, 2026-03-02T23:11:28.5696266Z inlined from ‘run’ at ../src/notify/notify.c:541:24, 2026-03-02T23:11:28.5696929Z inlined from ‘main’ at ../src/notify/notify.c:682:1: 2026-03-02T23:11:28.5697877Z ../src/basic/strv.h:108:23: error: ‘args’ may be used uninitialized [-Werror=maybe-uninitialized] 2026-03-02T23:11:28.5698655Z 108 | return !l || !*l; 2026-03-02T23:11:28.5699052Z | ^~ 2026-03-02T23:11:28.5700020Z ../src/notify/notify.c: In function ‘main’: 2026-03-02T23:11:28.5700681Z ../src/notify/notify.c:531:16: note: ‘args’ was declared here 2026-03-02T23:11:28.5701217Z 531 | char **args; 2026-03-02T23:11:28.5701574Z | ^~~~ 2026-03-02T23:11:28.5701960Z cc1: all warnings being treated as errors --- src/notify/notify.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/notify/notify.c b/src/notify/notify.c index d17df1eafa891..5535296760b14 100644 --- a/src/notify/notify.c +++ b/src/notify/notify.c @@ -528,7 +528,7 @@ static int run(int argc, char* argv[]) { _cleanup_strv_free_ char **final_env = NULL; const char *our_env[10]; size_t i = 0; - char **args; + char **args = NULL; /* unnecessary initialization to appease gcc */ int r; log_setup(); @@ -536,6 +536,7 @@ static int run(int argc, char* argv[]) { r = parse_argv(argc, argv, &args); if (r <= 0) return r; + assert(args); if (arg_action == ACTION_FORK) return action_fork(args); From 5adfd44c9e7a7485e93b799399589e45d09fffea Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 10 Feb 2026 15:41:22 +0100 Subject: [PATCH 0429/1296] copy: add new flags that cause a seek to beginning of files before copying This is quite useful in various cases where we so far did this manually. --- src/bootctl/bootctl-install.c | 5 +---- src/repart/repart.c | 5 +---- src/shared/copy.c | 14 ++++++++++++-- src/shared/copy.h | 2 ++ src/test/test-copy.c | 19 +++++-------------- 5 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index 3724a1cfb9402..c4693293ab909 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -554,10 +554,7 @@ static int copy_file_with_version_check( * might be left at the end of the file. (Resetting before rather than after a copy attempt is safer * because a previous attempt might have failed half-way, leaving the file offset at some undefined * place.) */ - if (lseek(source_fd, 0, SEEK_SET) < 0) - return log_error_errno(errno, "Failed to seek in \"%s\": %m", source_path); - - r = copy_bytes(source_fd, write_fd, UINT64_MAX, COPY_REFLINK); + r = copy_bytes(source_fd, write_fd, UINT64_MAX, COPY_REFLINK|COPY_SEEK0_SOURCE); if (r < 0) return log_error_errno(r, "Failed to copy data from \"%s\" to \"%s\": %m", source_path, dest_path); diff --git a/src/repart/repart.c b/src/repart/repart.c index eb334a7c4013d..1bdeec6ea5644 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -5061,9 +5061,6 @@ static int partition_target_sync(Context *context, Partition *p, PartitionTarget if (lseek(whole_fd, p->offset, SEEK_SET) < 0) return log_error_errno(errno, "Failed to seek to partition offset: %m"); - if (lseek(t->fd, 0, SEEK_SET) < 0) - return log_error_errno(errno, "Failed to seek to start of temporary file: %m"); - if (fstat(t->fd, &st) < 0) return log_error_errno(errno, "Failed to stat temporary file: %m"); @@ -5072,7 +5069,7 @@ static int partition_target_sync(Context *context, Partition *p, PartitionTarget "Partition %" PRIu64 "'s contents (%s) don't fit in the partition (%s).", p->partno, FORMAT_BYTES(st.st_size), FORMAT_BYTES(p->new_size)); - r = copy_bytes(t->fd, whole_fd, UINT64_MAX, COPY_REFLINK|COPY_HOLES|COPY_FSYNC); + r = copy_bytes(t->fd, whole_fd, UINT64_MAX, COPY_REFLINK|COPY_HOLES|COPY_FSYNC|COPY_SEEK0_SOURCE); if (r < 0) return log_error_errno(r, "Failed to copy bytes to partition: %m"); } else { diff --git a/src/shared/copy.c b/src/shared/copy.c index 445c246359a7a..3ac05c15b7b2a 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -193,16 +193,26 @@ int copy_bytes_full( if (fdt < 0) return fdt; + if (FLAGS_SET(copy_flags, COPY_SEEK0_SOURCE) && + lseek(fdf, 0, SEEK_SET) < 0) + return -errno; + + if (FLAGS_SET(copy_flags, COPY_SEEK0_TARGET) && + lseek(fdt, 0, SEEK_SET) < 0) + return -errno; + /* Try btrfs reflinks first. This only works on regular, seekable files, hence let's check the file offsets of * source and destination first. */ if ((copy_flags & COPY_REFLINK)) { off_t foffset; - foffset = lseek(fdf, 0, SEEK_CUR); + /* In reflink mode we need to know where the current file offset is, but if we just seeked to + * 0 anyway, we can suppress that. */ + foffset = FLAGS_SET(copy_flags, COPY_SEEK0_SOURCE) ? 0 : lseek(fdf, 0, SEEK_CUR); if (foffset >= 0) { off_t toffset; - toffset = lseek(fdt, 0, SEEK_CUR); + toffset = FLAGS_SET(copy_flags, COPY_SEEK0_TARGET) ? 0 : lseek(fdt, 0, SEEK_CUR); if (toffset >= 0) { if (foffset == 0 && toffset == 0 && max_bytes == UINT64_MAX) diff --git a/src/shared/copy.h b/src/shared/copy.h index 6e4a3b177b337..929973532678e 100644 --- a/src/shared/copy.h +++ b/src/shared/copy.h @@ -34,6 +34,8 @@ typedef enum CopyFlags { COPY_NOCOW_AFTER = 1 << 20, COPY_PRESERVE_FS_VERITY = 1 << 21, /* Preserve fs-verity when copying. */ COPY_MERGE_APPLY_STAT = 1 << 22, /* When we reuse an existing directory inode, apply source ownership/mode/xattrs/timestamps */ + COPY_SEEK0_SOURCE = 1 << 23, /* Seek back to start of source file before copying */ + COPY_SEEK0_TARGET = 1 << 24, /* Seek back to start of target file before copying */ } CopyFlags; typedef enum DenyType { diff --git a/src/test/test-copy.c b/src/test/test-copy.c index 758a597fc539a..719301478246e 100644 --- a/src/test/test-copy.c +++ b/src/test/test-copy.c @@ -364,9 +364,7 @@ static void test_copy_bytes_regular_file_one(const char *src, bool try_reflink, /* Make sure the file is now higher than max_bytes */ assert_se(ftruncate(fd2, max_bytes + 1) == 0); - assert_se(lseek(fd2, 0, SEEK_SET) == 0); - - r = copy_bytes(fd2, fd3, max_bytes, try_reflink ? COPY_REFLINK : 0); + r = copy_bytes(fd2, fd3, max_bytes, COPY_SEEK0_SOURCE | (try_reflink ? COPY_REFLINK : 0)); if (max_bytes == UINT64_MAX) assert_se(r == 0); else @@ -460,9 +458,8 @@ TEST_RET(copy_holes) { assert_se(lseek(fd, 0, SEEK_END) == 2 * blksz); /* Only ftruncate() can create holes at the end of a file. */ assert_se(ftruncate(fd, 3 * blksz) >= 0); - assert_se(lseek(fd, 0, SEEK_SET) >= 0); - assert_se(copy_bytes(fd, fd_copy, UINT64_MAX, COPY_HOLES) >= 0); + assert_se(copy_bytes(fd, fd_copy, UINT64_MAX, COPY_SEEK0_SOURCE|COPY_HOLES) >= 0); /* Test that the hole starts at the beginning of the file. */ assert_se(lseek(fd_copy, 0, SEEK_HOLE) == 0); @@ -526,26 +523,20 @@ TEST_RET(copy_holes_with_gaps) { assert_se(st.st_size == 3 * blksz); /* Copy to the middle of the second hole */ - assert_se(lseek(fd, 0, SEEK_SET) >= 0); - assert_se(lseek(fd_copy, 0, SEEK_SET) >= 0); assert_se(ftruncate(fd_copy, 0) >= 0); - assert_se(copy_bytes(fd, fd_copy, 4 * blksz, COPY_HOLES) >= 0); + assert_se(copy_bytes(fd, fd_copy, 4 * blksz, COPY_SEEK0_SOURCE|COPY_SEEK0_TARGET|COPY_HOLES) >= 0); ASSERT_OK_ERRNO(fstat(fd_copy, &st)); assert_se(st.st_size == 4 * blksz); /* Copy to the end of the second hole */ - assert_se(lseek(fd, 0, SEEK_SET) >= 0); - assert_se(lseek(fd_copy, 0, SEEK_SET) >= 0); assert_se(ftruncate(fd_copy, 0) >= 0); - assert_se(copy_bytes(fd, fd_copy, 5 * blksz, COPY_HOLES) >= 0); + assert_se(copy_bytes(fd, fd_copy, 5 * blksz, COPY_SEEK0_SOURCE|COPY_SEEK0_TARGET|COPY_HOLES) >= 0); ASSERT_OK_ERRNO(fstat(fd_copy, &st)); assert_se(st.st_size == 5 * blksz); /* Copy everything */ - assert_se(lseek(fd, 0, SEEK_SET) >= 0); - assert_se(lseek(fd_copy, 0, SEEK_SET) >= 0); assert_se(ftruncate(fd_copy, 0) >= 0); - assert_se(copy_bytes(fd, fd_copy, UINT64_MAX, COPY_HOLES) >= 0); + assert_se(copy_bytes(fd, fd_copy, UINT64_MAX, COPY_SEEK0_SOURCE|COPY_SEEK0_TARGET|COPY_HOLES) >= 0); ASSERT_OK_ERRNO(fstat(fd_copy, &st)); assert_se(st.st_size == 6 * blksz); From 053f4f1dbc94aa5a2d1004ac90f2401658ab8b6c Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Fri, 20 Feb 2026 18:51:35 -0700 Subject: [PATCH 0430/1296] resolved: resolve insecure answers with unsupported sig algorithms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sd-resolved does not support all the permissible DNSSEC signature algorithms, and some are intentionally unsupported as a matter of policy. Answers that can only be validated via unsupported algorithms should be treated as if they were unsigned, per RFC4035§5.2. Previously, sd-resolved tried to properly record insecure answers for unsupported algortihms, but did not record this status for each of the auxilliary DNSSEC transactions, so the primary transaction had no way to know if there was a plausible DNSKEY with an unsupported signature algorithm in the chain of trust. This commit adds the insecure DNSKEYs that use unsupported algorithms to the list of validated keys for each transaction, so that dependent transactions can learn that a plausible chain of trust exists, even if no authenticated one does, and report the insecure answer. --- src/resolve/resolved-dns-dnssec.c | 19 +++++++++++++------ src/resolve/resolved-dns-transaction.c | 23 +++++++++++++++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index ced874e2ba9f2..739f33747f07c 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -646,8 +646,10 @@ static int dnssec_rrset_verify_sig( if (!ctx) return -ENOMEM; + /* If the signature algorithm is supported by systemd-resolved but disabled by host policy, + * also return -EOPNOTSUPP. */ if (EVP_DigestInit_ex(ctx, md_algorithm, NULL) <= 0) - return -EIO; + return -EOPNOTSUPP; if (EVP_DigestUpdate(ctx, sig_data, sig_size) <= 0) return -EIO; @@ -912,9 +914,6 @@ int dnssec_verify_rrset_search( DNS_ANSWER_FOREACH_FLAGS(dnskey, flags, validated_dnskeys) { DnssecResult one_result; - if ((flags & DNS_ANSWER_AUTHENTICATED) == 0) - continue; - /* Is this a DNSKEY RR that matches they key of our RRSIG? */ r = dnssec_rrsig_match_dnskey(rrsig, dnskey, false); if (r < 0) @@ -922,6 +921,14 @@ int dnssec_verify_rrset_search( if (r == 0) continue; + if ((flags & DNS_ANSWER_AUTHENTICATED) == 0) { + /* An unauthenticated DNSKEY in validated_dnskeys is a key we are not able to + * authenticate, but might still be valid. Record this as an unsupported + * algorithm so we can still at least report an insecure answer. */ + found_unsupported_algorithm = true; + continue; + } + /* Take the time here, if it isn't set yet, so * that we do all validations with the same * time. */ @@ -1201,7 +1208,7 @@ int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { return -ENOMEM; if (EVP_DigestInit_ex(ctx, algorithm, NULL) <= 0) - return -EIO; + return -EOPNOTSUPP; r = dns_name_to_wire_format(name, wire_format, sizeof(wire_format), true); if (r < 0) @@ -1218,7 +1225,7 @@ int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { for (unsigned k = 0; k < nsec3->nsec3.iterations; k++) { if (EVP_DigestInit_ex(ctx, algorithm, NULL) <= 0) - return -EIO; + return -EOPNOTSUPP; if (EVP_DigestUpdate(ctx, result, hash_size) <= 0) return -EIO; if (EVP_DigestUpdate(ctx, nsec3->nsec3.salt, nsec3->nsec3.salt_size) <= 0) diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index d4a5dd8e17f02..1d54391f632a2 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -3259,6 +3259,12 @@ static int dns_transaction_copy_validated(DnsTransaction *t) { if (DNS_TRANSACTION_IS_LIVE(dt->state)) continue; + /* Some of the validated keys may not be authenticated, but are still useful to report + * insecure answers when the domain is signed only by unsupported algorithms. */ + r = dns_answer_extend(&t->validated_keys, dt->validated_keys); + if (r < 0) + return r; + if (!FLAGS_SET(dt->answer_query_flags, SD_RESOLVED_AUTHENTICATED)) continue; @@ -3478,6 +3484,23 @@ static int dnssec_validate_records( /* https://datatracker.ietf.org/doc/html/rfc6840#section-5.2 */ if (result == DNSSEC_UNSUPPORTED_ALGORITHM) { + if (rr->key->type == DNS_TYPE_DNSKEY) { + /* This is a DNSKEY we cannot authenticate, but it might still be the best + * offer from the resolver. Add it to the validated keys in case it's the + * best we can find, but do not mark it as authenticated. + */ + + r = dns_answer_copy_by_key(&t->validated_keys, t->answer, rr->key, 0, NULL); + if (r < 0) + return r; + + /* Some of the DNSKEYs we just added might already have been revoked, + * remove them again in that case. */ + r = dns_transaction_invalidate_revoked_keys(t); + if (r < 0) + return r; + } + r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0, NULL); if (r < 0) return r; From 3ddb73317e2f5f37ae4fc3648329905f436edf4d Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Sat, 21 Feb 2026 12:05:20 -0700 Subject: [PATCH 0431/1296] resolved: also validate unsupported dnssec digest algs --- src/resolve/resolved-dns-dnssec.c | 16 +++++++++++++--- src/resolve/resolved-dns-transaction.c | 11 ++++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 739f33747f07c..39b679ab04072 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -1099,8 +1099,10 @@ int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, if (!ctx) return -ENOMEM; + /* If the digest is supported by systemd-resolved but disabled by host policy, also return -EOPNOTSUPP + */ if (EVP_DigestInit_ex(ctx, md_algorithm, NULL) <= 0) - return -EIO; + return -EOPNOTSUPP; if (EVP_DigestUpdate(ctx, wire_format, encoded_length) <= 0) return -EIO; @@ -1128,6 +1130,7 @@ int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) { DnsResourceRecord *ds; DnsAnswerFlags flags; + bool found_unsupported_algorithm = false; int r; assert(dnskey); @@ -1152,14 +1155,21 @@ int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *vali continue; r = dnssec_verify_dnskey_by_ds(dnskey, ds, false); - if (IN_SET(r, -EKEYREJECTED, -EOPNOTSUPP)) - continue; /* The DNSKEY is revoked or otherwise invalid, or we don't support the digest algorithm */ + if (r == -EKEYREJECTED) + continue; /* The DNSKEY is revoked or otherwise invalid. */ + if (r == -EOPNOTSUPP) { + found_unsupported_algorithm = true; + continue; + } if (r < 0) return r; if (r > 0) return 1; } + if (found_unsupported_algorithm) + return -EOPNOTSUPP; + return 0; } diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 1d54391f632a2..1a786ccf270b2 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -2836,13 +2836,18 @@ static int dns_transaction_validate_dnskey_by_ds(DnsTransaction *t) { DNS_ANSWER_FOREACH_ITEM(item, t->answer) { r = dnssec_verify_dnskey_by_ds_search(item->rr, t->validated_keys); - if (r < 0) + if (r < 0 && r != -EOPNOTSUPP) return r; if (r == 0) continue; - /* If so, the DNSKEY is validated too. */ - r = dns_answer_add_extend(&t->validated_keys, item->rr, item->ifindex, item->flags|DNS_ANSWER_AUTHENTICATED, item->rrsig); + /* If so, the DNSKEY is validated too, but only mark it authenticated if the DS verification + * succeeded with a known algorithm. */ + if (r == -EOPNOTSUPP) + r = dns_answer_add_extend(&t->validated_keys, item->rr, item->ifindex, item->flags, NULL); + else + r = dns_answer_add_extend(&t->validated_keys, item->rr, item->ifindex, item->flags|DNS_ANSWER_AUTHENTICATED, item->rrsig); + if (r < 0) return r; } From 3af158759fedea440ce06d7b139dc0dcd28bab06 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 23 Mar 2026 21:13:03 +0000 Subject: [PATCH 0432/1296] creds: use CLEANUP_ERASE for symmetric key Just in case, ensure the sha256 that is used as a symmetric key for encrypted creds is safely erased from memory. Reported on yeswehack.com as YWH-PGM9780-166 Follow-up for 21bc0b6fa1de44b520353b935bf14160f9f70591 --- src/shared/creds-util.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index 54ae368fdfb09..9c093181c7b33 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -840,6 +840,8 @@ int encrypt_credential_and_warn( /* Only one of these two flags may be set at the same time */ assert(!FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL) || !FLAGS_SET(flags, CREDENTIAL_REFUSE_NULL)); + CLEANUP_ERASE(md); + if (!CRED_KEY_IS_VALID(with_key) && !CRED_KEY_IS_AUTO(with_key)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid key type: " SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(with_key)); @@ -1204,6 +1206,8 @@ int decrypt_credential_and_warn( /* Only one of these two flags may be set at the same time */ assert(!FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL) || !FLAGS_SET(flags, CREDENTIAL_REFUSE_NULL)); + CLEANUP_ERASE(md); + /* Relevant error codes: * * -EBADMSG → Corrupted file From 7aa94251fe07a62745df8014af2c93c3105fcb69 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 24 Mar 2026 10:15:21 +0100 Subject: [PATCH 0433/1296] ci: Generalize escaping instructions in claude-review prompt --- .github/workflows/claude-review.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 88d74295c2bd3..6e0b25c4536c2 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -275,9 +275,6 @@ jobs: The `commit` field MUST be the SHA of the commit being reviewed. Only comment on changes in that commit — not preceding commits. - Do NOT escape characters in `body`. Write plain markdown — no backslash - escaping of `!` or other characters. - `line` should be a line number from the NEW side of the diff **that appears inside a diff hunk**. GitHub rejects lines outside the diff context. If you cannot determine a valid diff line, omit `line`. @@ -351,6 +348,12 @@ jobs: not available, git commands that failed, etc.), append a `### Errors` section to the summary listing each failed action and the error message. + ## Output formatting + + Do NOT escape characters in `body` or `summary`. Write plain markdown — no + backslash escaping of `!` or other characters. In particular, HTML comments + like `` must be written verbatim, never as `<\!-- ... -->`. + ## CRITICAL: Write review result to file Your FINAL action must be to write `review-result.json` in the repo From da580dc1613592064dafe2fbae229a90f65986b8 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 23 Mar 2026 21:58:28 +0100 Subject: [PATCH 0434/1296] vmspawn: Drop --sandbox=chroot from virtiofsd command line It's unclear why I added this in fd05c6c7593c5e36864d8784df91b878bbf991ab, but it breaks bind mounting regular directories via --bind, so drop it again since it's not actually required to make virtiofsd work with the foreign UID range. --- src/vmspawn/vmspawn.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index cacfc15f7e768..c114693d91129 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -1558,7 +1558,6 @@ static int start_virtiofsd( "--shared-dir", source_uid == FOREIGN_UID_MIN ? "/run/systemd/mount-rootfs" : directory, "--xattr", "--fd", sockstr, - "--sandbox=chroot", "--no-announce-submounts"); if (!argv) return log_oom(); From 516a7b2baac8f9fd84bad7c91299045b6253d0d5 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 24 Mar 2026 10:21:04 +0100 Subject: [PATCH 0435/1296] ci: Only run claude-review automatically on PRs to main --- .github/workflows/claude-review.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 6e0b25c4536c2..d3500895c6a09 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -36,6 +36,7 @@ jobs: (github.event.action == 'labeled' && github.event.label.name == 'claude-review' && github.event.sender.login != 'github-actions[bot]' || github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'claude-review') || github.event.action == 'opened' && + github.event.pull_request.base.ref == 'main' && contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.pull_request.author_association) && github.event.pull_request.user.login != 'YHNdnzj')) || (github.event_name == 'issue_comment' && From 5cd3462014a1383514142b7d0863642148d878ec Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 8 Mar 2026 20:51:38 +0100 Subject: [PATCH 0436/1296] vmspawn: make efi variable nvram dependent on whether the EFI profile knows the concept, not on secureboot --- src/vmspawn/vmspawn.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index c114693d91129..8f08fdb81ca6e 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -2367,7 +2367,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return log_oom(); _cleanup_(unlink_and_freep) char *ovmf_vars_to = NULL; - if (ovmf_config->supports_sb) { + if (ovmf_config->vars) { const char *ovmf_vars_from = ovmf_config->vars; _cleanup_free_ char *escaped_ovmf_vars_to = NULL; _cleanup_close_ int source_fd = -EBADF, target_fd = -EBADF; From 9996cfd2c9e43d981edeeecfd16aed8d4b0876a1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 8 Mar 2026 21:52:08 +0100 Subject: [PATCH 0437/1296] vmspawn: manage EFI nvram (variables) state similar to TPM state --- man/systemd-vmspawn.xml | 17 ++++ src/vmspawn/vmspawn.c | 173 ++++++++++++++++++++++++++++------------ 2 files changed, 141 insertions(+), 49 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 136bd6534062b..331c7c16fd699 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -226,6 +226,23 @@ + + + + Configures where to place the EFI variable NVRAM state. This takes an absolute file + system path to a regular file to persistently place the state in. If the file is missing it is + created as needed. If set to the special string auto a persistent path is + automatically derived from the VM image path or directory path, with the + .efinvramstate suffix appended. If set to the special string + off the EFI variable NVRAM state is only maintained transiently and flushed out + when the VM shuts down. Defaults to auto. + + If is specified, auto behaves like + off. + + + + diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 8f08fdb81ca6e..fa433ba504b4f 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -88,13 +88,15 @@ #define VM_TAP_HASH_KEY SD_ID128_MAKE(01,d0,c6,4c,2b,df,24,fb,c0,f8,b2,09,7d,59,b2,93) -typedef enum TpmStateMode { - TPM_STATE_OFF, /* keep no state around */ - TPM_STATE_AUTO, /* keep state around if not ephemeral, derive path from image/directory */ - TPM_STATE_PATH, /* explicitly specified location */ - _TPM_STATE_MODE_MAX, - _TPM_STATE_MODE_INVALID = -EINVAL, -} TpmStateMode; +/* An enum controlling how auxiliary state for the VM are maintained, i.e. the TPM state and the EFI variable + * NVRAM. */ +typedef enum StateMode { + STATE_OFF, /* keep no state around */ + STATE_AUTO, /* keep state around if not ephemeral, derive path from image/directory */ + STATE_PATH, /* explicitly specified location */ + _STATE_MODE_MAX, + _STATE_MODE_INVALID = -EINVAL, +} StateMode; typedef struct SSHInfo { unsigned cid; @@ -144,7 +146,9 @@ static struct ether_addr arg_network_provided_mac = {}; static char **arg_smbios11 = NULL; static uint64_t arg_grow_image = 0; static char *arg_tpm_state_path = NULL; -static TpmStateMode arg_tpm_state_mode = TPM_STATE_AUTO; +static StateMode arg_tpm_state_mode = STATE_AUTO; +static char *arg_efi_nvram_state_path = NULL; +static StateMode arg_efi_nvram_state_mode = STATE_AUTO; static bool arg_ask_password = true; static bool arg_notify_ready = true; static char **arg_bind_user = NULL; @@ -171,6 +175,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_background, freep); STATIC_DESTRUCTOR_REGISTER(arg_ssh_key_type, freep); STATIC_DESTRUCTOR_REGISTER(arg_smbios11, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm_state_path, freep); +STATIC_DESTRUCTOR_REGISTER(arg_efi_nvram_state_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_property, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user_shell, freep); @@ -209,6 +214,8 @@ static int help(void) { " --tpm=BOOL Enable use of a virtual TPM\n" " --tpm-state=off|auto|PATH\n" " Where to store TPM state\n" + " --efi-nvram-state=off|auto|PATH\n" + " Where to store EFI Variable NVRAM state\n" " --linux=PATH Specify the linux kernel for direct kernel boot\n" " --initrd=PATH Specify the initrd for direct kernel boot\n" " -n --network-tap Create a TAP device for networking\n" @@ -317,6 +324,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_CONSOLE, ARG_BACKGROUND, ARG_TPM_STATE, + ARG_EFI_NVRAM_STATE, ARG_NO_ASK_PASSWORD, ARG_PROPERTY, ARG_NOTIFY_READY, @@ -374,6 +382,7 @@ static int parse_argv(int argc, char *argv[]) { { "smbios11", required_argument, NULL, 's' }, { "grow-image", required_argument, NULL, 'G' }, { "tpm-state", required_argument, NULL, ARG_TPM_STATE }, + { "efi-nvram-state", required_argument, NULL, ARG_EFI_NVRAM_STATE }, { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, { "property", required_argument, NULL, ARG_PROPERTY }, { "notify-ready", required_argument, NULL, ARG_NOTIFY_READY }, @@ -710,7 +719,7 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return r; - arg_tpm_state_mode = TPM_STATE_PATH; + arg_tpm_state_mode = STATE_PATH; break; } @@ -720,10 +729,30 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Failed to parse --tpm-state= parameter: %s", optarg); - arg_tpm_state_mode = r ? TPM_STATE_AUTO : TPM_STATE_OFF; + arg_tpm_state_mode = r ? STATE_AUTO : STATE_OFF; arg_tpm_state_path = mfree(arg_tpm_state_path); break; + case ARG_EFI_NVRAM_STATE: + if (path_is_valid(optarg) && (path_is_absolute(optarg) || path_startswith(optarg, "./"))) { + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_efi_nvram_state_path); + if (r < 0) + return r; + + arg_efi_nvram_state_mode = STATE_PATH; + break; + } + + r = isempty(optarg) ? false : + streq(optarg, "auto") ? true : + parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --efi-nvram-state= parameter: %s", optarg); + + arg_efi_nvram_state_mode = r ? STATE_AUTO : STATE_OFF; + arg_efi_nvram_state_path = mfree(arg_efi_nvram_state_path); + break; + case ARG_NO_ASK_PASSWORD: arg_ask_password = false; break; @@ -1948,6 +1977,30 @@ static int on_request_stop(sd_bus_message *m, void *userdata, sd_bus_error *erro return 0; } +static int make_sidecar_path(const char *suffix, char **ret) { + int r; + + assert(suffix); + assert(ret); + + const char *p = ASSERT_PTR(arg_image ?: arg_directory); + + _cleanup_free_ char *parent = NULL, *filename = NULL; + r = path_split_prefix_filename(p, &parent, &filename); + if (r < 0) + return log_error_errno(r, "Failed to extract parent directory and filename from '%s': %m", p); + + if (!strextend(&filename, suffix)) + return log_oom(); + + _cleanup_free_ char *j = path_join(parent, filename); + if (!j) + return log_oom(); + + *ret = TAKE_PTR(j); + return 0; +} + static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { _cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL; _cleanup_free_ char *qemu_binary = NULL, *mem = NULL, *kernel = NULL; @@ -2366,30 +2419,67 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (r < 0) return log_oom(); - _cleanup_(unlink_and_freep) char *ovmf_vars_to = NULL; - if (ovmf_config->vars) { - const char *ovmf_vars_from = ovmf_config->vars; - _cleanup_free_ char *escaped_ovmf_vars_to = NULL; - _cleanup_close_ int source_fd = -EBADF, target_fd = -EBADF; + if (arg_efi_nvram_state_mode == STATE_AUTO && !arg_ephemeral) { + assert(!arg_efi_nvram_state_path); - r = tempfn_random_child(NULL, "vmspawn-", &ovmf_vars_to); + r = make_sidecar_path(".efinvramstate", &arg_efi_nvram_state_path); if (r < 0) return r; - source_fd = open(ovmf_vars_from, O_RDONLY|O_CLOEXEC); - if (source_fd < 0) - return log_error_errno(source_fd, "Failed to open OVMF vars file %s: %m", ovmf_vars_from); + log_debug("Storing EFI NVRAM state persistently under '%s'.", arg_efi_nvram_state_path); + } - target_fd = open(ovmf_vars_to, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0600); - if (target_fd < 0) - return log_error_errno(errno, "Failed to create regular file for OVMF vars at %s: %m", ovmf_vars_to); + _cleanup_(unlink_and_freep) char *ovmf_vars = NULL; + if (ovmf_config->vars) { + _cleanup_close_ int target_fd = -EBADF; + _cleanup_(unlink_and_freep) char *destroy_path = NULL; + bool newly_created; + const char *state; + if (arg_efi_nvram_state_path) { + _cleanup_free_ char *d = strdup(arg_efi_nvram_state_path); + if (!d) + return log_oom(); - r = copy_bytes(source_fd, target_fd, UINT64_MAX, COPY_REFLINK); - if (r < 0) - return log_error_errno(r, "Failed to copy bytes from %s to %s: %m", ovmf_vars_from, ovmf_vars_to); + target_fd = openat_report_new(AT_FDCWD, arg_efi_nvram_state_path, O_WRONLY|O_CREAT|O_CLOEXEC, 0600, &newly_created); + if (target_fd < 0) + return log_error_errno(target_fd, "Failed to open file for OVMF vars at %s: %m", arg_efi_nvram_state_path); + + if (newly_created) + destroy_path = TAKE_PTR(d); + + r = fd_verify_regular(target_fd); + if (r < 0) + return log_error_errno(r, "Not a regular file for OVMF variables at %s: %m", arg_efi_nvram_state_path); + + state = arg_efi_nvram_state_path; + } else { + _cleanup_free_ char *t = NULL; + r = tempfn_random_child(/* p= */ NULL, "vmspawn-", &t); + if (r < 0) + return log_error_errno(r, "Failed to create temporary filename: %m"); + + target_fd = open(t, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0600); + if (target_fd < 0) + return log_error_errno(errno, "Failed to create regular file for OVMF vars at %s: %m", t); + + newly_created = true; + state = ovmf_vars = TAKE_PTR(t); + } - /* This isn't always available so don't raise an error if it fails */ - (void) copy_times(source_fd, target_fd, 0); + if (newly_created) { + _cleanup_close_ int source_fd = open(ovmf_config->vars, O_RDONLY|O_CLOEXEC); + if (source_fd < 0) + return log_error_errno(errno, "Failed to open OVMF vars file %s: %m", ovmf_config->vars); + + r = copy_bytes(source_fd, target_fd, UINT64_MAX, COPY_REFLINK); + if (r < 0) + return log_error_errno(r, "Failed to copy bytes from %s to %s: %m", ovmf_config->vars, state); + + /* This isn't always available so don't raise an error if it fails */ + (void) copy_times(source_fd, target_fd, 0); + } + + destroy_path = mfree(destroy_path); /* disarm auto-destroy */ r = strv_extend_many( &cmdline, @@ -2399,11 +2489,11 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (r < 0) return log_oom(); - escaped_ovmf_vars_to = escape_qemu_value(ovmf_vars_to); - if (!escaped_ovmf_vars_to) + _cleanup_free_ char *escaped_state = escape_qemu_value(state); + if (!escaped_state) return log_oom(); - r = strv_extendf(&cmdline, "file=%s,if=pflash,format=%s", escaped_ovmf_vars_to, ovmf_config_format(ovmf_config)); + r = strv_extendf(&cmdline, "file=%s,if=pflash,format=%s", escaped_state, ovmf_config_format(ovmf_config)); if (r < 0) return log_oom(); } @@ -2669,33 +2759,18 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM not supported on %s, refusing", architecture_to_string(native_architecture())); if (arg_tpm < 0) { arg_tpm = false; - log_debug("TPM not support on %s, disabling tpm autodetection and continuing", architecture_to_string(native_architecture())); + log_debug("TPM not supported on %s, disabling tpm autodetection and continuing", architecture_to_string(native_architecture())); } } _cleanup_free_ char *swtpm = NULL; if (arg_tpm != 0) { - if (arg_tpm_state_mode == TPM_STATE_AUTO && !arg_ephemeral) { + if (arg_tpm_state_mode == STATE_AUTO && !arg_ephemeral) { assert(!arg_tpm_state_path); - const char *p = ASSERT_PTR(arg_image ?: arg_directory); - - _cleanup_free_ char *parent = NULL; - r = path_extract_directory(p, &parent); + r = make_sidecar_path(".tpmstate", &arg_tpm_state_path); if (r < 0) - return log_error_errno(r, "Failed to extract parent directory from '%s': %m", p); - - _cleanup_free_ char *filename = NULL; - r = path_extract_filename(p, &filename); - if (r < 0) - return log_error_errno(r, "Failed to extract filename from '%s': %m", p); - - if (!strextend(&filename, ".tpmstate")) - return log_oom(); - - arg_tpm_state_path = path_join(parent, filename); - if (!arg_tpm_state_path) - return log_oom(); + return r; log_debug("Storing TPM state persistently under '%s'.", arg_tpm_state_path); } From dc91cb82da2a599564d7ea7903c42b131d328f89 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 19 Mar 2026 14:20:02 +0100 Subject: [PATCH 0438/1296] vmspawn: split out swtpm-setup logic, and beef it up a bit --- src/shared/meson.build | 1 + src/shared/swtpm-util.c | 158 ++++++++++++++++++++++++++++++++++++++++ src/shared/swtpm-util.h | 4 + src/vmspawn/vmspawn.c | 44 +---------- 4 files changed, 167 insertions(+), 40 deletions(-) create mode 100644 src/shared/swtpm-util.c create mode 100644 src/shared/swtpm-util.h diff --git a/src/shared/meson.build b/src/shared/meson.build index 3d4808dafee10..3088b419a5de3 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -187,6 +187,7 @@ shared_sources = files( 'socket-netlink.c', 'specifier.c', 'switch-root.c', + 'swtpm-util.c', 'tar-util.c', 'tmpfile-util-label.c', 'tomoyo-util.c', diff --git a/src/shared/swtpm-util.c b/src/shared/swtpm-util.c new file mode 100644 index 0000000000000..836f877e12d79 --- /dev/null +++ b/src/shared/swtpm-util.c @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-json.h" + +#include "alloc-util.h" +#include "escape.h" +#include "fd-util.h" +#include "json-util.h" +#include "log.h" +#include "memfd-util.h" +#include "path-util.h" +#include "pidref.h" +#include "process-util.h" +#include "string-util.h" +#include "strv.h" +#include "swtpm-util.h" + +int manufacture_swtpm(const char *state_dir, const char *secret) { + int r; + + assert(state_dir); + + _cleanup_free_ char *swtpm_setup = NULL; + r = find_executable("swtpm_setup", &swtpm_setup); + if (r < 0) + return log_error_errno(r, "Failed to find 'swtpm_setup' binary: %m"); + + _cleanup_strv_free_ char **args = strv_new( + swtpm_setup, + "--tpm2", + "--print-profiles"); + if (!args) + return log_oom(); + + _cleanup_close_ int mfd = memfd_new("swtpm-profiles"); + if (mfd < 0) + return log_error_errno(mfd, "Failed to allocate memfd: %m"); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *cmdline = quote_command_line(args, SHELL_ESCAPE_EMPTY); + log_debug("About to spawn: %s", strnull(cmdline)); + } + + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + r = pidref_safe_fork_full( + "(swtpm-lprof)", + (int[]) { -EBADF, mfd, STDERR_FILENO }, + /* except_fds= */ NULL, + /* n_except_fds= */ 0, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_REARRANGE_STDIO|FORK_LOG, + &pidref); + if (r < 0) + return log_error_errno(r, "Failed to run swtpm_setup: %m"); + if (r == 0) { + /* Child */ + execvp(args[0], args); + log_error_errno(errno, "Failed to execute '%s': %m", args[0]); + _exit(EXIT_FAILURE); + } + + r = pidref_wait_for_terminate_and_check("(swtpm-lprof)", &pidref, WAIT_LOG_ABNORMAL); + if (r < 0) + return r; + + /* NB: we ignore the exit status of --print-profiles, it's broken. Instead we check if we have + * received a valid JSON object via STDOUT. */ + (void) r; + + _cleanup_free_ char *text = NULL; + r = read_full_file_full( + mfd, + /* filename= */ NULL, + /* offset= */ 0, + /* size= */ SIZE_MAX, + /* flags= */ 0, + /* bind_name= */ NULL, + &text, + /* ret_size= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to read memory fd: %m"); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + const char *best_profile = NULL; + if (isempty(text)) + log_notice("No list of supported profiles could be acquired from swtpm, assuming the implementation is too old to know the concept of profiles."); + else { + r = sd_json_parse(text, SD_JSON_PARSE_MUST_BE_OBJECT, &j, /* reterr_line= */ NULL, /* reterr_column= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to parse swtpm's --print-profiles output: %m"); + + sd_json_variant *v = sd_json_variant_by_key(j, "builtin"); + if (v) { + if (!sd_json_variant_is_array(v)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'builtin' field is not an array: %m"); + + sd_json_variant *i; + JSON_VARIANT_ARRAY_FOREACH(i, v) { + if (!sd_json_variant_is_object(i)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Profile object is not a JSON object."); + + sd_json_variant *n = sd_json_variant_by_key(i, "Name"); + if (!n) { + log_debug("Object in profiles array does not have a 'Name', skipping."); + continue; + } + + if (!sd_json_variant_is_string(n)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Profile's 'Name' field is not a string."); + + const char *s = sd_json_variant_string(n); + + /* Pick the best of the default-v1, default-v2, … profiles */ + if (!startswith(s, "default-v")) + continue; + if (!best_profile || strverscmp_improved(s, best_profile) > 0) + best_profile = s; + } + } + } + + strv_free(args); + args = strv_new(swtpm_setup, + "--tpm-state", state_dir, + "--tpm2", + "--pcr-banks", "sha256", + "--ecc", + "--createek", + "--create-ek-cert", + "--create-platform-cert", + "--not-overwrite"); + if (!args) + return log_oom(); + + if (secret && strv_extendf(&args, "--keyfile=%s", secret) < 0) + return log_oom(); + + if (best_profile && strv_extendf(&args, "--profile-name=%s", best_profile) < 0) + return log_oom(); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *cmdline = quote_command_line(args, SHELL_ESCAPE_EMPTY); + log_debug("About to spawn: %s", strnull(cmdline)); + } + + r = pidref_safe_fork("(swtpm-setup)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_LOG|FORK_WAIT, /* ret= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to run swtpm_setup: %m"); + if (r == 0) { + /* Child */ + execvp(args[0], args); + log_error_errno(errno, "Failed to execute '%s': %m", args[0]); + _exit(EXIT_FAILURE); + } + + return 0; +} diff --git a/src/shared/swtpm-util.h b/src/shared/swtpm-util.h new file mode 100644 index 0000000000000..9c1c7377218e5 --- /dev/null +++ b/src/shared/swtpm-util.h @@ -0,0 +1,4 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +int manufacture_swtpm(const char *state_dir, const char *secret); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index fa433ba504b4f..a65f879449e6a 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -72,6 +72,7 @@ #include "stdio-util.h" #include "string-util.h" #include "strv.h" +#include "swtpm-util.h" #include "sync-util.h" #include "terminal-util.h" #include "tmpfile-util.h" @@ -1354,48 +1355,11 @@ static int start_tpm( if (r < 0) return log_error_errno(r, "Failed to create TPM state directory '%s': %m", state_dir); - _cleanup_free_ char *swtpm_setup = NULL; - r = find_executable("swtpm_setup", &swtpm_setup); + r = manufacture_swtpm(state_dir, /* secret= */ NULL); if (r < 0) - return log_error_errno(r, "Failed to find swtpm_setup binary: %m"); - - /* Try passing --profile-name default-v2 first, in order to support RSA4096 pcrsig keys, which was - * added in 0.11. */ - _cleanup_strv_free_ char **argv = strv_new( - swtpm_setup, - "--tpm-state", state_dir, - "--tpm2", - "--pcr-banks", "sha256", - "--not-overwrite", - "--profile-name", "default-v2"); - if (!argv) - return log_oom(); - - r = pidref_safe_fork("(swtpm-setup)", FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_WAIT, /* ret= */ NULL); - if (r == 0) { - /* Child */ - execvp(argv[0], argv); - log_error_errno(errno, "Failed to execute '%s': %m", argv[0]); - _exit(EXIT_FAILURE); - } - if (r == -EPROTO) { - /* If swtpm_setup fails, try again removing the default-v2 profile, as it might be an older - * version. */ - strv_remove(argv, "--profile-name"); - strv_remove(argv, "default-v2"); - - r = pidref_safe_fork("(swtpm-setup)", FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_WAIT, /* ret= */ NULL); - if (r == 0) { - /* Child */ - execvp(argv[0], argv); - log_error_errno(errno, "Failed to execute '%s': %m", argv[0]); - _exit(EXIT_FAILURE); - } - } - if (r < 0) - return log_error_errno(r, "Failed to run swtpm_setup: %m"); + return r; - strv_free(argv); + _cleanup_strv_free_ char **argv = NULL; argv = strv_new(sd_socket_activate, "--listen", listen_address, swtpm, "socket", "--tpm2", "--tpmstate"); if (!argv) return log_oom(); From 48ca2f6ff5c04c3099911938010679ba370d46b4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 9 Mar 2026 08:46:36 +0100 Subject: [PATCH 0439/1296] discover-image: remove tpm state + efi nvram state on image removal --- src/shared/discover-image.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c index dd924523158ba..6b4493b960b60 100644 --- a/src/shared/discover-image.c +++ b/src/shared/discover-image.c @@ -142,7 +142,9 @@ static const char auxiliary_suffixes_nulstr[] = ".roothash.p7s\0" ".usrhash\0" ".usrhash.p7s\0" - ".verity\0"; + ".verity\0" + ".raw.tpmstate\0" + ".raw.efinvramstate\0"; DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(image_dirname, ImageClass); From ec32afd525eb34000c99522d27b4387def8a2673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 13 Mar 2026 15:22:45 +0100 Subject: [PATCH 0440/1296] shell-completion: add shell completions for systemd-hwdb Co-developed-by: Claude --- TODO | 1 - shell-completion/bash/meson.build | 1 + shell-completion/bash/systemd-hwdb | 76 ++++++++++++++++++++++++++++++ shell-completion/zsh/_systemd-hwdb | 40 ++++++++++++++++ shell-completion/zsh/meson.build | 1 + 5 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 shell-completion/bash/systemd-hwdb create mode 100644 shell-completion/zsh/_systemd-hwdb diff --git a/TODO b/TODO index a3a92824f6670..22239961ef69c 100644 --- a/TODO +++ b/TODO @@ -23,7 +23,6 @@ External: * fedora: update policy to declare access mode and ownership of unit files to root:root 0644, and add an rpmlint check for it * missing shell completions: - - systemd-hwdb * zsh shell completions: - - should complete options, but currently does not diff --git a/shell-completion/bash/meson.build b/shell-completion/bash/meson.build index 178986e17165b..154910979ea56 100644 --- a/shell-completion/bash/meson.build +++ b/shell-completion/bash/meson.build @@ -46,6 +46,7 @@ foreach item : [ ['systemd-delta', ''], ['systemd-detect-virt', ''], ['systemd-dissect', 'HAVE_BLKID'], + ['systemd-hwdb', 'ENABLE_HWDB'], ['systemd-id128', ''], ['systemd-nspawn', 'ENABLE_NSPAWN'], ['systemd-path', ''], diff --git a/shell-completion/bash/systemd-hwdb b/shell-completion/bash/systemd-hwdb new file mode 100644 index 0000000000000..a401bbf1c7f50 --- /dev/null +++ b/shell-completion/bash/systemd-hwdb @@ -0,0 +1,76 @@ +# shellcheck shell=bash +# systemd-hwdb(8) completion -*- shell-script -*- +# 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. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see . + +__contains_word() { + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done +} + +_systemd_hwdb() { + local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} words cword + local i verb comps + + local -A OPTS=( + [STANDALONE]='-h --help --version -s --strict --usr' + [ARG]='-r --root' + ) + + local -A VERBS=( + [STANDALONE]='update' + [ARG]='query' + ) + + _init_completion || return + + if __contains_word "$prev" ${OPTS[ARG]}; then + case $prev in + --root|-r) + comps=$(compgen -A directory -- "$cur") + compopt -o dirnames + ;; + esac + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 + fi + + for ((i=0; i < COMP_CWORD; i++)); do + if __contains_word "${COMP_WORDS[i]}" ${VERBS[*]} && + ! __contains_word "${COMP_WORDS[i-1]}" ${OPTS[ARG]}; then + verb=${COMP_WORDS[i]} + break + fi + done + + if [[ -z ${verb-} ]]; then + COMPREPLY=( $(compgen -W '${OPTS[*]} ${VERBS[*]}' -- "$cur") ) + return 0 + fi + + if [[ "$cur" = -* ]]; then + COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") ) + return 0 + fi + + COMPREPLY=( $(compgen -W '${comps-}' -- "$cur") ) + return 0 +} + +complete -F _systemd_hwdb systemd-hwdb diff --git a/shell-completion/zsh/_systemd-hwdb b/shell-completion/zsh/_systemd-hwdb new file mode 100644 index 0000000000000..92238d63ad3c7 --- /dev/null +++ b/shell-completion/zsh/_systemd-hwdb @@ -0,0 +1,40 @@ +#compdef systemd-hwdb +# SPDX-License-Identifier: LGPL-2.1-or-later + +local context state state_descr line +typeset -A opt_args + +local -a opt_common=( + {-h,--help}'[show this help]' + '--version[show package version]' + {-s,--strict}'[when updating, return non-zero exit value on any parsing error]' + '--usr[generate in /usr/lib/udev instead of /etc/udev]' + {-r+,--root=}'[alternative root path in the filesystem]:path:_directories' +) + +local -a hwdb_commands=( + 'update:update the hwdb database' + 'query:query database and print result' +) + +local ret=1 +_arguments -s -A '-*' "$opt_common[@]" \ + ':command:->command' \ + '*:: :->option-or-argument' && ret=0 + +case $state in + command) + _describe -t command 'systemd-hwdb command' hwdb_commands && ret=0 + ;; + option-or-argument) + case $words[1] in + update) + _arguments -s "$opt_common[@]" && ret=0 + ;; + query) + _arguments -s "$opt_common[@]" ':modalias:' && ret=0 + ;; + esac + ;; +esac +return ret diff --git a/shell-completion/zsh/meson.build b/shell-completion/zsh/meson.build index eb5bb4b6a4a2f..b1bff151e41a3 100644 --- a/shell-completion/zsh/meson.build +++ b/shell-completion/zsh/meson.build @@ -36,6 +36,7 @@ foreach item : [ ['_systemd', ''], ['_systemd-analyze', ''], ['_systemd-delta', ''], + ['_systemd-hwdb', 'ENABLE_HWDB'], ['_systemd-id128', ''], ['_systemd-inhibit', 'ENABLE_LOGIND'], ['_systemd-nspawn', ''], From e4a58c38e9434e834b726622efc4c9f21d316574 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 24 Mar 2026 14:25:50 +0100 Subject: [PATCH 0441/1296] vmspawn: add virtio-scsi disk type support Add --image-disk-type= to select the disk type for the root disk, and allow specifying the disk type as a colon-separated prefix on --extra-drive=: systemd-vmspawn --image-disk-type=virtio-scsi --image=image.raw systemd-vmspawn --image=image.raw --extra-drive=virtio-scsi:data.raw For --extra-drive=, the format and disk type prefixes can appear in any order since the value sets don't overlap: --extra-drive=raw:virtio-scsi:/path --extra-drive=virtio-scsi:raw:/path Extra drives inherit --image-disk-type= by default unless overridden with an explicit prefix. vmspawn originally used virtio-scsi for all drives but switched to virtio-blk in 1f24a954e4 for simplicity and direct kernel boot compatibility. This makes virtio-scsi available again as an explicit option for cases where a SCSI storage topology is desired. For virtio-scsi, a shared virtio-scsi-pci controller is created and drives are attached as scsi-hd devices. The SCSI serial number is limited to 30 characters, so filenames exceeding this are hashed with SHA-256. Signed-off-by: Christian Brauner --- man/systemd-vmspawn.xml | 22 ++++- shell-completion/bash/systemd-vmspawn | 3 + src/vmspawn/vmspawn-settings.c | 7 ++ src/vmspawn/vmspawn-settings.h | 9 ++ src/vmspawn/vmspawn.c | 129 +++++++++++++++++++++++--- 5 files changed, 151 insertions(+), 19 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 331c7c16fd699..80798f7354c61 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -125,6 +125,17 @@ + + + + Specifies the disk type to use for the root disk passed to . + Extra drives added via inherit this disk type unless overridden + with an explicit disk type prefix. Takes one of virtio-blk or + virtio-scsi. Defaults to virtio-blk. + + + + @@ -506,13 +517,14 @@ - + Takes a disk image or block device on the host and supplies it to the virtual - machine as another drive. Optionally, the image format can be specified by prefixing the path with - raw or qcow2 and a colon. The format defaults to - raw. Note that qcow2 is only supported for regular files, not - block devices. + machine as another drive. Optionally, the image format and/or disk type can be specified by prefixing + the path with their values separated by colons. The format and disk type prefixes can appear in any + order. The format defaults to raw and the disk type defaults to the value of + (which itself defaults to virtio-blk). + Note that qcow2 is only supported for regular files, not block devices. diff --git a/shell-completion/bash/systemd-vmspawn b/shell-completion/bash/systemd-vmspawn index b17586de14555..955c59bef5bd8 100644 --- a/shell-completion/bash/systemd-vmspawn +++ b/shell-completion/bash/systemd-vmspawn @@ -38,6 +38,7 @@ _systemd_vmspawn() { [CONSOLE]='--console' [ARG]='--cpus --ram --vsock-cid -M --machine --uuid --private-users --background --set-credential --load-credential' [IMAGE_FORMAT]='--image-format' + [IMAGE_DISK_TYPE]='--image-disk-type' ) _init_completion || return @@ -59,6 +60,8 @@ _systemd_vmspawn() { comps='interactive native gui' elif __contains_word "$prev" ${OPTS[IMAGE_FORMAT]}; then comps='raw qcow2' + elif __contains_word "$prev" ${OPTS[IMAGE_DISK_TYPE]}; then + comps='virtio-blk virtio-scsi' elif __contains_word "$prev" ${OPTS[ARG]}; then comps='' else diff --git a/src/vmspawn/vmspawn-settings.c b/src/vmspawn/vmspawn-settings.c index 46dda4bfc325f..2e594e59b253c 100644 --- a/src/vmspawn/vmspawn-settings.c +++ b/src/vmspawn/vmspawn-settings.c @@ -10,6 +10,13 @@ static const char *const image_format_table[_IMAGE_FORMAT_MAX] = { DEFINE_STRING_TABLE_LOOKUP(image_format, ImageFormat); +static const char *const disk_type_table[_DISK_TYPE_MAX] = { + [DISK_TYPE_VIRTIO_BLK] = "virtio-blk", + [DISK_TYPE_VIRTIO_SCSI] = "virtio-scsi", +}; + +DEFINE_STRING_TABLE_LOOKUP(disk_type, DiskType); + void extra_drive_context_done(ExtraDriveContext *ctx) { assert(ctx); diff --git a/src/vmspawn/vmspawn-settings.h b/src/vmspawn/vmspawn-settings.h index ee937c993ac88..2fe3b84297d62 100644 --- a/src/vmspawn/vmspawn-settings.h +++ b/src/vmspawn/vmspawn-settings.h @@ -10,9 +10,17 @@ typedef enum ImageFormat { _IMAGE_FORMAT_INVALID = -EINVAL, } ImageFormat; +typedef enum DiskType { + DISK_TYPE_VIRTIO_BLK, + DISK_TYPE_VIRTIO_SCSI, + _DISK_TYPE_MAX, + _DISK_TYPE_INVALID = -EINVAL, +} DiskType; + typedef struct ExtraDrive { char *path; ImageFormat format; + DiskType disk_type; } ExtraDrive; typedef struct ExtraDriveContext { @@ -42,4 +50,5 @@ typedef enum SettingsMask { } SettingsMask; DECLARE_STRING_TABLE_LOOKUP(console_mode, ConsoleMode); +DECLARE_STRING_TABLE_LOOKUP(disk_type, DiskType); DECLARE_STRING_TABLE_LOOKUP(image_format, ImageFormat); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index a65f879449e6a..4abc54675b83f 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -65,6 +65,7 @@ #include "ptyfwd.h" #include "random-util.h" #include "rm-rf.h" +#include "sha256.h" #include "signal-util.h" #include "snapshot-util.h" #include "socket-util.h" @@ -143,6 +144,7 @@ static char *arg_background = NULL; static bool arg_pass_ssh_key = true; static char *arg_ssh_key_type = NULL; static bool arg_discard_disk = true; +static DiskType arg_image_disk_type = DISK_TYPE_VIRTIO_BLK; static struct ether_addr arg_network_provided_mac = {}; static char **arg_smbios11 = NULL; static uint64_t arg_grow_image = 0; @@ -206,6 +208,8 @@ static int help(void) { " -x --ephemeral Run VM with snapshot of the disk or directory\n" " -i --image=FILE|DEVICE Root file system disk image or device for the VM\n" " --image-format=FORMAT Specify disk image format (raw, qcow2; default: raw)\n" + " --image-disk-type=TYPE\n" + " Specify disk type (virtio-blk, virtio-scsi)\n" "\n%3$sHost Configuration:%4$s\n" " --cpus=CPUS Configure number of CPUs in guest\n" " --ram=BYTES Configure guest's RAM size\n" @@ -246,9 +250,9 @@ static int help(void) { " Mount a file or directory from the host into the VM\n" " --bind-ro=SOURCE[:TARGET]\n" " Mount a file or directory, but read-only\n" - " --extra-drive=[FORMAT:]PATH\n" + " --extra-drive=[FORMAT:][DISKTYPE:]PATH\n" " Adds an additional disk to the virtual machine\n" - " (FORMAT: raw, qcow2; default: raw)\n" + " (FORMAT: raw, qcow2; DISKTYPE: virtio-blk, virtio-scsi)\n" " --bind-user=NAME Bind user from host to virtual machine\n" " --bind-user-shell=BOOL|PATH\n" " Configure the shell to use for --bind-user= users\n" @@ -335,6 +339,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_SYSTEM, ARG_USER, ARG_IMAGE_FORMAT, + ARG_IMAGE_DISK_TYPE, }; static const struct option options[] = { @@ -344,6 +349,7 @@ static int parse_argv(int argc, char *argv[]) { { "no-pager", no_argument, NULL, ARG_NO_PAGER }, { "image", required_argument, NULL, 'i' }, { "image-format", required_argument, NULL, ARG_IMAGE_FORMAT }, + { "image-disk-type", required_argument, NULL, ARG_IMAGE_DISK_TYPE }, { "ephemeral", no_argument, NULL, 'x' }, { "directory", required_argument, NULL, 'D' }, { "machine", required_argument, NULL, 'M' }, @@ -434,6 +440,13 @@ static int parse_argv(int argc, char *argv[]) { "Invalid image format: %s", optarg); break; + case ARG_IMAGE_DISK_TYPE: + arg_image_disk_type = disk_type_from_string(optarg); + if (arg_image_disk_type < 0) + return log_error_errno(arg_image_disk_type, + "Invalid image disk type: %s", optarg); + break; + case 'M': if (isempty(optarg)) arg_machine = mfree(arg_machine); @@ -570,21 +583,36 @@ static int parse_argv(int argc, char *argv[]) { case ARG_EXTRA_DRIVE: { ImageFormat format = IMAGE_FORMAT_RAW; + DiskType extra_disk_type = _DISK_TYPE_INVALID; const char *dp = optarg; - const char *colon = strchr(dp, ':'); - if (colon) { - _cleanup_free_ char *fs = strndup(optarg, colon - optarg); - if (!fs) + /* Parse optional colon-separated prefixes. The format and disk type + * value sets don't overlap, so they can appear in any order. */ + for (;;) { + const char *colon = strchr(dp, ':'); + if (!colon) + break; + + _cleanup_free_ char *prefix = strndup(dp, colon - dp); + if (!prefix) return log_oom(); - ImageFormat f = image_format_from_string(fs); - if (f < 0) - log_debug_errno(f, "Cannot parse '%s' as an image format, assuming it is a part of path, ignoring.", fs); - else { + ImageFormat f = image_format_from_string(prefix); + if (f >= 0) { format = f; dp = colon + 1; + continue; + } + + DiskType dt = disk_type_from_string(prefix); + if (dt >= 0) { + extra_disk_type = dt; + dp = colon + 1; + continue; } + + /* Not a recognized prefix, treat the rest as the path */ + break; } _cleanup_free_ char *drive_path = NULL; @@ -598,6 +626,7 @@ static int parse_argv(int argc, char *argv[]) { arg_extra_drives.drives[arg_extra_drives.n_drives++] = (ExtraDrive) { .path = TAKE_PTR(drive_path), .format = format, + .disk_type = extra_disk_type, }; break; @@ -1965,6 +1994,31 @@ static int make_sidecar_path(const char *suffix, char **ret) { return 0; } +/* Device serial numbers have length limits (e.g. 30 for SCSI). + * If the filename fits, use it directly; otherwise hash it with SHA-256 and + * take the first max_len hex characters. max_len must be even and <= 64. + * The filename should already be QEMU-escaped (commas doubled) so that the + * result can be embedded directly in a -device argument. */ +static int disk_serial(const char *filename, size_t max_len, char **ret) { + assert(filename); + assert(ret); + assert(max_len % 2 == 0); + assert(max_len <= SHA256_DIGEST_SIZE * 2); + + if (strlen(filename) <= max_len) + return strdup_to(ret, filename); + + uint8_t hash[SHA256_DIGEST_SIZE]; + sha256_direct(filename, strlen(filename), hash); + + _cleanup_free_ char *serial = hexmem(hash, max_len / 2); + if (!serial) + return -ENOMEM; + + *ret = TAKE_PTR(serial); + return 0; +} + static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { _cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL; _cleanup_free_ char *qemu_binary = NULL, *mem = NULL, *kernel = NULL; @@ -2476,6 +2530,22 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } } + bool need_scsi_controller = + arg_image_disk_type == DISK_TYPE_VIRTIO_SCSI && arg_image; + if (!need_scsi_controller) + FOREACH_ARRAY(drive, arg_extra_drives.drives, arg_extra_drives.n_drives) { + DiskType dt = drive->disk_type >= 0 ? drive->disk_type : arg_image_disk_type; + if (dt == DISK_TYPE_VIRTIO_SCSI) { + need_scsi_controller = true; + break; + } + } + + if (need_scsi_controller) { + if (strv_extend_many(&cmdline, "-device", "virtio-scsi-pci,id=vmspawn_scsi") < 0) + return log_oom(); + } + if (arg_image) { assert(!arg_directory); @@ -2510,8 +2580,22 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (strv_extend(&cmdline, "-device") < 0) return log_oom(); - if (strv_extend_joined(&cmdline, "virtio-blk-pci,drive=vmspawn,bootindex=1,serial=", escaped_image_fn) < 0) - return log_oom(); + switch (arg_image_disk_type) { + case DISK_TYPE_VIRTIO_BLK: + if (strv_extend_joined(&cmdline, "virtio-blk-pci,drive=vmspawn,bootindex=1,serial=", escaped_image_fn) < 0) + return log_oom(); + break; + case DISK_TYPE_VIRTIO_SCSI: { + _cleanup_free_ char *serial = NULL; + if (disk_serial(escaped_image_fn, 30, &serial) < 0) + return log_oom(); + if (strv_extend_joined(&cmdline, "scsi-hd,bus=vmspawn_scsi.0,drive=vmspawn,bootindex=1,serial=", serial) < 0) + return log_oom(); + break; + } + default: + assert_not_reached(); + } r = grow_image(arg_image, arg_grow_image); if (r < 0) @@ -2637,8 +2721,25 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (strv_extend(&cmdline, "-device") < 0) return log_oom(); - if (strv_extendf(&cmdline, "virtio-blk-pci,drive=vmspawn_extra_%zu,serial=%s", i++, escaped_drive_fn) < 0) - return log_oom(); + DiskType dt = drive->disk_type >= 0 ? drive->disk_type : arg_image_disk_type; + + switch (dt) { + case DISK_TYPE_VIRTIO_BLK: + if (strv_extendf(&cmdline, "virtio-blk-pci,drive=vmspawn_extra_%zu,serial=%s", i++, escaped_drive_fn) < 0) + return log_oom(); + break; + case DISK_TYPE_VIRTIO_SCSI: { + _cleanup_free_ char *serial = NULL; + r = disk_serial(escaped_drive_fn, 30, &serial); + if (r < 0) + return log_oom(); + if (strv_extendf(&cmdline, "scsi-hd,bus=vmspawn_scsi.0,drive=vmspawn_extra_%zu,serial=%s", i++, serial) < 0) + return log_oom(); + break; + } + default: + assert_not_reached(); + } } if (arg_console_mode != CONSOLE_GUI) { From b76e1732f7c4781db4eb1611f7fdbbd7d8f830ad Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 24 Mar 2026 14:44:34 +0100 Subject: [PATCH 0442/1296] vmspawn: add nvme disk type support Extend --image-disk-type= and the --extra-drive= disk type prefix to support nvme in addition to virtio-blk and virtio-scsi: systemd-vmspawn --image-disk-type=nvme --image=image.raw systemd-vmspawn --image=image.raw --extra-drive=nvme:data.raw The NVMe serial number is limited to 20 characters by the NVMe spec. If the image filename exceeds this, it is hashed with SHA-256 and truncated to 20 hex characters via the disk_serial() helper introduced in the previous commit. Signed-off-by: Christian Brauner --- man/systemd-vmspawn.xml | 5 +++-- shell-completion/bash/systemd-vmspawn | 2 +- src/vmspawn/vmspawn-settings.c | 1 + src/vmspawn/vmspawn-settings.h | 1 + src/vmspawn/vmspawn.c | 26 ++++++++++++++++++++++---- 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 80798f7354c61..eec52f1374259 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -130,8 +130,9 @@ Specifies the disk type to use for the root disk passed to . Extra drives added via inherit this disk type unless overridden - with an explicit disk type prefix. Takes one of virtio-blk or - virtio-scsi. Defaults to virtio-blk. + with an explicit disk type prefix. Takes one of virtio-blk, + virtio-scsi, or nvme. Defaults to + virtio-blk. diff --git a/shell-completion/bash/systemd-vmspawn b/shell-completion/bash/systemd-vmspawn index 955c59bef5bd8..718cb300ce404 100644 --- a/shell-completion/bash/systemd-vmspawn +++ b/shell-completion/bash/systemd-vmspawn @@ -61,7 +61,7 @@ _systemd_vmspawn() { elif __contains_word "$prev" ${OPTS[IMAGE_FORMAT]}; then comps='raw qcow2' elif __contains_word "$prev" ${OPTS[IMAGE_DISK_TYPE]}; then - comps='virtio-blk virtio-scsi' + comps='virtio-blk virtio-scsi nvme' elif __contains_word "$prev" ${OPTS[ARG]}; then comps='' else diff --git a/src/vmspawn/vmspawn-settings.c b/src/vmspawn/vmspawn-settings.c index 2e594e59b253c..7c30ed753f56e 100644 --- a/src/vmspawn/vmspawn-settings.c +++ b/src/vmspawn/vmspawn-settings.c @@ -13,6 +13,7 @@ DEFINE_STRING_TABLE_LOOKUP(image_format, ImageFormat); static const char *const disk_type_table[_DISK_TYPE_MAX] = { [DISK_TYPE_VIRTIO_BLK] = "virtio-blk", [DISK_TYPE_VIRTIO_SCSI] = "virtio-scsi", + [DISK_TYPE_NVME] = "nvme", }; DEFINE_STRING_TABLE_LOOKUP(disk_type, DiskType); diff --git a/src/vmspawn/vmspawn-settings.h b/src/vmspawn/vmspawn-settings.h index 2fe3b84297d62..252ceecceb9a1 100644 --- a/src/vmspawn/vmspawn-settings.h +++ b/src/vmspawn/vmspawn-settings.h @@ -13,6 +13,7 @@ typedef enum ImageFormat { typedef enum DiskType { DISK_TYPE_VIRTIO_BLK, DISK_TYPE_VIRTIO_SCSI, + DISK_TYPE_NVME, _DISK_TYPE_MAX, _DISK_TYPE_INVALID = -EINVAL, } DiskType; diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 4abc54675b83f..72aa39e18a4e5 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -209,7 +209,7 @@ static int help(void) { " -i --image=FILE|DEVICE Root file system disk image or device for the VM\n" " --image-format=FORMAT Specify disk image format (raw, qcow2; default: raw)\n" " --image-disk-type=TYPE\n" - " Specify disk type (virtio-blk, virtio-scsi)\n" + " Specify disk type (virtio-blk, virtio-scsi, nvme; default: virtio-blk)\n" "\n%3$sHost Configuration:%4$s\n" " --cpus=CPUS Configure number of CPUs in guest\n" " --ram=BYTES Configure guest's RAM size\n" @@ -251,8 +251,9 @@ static int help(void) { " --bind-ro=SOURCE[:TARGET]\n" " Mount a file or directory, but read-only\n" " --extra-drive=[FORMAT:][DISKTYPE:]PATH\n" - " Adds an additional disk to the virtual machine\n" - " (FORMAT: raw, qcow2; DISKTYPE: virtio-blk, virtio-scsi)\n" + " Adds an additional disk to the VM\n" + " FORMAT: raw, qcow2\n" + " DISKTYPE: virtio-blk, virtio-scsi, nvme\n" " --bind-user=NAME Bind user from host to virtual machine\n" " --bind-user-shell=BOOL|PATH\n" " Configure the shell to use for --bind-user= users\n" @@ -1994,7 +1995,7 @@ static int make_sidecar_path(const char *suffix, char **ret) { return 0; } -/* Device serial numbers have length limits (e.g. 30 for SCSI). +/* Device serial numbers have length limits (e.g. 20 for NVMe, 30 for SCSI). * If the filename fits, use it directly; otherwise hash it with SHA-256 and * take the first max_len hex characters. max_len must be even and <= 64. * The filename should already be QEMU-escaped (commas doubled) so that the @@ -2593,6 +2594,14 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return log_oom(); break; } + case DISK_TYPE_NVME: { + _cleanup_free_ char *serial = NULL; + if (disk_serial(escaped_image_fn, 20, &serial) < 0) + return log_oom(); + if (strv_extend_joined(&cmdline, "nvme,drive=vmspawn,bootindex=1,serial=", serial) < 0) + return log_oom(); + break; + } default: assert_not_reached(); } @@ -2737,6 +2746,15 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return log_oom(); break; } + case DISK_TYPE_NVME: { + _cleanup_free_ char *serial = NULL; + r = disk_serial(escaped_drive_fn, 20, &serial); + if (r < 0) + return log_oom(); + if (strv_extendf(&cmdline, "nvme,drive=vmspawn_extra_%zu,serial=%s", i++, serial) < 0) + return log_oom(); + break; + } default: assert_not_reached(); } From 53d5f5c02f74105b2205c5181eba98cb4c5568d4 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 23 Mar 2026 11:31:56 +0100 Subject: [PATCH 0443/1296] ci: Drop codeql workflow After analyzing all 218 CodeQL alerts across the project's history, the workflow has not justified its CI cost: - The most impactful query (PotentiallyDangerousFunction) was a custom systemd-specific query that has already been replaced by clang-tidy's bugprone-unsafe-functions check (6fb5ec3dd1). - Of the remaining C++ queries, 6 never triggered at all (bad-strncpy-size, unsafe-strcat, unsafe-strncat, suspicious-pointer-scaling, suspicious-pointer-scaling-void, inconsistent-null-check). - Several high-value-sounding queries had extreme false positive rates: toctou-race-condition (95% FP), use-after-free (88% FP), cleartext-transmission (100% FP). - Many queries that did trigger are already covered by compiler warnings (-Wshadow, -Wformat, -Wunused-variable, -Wreturn-type, -Wtautological-compare) or existing clang-tidy checks (bugprone-sizeof-expression). - Across all alerts, only 3 genuinely useful C++ fixes can be attributed to CodeQL: 1 tainted-format-string, 2 incorrectly-checked-scanf. The rest were either false positives or incidental fixes during refactoring that weren't prompted by CodeQL. - The Python queries are largely superseded by ruff (already in CI) and had an 89% false positive rate on the security-focused checks. The workflow consumed significant CI resources (40+ minutes per run) and the ongoing maintenance burden of triaging false positives outweighs the marginal value of the 2-3 real findings it produced across its entire lifetime. --- .github/codeql-config.yml | 12 -- .github/codeql-custom.qls | 44 ------- .../UninitializedVariableWithCleanup.ql | 110 ------------------ .github/codeql-queries/qlpack.yml | 11 -- .github/workflows/codeql.yml | 68 ----------- docs/CODE_QUALITY.md | 4 - test/integration-tests/README.md | 65 ----------- 7 files changed, 314 deletions(-) delete mode 100644 .github/codeql-config.yml delete mode 100644 .github/codeql-custom.qls delete mode 100644 .github/codeql-queries/UninitializedVariableWithCleanup.ql delete mode 100644 .github/codeql-queries/qlpack.yml delete mode 100644 .github/workflows/codeql.yml diff --git a/.github/codeql-config.yml b/.github/codeql-config.yml deleted file mode 100644 index 7c01d32caa31c..0000000000000 --- a/.github/codeql-config.yml +++ /dev/null @@ -1,12 +0,0 @@ ---- -# vi: ts=2 sw=2 et: -# SPDX-License-Identifier: LGPL-2.1-or-later -name: "CodeQL config" - -disable-default-queries: false - -queries: - - name: Enable possibly useful queries which are disabled by default - uses: ./.github/codeql-custom.qls - - name: systemd-specific CodeQL queries - uses: ./.github/codeql-queries/ diff --git a/.github/codeql-custom.qls b/.github/codeql-custom.qls deleted file mode 100644 index d35fbe3114b93..0000000000000 --- a/.github/codeql-custom.qls +++ /dev/null @@ -1,44 +0,0 @@ ---- -# vi: ts=2 sw=2 et syntax=yaml: -# SPDX-License-Identifier: LGPL-2.1-or-later -# -# Note: it is not recommended to directly reference the respective queries from -# the github/codeql repository, so we have to "dance" around it using -# a custom QL suite -# See: -# - https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#running-additional-queries -# - https://github.com/github/codeql-action/issues/430#issuecomment-806092120 -# - https://codeql.github.com/docs/codeql-cli/creating-codeql-query-suites/ - -# Note: the codeql/-queries pack name can be found in the CodeQL repo[0] -# in /ql/src/qlpack.yml. The respective codeql-suites are then -# under /ql/src/codeql-suites/. -# -# [0] https://github.com/github/codeql -- import: codeql-suites/cpp-lgtm.qls - from: codeql/cpp-queries -- import: codeql-suites/python-lgtm.qls - from: codeql/python-queries -- include: - id: - - cpp/bad-strncpy-size - - cpp/declaration-hides-variable - - cpp/include-non-header - - cpp/inconsistent-null-check - - cpp/mistyped-function-arguments - - cpp/nested-loops-with-same-variable - - cpp/sizeof-side-effect - - cpp/suspicious-pointer-scaling - - cpp/suspicious-pointer-scaling-void - - cpp/suspicious-sizeof - - cpp/unsafe-strcat - - cpp/unsafe-strncat - - cpp/unsigned-difference-expression-compared-zero - - cpp/unused-local-variable - tags: - - "security" - - "correctness" - severity: "error" -- exclude: - id: - - cpp/fixme-comment diff --git a/.github/codeql-queries/UninitializedVariableWithCleanup.ql b/.github/codeql-queries/UninitializedVariableWithCleanup.ql deleted file mode 100644 index e514111f282c0..0000000000000 --- a/.github/codeql-queries/UninitializedVariableWithCleanup.ql +++ /dev/null @@ -1,110 +0,0 @@ -/** - * vi: sw=2 ts=2 et syntax=ql: - * - * Based on cpp/uninitialized-local. - * - * @name Potentially uninitialized local variable using the cleanup attribute - * @description Running the cleanup handler on a possibly uninitialized variable - * is generally a bad idea. - * @id cpp/uninitialized-local-with-cleanup - * @kind problem - * @problem.severity error - * @precision high - * @tags security - */ - -import cpp -import semmle.code.cpp.controlflow.StackVariableReachability - -/** Auxiliary predicate: List cleanup functions we want to explicitly ignore - * since they don't do anything illegal even when the variable is uninitialized - */ -predicate cleanupFunctionDenyList(string fun) { - fun = "erase_char" -} - -/** - * A declaration of a local variable using __attribute__((__cleanup__(x))) - * that leaves the variable uninitialized. - */ -DeclStmt declWithNoInit(LocalVariable v) { - result.getADeclaration() = v and - not v.hasInitializer() and - /* The variable has __attribute__((__cleanup__(...))) set */ - v.getAnAttribute().hasName("cleanup") and - /* Check if the cleanup function is not on a deny list */ - not cleanupFunctionDenyList(v.getAnAttribute().getAnArgument().getValueText()) -} - -class UninitialisedLocalReachability extends StackVariableReachability { - UninitialisedLocalReachability() { this = "UninitialisedLocal" } - - override predicate isSource(ControlFlowNode node, StackVariable v) { node = declWithNoInit(v) } - - /* Note: _don't_ use the `useOfVarActual()` predicate here (and a couple of lines - * below), as it assumes that the callee always modifies the variable if - * it's passed to the function. - * - * i.e.: - * _cleanup_free char *x; - * fun(&x); - * puts(x); - * - * `useOfVarActual()` won't treat this as an uninitialized read even if the callee - * doesn't modify the argument, however, `useOfVar()` will - */ - override predicate isSink(ControlFlowNode node, StackVariable v) { useOfVar(v, node) } - - override predicate isBarrier(ControlFlowNode node, StackVariable v) { - /* only report the _first_ possibly uninitialized use */ - useOfVar(v, node) or - ( - /* If there's a return statement somewhere between the variable declaration - * and a possible definition, don't accept is as a valid initialization. - * - * E.g.: - * _cleanup_free_ char *x; - * ... - * if (...) - * return; - * ... - * x = malloc(...); - * - * is not a valid initialization, since we might return from the function - * _before_ the actual initialization (emphasis on _might_, since we - * don't know if the return statement might ever evaluate to true). - */ - definitionBarrier(v, node) and - not exists(ReturnStmt rs | - /* The attribute check is "just" a complexity optimization */ - v.getFunction() = rs.getEnclosingFunction() and v.getAnAttribute().hasName("cleanup") | - rs.getLocation().isBefore(node.getLocation()) - ) - ) - } -} - -pragma[noinline] -predicate containsInlineAssembly(Function f) { exists(AsmStmt s | s.getEnclosingFunction() = f) } - -/** - * Auxiliary predicate: List common exceptions or false positives - * for this check to exclude them. - */ -VariableAccess commonException() { - /* If the uninitialized use we've found is in a macro expansion, it's - * typically something like va_start(), and we don't want to complain. */ - result.getParent().isInMacroExpansion() - or - result.getParent() instanceof BuiltInOperation - or - /* Finally, exclude functions that contain assembly blocks. It's - * anyone's guess what happens in those. */ - containsInlineAssembly(result.getEnclosingFunction()) -} - -from UninitialisedLocalReachability r, LocalVariable v, VariableAccess va -where - r.reaches(_, v, va) and - not va = commonException() -select va, "The variable $@ may not be initialized here, but has a cleanup handler.", v, v.getName() diff --git a/.github/codeql-queries/qlpack.yml b/.github/codeql-queries/qlpack.yml deleted file mode 100644 index a1a2dec6d6efe..0000000000000 --- a/.github/codeql-queries/qlpack.yml +++ /dev/null @@ -1,11 +0,0 @@ ---- -# vi: ts=2 sw=2 et syntax=yaml: -# SPDX-License-Identifier: LGPL-2.1-or-later - -library: false -name: systemd/cpp-queries -version: 0.0.1 -dependencies: - codeql/cpp-all: "*" - codeql/suite-helpers: "*" -extractor: cpp diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index c7b687c1fcace..0000000000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,68 +0,0 @@ ---- -# vi: ts=2 sw=2 et: -# SPDX-License-Identifier: LGPL-2.1-or-later -# -name: "CodeQL" - -on: - pull_request: - branches: - - main - - v[0-9]+-stable - paths: - - '**/meson.build' - - '.github/**/codeql*' - - 'src/**' - - 'test/**' - - 'tools/**' - push: - branches: - - main - - v[0-9]+-stable - -permissions: - contents: read - -jobs: - analyze: - name: Analyze - if: github.repository != 'systemd/systemd-security' - runs-on: ubuntu-24.04 - concurrency: - group: ${{ github.workflow }}-${{ matrix.language }}-${{ github.ref }} - cancel-in-progress: true - permissions: - actions: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: ['cpp', 'python'] - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - persist-credentials: false - - - name: Initialize CodeQL - uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e - with: - languages: ${{ matrix.language }} - config-file: ./.github/codeql-config.yml - - - run: | - sudo -E .github/workflows/unit-tests.sh SETUP - # TODO: drop after we switch to ubuntu 26.04 - bpftool_binary=$(find /usr/lib/linux-tools/ /usr/lib/linux-tools-* -name 'bpftool' -perm /u=x 2>/dev/null | sort -r | head -n1) - if [ -n "$bpftool_binary" ]; then - sudo rm -f /usr/{bin,sbin}/bpftool - sudo ln -s "$bpftool_binary" /usr/bin/ - fi - - - name: Autobuild - uses: github/codeql-action/autobuild@89a39a4e59826350b863aa6b6252a07ad50cf83e - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e diff --git a/docs/CODE_QUALITY.md b/docs/CODE_QUALITY.md index a9e663bd05790..46ee8d6c8ad37 100644 --- a/docs/CODE_QUALITY.md +++ b/docs/CODE_QUALITY.md @@ -70,10 +70,6 @@ available functionality: 13. When building systemd from a git checkout the build scripts will automatically enable a git commit hook that ensures whitespace cleanliness. -14. [CodeQL](https://codeql.github.com/) analyzes each PR and every commit - pushed to `main`. The list of active alerts can be found - [here](https://github.com/systemd/systemd/security/code-scanning). - 15. Each PR is automatically tested with [Address Sanitizer](https://clang.llvm.org/docs/AddressSanitizer.html) and [Undefined Behavior Sanitizer](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html). See [Testing systemd using sanitizers](/TESTING_WITH_SANITIZERS) diff --git a/test/integration-tests/README.md b/test/integration-tests/README.md index 4fea50660fe21..e0a345613caab 100644 --- a/test/integration-tests/README.md +++ b/test/integration-tests/README.md @@ -339,71 +339,6 @@ where `--test-name=` is the name of the test you want to run/debug. The `--shell-fail` option will pause the execution in case the test fails and shows you the information how to connect to the testbed for further debugging. -## Manually running CodeQL analysis - -This is mostly useful for debugging various CodeQL quirks. - -Download the CodeQL Bundle from https://github.com/github/codeql-action/releases -and unpack it somewhere. From now the 'tutorial' assumes you have the `codeql` -binary from the unpacked archive in $PATH for brevity. - -Switch to the systemd repository if not already: - -```shell -$ cd -``` - -Create an initial CodeQL database: - -```shell -$ CCACHE_DISABLE=1 codeql database create codeqldb --language=cpp -vvv -``` - -Disabling ccache is important, otherwise you might see CodeQL complaining: - -No source code was seen and extracted to -/home/mrc0mmand/repos/@ci-incubator/systemd/codeqldb. This can occur if the -specified build commands failed to compile or process any code. - - Confirm that there is some source code for the specified language in the - project. - - For codebases written in Go, JavaScript, TypeScript, and Python, do not - specify an explicit --command. - - For other languages, the --command must specify a "clean" build which - compiles all the source code files without reusing existing build artefacts. - -If you want to run all queries systemd uses in CodeQL, run: - -```shell -$ codeql database analyze codeqldb/ --format csv --output results.csv .github/codeql-custom.qls .github/codeql-queries/*.ql -vvv -``` - -Note: this will take a while. - -If you're interested in a specific check, the easiest way (without hunting down -the specific CodeQL query file) is to create a custom query suite. For example: - -```shell -$ cat >test.qls < Date: Tue, 24 Mar 2026 16:56:40 +0100 Subject: [PATCH 0444/1296] stat-util: also include inode type in hash ops This doesn't really have any major benefit, but it does make this nicely mirror stat_inode_same() which also checks this triplet for identifying identical inodes. --- src/basic/stat-util.c | 10 +++++++++- src/basic/stat-util.h | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index ef39562992ca0..98dfa8c5a9737 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -709,6 +709,10 @@ nsec_t statx_timestamp_load_nsec(const struct statx_timestamp *ts) { void inode_hash_func(const struct stat *q, struct siphash *state) { siphash24_compress_typesafe(q->st_dev, state); siphash24_compress_typesafe(q->st_ino, state); + + /* Also include inode type, to mirror stat_inode_same() */ + mode_t type = q->st_mode & S_IFMT; + siphash24_compress_typesafe(type, state); } int inode_compare_func(const struct stat *a, const struct stat *b) { @@ -718,7 +722,11 @@ int inode_compare_func(const struct stat *a, const struct stat *b) { if (r != 0) return r; - return CMP(a->st_ino, b->st_ino); + r = CMP(a->st_ino, b->st_ino); + if (r != 0) + return r; + + return CMP(a->st_mode & S_IFMT, b->st_mode & S_IFMT); } DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_hash_ops, struct stat, inode_hash_func, inode_compare_func, free); diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index c261014cd2953..939a0fc398db4 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -122,6 +122,7 @@ int xstatfsat(int dir_fd, const char *path, struct statfs *ret); usec_t statx_timestamp_load(const struct statx_timestamp *ts) _pure_; nsec_t statx_timestamp_load_nsec(const struct statx_timestamp *ts) _pure_; +/* This compares inode number, backing device and inode type, but not modification info */ void inode_hash_func(const struct stat *q, struct siphash *state); int inode_compare_func(const struct stat *a, const struct stat *b); extern const struct hash_ops inode_hash_ops; From 172ae47e659c76c66a2e00ff89f277cf9ce2b5f4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 24 Mar 2026 17:06:09 +0100 Subject: [PATCH 0445/1296] stat-util: introduce inode_unmodified_hash_ops This is almost the same as inode_hash_ops, but also hashes + compares all attributes that could affect the contents of a file. It ignores "superficial"/"external" attributes such as ownership or access mode however. --- src/basic/stat-util.c | 53 +++++++++++++++++++++++++++++++++++++++++++ src/basic/stat-util.h | 6 +++++ 2 files changed, 59 insertions(+) diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 98dfa8c5a9737..ac218d1552017 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -731,6 +731,59 @@ int inode_compare_func(const struct stat *a, const struct stat *b) { DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_hash_ops, struct stat, inode_hash_func, inode_compare_func, free); +void inode_unmodified_hash_func(const struct stat *q, struct siphash *state) { + inode_hash_func(q, state); + + siphash24_compress_typesafe(q->st_mtim.tv_sec, state); + siphash24_compress_typesafe(q->st_mtim.tv_nsec, state); + + if (S_ISREG(q->st_mode)) + siphash24_compress_typesafe(q->st_size, state); + else { + uint64_t invalid = UINT64_MAX; + siphash24_compress_typesafe(invalid, state); + } + + if (S_ISCHR(q->st_mode) || S_ISBLK(q->st_mode)) + siphash24_compress_typesafe(q->st_rdev, state); + else { + dev_t invalid = (dev_t) -1; + siphash24_compress_typesafe(invalid, state); + } +} + +int inode_unmodified_compare_func(const struct stat *a, const struct stat *b) { + int r; + + r = inode_compare_func(a, b); + if (r != 0) + return r; + + r = CMP(a->st_mtim.tv_sec, b->st_mtim.tv_sec); + if (r != 0) + return r; + + r = CMP(a->st_mtim.tv_nsec, b->st_mtim.tv_nsec); + if (r != 0) + return r; + + if (S_ISREG(a->st_mode)) { + r = CMP(a->st_size, b->st_size); + if (r != 0) + return r; + } + + if (S_ISCHR(a->st_mode) || S_ISBLK(a->st_mode)) { + r = CMP(a->st_rdev, b->st_rdev); + if (r != 0) + return r; + } + + return 0; +} + +DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_unmodified_hash_ops, struct stat, inode_unmodified_hash_func, inode_unmodified_compare_func, free); + const char* inode_type_to_string(mode_t m) { /* Returns a short string for the inode type. We use the same name as the underlying macros for each diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index 939a0fc398db4..2b50c9c55888d 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -127,6 +127,12 @@ void inode_hash_func(const struct stat *q, struct siphash *state); int inode_compare_func(const struct stat *a, const struct stat *b); extern const struct hash_ops inode_hash_ops; +/* This is a more thorough version of the above, and also checks the mtimes, the size, and the rdev. It does + * not check "external" attributes such as access mode or ownership. */ +void inode_unmodified_hash_func(const struct stat *q, struct siphash *state); +int inode_unmodified_compare_func(const struct stat *a, const struct stat *b); +extern const struct hash_ops inode_unmodified_hash_ops; + DECLARE_STRING_TABLE_LOOKUP(inode_type, mode_t); /* Macros that check whether the stat/statx structures have been initialized already. For "struct stat" we From 3839f5efec3abc24af880fe77aa5e8736116324d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 20 Mar 2026 14:59:27 +0100 Subject: [PATCH 0446/1296] json-util: optionally accept string-based serialization for IPv4 addresses --- src/libsystemd/sd-json/json-util.c | 16 ++++- src/test/test-json.c | 103 +++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/src/libsystemd/sd-json/json-util.c b/src/libsystemd/sd-json/json-util.c index 7f90b7fc7930c..32578168db03b 100644 --- a/src/libsystemd/sd-json/json-util.c +++ b/src/libsystemd/sd-json/json-util.c @@ -9,6 +9,7 @@ #include "errno-util.h" #include "fd-util.h" #include "glyph-util.h" +#include "in-addr-util.h" #include "iovec-util.h" #include "json-util.h" #include "log.h" @@ -189,12 +190,25 @@ int json_dispatch_in_addr(const char *name, sd_json_variant *variant, sd_json_di return 0; } + /* We support a more human readable string based encoding, and an array based encoding */ + if (sd_json_variant_is_string(variant)) { + union in_addr_union a; + r = in_addr_from_string(AF_INET, sd_json_variant_string(variant), &a); + if (r < 0) + return json_log(variant, flags, r, + "JSON field '%s' is not a valid IPv4 address string: %s", strna(name), sd_json_variant_string(variant)); + + *address = a.in; + return 0; + } + r = json_dispatch_byte_array_iovec(name, variant, flags, &iov); if (r < 0) return r; if (iov.iov_len != sizeof(struct in_addr)) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is array of unexpected size.", strna(name)); + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), + "Expected JSON field '%s' to be an array of %zu bytes.", strna(name), sizeof(struct in_addr)); memcpy(address, iov.iov_base, iov.iov_len); return 0; diff --git a/src/test/test-json.c b/src/test/test-json.c index 4994d42abc43c..e9650339a1a5e 100644 --- a/src/test/test-json.c +++ b/src/test/test-json.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include #include #include @@ -1630,4 +1631,106 @@ TEST(must_be) { ASSERT_OK(sd_json_parse("[]", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); } +TEST(json_dispatch_in_addr) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + + /* 192.168.1.1 = { 192, 168, 1, 1 } */ + ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", JSON_BUILD_IN4_ADDR(&(const struct in_addr) { .s_addr = htobe32(0xC0A80101U) })), + SD_JSON_BUILD_PAIR("null_addr", SD_JSON_BUILD_NULL)))); + + struct { + struct in_addr addr; + struct in_addr null_addr; + } data = {}; + + ASSERT_OK(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, offsetof(typeof(data), addr) }, + { "null_addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, offsetof(typeof(data), null_addr) }, + {}, + }, + /* flags= */ 0, + &data)); + + ASSERT_EQ(be32toh(data.addr.s_addr), 0xC0A80101U); + ASSERT_EQ(data.null_addr.s_addr, 0U); + + struct in_addr dummy = {}; + + /* Too few bytes (3 instead of 4) */ + j = sd_json_variant_unref(j); + ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_UNSIGNED(192), SD_JSON_BUILD_UNSIGNED(168), SD_JSON_BUILD_UNSIGNED(1)))))); + ASSERT_ERROR(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, 0 }, + {}, + }, + /* flags= */ 0, + &dummy), EINVAL); + + /* Too many bytes (5 instead of 4) */ + j = sd_json_variant_unref(j); + ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_UNSIGNED(192), SD_JSON_BUILD_UNSIGNED(168), SD_JSON_BUILD_UNSIGNED(1), SD_JSON_BUILD_UNSIGNED(1), SD_JSON_BUILD_UNSIGNED(0)))))); + ASSERT_ERROR(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, 0 }, + {}, + }, + /* flags= */ 0, + &dummy), EINVAL); + + /* Not an array or string */ + j = sd_json_variant_unref(j); + ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_BOOLEAN(true))))); + ASSERT_ERROR(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, 0 }, + {}, + }, + /* flags= */ 0, + &dummy), EINVAL); + + /* A string */ + j = sd_json_variant_unref(j); + ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", JSON_BUILD_CONST_STRING("192.168.1.1"))))); + zero(data); + ASSERT_OK(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, 0 }, + {}, + }, + /* flags= */ 0, + &data)); + ASSERT_EQ(be32toh(data.addr.s_addr), 0xC0A80101U); + + /* Byte value out of range (> 255) */ + j = sd_json_variant_unref(j); + ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_UNSIGNED(256), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(1)))))); + ASSERT_ERROR(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, 0 }, + {}, + }, + /* flags= */ 0, + &dummy), EINVAL); + + /* Negative element */ + j = sd_json_variant_unref(j); + ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_INTEGER(-1), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(1)))))); + ASSERT_ERROR(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, 0 }, + {}, + }, + /* flags= */ 0, + &dummy), EINVAL); +} + DEFINE_TEST_MAIN(LOG_DEBUG); From 9eb207356c9c75e80e976ee4cb1ad8a11ce8972f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 20 Mar 2026 15:16:19 +0100 Subject: [PATCH 0447/1296] json-util: add json_dispatch_in6_addr() --- src/libsystemd/sd-json/json-util.c | 34 +++++++++++ src/libsystemd/sd-json/json-util.h | 1 + src/test/test-json.c | 94 ++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+) diff --git a/src/libsystemd/sd-json/json-util.c b/src/libsystemd/sd-json/json-util.c index 32578168db03b..c321579ef5093 100644 --- a/src/libsystemd/sd-json/json-util.c +++ b/src/libsystemd/sd-json/json-util.c @@ -214,6 +214,40 @@ int json_dispatch_in_addr(const char *name, sd_json_variant *variant, sd_json_di return 0; } +int json_dispatch_in6_addr(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + struct in6_addr *address = ASSERT_PTR(userdata); + _cleanup_(iovec_done) struct iovec iov = {}; + int r; + + if (sd_json_variant_is_null(variant)) { + *address = (struct in6_addr) {}; + return 0; + } + + /* We support both a more human readable string based encoding and an array based encoding */ + if (sd_json_variant_is_string(variant)) { + union in_addr_union a; + r = in_addr_from_string(AF_INET6, sd_json_variant_string(variant), &a); + if (r < 0) + return json_log(variant, flags, r, + "JSON field '%s' is not a valid IPv6 address string: %s", strna(name), sd_json_variant_string(variant)); + + *address = a.in6; + return 0; + } + + r = json_dispatch_byte_array_iovec(name, variant, flags, &iov); + if (r < 0) + return r; + + if (iov.iov_len != sizeof(struct in6_addr)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), + "Expected JSON field '%s' to be an array of %zu bytes.", strna(name), sizeof(struct in6_addr)); + + memcpy(address, iov.iov_base, iov.iov_len); + return 0; +} + int json_dispatch_const_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { const char **p = ASSERT_PTR(userdata), *path; diff --git a/src/libsystemd/sd-json/json-util.h b/src/libsystemd/sd-json/json-util.h index 847725a41e292..478d2a2a2122b 100644 --- a/src/libsystemd/sd-json/json-util.h +++ b/src/libsystemd/sd-json/json-util.h @@ -115,6 +115,7 @@ int json_dispatch_user_group_name(const char *name, sd_json_variant *variant, sd int json_dispatch_const_user_group_name(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); int json_dispatch_const_unit_name(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); int json_dispatch_in_addr(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); +int json_dispatch_in6_addr(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); int json_dispatch_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); int json_dispatch_const_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); int json_dispatch_strv_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); diff --git a/src/test/test-json.c b/src/test/test-json.c index e9650339a1a5e..d6308b23e7dba 100644 --- a/src/test/test-json.c +++ b/src/test/test-json.c @@ -1733,4 +1733,98 @@ TEST(json_dispatch_in_addr) { &dummy), EINVAL); } +TEST(json_dispatch_in6_addr) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + + /* ::1 */ + ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", JSON_BUILD_IN6_ADDR(&(const struct in6_addr) { .s6_addr = { [15] = 1 } })), + SD_JSON_BUILD_PAIR("null_addr", SD_JSON_BUILD_NULL)))); + + struct { + struct in6_addr addr; + struct in6_addr null_addr; + } data = {}; + + ASSERT_OK(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in6_addr, offsetof(typeof(data), addr) }, + { "null_addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in6_addr, offsetof(typeof(data), null_addr) }, + {}, + }, + /* flags= */ 0, + &data)); + + ASSERT_EQ(data.addr.s6_addr[15], 1); + for (size_t i = 0; i < 15; i++) + ASSERT_EQ(data.addr.s6_addr[i], 0); + for (size_t i = 0; i < 16; i++) + ASSERT_EQ(data.null_addr.s6_addr[i], 0); + + struct in6_addr dummy = {}; + + /* Too few bytes (15 instead of 16) */ + j = sd_json_variant_unref(j); + ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_ARRAY( + SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), + SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), + SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), + SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(1)))))); + ASSERT_ERROR(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in6_addr, 0 }, + {}, + }, + /* flags= */ 0, + &dummy), EINVAL); + + /* Too many bytes (17 instead of 16) */ + j = sd_json_variant_unref(j); + ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_ARRAY( + SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), + SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), + SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), + SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(0), SD_JSON_BUILD_UNSIGNED(1), + SD_JSON_BUILD_UNSIGNED(0)))))); + ASSERT_ERROR(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in6_addr, 0 }, + {}, + }, + /* flags= */ 0, + &dummy), EINVAL); + + /* Not an array */ + j = sd_json_variant_unref(j); + ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_BOOLEAN(true))))); + ASSERT_ERROR(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in6_addr, 0 }, + {}, + }, + /* flags= */ 0, + &dummy), EINVAL); + + /* A string */ + j = sd_json_variant_unref(j); + ASSERT_OK(sd_json_build(&j, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", JSON_BUILD_CONST_STRING("::1"))))); + + zero(data); + ASSERT_OK(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in6_addr, 0 }, + {}, + }, + /* flags= */ 0, + &data)); + + ASSERT_EQ(data.addr.s6_addr[15], 1); + for (size_t i = 0; i < 15; i++) + ASSERT_EQ(data.addr.s6_addr[i], 0); +} + DEFINE_TEST_MAIN(LOG_DEBUG); From 333a9a91ef47f9f9bd226015df819a8aafed7a71 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 26 Feb 2026 15:51:09 +0100 Subject: [PATCH 0448/1296] dns-rr: tighten rules on parsing RR keys from JSON let's ensure the name is actually a valid DNS name. --- src/shared/dns-rr.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/shared/dns-rr.c b/src/shared/dns-rr.c index 0fa730c13baa2..58d26e3609b1b 100644 --- a/src/shared/dns-rr.c +++ b/src/shared/dns-rr.c @@ -2215,6 +2215,12 @@ int dns_resource_key_from_json(sd_json_variant *v, DnsResourceKey **ret) { if (r < 0) return r; + r = dns_name_is_valid(p.name); + if (r < 0) + return r; + if (r == 0) + return -EBADMSG; + key = dns_resource_key_new(p.class, p.type, p.name); if (!key) return -ENOMEM; From 2f1bbebeb96e6745bb13df70d8dbcc99d79b9cdd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 26 Feb 2026 15:50:26 +0100 Subject: [PATCH 0449/1296] dns-rr: add dns_resource_record_from_json() This only parses a small subset of RR types for now, but we can add more later. Covered are the most important RR types: A, AAAA, PTR. --- src/resolve/test-dns-rr.c | 43 +++++++++++++++++- src/shared/dns-rr.c | 95 ++++++++++++++++++++++++++++++++++++++- src/shared/dns-rr.h | 1 + 3 files changed, 135 insertions(+), 4 deletions(-) diff --git a/src/resolve/test-dns-rr.c b/src/resolve/test-dns-rr.c index e45f1d34238b0..2ded6b0ab96f9 100644 --- a/src/resolve/test-dns-rr.c +++ b/src/resolve/test-dns-rr.c @@ -7,6 +7,16 @@ #include "dns-type.h" #include "tests.h" +static void test_to_json_from_json(DnsResourceRecord *rr) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + ASSERT_OK(dns_resource_record_to_json(rr, &j)); + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr2 = NULL; + ASSERT_OK(dns_resource_record_from_json(j, &rr2)); + + ASSERT_TRUE(dns_resource_record_equal(rr, rr2)); +} + /* ================================================================ * DNS_RESOURCE_RECORD_RDATA() * ================================================================ */ @@ -802,6 +812,8 @@ TEST(dns_resource_record_new_address_ipv4) { ASSERT_EQ(rr->key->type, DNS_TYPE_A); ASSERT_STREQ(dns_resource_key_name(rr->key), "www.example.com"); ASSERT_EQ(rr->a.in_addr.s_addr, addr.in.s_addr); + + test_to_json_from_json(rr); } TEST(dns_resource_record_new_address_ipv6) { @@ -818,6 +830,8 @@ TEST(dns_resource_record_new_address_ipv6) { ASSERT_EQ(rr->key->type, DNS_TYPE_AAAA); ASSERT_STREQ(dns_resource_key_name(rr->key), "www.example.com"); ASSERT_EQ(memcmp(&rr->aaaa.in6_addr, &addr.in6, sizeof(struct in6_addr)), 0); + + test_to_json_from_json(rr); } /* ================================================================ @@ -1003,11 +1017,13 @@ TEST(dns_resource_record_equal_cname_copy) { a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_CNAME, "www.example.com"); ASSERT_NOT_NULL(a); - a->cname.name = strdup("example.com"); + a->cname.name = ASSERT_PTR(strdup("example.com")); b = dns_resource_record_copy(a); ASSERT_NOT_NULL(b); ASSERT_TRUE(dns_resource_record_equal(a, b)); + + test_to_json_from_json(a); } TEST(dns_resource_record_equal_cname_fail) { @@ -1220,11 +1236,13 @@ TEST(dns_resource_record_equal_ptr_copy) { a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR, "127.1.168.192.in-addr-arpa"); ASSERT_NOT_NULL(a); - a->ptr.name = strdup("example.com"); + a->ptr.name = ASSERT_PTR(strdup("example.com")); b = dns_resource_record_copy(a); ASSERT_NOT_NULL(b); ASSERT_TRUE(dns_resource_record_equal(a, b)); + + test_to_json_from_json(a); } TEST(dns_resource_record_equal_ptr_fail) { @@ -2461,4 +2479,25 @@ TEST(dns_resource_record_clamp_ttl_copy) { ASSERT_EQ(orig->ttl, 3600u); } +static void test_from_json(const char *text, int expected) { + log_notice("Trying to parse as JSON RR: %s", text); + _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + ASSERT_OK(sd_json_parse(text, /* flags= */ 0, &j, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_EQ(dns_resource_record_from_json(j, NULL), expected); +} + +TEST(from_bad_json) { + test_from_json("{}", -EBADMSG); + test_from_json("{\"key\":{}}", -ENXIO); + test_from_json("{\"key\":{\"name\":\"foobar\",\"type\":9}}", -EOPNOTSUPP); + test_from_json("{\"key\":{\"name\":\"foobar\"}}", -ENXIO); + test_from_json("{\"key\":{\"type\":9}}", -ENXIO); + test_from_json("{\"key\":{\"name\":\"foobar\",\"type\":1}}", -ENXIO); + test_from_json("{\"key\":{\"name\":\"foobar\",\"type\":1},\"address\":[1,2,3,4]}", 0); + test_from_json("{\"key\":{\"name\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"type\":1},\"address\":[1,2,3,4]}", 0); + test_from_json("{\"key\":{\"name\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"type\":1},\"address\":[1,2,3,4]}", -EBADMSG); + test_from_json("{\"key\":{\"name\":\"a.a\",\"type\":1},\"address\":[1,2,3,4]}", 0); + test_from_json("{\"key\":{\"name\":\"a..a\",\"type\":1},\"address\":[1,2,3,4]}", -EBADMSG); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/shared/dns-rr.c b/src/shared/dns-rr.c index 58d26e3609b1b..e807cef3638df 100644 --- a/src/shared/dns-rr.c +++ b/src/shared/dns-rr.c @@ -2308,7 +2308,6 @@ int dns_resource_record_to_json(DnsResourceRecord *rr, sd_json_variant **ret) { int r; assert(rr); - assert(ret); r = dns_resource_key_to_json(rr->key, &k); if (r < 0) @@ -2514,11 +2513,103 @@ int dns_resource_record_to_json(DnsResourceRecord *rr, sd_json_variant **ret) { default: /* Can't provide broken-down format */ - *ret = NULL; + if (ret) + *ret = NULL; return 0; } } +int dns_resource_record_from_json(sd_json_variant *v, DnsResourceRecord **ret) { + int r; + + assert(v); + + sd_json_variant *k = sd_json_variant_by_key(v, "key"); + if (!k) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Resource record entry lacks key field, refusing."); + + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + r = dns_resource_key_from_json(k, &key); + if (r < 0) + return r; + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + rr = dns_resource_record_new(key); + if (!rr) + return log_oom_debug(); + + /* Note, for now we only support the most common subset of RRs for decoding here. Please send patches for more. */ + switch (key->type) { + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: { + _cleanup_free_ char *name = NULL; + + static const struct sd_json_dispatch_field table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, 0, SD_JSON_MANDATORY }, + { "key", SD_JSON_VARIANT_OBJECT, NULL, 0, SD_JSON_MANDATORY }, + {} + }; + + r = sd_json_dispatch(v, table, /* flags= */ 0, &name); + if (r < 0) + return r; + + r = dns_name_is_valid(name); + if (r < 0) + return r; + if (r == 0) + return -EBADMSG; + + rr->ptr.name = TAKE_PTR(name); + break; + } + + case DNS_TYPE_A: { + struct in_addr addr = {}; + + static const struct sd_json_dispatch_field table[] = { + { "address", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, 0, SD_JSON_MANDATORY }, + { "key", SD_JSON_VARIANT_OBJECT, NULL, 0, SD_JSON_MANDATORY }, + {} + }; + + r = sd_json_dispatch(v, table, /* flags= */ 0, &addr); + if (r < 0) + return r; + + rr->a.in_addr = addr; + break; + } + + case DNS_TYPE_AAAA: { + struct in6_addr addr = {}; + + static const struct sd_json_dispatch_field table[] = { + { "address", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in6_addr, 0, SD_JSON_MANDATORY }, + { "key", SD_JSON_VARIANT_OBJECT, NULL, 0, SD_JSON_MANDATORY }, + {} + }; + + r = sd_json_dispatch(v, table, /* flags= */ 0, &addr); + if (r < 0) + return r; + + rr->aaaa.in6_addr = addr; + break; + } + + default: + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Decoding DNS type %s is currently not supported.", dns_type_to_string(key->type)); + } + + if (ret) + *ret = TAKE_PTR(rr); + return 0; +} + static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = { /* Mnemonics as listed on https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */ [DNSSEC_ALGORITHM_RSAMD5] = "RSAMD5", diff --git a/src/shared/dns-rr.h b/src/shared/dns-rr.h index c30cd71cfa5c7..d747083aa8a81 100644 --- a/src/shared/dns-rr.h +++ b/src/shared/dns-rr.h @@ -419,6 +419,7 @@ int dns_resource_record_new_from_raw(DnsResourceRecord **ret, const void *data, int dns_resource_key_to_json(DnsResourceKey *key, sd_json_variant **ret); int dns_resource_key_from_json(sd_json_variant *v, DnsResourceKey **ret); int dns_resource_record_to_json(DnsResourceRecord *rr, sd_json_variant **ret); +int dns_resource_record_from_json(sd_json_variant *v, DnsResourceRecord **ret); void dns_resource_key_hash_func(const DnsResourceKey *k, struct siphash *state); int dns_resource_key_compare_func(const DnsResourceKey *x, const DnsResourceKey *y); From 7ad4411a6105fac649bacefc783d597b317135da Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 4 Mar 2026 17:08:03 +0100 Subject: [PATCH 0450/1296] resolved: also flush /etc/hosts on reload When we are told to reload our configuration also flush out /etc/hosts explicitly. This is particularly relevant since we suppress too frequent reloads, and hence a synchronous way to force a reload is very useful. --- src/resolve/resolved-etc-hosts.c | 1 + src/resolve/resolved-manager.c | 1 + 2 files changed, 2 insertions(+) diff --git a/src/resolve/resolved-etc-hosts.c b/src/resolve/resolved-etc-hosts.c index e9100de5229d0..00c76a9977f85 100644 --- a/src/resolve/resolved-etc-hosts.c +++ b/src/resolve/resolved-etc-hosts.c @@ -72,6 +72,7 @@ void etc_hosts_clear(EtcHosts *hosts) { void manager_etc_hosts_flush(Manager *m) { etc_hosts_clear(&m->etc_hosts); m->etc_hosts_stat = (struct stat) {}; + m->etc_hosts_last = USEC_INFINITY; } static int parse_line(EtcHosts *hosts, unsigned nr, const char *line) { diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index a0fb74ec3567a..19ff92bfca5a9 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -659,6 +659,7 @@ static int manager_dispatch_reload_signal(sd_event_source *s, const struct signa m->unicast_scope = dns_scope_free(m->unicast_scope); m->delegates = hashmap_free(m->delegates); dns_trust_anchor_flush(&m->trust_anchor); + manager_etc_hosts_flush(m); manager_set_defaults(m); From 0718a21c13ef6402fd153835781d13b2f916653d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 26 Feb 2026 15:54:14 +0100 Subject: [PATCH 0451/1296] resolved: add ability to define additional local RRs via drop-ins This is an extension of the /etc/hosts concept, but can provide any kind of RRs (well, actually, we only parse A/AAAA/PTR for now, but the concept is open for more). Fixes: #17791 --- man/resolved.conf.xml | 24 ++- man/rules/meson.build | 1 + man/systemd-resolved.service.xml | 11 ++ man/systemd.rr.xml | 95 +++++++++++ src/resolve/meson.build | 1 + src/resolve/resolved-dns-query.c | 36 ++++ src/resolve/resolved-gperf.gperf | 1 + src/resolve/resolved-manager.c | 5 + src/resolve/resolved-manager.h | 6 + src/resolve/resolved-static-records.c | 226 ++++++++++++++++++++++++++ src/resolve/resolved-static-records.h | 7 + src/resolve/resolved.conf.in | 1 + test/units/TEST-75-RESOLVED.sh | 49 ++++++ 13 files changed, 462 insertions(+), 1 deletion(-) create mode 100644 man/systemd.rr.xml create mode 100644 src/resolve/resolved-static-records.c create mode 100644 src/resolve/resolved-static-records.h diff --git a/man/resolved.conf.xml b/man/resolved.conf.xml index 9adc0143c7c05..f8899fe662c95 100644 --- a/man/resolved.conf.xml +++ b/man/resolved.conf.xml @@ -363,12 +363,33 @@ DNSStubListenerExtra=udp:[2001:db8:0:f102::13]:9953 ReadEtcHosts= Takes a boolean argument. If yes (the default), systemd-resolved will read /etc/hosts, and try to resolve - hosts or address by using the entries in the file before sending query to DNS servers. + hosts or addresses by using the entries in the file before sending query to DNS servers. + + ReadStaticRecords= + Takes a boolean argument. If yes (the default), + systemd-resolved will read + /etc/systemd/resolve/static.d/*.rr, + /run/systemd/resolve/static.d/*.rr, + /usr/local/lib/systemd/resolve/static.d/*.rr, + /usr/lib/systemd/resolve/static.d/*.rr, and try to resolve lookups by using the + entries in these files before sending query to DNS servers. This functionality is very similar to the + one controlled by ReadEtcHosts=, but allows more flexible control of DNS resource + records fields beyond just A/AAAA/PTR. See + systemd.rr5 for + details. + + If both this option and ReadEtcHosts= are enabled then this mechanism takes + precedence: any records discovered via static resource records will take precedence over records + under the same name from /etc/hosts. + + + + ResolveUnicastSingleLabel= Takes a boolean argument. When false (the default), @@ -418,6 +439,7 @@ DNSStubListenerExtra=udp:[2001:db8:0:f102::13]:9953 systemd-resolved.service8 systemd-networkd.service8 dnssec-trust-anchors.d5 + systemd.rr5 resolv.conf5 diff --git a/man/rules/meson.build b/man/rules/meson.build index d2d26abe5da31..682f55c774dd6 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -1280,6 +1280,7 @@ manpages = [ ['systemd.pcrlock', '5', ['systemd.pcrlock.d'], ''], ['systemd.preset', '5', [], ''], ['systemd.resource-control', '5', [], ''], + ['systemd.rr', '5', [], 'ENABLE_RESOLVE'], ['systemd.scope', '5', [], ''], ['systemd.service', '5', [], ''], ['systemd.slice', '5', [], ''], diff --git a/man/systemd-resolved.service.xml b/man/systemd-resolved.service.xml index a5ab48d2fa05c..1d27d2c3c59e7 100644 --- a/man/systemd-resolved.service.xml +++ b/man/systemd-resolved.service.xml @@ -131,6 +131,16 @@ The hostname _localdnsproxy is resolved to the IP address 127.0.0.54, i.e. the address the local DNS proxy (see above) is listening on. + The files matching /etc/systemd/resolve/static.d/*.rr, + /run/systemd/resolve/static.d/*.rr, + /usr/local/lib/systemd/resolve/static.d/*.rr, + /usr/lib/systemd/resolve/static.d/*.rr may be used to define arbitrary + records. See + systemd.rr5 for + details. Support for this may be disabled with ReadStaticRecords=no, see + resolved.conf5. + + The mappings defined in /etc/hosts are resolved to their configured addresses and back, but they will not affect lookups for non-address types (like MX). Support for /etc/hosts may be disabled with ReadEtcHosts=no, @@ -510,6 +520,7 @@ search foobar.com barbar.com systemd1 resolved.conf5 systemd.dns-delegate5 + systemd.rr5 systemd.dnssd5 dnssec-trust-anchors.d5 nss-resolve8 diff --git a/man/systemd.rr.xml b/man/systemd.rr.xml new file mode 100644 index 0000000000000..d6718ccf48e8b --- /dev/null +++ b/man/systemd.rr.xml @@ -0,0 +1,95 @@ + + + + + + + + systemd.rr + systemd + + + + systemd.rr + 5 + + + + systemd.rr + Local static DNS resource record definitions + + + + + /etc/systemd/resolve/static.d/*.rr + /run/systemd/resolve/static.d/*.rr + /usr/local/lib/systemd/resolve/static.d/*.rr + /usr/lib/systemd/resolve/static.d/*.rr + + + + + Description + + *.rr files may be used to define resource record sets ("RRsets") that shall be + resolvable locally, similar in style to address records defined by /etc/hosts (see + hosts5 for + details). These files are read by + systemd-resolved.service8, + and are used to synthesize local responses to local queries matching the defined resource record set. + + These drop-in files are in JSON format. Each file may either contain a single top-level DNS RR + object, or an array of one or more DNS RR objects. Each RR object has at least a key + subobject consisting of a name string field and a type integer + field (which contains the RR type in numeric form). Depending on the chosen type the RR object also has + the following fields: + + + For A/AAAA RRs, the RR object should have an address field set to + either an IP address formatted as string, or an array consisting of 4 or 16 8-bit unsigned integers for + the IP address. + + For PTR/NS/CNAME/DNAME RRs, the RR object should have a name field + set to the name the record shall point to. + + + This JSON serialization of DNS RRs matches the one returned by resolvectl. + + Currently no other RR types are supported. + + + + Examples + + Simple A Record + To make local address lookups for foobar.example.com resolve to the + 192.168.100.1 IPv4 address, create + /run/systemd/resolve/static.d/foobar_example_com.rr: + + +{ + "key" : { + "type" : 1, + "name" : "foobar.example.com" + }, + "address" : [ 192, 168, 100, 1 ] +} + + + + + + See Also + + systemd1 + systemd-resolved.service8 + resolved.conf5 + hosts5 + resolvectl1 + + + + diff --git a/src/resolve/meson.build b/src/resolve/meson.build index be2979343f3f0..b9b2e24b18123 100644 --- a/src/resolve/meson.build +++ b/src/resolve/meson.build @@ -36,6 +36,7 @@ systemd_resolved_extract_sources = files( 'resolved-mdns.c', 'resolved-resolv-conf.c', 'resolved-socket-graveyard.c', + 'resolved-static-records.c', 'resolved-util.c', 'resolved-varlink.c', ) diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index a0ef750447179..6ec6569ae7639 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -21,6 +21,7 @@ #include "resolved-etc-hosts.h" #include "resolved-hook.h" #include "resolved-manager.h" +#include "resolved-static-records.h" #include "resolved-timeouts.h" #include "set.h" #include "string-util.h" @@ -910,6 +911,33 @@ static int dns_query_try_etc_hosts(DnsQuery *q) { return 1; } +static int dns_query_try_static_records(DnsQuery *q) { + int r; + + assert(q); + + if (FLAGS_SET(q->flags, SD_RESOLVED_NO_SYNTHESIZE)) + return 0; + + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + r = manager_static_records_lookup( + q->manager, + q->question_bypass ? q->question_bypass->question : q->question_utf8, + &answer); + if (r <= 0) + return r; + + dns_query_reset_answer(q); + + q->answer = TAKE_PTR(answer); + q->answer_rcode = DNS_RCODE_SUCCESS; + q->answer_protocol = dns_synthesize_protocol(q->flags); + q->answer_family = dns_synthesize_family(q->flags); + q->answer_query_flags = SD_RESOLVED_AUTHENTICATED|SD_RESOLVED_CONFIDENTIAL|SD_RESOLVED_SYNTHETIC; + + return 1; +} + static int dns_query_go_scopes(DnsQuery *q) { int r; @@ -1038,6 +1066,14 @@ int dns_query_go(DnsQuery *q) { q->state != DNS_TRANSACTION_NULL) return 0; + r = dns_query_try_static_records(q); + if (r < 0) + return r; + if (r > 0) { + dns_query_complete(q, DNS_TRANSACTION_SUCCESS); + return 1; + } + r = dns_query_try_etc_hosts(q); if (r < 0) return r; diff --git a/src/resolve/resolved-gperf.gperf b/src/resolve/resolved-gperf.gperf index c548320449b6f..8b8a66d0369bf 100644 --- a/src/resolve/resolved-gperf.gperf +++ b/src/resolve/resolved-gperf.gperf @@ -31,6 +31,7 @@ Resolve.DNSOverTLS, config_parse_dns_over_tls_mode, 0, Resolve.Cache, config_parse_dns_cache_mode, DNS_CACHE_MODE_YES, offsetof(Manager, enable_cache) Resolve.DNSStubListener, config_parse_dns_stub_listener_mode, 0, offsetof(Manager, dns_stub_listener_mode) Resolve.ReadEtcHosts, config_parse_bool, 0, offsetof(Manager, read_etc_hosts) +Resolve.ReadStaticRecords, config_parse_bool, 0, offsetof(Manager, read_static_records) Resolve.ResolveUnicastSingleLabel, config_parse_bool, 0, offsetof(Manager, resolve_unicast_single_label) Resolve.DNSStubListenerExtra, config_parse_dns_stub_listener_extra, 0, offsetof(Manager, dns_extra_stub_listeners) Resolve.CacheFromLocalhost, config_parse_bool, 0, offsetof(Manager, cache_from_localhost) diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 19ff92bfca5a9..25a51ed02b042 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -49,6 +49,7 @@ #include "resolved-mdns.h" #include "resolved-resolv-conf.h" #include "resolved-socket-graveyard.h" +#include "resolved-static-records.h" #include "resolved-util.h" #include "resolved-varlink.h" #include "set.h" @@ -637,6 +638,7 @@ static void manager_set_defaults(Manager *m) { m->enable_cache = DNS_CACHE_MODE_YES; m->dns_stub_listener_mode = DNS_STUB_LISTENER_YES; m->read_etc_hosts = true; + m->read_static_records = true; m->resolve_unicast_single_label = false; m->cache_from_localhost = false; m->stale_retention_usec = 0; @@ -660,6 +662,7 @@ static int manager_dispatch_reload_signal(sd_event_source *s, const struct signa m->delegates = hashmap_free(m->delegates); dns_trust_anchor_flush(&m->trust_anchor); manager_etc_hosts_flush(m); + manager_static_records_flush(m); manager_set_defaults(m); @@ -730,6 +733,7 @@ int manager_new(Manager **ret) { .read_resolv_conf = true, .need_builtin_fallbacks = true, .etc_hosts_last = USEC_INFINITY, + .static_records_last = USEC_INFINITY, .sigrtmin18_info.memory_pressure_handler = manager_memory_pressure, .sigrtmin18_info.memory_pressure_userdata = m, @@ -918,6 +922,7 @@ Manager* manager_free(Manager *m) { dns_trust_anchor_flush(&m->trust_anchor); manager_etc_hosts_flush(m); + manager_static_records_flush(m); while ((sb = hashmap_first(m->dns_service_browsers))) dns_service_browser_free(sb); diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index 4f595e6d04c24..d72e9104d79d0 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -123,6 +123,12 @@ typedef struct Manager { struct stat etc_hosts_stat; bool read_etc_hosts; + /* Data from {/etc,/run,/usr/local/lib,/usr/lib}/systemd/resolve/static.d/ */ + Hashmap *static_records; + usec_t static_records_last; + Set *static_records_stat; + bool read_static_records; + /* List of refused DNS Record Types */ Set *refuse_record_types; diff --git a/src/resolve/resolved-static-records.c b/src/resolve/resolved-static-records.c new file mode 100644 index 0000000000000..4aa6f2e421216 --- /dev/null +++ b/src/resolve/resolved-static-records.c @@ -0,0 +1,226 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "alloc-util.h" +#include "conf-files.h" +#include "constants.h" +#include "dns-answer.h" +#include "dns-domain.h" +#include "dns-question.h" +#include "dns-rr.h" +#include "errno-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "hashmap.h" +#include "json-util.h" +#include "log.h" +#include "resolved-manager.h" +#include "resolved-static-records.h" +#include "set.h" +#include "stat-util.h" + +/* This implements a mechanism to extend what systemd-resolved resolves locally, via .rr drop-ins in + * {/etc,/run,/usr/local/lib,/usr/lib}/systemd/resolve/static.d/. These files are in JSON format, and are RR + * serializations, that match the usual way we serialize RRs to JSON. + * + * Note that this deliberately doesn't use the (probably more user-friendly) classic DNS zone file format, + * to keep things a bit simpler, and symmetric to the places we currently already generate JSON + * serializations of DNS RRs. Also note the semantics are different from DNS zone file format, for example + * regarding delegation (i.e. the RRs defined here have no effect on subdomains), which is probably nicer for + * one-off mappings of domains to specific resources. Or in other words, this is supposed to be a drop-in + * based alternative to /etc/hosts, not a one to DNS zone files. (The JSON format is also a lot more + * extensible to us, for example we could teach it to map certain lookups to specific DNS errors, or extend + * it so that subdomains always get NXDOMAIN or similar). + * + * (That said, if there's a good reason, we can also support *.zone files too one day). + */ + +/* Recheck static records at most once every 2s */ +#define STATIC_RECORDS_RECHECK_USEC (2*USEC_PER_SEC) + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + answer_by_name_hash_ops, + char, + dns_name_hash_func, + dns_name_compare_func, + DnsAnswer, + dns_answer_unref); + +static int load_static_record_file_item(sd_json_variant *rj, Hashmap **records) { + int r; + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + r = dns_resource_record_from_json(rj, &rr); + if (r < 0) + return log_error_errno(r, "Failed to parse DNS record from JSON: %m"); + + _cleanup_(dns_answer_unrefp) DnsAnswer *a = + hashmap_remove(*records, dns_resource_key_name(rr->key)); + + r = dns_answer_add_extend_full(&a, rr, /* ifindex= */ 0, DNS_ANSWER_AUTHENTICATED, /* rrsig= */ NULL, /* until= */ USEC_INFINITY); + if (r < 0) + return log_error_errno(r, "Failed to append RR to DNS answer: %m"); + + DnsAnswerItem *item = ASSERT_PTR(ordered_set_first(a->items)); + + r = hashmap_ensure_put(records, &answer_by_name_hash_ops, dns_resource_key_name(item->rr->key), a); + if (r < 0) + return log_error_errno(r, "Failed to add RR to static record set: %m"); + + TAKE_PTR(a); + + log_debug("Added static resource record: %s", dns_resource_record_to_string(rr)); + return 1; +} + +static int load_static_record_file(const ConfFile *cf, Hashmap **records, Set **stats) { + int r; + + assert(cf); + assert(records); + assert(stats); + + /* Have we seen this file before? Then we might as well skip loading it again, it wouldn't have any + * additional effect anyway. (Note: masking/overriding has already been applied before we reach this + * point, here everything is purely additive.) */ + if (set_contains(*stats, &cf->st)) + return 0; + + _cleanup_free_ struct stat *st_copy = memdup(&cf->st, sizeof(cf->st)); + if (!st_copy) + return log_oom(); + + if (set_ensure_consume(stats, &inode_unmodified_hash_ops, TAKE_PTR(st_copy)) < 0) + return log_oom(); + + _cleanup_fclose_ FILE *f = NULL; + r = xfopenat(cf->fd, /* path= */ NULL, "re", /* open_flags= */ 0, &f); + if (r < 0) { + log_warning_errno(r, "Failed to open '%s', skipping: %m", cf->result); + return 0; + } + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + unsigned line = 0, column = 0; + r = sd_json_parse_file(f, cf->result, /* flags= */ 0, &j, &line, &column); + if (r < 0) { + if (line > 0) + log_syntax(/* unit= */ NULL, LOG_WARNING, cf->result, line, r, "Failed to parse JSON, skipping: %m"); + else + log_warning_errno(r, "Failed to parse JSON file '%s', skipping: %m", cf->result); + return 0; + } + + if (sd_json_variant_is_array(j)) { + sd_json_variant *i; + int ret = 0; + JSON_VARIANT_ARRAY_FOREACH(i, j) + RET_GATHER(ret, load_static_record_file_item(i, records)); + if (ret < 0) + return ret; + } else if (sd_json_variant_is_object(j)) { + r = load_static_record_file_item(j, records); + if (r < 0) + return r; + } else { + log_warning("JSON file '%s' contains neither array nor object, skipping.", cf->result); + return 0; + } + + return 1; +} + +static int manager_static_records_read(Manager *m) { + int r; + + usec_t ts; + assert_se(sd_event_now(m->event, CLOCK_BOOTTIME, &ts) >= 0); + + /* See if we checked the static records db recently already */ + if (m->static_records_last != USEC_INFINITY && usec_add(m->static_records_last, STATIC_RECORDS_RECHECK_USEC) > ts) + return 0; + + m->static_records_last = ts; + + ConfFile **files = NULL; + size_t n_files = 0; + CLEANUP_ARRAY(files, n_files, conf_file_free_many); + + r = conf_files_list_nulstr_full( + ".rr", + /* root= */ NULL, + CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED|CONF_FILES_WARN, + CONF_PATHS_NULSTR("systemd/resolve/static.d/"), + &files, + &n_files); + if (r < 0) + return log_error_errno(r, "Failed to enumerate static record drop-ins: %m"); + + /* Let's suppress reloads if nothing changed. For that keep the set of inodes from the previous + * reload around, and see if there are any changes on them. */ + bool reload; + if (set_size(m->static_records_stat) != n_files) + reload = true; + else { + reload = false; + FOREACH_ARRAY(f, files, n_files) + if (!set_contains(m->static_records_stat, &(*f)->st)) { + reload = true; + break; + } + } + + if (!reload) { + log_debug("No static record files changed, not re-reading."); + return 0; + } + + _cleanup_(hashmap_freep) Hashmap *records = NULL; + _cleanup_(set_freep) Set *stats = NULL; + FOREACH_ARRAY(f, files, n_files) + (void) load_static_record_file(*f, &records, &stats); + + hashmap_free(m->static_records); + m->static_records = TAKE_PTR(records); + + set_free(m->static_records_stat); + m->static_records_stat = TAKE_PTR(stats); + + return 0; +} + +int manager_static_records_lookup(Manager *m, DnsQuestion *q, DnsAnswer **answer) { + int r; + + assert(m); + assert(q); + assert(answer); + + if (!m->read_static_records) + return 0; + + (void) manager_static_records_read(m); + + const char *n = dns_question_first_name(q); + if (!n) + return 0; + + DnsAnswer *f = hashmap_get(m->static_records, n); + if (!f) + return 0; + + r = dns_answer_extend(answer, f); + if (r < 0) + return r; + + return 1; +} + +void manager_static_records_flush(Manager *m) { + assert(m); + + m->static_records = hashmap_free(m->static_records); + m->static_records_stat = set_free(m->static_records_stat); + m->static_records_last = USEC_INFINITY; +} diff --git a/src/resolve/resolved-static-records.h b/src/resolve/resolved-static-records.h new file mode 100644 index 0000000000000..f50c70ef459a6 --- /dev/null +++ b/src/resolve/resolved-static-records.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "resolved-forward.h" + +void manager_static_records_flush(Manager *m); +int manager_static_records_lookup(Manager *m, DnsQuestion* q, DnsAnswer **answer); diff --git a/src/resolve/resolved.conf.in b/src/resolve/resolved.conf.in index 656bc7c0eb7ea..147d30845b129 100644 --- a/src/resolve/resolved.conf.in +++ b/src/resolve/resolved.conf.in @@ -39,6 +39,7 @@ #DNSStubListener=yes #DNSStubListenerExtra= #ReadEtcHosts=yes +#ReadStaticRecords=yes #ResolveUnicastSingleLabel=no #StaleRetentionSec=0 #RefuseRecordTypes= diff --git a/test/units/TEST-75-RESOLVED.sh b/test/units/TEST-75-RESOLVED.sh index b3656da94043a..bb1cf9576c292 100755 --- a/test/units/TEST-75-RESOLVED.sh +++ b/test/units/TEST-75-RESOLVED.sh @@ -1487,6 +1487,55 @@ EOF grep -qF "1.2.3.4" "$RUN_OUT" } +testcase_static_record() { + mkdir -p /run/systemd/resolve/static.d/ + cat >/run/systemd/resolve/static.d/statictest.rr </run/systemd/resolve/static.d/statictest2.rr </run/systemd/resolve/static.d/garbage.rr </run/systemd/resolve/static.d/garbage2.rr < Date: Tue, 24 Mar 2026 19:58:45 +0000 Subject: [PATCH 0452/1296] po: Translated using Weblate (Kabyle) Currently translated at 22.5% (60 of 266 strings) Co-authored-by: Massii Aqvayli Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/kab/ Translation: systemd/main --- po/kab.po | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/po/kab.po b/po/kab.po index 07954f69bbbe7..f37a23008bf35 100644 --- a/po/kab.po +++ b/po/kab.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2026-03-19 20:58+0000\n" +"PO-Revision-Date: 2026-03-24 19:58+0000\n" "Last-Translator: Massii Aqvayli \n" "Language-Team: Kabyle \n" @@ -172,15 +172,15 @@ msgstr "Awal n uɛeddi: " #: src/home/pam_systemd_home.c:349 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." -msgstr "" +msgstr "Awal n uɛeddi d armeɣtu neɣ ur yekfa ara i usesteb n useqdac %s." #: src/home/pam_systemd_home.c:350 msgid "Sorry, try again: " -msgstr "" +msgstr "Suref-aɣ, ɛreḍ tikkelt nniḍen: " #: src/home/pam_systemd_home.c:372 msgid "Recovery key: " -msgstr "" +msgstr "Tasarut n tririt: " #: src/home/pam_systemd_home.c:374 #, c-format @@ -188,15 +188,17 @@ msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " "%s." msgstr "" +"Awal n uɛeddi/tasarut n tririt d armeɣtu neɣ ur yekfa ara i usesteb n " +"useqdac %s." #: src/home/pam_systemd_home.c:375 msgid "Sorry, reenter recovery key: " -msgstr "" +msgstr "Suref-aɣ, sekcem tikelt nniḍen tasarutt n tririt: " #: src/home/pam_systemd_home.c:395 #, c-format msgid "Security token of user %s not inserted." -msgstr "" +msgstr "Tasarutt n tɣellist n useqdac %s ur tettwasekcem ara." #: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 msgid "Try again with password: " @@ -208,70 +210,82 @@ msgid "" "Password incorrect or not sufficient, and configured security token of user " "%s not inserted." msgstr "" +"Awal n uɛeddi d armeɣtu neɣ ur yekfa ara, u tasarut n tasarutt n tɣellist n " +"useqdac %s ur tettwasekcam ara." #: src/home/pam_systemd_home.c:418 msgid "Security token PIN: " -msgstr "" +msgstr "PIN n tsarut n tɣellist: " #: src/home/pam_systemd_home.c:435 #, c-format msgid "Please authenticate physically on security token of user %s." -msgstr "" +msgstr "Ma ulac aɣilif, sesteb s useqdec n tsarut n tɣellist n useqdac %s." #: src/home/pam_systemd_home.c:446 #, c-format msgid "Please confirm presence on security token of user %s." -msgstr "" +msgstr "Ma ulac aɣilif, sentem tilin-ik·im ɣef tsarut n tɣellist useqdac %s." #: src/home/pam_systemd_home.c:457 #, c-format msgid "Please verify user on security token of user %s." -msgstr "" +msgstr "Ma ulac aɣilif, sentem aseqdac ɣef tsarutt n tɣellist n useqdac %s." #: src/home/pam_systemd_home.c:466 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" msgstr "" +"Tangalt PIN n tsarut n tɣellist tettusekkeṛ, ttxil-k·m kkes-as asekkeṛ deg " +"tazwara. (Amatar: Tukksa akked walus n taguri yezmer ad d-yekfu.)" #: src/home/pam_systemd_home.c:474 #, c-format msgid "Security token PIN incorrect for user %s." -msgstr "" +msgstr "Tangalt PIN n tsarutt n tɣellist mačči d tameɣtut i useqdac %s." #: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 #: src/home/pam_systemd_home.c:513 msgid "Sorry, retry security token PIN: " -msgstr "" +msgstr "Suref-aɣ, sekcem tikelt nniḍen tangalt PIN n tsarut n tɣellist: " #: src/home/pam_systemd_home.c:493 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" +"Tangalt PIN n tsarut n tɣellist n useqdac %s d tarmeɣtut (kra n yineɛruḍen " +"kan i d-yegran!)" #: src/home/pam_systemd_home.c:512 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" +"Tangalt PIN n tsarut n tɣellist n useqdac %s d tarmeɣtut (yiwen kan n uɛraḍ " +"i d-yeqqimen!)" #: src/home/pam_systemd_home.c:679 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" +"Akaram agejdan n useqdac %s ur yermid ara akka tura, ttxil-k·m qqen s wudem " +"adigan deg tazwara." #: src/home/pam_systemd_home.c:681 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" +"Akaram agejdan n useqdac %s isekkeṛ akka tura, ttxil-k·m kkes asekkeṛ s " +"wudem adigan deg tazwara." #: src/home/pam_systemd_home.c:715 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." -msgstr "" +msgstr "Ddeqs n uɛraḍ n tuqqna ur neddi ara i useqdac %s, yugi." #: src/home/pam_systemd_home.c:1012 msgid "User record is blocked, prohibiting access." -msgstr "" +msgstr "Yewḥel usekles n useqdac, yegdel anekcum." #: src/home/pam_systemd_home.c:1016 msgid "User record is not valid yet, prohibiting access." From a8c2aa9e2fdde65939300e83f3782237d300e614 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 23 Mar 2026 22:00:03 +0100 Subject: [PATCH 0453/1296] vmspawn: Add headless console support --- man/systemd-vmspawn.xml | 12 +++++++----- shell-completion/bash/systemd-vmspawn | 2 +- src/vmspawn/vmspawn-settings.c | 1 + src/vmspawn/vmspawn-settings.h | 1 + src/vmspawn/vmspawn.c | 11 +++++++++-- 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 331c7c16fd699..23ecc51bac3ee 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -658,11 +658,13 @@ Configures how to set up the console of the VM. Takes one of interactive, read-only, native, - gui. Defaults to interactive. interactive - provides an interactive terminal interface to the VM. read-only is similar, but - is strictly read-only, i.e. does not accept any input from the user. native also - provides a TTY-based interface, but uses qemu native implementation (which means the qemu monitor - is available). gui shows the qemu graphical UI. + gui, headless. Defaults to interactive. + interactive provides an interactive terminal interface to the VM. + read-only is similar, but is strictly read-only, i.e. does not accept any input + from the user. native also provides a TTY-based interface, but uses qemu native + implementation (which means the qemu monitor is available). gui shows the qemu + graphical UI. headless runs the VM without any console, which is useful for + automated or scripted usage. diff --git a/shell-completion/bash/systemd-vmspawn b/shell-completion/bash/systemd-vmspawn index b17586de14555..08e92e0a4b503 100644 --- a/shell-completion/bash/systemd-vmspawn +++ b/shell-completion/bash/systemd-vmspawn @@ -56,7 +56,7 @@ _systemd_vmspawn() { elif __contains_word "$prev" ${OPTS[SSH_KEY]}; then comps='dsa ecdsa ecdsa-sk ed25519 ed25519-sk rsa' elif __contains_word "$prev" ${OPTS[CONSOLE]}; then - comps='interactive native gui' + comps='interactive native gui read-only headless' elif __contains_word "$prev" ${OPTS[IMAGE_FORMAT]}; then comps='raw qcow2' elif __contains_word "$prev" ${OPTS[ARG]}; then diff --git a/src/vmspawn/vmspawn-settings.c b/src/vmspawn/vmspawn-settings.c index 46dda4bfc325f..1c4bc102b94e3 100644 --- a/src/vmspawn/vmspawn-settings.c +++ b/src/vmspawn/vmspawn-settings.c @@ -24,6 +24,7 @@ static const char *const console_mode_table[_CONSOLE_MODE_MAX] = { [CONSOLE_READ_ONLY] = "read-only", [CONSOLE_NATIVE] = "native", [CONSOLE_GUI] = "gui", + [CONSOLE_HEADLESS] = "headless", }; DEFINE_STRING_TABLE_LOOKUP(console_mode, ConsoleMode); diff --git a/src/vmspawn/vmspawn-settings.h b/src/vmspawn/vmspawn-settings.h index ee937c993ac88..1d59db6418b52 100644 --- a/src/vmspawn/vmspawn-settings.h +++ b/src/vmspawn/vmspawn-settings.h @@ -27,6 +27,7 @@ typedef enum ConsoleMode { CONSOLE_READ_ONLY, /* ptyfwd, but in read-only mode */ CONSOLE_NATIVE, /* qemu's native TTY handling */ CONSOLE_GUI, /* qemu's graphical UI */ + CONSOLE_HEADLESS, /* no console */ _CONSOLE_MODE_MAX, _CONSOLE_MODE_INVALID = -EINVAL, } ConsoleMode; diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index a65f879449e6a..d41a9203891a8 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -260,7 +260,7 @@ static int help(void) { " --pass-ssh-key=BOOL Create an SSH key to access the VM\n" " --ssh-key-type=TYPE Choose what type of SSH key to pass\n" "\n%3$sInput/Output:%4$s\n" - " --console=MODE Console mode (interactive, native, gui)\n" + " --console=MODE Console mode (interactive, native, gui, read-only, headless)\n" " --background=COLOR Set ANSI color for background\n" "\n%3$sCredentials:%4$s\n" " --set-credential=ID:VALUE\n" @@ -2365,6 +2365,13 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { "-mon", "console"); break; + case CONSOLE_HEADLESS: + r = strv_extend_many( + &cmdline, + "-nographic", + "-nodefaults"); + break; + default: assert_not_reached(); } @@ -2641,7 +2648,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return log_oom(); } - if (arg_console_mode != CONSOLE_GUI) { + if (!IN_SET(arg_console_mode, CONSOLE_GUI, CONSOLE_HEADLESS)) { r = strv_prepend(&arg_kernel_cmdline_extra, "console=hvc0"); if (r < 0) return log_oom(); From 4c02d63b367bb3618b602fdd17d2b7b7078882f8 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 24 Mar 2026 22:01:40 +0100 Subject: [PATCH 0454/1296] vmspawn: Fix --help width --- src/vmspawn/vmspawn.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 91da71d669832..b017ce85b6247 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -265,7 +265,8 @@ static int help(void) { " --pass-ssh-key=BOOL Create an SSH key to access the VM\n" " --ssh-key-type=TYPE Choose what type of SSH key to pass\n" "\n%3$sInput/Output:%4$s\n" - " --console=MODE Console mode (interactive, native, gui, read-only, headless)\n" + " --console=MODE Console mode (interactive, native, gui, read-only\n" + " or headless)\n" " --background=COLOR Set ANSI color for background\n" "\n%3$sCredentials:%4$s\n" " --set-credential=ID:VALUE\n" From 848dd000e035ed656e8f1f5e5cdefb807acb1db4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 25 Mar 2026 00:32:20 +0100 Subject: [PATCH 0455/1296] shared/options: add helper function to count positional args --- src/shared/options.c | 2 +- src/shared/options.h | 7 ++++++- src/test/test-options.c | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/shared/options.c b/src/shared/options.c index 903ed62440fd1..3847bd648c1fa 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -240,7 +240,7 @@ int option_parse( return option->id; } -char** option_parser_get_args(OptionParser *state, int argc, char *argv[]) { +char** option_parser_get_args(const OptionParser *state, int argc, char *argv[]) { /* Returns positional args as a strv. * If "--" was found, it has been removed. */ diff --git a/src/shared/options.h b/src/shared/options.h index f548538bd048a..fc5b748fc66d6 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -2,6 +2,7 @@ #pragma once #include "shared-forward.h" +#include "strv.h" typedef enum OptionFlags { OPTION_OPTIONAL_ARG = 1U << 0, /* Same as optional_argument in getopt */ @@ -89,7 +90,11 @@ int option_parse( #define FOREACH_OPTION(parser, opt, argc, argv, ret_a, on_error) \ FOREACH_OPTION_FULL(parser, opt, argc, argv, /* ret_o= */ NULL, ret_a, on_error) -char** option_parser_get_args(OptionParser *state, int argc, char *argv[]); +char** option_parser_get_args(const OptionParser *state, int argc, char *argv[]); +static inline size_t option_parser_get_n_args(const OptionParser *state, int argc, char *argv[]) { + return strv_length(option_parser_get_args(state, argc, argv)); +} + int _option_parser_get_help_table( const Option options[], const Option options_end[], diff --git a/src/test/test-options.c b/src/test/test-options.c index ab3a42936958c..201d16d51f895 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -57,6 +57,8 @@ static void test_option_parse_one( char **args = option_parser_get_args(&state, argc, argv); ASSERT_TRUE(strv_equal(args, remaining)); ASSERT_STREQ(argv[0], saved_argv0); + + ASSERT_EQ(option_parser_get_n_args(&state, argc, argv), strv_length(remaining)); } static void test_option_invalid_one( From 9b8d74a4e719252e4cc2f7673f7b2028160881a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 24 Mar 2026 09:34:15 +0100 Subject: [PATCH 0456/1296] ac-power: use the new option parser Co-developed-by: Claude --- src/ac-power/ac-power.c | 67 ++++++++++++++--------------------------- 1 file changed, 23 insertions(+), 44 deletions(-) diff --git a/src/ac-power/ac-power.c b/src/ac-power/ac-power.c index 13382b9994377..ec07a914c59a2 100644 --- a/src/ac-power/ac-power.c +++ b/src/ac-power/ac-power.c @@ -1,13 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "alloc-util.h" #include "ansi-color.h" #include "battery-util.h" #include "build.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pretty-print.h" #include "string-util.h" @@ -20,77 +20,56 @@ static enum { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-ac-power", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTION]\n" - "\n%2$sReport whether we are connected to an external power source.%3$s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -v --verbose Show state as text\n" - " --low Check if battery is discharging and low\n" - "\nSee the %4$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n" + "\n%sReport whether we are connected to an external power source.%s\n" + "\nOptions:\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + table_print(options, stdout); + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_LOW, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "verbose", no_argument, NULL, 'v' }, - { "low", no_argument, NULL, ARG_LOW }, - {} - }; - - int c; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0) + OptionParser state = {}; + const char *arg; + FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) switch (c) { + OPTION_COMMON_HELP: + return help(); - case 'h': - help(); - return 0; - - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'v': + OPTION('v', "verbose", NULL, "Show state as text"): arg_verbose = true; break; - case ARG_LOW: + OPTION_LONG("low", NULL, "Check if battery is discharging and low"): arg_action = ACTION_LOW; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind < argc) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "%s takes no arguments.", - program_invocation_short_name); + if (option_parser_get_n_args(&state, argc, argv) > 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); return 1; } From 0765919f056c5f57a85e01c6f7d3e95257a41615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 24 Mar 2026 09:40:17 +0100 Subject: [PATCH 0457/1296] shared/options: add common option macros for --cat-config and --tldr Co-developed-by: Claude --- src/shared/options.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/shared/options.h b/src/shared/options.h index fc5b748fc66d6..bae42cdcf61cf 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -54,6 +54,10 @@ typedef struct Option { OPTION_LONG("no-pager", NULL, "Do not start a pager") #define OPTION_COMMON_NO_LEGEND \ OPTION_LONG("no-legend", NULL, "Do not show headers and footers") +#define OPTION_COMMON_CAT_CONFIG \ + OPTION_LONG("cat-config", NULL, "Show configuration files") +#define OPTION_COMMON_TLDR \ + OPTION_LONG("tldr", NULL, "Show non-comment parts of configuration") #define OPTION_COMMON_JSON \ OPTION_LONG("json", "FORMAT", "Generate JSON output (pretty, short, or off)") From 1ccfe9ec5f2feaf51a8ba410d57db4a5da1a6ad9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 24 Mar 2026 09:46:16 +0100 Subject: [PATCH 0458/1296] binfmt: use the new option parser Co-developed-by: Claude --- src/binfmt/binfmt.c | 83 ++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 50 deletions(-) diff --git a/src/binfmt/binfmt.c b/src/binfmt/binfmt.c index ee7d2a4d0711e..bdd62398e5ef0 100644 --- a/src/binfmt/binfmt.c +++ b/src/binfmt/binfmt.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -12,8 +11,10 @@ #include "errno-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pager.h" #include "path-util.h" #include "pretty-print.h" @@ -108,88 +109,69 @@ static int cat_config(char **files) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-binfmt.service", "8", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n" - "Registers binary formats with the kernel.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --cat-config Show configuration files\n" - " --tldr Show non-comment parts of configuration\n" - " --no-pager Do not pipe output into a pager\n" - " --unregister Unregister all existing entries\n" - "\nSee the %s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n" + "\n%sRegisters binary formats with the kernel.%s\n" + "\nOptions:\n", program_invocation_short_name, - link); + ansi_highlight(), + ansi_normal()); + table_print(options, stdout); + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_CAT_CONFIG, - ARG_TLDR, - ARG_NO_PAGER, - ARG_UNREGISTER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "cat-config", no_argument, NULL, ARG_CAT_CONFIG }, - { "tldr", no_argument, NULL, ARG_TLDR }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "unregister", no_argument, NULL, ARG_UNREGISTER }, - {} - }; - - int c; +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = {}; + const char *arg; + FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) switch (c) { - - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_CAT_CONFIG: + OPTION_COMMON_CAT_CONFIG: arg_cat_flags = CAT_CONFIG_ON; break; - case ARG_TLDR: + OPTION_COMMON_TLDR: arg_cat_flags = CAT_TLDR; break; - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_UNREGISTER: + OPTION_LONG("unregister", NULL, "Unregister all existing entries"): arg_unregister = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if ((arg_unregister || arg_cat_flags != CAT_CONFIG_OFF) && argc > optind) + char **args = option_parser_get_args(&state, argc, argv); + + if ((arg_unregister || arg_cat_flags != CAT_CONFIG_OFF) && !strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Positional arguments are not allowed with --cat-config/--tldr or --unregister."); + *ret_args = args; return 1; } @@ -208,7 +190,8 @@ static int binfmt_mounted_and_writable_warn(void) { static int run(int argc, char *argv[]) { int r; - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -221,13 +204,13 @@ static int run(int argc, char *argv[]) { if (arg_unregister) return disable_binfmt(); - if (argc > optind) { + if (!strv_isempty(args)) { r = binfmt_mounted_and_writable_warn(); if (r <= 0) return r; - for (int i = optind; i < argc; i++) - RET_GATHER(r, apply_file(argv[i], false)); + STRV_FOREACH(f, args) + RET_GATHER(r, apply_file(*f, false)); } else { _cleanup_strv_free_ char **files = NULL; From 9a96415c692d37e591719594c1e3f08eb8537132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 24 Mar 2026 11:01:37 +0100 Subject: [PATCH 0459/1296] shared/options: allow option help to be extended with fake lines Useful when we want to enumerate options rvalues with custom help texts. --- src/shared/options.c | 5 ++--- src/shared/options.h | 3 +++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/shared/options.c b/src/shared/options.c index 3847bd648c1fa..8ea22c9b7a18d 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -21,7 +21,8 @@ static bool option_arg_required(const Option *opt) { static bool option_is_metadata(const Option *opt) { /* A metadata entry that is not a real option, like the group marker */ - return FLAGS_SET(ASSERT_PTR(opt)->flags, OPTION_GROUP_MARKER); + return FLAGS_SET(ASSERT_PTR(opt)->flags, OPTION_GROUP_MARKER) || + FLAGS_SET(ASSERT_PTR(opt)->flags, OPTION_HELP_ENTRY); } static void kill_arg(char* argv[], int argc, int index) { @@ -274,8 +275,6 @@ int _option_parser_get_help_table( if (group_marker) break; /* End of group */ - assert(!option_is_metadata(opt)); - if (!opt->help) /* No help string — we do not show the option */ continue; diff --git a/src/shared/options.h b/src/shared/options.h index bae42cdcf61cf..fd2d048db00c4 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -8,6 +8,7 @@ typedef enum OptionFlags { OPTION_OPTIONAL_ARG = 1U << 0, /* Same as optional_argument in getopt */ OPTION_STOPS_PARSING = 1U << 1, /* This option acts like "--" */ OPTION_GROUP_MARKER = 1U << 2, /* Fake option entry to separate groups */ + OPTION_HELP_ENTRY = 1U << 3, /* Fake option entry to insert an additional help line */ } OptionFlags; typedef struct Option { @@ -44,7 +45,9 @@ typedef struct Option { #define OPTION_FULL(fl, sc, lc, mv, h) _OPTION(__COUNTER__, fl, sc, lc, mv, h) #define OPTION(sc, lc, mv, h) OPTION_FULL(/* fl= */ 0, sc, lc, mv, h) #define OPTION_LONG(lc, mv, h) OPTION(/* sc= */ 0, lc, mv, h) +#define OPTION_LONG_FLAGS(fl, lc, mv, h) OPTION_FULL(fl, /* sc= */ 0, lc, mv, h) #define OPTION_SHORT(sc, mv, h) OPTION(sc, /* lc= */ NULL, mv, h) +#define OPTION_SHORT_FLAGS(fl, sc, mv, h) OPTION_FULL(fl, sc, /* lc= */ NULL, mv, h) #define OPTION_COMMON_HELP \ OPTION('h', "help", NULL, "Show this help") From 6e2c5624e6de111f81605316ba8dad79d06b9827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 24 Mar 2026 10:12:13 +0100 Subject: [PATCH 0460/1296] ask-password: use the new option parser --version was missing from the old help text and is now included. Co-developed-by: Claude --- src/ask-password/ask-password.c | 156 +++++++++++--------------------- 1 file changed, 51 insertions(+), 105 deletions(-) diff --git a/src/ask-password/ask-password.c b/src/ask-password/ask-password.c index f6634b54f6891..13e23687952a8 100644 --- a/src/ask-password/ask-password.c +++ b/src/ask-password/ask-password.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-varlink.h" @@ -10,10 +9,12 @@ #include "build.h" #include "bus-polkit.h" #include "constants.h" +#include "format-table.h" #include "hashmap.h" #include "json-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" #include "string-table.h" @@ -39,124 +40,72 @@ STATIC_DESTRUCTOR_REGISTER(arg_message, freep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-ask-password", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] MESSAGE\n\n" - "%3$sQuery the user for a passphrase, via the TTY or a UI agent.%4$s\n\n" - " -h --help Show this help\n" - " --icon=NAME Icon name\n" - " --id=ID Query identifier (e.g. \"cryptsetup:/dev/sda5\")\n" - " --keyname=NAME Kernel key name for caching passwords (e.g. \"cryptsetup\")\n" - " --credential=NAME\n" - " Credential name for ImportCredential=, LoadCredential= or\n" - " SetCredential= credentials\n" - " --timeout=SEC Timeout in seconds\n" - " --echo=yes|no|masked\n" - " Control whether to show password while typing (echo)\n" - " -e --echo Equivalent to --echo=yes\n" - " --emoji=yes|no|auto\n" - " Show a lock and key emoji\n" - " --no-tty Ask question via agent even on TTY\n" - " --accept-cached Accept cached passwords\n" - " --multiple List multiple passwords if available\n" - " --no-output Do not print password to standard output\n" - " -n Do not suffix password written to standard output with\n" - " newline\n" - " --user Ask only our own user's agents\n" - " --system Ask agents of the system and of all users\n" - "\nSee the %2$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] MESSAGE\n" + "\n%sQuery the user for a passphrase, via the TTY or a UI agent.%s\n" + "\nOptions:\n", program_invocation_short_name, - link, ansi_highlight(), ansi_normal()); + table_print(options, stdout); + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_ICON = 0x100, - ARG_TIMEOUT, - ARG_EMOJI, - ARG_NO_TTY, - ARG_ACCEPT_CACHED, - ARG_MULTIPLE, - ARG_ID, - ARG_KEYNAME, - ARG_NO_OUTPUT, - ARG_VERSION, - ARG_CREDENTIAL, - ARG_USER, - ARG_SYSTEM, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "icon", required_argument, NULL, ARG_ICON }, - { "timeout", required_argument, NULL, ARG_TIMEOUT }, - { "echo", optional_argument, NULL, 'e' }, - { "emoji", required_argument, NULL, ARG_EMOJI }, - { "no-tty", no_argument, NULL, ARG_NO_TTY }, - { "accept-cached", no_argument, NULL, ARG_ACCEPT_CACHED }, - { "multiple", no_argument, NULL, ARG_MULTIPLE }, - { "id", required_argument, NULL, ARG_ID }, - { "keyname", required_argument, NULL, ARG_KEYNAME }, - { "no-output", no_argument, NULL, ARG_NO_OUTPUT }, - { "credential", required_argument, NULL, ARG_CREDENTIAL }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - {} - }; - const char *emoji = NULL; - int c, r; + int r; assert(argc >= 0); assert(argv); - /* Note the asymmetry: the long option --echo= allows an optional argument, the short option does - * not. */ - - /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long() - * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */ - optind = 0; - while ((c = getopt_long(argc, argv, "+hen", options, NULL)) >= 0) + OptionParser state = {}; + const char *arg; + FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) switch (c) { - - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_ICON: - arg_icon = optarg; + OPTION_LONG("icon", "NAME", "Icon name"): + arg_icon = arg; break; - case ARG_TIMEOUT: - r = parse_sec(optarg, &arg_timeout); + OPTION_LONG("timeout", "SEC", "Timeout in seconds"): + r = parse_sec(arg, &arg_timeout); if (r < 0) - return log_error_errno(r, "Failed to parse --timeout= parameter: %s", optarg); - + return log_error_errno(r, "Failed to parse --timeout= parameter: %s", arg); break; - case 'e': - if (!optarg) { + /* Note the asymmetry: the long option --echo= allows an optional argument, + * the short option does not. */ + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "echo", "yes|no|masked", + "Control whether to show password while typing"): {} + OPTION('e', "echo", NULL, "Equivalent to --echo=yes"): + if (!arg) { /* Short option -e is used, or no argument to long option --echo= */ arg_flags |= ASK_PASSWORD_ECHO; arg_flags &= ~ASK_PASSWORD_SILENT; - } else if (isempty(optarg) || streq(optarg, "masked")) + } else if (isempty(arg) || streq(arg, "masked")) /* Empty argument or explicit string "masked" for default behaviour. */ arg_flags &= ~(ASK_PASSWORD_ECHO|ASK_PASSWORD_SILENT); else { - r = parse_boolean_argument("--echo=", optarg, NULL); + r = parse_boolean_argument("--echo=", arg, NULL); if (r < 0) return r; @@ -165,55 +114,50 @@ static int parse_argv(int argc, char *argv[]) { } break; - case ARG_EMOJI: - emoji = optarg; + OPTION_LONG("emoji", "yes|no|auto", "Show a lock and key emoji"): + emoji = arg; break; - case ARG_NO_TTY: + OPTION_LONG("no-tty", NULL, "Ask question via agent even on TTY"): arg_flags |= ASK_PASSWORD_NO_TTY; break; - case ARG_ACCEPT_CACHED: + OPTION_LONG("accept-cached", NULL, "Accept cached passwords"): arg_flags |= ASK_PASSWORD_ACCEPT_CACHED; break; - case ARG_MULTIPLE: + OPTION_LONG("multiple", NULL, "List multiple passwords if available"): arg_multiple = true; break; - case ARG_ID: - arg_id = optarg; + OPTION_LONG("id", "ID", "Query identifier (e.g. \"cryptsetup:/dev/sda5\")"): + arg_id = arg; break; - case ARG_KEYNAME: - arg_key_name = optarg; + OPTION_LONG("keyname", "NAME", "Kernel key name for caching passwords"): + arg_key_name = arg; break; - case ARG_NO_OUTPUT: + OPTION_LONG("no-output", NULL, "Do not print password to standard output"): arg_no_output = true; break; - case ARG_CREDENTIAL: - arg_credential_name = optarg; + OPTION_LONG("credential", "NAME", + "Credential name for ImportCredential=, LoadCredential= or SetCredential= credentials"): + arg_credential_name = arg; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Ask only our own user's agents"): arg_flags |= ASK_PASSWORD_USER; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Ask agents of the system and of all users"): arg_flags &= ~ASK_PASSWORD_USER; break; - case 'n': + OPTION_SHORT('n', NULL, "Do not suffix password written to standard output with newline"): arg_newline = false; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (isempty(emoji) || streq(emoji, "auto")) @@ -226,8 +170,10 @@ static int parse_argv(int argc, char *argv[]) { SET_FLAG(arg_flags, ASK_PASSWORD_HIDE_EMOJI, !r); } - if (argc > optind) { - arg_message = strv_join(argv + optind, " "); + char **args = option_parser_get_args(&state, argc, argv); + + if (!strv_isempty(args)) { + arg_message = strv_join(args, " "); if (!arg_message) return log_oom(); } else if (FLAGS_SET(arg_flags, ASK_PASSWORD_ECHO)) { From 58e891e5f6168ae9d10629aaafdeb8eb22f49ae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 24 Mar 2026 11:07:06 +0100 Subject: [PATCH 0461/1296] update-done: use the new option parser While at it, add --version. Co-developed-by: Claude --- man/systemd-update-done.service.xml | 1 + src/update-done/update-done.c | 59 +++++++++++++---------------- 2 files changed, 27 insertions(+), 33 deletions(-) diff --git a/man/systemd-update-done.service.xml b/man/systemd-update-done.service.xml index d9d78262a142e..8bb92ca5b5043 100644 --- a/man/systemd-update-done.service.xml +++ b/man/systemd-update-done.service.xml @@ -79,6 +79,7 @@ + diff --git a/src/update-done/update-done.c b/src/update-done/update-done.c index c50acca04529d..03e5479aaefa4 100644 --- a/src/update-done/update-done.c +++ b/src/update-done/update-done.c @@ -1,16 +1,18 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" +#include "build.h" #include "chase.h" #include "errno-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "label-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" #include "string-util.h" @@ -61,65 +63,56 @@ static int save_timestamp(const char *dir, struct timespec *ts) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-update-done", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...]\n\n" - "%5$sMark /etc/ and /var/ as fully updated.%6$s\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --root=PATH Operate on root directory PATH\n" - "\nSee the %2$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n" + "\n%sMark /etc/ and /var/ as fully updated.%s\n" + "\n%sOptions:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); + table_print(options, stdout); + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_ROOT = 0x100, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "root", required_argument, NULL, ARG_ROOT }, - {}, - }; - - int r, c; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = {}; + const char *arg; + FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) switch (c) { - - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root); + OPTION_COMMON_VERSION: + return version(); + + OPTION_LONG("root", "PATH", "Operate on root directory PATH"): + r = parse_path_argument(arg, /* suppress_root= */ true, &arg_root); if (r < 0) return r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind < argc) + if (option_parser_get_n_args(&state, argc, argv) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); return 1; From 34d08e2ec3260b511011595f6ea40925c8d94ef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 24 Mar 2026 11:47:09 +0100 Subject: [PATCH 0462/1296] validatefs: use the new option parser Co-developed-by: Claude --- src/validatefs/validatefs.c | 70 ++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/src/validatefs/validatefs.c b/src/validatefs/validatefs.c index 74a784d8ab1f9..2b760a285b14a 100644 --- a/src/validatefs/validatefs.c +++ b/src/validatefs/validatefs.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-device.h" #include "alloc-util.h" @@ -12,11 +10,13 @@ #include "device-util.h" #include "errno-util.h" #include "fd-util.h" +#include "format-table.h" #include "gpt.h" #include "initrd-util.h" #include "log.h" #include "main-func.h" #include "mountpoint-util.h" +#include "options.h" #include "parse-argument.h" #include "path-util.h" #include "pretty-print.h" @@ -32,55 +32,49 @@ STATIC_DESTRUCTOR_REGISTER(arg_target, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); static int help(void) { + _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - _cleanup_free_ char *link = NULL; r = terminal_urlify_man("systemd-validatefs@.service", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] /path/to/mountpoint\n" - "\n%3$sCheck file system validation constraints.%4$s\n\n" - " -h --help Show this help and exit\n" - " --version Print version string and exit\n" - " --root=PATH|auto Operate relative to the specified path\n" - "\nSee the %2$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] /path/to/mountpoint\n" + "\n%sCheck file system validation constraints.%s\n" + "\nOptions:\n", program_invocation_short_name, - link, ansi_highlight(), ansi_normal()); + table_print(options, stdout); + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_ROOT, - }; - - int c, r; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version" , no_argument, NULL, ARG_VERSION }, - { "root", required_argument, NULL, ARG_ROOT }, - {} - }; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = {}; + const char *arg; + + FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_ROOT: - if (streq(optarg, "auto")) { + OPTION_LONG("root", "PATH|auto", "Operate relative to the specified path"): + if (streq(arg, "auto")) { arg_root = mfree(arg_root); if (in_initrd()) { @@ -92,27 +86,23 @@ static int parse_argv(int argc, char *argv[]) { break; } - if (!path_is_absolute(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--root= argument must be 'auto' or absolute path, got: %s", optarg); + if (!path_is_absolute(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--root= argument must be 'auto' or absolute path, got: %s", arg); - r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root); + r = parse_path_argument(arg, /* suppress_root= */ true, &arg_root); if (r < 0) return r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind + 1 != argc) + char **args = option_parser_get_args(&state, argc, argv); + + if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "%s excepts exactly one argument (the mount point).", + "%s expects exactly one argument (the mount point).", program_invocation_short_name); - arg_target = strdup(argv[optind]); + arg_target = strdup(args[0]); if (!arg_target) return log_oom(); From ca6c3bd0e54971de92ff7e270ec7aa8a3d109145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 24 Mar 2026 11:40:12 +0100 Subject: [PATCH 0463/1296] validatefs: shorten and reindent code --- src/validatefs/validatefs.c | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/validatefs/validatefs.c b/src/validatefs/validatefs.c index 2b760a285b14a..c507e4d98a472 100644 --- a/src/validatefs/validatefs.c +++ b/src/validatefs/validatefs.c @@ -74,22 +74,15 @@ static int parse_argv(int argc, char *argv[]) { return version(); OPTION_LONG("root", "PATH|auto", "Operate relative to the specified path"): - if (streq(arg, "auto")) { - arg_root = mfree(arg_root); - - if (in_initrd()) { - arg_root = strdup("/sysroot"); - if (!arg_root) - return log_oom(); - } - - break; + if (streq(arg, "auto")) + r = free_and_strdup_warn(&arg_root, in_initrd() ? "/sysroot" : NULL); + else { + if (!path_is_absolute(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--root= argument must be 'auto' or absolute path, got: %s", arg); + + r = parse_path_argument(arg, /* suppress_root= */ true, &arg_root); } - - if (!path_is_absolute(arg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--root= argument must be 'auto' or absolute path, got: %s", arg); - - r = parse_path_argument(arg, /* suppress_root= */ true, &arg_root); if (r < 0) return r; break; @@ -107,8 +100,9 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); if (arg_root && !path_startswith(arg_target, arg_root)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path '%s' does not start with specified root '%s', refusing.", arg_target, arg_root); - + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Specified path '%s' does not start with specified root '%s', refusing.", + arg_target, arg_root); return 1; } From 7be9bb1845a8a521d16492213110eb043a7bb0c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 24 Mar 2026 23:33:49 +0100 Subject: [PATCH 0464/1296] shared/verbs: allow multiple verbs to be handled by a single function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With the uintptr_t data parameter, it is actually quite nice to have VERB(do_impl, "name-a", …) VERB(do_impl, "name-b", …) int do_impl(…) { … } To make this work, the do_impl_data struct needs to have a unique name and we also need to suppress the warning about the forward declaration for do_impl being repeated. I think it's fine to suppress the warning, it's not needed for anything. If somebody declares the function with the same name by mistake, the implementations are going to conflict too. --- src/fundamental/macro-fundamental.h | 1 + src/shared/verbs.h | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro-fundamental.h index 39004183d90f2..1941e88d3760e 100644 --- a/src/fundamental/macro-fundamental.h +++ b/src/fundamental/macro-fundamental.h @@ -143,6 +143,7 @@ #define XCONCATENATE(x, y) x ## y #define CONCATENATE(x, y) XCONCATENATE(x, y) +#define CONCATENATE3(x, y, z) CONCATENATE(x, CONCATENATE(y, z)) #define assert_cc(expr) _Static_assert(expr, #expr) diff --git a/src/shared/verbs.h b/src/shared/verbs.h index 062e49466f9ea..b7bc66fc19515 100644 --- a/src/shared/verbs.h +++ b/src/shared/verbs.h @@ -21,13 +21,15 @@ typedef struct { } Verb; #define VERB_FULL(d, v, a, amin, amax, f, dat, h) \ + DISABLE_WARNING_REDUNDANT_DECLS \ static int d(int, char**, uintptr_t, void*); \ + REENABLE_WARNING \ _section_("SYSTEMD_VERBS") \ _alignptr_ \ _used_ \ _retain_ \ _variable_no_sanitize_address_ \ - static const Verb CONCATENATE(d, _data) = { \ + static const Verb CONCATENATE3(d, _data_, __COUNTER__) = { \ .verb = v, \ .min_args = amin, \ .max_args = amax, \ From baae6e9476cc47a69219e186910e0178d659d699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 24 Mar 2026 12:54:40 +0100 Subject: [PATCH 0465/1296] shared/verbs: add VERB_COMMON_HELP_HIDDEN macro and skip verbs with NULL help Co-developed-by: Claude --- src/shared/verbs.c | 6 ++++-- src/shared/verbs.h | 11 +++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/shared/verbs.c b/src/shared/verbs.c index bf2d440bb0ec8..47f219fab6fcc 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -160,6 +160,9 @@ int _verbs_get_help_table(const Verb verbs[], const Verb verbs_end[], Table **re for (const Verb *verb = verbs; verb < verbs_end; verb++) { assert(verb->dispatch); + if (!verb->help) + continue; + /* We indent the option string by two spaces. We could set the minimum cell width and * right-align for a similar result, but that'd be more work. This is only used for * display. */ @@ -170,8 +173,7 @@ int _verbs_get_help_table(const Verb verbs[], const Verb verbs_end[], Table **re if (r < 0) return table_log_add_error(r); - const char *help = verb->help ?: "FIXME"; - _cleanup_strv_free_ char **s = strv_split(help, /* separators= */ NULL); + _cleanup_strv_free_ char **s = strv_split(verb->help, /* separators= */ NULL); if (!s) return log_oom(); diff --git a/src/shared/verbs.h b/src/shared/verbs.h index b7bc66fc19515..7980db62fe913 100644 --- a/src/shared/verbs.h +++ b/src/shared/verbs.h @@ -68,8 +68,15 @@ int _verbs_get_help_table(const Verb verbs[], const Verb verbs_end[], Table **re #define verbs_get_help_table(ret) \ _verbs_get_help_table(ALIGN_PTR(__start_SYSTEMD_VERBS), __stop_SYSTEMD_VERBS, ret) -#define VERB_COMMON_HELP(impl) \ - VERB(verb_help, "help", NULL, VERB_ANY, VERB_ANY, 0, "Show this help"); \ +#define _VERB_COMMON_HELP_IMPL(impl) \ static int verb_help(int argc, char **argv, uintptr_t data, void *userdata) { \ return impl(); \ } + +#define VERB_COMMON_HELP(impl) \ + VERB(verb_help, "help", NULL, VERB_ANY, VERB_ANY, 0, "Show this help"); \ + _VERB_COMMON_HELP_IMPL(impl) + +#define VERB_COMMON_HELP_HIDDEN(impl) \ + VERB(verb_help, "help", NULL, VERB_ANY, VERB_ANY, 0, NULL); \ + _VERB_COMMON_HELP_IMPL(impl) From cad2dca504cbcb642c8b816bd86467252d83e3e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 24 Mar 2026 10:20:52 +0100 Subject: [PATCH 0466/1296] bless-boot: use the new option parser and verb macros Co-developed-by: Claude --- src/bless-boot/bless-boot.c | 95 ++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 53 deletions(-) diff --git a/src/bless-boot/bless-boot.c b/src/bless-boot/bless-boot.c index c4a9eeee76cea..daabff405f226 100644 --- a/src/bless-boot/bless-boot.c +++ b/src/bless-boot/bless-boot.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" @@ -9,9 +8,11 @@ #include "efivars.h" #include "fd-util.h" #include "find-esp.h" +#include "format-table.h" #include "fs-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-util.h" #include "path-util.h" #include "pretty-print.h" @@ -34,76 +35,65 @@ typedef enum Status { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-bless-boot.service", "8", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + (void) table_sync_column_width(options, 0, verbs, 0); + printf("%s [OPTIONS...] COMMAND\n" "\n%sMark the boot process as good or bad.%s\n" - "\nCommands:\n" - " status Show status of current boot loader entry\n" - " good Mark this boot as good\n" - " bad Mark this boot as bad\n" - " indeterminate Undo any marking as good or bad\n" - "\nOptions:\n" - " -h --help Show this help\n" - " --version Print version\n" - " --path=PATH Path to the $BOOT partition (may be used multiple times)\n" - "\nSee the %s for details.\n", + "\nCommands:\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + table_print(verbs, stdout); - return 0; -} + printf("\nOptions:\n"); + table_print(options, stdout); -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); + printf("\nSee the %s for details.\n", link); + return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_PATH = 0x100, - ARG_VERSION, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "path", required_argument, NULL, ARG_PATH }, - {} - }; +VERB_COMMON_HELP_HIDDEN(help); - int c, r; +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - switch (c) { + OptionParser state = {}; + const char *arg; - case 'h': + FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) + switch (c) { + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_PATH: - r = strv_extend(&arg_path, optarg); + OPTION_LONG("path", "PATH", "Path to the $BOOT partition (may be used multiple times)"): + r = strv_extend(&arg_path, arg); if (r < 0) return log_oom(); break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state, argc, argv); return 1; } @@ -344,6 +334,7 @@ static int make_bad(const char *prefix, uint64_t done, const char *suffix, char return 0; } +VERB(verb_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT, "Show status of current boot loader entry"); static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL; uint64_t left, done; @@ -451,6 +442,12 @@ static int rename_in_dir_idempotent(int fd, const char *from, const char *to) { return 1; } +VERB_FULL(verb_set, "good", NULL, VERB_ANY, 1, 0, STATUS_GOOD, + "Mark this boot as good"); +VERB_FULL(verb_set, "bad", NULL, VERB_ANY, 1, 0, STATUS_BAD, + "Mark this boot as bad"); +VERB_FULL(verb_set, "indeterminate", NULL, VERB_ANY, 1, 0, STATUS_INDETERMINATE, + "Undo any marking as good or bad"); static int verb_set(int argc, char *argv[], uintptr_t data, void *userdata) { _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL; const char *target, *source1, *source2; @@ -561,20 +558,12 @@ static int verb_set(int argc, char *argv[], uintptr_t data, void *userdata) { } static int run(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, - { "good", VERB_ANY, 1, 0, verb_set, STATUS_GOOD }, - { "bad", VERB_ANY, 1, 0, verb_set, STATUS_BAD }, - { "indeterminate", VERB_ANY, 1, 0, verb_set, STATUS_INDETERMINATE }, - {} - }; - + char **args = NULL; int r; log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -586,7 +575,7 @@ static int run(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Marking a boot is only supported on EFI systems."); - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); From 9e8658b887df6af25af43b486f0418f0b1cea358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 25 Mar 2026 01:43:29 +0100 Subject: [PATCH 0467/1296] various: fix typos --- NEWS | 2 +- src/growfs/growfs.c | 2 +- test/test-network/conf/25-routing-policy-rule-test1.network | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 85cc7ac2d1d35..cdf3c3a0ad918 100644 --- a/NEWS +++ b/NEWS @@ -14264,7 +14264,7 @@ CHANGES WITH 235: the "utmp" group already, and it appears to be generally understood that members of "utmp" can modify/flush the utmp/wtmp/lastlog/btmp databases. Previously this was implemented correctly for all these - databases excepts btmp, which has been opened up like this now + databases except btmp, which has been opened up like this now too. Note that while the other databases are world-readable (i.e. 0644), btmp is not and remains more restrictive. diff --git a/src/growfs/growfs.c b/src/growfs/growfs.c index d991b82d67ce6..8481257ab7166 100644 --- a/src/growfs/growfs.c +++ b/src/growfs/growfs.c @@ -189,7 +189,7 @@ static int parse_argv(int argc, char *argv[]) { if (optind + 1 != argc) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "%s excepts exactly one argument (the mount point).", + "%s expects exactly one argument (the mount point).", program_invocation_short_name); arg_target = argv[optind]; diff --git a/test/test-network/conf/25-routing-policy-rule-test1.network b/test/test-network/conf/25-routing-policy-rule-test1.network index 66ea59d3a99cc..4c83fd5fb78ff 100644 --- a/test/test-network/conf/25-routing-policy-rule-test1.network +++ b/test/test-network/conf/25-routing-policy-rule-test1.network @@ -67,7 +67,7 @@ Priority=202 Table=22 # The four routing policy rules below intentionally have the same config -# excepts for their To= addresses. See issue #35874. +# except for their To= addresses. See issue #35874. [RoutingPolicyRule] To=192.0.2.0/26 Table=1001 From f912de93125bcf0b6c59770503424bcafc683e78 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Tue, 24 Mar 2026 14:29:27 +0100 Subject: [PATCH 0468/1296] homectl: apply all --member-of= groups from a comma-separated list Commit 0e1ede4b4b6d1ce6b5b6cda5f803e4f1b5aa4a03 introduced a bug where we'd always fetch the "original" (empty) list of groups when processing a comma-separated list of groups from the --member-of= option, so only the last group from the list would get applied. This bug was then later (in 316e9887f2a48bd1c4efa3e31b4bfbaeb22de3a3) refactored into a separate function. Follow-up for 0e1ede4b4b6d1ce6b5b6cda5f803e4f1b5aa4a03. Resolves: #41286 --- src/home/homectl.c | 9 +++------ test/units/TEST-46-HOMED.sh | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index db4e6639d5d9a..9dc135fd70508 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3691,7 +3691,6 @@ static int parse_language_field(char ***languages, const char *arg) { } static int parse_group_field( - sd_json_variant *source_identity, sd_json_variant **identity, const char *field, const char *arg) { @@ -3717,7 +3716,7 @@ static int parse_group_field( return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid group name %s.", word); _cleanup_(sd_json_variant_unrefp) sd_json_variant *mo = - sd_json_variant_ref(sd_json_variant_by_key(source_identity, field)); + sd_json_variant_ref(sd_json_variant_by_key(*identity, field)); r = sd_json_variant_strv(mo, &list); if (r < 0) @@ -4383,7 +4382,7 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_ALIAS: - r = parse_group_field(arg_identity_extra, &arg_identity_extra, "aliases", optarg); + r = parse_group_field(&arg_identity_extra, "aliases", optarg); if (r < 0) return r; break; @@ -4692,9 +4691,7 @@ static int parse_argv(int argc, char *argv[]) { } case 'G': - r = parse_group_field(arg_identity_extra, - match_identity ?: &arg_identity_extra, - "memberOf", optarg); + r = parse_group_field(match_identity ?: &arg_identity_extra, "memberOf", optarg); if (r < 0) return r; break; diff --git a/test/units/TEST-46-HOMED.sh b/test/units/TEST-46-HOMED.sh index 46abca9bace72..4b81799ef3dea 100755 --- a/test/units/TEST-46-HOMED.sh +++ b/test/units/TEST-46-HOMED.sh @@ -87,6 +87,29 @@ testcase_basic() { PASSWORD=xEhErW0ndafV4s homectl update test-user --real-name="Inline test" inspect test-user + # --member-of= + systemd-sysusers --inline "g test-group1" "g test-group2" + # Single group + PASSWORD=xEhErW0ndafV4s homectl update test-user --member-of="test-group1" + [[ "$(homectl inspect -j test-user | jq -c .memberOf)" == '["test-group1"]' ]] + # Multiple groups + PASSWORD=xEhErW0ndafV4s homectl update test-user --member-of="test-group1,test-group2" + [[ "$(homectl inspect -j test-user | jq -c .memberOf)" == '["test-group1","test-group2"]' ]] + # Empty argument + PASSWORD=xEhErW0ndafV4s homectl update test-user --member-of= + [[ "$(homectl inspect -j test-user | jq -c .memberOf)" == 'null' ]] + # Argument shenanigans + # - only separators + (! PASSWORD=xEhErW0ndafV4s homectl update test-user --member-of=",,,,,,,,,,,,,,,,,,") + # - invalid group + (! PASSWORD=xEhErW0ndafV4s homectl update test-user --member-of="test-group1,inv@lid.group?") + # - separators & valid groups + PASSWORD=xEhErW0ndafV4s homectl update test-user --member-of=",,,,,test-group1,,,,,,,,,,,,,,test-group2," + [[ "$(homectl inspect -j test-user | jq -c .memberOf)" == '["test-group1","test-group2"]' ]] + # - duplicate groups + PASSWORD=xEhErW0ndafV4s homectl update test-user --member-of="test-group2,test-group1,test-group1,test-group2" + [[ "$(homectl inspect -j test-user | jq -c .memberOf)" == '["test-group1","test-group2"]' ]] + homectl deactivate test-user inspect test-user From 27eedfa0efd0bcf6fe31e64f4c193d4c2d1fed4e Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Wed, 11 Mar 2026 10:52:49 -0700 Subject: [PATCH 0469/1296] resolved: use the SOA to find chain of trust quicker sd-resolved does dnssec "backwards" compared to most resolvers. A typical strategy is to start from the DNS root and gather the requisite keys on the way down, but sd-resolved requests the final answer it wants and then goes searching for the requisite keys later. We don't know in advance under which names we should expect to find those keys, because we don't know the zone cuts a priori, but we can use what we have found in prior responses to make an educated guess. This was more or less the intent of 47690634f157, but it was partially regressed in d840783db520 while fixing a bug handling totally empty responses. Fixes #37472 Ref: 47690634f157 ("resolved: don't request the SOA for every dns label") Fixes: d840783db520 ("resolved: always progress DS queries") --- src/resolve/resolved-dns-transaction.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 1a786ccf270b2..a320825d0d5a3 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -2621,7 +2621,10 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { continue; /* If we were looking for the DS RR, don't request it again. */ - if (dns_transaction_key(t)->type == DNS_TYPE_DS) + r = dns_name_equal(dns_resource_key_name(dns_transaction_key(t)), dns_resource_key_name(rr->key)); + if (r < 0) + return r; + if (r > 0 && dns_transaction_key(t)->type == DNS_TYPE_DS) continue; } From 8ddc1c07f69956a40e44d66280140ae4548b6387 Mon Sep 17 00:00:00 2001 From: Walter McKelvie Date: Wed, 25 Mar 2026 06:37:31 -0400 Subject: [PATCH 0470/1296] network: increase transmit/receive queues size to 16384 (#41289) A 10G Marvell AQC113 included in an ASRock TRX50WS motherboard NIC claims to support tx/rx queues as large as 8184. After boot 'ethtool -g eth0' outputs: Ring parameters for eth0: RX: 8184 RX Mini: n/a RX Jumbo: n/a TX: 8184 TX push buff len: n/a HDS thresh: n/a RX: 2048 RX Mini: n/a RX Jumbo: n/a TX: 4096 RX Buf Len: n/a CQE Size: n/a TX Push: off RX Push: off TX push buff len: n/a TCP data split: n/a HDS thresh: n/a 'ethtool --set-ring eth0 rx 8184 tx 8184 && ethtool -g eth0' yields: Ring parameters for eth0: RX: 8184 RX Mini: n/a RX Jumbo: n/a TX: 8184 TX push buff len: n/a HDS thresh: n/a RX: 8184 RX Mini: n/a RX Jumbo: n/a TX: 8184 RX Buf Len: n/a CQE Size: n/a TX Push: off RX Push: off TX push buff len: n/a TCP data split: n/a HDS thresh: n/a I can measure a throughput difference between using using buffer sizes 4096 and 8184 on my hardware, so it really seems that this is doing something beyond buggy firmware. Original PR https://github.com/systemd/systemd/pull/17635 didn't give any explanation for the limit of 4096, but that's probably what was supported by the kernel drivers at the time. A web search shows that CISCO VIC 15000 supports 16k, so allow up to that. [zjs: edited the message] --- man/systemd.link.xml | 4 ++-- src/udev/net/link-config.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/man/systemd.link.xml b/man/systemd.link.xml index 602f19b60030c..d26431b2b2bbe 100644 --- a/man/systemd.link.xml +++ b/man/systemd.link.xml @@ -666,7 +666,7 @@ TransmitQueues= - Specifies the device's number of transmit queues. An integer in the range 1…4096. + Specifies the device's number of transmit queues. An integer in the range 1…16384. When unset, the kernel's default will be used. @@ -675,7 +675,7 @@ ReceiveQueues= - Specifies the device's number of receive queues. An integer in the range 1…4096. + Specifies the device's number of receive queues. An integer in the range 1…16384. When unset, the kernel's default will be used. diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c index eefa95dc5f68d..704a38831e13d 100644 --- a/src/udev/net/link-config.c +++ b/src/udev/net/link-config.c @@ -1271,7 +1271,7 @@ int config_parse_rx_tx_queues( log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse %s=, ignoring assignment: %s.", lvalue, rvalue); return 0; } - if (k == 0 || k > 4096) { + if (k == 0 || k > 16384) { log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid %s=, ignoring assignment: %s.", lvalue, rvalue); return 0; } From 16a3857dcf64dcbd90935b8ab3cc87d7092efb0d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 24 Mar 2026 08:57:26 +0100 Subject: [PATCH 0471/1296] test-iovec: add unit test for IOVEC_MAKE_BYTE() As requested here: https://github.com/systemd/systemd/pull/40980#discussion_r2964650885 --- src/test/test-iovec-util.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/test/test-iovec-util.c b/src/test/test-iovec-util.c index 217ee8cf5d9ec..e091463a93423 100644 --- a/src/test/test-iovec-util.c +++ b/src/test/test-iovec-util.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "iovec-util.h" +#include "memory-util.h" #include "tests.h" TEST(iovec_memcmp) { @@ -67,4 +68,11 @@ TEST(iovec_append) { assert_se(iovec_memcmp(&iov, &IOVEC_MAKE_STRING("waldoquuxp")) == 0); } +TEST(iovec_make_byte) { + struct iovec x = IOVEC_MAKE_BYTE('x'); + + ASSERT_EQ(x.iov_len, 1U); + ASSERT_EQ(memcmp_nn(x.iov_base, x.iov_len, "x", 1), 0); +} + DEFINE_TEST_MAIN(LOG_INFO); From 588c22b04d2b81e808d396cd5b73fb042646f13b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 24 Mar 2026 09:03:49 +0100 Subject: [PATCH 0472/1296] macro.h: move DEFER_VOID_CALL() to cleanup-util.h For some reason the IMDS PR for the first time triggers an issue with the DEFER_VOID_CALL() logic relying on assert() and being places in macro.h, let's hence move this elsewhere. --- src/basic/cleanup-util.h | 14 ++++++++++++++ src/basic/macro.h | 13 ------------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/basic/cleanup-util.h b/src/basic/cleanup-util.h index 9fd48dbf29733..068696d9771ca 100644 --- a/src/basic/cleanup-util.h +++ b/src/basic/cleanup-util.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "assert-util.h" #include "cleanup-fundamental.h" /* IWYU pragma: export */ typedef void (*free_func_t)(void *p); @@ -89,3 +90,16 @@ typedef void* (*mfree_func_t)(void *p); #define DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(type, name, free_func) \ DEFINE_PUBLIC_TRIVIAL_REF_FUNC(type, name); \ DEFINE_PUBLIC_TRIVIAL_UNREF_FUNC(type, name, free_func); + +typedef void (*void_func_t)(void); + +static inline void dispatch_void_func(void_func_t *f) { + assert(f); + assert(*f); + (*f)(); +} + +/* Inspired by Go's "defer" construct, but much more basic. This basically just calls a void function when + * the current scope is left. Doesn't do function parameters (i.e. no closures). */ +#define DEFER_VOID_CALL(x) _DEFER_VOID_CALL(UNIQ, x) +#define _DEFER_VOID_CALL(uniq, x) _unused_ _cleanup_(dispatch_void_func) void_func_t UNIQ_T(defer, uniq) = (x) diff --git a/src/basic/macro.h b/src/basic/macro.h index 7001c331399d6..390a9fab38ca3 100644 --- a/src/basic/macro.h +++ b/src/basic/macro.h @@ -205,16 +205,3 @@ static inline size_t size_add(size_t x, size_t y) { for (typeof(entry) _va_sentinel_[1] = {}, _entries_[] = { __VA_ARGS__ __VA_OPT__(,) _va_sentinel_[0] }, *_current_ = _entries_; \ ((long)(_current_ - _entries_) < (long)(ELEMENTSOF(_entries_) - 1)) && ({ entry = *_current_; true; }); \ _current_++) - -typedef void (*void_func_t)(void); - -static inline void dispatch_void_func(void_func_t *f) { - assert(f); - assert(*f); - (*f)(); -} - -/* Inspired by Go's "defer" construct, but much more basic. This basically just calls a void function when - * the current scope is left. Doesn't do function parameters (i.e. no closures). */ -#define DEFER_VOID_CALL(x) _DEFER_VOID_CALL(UNIQ, x) -#define _DEFER_VOID_CALL(uniq, x) _unused_ _cleanup_(dispatch_void_func) void_func_t UNIQ_T(defer, uniq) = (x) From 9f691ad3c073f60df9b7902fab46b41e00bbb9c5 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 25 Mar 2026 11:53:36 +0100 Subject: [PATCH 0473/1296] varlink: comment that "more" flag IDL comment is API External tools that use the systemd varlink ecosystem require to know if a specific varlink method supports/requires the "more" flag from the IDL. This is tracked upstream in https://github.com/varlink/varlink.github.io/issues/26 As an intermediate step systemd adds the (very nice) comments ``` # [Requires 'more' flag] or # [Supports 'more' flag] ``` to the various methods. This commit extends the comment around the code that adds the comment to clarify that this should be considered API and that the comment should not be changed as external tools (like e.g. the varlink-http-bridge) rely on it. --- src/libsystemd/sd-varlink/sd-varlink-idl.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libsystemd/sd-varlink/sd-varlink-idl.c b/src/libsystemd/sd-varlink/sd-varlink-idl.c index 144450b702d26..22b6026a01f14 100644 --- a/src/libsystemd/sd-varlink/sd-varlink-idl.c +++ b/src/libsystemd/sd-varlink/sd-varlink-idl.c @@ -387,7 +387,10 @@ static int varlink_idl_format_symbol( /* Sooner or later we want to export this in a proper IDL language construct, see * https://github.com/varlink/varlink.github.io/issues/26 – but for now export this as a - * comment. */ + * comment. + * + * Until this is resolved upsteam, consider this comment part of the API (i.e. don't change + * only extend). It is used by tools like varlink-http-bridge. */ if ((symbol->symbol_flags & (SD_VARLINK_REQUIRES_MORE|SD_VARLINK_SUPPORTS_MORE)) != 0) { fputs(colors[COLOR_COMMENT], f); if (FLAGS_SET(symbol->symbol_flags, SD_VARLINK_REQUIRES_MORE)) From 21bfad87e48203754ee30f4350bd7c282dc372ab Mon Sep 17 00:00:00 2001 From: Hadi Chokr Date: Wed, 25 Feb 2026 15:27:24 +0100 Subject: [PATCH 0474/1296] Add condition for mutable extensions directory Signed-off-by: Hadi Chokr --- units/systemd-sysext.service | 1 + 1 file changed, 1 insertion(+) diff --git a/units/systemd-sysext.service b/units/systemd-sysext.service index 672faa946ffce..f20a076128022 100644 --- a/units/systemd-sysext.service +++ b/units/systemd-sysext.service @@ -15,6 +15,7 @@ ConditionCapability=CAP_SYS_ADMIN ConditionDirectoryNotEmpty=|/etc/extensions ConditionDirectoryNotEmpty=|/run/extensions ConditionDirectoryNotEmpty=|/var/lib/extensions +ConditionDirectoryNotEmpty=|/var/lib/extensions.mutable ConditionPathExists=!/etc/initrd-release DefaultDependencies=no From e7b3a7bb24685fc209f552b9a7c23641f6f20d04 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 6 Mar 2026 21:17:53 +0100 Subject: [PATCH 0475/1296] memory-util: move memeqbyte() & friends to src/fundamental/ --- src/basic/memory-util.c | 21 -------------------- src/basic/memory-util.h | 6 ------ src/fundamental/memory-util-fundamental.c | 24 +++++++++++++++++++++++ src/fundamental/memory-util-fundamental.h | 7 +++++-- src/fundamental/meson.build | 1 + 5 files changed, 30 insertions(+), 29 deletions(-) create mode 100644 src/fundamental/memory-util-fundamental.c diff --git a/src/basic/memory-util.c b/src/basic/memory-util.c index b39ec725a9967..e091cfb8f16f8 100644 --- a/src/basic/memory-util.c +++ b/src/basic/memory-util.c @@ -20,27 +20,6 @@ size_t page_size(void) { return pgsz; } -bool memeqbyte(uint8_t byte, const void *data, size_t length) { - /* Does the buffer consist entirely of the same specific byte value? - * Copied from https://github.com/systemd/casync/, copied in turn from - * https://github.com/rustyrussell/ccan/blob/master/ccan/mem/mem.c#L92, - * which is licensed CC-0. - */ - - const uint8_t *p = data; - - /* Check first 16 bytes manually */ - for (size_t i = 0; i < 16; i++, length--) { - if (length == 0) - return true; - if (p[i] != byte) - return false; - } - - /* Now we know first 16 bytes match, memcmp() with self. */ - return memcmp(data, p + 16, length) == 0; -} - void* memdup_reverse(const void *mem, size_t size) { assert(mem); assert(size != 0); diff --git a/src/basic/memory-util.h b/src/basic/memory-util.h index 1a609dea98bd8..f16118fbb09c2 100644 --- a/src/basic/memory-util.h +++ b/src/basic/memory-util.h @@ -57,12 +57,6 @@ static inline int memcmp_nn(const void *s1, size_t n1, const void *s2, size_t n2 #define zero(x) (memzero(&(x), sizeof(x))) -bool memeqbyte(uint8_t byte, const void *data, size_t length) _nonnull_if_nonzero_(2, 3); - -#define memeqzero(data, length) memeqbyte(0x00, data, length) - -#define eqzero(x) memeqzero(x, sizeof(x)) - static inline void* mempset(void *s, int c, size_t n) { memset(s, c, n); return (uint8_t*) s + n; diff --git a/src/fundamental/memory-util-fundamental.c b/src/fundamental/memory-util-fundamental.c new file mode 100644 index 0000000000000..02b55251fdb28 --- /dev/null +++ b/src/fundamental/memory-util-fundamental.c @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "memory-util-fundamental.h" + +bool memeqbyte(uint8_t byte, const void *data, size_t length) { + /* Does the buffer consist entirely of the same specific byte value? + * Copied from https://github.com/systemd/casync/, copied in turn from + * https://github.com/rustyrussell/ccan/blob/master/ccan/mem/mem.c#L92, + * which is licensed CC-0. + */ + + const uint8_t *p = data; + + /* Check first 16 bytes manually */ + for (size_t i = 0; i < 16; i++, length--) { + if (length == 0) + return true; + if (p[i] != byte) + return false; + } + + /* Now we know first 16 bytes match, memcmp() with self. */ + return memcmp(data, p + 16, length) == 0; +} diff --git a/src/fundamental/memory-util-fundamental.h b/src/fundamental/memory-util-fundamental.h index c2a99a2039770..7c88264053ccd 100644 --- a/src/fundamental/memory-util-fundamental.h +++ b/src/fundamental/memory-util-fundamental.h @@ -9,8 +9,7 @@ # include #endif -#include "assert-fundamental.h" -#include "cleanup-fundamental.h" +#include "assert-fundamental.h" /* IWYU pragma: keep */ #include "macro-fundamental.h" #define memzero(x, l) \ @@ -148,3 +147,7 @@ static inline uint64_t ALIGN_OFFSET_U64(uint64_t l, uint64_t ali) { assert(((uintptr_t) _p) % alignof(t) == 0); \ (t *) _p; \ }) + +bool memeqbyte(uint8_t byte, const void *data, size_t length) _nonnull_if_nonzero_(2, 3); +#define memeqzero(data, length) memeqbyte(0x00, data, length) +#define eqzero(x) memeqzero(x, sizeof(x)) diff --git a/src/fundamental/meson.build b/src/fundamental/meson.build index 6bc26caad2b1d..14d956ac07edf 100644 --- a/src/fundamental/meson.build +++ b/src/fundamental/meson.build @@ -8,6 +8,7 @@ fundamental_sources = files( 'edid-fundamental.c', 'efivars-fundamental.c', 'iovec-util-fundamental.h', + 'memory-util-fundamental.c', 'sha1-fundamental.c', 'sha256-fundamental.c', 'string-util-fundamental.c', From c678b0b06063c1aae3e3c5a089982f83f6e216a0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 6 Mar 2026 17:53:09 +0100 Subject: [PATCH 0476/1296] efi: add efivar_get_raw_full() flavour that returns the variable attributes too --- src/boot/efi-efivars.c | 13 +++++++++++-- src/boot/efi-efivars.h | 5 ++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/boot/efi-efivars.c b/src/boot/efi-efivars.c index 0358a071e07d7..5581d98ec3502 100644 --- a/src/boot/efi-efivars.c +++ b/src/boot/efi-efivars.c @@ -177,7 +177,13 @@ EFI_STATUS efivar_get_uint64_le(const EFI_GUID *vendor, const char16_t *name, ui return EFI_SUCCESS; } -EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, const char16_t *name, void **ret_data, size_t *ret_size) { +EFI_STATUS efivar_get_raw_full( + const EFI_GUID *vendor, + const char16_t *name, + uint32_t *ret_attributes, + void **ret_data, + size_t *ret_size) { + EFI_STATUS err; assert(vendor); @@ -188,11 +194,14 @@ EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, const char16_t *name, void **r if (err != EFI_BUFFER_TOO_SMALL) return err; + uint32_t attributes = 0; _cleanup_free_ void *buf = xmalloc(size); - err = RT->GetVariable((char16_t *) name, (EFI_GUID *) vendor, NULL, &size, buf); + err = RT->GetVariable((char16_t *) name, (EFI_GUID *) vendor, ret_attributes ? &attributes : NULL, &size, buf); if (err != EFI_SUCCESS) return err; + if (ret_attributes) + *ret_attributes = attributes; if (ret_data) *ret_data = TAKE_PTR(buf); if (ret_size) diff --git a/src/boot/efi-efivars.h b/src/boot/efi-efivars.h index 1e74d6483cf4c..6d88f56e71296 100644 --- a/src/boot/efi-efivars.h +++ b/src/boot/efi-efivars.h @@ -22,7 +22,10 @@ void efivar_set_time_usec(const EFI_GUID *vendor, const char16_t *name, uint64_t EFI_STATUS efivar_unset(const EFI_GUID *vendor, const char16_t *name, uint32_t flags); EFI_STATUS efivar_get_str16(const EFI_GUID *vendor, const char16_t *name, char16_t **ret); -EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, const char16_t *name, void **ret_data, size_t *ret_size); +EFI_STATUS efivar_get_raw_full(const EFI_GUID *vendor, const char16_t *name, uint32_t *ret_attributes, void **ret_data, size_t *ret_size); +static inline EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, const char16_t *name, void **ret_data, size_t *ret_size) { + return efivar_get_raw_full(vendor, name, NULL, ret_data, ret_size); +} EFI_STATUS efivar_get_uint64_str16(const EFI_GUID *vendor, const char16_t *name, uint64_t *ret); EFI_STATUS efivar_get_uint32_le(const EFI_GUID *vendor, const char16_t *name, uint32_t *ret); EFI_STATUS efivar_get_uint64_le(const EFI_GUID *vendor, const char16_t *name, uint64_t *ret); From 9b15ddd9cf4ca973e3b5453e7583595550d3ec42 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sat, 7 Mar 2026 22:11:31 +0100 Subject: [PATCH 0477/1296] random-seed: move seed efi table definitions to header --- src/boot/random-seed.c | 8 -------- src/boot/random-seed.h | 8 ++++++++ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/boot/random-seed.c b/src/boot/random-seed.c index 30e74af214f49..8ef7ee7e52ed0 100644 --- a/src/boot/random-seed.c +++ b/src/boot/random-seed.c @@ -12,14 +12,6 @@ #define RANDOM_MAX_SIZE_MIN (32U) #define RANDOM_MAX_SIZE_MAX (32U*1024U) -struct linux_efi_random_seed { - uint32_t size; - uint8_t seed[]; -}; - -#define LINUX_EFI_RANDOM_SEED_TABLE_GUID \ - { 0x1ce1e5bc, 0x7ceb, 0x42f2, { 0x81, 0xe5, 0x8a, 0xad, 0xf1, 0x80, 0xf5, 0x7b } } - /* SHA256 gives us 256/8=32 bytes */ #define HASH_VALUE_SIZE 32 diff --git a/src/boot/random-seed.h b/src/boot/random-seed.h index 67f005dff54f0..4a9f01bf45330 100644 --- a/src/boot/random-seed.h +++ b/src/boot/random-seed.h @@ -3,4 +3,12 @@ #include "efi.h" +struct linux_efi_random_seed { + uint32_t size; + uint8_t seed[]; +}; + +#define LINUX_EFI_RANDOM_SEED_TABLE_GUID \ + { 0x1ce1e5bc, 0x7ceb, 0x42f2, { 0x81, 0xe5, 0x8a, 0xad, 0xf1, 0x80, 0xf5, 0x7b } } + EFI_STATUS process_random_seed(EFI_FILE *root_dir); From 901024eea18ddd3ac27c0e69a69a48d2b23bdfe2 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 25 Mar 2026 14:23:11 +0100 Subject: [PATCH 0478/1296] swtpm: Properly configure state directory Otherwise it will unconditionally try to use /var/lib/xxx which won't work when running unprivileged. --- src/shared/swtpm-util.c | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/shared/swtpm-util.c b/src/shared/swtpm-util.c index 836f877e12d79..1a475f0e08f3f 100644 --- a/src/shared/swtpm-util.c +++ b/src/shared/swtpm-util.c @@ -7,6 +7,7 @@ #include "alloc-util.h" #include "escape.h" #include "fd-util.h" +#include "fileio.h" #include "json-util.h" #include "log.h" #include "memfd-util.h" @@ -120,6 +121,43 @@ int manufacture_swtpm(const char *state_dir, const char *secret) { } } + /* Create custom swtpm config files so that swtpm_localca uses our state directory instead of + * the system-wide /var/lib/swtpm-localca/ which may not be writable. */ + _cleanup_free_ char *localca_conf = path_join(state_dir, "swtpm-localca.conf"); + if (!localca_conf) + return log_oom(); + + r = write_string_filef( + localca_conf, + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE|WRITE_STRING_FILE_MKDIR_0755, + "statedir = %1$s\n" + "signingkey = %1$s/signing-private-key.pem\n" + "issuercert = %1$s/issuer-certificate.pem\n" + "certserial = %1$s/certserial\n", + state_dir); + if (r < 0) + return log_error_errno(r, "Failed to write swtpm-localca.conf: %m"); + + _cleanup_free_ char *swtpm_localca = NULL; + r = find_executable("swtpm_localca", &swtpm_localca); + if (r < 0) + return log_error_errno(r, "Failed to find 'swtpm_localca' binary: %m"); + + _cleanup_free_ char *setup_conf = path_join(state_dir, "swtpm_setup.conf"); + if (!setup_conf) + return log_oom(); + + r = write_string_filef( + setup_conf, + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE|WRITE_STRING_FILE_MKDIR_0755, + "create_certs_tool = %1$s\n" + "create_certs_tool_config = %2$s\n" + "create_certs_tool_options = /etc/swtpm-localca.options\n", + swtpm_localca, + localca_conf); + if (r < 0) + return log_error_errno(r, "Failed to write swtpm_setup.conf: %m"); + strv_free(args); args = strv_new(swtpm_setup, "--tpm-state", state_dir, @@ -129,7 +167,8 @@ int manufacture_swtpm(const char *state_dir, const char *secret) { "--createek", "--create-ek-cert", "--create-platform-cert", - "--not-overwrite"); + "--not-overwrite", + "--config", setup_conf); if (!args) return log_oom(); From 82d69529cb6be01c65eec69ba8189b9bac9ba3a3 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 25 Mar 2026 15:34:58 +0100 Subject: [PATCH 0479/1296] swtpm-util: Write our own CA options rather than using the distro ones --- src/shared/swtpm-util.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/shared/swtpm-util.c b/src/shared/swtpm-util.c index 1a475f0e08f3f..55e3f2f34c52a 100644 --- a/src/shared/swtpm-util.c +++ b/src/shared/swtpm-util.c @@ -138,6 +138,19 @@ int manufacture_swtpm(const char *state_dir, const char *secret) { if (r < 0) return log_error_errno(r, "Failed to write swtpm-localca.conf: %m"); + _cleanup_free_ char *localca_options = path_join(state_dir, "swtpm-localca.options"); + if (!localca_options) + return log_oom(); + + r = write_string_file( + localca_options, + "--platform-manufacturer systemd\n" + "--platform-version 2.1\n" + "--platform-model swtpm\n", + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE|WRITE_STRING_FILE_MKDIR_0755); + if (r < 0) + return log_error_errno(r, "Failed to write swtpm-localca.options: %m"); + _cleanup_free_ char *swtpm_localca = NULL; r = find_executable("swtpm_localca", &swtpm_localca); if (r < 0) @@ -152,9 +165,10 @@ int manufacture_swtpm(const char *state_dir, const char *secret) { WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE|WRITE_STRING_FILE_MKDIR_0755, "create_certs_tool = %1$s\n" "create_certs_tool_config = %2$s\n" - "create_certs_tool_options = /etc/swtpm-localca.options\n", + "create_certs_tool_options = %3$s\n", swtpm_localca, - localca_conf); + localca_conf, + localca_options); if (r < 0) return log_error_errno(r, "Failed to write swtpm_setup.conf: %m"); From 6dc6b48ec98ad6eccd5af00b4c96bb70cff9286c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sat, 7 Mar 2026 22:11:44 +0100 Subject: [PATCH 0480/1296] random-seed: when we have a reasonable RNG then create random seed file if missing Previously we'd never write the ESP random seed file (or initialize the random seed EFI table) if it didn't already exist. Let's adjust this a bit, and also create it fresh if we have a "good" random source, i.e. if the EFI table already existed or if the RNG protocol is implemented by EFI. This is useful as it increases the chance the random seed table is valid, and we can use it as source for randomness in later stages. --- src/boot/random-seed.c | 68 +++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 24 deletions(-) diff --git a/src/boot/random-seed.c b/src/boot/random-seed.c index 8ef7ee7e52ed0..a31215c3b0262 100644 --- a/src/boot/random-seed.c +++ b/src/boot/random-seed.c @@ -185,46 +185,66 @@ EFI_STATUS process_random_seed(EFI_FILE *root_dir) { explicit_bzero_safe(system_token, size); } + bool created = false; err = root_dir->Open( root_dir, &handle, (char16_t *) u"\\loader\\random-seed", EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE, 0); + if (err == EFI_NOT_FOUND && seeded_by_efi) { + /* If the file does not exist, but we are reasonably well seeded, create the seed file */ + created = true; + err = root_dir->Open( + root_dir, + &handle, + (char16_t *) u"\\loader\\random-seed", + EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, + 0); + } if (err != EFI_SUCCESS) { if (!IN_SET(err, EFI_NOT_FOUND, EFI_WRITE_PROTECTED)) log_error_status(err, "Failed to open random seed file: %m"); return err; } - err = get_file_info(handle, &info, NULL); - if (err != EFI_SUCCESS) - return log_error_status(err, "Failed to get file info for random seed: %m"); + if (!created) { + err = get_file_info(handle, &info, /* ret_size= */ NULL); + if (err != EFI_SUCCESS) + return log_error_status(err, "Failed to get file info for random seed: %m"); - size = info->FileSize; - if (size < RANDOM_MAX_SIZE_MIN) - return log_error_status(EFI_INVALID_PARAMETER, "Random seed file is too short."); + /* Treat a short file just like a freshly created one for robustness reasons: consider a case + * where in a previous run a file was just created and the system was then powered off. In + * such a case the file will already exist, but be too short. */ + created = info->FileSize < RANDOM_MAX_SIZE_MIN; + } - if (size > RANDOM_MAX_SIZE_MAX) - return log_error_status(EFI_INVALID_PARAMETER, "Random seed file is too large."); + if (created) { + size = 0; + sha256_process_bytes(&size, sizeof(size), &hash); + } else { + size = info->FileSize; + if (size > RANDOM_MAX_SIZE_MAX) + return log_error_status(EFI_INVALID_PARAMETER, "Random seed file is too large."); - seed = xmalloc(size); - rsize = size; - err = handle->Read(handle, &rsize, seed); - if (err != EFI_SUCCESS) - return log_error_status(err, "Failed to read random seed file: %m"); - if (rsize != size) { - explicit_bzero_safe(seed, rsize); - return log_error_status(EFI_PROTOCOL_ERROR, "Short read on random seed file."); - } + seed = xmalloc(size); + rsize = size; + err = handle->Read(handle, &rsize, seed); + if (err != EFI_SUCCESS) + return log_error_status(err, "Failed to read random seed file: %m"); + if (rsize != size) { + explicit_bzero_safe(seed, rsize); + return log_error_status(EFI_PROTOCOL_ERROR, "Short read on random seed file."); + } - sha256_process_bytes(&size, sizeof(size), &hash); - sha256_process_bytes(seed, size, &hash); - explicit_bzero_safe(seed, size); + sha256_process_bytes(&size, sizeof(size), &hash); + sha256_process_bytes(seed, size, &hash); + explicit_bzero_safe(seed, size); - err = handle->SetPosition(handle, 0); - if (err != EFI_SUCCESS) - return log_error_status(err, "Failed to seek to beginning of random seed file: %m"); + err = handle->SetPosition(handle, 0); + if (err != EFI_SUCCESS) + return log_error_status(err, "Failed to seek to beginning of random seed file: %m"); + } /* Let's also include the UEFI monotonic counter (which is supposedly increasing on every single * boot) in the hash, so that even if the changes to the ESP for some reason should not be @@ -253,7 +273,7 @@ EFI_STATUS process_random_seed(EFI_FILE *root_dir) { size = sizeof(random_bytes); /* If the file size is too large, zero out the remaining bytes on disk. */ - if (size < info->FileSize) { + if (!created && size < info->FileSize) { err = handle->SetPosition(handle, size); if (err != EFI_SUCCESS) return log_error_status(err, "Failed to seek to offset of random seed file: %m"); From dcad61c74d65a46913cac7ac4983a8eb35854dbb Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sat, 7 Mar 2026 23:44:37 +0100 Subject: [PATCH 0481/1296] stub: introduce "boot secret" stored in an EFI variable inaccessible to the OS --- man/systemd-stub.xml | 38 +++ src/boot/boot-secret.c | 374 +++++++++++++++++++++++++++++ src/boot/boot-secret.h | 13 + src/boot/meson.build | 1 + src/boot/stub.c | 30 +++ tmpfiles.d/20-systemd-stub.conf.in | 1 + 6 files changed, 457 insertions(+) create mode 100644 src/boot/boot-secret.c create mode 100644 src/boot/boot-secret.h diff --git a/man/systemd-stub.xml b/man/systemd-stub.xml index 251d79ea6e14e..bf23c900d026c 100644 --- a/man/systemd-stub.xml +++ b/man/systemd-stub.xml @@ -655,6 +655,17 @@ + + + LoaderBootSecret + + A non-volatile EFI variable only accessible from the pre-boot environment + (i.e. access from the OS is not permitted) that contains a per-system secret. It is set automatically + by systemd-stub if not present already. A secret derived from the value of this + EFI variable is passed to the OS in /.extra/boot-secret, see below. + + + Note that some of the variables above may also be set by the boot loader. The stub will only set @@ -762,6 +773,33 @@ + + + /.extra/boot-secret + A 32 byte per-system secret which is derived from a 32 byte secret stored in an EFI + variable (LoaderBootSecret, see above), which itself is only accessible to the + pre-boot environment. This may be used for various early-boot cryptographic purposes, and OS file + system access to it is restricted to root. The IMAGE_ID=/ID= + data from the .osrel is hashed into the secret, to ensure that different images + get a distinct secret passed. Moreover, a randomized 32 byte value stored in the ESP in the + /loader/boot-secret-mixin file is hashed in as well, ensuring that distinct disks will + result in different boot secrets. + + Note: this boot secret is ultimately protected only by firmware-enforced access controls on the + EFI variable. This is generally a much weaker protection than TPM-based approaches have, and it is + hence strongly recommended to use the TPM on systems that possess one. The boot secret is primarily + intended to be a lower-security fallback for cases where a TPM is not available. + + Applications should never protect resources directly with this secret, but derive their own + secret from it (by hashing it together with some application ID, in HMAC mode for example), in order + not to accidentally leak the primary boot secret. + + Note that the boot secret is only available if the pre-boot environment had a suitable RNG + source at the current boot or an earlier one. This source can be an initialized on-disk + random seed or the EFI RNG support, or both. + + + Note that all these files are located in the tmpfs file system the kernel sets diff --git a/src/boot/boot-secret.c b/src/boot/boot-secret.c new file mode 100644 index 0000000000000..54078b51c23bd --- /dev/null +++ b/src/boot/boot-secret.c @@ -0,0 +1,374 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "boot-secret.h" +#include "efi-efivars.h" +#include "efi-log.h" +#include "random-seed.h" +#include "sha256-fundamental.h" +#include "util.h" + +#define BOOT_SECRET_MIXIN_PATH u"\\loader\\boot-secret-mixin" + +/* This maintains a per-system secret that is stored in an EFI variable that is only accessible during EFI + * boot, and becomes inaccessible afterwards, once ExitBootServices() is called. The variable is + * automatically initialized if missing. A secret derived by hashing from this EFI variable secret is then + * passed to the OS, in an initrd file inaccessible to unprivileged userspace. To make things a bit more + * robust while hashing two more pieces of information are mixed in: a random "mixin" that is stored in the + * ESP and is supposed to ensure that the passed boot secrets are distinct for each disk used on the system; + * moreover an OS identifier derived from the UKI's .osrel field (ideally IMAGE_ID=, but if not defined ID= + * will do, with a final fallback to "linux"). Note that these two additions are not supposed to enhance the + * cryptographic quality of the secret, they are just supposed to make things more robust on systems with + * multiple disks and OSes. + * + * The boot secret passed to the OS can be used to protect resources during OS runtime, from earliest boot + * phases on, as a fallback for the usual TPM based protections. + * + * Note that this secret comes with much weaker protection than TPM backed secrets: there's no physical + * isolation, there are no cryptographic access policies, there's just the hope the firmware reasonably + * correctly implements boot-time-only EFI variable mechanism. (But then again, this is what mok/shim's + * security also relies on, and hence this all is not too bad?) */ + +static EFI_STATUS random_seed_find_table(struct linux_efi_random_seed **ret) { + assert(ret); + + /* We use the Linux random seed EFI table as our source of randomness, since there's reason to + * believe it is as good as it possibly would get. Note that we ourselves might be the ones + * initializing it, based on EFI RNG APIs, the monotonic boot counter, a random seed file on disk and + * the clock. */ + + struct linux_efi_random_seed *seed_table = + find_configuration_table(MAKE_GUID_PTR(LINUX_EFI_RANDOM_SEED_TABLE)); + if (!seed_table) + return log_debug_status(EFI_NOT_FOUND, "No random seed available, not creating a boot secret."); + if (seed_table->size < BOOT_SECRET_SIZE) + return log_debug_status(EFI_NOT_FOUND, "Random seed is available, but too short."); + + *ret = seed_table; + return EFI_SUCCESS; +} + +static void random_seed_evolve(struct linux_efi_random_seed *seed_table) { + static const char label[] = "systemd-stub random seed evolve label v1"; + + assert(seed_table); + + /* Whenever we derived something from the Linux random seed EFI table we evolve the secret in it, so + * that the seed is never reused. */ + + struct sha256_ctx hash; + CLEANUP_ERASE(hash); + sha256_init_ctx(&hash); + sha256_process_bytes(label, sizeof(label) - 1, &hash); + sha256_process_bytes(&seed_table->size, sizeof(seed_table->size), &hash); + sha256_process_bytes(seed_table->seed, seed_table->size, &hash); + assert(seed_table->size >= SHA256_DIGEST_SIZE); + sha256_finish_ctx(&hash, seed_table->seed); +} + +static void random_seed_make_secret( + struct linux_efi_random_seed *seed_table, + uint8_t ret_secret[static BOOT_SECRET_SIZE]) { + + static const char label[] = "systemd-stub random seed make secret label v1"; + + assert(seed_table); + assert(ret_secret); + + /* Derive a new secret from the Linux random seed EFI table data */ + + struct sha256_ctx hash; + CLEANUP_ERASE(hash); + sha256_init_ctx(&hash); + sha256_process_bytes(label, sizeof(label) - 1, &hash); + sha256_process_bytes(&seed_table->size, sizeof(seed_table->size), &hash); + sha256_process_bytes(seed_table->seed, seed_table->size, &hash); + sha256_finish_ctx(&hash, ret_secret); + + random_seed_evolve(seed_table); /* ← ensure the same seed is not reused */ +} + +static EFI_STATUS read_efivar_secret(uint8_t ret_secret[static BOOT_SECRET_SIZE]) { + EFI_STATUS err; + + assert(ret_secret); + + /* Reads the boot secret from the EFI variable, ensuring it's properly protected from the OS, as per + * the attribute flags */ + + _cleanup_free_ void* data = NULL; + uint32_t attributes; + size_t size = 0; + err = efivar_get_raw_full(MAKE_GUID_PTR(LOADER), u"LoaderBootSecret", &attributes, &data, &size); + if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to read LoaderBootSecret EFI variable: %m"); + + if (size != BOOT_SECRET_SIZE) { + err = log_debug_status(EFI_PROTOCOL_ERROR, "Unexpected size of BootSecret EFI variable, ignoring."); + goto finish; + } + + if ((attributes & (EFI_VARIABLE_NON_VOLATILE|EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS)) != + (EFI_VARIABLE_NON_VOLATILE|EFI_VARIABLE_BOOTSERVICE_ACCESS)) { + err = log_debug_status(EFI_PROTOCOL_ERROR, "Unexpected attributes of BootSecret EFI variable, ignoring."); + goto finish; + } + + memcpy(ret_secret, data, size); + err = EFI_SUCCESS; +finish: + explicit_bzero_safe(data, size); + return err; +} + +static EFI_STATUS setup_efivar_secret( + struct linux_efi_random_seed *seed_table, + uint8_t ret_secret[static BOOT_SECRET_SIZE]) { + + EFI_STATUS err; + + assert(seed_table); + assert(ret_secret); + + /* Generates a new EFI variable secret, and stores it in an EFI variable. */ + + uint8_t secret[BOOT_SECRET_SIZE]; + CLEANUP_ERASE(secret); + random_seed_make_secret(seed_table, secret); + + /* Set the variable with the EFI_VARIABLE_RUNTIME_ACCESS flag off (!), so that it's invisible after + * ExitBootServices()! */ + err = RT->SetVariable( + (char16_t*) u"LoaderBootSecret", + MAKE_GUID_PTR(LOADER), + EFI_VARIABLE_NON_VOLATILE|EFI_VARIABLE_BOOTSERVICE_ACCESS, /* ← No EFI_VARIABLE_RUNTIME_ACCESS here */ + sizeof(secret), + secret); + if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to set boot secret EFI variable: %m"); + + memcpy(ret_secret, secret, sizeof(secret)); + return EFI_SUCCESS; +} + +static EFI_STATUS acquire_efivar_secret( + struct linux_efi_random_seed *seed_table, + uint8_t ret_secret[static BOOT_SECRET_SIZE]) { + + EFI_STATUS err; + + assert(seed_table); + assert(ret_secret); + + /* Try to read the boot secret EFI variable, but if it doesn't exist create a new one */ + + err = read_efivar_secret(ret_secret); + if (err != EFI_NOT_FOUND) + return err; + + return setup_efivar_secret(seed_table, ret_secret); +} + +static EFI_STATUS setup_secret_mixin( + EFI_FILE *handle, + struct linux_efi_random_seed *seed_table, + uint8_t ret_mixin[static BOOT_SECRET_SIZE]) { + + EFI_STATUS err; + + assert(handle); + assert(seed_table); + assert(ret_mixin); + + /* This writes a new 'mixin' to the ESP, in case the ESP so far had none */ + + uint8_t mixin[BOOT_SECRET_SIZE]; + random_seed_make_secret(seed_table, mixin); + + size_t wsize = sizeof(mixin); + err = handle->Write(handle, &wsize, mixin); + if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to write secret mixin file: %m"); + if (wsize != sizeof(mixin)) + return log_debug_status(EFI_LOAD_ERROR, "Short write while writing secret mixin file: %m"); + + err = handle->Flush(handle); + if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to flush secret mixin file: %m"); + + memcpy(ret_mixin, mixin, sizeof(mixin)); + return EFI_SUCCESS; +} + +static EFI_STATUS acquire_secret_mixin( + EFI_FILE *root_dir, + struct linux_efi_random_seed *seed_table, + uint8_t ret_mixin[static BOOT_SECRET_SIZE]) { + + EFI_STATUS err; + + assert(seed_table); + assert(ret_mixin); + + if (!root_dir) + return EFI_NOT_FOUND; + + /* Acquires the mixin for the boot secret stored in the ESP. If it already exists we'll read it. If + * it doesn't we'll initialize it */ + + bool writable; + _cleanup_file_close_ EFI_FILE *handle = NULL; + err = root_dir->Open( + root_dir, + &handle, + (char16_t *) BOOT_SECRET_MIXIN_PATH, + EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, + /* Attributes= */ 0); + if (err == EFI_WRITE_PROTECTED) { + err = root_dir->Open( + root_dir, + &handle, + (char16_t *) BOOT_SECRET_MIXIN_PATH, + EFI_FILE_MODE_READ, + /* Attributes= */ 0); + if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to read the boot secret mixin file '%ls': %m", BOOT_SECRET_MIXIN_PATH); + + writable = false; + } else if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to access the boot secret mixin file '%ls': %m", BOOT_SECRET_MIXIN_PATH); + else + writable = true; + + _cleanup_free_ EFI_FILE_INFO *info = NULL; + err = get_file_info(handle, &info, /* ret_size= */ NULL); + if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to get boot secret mixin file '%ls' info: %m", BOOT_SECRET_MIXIN_PATH); + if (info->FileSize == 0 && writable) /* New file? Fill it. */ + return setup_secret_mixin(handle, seed_table, ret_mixin); + + /* If the mixin file is too small we won't overwrite it (in order to not destroy some potentially + * load bearing key), but we won't use it either. */ + if (info->FileSize < BOOT_SECRET_SIZE) + return log_debug_status(EFI_PROTOCOL_ERROR, "Boot secret mixin file '%ls' is too short %" PRIu64 " < %u", BOOT_SECRET_MIXIN_PATH, info->FileSize, BOOT_SECRET_SIZE); + + uint8_t mixin[BOOT_SECRET_SIZE]; + size_t rsize = sizeof(mixin); + err = handle->Read(handle, &rsize, mixin); + if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to read boot secret mixin file '%ls': %m", BOOT_SECRET_MIXIN_PATH); + if (rsize != BOOT_SECRET_SIZE) + return log_debug_status(EFI_PROTOCOL_ERROR, "Unexpected size from Read(): %zu != %zu", rsize, sizeof(mixin)); + + memcpy(ret_mixin, mixin, BOOT_SECRET_SIZE); + return EFI_SUCCESS; +} + +static char* pick_id(const char *_osrel, size_t osrel_size) { + assert(_osrel || osrel_size == 0); + + /* Make a NUL terminated copy we can chop into pieces */ + _cleanup_free_ char *osrel = NULL; + osrel = xmalloc(osrel_size + 1); + if (osrel_size > 0) + memcpy(osrel, _osrel, osrel_size); + osrel[osrel_size] = 0; + + /* Find an OS ID. Preferably the IMAGE_ID. */ + _cleanup_free_ char *os_id = NULL; + char *line, *key, *value; + size_t pos = 0; + while ((line = line_get_key_value(osrel, "=", &pos, &key, &value))) { + if (streq8(key, "IMAGE_ID")) + return xstrdup8(value); + + if (streq8(key, "ID")) { + free(os_id); + os_id = xstrdup8(value); + } + } + + /* If the IMAGE_ID= wasn't set, use the OS ID=. If that one isn't set either fall back to "linux". */ + return TAKE_PTR(os_id) ?: xstrdup8("linux"); +} + +static void derive_secret( + uint8_t efivar_secret[static BOOT_SECRET_SIZE], + uint8_t secret_mixin[static BOOT_SECRET_SIZE], + const char *id, + uint8_t ret[static BOOT_SECRET_SIZE]) { + + static const char hash_label[] = "systemd-stub derive secret label v1"; + + assert(efivar_secret); + assert(secret_mixin); + assert(id); + assert(ret); + + /* Now combine the EFI variable secret, the mixin from the ESP and the OS id to generate the secret + * to pass to the OS */ + + struct sha256_ctx hash; + CLEANUP_ERASE(hash); + sha256_init_ctx(&hash); + sha256_process_bytes(hash_label, sizeof(hash_label) - 1, &hash); + sha256_process_bytes(efivar_secret, BOOT_SECRET_SIZE, &hash); + sha256_process_bytes(secret_mixin, BOOT_SECRET_SIZE, &hash); + + /* Include an OS id in the hash, so that every OS gets a different derived secret */ + size_t size = strlen8(id); + sha256_process_bytes(&size, sizeof(size), &hash); + sha256_process_bytes(id, size, &hash); + + assert_cc(SHA256_DIGEST_SIZE == BOOT_SECRET_SIZE); + sha256_finish_ctx(&hash, ret); +} + +EFI_STATUS prepare_boot_secret( + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + const PeSectionVector *osrel_section, + uint8_t ret[static BOOT_SECRET_SIZE]) { + + EFI_STATUS err; + + assert(loaded_image); + assert(ret); + + /* Prepares the boot secret to pass to the OS */ + + if (!loaded_image->DeviceHandle) + return EFI_SUCCESS; + + _cleanup_file_close_ EFI_FILE *root = NULL; + err = open_volume(loaded_image->DeviceHandle, &root); + if (err != EFI_SUCCESS) + return err; + + /* We need the Linux random seed EFI table, so that we can initialize the EFI variable secret and + * generate the secret mixin. */ + struct linux_efi_random_seed *seed_table = NULL; + err = random_seed_find_table(&seed_table); + if (err != EFI_SUCCESS) + return err; + + uint8_t efivar_secret[BOOT_SECRET_SIZE]; + CLEANUP_ERASE(efivar_secret); + err = acquire_efivar_secret(seed_table, efivar_secret); + if (err != EFI_SUCCESS) + return err; + + uint8_t secret_mixin[BOOT_SECRET_SIZE]; + err = acquire_secret_mixin(root, seed_table, secret_mixin); + if (err != EFI_SUCCESS) + return err; + + const char *osrel = NULL; + size_t osrel_size = 0; + if (PE_SECTION_VECTOR_IS_SET(osrel_section)) { + osrel = (const char*) loaded_image->ImageBase + osrel_section->memory_offset; + osrel_size = osrel_section->memory_size; + } + _cleanup_free_ char *id = pick_id(osrel, osrel_size); + + derive_secret(efivar_secret, secret_mixin, id, ret); + return EFI_SUCCESS; +} diff --git a/src/boot/boot-secret.h b/src/boot/boot-secret.h new file mode 100644 index 0000000000000..d3e9f53d9ceec --- /dev/null +++ b/src/boot/boot-secret.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "efi.h" +#include "pe.h" +#include "proto/loaded-image.h" + +#define BOOT_SECRET_SIZE 32U + +EFI_STATUS prepare_boot_secret( + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + const PeSectionVector *osrel_section, + uint8_t ret[static BOOT_SECRET_SIZE]); diff --git a/src/boot/meson.build b/src/boot/meson.build index c51510e96f4a1..058d9276bd1fe 100644 --- a/src/boot/meson.build +++ b/src/boot/meson.build @@ -337,6 +337,7 @@ systemd_boot_sources = files( ) stub_sources = files( + 'boot-secret.c', 'cpio.c', 'linux.c', 'splash.c', diff --git a/src/boot/stub.c b/src/boot/stub.c index 7ef5a43a04ca5..66b20805d5389 100644 --- a/src/boot/stub.c +++ b/src/boot/stub.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "boot-secret.h" #include "cpio.h" #include "device-path-util.h" #include "devicetree.h" @@ -45,6 +46,7 @@ enum { INITRD_PCRPKEY, INITRD_OSREL, INITRD_PROFILE, + INITRD_BOOT_SECRET, _INITRD_MAX, }; @@ -978,6 +980,29 @@ static void generate_embedded_initrds( } } +static void generate_boot_secret_initrd( + const uint8_t boot_secret[static BOOT_SECRET_SIZE], + struct iovec initrds[static _INITRD_MAX]) { + + assert(initrds); + + /* All zero means: no boot secret acquired */ + if (memeqzero(boot_secret, BOOT_SECRET_SIZE)) + return; + + (void) pack_cpio_literal( + boot_secret, + BOOT_SECRET_SIZE, + ".extra", + u"boot-secret", + /* dir_mode= */ 0555, + /* access_mode= */ 0400, + /* tpm_pcr= */ UINT32_MAX, + /* tpm_description= */ NULL, + initrds + INITRD_BOOT_SECRET, + /* ret_measured= */ NULL); +} + static void lookup_embedded_initrds( EFI_LOADED_IMAGE_PROTOCOL *loaded_image, const PeSectionVector sections[static _UNIFIED_SECTION_MAX], @@ -1256,6 +1281,10 @@ static EFI_STATUS run(EFI_HANDLE image) { refresh_random_seed(loaded_image); + uint8_t boot_secret[BOOT_SECRET_SIZE] = {}; /* all zeroes means: not acquired */ + CLEANUP_ERASE(boot_secret); + (void) prepare_boot_secret(loaded_image, sections + UNIFIED_SECTION_OSREL, boot_secret); + uname = pe_section_to_str8(loaded_image, sections + UNIFIED_SECTION_UNAME); /* Let's now check if we actually want to use the command line, measure it if it was passed in. */ @@ -1285,6 +1314,7 @@ static EFI_STATUS run(EFI_HANDLE image) { /* Generate & find all initrds */ generate_sidecar_initrds(loaded_image, initrds, ¶meters_measured, &sysext_measured, &confext_measured); generate_embedded_initrds(loaded_image, sections, initrds); + generate_boot_secret_initrd(boot_secret, initrds); lookup_embedded_initrds(loaded_image, sections, initrds); /* Add initrds in the right order. Generally, later initrds can overwrite files in earlier ones, diff --git a/tmpfiles.d/20-systemd-stub.conf.in b/tmpfiles.d/20-systemd-stub.conf.in index 512f39a3e9f61..916c9e503be3b 100644 --- a/tmpfiles.d/20-systemd-stub.conf.in +++ b/tmpfiles.d/20-systemd-stub.conf.in @@ -12,6 +12,7 @@ C /run/systemd/stub/profile 0444 root root - /.extra/profile C /run/systemd/stub/os-release 0444 root root - /.extra/os-release +C /run/systemd/stub/boot-secret 0400 root root - /.extra/boot-secret {% if ENABLE_TPM %} C /run/systemd/tpm2-pcr-signature.json 0444 root root - /.extra/tpm2-pcr-signature.json From e2cfdfff15383f3c16f7fb5e4dfbaee99f3bc64f Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 25 Mar 2026 15:43:35 +0100 Subject: [PATCH 0482/1296] AGENTS: Tell agents to not use mkosi box It's easier to run the AI tool within mkosi box rather than telling it to use mkosi box and forgetting to use it half the time. --- AGENTS.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 418d1705419be..ffc47c05b0562 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,15 +14,16 @@ Always consult these files as needed: ## Running arbitrary commands -- Always run arbitrary commands with the `mkosi box -- ` wrapper command. This runs in an environment where more tools are available. +- Never use `mkosi box` to wrap commands. You are either already running inside an mkosi box environment or +running outside of it — use the tools available in your current environment directly. ## Build and Test Commands -- Never compile individual files or targets. Always run `mkosi -f box -- meson compile -C build` to build -the entire project. Meson handles incremental compilation automatically. +- Never compile individual files. Always run `meson compile -C build ` to build the target you're +working on. Meson handles incremental compilation automatically. - Never run `meson compile` followed by `meson test` as separate steps. Always run -`mkosi -f box -- meson test -C build -v ` directly. Meson will automatically rebuild any required -targets before running tests. +`meson test -C build -v ` directly. Meson will automatically rebuild any required targets before +running tests. - Never invent your own build commands or try to optimize the build process. - Never use `head`, `tail`, or pipe (`|`) the output of build or test commands. Always let the full output display. This is critical for diagnosing build and test failures. From f3d385da2a25b58589df49eca91e7c814c4fa1b8 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 25 Mar 2026 18:30:14 +0100 Subject: [PATCH 0483/1296] memory-util: avoid passing invalid pointer to memcmp() when length == 16 If length is exactly 16, the loop would finish with length == 0, but we'd carry on to the memcmp() check, where the 'p + 16' passed would be invalid memory. memcmp() demands valid pointers even if size is specified to 0, hence let's catch this ourselves. --- src/fundamental/memory-util-fundamental.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/fundamental/memory-util-fundamental.c b/src/fundamental/memory-util-fundamental.c index 02b55251fdb28..1a64fbe514ff8 100644 --- a/src/fundamental/memory-util-fundamental.c +++ b/src/fundamental/memory-util-fundamental.c @@ -3,6 +3,8 @@ #include "memory-util-fundamental.h" bool memeqbyte(uint8_t byte, const void *data, size_t length) { + assert(data || length == 0); + /* Does the buffer consist entirely of the same specific byte value? * Copied from https://github.com/systemd/casync/, copied in turn from * https://github.com/rustyrussell/ccan/blob/master/ccan/mem/mem.c#L92, @@ -12,12 +14,12 @@ bool memeqbyte(uint8_t byte, const void *data, size_t length) { const uint8_t *p = data; /* Check first 16 bytes manually */ - for (size_t i = 0; i < 16; i++, length--) { - if (length == 0) - return true; + for (size_t i = 0; i < 16 && length > 0; i++, length--) if (p[i] != byte) return false; - } + + if (length == 0) + return true; /* Now we know first 16 bytes match, memcmp() with self. */ return memcmp(data, p + 16, length) == 0; From aa71035a20ef8fa575e05b1bf5b905c2ae367c94 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 25 Mar 2026 18:13:11 +0100 Subject: [PATCH 0484/1296] meson: detect availability of attributes using cc.has_function_attribute() Alternative to fabc22f5998e610eb7ba70a963cab9f94dca5c0a As suggested in https://github.com/systemd/systemd/pull/41174#discussion_r2966411375 --- meson.build | 17 +++++++++++------ src/boot/meson.build | 11 +++++++---- src/fundamental/macro-fundamental.h | 18 +++++++++--------- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/meson.build b/meson.build index c52f8e17c2bbe..925b1b13ed0e0 100644 --- a/meson.build +++ b/meson.build @@ -524,12 +524,6 @@ if cc.compiles(''' add_project_arguments('-Werror=shadow', language : 'c') endif -have = cc.compiles( - '__attribute__((__retain__)) int x;', - args : '-Werror=attributes', - name : '__attribute__((__retain__))') -conf.set10('HAVE_ATTRIBUTE_RETAIN', have) - if cxx_cmd != '' add_project_arguments(cxx.get_supported_arguments(basic_disabled_warnings), language : 'cpp') endif @@ -544,6 +538,17 @@ conf.set10('HAVE_WARNING_ZERO_LENGTH_BOUNDS', have) have = cc.has_argument('-Wzero-as-null-pointer-constant') conf.set10('HAVE_WARNING_ZERO_AS_NULL_POINTER_CONSTANT', have) +possible_c_attributes = [ + 'alloc_size', + 'fallthrough', + 'retain', +] + +foreach attr : possible_c_attributes + have = cc.has_function_attribute(attr) + conf.set10('HAVE_ATTRIBUTE_' + attr.to_upper(), have) +endforeach + ##################################################################### # compilation result tests diff --git a/src/boot/meson.build b/src/boot/meson.build index 058d9276bd1fe..dfac98f034a6d 100644 --- a/src/boot/meson.build +++ b/src/boot/meson.build @@ -80,10 +80,13 @@ endif efi_conf = configuration_data() # import several configs from userspace -foreach name : ['HAVE_ATTRIBUTE_RETAIN', - 'HAVE_WARNING_ZERO_AS_NULL_POINTER_CONSTANT', - 'HAVE_WARNING_ZERO_LENGTH_BOUNDS', - ] +foreach name : ['HAVE_WARNING_ZERO_LENGTH_BOUNDS', + 'HAVE_WARNING_ZERO_AS_NULL_POINTER_CONSTANT'] + efi_conf.set(name, conf.get(name)) +endforeach + +foreach attr : possible_c_attributes + name = 'HAVE_ATTRIBUTE_' + attr.to_upper() efi_conf.set(name, conf.get(name)) endforeach diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro-fundamental.h index 1941e88d3760e..d99a00c9bf936 100644 --- a/src/fundamental/macro-fundamental.h +++ b/src/fundamental/macro-fundamental.h @@ -98,22 +98,22 @@ #define _weak_ __attribute__((__weak__)) #define _weakref_(x) __attribute__((__weakref__(#x))) -#if HAVE_ATTRIBUTE_RETAIN -# define _retain_ __attribute__((__retain__)) +#if HAVE_ATTRIBUTE_ALLOC_SIZE +# define _alloc_(...) __attribute__((__alloc_size__(__VA_ARGS__))) #else -# define _retain_ +# define _alloc_(...) #endif -#ifdef __clang__ -# define _alloc_(...) +#if HAVE_ATTRIBUTE_FALLTHROUGH +# define _fallthrough_ __attribute__((__fallthrough__)) #else -# define _alloc_(...) __attribute__((__alloc_size__(__VA_ARGS__))) +# define _fallthrough_ #endif -#if defined(__clang__) && __clang_major__ < 10 -# define _fallthrough_ +#if HAVE_ATTRIBUTE_RETAIN +# define _retain_ __attribute__((__retain__)) #else -# define _fallthrough_ __attribute__((__fallthrough__)) +# define _retain_ #endif #if __GNUC__ >= 15 From 5442fbfb07883870dababb1252609dcf173f8ece Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 25 Mar 2026 15:16:55 +0100 Subject: [PATCH 0485/1296] vmspawn: Fix --tpm-state= parsing path_startswith() considers "no" and "./no" equal. Use startswith() to avoid that. --- src/vmspawn/vmspawn.c | 54 ++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index b017ce85b6247..a197132c0434d 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -746,43 +746,49 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_TPM_STATE: - if (path_is_valid(optarg) && (path_is_absolute(optarg) || path_startswith(optarg, "./"))) { - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm_state_path); - if (r < 0) - return r; - - arg_tpm_state_mode = STATE_PATH; - break; - } - r = isempty(optarg) ? false : streq(optarg, "auto") ? true : parse_boolean(optarg); + if (r >= 0) { + arg_tpm_state_mode = r ? STATE_AUTO : STATE_OFF; + arg_tpm_state_path = mfree(arg_tpm_state_path); + break; + } + + if (!path_is_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path in --tpm-state= parameter: %s", optarg); + + if (!path_is_absolute(optarg) && !startswith(optarg, "./")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path in --tpm-state= parameter must be absolute or start with './': %s", optarg); + + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm_state_path); if (r < 0) - return log_error_errno(r, "Failed to parse --tpm-state= parameter: %s", optarg); + return r; - arg_tpm_state_mode = r ? STATE_AUTO : STATE_OFF; - arg_tpm_state_path = mfree(arg_tpm_state_path); + arg_tpm_state_mode = STATE_PATH; break; case ARG_EFI_NVRAM_STATE: - if (path_is_valid(optarg) && (path_is_absolute(optarg) || path_startswith(optarg, "./"))) { - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_efi_nvram_state_path); - if (r < 0) - return r; - - arg_efi_nvram_state_mode = STATE_PATH; - break; - } - r = isempty(optarg) ? false : streq(optarg, "auto") ? true : parse_boolean(optarg); + if (r >= 0) { + arg_efi_nvram_state_mode = r ? STATE_AUTO : STATE_OFF; + arg_efi_nvram_state_path = mfree(arg_efi_nvram_state_path); + break; + } + + if (!path_is_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path in --efi-nvram-state= parameter: %s", optarg); + + if (!path_is_absolute(optarg) && !startswith(optarg, "./")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path in --efi-nvram-state= parameter must be absolute or start with './': %s", optarg); + + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_efi_nvram_state_path); if (r < 0) - return log_error_errno(r, "Failed to parse --efi-nvram-state= parameter: %s", optarg); + return r; - arg_efi_nvram_state_mode = r ? STATE_AUTO : STATE_OFF; - arg_efi_nvram_state_path = mfree(arg_efi_nvram_state_path); + arg_efi_nvram_state_mode = STATE_PATH; break; case ARG_NO_ASK_PASSWORD: From f83371324d60e47998f5972590119aecfd11f9c8 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 25 Mar 2026 10:22:30 +0100 Subject: [PATCH 0486/1296] vmspawn: Create journal parent directories if needed --- src/test/test-chase.c | 27 +++++++++++++++++++++++++++ src/vmspawn/vmspawn.c | 15 +++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/test/test-chase.c b/src/test/test-chase.c index 129ea19b7237d..721f56a250663 100644 --- a/src/test/test-chase.c +++ b/src/test/test-chase.c @@ -620,6 +620,16 @@ TEST(chaseat) { assert_se(chaseat(tfd, "i/../p", CHASE_MKDIR_0755|CHASE_NONEXISTENT, NULL, NULL) == -ENOENT); + /* Test CHASE_MKDIR_0755|CHASE_PARENT — creates intermediate dirs but not the final component */ + + ASSERT_OK(chaseat(tfd, "mkp/a/r/e/n/t/file", CHASE_MKDIR_0755|CHASE_PARENT, &result, &fd)); + ASSERT_OK(faccessat(tfd, "mkp/a/r/e/n/t", F_OK, 0)); + assert_se(RET_NERRNO(faccessat(tfd, "mkp/a/r/e/n/t/file", F_OK, 0)) == -ENOENT); + ASSERT_OK(fd_verify_directory(fd)); + fd = safe_close(fd); + ASSERT_STREQ(result, "mkp/a/r/e/n/t/file"); + result = mfree(result); + /* Test CHASE_EXTRACT_FILENAME */ ASSERT_OK(chaseat(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME, &result, &fd)); @@ -664,6 +674,23 @@ TEST(chaseat) { fd = safe_close(fd); result = mfree(result); + /* Test chase_and_openat() with CHASE_MKDIR_0755|CHASE_PARENT — opens parent dir */ + + fd = chase_and_openat(tfd, "mkopen/p/a/r/file.txt", CHASE_MKDIR_0755|CHASE_PARENT, O_RDONLY|O_CLOEXEC, NULL); + ASSERT_OK(fd); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK(faccessat(tfd, "mkopen/p/a/r", F_OK, 0)); + assert_se(RET_NERRNO(faccessat(tfd, "mkopen/p/a/r/file.txt", F_OK, 0)) == -ENOENT); + fd = safe_close(fd); + + /* Test chase_and_openat() with CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY + O_CREAT — creates and opens target dir */ + + fd = chase_and_openat(tfd, "mkopen/d/i/r/target", CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, O_CREAT|O_RDONLY|O_CLOEXEC, NULL); + ASSERT_OK(fd); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK(faccessat(tfd, "mkopen/d/i/r/target", F_OK, 0)); + fd = safe_close(fd); + /* Test chase_and_openatdir() */ ASSERT_OK(chase_and_opendirat(tfd, "o/p/e/n/d/i", 0, &result, &dir)); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index a197132c0434d..f730a756e28ed 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -26,6 +26,8 @@ #include "bus-locator.h" #include "bus-util.h" #include "capability-util.h" +#include "chase.h" +#include "chattr-util.h" #include "common-signal.h" #include "copy.h" #include "discover-image.h" @@ -2953,6 +2955,19 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (arg_forward_journal) { _cleanup_free_ char *listen_address = NULL; + ChaseFlags chase_flags = CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY; + if (endswith(arg_forward_journal, ".journal")) + chase_flags |= CHASE_PARENT; + + _cleanup_close_ int journal_fd = -EBADF; + r = chase(arg_forward_journal, /* root= */ NULL, chase_flags, /* ret_path= */ NULL, &journal_fd); + if (r < 0) + return log_error_errno(r, "Failed to create journal directory for '%s': %m", arg_forward_journal); + + r = chattr_fd(journal_fd, FS_NOCOW_FL, FS_NOCOW_FL); + if (r < 0) + log_debug_errno(r, "Failed to set NOCOW flag on journal directory for '%s', ignoring: %m", arg_forward_journal); + if (!GREEDY_REALLOC(children, n_children + 1)) return log_oom(); From 8ff77aac1342d06d2b196fe7d0163bffc7590404 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 25 Mar 2026 14:20:03 +0100 Subject: [PATCH 0487/1296] vmspawn: Use qemu config files Let's avoid generating giant qemu command lines by using qemu config files instead. --- src/vmspawn/meson.build | 1 + src/vmspawn/vmspawn-qemu-config.c | 97 ++++++ src/vmspawn/vmspawn-qemu-config.h | 28 ++ src/vmspawn/vmspawn.c | 551 +++++++++++++++++++----------- 4 files changed, 469 insertions(+), 208 deletions(-) create mode 100644 src/vmspawn/vmspawn-qemu-config.c create mode 100644 src/vmspawn/vmspawn-qemu-config.h diff --git a/src/vmspawn/meson.build b/src/vmspawn/meson.build index a836b316578a0..722e6a52cc7f2 100644 --- a/src/vmspawn/meson.build +++ b/src/vmspawn/meson.build @@ -6,6 +6,7 @@ endif vmspawn_sources = files( 'vmspawn.c', + 'vmspawn-qemu-config.c', 'vmspawn-settings.c', 'vmspawn-scope.c', 'vmspawn-mount.c', diff --git a/src/vmspawn/vmspawn-qemu-config.c b/src/vmspawn/vmspawn-qemu-config.c new file mode 100644 index 0000000000000..08908ff294692 --- /dev/null +++ b/src/vmspawn/vmspawn-qemu-config.c @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "alloc-util.h" +#include "errno-util.h" +#include "log.h" +#include "vmspawn-qemu-config.h" + +static bool qemu_config_type_valid(const char *type) { + return !strchr(type, '\n'); +} + +static bool qemu_config_id_valid(const char *id) { + return !strpbrk(id, "\"\n"); +} + +static bool qemu_config_key_name_valid(const char *key) { + return !strpbrk(key, "=\n"); +} + +static bool qemu_config_value_valid(const char *value) { + return !strpbrk(value, "\"\n"); +} + +int qemu_config_key(FILE *f, const char *key, const char *value) { + assert(f); + assert(key); + assert(value); + + if (!qemu_config_key_name_valid(key)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "QEMU config key '%s' contains '=' or newline.", key); + if (!qemu_config_value_valid(value)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "QEMU config value '%s' contains quote or newline.", value); + + if (fprintf(f, " %s = \"%s\"\n", key, value) < 0) + return -errno_or_else(EIO); + + return 0; +} + +int qemu_config_keyf(FILE *f, const char *key, const char *format, ...) { + _cleanup_free_ char *value = NULL; + va_list ap; + int r; + + assert(f); + assert(key); + assert(format); + + va_start(ap, format); + r = vasprintf(&value, format, ap); + va_end(ap); + if (r < 0) + return -ENOMEM; + + return qemu_config_key(f, key, value); +} + +int qemu_config_section_impl(FILE *f, const char *type, const char *id, ...) { + va_list ap; + int r; + + assert(f); + assert(type); + + if (!qemu_config_type_valid(type)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "QEMU config section type '%s' contains newline.", type); + + if (id) { + if (!qemu_config_id_valid(id)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "QEMU config section id '%s' contains quote or newline.", id); + fprintf(f, "\n[%s \"%s\"]\n", type, id); + } else + fprintf(f, "\n[%s]\n", type); + + va_start(ap, id); + for (;;) { + const char *key = va_arg(ap, const char *); + if (!key) + break; + + const char *value = ASSERT_PTR(va_arg(ap, const char *)); + + r = qemu_config_key(f, key, value); + if (r < 0) { + va_end(ap); + return r; + } + } + va_end(ap); + + if (ferror(f)) + return -errno_or_else(EIO); + + return 0; +} diff --git a/src/vmspawn/vmspawn-qemu-config.h b/src/vmspawn/vmspawn-qemu-config.h new file mode 100644 index 0000000000000..cd782be80ef66 --- /dev/null +++ b/src/vmspawn/vmspawn-qemu-config.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "macro.h" + +/* Helpers for writing QEMU -readconfig INI-style config files. + * + * QEMU config format: + * [type "id"] + * key = "value" + * + * Usage: + * qemu_config_section(f, "device", "rng0", + * "driver", "virtio-rng-pci", + * "rng", "rng0"); + */ + +/* Write a single key = "value" pair (for conditional keys added after a section header) */ +int qemu_config_key(FILE *f, const char *key, const char *value); + +/* Write a single key with a printf-formatted value */ +int qemu_config_keyf(FILE *f, const char *key, const char *format, ...) _printf_(3, 4); + +/* Write a section header with key-value pairs. Varargs are alternating key, value strings. */ +int qemu_config_section_impl(FILE *f, const char *type, const char *id, ...) _sentinel_; +#define qemu_config_section(...) qemu_config_section_impl(__VA_ARGS__, NULL) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index f730a756e28ed..2e41e31d712d7 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -37,6 +37,7 @@ #include "event-util.h" #include "extract-word.h" #include "fd-util.h" +#include "fileio.h" #include "fork-notify.h" #include "format-util.h" #include "fs-util.h" @@ -85,6 +86,7 @@ #include "user-util.h" #include "utf8.h" #include "vmspawn-mount.h" +#include "vmspawn-qemu-config.h" #include "vmspawn-register.h" #include "vmspawn-scope.h" #include "vmspawn-settings.h" @@ -2040,7 +2042,6 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { _cleanup_free_ int *pass_fds = NULL; sd_event_source **children = NULL; size_t n_children = 0, n_pass_fds = 0; - const char *accel; int r; CLEANUP_ARRAY(children, n_children, fork_notify_terminate_many); @@ -2119,16 +2120,6 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (r < 0) return r; - _cleanup_free_ char *machine = NULL; - const char *shm = arg_directory || arg_runtime_mounts.n_mounts != 0 ? ",memory-backend=mem" : ""; - const char *hpet = ARCHITECTURE_SUPPORTS_HPET ? ",hpet=off" : ""; - if (ARCHITECTURE_SUPPORTS_SMM) - machine = strjoin("type=" QEMU_MACHINE_TYPE ",smm=", on_off(ovmf_config->supports_sb), shm, hpet); - else - machine = strjoin("type=" QEMU_MACHINE_TYPE, shm, hpet); - if (!machine) - return log_oom(); - if (arg_linux) { kernel = strdup(arg_linux); if (!kernel) @@ -2151,45 +2142,10 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (asprintf(&mem, "%" PRIu64 "M", DIV_ROUND_UP(arg_ram, U64_MB)) < 0) return log_oom(); - cmdline = strv_new( - qemu_binary, - "-machine", machine, - "-smp", arg_cpus ?: "1", - "-m", mem, - "-object", "rng-random,filename=/dev/urandom,id=rng0", - "-device", "virtio-rng-pci,rng=rng0,id=rng-device0", - "-device", "virtio-balloon,free-page-reporting=on" - ); - if (!cmdline) - return log_oom(); - - if (!sd_id128_is_null(arg_uuid)) - if (strv_extend_many(&cmdline, "-uuid", SD_ID128_TO_UUID_STRING(arg_uuid)) < 0) - return log_oom(); - - if (ARCHITECTURE_SUPPORTS_VMGENID) { - /* Derive a vmgenid automatically from the invocation ID, in a deterministic way. */ - sd_id128_t vmgenid; - r = sd_id128_get_invocation_app_specific(SD_ID128_MAKE(bd,84,6d,e3,e4,7d,4b,6c,a6,85,4a,87,0f,3c,a3,a0), &vmgenid); - if (r < 0) { - log_debug_errno(r, "Failed to get invocation ID, making up randomized vmgenid: %m"); - - r = sd_id128_randomize(&vmgenid); - if (r < 0) - return log_error_errno(r, "Failed to make up randomized vmgenid: %m"); - } - - if (strv_extend(&cmdline, "-device") < 0) - return log_oom(); - - if (strv_extendf(&cmdline, "vmgenid,guid=" SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(vmgenid)) < 0) - return log_oom(); - } - - /* if we are going to be starting any units with state then create our runtime dir */ + /* Create runtime directory for the QEMU config file and other state */ _cleanup_free_ char *runtime_dir = NULL; _cleanup_(rm_rf_physical_and_freep) char *runtime_dir_destroy = NULL; - if (arg_tpm != 0 || arg_directory || arg_runtime_mounts.n_mounts != 0 || arg_pass_ssh_key) { + { _cleanup_free_ char *subdir = NULL; if (asprintf(&subdir, "systemd/vmspawn.%" PRIx64, random_u64()) < 0) @@ -2214,6 +2170,88 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { log_debug("Using runtime directory: %s", runtime_dir); } + /* Build a QEMU config file for -readconfig. Items that can be expressed as QemuOpts sections go + * here; things that require cmdline-only switches (e.g. -kernel, -smbios, -nographic, --add-fd) + * are added to the cmdline strv below. */ + _cleanup_fclose_ FILE *config_file = NULL; + _cleanup_(unlink_and_freep) char *config_path = NULL; + r = fopen_temporary_child(runtime_dir, &config_file, &config_path); + if (r < 0) + return log_error_errno(r, "Failed to create QEMU config file: %m"); + + r = qemu_config_section(config_file, "machine", /* id= */ NULL, + "type", QEMU_MACHINE_TYPE); + if (r < 0) + return r; + + if (ARCHITECTURE_SUPPORTS_SMM) { + r = qemu_config_key(config_file, "smm", on_off(ovmf_config->supports_sb)); + if (r < 0) + return r; + } + + if (arg_directory || arg_runtime_mounts.n_mounts != 0) { + r = qemu_config_key(config_file, "memory-backend", "mem"); + if (r < 0) + return r; + } + + if (ARCHITECTURE_SUPPORTS_HPET) { + r = qemu_config_key(config_file, "hpet", "off"); + if (r < 0) + return r; + } + + r = qemu_config_section(config_file, "object", "rng0", + "qom-type", "rng-random", + "filename", "/dev/urandom"); + if (r < 0) + return r; + + r = qemu_config_section(config_file, "device", "rng-device0", + "driver", "virtio-rng-pci", + "rng", "rng0"); + if (r < 0) + return r; + + r = qemu_config_section(config_file, "device", "balloon0", + "driver", "virtio-balloon", + "free-page-reporting", "on"); + if (r < 0) + return r; + + if (ARCHITECTURE_SUPPORTS_VMGENID) { + sd_id128_t vmgenid; + r = sd_id128_get_invocation_app_specific(SD_ID128_MAKE(bd,84,6d,e3,e4,7d,4b,6c,a6,85,4a,87,0f,3c,a3,a0), &vmgenid); + if (r < 0) { + log_debug_errno(r, "Failed to get invocation ID, making up randomized vmgenid: %m"); + + r = sd_id128_randomize(&vmgenid); + if (r < 0) + return log_error_errno(r, "Failed to make up randomized vmgenid: %m"); + } + + r = qemu_config_section(config_file, "device", "vmgenid0", + "driver", "vmgenid"); + if (r < 0) + return r; + + r = qemu_config_keyf(config_file, "guid", SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(vmgenid)); + if (r < 0) + return r; + } + + /* Start building the cmdline for items that must remain as command line arguments */ + cmdline = strv_new(qemu_binary, + "-smp", arg_cpus ?: "1", + "-m", mem); + if (!cmdline) + return log_oom(); + + if (!sd_id128_is_null(arg_uuid)) + if (strv_extend_many(&cmdline, "-uuid", SD_ID128_TO_UUID_STRING(arg_uuid)) < 0) + return log_oom(); + _cleanup_close_ int delegate_userns_fd = -EBADF, tap_fd = -EBADF; if (arg_network_stack == NETWORK_STACK_TAP) { if (have_effective_cap(CAP_NET_ADMIN) <= 0) { @@ -2238,11 +2276,15 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (tap_fd < 0) return log_error_errno(tap_fd, "Failed to allocate network tap device: %m"); - r = strv_extend(&cmdline, "-nic"); + r = strv_extend(&cmdline, "-netdev"); + if (r < 0) + return log_oom(); + + r = strv_extendf(&cmdline, "tap,id=net0,fd=%i", tap_fd); if (r < 0) return log_oom(); - r = strv_extendf(&cmdline, "tap,fd=%i,model=virtio-net-pci", tap_fd); + r = strv_extend_many(&cmdline, "-device", "virtio-net-pci,netdev=net0"); if (r < 0) return log_oom(); @@ -2267,30 +2309,46 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } else mac_vm = arg_network_provided_mac; - r = strv_extend(&cmdline, "-nic"); + r = qemu_config_section(config_file, "netdev", "net0", + "type", "tap", + "ifname", tap_name, + "script", "no", + "downscript", "no"); if (r < 0) - return log_oom(); + return r; - r = strv_extendf(&cmdline, "tap,ifname=%s,script=no,downscript=no,model=virtio-net-pci,mac=%s", tap_name, ETHER_ADDR_TO_STR(&mac_vm)); + r = qemu_config_section(config_file, "device", "nic0", + "driver", "virtio-net-pci", + "netdev", "net0", + "mac", ETHER_ADDR_TO_STR(&mac_vm)); if (r < 0) - return log_oom(); + return r; } - } else if (arg_network_stack == NETWORK_STACK_USER) - r = strv_extend_many(&cmdline, "-nic", "user,model=virtio-net-pci"); - else - r = strv_extend_many(&cmdline, "-nic", "none"); - if (r < 0) - return log_oom(); + } else if (arg_network_stack == NETWORK_STACK_USER) { + r = qemu_config_section(config_file, "netdev", "net0", + "type", "user"); + if (r < 0) + return r; - /* A shared memory backend might increase ram usage so only add one if actually necessary for virtiofsd. */ - if (arg_directory || arg_runtime_mounts.n_mounts != 0) { - r = strv_extend(&cmdline, "-object"); + r = qemu_config_section(config_file, "device", "nic0", + "driver", "virtio-net-pci", + "netdev", "net0"); + if (r < 0) + return r; + } else { + r = strv_extend_many(&cmdline, "-nic", "none"); if (r < 0) return log_oom(); + } - r = strv_extendf(&cmdline, "memory-backend-memfd,id=mem,size=%s,share=on", mem); + /* A shared memory backend might increase ram usage so only add one if actually necessary for virtiofsd. */ + if (arg_directory || arg_runtime_mounts.n_mounts != 0) { + r = qemu_config_section(config_file, "object", "mem", + "qom-type", "memory-backend-memfd", + "size", mem, + "share", "on"); if (r < 0) - return log_oom(); + return r; } bool use_vsock = arg_vsock > 0 && ARCHITECTURE_SUPPORTS_SMBIOS; @@ -2308,10 +2366,6 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } if (use_kvm && kvm_device_fd >= 0) { - /* /dev/fdset/1 is magic string to tell qemu where to find the fd for /dev/kvm - * we use this so that we can take a fd to /dev/kvm and then give qemu that fd */ - accel = "kvm,device=/dev/fdset/1"; - r = strv_extend(&cmdline, "--add-fd"); if (r < 0) return log_oom(); @@ -2324,14 +2378,18 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return log_oom(); pass_fds[n_pass_fds++] = kvm_device_fd; - } else if (use_kvm) - accel = "kvm"; - else - accel = "tcg"; - r = strv_extend_many(&cmdline, "-accel", accel); - if (r < 0) - return log_oom(); + r = qemu_config_section(config_file, "accel", /* id= */ NULL, + "accel", "kvm", + "device", "/dev/fdset/1"); + if (r < 0) + return r; + } else { + r = qemu_config_section(config_file, "accel", /* id= */ NULL, + "accel", use_kvm ? "kvm" : "tcg"); + if (r < 0) + return r; + } _cleanup_close_ int child_vsock_fd = -EBADF; unsigned child_cid = arg_vsock_cid; @@ -2350,13 +2408,18 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (r < 0) return log_error_errno(r, "Failed to fix CID for the guest VSOCK socket: %m"); - r = strv_extend(&cmdline, "-device"); + r = qemu_config_section(config_file, "device", "vsock0", + "driver", "vhost-vsock-pci"); if (r < 0) - return log_oom(); + return r; - r = strv_extendf(&cmdline, "vhost-vsock-pci,guest-cid=%u,vhostfd=%d", child_cid, device_fd); + r = qemu_config_keyf(config_file, "guest-cid", "%u", child_cid); if (r < 0) - return log_oom(); + return r; + + r = qemu_config_keyf(config_file, "vhostfd", "%d", device_fd); + if (r < 0) + return r; if (!GREEDY_REALLOC(pass_fds, n_pass_fds + 1)) return log_oom(); @@ -2364,6 +2427,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { pass_fds[n_pass_fds++] = device_fd; } + /* -cpu stays on cmdline since not all flags are supported in config */ r = strv_extend_many(&cmdline, "-cpu", #ifdef __x86_64__ "max,hv_relaxed,hv-vapic,hv-time" @@ -2390,69 +2454,105 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (master < 0) return log_error_errno(master, "Failed to setup pty: %m"); - if (strv_extend_many( - &cmdline, - "-nographic", - "-nodefaults", - "-device", "virtio-serial-pci,id=vmspawn-virtio-serial-pci", - "-chardev") < 0) + r = strv_extend_many(&cmdline, "-nographic", "-nodefaults"); + if (r < 0) return log_oom(); - if (strv_extend_joined(&cmdline, "serial,id=console,path=", pty_path) < 0) - return log_oom(); + r = qemu_config_section(config_file, "device", "vmspawn-virtio-serial-pci", + "driver", "virtio-serial-pci"); + if (r < 0) + return r; + + r = qemu_config_section(config_file, "chardev", "console", + "backend", "serial", + "path", pty_path); + if (r < 0) + return r; + + r = qemu_config_section(config_file, "device", "virtconsole0", + "driver", "virtconsole", + "chardev", "console"); + if (r < 0) + return r; - r = strv_extend_many( - &cmdline, - "-device", "virtconsole,chardev=console"); break; } case CONSOLE_GUI: - /* Enable support for the qemu guest agent for clipboard sharing, resolution scaling, etc. */ - r = strv_extend_many( - &cmdline, - "-vga", - "virtio", - "-device", "virtio-serial", - "-chardev", "spicevmc,id=vdagent,debug=0,name=vdagent", - "-device", "virtserialport,chardev=vdagent,name=org.qemu.guest_agent.0"); + /* -vga is a convenience option, keep on cmdline */ + r = strv_extend_many(&cmdline, "-vga", "virtio"); + if (r < 0) + return log_oom(); + + r = qemu_config_section(config_file, "device", "virtio-serial0", + "driver", "virtio-serial"); + if (r < 0) + return r; + + r = qemu_config_section(config_file, "chardev", "vdagent", + "backend", "spicevmc", + "debug", "0", + "name", "vdagent"); + if (r < 0) + return r; + + r = qemu_config_section(config_file, "device", "vdagent-port0", + "driver", "virtserialport", + "chardev", "vdagent", + "name", "org.qemu.guest_agent.0"); + if (r < 0) + return r; + break; case CONSOLE_NATIVE: - r = strv_extend_many( - &cmdline, - "-nographic", - "-nodefaults", - "-chardev", "stdio,mux=on,id=console,signal=off", - "-device", "virtio-serial-pci,id=vmspawn-virtio-serial-pci", - "-device", "virtconsole,chardev=console", - "-mon", "console"); + r = strv_extend_many(&cmdline, "-nographic", "-nodefaults"); + if (r < 0) + return log_oom(); + + r = qemu_config_section(config_file, "chardev", "console", + "backend", "stdio", + "mux", "on", + "signal", "off"); + if (r < 0) + return r; + + r = qemu_config_section(config_file, "device", "vmspawn-virtio-serial-pci", + "driver", "virtio-serial-pci"); + if (r < 0) + return r; + + r = qemu_config_section(config_file, "device", "virtconsole0", + "driver", "virtconsole", + "chardev", "console"); + if (r < 0) + return r; + + r = qemu_config_section(config_file, "mon", "mon0", + "chardev", "console"); + if (r < 0) + return r; + break; case CONSOLE_HEADLESS: - r = strv_extend_many( - &cmdline, - "-nographic", - "-nodefaults"); + r = strv_extend_many(&cmdline, "-nographic", "-nodefaults"); + if (r < 0) + return log_oom(); + break; default: assert_not_reached(); } - if (r < 0) - return log_oom(); - - r = strv_extend(&cmdline, "-drive"); - if (r < 0) - return log_oom(); - - _cleanup_free_ char *escaped_ovmf_config_path = escape_qemu_value(ovmf_config->path); - if (!escaped_ovmf_config_path) - return log_oom(); - r = strv_extendf(&cmdline, "if=pflash,format=%s,readonly=on,file=%s", ovmf_config_format(ovmf_config), escaped_ovmf_config_path); + r = qemu_config_section(config_file, "drive", "ovmf-code", + "if", "pflash", + "format", ovmf_config_format(ovmf_config), + "readonly", "on", + "file", ovmf_config->path); if (r < 0) - return log_oom(); + return r; if (arg_efi_nvram_state_mode == STATE_AUTO && !arg_ephemeral) { assert(!arg_efi_nvram_state_path); @@ -2516,21 +2616,26 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { destroy_path = mfree(destroy_path); /* disarm auto-destroy */ - r = strv_extend_many( - &cmdline, - "-global", "ICH9-LPC.disable_s3=1", - "-global", "driver=cfi.pflash01,property=secure,value=on", - "-drive"); + r = qemu_config_section(config_file, "global", /* id= */ NULL, + "driver", "ICH9-LPC", + "property", "disable_s3", + "value", "1"); if (r < 0) - return log_oom(); + return r; - _cleanup_free_ char *escaped_state = escape_qemu_value(state); - if (!escaped_state) - return log_oom(); + r = qemu_config_section(config_file, "global", /* id= */ NULL, + "driver", "cfi.pflash01", + "property", "secure", + "value", "on"); + if (r < 0) + return r; - r = strv_extendf(&cmdline, "file=%s,if=pflash,format=%s", escaped_state, ovmf_config_format(ovmf_config)); + r = qemu_config_section(config_file, "drive", "ovmf-vars", + "file", state, + "if", "pflash", + "format", ovmf_config_format(ovmf_config)); if (r < 0) - return log_oom(); + return r; } if (kernel) { @@ -2559,8 +2664,10 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } if (need_scsi_controller) { - if (strv_extend_many(&cmdline, "-device", "virtio-scsi-pci,id=vmspawn_scsi") < 0) - return log_oom(); + r = qemu_config_section(config_file, "device", "vmspawn_scsi", + "driver", "virtio-scsi-pci"); + if (r < 0) + return r; } if (arg_image) { @@ -2574,54 +2681,60 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { arg_image); } - if (strv_extend(&cmdline, "-drive") < 0) - return log_oom(); - - _cleanup_free_ char *escaped_image = escape_qemu_value(arg_image); - if (!escaped_image) - return log_oom(); - - if (strv_extendf(&cmdline, "if=none,id=vmspawn,file=%s,format=%s,discard=%s,snapshot=%s", - escaped_image, image_format_to_string(arg_image_format), on_off(arg_discard_disk), on_off(arg_ephemeral)) < 0) - return log_oom(); + r = qemu_config_section(config_file, "drive", "vmspawn", + "if", "none", + "file", arg_image, + "format", image_format_to_string(arg_image_format), + "discard", on_off(arg_discard_disk), + "snapshot", on_off(arg_ephemeral)); + if (r < 0) + return r; _cleanup_free_ char *image_fn = NULL; r = path_extract_filename(arg_image, &image_fn); if (r < 0) return log_error_errno(r, "Failed to extract filename from path '%s': %m", image_fn); - _cleanup_free_ char *escaped_image_fn = escape_qemu_value(image_fn); - if (!escaped_image_fn) - return log_oom(); - - if (strv_extend(&cmdline, "-device") < 0) - return log_oom(); + const char *disk_driver; + _cleanup_free_ char *serial = NULL; switch (arg_image_disk_type) { case DISK_TYPE_VIRTIO_BLK: - if (strv_extend_joined(&cmdline, "virtio-blk-pci,drive=vmspawn,bootindex=1,serial=", escaped_image_fn) < 0) + disk_driver = "virtio-blk-pci"; + serial = strdup(image_fn); + if (!serial) return log_oom(); break; - case DISK_TYPE_VIRTIO_SCSI: { - _cleanup_free_ char *serial = NULL; - if (disk_serial(escaped_image_fn, 30, &serial) < 0) - return log_oom(); - if (strv_extend_joined(&cmdline, "scsi-hd,bus=vmspawn_scsi.0,drive=vmspawn,bootindex=1,serial=", serial) < 0) + case DISK_TYPE_VIRTIO_SCSI: + disk_driver = "scsi-hd"; + r = disk_serial(image_fn, 30, &serial); + if (r < 0) return log_oom(); break; - } - case DISK_TYPE_NVME: { - _cleanup_free_ char *serial = NULL; - if (disk_serial(escaped_image_fn, 20, &serial) < 0) - return log_oom(); - if (strv_extend_joined(&cmdline, "nvme,drive=vmspawn,bootindex=1,serial=", serial) < 0) + case DISK_TYPE_NVME: + disk_driver = "nvme"; + r = disk_serial(image_fn, 20, &serial); + if (r < 0) return log_oom(); break; - } default: assert_not_reached(); } + r = qemu_config_section(config_file, "device", "vmspawn-disk", + "driver", disk_driver, + "drive", "vmspawn", + "bootindex", "1", + "serial", serial); + if (r < 0) + return r; + + if (arg_image_disk_type == DISK_TYPE_VIRTIO_SCSI) { + r = qemu_config_key(config_file, "bus", "vmspawn_scsi.0"); + if (r < 0) + return r; + } + r = grow_image(arg_image, arg_grow_image); if (r < 0) return r; @@ -2686,21 +2799,19 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { pidref_done(&child); children[n_children++] = TAKE_PTR(source); - _cleanup_free_ char *escaped_listen_address = escape_qemu_value(listen_address); - if (!escaped_listen_address) - return log_oom(); - - if (strv_extend(&cmdline, "-chardev") < 0) - return log_oom(); - - if (strv_extendf(&cmdline, "socket,id=rootdir,path=%s", escaped_listen_address) < 0) - return log_oom(); + r = qemu_config_section(config_file, "chardev", "rootdir", + "backend", "socket", + "path", listen_address); + if (r < 0) + return r; - if (strv_extend_many( - &cmdline, - "-device", - "vhost-user-fs-pci,queue-size=1024,chardev=rootdir,tag=root") < 0) - return log_oom(); + r = qemu_config_section(config_file, "device", "rootdir", + "driver", "vhost-user-fs-pci", + "queue-size", "1024", + "chardev", "rootdir", + "tag", "root"); + if (r < 0) + return r; if (strv_extend(&arg_kernel_cmdline_extra, "root=root rootfstype=virtiofs rw") < 0) return log_oom(); @@ -2810,25 +2921,23 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { pidref_done(&child); children[n_children++] = TAKE_PTR(source); - _cleanup_free_ char *escaped_listen_address = escape_qemu_value(listen_address); - if (!escaped_listen_address) - return log_oom(); - - if (strv_extend(&cmdline, "-chardev") < 0) - return log_oom(); - _cleanup_free_ char *id = NULL; if (asprintf(&id, "mnt%zu", j) < 0) return log_oom(); - if (strv_extendf(&cmdline, "socket,id=%s,path=%s", id, escaped_listen_address) < 0) - return log_oom(); - - if (strv_extend(&cmdline, "-device") < 0) - return log_oom(); + r = qemu_config_section(config_file, "chardev", id, + "backend", "socket", + "path", listen_address); + if (r < 0) + return r; - if (strv_extendf(&cmdline, "vhost-user-fs-pci,queue-size=1024,chardev=%1$s,tag=%1$s", id) < 0) - return log_oom(); + r = qemu_config_section(config_file, "device", id, + "driver", "vhost-user-fs-pci", + "queue-size", "1024", + "chardev", id, + "tag", id); + if (r < 0) + return r; _cleanup_free_ char *clean_target = xescape(m->target, "\":"); if (!clean_target) @@ -2911,25 +3020,33 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } if (tpm_socket_address) { - _cleanup_free_ char *escaped_tpm_socket_address = escape_qemu_value(tpm_socket_address); - if (!escaped_tpm_socket_address) - return log_oom(); - - if (strv_extend(&cmdline, "-chardev") < 0) - return log_oom(); - - if (strv_extend_joined(&cmdline, "socket,id=chrtpm,path=", tpm_socket_address) < 0) - return log_oom(); + r = qemu_config_section(config_file, "chardev", "chrtpm", + "backend", "socket", + "path", tpm_socket_address); + if (r < 0) + return r; - if (strv_extend_many(&cmdline, "-tpmdev", "emulator,id=tpm0,chardev=chrtpm") < 0) - return log_oom(); + r = qemu_config_section(config_file, "tpmdev", "tpm0", + "type", "emulator", + "chardev", "chrtpm"); + if (r < 0) + return r; + const char *tpm_driver; if (native_architecture() == ARCHITECTURE_X86_64) - r = strv_extend_many(&cmdline, "-device", "tpm-tis,tpmdev=tpm0"); + tpm_driver = "tpm-tis"; else if (IN_SET(native_architecture(), ARCHITECTURE_ARM64, ARCHITECTURE_ARM64_BE)) - r = strv_extend_many(&cmdline, "-device", "tpm-tis-device,tpmdev=tpm0"); - if (r < 0) - return log_oom(); + tpm_driver = "tpm-tis-device"; + else + tpm_driver = NULL; + + if (tpm_driver) { + r = qemu_config_section(config_file, "device", "tpmdev0", + "driver", tpm_driver, + "tpmdev", "tpm0"); + if (r < 0) + return r; + } } char *initrd = NULL; @@ -3083,6 +3200,16 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return log_error_errno(r, "Failed to call getsockname on VSOCK: %m"); } + /* Finalize the config file and add -readconfig to the cmdline */ + r = fflush_and_check(config_file); + if (r < 0) + return log_error_errno(r, "Failed to write QEMU config file: %m"); + config_file = safe_fclose(config_file); + + r = strv_extend_many(&cmdline, "-readconfig", config_path); + if (r < 0) + return log_oom(); + const char *e = secure_getenv("SYSTEMD_VMSPAWN_QEMU_EXTRA"); if (e) { r = strv_split_and_extend_full(&cmdline, e, @@ -3093,6 +3220,14 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } if (DEBUG_LOGGING) { + _cleanup_free_ char *config_contents = NULL; + + r = read_full_file(config_path, &config_contents, /* ret_size= */ NULL); + if (r < 0) + log_debug_errno(r, "Failed to read back QEMU config file, ignoring: %m"); + else + log_debug("QEMU config file %s:\n%s", config_path, config_contents); + _cleanup_free_ char *joined = quote_command_line(cmdline, SHELL_ESCAPE_EMPTY); if (!joined) return log_oom(); From 7863f6ba85e0ac5bbb65c4ceffa23be7f512f11f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Mar 2026 22:18:58 +0100 Subject: [PATCH 0488/1296] resolved: fix typo in comment --- src/resolve/resolved-static-records.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resolve/resolved-static-records.c b/src/resolve/resolved-static-records.c index 4aa6f2e421216..0f2d09b324fb1 100644 --- a/src/resolve/resolved-static-records.c +++ b/src/resolve/resolved-static-records.c @@ -29,7 +29,7 @@ * serializations of DNS RRs. Also note the semantics are different from DNS zone file format, for example * regarding delegation (i.e. the RRs defined here have no effect on subdomains), which is probably nicer for * one-off mappings of domains to specific resources. Or in other words, this is supposed to be a drop-in - * based alternative to /etc/hosts, not a one to DNS zone files. (The JSON format is also a lot more + * based alternative to /etc/hosts, not one to DNS zone files. (The JSON format is also a lot more * extensible to us, for example we could teach it to map certain lookups to specific DNS errors, or extend * it so that subdomains always get NXDOMAIN or similar). * From b734340596a27c067a27bc0774ba30d7dd22b553 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Mar 2026 22:18:12 +0100 Subject: [PATCH 0489/1296] resolved: move resetting of {etc_hosts|static_records}_last to manager_dispatch_reload_signal() This addresses https://github.com/systemd/systemd/pull/41213#pullrequestreview-4002247053 which I somehow missed earlier. Claude found a real issue for the case of manager_etc_hosts_flush(). We'll do the equivalent change in manager_static_records_flush() too, even though it's not really necessary there, simply to keep things nicely mirrored. --- src/resolve/resolved-etc-hosts.c | 3 ++- src/resolve/resolved-manager.c | 3 +++ src/resolve/resolved-static-records.c | 1 - 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/resolve/resolved-etc-hosts.c b/src/resolve/resolved-etc-hosts.c index 00c76a9977f85..b38c011e7c6e7 100644 --- a/src/resolve/resolved-etc-hosts.c +++ b/src/resolve/resolved-etc-hosts.c @@ -72,7 +72,8 @@ void etc_hosts_clear(EtcHosts *hosts) { void manager_etc_hosts_flush(Manager *m) { etc_hosts_clear(&m->etc_hosts); m->etc_hosts_stat = (struct stat) {}; - m->etc_hosts_last = USEC_INFINITY; + /* NB: We do not reset m->etc_hosts_last here, because manager_etc_hosts_read() calls us and needs it + * to stay in effect for the reload suppression to work */ } static int parse_line(EtcHosts *hosts, unsigned nr, const char *line) { diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 25a51ed02b042..e96ae4393c682 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -664,6 +664,9 @@ static int manager_dispatch_reload_signal(sd_event_source *s, const struct signa manager_etc_hosts_flush(m); manager_static_records_flush(m); + m->etc_hosts_last = USEC_INFINITY; + m->static_records_last = USEC_INFINITY; + manager_set_defaults(m); r = dns_trust_anchor_load(&m->trust_anchor); diff --git a/src/resolve/resolved-static-records.c b/src/resolve/resolved-static-records.c index 0f2d09b324fb1..d905d507d6ab3 100644 --- a/src/resolve/resolved-static-records.c +++ b/src/resolve/resolved-static-records.c @@ -222,5 +222,4 @@ void manager_static_records_flush(Manager *m) { m->static_records = hashmap_free(m->static_records); m->static_records_stat = set_free(m->static_records_stat); - m->static_records_last = USEC_INFINITY; } From 91af485544b7f1ec7436f4b73b4a9bdcc6607f72 Mon Sep 17 00:00:00 2001 From: Massii Aqvayli Date: Wed, 25 Mar 2026 21:58:45 +0000 Subject: [PATCH 0490/1296] po: Translated using Weblate (Kabyle) Currently translated at 36.4% (97 of 266 strings) Co-authored-by: Massii Aqvayli Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/kab/ Translation: systemd/main --- po/kab.po | 76 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/po/kab.po b/po/kab.po index f37a23008bf35..5f907a00884a0 100644 --- a/po/kab.po +++ b/po/kab.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2026-03-24 19:58+0000\n" +"PO-Revision-Date: 2026-03-25 21:58+0000\n" "Last-Translator: Massii Aqvayli \n" "Language-Team: Kabyle \n" @@ -289,20 +289,20 @@ msgstr "Yewḥel usekles n useqdac, yegdel anekcum." #: src/home/pam_systemd_home.c:1016 msgid "User record is not valid yet, prohibiting access." -msgstr "" +msgstr "Asekles n useqdac mačči d ameɣtu akka tura, anekcum yettwagdel." #: src/home/pam_systemd_home.c:1020 msgid "User record is not valid anymore, prohibiting access." -msgstr "" +msgstr "Asekles n useqdac ur d-yiqqim ara d ameɣtu, anekcum yettwagdel." #: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 msgid "User record not valid, prohibiting access." -msgstr "" +msgstr "Asekles n useqdac mačči d ameɣtu, anekcum yettwagdel." #: src/home/pam_systemd_home.c:1035 #, c-format msgid "Too many logins, try again in %s." -msgstr "" +msgstr "Ddeqs n tuqqniwin, ɛreḍ tikelt nniḍen di %s." #: src/home/pam_systemd_home.c:1046 msgid "Password change required." @@ -310,15 +310,15 @@ msgstr "Asnifel n wawal n uɛeddi yettwasra." #: src/home/pam_systemd_home.c:1050 msgid "Password expired, change required." -msgstr "" +msgstr "Awal n uɛeddi yemmut, asnifel yettwasra." #: src/home/pam_systemd_home.c:1056 msgid "Password is expired, but can't change, refusing login." -msgstr "" +msgstr "Awal n uɛeddi yemmut, yerna ur izmir ara ad ibeddel, tuqqna tettwagi." #: src/home/pam_systemd_home.c:1060 msgid "Password will expire soon, please change." -msgstr "" +msgstr "Ur yettɛeṭṭil ara ad yemmet wawal n uɛeddi, ma ulac aɣilif, snifel-it." #: src/hostname/org.freedesktop.hostname1.policy:20 msgid "Set hostname" @@ -326,11 +326,11 @@ msgstr "Sbadu isem n usenneftaɣ" #: src/hostname/org.freedesktop.hostname1.policy:21 msgid "Authentication is required to set the local hostname." -msgstr "" +msgstr "Asesteb yettwasra i usbadu n yisem n usenneftaɣ adigan." #: src/hostname/org.freedesktop.hostname1.policy:30 msgid "Set static hostname" -msgstr "" +msgstr "Sbadu isem n usenneftaɣ udmis" #: src/hostname/org.freedesktop.hostname1.policy:31 msgid "" @@ -344,64 +344,64 @@ msgstr "Sbadu talɣut n tmacint" #: src/hostname/org.freedesktop.hostname1.policy:42 msgid "Authentication is required to set local machine information." -msgstr "" +msgstr "Asesteb yettwasra i usbadu n telɣut n tmacint tadigant." #: src/hostname/org.freedesktop.hostname1.policy:51 msgid "Get product UUID" -msgstr "" +msgstr "Awi-d UUID n ufaris" #: src/hostname/org.freedesktop.hostname1.policy:52 msgid "Authentication is required to get product UUID." -msgstr "" +msgstr "Asesteb yettwasra i wawway n UUID n ufaris." #: src/hostname/org.freedesktop.hostname1.policy:61 msgid "Get hardware serial number" -msgstr "" +msgstr "Awi-d uṭṭun n umazrar n warrum" #: src/hostname/org.freedesktop.hostname1.policy:62 msgid "Authentication is required to get hardware serial number." -msgstr "" +msgstr "Asesteb yettwasra akken ad tawiḍ uṭṭun n umazrar n warrum." #: src/hostname/org.freedesktop.hostname1.policy:71 msgid "Get system description" -msgstr "" +msgstr "Awi-d aglam n unagraw" #: src/hostname/org.freedesktop.hostname1.policy:72 msgid "Authentication is required to get system description." -msgstr "" +msgstr "Asesteb yettwasra i wawway n uglam n unagraw." #: src/import/org.freedesktop.import1.policy:22 msgid "Import a disk image" -msgstr "" +msgstr "Kter tugna n uḍebsi" #: src/import/org.freedesktop.import1.policy:23 msgid "Authentication is required to import an image." -msgstr "" +msgstr "Asesteb yettwasra i ukter n tugna n uḍebsi." #: src/import/org.freedesktop.import1.policy:32 msgid "Export a disk image" -msgstr "" +msgstr "Sifeḍ tugna n uḍebsi" #: src/import/org.freedesktop.import1.policy:33 msgid "Authentication is required to export disk image." -msgstr "" +msgstr "Asesteb yettwasra i wesifeḍ n tugna n uḍebsi." #: src/import/org.freedesktop.import1.policy:42 msgid "Download a disk image" -msgstr "" +msgstr "Sider tugna n uḍebsi" #: src/import/org.freedesktop.import1.policy:43 msgid "Authentication is required to download a disk image." -msgstr "" +msgstr "Asesteb yettwasra i usider n tugna n uḍebsi." #: src/import/org.freedesktop.import1.policy:52 msgid "Cancel transfer of a disk image" -msgstr "" +msgstr "Sefsex asiweḍ n tugna n uḍebsi" #: src/import/org.freedesktop.import1.policy:53 msgid "" "Authentication is required to cancel the ongoing transfer of a disk image." -msgstr "" +msgstr "Asesteb yettwasra i wessefsex n usiweḍ itteddun n tugna n uḍebsi." #: src/locale/org.freedesktop.locale1.policy:22 msgid "Set system locale" @@ -409,32 +409,34 @@ msgstr "Sbedd tutlayt n unagraw" #: src/locale/org.freedesktop.locale1.policy:23 msgid "Authentication is required to set the system locale." -msgstr "" +msgstr "Asesteb yettwasra i usbadu n tutlayt tadigant n unagraw." #: src/locale/org.freedesktop.locale1.policy:33 msgid "Set system keyboard settings" -msgstr "" +msgstr "Sbadu iɣewwaṛen n unasiw n unagraw" #: src/locale/org.freedesktop.locale1.policy:34 msgid "Authentication is required to set the system keyboard settings." -msgstr "" +msgstr "Asesteb yettwasra i usbadu n yiɣewwaṛen n unasiw n unagraw." #: src/login/org.freedesktop.login1.policy:22 msgid "Allow applications to inhibit system shutdown" -msgstr "" +msgstr "Sireg isnasen ad sḥebsen asexsi n unagraw" #: src/login/org.freedesktop.login1.policy:23 msgid "" "Authentication is required for an application to inhibit system shutdown." msgstr "" +"Asesteb yettwasra akken ad isireg asnas ad yezmer i useḥbes n usexsi n " +"unagraw." #: src/login/org.freedesktop.login1.policy:33 msgid "Allow applications to delay system shutdown" -msgstr "" +msgstr "Sireg i yisnasen ad izmiren ad smezgren asexsi n unagraw" #: src/login/org.freedesktop.login1.policy:34 msgid "Authentication is required for an application to delay system shutdown." -msgstr "" +msgstr "Asesteb yettwasra i usnas akken ad yesmezger asexsi n unagraw." #: src/login/org.freedesktop.login1.policy:44 msgid "Allow applications to inhibit system sleep" @@ -514,7 +516,7 @@ msgstr "" #: src/login/org.freedesktop.login1.policy:128 msgid "Allow non-logged-in user to run programs" -msgstr "" +msgstr "Sireg aseqdac aruqqin i uselkem n wahilen" #: src/login/org.freedesktop.login1.policy:129 msgid "Explicit request is required to run programs as a non-logged-in user." @@ -522,7 +524,7 @@ msgstr "" #: src/login/org.freedesktop.login1.policy:138 msgid "Allow non-logged-in users to run programs" -msgstr "" +msgstr "Sireg iseqdacen ur yeqqinen ara i wakken ad slekmen ahilen" #: src/login/org.freedesktop.login1.policy:139 msgid "Authentication is required to run programs as a non-logged-in user." @@ -901,22 +903,22 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:88 #: src/resolve/org.freedesktop.resolve1.policy:99 msgid "Enable/disable DNS over TLS" -msgstr "" +msgstr "Sermed/sens DNS ɣef TLS" #: src/network/org.freedesktop.network1.policy:89 #: src/resolve/org.freedesktop.resolve1.policy:100 msgid "Authentication is required to enable or disable DNS over TLS." -msgstr "" +msgstr "Asesteb yettwasra akken ad tremdeḍ neɣ ad tsenseḍ DNS ɣef TLS." #: src/network/org.freedesktop.network1.policy:99 #: src/resolve/org.freedesktop.resolve1.policy:110 msgid "Enable/disable DNSSEC" -msgstr "" +msgstr "Sermed/Sens DNSSEC" #: src/network/org.freedesktop.network1.policy:100 #: src/resolve/org.freedesktop.resolve1.policy:111 msgid "Authentication is required to enable or disable DNSSEC." -msgstr "" +msgstr "Asesteb yettwasra akken ad tremdeḍ neɣ ad tsenseḍ DNSSEC." #: src/network/org.freedesktop.network1.policy:110 #: src/resolve/org.freedesktop.resolve1.policy:121 From 6c7e5b81ac4dd79952b3d0428a038dd5febb2bc3 Mon Sep 17 00:00:00 2001 From: Patrick Wicki Date: Fri, 20 Mar 2026 15:56:56 +0100 Subject: [PATCH 0491/1296] tpm2-util: fix PCR bank guessing without EFI Since 7643e4a89 efi_get_active_pcr_banks() is used to determine the active PCR banks. Without EFI support, this returns -EOPNOTSUPP. This in turns leads to cryptenroll and cryptsetup attach failures unless the PCR bank is explicitly set, i.e. $ systemd-cryptenroll $LUKS_PART --tpm2-device=auto --tpm2-pcrs='7' [...] Could not read pcr values: Operation not supported But it works fine with --tpm2-pcrs='7:sha256'. Similarly, unsealing during cryptsetup attach also fails if the bank needs to be determined: Failed to unseal secret using TPM2: Operation not supported Catch the -EOPNOTSUPP and fallback to the guessing strategy. Signed-off-by: Patrick Wicki --- src/shared/tpm2-util.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index c12ba2d28c778..cfa057c02ba7a 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -2892,11 +2892,11 @@ int tpm2_get_best_pcr_bank( uint32_t efi_banks; r = efi_get_active_pcr_banks(&efi_banks); if (r < 0) { - if (r != -ENOENT) + if (!IN_SET(r, -ENOENT, -EOPNOTSUPP)) return r; /* If variable is not set use guesswork below */ - log_debug("Boot loader didn't set the LoaderTpm2ActivePcrBanks EFI variable, we have to guess the used PCR banks."); + log_debug("Boot loader didn't set the LoaderTpm2ActivePcrBanks EFI variable or EFI support is unavailable, we have to guess the used PCR banks."); } else if (efi_banks == UINT32_MAX) log_debug("Boot loader set the LoaderTpm2ActivePcrBanks EFI variable to indicate that the GetActivePcrBanks() API is not available in the firmware. We have to guess the used PCR banks."); else { @@ -3001,11 +3001,11 @@ int tpm2_get_good_pcr_banks( uint32_t efi_banks; r = efi_get_active_pcr_banks(&efi_banks); if (r < 0) { - if (r != -ENOENT) + if (!IN_SET(r, -ENOENT, -EOPNOTSUPP)) return r; /* If the variable is not set we have to guess via the code below */ - log_debug("Boot loader didn't set the LoaderTpm2ActivePcrBanks EFI variable, we have to guess the used PCR banks."); + log_debug("Boot loader didn't set the LoaderTpm2ActivePcrBanks EFI variable or EFI support is unavailable, we have to guess the used PCR banks."); } else if (efi_banks == UINT32_MAX) log_debug("Boot loader set the LoaderTpm2ActivePcrBanks EFI variable to indicate that the GetActivePcrBanks() API is not available in the firmware. We have to guess the used PCR banks."); else { From 7d320c3a992e71f05816c76e2f7bbb2fc392be4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luan=20Vitor=20Simi=C3=A3o=20Oliveira?= Date: Wed, 25 Mar 2026 19:11:00 -0300 Subject: [PATCH 0492/1296] hwdb: Add PXN HB S handbrake otherwise, it is not classified. reports 2 axes and 2 buttons although only 1 is actually used. --- hwdb.d/60-input-id.hwdb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hwdb.d/60-input-id.hwdb b/hwdb.d/60-input-id.hwdb index d32bfedf59416..03535884e5dbc 100644 --- a/hwdb.d/60-input-id.hwdb +++ b/hwdb.d/60-input-id.hwdb @@ -118,3 +118,7 @@ id-input:modalias:input:b0003v26CEp01A2* # Saitek PLC Pro Flight Rudder Pedals id-input:modalias:input:b0003v06A3p0763* ID_INPUT_JOYSTICK=1 + +# PXN HB S handbrake +id-input:modalias:input:b0003v11FFpA701* + ID_INPUT_JOYSTICK=1 From db3ace5da57f4d9233778751c1ed6e864724b3e8 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 26 Mar 2026 09:36:51 +0100 Subject: [PATCH 0493/1296] ci: Use path instead of file in claude-review prompt as JSON key In https://github.com/systemd/systemd/pull/40980 claude hallucinated and used "path" instead of "file" as the JSON key. Since "path" is arguably more correct than "file" anyway, let's switch to that. --- .github/workflows/claude-review.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index d3500895c6a09..07fe700b95e1a 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -271,7 +271,7 @@ jobs: Each reviewer reviews code quality, style, potential bugs, and security implications. It must return a JSON array of issues: - `[{"file": "path", "line": (optional), "severity": "must-fix|suggestion|nit", "body": "...", "commit": ""}]` + `[{"path": "path/to/file", "line": (optional), "severity": "must-fix|suggestion|nit", "body": "...", "commit": ""}]` The `commit` field MUST be the SHA of the commit being reviewed. Only comment on changes in that commit — not preceding commits. @@ -318,13 +318,13 @@ jobs: ### Must fix - - [ ] **short title** — `file:line` — brief explanation + - [ ] **short title** — `path:line` — brief explanation ### Suggestions - - [ ] **short title** — `file:line` — brief explanation + - [ ] **short title** — `path:line` — brief explanation ### Nits - - [ ] **short title** — `file:line` — brief explanation + - [ ] **short title** — `path:line` — brief explanation ``` Omit empty sections. Each checkbox item must correspond to an entry in `comments`. @@ -365,7 +365,7 @@ jobs: "summary": "...", "comments": [ { - "file": "path/to/file", + "path": "path/to/file", "line": 42, "severity": "must-fix|suggestion|nit", "body": "review comment in markdown", @@ -377,7 +377,7 @@ jobs: ``` - `summary` (required): markdown summary for the tracking comment - - `comments` (required): array of review comments; `line` is optional + - `comments` (required): array of review comments; `path` is the file path, `line` is optional - `resolve` (optional): REST API IDs of review comment threads to resolve Do NOT attempt to post comments or use any MCP tools to modify the PR. @@ -470,21 +470,21 @@ jobs: * comments is handled by Claude in the prompt, so we just post whatever * it returns. Using individual comments (rather than a review) means * re-runs only add new comments instead of creating a whole new review. */ - const inlineComments = comments.filter((c) => c.line); + const inlineComments = comments.filter((c) => c.path && c.line); const skipped = comments.length - inlineComments.length; if (skipped > 0) - console.log(`Skipping ${skipped} file-level comment(s) (no line number).`); + console.log(`Skipping ${skipped} comment(s) missing path or line number.`); let posted = 0; for (const c of inlineComments) { - console.log(` Posting comment on ${c.file}:${c.line}`); + console.log(` Posting comment on ${c.path}:${c.line}`); try { await github.rest.pulls.createReviewComment({ owner, repo, pull_number: prNumber, commit_id: c.commit, - path: c.file, + path: c.path, line: c.line, body: `Claude: **${c.severity}**: ${c.body}`, }); @@ -492,7 +492,7 @@ jobs: } catch (e) { /* GitHub rejects comments on lines outside the diff context. Log * and continue — the tracking comment still contains all findings. */ - console.log(` Warning: failed to post comment on ${c.file}:${c.line}: ${e.message}`); + console.log(` Warning: failed to post comment on ${c.path}:${c.line}: ${e.message}`); } } From 211cd6e9a34d957dfa3b7616f0e618b6d17a51c2 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 26 Mar 2026 09:38:03 +0100 Subject: [PATCH 0494/1296] ci: Add subject_type to createReviewComment() Apparently this is required by the createReviewComment() API. --- .github/workflows/claude-review.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 07fe700b95e1a..1d08fe01a9497 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -486,6 +486,7 @@ jobs: commit_id: c.commit, path: c.path, line: c.line, + subject_type: "line", body: `Claude: **${c.severity}**: ${c.body}`, }); posted++; From 5dd2ae14db77a3c55540f90fe4859b89c9d06ca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 25 Mar 2026 08:15:21 +0100 Subject: [PATCH 0495/1296] shared/options: stop removing items from argv array If we remove "--" from argv, the argc parameter stops being valid. But that state is effectively global, albeit readonly, and somebody looking at argv+argc after that will see an inconsistent state. Let's behave like the libc parsing code and instead just shuffle things around in argv, so that the argv+argc pair remains consistent. This allows the code to calculate how many options are remaining to be simplified. --- src/ac-power/ac-power.c | 2 +- src/shared/options.c | 30 ++++++++++++++++++------------ src/shared/options.h | 5 +---- src/test/test-options.c | 2 +- src/update-done/update-done.c | 2 +- 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/ac-power/ac-power.c b/src/ac-power/ac-power.c index ec07a914c59a2..e6b58810a6d00 100644 --- a/src/ac-power/ac-power.c +++ b/src/ac-power/ac-power.c @@ -68,7 +68,7 @@ static int parse_argv(int argc, char *argv[]) { break; } - if (option_parser_get_n_args(&state, argc, argv) > 0) + if (option_parser_get_n_args(&state, argc) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); return 1; diff --git a/src/shared/options.c b/src/shared/options.c index 8ea22c9b7a18d..508db28ea0a90 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -25,14 +25,6 @@ static bool option_is_metadata(const Option *opt) { FLAGS_SET(ASSERT_PTR(opt)->flags, OPTION_HELP_ENTRY); } -static void kill_arg(char* argv[], int argc, int index) { - assert(index < argc); - assert(!argv[argc]); - - /* Eliminate argv[index] */ - memmove(argv + index, argv + index + 1, (argc - index) * sizeof(char*)); -} - static void shift_arg(char* argv[], int target, int source) { assert(argv); assert(target <= source); @@ -108,9 +100,10 @@ int option_parse( return 0; if (streq(argv[state->optind], "--")) { - /* No more options. Eliminate "--" so that the list of positional args is clean. */ - kill_arg(argv, argc, state->optind); - return 0; + /* No more options. Move "--" before positional args so that + * the list of positional args is clean. */ + shift_arg(argv, state->positional_offset++, state->optind++); + state->parsing_stopped = true; } if (state->parsing_stopped) @@ -243,12 +236,25 @@ int option_parse( char** option_parser_get_args(const OptionParser *state, int argc, char *argv[]) { /* Returns positional args as a strv. - * If "--" was found, it has been removed. */ + * If "--" was found, it has been moved before state->positional_offset. + * The array is only valid, i.e. clean without any options, after parsing + * has naturally finished. */ assert(state->optind > 0); + assert(state->optind == argc || state->parsing_stopped); + assert(state->positional_offset <= argc); + return argv + state->positional_offset; } +size_t option_parser_get_n_args(const OptionParser *state, int argc) { + assert(state->optind > 0); + assert(state->optind == argc || state->parsing_stopped); + assert(state->positional_offset <= argc); + + return argc - state->positional_offset; +} + int _option_parser_get_help_table( const Option options[], const Option options_end[], diff --git a/src/shared/options.h b/src/shared/options.h index fd2d048db00c4..4895c10cbd014 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -2,7 +2,6 @@ #pragma once #include "shared-forward.h" -#include "strv.h" typedef enum OptionFlags { OPTION_OPTIONAL_ARG = 1U << 0, /* Same as optional_argument in getopt */ @@ -98,9 +97,7 @@ int option_parse( FOREACH_OPTION_FULL(parser, opt, argc, argv, /* ret_o= */ NULL, ret_a, on_error) char** option_parser_get_args(const OptionParser *state, int argc, char *argv[]); -static inline size_t option_parser_get_n_args(const OptionParser *state, int argc, char *argv[]) { - return strv_length(option_parser_get_args(state, argc, argv)); -} +size_t option_parser_get_n_args(const OptionParser *state, int argc); int _option_parser_get_help_table( const Option options[], diff --git a/src/test/test-options.c b/src/test/test-options.c index 201d16d51f895..e849365a2f2ee 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -58,7 +58,7 @@ static void test_option_parse_one( ASSERT_TRUE(strv_equal(args, remaining)); ASSERT_STREQ(argv[0], saved_argv0); - ASSERT_EQ(option_parser_get_n_args(&state, argc, argv), strv_length(remaining)); + ASSERT_EQ(option_parser_get_n_args(&state, argc), strv_length(remaining)); } static void test_option_invalid_one( diff --git a/src/update-done/update-done.c b/src/update-done/update-done.c index 03e5479aaefa4..7fb1c58d19bac 100644 --- a/src/update-done/update-done.c +++ b/src/update-done/update-done.c @@ -112,7 +112,7 @@ static int parse_argv(int argc, char *argv[]) { break; } - if (option_parser_get_n_args(&state, argc, argv) > 0) + if (option_parser_get_n_args(&state, argc) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); return 1; From f4a5bf92e9d3860958ef1aecd2208f81c3dcbd90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 25 Mar 2026 16:35:12 +0100 Subject: [PATCH 0496/1296] shared/options: store argc+argv in the OptionParser state struct After writing the code to parse options in a bunch of places, I think passing the argc+argv to various functions to query state is annoying. It seems nicer to just stash them in the state struct once. --- src/ac-power/ac-power.c | 6 ++-- src/ask-password/ask-password.c | 6 ++-- src/binfmt/binfmt.c | 6 ++-- src/bless-boot/bless-boot.c | 6 ++-- src/dissect/dissect.c | 6 ++-- src/id128/id128.c | 6 ++-- src/notify/notify.c | 6 ++-- src/shared/options.c | 56 +++++++++++++++------------------ src/shared/options.h | 17 +++++----- src/test/test-options.c | 18 +++++------ src/update-done/update-done.c | 6 ++-- src/validatefs/validatefs.c | 6 ++-- 12 files changed, 72 insertions(+), 73 deletions(-) diff --git a/src/ac-power/ac-power.c b/src/ac-power/ac-power.c index e6b58810a6d00..1ca1048c5a4e9 100644 --- a/src/ac-power/ac-power.c +++ b/src/ac-power/ac-power.c @@ -48,10 +48,10 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = {}; + OptionParser state = { argc, argv }; const char *arg; - FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -68,7 +68,7 @@ static int parse_argv(int argc, char *argv[]) { break; } - if (option_parser_get_n_args(&state, argc) > 0) + if (option_parser_get_n_args(&state) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); return 1; diff --git a/src/ask-password/ask-password.c b/src/ask-password/ask-password.c index 13e23687952a8..2c032c1afbc7f 100644 --- a/src/ask-password/ask-password.c +++ b/src/ask-password/ask-password.c @@ -71,10 +71,10 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = {}; + OptionParser state = { argc, argv }; const char *arg; - FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -170,7 +170,7 @@ static int parse_argv(int argc, char *argv[]) { SET_FLAG(arg_flags, ASK_PASSWORD_HIDE_EMOJI, !r); } - char **args = option_parser_get_args(&state, argc, argv); + char **args = option_parser_get_args(&state); if (!strv_isempty(args)) { arg_message = strv_join(args, " "); diff --git a/src/binfmt/binfmt.c b/src/binfmt/binfmt.c index bdd62398e5ef0..23c09fe3496e3 100644 --- a/src/binfmt/binfmt.c +++ b/src/binfmt/binfmt.c @@ -137,10 +137,10 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = {}; + OptionParser state = { argc, argv }; const char *arg; - FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -165,7 +165,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; } - char **args = option_parser_get_args(&state, argc, argv); + char **args = option_parser_get_args(&state); if ((arg_unregister || arg_cat_flags != CAT_CONFIG_OFF) && !strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), diff --git a/src/bless-boot/bless-boot.c b/src/bless-boot/bless-boot.c index daabff405f226..bf1c6e7a0cbc3 100644 --- a/src/bless-boot/bless-boot.c +++ b/src/bless-boot/bless-boot.c @@ -75,10 +75,10 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = {}; + OptionParser state = { argc, argv }; const char *arg; - FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -93,7 +93,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; } - *ret_args = option_parser_get_args(&state, argc, argv); + *ret_args = option_parser_get_args(&state); return 1; } diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index d36f31e5c717f..3597971af9b03 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -207,11 +207,11 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = {}; + OptionParser state = { argc, argv }; const Option *current; const char *arg; - FOREACH_OPTION_FULL(&state, c, argc, argv, ¤t, &arg, /* on_error= */ return c) + FOREACH_OPTION_FULL(&state, c, ¤t, &arg, /* on_error= */ return c) switch (c) { OPTION_COMMON_NO_PAGER: @@ -495,7 +495,7 @@ static int parse_argv(int argc, char *argv[]) { arg_runtime_scope = system_scope_requested && user_scope_requested ? _RUNTIME_SCOPE_INVALID : system_scope_requested ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER; - char **args = option_parser_get_args(&state, argc, argv); + char **args = option_parser_get_args(&state); switch (arg_action) { diff --git a/src/id128/id128.c b/src/id128/id128.c index ebed02913ff6e..688504c71480e 100644 --- a/src/id128/id128.c +++ b/src/id128/id128.c @@ -233,10 +233,10 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = {}; + OptionParser state = { argc, argv }; const char *arg; - FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -288,7 +288,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { break; } - *ret_args = option_parser_get_args(&state, argc, argv); + *ret_args = option_parser_get_args(&state); return 1; } diff --git a/src/notify/notify.c b/src/notify/notify.c index 5535296760b14..a06f5ce7734e3 100644 --- a/src/notify/notify.c +++ b/src/notify/notify.c @@ -158,10 +158,10 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = {}; + OptionParser state = { argc, argv }; const char *arg; - FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -283,7 +283,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { bool have_env = arg_ready || arg_stopping || arg_reloading || arg_status || pidref_is_set(&arg_pid) || !fdset_isempty(arg_fds); - char **args = option_parser_get_args(&state, argc, argv); + char **args = option_parser_get_args(&state); switch (arg_action) { diff --git a/src/shared/options.c b/src/shared/options.c index 508db28ea0a90..86f274821eda8 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -69,7 +69,6 @@ int option_parse( const Option options[], const Option options_end[], OptionParser *state, - int argc, char *argv[], const Option **ret_option, const char **ret_arg) { @@ -77,13 +76,10 @@ int option_parse( /* Check and initialize */ if (state->optind == 0) { - if (argc < 1 || strv_isempty(argv)) + if (state->argc < 1 || strv_isempty(state->argv)) return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "argv cannot be empty"); - *state = (OptionParser) { - .optind = 1, - .positional_offset = 1, - }; + state->optind = state->positional_offset = 1; } /* Look for the next option */ @@ -96,21 +92,21 @@ int option_parse( if (state->short_option_offset == 0) { /* Skip over non-option parameters */ for (;;) { - if (state->optind == argc) + if (state->optind == state->argc) return 0; - if (streq(argv[state->optind], "--")) { + if (streq(state->argv[state->optind], "--")) { /* No more options. Move "--" before positional args so that * the list of positional args is clean. */ - shift_arg(argv, state->positional_offset++, state->optind++); + shift_arg(state->argv, state->positional_offset++, state->optind++); state->parsing_stopped = true; } if (state->parsing_stopped) return 0; - if (argv[state->optind][0] == '-' && - argv[state->optind][1] != '\0') + if (state->argv[state->optind][0] == '-' && + state->argv[state->optind][1] != '\0') /* Looks like we found an option parameter */ break; @@ -119,13 +115,13 @@ int option_parse( /* Find matching option entry. * First, figure out if we have a long option or a short option. */ - assert(argv[state->optind][0] == '-'); + assert(state->argv[state->optind][0] == '-'); - if (argv[state->optind][1] == '-') { + if (state->argv[state->optind][1] == '-') { /* We have a long option. */ - char *eq = strchr(argv[state->optind], '='); + char *eq = strchr(state->argv[state->optind], '='); if (eq) { - optname = _optname = strndup(argv[state->optind], eq - argv[state->optind]); + optname = _optname = strndup(state->argv[state->optind], eq - state->argv[state->optind]); if (!_optname) return log_oom(); @@ -133,7 +129,7 @@ int option_parse( optval = eq + 1; } else /* argument (if any) is separate */ - optname = argv[state->optind]; + optname = state->argv[state->optind]; const Option *last_partial = NULL; unsigned n_partial_matches = 0; /* The commandline option matches a defined prefix. */ @@ -172,7 +168,7 @@ int option_parse( } if (state->short_option_offset > 0) { - char optchar = argv[state->optind][state->short_option_offset]; + char optchar = state->argv[state->optind][state->short_option_offset]; if (asprintf(&_optname, "-%c", optchar) < 0) return log_oom(); @@ -187,7 +183,7 @@ int option_parse( if (option_is_metadata(option) || optchar != option->short_code) continue; - const char *rest = argv[state->optind] + state->short_option_offset + 1; + const char *rest = state->argv[state->optind] + state->short_option_offset + 1; if (option_takes_arg(option) && !isempty(rest)) { /* The rest of this parameter is the value. */ @@ -209,19 +205,19 @@ int option_parse( "%s: option '%s' doesn't allow an argument", program_invocation_short_name, optname); if (!optval && option_arg_required(option)) { - if (!argv[state->optind + 1]) + if (!state->argv[state->optind + 1]) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s: option '%s' requires an argument", program_invocation_short_name, optname); - optval = argv[state->optind + 1]; + optval = state->argv[state->optind + 1]; separate_optval = true; } if (state->short_option_offset == 0) { /* We're done with this option. Adjust the array and position. */ - shift_arg(argv, state->positional_offset++, state->optind++); + shift_arg(state->argv, state->positional_offset++, state->optind++); if (separate_optval) - shift_arg(argv, state->positional_offset++, state->optind++); + shift_arg(state->argv, state->positional_offset++, state->optind++); } if (FLAGS_SET(option->flags, OPTION_STOPS_PARSING)) @@ -234,25 +230,25 @@ int option_parse( return option->id; } -char** option_parser_get_args(const OptionParser *state, int argc, char *argv[]) { +char** option_parser_get_args(const OptionParser *state) { /* Returns positional args as a strv. * If "--" was found, it has been moved before state->positional_offset. * The array is only valid, i.e. clean without any options, after parsing * has naturally finished. */ assert(state->optind > 0); - assert(state->optind == argc || state->parsing_stopped); - assert(state->positional_offset <= argc); + assert(state->optind == state->argc || state->parsing_stopped); + assert(state->positional_offset <= state->argc); - return argv + state->positional_offset; + return state->argv + state->positional_offset; } -size_t option_parser_get_n_args(const OptionParser *state, int argc) { +size_t option_parser_get_n_args(const OptionParser *state) { assert(state->optind > 0); - assert(state->optind == argc || state->parsing_stopped); - assert(state->positional_offset <= argc); + assert(state->optind == state->argc || state->parsing_stopped); + assert(state->positional_offset <= state->argc); - return argc - state->positional_offset; + return state->argc - state->positional_offset; } int _option_parser_get_help_table( diff --git a/src/shared/options.h b/src/shared/options.h index 4895c10cbd014..b86a728794d9d 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -68,6 +68,10 @@ extern const Option __start_SYSTEMD_OPTIONS[]; extern const Option __stop_SYSTEMD_OPTIONS[]; typedef struct OptionParser { + /* Those two should stay first so that it's possible to initialize the struct as { argc, argv }. */ + int argc; /* The original argc. */ + char **argv; /* The argv array, possibly reordered. */ + int optind; /* Position of the parameter being handled. * 0 → option parsing hasn't been started yet. */ int short_option_offset; /* Set when we're parsing an argument with one or more short options. @@ -81,23 +85,22 @@ int option_parse( const Option options[], const Option options_end[], OptionParser *state, - int argc, char *argv[], const Option **ret_option, const char **ret_arg); /* Iterate over options. */ -#define FOREACH_OPTION_FULL(parser, opt, argc, argv, ret_o, ret_a, on_error) \ - for (int opt; (opt = option_parse(ALIGN_PTR(__start_SYSTEMD_OPTIONS), __stop_SYSTEMD_OPTIONS, parser, argc, argv, ret_o, ret_a)) != 0; ) \ +#define FOREACH_OPTION_FULL(parser, opt, ret_o, ret_a, on_error) \ + for (int opt; (opt = option_parse(ALIGN_PTR(__start_SYSTEMD_OPTIONS), __stop_SYSTEMD_OPTIONS, parser, ret_o, ret_a)) != 0; ) \ if (opt < 0) { \ on_error; \ break; \ } else -#define FOREACH_OPTION(parser, opt, argc, argv, ret_a, on_error) \ - FOREACH_OPTION_FULL(parser, opt, argc, argv, /* ret_o= */ NULL, ret_a, on_error) +#define FOREACH_OPTION(parser, opt, ret_a, on_error) \ + FOREACH_OPTION_FULL(parser, opt, /* ret_o= */ NULL, ret_a, on_error) -char** option_parser_get_args(const OptionParser *state, int argc, char *argv[]); -size_t option_parser_get_n_args(const OptionParser *state, int argc); +char** option_parser_get_args(const OptionParser *state); +size_t option_parser_get_n_args(const OptionParser *state); int _option_parser_get_help_table( const Option options[], diff --git a/src/test/test-options.c b/src/test/test-options.c index e849365a2f2ee..fb1f61f358d02 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -31,10 +31,10 @@ static void test_option_parse_one( for (const Entry *e = entries; e && (e->long_code || e->short_code != 0); e++) n_entries++; - OptionParser state = {}; + OptionParser state = { argc, argv }; const Option *opt; const char *arg; - for (int c; (c = option_parse(options, options + n_options, &state, argc, argv, &opt, &arg)) != 0; ) { + for (int c; (c = option_parse(options, options + n_options, &state, &opt, &arg)) != 0; ) { ASSERT_OK(c); ASSERT_NOT_NULL(opt); @@ -54,11 +54,11 @@ static void test_option_parse_one( ASSERT_EQ(i, n_entries); - char **args = option_parser_get_args(&state, argc, argv); + char **args = option_parser_get_args(&state); ASSERT_TRUE(strv_equal(args, remaining)); ASSERT_STREQ(argv[0], saved_argv0); - ASSERT_EQ(option_parser_get_n_args(&state, argc), strv_length(remaining)); + ASSERT_EQ(option_parser_get_n_args(&state), strv_length(remaining)); } static void test_option_invalid_one( @@ -77,11 +77,11 @@ static void test_option_invalid_one( for (const Option *o = options; o->short_code != 0 || o->long_code; o++) n_options++; - OptionParser state = {}; + OptionParser state = { argc, argv }; const Option *opt; const char *arg; - int c = option_parse(options, options + n_options, &state, argc, argv, &opt, &arg); + int c = option_parse(options, options + n_options, &state, &opt, &arg); ASSERT_ERROR(c, EINVAL); } @@ -691,11 +691,11 @@ static void test_macros_parse_one( for (const Entry *e = entries; e && (e->long_code || e->short_code != 0); e++) n_entries++; - OptionParser state = {}; + OptionParser state = { argc, argv }; const Option *opt; const char *arg; - FOREACH_OPTION_FULL(&state, c, argc, argv, &opt, &arg, ASSERT_TRUE(false)) { + FOREACH_OPTION_FULL(&state, c, &opt, &arg, ASSERT_TRUE(false)) { log_debug("%c %s: %s=%s", opt->short_code != 0 ? opt->short_code : ' ', opt->long_code ?: "", @@ -751,7 +751,7 @@ static void test_macros_parse_one( ASSERT_EQ(i, n_entries); - char **args = option_parser_get_args(&state, argc, argv); + char **args = option_parser_get_args(&state); ASSERT_TRUE(strv_equal(args, remaining)); ASSERT_STREQ(argv[0], saved_argv0); } diff --git a/src/update-done/update-done.c b/src/update-done/update-done.c index 7fb1c58d19bac..b3c45c352b98d 100644 --- a/src/update-done/update-done.c +++ b/src/update-done/update-done.c @@ -94,10 +94,10 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = {}; + OptionParser state = { argc, argv }; const char *arg; - FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -112,7 +112,7 @@ static int parse_argv(int argc, char *argv[]) { break; } - if (option_parser_get_n_args(&state, argc) > 0) + if (option_parser_get_n_args(&state) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); return 1; diff --git a/src/validatefs/validatefs.c b/src/validatefs/validatefs.c index c507e4d98a472..9645fd187fe50 100644 --- a/src/validatefs/validatefs.c +++ b/src/validatefs/validatefs.c @@ -62,10 +62,10 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = {}; + OptionParser state = { argc, argv }; const char *arg; - FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: return help(); @@ -88,7 +88,7 @@ static int parse_argv(int argc, char *argv[]) { break; } - char **args = option_parser_get_args(&state, argc, argv); + char **args = option_parser_get_args(&state); if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), From b59daaa4cf05f9354c20eb8e6ad6ed13c0cede3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 25 Mar 2026 13:57:54 +0100 Subject: [PATCH 0497/1296] shared/verbs: introduce verb groups This mirrors the idea and implementation for options. Previously, the Verb struct was named as verb_func_data_nn, but with the group marker entry, we don't have 'verb_func', so let's just call the item verb_data_nn. Using the verb here is a complication that is not needed. --- src/fundamental/macro-fundamental.h | 1 - src/shared/options.c | 2 +- src/shared/verbs.c | 37 ++++++++++++++++++++++--- src/shared/verbs.h | 31 +++++++++++++++------ src/test/test-verbs.c | 42 ++++++++++++++++++----------- 5 files changed, 83 insertions(+), 30 deletions(-) diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro-fundamental.h index 1941e88d3760e..39004183d90f2 100644 --- a/src/fundamental/macro-fundamental.h +++ b/src/fundamental/macro-fundamental.h @@ -143,7 +143,6 @@ #define XCONCATENATE(x, y) x ## y #define CONCATENATE(x, y) XCONCATENATE(x, y) -#define CONCATENATE3(x, y, z) CONCATENATE(x, CONCATENATE(y, z)) #define assert_cc(expr) _Static_assert(expr, #expr) diff --git a/src/shared/options.c b/src/shared/options.c index 86f274821eda8..c5118f33426c3 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -266,7 +266,7 @@ int _option_parser_get_help_table( bool in_group = group == NULL; /* Are we currently in the section on the array that forms * group ? The first part is the default group, so - * the group was not specified, we are in. */ + * if the group was not specified, we are in. */ for (const Option *opt = options; opt < options_end; opt++) { bool group_marker = FLAGS_SET(opt->flags, OPTION_GROUP_MARKER); diff --git a/src/shared/verbs.c b/src/shared/verbs.c index 47f219fab6fcc..dfecf048612b7 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -58,12 +58,21 @@ bool should_bypass(const char *env_prefix) { return true; } +static bool verb_is_metadata(const Verb *verb) { + /* A metadata entry that is not a real verb, like the group marker */ + return FLAGS_SET(ASSERT_PTR(verb)->flags, VERB_GROUP_MARKER); +} + const Verb* verbs_find_verb(const char *name, const Verb verbs[], const Verb verbs_end[]) { assert(verbs); - for (const Verb *verb = verbs; verb < verbs_end; verb++) + for (const Verb *verb = verbs; verb < verbs_end; verb++) { + if (verb_is_metadata(verb)) + continue; + if (name ? streq(name, verb->verb) : FLAGS_SET(verb->flags, VERB_DEFAULT)) return verb; + } /* At the end of the list? */ return NULL; @@ -85,6 +94,9 @@ int _dispatch_verb_with_args(char **args, const Verb verbs[], const Verb verbs_e _cleanup_strv_free_ char **verb_strv = NULL; for (verb = verbs; verb < verbs_end; verb++) { + if (verb_is_metadata(verb)) + continue; + r = strv_extend(&verb_strv, verb->verb); if (r < 0) return log_oom(); @@ -142,13 +154,17 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { assert(argc >= optind); size_t n = 0; - while (verbs[n].dispatch) + while (verbs[n].verb) n++; return _dispatch_verb_with_args(strv_skip(argv, optind), verbs, verbs + n, userdata); } -int _verbs_get_help_table(const Verb verbs[], const Verb verbs_end[], Table **ret) { +int _verbs_get_help_table( + const Verb verbs[], + const Verb verbs_end[], + const char *group, + Table **ret) { int r; assert(ret); @@ -157,10 +173,23 @@ int _verbs_get_help_table(const Verb verbs[], const Verb verbs_end[], Table **re if (!table) return log_oom(); + bool in_group = group == NULL; /* Are we currently in the section on the array that forms + * group ? The first part is the default group, so + * if the group was not specified, we are in. */ + for (const Verb *verb = verbs; verb < verbs_end; verb++) { - assert(verb->dispatch); + assert(verb->verb); + + bool group_marker = FLAGS_SET(verb->flags, VERB_GROUP_MARKER); + if (!in_group) { + in_group = group_marker && streq(group, verb->verb); + continue; + } + if (group_marker) + break; /* End of group */ if (!verb->help) + /* No help string — we do not show the verb */ continue; /* We indent the option string by two spaces. We could set the minimum cell width and diff --git a/src/shared/verbs.h b/src/shared/verbs.h index 7980db62fe913..6b380041b81e5 100644 --- a/src/shared/verbs.h +++ b/src/shared/verbs.h @@ -8,6 +8,7 @@ typedef enum VerbFlags { VERB_DEFAULT = 1 << 0, /* The verb to run if no verb is specified */ VERB_ONLINE_ONLY = 1 << 1, /* Just do nothing when running in chroot or offline */ + VERB_GROUP_MARKER = 1 << 2, /* Fake verb entry to separate groups */ } VerbFlags; typedef struct { @@ -20,16 +21,13 @@ typedef struct { const char *help; } Verb; -#define VERB_FULL(d, v, a, amin, amax, f, dat, h) \ - DISABLE_WARNING_REDUNDANT_DECLS \ - static int d(int, char**, uintptr_t, void*); \ - REENABLE_WARNING \ +#define _VERB_DATA(d, v, a, amin, amax, f, dat, h) \ _section_("SYSTEMD_VERBS") \ _alignptr_ \ _used_ \ _retain_ \ _variable_no_sanitize_address_ \ - static const Verb CONCATENATE3(d, _data_, __COUNTER__) = { \ + static const Verb CONCATENATE(verb_data_, __COUNTER__) = { \ .verb = v, \ .min_args = amin, \ .max_args = amax, \ @@ -40,6 +38,12 @@ typedef struct { .help = h, \ } +#define VERB_FULL(d, v, a, amin, amax, f, dat, h) \ + DISABLE_WARNING_REDUNDANT_DECLS \ + static int d(int, char**, uintptr_t, void*); \ + REENABLE_WARNING \ + _VERB_DATA(d, v, a, amin, amax, f, dat, h) + /* The same as VERB_FULL, but without the data argument */ #define VERB(d, v, a, amin, amax, f, h) \ VERB_FULL(d, v, a, amin, amax, f, /* dat= */ 0, h) @@ -48,6 +52,11 @@ typedef struct { #define VERB_NOARG(d, v, h) \ VERB(d, v, /* a= */ NULL, /* amin= */ VERB_ANY, /* amax= */ 1, /* f= */ 0, h) +/* Magic entry in the table (which will not be returned) that designates the start of the group . */ +#define VERB_GROUP(gr) \ + _VERB_DATA(/* d= */ NULL, /* v= */ gr, /* a= */ NULL, /* amin= */ 0, /* amax= */ 0, \ + /* f= */ VERB_GROUP_MARKER, /* dat= */ 0, /* h= */ NULL) + /* This is magically mapped to the beginning and end of the section */ extern const Verb __start_SYSTEMD_VERBS[]; extern const Verb __stop_SYSTEMD_VERBS[]; @@ -64,9 +73,15 @@ int _dispatch_verb_with_args(char **args, const Verb verbs[], const Verb verbs_e int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata); -int _verbs_get_help_table(const Verb verbs[], const Verb verbs_end[], Table **ret); -#define verbs_get_help_table(ret) \ - _verbs_get_help_table(ALIGN_PTR(__start_SYSTEMD_VERBS), __stop_SYSTEMD_VERBS, ret) +int _verbs_get_help_table( + const Verb verbs[], + const Verb verbs_end[], + const char *group, + Table **ret); +#define verbs_get_help_table_group(group, ret) \ + _verbs_get_help_table(ALIGN_PTR(__start_SYSTEMD_VERBS), __stop_SYSTEMD_VERBS, group, ret) +#define verbs_get_help_table(ret) \ + verbs_get_help_table_group(/* group= */ NULL, ret) #define _VERB_COMMON_HELP_IMPL(impl) \ static int verb_help(int argc, char **argv, uintptr_t data, void *userdata) { \ diff --git a/src/test/test-verbs.c b/src/test/test-verbs.c index 79c5c27dd94f3..41ae5a87f3766 100644 --- a/src/test/test-verbs.c +++ b/src/test/test-verbs.c @@ -16,14 +16,16 @@ static int noop_dispatcher(int argc, char *argv[], uintptr_t _data, void *userda TEST(verbs) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, - { "list-images", VERB_ANY, 1, 0, noop_dispatcher }, - { "list", VERB_ANY, 2, VERB_DEFAULT, noop_dispatcher }, - { "status", 2, VERB_ANY, 0, noop_dispatcher }, - { "show", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, - { "terminate", 2, VERB_ANY, 0, noop_dispatcher }, - { "login", 2, 2, 0, noop_dispatcher }, - { "copy-to", 3, 4, 0, noop_dispatcher }, + { "help", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, + { "list-images", VERB_ANY, 1, 0, noop_dispatcher }, + { "list", VERB_ANY, 2, VERB_DEFAULT, noop_dispatcher }, + { "status", 2, VERB_ANY, 0, noop_dispatcher }, + { "Group2", VERB_ANY, VERB_ANY, VERB_GROUP_MARKER, NULL }, + { "show", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, + { "terminate", 2, VERB_ANY, 0, noop_dispatcher }, + { "Group3", 0, 0, VERB_GROUP_MARKER, NULL }, + { "login", 2, 2, 0, noop_dispatcher }, + { "copy-to", 3, 4, 0, noop_dispatcher }, {} }; @@ -44,6 +46,12 @@ TEST(verbs) { /* no verb, but a default is set */ test_dispatch_one(STRV_EMPTY, verbs, 0); + + /* the group entry shall not be found */ + test_dispatch_one(STRV_MAKE("Group2"), verbs, -EINVAL); + + /* the group entry shall not be found */ + test_dispatch_one(STRV_MAKE("Group3"), verbs, -EINVAL); } TEST(verbs_no_default) { @@ -60,14 +68,15 @@ TEST(verbs_no_default) { TEST(verbs_no_default_many) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, - { "list-images", VERB_ANY, 1, 0, noop_dispatcher }, - { "list", VERB_ANY, 2, 0, noop_dispatcher }, - { "status", 2, VERB_ANY, 0, noop_dispatcher }, - { "show", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, - { "terminate", 2, VERB_ANY, 0, noop_dispatcher }, - { "login", 2, 2, 0, noop_dispatcher }, - { "copy-to", 3, 4, 0, noop_dispatcher }, + { "help", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, + { "list-images", VERB_ANY, 1, 0, noop_dispatcher }, + { "list", VERB_ANY, 2, 0, noop_dispatcher }, + { "status", 2, VERB_ANY, 0, noop_dispatcher }, + { "Specials", VERB_ANY, VERB_ANY, VERB_GROUP_MARKER, NULL }, + { "show", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, + { "terminate", 2, VERB_ANY, 0, noop_dispatcher }, + { "login", 2, 2, 0, noop_dispatcher }, + { "copy-to", 3, 4, 0, noop_dispatcher }, {} }; @@ -75,6 +84,7 @@ TEST(verbs_no_default_many) { test_dispatch_one(STRV_MAKE("hel"), verbs, -EINVAL); test_dispatch_one(STRV_MAKE("helpp"), verbs, -EINVAL); test_dispatch_one(STRV_MAKE("hgrejgoraoiosafso"), verbs, -EINVAL); + test_dispatch_one(STRV_MAKE("Specials"), verbs, -EINVAL); } DEFINE_TEST_MAIN(LOG_INFO); From 1e1143aba06f4a520561509845cfd3d5ca5866d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 24 Mar 2026 09:49:24 +0100 Subject: [PATCH 0498/1296] options: add common option macros for --no-ask-password, --host, --machine Co-developed-by: Claude --- src/shared/options.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/shared/options.h b/src/shared/options.h index b86a728794d9d..6baec72b4feaf 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -60,6 +60,12 @@ typedef struct Option { OPTION_LONG("cat-config", NULL, "Show configuration files") #define OPTION_COMMON_TLDR \ OPTION_LONG("tldr", NULL, "Show non-comment parts of configuration") +#define OPTION_COMMON_NO_ASK_PASSWORD \ + OPTION_LONG("no-ask-password", NULL, "Do not prompt for password") +#define OPTION_COMMON_HOST \ + OPTION('H', "host", "[USER@]HOST", "Operate on remote host") +#define OPTION_COMMON_MACHINE \ + OPTION('M', "machine", "CONTAINER", "Operate on local container") #define OPTION_COMMON_JSON \ OPTION_LONG("json", "FORMAT", "Generate JSON output (pretty, short, or off)") From 5e4003cd87817211a35fe6fadf79af7b7a3092da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 25 Mar 2026 14:49:55 +0100 Subject: [PATCH 0499/1296] shared/table-format: generalize table_sync_column_width to more columns The column index is moved to the first position. I think we're unlikely to want to synchronize widths of *different* columns, and having just one column argument makes the callers simpler. Also, the type is changed to size_t to match other functions, and this avoids the need to cast to size_t in the callers. --- src/bless-boot/bless-boot.c | 2 +- src/dissect/dissect.c | 2 +- src/id128/id128.c | 2 +- src/shared/format-table.c | 31 ++++++++++++++++++++----------- src/shared/format-table.h | 3 ++- 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/bless-boot/bless-boot.c b/src/bless-boot/bless-boot.c index bf1c6e7a0cbc3..b82be92dbdf05 100644 --- a/src/bless-boot/bless-boot.c +++ b/src/bless-boot/bless-boot.c @@ -50,7 +50,7 @@ static int help(void) { if (r < 0) return r; - (void) table_sync_column_width(options, 0, verbs, 0); + (void) table_sync_column_widths(0, options, verbs); printf("%s [OPTIONS...] COMMAND\n" "\n%sMark the boot process as good or bad.%s\n" diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 3597971af9b03..bcfd7f5a816e1 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -141,7 +141,7 @@ static int help(void) { return r; /* Make the 1st column same width in both tables */ - (void) table_sync_column_width(options, 0, commands, 0); + (void) table_sync_column_widths(0, options, commands); printf("%1$s [OPTIONS...] IMAGE\n" "%1$s [OPTIONS...] --mount IMAGE PATH\n" diff --git a/src/id128/id128.c b/src/id128/id128.c index 688504c71480e..fe4d2f2283644 100644 --- a/src/id128/id128.c +++ b/src/id128/id128.c @@ -208,7 +208,7 @@ static int help(void) { return r; /* Make the 1st column same width in both tables */ - (void) table_sync_column_width(options, 0, verbs, 0); + (void) table_sync_column_widths(0, options, verbs); printf("%s [OPTIONS...] COMMAND\n\n" "%sGenerate and print 128-bit identifiers.%s\n" diff --git a/src/shared/format-table.c b/src/shared/format-table.c index 279e7fda68e7f..04552b5b56776 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -2185,26 +2185,35 @@ int table_set_column_width(Table *t, size_t column, size_t width) { return r; } -int table_sync_column_width(Table *a, size_t column_a, Table *b, size_t column_b) { - size_t w1, w2; - int r; +int _table_sync_column_widths(size_t column, Table *a, ...) { + size_t max = 0; + va_list ap; + int r = 0; assert(a); - assert(b); - /* Make both tables have specified columns of same width */ + /* Make the specified column have the same width in the tables. */ - r = table_data_requested_width(a, column_a, &w1); - if (r < 0) - return log_error_errno(r, "Failed to query table column width: %m"); + va_start(ap, a); + for (Table *t = a; t; t = va_arg(ap, Table*)) { + size_t w; - r = table_data_requested_width(b, column_b, &w2); + r = table_data_requested_width(t, column, &w); + if (r < 0) + break; + + max = MAX(max, w); + } + va_end(ap); if (r < 0) return log_error_errno(r, "Failed to query table column width: %m"); r = 0; - RET_GATHER(r, table_set_column_width(a, column_a, MAX(w1, w2))); - RET_GATHER(r, table_set_column_width(b, column_b, MAX(w1, w2))); + va_start(ap, a); + for (Table *t = a; t; t = va_arg(ap, Table*)) + RET_GATHER(r, table_set_column_width(t, column, max)); + va_end(ap); + return r; } diff --git a/src/shared/format-table.h b/src/shared/format-table.h index 9a11fb7c30cef..bb5a68b7e9fa3 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -144,7 +144,8 @@ int table_hide_column_from_display_internal(Table *t, ...); int table_data_requested_width(Table *table, size_t column, size_t *ret); int table_set_column_width(Table *t, size_t column, size_t width); -int table_sync_column_width(Table *a, size_t column_a, Table *b, size_t column_b); +int _table_sync_column_widths(size_t column, Table *a, ...); +#define table_sync_column_widths(column, a, ...) _table_sync_column_widths(column, a, __VA_ARGS__, NULL) int table_print(Table *t, FILE *f); int table_format(Table *t, char **ret); From f3eac272e6ce8671bb6ab71b46a89d5afb916628 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 4 Mar 2026 15:07:09 +0100 Subject: [PATCH 0500/1296] hwdb: add database for basic IMDS properties This adds a hardware database that contains information about IDMS functionality of various clouds, keyed off the SMBIOS identification of each. Currently this contains information about 6 major clouds, but the idea is that this grows to include more and more major clouds. Nothing uses this data yet, that's added in a later commit. --- hwdb.d/40-imds.hwdb | 105 +++++++++++++++++++++++++++++++++++++++++++ hwdb.d/meson.build | 1 + hwdb.d/parse_hwdb.py | 20 ++++++++- 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 hwdb.d/40-imds.hwdb diff --git a/hwdb.d/40-imds.hwdb b/hwdb.d/40-imds.hwdb new file mode 100644 index 0000000000000..397a32b42fd31 --- /dev/null +++ b/hwdb.d/40-imds.hwdb @@ -0,0 +1,105 @@ +# This file is part of systemd + +# This provides various properties that declare if and how IMDS is available on +# the local system, i.e. we are running in a major cloud service that provides +# something resembling AWS' or Azure's Instance Metadata Service. +# +# General IMDS endpoint data: +# IMDS_VENDOR= → Indicates IMDS is available, and which vendor it is +# IMDS_TOKEN_URL= → The URL to request an API token from. If not set, no API token is requested. +# IMDS_REFRESH_HEADER_NAME= → The HTTP request header field (everything before the ":") that contains the refresh TTL (in seconds) when requesting a token. +# IMDS_DATA_URL= → The base URL to request actual IMDS data fields from +# IMDS_DATA_URL_SUFFIX= → Parameters to suffix the URLs with +# IMDS_TOKEN_HEADER_NAME= → The HTTP request header field (everything before the ":") used to pass the token +# IMDS_EXTRA_HEADER=, IMDS_EXTRA_HEADER2=, IMDS_EXTRA_HEADER3=, … +# → Additional HTTP headers to pass when requesting a data field (full header, including ":") +# IMDS_ADDRESS_IPV4= → IPv4 address of the IMDS server +# IMDS_ADDRESS_IPV6= → IPv6 address of the IMDS server +# +# Well-known IMDS keys: +# IMDS_KEY_HOSTNAME= → IMDS key for the hostname +# IMDS_KEY_REGION= → IMDS key for the region, if that concept applies +# IMDS_KEY_ZONE= → IMDS key for the zone, if that concept applies +# IMDS_KEY_IPV4_PUBLIC= → IMDS key for the primary public IPv4 address if there is any +# IMDS_KEY_IPV6_PUBLIC= → IMDS key for the primary public IPv6 address if there is any +# IMDS_KEY_SSH_KEY= → IMDS key for an SSH public key to install in the root account +# IMDS_KEY_USERDATA= → IMDS key for arbitrary userdata (if there's only one) +# IMDS_KEY_USERDATA_BASE= → IMDS key for arbitrary userdata (if there are multiple, this is the common prefix) +# IMDS_KEY_USERDATA_BASE64= → IMDS key for arbitrary userdata (if there's only one, but it is base64 encoded) + +# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html +dmi:bvnAmazonEC2:* + IMDS_VENDOR=amazon-ec2 + IMDS_TOKEN_URL=http://169.254.169.254/latest/api/token + IMDS_REFRESH_HEADER_NAME=X-aws-ec2-metadata-token-ttl-seconds + IMDS_DATA_URL=http://169.254.169.254/latest + IMDS_TOKEN_HEADER_NAME=X-aws-ec2-metadata-token + IMDS_ADDRESS_IPV4=169.254.169.254 + IMDS_ADDRESS_IPV6=fd00:ec2::254 + IMDS_KEY_HOSTNAME=/meta-data/hostname + IMDS_KEY_REGION=/meta-data/placement/region + IMDS_KEY_ZONE=/meta-data/placement/availability-zone + IMDS_KEY_IPV4_PUBLIC=/meta-data/public-ipv4 + IMDS_KEY_IPV6_PUBLIC=/meta-data/ipv6 + IMDS_KEY_SSH_KEY=/meta-data/public-keys/0/openssh-key + IMDS_KEY_USERDATA=/user-data + +# https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service#instance-metadata +dmi:*:cat7783-7084-3265-9085-8269-3286-77:* + IMDS_VENDOR=microsoft-azure + IMDS_DATA_URL=http://169.254.169.254/metadata + IMDS_DATA_URL_SUFFIX=?api-version=2025-04-07&format=text + IMDS_EXTRA_HEADER=Metadata: true + IMDS_ADDRESS_IPV4=169.254.169.254 + IMDS_KEY_HOSTNAME=/instance/compute/osProfile/computerName + IMDS_KEY_REGION=/instance/compute/location + IMDS_KEY_ZONE=/instance/compute/physicalZone + IMDS_KEY_IPV4_PUBLIC=/instance/network/interface/0/ipv4/ipAddress/0/publicIpAddress + IMDS_KEY_IPV6_PUBLIC=/instance/network/interface/0/ipv6/ipAddress/0/publicIpAddress + IMDS_KEY_SSH_KEY=/instance/compute/publicKeys/0/keyData + IMDS_KEY_USERDATA_BASE64=/instance/compute/userData + +# https://docs.cloud.google.com/compute/docs/metadata/predefined-metadata-keys +dmi:*:pnGoogleComputeEngine:* + IMDS_VENDOR=google-gcp + IMDS_DATA_URL=http://169.254.169.254/computeMetadata/v1 + IMDS_EXTRA_HEADER=Metadata-Flavor: Google + IMDS_ADDRESS_IPV4=169.254.169.254 + IMDS_KEY_HOSTNAME=/instance/hostname + IMDS_KEY_REGION=/instance/region + IMDS_KEY_ZONE=/instance/zone + IMDS_KEY_IPV4_PUBLIC=/instance/network-interfaces/0/access-configs/0/external-ip + IMDS_KEY_USERDATA_BASE=/instance/attributes + +# https://docs.hetzner.cloud/reference/cloud#description/server-metadata +dmi:bvnHetzner:* + IMDS_VENDOR=hetzner-cloud + IMDS_DATA_URL=http://169.254.169.254/hetzner/v1/metadata + IMDS_ADDRESS_IPV4=169.254.169.254 + IMDS_KEY_HOSTNAME=/hostname + IMDS_KEY_REGION=/region + IMDS_KEY_ZONE=/availability-zone + IMDS_KEY_IPV4_PUBLIC=/public-ipv4 + IMDS_KEY_SSH_KEY=/public-keys/0 + IMDS_KEY_USERDATA=/userdata + +# https://docs.oracle.com/en-us/iaas/Content/Compute/Tasks/gettingmetadata.htm#metadata-keys +dmi:*:catOracleCloud.com:* + IMDS_VENDOR=oracle-cloud-oci + IMDS_DATA_URL=http://169.254.169.254/opc/v2 + IMDS_ADDRESS_IPV4=169.254.169.254 + IMDS_ADDRESS_IPV6=fd00:c1::a9fe:a9fe + IMDS_EXTRA_HEADER=Authorization: Bearer Oracle + IMDS_KEY_HOSTNAME=/instance/hostname + IMDS_KEY_REGION=/instance/region + IMDS_KEY_ZONE=/instance/availabilityDomain + IMDS_KEY_SSH_KEY=/instance/metadata/ssh_authorized_keys + IMDS_KEY_USERDATA_BASE64=/metadata/user_data + +# https://www.scaleway.com/en/docs/instances/how-to/use-cloud-init/ +dmi:*:svnScaleway:* + IMDS_VENDOR=scaleway + IMDS_DATA_URL=http://169.254.42.42 + IMDS_ADDRESS_IPV4=169.254.42.42 + IMDS_ADDRESS_IPV6=fd00:42::42 + IMDS_KEY_USERDATA=/user_data diff --git a/hwdb.d/meson.build b/hwdb.d/meson.build index 9ba73b21d6393..3299eaf8a75bf 100644 --- a/hwdb.d/meson.build +++ b/hwdb.d/meson.build @@ -19,6 +19,7 @@ hwdb_files_notest = files( hwdb_files_test = files( '20-dmi-id.hwdb', '20-net-ifname.hwdb', + '40-imds.hwdb', '60-autosuspend.hwdb', '60-autosuspend-fingerprint-reader.hwdb', '60-evdev.hwdb', diff --git a/hwdb.d/parse_hwdb.py b/hwdb.d/parse_hwdb.py index e98510839b73f..e70b0ff04e94e 100755 --- a/hwdb.d/parse_hwdb.py +++ b/hwdb.d/parse_hwdb.py @@ -125,7 +125,7 @@ def hwdb_grammar(): matchline = (matchline_typed | matchline_general) + EOL propertyline = (White(' ', exact=1).suppress() + - Combine(UDEV_TAG - '=' - Optional(Word(alphanums + '_=:@*.!-;, "/')) + Combine(UDEV_TAG - '=' - Optional(Word(alphanums + '_=:@*.!-;, "/?&')) - Optional(pythonStyleComment)) + EOL) propertycomment = White(' ', exact=1) + pythonStyleComment + EOL @@ -215,6 +215,24 @@ def property_grammar(): ('ID_NET_NAME_FROM_DATABASE', name_literal), ('ID_NET_NAME_INCLUDE_DOMAIN', zero_one), ('TPM2_BROKEN_NVPCR', zero_one), + ('IMDS_VENDOR', name_literal), + ('IMDS_TOKEN_URL', name_literal), + ('IMDS_REFRESH_HEADER_NAME', name_literal), + ('IMDS_DATA_URL', name_literal), + ('IMDS_DATA_URL_SUFFIX', name_literal), + ('IMDS_TOKEN_HEADER_NAME', name_literal), + ('IMDS_EXTRA_HEADER', name_literal), + ('IMDS_ADDRESS_IPV4', name_literal), + ('IMDS_ADDRESS_IPV6', name_literal), + ('IMDS_KEY_HOSTNAME', name_literal), + ('IMDS_KEY_REGION', name_literal), + ('IMDS_KEY_ZONE', name_literal), + ('IMDS_KEY_IPV4_PUBLIC', name_literal), + ('IMDS_KEY_IPV6_PUBLIC', name_literal), + ('IMDS_KEY_SSH_KEY', name_literal), + ('IMDS_KEY_USERDATA', name_literal), + ('IMDS_KEY_USERDATA_BASE', name_literal), + ('IMDS_KEY_USERDATA_BASE64', name_literal), ) fixed_props = [Literal(name)('NAME') - Suppress('=') - val('VALUE') for name, val in props] From eb6e5b07f13cefddf1f49e1f7bda4af22f5aba17 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 4 Mar 2026 15:10:37 +0100 Subject: [PATCH 0501/1296] imds: add new systemd-imdsd.service that makes IMDS data accessible locally This service's job is to talk to a VM associated IMDS service provided by the local Cloud. It tries to abstract the protocol differences various IMDS implementations implement, but does *not* really try to abstract more than a few basic fields of the actual IMDS metadata. IMDS access is wrapped in a Varlink API that local clients can talk to. If possible this makes use of the IMDS endpoint information that has been added to hwdb in the preceeding commit. However, endpoint info can also be provided via kernel command line and credentials. For debugging purposes we also accept them via environment variables and command line arguments. This adds a concept of early-boot networking, just enough to be able to talk to the IMDS service. It is minimally configurable via a kernel cmdline option (and a build-time option): the user may choose between "locked" and "unlocked" mode. In the former mode direct access to IMDS via HTTPS is blocked via a prohibit route (and thus all IMDS communication has to be done via systemd-imdsd@.service). In the latter case no such lockdown takes place, and IMDS may be acquired both via this new service and directly. The latter is typically a good idea for compatibility with current systems, the former is preferable for secure installations. Access to IMDS fields is controlled via PK. --- man/kernel-command-line.xml | 13 + man/rules/meson.build | 6 + man/systemd-imdsd@.service.xml | 269 ++ man/systemd.system-credentials.xml | 10 + meson.build | 8 + meson_options.txt | 4 + src/imds/imds-util.c | 50 + src/imds/imds-util.h | 38 + src/imds/imdsd.c | 3163 +++++++++++++++++ src/imds/io.systemd.imds.policy | 30 + src/imds/meson.build | 21 + src/import/meson.build | 5 +- src/shared/meson.build | 1 + .../varlink-io.systemd.InstanceMetadata.c | 103 + .../varlink-io.systemd.InstanceMetadata.h | 6 + src/test/test-varlink-idl.c | 2 + sysusers.d/meson.build | 3 +- sysusers.d/systemd-imds.conf.in | 8 + units/meson.build | 12 + units/systemd-imds-early-network.service.in | 23 + units/systemd-imdsd.socket | 28 + units/systemd-imdsd@.service.in | 28 + 22 files changed, 3828 insertions(+), 3 deletions(-) create mode 100644 man/systemd-imdsd@.service.xml create mode 100644 src/imds/imds-util.c create mode 100644 src/imds/imds-util.h create mode 100644 src/imds/imdsd.c create mode 100644 src/imds/io.systemd.imds.policy create mode 100644 src/imds/meson.build create mode 100644 src/shared/varlink-io.systemd.InstanceMetadata.c create mode 100644 src/shared/varlink-io.systemd.InstanceMetadata.h create mode 100644 sysusers.d/systemd-imds.conf.in create mode 100644 units/systemd-imds-early-network.service.in create mode 100644 units/systemd-imdsd.socket create mode 100644 units/systemd-imdsd@.service.in diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 4da1796a97ca2..98673e0a51674 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -793,6 +793,19 @@ + + systemd.imds= + systemd.imds.*= + + Controls various Instance Metadata Service (IMDS) cloud aspects, see + systemd-imdsd@.service8 + and + systemd-imds-generator8 + for details. + + + + diff --git a/man/rules/meson.build b/man/rules/meson.build index 682f55c774dd6..60fefdfb11cde 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -1045,6 +1045,12 @@ manpages = [ ['systemd-hostnamed.service', '8', ['systemd-hostnamed'], 'ENABLE_HOSTNAMED'], ['systemd-hwdb', '8', [], 'ENABLE_HWDB'], ['systemd-id128', '1', [], ''], + ['systemd-imdsd@.service', + '8', + ['systemd-imdsd', + 'systemd-imdsd-early-network.service', + 'systemd-imdsd.socket'], + 'ENABLE_IMDS'], ['systemd-import-generator', '8', [], ''], ['systemd-importd.service', '8', ['systemd-importd'], 'ENABLE_IMPORTD'], ['systemd-inhibit', '1', [], ''], diff --git a/man/systemd-imdsd@.service.xml b/man/systemd-imdsd@.service.xml new file mode 100644 index 0000000000000..0d08a58120c38 --- /dev/null +++ b/man/systemd-imdsd@.service.xml @@ -0,0 +1,269 @@ + + + + + + + + systemd-imdsd@.service + systemd + + + + systemd-imdsd@.service + 8 + + + + systemd-imdsd@.service + systemd-imdsd + systemd-imdsd.socket + systemd-imdsd-early-network.service + Cloud IMDS (Instance Metadata Service) client + + + + systemd-imdsd@.service + systemd-imdsd.socket + systemd-imdsd-early-network.service + /usr/lib/systemd/systemd-imdsd + + + + Description + + systemd-imdsd@.service is a system service that provides local access to IMDS + (Instance Metadata Service; or equivalent) functionality, as provided by many public clouds. + + The service provides a Varlink IPC interface via + /run/systemd/io.systemd.InstanceMetadata to query IMDS fields. + + systemd-imdsd-early-network.service is a system service that generates a + systemd-networkd.service8 + compatible + systemd.network5 file + for configuring the early-boot network in order to be able to contact the IMDS endpoint. + + The + systemd-imds1 tool may + be used to query information from this service. + + + + + + Kernel Command Line Options + + The IMDS endpoint is typically determined automatically via + hwdb7 records, but can + also be configured explicitly via the kernel command line, via the following options: + + + + systemd.imds.network= + + Takes one of off, locked, + unlocked. Controls whether and how to set up networking for IMDS endpoint + access. Unless set to off early boot networking is enabled, ensuring that the + IMDS endpoint can be reached. If set to locked (the default) direct access to + the IMDS endpoint by regular unprivileged processes is disabled via a "prohibit" route, so that any + access must be done through systemd-imdsd@.service or its associated tools. If + set to unlocked this "prohibit" route is not created, and regular unprivileged + processes can directly contact IMDS. + + + + + + + systemd.imds.vendor= + + A short string identifying the cloud vendor. + + Example: systemd.imds.vendor=foobarcloud + + + + + + + systemd.imds.token_url= + + If a bearer token must be acquired to talk to the IMDS service, this is the URL to acquire it + from. + + + + + + + systemd.imds.refresh_header_name= + + Takes a HTTP header field name (excluding the :) that declares the header + field for passing the TTL value (in seconds) to the HTTP server when acquiring a token. Only + applies if systemd.imds.token_url= is set too. + + + + + + + systemd.imds.data_url= + + Takes the base URL to acquire the IMDS data from (the IMDS "endpoint"). All data fields are + acquired from below this URL. This URL should typically not end in /. + + The data URLs are concatenated from this base URL, the IMDS "key" and the suffix configured + via systemd.imds.data_url_suffix= below. Well-known IMDS "keys" can be + configured via the systemd.imds.key=* options below. + + Example: systemd.imds.data_url=http://169.254.169.254/metadata + + + + + + + systemd.imds.data_url_suffix= + + If specified, this field is appended to the end of the data URL (after appending the IMDS + "key" to the data base URL), see above. + + Example: systemd.imds.data_url_suffix=?api-version=2025-04-07&format=text + + + + + + + systemd.imds.token_header_name= + + Takes a HTTP header field name (excluding the :) that declares the header + field to pass the bearer token acquired from the token URL (see above) in. Only applies if + systemd.imds.token_url= is set too. + + + + + + + systemd.imds.extra_header= + + Takes a full HTTP header expression (both field name and value, separated by a colon + :) to pass to the HTTP server when requesting data. May be used multiple times + to set multiple headers. + + Example: systemd.imds.extra_header=Metadata:true + + + + + + + systemd.imds.address_ipv4= + + Configures the IPv4 address the IMDS endpoint is contacted on. This should typically be the + IP address also configured via systemd.imds.data_url= (if IPv4 is used) and is + used to set up IP routing. + + Example: systemd.imds.address_ipv4=169.254.169.254 + + + + + + + systemd.imds.address_ipv6= + + Configures the IPv6 address the IMDS endpoint is contacted on. This should typically be the + IP address also configured via systemd.imds.data_url= (if IPv6 is used) and is + used to set up IP routing. + + + + + + + systemd.imds.key.hostname= + systemd.imds.key.region= + systemd.imds.key.zone= + systemd.imds.key.ipv4_public= + systemd.imds.key.ipv6_public= + systemd.imds.key.ssh_key= + systemd.imds.key.userdata= + systemd.imds.key.userdata_base= + systemd.imds.key.userdata_base64= + + Configures strings to concatenate to the data base URL (see above) to acquire data for + various "well-known" fields. These strings must begin with a /. They should + return the relevant data in plain text. + + A special case are the three "userdata" keys: the option + systemd.imds.key.userdata_base= should be used if the IMDS service knows a + concept of multiple userdata fields, and a field identifier thus still needs to be appended to the + userdata base URL. The option systemd.imds.key.userdata= should be used if only + a single userdata field is supported. The option systemd.imds.key.userdata_base64= + should be used in the same case, but only if the userdata field is encoded in Base64. + + Example: systemd.imds.key.hostname=/instance/compute/osProfile/computerName + + + + + + + + + Credentials + + systemd-imdsd@.service supports the service credentials logic as implemented by + ImportCredential=/LoadCredential=/SetCredential= + (see systemd.exec5 for + details). The following credentials are used when passed in: + + + + imds.vendor + imds.vendor_token + imds.refresh_header_name + imds.data_url + imds.data_url_suffix + imds.token_header_name + imds.extra_header + imds.extra_header2 + imds.extra_header3 + imds.extra_header… + imds.address_ipv4 + imds.address_ipv6 + imds.key_hostname + imds.key_region + imds.key_zone + imds.key_ipv4_public + imds.key_ipv6_public + imds.key_ssh_key + imds.key_userdata + imds.key_userdata_base + imds.key_userdata_base64 + The various IMDS endpoint parameters. The semantics are very close to those configurable + via kernel command line, see above for the matching list. + + + + + + + + See Also + + systemd1 + systemd-imds1 + systemd-imds-generator8 + systemd-networkd.service8 + + + + diff --git a/man/systemd.system-credentials.xml b/man/systemd.system-credentials.xml index a302be236d40d..fb1377c560c75 100644 --- a/man/systemd.system-credentials.xml +++ b/man/systemd.system-credentials.xml @@ -580,6 +580,16 @@ + + + imds.* + + + Read by systemd-imdsd@.service8. + + + + diff --git a/meson.build b/meson.build index 925b1b13ed0e0..e35237e452be0 100644 --- a/meson.build +++ b/meson.build @@ -906,6 +906,7 @@ foreach option : ['adm-gid', 'video-gid', 'wheel-gid', 'systemd-journal-gid', + 'systemd-imds-uid', 'systemd-network-uid', 'systemd-resolve-uid', 'systemd-timesync-uid'] @@ -1539,6 +1540,11 @@ conf.set('DEFAULT_DNSSEC_MODE', 'DNSSEC_' + default_dnssec.underscorify().to_upper()) conf.set_quoted('DEFAULT_DNSSEC_MODE_STR', default_dnssec) +have = get_option('imds').require( + conf.get('HAVE_LIBCURL') == 1, + error_message : 'curl required').allowed() +conf.set10('ENABLE_IMDS', have) + have = get_option('importd').require( conf.get('HAVE_LIBCURL') == 1 and conf.get('HAVE_OPENSSL') == 1 and @@ -2375,6 +2381,7 @@ subdir('src/hostname') subdir('src/hwdb') subdir('src/id128') subdir('src/import') +subdir('src/imds') # Note, we are not alphabetically here, since we want to use a variable from src/import/ here subdir('src/integritysetup') subdir('src/journal') subdir('src/journal-remote') @@ -3145,6 +3152,7 @@ foreach tuple : [ ['homed'], ['hostnamed'], ['hwdb'], + ['imds'], ['importd'], ['initrd'], ['kernel-install'], diff --git a/meson_options.txt b/meson_options.txt index c1af7ce237492..7835f716662d9 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -142,6 +142,8 @@ option('timedated', type : 'boolean', description : 'install the systemd-timedated daemon') option('timesyncd', type : 'boolean', description : 'install the systemd-timesyncd daemon') +option('imds', type : 'feature', + description : 'install the systemd-imds stack') option('journal-storage-default', type : 'combo', choices : ['persistent', 'auto', 'volatile', 'none'], description : 'default storage mode for journald (main namespace)') option('remote', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' }, @@ -334,6 +336,8 @@ option('systemd-resolve-uid', type : 'integer', value : 0, description : 'soft-static allocation for the systemd-resolve user') option('systemd-timesync-uid', type : 'integer', value : 0, description : 'soft-static allocation for the systemd-timesync user') +option('systemd-imds-uid', type : 'integer', value : 0, + description : 'soft-static allocation for the systemd-imds user') option('dev-kvm-mode', type : 'string', value : '0666', description : '/dev/kvm access mode') diff --git a/src/imds/imds-util.c b/src/imds/imds-util.c new file mode 100644 index 0000000000000..3c67417e4ba5f --- /dev/null +++ b/src/imds/imds-util.c @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "imds-util.h" +#include "string-table.h" +#include "string-util.h" +#include "utf8.h" + +bool imds_key_is_valid(const char *key) { + /* Just some pretty superficial validation. */ + + if (!key) + return false; + + if (!startswith(key, "/")) + return false; + + if (!ascii_is_valid(key)) + return false; + + if (string_has_cc(key, /* ok= */ NULL)) + return false; + + return true; +} + +static const char* const imds_well_known_table[_IMDS_WELL_KNOWN_MAX] = { + [IMDS_BASE] = "base", + [IMDS_HOSTNAME] = "hostname", + [IMDS_REGION] = "region", + [IMDS_ZONE] = "zone", + [IMDS_IPV4_PUBLIC] = "ipv4-public", + [IMDS_IPV6_PUBLIC] = "ipv6-public", + [IMDS_SSH_KEY] = "ssh-key", + [IMDS_USERDATA] = "userdata", + [IMDS_USERDATA_BASE] = "userdata-base", + [IMDS_USERDATA_BASE64] = "userdata-base64", +}; + +DEFINE_STRING_TABLE_LOOKUP(imds_well_known, ImdsWellKnown); + + +static const char* const imds_network_mode_table[_IMDS_NETWORK_MODE_MAX] = { + [IMDS_NETWORK_OFF] = "off", + [IMDS_NETWORK_LOCKED] = "locked", + [IMDS_NETWORK_UNLOCKED] = "unlocked", +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(imds_network_mode, ImdsNetworkMode, IMDS_NETWORK_LOCKED); diff --git a/src/imds/imds-util.h b/src/imds/imds-util.h new file mode 100644 index 0000000000000..55ab79510f44e --- /dev/null +++ b/src/imds/imds-util.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "macro.h" +#include "string-table.h" /* IWYU pragma: keep */ + +typedef enum ImdsNetworkMode { + IMDS_NETWORK_OFF, /* No automatic pre-IMDS network configuration, something else has to do this. (Also: no "prohibit" route) */ + IMDS_NETWORK_LOCKED, /* "Prohibit" route for the IMDS server, unless you have SO_MARK set to 0x7FFF0815 */ + IMDS_NETWORK_UNLOCKED, /* No "prohibit" route for the IMDS server */ + _IMDS_NETWORK_MODE_MAX, + _IMDS_NETWORK_MODE_INVALID = -EINVAL, +} ImdsNetworkMode; + +/* Various well-known keys */ +typedef enum ImdsWellKnown { + IMDS_BASE, /* The same as "/", typically suffixed */ + IMDS_HOSTNAME, + IMDS_REGION, + IMDS_ZONE, + IMDS_IPV4_PUBLIC, + IMDS_IPV6_PUBLIC, + IMDS_SSH_KEY, + IMDS_USERDATA, + IMDS_USERDATA_BASE, /* typically suffixed */ + IMDS_USERDATA_BASE64, + _IMDS_WELL_KNOWN_MAX, + _IMDS_WELL_KNOWN_INVALID = -EINVAL, +} ImdsWellKnown; + +static inline bool imds_well_known_can_suffix(ImdsWellKnown wk) { + return IN_SET(wk, IMDS_BASE, IMDS_USERDATA_BASE); +} + +bool imds_key_is_valid(const char *key); + +DECLARE_STRING_TABLE_LOOKUP(imds_well_known, ImdsWellKnown); +DECLARE_STRING_TABLE_LOOKUP(imds_network_mode, ImdsNetworkMode); diff --git a/src/imds/imdsd.c b/src/imds/imdsd.c new file mode 100644 index 0000000000000..565f21cfaa66d --- /dev/null +++ b/src/imds/imdsd.c @@ -0,0 +1,3163 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include +#include + +#include "sd-bus.h" +#include "sd-device.h" +#include "sd-event.h" +#include "sd-json.h" +#include "sd-netlink.h" + +#include "alloc-util.h" +#include "build-path.h" +#include "build.h" +#include "bus-polkit.h" +#include "chase.h" +#include "copy.h" +#include "creds-util.h" +#include "device-private.h" +#include "dns-rr.h" +#include "errno-util.h" +#include "escape.h" +#include "event-util.h" +#include "fd-util.h" +#include "format-ifname.h" +#include "hash-funcs.h" +#include "hashmap.h" +#include "imds-util.h" +#include "in-addr-util.h" +#include "io-util.h" +#include "iovec-util.h" +#include "json-util.h" +#include "log.h" +#include "main-func.h" +#include "netlink-util.h" +#include "parse-argument.h" +#include "parse-util.h" +#include "path-util.h" +#include "pretty-print.h" +#include "proc-cmdline.h" +#include "socket-util.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" +#include "tmpfile-util.h" +#include "utf8.h" +#include "varlink-io.systemd.InstanceMetadata.h" +#include "varlink-util.h" +#include "web-util.h" +#include "xattr-util.h" + +#include "../import/curl-util.h" + +/* This implements a client to the AWS' and Azure's "Instance Metadata Service", as well as GCP's "VM + * Metadata", i.e.: + * + * https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html + * https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service + * https://docs.cloud.google.com/compute/docs/metadata/overview + * https://docs.hetzner.cloud/reference/cloud#description/server-metadata + * + * Some notes: + * - IMDS service are heavily rate limited, and hence we want to centralize requests in one place and cache + * - In order to isolate IMDS access this expects that traffic to the IMDS address 169.254.169.254 is + * generally prohibited (via a prohibit route), but our service uses fwmark 0x7FFF0815, which (via source + * routing) can bypass this route. + * - To be robust to situations with multiple interfaces, if we have no hint which interface we shall use, + * we'll fork our own binary off, once for each interface, and communicate to it via Varlink. + * - This is supposed to run under its own UID, but with CAP_NET_ADMIN held (since we want to use + * IP_UNICAST_IF + SO_MARK) + * - This daemon either be invoked manually from the command line, to do a single request, mostly for + * debugging purposes. Or it can be invoked as a Varlink service, which is the primary intended mode of + * operation. + */ + +#define TOKEN_SIZE_MAX (4096U) +#define DATA_SIZE_MAX (4*1024*1024U) +#define FWMARK_DEFAULT UINT32_C(0x7FFF0815) +#define REFRESH_USEC_DEFAULT (15U * USEC_PER_MINUTE) +#define REFRESH_USEC_MIN (1U * USEC_PER_SEC) +#define DIRECT_OVERALL_TIMEOUT_USEC (40U * USEC_PER_SEC) /* a bit shorter than the default D-Bus/Varlink method call time-out) */ +#define INDIRECT_OVERALL_TIMEOUT_USEC (DIRECT_OVERALL_TIMEOUT_USEC + 5U * USEC_PER_SEC) +#define RETRY_MIN_USEC (20U * USEC_PER_MSEC) +#define RETRY_MAX_USEC (3U * USEC_PER_SEC) +#define RETRY_MAX 10U + +/* Which endpoint configuration source has been used, in order of preference */ +typedef enum EndpointSource { + ENDPOINT_USER, /* Explicit command line options */ + ENDPOINT_ENVIRONMENT, /* Fallback environment variables */ + ENDPOINT_PROC_CMDLINE, /* Acquired via kernel command line */ + ENDPOINT_CREDENTIALS, /* Acquired via system credentials */ + ENDPOINT_UDEV, /* Acquired via udev SMBIOS object */ + _ENDPOINT_SOURCE_MAX, + _ENDPOINT_SOURCE_INVALID = -EINVAL, +} EndpointSource; + +static char *arg_ifname = NULL; +static usec_t arg_refresh_usec = REFRESH_USEC_DEFAULT; +static uint32_t arg_fwmark = FWMARK_DEFAULT; +static bool arg_fwmark_set = true; +static ImdsWellKnown arg_well_known = _IMDS_WELL_KNOWN_INVALID; +static char* arg_key = NULL; +static bool arg_cache = true; +static bool arg_wait = false; +static bool arg_varlink = false; +static ImdsNetworkMode arg_network_mode = _IMDS_NETWORK_MODE_INVALID; +static bool arg_setup_network = false; + +/* The follow configure the IMDS service endpoint details */ +static EndpointSource arg_endpoint_source = _ENDPOINT_SOURCE_INVALID; +static char *arg_vendor = NULL; +static char *arg_token_url = NULL; +static char *arg_refresh_header_name = NULL; +static char *arg_data_url = NULL; +static char *arg_data_url_suffix = NULL; +static char *arg_token_header_name = NULL; +static char **arg_extra_header = NULL; +static struct in_addr arg_address_ipv4 = {}; +static struct in6_addr arg_address_ipv6 = {}; +static char *arg_well_known_key[_IMDS_WELL_KNOWN_MAX] = {}; + +static void imds_well_known_key_free(typeof(arg_well_known_key) *array) { + FOREACH_ARRAY(i, *array, _IMDS_WELL_KNOWN_MAX) + free(*i); +} + +STATIC_DESTRUCTOR_REGISTER(arg_ifname, freep); +STATIC_DESTRUCTOR_REGISTER(arg_key, freep); +STATIC_DESTRUCTOR_REGISTER(arg_vendor, freep); +STATIC_DESTRUCTOR_REGISTER(arg_token_url, freep); +STATIC_DESTRUCTOR_REGISTER(arg_refresh_header_name, freep); +STATIC_DESTRUCTOR_REGISTER(arg_data_url, freep); +STATIC_DESTRUCTOR_REGISTER(arg_data_url_suffix, freep); +STATIC_DESTRUCTOR_REGISTER(arg_token_header_name, freep); +STATIC_DESTRUCTOR_REGISTER(arg_extra_header, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_well_known_key, imds_well_known_key_free); + +typedef struct Context Context; + +typedef struct ChildData { + /* If there are multiple network interfaces, and we are not sure where to look for things, we'll fork + * additional instances of ourselves, one for each interface. */ + Context *context; + int ifindex; + sd_varlink *link; /* outgoing varlink connection towards the child */ + bool retry; /* If true then new information came to light and we should restart the request */ +} ChildData; + +struct Context { + /* Fields shared between requests (these remain allocated between Varlink requests) */ + sd_event *event; + sd_netlink *rtnl; + bool rtnl_attached; + sd_bus *system_bus; /* for polkit */ + CurlGlue *glue; + struct iovec token; /* token in binary */ + char *token_string; /* token as string, once complete and validated */ + int cache_dir_fd; + Hashmap *polkit_registry; + + /* Request-specific fields (these get reset whenever we start processing a new Varlink call) */ + int ifindex; + usec_t timestamp; /* CLOCK_BOOTTIME */ + int cache_fd; + char *cache_filename, *cache_temporary_filename; + uint64_t data_size; + usec_t refresh_usec; + char *key; + ImdsWellKnown well_known; + bool write_stdout; + struct iovec write_iovec; + bool cache; + bool wait; + sd_varlink *current_link; /* incoming varlink connection we are processing */ + uint32_t fwmark; + bool fwmark_set; + sd_event_source *overall_timeout_source; + + /* Mode 1 "direct": we go directly to the network (this is done if we know the interface index to + * use) */ + CURL *curl_token; + CURL *curl_data; + struct curl_slist *request_header_token, *request_header_data; + sd_event_source *retry_source; + unsigned n_retry; + usec_t retry_interval_usec; + + /* Mode 2 "indirect": we fork off a number of children which go to the network on behalf of us, + * because we have multiple network interfaces to deal with. */ + Hashmap *child_data; + sd_netlink_slot *address_change_slot; +}; + +#define CONTEXT_NULL \ + (Context) { \ + .cache_dir_fd = -EBADF, \ + .cache_fd = -EBADF, \ + .well_known = _IMDS_WELL_KNOWN_INVALID, \ + } + +/* Log helpers that cap at debug logging if we are operating on behalf of a Varlink client */ +#define context_log_errno(c, level, r, fmt, ...) \ + log_full_errno((c)->current_link ? LOG_DEBUG : (level), r, fmt, ##__VA_ARGS__) +#define context_log(c, level, fmt, ...) \ + log_full((c)->current_link ? LOG_DEBUG : (level), fmt, ##__VA_ARGS__) +#define context_log_oom(c) \ + (c)->current_link ? log_oom_debug() : log_oom() + +static int context_acquire_data(Context *c); +static int context_acquire_token(Context *c); +static int context_spawn_child(Context *c, int ifindex, sd_varlink **ret); + +static ChildData* child_data_free(ChildData *cd) { + if (!cd) + return NULL; + + if (cd->context) + hashmap_remove(cd->context->child_data, INT_TO_PTR(cd->ifindex)); + + sd_varlink_close_unref(cd->link); + return mfree(cd); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(ChildData*, child_data_free); + +static void context_reset_token(Context *c) { + assert(c); + + iovec_done(&c->token); + c->token_string = mfree(c->token_string); +} + +static void context_flush_token(Context *c) { + + if (c->cache_dir_fd >= 0) + (void) unlinkat(c->cache_dir_fd, "token", /* flags= */ 0); + + context_reset_token(c); +} + +static void context_reset_for_refresh(Context *c) { + assert(c); + + /* Flush out all fields, up to the point we can restart the current request */ + + if (c->curl_token) { + curl_glue_remove_and_free(c->glue, c->curl_token); + c->curl_token = NULL; + } + + if (c->curl_data) { + curl_glue_remove_and_free(c->glue, c->curl_data); + c->curl_data = NULL; + } + + curl_slist_free_all(c->request_header_token); + c->request_header_token = NULL; + curl_slist_free_all(c->request_header_data); + c->request_header_data = NULL; + + c->cache_fd = safe_close(c->cache_fd); + c->cache_filename = mfree(c->cache_filename); + + if (c->cache_temporary_filename && c->cache_dir_fd >= 0) + (void) unlinkat(c->cache_dir_fd, c->cache_temporary_filename, /* flags= */ 0); + + c->cache_temporary_filename = mfree(c->cache_temporary_filename); + + iovec_done(&c->write_iovec); + + c->child_data = hashmap_free(c->child_data); + c->data_size = 0; + + sd_event_source_set_enabled(c->retry_source, SD_EVENT_OFF); +} + +static void context_reset_full(Context *c) { + assert(c); + + /* Flush out all fields relevant to the current request, comprehensively */ + + context_reset_for_refresh(c); + c->key = mfree(c->key); + c->well_known = _IMDS_WELL_KNOWN_INVALID; + c->current_link = sd_varlink_unref(c->current_link); + c->address_change_slot = sd_netlink_slot_unref(c->address_change_slot); + c->retry_source = sd_event_source_unref(c->retry_source); + c->overall_timeout_source = sd_event_source_unref(c->overall_timeout_source); + c->cache_dir_fd = safe_close(c->cache_dir_fd); +} + +static void context_new_request(Context *c) { + assert(c); + + /* Flush everything out from the previous request */ + context_reset_full(c); + + /* Reinitialize settings from defaults. */ + c->ifindex = 0; + c->timestamp = now(CLOCK_BOOTTIME); + c->refresh_usec = arg_refresh_usec; + c->cache = arg_cache; + c->wait = arg_wait; + c->fwmark = arg_fwmark; + c->fwmark_set = arg_fwmark_set; + c->n_retry = 0; +} + +static void context_done(Context *c) { + assert(c); + + /* Flush out everything specific to the current request first */ + context_reset_full(c); + context_reset_token(c); + + /* And then also flush out everything shared between requests */ + c->glue = curl_glue_unref(c->glue); + c->rtnl = sd_netlink_unref(c->rtnl); + c->event = sd_event_unref(c->event); + c->polkit_registry = hashmap_free(c->polkit_registry); + c->system_bus = sd_bus_flush_close_unref(c->system_bus); +} + +static void context_fail_full(Context *c, int r, const char *varlink_error) { + assert(c); + assert(r != 0); + + /* Called whenever the current retrieval fails asynchronously */ + + r = -abs(r); + + if (varlink_error) + context_log_errno(c, LOG_ERR, r, "Operation failed (%s).", varlink_error); + else + context_log_errno(c, LOG_ERR, r, "Operation failed (%m)."); + + /* If we are running in Varlink mode, return the error on the connection */ + if (c->current_link) { + if (varlink_error) + (void) sd_varlink_error(c->current_link, varlink_error, NULL); + else + (void) sd_varlink_error_errno(c->current_link, r); + } else + /* Otherwise terminate the whole process. */ + sd_event_exit(c->event, r); + + context_reset_full(c); +} + +static void context_fail(Context *c, int r) { + context_fail_full(c, r, /* varlink_error= */ NULL); +} + +static void context_success(Context *c) { + int r; + + assert(c); + + /* Called whenever the current retrieval succeeds asynchronously */ + + context_log(c, LOG_DEBUG, "Operation succeeded."); + + if (c->current_link) { + r = sd_varlink_replybo( + c->current_link, + JSON_BUILD_PAIR_IOVEC_BASE64("data", &c->write_iovec), + SD_JSON_BUILD_PAIR_CONDITION(c->ifindex > 0, "interface", SD_JSON_BUILD_INTEGER(c->ifindex))); + if (r < 0) + context_log_errno(c, LOG_WARNING, r, "Failed to reply to Varlink call, ignoring: %m"); + } else + sd_event_exit(c->event, 0); + + context_reset_full(c); +} + +static int setsockopt_callback(void *userdata, curl_socket_t curlfd, curlsocktype purpose) { + Context *c = ASSERT_PTR(userdata); + int r; + + assert(curlfd >= 0); + + if (purpose != CURLSOCKTYPE_IPCXN) + return CURL_SOCKOPT_OK; + + r = socket_set_unicast_if(curlfd, AF_UNSPEC, c->ifindex); + if (r < 0) { + context_log_errno(c, LOG_ERR, r, "Failed to bind HTTP socket to interface: %m"); + return CURL_SOCKOPT_ERROR; + } + + if (c->fwmark_set && + setsockopt(curlfd, SOL_SOCKET, SO_MARK, &c->fwmark, sizeof(c->fwmark)) < 0) { + context_log_errno(c, LOG_ERR, errno, "Failed to set firewall mark on HTTP socket: %m"); + return CURL_SOCKOPT_ERROR; + } + + return CURL_SOCKOPT_OK; +} + +static int context_combine_key(Context *c, char **ret) { + assert(ret); + + /* Combines the well known key with the explicitly configured key */ + + char *s; + if (c->well_known < 0 || c->well_known == IMDS_BASE) { + if (!c->key) + return -ENODATA; + + s = strdup(c->key); + } else { + const char *wk = arg_well_known_key[c->well_known]; + if (!wk) + return -ENODATA; + if (c->key) + s = strjoin(wk, c->key); + else + s = strdup(wk); + } + if (!s) + return -ENOMEM; + + *ret = TAKE_PTR(s); + return 0; +} + +static const char *context_get_runtime_directory(Context *c) { + assert(c); + + /* Returns the discovered runtime directory, but only if caching is enabled. */ + + if (!c->cache) { + context_log(c, LOG_DEBUG, "Cache disabled."); + return NULL; + } + + const char *e = secure_getenv("RUNTIME_DIRECTORY"); + if (!e) { + context_log(c, LOG_DEBUG, "Not using cache as $RUNTIME_DIRECTORY is not set."); + return NULL; + } + + return e; +} + +static int context_save_ifname(Context *c) { + int r; + + assert(c); + + /* Saves the used interface name for later retrievals, so that we don't have to wildcard search on + * all interfaces anymore. */ + + if (c->ifindex <= 0) + return 0; + + const char *d = context_get_runtime_directory(c); + if (!d) + return 0; + + _cleanup_close_ int dirfd = open(d, O_PATH|O_CLOEXEC); + if (dirfd < 0) + return context_log_errno(c, LOG_ERR, errno, "Failed to open runtime directory: %m"); + + _cleanup_free_ char *ifname = NULL; + r = rtnl_get_ifname_full(&c->rtnl, c->ifindex, &ifname, /* ret_altnames= */ NULL); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to resolve interface index %i: %m", c->ifindex); + + r = write_string_file_at(dirfd, "ifname", ifname, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to write 'ifname' file: %m"); + + return 1; +} + +typedef enum CacheResult { + CACHE_RESULT_DISABLED, /* caching is disabled */ + CACHE_RESULT_HIT, /* found a positive entry */ + CACHE_RESULT_MISS, /* did not find an entry */ + CACHE_RESULT_KEY_NOT_FOUND, /* found a negative entry */ + CACHE_RESULT_NOT_CACHEABLE, /* not suitable for caching */ + _CACHE_RESULT_MAX, + _CACHE_RESULT_INVALID = -EINVAL, + _CACHE_RESULT_ERRNO_MAX = -ERRNO_MAX, +} CacheResult; + +static CacheResult context_process_cache(Context *c) { + int r; + + assert(c); + + assert(c->key || c->well_known >= 0); + assert(c->cache_fd < 0); + assert(!c->cache_filename); + assert(!c->cache_temporary_filename); + + /* Checks the local cache – if we have one – for the current request */ + + if (c->cache_dir_fd < 0) { + const char *e = context_get_runtime_directory(c); + if (!e) + return CACHE_RESULT_DISABLED; + + char ifname[IF_NAMESIZE]; + r = format_ifname(c->ifindex, ifname); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to format interface name: %m"); + + if (!filename_is_valid(ifname)) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EINVAL), "Network interface name '%s' is not a valid filename, refusing.", ifname); + + _cleanup_free_ char *cache_dir = path_join("cache", ifname); + if (!cache_dir) + return context_log_oom(c); + + r = chase(cache_dir, + e, + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY|CHASE_PREFIX_ROOT, + /* ret_path= */ NULL, + &c->cache_dir_fd); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to open cache directory: %m"); + } + + _cleanup_free_ char *k = NULL; + r = context_combine_key(c, &k); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to combine IMDS key: %m"); + + _cleanup_free_ char *escaped = xescape(k, "/."); + if (!escaped) + return context_log_oom(c); + + _cleanup_free_ char *fn = strjoin("key-", escaped); + if (!fn) + return context_log_oom(c); + + if (!filename_is_valid(fn)) { + context_log(c, LOG_WARNING, "Cache filename for '%s' is not valid, not caching.", fn); + return CACHE_RESULT_NOT_CACHEABLE; + } + + c->cache_filename = TAKE_PTR(fn); + + _cleanup_close_ int fd = openat(c->cache_dir_fd, c->cache_filename, O_RDONLY|O_CLOEXEC); + if (fd < 0) { + if (errno != ENOENT) + return context_log_errno(c, LOG_ERR, errno, "Failed to open cache file '%s': %m", c->cache_filename); + } else { + _cleanup_free_ char *d = NULL; + size_t l; + + context_log(c, LOG_DEBUG, "Found cached file '%s'.", c->cache_filename); + + r = fgetxattr_malloc(fd, "user.imds.timestamp", &d, &l); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to read timestamp from cache file: %m"); + if (l != sizeof(usec_t)) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EBADMSG), "Invalid timestamp xattr on cache file '%s': %m", c->cache_filename); + + usec_t *u = (usec_t*) d; + if (usec_add(*u, c->refresh_usec) > c->timestamp) { + _cleanup_free_ char *result = NULL; + r = fgetxattr_malloc(fd, "user.imds.result", &result, /* ret_size= */ NULL); + if (r == -ENODATA) { + /* No user.imds.result xattr means: hit! */ + if (c->write_stdout) { + r = copy_bytes(fd, STDOUT_FILENO, /* max_bytes= */ UINT64_MAX, /* copy_flags= */ 0); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to write cached data to standard output: %m"); + } else { + assert(!iovec_is_set(&c->write_iovec)); + r = read_full_file_at(fd, /* filename= */ NULL, (char**) &c->write_iovec.iov_base, &c->write_iovec.iov_len); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to read cache data: %m"); + } + + return CACHE_RESULT_HIT; + } + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to read 'user.imds.result' extended attribute: %m"); + + if (streq(result, "key-not-found")) + return CACHE_RESULT_KEY_NOT_FOUND; + + context_log(c, LOG_WARNING, "Unexpected 'user.imds.result' extended attribute value, ignoring: %s", result); + (void) unlinkat(c->cache_dir_fd, c->cache_filename, /* flags= */ 0); + } else { + context_log(c, LOG_DEBUG, "Cached data is older than '%s', ignoring.", FORMAT_TIMESPAN(c->refresh_usec, 0)); + (void) unlinkat(c->cache_dir_fd, c->cache_filename, /* flags= */ 0); + } + } + + /* So the above was not conclusive, let's then at least try to reuse the token */ + _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + r = sd_json_parse_file_at(/* f= */ NULL, c->cache_dir_fd, "token", /* flags= */ 0, &j, /* reterr_line= */ NULL, /* reterr_column= */ NULL); + if (r == -ENOENT) { + context_log_errno(c, LOG_DEBUG, r, "No cached token"); + return CACHE_RESULT_MISS; + } + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to read cached token: %m"); + + struct { + const char *token; + uint64_t until; + } d = {}; + + static const sd_json_dispatch_field table[] = { + { "token", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(d, token), SD_JSON_MANDATORY }, + { "validUntilUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(d, until), SD_JSON_MANDATORY }, + {} + }; + + r = sd_json_dispatch(j, table, SD_JSON_ALLOW_EXTENSIONS, &d); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to decode cached token data: %m"); + + if (d.until > c->timestamp) { + c->token_string = strdup(d.token); + if (!c->token_string) + return context_log_oom(c); + + context_log(c, LOG_INFO, "Reusing cached token."); + } else + context_log(c, LOG_DEBUG, "Cached token is stale, not using."); + + return CACHE_RESULT_MISS; +} + +static int on_retry(sd_event_source *s, uint64_t usec, void *userdata) { + Context *c = ASSERT_PTR(userdata); + int r; + + assert(s); + + /* Invoked whenever the retry timer event elapses and we need to retry again */ + + context_log(c, LOG_DEBUG, "Retrying..."); + + /* Maybe some other instance was successful in the meantime and already found something? */ + CacheResult cr = context_process_cache(c); + if (cr < 0) { + context_fail(c, cr); + return 0; + } + if (cr == CACHE_RESULT_HIT) { + context_success(c); + return 0; + } + if (cr == CACHE_RESULT_KEY_NOT_FOUND) { + context_fail(c, context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(ENOENT), "Cache reports: key not found")); + return 0; + } + + r = context_acquire_token(c); + if (r < 0) { + context_fail(c, r); + return 0; + } + + r = context_acquire_data(c); + if (r < 0) + context_fail(c, r); + + return 0; +} + +static int context_schedule_retry(Context *c) { + int r; + + assert(c); + + /* Schedules a new retry via a timer event */ + + if (c->n_retry >= RETRY_MAX) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EUCLEAN), "Retry limits reached, refusing."); + + if (c->n_retry == 0) + c->retry_interval_usec = RETRY_MIN_USEC; + else if (c->retry_interval_usec < RETRY_MAX_USEC / 2) + c->retry_interval_usec *= 2; + else + c->retry_interval_usec = RETRY_MAX_USEC; + + c->n_retry++; + context_log(c, LOG_DEBUG, "Retry attempt #%u in %s...", c->n_retry, FORMAT_TIMESPAN(c->retry_interval_usec, USEC_PER_MSEC)); + + context_reset_for_refresh(c); + + r = event_reset_time_relative( + c->event, + &c->retry_source, + CLOCK_BOOTTIME, + c->retry_interval_usec, + /* accuracy= */ 0, + on_retry, + c, + /* priority= */ 0, + "imds-retry", + /* force_reset= */ true); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to reset retry timer event source: %m"); + + return 0; +} + +static int context_acquire_http_status(Context *c, CURL *curl, long *ret_status) { + assert(c); + assert(ret_status); + + /* Acquires the HTTP status code, and does some generic validation that applies to both the token and + * the data transfer. + * + * Error handling as per: + * https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html#instance-metadata-returns + * https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service#rate-limiting + */ + + long status; + CURLcode code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); + if (code != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to retrieve response code: %s", curl_easy_strerror(code)); + + context_log(c, LOG_DEBUG, "Got HTTP error code %li.", status); + + if (status == 403) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EADDRNOTAVAIL), "IMDS is not available"); + + /* Automatically retry on some transient errors from HTTP */ + if (IN_SET(status, + 503, /* AWS + GCP */ + 429 /* Azure + GCP */)) { + *ret_status = 0; + return 0; /* no immediate answer, please schedule retry */ + } + + if (status < 200 || status > 600) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "HTTP request finished with unexpected code %li.", status); + + *ret_status = status; + return 1; /* valid answer */ +} + +static int context_validate_token_http_status(Context *c, long status) { + assert(c); + + /* Specific HTTP status checks for the token transfer */ + + if (status >= 300) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "HTTP request for token finished with unexpected code %li.", status); + + return 1; /* all good */ +} + +static int context_validate_data_http_status(Context *c, long status) { + int r; + + assert(c); + + /* Specific HTTP status checks for the data transfer */ + + if (status == 401 && arg_token_url) { + /* We need a new token */ + context_log(c, LOG_DEBUG, "Server requested a new token..."); + + /* Count token requests as a retry */ + if (c->n_retry >= RETRY_MAX) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EUCLEAN), "Retry limits reached, refusing."); + c->n_retry++; + + context_flush_token(c); + context_reset_for_refresh(c); + + r = context_acquire_token(c); + if (r < 0) + return r; + + r = context_acquire_data(c); + if (r < 0) + return r; + + return 0; /* restarted right-away */ + } + + if (status == 404) { + _cleanup_free_ char *key = NULL; + r = context_combine_key(c, &key); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to combine IMDS key: %m"); + + /* Do negative caching for not found */ + if (c->cache_fd >= 0) { + if (fsetxattr(c->cache_fd, "user.imds.result", "key-not-found", STRLEN("key-not-found"), /* flags= */ 0) < 0) + context_log_errno(c, LOG_DEBUG, errno, "Failed to set result xattr on '%s', ignoring: %m", c->cache_filename); + else { + r = link_tmpfile_at(c->cache_fd, c->cache_dir_fd, c->cache_temporary_filename, c->cache_filename, LINK_TMPFILE_REPLACE); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to move cache file into place: %m"); + + c->cache_fd = safe_close(c->cache_fd); + c->cache_temporary_filename = mfree(c->cache_temporary_filename); + + context_log(c, LOG_DEBUG, "Cached negative entry for '%s'.", key); + } + } + + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(ENOENT), "Key '%s' not found.", key); + } + + if (status >= 300) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "HTTP request for data finished with unexpected code %li.", status); + + return 1; /* all good */ +} + +static int context_validate_token(Context *c) { + int r; + + assert(c); + + /* Validates that the downloaded token data actually forms a valid string */ + + _cleanup_free_ char *t = NULL; + r = make_cstring( + c->token.iov_base, + c->token.iov_len, + MAKE_CSTRING_REFUSE_TRAILING_NUL, + &t); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to convert token into C string: %m"); + + if (string_has_cc(t, NULL) || + !utf8_is_valid(t)) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EINVAL), "Token not valid UTF-8 or contains control characters, refusing."); + + free_and_replace(c->token_string, t); + return 1; /* all good */ +} + +static int context_save_token(Context *c) { + int r; + + assert(c); + assert(c->token_string); + + /* Save the acquired token in the cache, so that we can reuse it later */ + + if (c->cache_dir_fd < 0) + return 0; + + /* Only store half the valid time, to make sure we have ample time to use it */ + usec_t until = usec_add(c->timestamp, c->refresh_usec/2); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + r = sd_json_buildo( + &j, + SD_JSON_BUILD_PAIR_STRING("token", c->token_string), + SD_JSON_BUILD_PAIR_UNSIGNED("validUntilUSec", until)); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to build token JSON: %m"); + + _cleanup_free_ char *t = NULL; + r = sd_json_variant_format(j, SD_JSON_FORMAT_NEWLINE, &t); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to format JSON: %m"); + + r = write_string_file_at(c->cache_dir_fd, "token", t, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_MODE_0600); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to write token cache file: %m"); + + return 0; +} + +static int context_save_data(Context *c) { + int r; + + assert(c); + + /* Finalize saving of the acquired data in the cache */ + + if (c->cache_fd < 0) + return 0; + + r = link_tmpfile_at(c->cache_fd, c->cache_dir_fd, c->cache_temporary_filename, c->cache_filename, LINK_TMPFILE_REPLACE); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to move cache file into place: %m"); + + c->cache_fd = safe_close(c->cache_fd); + c->cache_temporary_filename = mfree(c->cache_temporary_filename); + + context_log(c, LOG_DEBUG, "Cached data."); + return 0; +} + +static void curl_glue_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { + int r; + + assert(g); + + /* Called whenever libcurl did its thing and reports a download being complete or having failed */ + + Context *c = NULL; + if (curl_easy_getinfo(curl, CURLINFO_PRIVATE, (char**) &c) != CURLE_OK) + return; + + switch (result) { + + case CURLE_OK: /* yay! */ + /* If we managed to get a HTTP reply, this is good enough, let's pin the interface now for + * later calls */ + (void) context_save_ifname(c); + break; + + case CURLE_WRITE_ERROR: + /* CURLE_WRITE_ERROR we'll see if the data callbacks failed already. We'll try to look at the + * HTTP status below, and use that ideally. */ + break; + + case CURLE_COULDNT_CONNECT: + case CURLE_OPERATION_TIMEDOUT: + case CURLE_GOT_NOTHING: + case CURLE_SEND_ERROR: + case CURLE_RECV_ERROR: + context_log(c, LOG_INFO, "Connection error from curl: %s", curl_easy_strerror(result)); + + /* Automatically retry on some transient errors from curl itself */ + r = context_schedule_retry(c); + if (r < 0) + return context_fail(c, r); + + return; + + default: + return context_fail_full( + c, + context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EHOSTDOWN), "Transfer failed: %s", curl_easy_strerror(result)), + "io.systemd.InstanceMetadata.CommunicationFailure"); + } + + long status; + r = context_acquire_http_status(c, curl, &status); + if (r == -EADDRNOTAVAIL) + return context_fail_full(c, r, "io.systemd.InstanceMetadata.NotAvailable"); + if (r < 0) + return context_fail(c, r); + if (r == 0) { /* We shall retry */ + (void) context_schedule_retry(c); + return; + } + if (result != CURLE_OK) /* if getting the HTTP status didn't work, propagate a generic error */ + return context_fail(c, SYNTHETIC_ERRNO(ENOTRECOVERABLE)); + + if (curl == c->curl_token) { + r = context_validate_token_http_status(c, status); + if (r < 0) + return context_fail(c, r); + + r = context_validate_token(c); + if (r < 0) + return context_fail(c, r); + + context_log(c, LOG_DEBUG, "Token successfully acquired."); + + r = context_save_token(c); + if (r < 0) + return context_fail(c, r); + + r = context_acquire_data(c); + if (r < 0) + return context_fail(c, r); + + } else if (curl == c->curl_data) { + + r = context_validate_data_http_status(c, status); + if (r == -ENOENT) + return context_fail_full(c, r, "io.systemd.InstanceMetadata.KeyNotFound"); + if (r < 0) + return context_fail(c, r); + if (r == 0) /* Immediately restarted */ + return; + + context_log(c, LOG_DEBUG, "Data download successful."); + + r = context_save_data(c); + if (r < 0) + return context_fail(c, r); + + context_success(c); + } else + assert_not_reached(); +} + +static int context_acquire_glue(Context *c) { + int r; + + assert(c); + + /* Allocates a curl object if we don't have one yet */ + + if (c->glue) + return 0; + + r = curl_glue_new(&c->glue, c->event); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to allocate curl glue: %m"); + + c->glue->on_finished = curl_glue_on_finished; + c->glue->userdata = c; + + return 0; +} + +static size_t data_write_callback(void *contents, size_t size, size_t nmemb, void *userdata) { + Context *c = ASSERT_PTR(userdata); + size_t sz = size * nmemb; + int r; + + /* Called whenever we receive new payload from the server */ + assert(contents); + + /* If we managed to get a HTTP reply, this is good enough, let's pin the interface now for later calls */ + (void) context_save_ifname(c); + + /* Before we use the acquired data, let's verify the HTTP status, if there's a failure or we need to + * restart, abort the write here. Note that the curl_glue_on_finished() call will then check the HTTP + * status again and act on it. */ + long status; + r = context_acquire_http_status(c, c->curl_data, &status); + if (r <= 0) + return 0; /* fail the thing, so that curl_glue_on_finished() can handle this failure or retry request */ + if (status >= 300) /* any status equal or above 300 needs to be handled by curl_glue_on_finished() too */ + return 0; + + if (sz > UINT64_MAX - c->data_size || + c->data_size + sz > DATA_SIZE_MAX) { + context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(E2BIG), "Data too large, refusing."); + return 0; + } + + c->data_size += sz; + + if (c->write_stdout) + (void) fwrite(contents, 1, sz, stdout); + else if (!iovec_append(&c->write_iovec, &IOVEC_MAKE(contents, sz))) { + context_log_oom(c); + return 0; + } + + if (c->cache_fd >= 0) { + r = loop_write(c->cache_fd, contents, sz); + if (r < 0) { + context_log_errno(c, LOG_ERR, r, "Failed to write data to cache: %m"); + return 0; + } + } + + return sz; +} + +static int context_acquire_data(Context *c) { + int r; + + assert(c); + assert(c->key || c->well_known >= 0); + + /* Called to initiate getting the actual IMDS key payload */ + + if (arg_token_url && !c->token_string) + return 0; /* If we need a token first, let's not do anything */ + + _cleanup_free_ char *k = NULL; + r = context_combine_key(c, &k); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to combine key: %m"); + + context_log(c, LOG_INFO, "Requesting data for key '%s'.", k); + + if (c->cache_dir_fd >= 0 && + c->cache_filename && + c->cache_fd < 0) { + c->cache_fd = open_tmpfile_linkable_at(c->cache_dir_fd, c->cache_filename, O_WRONLY|O_CLOEXEC, &c->cache_temporary_filename); + if (c->cache_fd < 0) + return context_log_errno(c, LOG_ERR, c->cache_fd, "Failed to create cache file '%s': %m", c->cache_filename); + + if (fchmod(c->cache_fd, 0600) < 0) + return context_log_errno(c, LOG_ERR, errno, "Failed to adjust cache node access mode: %m"); + + if (fsetxattr(c->cache_fd, "user.imds.timestamp", &c->timestamp, sizeof(c->timestamp), /* flags= */ 0) < 0) + return context_log_errno(c, LOG_ERR, errno, "Failed to set timestamp xattr on '%s': %m", c->cache_filename); + } + + r = context_acquire_glue(c); + if (r < 0) + return r; + + _cleanup_free_ char *url = strjoin(arg_data_url, k, arg_data_url_suffix); + if (!url) + return context_log_oom(c); + + r = curl_glue_make(&c->curl_data, url, c); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to create CURL request for data: %m"); + + if (c->token_string) { + _cleanup_free_ char *token_header = strjoin(arg_token_header_name, ": ", c->token_string); + if (!token_header) + return context_log_oom(c); + + struct curl_slist *n = curl_slist_append(c->request_header_data, token_header); + if (!n) + return context_log_oom(c); + + c->request_header_data = n; + } + + STRV_FOREACH(i, arg_extra_header) { + struct curl_slist *n = curl_slist_append(c->request_header_data, *i); + if (!n) + return context_log_oom(c); + + c->request_header_data = n; + } + + if (c->request_header_data) + if (curl_easy_setopt(c->curl_data, CURLOPT_HTTPHEADER, c->request_header_data) != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set HTTP request header."); + + if (curl_easy_setopt(c->curl_data, CURLOPT_WRITEFUNCTION, data_write_callback) != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL write function."); + + if (curl_easy_setopt(c->curl_data, CURLOPT_WRITEDATA, c) != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL write function userdata."); + + if (curl_easy_setopt(c->curl_data, CURLOPT_SOCKOPTFUNCTION, setsockopt_callback) != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL setsockopt function."); + + if (curl_easy_setopt(c->curl_data, CURLOPT_SOCKOPTDATA, c) != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL setsockopt function userdata."); + + if (curl_easy_setopt(c->curl_data, CURLOPT_LOCALPORT, 1L) != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL setsockopt local port"); + + if (curl_easy_setopt(c->curl_data, CURLOPT_LOCALPORTRANGE, 1023L) != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL setsockopt local port range"); + + r = curl_glue_add(c->glue, c->curl_data); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to add CURL request to glue: %m"); + + return 0; +} + +static size_t token_write_callback(void *contents, size_t size, size_t nmemb, void *userdata) { + Context *c = ASSERT_PTR(userdata); + size_t sz = size * nmemb; + int r; + + /* Called whenever we get data from the token download */ + assert(contents); + + /* If we managed to get a HTTP reply, this is good enough, let's pin the interface now for later calls */ + (void) context_save_ifname(c); + + /* Before we use acquired data, let's verify the HTTP status */ + long status; + r = context_acquire_http_status(c, c->curl_token, &status); + if (r <= 0) + return 0; /* fail the thing, so that curl_glue_on_finished() can handle this failure or retry request */ + if (status >= 300) /* any status equal or above 300 needs to be handled by curl_glue_on_finished() */ + return 0; + + if (sz > SIZE_MAX - c->token.iov_len || + c->token.iov_len + sz > TOKEN_SIZE_MAX) { + context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(E2BIG), "IMDS token too large."); + return 0; + } + + if (!iovec_append(&c->token, &IOVEC_MAKE(contents, sz))) { + context_log_oom(c); + return 0; + } + + return sz; +} + +static int context_acquire_token(Context *c) { + int r; + + assert(c); + + /* Called to initiate getting the token if we need one. */ + + if (c->token_string || !arg_token_url) + return 0; + + context_log(c, LOG_INFO, "Requesting token."); + + r = context_acquire_glue(c); + if (r < 0) + return r; + + r = curl_glue_make(&c->curl_token, arg_token_url, c); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to create CURL request for API token: %m"); + + if (arg_refresh_header_name) { + _cleanup_free_ char *ttl_header = NULL; + if (asprintf(&ttl_header, + "%s: %" PRIu64, + arg_refresh_header_name, + DIV_ROUND_UP(c->refresh_usec, USEC_PER_SEC)) < 0) + return context_log_oom(c); + + c->request_header_token = curl_slist_new(ttl_header, NULL); + if (!c->request_header_token) + return context_log_oom(c); + } + + if (curl_easy_setopt(c->curl_token, CURLOPT_HTTPHEADER, c->request_header_token) != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set HTTP request header."); + + if (curl_easy_setopt(c->curl_token, CURLOPT_CUSTOMREQUEST, "PUT") != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set HTTP request method."); + + if (curl_easy_setopt(c->curl_token, CURLOPT_WRITEFUNCTION, token_write_callback) != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL write function."); + + if (curl_easy_setopt(c->curl_token, CURLOPT_WRITEDATA, c) != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL write function userdata."); + + if (curl_easy_setopt(c->curl_token, CURLOPT_SOCKOPTFUNCTION, setsockopt_callback) != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL setsockopt function."); + + if (curl_easy_setopt(c->curl_token, CURLOPT_SOCKOPTDATA, c) != CURLE_OK) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL setsockopt function userdata."); + + r = curl_glue_add(c->glue, c->curl_token); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to add CURL request to glue: %m"); + + return 0; +} + +static int vl_on_reply(sd_varlink *link, sd_json_variant *m, const char *error_id, sd_varlink_reply_flags_t flags, void *userdata) { + ChildData *cd = ASSERT_PTR(userdata); + Context *c = ASSERT_PTR(cd->context); + int r; + + assert(link); + assert(m); + + /* When we spawned off worker instances of ourselves (one for each local network interface), then + * we'll get a response from them via a Varlink reply. Handle it. */ + + if (error_id) { + r = sd_varlink_error_to_errno(error_id, m); + if (r == -EBADR) + context_log_errno(c, LOG_WARNING, r, "Varlink error from interface %i: %s", cd->ifindex, error_id); + else + context_log_errno(c, LOG_WARNING, r, "Varlink error from interface %i: %m", cd->ifindex); + + /* Propagate these errors immediately */ + if (streq(error_id, "io.systemd.InstanceMetadata.KeyNotFound")) { + context_fail_full(c, -ENOENT, error_id); + return 0; + } + if (streq(error_id, "io.systemd.InstanceMetadata.WellKnownKeyUnset")) { + context_fail_full(c, -ENODATA, error_id); + return 0; + } + if (streq(error_id, "io.systemd.InstanceMetadata.NotAvailable")) { + context_fail_full(c, -EADDRNOTAVAIL, error_id); + return 0; + } + + /* The other errors we consider transient. Let's see if we shall immediately restart the request. */ + if (cd->retry) { + context_log(c, LOG_DEBUG, "Child for network interface %i was scheduled for immediate retry, executing now.", cd->ifindex); + cd->link = sd_varlink_close_unref(cd->link); + cd->retry = false; + + r = context_spawn_child(c, cd->ifindex, &cd->link); + if (r < 0) { + context_fail(c, r); + return 0; + } + + sd_varlink_set_userdata(cd->link, cd); + return 0; + } + + /* We shall not retry immediately. In that case, we give up on the child, and propagate the + * error if it was the last child, otherwise we continue until the last one dies too. */ + cd = child_data_free(cd); + + if (hashmap_isempty(c->child_data) && !c->wait) { + /* This is the last child, propagate the error */ + context_log(c, LOG_DEBUG, "Last child failed, propagating error."); + + if (streq(error_id, "io.systemd.InstanceMetadata.CommunicationFailure")) + context_fail_full(c, -EHOSTDOWN, error_id); + else if (streq(error_id, "io.systemd.InstanceMetadata.Timeout")) + context_fail_full(c, -ETIMEDOUT, error_id); + else + context_fail_full(c, r, error_id); + + return 0; + } + + context_log(c, LOG_DEBUG, "Pending children remaining, continuing to wait."); + return 0; + } + + assert(!iovec_is_set(&c->write_iovec)); + + static const sd_json_dispatch_field table[] = { + { "data", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Context, write_iovec), SD_JSON_MANDATORY }, + { "interface", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(Context, ifindex), 0 }, + {} + }; + + r = sd_json_dispatch(m, table, SD_JSON_ALLOW_EXTENSIONS, c); + if (r < 0) { + context_fail(c, context_log_errno(c, LOG_ERR, r, "Failed to decode reply data: %m")); + return 0; + } + + if (c->write_stdout) { + r = loop_write(STDOUT_FILENO, c->write_iovec.iov_base, c->write_iovec.iov_len); + if (r < 0) { + context_fail(c, context_log_errno(c, LOG_ERR, r, "Failed to output data: %m")); + return 0; + } + } + + context_success(c); + return 0; +} + +static int context_load_ifname(Context *c) { + int r; + + assert(c); + + /* Tries to load the previously used interface name, so that we don't have to wildcard search on all + * interfaces. */ + + const char *e = context_get_runtime_directory(c); + if (!e) + return 0; + + _cleanup_close_ int dirfd = open(e, O_PATH|O_CLOEXEC); + if (dirfd < 0) + return context_log_errno(c, LOG_ERR, errno, "Failed to open runtime directory: %m"); + + _cleanup_free_ char *ifname = NULL; + r = read_one_line_file_at(dirfd, "ifname", &ifname); + if (r == -ENOENT) + return 0; + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to load 'ifname' file from runtime directory: %m"); + + if (!ifname_valid(ifname)) + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EINVAL), "Loaded interface name not valid, refusing: %s", ifname); + + c->ifindex = rtnl_resolve_interface(&c->rtnl, ifname); + if (c->ifindex < 0) { + (void) unlinkat(dirfd, "ifname", /* flags= */ 0); + context_log_errno(c, LOG_ERR, c->ifindex, "Failed to resolve saved interface name '%s', assuming interface disappeared, ignoring: %m", ifname); + c->ifindex = 0; + return 0; + } + + log_debug("Using previously pinned interface '%s' (ifindex: %i).", ifname, c->ifindex); + return 1; +} + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + child_data_hash_ops, + void, + trivial_hash_func, + trivial_compare_func, + ChildData, + child_data_free); + +static int context_spawn_child(Context *c, int ifindex, sd_varlink **ret) { + int r; + + assert(c); + assert(ifindex > 0); + assert(ret); + + /* If we don't know yet on which network interface the IMDS server can be found, let's spawn separate + * instances of ourselves, one for each interface, and collect the results. We communicate with + * each one via Varlink, the same way as clients talk to us. */ + + context_log(c, LOG_DEBUG, "Spawning child for interface '%i'.", ifindex); + + _cleanup_free_ char *p = NULL; + _cleanup_close_ int fd = pin_callout_binary(LIBEXECDIR "/systemd-imdsd", &p); + if (fd < 0) + return context_log_errno(c, LOG_ERR, fd, "Failed to find imdsd binary: %m"); + + _cleanup_strv_free_ char **argv = strv_new( + p, + "--vendor", strempty(arg_vendor), + "--token-url", strempty(arg_token_url), + "--refresh-header-name", strempty(arg_refresh_header_name), + "--data-url", strempty(arg_data_url), + "--data-url-suffix", strempty(arg_data_url_suffix), + "--token-header-name", strempty(arg_token_header_name), + "--address-ipv4", in4_addr_is_null(&arg_address_ipv4) ? "" : IN4_ADDR_TO_STRING(&arg_address_ipv4), + "--address-ipv6", in6_addr_is_null(&arg_address_ipv6) ? "" : IN6_ADDR_TO_STRING(&arg_address_ipv6)); + if (!argv) + return log_oom(); + + STRV_FOREACH(i, arg_extra_header) + if (strv_extend_strv(&argv, STRV_MAKE("--extra-header", *i), /* filter_duplicates= */ false) < 0) + return log_oom(); + + for (ImdsWellKnown wk = 0; wk < _IMDS_WELL_KNOWN_MAX; wk++) { + if (!arg_well_known_key[wk]) + continue; + + if (strv_extendf(&argv, "--well-known-key=%s:%s", imds_well_known_to_string(wk), arg_well_known_key[wk]) < 0) + return log_oom(); + } + + if (DEBUG_LOGGING) { + _cleanup_free_ char *cmdline = quote_command_line(argv, SHELL_ESCAPE_EMPTY); + log_debug("About to fork off: %s", strnull(cmdline)); + } + + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + r = sd_varlink_connect_exec(&vl, p, argv); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to fork off imdsd binary for interface %i: %m", ifindex); + + r = sd_varlink_attach_event( + vl, + c->event, + SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to attach Varlink connection to event loop: %m"); + + r = sd_varlink_bind_reply(vl, vl_on_reply); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to bind reply callback: %m"); + + r = sd_varlink_invokebo( + vl, + "io.systemd.InstanceMetadata.Get", + JSON_BUILD_PAIR_STRING_NON_EMPTY("key", c->key), + SD_JSON_BUILD_PAIR_CONDITION(c->well_known >= 0, "wellKnown", JSON_BUILD_STRING_UNDERSCORIFY(imds_well_known_to_string(c->well_known))), + SD_JSON_BUILD_PAIR_INTEGER("interface", ifindex), + SD_JSON_BUILD_PAIR_INTEGER("refreshUSec", c->refresh_usec), + SD_JSON_BUILD_PAIR_BOOLEAN("cache", c->cache), + SD_JSON_BUILD_PAIR_CONDITION(c->fwmark_set, "firewallMark", SD_JSON_BUILD_UNSIGNED(c->fwmark)), + SD_JSON_BUILD_PAIR_CONDITION(!c->fwmark_set, "firewallMark", SD_JSON_BUILD_NULL)); /* explicitly turn off fwmark, if not set */ + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to issue Get() command to Varlink child: %m"); + + *ret = TAKE_PTR(vl); + return 0; +} + +static int context_spawn_new_child(Context *c, int ifindex) { + int r; + + assert(c); + + /* Spawn a child, and keep track of it */ + + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + r = context_spawn_child(c, ifindex, &vl); + if (r < 0) + return r; + + _cleanup_(child_data_freep) ChildData *cd = new(ChildData, 1); + if (!cd) + return context_log_oom(c); + + *cd = (ChildData) { + .ifindex = ifindex, + .link = sd_varlink_ref(vl), + }; + + sd_varlink_set_userdata(vl, cd); + + if (hashmap_ensure_put(&c->child_data, &child_data_hash_ops, INT_TO_PTR(ifindex), cd) < 0) + return context_log_oom(c); + + cd->context = c; + TAKE_PTR(cd); + + return 0; +} + +static int on_address_change(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { + Context *c = ASSERT_PTR(userdata); + int ifindex, r; + + assert(rtnl); + assert(m); + + /* Called whenever an address appears on the network stack. We use that as hint that it is worth to + * invoke a child processing that interface (either for the first time, or again) */ + + r = sd_rtnl_message_addr_get_ifindex(m, &ifindex); + if (r < 0) { + context_log_errno(c, LOG_WARNING, r, "rtnl: could not get ifindex from message, ignoring: %m"); + return 0; + } + if (ifindex <= 0) { + context_log(c, LOG_WARNING, "rtnl: received address message with invalid ifindex %d, ignoring.", ifindex); + return 0; + } + + if (ifindex == LOOPBACK_IFINDEX) { + context_log(c, LOG_DEBUG, "Ignoring loopback device."); + return 0; + } + + if (!c->key && c->well_known < 0) + return 0; + + ChildData *existing = hashmap_get(c->child_data, INT_TO_PTR(ifindex)); + if (existing) { + /* We already have an attempt ongoing for this one? Remember there's a reason now to retry + * this, because new connectivity appeared. */ + context_log(c, LOG_DEBUG, "Child for network interface %i already spawned off, scheduling for immediate retry.", ifindex); + existing->retry = true; + return 0; + } + + return context_spawn_new_child(c, ifindex); +} + +static int context_acquire_rtnl_with_match(Context *c) { + int r; + + assert(c); + assert(c->event); + + /* Acquire a netlink connection and a match if we don't have one yet */ + + if (!c->rtnl) { + r = sd_netlink_open(&c->rtnl); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to connect to netlink: %m"); + } + + if (!c->rtnl_attached) { + /* The netlink connection might have created previously via rtnl_resolve_interface() – which + * however didn't attach it to our event loop. Do so now. */ + r = sd_netlink_attach_event(c->rtnl, c->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to attach netlink socket to event loop: %m"); + + c->rtnl_attached = true; + } + + if (!c->address_change_slot) { + r = sd_netlink_add_match(c->rtnl, &c->address_change_slot, RTM_NEWADDR, on_address_change, /* destroy_callback= */ NULL, c, "newaddr"); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to subscribe to RTM_NEWADDR events: %m"); + } + + return 0; +} + +static int context_spawn_children(Context *c) { + int r; + + assert(c); + assert(c->key || c->well_known >= 0); + + /* If we don't know yet on which interface to query, let's see which interfaces there are and spawn + * ourselves, once on each */ + + r = context_acquire_rtnl_with_match(c); + if (r < 0) + return r; + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + r = sd_rtnl_message_new_addr(c->rtnl, &req, RTM_GETADDR, /* ifindex= */ 0, AF_UNSPEC); + if (r < 0) + return r; + + r = sd_netlink_message_set_request_dump(req, true); + if (r < 0) + return r; + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *reply = NULL; + r = sd_netlink_call(c->rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (sd_netlink_message *i = reply; i; i = sd_netlink_message_next(i)) { + r = on_address_change(c->rtnl, i, c); + if (r < 0) + return r; + } + + return 0; +} + +static int imds_configured(int level) { + /* Checks if we have enough endpoint information to operate */ + + if (arg_endpoint_source < 0) + return log_full_errno(level, SYNTHETIC_ERRNO(EOPNOTSUPP), "No IMDS endpoint information provided or detected, cannot operate."); + + if (!arg_data_url) + return log_full_errno(level, SYNTHETIC_ERRNO(EOPNOTSUPP), "No data base URL provided."); + + if (!!arg_token_url != !!arg_token_header_name) + return log_full_errno(level, SYNTHETIC_ERRNO(EOPNOTSUPP), "Incomplete token parameters configured for endpoint."); + + return 0; +} + +static int setup_network(void) { + int r; + + /* Generates a .network file based on the IMDS endpoint information we have */ + + if (arg_network_mode == IMDS_NETWORK_OFF) { + log_debug("IMDS networking turned off, not generating .network file."); + return 0; + } + + _cleanup_close_ int network_dir_fd = -EBADF; + r = chase("/run/systemd/network", + /* root= */ NULL, + CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + /* ret_path= */ NULL, + &network_dir_fd); + if (r < 0) + return log_error_errno(r, "Failed to open .network directory: %m"); + + _cleanup_free_ char *t = NULL; + _cleanup_fclose_ FILE *f = NULL; + r = fopen_tmpfile_linkable_at(network_dir_fd, "85-imds-early.network", O_WRONLY|O_CLOEXEC, &t, &f); + if (r < 0) + return log_error_errno(r, "Failed to create 85-imds-early.network file: %m"); + + CLEANUP_TMPFILE_AT(network_dir_fd, t); + + fputs("# Generated by systemd-imdsd, do not edit.\n" + "#\n" + "# This configures Ethernet devices on cloud hosts that support IMDS, given that\n" + "# before doing IMDS we need to activate the network.\n", f); + + if (arg_network_mode != IMDS_NETWORK_UNLOCKED && + (in4_addr_is_set(&arg_address_ipv4) || in6_addr_is_set(&arg_address_ipv6))) + fputs("#\n" + "# Note: this will create a 'prohibit' route to the IMDS endpoint,\n" + "# blocking direct access to IMDS. Direct IMDS access is then only\n" + "# available to traffic marked with fwmark 0x7FFF0815, which can be\n" + "# set via SO_MARK and various other methods, which require\n" + "# privileges.\n", + f); + + fputs("\n" + "[Match]\n" + "Type=ether\n" + "Kind=!*\n" + "\n" + "[Network]\n" + "DHCP=yes\n" + "LinkLocalAddressing=ipv6\n" + "\n" + "[DHCP]\n" + "UseTimezone=yes\n" + "UseHostname=yes\n" + "UseMTU=yes\n", f); + + if (in4_addr_is_set(&arg_address_ipv4)) + fputs("\n" + "[Link]\n" + "RequiredFamilyForOnline=ipv4\n", f); + else if (in6_addr_is_set(&arg_address_ipv6)) + fputs("\n" + "[Link]\n" + "RequiredFamilyForOnline=ipv6\n", f); + + if (arg_network_mode != IMDS_NETWORK_UNLOCKED) { + if (in4_addr_is_set(&arg_address_ipv4)) + fprintf(f, + "\n" + "# Prohibit regular access to IMDS (IPv4)\n" + "[Route]\n" + "Destination=%s\n" + "Type=prohibit\n", + IN4_ADDR_TO_STRING(&arg_address_ipv4)); + + if (in6_addr_is_set(&arg_address_ipv6)) + fprintf(f, + "\n" + "# Prohibit regular access to IMDS (IPv6)\n" + "[Route]\n" + "Destination=%s\n" + "Type=prohibit\n", + IN6_ADDR_TO_STRING(&arg_address_ipv6)); + } + + if (in4_addr_is_set(&arg_address_ipv4)) + fprintf(f, + "\n" + "# Always allow IMDS access via a special routing table (IPv4)\n" + "[Route]\n" + "Destination=%s\n" + "Scope=link\n" + "Table=0x7FFF0815\n" + "\n" + "# Sockets marked with firewall mark 0x7FFF0815 get access to the IMDS route by\n" + "# using the 0x7FFF0815 table populated above.\n" + "[RoutingPolicyRule]\n" + "Family=ipv4\n" + "FirewallMark=0x7FFF0815\n" + "Table=0x7FFF0815\n", + IN4_ADDR_TO_STRING(&arg_address_ipv4)); + + if (in6_addr_is_set(&arg_address_ipv6)) + fprintf(f, + "\n" + "# Always allow IMDS access via a special routing table (IPv6)\n" + "[Route]\n" + "Destination=%s\n" + "Table=0x7FFF0815\n" + "\n" + "# Sockets marked with firewall mark 0x7FFF0815 get access to the IMDS route by\n" + "# using the 0x7FFF0815 table populated above.\n" + "[RoutingPolicyRule]\n" + "Family=ipv6\n" + "FirewallMark=0x7FFF0815\n" + "Table=0x7FFF0815\n", + IN6_ADDR_TO_STRING(&arg_address_ipv6)); + + if (fchmod(fileno(f), 0644) < 0) + return log_error_errno(errno, "Failed to set access mode for 85-imds-early.network: %m"); + + r = flink_tmpfile_at(f, network_dir_fd, t, "85-imds-early.network", LINK_TMPFILE_REPLACE); + if (r < 0) + return log_error_errno(r, "Failed to move 85-imds-early.network into place: %m"); + + t = mfree(t); /* disarm auto-cleanup */ + + log_info("Created 85-imds-early.network."); + return 0; +} + +static int add_address_to_json_array(sd_json_variant **array, int family, const union in_addr_union *addr) { + int r; + + assert(array); + assert(IN_SET(family, AF_INET, AF_INET6)); + assert(addr); + + /* Appends the specified IP address, turned into A/AAAA RRs to the specified JSON array */ + + if (in_addr_is_null(family, addr)) + return 0; + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + if (dns_resource_record_new_address(&rr, family, addr, "_imds") < 0) + return log_oom(); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *rrj = NULL; + r = dns_resource_record_to_json(rr, &rrj); + if (r < 0) + return log_error_errno(r, "Failed to convert A RR to JSON: %m"); + + r = sd_json_variant_append_array(array, rrj); + if (r < 0) + return log_error_errno(r, "Failed to append A RR to JSON array: %m"); + + log_debug("Writing IMDS RR for: %s", dns_resource_record_to_string(rr)); + return 1; +} + +static int setup_address_rrs(void) { + int r; + + /* Creates local RRs (honoured by systemd-resolved) for the IMDS endpoint addresses. */ + + if (arg_network_mode == IMDS_NETWORK_OFF) { + log_debug("IMDS networking turned off, not generating .rr file."); + return 0; + } + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *aj = NULL; + + union in_addr_union u = { .in = arg_address_ipv4 }; + r = add_address_to_json_array(&aj, AF_INET, &u); + if (r < 0) + return r; + + u = (union in_addr_union) { .in6 = arg_address_ipv6 }; + r = add_address_to_json_array(&aj, AF_INET6, &u); + if (r < 0) + return r; + + if (sd_json_variant_elements(aj) == 0) { + log_debug("No IMDS endpoint addresses known, not writing out RRs."); + return 0; + } + + _cleanup_free_ char *text = NULL; + r = sd_json_variant_format(aj, SD_JSON_FORMAT_NEWLINE, &text); + if (r < 0) + return log_error_errno(r, "Failed to format JSON text: %m"); + + r = write_string_file("/run/systemd/resolve/static.d/imds-endpoint.rr", text, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_MKDIR_0755); + if (r < 0) + return log_error_errno(r, "Failed to write IMDS RR data: %m"); + + log_info("Created imds-endpoint.rr."); + return 0; +} + +static int on_overall_timeout(sd_event_source *s, uint64_t usec, void *userdata) { + Context *c = ASSERT_PTR(userdata); + + assert(s); + + /* Invoked whenever the overall time-out event elapses, and we just give up */ + + context_fail_full(c, context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(ETIMEDOUT), "Overall timeout reached."), "io.systemd.InstanceMetadata.Timeout"); + return 0; +} + +static int context_start_overall_timeout(Context *c, usec_t usec) { + int r; + + assert(c); + + r = event_reset_time_relative( + c->event, + &c->overall_timeout_source, + CLOCK_BOOTTIME, + usec, + /* accuracy= */ 0, + on_overall_timeout, + c, + /* priority= */ 0, + "imds-overall-timeout", + /* force_reset= */ true); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to reset retry timer event source: %m"); + + return 0; +} + +static int cmdline_run(void) { + int r; + + /* Process the request when invoked via the command line (i.e. not via Varlink) */ + + r = imds_configured(LOG_ERR); + if (r < 0) + return r; + + if (arg_setup_network) { + r = setup_network(); + return RET_GATHER(r, setup_address_rrs()); + } + + assert(arg_key || arg_well_known >= 0); + + _cleanup_(context_done) Context c = CONTEXT_NULL; + c.write_stdout = true; + context_new_request(&c); + + c.well_known = arg_well_known; + if (arg_key) { + c.key = strdup(arg_key); + if (!c.key) + return context_log_oom(&c); + } + + if (arg_ifname) { + c.ifindex = rtnl_resolve_interface_or_warn(&c.rtnl, arg_ifname); + if (c.ifindex < 0) + return c.ifindex; + } else { + /* Try to load the previously cached interface */ + r = context_load_ifname(&c); + if (r < 0) + return r; + } + + r = sd_event_default(&c.event); + if (r < 0) + return context_log_errno(&c, LOG_ERR, r, "Failed to allocate event loop: %m"); + + if (c.ifindex > 0) { + CacheResult cr = context_process_cache(&c); + if (cr < 0) + return cr; + if (cr == CACHE_RESULT_HIT) + return 0; + if (cr == CACHE_RESULT_KEY_NOT_FOUND) + return context_log_errno(&c, LOG_ERR, SYNTHETIC_ERRNO(ENOENT), "Cache reports: key not found"); + + r = context_acquire_token(&c); + if (r < 0) + return r; + + r = context_acquire_data(&c); + if (r < 0) + return r; + + r = context_start_overall_timeout(&c, DIRECT_OVERALL_TIMEOUT_USEC); + if (r < 0) + return r; + } else { + /* Couldn't find anything, let's spawn off parallel clients for all interfaces */ + r = context_spawn_children(&c); + if (r < 0) + return r; + + r = context_start_overall_timeout(&c, INDIRECT_OVERALL_TIMEOUT_USEC); + if (r < 0) + return r; + } + + r = sd_event_loop(c.event); + if (r < 0) + return r; + + return 0; +} + +static int context_acquire_system_bus(Context *c) { + int r; + + assert(c); + + /* Connect to the bus if we haven't yet */ + + if (c->system_bus) + return 0; + + r = sd_bus_default_system(&c->system_bus); + if (r < 0) + return r; + + r = sd_bus_attach_event(c->system_bus, c->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return r; + + return 0; +} + +static JSON_DISPATCH_ENUM_DEFINE(dispatch_well_known, ImdsWellKnown, imds_well_known_from_string); + +static int dispatch_fwmark(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + Context *c = ASSERT_PTR(userdata); + int r; + + /* Parses a firewall mark passed via Varlink/JSON. Note that any 32bit fwmark is valid, hence we keep + * track if it is set or not in a separate boolean. */ + + if (sd_json_variant_is_null(variant)) { + c->fwmark_set = false; + return 0; + } + + r = sd_json_dispatch_uint32(name, variant, flags, &c->fwmark); + if (r < 0) + return r; + + c->fwmark_set = true; + return 0; +} + +static int vl_method_get(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + Context *c = ASSERT_PTR(userdata); + int r; + + assert(link); + + if (!c->event) + c->event = sd_event_ref(sd_varlink_get_event(link)); + + context_new_request(c); + + static const sd_json_dispatch_field dispatch_table[] = { + { "wellKnown", SD_JSON_VARIANT_STRING, dispatch_well_known, offsetof(Context, well_known), 0 }, + { "key", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(Context, key), 0 }, + { "interface", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(Context, ifindex), 0 }, + { "refreshUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(Context, refresh_usec), 0 }, + { "firewallMark", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_fwmark, 0, 0 }, + { "cache", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(Context, cache), 0 }, + { "wait", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(Context, wait), 0 }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, c); + if (r != 0) + return r; + + if (c->key) { + if (!imds_key_is_valid(c->key)) + return sd_varlink_error_invalid_parameter_name(link, "key"); + + if (c->well_known < 0) + c->well_known = IMDS_BASE; + else if (!imds_well_known_can_suffix(c->well_known)) + return sd_varlink_error_invalid_parameter_name(link, "key"); + } else if (c->well_known < 0) + return sd_varlink_error_invalid_parameter_name(link, "key"); + + if (c->refresh_usec < REFRESH_USEC_MIN) + c->refresh_usec = REFRESH_USEC_MIN; + + uid_t peer_uid; + r = sd_varlink_get_peer_uid(link, &peer_uid); + if (r < 0) + return r; + + if (peer_uid != 0 && peer_uid != getuid()) { + /* Ask polkit if client is not privileged */ + + r = context_acquire_system_bus(c); + if (r < 0) + return r; + + const char* l[5]; + size_t k = 0; + if (c->well_known >= 0) { + l[k++] = "wellKnown"; + l[k++] = imds_well_known_to_string(c->well_known); + } + if (c->key) { + l[k++] = "key"; + l[k++] = c->key; + } + l[k] = NULL; + + r = varlink_verify_polkit_async( + link, + c->system_bus, + "io.systemd.imds.get", + l, + &c->polkit_registry); + if (r <= 0) + return r; + } + + if (imds_configured(LOG_DEBUG) < 0) + return sd_varlink_error(link, "io.systemd.InstanceMetadata.NotSupported", NULL); + + /* Up to this point we only validated/parsed stuff. Now we actually execute stuff, hence from now on + * we need to go through context_fail() when failing (context_success() if we succeed early), to + * release resources we might have allocated. */ + assert(!c->current_link); + c->current_link = sd_varlink_ref(link); + + _cleanup_free_ char *k = NULL; /* initialize here, to avoid that this remains uninitialized due to the gotos below */ + + if (c->ifindex <= 0) { + /* Try to load the previously used network interface */ + r = context_load_ifname(c); + if (r < 0) + goto fail; + } + + r = context_combine_key(c, &k); + if (r == -ENODATA) { + context_fail_full(c, r, "io.systemd.InstanceMetadata.WellKnownKeyUnset"); + return r; + } + if (r < 0) + goto fail; + + context_log(c, LOG_DEBUG, "Will request '%s' now.", k); + + if (c->ifindex > 0) { + CacheResult cr = context_process_cache(c); + if (cr < 0) { + r = cr; + goto fail; + } + if (cr == CACHE_RESULT_HIT) { + context_success(c); + return 0; + } + if (cr == CACHE_RESULT_KEY_NOT_FOUND) { + r = context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(ENOENT), "Cache reports: key not found"); + context_fail_full(c, r, "io.systemd.InstanceMetadata.KeyNotFound"); + return r; + } + + r = context_acquire_token(c); + if (r < 0) + goto fail; + + r = context_acquire_data(c); + if (r < 0) + goto fail; + + r = context_start_overall_timeout(c, DIRECT_OVERALL_TIMEOUT_USEC); + if (r < 0) + goto fail; + } else { + r = context_spawn_children(c); + if (r < 0) + goto fail; + + r = context_start_overall_timeout(c, INDIRECT_OVERALL_TIMEOUT_USEC); + if (r < 0) + goto fail; + } + + context_log(c, LOG_DEBUG, "Incoming method call is now pending"); + return 1; + +fail: + context_fail(c, r); + return r; +} + +static int vl_method_get_vendor_info(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + Context *c = ASSERT_PTR(userdata); + int r; + + assert(link); + + r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, c); + if (r != 0) + return r; + + /* NB! We allow access to this call without Polkit */ + + if (imds_configured(LOG_DEBUG) < 0) + return sd_varlink_error(link, "io.systemd.InstanceMetadata.NotSupported", NULL); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *wkj = NULL; + for (ImdsWellKnown i = 0; i < _IMDS_WELL_KNOWN_MAX; i++) { + if (!arg_well_known_key[i]) + continue; + + r = sd_json_variant_set_field_string(&wkj, imds_well_known_to_string(i), arg_well_known_key[i]); + if (r < 0) + return r; + } + + return sd_varlink_replybo( + link, + JSON_BUILD_PAIR_STRING_NON_EMPTY("vendor", arg_vendor), + JSON_BUILD_PAIR_STRING_NON_EMPTY("tokenUrl", arg_token_url), + JSON_BUILD_PAIR_STRING_NON_EMPTY("refreshHeaderName", arg_refresh_header_name), + JSON_BUILD_PAIR_STRING_NON_EMPTY("dataUrl", arg_data_url), + JSON_BUILD_PAIR_STRING_NON_EMPTY("dataUrlSuffix", arg_data_url_suffix), + JSON_BUILD_PAIR_STRING_NON_EMPTY("tokenHeaderName", arg_token_header_name), + JSON_BUILD_PAIR_STRV_NON_EMPTY("extraHeader", arg_extra_header), + JSON_BUILD_PAIR_IN4_ADDR_NON_NULL("addressIPv4", &arg_address_ipv4), + JSON_BUILD_PAIR_IN6_ADDR_NON_NULL("addressIPv6", &arg_address_ipv6), + JSON_BUILD_PAIR_VARIANT_NON_EMPTY("wellKnown", wkj)); +} + +static int vl_server(void) { + _cleanup_(context_done) Context c = CONTEXT_NULL; + int r; + + /* Invocation as Varlink service */ + + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; + r = varlink_server_new( + &varlink_server, + SD_VARLINK_SERVER_INHERIT_USERDATA, + &c); + 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_InstanceMetadata); + 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.InstanceMetadata.Get", vl_method_get, + "io.systemd.InstanceMetadata.GetVendorInfo", vl_method_get_vendor_info); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink methods: %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 help(void) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("systemd-imdsd@.service", "8", &link); + if (r < 0) + return log_oom(); + + printf("%1$s [OPTIONS...] KEY\n" + "\n%5$sLow-level IMDS data acquisition.%6$s\n" + "\n%3$sOptions:%4$s\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -i --interface=INTERFACE\n" + " Use the specified interface\n" + " --refresh=SEC Set token refresh time\n" + " --fwmark=INTEGER Choose firewall mark for HTTP traffic\n" + " --cache=no Disable cache use\n" + " -w --wait=yes Wait for connectivity\n" + " -K --well-known= Select well-known key\n" + " --setup-network Generate .network and .rr files\n" + "\n%3$sManual Endpoint Configuration:%4$s\n" + " --vendor=VENDOR Specify IMDS vendor literally\n" + " --token-url=URL URL for acquiring token\n" + " --refresh-header-name=NAME\n" + " Header name for passing refresh time\n" + " --data-url=URL Base URL for acquiring data\n" + " --data-url-suffix=STRING\n" + " Suffix to append to data URL\n" + " --token-header-name=NAME\n" + " Header name for passing token string\n" + " --extra-header='NAME: VALUE'\n" + " Additional header to pass to data transfer\n" + " --address-ipv4=ADDRESS\n" + " --address-ipv6=ADDRESS\n" + " Configure the IPv4 and IPv6 address of the IMDS server\n" + " --well-known-key=NAME:KEY\n" + " Configure the location of well-known keys\n" + "\nSee the %2$s for details.\n", + program_invocation_short_name, + link, + ansi_underline(), + ansi_normal(), + ansi_highlight(), + ansi_normal()); + + return 0; +} + +static bool http_header_name_valid(const char *a) { + return a && ascii_is_valid(a) && !string_has_cc(a, /* ok= */ NULL) && !strchr(a, ':'); +} + +static bool http_header_valid(const char *a) { + return a && ascii_is_valid(a) && !string_has_cc(a, /* ok= */ NULL) && strchr(a, ':'); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_REFRESH, + ARG_FWMARK, + ARG_CACHE, + ARG_WAIT, + ARG_VENDOR, + ARG_TOKEN_URL, + ARG_REFRESH_HEADER_NAME, + ARG_DATA_URL, + ARG_DATA_URL_SUFFIX, + ARG_TOKEN_HEADER_NAME, + ARG_EXTRA_HEADER, + ARG_ADDRESS_IPV4, + ARG_ADDRESS_IPV6, + ARG_WELL_KNOWN_KEY, + ARG_SETUP_NETWORK, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "interface", required_argument, NULL, 'i' }, + { "refresh", required_argument, NULL, ARG_REFRESH }, + { "fwmark", required_argument, NULL, ARG_FWMARK }, + { "cache", required_argument, NULL, ARG_CACHE }, + { "wait", required_argument, NULL, ARG_WAIT }, + { "well-known", required_argument, NULL, 'K' }, + { "setup-network", no_argument, NULL, ARG_SETUP_NETWORK }, + + /* The following all configure endpoint information explicitly */ + { "vendor", required_argument, NULL, ARG_VENDOR }, + { "token-url", required_argument, NULL, ARG_TOKEN_URL }, + { "refresh-header-name", required_argument, NULL, ARG_REFRESH_HEADER_NAME }, + { "data-url", required_argument, NULL, ARG_DATA_URL }, + { "data-url-suffix", required_argument, NULL, ARG_DATA_URL_SUFFIX }, + { "token-header-name", required_argument, NULL, ARG_TOKEN_HEADER_NAME }, + { "extra-header", required_argument, NULL, ARG_EXTRA_HEADER }, + { "address-ipv4", required_argument, NULL, ARG_ADDRESS_IPV4 }, + { "address-ipv6", required_argument, NULL, ARG_ADDRESS_IPV6 }, + { "well-known-key", required_argument, NULL, ARG_WELL_KNOWN_KEY }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hi:wK:", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + return help(); + + case ARG_VERSION: + return version(); + + case 'i': + if (isempty(optarg)) { + arg_ifname = mfree(arg_ifname); + break; + } + + if (!ifname_valid_full(optarg, IFNAME_VALID_ALTERNATIVE|IFNAME_VALID_NUMERIC)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Interface name '%s' is not valid.", optarg); + + r = free_and_strdup_warn(&arg_ifname, optarg); + if (r < 0) + return r; + + break; + + case ARG_REFRESH: { + if (isempty(optarg)) { + arg_refresh_usec = REFRESH_USEC_DEFAULT; + break; + } + + usec_t t; + r = parse_sec(optarg, &t); + if (r < 0) + return log_error_errno(r, "Failed to parse refresh timeout: %s", optarg); + if (t < REFRESH_USEC_MIN) { + log_warning("Increasing specified refresh time to %s, lower values are not supported.", FORMAT_TIMESPAN(REFRESH_USEC_MIN, 0)); + arg_refresh_usec = REFRESH_USEC_MIN; + } else + arg_refresh_usec = t; + break; + } + + case ARG_FWMARK: + if (isempty(optarg)) { + arg_fwmark_set = false; + break; + } + + if (streq(optarg, "default")) { + arg_fwmark = FWMARK_DEFAULT; + arg_fwmark_set = true; + break; + } + + r = safe_atou32(optarg, &arg_fwmark); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse --fwmark= parameter: %s", optarg); + + arg_fwmark_set = true; + break; + + case ARG_CACHE: + r = parse_boolean_argument("--cache", optarg, &arg_cache); + if (r < 0) + return r; + + break; + + case ARG_WAIT: + r = parse_boolean_argument("--wait", optarg, &arg_wait); + if (r < 0) + return r; + + break; + + case 'w': + arg_wait = true; + break; + + case 'K': { + if (isempty(optarg)) { + arg_well_known = _IMDS_WELL_KNOWN_INVALID; + break; + } + + ImdsWellKnown wk = imds_well_known_from_string(optarg); + if (wk < 0) + return log_error_errno(wk, "Failed to parse --well-known= parameter: %m"); + + arg_well_known = wk; + break; + } + + case ARG_VENDOR: + if (isempty(optarg)) { + arg_vendor = mfree(arg_vendor); + break; + } + + r = free_and_strdup_warn(&arg_vendor, optarg); + if (r < 0) + return r; + break; + + case ARG_TOKEN_URL: + if (isempty(optarg)) { + arg_token_url = mfree(arg_token_url); + break; + } + + if (!http_url_is_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid URL: %s", optarg); + + r = free_and_strdup_warn(&arg_token_url, optarg); + if (r < 0) + return r; + + break; + + case ARG_REFRESH_HEADER_NAME: + if (isempty(optarg)) { + arg_refresh_header_name = mfree(arg_refresh_header_name); + break; + } + + if (!http_header_name_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header name: %s", optarg); + + r = free_and_strdup_warn(&arg_refresh_header_name, optarg); + if (r < 0) + return r; + + break; + + case ARG_DATA_URL: + if (isempty(optarg)) { + arg_data_url = mfree(arg_data_url); + break; + } + + if (!http_url_is_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid URL: %s", optarg); + + r = free_and_strdup_warn(&arg_data_url, optarg); + if (r < 0) + return r; + + break; + + case ARG_DATA_URL_SUFFIX: + if (isempty(optarg)) { + arg_data_url_suffix = mfree(arg_data_url_suffix); + break; + } + + if (!ascii_is_valid(optarg) || string_has_cc(optarg, /* ok= */ NULL)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid URL suffix: %s", optarg); + + r = free_and_strdup_warn(&arg_data_url_suffix, optarg); + if (r < 0) + return r; + + break; + + case ARG_TOKEN_HEADER_NAME: + if (isempty(optarg)) { + arg_token_header_name = mfree(arg_token_header_name); + break; + } + + if (!http_header_name_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header name: %s", optarg); + + r = free_and_strdup_warn(&arg_token_header_name, optarg); + if (r < 0) + return r; + + break; + + case ARG_EXTRA_HEADER: + if (isempty(optarg)) { + arg_extra_header = strv_free(arg_extra_header); + break; + } + + if (!http_header_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header: %s", optarg); + + if (strv_extend(&arg_extra_header, optarg) < 0) + return log_oom(); + + break; + + case ARG_ADDRESS_IPV4: { + if (isempty(optarg)) { + arg_address_ipv4 = (struct in_addr) {}; + break; + } + + union in_addr_union u; + r = in_addr_from_string(AF_INET, optarg, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse IPv4 address: %s", optarg); + arg_address_ipv4 = u.in; + break; + } + + case ARG_ADDRESS_IPV6: { + if (isempty(optarg)) { + arg_address_ipv6 = (struct in6_addr) {}; + break; + } + + union in_addr_union u; + r = in_addr_from_string(AF_INET6, optarg, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse IPv6 address: %s", optarg); + arg_address_ipv6 = u.in6; + break; + } + + case ARG_WELL_KNOWN_KEY: { + if (isempty(optarg)) { + for (ImdsWellKnown wk = 0; wk < _IMDS_WELL_KNOWN_MAX; wk++) + arg_well_known_key[wk] = mfree(arg_well_known_key[wk]); + break; + } + + const char *e = strchr(optarg, ':'); + if (!e) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--well-known-key= expects colon separated name and key pairs."); + + _cleanup_free_ char *name = strndup(optarg, e - optarg); + if (!name) + return log_oom(); + + ImdsWellKnown wk = imds_well_known_from_string(name); + if (wk < 0) + return log_error_errno(wk, "Failed to parse --well-known-key= argument: %m"); + + e++; + if (!imds_key_is_valid(e)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Well known key '%s' is not valid.", e); + + r = free_and_strdup_warn(arg_well_known_key + wk, e); + if (r < 0) + return r; + + break; + } + + case ARG_SETUP_NETWORK: + arg_setup_network = true; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + } + + if (arg_vendor || arg_token_url || arg_refresh_header_name || arg_data_url || arg_data_url_suffix || arg_token_header_name || arg_extra_header) + arg_endpoint_source = ENDPOINT_USER; + + 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"); + + arg_varlink = r; + + if (!arg_varlink) { + + if (arg_setup_network) { + if (optind != argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No argument expected."); + } else { + if (arg_well_known < 0) { + /* if no --well-known= parameter was specified we require an argument */ + if (argc != optind+1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "A single argument expected."); + } else if (argc > optind+1) /* if not, then the additional parameter is optional */ + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "At most a single argument expected."); + + if (argc > optind) { + if (!imds_key_is_valid(argv[optind])) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified IMDS key is not valid, refusing: %s", argv[optind]); + + r = free_and_strdup_warn(&arg_key, argv[optind]); + if (r < 0) + return r; + } + } + } + + return 1; +} + +static int device_get_property_ip_address( + sd_device *d, + const char *name, + int family, + union in_addr_union *ret) { + + int r; + + /* Parses an IP address stored in the udev database for a device */ + + assert(d); + assert(name); + assert(IN_SET(family, AF_INET, AF_INET6)); + + const char *v = NULL; + r = sd_device_get_property_value(d, name, &v); + if (r < 0) + return r; + + return in_addr_from_string(family, v, ret); +} + +static const char * const imds_well_known_udev_table[_IMDS_WELL_KNOWN_MAX] = { + [IMDS_HOSTNAME] = "IMDS_KEY_HOSTNAME", + [IMDS_REGION] = "IMDS_KEY_REGION", + [IMDS_ZONE] = "IMDS_KEY_ZONE", + [IMDS_IPV4_PUBLIC] = "IMDS_KEY_IPV4_PUBLIC", + [IMDS_IPV6_PUBLIC] = "IMDS_KEY_IPV6_PUBLIC", + [IMDS_SSH_KEY] = "IMDS_KEY_SSH_KEY", + [IMDS_USERDATA] = "IMDS_KEY_USERDATA", + [IMDS_USERDATA_BASE] = "IMDS_KEY_USERDATA_BASE", + [IMDS_USERDATA_BASE64] = "IMDS_KEY_USERDATA_BASE64", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(imds_well_known_udev, ImdsWellKnown); + +static int smbios_server_info(void) { + int r; + + /* Acquires IMDS server information from udev/hwdb */ + + if (arg_endpoint_source >= 0) + return 0; + + _cleanup_(sd_device_unrefp) sd_device *d = NULL; + r = sd_device_new_from_syspath(&d, "/sys/class/dmi/id/"); + if (ERRNO_IS_NEG_DEVICE_ABSENT(r)) { + log_debug_errno(r, "Failed to open /sys/class/dmi/id/ device, ignoring: %m"); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to open /sys/class/dmi/id/ device: %m"); + + const char *vendor; + r = sd_device_get_property_value(d, "IMDS_VENDOR", &vendor); + if (r == -ENOENT) { + log_debug_errno(r, "IMDS_VENDOR= property not set on DMI device, skipping."); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to read IMDS_VENDOR= property of DMI device: %m"); + + log_debug("Detected IMDS vendor support '%s'.", vendor); + + r = free_and_strdup_warn(&arg_vendor, vendor); + if (r < 0) + return r; + + struct { + const char *property; + char **variable; + } table[] = { + { "IMDS_TOKEN_URL", &arg_token_url }, + { "IMDS_REFRESH_HEADER_NAME", &arg_refresh_header_name }, + { "IMDS_DATA_URL", &arg_data_url }, + { "IMDS_DATA_URL_SUFFIX", &arg_data_url_suffix }, + { "IMDS_TOKEN_HEADER_NAME", &arg_token_header_name }, + }; + + FOREACH_ELEMENT(i, table) { + const char *v = NULL; + + r = sd_device_get_property_value(d, i->property, &v); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to read property '%s' of DMI: %m", i->property); + + r = free_and_strdup_warn(i->variable, v); + if (r < 0) + return r; + } + + for (size_t i = 0; i < 64U; i++) { + _cleanup_free_ char *property = NULL; + const char *p = NULL; + if (i > 0) { + if (asprintf(&property, "IMDS_EXTRA_HEADER%zu", i + 1) < 0) + return log_oom(); + p = property; + } else + p = "IMDS_EXTRA_HEADER"; + + const char *v = NULL; + r = sd_device_get_property_value(d, p, &v); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to read property '%s' of DMI: %m", p); + + if (v) + if (strv_extend(&arg_extra_header, v) < 0) + return log_oom(); + } + + union in_addr_union u; + r = device_get_property_ip_address(d, "IMDS_ADDRESS_IPV4", AF_INET, &u); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to read property 'IMDS_ADDRESS_IPV4' of DMI: %m"); + else if (r >= 0) + arg_address_ipv4 = u.in; + + r = device_get_property_ip_address(d, "IMDS_ADDRESS_IPV6", AF_INET6, &u); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to read property 'IMDS_ADDRESS_IPV6' of DMI: %m"); + else if (r >= 0) + arg_address_ipv6 = u.in6; + + for (ImdsWellKnown k = 0; k < _IMDS_WELL_KNOWN_MAX; k++) { + const char *p = imds_well_known_udev_to_string(k); + if (!p) + continue; + + const char *v = NULL; + r = sd_device_get_property_value(d, p, &v); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to read property '%s' of DMI: %m", p); + + r = free_and_strdup_warn(arg_well_known_key + k, v); + if (r < 0) + return r; + } + + log_debug("IMDS endpoint data set from SMBIOS device."); + arg_endpoint_source = ENDPOINT_UDEV; + return 0; +} + +static int secure_getenv_ip_address( + const char *name, + int family, + union in_addr_union *ret) { + + assert(name); + assert(IN_SET(family, AF_INET, AF_INET6)); + + /* Parses an IP address specified in an environment variable */ + + const char *e = secure_getenv(name); + if (!e) + return -ENXIO; + + return in_addr_from_string(family, e, ret); +} + +static const char * const imds_well_known_environment_table[_IMDS_WELL_KNOWN_MAX] = { + [IMDS_HOSTNAME] = "SYSTEMD_IMDS_KEY_HOSTNAME", + [IMDS_REGION] = "SYSTEMD_IMDS_KEY_REGION", + [IMDS_ZONE] = "SYSTEMD_IMDS_KEY_ZONE", + [IMDS_IPV4_PUBLIC] = "SYSTEMD_IMDS_KEY_IPV4_PUBLIC", + [IMDS_IPV6_PUBLIC] = "SYSTEMD_IMDS_KEY_IPV6_PUBLIC", + [IMDS_SSH_KEY] = "SYSTEMD_IMDS_KEY_SSH_KEY", + [IMDS_USERDATA] = "SYSTEMD_IMDS_KEY_USERDATA", + [IMDS_USERDATA_BASE] = "SYSTEMD_IMDS_KEY_USERDATA_BASE", + [IMDS_USERDATA_BASE64] = "SYSTEMD_IMDS_KEY_USERDATA_BASE64", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(imds_well_known_environment, ImdsWellKnown); + +static int environment_server_info(void) { + int r; + + /* Acquires IMDS endpoint info from environment variables */ + + if (arg_endpoint_source >= 0) + return 0; + + static const struct { + const char *name; + char **variable; + } table[] = { + { "SYSTEMD_IMDS_VENDOR", &arg_vendor }, + { "SYSTEMD_IMDS_TOKEN_URL", &arg_token_url }, + { "SYSTEMD_IMDS_REFRESH_HEADER_NAME", &arg_refresh_header_name }, + { "SYSTEMD_IMDS_DATA_URL", &arg_data_url }, + { "SYSTEMD_IMDS_DATA_URL_SUFFIX", &arg_data_url_suffix }, + { "SYSTEMD_IMDS_TOKEN_HEADER_NAME", &arg_token_header_name }, + }; + + FOREACH_ELEMENT(i, table) { + const char *e = secure_getenv(i->name); + if (!e) + continue; + + r = free_and_strdup_warn(i->variable, e); + if (r < 0) + return r; + + arg_endpoint_source = ENDPOINT_ENVIRONMENT; + } + + for (unsigned u = 1; u < 64; u++) { + _cleanup_free_ char *name = NULL; + + if (u > 1 && asprintf(&name, "SYSTEMD_IMDS_EXTRA_HEADER%u", u) < 0) + return log_oom(); + + const char *e = secure_getenv(name ?: "SYSTEMD_IMDS_EXTRA_HEADER"); + if (!e) + break; + + if (strv_extend(&arg_extra_header, e) < 0) + return log_oom(); + + arg_endpoint_source = ENDPOINT_ENVIRONMENT; + } + + union in_addr_union u; + r = secure_getenv_ip_address("SYSTEMD_IMDS_ADDRESS_IPV4", AF_INET, &u); + if (r < 0 && r != -ENXIO) + return log_error_errno(r, "Failed read IPv4 address from environment variable 'SYSTEMD_IMDS_ADDRESS_IPV4': %m"); + if (r >= 0) { + arg_address_ipv4 = u.in; + arg_endpoint_source = ENDPOINT_ENVIRONMENT; + } + + r = secure_getenv_ip_address("SYSTEMD_IMDS_ADDRESS_IPV6", AF_INET6, &u); + if (r < 0 && r != -ENXIO) + return log_error_errno(r, "Failed read IPv6 address from environment variable 'SYSTEMD_IMDS_ADDRESS_IPV6': %m"); + if (r >= 0) { + arg_address_ipv6 = u.in6; + arg_endpoint_source = ENDPOINT_ENVIRONMENT; + } + + for (ImdsWellKnown k = 0; k < _IMDS_WELL_KNOWN_MAX; k++) { + const char *n = imds_well_known_environment_to_string(k); + if (!n) + continue; + + const char *e = secure_getenv(n); + if (!e) + continue; + + r = free_and_strdup_warn(arg_well_known_key + k, e); + if (r < 0) + return r; + + arg_endpoint_source = ENDPOINT_ENVIRONMENT; + } + + if (arg_endpoint_source >= 0) + log_debug("IMDS endpoint data set from environment."); + + return 0; +} + +static int read_credential_ip_address( + const char *name, + int family, + union in_addr_union *ret) { + + int r; + + assert(name); + assert(IN_SET(family, AF_INET, AF_INET6)); + + /* Parses an IP address specified in a credential */ + + _cleanup_free_ char *s = NULL; + r = read_credential(name, (void**) &s, /* ret_size= */ NULL); + if (r < 0) + return r; + + return in_addr_from_string(family, s, ret); +} + +static const char * const imds_well_known_credential_table[_IMDS_WELL_KNOWN_MAX] = { + [IMDS_HOSTNAME] = "imds.key_hostname", + [IMDS_REGION] = "imds.key_region", + [IMDS_ZONE] = "imds.key_zone", + [IMDS_IPV4_PUBLIC] = "imds.key_ipv4_public", + [IMDS_IPV6_PUBLIC] = "imds.key_ipv6_public", + [IMDS_SSH_KEY] = "imds.key_ssh_key", + [IMDS_USERDATA] = "imds.key_userdata", + [IMDS_USERDATA_BASE] = "imds.key_userdata_base", + [IMDS_USERDATA_BASE64] = "imds.key_userdata_base64", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(imds_well_known_credential, ImdsWellKnown); + +static int credential_server_info(void) { + int r; + + /* Acquires IMDS endpoint info from credentials */ + + if (arg_endpoint_source >= 0) + return 0; + + static const struct { + const char *name; + char **variable; + } table[] = { + { "imds.vendor", &arg_vendor }, + { "imds.vendor_token", &arg_token_url }, + { "imds.refresh_header_name", &arg_refresh_header_name }, + { "imds.data_url", &arg_data_url }, + { "imds.data_url_suffix", &arg_data_url_suffix }, + { "imds.token_header_name", &arg_token_header_name }, + }; + + FOREACH_ELEMENT(i, table) { + _cleanup_free_ char *s = NULL; + + r = read_credential(i->name, (void**) &s, /* ret_size= */ NULL); + if (r == -ENOENT) + continue; + if (r < 0) { + log_warning_errno(r, "Failed to read credential '%s', ignoring: %m", i->name); + continue; + } + + r = free_and_strdup_warn(i->variable, s); + if (r < 0) + return r; + + arg_endpoint_source = ENDPOINT_CREDENTIALS; + } + + for (unsigned u = 1; u < 64; u++) { + _cleanup_free_ char *name = NULL; + if (u > 1 && asprintf(&name, "imds.extra_header%u", u) < 0) + return log_oom(); + + const char *n = name ?: "imds.extra_header"; + + _cleanup_free_ char *s = NULL; + r = read_credential(n, (void**) &s, /* ret_size= */ NULL); + if (r == -ENOENT) + continue; + if (r < 0) { + log_warning_errno(r, "Failed to read credential '%s', ignoring: %m", n); + continue; + } + + if (strv_extend(&arg_extra_header, s) < 0) + return log_oom(); + + arg_endpoint_source = ENDPOINT_CREDENTIALS; + } + + union in_addr_union u; + r = read_credential_ip_address("imds.address_ipv4", AF_INET, &u); + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed read IPv4 address from credential 'imds.address_ipv4', ignoring: %m"); + if (r >= 0) { + arg_address_ipv4 = u.in; + arg_endpoint_source = ENDPOINT_CREDENTIALS; + } + + r = read_credential_ip_address("imds.address_ipv6", AF_INET6, &u); + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed read IPv6 address from credential 'imds.address_ipv6', ignoring: %m"); + if (r >= 0) { + arg_address_ipv6 = u.in6; + arg_endpoint_source = ENDPOINT_CREDENTIALS; + } + + for (ImdsWellKnown k = 0; k < _IMDS_WELL_KNOWN_MAX; k++) { + const char *n = imds_well_known_credential_to_string(k); + if (!n) + continue; + + _cleanup_free_ char *s = NULL; + r = read_credential(n, (void**) &s, /* ret_size= */ NULL); + if (r == -ENOENT) + continue; + if (r < 0) { + log_warning_errno(r, "Failed to read credential '%s', ignoring: %m", n); + continue; + } + + free_and_replace(arg_well_known_key[k], s); + arg_endpoint_source = ENDPOINT_CREDENTIALS; + } + + if (arg_endpoint_source >= 0) + log_debug("IMDS endpoint data set from credentials."); + + return 0; +} + +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { + int r; + + assert(key); + + /* Called for each kernel command line option. */ + + if (proc_cmdline_key_streq(key, "systemd.imds.network")) { + if (proc_cmdline_value_missing(key, value)) + return 0; + + ImdsNetworkMode m = imds_network_mode_from_string(value); + if (m < 0) + return log_warning_errno(m, "Failed to parse systemd.imds.network= value: %m"); + + arg_network_mode = m; + return 0; + } + + /* The other kernel command line options configured IMDS endpoint data. We'll only check it if no + * other configuration source for it has been used */ + if (arg_endpoint_source >= 0 && arg_endpoint_source != ENDPOINT_PROC_CMDLINE) + return 0; + + static const struct { + const char *key; + char **variable; + } table[] = { + { "systemd.imds.vendor", &arg_vendor }, + { "systemd.imds.token_url", &arg_token_url }, + { "systemd.imds.refresh_header_name", &arg_refresh_header_name }, + { "systemd.imds.data_url", &arg_data_url }, + { "systemd.imds.data_url_suffix", &arg_data_url_suffix }, + { "systemd.imds.token_header_name", &arg_token_header_name }, + }; + + FOREACH_ELEMENT(i, table) { + if (!proc_cmdline_key_streq(key, i->key)) + continue; + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = free_and_strdup_warn(i->variable, value); + if (r < 0) + return r; + + arg_endpoint_source = ENDPOINT_PROC_CMDLINE; + return 0; + } + + if (proc_cmdline_key_streq(key, "systemd.imds.extra_header")) { + if (proc_cmdline_value_missing(key, value)) + return 0; + + if (isempty(value)) + arg_extra_header = strv_free(arg_extra_header); + else if (strv_extend(&arg_extra_header, value) < 0) + return log_oom(); + + arg_endpoint_source = ENDPOINT_PROC_CMDLINE; + return 0; + } + + if (proc_cmdline_key_streq(key, "systemd.imds.address_ipv4")) { + if (proc_cmdline_value_missing(key, value)) + return 0; + + union in_addr_union u; + r = in_addr_from_string(AF_INET, value, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse 'systemd.imds.address_ipv4=' parameter: %s", value); + + arg_address_ipv4 = u.in; + arg_endpoint_source = ENDPOINT_PROC_CMDLINE; + return 0; + } + + if (proc_cmdline_key_streq(key, "systemd.imds.address_ipv6")) { + if (proc_cmdline_value_missing(key, value)) + return 0; + + union in_addr_union u; + r = in_addr_from_string(AF_INET6, value, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse 'systemd.imds.address_ipv6=' parameter: %s", value); + + arg_address_ipv6 = u.in6; + arg_endpoint_source = ENDPOINT_PROC_CMDLINE; + return 0; + } + + static const char * const well_known_table[_IMDS_WELL_KNOWN_MAX] = { + [IMDS_HOSTNAME] = "systemd.imds.key.hostname", + [IMDS_REGION] = "systemd.imds.key.region", + [IMDS_ZONE] = "systemd.imds.key.zone", + [IMDS_IPV4_PUBLIC] = "systemd.imds.key.ipv4_public", + [IMDS_IPV6_PUBLIC] = "systemd.imds.key.ipv6_public", + [IMDS_SSH_KEY] = "systemd.imds.key.ssh_key", + [IMDS_USERDATA] = "systemd.imds.key.userdata", + [IMDS_USERDATA_BASE] = "systemd.imds.key.userdata_base", + [IMDS_USERDATA_BASE64] = "systemd.imds.key.userdata_base64", + }; + + for (ImdsWellKnown wk = 0; wk < _IMDS_WELL_KNOWN_MAX; wk++) { + const char *k = well_known_table[wk]; + if (!k) + continue; + + if (!proc_cmdline_key_streq(key, k)) + continue; + + r = free_and_strdup_warn(arg_well_known_key + wk, value); + if (r < 0) + return r; + + arg_endpoint_source = ENDPOINT_PROC_CMDLINE; + return 0; + } + + return 0; +} + +static int run(int argc, char* argv[]) { + int r; + + log_setup(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + r = environment_server_info(); + if (r < 0) + return r; + + r = proc_cmdline_parse(parse_proc_cmdline_item, /* userdata= */ NULL, PROC_CMDLINE_STRIP_RD_PREFIX); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + + r = credential_server_info(); + if (r < 0) + return r; + + r = smbios_server_info(); + if (r < 0) + return r; + + if (arg_varlink) + return vl_server(); + + return cmdline_run(); +} + +DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/imds/io.systemd.imds.policy b/src/imds/io.systemd.imds.policy new file mode 100644 index 0000000000000..e844f60b600bc --- /dev/null +++ b/src/imds/io.systemd.imds.policy @@ -0,0 +1,30 @@ + + + + + + + + The systemd Project + https://systemd.io + + + Acquire IMDS instance metadata. + Authentication is required for an application to acquire IMDS instance metadata. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + diff --git a/src/imds/meson.build b/src/imds/meson.build new file mode 100644 index 0000000000000..79214890ea05c --- /dev/null +++ b/src/imds/meson.build @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +if conf.get('ENABLE_IMDS') != 1 + subdir_done() +endif + +executables += [ + libexec_template + { + 'name' : 'systemd-imdsd', + 'public' : true, + 'sources' : files( + 'imdsd.c', + 'imds-util.c' + ) + import_curl_util_c, + 'dependencies' : [ libcurl ], + }, +] + +install_data( + 'io.systemd.imds.policy', + install_dir : polkitpolicydir) diff --git a/src/import/meson.build b/src/import/meson.build index 8349202a329ad..30751058f1195 100644 --- a/src/import/meson.build +++ b/src/import/meson.build @@ -1,5 +1,7 @@ # SPDX-License-Identifier: LGPL-2.1-or-later +import_curl_util_c = files('curl-util.c') + if conf.get('ENABLE_IMPORTD') != 1 subdir_done() endif @@ -32,14 +34,13 @@ executables += [ 'name' : 'systemd-pull', 'public' : true, 'sources' : files( - 'curl-util.c', 'pull.c', 'pull-common.c', 'pull-job.c', 'pull-oci.c', 'pull-raw.c', 'pull-tar.c', - ), + ) + import_curl_util_c, 'objects' : ['systemd-importd'], 'dependencies' : common_deps + [ libopenssl, diff --git a/src/shared/meson.build b/src/shared/meson.build index 3088b419a5de3..cdbe763d0137d 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -207,6 +207,7 @@ shared_sources = files( 'varlink-io.systemd.FactoryReset.c', 'varlink-io.systemd.Hostname.c', 'varlink-io.systemd.Import.c', + 'varlink-io.systemd.InstanceMetadata.c', 'varlink-io.systemd.Journal.c', 'varlink-io.systemd.JournalAccess.c', 'varlink-io.systemd.Login.c', diff --git a/src/shared/varlink-io.systemd.InstanceMetadata.c b/src/shared/varlink-io.systemd.InstanceMetadata.c new file mode 100644 index 0000000000000..b40bb6d4f35ed --- /dev/null +++ b/src/shared/varlink-io.systemd.InstanceMetadata.c @@ -0,0 +1,103 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "bus-polkit.h" +#include "varlink-io.systemd.InstanceMetadata.h" + +static SD_VARLINK_DEFINE_ENUM_TYPE( + WellKnown, + SD_VARLINK_DEFINE_ENUM_VALUE(base), + SD_VARLINK_DEFINE_ENUM_VALUE(hostname), + SD_VARLINK_DEFINE_ENUM_VALUE(region), + SD_VARLINK_DEFINE_ENUM_VALUE(zone), + SD_VARLINK_DEFINE_ENUM_VALUE(ipv4_public), + SD_VARLINK_DEFINE_ENUM_VALUE(ipv6_public), + SD_VARLINK_DEFINE_ENUM_VALUE(ssh_key), + SD_VARLINK_DEFINE_ENUM_VALUE(userdata), + SD_VARLINK_DEFINE_ENUM_VALUE(userdata_base), + SD_VARLINK_DEFINE_ENUM_VALUE(userdata_base64)); + +static SD_VARLINK_DEFINE_METHOD( + Get, + SD_VARLINK_FIELD_COMMENT("The key to retrieve"), + SD_VARLINK_DEFINE_INPUT(key, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Start with a well-known key"), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(wellKnown, WellKnown, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The network interface to use"), + SD_VARLINK_DEFINE_INPUT(interface, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Refresh cached data if older (CLOCK_BOOTTIME, µs)"), + SD_VARLINK_DEFINE_INPUT(refreshUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Whether to accept cached data"), + SD_VARLINK_DEFINE_INPUT(cache, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The firewall mark value to use"), + SD_VARLINK_DEFINE_INPUT(firewallMark, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Controls whether to wait for connectivity"), + SD_VARLINK_DEFINE_INPUT(wait, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT, + SD_VARLINK_FIELD_COMMENT("The data in Base64 encoding."), + SD_VARLINK_DEFINE_OUTPUT(data, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("The interface the data was found on."), + SD_VARLINK_DEFINE_OUTPUT(interface, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD( + GetVendorInfo, + SD_VARLINK_FIELD_COMMENT("The detected cloud vendor"), + SD_VARLINK_DEFINE_OUTPUT(vendor, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The URL to acquire the token from"), + SD_VARLINK_DEFINE_OUTPUT(tokenUrl, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The HTTP header to configure the refresh timeout for the token in"), + SD_VARLINK_DEFINE_OUTPUT(refreshHeaderName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The base URL to acquire the data from"), + SD_VARLINK_DEFINE_OUTPUT(dataUrl, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("A suffix to append to the data URL"), + SD_VARLINK_DEFINE_OUTPUT(dataUrlSuffix, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The HTTP header to pass the token in when requesting data"), + SD_VARLINK_DEFINE_OUTPUT(tokenHeaderName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Additional HTTP headers to pass when acquiring data"), + SD_VARLINK_DEFINE_OUTPUT(extraHeader, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("IPv4 address of IMDS server"), + SD_VARLINK_DEFINE_OUTPUT(addressIPv4, SD_VARLINK_INT, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("IPv6 address of IMDS server"), + SD_VARLINK_DEFINE_OUTPUT(addressIPv6, SD_VARLINK_INT, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("Well-known fields"), + SD_VARLINK_DEFINE_OUTPUT(wellKnown, SD_VARLINK_OBJECT, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_ERROR( + KeyNotFound); + +static SD_VARLINK_DEFINE_ERROR( + WellKnownKeyUnset); + +static SD_VARLINK_DEFINE_ERROR( + NotAvailable); + +static SD_VARLINK_DEFINE_ERROR( + NotSupported); + +static SD_VARLINK_DEFINE_ERROR( + CommunicationFailure); + +static SD_VARLINK_DEFINE_ERROR( + Timeout); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_InstanceMetadata, + "io.systemd.InstanceMetadata", + SD_VARLINK_INTERFACE_COMMENT("APIs for acquiring cloud IMDS information."), + SD_VARLINK_SYMBOL_COMMENT("Well known data fields"), + &vl_type_WellKnown, + SD_VARLINK_SYMBOL_COMMENT("Acquire data."), + &vl_method_Get, + SD_VARLINK_SYMBOL_COMMENT("Get information about cloud vendor and IMDS connectivity."), + &vl_method_GetVendorInfo, + SD_VARLINK_SYMBOL_COMMENT("The requested key is not found on the IMDS server."), + &vl_error_KeyNotFound, + SD_VARLINK_SYMBOL_COMMENT("IMDS is disabled or otherwise not available."), + &vl_error_NotAvailable, + SD_VARLINK_SYMBOL_COMMENT("IMDS is not supported."), + &vl_error_NotSupported, + SD_VARLINK_SYMBOL_COMMENT("Well-known key is not set."), + &vl_error_WellKnownKeyUnset, + SD_VARLINK_SYMBOL_COMMENT("Communication with IMDS failed."), + &vl_error_CommunicationFailure, + SD_VARLINK_SYMBOL_COMMENT("Timeout reached"), + &vl_error_Timeout); diff --git a/src/shared/varlink-io.systemd.InstanceMetadata.h b/src/shared/varlink-io.systemd.InstanceMetadata.h new file mode 100644 index 0000000000000..60920bd9c9f55 --- /dev/null +++ b/src/shared/varlink-io.systemd.InstanceMetadata.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_InstanceMetadata; diff --git a/src/test/test-varlink-idl.c b/src/test/test-varlink-idl.c index 469bf8c2fe08e..0759c8292dcf3 100644 --- a/src/test/test-varlink-idl.c +++ b/src/test/test-varlink-idl.c @@ -24,6 +24,7 @@ #include "varlink-io.systemd.FactoryReset.h" #include "varlink-io.systemd.Hostname.h" #include "varlink-io.systemd.Import.h" +#include "varlink-io.systemd.InstanceMetadata.h" #include "varlink-io.systemd.Journal.h" #include "varlink-io.systemd.JournalAccess.h" #include "varlink-io.systemd.Login.h" @@ -190,6 +191,7 @@ TEST(parse_format) { &vl_interface_io_systemd_FactoryReset, &vl_interface_io_systemd_Hostname, &vl_interface_io_systemd_Import, + &vl_interface_io_systemd_InstanceMetadata, &vl_interface_io_systemd_Journal, &vl_interface_io_systemd_JournalAccess, &vl_interface_io_systemd_Login, diff --git a/sysusers.d/meson.build b/sysusers.d/meson.build index 84fadfe3f7020..3c2e450a183bb 100644 --- a/sysusers.d/meson.build +++ b/sysusers.d/meson.build @@ -15,7 +15,8 @@ in_files = [['basic.conf', true], ['systemd-journal.conf', true], ['systemd-network.conf', conf.get('ENABLE_NETWORKD') == 1], ['systemd-resolve.conf', conf.get('ENABLE_RESOLVE') == 1], - ['systemd-timesync.conf', conf.get('ENABLE_TIMESYNCD') == 1]] + ['systemd-timesync.conf', conf.get('ENABLE_TIMESYNCD') == 1], + ['systemd-imds.conf', conf.get('ENABLE_IMDS') == 1]] foreach tuple : in_files file = tuple[0] diff --git a/sysusers.d/systemd-imds.conf.in b/sysusers.d/systemd-imds.conf.in new file mode 100644 index 0000000000000..adb8d5b1fb1c6 --- /dev/null +++ b/sysusers.d/systemd-imds.conf.in @@ -0,0 +1,8 @@ +# 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. + +u! systemd-imds {{SYSTEMD_IMDS_UID}} "systemd Instance Metadata" diff --git a/units/meson.build b/units/meson.build index b2cf9bd8f39ce..782d1ecadfbe4 100644 --- a/units/meson.build +++ b/units/meson.build @@ -392,6 +392,18 @@ units = [ 'file' : 'systemd-hybrid-sleep.service.in', 'conditions' : ['ENABLE_HIBERNATE'], }, + { + 'file' : 'systemd-imdsd@.service.in', + 'conditions' : ['ENABLE_IMDS'], + }, + { + 'file' : 'systemd-imdsd.socket', + 'conditions' : ['ENABLE_IMDS'], + }, + { + 'file' : 'systemd-imds-early-network.service.in', + 'conditions' : ['ENABLE_IMDS'], + }, { 'file' : 'systemd-importd.service.in', 'conditions' : ['ENABLE_IMPORTD'], diff --git a/units/systemd-imds-early-network.service.in b/units/systemd-imds-early-network.service.in new file mode 100644 index 0000000000000..b4241237f0983 --- /dev/null +++ b/units/systemd-imds-early-network.service.in @@ -0,0 +1,23 @@ +# 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=Enable Pre-IMDS Networking +Documentation=man:systemd-imdsd@.service(8) +DefaultDependencies=no +Before=network-pre.target +Wants=network-pre.target +Conflicts=shutdown.target initrd-switch-root.target +Before=shutdown.target initrd-switch-root.target +After=sys-devices-virtual-dmi-id.device + +[Service] +ExecStart={{LIBEXECDIR}}/systemd-imdsd --setup-network +Type=oneshot +RemainAfterExit=yes diff --git a/units/systemd-imdsd.socket b/units/systemd-imdsd.socket new file mode 100644 index 0000000000000..daeb7840b3ec0 --- /dev/null +++ b/units/systemd-imdsd.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=Cloud Instance Metadata Access (IMDS) +Documentation=man:systemd-imdsd@.service(8) +DefaultDependencies=no +Before=sockets.target + +[Socket] +ListenStream=/run/systemd/io.systemd.InstanceMetadata +Symlinks=/run/varlink/registry/io.systemd.InstanceMetadata +FileDescriptorName=varlink +SocketMode=0666 +Accept=yes +MaxConnectionsPerSource=16 +RemoveOnStop=yes + +# Note that this is typically pulled in automatically by +# systemd-imds-generator, but you can also enable it manually if you like. +[Install] +WantedBy=sockets.target diff --git a/units/systemd-imdsd@.service.in b/units/systemd-imdsd@.service.in new file mode 100644 index 0000000000000..49001cb6264a2 --- /dev/null +++ b/units/systemd-imdsd@.service.in @@ -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=Cloud Instance Metadata Access (IMDS) +Documentation=man:systemd-imdsd@.service(8) +DefaultDependencies=no +Conflicts=shutdown.target initrd-switch-root.target +Before=shutdown.target initrd-switch-root.target +After=sys-devices-virtual-dmi-id.device + +[Service] +ExecStart=-{{LIBEXECDIR}}/systemd-imdsd +User=systemd-imds +RuntimeDirectory=systemd/imds +RuntimeDirectoryPreserve=yes +# CAP_NET_ADMIN is required to set SO_FWMARK and bypass the routing restrictions, and CAP_NET_BIND_SERVICE to bind to a low port +AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE +LockPersonality=yes +MemoryDenyWriteExecute=yes +NoNewPrivileges=yes +ImportCredential=imds.* From 12286604000ed53acd4079423f82e3b203e1d505 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 4 Mar 2026 15:13:25 +0100 Subject: [PATCH 0502/1296] imds: add "systemd-imds" tool that is a simple client to "systemd-imdsd" This is a client tool to the systemd-imdsd@.service added in the previous commit. It's mostly just a 1:1 IPC client via Varlink. It can be used to query any IMDS key, but it's primary usecase is to acquire the "userdata" from IMDS. Moreover, if invoked with the --import switch it will check if the userdata contains a list of system credentials. If so, it will import them into the local credstore. If the userdata does not look like a list of system credentials no operation is executed, under the assumption the data is intended for cloud-init instead. It also imports a couple of other fields, if available and recogniuzed, such as SSH keys and the hostname. --- man/rules/meson.build | 1 + man/systemd-imds.xml | 174 ++++++ src/imds/imds-tool.c | 892 +++++++++++++++++++++++++++ src/imds/meson.build | 8 + units/meson.build | 4 + units/systemd-imds-import.service.in | 25 + 6 files changed, 1104 insertions(+) create mode 100644 man/systemd-imds.xml create mode 100644 src/imds/imds-tool.c create mode 100644 units/systemd-imds-import.service.in diff --git a/man/rules/meson.build b/man/rules/meson.build index 60fefdfb11cde..0ecf0db5d6957 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -1045,6 +1045,7 @@ manpages = [ ['systemd-hostnamed.service', '8', ['systemd-hostnamed'], 'ENABLE_HOSTNAMED'], ['systemd-hwdb', '8', [], 'ENABLE_HWDB'], ['systemd-id128', '1', [], ''], + ['systemd-imds', '1', ['systemd-imds-import.service'], 'ENABLE_IMDS'], ['systemd-imdsd@.service', '8', ['systemd-imdsd', diff --git a/man/systemd-imds.xml b/man/systemd-imds.xml new file mode 100644 index 0000000000000..3980c7560c351 --- /dev/null +++ b/man/systemd-imds.xml @@ -0,0 +1,174 @@ + + + + + + + + systemd-imds + systemd + + + + systemd-imds + 1 + + + + systemd-imds + systemd-imds-import.service + Cloud IMDS (Instance Metadata Service) tool + + + + systemd-imds-import.service + + systemd-imds OPTIONS KEY + + + + + Description + + systemd-imds is a tool for acquiring data from IMDS (Instance Metadata Service), + as provided in many cloud environments. It is a client to + systemd-imdsd@.service8, + and provides access to IMDS data from shell environments. + + The tool can operate in one of five modes: + + + Without positional arguments (and without the switch) + general IMDS service data and a few well known fields are displayed in human friendly + form. + + With a positional argument (and without ) the IMDS data + referenced by the specified key is acquired and written to standard output, in unprocessed form. IMDS + keys are the part of the IMDS acquisition URL that are suffixed to the base URL. IMDS keys must begin + with a slash (/). Note that IMDS keys are typically + implementation-specific. + + With the option specified (see below), the indicated + well-known field is written to standard output, in unprocessed form. The concept of well-known fields + abstracts IMDS implementation differences to some level, exposing a unified interface for IMDS fields + that typically exist on many different implementations, but under implementation-specific + keys. + + With the option specified (see below) the "userdata" + provided via IMDS is written to standard output. Under the hood this is similar to + , or + . Each of the three is tried in turn (in this order), and + the first available is returned. For the + systemd-userdata userdata item is requested. For + the returned data is automatically + Base64-decoded. + + With the option specified, various well known and userdata + fields are imported into the local credential store, where they are used to configure and parameterize + the system. For details see below. + + + + + Options and Commands + + + + + + + Takes one of hostname, region, + zone, ipv4-public, ipv6-public, + ssh-key, userdata, userdata-base, + userdata-base64. Acquires a specific "well-known" field from IMDS. Many of these + fields are commonly supported by various IMDS implementations, but typically some fields are + not. Note that if is used an additional subkey should be + specified as positional argument, which encodes the specific userdata item to acquire. + + + + + + + + Takes a time in seconds as argument, and indicates the required "freshness" of the + data, in case cached data is used. + + + + + + + + Takes a boolean. If set to false local caching of IMDS is disabled, and the data is + always acquired fresh from the IMDS endpoint. + + + + + + + + + Acquire this instance's IMDS user data, if available. See above for + details. + + + + + + + + Acquires IMDS data and writes relevant fields as credentials to + /run/credstore/. This currently covers: + + + If the IMDS user data is a valid JSON object containing a field + systemd.credentials (with a JSON array as value) it is processed, importing + arbitrary credentials listed in the array. Each array item must have a name + field indicating the credential name. It may have one text, + data or encrypted field, containing the credential data. If + text is used the value shall be a literal string of the credential value. If + data is used the value may be arbitrary binary data encoded in a Base64 + string. If encrypted is used the value shall be a Base64 encoded encrypted + credential. See + systemd.system-credentials7 + for information about credentials that may be imported this way. + + If the well-known ssh-key field is available, its value will be + imported into the ssh.authorized_keys.root credential. + + If the well-known hostname field is available, its value will be + imported into the firstboot.hostname credential. + + + This command is invoked by the systemd-imds-import.service run at + boot. + + + + + + + + + + + Exit status + + On success, 0 is returned, a non-zero failure code otherwise. + + + + See Also + + systemd1 + systemd-imdsd@.service8 + systemd-imds-generator8 + systemd.system-credentials7 + + + + diff --git a/src/imds/imds-tool.c b/src/imds/imds-tool.c new file mode 100644 index 0000000000000..d4a5b6b6eb348 --- /dev/null +++ b/src/imds/imds-tool.c @@ -0,0 +1,892 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include + +#include "sd-varlink.h" + +#include "alloc-util.h" +#include "build.h" +#include "build-path.h" +#include "creds-util.h" +#include "dns-rr.h" +#include "errno-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "format-table.h" +#include "format-util.h" +#include "fs-util.h" +#include "hexdecoct.h" +#include "imds-util.h" +#include "in-addr-util.h" +#include "io-util.h" +#include "iovec-util.h" +#include "json-util.h" +#include "log.h" +#include "main-func.h" +#include "parse-argument.h" +#include "pretty-print.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" +#include "tmpfile-util.h" + +static enum { + ACTION_SUMMARY, + ACTION_GET, + ACTION_USERDATA, + ACTION_IMPORT, + _ACTION_INVALID = -EINVAL, +} arg_action = _ACTION_INVALID; +static char *arg_key = NULL; +static ImdsWellKnown arg_well_known = _IMDS_WELL_KNOWN_INVALID; +static int arg_cache = -1; +static usec_t arg_refresh_usec = 0; +static bool arg_refresh_usec_set = false; + +STATIC_DESTRUCTOR_REGISTER(arg_key, freep); + +static int help(void) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("systemd-imds", "1", &link); + if (r < 0) + return log_oom(); + + printf("%s [OPTIONS...] [KEY]\n" + "\n%sIMDS data acquisition.%s\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -K --well-known=[hostname|region|zone|ipv4-public|ipv6-public|ssh-key|\n" + " userdata|userdata-base|userdata-base64]\n" + " Select well-known key/base\n" + " --refresh=SEC Set minimum freshness time for returned data\n" + " --cache=no Disable cache use\n" + " -u --userdata Dump user data\n" + " --import Import system credentials from IMDS userdata\n" + " and place them in /run/credstore/\n" + "\nSee the %s for details.\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal(), + link); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_REFRESH, + ARG_CACHE, + ARG_IMPORT, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "well-known", required_argument, NULL, 'K' }, + { "refresh", required_argument, NULL, ARG_REFRESH }, + { "cache", required_argument, NULL, ARG_CACHE }, + { "userdata", no_argument, NULL, 'u' }, + { "import", no_argument, NULL, ARG_IMPORT }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hK:u", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + return help(); + + case ARG_VERSION: + return version(); + + case 'K': { + if (isempty(optarg)) { + arg_well_known = _IMDS_WELL_KNOWN_INVALID; + break; + } + + if (streq(optarg, "help")) + return DUMP_STRING_TABLE(imds_well_known, ImdsWellKnown, _IMDS_WELL_KNOWN_MAX); + + ImdsWellKnown wk = imds_well_known_from_string(optarg); + if (wk < 0) + return log_error_errno(wk, "Failed to parse --well-known= argument: %s", optarg); + + arg_well_known = wk; + break; + } + + case ARG_CACHE: + r = parse_tristate_argument_with_auto("--cache=", optarg, &arg_cache); + if (r < 0) + return r; + + break; + + case ARG_REFRESH: { + if (isempty(optarg)) { + arg_refresh_usec_set = false; + break; + } + + usec_t t; + r = parse_sec(optarg, &t); + if (r < 0) + return log_error_errno(r, "Failed to parse refresh timeout: %s", optarg); + + arg_refresh_usec = t; + arg_refresh_usec_set = true; + break; + } + + case 'u': + arg_action = ACTION_USERDATA; + break; + + case ARG_IMPORT: + arg_action = ACTION_IMPORT; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + } + + if (IN_SET(arg_action, ACTION_USERDATA, ACTION_IMPORT)) { + if (argc != optind) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No parameters expected."); + + } else { + assert(arg_action < 0); + + if (argc > optind + 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "None or one argument expected."); + + if (argc == optind && arg_well_known < 0) + arg_action = ACTION_SUMMARY; + else { + if (arg_well_known < 0) + arg_well_known = IMDS_BASE; + + if (argc > optind) { + if (!imds_key_is_valid(argv[optind])) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified IMDS key is not valid, refusing: %s", argv[optind]); + + if (!imds_well_known_can_suffix(arg_well_known)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Well known key '%s' does not take a key suffix, refusing.", imds_well_known_to_string(arg_well_known)); + + r = free_and_strdup_warn(&arg_key, argv[optind]); + if (r < 0) + return r; + } + + arg_action = ACTION_GET; + } + } + + return 1; +} + +static int acquire_imds_key( + sd_varlink *link, + ImdsWellKnown wk, + const char *key, + struct iovec *ret) { + + int r; + + assert(link); + assert(wk >= 0); + assert(wk < _IMDS_WELL_KNOWN_MAX); + assert(ret); + + const char *error_id = NULL; + sd_json_variant *reply = NULL; + r = sd_varlink_callbo( + link, + "io.systemd.InstanceMetadata.Get", + &reply, + &error_id, + SD_JSON_BUILD_PAIR_CONDITION(wk != IMDS_BASE, "wellKnown", JSON_BUILD_STRING_UNDERSCORIFY(imds_well_known_to_string(wk))), + JSON_BUILD_PAIR_STRING_NON_EMPTY("key", key), + SD_JSON_BUILD_PAIR_CONDITION(arg_refresh_usec_set, "refreshUSec", SD_JSON_BUILD_UNSIGNED(arg_refresh_usec)), + SD_JSON_BUILD_PAIR_CONDITION(arg_cache >= 0, "cache", SD_JSON_BUILD_BOOLEAN(arg_cache))); + if (r < 0) + return log_error_errno(r, "Failed to issue io.systemd.InstanceMetadata.Get(): %m"); + if (error_id) { + if (STR_IN_SET(error_id, "io.systemd.InstanceMetadata.KeyNotFound", "io.systemd.InstanceMetadata.WellKnownKeyUnset")) { + *ret = (struct iovec) {}; + return 0; + } + + return log_error_errno(sd_varlink_error_to_errno(error_id, reply), "Failed to issue io.systemd.InstanceMetadata.Get(): %s", error_id); + } + + _cleanup_(iovec_done) struct iovec data = {}; + static const sd_json_dispatch_field dispatch_table[] = { + { "data", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, 0, SD_JSON_MANDATORY }, + {}, + }; + r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &data); + if (r < 0) + return r; + + *ret = TAKE_STRUCT(data); + return 1; +} + +static int acquire_imds_key_as_string( + sd_varlink *link, + ImdsWellKnown wk, + const char *key, + char **ret) { + + int r; + + assert(link); + assert(wk >= 0); + assert(wk < _IMDS_WELL_KNOWN_MAX); + assert(ret); + + _cleanup_(iovec_done) struct iovec data = {}; + r = acquire_imds_key(link, wk, key, &data); + if (r < 0) + return r; + if (r == 0) { + *ret = NULL; + return 0; + } + + _cleanup_free_ char *s = NULL; + r = make_cstring(data.iov_base, data.iov_len, MAKE_CSTRING_REFUSE_TRAILING_NUL, &s); + if (r < 0) + return r; + + *ret = TAKE_PTR(s); + return 1; +} + +static int acquire_imds_key_as_ip_address( + sd_varlink *link, + ImdsWellKnown wk, + const char *key, + int family, + union in_addr_union *ret) { + int r; + + assert(link); + assert(wk >= 0); + assert(wk < _IMDS_WELL_KNOWN_MAX); + assert(ret); + + _cleanup_free_ char *s = NULL; + r = acquire_imds_key_as_string(link, wk, key, &s); + if (r < 0) + return r; + if (r == 0 || isempty(s)) { + *ret = (union in_addr_union) {}; + return 0; + } + + r = in_addr_from_string(family, s, ret); + if (r < 0) + return r; + + return 1; +} + +static int action_summary(sd_varlink *link) { + int r; + + assert(link); + + _cleanup_(table_unrefp) Table *table = table_new_vertical(); + if (!table) + return log_oom(); + + const char *error_id = NULL; + sd_json_variant *reply = NULL; + r = sd_varlink_call( + link, + "io.systemd.InstanceMetadata.GetVendorInfo", + /* parameters= */ NULL, + &reply, + &error_id); + if (r < 0) + return log_error_errno(r, "Failed to issue io.systemd.InstanceMetadata.GetVendorInfo(): %m"); + if (error_id) + return log_error_errno(sd_varlink_error_to_errno(error_id, reply), "Failed to issue io.systemd.InstanceMetadata.GetVendorInfo(): %s", error_id); + + const char *vendor = NULL; + static const sd_json_dispatch_field dispatch_table[] = { + { "vendor", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, 0 }, + {} + }; + r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &vendor); + if (r < 0) + return r; + if (vendor) { + r = table_add_many(table, + TABLE_FIELD, "Vendor", + TABLE_SET_JSON_FIELD_NAME, "vendor", + TABLE_STRING, vendor); + if (r < 0) + return table_log_add_error(r); + } + + static const struct { + ImdsWellKnown well_known; + const char *field; + } wktable[] = { + { IMDS_HOSTNAME, "Hostname" }, + { IMDS_REGION, "Region" }, + { IMDS_ZONE, "Zone" }, + { IMDS_IPV4_PUBLIC, "Public IPv4 Address" }, + { IMDS_IPV6_PUBLIC, "Public IPv6 Address" }, + }; + FOREACH_ELEMENT(i, wktable) { + _cleanup_free_ char *text = NULL; + + r = acquire_imds_key_as_string(link, i->well_known, /* key= */ NULL, &text); + if (r < 0) + return r; + if (r == 0 || isempty(text)) + continue; + + r = table_add_many(table, + TABLE_FIELD, i->field, + TABLE_SET_JSON_FIELD_NAME, imds_well_known_to_string(i->well_known), + TABLE_STRING, text); + if (r < 0) + return table_log_add_error(r); + } + + if (table_isempty(table)) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "No well-known IMDS data available."); + + r = table_print(table, NULL); + if (r < 0) + return table_log_print_error(r); + + return 0; +} + +static const char *detect_json_object(const char *text) { + assert(text); + + /* Checks if the provided text looks like a JSON object. It checks if the first non-whitespace + * characters are {" or {}. */ + + text += strspn(text, WHITESPACE); + if (*text != '{') + return NULL; + + const char *e = text + 1; + e += strspn(e, WHITESPACE); + if (!IN_SET(*e, '"', '}')) + return NULL; + + return text; +} + +static int write_credential(const char *dir, const char *name, const struct iovec *data) { + int r; + + assert(dir); + assert(name); + + _cleanup_close_ int dfd = open_mkdir(dir, O_CLOEXEC|O_PATH, 0700); + if (dfd < 0) + return log_error_errno(dfd, "Failed to open credential directory '%s': %m", dir); + + if (faccessat(dfd, name, F_OK, AT_SYMLINK_NOFOLLOW) < 0) { + if (errno != ENOENT) + return log_error_errno(errno, "Failed to check if '%s' exists in credential directory '%s': %m", name, dir); + } else { + log_notice("Skipping importing of credential '%s', it already exists locally in '%s'.", name, dir); + return 0; + } + + _cleanup_free_ char *t = NULL; + _cleanup_close_ int fd = open_tmpfile_linkable_at(dfd, name, O_WRONLY|O_CLOEXEC, &t); + if (fd < 0) + return log_error_errno(fd, "Failed to create credential file '%s/%s': %m", dir, name); + + CLEANUP_TMPFILE_AT(dfd, t); + + r = loop_write(fd, data->iov_base, data->iov_len); + if (r < 0) + return log_error_errno(r, "Failed to write credential file '%s/%s': %m", dir, name); + + if (fchmod(fd, 0400) < 0) + return log_error_errno(errno, "Failed to set access mode on credential file '%s/%s': %m", dir, name); + + r = link_tmpfile_at(fd, dfd, t, name, /* flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to move credential file '%s/%s' into place: %m", dir, name); + + t = mfree(t); /* Disarm auto-cleanup */ + return 1; +} + +typedef struct CredentialData { + const char *name; + const char *text; + struct iovec data, encrypted; +} CredentialData; + +static void credential_data_done(CredentialData *d) { + assert(d); + + iovec_done(&d->data); + iovec_done(&d->encrypted); +} + +static int import_credential_one(CredentialData *d) { + int r; + + assert(d); + assert(d->name); + + log_debug("Importing credential '%s' from IMDS.", d->name); + + const char *dir = "/run/credstore"; + struct iovec *v, _v; + if (d->text) { + _v = IOVEC_MAKE_STRING(d->text); + v = &_v; + } else if (iovec_is_set(&d->data)) + v = &d->data; + else if (iovec_is_set(&d->encrypted)) { + dir = "/run/credstore.encrypted"; + v = &d->encrypted; + } else + assert_not_reached(); + + r = write_credential(dir, d->name, v); + if (r <= 0) + return r; + + log_info("Imported credential '%s' from IMDS (%s).", d->name, FORMAT_BYTES(v->iov_len)); + return 1; +} + +static int import_credentials(const char *text) { + int r; + + assert(text); + + /* We cannot be sure if the data is actually intended for us. Hence let's be somewhat defensive, and + * accept data in two ways: either immediately as a JSON object, or alternatively marked with a first + * line of "#systemd-userdata". The latter mimics the markers cloud-init employs. */ + + const char *e = startswith(text, "#systemd-userdata\n"); + if (!e) { + e = detect_json_object(text); + if (!e) { + log_info("IMDS user data does not look like JSON or systemd userdata, not processing."); + return 0; + } + } + + log_debug("Detected JSON userdata"); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + unsigned line = 0, column = 0; + r = sd_json_parse(e, /* flags= */ 0, &j, &line, &column); + if (r < 0) { + if (line > 0) + log_syntax(/* unit= */ NULL, LOG_WARNING, /* filename= */ NULL, line, r, "JSON parse failure."); + else + log_error_errno(r, "Failed to parse IMDS userdata JSON: %m"); + return 0; + } + + static const sd_json_dispatch_field top_table[] = { + { "systemd.credentials", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant_noref, 0, 0 }, + {}, + }; + + sd_json_variant *creds = NULL; + r = sd_json_dispatch(j, top_table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG, &creds); + if (r < 0) + return r; + + unsigned n_imported = 0; + int ret = 0; + if (creds) { + log_debug("Found 'systemd.credentials' field"); + + sd_json_variant *c; + JSON_VARIANT_ARRAY_FOREACH(c, creds) { + static const sd_json_dispatch_field credential_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CredentialData, name), SD_JSON_MANDATORY }, + { "text", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CredentialData, text), 0 }, + { "data", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(CredentialData, data), 0 }, + { "encrypted", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(CredentialData, encrypted), 0 }, + {}, + }; + + _cleanup_(credential_data_done) CredentialData d = {}; + r = sd_json_dispatch(c, credential_table, SD_JSON_LOG|SD_JSON_WARNING, &d); + if (r < 0) { + RET_GATHER(ret, r); + continue; + } + + if (!credential_name_valid(d.name)) { + RET_GATHER(ret, log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Credential name '%s' is not valid, refusing.", d.name)); + continue; + } + + if ((!!d.text + !!iovec_is_set(&d.data) + !!iovec_is_set(&d.encrypted)) != 1) { + RET_GATHER(ret, log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Exactly one of 'text', 'data', 'encrypted' must be set for credential '%s', refusing.", d.name)); + continue; + } + + r = import_credential_one(&d); + if (r < 0) + RET_GATHER(ret, r); + else if (r > 0) + n_imported++; + } + } + + log_full(n_imported == 0 ? LOG_DEBUG : LOG_INFO, "Imported %u credentials from IMDS.", n_imported); + return ret; +} + +static int add_public_address_to_json_array(sd_json_variant **array, int family, const union in_addr_union *addr) { + int r; + + assert(array); + assert(IN_SET(family, AF_INET, AF_INET6)); + assert(addr); + + if (in_addr_is_null(family, addr)) + return 0; + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + if (dns_resource_record_new_address(&rr, family, addr, "_public") < 0) + return log_oom(); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *rrj = NULL; + r = dns_resource_record_to_json(rr, &rrj); + if (r < 0) + return log_error_errno(r, "Failed to convert A RR to JSON: %m"); + + r = sd_json_variant_append_array(array, rrj); + if (r < 0) + return log_error_errno(r, "Failed to append A RR to JSON array: %m"); + + log_debug("Writing IMDS RR for: %s", dns_resource_record_to_string(rr)); + return 1; +} + +static int import_imds_public_addresses(sd_varlink *link) { + int r, ret = 0; + + assert(link); + + /* Creates local RRs (honoured by systemd-resolved) for our public addresses. */ + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *aj = NULL; + + union in_addr_union u = {}; + r = acquire_imds_key_as_ip_address(link, IMDS_IPV4_PUBLIC, /* key= */ NULL, AF_INET, &u); + if (r < 0) + RET_GATHER(ret, r); + else if (r > 0) { + r = add_public_address_to_json_array(&aj, AF_INET, &u); + if (r < 0) + return r; + } + + u = (union in_addr_union) {}; + r = acquire_imds_key_as_ip_address(link, IMDS_IPV6_PUBLIC, /* key= */ NULL, AF_INET6, &u); + if (r < 0) + RET_GATHER(ret, r); + else if (r > 0) { + r = add_public_address_to_json_array(&aj, AF_INET6, &u); + if (r < 0) + return r; + } + + if (sd_json_variant_elements(aj) == 0) { + log_debug("No IMDS public addresses known, not writing our RRs."); + return 0; + } + + _cleanup_free_ char *text = NULL; + r = sd_json_variant_format(aj, SD_JSON_FORMAT_NEWLINE, &text); + if (r < 0) + return log_error_errno(r, "Failed to format JSON text: %m"); + + r = write_string_file("/run/systemd/resolve/static.d/imds-public.rr", text, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_MKDIR_0755); + if (r < 0) + return log_error_errno(r, "Failed to write IMDS RR data: %m"); + + log_debug("IMDS public addresses written out."); + return 1; +} + +static int import_imds_ssh_key(sd_varlink *link) { + int r; + + assert(link); + + _cleanup_(iovec_done) struct iovec data = {}; + r = acquire_imds_key(link, IMDS_SSH_KEY, /* key= */ NULL, &data); + if (r < 0) + return r; + if (r == 0 || !iovec_is_set(&data)) { + log_debug("No SSH key supplied via IMDS, not importing."); + return 0; + } + + r = write_credential("/run/credstore", "ssh.authorized_keys.root", &data); + if (r <= 0) + return r; + + log_info("Imported SSH key as credential 'ssh.authorized_keys.root'."); + return 0; +} + +static int import_imds_hostname(sd_varlink *link) { + int r; + + assert(link); + + _cleanup_(iovec_done) struct iovec data = {}; + r = acquire_imds_key(link, IMDS_HOSTNAME, /* key= */ NULL, &data); + if (r < 0) + return r; + if (r == 0 || !iovec_is_set(&data)) { + log_debug("No hostname supplied via IMDS, not importing."); + return 0; + } + + r = write_credential("/run/credstore", "firstboot.hostname", &data); + if (r <= 0) + return r; + + log_info("Imported hostname as credential 'firstboot.hostname'."); + return 0; +} + +static int acquire_imds_userdata(sd_varlink *link, struct iovec *ret) { + int r; + + assert(link); + assert(ret); + + /* First try our private namespace, if the concept exists, and then fall back to the singleton */ + _cleanup_(iovec_done) struct iovec data = {}; + r = acquire_imds_key(link, IMDS_USERDATA_BASE, "/systemd-userdata", &data); + if (r == 0) + r = acquire_imds_key(link, IMDS_USERDATA, /* key= */ NULL, &data); + if (r < 0) + return r; + if (r > 0) { + if (!iovec_is_set(&data)) { /* Treat empty user data like empty */ + *ret = (struct iovec) {}; + return 0; + } + + *ret = TAKE_STRUCT(data); + return 1; + } + + r = acquire_imds_key(link, IMDS_USERDATA_BASE64, /* key= */ NULL, &data); + if (r < 0) + return r; + _cleanup_(iovec_done) struct iovec decoded = {}; + if (r > 0) { + r = unbase64mem_full(data.iov_base, data.iov_len, /* secure= */ false, &decoded.iov_base, &decoded.iov_len); + if (r < 0) + return r; + } + + if (!iovec_is_set(&decoded)) { /* Treat empty user data like empty */ + *ret = (struct iovec) {}; + return 0; + } + + *ret = TAKE_STRUCT(decoded); + return 1; +} + +static int action_get(sd_varlink *link) { + int r; + + assert(link); + + _cleanup_(iovec_done) struct iovec data = {}; + r = acquire_imds_key(link, arg_well_known, arg_key, &data); + if (r < 0) + return r; + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Key not available."); + + r = loop_write(STDOUT_FILENO, data.iov_base, data.iov_len); + if (r < 0) + return log_error_errno(r, "Failed to write data to standard output: %m"); + + return 0; +} + +static int action_userdata(sd_varlink *link) { + int r; + + assert(link); + + _cleanup_(iovec_done) struct iovec data = {}; + r = acquire_imds_userdata(link, &data); + if (r < 0) + return r; + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "User data not available."); + + r = loop_write(STDOUT_FILENO, data.iov_base, data.iov_len); + if (r < 0) + return log_error_errno(r, "Failed to write data to standard output: %m"); + + return 0; +} + +static int remove_userdata(const char *path) { + assert(path); + + if (unlink(path) < 0) { + + if (errno != ENOENT) + log_debug_errno(errno, "Failed to remove '%s', ignoring: %m", path); + + return 0; + } + + log_debug("Removed '%s'.", path); + return 1; +} + +static int save_userdata(const struct iovec *data, const char *path) { + int r; + + assert(data); + assert(path); + + if (!iovec_is_set(data)) + return remove_userdata(path); + + r = write_data_file_atomic_at(AT_FDCWD, path, data, WRITE_DATA_FILE_MKDIR_0755); + if (r < 0) + return log_error_errno(r, "Failed to save userdata to '%s': %m", path); + + log_debug("Saved userdata to '%s'.", path); + return 1; +} + +static int action_import(sd_varlink *link) { + int r; + + assert(link); + + int ret = 0; + RET_GATHER(ret, import_imds_public_addresses(link)); + RET_GATHER(ret, import_imds_hostname(link)); + RET_GATHER(ret, import_imds_ssh_key(link)); + + _cleanup_(iovec_done) struct iovec data = {}; + r = acquire_imds_userdata(link, &data); + if (r < 0) + return RET_GATHER(ret, r); + if (r == 0) { + log_info("No IMDS data available, not importing credentials."); + (void) remove_userdata("/run/systemd/imds/userdata"); + return ret; + } + + /* Keep a pristine copy of the userdata we actually applied. (Note that this data is typically also + * kept as cached item on systemd-imdsd, but that one is possibly subject to cache invalidation, + * while this one is supposed to pin the data actually in effect.) */ + (void) save_userdata(&data, "/run/systemd/imds/userdata"); + + /* Ensure no inner NUL byte */ + if (memchr(data.iov_base, 0, data.iov_len)) { + log_info("IMDS user data contains NUL byte, not processing."); + return ret; + } + + /* Turn this into a proper C string */ + if (!iovec_append(&data, &IOVEC_MAKE_BYTE(0))) + return log_oom(); + + return RET_GATHER(ret, import_credentials(data.iov_base)); +} + +static int run(int argc, char* argv[]) { + int r; + + log_setup(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; + r = sd_varlink_connect_address(&link, "/run/systemd/io.systemd.InstanceMetadata"); + if (r < 0) { + if (r != -ENOENT && !ERRNO_IS_NEG_DISCONNECT(r)) + return log_error_errno(r, "Failed to connect to systemd-imdsd: %m"); + + log_debug_errno(r, "Couldn't connect to /run/systemd/io.systemd.InstanceMetadata, will try to fork off systemd-imdsd as child now."); + + /* Try to fork off systemd-imdsd as a child as a fallback. If we have privileges and the + * SO_FWMARK trickery is not necessary, then this might just work. */ + _cleanup_free_ char *p = NULL; + _cleanup_close_ int pin_fd = + pin_callout_binary(LIBEXECDIR "/systemd-imdsd", &p); + if (pin_fd < 0) + return log_error_errno(pin_fd, "Failed to pick up imdsd binary: %m"); + + r = sd_varlink_connect_exec(&link, p, /* argv[]= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to connect to imdsd service: %m"); + } + + switch (arg_action) { + + case ACTION_SUMMARY: + return action_summary(link); + + case ACTION_GET: + return action_get(link); + + case ACTION_USERDATA: + return action_userdata(link); + + case ACTION_IMPORT: + return action_import(link); + + default: + assert_not_reached(); + } +} + +DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/imds/meson.build b/src/imds/meson.build index 79214890ea05c..a28dd0ca3a510 100644 --- a/src/imds/meson.build +++ b/src/imds/meson.build @@ -14,6 +14,14 @@ executables += [ ) + import_curl_util_c, 'dependencies' : [ libcurl ], }, + libexec_template + { + 'name' : 'systemd-imds', + 'public' : true, + 'sources' : files( + 'imds-tool.c', + 'imds-util.c' + ), + }, ] install_data( diff --git a/units/meson.build b/units/meson.build index 782d1ecadfbe4..ca17237dd0b16 100644 --- a/units/meson.build +++ b/units/meson.build @@ -404,6 +404,10 @@ units = [ 'file' : 'systemd-imds-early-network.service.in', 'conditions' : ['ENABLE_IMDS'], }, + { + 'file' : 'systemd-imds-import.service.in', + 'conditions' : ['ENABLE_IMDS'], + }, { 'file' : 'systemd-importd.service.in', 'conditions' : ['ENABLE_IMPORTD'], diff --git a/units/systemd-imds-import.service.in b/units/systemd-imds-import.service.in new file mode 100644 index 0000000000000..9704557fbb5bf --- /dev/null +++ b/units/systemd-imds-import.service.in @@ -0,0 +1,25 @@ +# 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=Import System Credentials from IMDS +Documentation=man:systemd-imds(1) +Documentation=man:systemd.system-credentials(7) +DefaultDependencies=no +Wants=systemd-imdsd.socket network-online.target +After=systemd-imdsd.socket network-online.target +Before=sysinit.target systemd-firstboot.service +Conflicts=shutdown.target +Before=shutdown.target +ConditionPathExists=/etc/initrd-release + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart={{LIBEXECDIR}}/systemd-imds --import From 19a783f4dd558a7b83dee7f950f393b29a452bc1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 24 Mar 2026 09:41:03 +0100 Subject: [PATCH 0503/1296] imds: add TPM measurements to imds tool This automatically measures the IMDS 'userdata' into PCR 12, i.e. where we measure the other owner-supplied configuration, such as confexts and credentials and similar. (Why 12? It's really about who owns the data and what it is for. PCRs/NvPCRs are scarce hence there's a strong incentive to not go overboard with new allocations, and IMDS userdata in purpose and owner is very very similar to confexts and credentials, hence let's reuse the PCR for this purpose.) --- src/imds/imds-tool.c | 4 ++ src/shared/pcrextend-util.c | 69 +++++++++++++++++++++++ src/shared/pcrextend-util.h | 6 +- src/shared/tpm2-util.c | 1 + src/shared/tpm2-util.h | 1 + src/shared/varlink-io.systemd.PCRExtend.c | 3 +- 6 files changed, 82 insertions(+), 2 deletions(-) diff --git a/src/imds/imds-tool.c b/src/imds/imds-tool.c index d4a5b6b6eb348..4ae8dbb33cec9 100644 --- a/src/imds/imds-tool.c +++ b/src/imds/imds-tool.c @@ -27,6 +27,7 @@ #include "log.h" #include "main-func.h" #include "parse-argument.h" +#include "pcrextend-util.h" #include "pretty-print.h" #include "string-util.h" #include "strv.h" @@ -822,6 +823,9 @@ static int action_import(sd_varlink *link) { return ret; } + /* Measure the userdata before we use it */ + (void) pcrextend_imds_userdata_now(&data); + /* Keep a pristine copy of the userdata we actually applied. (Note that this data is typically also * kept as cached item on systemd-imdsd, but that one is possibly subject to cache invalidation, * while this one is supposed to pin the data actually in effect.) */ diff --git a/src/shared/pcrextend-util.c b/src/shared/pcrextend-util.c index 8586e85cbbd3f..7af436217d5eb 100644 --- a/src/shared/pcrextend-util.c +++ b/src/shared/pcrextend-util.c @@ -18,8 +18,10 @@ #include "mountpoint-util.h" #include "pcrextend-util.h" #include "pkcs7-util.h" +#include "sha256.h" #include "string-util.h" #include "strv.h" +#include "tpm2-pcr.h" static int device_get_file_system_word( sd_device *d, @@ -291,3 +293,70 @@ int pcrextend_verity_now( return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring Verity root hashes and signatures."); #endif } + +#define IMDS_USERDATA_TRUNCATED_MAX 256U + +int pcrextend_imds_userdata_word(const struct iovec *data, char **ret) { + assert(iovec_is_set(data)); + assert(ret); + + /* We include both a hash of the complete user data, and a truncated version of the data in the word + * we measure. The former protects the actual data, the latter is useful for debugging. */ + + _cleanup_free_ char *hash = hexmem(SHA256_DIRECT(data->iov_base, data->iov_len), SHA256_DIGEST_SIZE); + if (!hash) + return log_oom(); + + _cleanup_free_ char *data_encoded = NULL; + if (base64mem_full(data->iov_base, MIN(data->iov_len, IMDS_USERDATA_TRUNCATED_MAX), /* line_break= */ SIZE_MAX, &data_encoded) < 0) + return log_oom(); + + _cleanup_free_ char *word = strjoin("imds-userdata:", hash, ":", data_encoded); + if (!word) + return log_oom(); + + *ret = TAKE_PTR(word); + return 0; +} + +int pcrextend_imds_userdata_now(const struct iovec *data) { + +#if HAVE_TPM2 + int r; + + _cleanup_free_ char *word = NULL; + r = pcrextend_imds_userdata_word(data, &word); + if (r < 0) + return r; + + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + r = sd_varlink_connect_address(&vl, "/run/systemd/io.systemd.PCRExtend"); + if (r < 0) + return r; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = sd_varlink_callbo( + vl, + "io.systemd.PCRExtend.Extend", + /* ret_reply= */ NULL, + &error_id, + SD_JSON_BUILD_PAIR_INTEGER("pcr", TPM2_PCR_KERNEL_CONFIG), + SD_JSON_BUILD_PAIR_STRING("text", word), + SD_JSON_BUILD_PAIR_STRING("eventType", "imds_userdata")); + if (r < 0) + return log_debug_errno(r, "Failed to issue io.systemd.PCRExtend.Extend() varlink call: %m"); + if (error_id) { + r = sd_varlink_error_to_errno(error_id, reply); + if (r != -EBADR) + return log_debug_errno(r, "Failed to issue io.systemd.PCRExtend.Extend() varlink call: %m"); + + return log_debug_errno(r, "Failed to issue io.systemd.PCRExtend.Extend() varlink call: %s", error_id); + } + + log_debug("Measurement of '%s' into PCR 12 completed.", word); + return 1; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring IMDS userdata."); +#endif +} diff --git a/src/shared/pcrextend-util.h b/src/shared/pcrextend-util.h index 00bc5b9b48dc7..eadc2d5cffc98 100644 --- a/src/shared/pcrextend-util.h +++ b/src/shared/pcrextend-util.h @@ -1,9 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include + int pcrextend_file_system_word(const char *path, char **ret, char **ret_normalized_path); int pcrextend_machine_id_word(char **ret); int pcrextend_product_id_word(char **ret); int pcrextend_verity_word(const char *name, const struct iovec *root_hash, const struct iovec *root_hash_sig, char **ret); +int pcrextend_imds_userdata_word(const struct iovec *data, char **ret); -int pcrextend_verity_now(const char *name, const struct iovec *root_hash,const struct iovec *root_hash_sig); +int pcrextend_verity_now(const char *name, const struct iovec *root_hash, const struct iovec *root_hash_sig); +int pcrextend_imds_userdata_now(const struct iovec *data); diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index cfa057c02ba7a..47a6a309ddb47 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -6675,6 +6675,7 @@ static const char* tpm2_userspace_event_type_table[_TPM2_USERSPACE_EVENT_TYPE_MA [TPM2_EVENT_NVPCR_INIT] = "nvpcr-init", [TPM2_EVENT_NVPCR_SEPARATOR] = "nvpcr-separator", [TPM2_EVENT_DM_VERITY] = "dm-verity", + [TPM2_EVENT_IMDS_USERDATA] = "imds-userdata", }; DEFINE_STRING_TABLE_LOOKUP(tpm2_userspace_event_type, Tpm2UserspaceEventType); diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 51670e8b061a8..841f33b8deaa3 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -148,6 +148,7 @@ typedef enum Tpm2UserspaceEventType { TPM2_EVENT_NVPCR_INIT, TPM2_EVENT_NVPCR_SEPARATOR, TPM2_EVENT_DM_VERITY, + TPM2_EVENT_IMDS_USERDATA, _TPM2_USERSPACE_EVENT_TYPE_MAX, _TPM2_USERSPACE_EVENT_TYPE_INVALID = -EINVAL, } Tpm2UserspaceEventType; diff --git a/src/shared/varlink-io.systemd.PCRExtend.c b/src/shared/varlink-io.systemd.PCRExtend.c index 87edec349ef80..d309330f405a6 100644 --- a/src/shared/varlink-io.systemd.PCRExtend.c +++ b/src/shared/varlink-io.systemd.PCRExtend.c @@ -12,7 +12,8 @@ static SD_VARLINK_DEFINE_ENUM_TYPE( SD_VARLINK_DEFINE_ENUM_VALUE(keyslot), SD_VARLINK_DEFINE_ENUM_VALUE(nvpcr_init), SD_VARLINK_DEFINE_ENUM_VALUE(nvpcr_separator), - SD_VARLINK_DEFINE_ENUM_VALUE(dm_verity)); + SD_VARLINK_DEFINE_ENUM_VALUE(dm_verity), + SD_VARLINK_DEFINE_ENUM_VALUE(imds_userdata)); static SD_VARLINK_DEFINE_METHOD( Extend, From 168a7f7770bee0b4e5f7237a5fbd63a9e5ea068f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 4 Mar 2026 15:16:14 +0100 Subject: [PATCH 0504/1296] imds: add generator that hooks in IMDS logic on cloud guests The infrastructure added in the previous commits added support for IMDS client functionality, but didn't really to enable the logic by default on suitable hosts. This commit adds a generator that automatically hooks the IMDS functionality into the boot process if it detects that the system is running on a compliant cloud system. it enables both the imds daemon and the client. --- NEWS | 8 ++ man/rules/meson.build | 1 + man/systemd-imds-generator.xml | 107 ++++++++++++++++++ meson.build | 4 +- meson_options.txt | 2 + src/imds/imds-generator.c | 193 +++++++++++++++++++++++++++++++++ src/imds/meson.build | 7 ++ 7 files changed, 321 insertions(+), 1 deletion(-) create mode 100644 man/systemd-imds-generator.xml create mode 100644 src/imds/imds-generator.c diff --git a/NEWS b/NEWS index cdf3c3a0ad918..c34c55603e46a 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,14 @@ CHANGES WITH 261 in spe: attestation environments which use hardware CC registers and not the TPM quote. + * By default networking to cloud IMDS services is now locked down, for + recognized clouds. This is recommended for secure installations, but + typically conflicts with traditional IMDS clients such as cloud-init, + which require direct IMDS access currently. The new meson option + "imds-network" can be used to change the default networking mode to + "unlocked" at build-time, for compatibility. This is probably what + general purpose distributions should set for now. + CHANGES WITH 260: Feature Removals and Incompatible Changes: diff --git a/man/rules/meson.build b/man/rules/meson.build index 0ecf0db5d6957..5c14fb626bb42 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -1045,6 +1045,7 @@ manpages = [ ['systemd-hostnamed.service', '8', ['systemd-hostnamed'], 'ENABLE_HOSTNAMED'], ['systemd-hwdb', '8', [], 'ENABLE_HWDB'], ['systemd-id128', '1', [], ''], + ['systemd-imds-generator', '8', [], 'ENABLE_IMDS'], ['systemd-imds', '1', ['systemd-imds-import.service'], 'ENABLE_IMDS'], ['systemd-imdsd@.service', '8', diff --git a/man/systemd-imds-generator.xml b/man/systemd-imds-generator.xml new file mode 100644 index 0000000000000..d8e1f1aa05b55 --- /dev/null +++ b/man/systemd-imds-generator.xml @@ -0,0 +1,107 @@ + + + + + + + + systemd-imds-generator + systemd + + + + systemd-imds-generator + 8 + + + + systemd-imds-generator + Generator to automatically enable IMDS on supporting environments + + + + /usr/lib/systemd/system-generators/systemd-imds-generator + + + + Description + + systemd-imds-generator is a generator that enables IMDS (Instance Metadata + Service) functionality at boot on systems that support it. Specifically it does three things: + + + It pulls the systemd-imdsd.socket unit (which activates + systemd-imdsd@.service8) + into the initial transaction, which provides IMDS access to local applications via Varlink + IPC. + + It pulls the systemd-imds-early-network.service unit into the + initial transaction, which generates a suitable + systemd.network5 + network configuration file that allows early-boot network access to the IMDS + functionality. + + It pulls the systemd-imds-import.service unit into the initial + transaction, which automatically imports various credentials from IMDS into the local system, storing + them in /run/credstore/. + + + By default, whether to pull in these services or not is decided based on + hwdb7 information, + that detects various IMDS environments automatically. However, this logic may be overridden via + systemd.imds=, see below. + + systemd-imds-generator implements + systemd.generator7. + + + + Kernel Command Line + + systemd-imds-generator understands the following kernel command line + parameters: + + + + + systemd.imds= + + Takes a boolean argument or the special value auto, and may be used to + enable or disable the IMDS logic. Note that this controls only whether the relevant services (as + listed above) are automatically pulled into the initial transaction, it has no effect if some other + unit or the user explicitly activate the relevant units. If this option is not used (or set to + auto) automatic detection of IMDS is used, see above. + + + + + + + + + systemd.imds.import= + + Takes a boolean argument. If false the systemd-imds-import.service (see + above) is not pulled into the initial transaction, i.e. no credentials are imported from + IMDS. Defaults to true. + + + + + + + + + + See Also + + systemd1 + systemd-imds1 + systemd-imdsd@.service8 + systemd.system-credentials7 + + + + diff --git a/meson.build b/meson.build index e35237e452be0..2893bea332f29 100644 --- a/meson.build +++ b/meson.build @@ -1544,6 +1544,7 @@ have = get_option('imds').require( conf.get('HAVE_LIBCURL') == 1, error_message : 'curl required').allowed() conf.set10('ENABLE_IMDS', have) +conf.set10('IMDS_NETWORK_LOCKED_DEFAULT', get_option('imds-network') == 'locked') have = get_option('importd').require( conf.get('HAVE_LIBCURL') == 1 and @@ -3087,7 +3088,8 @@ summary({ 'default user $PATH' : default_user_path != '' ? default_user_path : '(same as system services)', 'systemd service watchdog' : service_watchdog == '' ? 'disabled' : service_watchdog, 'time epoch' : f'@time_epoch@ (@alt_time_epoch@)', - 'TPM2 nvpcr base' : run_command(sh, '-c', 'printf 0x%x @0@'.format(get_option('tpm2-nvpcr-base')), check : true).stdout() + 'TPM2 nvpcr base' : run_command(sh, '-c', 'printf 0x%x @0@'.format(get_option('tpm2-nvpcr-base')), check : true).stdout(), + 'IMDS networking' : get_option('imds-network'), }) # TODO: diff --git a/meson_options.txt b/meson_options.txt index 7835f716662d9..30c5fd3ab67fe 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -144,6 +144,8 @@ option('timesyncd', type : 'boolean', description : 'install the systemd-timesyncd daemon') option('imds', type : 'feature', description : 'install the systemd-imds stack') +option('imds-network', type : 'combo', choices : [ 'locked', 'unlocked' ], + description : 'whether to default to locked/unlocked IMDS network mode') option('journal-storage-default', type : 'combo', choices : ['persistent', 'auto', 'volatile', 'none'], description : 'default storage mode for journald (main namespace)') option('remote', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' }, diff --git a/src/imds/imds-generator.c b/src/imds/imds-generator.c new file mode 100644 index 0000000000000..d33e63bddd323 --- /dev/null +++ b/src/imds/imds-generator.c @@ -0,0 +1,193 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-hwdb.h" + +#include "dropin.h" +#include "fileio.h" +#include "generator.h" +#include "imds-util.h" +#include "log.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "special.h" +#include "string-util.h" +#include "virt.h" + +static int arg_enabled = -1; /* Whether we shall offer local IMDS APIs */ +static bool arg_import = true; /* Whether we shall import IMDS credentials, SSH keys, … into the local system */ +static ImdsNetworkMode arg_network_mode = + IMDS_NETWORK_LOCKED_DEFAULT ? IMDS_NETWORK_LOCKED : IMDS_NETWORK_UNLOCKED; + +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { + int r; + + assert(key); + + if (proc_cmdline_key_streq(key, "systemd.imds")) { + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = parse_tristate_full(value, "auto", &arg_enabled); + if (r < 0) + return log_warning_errno(r, "Failed to parse systemd.imds= value: %m"); + + } else if (proc_cmdline_key_streq(key, "systemd.imds.import")) { + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = parse_boolean(value); + if (r < 0) + return log_warning_errno(r, "Failed to parse systemd.imds.import= value: %m"); + + arg_import = r; + } else if (proc_cmdline_key_streq(key, "systemd.imds.network")) { + if (proc_cmdline_value_missing(key, value)) + return 0; + + ImdsNetworkMode m = imds_network_mode_from_string(value); + if (m < 0) + return log_warning_errno(m, "Failed to parse systemd.imds.network= value: %m"); + + arg_network_mode = m; + } + + return 0; +} + +static int smbios_get_modalias(char **ret) { + int r; + + assert(ret); + + _cleanup_free_ char *modalias = NULL; + r = read_virtual_file("/sys/devices/virtual/dmi/id/modalias", SIZE_MAX, &modalias, /* ret_size= */ NULL); + if (r < 0) + return r; + + truncate_nl(modalias); + + /* To detect Azure we need to check the chassis asset tag. Unfortunately the kernel does not include + * it in the modalias string right now. Let's hence append it manually. This matches similar logic in + * rules.d/60-dmi-id.rules. */ + _cleanup_free_ char *cat = NULL; + r = read_virtual_file("/sys/devices/virtual/dmi/id/chassis_asset_tag", SIZE_MAX, &cat, /* ret_size= */ NULL); + if (r < 0) + log_debug_errno(r, "Failed to read chassis asset tag, ignoring: %m"); + else { + truncate_nl(cat); + + if (!string_has_cc(cat, /* ok= */ NULL) && !isempty(cat) && !strextend(&modalias, "cat", cat, ":")) + return -ENOMEM; + } + + log_debug("Constructed SMBIOS modalias string: %s", modalias); + *ret = TAKE_PTR(modalias); + return 0; +} + +static int smbios_query(void) { + int r; + + /* Let's check whether the DMI device's hwdb data suggests IMDS support is available. Note, we cannot + * ask udev for this, as we typically run long before udev. Hence we'll do the hwdb lookup via + * sd-hwdb directly. */ + + _cleanup_free_ char *modalias = NULL; + r = smbios_get_modalias(&modalias); + if (r == -ENOENT) { + log_debug("No DMI device found, assuming IMDS is not available."); + return false; + } + if (r < 0) + return log_error_errno(r, "Failed to read DMI modalias: %m"); + + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + r = sd_hwdb_new(&hwdb); + if (r < 0) + return log_error_errno(r, "Failed to open hwdb: %m"); + + r = sd_hwdb_seek(hwdb, modalias); + if (r < 0) + return log_error_errno(r, "Failed to seek in hwdb for '%s': %m", modalias); + + for (;;) { + const char *key, *value; + r = sd_hwdb_enumerate(hwdb, &key, &value); + if (r < 0) + return log_error_errno(r, "Failed to enumerate hwdb entry for '%s': %m", modalias); + if (r == 0) + break; + + if (streq(key, "IMDS_VENDOR")) + return true; + } + + log_debug("IMDS_VENDOR= property for DMI device not set, assuming IMDS is not available."); + return false; +} + +static int run(const char *dest, const char *dest_early, const char *dest_late) { + int r; + + r = proc_cmdline_parse(parse_proc_cmdline_item, /* userdata= */ NULL, PROC_CMDLINE_STRIP_RD_PREFIX); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + + if (arg_enabled < 0) { + Virtualization v = detect_container(); + if (v < 0) + log_debug_errno(v, "Container detection failed, ignoring: %m"); + if (v > 0) { + log_debug("Running in a container, disabling IMDS logic."); + arg_enabled = false; + } else { + r = smbios_query(); + if (r < 0) + return r; + arg_enabled = r > 0; + } + } + + if (!arg_enabled) { + log_debug("IMDS not enabled, skipping generator."); + return 0; + } + + log_info("IMDS support enabled, pull in IMDS units."); + + /* Enable IMDS early networking, so that we can actually reach the IMDS server. */ + if (arg_network_mode != IMDS_NETWORK_OFF) { + r = generator_add_symlink(dest_early, SPECIAL_SYSINIT_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/systemd-imds-early-network.service"); + if (r < 0) + return log_error_errno(r, "Failed to hook in systemd-imds-early-network.service: %m"); + } + + /* Enable the IMDS service socket */ + r = generator_add_symlink(dest_early, SPECIAL_SOCKETS_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/systemd-imdsd.socket"); + if (r < 0) + return log_error_errno(r, "Failed to hook in systemd-imdsd.socket: %m"); + + /* We now know the SMBIOS device exists, hence it's safe now to order the IMDS service after it, so + * that it has all properties properly initialized. */ + r = write_drop_in( + dest_early, + "systemd-imdsd@.service", + 50, "dmi-id", + "# Automatically generated by systemd-imds-generator\n\n" + "[Unit]\n" + "Wants=sys-devices-virtual-dmi-id.device\n" + "After=sys-devices-virtual-dmi-id.device\n"); + if (r < 0) + return log_error_errno(r, "Failed to hook DMI id device before systemd-imdsd@.service: %m"); + + if (arg_import) { + /* Enable that we import IMDS data */ + r = generator_add_symlink(dest_early, SPECIAL_SYSINIT_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/systemd-imds-import.service"); + if (r < 0) + return log_error_errno(r, "Failed to hook in systemd-imds-import.service: %m"); + } + + return 0; +} + +DEFINE_MAIN_GENERATOR_FUNCTION(run); diff --git a/src/imds/meson.build b/src/imds/meson.build index a28dd0ca3a510..f9fa9b5f0fb1a 100644 --- a/src/imds/meson.build +++ b/src/imds/meson.build @@ -22,6 +22,13 @@ executables += [ 'imds-util.c' ), }, + generator_template + { + 'name' : 'systemd-imds-generator', + 'sources' : files( + 'imds-generator.c', + 'imds-util.c' + ), + }, ] install_data( From 41dc0dc7b6f71d448d3403e0403ecc34985fffc7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 6 Mar 2026 17:31:10 +0100 Subject: [PATCH 0505/1296] test: add simple integration test for systemd-imdsd --- mkosi/mkosi.sanitizers/mkosi.postinst | 1 + .../TEST-74-AUX-UTILS.units/fake-imds.py | 51 +++++++++++++++ test/units/TEST-74-AUX-UTILS.imds.sh | 62 +++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100755 test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-imds.py create mode 100755 test/units/TEST-74-AUX-UTILS.imds.sh diff --git a/mkosi/mkosi.sanitizers/mkosi.postinst b/mkosi/mkosi.sanitizers/mkosi.postinst index 72356005e9337..17c7d7bad90a4 100755 --- a/mkosi/mkosi.sanitizers/mkosi.postinst +++ b/mkosi/mkosi.sanitizers/mkosi.postinst @@ -44,6 +44,7 @@ wrap=( /usr/lib/polkit-1/polkitd /usr/lib/systemd/tests/testdata/TEST-74-AUX-UTILS.units/proxy-echo.py /usr/libexec/polkit-1/polkitd + /usr/lib/systemd/tests/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-imds.py agetty btrfs capsh diff --git a/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-imds.py b/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-imds.py new file mode 100755 index 0000000000000..e0a28ca766baa --- /dev/null +++ b/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-imds.py @@ -0,0 +1,51 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: LGPL-2.1-or-later + +import os, socket +from http.server import BaseHTTPRequestHandler, HTTPServer + +def sd_notify(state: str) -> bool: + notify_socket = os.environ.get("NOTIFY_SOCKET") + if not notify_socket: + return False + if notify_socket.startswith("@"): + notify_socket = "\0" + notify_socket[1:] + try: + with socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) as sock: + sock.sendto(state.encode(), notify_socket) + except OSError: + return False + + return True + +class Handler(BaseHTTPRequestHandler): + def do_GET(self): + if self.path == "/userdata": + body = b"{\"systemd.credentials\":[{\"name\":\"acredtest\",\"text\":\"avalue\"}]}" + self.send_response(200) + self.send_header("Content-Type", "text/plain") + self.send_header("Content-Length", len(body)) + self.end_headers() + self.wfile.write(body) + elif self.path == "/hostname": + body = b"piff" + self.send_response(200) + self.send_header("Content-Type", "text/plain") + self.send_header("Content-Length", len(body)) + self.end_headers() + self.wfile.write(body) + else: + self.send_error(404) + + def log_message(self, fmt, *args): + print(f"{self.address_string()} - {fmt % args}") + +PORT=8088 + +server = HTTPServer(("", PORT), Handler) +print(f"Serving on http://localhost:{PORT}/") +try: + sd_notify("READY=1") + server.serve_forever() +except KeyboardInterrupt: + print("\nStopped.") diff --git a/test/units/TEST-74-AUX-UTILS.imds.sh b/test/units/TEST-74-AUX-UTILS.imds.sh new file mode 100755 index 0000000000000..2ee0c632d2f6d --- /dev/null +++ b/test/units/TEST-74-AUX-UTILS.imds.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + + +if ! test -x /usr/lib/systemd/systemd-imdsd ; then + echo "No imdsd installed, skipping test." + exit 0 +fi + +at_exit() { + set +e + systemctl stop fake-imds systemd-imdsd.socket ||: + ip link del dummy0 ||: + rm -f /run/credstore/firstboot.hostname /run/credstore/acredtest /run/systemd/system/systemd-imdsd@.service.d/50-env.conf + rmdir /run/systemd/system/systemd-imdsd@.service.d ||: +} + +trap at_exit EXIT + +systemd-run -p Type=notify --unit=fake-imds /usr/lib/systemd/tests/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-imds.py +systemctl status fake-imds + +# Add a fake network interface so that IMDS gets going +ip link add dummy0 type dummy +ip link set dummy0 up +ip addr add 192.168.47.11/24 dev dummy0 + +USERDATA='{"systemd.credentials":[{"name":"acredtest","text":"avalue"}]}' + +# First try imdsd directly +IMDSD="/usr/lib/systemd/systemd-imdsd --vendor=test --data-url=http://192.168.47.11:8088 --well-known-key=userdata:/userdata --well-known-key=hostname:/hostname" +assert_eq "$($IMDSD --well-known=hostname)" "piff" +assert_eq "$($IMDSD --well-known=userdata)" "$USERDATA" +assert_eq "$($IMDSD /hostname)" "piff" +assert_eq "$($IMDSD /userdata)" "$USERDATA" + +# Then, try it as Varlink service +mkdir -p /run/systemd/system/systemd-imdsd@.service.d/ +cat >/run/systemd/system/systemd-imdsd@.service.d/50-env.conf < Date: Thu, 5 Mar 2026 11:36:03 +0100 Subject: [PATCH 0506/1296] update TODO --- TODO | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/TODO b/TODO index 22239961ef69c..5855e36390ab9 100644 --- a/TODO +++ b/TODO @@ -125,11 +125,21 @@ Features: * start making use of the new --graceful switch to util-linux' umount command +* sysusers: allow specifying a path to an inode *and* a literal UID in the UID + column, so that if the inode exists it is used, and if not the literal UID is + used. Use this for services such as the imds one, which run under their own + UID in the initrd, and whose data should survive to the host, properly owned. + +* add service file setting to force the fwmark (a la SO_MARK) to some value, so + that we can allowlist certain services for imds this way. + * make systemd work nicely without /bin/sh, logins and associated shell tools around - make sure debug shell service (sushell) has a nice failure mode, prints a message and reboots - varlink interface for "systemctl start" and friends - https://github.com/util-linux/util-linux/issues/4117 +* imds: maybe do smarter api version handling + * drop NV_ORDERLY flag from the product uuid nvpcr. Effect of the flag is that it pushes the thing into TPM RAM, but a TPM usually has very little of that, less than NVRAM. hence setting the flag amplifies space issues. Unsetting the From c78ba976e179783097ee4799527ba3e474030f35 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 26 Mar 2026 09:25:02 +0100 Subject: [PATCH 0507/1296] mkosi: Install clang-tidy package instead of clang-tools clang-tools surprisingly enough doesn't provide clang-tidy --- mkosi/mkosi.tools.conf/mkosi.conf.d/debian-ubuntu.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkosi/mkosi.tools.conf/mkosi.conf.d/debian-ubuntu.conf b/mkosi/mkosi.tools.conf/mkosi.conf.d/debian-ubuntu.conf index a165ccb04a0cb..4bd4c12fd94de 100644 --- a/mkosi/mkosi.tools.conf/mkosi.conf.d/debian-ubuntu.conf +++ b/mkosi/mkosi.tools.conf/mkosi.conf.d/debian-ubuntu.conf @@ -7,7 +7,7 @@ Distribution=|ubuntu [Content] PrepareScripts=%D/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.prepare Packages= - clang-tools + clang-tidy lcov mypy shellcheck From 82d96837dbe96ddade8ade60393ce3b537ce5cde Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 13 Mar 2026 01:51:21 +0000 Subject: [PATCH 0508/1296] boot: fix typo in function name Follow-up for dde03dd2a843b05d65885ce1242e43c8cabb9924 --- src/boot/splash.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/boot/splash.c b/src/boot/splash.c index 451909eb4ca29..86d4238eb1632 100644 --- a/src/boot/splash.c +++ b/src/boot/splash.c @@ -127,7 +127,7 @@ static EFI_STATUS bmp_parse_header( } enum Channels { R, G, B, A, _CHANNELS_MAX }; -static void read_channel_maks( +static void read_channel_mask( const struct bmp_dib *dib, uint32_t channel_mask[static _CHANNELS_MAX], uint8_t channel_shift[static _CHANNELS_MAX], @@ -187,7 +187,7 @@ static EFI_STATUS bmp_to_blt( uint32_t channel_mask[_CHANNELS_MAX]; uint8_t channel_shift[_CHANNELS_MAX], channel_scale[_CHANNELS_MAX]; - read_channel_maks(dib, channel_mask, channel_shift, channel_scale); + read_channel_mask(dib, channel_mask, channel_shift, channel_scale); /* transform and copy pixels */ in = pixmap; From 186032e1ed93dde8671d4a3106715bd34f9181e0 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 13 Mar 2026 01:52:12 +0000 Subject: [PATCH 0509/1296] boot: add checks for invalid splash images in UKI A malformed bmp with 8bits depth but smaller color map would cause out of bounds reads. This is not a real problem as the image is signed, but better to be safe. Reported on yeswehack.com as: YWH-PGM9780-135 Follow-up for 0fa2cac4f0cdefaf1addd7f1fe0fd8113db9360b --- src/boot/splash.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/boot/splash.c b/src/boot/splash.c index 86d4238eb1632..19bd4bff74a51 100644 --- a/src/boot/splash.c +++ b/src/boot/splash.c @@ -119,6 +119,12 @@ static EFI_STATUS bmp_parse_header( return EFI_INVALID_PARAMETER; } + /* Ensure there can be no OOB accesses in bmp_to_blt() due to malformed images (e.g.: color depth 8 + * but smaller color map) via map[*in]. */ + if (IN_SET(dib->depth, 1, 4, 8) && + file->offset - (sizeof(struct bmp_file) + dib->size) < sizeof(struct bmp_map) * (1U << dib->depth)) + return EFI_INVALID_PARAMETER; + *ret_map = map; *ret_dib = dib; *pixmap = bmp + file->offset; From f9363bc5dacc02f5b9996f5f4677999872ba9a83 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 26 Mar 2026 13:19:15 +0100 Subject: [PATCH 0510/1296] Revert "ci: Add subject_type to createReviewComment()" This reverts commit 211cd6e9a34d957dfa3b7616f0e618b6d17a51c2. They document it here: https://octokit.github.io/rest.js/v22/#pulls-create-review-comment but apparently that's out of date and this doesn't work anymore. --- .github/workflows/claude-review.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 1d08fe01a9497..07fe700b95e1a 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -486,7 +486,6 @@ jobs: commit_id: c.commit, path: c.path, line: c.line, - subject_type: "line", body: `Claude: **${c.severity}**: ${c.body}`, }); posted++; From 437278abd56c3d5593c258e877558f40036845be Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 26 Mar 2026 12:33:38 +0000 Subject: [PATCH 0511/1296] ci: Support multi-line review comments in claude-review Pass side, start_line, and start_side through to createReviewComment() when present, enabling multi-line review comments. Update the prompt to document all positioning fields using JSON Schema and make line required. --- .github/workflows/claude-review.yml | 57 +++++++++++++++++++---------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 07fe700b95e1a..f05ea14d2d5db 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -270,15 +270,37 @@ jobs: for PR context, and reads the codebase to verify findings. Each reviewer reviews code quality, style, potential bugs, and security - implications. It must return a JSON array of issues: - `[{"path": "path/to/file", "line": (optional), "severity": "must-fix|suggestion|nit", "body": "...", "commit": ""}]` + implications. It must return a JSON array of issues matching this schema: + + ```json + { + "type": "array", + "items": { + "type": "object", + "required": ["path", "line", "severity", "body", "commit"], + "properties": { + "path": { "type": "string", "description": "File path relative to repo root" }, + "line": { "type": "integer", "description": "Diff line number (last line for multi-line)" }, + "side": { "enum": ["LEFT", "RIGHT"], "description": "Diff side: LEFT for deletions, RIGHT for additions/context (default: RIGHT)" }, + "start_line": { "type": "integer", "description": "First line of a multi-line comment range" }, + "start_side": { "enum": ["LEFT", "RIGHT"], "description": "Diff side for start_line" }, + "severity": { "enum": ["must-fix", "suggestion", "nit"] }, + "body": { "type": "string", "description": "Review comment in markdown" }, + "commit": { "type": "string", "description": "SHA of the commit being reviewed" } + } + } + } + ``` The `commit` field MUST be the SHA of the commit being reviewed. Only comment on changes in that commit — not preceding commits. - `line` should be a line number from the NEW side of the diff **that appears - inside a diff hunk**. GitHub rejects lines outside the diff context. If you - cannot determine a valid diff line, omit `line`. + `line` should be a line number from the diff **that appears inside a + diff hunk**. GitHub rejects lines outside the diff context. `side` + indicates which side of the diff (`LEFT` for deletions, `RIGHT` for + additions or context lines); defaults to `RIGHT` if omitted. For + multi-line comments, set `start_line` and `start_side` to the first + line of the range and `line`/`side` to the last. Each reviewer MUST verify findings before returning them: - For style/convention claims, check at least 3 existing examples in the @@ -362,24 +384,16 @@ jobs: ```json { - "summary": "...", - "comments": [ - { - "path": "path/to/file", - "line": 42, - "severity": "must-fix|suggestion|nit", - "body": "review comment in markdown", - "commit": "abc123" - } - ], - "resolve": [12345] + "type": "object", + "required": ["summary", "comments"], + "properties": { + "summary": { "type": "string", "description": "Markdown summary for the tracking comment" }, + "comments": { "description": "Array of review comments (same schema as the reviewer output above)" }, + "resolve": { "type": "array", "items": { "type": "integer" }, "description": "REST API IDs of review comment threads to resolve" } + } } ``` - - `summary` (required): markdown summary for the tracking comment - - `comments` (required): array of review comments; `path` is the file path, `line` is optional - - `resolve` (optional): REST API IDs of review comment threads to resolve - Do NOT attempt to post comments or use any MCP tools to modify the PR. - name: Upload review result @@ -486,6 +500,9 @@ jobs: commit_id: c.commit, path: c.path, line: c.line, + ...(c.side != null && { side: c.side }), + ...(c.start_line != null && { start_line: c.start_line }), + ...(c.start_side != null && { start_side: c.start_side }), body: `Claude: **${c.severity}**: ${c.body}`, }); posted++; From 0c2747e7892c70cb3454523b96e5c801d4ab3af4 Mon Sep 17 00:00:00 2001 From: Cynthia Date: Tue, 17 Mar 2026 23:30:31 +0100 Subject: [PATCH 0512/1296] kernel-install(uki): filter comments from cmdline This change aligns the behaviour of UKI generation with the behaviour of BLS. The latter filters out lines starting with a #, allowing users to add comments and/or temporarily remove some flags from the kernel command line. The kernel-install test have been adjusted to use a multiline cmdline with a comment in it. Without this patch, the test fails. --- man/kernel-install.xml | 6 +++--- src/kernel-install/60-ukify.install.in | 7 ++++++- src/kernel-install/test-kernel-install.sh | 7 ++++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/man/kernel-install.xml b/man/kernel-install.xml index 38c183be243e3..0bf57b4d5341e 100644 --- a/man/kernel-install.xml +++ b/man/kernel-install.xml @@ -616,9 +616,9 @@ /proc/cmdline Specifies the kernel command line to use. The first of the files that is found will be used. - When running in a container, /proc/cmdline is ignored. - $KERNEL_INSTALL_CONF_ROOT may be used to override the search path; see below for - details. + Lines starting with the # character are ignored. When running in a container, + /proc/cmdline is ignored. $KERNEL_INSTALL_CONF_ROOT may be + used to override the search path; see below for details. diff --git a/src/kernel-install/60-ukify.install.in b/src/kernel-install/60-ukify.install.in index 076390dd0475e..310b6f26cafc0 100755 --- a/src/kernel-install/60-ukify.install.in +++ b/src/kernel-install/60-ukify.install.in @@ -185,7 +185,12 @@ def devicetree_file_location(opts) -> Optional[Path]: def kernel_cmdline_base() -> list[str]: path = input_file_location('cmdline') if path: - return path.read_text().split() + # Filter out commented out lines from cmdline. + lines = path.read_text().splitlines() + return [opt + for line in lines + if not line.startswith('#') + for opt in line.split()] # If we read /proc/cmdline, we need to do some additional filtering. options = Path('/proc/cmdline').read_text().split() diff --git a/src/kernel-install/test-kernel-install.sh b/src/kernel-install/test-kernel-install.sh index e2add8ba80b5c..399979a0260e2 100755 --- a/src/kernel-install/test-kernel-install.sh +++ b/src/kernel-install/test-kernel-install.sh @@ -29,7 +29,12 @@ mkdir -p "$D/sources" echo 'buzy image' >"$D/sources/linux" echo 'the initrd' >"$D/sources/initrd" echo 'the-token' >"$D/sources/entry-token" -echo 'opt1 opt2' >"$D/sources/cmdline" + +cat >"$D/sources/cmdline" <"$D/sources/install.conf" < Date: Mon, 9 Mar 2026 13:06:42 +0100 Subject: [PATCH 0513/1296] fileio: add WRITE_DATA_FILE_MODE_0400 mode --- src/basic/fileio.c | 2 +- src/basic/fileio.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 66d06484dc981..7edf54edf37fe 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -1706,7 +1706,7 @@ int write_data_file_atomic_at( return r; } - r = fchmod_umask(fd, 0644); + r = fchmod_umask(fd, FLAGS_SET(flags, WRITE_DATA_FILE_MODE_0400) ? 0400 : 0644); if (r < 0) return r; diff --git a/src/basic/fileio.h b/src/basic/fileio.h index 3e2372c4dddbc..274fdfbd7c89a 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -166,6 +166,7 @@ int fopen_mode_to_flags(const char *mode); typedef enum WriteDataFileFlags { WRITE_DATA_FILE_MKDIR_0755 = 1 << 0, + WRITE_DATA_FILE_MODE_0400 = 1 << 1, } WriteDataFileFlags; int write_data_file_atomic_at(int dir_fd, const char *path, const struct iovec *iovec, WriteDataFileFlags flags); From 6718ba1769184d3e7ebe06801d44d55e30e92ac8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 9 Mar 2026 18:52:20 +0100 Subject: [PATCH 0514/1296] gpt-auto-generator: generate an initrd ESP mount if it makes sense We need to store state persistently for the software TPM (i.e. the root key). But given that TPMs are generally used to unlock the rootfs, this storage cannot be on the rootfs. Hence let's use the ESP instead, as the next best thing, that is guaranteed to exist during early boot, given we just were booted from it. This defines automatic logic for this, but does not cause the ESP mount job to be enqueued (since typically we don't actually want that mounted), this is left for the actual services that needs to be done. Note that the mount here is set up quite differently from the one from the host: since initrds are short-lived anyway, it seemed pointless to use autofs. Moreover this uses a fixed place to mount the ESP, inspired by the /sysroot/ + /sysusr/ mount naming. All that to simplify things a bit for the consumers (which is mostly swtpm) --- man/systemd-gpt-auto-generator.xml | 13 +++- src/gpt-auto-generator/gpt-auto-generator.c | 73 ++++++++++++++++++--- 2 files changed, 74 insertions(+), 12 deletions(-) diff --git a/man/systemd-gpt-auto-generator.xml b/man/systemd-gpt-auto-generator.xml index e267fc952870e..6494a79d22dcc 100644 --- a/man/systemd-gpt-auto-generator.xml +++ b/man/systemd-gpt-auto-generator.xml @@ -75,6 +75,13 @@ discovery based on the boot loader reported ESP which is also enabled if no root= parameter is specified at all. (The latter relies on systemd-udevd.service's /dev/gpt-auto-root block device symlink generation). + + It will also generate a unit file mounting the EFI System Partition (ESP) to + /sysefi/, if applicable. Unlike the file systems mentioned above this mount is not + activated by default however, but can be pulled in by services requiring ESP access from within the + initrd. Note that this mount point is initrd-specific and does not make use of autofs. The ESP is + typically mounted at a different place and via autofs once the system transitions out of the + initrd. @@ -179,7 +186,7 @@ SD_GPT_ESP c12a7328-f81f-11d2-ba4b-00a0c93ec93b EFI System Partition (ESP) - /efi/ or /boot/ + /efi/ or /boot/ once the system transitioned out of the initrd, /sysefi/ before The first partition with this type UUID located on the same disk as the root partition is mounted to /boot/ or /efi/, see below. @@ -259,7 +266,9 @@ The ESP is mounted to /boot/ if that directory exists and is not used for XBOOTLDR, and otherwise to /efi/. Same as for /boot/, an - automount unit is used. The mount point will be created if necessary. + automount unit is used. The mount point will be created if necessary. These apply once the system + transitioned out of the initrd phase. Before that, if components in the initrd require ESP access, it + will be mounted to /sysefi/. No configuration is created for mount points that are configured in fstab5 or when diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index 4fd92a6057f6a..a87b514080587 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -307,7 +307,8 @@ static int add_mount( MountPointFlags flags, const char *options, const char *description, - const char *post) { + const char *post, + const char *conflicts) { _cleanup_free_ char *unit = NULL, *crypto_what = NULL, *opts_filtered = NULL; _cleanup_fclose_ FILE *f = NULL; @@ -377,6 +378,12 @@ static int add_mount( if (r < 0) return r; + if (conflicts) + fprintf(f, + "Conflicts=%1$s\n" + "Before=%1$s\n", + conflicts); + fprintf(f, "\n" "[Mount]\n" @@ -493,7 +500,8 @@ static int add_partition_mount( (STR_IN_SET(id, "root", "var") ? MOUNT_MEASURE : 0), /* by default measure rootfs and /var, since they contain the "identity" of the system */ options, description, - SPECIAL_LOCAL_FS_TARGET); + SPECIAL_LOCAL_FS_TARGET, + /* conflicts= */ NULL); } static int add_partition_swap(DissectedPartition *p) { @@ -582,7 +590,8 @@ static int add_automount( flags, options, description, - /* post= */ NULL); + /* post= */ NULL, + /* conflicts= */ NULL); if (r < 0) return r; @@ -919,7 +928,8 @@ static int add_root_mount(void) { MOUNT_MEASURE, options, "Root Partition", - in_initrd() ? SPECIAL_INITRD_ROOT_FS_TARGET : SPECIAL_LOCAL_FS_TARGET); + in_initrd() ? SPECIAL_INITRD_ROOT_FS_TARGET : SPECIAL_LOCAL_FS_TARGET, + /* conflicts= */ NULL); #else return 0; #endif @@ -995,7 +1005,8 @@ static int add_usr_mount(void) { /* flags= */ 0, options, "/usr/ Partition", - in_initrd() ? SPECIAL_INITRD_USR_FS_TARGET : SPECIAL_LOCAL_FS_TARGET); + in_initrd() ? SPECIAL_INITRD_USR_FS_TARGET : SPECIAL_LOCAL_FS_TARGET, + /* conflicts= */ NULL); if (r < 0) return r; @@ -1009,7 +1020,8 @@ static int add_usr_mount(void) { MOUNT_VALIDATEFS, "bind", "/usr/ Partition (Final)", - SPECIAL_INITRD_FS_TARGET); + SPECIAL_INITRD_FS_TARGET, + /* conflicts= */ NULL); if (r < 0) return r; } @@ -1017,6 +1029,49 @@ static int add_usr_mount(void) { return 0; } +static int add_early_esp_mount(void) { + int r; + + /* Early ESP discovery is a bit different than the other mounts here: it's purely about the initrd, + * and goes away during the transition to the host (where it might likely be mounted again, but then + * via autofs, hence lazily). Moreover, the location is fixed → /sysefi/, i.e. we do not bother with + * XBOOTLDR vs. ESP for this. Also, the mount is not pulled in by default, but is expected to be + * pulled in by the component that uses it. + * + * The initial usecase for this is software TPM that needs a place to store its state before the root + * file system can be mounted. + * + * Or in other words: this is much simpler, more focussed on a short-lived boot-time operation than + * the regular logic during later boot. */ + + if (!in_initrd()) + return 0; + + if (!is_efi_boot()) + return 0; + + _cleanup_free_ char *options = NULL; + r = partition_pick_mount_options( + PARTITION_ESP, + "vfat", + /* rw= */ true, + /* discard= */ false, + &options, + /* ret_ms_flags= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to pick ESP mount options: %m"); + + return add_mount("esp", + "/dev/disk/by-designator/esp", + "/sysefi/", + "vfat", + MOUNT_RW, + options, + "EFI System Partition (Early)", + /* post= */ NULL, + /* conflicts= */ "initrd-switch-root.target"); +} + static int process_loader_partitions(DissectedPartition *esp, DissectedPartition *xbootldr) { sd_id128_t loader_uuid; int r; @@ -1190,7 +1245,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat return 0; /* Disable root disk logic if there's a root= value specified (unless it happens to be - * "gpt-auto" or "gpt-auto-force") */ + * "gpt-auto", "gpt-auto-force", "dissect", "dissect-force") */ arg_auto_root = parse_gpt_auto_root("root=", value); assert(arg_auto_root >= 0); @@ -1244,9 +1299,6 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat if (proc_cmdline_value_missing(key, value)) return 0; - /* Disable root disk logic if there's a root= value specified (unless it happens to be - * "gpt-auto" or "gpt-auto-force") */ - arg_auto_usr = parse_gpt_auto_root("mount.usr=", value); assert(arg_auto_usr >= 0); @@ -1325,6 +1377,7 @@ static int run(const char *dest, const char *dest_early, const char *dest_late) r = 0; RET_GATHER(r, add_root_mount()); RET_GATHER(r, add_usr_mount()); + RET_GATHER(r, add_early_esp_mount()); RET_GATHER(r, add_mounts()); return r; From 1bdb3625c76ea238571793e09def750291a434d8 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Thu, 26 Mar 2026 12:46:56 +0100 Subject: [PATCH 0515/1296] core: drop `_manager()` from new vl_method to shutdown Drop the _manager suffix for vl_method_{poweroff,{soft,}reboot, halt,kexec}_manager. They're unambiguous enough and the power mgmt operations are not manager-wide but system-wide. Thanks to @YHNdnzj for suggesting this. --- src/core/varlink-manager.c | 10 +++++----- src/core/varlink-manager.h | 11 ++++++----- src/core/varlink.c | 10 +++++----- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index bad37206328dd..16b640591b398 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -458,22 +458,22 @@ static int manager_do_set_objective(sd_varlink *link, sd_json_variant *parameter return sd_varlink_reply(link, NULL); } -int vl_method_poweroff_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { +int vl_method_poweroff(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { return manager_do_set_objective(link, parameters, MANAGER_POWEROFF, "halt", /* can_do_root= */ false); } -int vl_method_reboot_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { +int vl_method_reboot(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { return manager_do_set_objective(link, parameters, MANAGER_REBOOT, "reboot", /* can_do_root= */ false); } -int vl_method_halt_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { +int vl_method_halt(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { return manager_do_set_objective(link, parameters, MANAGER_HALT, "halt", /* can_do_root= */ false); } -int vl_method_kexec_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { +int vl_method_kexec(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { return manager_do_set_objective(link, parameters, MANAGER_KEXEC, "reboot", /* can_do_root= */ false); } -int vl_method_soft_reboot_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { +int vl_method_soft_reboot(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { return manager_do_set_objective(link, parameters, MANAGER_SOFT_REBOOT, "reboot", /* can_do_root= */ true); } diff --git a/src/core/varlink-manager.h b/src/core/varlink-manager.h index 0e477e761b0d9..46c737f9a94c6 100644 --- a/src/core/varlink-manager.h +++ b/src/core/varlink-manager.h @@ -9,8 +9,9 @@ int vl_method_describe_manager(sd_varlink *link, sd_json_variant *parameters, sd int vl_method_reexecute_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_reload_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_enqueue_marked_jobs_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); -int vl_method_poweroff_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); -int vl_method_reboot_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); -int vl_method_halt_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); -int vl_method_kexec_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); -int vl_method_soft_reboot_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); + +int vl_method_poweroff(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_reboot(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_halt(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_kexec(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_soft_reboot(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/core/varlink.c b/src/core/varlink.c index 77b2d3bd82997..533d1061b8eb7 100644 --- a/src/core/varlink.c +++ b/src/core/varlink.c @@ -390,11 +390,11 @@ int manager_setup_varlink_server(Manager *m) { "io.systemd.Manager.Reexecute", vl_method_reexecute_manager, "io.systemd.Manager.Reload", vl_method_reload_manager, "io.systemd.Manager.EnqueueMarkedJobs", vl_method_enqueue_marked_jobs_manager, - "io.systemd.Manager.PowerOff", vl_method_poweroff_manager, - "io.systemd.Manager.Reboot", vl_method_reboot_manager, - "io.systemd.Manager.Halt", vl_method_halt_manager, - "io.systemd.Manager.KExec", vl_method_kexec_manager, - "io.systemd.Manager.SoftReboot", vl_method_soft_reboot_manager, + "io.systemd.Manager.PowerOff", vl_method_poweroff, + "io.systemd.Manager.Reboot", vl_method_reboot, + "io.systemd.Manager.Halt", vl_method_halt, + "io.systemd.Manager.KExec", vl_method_kexec, + "io.systemd.Manager.SoftReboot", vl_method_soft_reboot, "io.systemd.Unit.List", vl_method_list_units, "io.systemd.Unit.SetProperties", vl_method_set_unit_properties, "io.systemd.service.Ping", varlink_method_ping, From 34eec125d63ee99559bb5528ac479c0ace65173c Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Thu, 26 Mar 2026 13:16:57 +0100 Subject: [PATCH 0516/1296] core: simplify manager_do_set_objective `root` path handling The manager_do_set_objective() was doing a bunch of work to check the `root` path that can already be done via `json_dispatch_path` so instead of duplicating use the helper. Thanks to @YHNdnzj for suggesting this. --- src/core/varlink-manager.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index 16b640591b398..ce7f0599deab8 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -405,8 +405,7 @@ int vl_method_enqueue_marked_jobs_manager(sd_varlink *link, sd_json_variant *par static int manager_do_set_objective(sd_varlink *link, sd_json_variant *parameters, ManagerObjective objective, const char *selinux_permission, bool can_do_root) { Manager *m = ASSERT_PTR(sd_varlink_get_userdata(link)); - _cleanup_free_ char *rt = NULL; - const char *root = NULL; + _cleanup_free_ char *root = NULL; int r; assert(link); @@ -417,7 +416,7 @@ static int manager_do_set_objective(sd_varlink *link, sd_json_variant *parameter if (can_do_root) { static const sd_json_dispatch_field dispatch_table[] = { - { "root", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, 0 }, + { "root", SD_JSON_VARIANT_STRING, json_dispatch_path, 0, 0 }, {} }; @@ -438,21 +437,15 @@ static int manager_do_set_objective(sd_varlink *link, sd_json_variant *parameter if (r < 0) return r; - if (!isempty(root)) { - if (!path_is_valid(root)) - return sd_varlink_error_invalid_parameter_name(link, "root"); - if (!path_is_absolute(root)) - return sd_varlink_error_invalid_parameter_name(link, "root"); - - r = path_simplify_alloc(root, &rt); - if (r < 0) - return r; + if (root) { + assert(can_do_root); + path_simplify(root); } varlink_log_caller(link, m, manager_objective_to_string(objective)); if (can_do_root) - free_and_replace(m->switch_root, rt); + free_and_replace(m->switch_root, root); m->objective = objective; return sd_varlink_reply(link, NULL); From 21b25d84709d124df68253061ca2a7a0e04628d9 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Thu, 26 Mar 2026 12:55:54 +0100 Subject: [PATCH 0517/1296] core: add assert(selinux_permission) to manager_do_set_objective Thanks to @YHNdnzj for suggesting this. --- src/core/varlink-manager.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index ce7f0599deab8..5528c2f8ef277 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -410,6 +410,7 @@ static int manager_do_set_objective(sd_varlink *link, sd_json_variant *parameter assert(link); assert(parameters); + assert(selinux_permission); if (!MANAGER_IS_SYSTEM(m)) return sd_varlink_error(link, SD_VARLINK_ERROR_METHOD_NOT_IMPLEMENTED, NULL); From 1e9f0d76fb2a984c53adc4ddb7b481ae8f3f9b90 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Thu, 26 Mar 2026 13:25:02 +0100 Subject: [PATCH 0518/1296] core: drop incorrect comment about SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT) The comment about `SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)` is incorrect. To quote @YHNdnzj: ``` nah, the capability-based permission model is a legacy from kdbus. We cannot do it race-freely without it. Please simply drop the comment. ``` Thanks to @YHNdnzj for suggesting this. --- src/core/varlink-manager.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index 5528c2f8ef277..8082b000aaedb 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -431,9 +431,6 @@ static int manager_do_set_objective(sd_varlink *link, sd_json_variant *parameter if (r < 0) return r; - /* dbus uses SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT) in its checking. We cannot do the same - * because reading capabilities from /proc is racy (TOCTOU). So we use the stricter check - * TODO: figure out a way to check for CAP_SYS_BOOT */ r = varlink_check_privileged_peer(link); if (r < 0) return r; From 31b9f451d73e19a43a0f0ead495ee525adc45fbd Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Thu, 26 Mar 2026 12:51:23 +0100 Subject: [PATCH 0519/1296] core: always call manager_log_caller() even without pidref Its fine if `manager_log_caller()` with an empty pidref, it will log an unknown caller. Thanks to @YHNdnzj for suggesting this. --- src/core/dbus-manager.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index fec53341caecf..088d6c508ee91 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -1543,17 +1543,20 @@ static int method_refuse_snapshot(sd_bus_message *message, void *userdata, sd_bu static void log_caller(sd_bus_message *message, Manager *manager, const char *method) { _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + int r; assert(message); assert(manager); assert(method); - if (sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID|SD_BUS_CREDS_PIDFD|SD_BUS_CREDS_AUGMENT, &creds) < 0) - return; - - /* We need at least the PID, otherwise there's nothing to log, the rest is optional. */ - if (bus_creds_get_pidref(creds, &pidref) < 0) - return; + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID|SD_BUS_CREDS_PIDFD|SD_BUS_CREDS_AUGMENT, &creds); + if (r < 0) + log_debug_errno(r, "Failed to get dbus sender creds, ignoring: %m"); + else { + r = bus_creds_get_pidref(creds, &pidref); + if (r < 0) + log_debug_errno(r, "Failed to get peer pidref, ignoring: %m"); + } manager_log_caller(manager, &pidref, method); } From 82bfcc37e2677394ccb075cdb64e5873ec5e906d Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 25 Mar 2026 16:45:27 +0100 Subject: [PATCH 0520/1296] cleanup-util: include assert-fundamental.h instead of -util The split was initially done to reduce transitive includes, and for assert() only the former is needed. --- src/basic/cleanup-util.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basic/cleanup-util.h b/src/basic/cleanup-util.h index 068696d9771ca..041c37530aaa9 100644 --- a/src/basic/cleanup-util.h +++ b/src/basic/cleanup-util.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "assert-util.h" +#include "assert-fundamental.h" #include "cleanup-fundamental.h" /* IWYU pragma: export */ typedef void (*free_func_t)(void *p); From 1060f82611be352bf77ec1eb82ec8e536849174a Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 25 Mar 2026 15:39:26 +0100 Subject: [PATCH 0521/1296] creds: use parse_tristate_argument_with_auto() where appropriate --- src/creds/creds.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/creds/creds.c b/src/creds/creds.c index 5f60f781f6c45..9e1b2cfbcc46f 100644 --- a/src/creds/creds.c +++ b/src/creds/creds.c @@ -940,15 +940,9 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_NEWLINE: - if (isempty(optarg) || streq(optarg, "auto")) - arg_newline = -1; - else { - r = parse_boolean_argument("--newline=", optarg, NULL); - if (r < 0) - return r; - - arg_newline = r; - } + r = parse_tristate_argument_with_auto("--newline=", optarg, &arg_newline); + if (r < 0) + return r; break; case 'p': From 64d1c102ad3af9ce5f640b2fbffb8113697fd0e3 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 25 Mar 2026 16:00:14 +0100 Subject: [PATCH 0522/1296] creds: if newline is explicitly requested, skip tty check Before this commit, the > 0 state of arg_newline tristate is simply ignored. Yes, this is a minor compat break, but I'd argue the previous behavior was not useful as "yes" is treated the same as "auto". An issue also reported that it was quite surprising. Fixes #41348 --- src/creds/creds.c | 12 ++++++------ test/units/TEST-54-CREDS.sh | 5 ++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/creds/creds.c b/src/creds/creds.c index 9e1b2cfbcc46f..3103686a9f8e3 100644 --- a/src/creds/creds.c +++ b/src/creds/creds.c @@ -398,8 +398,6 @@ static int transcode( } static int print_newline(FILE *f, const char *data, size_t l) { - int fd; - assert(f); assert(data || l == 0); @@ -411,10 +409,12 @@ static int print_newline(FILE *f, const char *data, size_t l) { if (l > 0 && data[l-1] == '\n') return 0; - /* Don't bother unless this is a tty */ - fd = fileno(f); - if (fd >= 0 && !isatty_safe(fd)) - return 0; + /* If not explicitly requested, don't bother if the output is not a tty */ + if (arg_newline < 0) { + int fd = fileno(f); + if (fd >= 0 && !isatty_safe(fd)) + return 0; + } if (fputc('\n', f) != '\n') return log_error_errno(errno, "Failed to write trailing newline: %m"); diff --git a/test/units/TEST-54-CREDS.sh b/test/units/TEST-54-CREDS.sh index 0eaf8a2dfb0d8..523046ec765b0 100755 --- a/test/units/TEST-54-CREDS.sh +++ b/test/units/TEST-54-CREDS.sh @@ -114,10 +114,9 @@ run_with_cred_compare "mycred:" "" cat mycred run_with_cred_compare "mycred:\n" "\n" cat mycred run_with_cred_compare "mycred:foo" "foo" cat mycred run_with_cred_compare "mycred:foo" "foofoofoo" cat mycred mycred mycred -# Note: --newline= does nothing when stdout is not a tty, which is the case here -run_with_cred_compare "mycred:foo" "foo" --newline=yes cat mycred -run_with_cred_compare "mycred:foo" "foo" --newline=no cat mycred run_with_cred_compare "mycred:foo" "foo" --newline=auto cat mycred +run_with_cred_compare "mycred:foo" "foo" --newline=no cat mycred +run_with_cred_compare "mycred:foo" "foo\n" --newline=yes cat mycred run_with_cred_compare "mycred:foo" "foo" --transcode=no cat mycred run_with_cred_compare "mycred:foo" "foo" --transcode=0 cat mycred run_with_cred_compare "mycred:foo" "foo" --transcode=false cat mycred From 734af2908a803c17ad587da7db334fc225f18dde Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 25 Mar 2026 16:03:07 +0100 Subject: [PATCH 0523/1296] creds: minor tweak for fputc() error handling Let's do not assume errno is set for return values other than EOF, following what we do in fileio.c. --- src/creds/creds.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/creds/creds.c b/src/creds/creds.c index 3103686a9f8e3..3b1cc8e86b64c 100644 --- a/src/creds/creds.c +++ b/src/creds/creds.c @@ -416,7 +416,7 @@ static int print_newline(FILE *f, const char *data, size_t l) { return 0; } - if (fputc('\n', f) != '\n') + if (fputc('\n', f) == EOF) return log_error_errno(errno, "Failed to write trailing newline: %m"); return 1; From a42fbad472c59bf0320d07ea92d3b19f8352990a Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 26 Mar 2026 15:06:16 +0000 Subject: [PATCH 0524/1296] labeler: update to latest commit Adds 'changed-files-labels-limit' and 'max-files-changed' configs --- .github/workflows/labeler.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 49b6d1fb36734..48d926a62b9a4 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -36,7 +36,7 @@ jobs: persist-credentials: false - name: Label PR based on policy in labeler.yml - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b + uses: actions/labeler@c5dadc2a45784a4b6adfcd20fea3465da3a5f904 if: startsWith(github.event_name, 'pull_request') && github.base_ref == 'main' && github.event.action != 'closed' with: repo-token: "${{ secrets.GITHUB_TOKEN }}" From c30656f35a8c9db015eb9ae850378d78d37da244 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 26 Mar 2026 15:07:56 +0000 Subject: [PATCH 0525/1296] labeler: limit file-based label to 5 When doing large refactors or large changes the bot spams labels left and right, making the PR unreadable. Use the new option to limit the bot to a max of 5 file-based labels. If more than 5 would be set, all file-based labels are skipped. --- .github/labeler.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/labeler.yml b/.github/labeler.yml index 65ac975025214..5e90662c284ea 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,6 +1,7 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # vi: sw=2 ts=2 et: +changed-files-labels-limit: 5 analyze: - changed-files: - any-glob-to-any-file: 'src/analyze/*' From 056c21aaebce6f7fd83ffe6a1784ff2692dc8744 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 9 Mar 2026 13:06:58 +0100 Subject: [PATCH 0526/1296] tpm2: add "systemd-tpm2-swtpm" wrapper for "swtpm" For TPM-less systems it's sometimes valuable to have a fill-in software TPM running from early boot on, so that TPM-based functionality can "just work" and rely on TPM semantics, even if it's at a substantially weaker security level. This adds a wrapper around swtpm. It's a binary that chainloads swtpm but does a few preparatory steps and integrates into systemd's logic otherwise. All this is then exposed as systemd-tpm2-swtpm.service. The service is not hooked into much yet, that is added in later commits. --- man/rules/meson.build | 4 + man/systemd-tpm2-swtpm.service.xml | 63 +++++ mkosi/mkosi.conf | 1 + mkosi/mkosi.conf.d/centos-fedora/mkosi.conf | 1 + mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf | 1 + mkosi/mkosi.initrd.conf/mkosi.conf | 1 + .../mkosi.conf.d/centos-fedora.conf | 1 + .../mkosi.conf.d/debian-ubuntu.conf | 1 + src/tpm2-setup/meson.build | 9 + src/tpm2-setup/tpm2-swtpm.c | 221 ++++++++++++++++++ units/meson.build | 4 + units/systemd-tpm2-swtpm.service.in | 26 +++ 12 files changed, 333 insertions(+) create mode 100644 man/systemd-tpm2-swtpm.service.xml create mode 100644 src/tpm2-setup/tpm2-swtpm.c create mode 100644 units/systemd-tpm2-swtpm.service.in diff --git a/man/rules/meson.build b/man/rules/meson.build index 5c14fb626bb42..0b5e438200f7a 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -1237,6 +1237,10 @@ manpages = [ '8', ['systemd-tpm2-setup', 'systemd-tpm2-setup-early.service'], 'ENABLE_BOOTLOADER'], + ['systemd-tpm2-swtpm.service', + '8', + ['systemd-tpm2-swtpm'], + 'ENABLE_BOOTLOADER'], ['systemd-tty-ask-password-agent', '1', [], ''], ['systemd-udev-settle.service', '8', [], ''], ['systemd-udevd.service', diff --git a/man/systemd-tpm2-swtpm.service.xml b/man/systemd-tpm2-swtpm.service.xml new file mode 100644 index 0000000000000..3111a782c7846 --- /dev/null +++ b/man/systemd-tpm2-swtpm.service.xml @@ -0,0 +1,63 @@ + + + + + + + + systemd-tpm2-swtpm.service + systemd + + + + systemd-tpm2-swtpm.service + 8 + + + + systemd-tpm2-swtpm.service + systemd-tpm2-swtpm + Provide a fallback software TPM + + + + systemd-tpm2-swtpm.service + /usr/lib/systemd/systemd-tpm2-swtpm + + + + Description + + The systemd-tpm2-swtpm.service provides fallback software TPM functionality, + intended for use in environments where a discrete or firmware TPM ("hardware TPM") is not available. It is + pulled into the boot process by + systemd-tpm2-generator8 + if a hardware TPM is not available, and the system is configured to provide a software TPM in that case. + + Note that a software TPM provides only very weak security properties compared to a hardware TPM, + and hence should only be used as a fallback mechanism if a hardware TPM is not available but TPM + semantics are desired. This service ultimately wraps + swtpm8. + + If the boot secret /.extra/boot-secret (in the initrd) or + /run/systemd/stub/boot-secret (on the host) is available the software TPM NVRAM + storage is encrypted with this key. See + systemd-stub7 for + details. + + The TPM NVRAM storage is placed on the EFI System Partition as it needs to be accessible during + very early boot-up, in particular before the root file system is decrypted and mounted. + + + + See Also + + systemd1 + systemd-tpm2-generator8 + swtpm8 + systemd-stub7 + + + diff --git a/mkosi/mkosi.conf b/mkosi/mkosi.conf index 80c4e59390c95..8bbf964664e85 100644 --- a/mkosi/mkosi.conf +++ b/mkosi/mkosi.conf @@ -123,6 +123,7 @@ Packages= sed socat strace + swtpm tar tree util-linux diff --git a/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf b/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf index fbbc6a90bf91c..416e71ba32175 100644 --- a/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf +++ b/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf @@ -63,6 +63,7 @@ Packages= softhsm squashfs-tools stress-ng + swtpm-tools tpm2-tools veritysetup vim-common diff --git a/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf b/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf index f024eae204d0f..80dc87213a4eb 100644 --- a/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf +++ b/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf @@ -71,6 +71,7 @@ Packages= softhsm2 squashfs-tools stress-ng + swtpm-tools tgt tpm2-tools tzdata diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf b/mkosi/mkosi.initrd.conf/mkosi.conf index 1c73f3a328440..207b15f98c903 100644 --- a/mkosi/mkosi.initrd.conf/mkosi.conf +++ b/mkosi/mkosi.initrd.conf/mkosi.conf @@ -16,4 +16,5 @@ Packages= findutils grep sed + swtpm tar diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf.d/centos-fedora.conf b/mkosi/mkosi.initrd.conf/mkosi.conf.d/centos-fedora.conf index 1a971625bfe7f..a35ccb4a0e446 100644 --- a/mkosi/mkosi.initrd.conf/mkosi.conf.d/centos-fedora.conf +++ b/mkosi/mkosi.initrd.conf/mkosi.conf.d/centos-fedora.conf @@ -7,6 +7,7 @@ Distribution=|fedora [Content] PrepareScripts=%D/mkosi/mkosi.conf.d/centos-fedora/systemd.prepare Packages= + swtpm-tools tpm2-tools VolatilePackages= diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf.d/debian-ubuntu.conf b/mkosi/mkosi.initrd.conf/mkosi.conf.d/debian-ubuntu.conf index 7f2566e9938d2..1e5e8942373bd 100644 --- a/mkosi/mkosi.initrd.conf/mkosi.conf.d/debian-ubuntu.conf +++ b/mkosi/mkosi.initrd.conf/mkosi.conf.d/debian-ubuntu.conf @@ -9,6 +9,7 @@ PrepareScripts=%D/mkosi/mkosi.conf.d/debian-ubuntu/systemd.prepare Packages= btrfs-progs tpm2-tools + swtpm-tools VolatilePackages= libsystemd-shared diff --git a/src/tpm2-setup/meson.build b/src/tpm2-setup/meson.build index a862e7239cc6b..bac29cbbdcabe 100644 --- a/src/tpm2-setup/meson.build +++ b/src/tpm2-setup/meson.build @@ -22,6 +22,15 @@ executables += [ 'HAVE_TPM2', ], }, + libexec_template + { + 'name' : 'systemd-tpm2-swtpm', + 'sources' : files('tpm2-swtpm.c'), + 'conditions' : [ + 'ENABLE_BOOTLOADER', + 'HAVE_OPENSSL', + 'HAVE_TPM2', + ], + }, generator_template + { 'name' : 'systemd-tpm2-generator', 'sources' : files('tpm2-generator.c'), diff --git a/src/tpm2-setup/tpm2-swtpm.c b/src/tpm2-setup/tpm2-swtpm.c new file mode 100644 index 0000000000000..9522420d86645 --- /dev/null +++ b/src/tpm2-setup/tpm2-swtpm.c @@ -0,0 +1,221 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-daemon.h" + +#include "alloc-util.h" +#include "chase.h" +#include "errno-util.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "find-esp.h" +#include "fs-util.h" +#include "hexdecoct.h" +#include "hmac.h" +#include "initrd-util.h" +#include "iovec-util.h" +#include "log.h" +#include "main-func.h" +#include "path-lookup.h" +#include "path-util.h" +#include "sha256.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "swtpm-util.h" + +#define BOOT_SECRET_SIZE 32U + +static int load_boot_secret(struct iovec *ret) { + _cleanup_(iovec_done_erase) struct iovec buf = {}; + int r; + + const char *bs = in_initrd() ? "/.extra/boot-secret" : "/run/systemd/stub/boot-secret"; + r = read_full_file_full( + AT_FDCWD, + bs, + UINT64_MAX, + BOOT_SECRET_SIZE, + READ_FULL_FILE_SECURE|READ_FULL_FILE_VERIFY_REGULAR, + /* bind_name= */ NULL, + (char**) &buf.iov_base, + &buf.iov_len); + if (r == -ENOENT) { + log_warning_errno(r, "Boot secret (%s) not found, not encrypting software TPM state!", bs); + *ret = (struct iovec) {}; + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to read '%s': %m", bs); + + if (buf.iov_len < BOOT_SECRET_SIZE) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Boot secret too short, refusing."); + + *ret = TAKE_STRUCT(buf); + return 1; +} + +static int prepare_secret(const char *runtime_dir, char **ret) { + int r; + + assert(runtime_dir); + assert(ret); + + _cleanup_(iovec_done_erase) struct iovec boot_secret = {}; + r = load_boot_secret(&boot_secret); + if (r < 0) + return r; + if (r == 0) { + *ret = NULL; + return 0; + } + + /* Derive a suitable swtpm specific secret */ + static const char tag[] = "systemd swtpm tag v1"; + uint8_t secret[SHA256_DIGEST_SIZE]; + CLEANUP_ERASE(secret); + hmac_sha256(boot_secret.iov_base, + boot_secret.iov_len, + tag, + strlen(tag), + secret); + + _cleanup_free_ char *p = path_join(runtime_dir, "secret"); + if (!p) + return log_oom(); + + assert_cc(sizeof(secret) >= 16); /* swtpm only wants a 16 byte key */ + _cleanup_(erase_and_freep) char *h = hexmem(secret, 16); + if (!h) + return log_oom(); + + r = write_data_file_atomic_at(XAT_FDROOT, p, &IOVEC_MAKE_STRING(h), WRITE_DATA_FILE_MODE_0400); + if (r < 0) + return log_error_errno(r, "Failed to write secret file: %m"); + + *ret = TAKE_PTR(p); + return 1; +} + +static int setup_swtpm(const char *state_dir, int state_fd, const char *secret) { + int r; + + assert(state_dir); + assert(state_fd >= 0); + + /* Sets up the state directory via swtpm_setup */ + + if (in_initrd()) { + /* In the initrd remove previous transient state */ + r = RET_NERRNO(unlinkat(state_fd, "tpm2-00.volatilestate", /* flags= */ 0)); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to remove 'tpm2-00.volatilestate': %m"); + } + + r = dir_is_empty_at(state_fd, /* path= */ NULL, /* ignore_hidden_or_backup= */ false); + if (r < 0) + return log_error_errno(r, "Failed to check if TPM state directory is empty: %m"); + if (r == 0) { + log_debug("TPM state directory is already populated, not manufacturing a TPM."); + return 0; + } + + if (!in_initrd()) + return log_error_errno(SYNTHETIC_ERRNO(ESTALE), "swtpm TPM state directory has not been initialized in the initrd, refusing."); + + log_debug("TPM state directory is unpopulated, manufacturing a TPM."); + + return manufacture_swtpm(state_dir, secret); +} + +static int run(int argc, char *argv[]) { + int r; + + log_setup(); + + _cleanup_free_ char *runtime_dir = NULL; + r = runtime_directory(RUNTIME_SCOPE_SYSTEM, "systemd/swtpm", &runtime_dir); + if (r < 0) + return log_error_errno(r, "Unable to determine runtime directory: %m"); + + _cleanup_free_ char *swtpm = NULL; + r = find_executable("swtpm", &swtpm); + if (r < 0) + return log_error_errno(r, "Failed to find 'swtpm' binary: %m"); + + _cleanup_free_ char *_esp = NULL; + const char *esp; + if (in_initrd()) + /* The early ESP support uses only a single mount point, we do not need to search for it. */ + esp = "/sysefi"; + else { + r = find_esp_and_warn( + /* root= */ NULL, + /* path= */ NULL, + /* unprivileged_mode= */ false, + &_esp); + if (r == -ENOKEY) /* This one find_esp_and_warn() doesn't actually log about. */ + return log_error_errno(r, "No ESP discovered."); + if (r < 0) + return r; + esp = _esp; + } + + _cleanup_free_ char *state_dir = NULL; + _cleanup_close_ int state_fd = -EBADF; + r = chase("/loader/swtpm", + esp, CHASE_PREFIX_ROOT|CHASE_TRIGGER_AUTOFS|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + &state_dir, + &state_fd); + if (r < 0) + return log_error_errno(r, "Failed to open swtpm state directory in ESP: %m"); + + _cleanup_(unlink_and_freep) char *secret = NULL; + r = prepare_secret(runtime_dir, &secret); + if (r < 0) + return r; + + r = setup_swtpm(state_dir, state_fd, secret); + if (r < 0) + return r; + + _cleanup_strv_free_ char **args = + strv_new(swtpm, + "chardev", + "--vtpm-proxy", + "--tpm2", + /* Make sure that in the initrd swtpm never sends TPM2_Shutdown() for us, we want to + * be able to stop the daemon after all temporarily during the initrd→host + * transition. */ + in_initrd() ? "--flags=startup-clear,disable-auto-shutdown" : "--flags=startup-clear"); + if (!args) + return log_oom(); + + if (strv_extendf(&args, "--ctrl=type=unixio,path=%s/socket", runtime_dir) < 0) + return log_oom(); + + if (secret && strv_extendf(&args, "--key=file=%s,format=hex,mode=aes-cbc,remove=true", secret) < 0) + return log_oom(); + + if (strv_extendf(&args, "--tpmstate=dir=%s,mode=0600", state_dir) < 0) + return log_oom(); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *cmd = quote_command_line(args, SHELL_ESCAPE_EMPTY); + + log_debug("Chain-loading: %s", strnull(cmd)); + } + + /* Ideally swtpm could send this itself, but for now let's accept it like this. */ + // FIXME: remove this once swtpm 0.11 is released and hit all relevant distros. Then bump version + // requirements. + (void) sd_notify(/* unset_environment= */ true, "READY=1"); + + /* NB: if the execve() succeeds it's swtpm's job to actually unlink the secret file */ + execv(swtpm, args); + return log_error_errno(errno, "Failed to chainload swtpm: %m"); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/units/meson.build b/units/meson.build index ca17237dd0b16..1d0e145287e61 100644 --- a/units/meson.build +++ b/units/meson.build @@ -633,6 +633,10 @@ units = [ 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'], 'symlinks' : ['sysinit.target.wants/'], }, + { + 'file' : 'systemd-tpm2-swtpm.service.in', + 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'], + }, { 'file' : 'systemd-pcrlock-make-policy.service.in', 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'], diff --git a/units/systemd-tpm2-swtpm.service.in b/units/systemd-tpm2-swtpm.service.in new file mode 100644 index 0000000000000..10856f70d9e9f --- /dev/null +++ b/units/systemd-tpm2-swtpm.service.in @@ -0,0 +1,26 @@ +# 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=Fallback Software TPM +Documentation=man:systemd-tpm2-swtpm.service(8) +DefaultDependencies=no +After=systemd-sysusers.service +Wants=modprobe@tpm_vtpm_proxy.service +After=modprobe@tpm_vtpm_proxy.service +Before=tpm2.target sysinit.target + +[Service] +Type=notify +RuntimeDirectory=systemd/swtpm +ExecStart={{LIBEXECDIR}}/systemd-tpm2-swtpm +# Write out volatile state (so that we can read it back after the initrd transition +ExecStop=swtpm_ioctl --unix %t/systemd/swtpm/socket -v +# Initiate graceful shutdown +ExecStop=swtpm_ioctl --unix %t/systemd/swtpm/socket -s From 750795d1030fa5155224676d6e38554a35c6f076 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 9 Mar 2026 13:08:28 +0100 Subject: [PATCH 0527/1296] tpm2-generator: if requested run things with an swtpm We want to start the software TPM fallback only if no real hw is evailable and if the user opts-in to this behaviour. Add a generator that drives all this, based on kernel command line configuration. --- man/kernel-command-line.xml | 10 +++ man/systemd-tpm2-generator.xml | 9 +++ src/tpm2-setup/tpm2-generator.c | 114 +++++++++++++++++++++++++++++--- 3 files changed, 123 insertions(+), 10 deletions(-) diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 98673e0a51674..1292bbfbee139 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -782,6 +782,16 @@ + + systemd.tpm2_software_fallback= + + Controls whether to start a fallback software TPM service in case a hardware TPM is + not available, implemented by + systemd-tpm2-generator8. + + + + systemd.factory_reset= diff --git a/man/systemd-tpm2-generator.xml b/man/systemd-tpm2-generator.xml index 2e22b99b5a0fc..b45cf29be8698 100644 --- a/man/systemd-tpm2-generator.xml +++ b/man/systemd-tpm2-generator.xml @@ -45,6 +45,14 @@ for it yet. The latter might be useful in environments where a suitable TPM2 driver for the available hardware is not available. + The kernel command line option (which takes a + boolean argument, defaulting to false) may be used to enable an automatic software TPM fallback in case a + hardware TPM is not detected and + swtpm8 is + available. This pulls in the + systemd-tpm2-swtpm.service8 + service. + systemd-tpm2-generator implements systemd.generator7. @@ -55,6 +63,7 @@ systemd1 systemd.special7 kernel-command-line7 + systemd-tpm2-swtpm.service8 diff --git a/src/tpm2-setup/tpm2-generator.c b/src/tpm2-setup/tpm2-generator.c index 043e0fd4b7281..65f450daaffb9 100644 --- a/src/tpm2-setup/tpm2-generator.c +++ b/src/tpm2-setup/tpm2-generator.c @@ -1,8 +1,14 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + +#include "dropin.h" +#include "efivars.h" #include "generator.h" +#include "initrd-util.h" #include "log.h" #include "parse-util.h" +#include "path-util.h" #include "proc-cmdline.h" #include "special.h" #include "tpm2-util.h" @@ -15,6 +21,7 @@ static const char *arg_dest = NULL; static int arg_tpm2_wait = -1; /* tri-state: negative → don't know */ +static bool arg_tpm2_software_fallback = false; static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { int r; @@ -27,12 +34,19 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat log_warning_errno(r, "Failed to parse 'systemd.tpm2_wait=' kernel command line argument, ignoring: %s", value); else arg_tpm2_wait = r; + + } else if (proc_cmdline_key_streq(key, "systemd.tpm2_software_fallback")) { + r = value ? parse_boolean(value) : 1; + if (r < 0) + log_warning_errno(r, "Failed to parse 'systemd.tpm2_software_fallback=' kernel command line argument, ignoring: %s", value); + else + arg_tpm2_software_fallback = r; } return 0; } -static int generate_tpm_target_symlink(void) { +static int generate_tpm_target_symlink(Tpm2Support support, bool software_fallback_enabled) { int r; if (arg_tpm2_wait == 0) { @@ -40,9 +54,7 @@ static int generate_tpm_target_symlink(void) { return 0; } - if (arg_tpm2_wait < 0) { - Tpm2Support support = tpm2_support(); - + if (arg_tpm2_wait < 0 && !software_fallback_enabled) { if (FLAGS_SET(support, TPM2_SUPPORT_DRIVER)) { log_debug("Not generating tpm2.target synchronization point, as TPM2 device is already present."); return 0; @@ -52,11 +64,6 @@ static int generate_tpm_target_symlink(void) { log_debug("Not generating tpm2.target synchronization point, as firmware reports no TPM2 present."); return 0; } - - if (!FLAGS_SET(support, TPM2_SUPPORT_SYSTEM|TPM2_SUPPORT_SUBSYSTEM|TPM2_SUPPORT_LIBRARIES)) { - log_debug("Not generating tpm2.target synchronization point, as userspace support for TPM2 is not complete."); - return 0; - } } r = generator_add_symlink(arg_dest, SPECIAL_SYSINIT_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_TPM2_TARGET); @@ -66,6 +73,93 @@ static int generate_tpm_target_symlink(void) { return 0; } +static int generate_swtpm_symlink(Tpm2Support support) { + int r; + + if (!arg_tpm2_software_fallback) + return 0; + + if (FLAGS_SET(support, TPM2_SUPPORT_DRIVER) || FLAGS_SET(support, TPM2_SUPPORT_FIRMWARE)) { + log_debug("Not generating software TPM units, as a TPM2 device is otherwise available."); + return 0; + } + + if (!is_efi_boot()) { /* We need the ESP to store the TPM state. */ + log_warning("TPM software fallback requested but not booted in EFI mode, not pulling in software TPM unit."); + return 0; + } + + r = find_executable("swtpm", /* ret_filename= */ NULL); + if (r == -ENOENT) { + log_warning("TPM software fallback requested but swtpm not available, not pulling in software TPM unit."); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to determine if 'swtpm' is available: %m"); + + r = generator_add_symlink(arg_dest, SPECIAL_SYSINIT_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/systemd-tpm2-swtpm.service"); + if (r < 0) + return log_error_errno(r, "Failed to hook in systemd-tpm2-swtpm.service: %m"); + + if (in_initrd()) + /* Order + pull in the early ESP mount so that swtpm has a place to store its data. */ + r = write_drop_in( + arg_dest, + "systemd-tpm2-swtpm.service", + 50, "esp", + "# Automatically generated by systemd-tpm2-generator\n\n" + "[Unit]\n" + "Wants=sysefi.mount\n" + "After=sysefi.mount\n"); + else + /* Order (but not pull in) the regular ESP automount so that swtpm has a place to store its + * data. Note that it might be mounted to two different places depending on the existence of + * XBOOTLDR, hence order after both. */ + r = write_drop_in( + arg_dest, + "systemd-tpm2-swtpm.service", + 50, "esp", + "# Automatically generated by systemd-tpm2-generator\n\n" + "[Unit]\n" + "After=boot.automount efi.automount\n"); + if (r < 0) + return log_error_errno(r, "Failed to hook ESP mount before systemd-tpm2-swtpm.service: %m"); + + return 1; /* Tell caller we now created swtpm units */ +} + +static int generate_now(void) { + int r; + + /* Let's shortcut things before we check for TPM2 support if no one cares anyway */ + if (arg_tpm2_wait == 0 && !arg_tpm2_software_fallback) { + log_debug("Not generating tpm2.target synchronization point or activating software TPM, as turned off via kernel command line."); + return 0; + } + + /* We are supposed to sync on TPM or do a software fallback, let's first and unconditionally validate this makes sense at + * all, i.e. if we have a suitable kernel+userspace. */ + Tpm2Support support = tpm2_support(); + if (!FLAGS_SET(support, TPM2_SUPPORT_SYSTEM|TPM2_SUPPORT_SUBSYSTEM|TPM2_SUPPORT_LIBRARIES)) { + + /* Raise log level if things were explicitly configured */ + log_full((arg_tpm2_wait > 0 || + arg_tpm2_software_fallback) ? LOG_NOTICE : LOG_DEBUG, + "Not generating tpm2.target synchronization point or activating software TPM, as userspace support for TPM2 is not complete."); + return 0; + } + + r = generate_swtpm_symlink(support); + if (r < 0) + return r; + + r = generate_tpm_target_symlink(support, /* software_fallback_enabled= */ r > 0); + if (r < 0) + return r; + + return 0; +} + static int run(const char *dest, const char *dest_early, const char *dest_late) { int r; @@ -75,7 +169,7 @@ static int run(const char *dest, const char *dest_early, const char *dest_late) if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); - return generate_tpm_target_symlink(); + return generate_now(); } DEFINE_MAIN_GENERATOR_FUNCTION(run); From ca03d178a077a7705c58619fcc83fb3b90845fdb Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 9 Mar 2026 18:51:49 +0100 Subject: [PATCH 0528/1296] tree-wide: relax TPM available checks for many cases In many cases it's essential to know if the firmware supports a TPM, but in others we should accept it if the firmware doesn't have TPM support, in particular if we want to run the OS with a software TPM. Hence, add tpm2_is_mostly_supported() as function similar to tpm2_is_fully_supported(), with the only difference that the former doesn't insist on a firmware supported TPM. Then, change a number of users over to this (but not all). --- src/analyze/analyze-nvpcrs.c | 2 +- src/analyze/analyze-pcrs.c | 4 ++-- src/pcrextend/pcrextend.c | 2 +- src/shared/creds-util.c | 2 +- src/shared/tpm2-util.h | 4 ++++ src/tpm2-setup/tpm2-setup.c | 2 +- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/analyze/analyze-nvpcrs.c b/src/analyze/analyze-nvpcrs.c index 68e7acb33ac3e..56b5c9a204945 100644 --- a/src/analyze/analyze-nvpcrs.c +++ b/src/analyze/analyze-nvpcrs.c @@ -56,7 +56,7 @@ int verb_nvpcrs(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; int r; - bool have_tpm2 = tpm2_is_fully_supported(); + bool have_tpm2 = tpm2_is_mostly_supported(); if (!have_tpm2) log_notice("System lacks full TPM2 support, not showing NvPCR state."); diff --git a/src/analyze/analyze-pcrs.c b/src/analyze/analyze-pcrs.c index f98f4a8d50fe9..7e3ddde800bc2 100644 --- a/src/analyze/analyze-pcrs.c +++ b/src/analyze/analyze-pcrs.c @@ -101,8 +101,8 @@ int verb_pcrs(int argc, char *argv[], uintptr_t _data, void *userdata) { const char *alg = NULL; int r; - if (!tpm2_is_fully_supported()) - log_notice("System lacks full TPM2 support, not showing PCR state."); + if (!tpm2_is_mostly_supported()) + log_notice("System lacks sufficient TPM2 support, not showing PCR state."); else { r = get_pcr_alg(&alg); if (r < 0) diff --git a/src/pcrextend/pcrextend.c b/src/pcrextend/pcrextend.c index c319ddd0f8847..c0b111a0964e6 100644 --- a/src/pcrextend/pcrextend.c +++ b/src/pcrextend/pcrextend.c @@ -531,7 +531,7 @@ static int run(int argc, char *argv[]) { if (arg_event_type >= 0) event = arg_event_type; - if (arg_graceful && !tpm2_is_fully_supported()) { + if (arg_graceful && !tpm2_is_mostly_supported()) { log_notice("No complete TPM2 support detected, exiting gracefully."); return EXIT_SUCCESS; } diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index 9c093181c7b33..8071629c17086 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -894,7 +894,7 @@ int encrypt_credential_and_warn( * container tpm2_support will detect this, and will return a different flag combination of * TPM2_SUPPORT_FULL, effectively skipping the use of TPM2 when inside one. */ - try_tpm2 = tpm2_is_fully_supported(); + try_tpm2 = tpm2_is_mostly_supported(); if (!try_tpm2) log_debug("System lacks TPM2 support or running in a container, not attempting to use TPM2."); } else diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 841f33b8deaa3..2f5d8632de5d2 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -496,6 +496,7 @@ typedef enum Tpm2Support { /* Combined flags for generic (i.e. not tool-specific) support */ TPM2_SUPPORT_FULL = TPM2_SUPPORT_API|TPM2_SUPPORT_LIBTSS2_ALL, + TPM2_SUPPORT_SOFTWARE = TPM2_SUPPORT_FULL & ~TPM2_SUPPORT_FIRMWARE, /* Same, just without PC firmware support */ } Tpm2Support; Tpm2Support tpm2_support_full(Tpm2Support mask); @@ -505,6 +506,9 @@ static inline Tpm2Support tpm2_support(void) { static inline bool tpm2_is_fully_supported(void) { return tpm2_support() == TPM2_SUPPORT_FULL; } +static inline bool tpm2_is_mostly_supported(void) { + return (tpm2_support() & TPM2_SUPPORT_SOFTWARE) == TPM2_SUPPORT_SOFTWARE; +} int verb_has_tpm2_generic(bool quiet); diff --git a/src/tpm2-setup/tpm2-setup.c b/src/tpm2-setup/tpm2-setup.c index d243f199e99b2..92a4bfa12a615 100644 --- a/src/tpm2-setup/tpm2-setup.c +++ b/src/tpm2-setup/tpm2-setup.c @@ -516,7 +516,7 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - if (arg_graceful && !tpm2_is_fully_supported()) { + if (arg_graceful && !tpm2_is_mostly_supported()) { log_notice("No complete TPM2 support detected, exiting gracefully."); return EXIT_SUCCESS; } From cd911bec6eec21a2cc775c98bf599ea38cb1fa1f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 9 Mar 2026 18:53:09 +0100 Subject: [PATCH 0529/1296] core: introduce ConditionSecurity=measured-os So far we always conditioned our TPM magic on the UKI having detected TPM support in the firmware. This is a bit limiting when we want to support a software TPM that is not visible to the firmware. Hence let's split this up, and add a separate control that can be set via the kernel command line. However, as before, let's by default inherit the firmare TPM discovery state into it, to retain the current behaviour unless overriden. With this in place, boot with "systemd.tpm2_measured_os=1 systemd.tpm2_software_fallback=1" on the kernel cmdline to get the swtpm fallback and then a measured OS based on it. --- man/kernel-command-line.xml | 12 +++++++++ man/systemd.unit.xml | 4 +++ src/bootctl/bootctl-status.c | 10 ++++++++ src/cryptsetup/cryptsetup.c | 8 +++--- src/fstab-generator/fstab-generator.c | 4 +-- src/gpt-auto-generator/gpt-auto-generator.c | 14 ++++++----- .../hibernate-resume-generator.c | 2 +- src/pcrextend/pcrextend.c | 6 ++--- src/shared/condition.c | 2 ++ src/shared/efi-loader.c | 25 +++++++++++++++++++ src/shared/efi-loader.h | 1 + units/systemd-pcrextend.socket | 2 +- units/systemd-pcrfs-root.service.in | 2 +- units/systemd-pcrfs@.service.in | 2 +- units/systemd-pcrmachine.service.in | 2 +- units/systemd-pcrnvdone.service.in | 2 +- .../systemd-pcrphase-factory-reset.service.in | 2 +- units/systemd-pcrphase-initrd.service.in | 2 +- ...md-pcrphase-storage-target-mode.service.in | 2 +- units/systemd-pcrphase-sysinit.service.in | 2 +- units/systemd-pcrphase.service.in | 2 +- units/systemd-pcrproduct.service.in | 2 +- units/systemd-tpm2-clear.service.in | 2 +- units/systemd-tpm2-setup-early.service.in | 2 +- units/systemd-tpm2-setup.service.in | 2 +- 25 files changed, 86 insertions(+), 30 deletions(-) diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 1292bbfbee139..088ce24154042 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -792,6 +792,18 @@ + + systemd.tpm2_measured_os= + + Controls whether to execute various boot and runtime TPM PCR measurements. Takes a + boolean argument. If not specified explicitly this behaviour is enabled automatically in case + systemd-stub7 is + used and it succeeded in doing pre-boot measurements of the booted UKI, and otherwise + disabled. + + + + systemd.factory_reset= diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index 37022ecc1c3aa..8bbff2f210a7f 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -1605,6 +1605,10 @@ measured-uki Unified Kernel Image with PCR 11 Measurements, as per systemd-stub7. + + measured-os + OS PCR measurements enabled. This is typically equivalent to measured-uki, however may also be set explicitly via the systemd.tpm2_measured_os= kernel command line switch, see kernel-command-line7 for details. The various system services doing boot and runtime measurements are conditioned on this flag. + diff --git a/src/bootctl/bootctl-status.c b/src/bootctl/bootctl-status.c index 4184e3d4249aa..178bffb36522c 100644 --- a/src/bootctl/bootctl-status.c +++ b/src/bootctl/bootctl-status.c @@ -477,6 +477,16 @@ int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { printf(" Measured UKI: %sfailed%s (%m)\n", ansi_highlight_red(), ansi_normal()); } + k = efi_measured_os(LOG_DEBUG); + if (k > 0) + printf(" Measured OS: %syes%s\n", ansi_highlight_green(), ansi_normal()); + else if (k == 0) + printf(" Measured OS: no\n"); + else { + errno = -k; + printf(" Measured OS: %sfailed%s (%m)\n", ansi_highlight_red(), ansi_normal()); + } + k = efi_get_reboot_to_firmware(); if (k > 0) printf(" Boot into FW: %sactive%s\n", ansi_highlight_yellow(), ansi_normal()); diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index bda9a8cc84a97..64cd3813b8728 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -1030,11 +1030,11 @@ static int measure_volume_key( return 0; } - r = efi_measured_uki(LOG_WARNING); + r = efi_measured_os(LOG_WARNING); if (r < 0) return r; if (r == 0) { - log_debug("Kernel stub did not measure kernel image into the expected PCR, skipping userspace volume key measurement, too."); + log_debug("OS measurements not explicitly requested and kernel stub did not measure kernel image into the expected PCR, skipping userspace volume key measurement, too."); return 0; } @@ -1109,11 +1109,11 @@ static int measure_keyslot( } #if HAVE_TPM2 - r = efi_measured_uki(LOG_WARNING); + r = efi_measured_os(LOG_WARNING); if (r < 0) return r; if (r == 0) { - log_debug("Kernel stub did not measure kernel image into the expected PCR, skipping userspace key slot measurement, too."); + log_debug("OS measurements not explicitly requested and kernel stub did not measure kernel image into the expected PCR, skipping userspace key slot measurement, too."); return 0; } diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c index d60db7e9c1de1..1bfebf3c4089f 100644 --- a/src/fstab-generator/fstab-generator.c +++ b/src/fstab-generator/fstab-generator.c @@ -672,9 +672,9 @@ static int add_mount( } if (flags & MOUNT_PCRFS) { - r = efi_measured_uki(LOG_WARNING); + r = efi_measured_os(LOG_WARNING); if (r == 0) - log_debug("Kernel stub did not measure kernel image into PCR, skipping userspace measurement, too."); + log_debug("OS measurements not explicitly requested and kernel stub did not measure kernel image into PCR, skipping userspace measurement, too."); else if (r > 0) { r = generator_hook_up_pcrfs(dest, where, target_unit); if (r < 0) diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index a87b514080587..6716a8d1aaf7c 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -115,11 +115,13 @@ static int add_cryptsetup( return log_oom(); } - r = efi_measured_uki(LOG_WARNING); - if (r > 0) + r = efi_measured_os(LOG_WARNING); + if (r > 0) { /* Enable TPM2 based unlocking automatically, if we have a TPM. See #30176. */ if (!strextend_with_separator(&options, ",", "tpm2-device=auto")) return log_oom(); + } else if (r == 0) + log_debug("Will not enable TPM based unlocking of volume '%s', OS measurements are not explicitly requested and not booted via systemd-stub with measurements enabled.", id); if (FLAGS_SET(flags, MOUNT_MEASURE)) { /* We only measure the root volume key into PCR 15 if we are booted with sd-stub (i.e. in a @@ -130,7 +132,7 @@ static int add_cryptsetup( if (!strextend_with_separator(&options, ",", "tpm2-measure-pcr=yes,tpm2-measure-keyslot-nvpcr=yes")) return log_oom(); if (r == 0) - log_debug("Will not measure volume key of volume '%s', not booted via systemd-stub with measurements enabled.", id); + log_debug("Will not measure volume key of volume '%s', as OS measurements are not explicitly requested and not booted via systemd-stub with measurements enabled.", id); } r = generator_write_cryptsetup_service_section(f, id, what, NULL, options); @@ -240,11 +242,11 @@ static int add_veritysetup( return log_oom(); if (FLAGS_SET(flags, MOUNT_MEASURE)) { - r = efi_measured_uki(LOG_WARNING); + r = efi_measured_os(LOG_WARNING); if (r > 0 && !strextend_with_separator(&options, ",", "tpm2-measure-nvpcr=yes")) return log_oom(); - if (r == 0) - log_debug("Will not measure root hash/signature of volume '%s', not booted via systemd-stub with measurements enabled.", id); + else if (r == 0) + log_debug("Will not measure root hash/signature of volume '%s', OS measurements not explicitly requested and not booted via systemd-stub with measurements enabled.", id); } r = generator_write_veritysetup_service_section( diff --git a/src/hibernate-resume/hibernate-resume-generator.c b/src/hibernate-resume/hibernate-resume-generator.c index 79c7d41bb453d..998c8e84d9504 100644 --- a/src/hibernate-resume/hibernate-resume-generator.c +++ b/src/hibernate-resume/hibernate-resume-generator.c @@ -86,7 +86,7 @@ static int add_dissected_swap_cryptsetup(void) { r = generator_write_cryptsetup_service_section( f, "swap", DISSECTED_SWAP_LUKS_DEVICE, /* key_file= */ NULL, - efi_measured_uki(LOG_DEBUG) > 0 ? "tpm2-device=auto" : NULL); + efi_measured_os(LOG_DEBUG) > 0 ? "tpm2-device=auto" : NULL); if (r < 0) return r; diff --git a/src/pcrextend/pcrextend.c b/src/pcrextend/pcrextend.c index c0b111a0964e6..8ec0a733c68aa 100644 --- a/src/pcrextend/pcrextend.c +++ b/src/pcrextend/pcrextend.c @@ -536,12 +536,12 @@ static int run(int argc, char *argv[]) { return EXIT_SUCCESS; } - /* Skip logic if sd-stub is not used, after all PCR 11 might have a very different purpose then. */ - r = efi_measured_uki(LOG_ERR); + /* Skip logic if measured OS functionality is not enabled. */ + r = efi_measured_os(LOG_ERR); if (r < 0) return r; if (r == 0) { - log_info("Kernel stub did not measure kernel image into PCR %i, skipping userspace measurement, too.", TPM2_PCR_KERNEL_BOOT); + log_info("OS measurements not explicitly requested and kernel stub did not measure kernel image into PCR %i, skipping userspace measurement, too.", TPM2_PCR_KERNEL_BOOT); return EXIT_SUCCESS; } diff --git a/src/shared/condition.c b/src/shared/condition.c index 903662edf1a8f..dd720c55bd9e5 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -741,6 +741,8 @@ static int condition_test_security(Condition *c, char **env) { return detect_confidential_virtualization() > 0; if (streq(c->parameter, "measured-uki")) return efi_measured_uki(LOG_DEBUG); + if (streq(c->parameter, "measured-os")) + return efi_measured_os(LOG_DEBUG); return false; } diff --git a/src/shared/efi-loader.c b/src/shared/efi-loader.c index 1f4fc665c03b8..ce10a44d34ccc 100644 --- a/src/shared/efi-loader.c +++ b/src/shared/efi-loader.c @@ -8,6 +8,7 @@ #include "log.h" #include "parse-util.h" #include "path-util.h" +#include "proc-cmdline.h" #include "stat-util.h" #include "string-util.h" #include "strv.h" @@ -312,6 +313,30 @@ int efi_measured_uki(int log_level) { #endif } +int efi_measured_os(int log_level) { +#if ENABLE_EFI + static int cached = -1; + int r; + + /* Returns if we shall enable our measurement machinery */ + + if (cached >= 0) + return cached; + + bool b; + r = proc_cmdline_get_bool("systemd.tpm2_measured_os", /* flags= */ 0, &b); + if (r < 0) + log_debug_errno(r, "Failed to parse systemd.tpm2_measured_os= kernel command line argument, ignoring: %m"); + else if (r > 0) + return (cached = b); + + /* If nothing is explicitly configured, just assume that if we booted with a measured UKI we also want a measured OS */ + return (cached = efi_measured_uki(log_level)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), "Compiled without support for EFI"); +#endif +} + int efi_loader_get_config_timeout_one_shot(usec_t *ret) { #if ENABLE_EFI _cleanup_free_ char *v = NULL; diff --git a/src/shared/efi-loader.h b/src/shared/efi-loader.h index 5b614cd0a7ee0..abf8bdc49ef04 100644 --- a/src/shared/efi-loader.h +++ b/src/shared/efi-loader.h @@ -15,6 +15,7 @@ int efi_loader_get_features(uint64_t *ret); int efi_stub_get_features(uint64_t *ret); int efi_measured_uki(int log_level); +int efi_measured_os(int log_level); int efi_loader_get_config_timeout_one_shot(usec_t *ret); int efi_loader_update_entry_one_shot_cache(char **cache, struct stat *cache_stat); diff --git a/units/systemd-pcrextend.socket b/units/systemd-pcrextend.socket index d429150eda0d7..0f4ab11e2fd3c 100644 --- a/units/systemd-pcrextend.socket +++ b/units/systemd-pcrextend.socket @@ -13,7 +13,7 @@ Documentation=man:systemd-pcrextend(8) DefaultDependencies=no After=tpm2.target Before=sockets.target -ConditionSecurity=measured-uki +ConditionSecurity=measured-os [Socket] ListenStream=/run/systemd/io.systemd.PCRExtend diff --git a/units/systemd-pcrfs-root.service.in b/units/systemd-pcrfs-root.service.in index f774c4c8bf6bf..88551d7ed0893 100644 --- a/units/systemd-pcrfs-root.service.in +++ b/units/systemd-pcrfs-root.service.in @@ -15,7 +15,7 @@ Conflicts=shutdown.target After=tpm2.target systemd-pcrmachine.service Before=shutdown.target ConditionPathExists=!/etc/initrd-release -ConditionSecurity=measured-uki +ConditionSecurity=measured-os FailureAction=reboot-force [Service] diff --git a/units/systemd-pcrfs@.service.in b/units/systemd-pcrfs@.service.in index 3d18fe4d30e16..38cc41976f66c 100644 --- a/units/systemd-pcrfs@.service.in +++ b/units/systemd-pcrfs@.service.in @@ -16,7 +16,7 @@ Conflicts=shutdown.target After=%i.mount tpm2.target systemd-pcrfs-root.service Before=shutdown.target ConditionPathExists=!/etc/initrd-release -ConditionSecurity=measured-uki +ConditionSecurity=measured-os FailureAction=reboot-force [Service] diff --git a/units/systemd-pcrmachine.service.in b/units/systemd-pcrmachine.service.in index ea2561ef79e3f..d97afa696554d 100644 --- a/units/systemd-pcrmachine.service.in +++ b/units/systemd-pcrmachine.service.in @@ -15,7 +15,7 @@ Conflicts=shutdown.target After=tpm2.target Before=sysinit.target shutdown.target ConditionPathExists=!/etc/initrd-release -ConditionSecurity=measured-uki +ConditionSecurity=measured-os FailureAction=reboot-force [Service] diff --git a/units/systemd-pcrnvdone.service.in b/units/systemd-pcrnvdone.service.in index e0dd9a8820988..7593dedfed189 100644 --- a/units/systemd-pcrnvdone.service.in +++ b/units/systemd-pcrnvdone.service.in @@ -14,7 +14,7 @@ DefaultDependencies=no Conflicts=shutdown.target After=systemd-tpm2-setup-early.service systemd-tpm2-setup.service Before=sysinit.target shutdown.target -ConditionSecurity=measured-uki +ConditionSecurity=measured-os ConditionPathExists=!/etc/initrd-release FailureAction=reboot-force diff --git a/units/systemd-pcrphase-factory-reset.service.in b/units/systemd-pcrphase-factory-reset.service.in index 5dbcb0f53f160..2efd8830d3210 100644 --- a/units/systemd-pcrphase-factory-reset.service.in +++ b/units/systemd-pcrphase-factory-reset.service.in @@ -14,7 +14,7 @@ DefaultDependencies=no Conflicts=shutdown.target After=tpm2.target Before=shutdown.target factory-reset.target -ConditionSecurity=measured-uki +ConditionSecurity=measured-os FailureAction=reboot-force [Service] diff --git a/units/systemd-pcrphase-initrd.service.in b/units/systemd-pcrphase-initrd.service.in index 5aba32128c012..cbb833147018d 100644 --- a/units/systemd-pcrphase-initrd.service.in +++ b/units/systemd-pcrphase-initrd.service.in @@ -15,7 +15,7 @@ Conflicts=shutdown.target initrd-switch-root.target After=tpm2.target Before=sysinit.target cryptsetup-pre.target cryptsetup.target shutdown.target initrd-switch-root.target systemd-sysext.service ConditionPathExists=/etc/initrd-release -ConditionSecurity=measured-uki +ConditionSecurity=measured-os FailureAction=reboot-force [Service] diff --git a/units/systemd-pcrphase-storage-target-mode.service.in b/units/systemd-pcrphase-storage-target-mode.service.in index 52b53e5b819a8..b4330c560f5bb 100644 --- a/units/systemd-pcrphase-storage-target-mode.service.in +++ b/units/systemd-pcrphase-storage-target-mode.service.in @@ -15,7 +15,7 @@ Conflicts=shutdown.target After=tpm2.target Before=shutdown.target ConditionPathExists=/etc/initrd-release -ConditionSecurity=measured-uki +ConditionSecurity=measured-os FailureAction=reboot-force [Service] diff --git a/units/systemd-pcrphase-sysinit.service.in b/units/systemd-pcrphase-sysinit.service.in index 4a01279159d93..aa4d36409813a 100644 --- a/units/systemd-pcrphase-sysinit.service.in +++ b/units/systemd-pcrphase-sysinit.service.in @@ -15,7 +15,7 @@ Conflicts=shutdown.target After=sysinit.target tpm2.target Before=basic.target shutdown.target ConditionPathExists=!/etc/initrd-release -ConditionSecurity=measured-uki +ConditionSecurity=measured-os FailureAction=reboot-force [Service] diff --git a/units/systemd-pcrphase.service.in b/units/systemd-pcrphase.service.in index 43459a2fccba0..b2f925d40f46b 100644 --- a/units/systemd-pcrphase.service.in +++ b/units/systemd-pcrphase.service.in @@ -13,7 +13,7 @@ Documentation=man:systemd-pcrphase.service(8) After=remote-fs.target remote-cryptsetup.target tpm2.target Before=systemd-user-sessions.service ConditionPathExists=!/etc/initrd-release -ConditionSecurity=measured-uki +ConditionSecurity=measured-os FailureAction=reboot-force [Service] diff --git a/units/systemd-pcrproduct.service.in b/units/systemd-pcrproduct.service.in index 09e446c2a01b0..2562dea18fe4e 100644 --- a/units/systemd-pcrproduct.service.in +++ b/units/systemd-pcrproduct.service.in @@ -16,7 +16,7 @@ After=tpm2.target Before=sysinit.target shutdown.target RequiresMountsFor=/var/lib/systemd/nvpcr ConditionPathExists=!/etc/initrd-release -ConditionSecurity=measured-uki +ConditionSecurity=measured-os [Service] Type=oneshot diff --git a/units/systemd-tpm2-clear.service.in b/units/systemd-tpm2-clear.service.in index a47d99ac8e70d..501846180c974 100644 --- a/units/systemd-tpm2-clear.service.in +++ b/units/systemd-tpm2-clear.service.in @@ -22,7 +22,7 @@ ConditionPathExists=/sys/class/tpm/tpm0/ppi/request # derive here from the fact that UKIs are used. Because if they do they are OK # with our SRK initialization and our PCR measurements, and hence should also # be OK with our TPM resets. -ConditionSecurity=measured-uki +ConditionSecurity=measured-os [Service] Type=oneshot diff --git a/units/systemd-tpm2-setup-early.service.in b/units/systemd-tpm2-setup-early.service.in index ce1ee94cc4906..6b7ef34b8c00f 100644 --- a/units/systemd-tpm2-setup-early.service.in +++ b/units/systemd-tpm2-setup-early.service.in @@ -14,7 +14,7 @@ DefaultDependencies=no Conflicts=shutdown.target After=tpm2.target systemd-pcrphase-initrd.service Before=sysinit.target shutdown.target -ConditionSecurity=measured-uki +ConditionSecurity=measured-os ConditionPathExists=!/run/systemd/tpm2-srk-public-key.pem [Service] diff --git a/units/systemd-tpm2-setup.service.in b/units/systemd-tpm2-setup.service.in index dff516832d3c6..4593211c1ef8b 100644 --- a/units/systemd-tpm2-setup.service.in +++ b/units/systemd-tpm2-setup.service.in @@ -15,7 +15,7 @@ Conflicts=shutdown.target After=tpm2.target systemd-tpm2-setup-early.service systemd-remount-fs.service Before=sysinit.target shutdown.target RequiresMountsFor=/var/lib/systemd -ConditionSecurity=measured-uki +ConditionSecurity=measured-os ConditionPathExists=!/etc/initrd-release [Service] From 1494cb04ea145f8ce706413f05a28cadc93059f1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 10 Mar 2026 08:04:52 +0100 Subject: [PATCH 0530/1296] pcrlock: don't fail if firmware measurements aren't available With swtpm in place we now commonly have systems where TPM is available during runtime, but not in the firmware. Handle that nicely. --- src/pcrlock/pcrlock.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index ddae43dc54895..c940ab01e8d9b 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -899,6 +899,10 @@ static int event_log_load_firmware(EventLog *el) { path = tpm2_firmware_log_path(); r = read_full_file(path, (char**) &buf, &bufsize); + if (r == -ENOENT) { + log_notice("No '%s' file, assuming TPM without firmware support.", path); + return 0; + } if (r < 0) return log_error_errno(r, "Failed to open TPM2 event log '%s': %m", path); From 96bb950ffa8b606518b104a109ed7f0c1bfb2bce Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 10 Mar 2026 08:07:25 +0100 Subject: [PATCH 0531/1296] pcrlock: deal with firmwares which understand TPM but where no TPM is available This is a potentially common case in VMs: firmwares might know the concept of TPMs, but the hardware is not enabled in the specific VM. Let's handle this case nicely. --- src/shared/tpm2-util.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 47a6a309ddb47..05ea7c47be2cd 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -2899,6 +2899,8 @@ int tpm2_get_best_pcr_bank( log_debug("Boot loader didn't set the LoaderTpm2ActivePcrBanks EFI variable or EFI support is unavailable, we have to guess the used PCR banks."); } else if (efi_banks == UINT32_MAX) log_debug("Boot loader set the LoaderTpm2ActivePcrBanks EFI variable to indicate that the GetActivePcrBanks() API is not available in the firmware. We have to guess the used PCR banks."); + else if (efi_banks == 0) + log_debug("Boot loader set the LoaderTpm2ActivePcrBanks EFI variable to zero to indicate that TPM support is not available in the firmware. We'll have to guess the used PCR banks."); else { if (BIT_SET(efi_banks, TPM2_ALG_SHA256)) *ret = TPM2_ALG_SHA256; @@ -3008,6 +3010,8 @@ int tpm2_get_good_pcr_banks( log_debug("Boot loader didn't set the LoaderTpm2ActivePcrBanks EFI variable or EFI support is unavailable, we have to guess the used PCR banks."); } else if (efi_banks == UINT32_MAX) log_debug("Boot loader set the LoaderTpm2ActivePcrBanks EFI variable to indicate that the GetActivePcrBanks() API is not available in the firmware. We have to guess the used PCR banks."); + else if (efi_banks == 0) + log_debug("Boot loader set the LoaderTpm2ActivePcrBanks EFI variable to zero to indicate that TPM support is not available in the firmware. We'll have to guess the used PCR banks."); else { FOREACH_ARRAY(hash, tpm2_hash_algorithms, TPM2_N_HASH_ALGORITHMS) { if (!BIT_SET(efi_banks, *hash)) From 3b20cc4526e8068474d4c9b1f9eaa49cc6afcaa4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 10 Mar 2026 18:17:04 +0100 Subject: [PATCH 0532/1296] creds-util: only lock against public key PCR stuff if we are booted with UEFI supporting TPMs The UKI public key PCR stuff only works if we get PCR measurements from the pre-boot environment, hence automatically disable the logic by default if we don't have that. --- src/shared/creds-util.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index 8071629c17086..e7db1ff7eff33 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -901,7 +901,10 @@ int encrypt_credential_and_warn( try_tpm2 = CRED_KEY_REQUIRES_TPM2(with_key); if (try_tpm2) { - if (CRED_KEY_WANTS_TPM2_PK(with_key) || CRED_KEY_REQUIRES_TPM2_PK(with_key)) { + /* If the firmware does not support TPMs, then UKI measurements are not going to work, hence + * PCR 11 public key stuff cannot work. Because of that, if PK is only wanted (but not + * required) we won't try it. */ + if ((CRED_KEY_WANTS_TPM2_PK(with_key) && tpm2_is_fully_supported()) || CRED_KEY_REQUIRES_TPM2_PK(with_key)) { /* Load public key for PCR policies, if one is specified, or explicitly requested */ @@ -926,6 +929,8 @@ int encrypt_credential_and_warn( if (r < 0) return log_error_errno(r, "Could not find best pcr bank: %m"); + log_debug("Selected literal PCR mask: 0x%x, PK PCR mask: 0x%x", tpm2_hash_pcr_mask, tpm2_pubkey_pcr_mask); + TPML_PCR_SELECTION tpm2_hash_pcr_selection; tpm2_tpml_pcr_selection_from_mask(tpm2_hash_pcr_mask, tpm2_pcr_bank, &tpm2_hash_pcr_selection); From 7c27f9f59455b199e12976c76b28b708c525b55c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 10 Mar 2026 08:09:52 +0100 Subject: [PATCH 0533/1296] pcrextend: measure another separator at boot This has been requested previously for PCR 7 (#40567), but let's do that for all firmware owned PCRs, since some firmwares forget to measure their own separator. Let's hence measure our own guranteed one. Fixes: #40567 --- man/rules/meson.build | 1 + man/systemd-pcrphase.service.xml | 10 +++- src/pcrextend/pcrextend.c | 59 +++++++++++-------- src/pcrlock/meson.build | 1 + .../pcrlock.d/750-os-separator.pcrlock | 1 + src/shared/tpm2-util.c | 1 + src/shared/tpm2-util.h | 1 + test/units/TEST-70-TPM2.pcrextend.sh | 1 - test/units/TEST-70-TPM2.pcrlock.sh | 12 ++++ units/meson.build | 5 ++ units/systemd-pcrosseparator.service.in | 28 +++++++++ units/systemd-tpm2-setup-early.service.in | 2 +- 12 files changed, 94 insertions(+), 28 deletions(-) create mode 100644 src/pcrlock/pcrlock.d/750-os-separator.pcrlock create mode 100644 units/systemd-pcrosseparator.service.in diff --git a/man/rules/meson.build b/man/rules/meson.build index 0b5e438200f7a..911a68543e960 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -1138,6 +1138,7 @@ manpages = [ 'systemd-pcrfs@.service', 'systemd-pcrmachine.service', 'systemd-pcrnvdone.service', + 'systemd-pcrosseparator.service', 'systemd-pcrphase-initrd.service', 'systemd-pcrphase-sysinit.service', 'systemd-pcrproduct.service'], diff --git a/man/systemd-pcrphase.service.xml b/man/systemd-pcrphase.service.xml index 6b5ff05c3dfcd..b1049ea916f7d 100644 --- a/man/systemd-pcrphase.service.xml +++ b/man/systemd-pcrphase.service.xml @@ -21,6 +21,7 @@ systemd-pcrphase-sysinit.service systemd-pcrphase-initrd.service systemd-pcrmachine.service + systemd-pcrosseparator.service systemd-pcrproduct.service systemd-pcrfs-root.service systemd-pcrfs@.service @@ -34,6 +35,7 @@ systemd-pcrphase-sysinit.service systemd-pcrphase-initrd.service systemd-pcrmachine.service + systemd-pcrosseparator.service systemd-pcrproduct.service systemd-pcrfs-root.service systemd-pcrfs@.service @@ -53,6 +55,11 @@ (see machine-id5) into PCR 15. + systemd-pcrosseparator.service is a system service that measures the word + os-separator into PCRs 0-7, 9, 12-14. This acts as additional separator measurement + separating pre-boot (i.e. firmware + bootloader) measurements from OS measurements, and in particular + "seals" off the firmware PCRs. + systemd-pcrproduct.service is a system service that measures the firmware product UUID (as provided by one of SMBIOS, Devicetree, …) into a NvPCR named hardware. @@ -168,7 +175,8 @@ Takes the index of the PCR to extend. If or are specified defaults to 15, otherwise (and unless - is specified) defaults to 11. May not be combined with + is specified) defaults to 11. May be specified multiple times, in order + to measure to multiple PCRs at the same time. May not be combined with . diff --git a/src/pcrextend/pcrextend.c b/src/pcrextend/pcrextend.c index 8ec0a733c68aa..2a087ec8f89df 100644 --- a/src/pcrextend/pcrextend.c +++ b/src/pcrextend/pcrextend.c @@ -29,7 +29,7 @@ static char **arg_banks = NULL; static char *arg_file_system = NULL; static bool arg_machine_id = false; static bool arg_product_id = false; -static unsigned arg_pcr_index = UINT_MAX; +static uint32_t arg_pcr_mask = 0; static char *arg_nvpcr_name = NULL; static bool arg_varlink = false; static bool arg_early = false; @@ -139,11 +139,16 @@ static int parse_argv(int argc, char *argv[]) { } case ARG_PCR: + if (isempty(optarg)) { + arg_pcr_mask = 0; + break; + } + r = tpm2_pcr_index_from_string(optarg); if (r < 0) return log_error_errno(r, "Failed to parse PCR index: %s", optarg); - arg_pcr_index = r; + arg_pcr_mask |= INDEX_TO_MASK(uint32_t, r); break; case ARG_NVPCR: @@ -213,7 +218,7 @@ static int parse_argv(int argc, char *argv[]) { if (!!arg_file_system + arg_machine_id + arg_product_id > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--file-system=, --machine-id, --product-id may not be combined."); - if (arg_pcr_index != UINT_MAX && arg_nvpcr_name) + if (arg_pcr_mask != 0 && arg_nvpcr_name) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--pcr= and --nvpcr= may not be combined."); r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT); @@ -221,11 +226,11 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m"); if (r > 0) arg_varlink = true; - else if (arg_pcr_index == UINT_MAX && !arg_nvpcr_name) { - arg_pcr_index = - (arg_file_system || arg_machine_id) ? TPM2_PCR_SYSTEM_IDENTITY : /* → PCR 15 */ - !arg_product_id ? TPM2_PCR_KERNEL_BOOT : /* → PCR 11 */ - UINT_MAX; + else if (arg_pcr_mask == 0 && !arg_nvpcr_name) { + arg_pcr_mask = + (arg_file_system || arg_machine_id) ? INDEX_TO_MASK(uint32_t, TPM2_PCR_SYSTEM_IDENTITY) : /* → PCR 15 */ + !arg_product_id ? INDEX_TO_MASK(uint32_t, TPM2_PCR_KERNEL_BOOT) : /* → PCR 11 */ + 0; r = free_and_strdup_warn(&arg_nvpcr_name, arg_product_id ? "hardware" : NULL); if (r < 0) @@ -235,7 +240,7 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -static int determine_banks(Tpm2Context *c, unsigned target_pcr_nr) { +static int determine_banks(Tpm2Context *c, uint32_t target_pcr_mask) { _cleanup_strv_free_ char **l = NULL; int r; @@ -244,7 +249,7 @@ static int determine_banks(Tpm2Context *c, unsigned target_pcr_nr) { if (!strv_isempty(arg_banks)) /* Explicitly configured? Then use that */ return 0; - r = tpm2_get_good_pcr_banks_strv(c, UINT32_C(1) << target_pcr_nr, &l); + r = tpm2_get_good_pcr_banks_strv(c, target_pcr_mask, &l); if (r < 0) return log_error_errno(r, "Could not verify pcr banks: %m"); @@ -275,7 +280,7 @@ static int escape_and_truncate_data(const void *data, size_t size, char **ret) { } static int extend_pcr_now( - unsigned pcr, + uint32_t pcr_mask, const void *data, size_t size, Tpm2UserspaceEventType event) { @@ -283,11 +288,13 @@ static int extend_pcr_now( _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; int r; + assert(pcr_mask != 0); + r = tpm2_context_new_or_warn(arg_tpm2_device, &c); if (r < 0) return r; - r = determine_banks(c, pcr); + r = determine_banks(c, pcr_mask); if (r < 0) return r; if (strv_isempty(arg_banks)) /* Still none? */ @@ -302,18 +309,20 @@ static int extend_pcr_now( if (escape_and_truncate_data(data, size, &safe) < 0) return log_oom(); - log_debug("Measuring '%s' into PCR index %u, banks %s.", safe, pcr, joined_banks); - - r = tpm2_pcr_extend_bytes(c, arg_banks, pcr, &IOVEC_MAKE(data, size), /* secret= */ NULL, event, safe); - if (r < 0) - return log_error_errno(r, "Could not extend PCR: %m"); + BIT_FOREACH(pcr, pcr_mask) { + log_debug("Measuring '%s' into PCR index %i, banks %s.", safe, pcr, joined_banks); - log_struct(LOG_INFO, - LOG_MESSAGE_ID(SD_MESSAGE_TPM_PCR_EXTEND_STR), - LOG_MESSAGE("Extended PCR index %u with '%s' (banks %s).", pcr, safe, joined_banks), - LOG_ITEM("MEASURING=%s", safe), - LOG_ITEM("PCR=%u", pcr), - LOG_ITEM("BANKS=%s", joined_banks)); + r = tpm2_pcr_extend_bytes(c, arg_banks, pcr, &IOVEC_MAKE(data, size), /* secret= */ NULL, event, safe); + if (r < 0) + return log_error_errno(r, "Could not extend PCR: %m"); + + log_struct(LOG_INFO, + LOG_MESSAGE_ID(SD_MESSAGE_TPM_PCR_EXTEND_STR), + LOG_MESSAGE("Extended PCR index %i with '%s' (banks %s).", pcr, safe, joined_banks), + LOG_ITEM("MEASURING=%s", safe), + LOG_ITEM("PCR=%i", pcr), + LOG_ITEM("BANKS=%s", joined_banks)); + } return 0; } @@ -435,7 +444,7 @@ static int vl_method_extend(sd_varlink *link, sd_json_variant *parameters, sd_va if (r == -ENOENT) return sd_varlink_error(link, "io.systemd.PCRExtend.NoSuchNvPCR", NULL); } else - r = extend_pcr_now(p.pcr, extend_iovec->iov_base, extend_iovec->iov_len, p.event_type); + r = extend_pcr_now(INDEX_TO_MASK(uint32_t, p.pcr), extend_iovec->iov_base, extend_iovec->iov_len, p.event_type); if (r < 0) return r; @@ -548,7 +557,7 @@ static int run(int argc, char *argv[]) { if (arg_nvpcr_name) r = extend_nvpcr_now(arg_nvpcr_name, word, strlen(word), event); else - r = extend_pcr_now(arg_pcr_index, word, strlen(word), event); + r = extend_pcr_now(arg_pcr_mask, word, strlen(word), event); if (r < 0) return r; diff --git a/src/pcrlock/meson.build b/src/pcrlock/meson.build index f7fa3d18d9cbb..ff2b0f0cb2419 100644 --- a/src/pcrlock/meson.build +++ b/src/pcrlock/meson.build @@ -27,6 +27,7 @@ install_data('pcrlock.d/500-separator.pcrlock.d/600-0xffffffff.pcrlock', install install_data('pcrlock.d/700-action-efi-exit-boot-services.pcrlock.d/300-present.pcrlock', install_dir : pcrlockdir / '700-action-efi-exit-boot-services.pcrlock.d') install_data('pcrlock.d/700-action-efi-exit-boot-services.pcrlock.d/600-absent.pcrlock', install_dir : pcrlockdir / '700-action-efi-exit-boot-services.pcrlock.d') install_data('pcrlock.d/750-enter-initrd.pcrlock', install_dir : pcrlockdir) +install_data('pcrlock.d/750-os-separator.pcrlock', install_dir : pcrlockdir) install_data('pcrlock.d/770-nvpcr-separator.pcrlock', install_dir : pcrlockdir) install_data('pcrlock.d/800-leave-initrd.pcrlock', install_dir : pcrlockdir) install_data('pcrlock.d/850-sysinit.pcrlock', install_dir : pcrlockdir) diff --git a/src/pcrlock/pcrlock.d/750-os-separator.pcrlock b/src/pcrlock/pcrlock.d/750-os-separator.pcrlock new file mode 100644 index 0000000000000..aaeba174d1cbb --- /dev/null +++ b/src/pcrlock/pcrlock.d/750-os-separator.pcrlock @@ -0,0 +1 @@ +{"records":[{"pcr":0,"digests":[{"hashAlg":"sha512","digest":"c2c4de82bbe2dd787a6bdca1014b9557313f258e18aaee2c9c3e2f9c79e526d66283db9aef5d7aeed3ffecd628ecc045716583eecd23ca73eee63f299a8dc2cc"},{"hashAlg":"sha384","digest":"5f2a9be7b55475435f46c343bb8c224db427d4fc7cdca42ac02e50d61c67a8bf0b707f7e1527a5f65175efdf8b56ba24"},{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"},{"hashAlg":"sha1","digest":"0d3751a5a6707fb8918dfbe6b0d9853ca6c313c4"}]},{"pcr":1,"digests":[{"hashAlg":"sha512","digest":"c2c4de82bbe2dd787a6bdca1014b9557313f258e18aaee2c9c3e2f9c79e526d66283db9aef5d7aeed3ffecd628ecc045716583eecd23ca73eee63f299a8dc2cc"},{"hashAlg":"sha384","digest":"5f2a9be7b55475435f46c343bb8c224db427d4fc7cdca42ac02e50d61c67a8bf0b707f7e1527a5f65175efdf8b56ba24"},{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"},{"hashAlg":"sha1","digest":"0d3751a5a6707fb8918dfbe6b0d9853ca6c313c4"}]},{"pcr":2,"digests":[{"hashAlg":"sha512","digest":"c2c4de82bbe2dd787a6bdca1014b9557313f258e18aaee2c9c3e2f9c79e526d66283db9aef5d7aeed3ffecd628ecc045716583eecd23ca73eee63f299a8dc2cc"},{"hashAlg":"sha384","digest":"5f2a9be7b55475435f46c343bb8c224db427d4fc7cdca42ac02e50d61c67a8bf0b707f7e1527a5f65175efdf8b56ba24"},{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"},{"hashAlg":"sha1","digest":"0d3751a5a6707fb8918dfbe6b0d9853ca6c313c4"}]},{"pcr":3,"digests":[{"hashAlg":"sha512","digest":"c2c4de82bbe2dd787a6bdca1014b9557313f258e18aaee2c9c3e2f9c79e526d66283db9aef5d7aeed3ffecd628ecc045716583eecd23ca73eee63f299a8dc2cc"},{"hashAlg":"sha384","digest":"5f2a9be7b55475435f46c343bb8c224db427d4fc7cdca42ac02e50d61c67a8bf0b707f7e1527a5f65175efdf8b56ba24"},{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"},{"hashAlg":"sha1","digest":"0d3751a5a6707fb8918dfbe6b0d9853ca6c313c4"}]},{"pcr":4,"digests":[{"hashAlg":"sha512","digest":"c2c4de82bbe2dd787a6bdca1014b9557313f258e18aaee2c9c3e2f9c79e526d66283db9aef5d7aeed3ffecd628ecc045716583eecd23ca73eee63f299a8dc2cc"},{"hashAlg":"sha384","digest":"5f2a9be7b55475435f46c343bb8c224db427d4fc7cdca42ac02e50d61c67a8bf0b707f7e1527a5f65175efdf8b56ba24"},{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"},{"hashAlg":"sha1","digest":"0d3751a5a6707fb8918dfbe6b0d9853ca6c313c4"}]},{"pcr":5,"digests":[{"hashAlg":"sha512","digest":"c2c4de82bbe2dd787a6bdca1014b9557313f258e18aaee2c9c3e2f9c79e526d66283db9aef5d7aeed3ffecd628ecc045716583eecd23ca73eee63f299a8dc2cc"},{"hashAlg":"sha384","digest":"5f2a9be7b55475435f46c343bb8c224db427d4fc7cdca42ac02e50d61c67a8bf0b707f7e1527a5f65175efdf8b56ba24"},{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"},{"hashAlg":"sha1","digest":"0d3751a5a6707fb8918dfbe6b0d9853ca6c313c4"}]},{"pcr":6,"digests":[{"hashAlg":"sha512","digest":"c2c4de82bbe2dd787a6bdca1014b9557313f258e18aaee2c9c3e2f9c79e526d66283db9aef5d7aeed3ffecd628ecc045716583eecd23ca73eee63f299a8dc2cc"},{"hashAlg":"sha384","digest":"5f2a9be7b55475435f46c343bb8c224db427d4fc7cdca42ac02e50d61c67a8bf0b707f7e1527a5f65175efdf8b56ba24"},{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"},{"hashAlg":"sha1","digest":"0d3751a5a6707fb8918dfbe6b0d9853ca6c313c4"}]},{"pcr":7,"digests":[{"hashAlg":"sha512","digest":"c2c4de82bbe2dd787a6bdca1014b9557313f258e18aaee2c9c3e2f9c79e526d66283db9aef5d7aeed3ffecd628ecc045716583eecd23ca73eee63f299a8dc2cc"},{"hashAlg":"sha384","digest":"5f2a9be7b55475435f46c343bb8c224db427d4fc7cdca42ac02e50d61c67a8bf0b707f7e1527a5f65175efdf8b56ba24"},{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"},{"hashAlg":"sha1","digest":"0d3751a5a6707fb8918dfbe6b0d9853ca6c313c4"}]},{"pcr":9,"digests":[{"hashAlg":"sha512","digest":"c2c4de82bbe2dd787a6bdca1014b9557313f258e18aaee2c9c3e2f9c79e526d66283db9aef5d7aeed3ffecd628ecc045716583eecd23ca73eee63f299a8dc2cc"},{"hashAlg":"sha384","digest":"5f2a9be7b55475435f46c343bb8c224db427d4fc7cdca42ac02e50d61c67a8bf0b707f7e1527a5f65175efdf8b56ba24"},{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"},{"hashAlg":"sha1","digest":"0d3751a5a6707fb8918dfbe6b0d9853ca6c313c4"}]},{"pcr":12,"digests":[{"hashAlg":"sha512","digest":"c2c4de82bbe2dd787a6bdca1014b9557313f258e18aaee2c9c3e2f9c79e526d66283db9aef5d7aeed3ffecd628ecc045716583eecd23ca73eee63f299a8dc2cc"},{"hashAlg":"sha384","digest":"5f2a9be7b55475435f46c343bb8c224db427d4fc7cdca42ac02e50d61c67a8bf0b707f7e1527a5f65175efdf8b56ba24"},{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"},{"hashAlg":"sha1","digest":"0d3751a5a6707fb8918dfbe6b0d9853ca6c313c4"}]},{"pcr":13,"digests":[{"hashAlg":"sha512","digest":"c2c4de82bbe2dd787a6bdca1014b9557313f258e18aaee2c9c3e2f9c79e526d66283db9aef5d7aeed3ffecd628ecc045716583eecd23ca73eee63f299a8dc2cc"},{"hashAlg":"sha384","digest":"5f2a9be7b55475435f46c343bb8c224db427d4fc7cdca42ac02e50d61c67a8bf0b707f7e1527a5f65175efdf8b56ba24"},{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"},{"hashAlg":"sha1","digest":"0d3751a5a6707fb8918dfbe6b0d9853ca6c313c4"}]},{"pcr":14,"digests":[{"hashAlg":"sha512","digest":"c2c4de82bbe2dd787a6bdca1014b9557313f258e18aaee2c9c3e2f9c79e526d66283db9aef5d7aeed3ffecd628ecc045716583eecd23ca73eee63f299a8dc2cc"},{"hashAlg":"sha384","digest":"5f2a9be7b55475435f46c343bb8c224db427d4fc7cdca42ac02e50d61c67a8bf0b707f7e1527a5f65175efdf8b56ba24"},{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"},{"hashAlg":"sha1","digest":"0d3751a5a6707fb8918dfbe6b0d9853ca6c313c4"}]}]} diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 05ea7c47be2cd..fbf87d8d5a0a0 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -6680,6 +6680,7 @@ static const char* tpm2_userspace_event_type_table[_TPM2_USERSPACE_EVENT_TYPE_MA [TPM2_EVENT_NVPCR_SEPARATOR] = "nvpcr-separator", [TPM2_EVENT_DM_VERITY] = "dm-verity", [TPM2_EVENT_IMDS_USERDATA] = "imds-userdata", + [TPM2_EVENT_OS_SEPARATOR] = "os-separator", }; DEFINE_STRING_TABLE_LOOKUP(tpm2_userspace_event_type, Tpm2UserspaceEventType); diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 2f5d8632de5d2..5ada96e8e1174 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -149,6 +149,7 @@ typedef enum Tpm2UserspaceEventType { TPM2_EVENT_NVPCR_SEPARATOR, TPM2_EVENT_DM_VERITY, TPM2_EVENT_IMDS_USERDATA, + TPM2_EVENT_OS_SEPARATOR, _TPM2_USERSPACE_EVENT_TYPE_MAX, _TPM2_USERSPACE_EVENT_TYPE_INVALID = -EINVAL, } Tpm2UserspaceEventType; diff --git a/test/units/TEST-70-TPM2.pcrextend.sh b/test/units/TEST-70-TPM2.pcrextend.sh index fbb6b30a122a8..14808f07637bd 100755 --- a/test/units/TEST-70-TPM2.pcrextend.sh +++ b/test/units/TEST-70-TPM2.pcrextend.sh @@ -53,7 +53,6 @@ fi (! "$SD_PCREXTEND" --bank= foo) (! "$SD_PCREXTEND" --tpm2-device= foo) (! "$SD_PCREXTEND" --tpm2-device=/dev/null foo) -(! "$SD_PCREXTEND" --pcr= foo) (! "$SD_PCREXTEND" --pcr=-1 foo) (! "$SD_PCREXTEND" --pcr=1024 foo) (! "$SD_PCREXTEND" --foo=bar) diff --git a/test/units/TEST-70-TPM2.pcrlock.sh b/test/units/TEST-70-TPM2.pcrlock.sh index d90b4bc99fd6f..71f2ac53d75e8 100755 --- a/test/units/TEST-70-TPM2.pcrlock.sh +++ b/test/units/TEST-70-TPM2.pcrlock.sh @@ -42,6 +42,18 @@ PCRS="1+2+3+4+5+16" # (as the PCR values simply won't match the log). rm -f /run/log/systemd/tpm2-measure.log +# Add the os-separator measurements, they should be the only measurements that touch pcr 0…6 done from userspace. +RS=$'\x1e' +cat >/run/log/systemd/tpm2-measure.log < Date: Thu, 26 Mar 2026 11:16:10 +0100 Subject: [PATCH 0534/1296] units: make use of nvpcrs only after the NV anchor completion measurement is done This makes sure we don't use the "hardware" or "verity" nvpcrs before the NV anchor measurement is done. This is mostly to avoid confusing output, and to indirectly ensure the nvpcr allocation in tpm2-setup is the load bearing one, but it should not be load bearing for security afaics. --- units/systemd-pcrnvdone.service.in | 2 +- units/systemd-pcrproduct.service.in | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/units/systemd-pcrnvdone.service.in b/units/systemd-pcrnvdone.service.in index 7593dedfed189..bbd0e66e605ce 100644 --- a/units/systemd-pcrnvdone.service.in +++ b/units/systemd-pcrnvdone.service.in @@ -13,7 +13,7 @@ Documentation=man:systemd-pcrnvdone.service(8) DefaultDependencies=no Conflicts=shutdown.target After=systemd-tpm2-setup-early.service systemd-tpm2-setup.service -Before=sysinit.target shutdown.target +Before=sysinit.target cryptsetup-pre.target cryptsetup.target shutdown.target ConditionSecurity=measured-os ConditionPathExists=!/etc/initrd-release FailureAction=reboot-force diff --git a/units/systemd-pcrproduct.service.in b/units/systemd-pcrproduct.service.in index 2562dea18fe4e..1b121416a9423 100644 --- a/units/systemd-pcrproduct.service.in +++ b/units/systemd-pcrproduct.service.in @@ -12,7 +12,7 @@ Description=TPM NvPCR Product ID Measurement Documentation=man:systemd-pcrproduct.service(8) DefaultDependencies=no Conflicts=shutdown.target -After=tpm2.target +After=tpm2.target systemd-pcrnvdone.service Before=sysinit.target shutdown.target RequiresMountsFor=/var/lib/systemd/nvpcr ConditionPathExists=!/etc/initrd-release From 1e254ad1bd857ebead192dab27b2610a4185358f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 10 Mar 2026 17:12:23 +0100 Subject: [PATCH 0535/1296] update TODO --- TODO | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/TODO b/TODO index 5855e36390ab9..687d15b5ba83b 100644 --- a/TODO +++ b/TODO @@ -133,6 +133,14 @@ Features: * add service file setting to force the fwmark (a la SO_MARK) to some value, so that we can allowlist certain services for imds this way. +* lock down swtpm a bit to make it harder to extract keys from it as it is + running. i.e. make ptracing + termination hard from the outside. also run + swtpm as unpriv user (not trivial, probably requires patch swtpm, as it needs + to allocate vtpm device), to lock it down from the inside. + +* once swtpm's sd_notify() support has landed in the distributions, remove the + invocation in tpm2-swtpm.c and let swtpm handle it. + * make systemd work nicely without /bin/sh, logins and associated shell tools around - make sure debug shell service (sushell) has a nice failure mode, prints a message and reboots - varlink interface for "systemctl start" and friends @@ -156,13 +164,6 @@ Features: * on first login of a user, measure its identity to some nvpcr -* optionally spawn an swtpm instance if a system doesn't have a native tpm, do - it via the tpm generator - -* add a secret key logic to sd-stub, that uses early-boot efi variables for - storing, that can be used as fallback logic for tpm-less systems for disk - encryption, and swtpm state encryption. - * sd-lldp: pick up 802.3 maximum frame size/mtu, to be able to detect jumbo frame capable networks @@ -562,7 +563,7 @@ Features: service into the early boot, waiting for the DMI and network device to show up. -* Add UKI profile conditioning so that profles are only available if secure +* Add UKI profile conditioning so that profiles are only available if secure boot is turned off, or only on. similar, add conditions on TPM availability, network boot, and other conditions. From 4cb283f931994e2d7802bbc38551446b24600b2f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 26 Mar 2026 14:48:26 +0100 Subject: [PATCH 0536/1296] imds: some minor review fixes Addresses these issues: https://github.com/systemd/systemd/pull/40980#pullrequestreview-4013313066 --- man/systemd-imds-generator.xml | 2 +- src/imds/imds-generator.c | 2 +- test/units/TEST-74-AUX-UTILS.imds.sh | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/man/systemd-imds-generator.xml b/man/systemd-imds-generator.xml index d8e1f1aa05b55..d5eaf1e16a05f 100644 --- a/man/systemd-imds-generator.xml +++ b/man/systemd-imds-generator.xml @@ -71,7 +71,7 @@ Takes a boolean argument or the special value auto, and may be used to enable or disable the IMDS logic. Note that this controls only whether the relevant services (as listed above) are automatically pulled into the initial transaction, it has no effect if some other - unit or the user explicitly activate the relevant units. If this option is not used (or set to + unit or the user explicitly activates the relevant units. If this option is not used (or set to auto) automatic detection of IMDS is used, see above. diff --git a/src/imds/imds-generator.c b/src/imds/imds-generator.c index d33e63bddd323..9cb48688a8c64 100644 --- a/src/imds/imds-generator.c +++ b/src/imds/imds-generator.c @@ -153,7 +153,7 @@ static int run(const char *dest, const char *dest_early, const char *dest_late) return 0; } - log_info("IMDS support enabled, pull in IMDS units."); + log_info("IMDS support enabled, pulling in IMDS units."); /* Enable IMDS early networking, so that we can actually reach the IMDS server. */ if (arg_network_mode != IMDS_NETWORK_OFF) { diff --git a/test/units/TEST-74-AUX-UTILS.imds.sh b/test/units/TEST-74-AUX-UTILS.imds.sh index 2ee0c632d2f6d..ccd3d04c04265 100755 --- a/test/units/TEST-74-AUX-UTILS.imds.sh +++ b/test/units/TEST-74-AUX-UTILS.imds.sh @@ -14,10 +14,10 @@ fi at_exit() { set +e - systemctl stop fake-imds systemd-imdsd.socket ||: - ip link del dummy0 ||: + systemctl stop fake-imds systemd-imdsd.socket + ip link del dummy0 rm -f /run/credstore/firstboot.hostname /run/credstore/acredtest /run/systemd/system/systemd-imdsd@.service.d/50-env.conf - rmdir /run/systemd/system/systemd-imdsd@.service.d ||: + rmdir /run/systemd/system/systemd-imdsd@.service.d } trap at_exit EXIT From c3e4e8a527316b125572a70a00dd401bfdf0329a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 26 Mar 2026 17:28:21 +0100 Subject: [PATCH 0537/1296] meson: simplify setting of ImdsNetworkMode default This follows the pattern used for dnssec default mode right above. --- meson.build | 2 +- src/imds/imds-generator.c | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/meson.build b/meson.build index 2893bea332f29..03e337dccb61f 100644 --- a/meson.build +++ b/meson.build @@ -1544,7 +1544,7 @@ have = get_option('imds').require( conf.get('HAVE_LIBCURL') == 1, error_message : 'curl required').allowed() conf.set10('ENABLE_IMDS', have) -conf.set10('IMDS_NETWORK_LOCKED_DEFAULT', get_option('imds-network') == 'locked') +conf.set('IMDS_NETWORK_DEFAULT', 'IMDS_NETWORK_@0@'.format(get_option('imds-network')).to_upper()) have = get_option('importd').require( conf.get('HAVE_LIBCURL') == 1 and diff --git a/src/imds/imds-generator.c b/src/imds/imds-generator.c index 9cb48688a8c64..42399783faac5 100644 --- a/src/imds/imds-generator.c +++ b/src/imds/imds-generator.c @@ -15,8 +15,7 @@ static int arg_enabled = -1; /* Whether we shall offer local IMDS APIs */ static bool arg_import = true; /* Whether we shall import IMDS credentials, SSH keys, … into the local system */ -static ImdsNetworkMode arg_network_mode = - IMDS_NETWORK_LOCKED_DEFAULT ? IMDS_NETWORK_LOCKED : IMDS_NETWORK_UNLOCKED; +static ImdsNetworkMode arg_network_mode = IMDS_NETWORK_DEFAULT; static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { int r; From 6240d420d6ee8fea574200c327a2da583b823249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 26 Mar 2026 17:32:43 +0100 Subject: [PATCH 0538/1296] meson: unlock imds network by default Enabling locking by default would constitute a major footgun and compatibility break on upgrades. This functionality is useful, but it requires the rest of the system to be "ported" to use systemd-imds first. The user or distro should opt in to "locked" mode only after doing the integration work. --- NEWS | 14 +++++++------- meson_options.txt | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/NEWS b/NEWS index c34c55603e46a..c717a355ecdfa 100644 --- a/NEWS +++ b/NEWS @@ -13,13 +13,13 @@ CHANGES WITH 261 in spe: attestation environments which use hardware CC registers and not the TPM quote. - * By default networking to cloud IMDS services is now locked down, for - recognized clouds. This is recommended for secure installations, but - typically conflicts with traditional IMDS clients such as cloud-init, - which require direct IMDS access currently. The new meson option - "imds-network" can be used to change the default networking mode to - "unlocked" at build-time, for compatibility. This is probably what - general purpose distributions should set for now. + New features: + + * Networking to cloud IMDS services may be locked down for recognized + clouds. This is recommended for secure installations, but typically + conflicts with traditional IMDS clients such as cloud-init, which + require direct IMDS access. The new meson option "-Dimds-network=" + can be used to change the default mode to "locked" at build-time. CHANGES WITH 260: diff --git a/meson_options.txt b/meson_options.txt index 30c5fd3ab67fe..d61afac519d84 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -144,7 +144,7 @@ option('timesyncd', type : 'boolean', description : 'install the systemd-timesyncd daemon') option('imds', type : 'feature', description : 'install the systemd-imds stack') -option('imds-network', type : 'combo', choices : [ 'locked', 'unlocked' ], +option('imds-network', type : 'combo', choices : ['unlocked', 'locked'], description : 'whether to default to locked/unlocked IMDS network mode') option('journal-storage-default', type : 'combo', choices : ['persistent', 'auto', 'volatile', 'none'], description : 'default storage mode for journald (main namespace)') From 777d9c10ada1f027e488452b3777e3712f14f0a9 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 13 Mar 2026 14:46:37 +0100 Subject: [PATCH 0539/1296] coccinelle: add checks for pointer access without NULL check The fix in 8f1751a111 made me wonder if we could automatically detect when pointers are accessed but when this might not be safe. Systemd is already using a lot of `assert(dst)` and this change now forces us to use them. So this commit (ab)uses coccinelle to flag any pointer parameter dereference not preceded by assert(param), ASSERT_PTR(param), or an explicit NULL check. It adds integration into meson as a new "coccinelle" test suite (just like clang-tidy) and is run in CI. The check is not perfect but seems a reasonable heuristic. For this RFC commit it is scoped to a subset, it excludes 25 dirs right now and includes around 100. About 300 warnings left. Busywork that I am happy to do if there is agreement that it is worth it. With this in place we would have caught the bug from 8f1751a111 in CI: ``` FAIL: check-pointer-deref.cocci found issues in systemd/src/boot: diff -u -p systemd/src/boot/measure.c /tmp/nothing/measure.c --- systemd/src/boot/measure.c +++ /tmp/nothing/measure.c @@ -312,7 +312,6 @@ EFI_STATUS tpm_log_tagged_event( if (err != EFI_SUCCESS) return err; - *ret_measured = true; return EFI_SUCCESS; } ``` This also adds a new POINTER_MAY_BE_NULL() for the cases when the called function will do the NULL check (like `iovec_is_set()`). --- .github/workflows/linter.yml | 3 ++ coccinelle/check-pointer-deref.cocci | 35 +++++++++++++++++ meson.build | 39 +++++++++++++++++++ .../mkosi.tools.conf/mkosi.conf.d/fedora.conf | 1 + .../mkosi.conf.d/opensuse.conf | 1 + src/analyze/analyze-critical-chain.c | 3 ++ src/analyze/analyze-security.c | 2 + src/analyze/analyze-verify-util.c | 2 + src/analyze/analyze.c | 2 + src/boot/boot.c | 3 ++ src/boot/chid.c | 2 + src/boot/console.c | 5 +++ src/boot/efi-string.c | 2 + src/boot/initrd.c | 1 + src/boot/util.c | 1 + src/bootctl/bootctl-install.c | 2 + src/busctl/busctl.c | 2 + src/cgtop/cgtop.c | 2 +- src/coredump/coredumpctl.c | 4 ++ src/cryptsetup/cryptsetup.c | 2 + src/fundamental/assert-fundamental.h | 5 +++ src/hostname/hostnamed.c | 2 + src/journal-remote/journal-gatewayd.c | 4 +- src/journal-remote/journal-remote-main.c | 2 + src/journal-remote/microhttpd-util.c | 2 + src/libudev/test-libudev.c | 3 ++ src/machine/machinectl.c | 3 ++ src/oom/oomd-manager.c | 2 + src/portable/portable.c | 5 +++ src/portable/portablectl.c | 4 ++ src/repart/repart.c | 6 +++ src/storagetm/storagetm.c | 2 + src/sysext/sysext.c | 3 ++ src/systemctl/systemctl-list-dependencies.c | 3 ++ src/systemctl/systemctl-show.c | 3 ++ src/tmpfiles/tmpfiles.c | 1 + src/udev/udev-builtin-keyboard.c | 2 + src/udev/udev-builtin-path_id.c | 4 ++ src/udev/udev-rules.c | 1 + src/veritysetup/veritysetup.c | 2 + .../xdg-autostart-service.c | 3 ++ tools/check-coccinelle.sh | 28 +++++++++++++ tools/meson.build | 1 + 43 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 coccinelle/check-pointer-deref.cocci create mode 100755 tools/check-coccinelle.sh diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index ba293cf8be135..775b4f3f9d6fd 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -80,6 +80,9 @@ jobs: - name: Run clang-tidy run: mkosi box -- meson test -C build --suite=clang-tidy --print-errorlogs --no-stdsplit --quiet + - name: Run coccinelle checks + run: mkosi box -- meson test -C build --suite=coccinelle --print-errorlogs --no-stdsplit + - name: Build with musl run: | mkosi box -- \ diff --git a/coccinelle/check-pointer-deref.cocci b/coccinelle/check-pointer-deref.cocci new file mode 100644 index 0000000000000..e2376a314c5a1 --- /dev/null +++ b/coccinelle/check-pointer-deref.cocci @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Detect pointer parameters that are dereferenced without a prior NULL check + * or assertion. In systemd style, non-optional pointer parameters should have + * an assert() at the top of the function. + * + * Usage: + * spatch --sp-file coccinelle/check-pointer-deref.cocci --dir src/boot/ + * + * Note: this is a context-mode rule (flags, does not auto-fix). Each flagged + * dereference should be reviewed: if the parameter is never NULL, add + * assert(param) at the top. If it can legitimately be NULL, add an if() guard. + */ +@@ +identifier fn, param; +type T; +position p; +@@ + +fn(..., T *param, ...) { + ... when != assert(param) + when != assert(param != NULL) + when != assert_se(param) + when != assert_se(param != NULL) + when != assert_return(param, ...) + when != ASSERT_PTR(param) + when != POINTER_MAY_BE_NULL(param) + /* NULL-safe helpers used commonly enough in assert() to warrant inclusion + * here. For less common cases, use POINTER_MAY_BE_NULL(param) instead of + * extending this list. */ + when != assert(pidref_is_set(param)) + when != \( param == NULL \| param != NULL \| !param \) +* *param@p + ... +} diff --git a/meson.build b/meson.build index 2893bea332f29..36025023db174 100644 --- a/meson.build +++ b/meson.build @@ -2972,6 +2972,45 @@ if meson.version().version_compare('>=1.4.0') endforeach endif +spatch = find_program('spatch', required : false) +if spatch.found() + # Directories excluded from coccinelle checks until their warnings are fixed. + # Remove directories from this list as they are cleaned up. + coccinelle_exclude = [ + 'src/basic/', + 'src/core/', + 'src/import/', + 'src/journal/', + 'src/libc/', + 'src/libsystemd/', + 'src/libsystemd-network/', + 'src/network/', + 'src/nspawn/', + 'src/nss-systemd/', + 'src/resolve/', + 'src/shared/', + 'src/test/', + ] + + coccinelle_src_dirs = run_command( + 'sh', '-c', 'printf "%s\n" src/*/', + check : true, + ).stdout().strip().split('\n') + + foreach dir : coccinelle_src_dirs + if dir not in coccinelle_exclude + test( + 'coccinelle-@0@'.format(fs.name(dir)), + check_coccinelle_sh, + args : [meson.project_source_root() / dir, + meson.project_source_root() / 'coccinelle'], + suite : 'coccinelle', + timeout : 120, + ) + endif + endforeach +endif + symbol_analysis_exes = [] foreach name, exe : executables_by_name symbol_analysis_exes += exe diff --git a/mkosi/mkosi.tools.conf/mkosi.conf.d/fedora.conf b/mkosi/mkosi.tools.conf/mkosi.conf.d/fedora.conf index 7a9301c566cd1..e687fd788e266 100644 --- a/mkosi/mkosi.tools.conf/mkosi.conf.d/fedora.conf +++ b/mkosi/mkosi.tools.conf/mkosi.conf.d/fedora.conf @@ -13,4 +13,5 @@ Packages= musl-clang musl-gcc ruff + coccinelle shellcheck diff --git a/mkosi/mkosi.tools.conf/mkosi.conf.d/opensuse.conf b/mkosi/mkosi.tools.conf/mkosi.conf.d/opensuse.conf index 8a6711f901ce6..6f24649c54c6c 100644 --- a/mkosi/mkosi.tools.conf/mkosi.conf.d/opensuse.conf +++ b/mkosi/mkosi.tools.conf/mkosi.conf.d/opensuse.conf @@ -7,6 +7,7 @@ Distribution=opensuse PrepareScripts=%D/mkosi/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.prepare Packages= clang-tools + coccinelle gh lcov libtss2-tcti-device0 diff --git a/src/analyze/analyze-critical-chain.c b/src/analyze/analyze-critical-chain.c index ea6d83d417cb6..659d1b564c596 100644 --- a/src/analyze/analyze-critical-chain.c +++ b/src/analyze/analyze-critical-chain.c @@ -67,6 +67,9 @@ static int list_dependencies_compare(char *const *a, char *const *b) { usec_t usa = 0, usb = 0; UnitTimes *times; + assert(a); + assert(b); + times = hashmap_get(unit_times_hashmap, *a); if (times) usa = times->activated; diff --git a/src/analyze/analyze-security.c b/src/analyze/analyze-security.c index bdbd44910bfba..5e9db877b7106 100644 --- a/src/analyze/analyze-security.c +++ b/src/analyze/analyze-security.c @@ -558,6 +558,8 @@ static int assess_system_call_architectures( } static bool syscall_names_in_filter(Set *s, bool allow_list, const SyscallFilterSet *f, const char **ret_offending_syscall) { + assert(ret_offending_syscall); + NULSTR_FOREACH(syscall, f->value) { if (syscall[0] == '@') { const SyscallFilterSet *g; diff --git a/src/analyze/analyze-verify-util.c b/src/analyze/analyze-verify-util.c index dfa1cf7b3bc8a..e7ffae5a28787 100644 --- a/src/analyze/analyze-verify-util.c +++ b/src/analyze/analyze-verify-util.c @@ -268,6 +268,8 @@ static int verify_unit(Unit *u, bool check_man, const char *root) { } static void set_destroy_ignore_pointer_max(Set **s) { + assert(s); + if (*s == POINTER_MAX) return; set_free(*s); diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index 4d078a483851e..e23b0038a9944 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -124,6 +124,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); int acquire_bus(sd_bus **bus, bool *use_full_bus) { int r; + POINTER_MAY_BE_NULL(use_full_bus); + if (use_full_bus && *use_full_bus) { r = bus_connect_transport(arg_transport, arg_host, arg_runtime_scope, bus); if (IN_SET(r, 0, -EHOSTDOWN)) diff --git a/src/boot/boot.c b/src/boot/boot.c index 4a7e616faa688..bffedf78e2928 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -1046,6 +1046,8 @@ static BootEntry* boot_entry_free(BootEntry *entry) { DEFINE_TRIVIAL_CLEANUP_FUNC(BootEntry *, boot_entry_free); static EFI_STATUS config_timeout_sec_from_string(const char *value, uint64_t *dst) { + assert(dst); + if (streq8(value, "menu-disabled")) *dst = TIMEOUT_MENU_DISABLED; else if (streq8(value, "menu-force")) @@ -1555,6 +1557,7 @@ static void config_load_defaults(Config *config, EFI_FILE *root_dir) { EFI_STATUS err; assert(root_dir); + assert(config); *config = (Config) { .editor = true, diff --git a/src/boot/chid.c b/src/boot/chid.c index 28f2b7b898435..8abd9de47ceea 100644 --- a/src/boot/chid.c +++ b/src/boot/chid.c @@ -99,6 +99,8 @@ static EFI_STATUS populate_board_chids(EFI_GUID ret_chids[static CHID_TYPES_MAX] EFI_STATUS chid_match(const void *hwid_buffer, size_t hwid_length, uint32_t match_type, const Device **ret_device) { EFI_STATUS status; + assert(ret_device); + if ((uintptr_t) hwid_buffer % alignof(Device) != 0) return EFI_INVALID_PARAMETER; diff --git a/src/boot/console.c b/src/boot/console.c index 21b36e5a6e5f9..81a5641d40579 100644 --- a/src/boot/console.c +++ b/src/boot/console.c @@ -11,6 +11,8 @@ #define VIEWPORT_RATIO 10 static void event_closep(EFI_EVENT *event) { + assert(event); + if (!*event) return; @@ -191,6 +193,9 @@ EFI_STATUS query_screen_resolution(uint32_t *ret_w, uint32_t *ret_h) { EFI_STATUS err; EFI_GRAPHICS_OUTPUT_PROTOCOL *go; + assert(ret_w); + assert(ret_h); + err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_GRAPHICS_OUTPUT_PROTOCOL), NULL, (void **) &go); if (err != EFI_SUCCESS) return err; diff --git a/src/boot/efi-string.c b/src/boot/efi-string.c index 0f8986b5984b9..d0be7a1e160d3 100644 --- a/src/boot/efi-string.c +++ b/src/boot/efi-string.c @@ -349,6 +349,8 @@ static bool efi_fnmatch_prefix(const char16_t *p, const char16_t *h, const char1 /* Patterns are fnmatch-compatible (with reduced feature support). */ bool efi_fnmatch(const char16_t *pattern, const char16_t *haystack) { + assert(haystack); + /* Patterns can be considered as simple patterns (without '*') concatenated by '*'. By doing so we * simply have to make sure the very first simple pattern matches the start of haystack. Then we just * look for the remaining simple patterns *somewhere* within the haystack (in order) as any extra diff --git a/src/boot/initrd.c b/src/boot/initrd.c index d8cbe7deed425..b8086ac633759 100644 --- a/src/boot/initrd.c +++ b/src/boot/initrd.c @@ -74,6 +74,7 @@ EFI_STATUS initrd_register( EFI_HANDLE handle; struct initrd_loader *loader; + POINTER_MAY_BE_NULL(initrd); assert(ret_initrd_handle); /* If no initrd is specified we'll not install any. This avoids registration of the protocol for that diff --git a/src/boot/util.c b/src/boot/util.c index 4a4c4e9365012..c40a9aad65b0d 100644 --- a/src/boot/util.c +++ b/src/boot/util.c @@ -344,6 +344,7 @@ EFI_STATUS open_directory( EFI_STATUS err; assert(root); + assert(ret); /* Opens a file, and then verifies it is actually a directory */ diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index c4693293ab909..fc89ce143b94b 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -1230,6 +1230,8 @@ static bool same_entry(uint16_t id, sd_id128_t uuid, const char *path) { static int find_slot(sd_id128_t uuid, const char *path, uint16_t *id) { _cleanup_free_ uint16_t *options = NULL; + assert(id); + int n = efi_get_boot_options(&options); if (n < 0) return n; diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c index 48635fad64c4c..aa04c9bbf0e5d 100644 --- a/src/busctl/busctl.c +++ b/src/busctl/busctl.c @@ -393,6 +393,8 @@ static int verb_list_bus_names(int argc, char *argv[], uintptr_t _data, void *us } static void print_subtree(const char *prefix, const char *path, char **l) { + assert(l); + /* We assume the list is sorted. Let's first skip over the * entry we are looking at. */ for (;;) { diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c index 60181caffc122..a9bf64a61651d 100644 --- a/src/cgtop/cgtop.c +++ b/src/cgtop/cgtop.c @@ -509,7 +509,7 @@ static int refresh( } static int group_compare(Group * const *a, Group * const *b) { - const Group *x = *a, *y = *b; + const Group *x = *ASSERT_PTR(a), *y = *ASSERT_PTR(b); int r; if (arg_order != ORDER_TASKS || arg_recursive) { diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index f41d6fef310f5..96724de12b635 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -415,6 +415,8 @@ static int retrieve(const void *data, size_t ident; char *v; + assert(var); + ident = strlen(name) + 1; /* name + "=" */ if (len < ident) @@ -529,6 +531,8 @@ static int resolve_filename(const char *root, char **p) { char *resolved = NULL; int r; + assert(p); + if (!*p) return 0; diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 64cd3813b8728..04eeb6223e219 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -2540,6 +2540,8 @@ static uint32_t determine_flags(void) { static void remove_and_erasep(const char **p) { int r; + assert(p); + if (!*p) return; diff --git a/src/fundamental/assert-fundamental.h b/src/fundamental/assert-fundamental.h index 3168e5699aa93..e7f662512bff5 100644 --- a/src/fundamental/assert-fundamental.h +++ b/src/fundamental/assert-fundamental.h @@ -100,3 +100,8 @@ static inline int __coverity_check_and_return__(int condition) { assert_se(_expr_ >= _zero); \ _expr_; \ }) + +/* Mark a pointer parameter as intentionally nullable. This is a no-op at runtime but suppresses + * the coccinelle check-pointer-deref warning for parameters that are safely handled before any + * dereference (e.g. passed to a NULL-safe helper like iovec_is_set()). */ +#define POINTER_MAY_BE_NULL(ptr) ({ (void) (ptr); }) diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index dcd1264534970..49462c6a65d89 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -822,6 +822,8 @@ static int context_update_kernel_hostname( } static void unset_statp(struct stat **p) { + assert(p); + if (!*p) return; diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index ee190827615e2..c140c406cb6b6 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -104,8 +104,10 @@ static void request_meta_free( struct MHD_Connection *connection, void **connection_cls, enum MHD_RequestTerminationCode toe) { + RequestMeta *m; - RequestMeta *m = *connection_cls; + assert(connection_cls); + m = *connection_cls; if (!m) return; diff --git a/src/journal-remote/journal-remote-main.c b/src/journal-remote/journal-remote-main.c index 35ab12578b2a0..0ff44ede6fc1c 100644 --- a/src/journal-remote/journal-remote-main.c +++ b/src/journal-remote/journal-remote-main.c @@ -1103,6 +1103,8 @@ static int parse_argv(int argc, char *argv[]) { static int load_certificates(char **key, char **cert, char **trust) { int r; + assert(trust); + r = read_full_file_full( AT_FDCWD, arg_key ?: PRIV_KEY_FILE, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, diff --git a/src/journal-remote/microhttpd-util.c b/src/journal-remote/microhttpd-util.c index 73b6ed4c9466c..32751e85e1c34 100644 --- a/src/journal-remote/microhttpd-util.c +++ b/src/journal-remote/microhttpd-util.c @@ -230,6 +230,8 @@ static int get_auth_dn(gnutls_x509_crt_t client_cert, char **buf) { } static void gnutls_x509_crt_deinitp(gnutls_x509_crt_t *p) { + assert(p); + gnutls_x509_crt_deinit(*p); } diff --git a/src/libudev/test-libudev.c b/src/libudev/test-libudev.c index 63c24031240e6..f15cbc3a91edc 100644 --- a/src/libudev/test-libudev.c +++ b/src/libudev/test-libudev.c @@ -416,6 +416,9 @@ static int parse_args(int argc, char *argv[], const char **syspath, const char * }; int c; + assert(syspath); + assert(subsystem); + while ((c = getopt_long(argc, argv, "p:s:dhVm", options, NULL)) >= 0) switch (c) { case 'p': diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index d9ee2fe2bb64c..733b1a19ef100 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -1308,6 +1308,9 @@ static int parse_machine_uid(const char *spec, const char **machine, char **uid) char *_uid = NULL; const char *_machine = NULL; + assert(uid); + assert(machine); + if (spec) { const char *at; diff --git a/src/oom/oomd-manager.c b/src/oom/oomd-manager.c index 97ad9c0a9f77f..382a246c2dddb 100644 --- a/src/oom/oomd-manager.c +++ b/src/oom/oomd-manager.c @@ -438,6 +438,8 @@ static int monitor_swap_contexts_handler(sd_event_source *s, uint64_t usec, void } static void clear_candidate_hashmapp(Manager **m) { + assert(m); + if (*m) hashmap_clear((*m)->monitored_mem_pressure_cgroup_contexts_candidates); } diff --git a/src/portable/portable.c b/src/portable/portable.c index a7f3ce9dfd052..bae23b1a5115e 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -137,6 +137,9 @@ PortableMetadata *portable_metadata_unref(PortableMetadata *i) { } static int compare_metadata(PortableMetadata *const *x, PortableMetadata *const *y) { + assert(x); + assert(y); + return strcmp((*x)->name, (*y)->name); } @@ -146,6 +149,8 @@ int portable_metadata_hashmap_to_sorted_array(Hashmap *unit_files, PortableMetad PortableMetadata *item; size_t k = 0; + assert(ret); + sorted = new(PortableMetadata*, hashmap_size(unit_files)); if (!sorted) return -ENOMEM; diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index 98a1657a720a8..0cd461a997e23 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -57,6 +57,8 @@ static bool is_portable_managed(const char *unit) { static int determine_image(const char *image, bool permit_non_existing, char **ret) { int r; + assert(ret); + /* If the specified name is a valid image name, we pass it as-is to portabled, which will search for it in the * usual search directories. Otherwise we presume it's a path, and will normalize it on the client's side * (among other things, to make the path independent of the client's working directory) before passing it @@ -235,6 +237,8 @@ static int acquire_bus(sd_bus **bus) { static int maybe_reload(sd_bus **bus) { int r; + assert(bus); + if (!arg_reload) return 0; diff --git a/src/repart/repart.c b/src/repart/repart.c index 1bdeec6ea5644..7dbcfd6d5824e 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -652,6 +652,8 @@ static int calculate_verity_hash_size( uint64_t data_block_size, uint64_t *ret_bytes) { + assert(ret_bytes); + /* The calculation here is based on the documented on-disk format of the dm-verity * https://docs.kernel.org/admin-guide/device-mapper/verity.html#hash-tree * @@ -1264,6 +1266,8 @@ static uint64_t free_area_available_for_new_partitions(Context *context, const F } static int free_area_compare(FreeArea *const *a, FreeArea *const*b, Context *context) { + assert(a); + assert(b); assert(context); return CMP(free_area_available_for_new_partitions(context, *a), @@ -4042,6 +4046,8 @@ static void context_unload_partition_table(Context *context) { static int format_size_change(uint64_t from, uint64_t to, char **ret) { char *t; + assert(ret); + if (from != UINT64_MAX) { if (from == to || to == UINT64_MAX) t = strdup(FORMAT_BYTES(from)); diff --git a/src/storagetm/storagetm.c b/src/storagetm/storagetm.c index c6caaa1260ba3..7a4596e4724ef 100644 --- a/src/storagetm/storagetm.c +++ b/src/storagetm/storagetm.c @@ -746,6 +746,8 @@ static int plymouth_notify_port(NvmePort *port, struct local_address *a) { } static int nvme_port_report(NvmePort *port, bool *plymouth_done) { + POINTER_MAY_BE_NULL(plymouth_done); + if (!port) return 0; diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 32de5a454c8cb..157bcb0a0b0db 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -1785,6 +1785,9 @@ static int merge_hierarchy( } static int strverscmp_improvedp(char *const* a, char *const* b) { + assert(a); + assert(b); + /* usable in qsort() for sorting a string array with strverscmp_improved() */ return strverscmp_improved(*a, *b); } diff --git a/src/systemctl/systemctl-list-dependencies.c b/src/systemctl/systemctl-list-dependencies.c index 8e5736ef3531f..4e7c12e6b9e12 100644 --- a/src/systemctl/systemctl-list-dependencies.c +++ b/src/systemctl/systemctl-list-dependencies.c @@ -82,6 +82,9 @@ static int list_dependencies_print(const char *name, UnitActiveState state, int } static int list_dependencies_compare(char * const *a, char * const *b) { + assert(a); + assert(b); + if (unit_name_to_type(*a) == UNIT_TARGET && unit_name_to_type(*b) != UNIT_TARGET) return 1; if (unit_name_to_type(*a) != UNIT_TARGET && unit_name_to_type(*b) == UNIT_TARGET) diff --git a/src/systemctl/systemctl-show.c b/src/systemctl/systemctl-show.c index 0872f82c2a3f2..570aab7365922 100644 --- a/src/systemctl/systemctl-show.c +++ b/src/systemctl/systemctl-show.c @@ -320,6 +320,9 @@ static void unit_status_info_done(UnitStatusInfo *info) { } static void format_active_state(const char *active_state, const char **active_on, const char **active_off) { + assert(active_on); + assert(active_off); + if (streq_ptr(active_state, "failed")) { *active_on = ansi_highlight_red(); *active_off = ansi_normal(); diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 7885a668fd483..17f263790eda0 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -4433,6 +4433,7 @@ static int parse_arguments( int r; assert(c); + assert(invalid_config); STRV_FOREACH(arg, args) { if (arg_inline) { diff --git a/src/udev/udev-builtin-keyboard.c b/src/udev/udev-builtin-keyboard.c index 5ab40a35526d3..3ced8ad91ca04 100644 --- a/src/udev/udev-builtin-keyboard.c +++ b/src/udev/udev-builtin-keyboard.c @@ -90,6 +90,8 @@ static const char* parse_token(const char *current, int32_t *val_out) { char *next; int32_t val; + assert(val_out); + if (!current) return NULL; diff --git a/src/udev/udev-builtin-path_id.c b/src/udev/udev-builtin-path_id.c index cdd8da3203fea..a252ec99dd79b 100644 --- a/src/udev/udev-builtin-path_id.c +++ b/src/udev/udev-builtin-path_id.c @@ -398,6 +398,8 @@ static sd_device* handle_scsi_hyperv(sd_device *parent, char **path, size_t guid static sd_device* handle_scsi(sd_device *parent, char **path, char **compat_path, bool *supported_parent) { const char *id, *name; + assert(supported_parent); + if (device_is_devtype(parent, "scsi_device") <= 0) return parent; @@ -454,6 +456,8 @@ static sd_device* handle_cciss(sd_device *parent, char **path) { static void handle_scsi_tape(sd_device *dev, char **path) { const char *name; + assert(path); + /* must be the last device in the syspath */ if (*path) return; diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c index f0e4fbccfd791..691230d7535ce 100644 --- a/src/udev/udev-rules.c +++ b/src/udev/udev-rules.c @@ -1359,6 +1359,7 @@ static int parse_line(char **line, char **ret_key, char **ret_attr, UdevRuleOper assert(line); assert(*line); assert(ret_key); + assert(ret_attr); assert(ret_op); assert(ret_value); assert(ret_is_case_insensitive); diff --git a/src/veritysetup/veritysetup.c b/src/veritysetup/veritysetup.c index b2f6d3b8af726..4a244ae83dc42 100644 --- a/src/veritysetup/veritysetup.c +++ b/src/veritysetup/veritysetup.c @@ -116,6 +116,8 @@ static int parse_block_size(const char *t, uint64_t *size) { uint64_t u; int r; + assert(size); + r = parse_size(t, 1024, &u); if (r < 0) return r; diff --git a/src/xdg-autostart-generator/xdg-autostart-service.c b/src/xdg-autostart-generator/xdg-autostart-service.c index 62ddce1815e39..ad77e476c83e4 100644 --- a/src/xdg-autostart-generator/xdg-autostart-service.c +++ b/src/xdg-autostart-generator/xdg-autostart-service.c @@ -291,6 +291,9 @@ static int xdg_config_item_table_lookup( void *userdata) { assert(lvalue); + assert(ret_func); + assert(ret_ltype); + assert(ret_data); /* Ignore any keys with [] as those are translations. */ if (strchr(lvalue, '[')) { diff --git a/tools/check-coccinelle.sh b/tools/check-coccinelle.sh new file mode 100755 index 0000000000000..c7d1f6f6da0d5 --- /dev/null +++ b/tools/check-coccinelle.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eu +set -o pipefail + +SRC_DIR="${1:?}" +COCCI_DIR="${2:?}" + +FOUND=0 + +for cocci in "$COCCI_DIR"/check-*.cocci; do + [[ -f "$cocci" ]] || continue + output=$(spatch --very-quiet --sp-file "$cocci" --dir "$SRC_DIR" 2>&1) + if [[ -n "$output" ]]; then + echo "FAIL: $(basename "$cocci") found issues in $SRC_DIR:" + echo "$output" + FOUND=1 + fi +done + +if [[ "$FOUND" -ne 0 ]]; then + echo "" + echo "Coccinelle check(s) failed. For each flagged dereference, either:" + echo " - Add assert(param)/ASSERT_PTR(param) at the top of the function (if the parameter must not be NULL)" + echo " - Add an if (param) guard before the dereference (if NULL is valid)" + echo " - Add POINTER_MAY_BE_NULL(param) if NULL is okay for param" + exit 1 +fi diff --git a/tools/meson.build b/tools/meson.build index 3132eeddba51f..e8b3133d9c8ca 100644 --- a/tools/meson.build +++ b/tools/meson.build @@ -1,6 +1,7 @@ # SPDX-License-Identifier: LGPL-2.1-or-later check_api_docs_sh = files('check-api-docs.sh') +check_coccinelle_sh = files('check-coccinelle.sh') check_efi_alignment_py = files('check-efi-alignment.py') check_help_sh = files('check-help.sh') check_version_history_py = files('check-version-history.py') From 2d2dc38f0028bab3fa11c7a43ab26f6adf10b544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 26 Mar 2026 13:47:44 +0100 Subject: [PATCH 0540/1296] basic/proc-cmdline: extend comments Inspired by the discussion in #41161. Also change the order of flags to be more logical. First the option to specify at what fields we look, then the option to specify how we return their name, the the value, and finally what to do if the value is missing. --- src/basic/proc-cmdline.h | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/basic/proc-cmdline.h b/src/basic/proc-cmdline.h index 42a8ef1eb9c91..6abf57ac3c1ea 100644 --- a/src/basic/proc-cmdline.h +++ b/src/basic/proc-cmdline.h @@ -4,10 +4,15 @@ #include "basic-forward.h" typedef enum ProcCmdlineFlags { - PROC_CMDLINE_STRIP_RD_PREFIX = 1 << 0, /* automatically strip "rd." prefix if it is set (and we are in the initrd, since otherwise we'd not consider it anyway) */ - PROC_CMDLINE_VALUE_OPTIONAL = 1 << 1, /* the value is optional (for boolean switches that can omit the value) */ - PROC_CMDLINE_RD_STRICT = 1 << 2, /* ignore this in the initrd */ - PROC_CMDLINE_TRUE_WHEN_MISSING = 1 << 3, /* default to true when the key is missing for bool */ + PROC_CMDLINE_RD_STRICT = 1 << 0, /* Only look at options with the "rd." prefix when in the initrd and only + * at options without the prefix when not in the initrd. + */ + PROC_CMDLINE_STRIP_RD_PREFIX = 1 << 1, /* Automatically strip "rd." prefix if we are in the initrd. + * When this is specified, the handler function must check for unprefixed + * option names. */ + PROC_CMDLINE_VALUE_OPTIONAL = 1 << 2, /* The value is optional (for boolean switches that can omit the value). */ + PROC_CMDLINE_TRUE_WHEN_MISSING = 1 << 3, /* Make proc_cmdline_get_bool() return true instead of false (the default) + * when the key is not present on the command line. */ } ProcCmdlineFlags; typedef int (*proc_cmdline_parse_t)(const char *key, const char *value, void *data); From 1682845008cfcac36183cf7cc4f06e96904813a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 24 Mar 2026 23:44:01 +0100 Subject: [PATCH 0541/1296] shared/options: add OPTION_COMMON_LOWERCASE_J --- src/id128/id128.c | 3 +-- src/shared/options.h | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/id128/id128.c b/src/id128/id128.c index fe4d2f2283644..f23403b3f811b 100644 --- a/src/id128/id128.c +++ b/src/id128/id128.c @@ -259,8 +259,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return r; break; - OPTION_SHORT('j', NULL, - "Equivalent to --json=pretty (on TTY) or --json=short (otherwise)"): + OPTION_COMMON_LOWERCASE_J: arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; break; diff --git a/src/shared/options.h b/src/shared/options.h index 6baec72b4feaf..7980b69448b11 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -68,6 +68,9 @@ typedef struct Option { OPTION('M', "machine", "CONTAINER", "Operate on local container") #define OPTION_COMMON_JSON \ OPTION_LONG("json", "FORMAT", "Generate JSON output (pretty, short, or off)") +#define OPTION_COMMON_LOWERCASE_J \ + OPTION_SHORT('j', NULL, \ + "Equivalent to --json=pretty (on TTY) or --json=short (otherwise)") /* This is magically mapped to the beginning and end of the section */ extern const Option __start_SYSTEMD_OPTIONS[]; From 198306252f9400fd320bf9963f25bc306b3cb358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 25 Mar 2026 15:56:02 +0100 Subject: [PATCH 0542/1296] hostnamectl: use the new option and verb macros --help is the same except for strings in common options. Co-developed-by: Claude --- src/hostname/hostnamectl.c | 163 ++++++++++++++----------------------- 1 file changed, 59 insertions(+), 104 deletions(-) diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index 75a17a13aeeac..52fa3319d7070 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -23,6 +22,7 @@ #include "hostname-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "polkit-agent.h" #include "pretty-print.h" @@ -549,6 +549,7 @@ static int get_hostname_based_on_flag(sd_bus *bus) { return get_one_name(bus, attr, NULL); } +VERB(verb_show_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT, "Show current hostname settings"); static int verb_show_status(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = userdata; int r; @@ -687,26 +688,36 @@ static int verb_set_hostname(int argc, char *argv[], uintptr_t _data, void *user return ret; } +VERB(verb_get_or_set_hostname, "hostname", "[NAME]", VERB_ANY, 2, 0, "Get/set system hostname"); +VERB(verb_get_or_set_hostname, "set-hostname", "NAME", 2, 2, 0, NULL); /* obsolete */ static int verb_get_or_set_hostname(int argc, char *argv[], uintptr_t data, void *userdata) { return argc == 1 ? get_hostname_based_on_flag(userdata) : verb_set_hostname(argc, argv, data, userdata); } +VERB(verb_get_or_set_icon_name, "icon-name", "[NAME]", VERB_ANY, 2, 0, "Get/set icon name for host"); +VERB(verb_get_or_set_icon_name, "set-icon-name", "NAME", 2, 2, 0, NULL); /* obsolete */ static int verb_get_or_set_icon_name(int argc, char *argv[], uintptr_t _data, void *userdata) { return argc == 1 ? get_one_name(userdata, "IconName", NULL) : set_simple_string(userdata, "icon", "SetIconName", argv[1]); } +VERB(verb_get_or_set_chassis, "chassis", "[NAME]", VERB_ANY, 2, 0, "Get/set chassis type for host"); +VERB(verb_get_or_set_chassis, "set-chassis", "NAME", 2, 2, 0, NULL); /* obsolete */ static int verb_get_or_set_chassis(int argc, char *argv[], uintptr_t _data, void *userdata) { return argc == 1 ? get_one_name(userdata, "Chassis", NULL) : set_simple_string(userdata, "chassis", "SetChassis", argv[1]); } +VERB(verb_get_or_set_deployment, "deployment", "[NAME]", VERB_ANY, 2, 0, "Get/set deployment environment for host"); +VERB(verb_get_or_set_deployment, "set-deployment", "NAME", 2, 2, 0, NULL); /* obsolete */ static int verb_get_or_set_deployment(int argc, char *argv[], uintptr_t _data, void *userdata) { return argc == 1 ? get_one_name(userdata, "Deployment", NULL) : set_simple_string(userdata, "deployment", "SetDeployment", argv[1]); } +VERB(verb_get_or_set_location, "location", "[NAME]", VERB_ANY, 2, 0, "Get/set location for host"); +VERB(verb_get_or_set_location, "set-location", "NAME", 2, 2, 0, NULL); /* obsolete */ static int verb_get_or_set_location(int argc, char *argv[], uintptr_t _data, void *userdata) { return argc == 1 ? get_one_name(userdata, "Location", NULL) : set_simple_string(userdata, "location", "SetLocation", argv[1]); @@ -714,164 +725,108 @@ static int verb_get_or_set_location(int argc, char *argv[], uintptr_t _data, voi static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("hostnamectl", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%2$sQuery or change system hostname.%3$s\n" - "\n%4$sCommands:%5$s\n" - " status Show current hostname settings\n" - " hostname [NAME] Get/set system hostname\n" - " icon-name [NAME] Get/set icon name for host\n" - " chassis [NAME] Get/set chassis type for host\n" - " deployment [NAME] Get/set deployment environment for host\n" - " location [NAME] Get/set location for host\n" - "\n%4$sOptions:%5$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-ask-password Do not prompt for password\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --transient Only set transient hostname\n" - " --static Only set static hostname\n" - " --pretty Only set pretty hostname\n" - " --json=pretty|short|off\n" - " Generate JSON output\n" - " -j Same as --json=pretty on tty, --json=short otherwise\n" - "\nSee the %6$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, options, verbs); + + printf("%s [OPTIONS...] COMMAND ...\n" + "\n%sQuery or change system hostname.%s\n" + "\nCommands:\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - ansi_underline(), - ansi_normal(), - link); + ansi_normal()); + table_print(verbs, stdout); - return 0; -} + printf("\nOptions:\n"); + table_print(options, stdout); -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); + printf("\nSee the %s for details.\n", link); + return 0; } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_ASK_PASSWORD, - ARG_TRANSIENT, - ARG_STATIC, - ARG_PRETTY, - ARG_JSON, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "transient", no_argument, NULL, ARG_TRANSIENT }, - { "static", no_argument, NULL, ARG_STATIC }, - { "pretty", no_argument, NULL, ARG_PRETTY }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "json", required_argument, NULL, ARG_JSON }, - {} - }; +VERB_COMMON_HELP_HIDDEN(help); - int c, r; +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hH:M:j", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'H': + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + OPTION_COMMON_HOST: arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + arg_host = arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(arg, &arg_host, &arg_transport); if (r < 0) return r; break; - case ARG_TRANSIENT: + OPTION_LONG("transient", NULL, "Only set transient hostname"): arg_transient = true; break; - case ARG_PRETTY: - arg_pretty = true; - break; - - case ARG_STATIC: + OPTION_LONG("static", NULL, "Only set static hostname"): arg_static = true; break; - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; + OPTION_LONG("pretty", NULL, "Only set pretty hostname"): + arg_pretty = true; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); if (r <= 0) return r; - break; - case 'j': + OPTION_COMMON_LOWERCASE_J: arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state); return 1; } -static int hostnamectl_main(sd_bus *bus, int argc, char *argv[]) { - - static const Verb verbs[] = { - { "status", VERB_ANY, 1, VERB_DEFAULT, verb_show_status }, - { "hostname", VERB_ANY, 2, 0, verb_get_or_set_hostname }, - { "set-hostname", 2, 2, 0, verb_get_or_set_hostname }, /* obsolete */ - { "icon-name", VERB_ANY, 2, 0, verb_get_or_set_icon_name }, - { "set-icon-name", 2, 2, 0, verb_get_or_set_icon_name }, /* obsolete */ - { "chassis", VERB_ANY, 2, 0, verb_get_or_set_chassis }, - { "set-chassis", 2, 2, 0, verb_get_or_set_chassis }, /* obsolete */ - { "deployment", VERB_ANY, 2, 0, verb_get_or_set_deployment }, - { "set-deployment", 2, 2, 0, verb_get_or_set_deployment }, /* obsolete */ - { "location", VERB_ANY, 2, 0, verb_get_or_set_location }, - { "set-location", 2, 2, 0, verb_get_or_set_location }, /* obsolete */ - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, /* Not documented, but supported since it is created. */ - {} - }; - - return dispatch_verb(argc, argv, verbs, bus); -} - static int run(int argc, char *argv[]) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + char **args = NULL; int r; setlocale(LC_ALL, ""); log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -879,7 +834,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return bus_log_connect_error(r, arg_transport, RUNTIME_SCOPE_SYSTEM); - return hostnamectl_main(bus, argc, argv); + return dispatch_verb_with_args(args, bus); } DEFINE_MAIN_FUNCTION(run); From 777a738bbc8eef87f10adbc73bc3f77e618e418b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 25 Mar 2026 16:14:41 +0100 Subject: [PATCH 0543/1296] factory-reset: use the new option and verb macros --help is the same except for strings in common options. Co-developed-by: Claude --- src/factory-reset/factory-reset-tool.c | 94 +++++++++++--------------- 1 file changed, 39 insertions(+), 55 deletions(-) diff --git a/src/factory-reset/factory-reset-tool.c b/src/factory-reset/factory-reset-tool.c index 76aec0576480d..e7eabd8757b95 100644 --- a/src/factory-reset/factory-reset-tool.c +++ b/src/factory-reset/factory-reset-tool.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-json.h" #include "sd-varlink.h" @@ -12,9 +10,11 @@ #include "efivars.h" #include "errno-util.h" #include "factory-reset.h" +#include "format-table.h" #include "fs-util.h" #include "json-util.h" #include "main-func.h" +#include "options.h" #include "os-util.h" #include "pretty-print.h" #include "udev-util.h" @@ -28,76 +28,62 @@ static bool arg_varlink = false; static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-factory-reset", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND\n" - "\n%5$sQuery, request, cancel factory reset operation.%6$s\n" - "\n%3$sCommands:%4$s\n" - " status Report current factory reset status\n" - " request Request a factory reset on next boot\n" - " cancel Cancel a prior factory reset request for next boot\n" - " complete Mark a factory reset as complete\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version\n" - " --retrigger Retrigger block devices\n" - " -q --quiet Suppress output\n" - "\nSee the %2$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, options, verbs); + + printf("%s [OPTIONS...] COMMAND\n" + "\n%sQuery, request, cancel factory reset operation.%s\n" + "\nCommands:\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), ansi_normal()); + table_print(verbs, stdout); + printf("\nOptions:\n"); + table_print(options, stdout); + + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_RETRIGGER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "retrigger", no_argument, NULL, ARG_RETRIGGER }, - { "quiet", no_argument, NULL, 'q' }, - {} - }; - - int r, c; +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hq", options, NULL)) >= 0) - switch (c) { + OptionParser state = { argc, argv }; + const char *arg; - case 'h': + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + switch (c) { + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_RETRIGGER: + OPTION_LONG("retrigger", NULL, "Retrigger block devices"): arg_retrigger = true; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress output"): arg_quiet = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT); @@ -106,9 +92,11 @@ static int parse_argv(int argc, char *argv[]) { if (r > 0) arg_varlink = true; + *ret_args = option_parser_get_args(&state); return 1; } +VERB(verb_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT, "Report current factory reset status"); static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { static const int exit_status_table[_FACTORY_RESET_MODE_MAX] = { /* Report current mode also as via exit status, but only return a subset of states */ @@ -130,6 +118,7 @@ static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) return exit_status_table[f]; } +VERB_NOARG(verb_request, "request", "Request a factory reset on next boot"); static int verb_request(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -197,6 +186,7 @@ static int verb_request(int argc, char *argv[], uintptr_t _data, void *userdata) return 0; } +VERB_NOARG(verb_cancel, "cancel", "Cancel a prior factory reset request for next boot"); static int verb_cancel(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -269,6 +259,7 @@ static int retrigger_block_devices(void) { return 0; } +VERB_NOARG(verb_complete, "complete", "Mark a factory reset as complete"); static int verb_complete(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -359,26 +350,19 @@ static int varlink_service(void) { } static int run(int argc, char *argv[]) { - static const Verb verbs[] = { - { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, - { "request", VERB_ANY, 1, 0, verb_request }, - { "cancel", VERB_ANY, 1, 0, verb_cancel }, - { "complete", VERB_ANY, 1, 0, verb_complete }, - {} - }; - int r; log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; if (arg_varlink) return varlink_service(); - return dispatch_verb(argc, argv, verbs, /* userdata= */ NULL); + return dispatch_verb_with_args(args, /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); From 80f86394ab710748b24b9c28ac771e1e361027b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 25 Mar 2026 22:19:01 +0100 Subject: [PATCH 0544/1296] detect-virt: use the new option macros --help output changes: description is now highlighted, "Options:" header added, option order follows declaration order, column width is auto-computed. Co-developed-by: Claude --- src/detect-virt/detect-virt.c | 90 +++++++++++------------------------ 1 file changed, 29 insertions(+), 61 deletions(-) diff --git a/src/detect-virt/detect-virt.c b/src/detect-virt/detect-virt.c index d28e3024805e0..912f6fbdd67bb 100644 --- a/src/detect-virt/detect-virt.c +++ b/src/detect-virt/detect-virt.c @@ -1,12 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "alloc-util.h" +#include "ansi-color.h" #include "build.h" #include "confidential-virt.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pretty-print.h" #include "string-table.h" #include "virt.h" @@ -23,109 +24,76 @@ static enum { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-detect-virt", "1", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...]\n\n" - "Detect execution in a virtualized environment.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -c --container Only detect whether we are run in a container\n" - " -v --vm Only detect whether we are run in a VM\n" - " -r --chroot Detect whether we are run in a chroot() environment\n" - " --private-users Only detect whether we are running in a user namespace\n" - " --cvm Only detect whether we are run in a confidential VM\n" - " -q --quiet Don't output anything, just set return value\n" - " --list List all known and detectable types of virtualization\n" - " --list-cvm List all known and detectable types of confidential \n" - " virtualization\n" - "\nSee the %s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n" + "\n%sDetect execution in a virtualized environment.%s\n" + "\nOptions:\n", program_invocation_short_name, - link); + ansi_highlight(), + ansi_normal()); + table_print(options, stdout); + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_PRIVATE_USERS, - ARG_LIST, - ARG_CVM, - ARG_LIST_CVM, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "container", no_argument, NULL, 'c' }, - { "vm", no_argument, NULL, 'v' }, - { "chroot", no_argument, NULL, 'r' }, - { "private-users", no_argument, NULL, ARG_PRIVATE_USERS }, - { "quiet", no_argument, NULL, 'q' }, - { "cvm", no_argument, NULL, ARG_CVM }, - { "list", no_argument, NULL, ARG_LIST }, - { "list-cvm", no_argument, NULL, ARG_LIST_CVM }, - {} - }; - - int c; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hqcvr", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'q': + OPTION('q', "quiet", NULL, "Don't output anything, just set return value"): arg_quiet = true; break; - case 'c': + OPTION('c', "container", NULL, "Only detect whether we are run in a container"): arg_mode = ONLY_CONTAINER; break; - case ARG_PRIVATE_USERS: + OPTION_LONG("private-users", NULL, "Only detect whether we are running in a user namespace"): arg_mode = ONLY_PRIVATE_USERS; break; - case 'v': + OPTION('v', "vm", NULL, "Only detect whether we are run in a VM"): arg_mode = ONLY_VM; break; - case 'r': + OPTION('r', "chroot", NULL, "Detect whether we are run in a chroot() environment"): arg_mode = ONLY_CHROOT; break; - case ARG_LIST: + OPTION_LONG("list", NULL, "List all known and detectable types of virtualization"): return DUMP_STRING_TABLE(virtualization, Virtualization, _VIRTUALIZATION_MAX); - case ARG_CVM: + OPTION_LONG("cvm", NULL, "Only detect whether we are run in a confidential VM"): arg_mode = ONLY_CVM; return 1; - case ARG_LIST_CVM: + OPTION_LONG("list-cvm", NULL, "List all known and detectable types of confidential virtualization"): return DUMP_STRING_TABLE(confidential_virtualization, ConfidentialVirtualization, _CONFIDENTIAL_VIRTUALIZATION_MAX); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind < argc) + if (option_parser_get_n_args(&state) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s takes no arguments.", program_invocation_short_name); From 1fcb88b234147210069469bb6a14f836377d15e1 Mon Sep 17 00:00:00 2001 From: ssahani Date: Fri, 27 Mar 2026 09:19:44 +0530 Subject: [PATCH 0545/1296] networkd: Add IPv4SrcValidMark= support Add support for configuring net.ipv4.conf..src_valid_mark via the [Network] section in .network files. Co-developed-by: Claude Opus 4.6 --- src/network/networkd-network-gperf.gperf | 1 + src/network/networkd-network.c | 1 + src/network/networkd-network.h | 1 + src/network/networkd-sysctl.c | 17 +++++++++++++++++ 4 files changed, 20 insertions(+) diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index f1049cc7cc260..aaf974e312d67 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -168,6 +168,7 @@ Network.IPv6ProxyNDP, config_parse_tristate, Network.IPv6MTUBytes, config_parse_mtu, AF_INET6, offsetof(Network, ipv6_mtu) Network.IPv4AcceptLocal, config_parse_tristate, 0, offsetof(Network, ipv4_accept_local) Network.IPv4RouteLocalnet, config_parse_tristate, 0, offsetof(Network, ipv4_route_localnet) +Network.IPv4SrcValidMark, config_parse_tristate, 0, offsetof(Network, ipv4_src_valid_mark) Network.ActiveSlave, config_parse_bool, 0, offsetof(Network, active_slave) Network.PrimarySlave, config_parse_bool, 0, offsetof(Network, primary_slave) Network.IPv4ProxyARP, config_parse_tristate, 0, offsetof(Network, proxy_arp) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 1e159fa31027d..3ffe1640e767b 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -479,6 +479,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .ip_forwarding = { -1, -1, }, .ipv4_accept_local = -1, .ipv4_route_localnet = -1, + .ipv4_src_valid_mark = -1, .ipv6_privacy_extensions = _IPV6_PRIVACY_EXTENSIONS_INVALID, .ipv6_dad_transmits = -1, .ipv6_proxy_ndp = -1, diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 923828b2ea1e9..9a36c312f8920 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -332,6 +332,7 @@ typedef struct Network { int ip_forwarding[2]; int ipv4_accept_local; int ipv4_route_localnet; + int ipv4_src_valid_mark; int ipv6_dad_transmits; uint8_t ipv6_hop_limit; usec_t ipv6_retransmission_time; diff --git a/src/network/networkd-sysctl.c b/src/network/networkd-sysctl.c index 914fbccd09bf9..8946f36960705 100644 --- a/src/network/networkd-sysctl.c +++ b/src/network/networkd-sysctl.c @@ -662,6 +662,19 @@ static int link_set_ipv4_route_localnet(Link *link) { return sysctl_write_ip_property_boolean(AF_INET, link->ifname, "route_localnet", link->network->ipv4_route_localnet > 0, manager_get_sysctl_shadow(link->manager)); } +static int link_set_ipv4_src_valid_mark(Link *link) { + assert(link); + assert(link->manager); + + if (!link_is_configured_for_family(link, AF_INET)) + return 0; + + if (link->network->ipv4_src_valid_mark < 0) + return 0; + + return sysctl_write_ip_property_boolean(AF_INET, link->ifname, "src_valid_mark", link->network->ipv4_src_valid_mark > 0, manager_get_sysctl_shadow(link->manager)); +} + static int link_set_ipv4_promote_secondaries(Link *link) { assert(link); assert(link->manager); @@ -750,6 +763,10 @@ int link_set_sysctl(Link *link) { if (r < 0) log_link_warning_errno(link, r, "Cannot set IPv4 route_localnet flag for interface, ignoring: %m"); + r = link_set_ipv4_src_valid_mark(link); + if (r < 0) + log_link_warning_errno(link, r, "Cannot set IPv4 src_valid_mark flag for interface, ignoring: %m"); + r = link_set_ipv4_rp_filter(link); if (r < 0) log_link_warning_errno(link, r, "Cannot set IPv4 reverse path filtering for interface, ignoring: %m"); From 6e2a452118cf2cb0071490c8daa6829db45356e7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Mar 2026 18:08:16 +0100 Subject: [PATCH 0546/1296] fundamental: move strv_isempty() into src/fundamental/ --- src/basic/strv.h | 4 ---- src/boot/boot.c | 2 +- src/fundamental/strv-fundamental.h | 5 +++++ 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/basic/strv.h b/src/basic/strv.h index 7249d8a311767..d7c4b2bcf08ed 100644 --- a/src/basic/strv.h +++ b/src/basic/strv.h @@ -104,10 +104,6 @@ static inline const char* STRV_IFNOTNULL(const char *x) { return x ?: STRV_IGNORE; } -static inline bool strv_isempty(char * const *l) { - return !l || !*l; -} - int strv_split_full(char ***t, const char *s, const char *separators, ExtractFlags flags); char** strv_split(const char *s, const char *separators); diff --git a/src/boot/boot.c b/src/boot/boot.c index bffedf78e2928..237e78c5a9360 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -2547,7 +2547,7 @@ static EFI_STATUS initrd_prepare( assert(ret_initrd_pages); assert(ret_initrd_size); - if (entry->type != LOADER_LINUX || !entry->initrd) { + if (entry->type != LOADER_LINUX || strv_isempty(entry->initrd)) { *ret_options = NULL; *ret_initrd_pages = (Pages) {}; *ret_initrd_size = 0; diff --git a/src/fundamental/strv-fundamental.h b/src/fundamental/strv-fundamental.h index 3abcdc4b02eea..7e7e34822f515 100644 --- a/src/fundamental/strv-fundamental.h +++ b/src/fundamental/strv-fundamental.h @@ -2,9 +2,14 @@ #pragma once #include "macro-fundamental.h" +#include "string-util-fundamental.h" #define _STRV_FOREACH(s, l, i) \ for (typeof(*(l)) *s, *i = (l); (s = i) && *i; i++) #define STRV_FOREACH(s, l) \ _STRV_FOREACH(s, l, UNIQ_T(i, UNIQ)) + +static inline bool strv_isempty(sd_char * const *l) { + return !l || !*l; +} From 3d4e3c1a5e3a4a8c0fa531d205763de58e31bcab Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 26 Mar 2026 23:43:12 +0100 Subject: [PATCH 0547/1296] boot: properly track internal menu entries When showing the list of menu entries via "p", the "internal call:" field was showing nonsense, since fb6cf4bbb75baee8a6988d899de2c6b3e3805e31. Fix that by adding a proper entry type for "internal" menu items such as reboot/firmware/poweroff, and then check for that. With this in place all entries now have a loader type that makes sense and describes precisely what an entry is about. --- src/boot/boot.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/boot/boot.c b/src/boot/boot.c index 237e78c5a9360..1be098c287d16 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -61,6 +61,9 @@ typedef enum LoaderType { LOADER_SECURE_BOOT_KEYS, LOADER_BAD, /* Marker: this boot loader spec type #1 entry is invalid */ LOADER_IGNORE, /* Marker: this boot loader spec type #1 entry does not match local host */ + LOADER_REBOOT, + LOADER_POWEROFF, + LOADER_FWSETUP, _LOADER_TYPE_MAX, } LoaderType; @@ -82,6 +85,9 @@ typedef enum LoaderType { /* Whether to persistently save the selected entry in an EFI variable, if that's requested. */ #define LOADER_TYPE_SAVE_ENTRY(t) IN_SET(t, LOADER_AUTO, LOADER_EFI, LOADER_LINUX, LOADER_UKI, LOADER_UKI_URL, LOADER_TYPE2_UKI) +/* Whether this item is implemented fully inside of systemd-boot */ +#define LOADER_TYPE_IS_INTERNAL(t) IN_SET(t, LOADER_SECURE_BOOT_KEYS, LOADER_REBOOT, LOADER_POWEROFF, LOADER_FWSETUP) + typedef enum { REBOOT_NO, REBOOT_YES, @@ -419,7 +425,7 @@ static void print_status(Config *config, char16_t *loaded_image_path) { printf(" options: %ls\n", entry->options); if (entry->profile > 0) printf(" profile: %u\n", entry->profile); - printf(" internal call: %ls\n", yes_no(!!entry->call)); + printf(" internal call: %ls\n", yes_no(LOADER_TYPE_IS_INTERNAL(entry->type))); printf("counting boots: %ls\n", yes_no(entry->tries_left >= 0)); if (entry->tries_left >= 0) { @@ -3047,6 +3053,7 @@ static void config_add_system_entries(Config *config) { if (config->auto_firmware && FLAGS_SET(get_os_indications_supported(), EFI_OS_INDICATIONS_BOOT_TO_FW_UI)) { BootEntry *entry = xnew(BootEntry, 1); *entry = (BootEntry) { + .type = LOADER_FWSETUP, .id = xstrdup16(u"auto-reboot-to-firmware-setup"), .title = xstrdup16(u"Reboot Into Firmware Interface"), .call = call_reboot_into_firmware, @@ -3059,6 +3066,7 @@ static void config_add_system_entries(Config *config) { if (config->auto_poweroff) { BootEntry *entry = xnew(BootEntry, 1); *entry = (BootEntry) { + .type = LOADER_POWEROFF, .id = xstrdup16(u"auto-poweroff"), .title = xstrdup16(u"Power Off The System"), .call = call_poweroff_system, @@ -3071,6 +3079,7 @@ static void config_add_system_entries(Config *config) { if (config->auto_reboot) { BootEntry *entry = xnew(BootEntry, 1); *entry = (BootEntry) { + .type = LOADER_REBOOT, .id = xstrdup16(u"auto-reboot"), .title = xstrdup16(u"Reboot The System"), .call = call_reboot_system, From 208cc69c5005a7c9edec96ca5109c226126106a6 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 26 Mar 2026 23:43:32 +0100 Subject: [PATCH 0548/1296] boot: do no show pixel width/height in text mode When running in pure text mode (i.e. serial terminal) the pixel width/height is zero and makes no sense to report. Suppress it. --- src/boot/boot.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/boot/boot.c b/src/boot/boot.c index 1be098c287d16..4a5102f45c7e4 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -325,9 +325,13 @@ static void print_status(Config *config, char16_t *loaded_image_path) { secure_boot_mode_to_string(secure)); printf(" shim: %ls\n", yes_no(shim_loaded())); printf(" TPM: %ls\n", yes_no(tpm_present())); - printf(" console mode: %i/%" PRIi64 " (%zux%zu @%ux%u)\n", - ST->ConOut->Mode->Mode, ST->ConOut->Mode->MaxMode - INT64_C(1), - x_max, y_max, screen_width, screen_height); + printf(" console mode: %i/%" PRIi64 " (%zux%zu", + ST->ConOut->Mode->Mode, ST->ConOut->Mode->MaxMode - INT64_C(1), + x_max, y_max); + if (screen_width > 0 && screen_height > 0) + printf(" @ %ux%u", + screen_width, screen_height); + printf(")\n"); if (!ps_continue()) return; From 6a4a4f0302e78fda9ff2cfb7ac6a5644aabc4fc2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 26 Mar 2026 23:44:59 +0100 Subject: [PATCH 0549/1296] bootspec: honour profile number when sorting properly This corrects sorting of menu entries regarding profile numbers: 1. If the profile number is unset, let's treat this identical to profile 0, when ordering stuff, because an item with no profile is conceptually the same as an item with only a profile 0. 2. Let's take the profile number into account also if sort keys are used. This was makes profiles work sensibly in type 1 entries, via the recently added "profile" stanza. Follow-up for: 5fb90fa3194d998a971b21e4a643670ae5903f85 --- src/boot/boot.c | 12 +++++++++++- src/shared/bootspec.c | 12 +++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/boot/boot.c b/src/boot/boot.c index 4a5102f45c7e4..904f9bf589457 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -1745,6 +1745,12 @@ static void config_load_smbios_entries( } } +static unsigned boot_entry_profile(const BootEntry *a) { + assert(a); + + return a->profile == UINT_MAX ? 0 : a->profile; +} + static int boot_entry_compare(const BootEntry *a, const BootEntry *b) { int r; @@ -1778,6 +1784,10 @@ static int boot_entry_compare(const BootEntry *a, const BootEntry *b) { r = -strverscmp_improved(a->version, b->version); if (r != 0) return r; + + r = CMP(boot_entry_profile(a), boot_entry_profile(b)); + if (r != 0) + return r; } /* Now order by ID. The version is likely part of the ID, thus note that this will generatelly put @@ -1792,7 +1802,7 @@ static int boot_entry_compare(const BootEntry *a, const BootEntry *b) { /* Note: the strverscmp_improved() call above checked for us that we are looking at the very * same id, hence at this point we only need to compare profile numbers, since we know they * belong to the same UKI. */ - r = CMP(a->profile, b->profile); + r = CMP(boot_entry_profile(a), boot_entry_profile(b)); if (r != 0) return r; } diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index 2a898067f81ac..36eb2e7086e81 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -555,6 +555,12 @@ static int boot_loader_read_conf_path(BootConfig *config, const char *root, cons return boot_loader_read_conf(config, f, full); } +static unsigned boot_entry_profile(const BootEntry *a) { + assert(a); + + return a->profile == UINT_MAX ? 0 : a->profile; +} + static int boot_entry_compare(const BootEntry *a, const BootEntry *b) { int r; @@ -583,6 +589,10 @@ static int boot_entry_compare(const BootEntry *a, const BootEntry *b) { r = -strverscmp_improved(a->version, b->version); if (r != 0) return r; + + r = CMP(boot_entry_profile(a), boot_entry_profile(b)); + if (r != 0) + return r; } r = -strverscmp_improved(a->id_without_profile ?: a->id, b->id_without_profile ?: b->id); @@ -592,7 +602,7 @@ static int boot_entry_compare(const BootEntry *a, const BootEntry *b) { if (a->id_without_profile && b->id_without_profile) { /* The strverscmp_improved() call above already established that we are talking about the * same image here, hence order by profile, if there is one */ - r = CMP(a->profile, b->profile); + r = CMP(boot_entry_profile(a), boot_entry_profile(b)); if (r != 0) return r; } From bf2d68433de56f679bfed031023ebdcd6797e034 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 27 Mar 2026 10:44:03 +0100 Subject: [PATCH 0550/1296] coccinelle: generalize pidref_is_set() to `=~ _is_set()` Our coccinelle/check-pointer-deref.cocci checker has a special case for `assert(pidref_is_set(param))`. It turns out we can generalize this and catch the following: - iovec_is_set - sd_dhcp_duid_is_set - sd_dhcp_client_id_is_set --- coccinelle/check-pointer-deref.cocci | 11 +++++++---- src/boot/initrd.c | 1 - 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/coccinelle/check-pointer-deref.cocci b/coccinelle/check-pointer-deref.cocci index e2376a314c5a1..dd058fae3bfcb 100644 --- a/coccinelle/check-pointer-deref.cocci +++ b/coccinelle/check-pointer-deref.cocci @@ -13,6 +13,7 @@ */ @@ identifier fn, param; +identifier is_set =~ "_is_set$"; type T; position p; @@ @@ -25,10 +26,12 @@ fn(..., T *param, ...) { when != assert_return(param, ...) when != ASSERT_PTR(param) when != POINTER_MAY_BE_NULL(param) - /* NULL-safe helpers used commonly enough in assert() to warrant inclusion - * here. For less common cases, use POINTER_MAY_BE_NULL(param) instead of - * extending this list. */ - when != assert(pidref_is_set(param)) + /* Any foo_is_set(param) guard implies param != NULL, since all *_is_set() + * helpers in systemd return false for NULL input. Note the is_set regex + * in identifier. */ + when != assert(is_set(param)) + when != assert_return(is_set(param), ...) + when != \( is_set(param) \) when != \( param == NULL \| param != NULL \| !param \) * *param@p ... diff --git a/src/boot/initrd.c b/src/boot/initrd.c index b8086ac633759..d8cbe7deed425 100644 --- a/src/boot/initrd.c +++ b/src/boot/initrd.c @@ -74,7 +74,6 @@ EFI_STATUS initrd_register( EFI_HANDLE handle; struct initrd_loader *loader; - POINTER_MAY_BE_NULL(initrd); assert(ret_initrd_handle); /* If no initrd is specified we'll not install any. This avoids registration of the protocol for that From cf91fef57f37000ad4dba7130e688267f97da931 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 27 Mar 2026 11:20:40 +0100 Subject: [PATCH 0551/1296] coccinelle: document why src/libc/ and src/test/ are excluded For some of the directories it makes more sense to keep them excluded from the coccinelle check. Specifically: - libc: compatibility, no asserts or systemd headers yet - test: uses NUL internally to test crashes etc --- meson.build | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index d820be416c15b..e096992088c4b 100644 --- a/meson.build +++ b/meson.build @@ -2981,7 +2981,6 @@ if spatch.found() 'src/core/', 'src/import/', 'src/journal/', - 'src/libc/', 'src/libsystemd/', 'src/libsystemd-network/', 'src/network/', @@ -2989,6 +2988,9 @@ if spatch.found() 'src/nss-systemd/', 'src/resolve/', 'src/shared/', + # libc/ has no assert() or systemd-headers so leave it + 'src/libc/', + # test/ has some deliberate wonky pointers, just leave excluded 'src/test/', ] From 5d82ecc0e3a3460e6b6a3850ef69260355e11c31 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 27 Mar 2026 11:20:01 +0100 Subject: [PATCH 0552/1296] libsystemd-network: make check-pointer-deref clean Add the needed assert changes to make the code clean for the new check-pointer-deref script. --- meson.build | 1 - src/libsystemd-network/dhcp6-option.c | 2 ++ src/libsystemd-network/lldp-neighbor.c | 1 + src/libsystemd-network/sd-dhcp-client.c | 3 +++ src/libsystemd-network/sd-dhcp-lease.c | 2 ++ src/libsystemd-network/sd-dhcp-server.c | 1 + src/libsystemd-network/sd-dhcp6-client.c | 3 +++ src/libsystemd-network/sd-lldp-tx.c | 1 + src/libsystemd-network/test-dhcp-option.c | 3 +++ src/libsystemd-network/test-lldp-rx.c | 2 ++ 10 files changed, 18 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index e096992088c4b..19647a4c7ddd8 100644 --- a/meson.build +++ b/meson.build @@ -2982,7 +2982,6 @@ if spatch.found() 'src/import/', 'src/journal/', 'src/libsystemd/', - 'src/libsystemd-network/', 'src/network/', 'src/nspawn/', 'src/nss-systemd/', diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index 751aed78a7f02..1508d89781350 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -250,6 +250,8 @@ int dhcp6_option_append( int r; + assert(buf); + assert(offset); assert(optval || optlen == 0); r = option_append_hdr(buf, offset, code, optlen); diff --git a/src/libsystemd-network/lldp-neighbor.c b/src/libsystemd-network/lldp-neighbor.c index 727e8feb3319f..487bd50182c32 100644 --- a/src/libsystemd-network/lldp-neighbor.c +++ b/src/libsystemd-network/lldp-neighbor.c @@ -408,6 +408,7 @@ static int format_mac_address(const void *data, size_t sz, char **ret) { char *k; assert(data || sz <= 0); + assert(ret); if (sz != 7) return 0; diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index f2a1b4ba3d64c..a02f8db7cb8ec 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -776,6 +776,9 @@ static usec_t client_compute_reacquisition_timeout(usec_t now_usec, usec_t expir } static int cmp_uint8(const uint8_t *a, const uint8_t *b) { + assert(a); + assert(b); + return CMP(*a, *b); } diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index efded5df01025..0623bc0e4bf4d 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -1266,6 +1266,8 @@ int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const vo int dhcp_lease_new(sd_dhcp_lease **ret) { sd_dhcp_lease *lease; + assert(ret); + lease = new0(sd_dhcp_lease, 1); if (!lease) return -ENOMEM; diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index d88480a3464c5..34ed3e10d33bc 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -1641,6 +1641,7 @@ int sd_dhcp_server_set_callback(sd_dhcp_server *server, sd_dhcp_server_callback_ int sd_dhcp_server_set_relay_target(sd_dhcp_server *server, const struct in_addr *address) { assert_return(server, -EINVAL); + assert_return(address, -EINVAL); assert_return(!sd_dhcp_server_is_running(server), -EBUSY); if (memcmp(address, &server->relay_target, sizeof(struct in_addr)) == 0) diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index 448a2e7557ee9..ee67664364c9f 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -379,6 +379,9 @@ int sd_dhcp6_client_get_information_request(sd_dhcp6_client *client, int *enable } static int be16_compare_func(const be16_t *a, const be16_t *b) { + assert(a); + assert(b); + return CMP(be16toh(*a), be16toh(*b)); } diff --git a/src/libsystemd-network/sd-lldp-tx.c b/src/libsystemd-network/sd-lldp-tx.c index 4097091002a31..59da447ef342c 100644 --- a/src/libsystemd-network/sd-lldp-tx.c +++ b/src/libsystemd-network/sd-lldp-tx.c @@ -157,6 +157,7 @@ int sd_lldp_tx_set_multicast_mode(sd_lldp_tx *lldp_tx, sd_lldp_multicast_mode_t int sd_lldp_tx_set_hwaddr(sd_lldp_tx *lldp_tx, const struct ether_addr *hwaddr) { assert_return(lldp_tx, -EINVAL); + assert_return(hwaddr, -EINVAL); assert_return(!ether_addr_is_null(hwaddr), -EINVAL); lldp_tx->hwaddr = *hwaddr; diff --git a/src/libsystemd-network/test-dhcp-option.c b/src/libsystemd-network/test-dhcp-option.c index 05572e0b21a28..31f25fb5be854 100644 --- a/src/libsystemd-network/test-dhcp-option.c +++ b/src/libsystemd-network/test-dhcp-option.c @@ -120,6 +120,9 @@ static DHCPMessage *create_message(uint8_t *options, uint16_t optlen, } static void test_ignore_opts(uint8_t *descoption, int *descpos, int *desclen) { + assert(descoption); + assert(descpos); + assert(desclen); assert_se(*descpos >= 0); while (*descpos < *desclen) { diff --git a/src/libsystemd-network/test-lldp-rx.c b/src/libsystemd-network/test-lldp-rx.c index 629093fe2e03b..09c916db5183b 100644 --- a/src/libsystemd-network/test-lldp-rx.c +++ b/src/libsystemd-network/test-lldp-rx.c @@ -35,6 +35,8 @@ static void lldp_rx_handler(sd_lldp_rx *lldp_rx, sd_lldp_rx_event_t event, sd_ll static int start_lldp_rx(sd_lldp_rx **lldp_rx, sd_event *e, sd_lldp_rx_callback_t cb, void *cb_data) { int r; + assert(lldp_rx); + r = sd_lldp_rx_new(lldp_rx); if (r < 0) return r; From 39ef41e3881d34bdbb3e87309aa4560c87aefa01 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 27 Mar 2026 11:20:10 +0100 Subject: [PATCH 0553/1296] resolved: make check-pointer-deref clean Add the needed assert changes to make the code clean for the new check-pointer-deref script. --- meson.build | 1 - src/resolve/resolved-dns-dnssec.c | 2 +- src/resolve/resolved-dns-stub.c | 2 ++ src/resolve/resolved-dnssd.c | 4 ++++ src/resolve/resolved-link.c | 1 + src/resolve/resolved-manager.c | 2 ++ src/resolve/test-dns-zone.c | 2 ++ 7 files changed, 12 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index 19647a4c7ddd8..4f0ea8adbf1e7 100644 --- a/meson.build +++ b/meson.build @@ -2985,7 +2985,6 @@ if spatch.found() 'src/network/', 'src/nspawn/', 'src/nss-systemd/', - 'src/resolve/', 'src/shared/', # libc/ has no assert() or systemd-headers so leave it 'src/libc/', diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 39b679ab04072..c82569ccf9f19 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -48,7 +48,7 @@ REENABLE_WARNING; #if HAVE_OPENSSL static int rr_compare(DnsResourceRecord * const *a, DnsResourceRecord * const *b) { - const DnsResourceRecord *x = *a, *y = *b; + const DnsResourceRecord *x = *ASSERT_PTR(a), *y = *ASSERT_PTR(b); size_t m; int r; diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c index 96f7c62670e05..298db3ae78faa 100644 --- a/src/resolve/resolved-dns-stub.c +++ b/src/resolve/resolved-dns-stub.c @@ -81,6 +81,8 @@ int dns_stub_listener_extra_new( Manager *m, DnsStubListenerExtra **ret) { + assert(ret); + DnsStubListenerExtra *l; l = new(DnsStubListenerExtra, 1); diff --git a/src/resolve/resolved-dnssd.c b/src/resolve/resolved-dnssd.c index 6cc0f86796a52..498f975f39ee4 100644 --- a/src/resolve/resolved-dnssd.c +++ b/src/resolve/resolved-dnssd.c @@ -332,6 +332,8 @@ int dnssd_txt_item_new_from_string(const char *key, const char *value, DnsTxtIte size_t length; DnsTxtItem *i; + assert(ret_item); + length = strlen(key); if (!isempty(value)) @@ -357,6 +359,8 @@ int dnssd_txt_item_new_from_data(const char *key, const void *data, const size_t size_t length; DnsTxtItem *i; + assert(ret_item); + length = strlen(key); if (size > 0) diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index 59b6dc942b98e..ea089c1e100c7 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -890,6 +890,7 @@ int link_address_new(Link *l, assert(l); assert(in_addr); + assert(in_addr_broadcast); a = new(LinkAddress, 1); if (!a) diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index e96ae4393c682..f16364fe6a480 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -1464,6 +1464,8 @@ static int manager_next_random_name(const char *old, char **ret_new) { uint64_t u, a; char *n; + assert(ret_new); + p = strchr(old, 0); assert(p); diff --git a/src/resolve/test-dns-zone.c b/src/resolve/test-dns-zone.c index b9ee18fd22af0..4cdb98aee5202 100644 --- a/src/resolve/test-dns-zone.c +++ b/src/resolve/test-dns-zone.c @@ -10,6 +10,8 @@ #include "tests.h" static void dns_scope_freep(DnsScope **s) { + POINTER_MAY_BE_NULL(s); + if (s != NULL && *s != NULL) dns_scope_free(*s); } From f124464b45f242b302b5b6b8dbf41537363f9f4f Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 27 Mar 2026 11:20:20 +0100 Subject: [PATCH 0554/1296] nss-systemd: make check-pointer-deref clean Add the needed assert changes to make the code clean for the new check-pointer-deref script. --- meson.build | 1 - src/nss-systemd/nss-systemd.c | 4 ++++ src/nss-systemd/userdb-glue.c | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 4f0ea8adbf1e7..e6169ad052c2a 100644 --- a/meson.build +++ b/meson.build @@ -2984,7 +2984,6 @@ if spatch.found() 'src/libsystemd/', 'src/network/', 'src/nspawn/', - 'src/nss-systemd/', 'src/shared/', # libc/ has no assert() or systemd-headers so leave it 'src/libc/', diff --git a/src/nss-systemd/nss-systemd.c b/src/nss-systemd/nss-systemd.c index 6ed97f31a68f9..0689175a53c11 100644 --- a/src/nss-systemd/nss-systemd.c +++ b/src/nss-systemd/nss-systemd.c @@ -149,6 +149,7 @@ static enum nss_status copy_synthesized_passwd( assert(dest); assert(src); + assert(errnop); assert(src->pw_name); assert(src->pw_passwd); assert(src->pw_gecos); @@ -191,6 +192,7 @@ static enum nss_status copy_synthesized_spwd( assert(dest); assert(src); + assert(errnop); assert(src->sp_namp); assert(src->sp_pwdp); @@ -223,6 +225,7 @@ static enum nss_status copy_synthesized_group( assert(dest); assert(src); + assert(errnop); assert(src->gr_name); assert(src->gr_passwd); assert(src->gr_mem); @@ -259,6 +262,7 @@ static enum nss_status copy_synthesized_sgrp( assert(dest); assert(src); + assert(errnop); assert(src->sg_namp); assert(src->sg_passwd); assert(src->sg_adm); diff --git a/src/nss-systemd/userdb-glue.c b/src/nss-systemd/userdb-glue.c index 1d5e311ce8653..6f1bf1e2af5c3 100644 --- a/src/nss-systemd/userdb-glue.c +++ b/src/nss-systemd/userdb-glue.c @@ -415,6 +415,9 @@ enum nss_status userdb_getgrgid( * string vector strv and stores amount of pointers in n and total * length of all contained strings including NUL bytes in len. */ static void nss_count_strv(char * const *strv, size_t *n, size_t *len) { + assert(n); + assert(len); + STRV_FOREACH(str, strv) { (*len) += sizeof(char*); /* space for array entry */ (*len) += strlen(*str) + 1; From 78ab70a46ff75f5761f1fcf16c701ebb7acc637e Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 27 Mar 2026 01:04:25 +0000 Subject: [PATCH 0555/1296] boot: avoid division by zero in splash image handling A malformed image can cause a division by zero, check that the parameters are not zero. Reported on yeswehackl.com as YWH-PGM9780-173 Follow-up for 0fa2cac4f0cdefaf1addd7f1fe0fd8113db9360b --- src/boot/splash.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/boot/splash.c b/src/boot/splash.c index 19bd4bff74a51..e3365b04df07f 100644 --- a/src/boot/splash.c +++ b/src/boot/splash.c @@ -133,7 +133,7 @@ static EFI_STATUS bmp_parse_header( } enum Channels { R, G, B, A, _CHANNELS_MAX }; -static void read_channel_mask( +static EFI_STATUS read_channel_mask( const struct bmp_dib *dib, uint32_t channel_mask[static _CHANNELS_MAX], uint8_t channel_shift[static _CHANNELS_MAX], @@ -142,6 +142,9 @@ static void read_channel_mask( assert(dib); if (IN_SET(dib->depth, 16, 32) && dib->size >= SIZEOF_BMP_DIB_RGB) { + if (dib->channel_mask_r == 0 || dib->channel_mask_g == 0 || dib->channel_mask_b == 0) + return EFI_INVALID_PARAMETER; + channel_mask[R] = dib->channel_mask_r; channel_mask[G] = dib->channel_mask_g; channel_mask[B] = dib->channel_mask_b; @@ -176,6 +179,8 @@ static void read_channel_mask( channel_scale[B] = bpp16 ? 0x08 : 0x1; channel_scale[A] = bpp16 ? 0x00 : 0x0; } + + return EFI_SUCCESS; } static EFI_STATUS bmp_to_blt( @@ -193,7 +198,10 @@ static EFI_STATUS bmp_to_blt( uint32_t channel_mask[_CHANNELS_MAX]; uint8_t channel_shift[_CHANNELS_MAX], channel_scale[_CHANNELS_MAX]; - read_channel_mask(dib, channel_mask, channel_shift, channel_scale); + + EFI_STATUS status = read_channel_mask(dib, channel_mask, channel_shift, channel_scale); + if (status != EFI_SUCCESS) + return status; /* transform and copy pixels */ in = pixmap; From 44b2d1e1557fdded45b57ac0d431d1ea8d4cdbe1 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 27 Mar 2026 11:30:32 +0100 Subject: [PATCH 0556/1296] import: make check-pointer-deref clean Add the needed assert changes to make the code clean for the new check-pointer-deref script. --- meson.build | 1 - src/import/curl-util.c | 2 ++ src/import/import-compress.c | 4 ++++ src/import/pull-common.c | 1 + src/import/qcow2-util.c | 4 ++++ 5 files changed, 11 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index e6169ad052c2a..5c02ab8664063 100644 --- a/meson.build +++ b/meson.build @@ -2979,7 +2979,6 @@ if spatch.found() coccinelle_exclude = [ 'src/basic/', 'src/core/', - 'src/import/', 'src/journal/', 'src/libsystemd/', 'src/network/', diff --git a/src/import/curl-util.c b/src/import/curl-util.c index 4747d0993a8c3..bddc93d52b80d 100644 --- a/src/import/curl-util.c +++ b/src/import/curl-util.c @@ -359,6 +359,8 @@ int curl_header_strdup(const void *contents, size_t sz, const char *field, char const char *p; char *s; + assert(value); + p = memory_startswith_no_case(contents, sz, field); if (!p) return 0; diff --git a/src/import/import-compress.c b/src/import/import-compress.c index f893abc43e648..aca4041f2a416 100644 --- a/src/import/import-compress.c +++ b/src/import/import-compress.c @@ -318,6 +318,10 @@ static int enlarge_buffer(void **buffer, size_t *buffer_size, size_t *buffer_all size_t l; void *p; + assert(buffer); + assert(buffer_size); + assert(buffer_allocated); + if (*buffer_allocated > *buffer_size) return 0; diff --git a/src/import/pull-common.c b/src/import/pull-common.c index cc06fe4f1db0a..c0e0e9907e6b1 100644 --- a/src/import/pull-common.c +++ b/src/import/pull-common.c @@ -622,6 +622,7 @@ int pull_job_restart_with_sha256sum(PullJob *j, char **ret) { int r; assert(j); + assert(ret); /* Generic implementation of a PullJobNotFound handler, that restarts the job requesting SHA256SUMS */ diff --git a/src/import/qcow2-util.c b/src/import/qcow2-util.c index 77298bcbe2979..dd5c3c23ecb42 100644 --- a/src/import/qcow2-util.c +++ b/src/import/qcow2-util.c @@ -150,6 +150,10 @@ static int normalize_offset( bool *compressed, uint64_t *compressed_size) { + assert(ret); + POINTER_MAY_BE_NULL(compressed); + POINTER_MAY_BE_NULL(compressed_size); + uint64_t q; q = be64toh(p); From a8b9ec68c3974a9566f85e1f800ffce47e86684a Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 26 Mar 2026 23:36:25 +0000 Subject: [PATCH 0557/1296] mkosi: update debian commit reference to 23ef56be0050f78be704f288ed1ce30ace47cbfe * 23ef56be00 Install new files for upstream build * 98645a89ba Install new files for upstream build * dc2dd78cc0 Install new files for upstream build * aad316ec34 Drop wildcards, dh_exec does not suppor them for manpages * 3bf8703dab Install new files for upstream build * d1e92a6493 Update changelog for 260.1-1 release * e7a80fe2b8 Install basic.conf in sd-standalone-sysusers package * 48f796240e Add lpadmin group to basic.conf sysusers.d as requested by CUPS maintainer * c15703b8aa Update changelog for 260-1 release * f26cc52a43 Drop version from libselinux-dev dependency * 7f3701ae2f Do not run "systemctl enable getty@.service" unconditionally * ec59ddd832 Switch from libselinux1-dev to libselinux-dev * 35258cd599 Update changelog for 260~rc4-1 release * eb194c22ff Update changelog for 260~rc3-1 release * a6878815d6 Really enable getty@ via packaging scriptlets --- mkosi/mkosi.pkgenv/mkosi.conf.d/debian-ubuntu.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkosi/mkosi.pkgenv/mkosi.conf.d/debian-ubuntu.conf b/mkosi/mkosi.pkgenv/mkosi.conf.d/debian-ubuntu.conf index 173945be111fe..97607f9b59862 100644 --- a/mkosi/mkosi.pkgenv/mkosi.conf.d/debian-ubuntu.conf +++ b/mkosi/mkosi.pkgenv/mkosi.conf.d/debian-ubuntu.conf @@ -9,5 +9,5 @@ Environment= GIT_URL=https://salsa.debian.org/systemd-team/systemd.git GIT_SUBDIR=debian GIT_BRANCH=debian/master - GIT_COMMIT=56e0eed69a4782eb8e110650d93daebcf1ece49a + GIT_COMMIT=23ef56be0050f78be704f288ed1ce30ace47cbfe PKG_SUBDIR=debian From 90cc5b0159619fee309f5ed354506f86c7e43fee Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Thu, 26 Mar 2026 11:59:45 +0100 Subject: [PATCH 0558/1296] machine: introduce MACHINE_CLASS_CAN_REGISTER Follow-up for 6df5f80bd374be1b45c52d740e88f0236da922c7 Similar to SESSION_CAN_* macros in logind-session.h --- src/machine/machine-varlink.c | 2 +- src/machine/machine.h | 2 ++ src/machine/machined-dbus.c | 5 +++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c index 73edd781b58b5..40e9136b2705f 100644 --- a/src/machine/machine-varlink.c +++ b/src/machine/machine-varlink.c @@ -155,7 +155,7 @@ int vl_method_register(sd_varlink *link, sd_json_variant *parameters, sd_varlink if (r != 0) return r; - if (!IN_SET(machine->class, MACHINE_CONTAINER, MACHINE_VM)) + if (!MACHINE_CLASS_CAN_REGISTER(machine->class)) return sd_varlink_error_invalid_parameter_name(link, "class"); if (manager->runtime_scope != RUNTIME_SCOPE_USER) { diff --git a/src/machine/machine.h b/src/machine/machine.h index 7941eb365c15c..899218f48d567 100644 --- a/src/machine/machine.h +++ b/src/machine/machine.h @@ -25,6 +25,8 @@ typedef enum MachineClass { _MACHINE_CLASS_INVALID = -EINVAL, } MachineClass; +#define MACHINE_CLASS_CAN_REGISTER(class) IN_SET((class), MACHINE_CONTAINER, MACHINE_VM) + typedef enum KillWhom { KILL_LEADER, KILL_SUPERVISOR, diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index 87f0c15ee13d0..4e39594a44dfe 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -256,6 +256,7 @@ static int machine_add_from_params( assert(manager); assert(message); assert(name); + assert(c == _MACHINE_CLASS_INVALID || MACHINE_CLASS_CAN_REGISTER(c)); assert(ret); if (leader_pidref->pid == 1) @@ -433,7 +434,7 @@ static int method_create_or_register_machine( c = _MACHINE_CLASS_INVALID; else { c = machine_class_from_string(class); - if (c < 0 || !IN_SET(c, MACHINE_CONTAINER, MACHINE_VM)) + if (c < 0 || !MACHINE_CLASS_CAN_REGISTER(c)) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine class parameter"); } @@ -608,7 +609,7 @@ static int method_create_or_register_machine_ex( c = _MACHINE_CLASS_INVALID; else { c = machine_class_from_string(class); - if (c < 0 || !IN_SET(c, MACHINE_CONTAINER, MACHINE_VM)) + if (c < 0 || !MACHINE_CLASS_CAN_REGISTER(c)) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine class parameter"); } From 771800a403c7e21c88fff917321fd3f290bdc80c Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Thu, 26 Mar 2026 12:16:36 +0100 Subject: [PATCH 0559/1296] machine: never ever allow non-root-owned host machine We really should lock this down _hard_, as evidenced by recent security fallouts. --- src/machine/machine-dbus.c | 17 +++++++++-------- src/machine/machine-varlink.c | 11 ++++++----- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index d567cd6d503f7..2dab827d41c51 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -366,14 +366,15 @@ int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bu return r; user = isempty(user) ? "root" : user; - /* Ensure only root can shell into the root namespace, unless it's specifically the host machine, - * which is owned by uid 0 anyway and cannot be self-registered. This is to avoid unprivileged - * users registering a process they own in the root user namespace, and then shelling in as root - * or another user. Note that the shell operation is privileged and requires 'auth_admin', so we - * do not need to check the caller's uid, as that will be checked by polkit, and if they machine's - * and the caller's do not match, authorization will be required. It's only the case where the - * caller owns the machine that will be shortcut and needs to be checked here. */ - if (m->manager->runtime_scope != RUNTIME_SCOPE_USER && m->uid != 0 && m->class != MACHINE_HOST) { + /* Ensure only root can shell into the root namespace. This is to avoid unprivileged users registering + * a process they own in the root user namespace, and then shelling in as root or another user. Note that + * the shell operation is privileged and requires 'auth_admin', so we do not need to check the caller's uid, + * as that will be checked by polkit, and if the machine's and the caller's do not match, authorization + * will be required. It's only the case where the caller owns the machine that will be shortcut and needs + * to be checked here. */ + if (m->manager->runtime_scope != RUNTIME_SCOPE_USER && m->uid != 0) { + assert(m->class != MACHINE_HOST); + r = pidref_in_same_namespace(&PIDREF_MAKE_FROM_PID(1), &m->leader, NAMESPACE_USER); if (r < 0) return log_debug_errno( diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c index 40e9136b2705f..dfc7020fc9583 100644 --- a/src/machine/machine-varlink.c +++ b/src/machine/machine-varlink.c @@ -555,14 +555,15 @@ int vl_method_open(sd_varlink *link, sd_json_variant *parameters, sd_varlink_met return r; if (manager->runtime_scope != RUNTIME_SCOPE_USER) { - /* Ensure only root can shell into the root namespace, unless it's specifically the host machine, - * which is owned by uid 0 anyway and cannot be self-registered. This is to avoid unprivileged - * users registering a process they own in the root user namespace, and then shelling in as root + /* Ensure only root can shell into the root namespace. This is to avoid unprivileged users + * registering a process they own in the root user namespace, and then shelling in as root * or another user. Note that the shell operation is privileged and requires 'auth_admin', so we - * do not need to check the caller's uid, as that will be checked by polkit, and if they machine's + * do not need to check the caller's uid, as that will be checked by polkit, and if the machine's * and the caller's do not match, authorization will be required. It's only the case where the * caller owns the machine that will be shortcut and needs to be checked here. */ - if (machine->uid != 0 && machine->class != MACHINE_HOST) { + if (machine->uid != 0) { + assert(machine->class != MACHINE_HOST); + r = pidref_in_same_namespace(&PIDREF_MAKE_FROM_PID(1), &machine->leader, NAMESPACE_USER); if (r < 0) return log_debug_errno( From b34ff170d1e32f681dbf8b5d9a1a06092032836e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 26 Mar 2026 18:11:30 +0100 Subject: [PATCH 0560/1296] tmpfile-util: don't log about lack of O_TMPFILE support It's a very common case (vfat...), and it's just too much noise. After all the whole function exists primarily to deal with O_TMPFILE not being availeble everywhere... --- src/basic/tmpfile-util.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/basic/tmpfile-util.c b/src/basic/tmpfile-util.c index be7a930c44c18..9c2dc33f065d7 100644 --- a/src/basic/tmpfile-util.c +++ b/src/basic/tmpfile-util.c @@ -294,7 +294,8 @@ int open_tmpfile_linkable_at(int dir_fd, const char *target, int flags, char **r return fd; } - log_debug_errno(fd, "Failed to use O_TMPFILE for %s: %m", target); + if (!ERRNO_IS_NEG_NOT_SUPPORTED(fd)) + log_debug_errno(fd, "Failed to use O_TMPFILE for %s: %m", target); _cleanup_free_ char *tmp = NULL; r = tempfn_random(target, NULL, &tmp); From 36338926af32b99c095ac6515b50fc227f43c8c7 Mon Sep 17 00:00:00 2001 From: Pavel Borecki Date: Fri, 27 Mar 2026 12:58:47 +0000 Subject: [PATCH 0561/1296] po: Translated using Weblate (Czech) Currently translated at 100.0% (266 of 266 strings) Co-authored-by: Pavel Borecki Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/cs/ Translation: systemd/main --- po/cs.po | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/po/cs.po b/po/cs.po index e12f262c5f907..5023b457b3db6 100644 --- a/po/cs.po +++ b/po/cs.po @@ -3,14 +3,14 @@ # Czech translation for systemd. # # Daniel Rusek , 2022, 2023, 2025. -# Pavel Borecki , 2023, 2024, 2025. +# Pavel Borecki , 2023, 2024, 2025, 2026. # Jan Kalabza , 2025. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2025-12-05 15:00+0000\n" -"Last-Translator: Daniel Rusek \n" +"PO-Revision-Date: 2026-03-27 12:58+0000\n" +"Last-Translator: Pavel Borecki \n" "Language-Team: Czech \n" "Language: cs\n" @@ -19,7 +19,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2);\n" -"X-Generator: Weblate 5.14.3\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1013,12 +1013,12 @@ msgid "DHCP server sends force renew message" msgstr "DHCP server posílá zprávu vynuceného obnovení" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy -#| msgid "Authentication is required to send force renew message." msgid "" "Authentication is required to send a force renew message from the DHCP " "server." -msgstr "Pro poslání zprávy vynuceného obnovení je vyžadováno ověření." +msgstr "" +"Pro poslání zprávy z DHCP serveru o vynuceného obnovení je vyžadováno " +"ověření." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" @@ -1058,11 +1058,11 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Spravovat síťové linky" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" +msgstr "Pro správu síťových linek je zapotřebí ověření se." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" From 5a7d70e807176b1d81686835408a2684961c9ae9 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 27 Mar 2026 10:43:26 +0000 Subject: [PATCH 0562/1296] vmspawn: add --efi-nvram-template= and --firmware-features= options Add --efi-nvram-template=PATH to specify a custom firmware variables file to copy and use as the initial EFI NVRAM state instead of the default template from the firmware definition. Add --firmware-features=FEATURE[,FEATURE...] to require or exclude specific firmware features during automatic firmware discovery. Features prefixed with "!" are excluded. If a feature appears in both the included and excluded lists, inclusion takes priority. Firmware with the "enrolled-keys" feature is excluded by default. Refactor --secure-boot= to operate on the firmware features sets instead of maintaining a separate tristate. --secure-boot=yes adds "secure-boot" to the include set, --secure-boot=no adds it to the exclude set, and --secure-boot=auto removes it from both. Generalize find_ovmf_config() to accept include/exclude feature sets instead of a secure boot tristate, removing the special-cased enrolled-keys and secure-boot filtering logic. Co-developed-by: Claude Opus 4.6 --- man/systemd-vmspawn.xml | 37 +++++++-- shell-completion/bash/systemd-vmspawn | 10 ++- src/vmspawn/vmspawn-util.c | 76 ++++++++++++++--- src/vmspawn/vmspawn-util.h | 3 +- src/vmspawn/vmspawn.c | 112 ++++++++++++++++++++++---- 5 files changed, 203 insertions(+), 35 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 79ae2274d7ccb..72dbcb15d9d0f 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -238,6 +238,17 @@ + + + + Takes an absolute path, or a relative path beginning with + ./. Specifies the path to an EFI NVRAM template file to copy and use as the + initial EFI variable NVRAM state. If not specified, the default NVRAM template from the firmware + definition is copied and used. + + + + @@ -324,6 +335,22 @@ + + + + Takes a comma-delimited list of firmware feature strings. This option may be + specified multiple times, in which case the feature lists are combined. When specified, only + firmware definitions that have all the required features will be considered during automatic + firmware discovery. Features prefixed with ! are excluded: firmware that has + such a feature will be skipped. If a feature appears in both the included and excluded lists, + inclusion takes priority. By default, firmware with the enrolled-keys + feature is excluded. If an empty string is passed, both the included and excluded feature lists + are reset. If the special string list is specified, lists all available + firmware features. + + + + @@ -337,11 +364,11 @@ - Configure whether to search for firmware which supports Secure Boot. - - If the option is not specified or set to , the first firmware detected - will be used. If the option is set to yes, then the first firmware with Secure Boot support will - be selected. If no is specified, then the first firmware without Secure Boot will be selected. + Configure whether to search for firmware which supports Secure Boot. Takes a + boolean or auto. Setting this to yes is equivalent to + and setting this to no is equivalent to + . Setting this to auto + removes secure-boot from both the included and excluded feature lists. diff --git a/shell-completion/bash/systemd-vmspawn b/shell-completion/bash/systemd-vmspawn index 0e2e46a84c6b7..a6ce9708abe87 100644 --- a/shell-completion/bash/systemd-vmspawn +++ b/shell-completion/bash/systemd-vmspawn @@ -30,9 +30,11 @@ _systemd_vmspawn() { local -A OPTS=( [STANDALONE]='-h --help --version -q --quiet --no-pager -n --network-tap --network-user-mode --user --system -x --ephemeral' - [PATH]='-D --directory -i --image --linux --initrd --extra-drive --forward-journal' - [BOOL]='--kvm --vsock --tpm --secure-boot --discard-disk --register --pass-ssh-key' + [PATH]='-D --directory -i --image --linux --initrd --extra-drive --forward-journal --efi-nvram-template' + [BOOL]='--kvm --vsock --tpm --discard-disk --register --pass-ssh-key' + [SECURE_BOOT]='--secure-boot' [FIRMWARE]='--firmware' + [FIRMWARE_FEATURES]='--firmware-features' [BIND]='--bind --bind-ro' [SSH_KEY]='--ssh-key' [CONSOLE]='--console' @@ -45,12 +47,16 @@ _systemd_vmspawn() { if __contains_word "$prev" ${OPTS[BOOL]}; then comps='yes no' + elif __contains_word "$prev" ${OPTS[SECURE_BOOT]}; then + comps='yes no auto' elif __contains_word "$prev" ${OPTS[PATH]}; then compopt -o nospace -o filenames comps=$(compgen -f -- "$cur" ) elif __contains_word "$prev" ${OPTS[FIRMWARE]}; then compopt -o nospace -o filenames comps="list $(compgen -f -- "$cur" )" + elif __contains_word "$prev" ${OPTS[FIRMWARE_FEATURES]}; then + comps='list' elif __contains_word "$prev" ${OPTS[BIND]}; then compopt -o nospace -o filenames comps=$(compgen -f -- "${cur}" ) diff --git a/src/vmspawn/vmspawn-util.c b/src/vmspawn/vmspawn-util.c index c6e258c50af87..b8e2c09c830c2 100644 --- a/src/vmspawn/vmspawn-util.c +++ b/src/vmspawn/vmspawn-util.c @@ -18,6 +18,7 @@ #include "path-lookup.h" #include "path-util.h" #include "random-util.h" +#include "set.h" #include "siphash24.h" #include "string-table.h" #include "string-util.h" @@ -283,6 +284,41 @@ static int load_firmware_data(const char *path, FirmwareData **ret) { return 0; } +int list_ovmf_firmware_features(char ***ret) { + _cleanup_strv_free_ char **conf_files = NULL; + _cleanup_set_free_ Set *feature_set = NULL; + int r; + + assert(ret); + + r = list_ovmf_config(&conf_files); + if (r < 0) + return r; + + STRV_FOREACH(file, conf_files) { + _cleanup_(firmware_data_freep) FirmwareData *fwd = NULL; + + r = load_firmware_data(*file, &fwd); + if (r < 0) { + log_debug_errno(r, "Failed to load JSON file '%s', skipping: %m", *file); + continue; + } + + r = set_put_strdupv(&feature_set, fwd->features); + if (r < 0) + return log_oom_debug(); + } + + _cleanup_strv_free_ char **features = set_to_strv(&feature_set); + if (!features) + return log_oom_debug(); + + strv_sort(features); + + *ret = TAKE_PTR(features); + return 0; +} + static int ovmf_config_make(FirmwareData *fwd, OvmfConfig **ret) { assert(fwd); assert(ret); @@ -318,7 +354,7 @@ int load_ovmf_config(const char *path, OvmfConfig **ret) { return ovmf_config_make(fwd, ret); } -int find_ovmf_config(int search_sb, OvmfConfig **ret) { +int find_ovmf_config(Set *features_include, Set *features_exclude, OvmfConfig **ret) { _cleanup_(ovmf_config_freep) OvmfConfig *config = NULL; _cleanup_strv_free_ char **conf_files = NULL; const char* native_arch_qemu; @@ -351,20 +387,40 @@ int find_ovmf_config(int search_sb, OvmfConfig **ret) { continue; } - if (strv_contains(fwd->features, "enrolled-keys")) { - log_debug("Skipping %s, firmware has enrolled keys which has been known to cause issues.", *file); - continue; - } - if (!strv_contains(fwd->architectures, native_arch_qemu)) { log_debug("Skipping %s, firmware doesn't support the native architecture.", *file); continue; } - /* exclude firmware which doesn't match our Secure Boot requirements */ - if (search_sb >= 0 && !!search_sb != firmware_data_supports_sb(fwd)) { - log_debug("Skipping %s, firmware doesn't fit required Secure Boot configuration.", *file); - continue; + /* Skip firmware that doesn't have all required features */ + if (!set_isempty(features_include)) { + const char *feature; + bool skip = false; + + SET_FOREACH(feature, features_include) + if (!strv_contains(fwd->features, feature)) { + log_debug("Skipping %s, firmware is missing required feature '%s'.", *file, feature); + skip = true; + } + + if (skip) + continue; + } + + /* Skip firmware that has any excluded features (include wins over exclude) */ + if (!set_isempty(features_exclude)) { + const char *feature; + bool skip = false; + + SET_FOREACH(feature, features_exclude) + if (strv_contains(fwd->features, feature) && + !set_contains(features_include, feature)) { + log_debug("Skipping %s, firmware has excluded feature '%s'.", *file, feature); + skip = true; + } + + if (skip) + continue; } r = ovmf_config_make(fwd, &config); diff --git a/src/vmspawn/vmspawn-util.h b/src/vmspawn/vmspawn-util.h index 0d138c84c8c74..28c2df78a7014 100644 --- a/src/vmspawn/vmspawn-util.h +++ b/src/vmspawn/vmspawn-util.h @@ -87,8 +87,9 @@ DECLARE_STRING_TABLE_LOOKUP(network_stack, NetworkStack); int qemu_check_kvm_support(void); int qemu_check_vsock_support(void); int list_ovmf_config(char ***ret); +int list_ovmf_firmware_features(char ***ret); int load_ovmf_config(const char *path, OvmfConfig **ret); -int find_ovmf_config(int search_sb, OvmfConfig **ret); +int find_ovmf_config(Set *features_include, Set *features_exclude, OvmfConfig **ret); int find_qemu_binary(char **ret_qemu_binary); int vsock_fix_child_cid(int vhost_device_fd, unsigned *machine_cid, const char *machine); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 2e41e31d712d7..fbe4d2145bfe6 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -68,6 +68,7 @@ #include "ptyfwd.h" #include "random-util.h" #include "rm-rf.h" +#include "set.h" #include "sha256.h" #include "signal-util.h" #include "snapshot-util.h" @@ -133,11 +134,12 @@ static char *arg_linux = NULL; static char **arg_initrds = NULL; static ConsoleMode arg_console_mode = CONSOLE_INTERACTIVE; static NetworkStack arg_network_stack = NETWORK_STACK_NONE; -static int arg_secure_boot = -1; static MachineCredentialContext arg_credentials = {}; static uid_t arg_uid_shift = UID_INVALID, arg_uid_range = 0x10000U; static RuntimeMountContext arg_runtime_mounts = {}; static char *arg_firmware = NULL; +static Set *arg_firmware_features_include = NULL; +static Set *arg_firmware_features_exclude = NULL; static char *arg_forward_journal = NULL; static bool arg_register = true; static bool arg_keep_unit = false; @@ -154,6 +156,7 @@ static char **arg_smbios11 = NULL; static uint64_t arg_grow_image = 0; static char *arg_tpm_state_path = NULL; static StateMode arg_tpm_state_mode = STATE_AUTO; +static char *arg_efi_nvram_template = NULL; static char *arg_efi_nvram_state_path = NULL; static StateMode arg_efi_nvram_state_mode = STATE_AUTO; static bool arg_ask_password = true; @@ -172,6 +175,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_slice, freep); STATIC_DESTRUCTOR_REGISTER(arg_cpus, freep); STATIC_DESTRUCTOR_REGISTER(arg_credentials, machine_credential_context_done); STATIC_DESTRUCTOR_REGISTER(arg_firmware, freep); +STATIC_DESTRUCTOR_REGISTER(arg_firmware_features_include, set_freep); +STATIC_DESTRUCTOR_REGISTER(arg_firmware_features_exclude, set_freep); STATIC_DESTRUCTOR_REGISTER(arg_linux, freep); STATIC_DESTRUCTOR_REGISTER(arg_initrds, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_runtime_mounts, runtime_mount_context_done); @@ -182,6 +187,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_background, freep); STATIC_DESTRUCTOR_REGISTER(arg_ssh_key_type, freep); STATIC_DESTRUCTOR_REGISTER(arg_smbios11, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm_state_path, freep); +STATIC_DESTRUCTOR_REGISTER(arg_efi_nvram_template, freep); STATIC_DESTRUCTOR_REGISTER(arg_efi_nvram_state_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_property, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user, strv_freep); @@ -223,14 +229,19 @@ static int help(void) { " --tpm=BOOL Enable use of a virtual TPM\n" " --tpm-state=off|auto|PATH\n" " Where to store TPM state\n" + " --efi-nvram-template=PATH\n" + " Set the path to the EFI NVRAM template file to use\n" " --efi-nvram-state=off|auto|PATH\n" " Where to store EFI Variable NVRAM state\n" " --linux=PATH Specify the linux kernel for direct kernel boot\n" " --initrd=PATH Specify the initrd for direct kernel boot\n" " -n --network-tap Create a TAP device for networking\n" " --network-user-mode Use user mode networking\n" - " --secure-boot=BOOL Enable searching for firmware supporting SecureBoot\n" + " --secure-boot=BOOL|auto\n" + " Enable searching for firmware supporting SecureBoot\n" " --firmware=PATH|list Select firmware definition file (or list available)\n" + " --firmware-features=FEATURE[,FEATURE...]|list\n" + " Require/exclude specific firmware features\n" " --discard-disk=BOOL Control processing of discard requests\n" " -G --grow-image=BYTES Grow image file to specified size in bytes\n" "\n%3$sExecution:%4$s\n" @@ -304,6 +315,13 @@ static int parse_environment(void) { } static int parse_argv(int argc, char *argv[]) { + int r; + + /* Firmware with enrolled keys has been known to cause issues, skip by default */ + r = set_put_strdup(&arg_firmware_features_exclude, "enrolled-keys"); + if (r < 0) + return log_oom(); + enum { ARG_VERSION = 0x100, ARG_NO_PAGER, @@ -331,10 +349,12 @@ static int parse_argv(int argc, char *argv[]) { ARG_SET_CREDENTIAL, ARG_LOAD_CREDENTIAL, ARG_FIRMWARE, + ARG_FIRMWARE_FEATURES, ARG_DISCARD_DISK, ARG_CONSOLE, ARG_BACKGROUND, ARG_TPM_STATE, + ARG_EFI_NVRAM_TEMPLATE, ARG_EFI_NVRAM_STATE, ARG_NO_ASK_PASSWORD, ARG_PROPERTY, @@ -390,11 +410,13 @@ static int parse_argv(int argc, char *argv[]) { { "set-credential", required_argument, NULL, ARG_SET_CREDENTIAL }, { "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL }, { "firmware", required_argument, NULL, ARG_FIRMWARE }, + { "firmware-features", required_argument, NULL, ARG_FIRMWARE_FEATURES }, { "discard-disk", required_argument, NULL, ARG_DISCARD_DISK }, { "background", required_argument, NULL, ARG_BACKGROUND }, { "smbios11", required_argument, NULL, 's' }, { "grow-image", required_argument, NULL, 'G' }, { "tpm-state", required_argument, NULL, ARG_TPM_STATE }, + { "efi-nvram-template", required_argument, NULL, ARG_EFI_NVRAM_TEMPLATE }, { "efi-nvram-state", required_argument, NULL, ARG_EFI_NVRAM_STATE }, { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, { "property", required_argument, NULL, ARG_PROPERTY }, @@ -407,7 +429,7 @@ static int parse_argv(int argc, char *argv[]) { {} }; - int c, r; + int c; assert(argc >= 0); assert(argv); @@ -638,11 +660,24 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_SECURE_BOOT: - r = parse_tristate_argument_with_auto("--secure-boot=", optarg, &arg_secure_boot); + case ARG_SECURE_BOOT: { + int b; + + r = parse_tristate_argument_with_auto("--secure-boot=", optarg, &b); if (r < 0) return r; + + free(set_remove(arg_firmware_features_include, "secure-boot")); + free(set_remove(arg_firmware_features_exclude, "secure-boot")); + + if (b >= 0) { + r = set_put_strdup(b > 0 ? &arg_firmware_features_include : &arg_firmware_features_exclude, "secure-boot"); + if (r < 0) + return log_oom(); + } + break; + } case ARG_PRIVATE_USERS: r = parse_userns_uid_range(optarg, &arg_uid_shift, &arg_uid_range); @@ -711,6 +746,42 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_FIRMWARE_FEATURES: { + if (isempty(optarg)) { + arg_firmware_features_include = set_free(arg_firmware_features_include); + arg_firmware_features_exclude = set_free(arg_firmware_features_exclude); + break; + } + + if (streq(optarg, "list")) { + _cleanup_strv_free_ char **l = NULL; + + r = list_ovmf_firmware_features(&l); + if (r < 0) + return log_error_errno(r, "Failed to list firmware features: %m"); + + bool nl = false; + fputstrv(stdout, l, "\n", &nl); + if (nl) + putchar('\n'); + + return 0; + } + + _cleanup_strv_free_ char **features = strv_split(optarg, ","); + if (!features) + return log_oom(); + + STRV_FOREACH(feature, features) { + const char *e = startswith(*feature, "!"); + r = set_put_strdup(e ? &arg_firmware_features_exclude : &arg_firmware_features_include, e ?: *feature); + if (r < 0) + return log_oom(); + } + + break; + } + case ARG_DISCARD_DISK: r = parse_boolean_argument("--discard-disk=", optarg, &arg_discard_disk); if (r < 0) @@ -772,6 +843,16 @@ static int parse_argv(int argc, char *argv[]) { arg_tpm_state_mode = STATE_PATH; break; + case ARG_EFI_NVRAM_TEMPLATE: + if (!isempty(optarg) && !path_is_absolute(optarg) && !startswith(optarg, "./")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Absolute path or path starting with './' required."); + + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_efi_nvram_template); + if (r < 0) + return r; + + break; + case ARG_EFI_NVRAM_STATE: r = isempty(optarg) ? false : streq(optarg, "auto") ? true : @@ -2090,19 +2171,15 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (arg_firmware) r = load_ovmf_config(arg_firmware, &ovmf_config); else - r = find_ovmf_config(arg_secure_boot, &ovmf_config); + r = find_ovmf_config(arg_firmware_features_include, arg_firmware_features_exclude, &ovmf_config); if (r < 0) return log_error_errno(r, "Failed to find OVMF config: %m"); - if (arg_secure_boot > 0 && !ovmf_config->supports_sb) { - assert(arg_firmware); - + if (set_contains(arg_firmware_features_include, "secure-boot") && !ovmf_config->supports_sb) return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), - "Secure Boot requested, but supplied OVMF firmware blob doesn't support it."); - } + "Secure Boot requested, but selected OVMF firmware doesn't support it."); - if (arg_secure_boot < 0) - log_debug("Using OVMF firmware %s Secure Boot support.", ovmf_config->supports_sb ? "with" : "without"); + log_debug("Using OVMF firmware %s Secure Boot support.", ovmf_config->supports_sb ? "with" : "without"); _cleanup_(machine_bind_user_context_freep) MachineBindUserContext *bind_user_context = NULL; r = machine_bind_user_prepare( @@ -2565,7 +2642,8 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } _cleanup_(unlink_and_freep) char *ovmf_vars = NULL; - if (ovmf_config->vars) { + if (ovmf_config->vars || arg_efi_nvram_template) { + const char *vars_source = arg_efi_nvram_template ?: ovmf_config->vars; _cleanup_close_ int target_fd = -EBADF; _cleanup_(unlink_and_freep) char *destroy_path = NULL; bool newly_created; @@ -2602,13 +2680,13 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } if (newly_created) { - _cleanup_close_ int source_fd = open(ovmf_config->vars, O_RDONLY|O_CLOEXEC); + _cleanup_close_ int source_fd = open(vars_source, O_RDONLY|O_CLOEXEC); if (source_fd < 0) - return log_error_errno(errno, "Failed to open OVMF vars file %s: %m", ovmf_config->vars); + return log_error_errno(errno, "Failed to open OVMF vars file %s: %m", vars_source); r = copy_bytes(source_fd, target_fd, UINT64_MAX, COPY_REFLINK); if (r < 0) - return log_error_errno(r, "Failed to copy bytes from %s to %s: %m", ovmf_config->vars, state); + return log_error_errno(r, "Failed to copy bytes from %s to %s: %m", vars_source, state); /* This isn't always available so don't raise an error if it fails */ (void) copy_times(source_fd, target_fd, 0); From d6e5fbca73dbb732661efefb6cef5b3b656aa853 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 27 Mar 2026 12:55:32 +0100 Subject: [PATCH 0563/1296] vmspawn: Add --firmware=describe It's useful to be able to check what firmware description vmspawn will select. In particular, this will allow me to figure out the nvram template file that will be picked up so I can pick it up in mkosi and operate on it to pass a modified version of it to vmspawn with --efi-nvram-template=. --- man/systemd-vmspawn.xml | 4 +++- shell-completion/bash/systemd-vmspawn | 2 +- src/vmspawn/vmspawn-util.c | 23 ++++++++++++++----- src/vmspawn/vmspawn-util.h | 2 +- src/vmspawn/vmspawn.c | 32 +++++++++++++++++++++++++-- 5 files changed, 53 insertions(+), 10 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 72dbcb15d9d0f..28070cfe8f66c 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -330,7 +330,9 @@ Takes an absolute path, or a relative path beginning with ./. Specifies a JSON firmware definition file, which allows selecting the firmware to boot in the VM. If not specified, a suitable firmware is automatically discovered. If the - special string list is specified lists all discovered firmwares. + special string list is specified lists all discovered firmwares. If the special + string describe is specified, the firmware that would be selected (taking + into account) is printed and the program exits. diff --git a/shell-completion/bash/systemd-vmspawn b/shell-completion/bash/systemd-vmspawn index a6ce9708abe87..b035a42a6550e 100644 --- a/shell-completion/bash/systemd-vmspawn +++ b/shell-completion/bash/systemd-vmspawn @@ -54,7 +54,7 @@ _systemd_vmspawn() { comps=$(compgen -f -- "$cur" ) elif __contains_word "$prev" ${OPTS[FIRMWARE]}; then compopt -o nospace -o filenames - comps="list $(compgen -f -- "$cur" )" + comps="list describe $(compgen -f -- "$cur" )" elif __contains_word "$prev" ${OPTS[FIRMWARE_FEATURES]}; then comps='list' elif __contains_word "$prev" ${OPTS[BIND]}; then diff --git a/src/vmspawn/vmspawn-util.c b/src/vmspawn/vmspawn-util.c index b8e2c09c830c2..149fb8f7c9177 100644 --- a/src/vmspawn/vmspawn-util.c +++ b/src/vmspawn/vmspawn-util.c @@ -244,7 +244,7 @@ int list_ovmf_config(char ***ret) { return 0; } -static int load_firmware_data(const char *path, FirmwareData **ret) { +static int load_firmware_data(const char *path, FirmwareData **ret, sd_json_variant **ret_json) { int r; assert(path); @@ -281,6 +281,10 @@ static int load_firmware_data(const char *path, FirmwareData **ret) { return r; *ret = TAKE_PTR(fwd); + + if (ret_json) + *ret_json = TAKE_PTR(json); + return 0; } @@ -298,7 +302,7 @@ int list_ovmf_firmware_features(char ***ret) { STRV_FOREACH(file, conf_files) { _cleanup_(firmware_data_freep) FirmwareData *fwd = NULL; - r = load_firmware_data(*file, &fwd); + r = load_firmware_data(*file, &fwd, /* ret_json= */ NULL); if (r < 0) { log_debug_errno(r, "Failed to load JSON file '%s', skipping: %m", *file); continue; @@ -347,14 +351,18 @@ int load_ovmf_config(const char *path, OvmfConfig **ret) { assert(path); assert(ret); - r = load_firmware_data(path, &fwd); + r = load_firmware_data(path, &fwd, /* ret_json= */ NULL); if (r < 0) return r; return ovmf_config_make(fwd, ret); } -int find_ovmf_config(Set *features_include, Set *features_exclude, OvmfConfig **ret) { +int find_ovmf_config( + Set *features_include, + Set *features_exclude, + OvmfConfig **ret, + sd_json_variant **ret_firmware_json) { _cleanup_(ovmf_config_freep) OvmfConfig *config = NULL; _cleanup_strv_free_ char **conf_files = NULL; const char* native_arch_qemu; @@ -380,8 +388,9 @@ int find_ovmf_config(Set *features_include, Set *features_exclude, OvmfConfig ** STRV_FOREACH(file, conf_files) { _cleanup_(firmware_data_freep) FirmwareData *fwd = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL; - r = load_firmware_data(*file, &fwd); + r = load_firmware_data(*file, &fwd, ret_firmware_json ? &json : NULL); if (r < 0) { log_debug_errno(r, "Failed to load JSON file '%s', skipping: %m", *file); continue; @@ -428,6 +437,10 @@ int find_ovmf_config(Set *features_include, Set *features_exclude, OvmfConfig ** return r; log_debug("Selected firmware definition %s.", *file); + + if (ret_firmware_json) + *ret_firmware_json = TAKE_PTR(json); + break; } diff --git a/src/vmspawn/vmspawn-util.h b/src/vmspawn/vmspawn-util.h index 28c2df78a7014..90efd93661224 100644 --- a/src/vmspawn/vmspawn-util.h +++ b/src/vmspawn/vmspawn-util.h @@ -89,7 +89,7 @@ int qemu_check_vsock_support(void); int list_ovmf_config(char ***ret); int list_ovmf_firmware_features(char ***ret); int load_ovmf_config(const char *path, OvmfConfig **ret); -int find_ovmf_config(Set *features_include, Set *features_exclude, OvmfConfig **ret); +int find_ovmf_config(Set *features_include, Set *features_exclude, OvmfConfig **ret, sd_json_variant **ret_firmware_json); int find_qemu_binary(char **ret_qemu_binary); int vsock_fix_child_cid(int vhost_device_fd, unsigned *machine_cid, const char *machine); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index fbe4d2145bfe6..02f2b0df2e08a 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -138,6 +138,7 @@ static MachineCredentialContext arg_credentials = {}; static uid_t arg_uid_shift = UID_INVALID, arg_uid_range = 0x10000U; static RuntimeMountContext arg_runtime_mounts = {}; static char *arg_firmware = NULL; +static bool arg_firmware_describe = false; static Set *arg_firmware_features_include = NULL; static Set *arg_firmware_features_exclude = NULL; static char *arg_forward_journal = NULL; @@ -239,7 +240,9 @@ static int help(void) { " --network-user-mode Use user mode networking\n" " --secure-boot=BOOL|auto\n" " Enable searching for firmware supporting SecureBoot\n" - " --firmware=PATH|list Select firmware definition file (or list available)\n" + " --firmware=PATH|list|describe\n" + " Select firmware definition file (or list/describe\n" + " available)\n" " --firmware-features=FEATURE[,FEATURE...]|list\n" " Require/exclude specific firmware features\n" " --discard-disk=BOOL Control processing of discard requests\n" @@ -737,6 +740,16 @@ static int parse_argv(int argc, char *argv[]) { return 0; } + if (streq(optarg, "describe")) { + /* Handled after argument parsing so that --firmware-features= is + * taken into account. */ + arg_firmware = mfree(arg_firmware); + arg_firmware_describe = true; + break; + } + + arg_firmware_describe = false; + if (!isempty(optarg) && !path_is_absolute(optarg) && !startswith(optarg, "./")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Absolute path or path starting with './' required."); @@ -2171,7 +2184,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (arg_firmware) r = load_ovmf_config(arg_firmware, &ovmf_config); else - r = find_ovmf_config(arg_firmware_features_include, arg_firmware_features_exclude, &ovmf_config); + r = find_ovmf_config(arg_firmware_features_include, arg_firmware_features_exclude, &ovmf_config, /* ret_firmware_json= */ NULL); if (r < 0) return log_error_errno(r, "Failed to find OVMF config: %m"); @@ -3637,6 +3650,21 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + if (arg_firmware_describe) { + _cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL; + + r = find_ovmf_config(arg_firmware_features_include, arg_firmware_features_exclude, &ovmf_config, &json); + if (r < 0) + return log_error_errno(r, "Failed to find OVMF config: %m"); + + r = sd_json_variant_dump(json, SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_COLOR_AUTO, stdout, /* prefix= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to output JSON: %m"); + + return 0; + } + r = determine_names(); if (r < 0) return r; From bb5c7328cf6b63f8750bc0595b4ce03d097b9c6d Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 27 Mar 2026 11:40:59 +0000 Subject: [PATCH 0564/1296] vmspawn: improve firmware selection to match mkosi's implementation Align find_ovmf_config() with mkosi's find_ovmf_firmware() by adding checks that were previously missing: - Filter on interface-types, only selecting UEFI firmware definitions. Previously non-UEFI (e.g. BIOS-only) firmware could be selected. - Check machine type compatibility using substring matching against the target machine patterns in firmware descriptions (e.g. "q35" matches "pc-q35-*"), following the same approach as mkosi. - Make nvram-template optional in the firmware JSON mapping. Firmware definitions without an nvram-template are now parsed successfully (with vars remaining NULL) rather than failing entirely. Also rework the firmware target parsing to store both architecture and machine arrays per target (instead of just a flat architecture list), and extract the machine matching into firmware_data_matches_machine(). Co-developed-by: Claude Opus 4.6 --- src/vmspawn/vmspawn-util.c | 101 +++++++++++++++++++++++++++++-------- 1 file changed, 80 insertions(+), 21 deletions(-) diff --git a/src/vmspawn/vmspawn-util.c b/src/vmspawn/vmspawn-util.c index 149fb8f7c9177..7e085d7faf0fc 100644 --- a/src/vmspawn/vmspawn-util.c +++ b/src/vmspawn/vmspawn-util.c @@ -108,16 +108,57 @@ int qemu_check_vsock_support(void) { return -errno; } +typedef struct FirmwareTarget { + char *architecture; + char **machines; +} FirmwareTarget; + +static FirmwareTarget* firmware_target_free(FirmwareTarget *t) { + if (!t) + return NULL; + + free(t->architecture); + strv_free(t->machines); + + return mfree(t); +} + +static FirmwareTarget** firmware_target_free_many(FirmwareTarget **targets, size_t n) { + FOREACH_ARRAY(t, targets, n) + firmware_target_free(*t); + + return mfree(targets); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(FirmwareTarget*, firmware_target_free); + /* holds the data retrieved from the QEMU firmware interop JSON data */ typedef struct FirmwareData { + char **interface_types; char **features; char *firmware; char *firmware_format; char *vars; char *vars_format; - char **architectures; + FirmwareTarget **targets; + size_t n_targets; } FirmwareData; +static bool firmware_data_matches_machine(const FirmwareData *fwd, const char *arch, const char *machine) { + assert(fwd); + + FOREACH_ARRAY(t, fwd->targets, fwd->n_targets) { + if (!streq((*t)->architecture, arch)) + continue; + + STRV_FOREACH(m, (*t)->machines) + if (strstr(*m, machine)) + return true; + } + + return false; +} + static bool firmware_data_supports_sb(const FirmwareData *fwd) { assert(fwd); @@ -128,12 +169,13 @@ static FirmwareData* firmware_data_free(FirmwareData *fwd) { if (!fwd) return NULL; + strv_free(fwd->interface_types); strv_free(fwd->features); free(fwd->firmware); free(fwd->firmware_format); free(fwd->vars); free(fwd->vars_format); - strv_free(fwd->architectures); + firmware_target_free_many(fwd->targets, fwd->n_targets); return mfree(fwd); } @@ -163,34 +205,37 @@ static int firmware_mapping(const char *name, sd_json_variant *v, sd_json_dispat static const sd_json_dispatch_field table[] = { { "device", SD_JSON_VARIANT_STRING, NULL, 0, SD_JSON_MANDATORY }, { "executable", SD_JSON_VARIANT_OBJECT, firmware_executable, 0, SD_JSON_MANDATORY }, - { "nvram-template", SD_JSON_VARIANT_OBJECT, firmware_nvram_template, 0, SD_JSON_MANDATORY }, + { "nvram-template", SD_JSON_VARIANT_OBJECT, firmware_nvram_template, 0, 0 }, {} }; return sd_json_dispatch(v, table, flags, userdata); } -static int target_architecture(const char *name, sd_json_variant *v, sd_json_dispatch_flags_t flags, void *userdata) { - int r; +static int dispatch_targets(const char *name, sd_json_variant *v, sd_json_dispatch_flags_t flags, void *userdata) { + FirmwareData *fwd = ASSERT_PTR(userdata); sd_json_variant *e; - char ***supported_architectures = ASSERT_PTR(userdata); + int r; static const sd_json_dispatch_field table[] = { - { "architecture", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, 0, SD_JSON_MANDATORY }, - { "machines", SD_JSON_VARIANT_ARRAY, NULL, 0, SD_JSON_MANDATORY }, + { "architecture", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(FirmwareTarget, architecture), SD_JSON_MANDATORY }, + { "machines", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(FirmwareTarget, machines), SD_JSON_MANDATORY }, {} }; JSON_VARIANT_ARRAY_FOREACH(e, v) { - _cleanup_free_ char *arch = NULL; + _cleanup_(firmware_target_freep) FirmwareTarget *t = new0(FirmwareTarget, 1); + if (!t) + return -ENOMEM; - r = sd_json_dispatch(e, table, flags, &arch); + r = sd_json_dispatch(e, table, flags, t); if (r < 0) return r; - r = strv_consume(supported_architectures, TAKE_PTR(arch)); - if (r < 0) - return r; + if (!GREEDY_REALLOC(fwd->targets, fwd->n_targets + 1)) + return -ENOMEM; + + fwd->targets[fwd->n_targets++] = TAKE_PTR(t); } return 0; @@ -262,12 +307,12 @@ static int load_firmware_data(const char *path, FirmwareData **ret, sd_json_vari return r; static const sd_json_dispatch_field table[] = { - { "description", SD_JSON_VARIANT_STRING, NULL, 0, SD_JSON_MANDATORY }, - { "interface-types", SD_JSON_VARIANT_ARRAY, NULL, 0, SD_JSON_MANDATORY }, - { "mapping", SD_JSON_VARIANT_OBJECT, firmware_mapping, 0, SD_JSON_MANDATORY }, - { "targets", SD_JSON_VARIANT_ARRAY, target_architecture, offsetof(FirmwareData, architectures), SD_JSON_MANDATORY }, - { "features", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(FirmwareData, features), SD_JSON_MANDATORY }, - { "tags", SD_JSON_VARIANT_ARRAY, NULL, 0, SD_JSON_MANDATORY }, + { "description", SD_JSON_VARIANT_STRING, NULL, 0, SD_JSON_MANDATORY }, + { "interface-types", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(FirmwareData, interface_types), SD_JSON_MANDATORY }, + { "mapping", SD_JSON_VARIANT_OBJECT, firmware_mapping, 0, SD_JSON_MANDATORY }, + { "targets", SD_JSON_VARIANT_ARRAY, dispatch_targets, 0, SD_JSON_MANDATORY }, + { "features", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(FirmwareData, features), SD_JSON_MANDATORY }, + { "tags", SD_JSON_VARIANT_ARRAY, NULL, 0, SD_JSON_MANDATORY }, {} }; @@ -396,8 +441,22 @@ int find_ovmf_config( continue; } - if (!strv_contains(fwd->architectures, native_arch_qemu)) { - log_debug("Skipping %s, firmware doesn't support the native architecture.", *file); + if (!strv_contains(fwd->interface_types, "uefi")) { + log_debug("Skipping %s, firmware is not a UEFI firmware.", *file); + continue; + } + + if (!fwd->vars) { + log_debug("Skipping %s, firmware does not have an NVRAM template.", *file); + continue; + } + + /* Check if any target matches our architecture and machine type. Machine + * patterns in firmware descriptions use globs like "pc-q35-*", so we do a + * substring check to see if our machine type (e.g. "q35") appears in any of + * the glob patterns. */ + if (!firmware_data_matches_machine(fwd, native_arch_qemu, QEMU_MACHINE_TYPE)) { + log_debug("Skipping %s, firmware doesn't support the native architecture or machine type.", *file); continue; } From 1224e5d1952598db6706bf2e70f9cbfde6fe0987 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 27 Mar 2026 19:32:29 +0000 Subject: [PATCH 0565/1296] shutdown: remove kexec-tools dependency 'kexec -e' is just a small wrapper that does the xen hypercall on xen, or otherwise just calls reboot(). Drop the dependency, and reuse the existing xen hypercall helper. --- src/login/logind-action.c | 4 --- src/shared/reboot-util.c | 52 +++++++++++++++++++++++++++++++++------ src/shared/reboot-util.h | 1 + src/shutdown/shutdown.c | 16 +----------- 4 files changed, 46 insertions(+), 27 deletions(-) diff --git a/src/login/logind-action.c b/src/login/logind-action.c index 843bb1a5a085c..48f2031fc47c7 100644 --- a/src/login/logind-action.c +++ b/src/login/logind-action.c @@ -222,10 +222,6 @@ static int handle_action_execute( assert(m); assert(!IN_SET(handle, HANDLE_IGNORE, HANDLE_LOCK, HANDLE_SLEEP)); - if (handle == HANDLE_KEXEC && access(KEXEC, X_OK) < 0) - return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "Requested %s operation not supported, ignoring.", handle_action_to_string(handle)); - if (m->delayed_action) return log_debug_errno(SYNTHETIC_ERRNO(EALREADY), "Action %s already in progress, ignoring requested %s operation.", diff --git a/src/shared/reboot-util.c b/src/shared/reboot-util.c index 55ec6c0f0aac6..d9ff532921b38 100644 --- a/src/shared/reboot-util.c +++ b/src/shared/reboot-util.c @@ -139,13 +139,15 @@ bool shall_restore_state(void) { return (cached = b); } -static int xen_kexec_loaded(void) { #if HAVE_XENCTRL +static int xen_kexec_command(uint64_t cmd) { _cleanup_close_ int privcmd_fd = -EBADF, buf_fd = -EBADF; - xen_kexec_status_t *buffer; + void *buffer; size_t size; int r; + assert(IN_SET(cmd, KEXEC_CMD_kexec, KEXEC_CMD_kexec_status)); + if (access("/proc/xen", F_OK) < 0) { if (errno == ENOENT) return -EOPNOTSUPP; @@ -153,7 +155,8 @@ static int xen_kexec_loaded(void) { } size = page_size(); - if (sizeof(xen_kexec_status_t) > size) + if ((cmd == KEXEC_CMD_kexec_status && sizeof(xen_kexec_status_t) > size) || + (cmd == KEXEC_CMD_kexec && sizeof(xen_kexec_exec_t) > size)) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "page_size is too small for hypercall"); privcmd_fd = open("/dev/xen/privcmd", O_RDWR|O_CLOEXEC); @@ -168,25 +171,44 @@ static int xen_kexec_loaded(void) { if (buffer == MAP_FAILED) return log_debug_errno(errno, "Cannot allocate buffer for hypercall: %m"); - *buffer = (xen_kexec_status_t) { - .type = KEXEC_TYPE_DEFAULT, - }; + if (cmd == KEXEC_CMD_kexec_status) + *(xen_kexec_status_t *)buffer = (xen_kexec_status_t) { + .type = KEXEC_TYPE_DEFAULT, + }; + else + *(xen_kexec_exec_t *)buffer = (xen_kexec_exec_t) { + .type = KEXEC_TYPE_DEFAULT, + }; privcmd_hypercall_t call = { .op = __HYPERVISOR_kexec_op, .arg = { - KEXEC_CMD_kexec_status, + cmd, PTR_TO_UINT64(buffer), }, }; r = RET_NERRNO(ioctl(privcmd_fd, IOCTL_PRIVCMD_HYPERCALL, &call)); if (r < 0) - log_debug_errno(r, "kexec_status failed: %m"); + log_debug_errno(r, "kexec%s failed: %m", cmd == KEXEC_CMD_kexec_status ? "_status" : ""); munmap(buffer, size); return r; +} +#endif + +static int xen_kexec(void) { +#if HAVE_XENCTRL + return xen_kexec_command(KEXEC_CMD_kexec); +#else + return -EOPNOTSUPP; +#endif +} + +static int xen_kexec_loaded(void) { +#if HAVE_XENCTRL + return xen_kexec_command(KEXEC_CMD_kexec_status); #else return -EOPNOTSUPP; #endif @@ -210,6 +232,20 @@ bool kexec_loaded(void) { return s[0] == '1'; } +int kexec(void) { + int r; + + r = xen_kexec(); + if (r < 0 && r != -EOPNOTSUPP) + return log_error_errno(r, "Failed to call xen kexec: %m"); + + r = reboot(LINUX_REBOOT_CMD_KEXEC); + if (r < 0) + return log_error_errno(errno, "Failed to kexec: %m"); + + return 0; +} + int create_shutdown_run_nologin_or_warn(void) { int r; diff --git a/src/shared/reboot-util.h b/src/shared/reboot-util.h index eaa6614df05ea..4548903a4c311 100644 --- a/src/shared/reboot-util.h +++ b/src/shared/reboot-util.h @@ -26,5 +26,6 @@ int reboot_with_parameter(RebootFlags flags); bool shall_restore_state(void); bool kexec_loaded(void); +int kexec(void); int create_shutdown_run_nologin_or_warn(void); diff --git a/src/shutdown/shutdown.c b/src/shutdown/shutdown.c index fc6df238bed9e..73c6dd6d8708b 100644 --- a/src/shutdown/shutdown.c +++ b/src/shutdown/shutdown.c @@ -609,23 +609,9 @@ int main(int argc, char *argv[]) { case LINUX_REBOOT_CMD_KEXEC: if (!in_container) { - /* We cheat and exec kexec to avoid doing all its work */ log_info("Rebooting with kexec."); - r = pidref_safe_fork( - "(sd-kexec)", - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_WAIT, - /* ret= */ NULL); - if (r == 0) { - /* Child */ - - (void) execl(KEXEC, KEXEC, "-e", NULL); - log_debug_errno(errno, "Failed to execute '" KEXEC "' binary, proceeding with reboot(RB_KEXEC): %m"); - - /* execv failed (kexec binary missing?), so try simply reboot(RB_KEXEC) */ - (void) reboot(cmd); - _exit(EXIT_FAILURE); - } + (void) kexec(); /* If we are still running, then the kexec can't have worked, let's fall through */ } From 7208a74a2b7eee1b2465793fb3b2642c888fe0ce Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Fri, 13 Mar 2026 22:35:42 +0100 Subject: [PATCH 0566/1296] boot-entry: add 'auto' keyword to parse_boot_entry_token_type Add the auto keyword as documented in the help message and man pages of `kernel-install`, `bootctl` and `systemd-pcrlock`. --- src/shared/boot-entry.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/shared/boot-entry.c b/src/shared/boot-entry.c index 0f1d8090247a6..c9e966ba04ea8 100644 --- a/src/shared/boot-entry.c +++ b/src/shared/boot-entry.c @@ -253,6 +253,12 @@ int parse_boot_entry_token_type(const char *s, BootEntryTokenType *type, char ** * Hence, do not pass in uninitialized pointers. */ + if (streq(s, "auto")) { + *type = BOOT_ENTRY_TOKEN_AUTO; + *token = mfree(*token); + return 0; + } + if (streq(s, "machine-id")) { *type = BOOT_ENTRY_TOKEN_MACHINE_ID; *token = mfree(*token); From a1dada941d07a88300b593bf44eeba244f64e581 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 27 Mar 2026 17:03:32 +0000 Subject: [PATCH 0567/1296] mkosi: depend on bpftool for Ubuntu 26.04 build image bpftool was disentangled, so we can depend on it, and build with bpf --- .../debian-ubuntu/mkosi.conf.d/bpftool.conf | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.conf.d/bpftool.conf diff --git a/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.conf.d/bpftool.conf b/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.conf.d/bpftool.conf new file mode 100644 index 0000000000000..df2010cee4f5c --- /dev/null +++ b/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.conf.d/bpftool.conf @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# bpftool was untangled in resolute + +[TriggerMatch] +Distribution=ubuntu +Release=!jammy +Release=!noble + +[Content] +Packages=bpftool From 9f56d62f922135faf354b79d3258ce5c7c6a2ed8 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 27 Mar 2026 17:02:41 +0000 Subject: [PATCH 0568/1296] test: check for bin/bash in dissect --mtree instead of cat Ubuntu is doing shenanigans with their coreutils so they are now symlinks instead of binaries, so the grep fails. Check bash instead to fix test failure on 26.04. --- test/units/TEST-50-DISSECT.dissect.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/units/TEST-50-DISSECT.dissect.sh b/test/units/TEST-50-DISSECT.dissect.sh index d82f8c40fdc12..8cc07df3b967e 100755 --- a/test/units/TEST-50-DISSECT.dissect.sh +++ b/test/units/TEST-50-DISSECT.dissect.sh @@ -25,9 +25,9 @@ systemd-dissect "$MINIMAL_IMAGE.raw" | grep -F -f <(sed 's/"//g' "$OS_RELEASE") systemd-dissect --list "$MINIMAL_IMAGE.raw" | grep '^etc/os-release$' >/dev/null systemd-dissect --mtree "$MINIMAL_IMAGE.raw" --mtree-hash yes | \ - grep -E "^.(/usr|)/bin/cat type=file mode=0755 uid=0 gid=0 size=[0-9]* sha256sum=[a-z0-9]*$" >/dev/null + grep -E "^.(/usr|)/bin/bash type=file mode=0755 uid=0 gid=0 size=[0-9]* sha256sum=[a-z0-9]*$" >/dev/null systemd-dissect --mtree "$MINIMAL_IMAGE.raw" --mtree-hash no | \ - grep -E "^.(/usr|)/bin/cat type=file mode=0755 uid=0 gid=0 size=[0-9]*$" >/dev/null + grep -E "^.(/usr|)/bin/bash type=file mode=0755 uid=0 gid=0 size=[0-9]*$" >/dev/null read -r SHA256SUM1 _ < <(systemd-dissect --copy-from "$MINIMAL_IMAGE.raw" etc/os-release | sha256sum) test "$SHA256SUM1" != "" From f737b38977576654abec53f3e1ed1718fefdc3a4 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 27 Mar 2026 22:29:27 +0000 Subject: [PATCH 0569/1296] test: exclude gnusleep from coredumps parsing In Ubuntu 26.04 the actual binary is called gnusleep, and sleep is a symlink, so fix the regex exclusion for the coredump checks --- test/integration-tests/TEST-16-EXTEND-TIMEOUT/meson.build | 2 +- test/integration-tests/TEST-17-UDEV/meson.build | 2 +- test/integration-tests/TEST-59-RELOADING-RESTART/meson.build | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/integration-tests/TEST-16-EXTEND-TIMEOUT/meson.build b/test/integration-tests/TEST-16-EXTEND-TIMEOUT/meson.build index a7eac125e32e6..e4b2f8be84ae3 100644 --- a/test/integration-tests/TEST-16-EXTEND-TIMEOUT/meson.build +++ b/test/integration-tests/TEST-16-EXTEND-TIMEOUT/meson.build @@ -17,6 +17,6 @@ integration_tests += [ 'StopWhenUnneeded' : 'yes', }, }, - 'coredump-exclude-regex' : '/(bash|sleep)$', + 'coredump-exclude-regex' : '/(bash|coreutils|gnusleep|sleep)$', }, ] diff --git a/test/integration-tests/TEST-17-UDEV/meson.build b/test/integration-tests/TEST-17-UDEV/meson.build index 58f809ba2937d..6a3ae8ee07a7c 100644 --- a/test/integration-tests/TEST-17-UDEV/meson.build +++ b/test/integration-tests/TEST-17-UDEV/meson.build @@ -4,6 +4,6 @@ integration_tests += [ integration_test_template + { 'name' : fs.name(meson.current_source_dir()), 'vm' : true, - 'coredump-exclude-regex' : '/(coreutils|sleep|udevadm)$', + 'coredump-exclude-regex' : '/(coreutils|gnusleep|sleep|udevadm)$', }, ] diff --git a/test/integration-tests/TEST-59-RELOADING-RESTART/meson.build b/test/integration-tests/TEST-59-RELOADING-RESTART/meson.build index f5fae753e2d02..be275a81fc46e 100644 --- a/test/integration-tests/TEST-59-RELOADING-RESTART/meson.build +++ b/test/integration-tests/TEST-59-RELOADING-RESTART/meson.build @@ -3,6 +3,6 @@ integration_tests += [ integration_test_template + { 'name' : fs.name(meson.current_source_dir()), - 'coredump-exclude-regex' : '/(coreutils|sleep|bash|systemd-notify)$', + 'coredump-exclude-regex' : '/(coreutils|gnusleep|sleep|bash|systemd-notify)$', }, ] From 1d6585f2185e3656e655cf68c273a794904c8ff3 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 27 Mar 2026 20:59:46 +0000 Subject: [PATCH 0570/1296] mkosi: pull in gnu coreutils for Ubuntu 26.04 and newer The default coreutils in Ubuntu 26.04 moved to uutils, which is broken in many subtle and annoying ways, breaking various tests. It's also a giant monolithic megabinary which makes the minimal image size go up and break other tests. Force the gnu coreutils to be pulled in all images. --- mkosi/mkosi.conf | 1 - mkosi/mkosi.conf.d/arch/mkosi.conf | 1 + mkosi/mkosi.conf.d/centos-fedora/mkosi.conf | 1 + mkosi/mkosi.conf.d/debian/mkosi.conf | 1 + mkosi/mkosi.conf.d/opensuse/mkosi.conf | 1 + .../ubuntu/mkosi.conf.d/coreutils-gnu.conf | 13 +++++++++++++ .../mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils.conf | 10 ++++++++++ mkosi/mkosi.images/minimal-base/mkosi.conf | 1 - .../minimal-base/mkosi.conf.d/arch.conf | 1 + .../minimal-base/mkosi.conf.d/centos-fedora.conf | 1 + .../minimal-base/mkosi.conf.d/debian.conf | 8 ++++++++ .../minimal-base/mkosi.conf.d/opensuse.conf | 1 + .../minimal-base/mkosi.conf.d/ubuntu/mkosi.conf | 4 ++++ .../ubuntu/mkosi.conf.d/coreutils-gnu.conf | 13 +++++++++++++ .../mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils.conf | 10 ++++++++++ mkosi/mkosi.initrd.conf/mkosi.conf | 1 - mkosi/mkosi.initrd.conf/mkosi.conf.d/arch.conf | 1 + .../mkosi.conf.d/centos-fedora.conf | 1 + mkosi/mkosi.initrd.conf/mkosi.conf.d/debian.conf | 8 ++++++++ mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf | 1 + .../mkosi.conf.d/ubuntu/mkosi.conf | 4 ++++ .../ubuntu/mkosi.conf.d/coreutils-gnu.conf | 13 +++++++++++++ .../mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils.conf | 10 ++++++++++ 23 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 mkosi/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils-gnu.conf create mode 100644 mkosi/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils.conf create mode 100644 mkosi/mkosi.images/minimal-base/mkosi.conf.d/debian.conf create mode 100644 mkosi/mkosi.images/minimal-base/mkosi.conf.d/ubuntu/mkosi.conf create mode 100644 mkosi/mkosi.images/minimal-base/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils-gnu.conf create mode 100644 mkosi/mkosi.images/minimal-base/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils.conf create mode 100644 mkosi/mkosi.initrd.conf/mkosi.conf.d/debian.conf create mode 100644 mkosi/mkosi.initrd.conf/mkosi.conf.d/ubuntu/mkosi.conf create mode 100644 mkosi/mkosi.initrd.conf/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils-gnu.conf create mode 100644 mkosi/mkosi.initrd.conf/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils.conf diff --git a/mkosi/mkosi.conf b/mkosi/mkosi.conf index 8bbf964664e85..3b726d840e519 100644 --- a/mkosi/mkosi.conf +++ b/mkosi/mkosi.conf @@ -90,7 +90,6 @@ Packages= attr bash-completion binutils - coreutils cpio curl diffutils diff --git a/mkosi/mkosi.conf.d/arch/mkosi.conf b/mkosi/mkosi.conf.d/arch/mkosi.conf index 9bea621fcaa60..73001894c2214 100644 --- a/mkosi/mkosi.conf.d/arch/mkosi.conf +++ b/mkosi/mkosi.conf.d/arch/mkosi.conf @@ -17,6 +17,7 @@ Packages= bind bpf btrfs-progs + coreutils cryptsetup dbus-broker dbus-broker-units diff --git a/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf b/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf index 416e71ba32175..739b2d94eb4b5 100644 --- a/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf +++ b/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf @@ -23,6 +23,7 @@ VolatilePackages= Packages= bind-utils bpftool + coreutils cryptsetup device-mapper-event device-mapper-multipath diff --git a/mkosi/mkosi.conf.d/debian/mkosi.conf b/mkosi/mkosi.conf.d/debian/mkosi.conf index c960a1b2ecd4e..f0ecec311a875 100644 --- a/mkosi/mkosi.conf.d/debian/mkosi.conf +++ b/mkosi/mkosi.conf.d/debian/mkosi.conf @@ -8,4 +8,5 @@ Release=testing [Content] Packages= + coreutils linux-perf diff --git a/mkosi/mkosi.conf.d/opensuse/mkosi.conf b/mkosi/mkosi.conf.d/opensuse/mkosi.conf index d01c6658c0ffd..295ed53c5893d 100644 --- a/mkosi/mkosi.conf.d/opensuse/mkosi.conf +++ b/mkosi/mkosi.conf.d/opensuse/mkosi.conf @@ -35,6 +35,7 @@ Packages= bind-utils bpftool btrfs-progs + coreutils cryptsetup device-mapper dhcp-server diff --git a/mkosi/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils-gnu.conf b/mkosi/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils-gnu.conf new file mode 100644 index 0000000000000..86d4d1132b9ba --- /dev/null +++ b/mkosi/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils-gnu.conf @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# avoid pulling in the uutils package which is the default and is broken in several ways + +[TriggerMatch] +Distribution=ubuntu +Release=!jammy +Release=!noble + +[Content] +Packages= + coreutils-from-gnu + coreutils-from-uutils- + rust-coreutils- diff --git a/mkosi/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils.conf b/mkosi/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils.conf new file mode 100644 index 0000000000000..b680604f001d8 --- /dev/null +++ b/mkosi/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils.conf @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[TriggerMatch] +Distribution=ubuntu +Release=jammy +Release=noble + +[Content] +Packages= + coreutils diff --git a/mkosi/mkosi.images/minimal-base/mkosi.conf b/mkosi/mkosi.images/minimal-base/mkosi.conf index 8e57cd032dfea..60c6b4cc71153 100644 --- a/mkosi/mkosi.images/minimal-base/mkosi.conf +++ b/mkosi/mkosi.images/minimal-base/mkosi.conf @@ -15,7 +15,6 @@ CleanPackageMetadata=yes Packages= bash - coreutils grep socat util-linux diff --git a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/arch.conf b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/arch.conf index 6d77d2305d13b..ce62ded2943af 100644 --- a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/arch.conf +++ b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/arch.conf @@ -6,6 +6,7 @@ Distribution=arch [Content] PrepareScripts=%D/mkosi/mkosi.conf.d/arch/systemd.prepare Packages= + coreutils inetutils iproute nmap diff --git a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/centos-fedora.conf b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/centos-fedora.conf index 53cc68d794768..ac951fbf60f98 100644 --- a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/centos-fedora.conf +++ b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/centos-fedora.conf @@ -7,6 +7,7 @@ Distribution=|fedora [Content] PrepareScripts=%D/mkosi/mkosi.conf.d/centos-fedora/systemd.prepare Packages= + coreutils hostname iproute iproute-tc diff --git a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/debian.conf b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/debian.conf new file mode 100644 index 0000000000000..eed9f5d6d78a4 --- /dev/null +++ b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/debian.conf @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[Match] +Distribution=debian + +[Content] +Packages= + coreutils diff --git a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/opensuse.conf b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/opensuse.conf index 8b38a769a1eb3..ebf55a3188a9f 100644 --- a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/opensuse.conf +++ b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/opensuse.conf @@ -6,6 +6,7 @@ Distribution=opensuse [Content] PrepareScripts=%D/mkosi/mkosi.conf.d/opensuse/systemd.prepare Packages= + coreutils diffutils grep hostname diff --git a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/ubuntu/mkosi.conf b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/ubuntu/mkosi.conf new file mode 100644 index 0000000000000..b9fd7bcf34203 --- /dev/null +++ b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/ubuntu/mkosi.conf @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[Match] +Distribution=ubuntu diff --git a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils-gnu.conf b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils-gnu.conf new file mode 100644 index 0000000000000..86d4d1132b9ba --- /dev/null +++ b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils-gnu.conf @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# avoid pulling in the uutils package which is the default and is broken in several ways + +[TriggerMatch] +Distribution=ubuntu +Release=!jammy +Release=!noble + +[Content] +Packages= + coreutils-from-gnu + coreutils-from-uutils- + rust-coreutils- diff --git a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils.conf b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils.conf new file mode 100644 index 0000000000000..b680604f001d8 --- /dev/null +++ b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils.conf @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[TriggerMatch] +Distribution=ubuntu +Release=jammy +Release=noble + +[Content] +Packages= + coreutils diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf b/mkosi/mkosi.initrd.conf/mkosi.conf index 207b15f98c903..de37e7c3c9769 100644 --- a/mkosi/mkosi.initrd.conf/mkosi.conf +++ b/mkosi/mkosi.initrd.conf/mkosi.conf @@ -12,7 +12,6 @@ Environment=SYSTEMD_REQUIRED_DEPS_ONLY=1 ExtraTrees=%D/mkosi/mkosi.extra.common Packages= - coreutils findutils grep sed diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf.d/arch.conf b/mkosi/mkosi.initrd.conf/mkosi.conf.d/arch.conf index 909426a09cca6..72043184025e1 100644 --- a/mkosi/mkosi.initrd.conf/mkosi.conf.d/arch.conf +++ b/mkosi/mkosi.initrd.conf/mkosi.conf.d/arch.conf @@ -7,6 +7,7 @@ Distribution=arch PrepareScripts=%D/mkosi/mkosi.conf.d/arch/systemd.prepare Packages= btrfs-progs + coreutils tpm2-tools VolatilePackages= diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf.d/centos-fedora.conf b/mkosi/mkosi.initrd.conf/mkosi.conf.d/centos-fedora.conf index a35ccb4a0e446..2077f0662f899 100644 --- a/mkosi/mkosi.initrd.conf/mkosi.conf.d/centos-fedora.conf +++ b/mkosi/mkosi.initrd.conf/mkosi.conf.d/centos-fedora.conf @@ -7,6 +7,7 @@ Distribution=|fedora [Content] PrepareScripts=%D/mkosi/mkosi.conf.d/centos-fedora/systemd.prepare Packages= + coreutils swtpm-tools tpm2-tools diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf.d/debian.conf b/mkosi/mkosi.initrd.conf/mkosi.conf.d/debian.conf new file mode 100644 index 0000000000000..eed9f5d6d78a4 --- /dev/null +++ b/mkosi/mkosi.initrd.conf/mkosi.conf.d/debian.conf @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[Match] +Distribution=debian + +[Content] +Packages= + coreutils diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf b/mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf index 9308395763570..92fc255670fa6 100644 --- a/mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf +++ b/mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf @@ -7,6 +7,7 @@ Distribution=opensuse PrepareScripts=%D/mkosi/mkosi.conf.d/opensuse/systemd.prepare Packages= btrfs-progs + coreutils kmod tpm2.0-tools diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf.d/ubuntu/mkosi.conf b/mkosi/mkosi.initrd.conf/mkosi.conf.d/ubuntu/mkosi.conf new file mode 100644 index 0000000000000..b9fd7bcf34203 --- /dev/null +++ b/mkosi/mkosi.initrd.conf/mkosi.conf.d/ubuntu/mkosi.conf @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[Match] +Distribution=ubuntu diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils-gnu.conf b/mkosi/mkosi.initrd.conf/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils-gnu.conf new file mode 100644 index 0000000000000..86d4d1132b9ba --- /dev/null +++ b/mkosi/mkosi.initrd.conf/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils-gnu.conf @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# avoid pulling in the uutils package which is the default and is broken in several ways + +[TriggerMatch] +Distribution=ubuntu +Release=!jammy +Release=!noble + +[Content] +Packages= + coreutils-from-gnu + coreutils-from-uutils- + rust-coreutils- diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils.conf b/mkosi/mkosi.initrd.conf/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils.conf new file mode 100644 index 0000000000000..b680604f001d8 --- /dev/null +++ b/mkosi/mkosi.initrd.conf/mkosi.conf.d/ubuntu/mkosi.conf.d/coreutils.conf @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[TriggerMatch] +Distribution=ubuntu +Release=jammy +Release=noble + +[Content] +Packages= + coreutils From b4335ea9fc83c7207fc3192d6bbacdc122692f17 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 27 Mar 2026 17:04:29 +0000 Subject: [PATCH 0571/1296] mkosi: add test job for Ubuntu 26.04 It is now in beta freeze, so we can start adding test coverage --- .github/workflows/mkosi.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/mkosi.yml b/.github/workflows/mkosi.yml index e011c146231d9..c83be9c78dfea 100644 --- a/.github/workflows/mkosi.yml +++ b/.github/workflows/mkosi.yml @@ -109,6 +109,17 @@ jobs: no_qemu: 0 no_kvm: 0 shim: 0 + - distro: ubuntu + release: resolute + runner: ubuntu-24.04 + sanitizers: "" + llvm: 0 + cflags: "-Og" + relabel: no + vm: 0 + no_qemu: 0 + no_kvm: 0 + shim: 0 - distro: fedora release: "43" runner: ubuntu-24.04 From b3c3a40b35e94806c590116e50e9c49929b20efc Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 00:58:32 +0000 Subject: [PATCH 0572/1296] sysupdate: add more input validation Ensure bogus inputs are cleanly rejected. These are privileged interfaces so in practice it's not a problem. Reported on yeswehack.com as YWH-PGM9780-168 Follow-up for bf2c741fd772af6f04b4fa234ada2d364f9a5d6c --- src/sysupdate/sysupdated.c | 46 ++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/src/sysupdate/sysupdated.c b/src/sysupdate/sysupdated.c index 3fb88362e86aa..fde6124e849dc 100644 --- a/src/sysupdate/sysupdated.c +++ b/src/sysupdate/sysupdated.c @@ -977,8 +977,8 @@ static int target_method_describe(sd_bus_message *msg, void *userdata, sd_bus_er if (r < 0) return r; - if (isempty(version)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Version must be specified"); + if (!version_is_valid(version)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid version"); if ((flags & ~SD_SYSUPDATE_FLAGS_ALL) != 0) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags specified"); @@ -1126,8 +1126,12 @@ static int target_method_acquire(sd_bus_message *msg, void *userdata, sd_bus_err * an update anyway. */ if (isempty(version)) action = "org.freedesktop.sysupdate1.update"; - else + else { + if (!version_is_valid(version)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid version"); + action = "org.freedesktop.sysupdate1.update-to-version"; + } const char *details[] = { "class", target_class_to_string(t->class), @@ -1210,8 +1214,12 @@ static int target_method_install(sd_bus_message *msg, void *userdata, sd_bus_err * an update anyway. */ if (isempty(version)) action = "org.freedesktop.sysupdate1.update"; - else + else { + if (!version_is_valid(version)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid version"); + action = "org.freedesktop.sysupdate1.update-to-version"; + } const char *details[] = { "class", target_class_to_string(t->class), @@ -1417,6 +1425,19 @@ static int target_method_list_features(sd_bus_message *msg, void *userdata, sd_b return sd_bus_message_send(reply); } +static bool feature_name_is_valid(const char *name) { + if (isempty(name)) + return false; + + if (!ascii_is_valid(name)) + return false; + + if (!filename_is_valid(strjoina(name, ".feature.d"))) + return false; + + return true; +} + static int target_method_describe_feature(sd_bus_message *msg, void *userdata, sd_bus_error *error) { Target *t = ASSERT_PTR(userdata); _cleanup_(job_freep) Job *j = NULL; @@ -1430,8 +1451,8 @@ static int target_method_describe_feature(sd_bus_message *msg, void *userdata, s if (r < 0) return r; - if (isempty(feature)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Feature must be specified"); + if (!feature_name_is_valid(feature)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid feature name"); if (flags != 0) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0"); @@ -1452,19 +1473,6 @@ static int target_method_describe_feature(sd_bus_message *msg, void *userdata, s return 1; } -static bool feature_name_is_valid(const char *name) { - if (isempty(name)) - return false; - - if (!ascii_is_valid(name)) - return false; - - if (!filename_is_valid(strjoina(name, ".feature.d"))) - return false; - - return true; -} - static int target_method_set_feature_enabled(sd_bus_message *msg, void *userdata, sd_bus_error *error) { _cleanup_free_ char *feature_ext = NULL; Target *t = ASSERT_PTR(userdata); From 7471dc299451f37517125a25539ea8821630a1b4 Mon Sep 17 00:00:00 2001 From: RiskoZS Date: Fri, 27 Mar 2026 21:51:20 -0400 Subject: [PATCH 0573/1296] hwdb: Add keymaps for Acer Nitro 5 AN517-54 Add mappings for the Fn+F7 (microphone mute), NitroSense and power keys for the Acer Nitro 5 AN517-54 --- hwdb.d/60-keyboard.hwdb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hwdb.d/60-keyboard.hwdb b/hwdb.d/60-keyboard.hwdb index fcc4b063fbbef..ce3750a36af7f 100644 --- a/hwdb.d/60-keyboard.hwdb +++ b/hwdb.d/60-keyboard.hwdb @@ -242,6 +242,12 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnAcer*:pnNitro*AN*515-58:pvr* KEYBOARD_KEY_8a=micmute # Microphone mute button KEYBOARD_KEY_55=power +# Nitro AN517-54 +evdev:atkbd:dmi:bvn*:bvr*:bd*:svnAcer*:pnNitro*AN*517-54:pvr* + KEYBOARD_KEY_8a=micmute # Fn+F7; Microphone mute button + KEYBOARD_KEY_f5=prog1 # NitroSense button + KEYBOARD_KEY_55=power + # Nitro ANV15-51 evdev:atkbd:dmi:bvn*:bvr*:bd*:svnAcer*:pnNitro*ANV*15-51:pvr* KEYBOARD_KEY_66=micmute # Microphone mute button From e0ab84e21b4894490965cb272e81152110f74761 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 14:47:25 +0000 Subject: [PATCH 0574/1296] networkd: fix assert with IPFamily=both in MobileNetwork conf Fixes https://github.com/systemd/systemd/issues/41389 Follow-up for f8a4c3d375b83f3ee249ca3f4b7f407b618a9491 --- src/network/networkd-wwan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/networkd-wwan.c b/src/network/networkd-wwan.c index b84ace130102a..5852d5eb4354c 100644 --- a/src/network/networkd-wwan.c +++ b/src/network/networkd-wwan.c @@ -477,7 +477,7 @@ static int link_apply_bearer_impl(Link *link, Bearer *b) { if (r < 0) return r; - r = link_request_bearer_route(link, AF_INET6, &b->ip6_gateway, NULL); + r = link_request_bearer_route(link, AF_INET6, &b->ip6_gateway, &b->ip6_address); if (r < 0) return r; } From abe3d570f8006fca5138b2d5cfb4e8b530be02e5 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 14:40:11 +0000 Subject: [PATCH 0575/1296] test: add a mock ModemManager for basic coverage of sd-networkd's integration Just the minimal setup and test case required to cover https://github.com/systemd/systemd/issues/41389 for now, can be expanded in the future Boring boilerplate is bot-made, don't @ me Co-developed-by: Claude Opus 4.6 noreply@anthropic.com --- src/network/meson.build | 6 + src/network/test-modem-manager-mock.c | 486 ++++++++++++++++++ .../TEST-85-NETWORK/meson.build | 1 + test/test-network/conf/25-wwan-ipv4v6.network | 12 + .../test-network/conf/mock-modem-manager.conf | 13 + test/test-network/systemd-networkd-tests.py | 77 +++ 6 files changed, 595 insertions(+) create mode 100644 src/network/test-modem-manager-mock.c create mode 100644 test/test-network/conf/25-wwan-ipv4v6.network create mode 100644 test/test-network/conf/mock-modem-manager.conf diff --git a/src/network/meson.build b/src/network/meson.build index 85b57669e4602..00361a0017ed9 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -258,6 +258,12 @@ executables += [ network_test_template + { 'sources' : files('test-networkd-util.c'), }, + test_template + { + 'sources' : files('test-modem-manager-mock.c'), + 'conditions' : ['ENABLE_NETWORKD'], + 'link_with' : [libshared], + 'type' : 'manual', + }, network_fuzz_template + { 'sources' : files('fuzz-netdev-parser.c'), }, diff --git a/src/network/test-modem-manager-mock.c b/src/network/test-modem-manager-mock.c new file mode 100644 index 0000000000000..60f0dfa8d4ea2 --- /dev/null +++ b/src/network/test-modem-manager-mock.c @@ -0,0 +1,486 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* + * Minimal mock of ModemManager's D-Bus interface for testing systemd-networkd + * wwan/bearer support. + * + * Claims the org.freedesktop.ModemManager1 bus name and responds to: + * - GetManagedObjects on /org/freedesktop/ModemManager1 + * - GetAll on /org/freedesktop/ModemManager1/Bearer/0 + * - Simple.Connect on /org/freedesktop/ModemManager1/Modem/0 + */ + +#include + +#include "sd-bus.h" +#include "sd-daemon.h" +#include "sd-event.h" + +#include "alloc-util.h" +#include "build.h" +#include "log.h" +#include "main-func.h" +#include "parse-util.h" +#include "string-util.h" + +static char *arg_ifname = NULL; +static char *arg_ipv4_address = NULL; +static char *arg_ipv4_gateway = NULL; +static uint32_t arg_ipv4_prefix = 24; +static char *arg_ipv6_address = NULL; +static char *arg_ipv6_gateway = NULL; +static uint32_t arg_ipv6_prefix = 64; + +STATIC_DESTRUCTOR_REGISTER(arg_ifname, freep); +STATIC_DESTRUCTOR_REGISTER(arg_ipv4_address, freep); +STATIC_DESTRUCTOR_REGISTER(arg_ipv4_gateway, freep); +STATIC_DESTRUCTOR_REGISTER(arg_ipv6_address, freep); +STATIC_DESTRUCTOR_REGISTER(arg_ipv6_gateway, freep); + +/* ModemManager enum values */ +#define MM_BEARER_IP_METHOD_STATIC 2 +#define MM_MODEM_PORT_TYPE_NET 2 +#define MM_MODEM_STATE_CONNECTED 11 + +static int append_bearer_properties(sd_bus_message *reply) { + int r; + + /* a{sv} of bearer properties */ + r = sd_bus_message_open_container(reply, 'a', "{sv}"); + if (r < 0) + return r; + + /* Interface */ + r = sd_bus_message_append(reply, "{sv}", "Interface", "s", arg_ifname); + if (r < 0) + return r; + + /* Connected */ + r = sd_bus_message_append(reply, "{sv}", "Connected", "b", true); + if (r < 0) + return r; + + /* Ip4Config: a{sv} */ + if (arg_ipv4_address) { + r = sd_bus_message_open_container(reply, 'e', "sv"); + if (r < 0) + return r; + r = sd_bus_message_append_basic(reply, 's', "Ip4Config"); + if (r < 0) + return r; + r = sd_bus_message_open_container(reply, 'v', "a{sv}"); + if (r < 0) + return r; + r = sd_bus_message_open_container(reply, 'a', "{sv}"); + if (r < 0) + return r; + r = sd_bus_message_append(reply, "{sv}", "method", "u", (uint32_t) MM_BEARER_IP_METHOD_STATIC); + if (r < 0) + return r; + r = sd_bus_message_append(reply, "{sv}", "address", "s", arg_ipv4_address); + if (r < 0) + return r; + r = sd_bus_message_append(reply, "{sv}", "prefix", "u", arg_ipv4_prefix); + if (r < 0) + return r; + r = sd_bus_message_append(reply, "{sv}", "gateway", "s", arg_ipv4_gateway); + if (r < 0) + return r; + r = sd_bus_message_append(reply, "{sv}", "mtu", "u", (uint32_t) 1500); + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* a{sv} */ + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* v */ + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* e */ + if (r < 0) + return r; + } + + /* Ip6Config: a{sv} */ + if (arg_ipv6_address) { + r = sd_bus_message_open_container(reply, 'e', "sv"); + if (r < 0) + return r; + r = sd_bus_message_append_basic(reply, 's', "Ip6Config"); + if (r < 0) + return r; + r = sd_bus_message_open_container(reply, 'v', "a{sv}"); + if (r < 0) + return r; + r = sd_bus_message_open_container(reply, 'a', "{sv}"); + if (r < 0) + return r; + r = sd_bus_message_append(reply, "{sv}", "method", "u", (uint32_t) MM_BEARER_IP_METHOD_STATIC); + if (r < 0) + return r; + r = sd_bus_message_append(reply, "{sv}", "address", "s", arg_ipv6_address); + if (r < 0) + return r; + r = sd_bus_message_append(reply, "{sv}", "prefix", "u", arg_ipv6_prefix); + if (r < 0) + return r; + r = sd_bus_message_append(reply, "{sv}", "gateway", "s", arg_ipv6_gateway); + if (r < 0) + return r; + r = sd_bus_message_append(reply, "{sv}", "mtu", "u", (uint32_t) 1500); + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* a{sv} */ + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* v */ + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* e */ + if (r < 0) + return r; + } + + /* Properties: a{sv} with apn */ + r = sd_bus_message_open_container(reply, 'e', "sv"); + if (r < 0) + return r; + r = sd_bus_message_append_basic(reply, 's', "Properties"); + if (r < 0) + return r; + r = sd_bus_message_open_container(reply, 'v', "a{sv}"); + if (r < 0) + return r; + r = sd_bus_message_open_container(reply, 'a', "{sv}"); + if (r < 0) + return r; + r = sd_bus_message_append(reply, "{sv}", "apn", "s", "internet.test"); + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* a{sv} */ + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* v */ + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* e */ + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); /* outer a{sv} */ + if (r < 0) + return r; + + return 0; +} + +static int handle_get_managed_objects(sd_bus_message *msg, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + + r = sd_bus_message_new_method_return(msg, &reply); + if (r < 0) + return r; + + /* a{oa{sa{sv}}} */ + r = sd_bus_message_open_container(reply, 'a', "{oa{sa{sv}}}"); + if (r < 0) + return r; + + /* Modem object */ + r = sd_bus_message_open_container(reply, 'e', "oa{sa{sv}}"); + if (r < 0) + return r; + r = sd_bus_message_append_basic(reply, 'o', "/org/freedesktop/ModemManager1/Modem/0"); + if (r < 0) + return r; + + /* Array of interfaces */ + r = sd_bus_message_open_container(reply, 'a', "{sa{sv}}"); + if (r < 0) + return r; + + /* org.freedesktop.ModemManager1.Modem interface */ + r = sd_bus_message_open_container(reply, 'e', "sa{sv}"); + if (r < 0) + return r; + r = sd_bus_message_append_basic(reply, 's', "org.freedesktop.ModemManager1.Modem"); + if (r < 0) + return r; + + /* Modem properties: a{sv} */ + r = sd_bus_message_open_container(reply, 'a', "{sv}"); + if (r < 0) + return r; + + /* Bearers: ao */ + r = sd_bus_message_append(reply, "{sv}", "Bearers", "ao", 1, "/org/freedesktop/ModemManager1/Bearer/0"); + if (r < 0) + return r; + + /* State: i (CONNECTED) */ + r = sd_bus_message_append(reply, "{sv}", "State", "i", (int32_t) MM_MODEM_STATE_CONNECTED); + if (r < 0) + return r; + + /* StateFailedReason: u (NONE) */ + r = sd_bus_message_append(reply, "{sv}", "StateFailedReason", "u", (uint32_t) 0); + if (r < 0) + return r; + + /* Manufacturer */ + r = sd_bus_message_append(reply, "{sv}", "Manufacturer", "s", "MockModem"); + if (r < 0) + return r; + + /* Model */ + r = sd_bus_message_append(reply, "{sv}", "Model", "s", "Virtual"); + if (r < 0) + return r; + + /* Ports: a(su) — array of structs with port name and type */ + r = sd_bus_message_open_container(reply, 'e', "sv"); + if (r < 0) + return r; + r = sd_bus_message_append_basic(reply, 's', "Ports"); + if (r < 0) + return r; + r = sd_bus_message_open_container(reply, 'v', "a(su)"); + if (r < 0) + return r; + r = sd_bus_message_open_container(reply, 'a', "(su)"); + if (r < 0) + return r; + r = sd_bus_message_append(reply, "(su)", arg_ifname, (uint32_t) MM_MODEM_PORT_TYPE_NET); + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* a(su) */ + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* v */ + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* e */ + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); /* modem properties a{sv} */ + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* e sa{sv} */ + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* a{sa{sv}} */ + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* e oa{sa{sv}} */ + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); /* a{oa{sa{sv}}} */ + if (r < 0) + return r; + + r = sd_bus_send(NULL, reply, NULL); + if (r < 0) + return r; + + return 1; /* handled */ +} + +static int handle_get_all(sd_bus_message *msg, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + + r = sd_bus_message_new_method_return(msg, &reply); + if (r < 0) + return r; + + /* bearer_get_all_handler() in networkd expects a leading interface name string + * before the a{sv} properties dict (it calls sd_bus_message_skip(message, "s")). */ + r = sd_bus_message_append_basic(reply, 's', "org.freedesktop.ModemManager1.Bearer"); + if (r < 0) + return r; + + r = append_bearer_properties(reply); + if (r < 0) + return r; + + r = sd_bus_send(NULL, reply, NULL); + if (r < 0) + return r; + + return 1; /* handled */ +} + +static int handle_simple_connect(sd_bus_message *msg, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + + /* Return the bearer path */ + r = sd_bus_message_new_method_return(msg, &reply); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "o", "/org/freedesktop/ModemManager1/Bearer/0"); + if (r < 0) + return r; + + r = sd_bus_send(NULL, reply, NULL); + if (r < 0) + return r; + + return 1; /* handled */ +} + +static int filter_handler(sd_bus_message *m, void *userdata, sd_bus_error *error) { + const char *path, *interface, *member; + uint8_t type; + + if (sd_bus_message_get_type(m, &type) < 0 || type != SD_BUS_MESSAGE_METHOD_CALL) + return 0; + + path = sd_bus_message_get_path(m); + interface = sd_bus_message_get_interface(m); + member = sd_bus_message_get_member(m); + + if (!path || !interface || !member) + return 0; + + if (streq(path, "/org/freedesktop/ModemManager1") && + streq(interface, "org.freedesktop.DBus.ObjectManager") && + streq(member, "GetManagedObjects")) + return handle_get_managed_objects(m, userdata, error); + + if (startswith(path, "/org/freedesktop/ModemManager1/Bearer/") && + streq(interface, "org.freedesktop.DBus.Properties") && + streq(member, "GetAll")) + return handle_get_all(m, userdata, error); + + if (startswith(path, "/org/freedesktop/ModemManager1/Modem/") && + streq(interface, "org.freedesktop.ModemManager1.Modem.Simple") && + streq(member, "Connect")) + return handle_simple_connect(m, userdata, error); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_IFNAME = 0x100, + ARG_IPV4_ADDRESS, + ARG_IPV4_GATEWAY, + ARG_IPV4_PREFIX, + ARG_IPV6_ADDRESS, + ARG_IPV6_GATEWAY, + ARG_IPV6_PREFIX, + }; + + static const struct option options[] = { + { "ifname", required_argument, NULL, ARG_IFNAME }, + { "ipv4-address", required_argument, NULL, ARG_IPV4_ADDRESS }, + { "ipv4-gateway", required_argument, NULL, ARG_IPV4_GATEWAY }, + { "ipv4-prefix", required_argument, NULL, ARG_IPV4_PREFIX }, + { "ipv6-address", required_argument, NULL, ARG_IPV6_ADDRESS }, + { "ipv6-gateway", required_argument, NULL, ARG_IPV6_GATEWAY }, + { "ipv6-prefix", required_argument, NULL, ARG_IPV6_PREFIX }, + { "version", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + + int c, r; + + while ((c = getopt_long(argc, argv, "vh", options, NULL)) >= 0) + switch (c) { + case ARG_IFNAME: + if (free_and_strdup(&arg_ifname, optarg) < 0) + return log_oom(); + break; + case ARG_IPV4_ADDRESS: + if (free_and_strdup(&arg_ipv4_address, optarg) < 0) + return log_oom(); + break; + case ARG_IPV4_GATEWAY: + if (free_and_strdup(&arg_ipv4_gateway, optarg) < 0) + return log_oom(); + break; + case ARG_IPV4_PREFIX: + r = safe_atou32(optarg, &arg_ipv4_prefix); + if (r < 0) + return log_error_errno(r, "Failed to parse IPv4 prefix length: %m"); + break; + case ARG_IPV6_ADDRESS: + if (free_and_strdup(&arg_ipv6_address, optarg) < 0) + return log_oom(); + break; + case ARG_IPV6_GATEWAY: + if (free_and_strdup(&arg_ipv6_gateway, optarg) < 0) + return log_oom(); + break; + case ARG_IPV6_PREFIX: + r = safe_atou32(optarg, &arg_ipv6_prefix); + if (r < 0) + return log_error_errno(r, "Failed to parse IPv6 prefix length: %m"); + break; + case 'v': + return version(); + case 'h': + printf("Usage: %s [OPTIONS...]\n\n" + "Mock ModemManager D-Bus service for testing.\n\n" + " --ifname=NAME Interface name\n" + " --ipv4-address=ADDR IPv4 address\n" + " --ipv4-gateway=ADDR IPv4 gateway\n" + " --ipv4-prefix=LEN IPv4 prefix length\n" + " --ipv6-address=ADDR IPv6 address\n" + " --ipv6-gateway=ADDR IPv6 gateway\n" + " --ipv6-prefix=LEN IPv6 prefix length\n" + " -h, --help Show this help\n" + " -v, --version Show version\n", + program_invocation_short_name); + return 0; + default: + return -EINVAL; + } + + if (!arg_ifname) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--ifname is required"); + + return 1; /* work to do */ +} + +static int run(int argc, char *argv[]) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + int r; + + log_setup(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + r = sd_event_new(&event); + if (r < 0) + return log_error_errno(r, "Failed to create event loop: %m"); + + r = sd_bus_open_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to connect to system bus: %m"); + + r = sd_bus_add_filter(bus, NULL, filter_handler, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add filter: %m"); + + r = sd_bus_request_name(bus, "org.freedesktop.ModemManager1", 0); + if (r < 0) + return log_error_errno(r, "Failed to acquire bus name: %m"); + + r = sd_bus_attach_event(bus, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + (void) sd_notify(0, "READY=1"); + + return sd_event_loop(event); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/test/integration-tests/TEST-85-NETWORK/meson.build b/test/integration-tests/TEST-85-NETWORK/meson.build index 9e8534cff02e4..f708f05067b33 100644 --- a/test/integration-tests/TEST-85-NETWORK/meson.build +++ b/test/integration-tests/TEST-85-NETWORK/meson.build @@ -23,6 +23,7 @@ foreach testcase : [ 'NetworkdIPv6PrefixTests', 'NetworkdMTUTests', 'NetworkdSysctlTest', + 'NetworkdWWANTests', ] integration_tests += [ integration_test_template + { diff --git a/test/test-network/conf/25-wwan-ipv4v6.network b/test/test-network/conf/25-wwan-ipv4v6.network new file mode 100644 index 0000000000000..bf0a857716dc3 --- /dev/null +++ b/test/test-network/conf/25-wwan-ipv4v6.network @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=dummy98 + +[Network] +LLDP=no +LinkLocalAddressing=no +IPv6AcceptRA=no + +[MobileNetwork] +APN=internet.test +IPFamily=both diff --git a/test/test-network/conf/mock-modem-manager.conf b/test/test-network/conf/mock-modem-manager.conf new file mode 100644 index 0000000000000..0a762d7a72728 --- /dev/null +++ b/test/test-network/conf/mock-modem-manager.conf @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index bab725bd23943..03404e6cbeb41 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -73,6 +73,7 @@ timedatectl_bin = shutil.which('timedatectl', path=which_paths) udevadm_bin = shutil.which('udevadm', path=which_paths) test_ndisc_send = None +test_modem_manager_mock = None build_dir = None source_dir = None @@ -973,6 +974,28 @@ def start_radvd(*additional_options, config_file): def stop_radvd(): stop_by_pid_file(radvd_pid_file) +def start_modem_manager_mock(*additional_options): + dbus_policy_src = os.path.join(networkd_ci_temp_dir, 'mock-modem-manager.conf') + cp(dbus_policy_src, '/etc/dbus-1/system.d/mock-modem-manager.conf') + check_output('systemctl reload dbus.service') + + command = ' '.join([test_modem_manager_mock] + list(additional_options)) + with open('/run/systemd/system/test-modem-manager-mock.service', mode='w', encoding='utf-8') as f: + f.write('[Unit]\n' + 'Description=Mock ModemManager for networkd testing\n' + '[Service]\n' + 'Type=notify\n' + 'BusName=org.freedesktop.ModemManager1\n' + f'ExecStart={command}\n') + check_output('systemctl daemon-reload') + check_output('systemctl start test-modem-manager-mock.service') + +def stop_modem_manager_mock(): + call('systemctl stop test-modem-manager-mock.service') + rm_f('/run/systemd/system/test-modem-manager-mock.service') + call('systemctl daemon-reload') + rm_f('/etc/dbus-1/system.d/mock-modem-manager.conf') + def radvd_check_config(config_file): if not shutil.which('radvd'): print('radvd is not installed, assuming the config check failed') @@ -1099,6 +1122,7 @@ def tear_down_common(): stop_dnsmasq() stop_isc_dhcpd() stop_radvd() + stop_modem_manager_mock() # 2. remove modules call_quiet('rmmod netdevsim') @@ -9567,6 +9591,54 @@ def test_sysctl_monitor(self): self.assertNotIn("changed sysctl '/proc/sys/net/ipv6/conf/dummy98/max_addresses'", log) self.assertNotIn("Sysctl monitor BPF returned error", log) +class NetworkdWWANTests(unittest.TestCase, Utilities): + + def setUp(self): + setup_common() + + def tearDown(self): + tear_down_common() + + def test_wwan_ipv4v6_static(self): + """Test WWAN bearer with both IPv4 and IPv6 static configuration. + + Regression test for https://github.com/systemd/systemd/issues/41389 + """ + if not os.path.exists(test_modem_manager_mock): + self.skipTest(f'{test_modem_manager_mock} does not exist.') + + copy_network_unit('12-dummy.netdev', '25-wwan-ipv4v6.network') + try: + start_modem_manager_mock( + '--ifname', 'dummy98', + '--ipv4-address', '100.120.244.160', + '--ipv4-gateway', '100.120.244.161', + '--ipv4-prefix', '26', + '--ipv6-address', '2001:db8::1', + '--ipv6-gateway', '2001:db8::2', + '--ipv6-prefix', '64', + ) + except (subprocess.CalledProcessError, PermissionError, OSError) as e: + self.skipTest(f'Failed to start mock ModemManager: {e}') + start_networkd() + self.wait_online('dummy98:routable') + + output = check_output('ip -4 address show dev dummy98') + print(output) + self.assertIn('100.120.244.160/26', output) + + output = check_output('ip -6 address show dev dummy98') + print(output) + self.assertIn('2001:db8::1/64', output) + + output = check_output('ip -4 route show dev dummy98') + print(output) + self.assertIn('default via 100.120.244.161', output) + + output = check_output('ip -6 route show dev dummy98') + print(output) + self.assertIn('default via 2001:db8::2', output) + if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--build-dir', help='Path to build dir', dest='build_dir') @@ -9626,6 +9698,11 @@ def test_sysctl_monitor(self): else: test_ndisc_send = '/usr/lib/tests/test-ndisc-send' + if build_dir: + test_modem_manager_mock = os.path.normpath(os.path.join(build_dir, 'test-modem-manager-mock')) + else: + test_modem_manager_mock = '/usr/lib/systemd/tests/unit-tests/manual/test-modem-manager-mock' + if asan_options: env.update({'ASAN_OPTIONS': asan_options}) if lsan_options: From 7ff1bfdb68ada2c07eafb3683c88810bd86de47e Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 15:37:53 +0000 Subject: [PATCH 0576/1296] dissect: add asserts to appease coverity CID#1645844 CID#1645845 Follow-up for 91578e529395a0299a1e5eaa6da08e73db6eeacd --- src/shared/dissect-image.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index d68ea0bc9742a..4a8d4a5c0e021 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -1703,6 +1703,10 @@ static int dissect_image( PartitionDesignator dv = partition_verity_hash_of(*dd); PartitionDesignator ds = partition_verity_sig_of(*dd); + /* Hint to help static analyzers */ + assert(dv >= 0); + assert(ds >= 0); + if (!m->partitions[*dd].found && (m->partitions[dv].found || m->partitions[ds].found)) return log_debug_errno( SYNTHETIC_ERRNO(EADDRNOTAVAIL), From 9bdc8c8138373810e2509d19d2de61cf059fdfc2 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 17:56:51 +0000 Subject: [PATCH 0577/1296] imdsd: voidify unchecked call CID#1646046 Follow-up for eb6e5b07f13cefddf1f49e1f7bda4af22f5aba17 --- src/imds/imdsd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imds/imdsd.c b/src/imds/imdsd.c index 565f21cfaa66d..68a03b7232c7b 100644 --- a/src/imds/imdsd.c +++ b/src/imds/imdsd.c @@ -275,7 +275,7 @@ static void context_reset_for_refresh(Context *c) { c->child_data = hashmap_free(c->child_data); c->data_size = 0; - sd_event_source_set_enabled(c->retry_source, SD_EVENT_OFF); + (void) sd_event_source_set_enabled(c->retry_source, SD_EVENT_OFF); } static void context_reset_full(Context *c) { From 85b20f5fa7291eaf7effe62d88a8c0d5b79eee7c Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 18:29:28 +0000 Subject: [PATCH 0578/1296] boot: add overflow check in GPT parser ALIGN_TO() can overflow and return SIZE_MAX CID#1644887 Follow-up for ccbd324a3a522362de0863e8d06cdd06a58d2fca --- src/boot/part-discovery.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/boot/part-discovery.c b/src/boot/part-discovery.c index dc1aed0514b1d..25f60521acf30 100644 --- a/src/boot/part-discovery.c +++ b/src/boot/part-discovery.c @@ -117,6 +117,8 @@ static EFI_STATUS try_gpt( /* Now load the GPT entry table */ size = ALIGN_TO((size_t) gpt->SizeOfPartitionEntry * (size_t) gpt->NumberOfPartitionEntries, 512); + if (size == SIZE_MAX) /* overflow check */ + return EFI_OUT_OF_RESOURCES; entries_pages = xmalloc_aligned_pages( AllocateMaxAddress, EfiLoaderData, From 1a0fe20bc2278713b5185feee4578aabbce24b71 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 18:35:09 +0000 Subject: [PATCH 0579/1296] pe-binary: fix error reporting This is a local calculation, errno is not set Follow-up for a43427013949c6593629f551cf46e9cf9c167100 --- src/shared/pe-binary.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/pe-binary.c b/src/shared/pe-binary.c index f342801bea220..98b758dc4ebda 100644 --- a/src/shared/pe-binary.c +++ b/src/shared/pe-binary.c @@ -421,7 +421,7 @@ int pe_hash(int fd, if ((uint64_t) st.st_size > p) { if ((uint64_t) st.st_size - p < le32toh(certificate_table->Size)) - return log_debug_errno(errno, "No space for certificate table, refusing."); + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "No space for certificate table, refusing."); r = hash_file(fd, mdctx, p, st.st_size - p - le32toh(certificate_table->Size)); if (r < 0) From 74a9ed911d878cffbdf9cb1d3c88088fae41aaaa Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 18:45:52 +0000 Subject: [PATCH 0580/1296] pe-binary: add explicit cast to silence coverity Otherwise it gets confused about underflows (which are already checked) CID#1645068 Follow-up for a43427013949c6593629f551cf46e9cf9c167100 --- src/shared/pe-binary.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/pe-binary.c b/src/shared/pe-binary.c index 98b758dc4ebda..da54428306c11 100644 --- a/src/shared/pe-binary.c +++ b/src/shared/pe-binary.c @@ -423,7 +423,7 @@ int pe_hash(int fd, if ((uint64_t) st.st_size - p < le32toh(certificate_table->Size)) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "No space for certificate table, refusing."); - r = hash_file(fd, mdctx, p, st.st_size - p - le32toh(certificate_table->Size)); + r = hash_file(fd, mdctx, p, (uint64_t) st.st_size - p - le32toh(certificate_table->Size)); if (r < 0) return r; From 8fe512a02ea05e16abb575945e53b84e284b6bc5 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 18:50:49 +0000 Subject: [PATCH 0581/1296] stat-util: fix return type of mode_verify_socket() It returns an error code, not a mode Follow-up for 97fe03e12faa4e50d25a3ca8999967801c7e2da9 --- src/basic/stat-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index ac218d1552017..9adb43df51904 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -153,7 +153,7 @@ int is_symlink(const char *path) { return verify_stat_at(AT_FDCWD, path, false, stat_verify_symlink, false); } -static mode_t mode_verify_socket(mode_t mode) { +static int mode_verify_socket(mode_t mode) { if (S_ISLNK(mode)) return -ELOOP; From bf37ed0a659489c889ba9185f9f46c12c1ba7007 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 18:55:37 +0000 Subject: [PATCH 0582/1296] stat-util: add assert to silence coverity Coverity thinks _mntidb can be used uninitialized, but this is not the case when r == 0. Add a bool variable to make it clearer instead of reusing 'r' later, and an assert to guide static analyzers. CID#1644850 Follow-up for 5817c73391b5f3599c50df2c0873b26ea426f848 --- src/basic/stat-util.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 9adb43df51904..d04da52a78818 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -516,15 +516,17 @@ int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int fl goto fallback; } - if (r == 0) + bool have_unique_mntid = r > 0; + + if (!have_unique_mntid) mntida = _mntida; r = name_to_handle_at_try_fid( fdb, fileb, &hb, - r > 0 ? NULL : &_mntidb, /* if we managed to get unique mnt id for a, insist on that for b */ - r > 0 ? &mntidb : NULL, + have_unique_mntid ? NULL : &_mntidb, /* if we managed to get unique mnt id for a, insist on that for b */ + have_unique_mntid ? &mntidb : NULL, ntha_flags); if (r < 0) { if (is_name_to_handle_at_fatal_error(r)) @@ -532,8 +534,10 @@ int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int fl goto fallback; } - if (r == 0) + if (r == 0) { + assert(!have_unique_mntid); /* _mntidb was initialized by name_to_handle_at_try_fid() */ mntidb = _mntidb; + } /* Now compare the two file handles */ if (!file_handle_equal(ha, hb)) From 3e889473c9c0c16fe13c7e869203df1c274a0a2e Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 19:05:19 +0000 Subject: [PATCH 0583/1296] resolved: fix TOCTOU in hook discovery Coverity complains that the directory is not pinned by FD so it might changed between the stat and the open CID#1643236 Follow-up for 8209f4adcde08d225f56269e608ccd5f6704cd70 --- src/resolve/resolved-hook.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/resolve/resolved-hook.c b/src/resolve/resolved-hook.c index 4938e2d2a104d..9625e64fe25c7 100644 --- a/src/resolve/resolved-hook.c +++ b/src/resolve/resolved-hook.c @@ -391,19 +391,6 @@ static int manager_hook_discover(Manager *m) { usec_t seen_usec = now(CLOCK_MONOTONIC); - struct stat st; - if (stat(dp, &st) < 0) { - if (errno == ENOENT) - r = 0; - else - r = log_warning_errno(errno, "Failed to stat %s/: %m", dp); - - goto finish; - } - - if (stat_inode_unmodified(&st, &m->hook_stat)) - return 0; - d = opendir(dp); if (!d) { if (errno == ENOENT) @@ -414,6 +401,15 @@ static int manager_hook_discover(Manager *m) { goto finish; } + struct stat st; + if (fstat(dirfd(d), &st) < 0) { + r = log_warning_errno(errno, "Failed to fstat %s/: %m", dp); + goto finish; + } + + if (stat_inode_unmodified(&st, &m->hook_stat)) + return 0; + for (;;) { errno = 0; struct dirent *de = readdir_no_dot(d); From 86fd0337c652b04755008cdca23e2d9c727fa9a9 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 19:35:36 +0000 Subject: [PATCH 0584/1296] scsi_id: use strscpy instead of strncpy for wwn fields strncpy does not null-terminate the destination buffer if the source string is longer than the count parameter. Since wwn and wwn_vendor_extension are char[17] and we copy up to 16 bytes, there's a risk of missing null termination. Use strscpy which always null-terminates. CID#1469706 Follow-up for 4e9fdfccbdd16f0cfdb5c8fa8484a8ba0f2e69d3 --- src/udev/scsi_id/scsi_serial.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/udev/scsi_id/scsi_serial.c b/src/udev/scsi_id/scsi_serial.c index 20caf695bf47a..82557e3b057a9 100644 --- a/src/udev/scsi_id/scsi_serial.c +++ b/src/udev/scsi_id/scsi_serial.c @@ -21,6 +21,7 @@ #include "scsi.h" #include "scsi_id.h" #include "string-util.h" +#include "strxcpyx.h" #include "time-util.h" /* @@ -517,9 +518,9 @@ static int check_fill_0x83_id(struct scsi_id_device *dev_scsi, strcpy(serial_short, serial + s); if (id_search->id_type == SCSI_ID_NAA && wwn != NULL) { - strncpy(wwn, serial + s, 16); + strscpy(wwn, 17, serial + s); if (wwn_vendor_extension) - strncpy(wwn_vendor_extension, serial + s + 16, 16); + strscpy(wwn_vendor_extension, 17, serial + s + 16); } return 0; From 1929226e7e649b72f3f9acd464eaac771c00945c Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 19:47:27 +0000 Subject: [PATCH 0585/1296] debug-generator: use unsigned bit shift for breakpoint flags Using signed int literal '1' in left shift can lead to undefined behavior if the shift amount causes overflow of a signed int. Use UINT32_C(1) since the result is stored in a uint32_t variable. CID#1568482 Follow-up for e9f781a5a4721d3e58798b37e30bb4dcdbe54c02 --- src/debug-generator/debug-generator.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/debug-generator/debug-generator.c b/src/debug-generator/debug-generator.c index 878e1152328ae..e3b7768fbc8cf 100644 --- a/src/debug-generator/debug-generator.c +++ b/src/debug-generator/debug-generator.c @@ -101,7 +101,7 @@ static int parse_breakpoint_from_string(const char *s, uint32_t *ret_breakpoints FOREACH_ELEMENT(i, breakpoint_info_table) if (FLAGS_SET(i->validity, BREAKPOINT_DEFAULT) && breakpoint_applies(i, INT_MAX)) { - breakpoints |= 1 << i->type; + breakpoints |= UINT32_C(1) << i->type; found_default = true; break; } @@ -127,7 +127,7 @@ static int parse_breakpoint_from_string(const char *s, uint32_t *ret_breakpoints } if (breakpoint_applies(&breakpoint_info_table[tt], LOG_WARNING)) - breakpoints |= 1 << tt; + breakpoints |= UINT32_C(1) << tt; } *ret_breakpoints = breakpoints; From be85048e280bd99ffe0e4ed8ea8695a2faf4f1ed Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 19:49:20 +0000 Subject: [PATCH 0586/1296] cpu-set-util: add asserts to guide static analysis after realloc Coverity flags CPU_SET_S() calls as potential out-of-bounds writes because it cannot trace that cpu_set_realloc() guarantees the allocated buffer is large enough for the given index. Add asserts to make the size invariant explicit. CID#1611787 CID#1611788 Follow-up for 0985c7c4e22c8dbbea4398cf3453da45ebf63800 --- src/shared/cpu-set-util.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/shared/cpu-set-util.c b/src/shared/cpu-set-util.c index e4ef36da9aaba..9211dbe47e54a 100644 --- a/src/shared/cpu-set-util.c +++ b/src/shared/cpu-set-util.c @@ -159,6 +159,8 @@ int cpu_set_add(CPUSet *c, size_t i) { if (r < 0) return r; + /* Silence static analyzers */ + assert(i / CHAR_BIT < c->allocated); CPU_SET_S(i, c->allocated, c->set); return 0; } @@ -194,6 +196,8 @@ int cpu_set_add_range(CPUSet *c, size_t start, size_t end) { if (r < 0) return r; + /* Silence static analyzers */ + assert(end / CHAR_BIT < c->allocated); for (size_t i = start; i <= end; i++) CPU_SET_S(i, c->allocated, c->set); From c8b53fcfd3463679e6475e9b57b61a97dac1a287 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 19:52:09 +0000 Subject: [PATCH 0587/1296] sd-event: add assert to help static analysis trace signal bounds Coverity flags the signal_sources array access as a potential out-of-bounds read because it cannot trace through the SIGNAL_VALID() macro to know that ssi_signo < _NSIG. Add an explicit assert after the runtime check to make the constraint visible to static analyzers. CID#1548033 Follow-up for 7a64c5f23efbb51fe4f1229c1a8aed6dd858a0a9 --- src/libsystemd/sd-event/sd-event.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index b78cfe86fa40e..6867385e92a64 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -3807,6 +3807,9 @@ static int process_signal(sd_event *e, struct signal_data *d, uint32_t events, i if (_unlikely_(!SIGNAL_VALID(si.ssi_signo))) return -EIO; + /* Silence static analyzers */ + assert(si.ssi_signo < _NSIG); + if (e->signal_sources) s = e->signal_sources[si.ssi_signo]; if (!s) From 1b7d2fa978d76a1caed7dc9e615e403a29ff6971 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 19:55:35 +0000 Subject: [PATCH 0588/1296] uid-range: add asserts to document overflow safety in coalesce Coverity flags the x->start + x->nr and y->start + y->nr additions as potential overflows. These are safe because uid_range_add_internal() validates start + nr <= UINT32_MAX before inserting entries. Add asserts to document this invariant for static analyzers. CID#1548015 Follow-up for 8530dc4467691a893aa2e07319b18a84fec96cad --- src/basic/uid-range.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/basic/uid-range.c b/src/basic/uid-range.c index 31305952ba43c..628710a8709bc 100644 --- a/src/basic/uid-range.c +++ b/src/basic/uid-range.c @@ -63,6 +63,10 @@ static void uid_range_coalesce(UIDRange *range) { break; begin = MIN(x->start, y->start); + + /* Silence static analyzers, overflow is prevented by uid_range_add_internal() */ + assert(x->start <= UINT32_MAX - x->nr); + assert(y->start <= UINT32_MAX - y->nr); end = MAX(x->start + x->nr, y->start + y->nr); x->start = begin; From c34169e9d3048b7be6a7717860999029b58392df Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 20:08:55 +0000 Subject: [PATCH 0589/1296] sd-bus: add asserts for rbuffer_size accumulation bounds Coverity flags rbuffer_size += k as a potential overflow, but k is always bounded by the iov size (which is the difference between the allocated buffer and current rbuffer_size). Add asserts to make this invariant explicit. CID#1548044 CID#1548071 Follow-up for a7e3212d89d5aefee67de79c1e7eaccf2f5645ac --- src/libsystemd/sd-bus/bus-socket.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libsystemd/sd-bus/bus-socket.c b/src/libsystemd/sd-bus/bus-socket.c index 4cac317dffca6..3c6a2b2747fb9 100644 --- a/src/libsystemd/sd-bus/bus-socket.c +++ b/src/libsystemd/sd-bus/bus-socket.c @@ -676,6 +676,8 @@ static int bus_socket_read_auth(sd_bus *b) { return -ECONNRESET; } + /* Silence static analyzers, k is bounded by iov size: n - rbuffer_size */ + assert((size_t) k <= n - b->rbuffer_size); b->rbuffer_size += k; if (handle_cmsg) { @@ -1453,6 +1455,8 @@ int bus_socket_read_message(sd_bus *bus) { return -EXFULL; } + /* Silence static analyzers, k is bounded by iov size: need - rbuffer_size */ + assert((size_t) k <= need - bus->rbuffer_size); bus->rbuffer_size += k; if (handle_cmsg) { From 2bd6930efeace1ef0bae4683ef38e9ad6f74dcd0 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 20:10:14 +0000 Subject: [PATCH 0590/1296] importd: add assert for log_message_size accumulation bounds Coverity flags log_message_size += l as a potential overflow, but l is bounded by the read() count parameter which is sizeof(log_message) - log_message_size. Add an assert to make this invariant explicit. CID#1548062 Follow-up for 3d7415f43f0fe6a821d7bc4a341ba371e8a30ef3 --- src/import/importd.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/import/importd.c b/src/import/importd.c index d3363d446cb67..fed2af417019f 100644 --- a/src/import/importd.c +++ b/src/import/importd.c @@ -418,6 +418,8 @@ static int transfer_on_log(sd_event_source *s, int fd, uint32_t revents, void *u return 0; } + /* Silence static analyzers, l is bounded by read() count: sizeof - log_message_size */ + assert((size_t) l <= sizeof(t->log_message) - t->log_message_size); t->log_message_size += l; transfer_send_logs(t, false); From cda2962daf6fb9938237001ad8416ea3c7fff06b Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 20:13:03 +0000 Subject: [PATCH 0591/1296] compress: add assert for space doubling overflow safety Coverity flags 2 * space as a potential overflow. The space value is bounded by prior allocation success, but add an explicit assert to document this for static analyzers. CID#1548056 Follow-up for 5e592c66bdf76dfc8445b332f7a5088ca504ee90 --- src/basic/compress.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/basic/compress.c b/src/basic/compress.c index 5c9ca829dfef3..d9759ad417fba 100644 --- a/src/basic/compress.c +++ b/src/basic/compress.c @@ -398,6 +398,8 @@ int decompress_blob_xz( return -ENOBUFS; used = space - s.avail_out; + /* Silence static analyzers, space is bounded by allocation size */ + assert(space <= SIZE_MAX / 2); space = MIN(2 * space, dst_max ?: SIZE_MAX); if (!greedy_realloc(dst, space, 1)) return -ENOMEM; From 3e38052f1ddda29ee0d7b8a235fd9bebc39774c0 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 20:13:40 +0000 Subject: [PATCH 0592/1296] exec-util: use unsigned shift for ExecCommandFlags Using signed int literal '1' in left shift operations can theoretically lead to undefined behavior. Use 1U to be explicit about unsigned arithmetic. CID#1548018 Follow-up for b3d593673c5b8b0b7d781fd26ab2062ca6e7dbdb --- src/shared/exec-util.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/exec-util.c b/src/shared/exec-util.c index 2e15f311b8853..420803c77d43d 100644 --- a/src/shared/exec-util.c +++ b/src/shared/exec-util.c @@ -500,7 +500,7 @@ assert_cc((1 << ELEMENTSOF(exec_command_strings)) - 1 == _EXEC_COMMAND_FLAGS_ALL const char* exec_command_flags_to_string(ExecCommandFlags i) { for (size_t idx = 0; idx < ELEMENTSOF(exec_command_strings); idx++) - if (i == (1 << idx)) + if (i == (ExecCommandFlags) (1U << idx)) return exec_command_strings[idx]; return NULL; @@ -516,7 +516,7 @@ ExecCommandFlags exec_command_flags_from_string(const char *s) { if (idx < 0) return _EXEC_COMMAND_FLAGS_INVALID; - return 1 << idx; + return 1U << idx; } int fexecve_or_execve(int executable_fd, const char *executable, char *const argv[], char *const envp[]) { From 4b25c74c20b064143dd3367ddb26fefff1e2ebbf Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 20:24:22 +0000 Subject: [PATCH 0593/1296] recurse-dir: add assert_cc for DIRENT_SIZE_MAX allocation Coverity flags offsetof(DirectoryEntries, buffer) + DIRENT_SIZE_MAX * 8 as a potential overflow. All operands are compile-time constants, so add an assert_cc() to prove this at build time. CID#1548020 Follow-up for 6393b847f459dba14d2b615ee93babb143168b57 --- src/basic/recurse-dir.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/basic/recurse-dir.c b/src/basic/recurse-dir.c index 0efa731868e6f..bc3c32afe6954 100644 --- a/src/basic/recurse-dir.c +++ b/src/basic/recurse-dir.c @@ -41,6 +41,8 @@ int readdir_all(int dir_fd, RecurseDirFlags flags, DirectoryEntries **ret) { * Start with space for up to 8 directory entries. We expect at least 2 ("." + ".."), hence hopefully * 8 will cover most cases comprehensively. (Note that most likely a lot more entries will actually * fit in the buffer, given we calculate maximum file name length here.) */ + /* Silence static analyzers */ + assert_cc(offsetof(DirectoryEntries, buffer) <= SIZE_MAX - DIRENT_SIZE_MAX * 8); de = malloc(offsetof(DirectoryEntries, buffer) + DIRENT_SIZE_MAX * 8); if (!de) return -ENOMEM; From f377be7081f1f04269a9bb4191a4f1c2593272f8 Mon Sep 17 00:00:00 2001 From: Adrian Wannenmacher Date: Sat, 28 Mar 2026 20:55:19 +0100 Subject: [PATCH 0594/1296] fix list of inhibitor lock types Markdown and HTML don't support mixing ordered and unordered items within a single list. This means the previous syntax actually produced three separate lists. Also, markdown converters don't necesarrily respect the first number in an ordered list, and may just overwrite it to one. This is the case for the one that generates the systemd.io page. And even if that wasn't the case, the numbering of the second ordered list would be off by one. --- docs/INHIBITOR_LOCKS.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/INHIBITOR_LOCKS.md b/docs/INHIBITOR_LOCKS.md index 220c085e09a43..80f09d21e09db 100644 --- a/docs/INHIBITOR_LOCKS.md +++ b/docs/INHIBITOR_LOCKS.md @@ -26,12 +26,10 @@ Seven distinct inhibitor lock types may be taken, or a combination of them: 1. _sleep_ inhibits system suspend and hibernation requested by (unprivileged) **users** 2. _shutdown_ inhibits high-level system power-off and reboot requested by (unprivileged) **users** 3. _idle_ inhibits that the system goes into idle mode, possibly resulting in **automatic** system suspend or shutdown depending on configuration. - -- _handle-power-key_ inhibits the low-level (i.e. logind-internal) handling of the system power **hardware** key, allowing (possibly unprivileged) external code to handle the event instead. - -4. Similar, _handle-suspend-key_ inhibits the low-level handling of the system **hardware** suspend key. -5. Similar, _handle-hibernate-key_ inhibits the low-level handling of the system **hardware** hibernate key. -6. Similar, _handle-lid-switch_ inhibits the low-level handling of the systemd **hardware** lid switch. +4. _handle-power-key_ inhibits the low-level (i.e. logind-internal) handling of the system power **hardware** key, allowing (possibly unprivileged) external code to handle the event instead. +5. Similar, _handle-suspend-key_ inhibits the low-level handling of the system **hardware** suspend key. +6. Similar, _handle-hibernate-key_ inhibits the low-level handling of the system **hardware** hibernate key. +7. Similar, _handle-lid-switch_ inhibits the low-level handling of the systemd **hardware** lid switch. Two different modes of locks are supported: From 421bdc489f303800789c86e2f7f74526d0ae9d9a Mon Sep 17 00:00:00 2001 From: Valentin David Date: Mon, 16 Mar 2026 22:21:55 +0100 Subject: [PATCH 0595/1296] repart: Make it possible to set persistent allow-discards activation flag AllowDiscards= will set allow-discards in the persistent flags which will make activating the device automatically activate with that option. This is useful for devices discovered through gpt-auto-generator without needing to use some kernel command line to set the option. --- man/repart.d.xml | 16 +++++++++++++ src/repart/repart.c | 30 ++++++++++++++++++++++- src/shared/cryptsetup-util.c | 4 ++++ src/shared/cryptsetup-util.h | 2 ++ test/units/TEST-58-REPART.sh | 46 ++++++++++++++++++++++++++++++++++++ 5 files changed, 97 insertions(+), 1 deletion(-) diff --git a/man/repart.d.xml b/man/repart.d.xml index 7d3dc4e04b254..d0992830b7825 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -679,6 +679,22 @@ + + Discard= + + Takes a boolean argument. The default is no if + is used for the invocation of systemd-repart + or if Integrity=inline is set. It is yes otherwise. + + If set to yes, when creating the LUKS2 superblock for the partition, the + allow-discards activation flag will be set so that future activations will allow + discards by default. + + This option has no effect if the partition already exists or if Encrypt=off is used. + + + + Verity= diff --git a/src/repart/repart.c b/src/repart/repart.c index 7dbcfd6d5824e..5b4d8019587f7 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -472,6 +472,7 @@ typedef struct Partition { char *compression; char *compression_level; uint64_t fs_sector_size; + int discard; int add_validatefs; CopyFiles *copy_files; @@ -728,6 +729,7 @@ static Partition *partition_new(Context *c) { .last_percent = UINT_MAX, .progress_ratelimit = { 100 * USEC_PER_MSEC, 1 }, .fs_sector_size = UINT64_MAX, + .discard = -1, }; return p; @@ -851,6 +853,7 @@ static void partition_foreignize(Partition *p) { p->verity = VERITY_OFF; p->add_validatefs = false; p->fs_sector_size = UINT64_MAX; + p->discard = -1; partition_mountpoint_free_many(p->mountpoints, p->n_mountpoints); p->mountpoints = NULL; @@ -2882,6 +2885,7 @@ static int partition_read_definition( { "Partition", "SupplementFor", config_parse_string, 0, &p->supplement_for_name }, { "Partition", "AddValidateFS", config_parse_tristate, 0, &p->add_validatefs }, { "Partition", "FileSystemSectorSize", config_parse_fs_sector_size, 0, &p->fs_sector_size }, + { "Partition", "Discard", config_parse_tristate, 0, &p->discard }, {} }; _cleanup_free_ char *filename = NULL; @@ -3032,6 +3036,14 @@ static int partition_read_definition( "SupplementFor= cannot be combined with CopyBlocks=/Encrypt=/Verity="); } + if (p->encrypt == ENCRYPT_OFF && p->discard > 0) + log_syntax(NULL, LOG_WARNING, path, 1, 0, + "Discard=yes has no effect with Encrypt=off."); + + if (p->encrypt != ENCRYPT_OFF && p->integrity == INTEGRITY_INLINE && p->discard > 0) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "Integrity=inline is incompatible with Discard=yes."); + /* Verity partitions are read only, let's imply the RO flag hence, unless explicitly configured otherwise. */ if ((partition_designator_is_verity_hash(p->type.designator) || partition_designator_is_verity_sig(p->type.designator) || @@ -5225,6 +5237,22 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta if (r < 0) return log_error_errno(r, "Failed to LUKS2 format future partition: %m"); + bool allow_discards = p->integrity != INTEGRITY_INLINE && (arg_discard ? p->discard != 0 : p->discard > 0); + if (allow_discards) { + uint32_t flags; + + r = sym_crypt_persistent_flags_get(cd, CRYPT_FLAGS_ACTIVATION, &flags); + if (r < 0) + return log_error_errno(r, "Failed to get persistent activation flags for %s: %m", node); + + if (!FLAGS_SET(flags, CRYPT_ACTIVATE_ALLOW_DISCARDS)) { + flags |= CRYPT_ACTIVATE_ALLOW_DISCARDS; + r = sym_crypt_persistent_flags_set(cd, CRYPT_FLAGS_ACTIVATION, flags); + if (r < 0) + return log_error_errno(r, "Failed to set persistent activation flags for %s: %m", node); + } + } + if (p->encrypted_volume && p->encrypted_volume->fixate_volume_key) { _cleanup_free_ char *key_id = NULL, *hash_option = NULL; @@ -5543,7 +5571,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta dm_name, NULL, /* volume_key_size= */ volume_key_size, - (arg_discard && p->integrity != INTEGRITY_INLINE ? CRYPT_ACTIVATE_ALLOW_DISCARDS : 0) | CRYPT_ACTIVATE_PRIVATE); + (allow_discards ? CRYPT_ACTIVATE_ALLOW_DISCARDS : 0) | CRYPT_ACTIVATE_PRIVATE); if (r < 0) return log_error_errno(r, "Failed to activate LUKS superblock: %m"); diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c index 5058bca3fd83e..2ffd1b63bb2d5 100644 --- a/src/shared/cryptsetup-util.c +++ b/src/shared/cryptsetup-util.c @@ -42,6 +42,8 @@ DLSYM_PROTOTYPE(crypt_keyslot_destroy) = NULL; DLSYM_PROTOTYPE(crypt_keyslot_max) = NULL; DLSYM_PROTOTYPE(crypt_load) = NULL; DLSYM_PROTOTYPE(crypt_metadata_locking) = NULL; +DLSYM_PROTOTYPE(crypt_persistent_flags_get) = NULL; +DLSYM_PROTOTYPE(crypt_persistent_flags_set) = NULL; DLSYM_PROTOTYPE(crypt_reencrypt_init_by_passphrase) = NULL; DLSYM_PROTOTYPE(crypt_reencrypt_run); DLSYM_PROTOTYPE(crypt_resize) = NULL; @@ -302,6 +304,8 @@ int dlopen_cryptsetup(void) { DLSYM_ARG(crypt_keyslot_max), DLSYM_ARG(crypt_load), DLSYM_ARG(crypt_metadata_locking), + DLSYM_ARG(crypt_persistent_flags_get), + DLSYM_ARG(crypt_persistent_flags_set), DLSYM_ARG(crypt_reencrypt_init_by_passphrase), DLSYM_ARG(crypt_reencrypt_run), DLSYM_ARG(crypt_resize), diff --git a/src/shared/cryptsetup-util.h b/src/shared/cryptsetup-util.h index e9be8249fa1a0..2e3ffe4c9e384 100644 --- a/src/shared/cryptsetup-util.h +++ b/src/shared/cryptsetup-util.h @@ -30,6 +30,8 @@ extern DLSYM_PROTOTYPE(crypt_keyslot_destroy); extern DLSYM_PROTOTYPE(crypt_keyslot_max); extern DLSYM_PROTOTYPE(crypt_load); extern DLSYM_PROTOTYPE(crypt_metadata_locking); +extern DLSYM_PROTOTYPE(crypt_persistent_flags_get); +extern DLSYM_PROTOTYPE(crypt_persistent_flags_set); extern DLSYM_PROTOTYPE(crypt_reencrypt_init_by_passphrase); extern DLSYM_PROTOTYPE(crypt_reencrypt_run); extern DLSYM_PROTOTYPE(crypt_resize); diff --git a/test/units/TEST-58-REPART.sh b/test/units/TEST-58-REPART.sh index f25cf42c4b3ee..546df29f44aa8 100755 --- a/test/units/TEST-58-REPART.sh +++ b/test/units/TEST-58-REPART.sh @@ -380,6 +380,8 @@ $imgs/zzz7 : start= 6291416, size= 131072, type=3B8F8425-20E0-4F3B-907F loop="$(losetup -P --show --find "$imgs/zzz")" udevadm wait --timeout=60 --settle "${loop:?}p7" + cryptsetup luksDump "${loop}p7" | grep 'Flags:[[:space:]]*allow-discards' >/dev/null + volume="test-repart-$RANDOM" touch "$imgs/empty-password" @@ -396,6 +398,50 @@ $imgs/zzz7 : start= 6291416, size= 131072, type=3B8F8425-20E0-4F3B-907F PASSWORD="" systemd-dissect "$imgs/zzz" -M "$imgs/mount" udevadm info /dev/disk/by-label/schrupfel | grep ID_FS_TYPE=crypto_LUKS >/dev/null systemd-dissect -U "$imgs/mount" + + echo "*** 7. Testing Discard=no ***" + + tee "$defs/extra4.conf" </dev/null + losetup -d "$loop" } testcase_dropin() { From a0ba770edc2ab97e3cc3d2325e6bd68dc7ea14fe Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Sun, 29 Mar 2026 14:15:05 +0200 Subject: [PATCH 0596/1296] man/systemd-repart: remove extra pipe character in manpage Signed-off-by: Morten Linderud --- man/systemd-repart.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/systemd-repart.xml b/man/systemd-repart.xml index dac4759538446..2af431067f16c 100644 --- a/man/systemd-repart.xml +++ b/man/systemd-repart.xml @@ -832,7 +832,7 @@ systemd-repart --definitions repart.d \ --copy-source=/tmp/tree/ \ --empty=create --size=600M \ --json=short \ - /tmp/img.raw | | jq --raw-output0 .[-1].roothash > /tmp/img.roothash + /tmp/img.raw | jq --raw-output0 .[-1].roothash > /tmp/img.roothash openssl smime -sign -in /tmp/img.roothash \ -inkey verity-private-key.pem \ From 3242308ce32f0842a30eb9abb89e5f28de0cf9fc Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Sun, 29 Mar 2026 14:20:28 +0200 Subject: [PATCH 0597/1296] man/systemd-repart: quote jq expression Some shells will try to parse this, or expand it, causing an error. Lets quote it so it's simpler for people. Signed-off-by: Morten Linderud --- man/systemd-repart.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/systemd-repart.xml b/man/systemd-repart.xml index 2af431067f16c..18e127be4648d 100644 --- a/man/systemd-repart.xml +++ b/man/systemd-repart.xml @@ -832,7 +832,7 @@ systemd-repart --definitions repart.d \ --copy-source=/tmp/tree/ \ --empty=create --size=600M \ --json=short \ - /tmp/img.raw | jq --raw-output0 .[-1].roothash > /tmp/img.roothash + /tmp/img.raw | jq --raw-output0 ".[-1].roothash" > /tmp/img.roothash openssl smime -sign -in /tmp/img.roothash \ -inkey verity-private-key.pem \ From ab3a2f375f63b6966b6b053c674b71a14d6a5965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 28 Mar 2026 16:54:21 +0100 Subject: [PATCH 0598/1296] sd-varlink: export sd_varlink_set_sentinel I tried to implement a varlink service using sd-varlink, and not being able to use the approach with sentinel is exteremely painful. This is useful internally and likewise externally. --- src/bootctl/bootctl-status.c | 3 +-- src/core/varlink-dynamic-user.c | 5 ++--- src/core/varlink-manager.c | 2 +- src/core/varlink-unit.c | 2 +- src/home/homed-varlink.c | 7 +++---- src/import/importd.c | 2 +- src/journal/journalctl-varlink-server.c | 3 +-- src/libsystemd/libsystemd.sym | 5 +++++ src/libsystemd/sd-varlink/sd-varlink.c | 25 ++++++++++++++++++++++++ src/libsystemd/sd-varlink/varlink-util.c | 25 ------------------------ src/libsystemd/sd-varlink/varlink-util.h | 2 -- src/machine/machined-varlink.c | 4 ++-- src/pcrlock/pcrlock.c | 2 +- src/repart/repart.c | 2 +- src/shared/metrics.c | 4 ++-- src/sysext/sysext.c | 2 +- src/systemd/sd-varlink.h | 5 +++++ src/test/test-varlink.c | 12 ++++++------ src/userdb/userwork.c | 6 +++--- 19 files changed, 61 insertions(+), 57 deletions(-) diff --git a/src/bootctl/bootctl-status.c b/src/bootctl/bootctl-status.c index 178bffb36522c..2d694885e176a 100644 --- a/src/bootctl/bootctl-status.c +++ b/src/bootctl/bootctl-status.c @@ -22,7 +22,6 @@ #include "pretty-print.h" #include "string-util.h" #include "tpm2-util.h" -#include "varlink-util.h" static int status_entries( const BootConfig *config, @@ -705,7 +704,7 @@ int vl_method_list_boot_entries(sd_varlink *link, sd_json_variant *parameters, s if (r < 0) return r; - r = varlink_set_sentinel(link, "io.systemd.BootControl.NoSuchBootEntry"); + r = sd_varlink_set_sentinel(link, "io.systemd.BootControl.NoSuchBootEntry"); if (r < 0) return r; diff --git a/src/core/varlink-dynamic-user.c b/src/core/varlink-dynamic-user.c index 3f27a1f89140f..c7e851d005242 100644 --- a/src/core/varlink-dynamic-user.c +++ b/src/core/varlink-dynamic-user.c @@ -10,7 +10,6 @@ #include "uid-classification.h" #include "user-util.h" #include "varlink-dynamic-user.h" -#include "varlink-util.h" typedef struct LookupParameters { const char *user_name; @@ -78,7 +77,7 @@ int vl_method_get_user_record(sd_varlink *link, sd_json_variant *parameters, sd_ if (!streq_ptr(p.service, "io.systemd.DynamicUser")) return sd_varlink_error(link, "io.systemd.UserDatabase.BadService", NULL); - r = varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); if (r < 0) return r; @@ -182,7 +181,7 @@ int vl_method_get_group_record(sd_varlink *link, sd_json_variant *parameters, sd if (r != 0) return r; - r = varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); if (r < 0) return r; diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index 8082b000aaedb..0cbe26d5d588f 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -334,7 +334,7 @@ int vl_method_enqueue_marked_jobs_manager(sd_varlink *link, sd_json_variant *par if (r <= 0) return r; - r = varlink_set_sentinel(link, NULL); + r = sd_varlink_set_sentinel(link, NULL); if (r < 0) return r; diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index d222148c77c3d..2a2b75c8a9f03 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -514,7 +514,7 @@ int vl_method_list_units(sd_varlink *link, sd_json_variant *parameters, sd_varli if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)) return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, NULL); - r = varlink_set_sentinel(link, VARLINK_ERROR_UNIT_NO_SUCH_UNIT); + r = sd_varlink_set_sentinel(link, VARLINK_ERROR_UNIT_NO_SUCH_UNIT); if (r < 0) return r; diff --git a/src/home/homed-varlink.c b/src/home/homed-varlink.c index fb23dc9cde290..1bf4c795695ba 100644 --- a/src/home/homed-varlink.c +++ b/src/home/homed-varlink.c @@ -14,7 +14,6 @@ #include "user-record.h" #include "user-record-util.h" #include "user-util.h" -#include "varlink-util.h" typedef struct LookupParameters { const char *user_name; @@ -104,7 +103,7 @@ int vl_method_get_user_record(sd_varlink *link, sd_json_variant *parameters, sd_ if (!streq_ptr(p.service, m->userdb_service)) return sd_varlink_error(link, "io.systemd.UserDatabase.BadService", NULL); - r = varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); if (r < 0) return r; @@ -212,7 +211,7 @@ int vl_method_get_group_record(sd_varlink *link, sd_json_variant *parameters, sd if (!streq_ptr(p.service, m->userdb_service)) return sd_varlink_error(link, "io.systemd.UserDatabase.BadService", NULL); - r = varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); if (r < 0) return r; @@ -277,7 +276,7 @@ int vl_method_get_memberships(sd_varlink *link, sd_json_variant *parameters, sd_ if (!streq_ptr(p.service, m->userdb_service)) return sd_varlink_error(link, "io.systemd.UserDatabase.BadService", NULL); - r = varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); if (r < 0) return r; diff --git a/src/import/importd.c b/src/import/importd.c index d3363d446cb67..0de1b21fa510b 100644 --- a/src/import/importd.c +++ b/src/import/importd.c @@ -1837,7 +1837,7 @@ static int vl_method_list_transfers(sd_varlink *link, sd_json_variant *parameter if (r != 0) return r; - r = varlink_set_sentinel(link, "io.systemd.Import.NoTransfers"); + r = sd_varlink_set_sentinel(link, "io.systemd.Import.NoTransfers"); if (r < 0) return r; diff --git a/src/journal/journalctl-varlink-server.c b/src/journal/journalctl-varlink-server.c index f44e2a807cfcb..85b4e225f5137 100644 --- a/src/journal/journalctl-varlink-server.c +++ b/src/journal/journalctl-varlink-server.c @@ -14,7 +14,6 @@ #include "strv.h" #include "unit-name.h" /* IWYU pragma: keep */ #include "user-util.h" -#include "varlink-util.h" typedef struct GetEntriesParameters { char **units; @@ -100,7 +99,7 @@ int vl_method_get_entries(sd_varlink *link, sd_json_variant *parameters, sd_varl if (r < 0) return r; - r = varlink_set_sentinel(link, "io.systemd.JournalAccess.NoEntries"); + r = sd_varlink_set_sentinel(link, "io.systemd.JournalAccess.NoEntries"); if (r < 0) return r; diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym index aa270a483a40e..e90a9460e28e0 100644 --- a/src/libsystemd/libsystemd.sym +++ b/src/libsystemd/libsystemd.sym @@ -1090,3 +1090,8 @@ LIBSYSTEMD_260 { global: sd_session_get_extra_device_access; } LIBSYSTEMD_259; + +LIBSYSTEMD_261 { +global: + sd_varlink_set_sentinel; +} LIBSYSTEMD_260; diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index c1ffaedfc8077..9938ca7063dc4 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -2945,6 +2945,31 @@ _public_ void* sd_varlink_get_userdata(sd_varlink *v) { return v->userdata; } +_public_ int sd_varlink_set_sentinel(sd_varlink *v, const char *error_id) { + assert_return(v, -EINVAL); + + /* If the caller doesn't want a reply, then don't set a sentinel. */ + if (v->state == VARLINK_PROCESSING_METHOD_ONEWAY) + return 0; + + /* This has to be called during a callback, and not after it has exited. */ + assert_return(IN_SET(v->state, VARLINK_PROCESSING_METHOD, VARLINK_PROCESSING_METHOD_MORE), + -EUCLEAN); + + char *s = NULL; + if (error_id) { + s = strdup(error_id); + if (!s) + return log_oom_debug(); + } + + if (v->sentinel != POINTER_MAX) + free(v->sentinel); + + v->sentinel = s ?: POINTER_MAX; + return 0; +} + static int varlink_acquire_ucred(sd_varlink *v) { int r; diff --git a/src/libsystemd/sd-varlink/varlink-util.c b/src/libsystemd/sd-varlink/varlink-util.c index 916ac2ba996fe..83921c57e0a15 100644 --- a/src/libsystemd/sd-varlink/varlink-util.c +++ b/src/libsystemd/sd-varlink/varlink-util.c @@ -205,31 +205,6 @@ int varlink_check_privileged_peer(sd_varlink *vl) { return 0; } -int varlink_set_sentinel(sd_varlink *v, const char *error_id) { - _cleanup_free_ char *s = NULL; - - assert(v); - - /* If the caller doesn't want a reply, then don't set a sentinel. */ - if (v->state == VARLINK_PROCESSING_METHOD_ONEWAY) - return 0; - - /* This has to be called during a callback, and not after it has exited. */ - assert(IN_SET(v->state, VARLINK_PROCESSING_METHOD, VARLINK_PROCESSING_METHOD_MORE)); - - if (error_id) { - s = strdup(error_id); - if (!s) - return -ENOMEM; - } - - if (v->sentinel != POINTER_MAX) - free(v->sentinel); - - v->sentinel = s ? TAKE_PTR(s) : POINTER_MAX; - return 0; -} - DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( varlink_hash_ops, void, diff --git a/src/libsystemd/sd-varlink/varlink-util.h b/src/libsystemd/sd-varlink/varlink-util.h index dee79555ce921..ba0f23225356a 100644 --- a/src/libsystemd/sd-varlink/varlink-util.h +++ b/src/libsystemd/sd-varlink/varlink-util.h @@ -28,6 +28,4 @@ int varlink_server_new( int varlink_check_privileged_peer(sd_varlink *vl); -int varlink_set_sentinel(sd_varlink *v, const char *error_id); - extern const struct hash_ops varlink_hash_ops; diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c index fb03ee953fb82..82b8ed93b37c2 100644 --- a/src/machine/machined-varlink.c +++ b/src/machine/machined-varlink.c @@ -534,7 +534,7 @@ static int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varl if (r != 0) return r; - r = varlink_set_sentinel(link, VARLINK_ERROR_MACHINE_NO_SUCH_MACHINE); + r = sd_varlink_set_sentinel(link, VARLINK_ERROR_MACHINE_NO_SUCH_MACHINE); if (r < 0) return r; @@ -681,7 +681,7 @@ static int vl_method_list_images(sd_varlink *link, sd_json_variant *parameters, if (r != 0) return r; - r = varlink_set_sentinel(link, VARLINK_ERROR_MACHINE_IMAGE_NO_SUCH_IMAGE); + r = sd_varlink_set_sentinel(link, VARLINK_ERROR_MACHINE_IMAGE_NO_SUCH_IMAGE); if (r < 0) return r; diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index c940ab01e8d9b..67303d032b019 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -5420,7 +5420,7 @@ static int vl_method_read_event_log(sd_varlink *link, sd_json_variant *parameter return r; // FIXME: We can't use a NULL sentinel here because the output fields in the IDL are non-nullable. - r = varlink_set_sentinel(link, NULL); + r = sd_varlink_set_sentinel(link, NULL); if (r < 0) return r; diff --git a/src/repart/repart.c b/src/repart/repart.c index 5b4d8019587f7..bae376fc0ee39 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -10427,7 +10427,7 @@ static int vl_method_list_candidate_devices( if (r < 0) return r; - r = varlink_set_sentinel(link, "io.systemd.Repart.NoCandidateDevices"); + r = sd_varlink_set_sentinel(link, "io.systemd.Repart.NoCandidateDevices"); if (r < 0) return r; diff --git a/src/shared/metrics.c b/src/shared/metrics.c index 75a81789584e9..6c1490cbab8c1 100644 --- a/src/shared/metrics.c +++ b/src/shared/metrics.c @@ -90,7 +90,7 @@ int metrics_method_describe( if (r != 0) return r; - r = varlink_set_sentinel(link, "io.systemd.Metrics.NoSuchMetric"); + r = sd_varlink_set_sentinel(link, "io.systemd.Metrics.NoSuchMetric"); if (r < 0) return r; @@ -127,7 +127,7 @@ int metrics_method_list( if (r != 0) return r; - r = varlink_set_sentinel(link, "io.systemd.Metrics.NoSuchMetric"); + r = sd_varlink_set_sentinel(link, "io.systemd.Metrics.NoSuchMetric"); if (r < 0) return r; diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 157bcb0a0b0db..20f0ceb3f031e 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -2778,7 +2778,7 @@ static int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varl if (r < 0) return r; - r = varlink_set_sentinel(link, "io.systemd.sysext.NoImagesFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.sysext.NoImagesFound"); if (r < 0) return r; diff --git a/src/systemd/sd-varlink.h b/src/systemd/sd-varlink.h index fff6ea8a36fd8..7cda9e7e56ef7 100644 --- a/src/systemd/sd-varlink.h +++ b/src/systemd/sd-varlink.h @@ -205,6 +205,11 @@ int sd_varlink_bind_reply(sd_varlink *v, sd_varlink_reply_t reply); void* sd_varlink_set_userdata(sd_varlink *v, void *userdata); void* sd_varlink_get_userdata(sd_varlink *v); +/* Queue a reply to be sent if no other reply was sent by a method callback. + * Useful when implementing services which send a (possibly empty) series + * of objects and terminate. */ +int sd_varlink_set_sentinel(sd_varlink *v, const char *error_id); + int sd_varlink_get_peer_uid(sd_varlink *v, uid_t *ret); int sd_varlink_get_peer_gid(sd_varlink *v, gid_t *ret); int sd_varlink_get_peer_pid(sd_varlink *v, pid_t *ret); diff --git a/src/test/test-varlink.c b/src/test/test-varlink.c index 186564198c0ad..3324421f68787 100644 --- a/src/test/test-varlink.c +++ b/src/test/test-varlink.c @@ -443,7 +443,7 @@ TEST(invalid_parameter) { 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(varlink_set_sentinel(link, "io.test.SentinelError")); + ASSERT_OK(sd_varlink_set_sentinel(link, "io.test.SentinelError")); return 0; } @@ -482,7 +482,7 @@ TEST(sentinel_error) { static int method_with_empty_sentinel(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { /* Set an empty sentinel and return without sending a reply. An empty reply should be sent automatically. */ - ASSERT_OK(varlink_set_sentinel(link, /* error_id= */ NULL)); + ASSERT_OK(sd_varlink_set_sentinel(link, /* error_id= */ NULL)); return 0; } @@ -522,7 +522,7 @@ TEST(sentinel_empty) { static int method_with_sentinel_but_reply(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { /* Set a sentinel but also send a reply. The sentinel should not be used. */ - ASSERT_OK(varlink_set_sentinel(link, "io.test.SentinelError")); + ASSERT_OK(sd_varlink_set_sentinel(link, "io.test.SentinelError")); return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRING("result", "explicit-reply")); } @@ -561,10 +561,10 @@ TEST(sentinel_with_explicit_reply) { } static int method_with_oneway_sentinel(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { - /* The method was called oneway, so varlink_set_sentinel() should be a no-op and the server should + /* The method was called oneway, so sd_varlink_set_sentinel() should be a no-op and the server should * transition back to idle without sending any reply. */ ASSERT_TRUE(FLAGS_SET(flags, SD_VARLINK_METHOD_ONEWAY)); - ASSERT_OK(varlink_set_sentinel(link, "io.test.SentinelError")); + ASSERT_OK(sd_varlink_set_sentinel(link, "io.test.SentinelError")); return 0; } @@ -619,7 +619,7 @@ static int method_with_fd_sentinel(sd_varlink *link, sd_json_variant *parameters /* Set a sentinel so sd_varlink_reply() defers sending: each reply and its pushed fds are captured in * the queue, and the last one is sent as the final reply when the callback returns. */ - ASSERT_OK(varlink_set_sentinel(link, /* error_id= */ NULL)); + ASSERT_OK(sd_varlink_set_sentinel(link, /* error_id= */ NULL)); /* First reply: push one fd with "alpha" content */ ASSERT_OK(fd1 = memfd_new_and_seal_string("data", "alpha")); diff --git a/src/userdb/userwork.c b/src/userdb/userwork.c index 6abb8795e602d..aa77cde86b353 100644 --- a/src/userdb/userwork.c +++ b/src/userdb/userwork.c @@ -172,7 +172,7 @@ static int vl_method_get_user_record(sd_varlink *link, sd_json_variant *paramete * we are done'; == 0 means 'not processed, caller should process now' */ return r; - r = varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); if (r < 0) return r; @@ -313,7 +313,7 @@ static int vl_method_get_group_record(sd_varlink *link, sd_json_variant *paramet if (r != 0) return r; - r = varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); if (r < 0) return r; @@ -401,7 +401,7 @@ static int vl_method_get_memberships(sd_varlink *link, sd_json_variant *paramete if (r != 0) return r; - r = varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); + r = sd_varlink_set_sentinel(link, "io.systemd.UserDatabase.NoRecordFound"); if (r < 0) return r; From 365361f4908d9cfa9a371cd2c52a19c2414cb554 Mon Sep 17 00:00:00 2001 From: Valentin David Date: Sat, 21 Mar 2026 15:42:13 +0100 Subject: [PATCH 0599/1296] repart: Optionally write minimal an El Torito boot catalog for EFI This only points the firmware to the ESP. The ISO9660 is empty. The initramfs should create a loop device to change block size and enable GPT partitions. This was tested using OVMF on qemu, with: `-drive if=pflash,file=OVMF_CODE.fd,readonly=on,format=raw -drive if=pflash,file=OVMF_VARS.fd,format=raw -drive if=none,id=live-disk,file=dick.iso,media=cdrom,format=raw,readonly=on -device virtio-scsi-pci,id=scsi -device scsi-cd,drive=live-disk` And a simple definition: ``` [Partition] Type=esp Format=vfat CopyFiles=/usr/lib/systemd/boot/efi/systemd-bootx64.efi:/EFI/BOOT/BOOTX64.EFI ``` --- man/systemd-repart.xml | 53 ++++++ src/repart/iso9660.c | 116 ++++++++++++ src/repart/iso9660.h | 172 ++++++++++++++++++ src/repart/meson.build | 5 +- src/repart/repart.c | 402 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 747 insertions(+), 1 deletion(-) create mode 100644 src/repart/iso9660.c create mode 100644 src/repart/iso9660.h diff --git a/man/systemd-repart.xml b/man/systemd-repart.xml index 18e127be4648d..0cb14b6991392 100644 --- a/man/systemd-repart.xml +++ b/man/systemd-repart.xml @@ -718,6 +718,59 @@ + + + + Write a minimal ISO9660 header with El Torito boot catalog. That will + boot the ESP on EFI firmware. + + The ISO9660 filesystem created by it will be empty. The initramfs is expected to create a + partitionable loop device on top of the device to change the block size and enable GPT + partitions. + + The disk requires at least one partition with Type=esp. The first one will + be the one referenced in the boot catalog. + + This option is available only when creating a new partition table, that is when + has value require, force or + create. + + + + + + + + When creating an ISO9660 header, this value will be used as the system identifier. + This is useful for the media to be matched against osinfo db. + + The value is limited to 32 characters. + + + + + + + + When creating an ISO9660 header, this value will be used as volume identifier. + This is useful for the media to be matched against osinfo db. + + The value is limited to 32 characters. + + + + + + + + When creating an ISO9660 header, this value will be used as publisher identifier. + This is useful for the media to be matched against osinfo db. + + The value is limited to 128 characters. + + + + diff --git a/src/repart/iso9660.c b/src/repart/iso9660.c new file mode 100644 index 0000000000000..5bc9588e68928 --- /dev/null +++ b/src/repart/iso9660.c @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "iso9660.h" +#include "log.h" +#include "stdio-util.h" +#include "string-util.h" +#include "time-util.h" + +void no_iso9660_datetime(struct iso9660_datetime *ret) { + assert(ret); + + memcpy(ret->year, "0000", 4); + memcpy(ret->month, "00", 2); + memcpy(ret->day, "00", 2); + memcpy(ret->hour, "00", 2); + memcpy(ret->minute, "00", 2); + memcpy(ret->second, "00", 2); + memcpy(ret->deci, "00", 2); + ret->zone = 0; +} + +int time_to_iso9660_datetime(usec_t usec, bool utc, struct iso9660_datetime *ret) { + struct tm t; + int r; + + assert(ret); + + r = localtime_or_gmtime_usec(usec, utc, &t); + if (r < 0) + return r; + + if (t.tm_year >= 10000 - 1900) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Year has more than 4 digits and is incompatible with ISO9660."); + if (t.tm_year + 1900 < 0) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Year is negative and is incompatible with ISO9660."); + + char buf[17]; + /* Ignore leap seconds, no real hope for hardware. Deci-seconds always zero. */ + xsprintf(buf, "%04d%02d%02d%02d%02d%02d00", + t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, + t.tm_hour, t.tm_min, MIN(t.tm_sec, 59)); + memcpy(ret, buf, sizeof(buf)-1); + + /* The time zone is encoded by 15 minutes increments */ + ret->zone = t.tm_gmtoff / (15*60); + + return 0; +} + +int time_to_iso9660_dir_datetime(usec_t usec, bool utc, struct iso9660_dir_time *ret) { + struct tm t; + int r; + + assert(ret); + + r = localtime_or_gmtime_usec(usec, utc, &t); + if (r < 0) + return r; + + if (t.tm_year < 0 || t.tm_year > UINT8_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Year is incompatible with ISO9660."); + + *ret = (struct iso9660_dir_time) { + .year = t.tm_year, + .month = t.tm_mon + 1, + .day = t.tm_mday, + .hour = t.tm_hour, + .minute = t.tm_min, + .second = MIN(t.tm_sec, 59), + /* The time zone is encoded by 15 minutes increments */ + .offset = t.tm_gmtoff / (15*60), + }; + + return 0; +} + +static bool valid_iso9660_string(const char *str, bool allow_a_chars) { + /* note that a-chars are not supposed to accept lower case letters, but it looks like common practice + * to use them + */ + return in_charset(str, allow_a_chars ? UPPERCASE_LETTERS LOWERCASE_LETTERS DIGITS " _!\"%&'()*+,-./:;<=>?" : UPPERCASE_LETTERS DIGITS "_"); +} + +int set_iso9660_string(char target[], size_t len, const char *source, bool allow_a_chars) { + if (source && !valid_iso9660_string(source, allow_a_chars)) + return -EINVAL; + + if (source) { + size_t slen = strlen(source); + if (slen > len) + return -EINVAL; + void *p = mempcpy(target, source, slen); + memset(p, ' ', len - slen); + } else + memset(target, ' ', len); + + return 0; +} + +bool iso9660_volume_name_valid(const char *name) { + /* In theory the volume identifier should be d-chars, but in practice, a-chars are allowed */ + return valid_iso9660_string(name, /* allow_a_chars= */ true) && + strlen(name) <= 32; +} + +bool iso9660_system_name_valid(const char *name) { + return valid_iso9660_string(name, /* allow_a_chars= */ true) && + strlen(name) <= 32; +} + +bool iso9660_publisher_name_valid(const char *name) { + return valid_iso9660_string(name, /* allow_a_chars= */ true) && + strlen(name) <= 128; +} diff --git a/src/repart/iso9660.h b/src/repart/iso9660.h new file mode 100644 index 0000000000000..7a51928fd604b --- /dev/null +++ b/src/repart/iso9660.h @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" +#include "sparse-endian.h" + +/* ISO9660 is 5 blocks: + * - Primary descriptor + * - El torito descriptor + * - Terminal descriptor + * - El Torito boot catalog + * - Root directory + */ +#define ISO9660_BLOCK_SIZE 2048U +#define ISO9660_START 16U +#define ISO9660_PRIMARY_DESCRIPTOR (ISO9660_START+0U) +#define ISO9660_ELTORITO_DESCRIPTOR (ISO9660_START+1U) +#define ISO9660_TERMINAL_DESCRIPTOR (ISO9660_START+2U) +#define ISO9660_BOOT_CATALOG (ISO9660_START+3U) +#define ISO9660_ROOT_DIRECTORY (ISO9660_START+4U) +#define ISO9660_SIZE 5U + +struct _packed_ iso9660_volume_descriptor_header { + uint8_t type; + char identifier[5]; + uint8_t version; +}; + +struct _packed_ iso9660_terminal_descriptor { + struct iso9660_volume_descriptor_header header; + uint8_t data[2041]; +}; +assert_cc(sizeof(struct iso9660_terminal_descriptor) == 2048); + +struct _packed_ iso9660_datetime { + char year[4]; + char month[2]; + char day[2]; + char hour[2]; + char minute[2]; + char second[2]; + char deci[2]; + int8_t zone; +}; + +struct _packed_ iso9660_eltorito_descriptor { + struct iso9660_volume_descriptor_header header; + + char boot_system_identifier[32]; + uint8_t unused_1[32]; + le32_t boot_catalog_sector; + uint8_t unused_2[1973]; +}; + +assert_cc(sizeof(struct iso9660_eltorito_descriptor) == 2048); + +struct _packed_ iso9660_dir_time { + uint8_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + int8_t offset; +}; + +struct _packed_ iso9660_directory_entry { + uint8_t len; + uint8_t xattr_len; + le32_t extent_loc_little; + be32_t extent_loc_big; + le32_t data_len_little; + be32_t data_len_big; + struct iso9660_dir_time time; + uint8_t flags; + uint8_t unit_size; + uint8_t gap_size; + le16_t volume_seq_num_little; + be16_t volume_seq_num_big; + uint8_t ident_len; + char ident[1]; /* variable */ +}; + +struct _packed_ iso9660_primary_volume_descriptor { + struct iso9660_volume_descriptor_header header; + + uint8_t unused_1; + char system_identifier[32]; + char volume_identifier[32]; + uint8_t unused_2[8]; + le32_t volume_space_size_little; + be32_t volume_space_size_big; + uint8_t unused_3[32]; + + le16_t volume_set_size_little; + be16_t volume_set_size_big; + le16_t volume_sequence_number_little; + be16_t volume_sequence_number_big; + le16_t logical_block_size_little; + be16_t logical_block_size_big; + + le32_t path_table_size_little; + be32_t path_table_size_big; + + le32_t path_table_little; + le32_t opt_path_table_little; + + be32_t path_table_big; + be32_t opt_path_table_big; + + struct iso9660_directory_entry root_directory_entry; + + char volume_set_identifier[128]; + char publisher_identifier[128]; + char data_preparer_identifier[128]; + char application_identifier[128]; + + char copyright_file_identifier[37]; + char abstract_file_identifier[37]; + char bibliographic_file_identifier[37]; + + struct iso9660_datetime volume_creation_date; + struct iso9660_datetime volume_modification_date; + struct iso9660_datetime volume_expiration_date; + struct iso9660_datetime volume_effective_date; + + uint8_t file_structure_version; /* 1 */ + uint8_t unused_5; + char application_used[512]; + char reserved[653]; +}; +assert_cc(sizeof(struct iso9660_primary_volume_descriptor) == 2048); + +struct _packed_ el_torito_validation_entry { + uint8_t header_indicator; + uint8_t platform; + char reserved[2]; + char id_string[24]; + le16_t checksum; + uint8_t key_bytes[2]; +}; + +struct _packed_ el_torito_initial_entry { + uint8_t boot_indicator; + uint8_t boot_media_type; + le16_t load_segment; + uint8_t system_type; + uint8_t unused_1[1]; + le16_t sector_count; + le32_t load_rba; + uint8_t unused_2[20]; +}; + +struct _packed_ el_torito_section_header { + uint8_t header_indicator; + uint8_t platform; + le16_t nentries; + char id_string[28]; +}; + +void no_iso9660_datetime(struct iso9660_datetime *ret); +int time_to_iso9660_datetime(usec_t usec, bool utc, struct iso9660_datetime *ret); +int time_to_iso9660_dir_datetime(usec_t usec, bool utc, struct iso9660_dir_time *ret); +int set_iso9660_string(char target[], size_t len, const char *source, bool allow_a_chars); + +static inline void set_iso9660_const_string(char target[], size_t len, const char *source, bool allow_a_chars) { + assert_se(set_iso9660_string(target, len, source, allow_a_chars) == 0); +} + +bool iso9660_volume_name_valid(const char *name); +bool iso9660_system_name_valid(const char *name); +bool iso9660_publisher_name_valid(const char *name); diff --git a/src/repart/meson.build b/src/repart/meson.build index e6e32f54c7a25..92c7d37da5af8 100644 --- a/src/repart/meson.build +++ b/src/repart/meson.build @@ -8,7 +8,10 @@ executables += [ executable_template + { 'name' : 'systemd-repart', 'public' : true, - 'extract' : files('repart.c'), + 'extract' : files( + 'repart.c', + 'iso9660.c', + ), 'link_with' : [ libshared, libshared_fdisk, diff --git a/src/repart/repart.c b/src/repart/repart.c index bae376fc0ee39..5c36bad0758f2 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -49,6 +49,7 @@ #include "initrd-util.h" #include "install-file.h" #include "io-util.h" +#include "iso9660.h" #include "json-util.h" #include "libmount-util.h" #include "list.h" @@ -213,6 +214,10 @@ static char *arg_generate_crypttab = NULL; static Set *arg_verity_settings = NULL; static bool arg_relax_copy_block_security = false; static bool arg_varlink = false; +static bool arg_eltorito = false; +static char *arg_eltorito_system = NULL; +static char *arg_eltorito_volume = NULL; +static char *arg_eltorito_publisher = NULL; STATIC_DESTRUCTOR_REGISTER(arg_node, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); @@ -237,6 +242,9 @@ STATIC_DESTRUCTOR_REGISTER(arg_make_ddi, freep); STATIC_DESTRUCTOR_REGISTER(arg_generate_fstab, freep); STATIC_DESTRUCTOR_REGISTER(arg_generate_crypttab, freep); STATIC_DESTRUCTOR_REGISTER(arg_verity_settings, set_freep); +STATIC_DESTRUCTOR_REGISTER(arg_eltorito_system, freep); +STATIC_DESTRUCTOR_REGISTER(arg_eltorito_volume, freep); +STATIC_DESTRUCTOR_REGISTER(arg_eltorito_publisher, freep); typedef enum ProgressPhase { PROGRESS_LOADING_DEFINITIONS, @@ -7651,6 +7659,321 @@ static int context_split(Context *context) { return 0; } +static int write_primary_descriptor( + int fd, + uint32_t root_sector, + usec_t usec, + bool utc, + const char *system_id, + const char *volume_id, + const char *publisher_id) { + int r; + + struct iso9660_primary_volume_descriptor desc = { + .header = { + .type = 1, + .version = 1, + }, + .volume_space_size_little = htole32(ISO9660_START + ISO9660_SIZE), + .volume_space_size_big = htobe32(ISO9660_START + ISO9660_SIZE), + .volume_set_size_little = htole16(1), + .volume_set_size_big = htobe16(1), + .volume_sequence_number_little = htole16(1), + .volume_sequence_number_big = htobe16(1), + .logical_block_size_little = htole16(ISO9660_BLOCK_SIZE), + .logical_block_size_big = htobe16(ISO9660_BLOCK_SIZE), + .file_structure_version = 1, + .root_directory_entry = { + .len = sizeof(struct iso9660_directory_entry), + .extent_loc_little = htole32(root_sector), + .extent_loc_big = htobe32(root_sector), + .data_len_little = htole32(2*sizeof(struct iso9660_directory_entry)), /* 2 entries with ident size 1: . and .. */ + .data_len_big = htobe32(2*sizeof(struct iso9660_directory_entry)), /* 2 entries with ident size 1: . and .. */ + .flags = 2, /* directory */ + .volume_seq_num_little = htole16(1), + .volume_seq_num_big = htobe16(1), + .ident_len = 1, + .ident[0] = 0, /* special value for root */ + } + }; + + set_iso9660_const_string(desc.header.identifier, sizeof(desc.header.identifier), "CD001", /* allow_a_chars= */ true); + + r = time_to_iso9660_dir_datetime(usec, utc, &desc.root_directory_entry.time); + if (r < 0) + return r; + + r = set_iso9660_string(desc.system_identifier, sizeof(desc.system_identifier), system_id, /* allow_a_chars= */ true); + if (r < 0) + return r; + + /* In theory the volume identifier should be d-chars, but in practice, a-chars are allowed */ + r = set_iso9660_string(desc.volume_identifier, sizeof(desc.volume_identifier), volume_id, /* allow_a_chars= */ true); + if (r < 0) + return r; + + set_iso9660_const_string(desc.volume_set_identifier, sizeof(desc.volume_set_identifier), NULL, /* allow_a_chars= */ false); + + r = set_iso9660_string(desc.publisher_identifier, sizeof(desc.publisher_identifier), publisher_id, /* allow_a_chars= */ true); + if (r < 0) + return r; + + set_iso9660_const_string(desc.data_preparer_identifier, sizeof(desc.data_preparer_identifier), NULL, /* allow_a_chars= */ true); + set_iso9660_const_string(desc.application_identifier, sizeof(desc.application_identifier), "SYSTEMD-REPART", /* allow_a_chars= */ true); + set_iso9660_const_string(desc.copyright_file_identifier, sizeof(desc.copyright_file_identifier), NULL, /* allow_a_chars= */ false); + set_iso9660_const_string(desc.abstract_file_identifier, sizeof(desc.abstract_file_identifier), NULL, /* allow_a_chars= */ false); + set_iso9660_const_string(desc.bibliographic_file_identifier, sizeof(desc.bibliographic_file_identifier), NULL, /* allow_a_chars= */ false); + + r = time_to_iso9660_datetime(usec, utc, &desc.volume_creation_date); + if (r < 0) + return r; + + r = time_to_iso9660_datetime(usec, utc, &desc.volume_modification_date); + if (r < 0) + return r; + + no_iso9660_datetime(&desc.volume_expiration_date); + no_iso9660_datetime(&desc.volume_effective_date); + + ssize_t s = pwrite(fd, &desc, sizeof(desc), ISO9660_PRIMARY_DESCRIPTOR*ISO9660_BLOCK_SIZE); + if (s < 0) + return log_error_errno(errno, "Failed to write ISO9660 primary descriptor: %m"); + if (s != sizeof(desc)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to fully write ISO9660 primary descriptor"); + + return 0; +} + +static int write_eltorito_descriptor(int fd, uint32_t catalog_sector) { + struct iso9660_eltorito_descriptor desc = { + .header = { + .type = 0, + .version = 1, + }, + .boot_catalog_sector = htole32(catalog_sector), + }; + + set_iso9660_const_string(desc.header.identifier, sizeof(desc.header.identifier), "CD001", /* allow_a_chars= */ true); + + strncpy(desc.boot_system_identifier, "EL TORITO SPECIFICATION", sizeof(desc.boot_system_identifier)); + + ssize_t s = pwrite(fd, &desc, sizeof(desc), ISO9660_ELTORITO_DESCRIPTOR*ISO9660_BLOCK_SIZE); + if (s < 0) + return log_error_errno(errno, "Failed to write ISO9660 El-Torito descriptor: %m"); + if (s != sizeof(desc)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to fully write ISO9660 El-Torito descriptor"); + + return 0; +} + +static int write_terminal_descriptor(int fd) { + struct iso9660_terminal_descriptor desc = { + .header = { + .type = 255, + .version = 1, + }, + }; + + set_iso9660_const_string(desc.header.identifier, sizeof(desc.header.identifier), "CD001", /* allow_a_chars= */ true); + + ssize_t s = pwrite(fd, &desc, sizeof(desc), ISO9660_TERMINAL_DESCRIPTOR*ISO9660_BLOCK_SIZE); + if (s < 0) + return log_error_errno(errno, "Failed to write ISO9660 terminal descriptor: %m"); + if (s != sizeof(desc)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to fully write ISO9660 terminal descriptor"); + + return 0; +} + +static uint16_t calculate_validation_entry_checksum(const void *p, size_t size) { + assert(size % 2 == 0); + + uint16_t checksum = 0; + + for (size_t i = 0; i < (size/2); i++) + checksum -= le16toh(((const le16_t*)p)[i]); + + return checksum; +} + +static int write_boot_catalog(int fd, uint32_t load_block) { + struct el_torito_validation_entry ve = { + .header_indicator = 1, + .platform = 0xef, /* EFI */ + .key_bytes = {0x55, 0xaa}, + }; + + ve.checksum = htole16(calculate_validation_entry_checksum(&ve, sizeof(ve))); + + struct el_torito_initial_entry ie = { + .boot_indicator = 0x88, /* bootable */ + .boot_media_type = 0, /* no emul */ + /* From UEFI specification: + * > If the value of Sector Count is set to 0 or 1, EFI will assume the system partition + * > consumes the space from the beginning of the “no emulation” image to the end of the + * > CD-ROM. + */ + .sector_count = htole16(0), + .load_rba = htole32(load_block), + + }; + + struct el_torito_section_header sh = { + .header_indicator = 0x91, /* final header */ + .nentries = htole16(0), /* no more entries */ + }; + + uint8_t sector[ISO9660_BLOCK_SIZE] = {}; + uint8_t *p = sector; + p = mempcpy(p, &ve, sizeof(ve)); + p = mempcpy(p, &ie, sizeof(ie)); + p = mempcpy(p, &sh, sizeof(sh)); + assert((size_t) (p - sector) <= sizeof(sector)); + + ssize_t s = pwrite(fd, §or, sizeof(sector), ISO9660_BOOT_CATALOG*ISO9660_BLOCK_SIZE); + if (s < 0) + return log_error_errno(errno, "Failed to write El-Torito boot catalog: %m"); + if (s != sizeof(sector)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to fully write El-Torito boot catalog"); + + return 0; +} + +static int write_directories(int fd, usec_t usec, bool utc, uint32_t root_sector) { + int r; + + uint32_t dir_size = 2*sizeof(struct iso9660_directory_entry); /* 2 entries with ident size 1: . and .. */ + + struct iso9660_directory_entry self = { + .len = sizeof(struct iso9660_directory_entry), + .extent_loc_little = htole32(root_sector), + .extent_loc_big = htobe32(root_sector), + .data_len_little = htole32(dir_size), + .data_len_big = htobe32(dir_size), + .flags = 2, /* directory */ + .volume_seq_num_little = htole16(1), + .volume_seq_num_big = htobe16(1), + .ident_len = 1, + .ident[0] = 0, /* special value for self */ + }; + + r = time_to_iso9660_dir_datetime(usec, utc, &self.time); + if (r < 0) + return r; + + struct iso9660_directory_entry parent = { + .len = sizeof(struct iso9660_directory_entry), + .extent_loc_little = htole32(root_sector), + .extent_loc_big = htobe32(root_sector), + .data_len_little = htole32(dir_size), + .data_len_big = htobe32(dir_size), + .flags = 2, /* directory */ + .volume_seq_num_little = htole16(1), + .volume_seq_num_big = htobe16(1), + .ident_len = 1, + .ident[0] = 1, /* special value for parent */ + }; + + // TODO: we should probably add some text file explaining there is no content through ISO9660 + + r = time_to_iso9660_dir_datetime(usec, utc, &parent.time); + if (r < 0) + return r; + + uint8_t sector[ISO9660_BLOCK_SIZE] = {}; + uint8_t *p = sector; + p = mempcpy(p, &self, sizeof(self)); + p = mempcpy(p, &parent, sizeof(parent)); + assert((size_t) (p - sector) <= sizeof(sector)); + + ssize_t s = pwrite(fd, §or, sizeof(sector), ISO9660_ROOT_DIRECTORY*ISO9660_BLOCK_SIZE); + if (s < 0) + return log_error_errno(errno, "Failed to write ISO9660 root directory: %m"); + if (s != sizeof(sector)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to fully write ISO9660 root directory"); + + return 0; +} + +static int write_eltorito(int fd, usec_t usec, bool utc, uint32_t load_block, const char *system_id, const char *volume_id, const char *publisher_id) { + int r; + + r = write_primary_descriptor(fd, ISO9660_ROOT_DIRECTORY, usec, utc, system_id, volume_id, publisher_id); + if (r < 0) + return r; + + r = write_eltorito_descriptor(fd, ISO9660_BOOT_CATALOG); + if (r < 0) + return r; + + r = write_terminal_descriptor(fd); + if (r < 0) + return r; + + r = write_boot_catalog(fd, load_block); + if (r < 0) + return r; + + r = write_directories(fd, usec, utc, ISO9660_ROOT_DIRECTORY); + if (r < 0) + return r; + + return 0; +} + +static int context_verify_eltorito_overlap(Context *context) { + /* before writing the partition table, we check if we have collision with ISO9660 */ + assert(context); + + if (!arg_eltorito) + return 0; + + /* Check how many GPT partition entries can be stored. */ + size_t nents = fdisk_get_npartitions(context->fdisk_context); + /* The GPT contains + * - 1 unused block (protective MBR) + * - GPT header + * - N entries of 128 bytes each. + */ + size_t first_free_offset = 2*context->sector_size + round_up_size(nents*128, context->sector_size); + + if (first_free_offset > ISO9660_START*ISO9660_BLOCK_SIZE) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The partition table is overlapping with the El Torito boot catalog."); + + /* The first lba is the first block where a partition could exist. Even if there is no partition + * there, we should still not overlap with it since a partition could be added later. + * It is unexpected for tools to change the first lba in the GPT header. So this should be safe. + */ + if (fdisk_get_first_lba(context->fdisk_context) * context->sector_size < (ISO9660_START+ISO9660_SIZE)*ISO9660_BLOCK_SIZE) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "El Torito is overlapping with the first partition block."); + + return 0; +} + +static int context_find_esp_offset(Context *context, uint64_t *ret) { + assert(ret); + + uint64_t esp_offset = UINT64_MAX; + LIST_FOREACH(partitions, p, context->partitions) { + if (p->dropped || PARTITION_IS_FOREIGN(p)) + continue; + if (p->type.designator == PARTITION_ESP) { + esp_offset = p->offset; + break; + } + } + + if (esp_offset == UINT64_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "El Torito boot catalog requires an ESP."); + if (esp_offset / ISO9660_BLOCK_SIZE > UINT32_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "ESP offset is farther than El Torito boot catalog can support."); + if (esp_offset % ISO9660_BLOCK_SIZE != 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "ESP offset not aligned on 2K blocks."); + + *ret = esp_offset; + return 0; +} + static int context_write_partition_table(Context *context) { _cleanup_(fdisk_unref_tablep) struct fdisk_table *original_table = NULL; int capable, r; @@ -7717,6 +8040,10 @@ static int context_write_partition_table(Context *context) { (void) context_notify(context, PROGRESS_WRITING_TABLE, /* object= */ NULL, UINT_MAX); + r = context_verify_eltorito_overlap(context); + if (r < 0) + return r; + r = fdisk_write_disklabel(context->fdisk_context); if (r < 0) return log_error_errno(r, "Failed to write partition table: %m"); @@ -7736,6 +8063,24 @@ static int context_write_partition_table(Context *context) { } else log_notice("Not telling kernel to reread partition table, because selected image does not support kernel partition block devices."); + if (arg_eltorito) { + bool utc = true; + usec_t usec = parse_source_date_epoch(); + if (usec == USEC_INFINITY) { + usec = now(CLOCK_REALTIME); + utc = false; + } + + uint64_t esp_offset; + r = context_find_esp_offset(context, &esp_offset); + if (r < 0) + return r; + + r = write_eltorito(fdisk_get_devfd(context->fdisk_context), usec, utc, esp_offset / ISO9660_BLOCK_SIZE, arg_eltorito_system, arg_eltorito_volume, arg_eltorito_publisher); + if (r < 0) + return log_error_errno(r, "Failed to write El Torito boot catalog: %m"); + } + log_info("All done."); return 0; @@ -9205,6 +9550,14 @@ static int help(void) { " Write fstab configuration to the given path\n" " --generate-crypttab=PATH\n" " Write crypttab configuration to the given path\n" + "\n%3$sEl Torito boot catalog:%4$s\n" + " --el-torito=BOOL Whether to add a boot catalog to boot the ESP\n" + " --el-torito-system=STRING\n" + " Set the system identifier in the ISO9660 descriptor\n" + " --el-torito-volume=STRING\n" + " Set the volume identifier in the ISO9660 descriptor\n" + " --el-torito-publisher=STRING\n" + " Set the publisher identifier in the ISO9660 descriptor\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -9264,6 +9617,10 @@ static int parse_argv(int argc, char *argv[]) { ARG_GENERATE_CRYPTTAB, ARG_LIST_DEVICES, ARG_JOIN_SIGNATURE, + ARG_ELTORITO, + ARG_ELTORITO_SYSTEM, + ARG_ELTORITO_VOLUME, + ARG_ELTORITO_PUBLISHER, }; static const struct option options[] = { @@ -9314,6 +9671,10 @@ static int parse_argv(int argc, char *argv[]) { { "generate-crypttab", required_argument, NULL, ARG_GENERATE_CRYPTTAB }, { "list-devices", no_argument, NULL, ARG_LIST_DEVICES }, { "join-signature", required_argument, NULL, ARG_JOIN_SIGNATURE }, + { "el-torito", required_argument, NULL, ARG_ELTORITO }, + { "el-torito-system", required_argument, NULL, ARG_ELTORITO_SYSTEM }, + { "el-torito-volume", required_argument, NULL, ARG_ELTORITO_VOLUME }, + { "el-torito-publisher", required_argument, NULL, ARG_ELTORITO_PUBLISHER }, {} }; @@ -9738,6 +10099,43 @@ static int parse_argv(int argc, char *argv[]) { return r; break; + case ARG_ELTORITO: + r = parse_boolean_argument("--el-torito=", optarg, &arg_eltorito); + if (r < 0) + return r; + + break; + + case ARG_ELTORITO_SYSTEM: + if (!iso9660_system_name_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-system=.", optarg); + + r = free_and_strdup_warn(&arg_eltorito_system, optarg); + if (r < 0) + return r; + + break; + + case ARG_ELTORITO_VOLUME: + if (!iso9660_volume_name_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-volume=.", optarg); + + r = free_and_strdup_warn(&arg_eltorito_volume, optarg); + if (r < 0) + return r; + + break; + + case ARG_ELTORITO_PUBLISHER: + if (!iso9660_publisher_name_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-publisher=.", optarg); + + r = free_and_strdup_warn(&arg_eltorito_publisher, optarg); + if (r < 0) + return r; + + break; + case '?': return -EINVAL; @@ -9882,6 +10280,10 @@ static int parse_argv(int argc, char *argv[]) { arg_pager_flags |= PAGER_DISABLE; } + if (arg_eltorito && !IN_SET(arg_empty, EMPTY_REQUIRE, EMPTY_FORCE, EMPTY_CREATE)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--el-torito=yes requires --empty= to be either require, force or create."); + return 1; } From be2ac4beb5298978ca5f6b63f2e48a5f1d660079 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 29 Mar 2026 21:11:52 +0000 Subject: [PATCH 0600/1296] repart: allow --el-torito= with any --empty= value The restriction requiring --empty= to be require, force, or create when using --el-torito= is unnecessary. context_verify_eltorito_overlap() already validates that the ISO 9660 blocks don't collide with GPT partition entries or the first usable LBA, which is sufficient to guarantee safety regardless of the empty mode. This is needed for two-stage image builds where the first stage creates the usr and verity partitions, and the second stage adds --el-torito= to produce a bootable ISO with a UKI containing usrhash= derived from the verity hash of the first stage. In the second stage, repart runs with --empty=allow since the image already exists. Co-developed-by: Claude Opus 4.6 --- man/systemd-repart.xml | 4 ---- src/repart/repart.c | 4 ---- 2 files changed, 8 deletions(-) diff --git a/man/systemd-repart.xml b/man/systemd-repart.xml index 0cb14b6991392..271366e5efdb0 100644 --- a/man/systemd-repart.xml +++ b/man/systemd-repart.xml @@ -731,10 +731,6 @@ The disk requires at least one partition with Type=esp. The first one will be the one referenced in the boot catalog. - This option is available only when creating a new partition table, that is when - has value require, force or - create. - diff --git a/src/repart/repart.c b/src/repart/repart.c index 5c36bad0758f2..d672db6d266b4 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -10280,10 +10280,6 @@ static int parse_argv(int argc, char *argv[]) { arg_pager_flags |= PAGER_DISABLE; } - if (arg_eltorito && !IN_SET(arg_empty, EMPTY_REQUIRE, EMPTY_FORCE, EMPTY_CREATE)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--el-torito=yes requires --empty= to be either require, force or create."); - return 1; } From 2f69c8e712a31f9b99019b602bebe0c7dc82c41b Mon Sep 17 00:00:00 2001 From: ssahani Date: Fri, 27 Mar 2026 09:19:49 +0530 Subject: [PATCH 0601/1296] networkd: Add docs and tests for IPv4SrcValidMark= Document the new setting in systemd.network(5) man page and add coverage in the networkd integration tests. Co-developed-by: Claude Opus 4.6 --- man/systemd.network.xml | 13 +++++++++++++ src/network/networkd-sysctl.c | 1 + test/test-network/conf/25-sysctl.network | 1 + test/test-network/systemd-networkd-tests.py | 1 + 4 files changed, 16 insertions(+) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 4c777ef4e0876..554d8da8ef606 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -1086,6 +1086,19 @@ DuplicateAddressDetection=none + + IPv4SrcValidMark= + + Takes a boolean. When enabled, the packet's firewall mark (fwmark) is included in the + reverse path filter route lookup for source address validation on this interface. This is + particularly useful for policy routing setups where packets may arrive with source addresses + that are only valid in routing tables selected by their fwmark. When unset, the kernel's + default will be used. + + + + + IPv4ProxyARP= diff --git a/src/network/networkd-sysctl.c b/src/network/networkd-sysctl.c index 8946f36960705..e5f5c07ff165e 100644 --- a/src/network/networkd-sysctl.c +++ b/src/network/networkd-sysctl.c @@ -665,6 +665,7 @@ static int link_set_ipv4_route_localnet(Link *link) { static int link_set_ipv4_src_valid_mark(Link *link) { assert(link); assert(link->manager); + assert(link->network); if (!link_is_configured_for_family(link, AF_INET)) return 0; diff --git a/test/test-network/conf/25-sysctl.network b/test/test-network/conf/25-sysctl.network index dcc4f0d293a3c..c0c709c32ce79 100644 --- a/test/test-network/conf/25-sysctl.network +++ b/test/test-network/conf/25-sysctl.network @@ -12,5 +12,6 @@ IPv4ProxyARPPrivateVLAN=yes IPv6ProxyNDP=yes IPv6AcceptRA=no IPv4AcceptLocal=yes +IPv4SrcValidMark=yes IPv4ReversePathFilter=no MulticastIGMPVersion=v1 diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index bab725bd23943..38443315e6d10 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -4972,6 +4972,7 @@ def test_sysctl(self): self.check_ipv4_sysctl_attr('dummy98', 'proxy_arp', '1') self.check_ipv4_sysctl_attr('dummy98', 'proxy_arp_pvlan', '1') self.check_ipv4_sysctl_attr('dummy98', 'accept_local', '1') + self.check_ipv4_sysctl_attr('dummy98', 'src_valid_mark', '1') self.check_ipv4_sysctl_attr('dummy98', 'rp_filter', '0') self.check_ipv4_sysctl_attr('dummy98', 'force_igmp_version', '1') From 566a4f3437d44e25ea4f1175c14a9bf90ffd230b Mon Sep 17 00:00:00 2001 From: Adam Dinwoodie Date: Wed, 11 Mar 2026 23:04:44 +0000 Subject: [PATCH 0602/1296] man: fix caps in example path --- man/systemd.special.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/systemd.special.xml b/man/systemd.special.xml index 447ec57bfd496..f6f35b861d01a 100644 --- a/man/systemd.special.xml +++ b/man/systemd.special.xml @@ -745,7 +745,7 @@ Before=sleep.target Type=oneshot RemainAfterExit=yes ExecStart=/usr/bin/some-before-command -ExecStop=/Usr/bin/some-after-command +ExecStop=/usr/bin/some-after-command [Install] WantedBy=sleep.target From 1b9c63de7ae2239f3dfac2ce022c045f48822092 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 27 Mar 2026 13:13:18 +0100 Subject: [PATCH 0603/1296] TODO: fix formatting inconsistencies Normalize section header capitalization, add missing colons to sub-topic headers, replace bullet character variants with dashes, and fix sub-item indentation to use two spaces consistently. Signed-off-by: Christian Brauner --- TODO | 126 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/TODO b/TODO index 687d15b5ba83b..f04bf26247087 100644 --- a/TODO +++ b/TODO @@ -13,8 +13,8 @@ External: * Fedora: add an rpmlint check that verifies that all unit files in the RPM are listed in %systemd_post macros. * dbus: - - natively watch for dbus-*.service symlinks (PENDING) - - teach dbus to activate all services it finds in /etc/systemd/services/org-*.service + - natively watch for dbus-*.service symlinks (PENDING) + - teach dbus to activate all services it finds in /etc/systemd/services/org-*.service * fedora: suggest auto-restart on failure, but not on success and not on coredump. also, ask people to think about changing the start limit logic. Also point people to RestartPreventExitStatus=, SuccessExitStatus= @@ -63,7 +63,7 @@ Regularly: * link up selected blog stories from man pages and unit files Documentation= fields -Janitorial Clean-ups: +Janitorial Cleanups: * machined: make remaining machine bus calls compatible with unpriv machined + unpriv npsawn: GetAddresses(), GetSSHInfo(), GetOSRelease(), OpenPTY(), @@ -93,7 +93,7 @@ Janitorial Clean-ups: * use CHASE_MUST_BE_DIRECTORY and CHASE_MUST_BE_REGULAR at more places (the majority of places that currently employ chase() probably should use this) -Deprecations and removals: +Deprecations and Removals: * Remove any support for booting without /usr pre-mounted in the initrd entirely. Update INITRD_INTERFACE.md accordingly. @@ -159,7 +159,7 @@ Features: possibly up to 100ms supposedly) * instead of going directly for DefineSpace when initializing nvpcrs, check if - they exist first. apparently DEfineSpace is broken on some tpms, and also + they exist first. apparently DefineSpace is broken on some tpms, and also creates log spam if the nvindex already exists. * on first login of a user, measure its identity to some nvpcr @@ -308,7 +308,7 @@ Features: not * automatically reset specific EFI vars on factory reset (make this generic - enough so that infrac can be used to erase shim's mok vars?) + enough so that infra can be used to erase shim's mok vars?) * similar: add a plugin for factory reset logic that erases certain parts of the ESP, but leaves others in place. @@ -424,7 +424,7 @@ Features: * maybe introduce a new partition that we can store debug logs and similar at the very last moment of shutdown. idea would be to store reference to block - device (major + minor + partition id + diskeq?) in /run somewhere, than use + device (major + minor + partition id + diskseq?) in /run somewhere, than use that from systemd-shutdown, just write a raw JSON blob into the partition. Include timestamp, boot id and such, plus kmsg. on next boot immediately import into journal. maybe use timestamp for making clock more monotonic. @@ -571,7 +571,7 @@ Features: * Reset TPM2 DA bit on each successful boot -* systemd-repart: add --installer or so, that will intractively ask for a +* systemd-repart: add --installer or so, that will interactively ask for a target disk, maybe ask for confirmation, and install something on disk. Then, hook that into installer.target or so, so that it can be used to install/replicate installs @@ -634,14 +634,14 @@ Features: cgroup information. This way if a service consisting of many logging processes can take benefit of the cgroup caching. -* system lsmbpf policy that prohibits creating files owned by "nobody" +* system LSFMMBPF policy that prohibits creating files owned by "nobody" system-wide -* system lsmpbf policy that prohibits creating or opening device nodes outside +* system LSFMMBPF policy that prohibits creating or opening device nodes outside of devtmpfs/tmpfs, except if they are the pseudo-devices /dev/null, /dev/zero, /dev/urandom and so on. -* system lsmbpf policy that enforces that block device backed mounts may only +* system LSFMMBPF policy that enforces that block device backed mounts may only be established on top of dm-crypt or dm-verity devices, or an allowlist of file systems (which should probably include vfat, for compat with the ESP) @@ -825,7 +825,7 @@ Features: * add a new specifier to unit files that figures out the DDI the unit file is from, tracing through overlayfs, DM, loopback block device. -* importd/importctl +* importd/importctl: - complete varlink interface - download images into .v/ dirs @@ -884,7 +884,7 @@ Features: * introduce mntid_t, and make it 64bit, as apparently the kernel switched to 64bit mount ids -* mountfsd/nsresourced +* mountfsd/nsresourced: - userdb: maybe allow callers to map one uid to their own uid - bpflsm: allow writes if resulting UID on disk would be userns' owner UID - make encrypted DDIs work (password…) @@ -1518,20 +1518,20 @@ Features: should probably also one you can use to get a remote attestation quote. * Process credentials in: - • crypttab-generator: allow defining additional crypttab-like volumes via + - crypttab-generator: allow defining additional crypttab-like volumes via credentials (similar: verity-generator, integrity-generator). Use fstab-generator logic as inspiration. - • run-generator: allow defining additional commands to run via a credential - • resolved: allow defining additional /etc/hosts entries via a credential (it + - run-generator: allow defining additional commands to run via a credential + - resolved: allow defining additional /etc/hosts entries via a credential (it might make sense to then synthesize a new combined /etc/hosts file in /run and bind mount it on /etc/hosts for other clients that want to read it. - • repart: allow defining additional partitions via credential - • timesyncd: pick NTP server info from credential - • portabled: read a credential "portable.extra" or so, that takes a list of + - repart: allow defining additional partitions via credential + - timesyncd: pick NTP server info from credential + - portabled: read a credential "portable.extra" or so, that takes a list of file system paths to enable on start. - • make systemd-fstab-generator look for a system credential encoding root= or + - make systemd-fstab-generator look for a system credential encoding root= or usr= - • in gpt-auto-generator: check partition uuids against such uuids supplied via + - in gpt-auto-generator: check partition uuids against such uuids supplied via sd-stub credentials. That way, we can support parallel OS installations with pre-built kernels. @@ -1965,7 +1965,7 @@ Features: * augment CODE_FILE=, CODE_LINE= with something like CODE_BASE= or so which contains some identifier for the project, which allows us to include clickable links to source files generating these log messages. The identifier - could be some abberviated URL prefix or so (taking inspiration from Go + could be some abbreviated URL prefix or so (taking inspiration from Go imports). For example, for systemd we could use CODE_BASE=github.com/systemd/systemd/blob/98b0b1123cc or so which is sufficient to build a link by prefixing "http://" and suffixing the @@ -2107,7 +2107,7 @@ Features: * define gpt header bits to select volatility mode -* ProtectClock= (drops CAP_SYS_TIMES, adds seecomp filters for settimeofday, adjtimex), sets DeviceAllow o /dev/rtc +* ProtectClock= (drops CAP_SYS_TIMES, adds seccomp filters for settimeofday, adjtimex), sets DeviceAllow o /dev/rtc * ProtectTracing= (drops CAP_SYS_PTRACE, blocks ptrace syscall, makes /sys/kernel/tracing go away) @@ -2400,11 +2400,11 @@ Features: - add API to clone sd_bus_message objects - longer term: priority inheritance - dbus spec updates: - - NameLost/NameAcquired obsolete - - path escaping + - NameLost/NameAcquired obsolete + - path escaping - update systemd.special(7) to mention that dbus.socket is only about the compatibility socket now -* sd-event +* sd-event: - allow multiple signal handlers per signal? - document chaining of signal handler for SIGCHLD and child handlers - define more intervals where we will shift wakeup intervals around in, 1h, 6h, 24h, ... @@ -2427,7 +2427,7 @@ Features: * EFI: - honor language efi variables for default language selection (if there are any?) - honor timezone efi variables for default timezone selection (if there are any?) -* bootctl +* bootctl: - recognize the case when not booted on EFI * bootctl: @@ -2870,54 +2870,54 @@ Features: - add -n as shortcut for --dry-run in tmpfiles & sysusers & possibly other places * udev-link-config: - - Make sure ID_PATH is always exported and complete for - network devices where possible, so we can safely rely - on Path= matching + - Make sure ID_PATH is always exported and complete for + network devices where possible, so we can safely rely + on Path= matching * sd-rtnl: - - add support for more attribute types - - inbuilt piping support (essentially degenerate async)? see loopback-setup.c and other places + - add support for more attribute types + - inbuilt piping support (essentially degenerate async)? see loopback-setup.c and other places * networkd: - - add more keys to [Route] and [Address] sections - - add support for more DHCPv4 options (and, longer term, other kinds of dynamic config) - - add reduced [Link] support to .network files - - properly handle routerless dhcp leases - - work with non-Ethernet devices - - dhcp: do we allow configuring dhcp routes on interfaces that are not the one we got the dhcp info from? - - the DHCP lease data (such as NTP/DNS) is still made available when - a carrier is lost on a link. It should be removed instantly. - - expose in the API the following bits: - - option 15, domain name - - option 12, hostname and/or option 81, fqdn - - option 123, 144, geolocation - - option 252, configure http proxy (PAC/wpad) - - provide a way to define a per-network interface default metric value - for all routes to it. possibly a second default for DHCP routes. - - allow Name= to be specified repeatedly in the [Match] section. Maybe also - support Name=foo*|bar*|baz ? - - whenever uplink info changes, make DHCP server send out FORCERENEW + - add more keys to [Route] and [Address] sections + - add support for more DHCPv4 options (and, longer term, other kinds of dynamic config) + - add reduced [Link] support to .network files + - properly handle routerless dhcp leases + - work with non-Ethernet devices + - dhcp: do we allow configuring dhcp routes on interfaces that are not the one we got the dhcp info from? + - the DHCP lease data (such as NTP/DNS) is still made available when + a carrier is lost on a link. It should be removed instantly. + - expose in the API the following bits: + - option 15, domain name + - option 12, hostname and/or option 81, fqdn + - option 123, 144, geolocation + - option 252, configure http proxy (PAC/wpad) + - provide a way to define a per-network interface default metric value + for all routes to it. possibly a second default for DHCP routes. + - allow Name= to be specified repeatedly in the [Match] section. Maybe also + support Name=foo*|bar*|baz ? + - whenever uplink info changes, make DHCP server send out FORCERENEW * in networkd, when matching device types, fix up DEVTYPE rubbish the kernel passes to us * Figure out how to do unittests of networkd's state serialization * dhcp: - - figure out how much we can increase Maximum Message Size + - figure out how much we can increase Maximum Message Size * dhcp6: - - add functions to set previously stored IPv6 addresses on startup and get - them at shutdown; store them in client->ia_na - - write more test cases - - implement reconfigure support, see 5.3., 15.11. and 22.20. - - implement support for temporary addresses (IA_TA) - - implement dhcpv6 authentication - - investigate the usefulness of Confirm messages; i.e. are there any - situations where the link changes without any loss in carrier detection - or interface down - - some servers don't do rapid commit without a filled in IA_NA, verify - this behavior - - RouteTable= ? + - add functions to set previously stored IPv6 addresses on startup and get + them at shutdown; store them in client->ia_na + - write more test cases + - implement reconfigure support, see 5.3., 15.11. and 22.20. + - implement support for temporary addresses (IA_TA) + - implement dhcpv6 authentication + - investigate the usefulness of Confirm messages; i.e. are there any + situations where the link changes without any loss in carrier detection + or interface down + - some servers don't do rapid commit without a filled in IA_NA, verify + this behavior + - RouteTable= ? * shared/wall: Once more programs are taught to prefer sd-login over utmp, switch the default wall implementation to wall_logind From e9e811ca2c6c6a8607b608e9c46c37d2ca69326d Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 27 Mar 2026 13:33:44 +0100 Subject: [PATCH 0604/1296] TODO: merge scattered items into their grouped sections Items about the same topic were spread across the file as separate bullet points instead of being collected under a single heading. Consolidate them so each topic appears in one place. Compound items that span multiple topics (e.g. cryptsetup/homed) are left as standalone entries. Signed-off-by: Christian Brauner --- TODO | 681 ++++++++++++++++++++++++++--------------------------------- 1 file changed, 294 insertions(+), 387 deletions(-) diff --git a/TODO b/TODO index f04bf26247087..c238794c2f404 100644 --- a/TODO +++ b/TODO @@ -205,16 +205,6 @@ Features: * report: have something that requests cloud workload identity bearer tokens and includes it in the report -* sysupdate: download multiple arbitrary patterns from same source - -* sysupdate: SHA256SUMS format with bearer tokens for each resource to download - -* sysupdate: decrypt SHA256SUMS with key from tpm - -* sysupdate: clean up stuff on disk that disappears from SHA256SUMS - -* sysupdate: turn http backend stuff int plugin via varlink - * add new tool that can be used in debug mode runs in very early boot, generates a random password, passes it as credential to sysusers for the root user, then displays it on screen. people can use this to remotely log in. @@ -228,14 +218,6 @@ Features: InodeRef which *both* pins the inode via an fd, *and* gives us a friendly name for it. -* systemd-sysupdate: for each transfer support looking at multiple sources, - pick source with newest entry. If multiple sources have the same entry, use - first configured source. Usecase: "sideload" components from local dirs, - without disabling remote sources. - -* systemd-sysupdate: support "revoked" items, which cause the client to - downgrade/upgrade - * portable services: attach not only unit files to host, but also simple binaries to a tmpfs path in $PATH. @@ -243,17 +225,9 @@ Features: runs it in a new namespace and then just executes the selected binary within it. Could be useful to run one-off binaries inside a sysext as a CLI tool. -* systemd-repart: implement Integrity=data/meta and Integrity=inline for non-LUKS - case. Currently, only Integrity=inline combined with Encrypt= is implemented - and uses libcryptsetup features. Add support for plain dm-integrity setups when - integrity tags are stored by the device (inline), interleaved with data (data), - and on a separate device (meta). - * homed/pam_systemd: allow authentication by ssh-agent, so that run0/polkit can be allowed if caller comes with the right ssh-agent keys. -* machined: gc for OCI layers that are not referenced anymore by any .mstack/ links. - * pull-oci: progress notification * networkd/machined: implement reverse name lookups in the resolved hook @@ -264,16 +238,6 @@ Features: altname or so). This way, when spawning a VM the host could pick the hostname for it and the client gets no say. -* systemd-repart: add --ghost, that creates file systems, updates the kernel's - partition table but does *not* update partition table on disk. This way, we - have disk backed file systems that go effectively disappear on reboot. This - is useful when booting from a "live" usb stick that is writable, as it means - we do not have to place everything in memory. Moreover, we could then migrate - the file systems to disk later (using btrfs device replacement), if needed as - part of an installer logic. - -* journald: log pidfid as another field, i.e. _PIDFDID= - * measure all log-in attempts into a new nvpcr * maybe rework systemd-modules-load to be a generator that just instantiates @@ -324,8 +288,6 @@ Features: * maybe introduce a new per-unit drop-in directory .confext.d/ that may contain symlinks to confext images to enable for the unit. -* nspawn: map foreign UID range through 1:1 - * a small tool that can do basic btrfs raid policy mgmt. i.e. gets started as part of the initial transaction for some btrfs raid fs, waits for some time, then puts message on screen (plymouth, console) that some devices apparently @@ -333,11 +295,6 @@ Features: retriggers the fs is was invoked for, which causes the udev rules to rerun that assemble the btrfs raid, but this time force degraded assembly. -* systemd-repart: make useful to duplicate current OS onto a second disk, so - that we can sanely copy ESP contents, /usr/ images, and then set up btrfs - raid for the root fs to extend/mirror the existing install. This would be - very similar to the concept of live-install-through-btrfs-migration. - * introduce /etc/boottab or so which lists block devices that bootctl + kernel-install shall update the ESPs on (and register in EFI BootXYZ variables), in addition to whatever is currently the booted /usr/. @@ -384,10 +341,6 @@ Features: it. if it doesn't check out, i.e. the measurement we made doesn't appear in the PCR then also reboot. -* cryptsetup: add boolean for disabling use of any password/recovery key slots. - (i.e. that we can operate in a tpm-only mode, and thus protect us from rogue - root disks) - * complete varlink introspection comments: - io.systemd.Hostname - io.systemd.ManagedOOM @@ -435,12 +388,6 @@ Features: vs. "home" vs. "home area". Stick to one term for the concept, and it probably shouldn't contain "area". -* sd-boot: do something useful if we find exactly zero entries (ignoring items - such as reboot/poweroff/factory reset). Show a help text or so. - -* sd-boot: optionally ask for confirmation before executing certain operations - (e.g. factory resets, storagetm with world access, and so on) - * add field to bls type 1 and type 2 profiles that ensures an item is never considered for automatic selection @@ -448,11 +395,6 @@ Features: them under various conditions: 1. if tpm2 is available or not available; 2. if sb is on or off; 3. if we are netbooted or not; … -* logind: invoke a service manager for "area" logins too. i.e. instantiate - user@.service also for logins where XDG_AREA is set, in per-area fashion, and - ref count it properly. Benefit: graphical logins should start working with - the area logic. - * repart: introduce concept of "ghost" partitions, that we setup in almost all ways like other partitions, but do not actually register in the actual gpt table, but only tell the kernel about via BLKPG ioctl. These partitions are @@ -571,17 +513,9 @@ Features: * Reset TPM2 DA bit on each successful boot -* systemd-repart: add --installer or so, that will interactively ask for a - target disk, maybe ask for confirmation, and install something on disk. Then, - hook that into installer.target or so, so that it can be used to - install/replicate installs - * systemd-cryptenroll: add --firstboot or so, that will interactively ask user whether recovery key shall be enrolled and do so -* bootctl: add tool for registering BootXXX entry that boots from some http - server of your choice (i.e. like kernel-bootcfg --add-uri=) - * maybe introduce container-shell@.service or so, to match container-getty.service but skips authentication, so you get a shell prompt directly. Usecase: wsl-like stuff (they have something pretty much like @@ -603,12 +537,8 @@ Features: * allow dynamic modifications of ConcurrencyHardMax= and ConcurrencySoftMax= via DBus (and with that also by daemon-reload) -* sysupdated: introduce per-user version that can update per-user installed dDIs - * portabled: similar -* resolved: make resolved process DNR DHCP info - * maybe introduce an OSC sequence that signals when we ask for a password, so that terminal emulators can maybe connect a password manager or so, and highlight things specially. @@ -620,20 +550,6 @@ Features: - add support to export-fs, import-fs - systemd-dissect should learn mappings, too, when doing mtree and such -* resolved: report ttl in resolution replies if we know it. This data is useful - for tools such as wireguard which want to periodically re-resolve DNS names, - and might want to use the TTL has hint for that. - -* journald: beef up ClientContext logic to store pidfd_id of peer, to validate - we really use the right cache entry - -* journald: log client's pidfd id as a new automatic field _PIDFDID= or so. - -* journald: split up ClientContext cache in two: one cache keyed by pid/pidfdid - with process information, and another one keyed by cgroup path/cgroupid with - cgroup information. This way if a service consisting of many logging - processes can take benefit of the cgroup caching. - * system LSFMMBPF policy that prohibits creating files owned by "nobody" system-wide @@ -785,12 +701,6 @@ Features: * systemd-tpm2-support: add a some logic that detects if system is in DA lockout mode, and queries the user for TPM recovery PIN then. -* systemd-repart should probably enable btrfs' "temp_fsid" feature for all file - systems it creates, as we have no interest in RAID for repart, and it should - make sure that we can mount them trivially everywhere. - -* systemd-nspawn should get the same SSH key support that vmspawn now has. - * move documentation about our common env vars (SYSTEMD_LOG_LEVEL, SYSTEMD_PAGER, …) into a man page of its own, and just link it from our various man pages that so far embed the whole list again and again, in an @@ -854,33 +764,11 @@ Features: * ditto: rewrite bpf-firewall in libbpf/C code -* credentials: if we ever acquire a secure way to derive cgroup id of socket - peers (i.e. SO_PEERCGROUPID), then extend the "scoped" credential logic to - allow cgroup-scoped (i.e. app or service scoped) credentials. Then, as next - step use this to implement per-app/per-service encrypted directories, where - we set up fscrypt on the StateDirectory= with a randomized key which is - stored as xattr on the directory, encrypted as a credential. - -* credentials: optionally include a per-user secret in scoped user-credential - encryption keys. should come from homed in some way, derived from the luks - volume key or fscrypt directory key. - -* credentials: add a flag to the scoped credentials that if set require PK - reauthentication when unlocking a secret. - -* credentials: rework docs. The list in - https://systemd.io/CREDENTIALS/#well-known-credentials is very stale. - Document credentials in individual man pages, generate list as in - systemd.directives. - * extend the smbios11 logic for passing credentials so that instead of passing the credential data literally it can also just reference an AF_VSOCK CID/port to read them from. This way the data doesn't remain in the SMBIOS blob during runtime, but only in the credentials fs. -* machined: optionally track nspawn unix-export/ runtime for each machined, and - then update systemd-ssh-proxy so that it can connect to that. - * introduce mntid_t, and make it 64bit, as apparently the kernel switched to 64bit mount ids @@ -901,10 +789,6 @@ Features: writing. This would then mean: systemd-firstboot would process creds but not ask interactively, getty would not be started and so on. -* cryptsetup: new crypttab option to auto-grow a luks device to its backing - partition size. new crypttab option to reencrypt a luks device with a new - volume key. - * we probably should have some infrastructure to acquire sysexts with drivers/firmware for local hardware automatically. Idea: reuse the modalias logic of the kernel for this: make the main OS image install a hwdb file @@ -917,17 +801,10 @@ Features: on top. Usecase: confexts that shall be signed by the admin but also be confidential. Then, add a new --make-ddi=confext-encrypted for this. -* tmpfiles: add new line type for moving files from some source dir to some - target dir. then use that to move sysexts/confexts and stuff from initrd - tmpfs to /run/, so that host can pick things up. - * tiny varlink service that takes a fd passed in and serves it via http. Then make use of that in networkd, and expose some EFI binary of choice for DHCP/HTTP base EFI boot. -* bootctl: add reboot-to-disk which takes a block device name, and - automatically sets things up so that system reboots into that device next. - * maybe: in PID1, when we detect we run in an initrd, make superblock read-only early on, but provide opt-out via kernel cmdline. @@ -976,31 +853,10 @@ Features: by sd-boot and sd-stub by adding LoaderFeatures/StubFeatures flag for this, so that sd-stub can avoid it if sd-boot already did it. -* cryptsetup: a mechanism that allows signing a volume key with some key that - has to be present in the kernel keyring, or similar, to ensure that confext - DDIs can be encrypted against the local SRK but signed with the admin's key - and thus can authenticated locally before they are decrypted. - * image policy should be extended to allow dictating *how* a disk is unlocked, i.e. root=encrypted-tpm2+encrypted-fido2 would mean "root fs must be encrypted and unlocked via fido2 or tpm2, but not otherwise" -* systemd-repart: add support for formatting dm-crypt + dm-integrity file - systems. - -* homed: use systemd-storagetm to expose home dirs via nvme-tcp. Then, - teach homed/pam_systemd_homed with a user name such as - lennart%nvme_tcp_192.168.100.77_8787 to log in from any linux host with the - same home dir. Similar maybe for nbd, iscsi? this should then first ask for - the local root pw, to authenticate that logging in like this is ok, and would - then be followed by another password prompt asking for the user's own - password. Also, do something similar for CIFS: if you log in via - lennart%cifs-someserver_someshare, then set up the homed dir for it - automatically. The PAM module should update the user name used for login to - the short version once it set up the user. Some care should be taken, so that - the long version can be still be resolved via NSS afterwards, to deal with - PAM clients that do not support PAM sessions where PAM_USER changes half-way. - * redefine /var/lib/extensions/ as the dir one can place all three of sysext, confext as well is multi-modal DDIs that qualify as both. Then introduce /var/lib/sysexts/ which can be used to place only DDIs that shall be used as @@ -1066,14 +922,6 @@ Features: * similar, measure some string via pcrphase whenever we resume from hibernate -* homed: add a basic form of secrets management to homed, that stores - secrets in $HOME somewhere, is protected by the accounts own authentication - mechanisms. Should implement something PKCS#11-like that can be used to - implement emulated FIDO2 in unpriv userspace on top (which should happen - outside of homed), emulated PKCS11, and libsecrets support. Operate with a - 2nd key derived from volume key of the user, with which to wrap all - keys. maintain keys in kernel keyring if possible. - * use sd-event ratelimit feature optionally for journal stream clients that log too much @@ -1110,10 +958,6 @@ Features: also mean that the key would be in effect whenever I boot an archlinux UKI built the same way, signed with the same lennart key. -* resolved: take possession of some IPv6 ULA address (let's say - fd00:5353:5353:5353:5353:5353:5353:5353), and listen on port 53 on it for the - local stubs, so that we can make the stub available via ipv6 too. - * Maybe add SwitchRootEx() as new bus call that takes env vars to set for new PID 1 as argument. When adding SwitchRootEx() we should maybe also add a flags param that allows disabling and enabling whether serialization is @@ -1129,11 +973,6 @@ Features: scenarios. Maybe insist sealing is done additionally against some keypair in the TPM to which access is updated on each boot, for the next, or so? -* logind: when logging in, always take an fd to the home dir, to keep the dir - busy, so that autofs release can never happen. (this is generally a good - idea, and specifically works around the fact the autofs ignores busy by mount - namespaces) - * mount most file systems with a restrictive uidmap. e.g. mount /usr/ with a uidmap that blocks out anything outside 0…1000 (i.e. system users) and similar. @@ -1219,21 +1058,11 @@ Features: the executor: via unit files/dbus/varlink through PID1 and via cmdline/OCI through nspawn. -* sd-stub: detect if we are running with uefi console output on serial, and if so - automatically add console= to kernel cmdline matching the same port. - * add a utility that can be used with the kernel's CONFIG_STATIC_USERMODEHELPER_PATH and then handles them within pid1 so that security, resource management and cgroup settings can be enforced properly for all umh processes. -* homed: when resizing an fs don't sync identity beforehand there might simply - not be enough disk space for that. try to be defensive and sync only after - resize. - -* homed: if for some reason the partition ended up being much smaller than - whole disk, recover from that, and grow it again. - * timesyncd: when saving/restoring clock try to take boot time into account. Specifically, along with the saved clock, store the current boot ID. When starting, check if the boot id matches. If so, don't do anything (we are on @@ -1263,8 +1092,11 @@ Features: device to dissect. also support dissecting a regular file. useccase: include encrypted/verity root fs in UKI. -* sd-stub: add ".bootcfg" section for kernel bootconfig data (as per - https://docs.kernel.org/admin-guide/bootconfig.html) +* sd-stub: + - detect if we are running with uefi console output on serial, and if so + automatically add console= to kernel cmdline matching the same port. + - add ".bootcfg" section for kernel bootconfig data (as per + https://docs.kernel.org/admin-guide/bootconfig.html) * tpm2: add (optional) support for generating a local signing key from PCR 15 state. use private key part to sign PCR 7+14 policies. stash signatures for @@ -1312,9 +1144,6 @@ Features: enforce the uuids for partitions created, so that they can calculate PCR 15 ahead of time. -* systemd-repart: also derive the volume key from the seed value, for the - aforementioned purpose. - * in the initrd: derive the default machine ID to pass to the host PID 1 via $machine_id from the same seed credential. @@ -1345,13 +1174,6 @@ Features: * automatic boot assessment: add one more default success check that just waits for a bit after boot, and blesses the boot if the system stayed up that long. -* systemd-repart: add support for generating ISO9660 images - -* systemd-repart: in addition to the existing "factory reset" mode (which - simply empties existing partitions marked for that). add a mode where - partitions marked for it are entirely removed. Use case: remove secondary OS - copy, and redundant partitions entirely, and recreate them anew. - * systemd-boot: maybe add support for collapsing menu entries of the same OS into one item that can be opened (like in a "tree view" UI element) or collapsed. If only a single OS is installed, disable this mode, but if @@ -1359,15 +1181,6 @@ Features: is not immediately bombarded with a multitude of Linux kernel versions but only one for each OS. -* systemd-repart: if the GPT *disk* UUID (i.e. the one global for the entire - disk) is set to all FFFFF then use this as trigger for factory reset, in - addition to the existing mechanisms via EFI variables and kernel command - line. Benefit: works also on non-EFI systems, and can be requested on one - boot, for the next. - -* systemd-sysupdate: make transport pluggable, so people can plug casync or - similar behind it, instead of http. - * systemd-tmpfiles: add concept for conditionalizing lines on factory reset boot, or on first boot. @@ -1423,9 +1236,6 @@ Features: * in the initrd, once the rootfs encryption key has been measured to PCR 15, derive default machine ID to use from it, and pass it to host PID 1. -* sd-boot: for each installed OS, grey out older entries (i.e. all but the - newest), to indicate they are obsolete - * automatically propagate LUKS password credential into cryptsetup from host (i.e. SMBIOS type #11, …), so that one can unlock LUKS via VM hypervisor supplied password. @@ -1472,21 +1282,9 @@ Features: * Add and pickup tpm2 metadata for creds structure. -* sd-boot: we probably should include all BootXY EFI variable defined boot - entries in our menu, and then suppress ourselves. Benefit: instant - compatibility with all other OSes which register things there, in particular - on other disks. Always boot into them via NextBoot EFI variable, to not - affect PCR values. - * systemd-measure tool: - pre-calculate PCR 12 (command line) + PCR 13 (sysext) the same way we can precalculate PCR 11 -* sd-device: add an API for acquiring list of child devices, given a device - objects (i.e. all child dirents that dirs or symlinks to dirs) - -* sd-device: maybe pin the sysfs dir with an fd, during the entire runtime of - an sd_device, then always work based on that. - * maybe add new flags to gpt partition tables for rootfs and usrfs indicating purpose, i.e. whether something is supposed to be bootable in a VM, on baremetal, on an nspawn-style container, if it is a portable service image, @@ -1494,9 +1292,6 @@ Features: portabled/… up to udev to watch block devices coming up with the flags set, and use it. -* sd-boot should look for information what to boot in SMBIOS, too, so that VM - managers can tell sd-boot what to boot into and suchlike - * add "systemd-sysext identify" verb, that you can point on any file in /usr/ and that determines from which overlayfs layer it originates, which image, and with what it was signed. @@ -1553,12 +1348,6 @@ Features: * pam_systemd: on interactive logins, maybe show SUPPORT_END information at login time, à la motd -* sd-boot: instead of unconditionally deriving the ESP to search boot loader - spec entries in from the paths of sd-boot binary, let's optionally allow it - to be configured on sd-boot cmdline + efi var. Use case: embed sd-boot in the - UEFI firmware (for example, ovmf supports that via qemu cmdline option), and - use it to load stuff from the ESP. - * mount /var/ from initrd, so that we can apply sysext and stuff before the initrd transition. Specifically: 1. There should be a var= kernel cmdline option, matching root= and usr= @@ -1572,15 +1361,6 @@ Features: the files are reboot. The files would be backed by tmpfs, pmem or /var depending on desired level of persistency. -* sd-event: add ability to "chain" event sources. Specifically, add a call - sd_event_source_chain(x, y), which will automatically enable event source y - in oneshot mode once x is triggered. Use case: in src/core/mount.c implement - the /proc/self/mountinfo rescan on SIGCHLD with this: whenever a SIGCHLD is - seen, trigger the rescan defer event source automatically, and allow it to be - dispatched *before* the SIGCHLD is handled (based on priorities). Benefit: - dispatch order is strictly controlled by priorities again. (next step: chain - event sources to the ratelimit being over) - * if we fork of a service with StandardOutput=journal, and it forks off a subprocess that quickly dies, we might not be able to identify the cgroup it comes from, but we can still derive that from the stdin socket its output @@ -1626,14 +1406,6 @@ Features: https://0pointer.net/blog/testing-my-system-code-in-usr-without-modifying-usr.html https://0pointer.net/blog/running-an-container-off-the-host-usr.html -* sd-event: compat wd reuse in inotify code: keep a set of removed watch - descriptors, and clear this set piecemeal when we see the IN_IGNORED event - for it, or when read() returns EAGAIN or on IN_Q_OVERFLOW. Then, whenever we - see an inotify wd event check against this set, and if it is contained ignore - the event. (to be fully correct this would have to count the occurrences, in - case the same wd is reused multiple times before we start processing - IN_IGNORED again) - * for vendor-built signed initrds: - kernel-install should be able to install encrypted creds automatically for machine id, root pw, rootfs uuid, resume partition uuid, and place next to @@ -1655,22 +1427,14 @@ Features: appropriate qemu cmdline. That way qemu payloads could talk sd_notify() directly to host service manager. -* sd-device should return the devnum type (i.e. 'b' or 'c') via some API for an - sd_device object, so that data passed into sd_device_new_from_devnum() can - also be queried. - -* sd-event: optionally, if per-event source rate limit is hit, downgrade - priority, but leave enabled, and once ratelimit window is over, upgrade - priority again. That way we can combat event source starvation without - stopping processing events from one source entirely. - -* sd-event: similar to existing inotify support add fanotify support (given - that apparently new features in this area are only going to be added to the - latter). - -* sd-event: add 1st class event source for clock changes - -* sd-event: add 1st class event source for timezone changes +* sd-device: + - add an API for acquiring list of child devices, given a device + objects (i.e. all child dirents that dirs or symlinks to dirs) + - maybe pin the sysfs dir with an fd, during the entire runtime of + an sd_device, then always work based on that. + - should return the devnum type (i.e. 'b' or 'c') via some API for an + sd_device object, so that data passed into sd_device_new_from_devnum() can + also be queried. * sysext: measure all activated sysext into a TPM PCR @@ -1723,14 +1487,6 @@ Features: passwords, not just the first. i.e. if there are multiple defined, prefer unlocked over locked and prefer non-empty over empty. -* homed: if the homed shell fallback thing has access to an SSH agent, try to - use it to unlock home dir (if ssh-agent forwarding is enabled). We - could implement SSH unlocking of a homedir with that: when enrolling a new - ssh pubkey in a user record we'd ask the ssh-agent to sign some random value - with the privkey, then use that as luks key to unlock the home dir. Will not - work for ECDSA keys since their signatures contain a random component, but - will work for RSA and Ed25519 keys. - * userdbd: implement an additional varlink service socket that provides the host user db in restricted form, then allow this to be bind mounted into sandboxed environments that want the host database in minimal form. All @@ -1762,6 +1518,20 @@ Features: --definitions= pointing to a file rather than a dir. - add ability to disable implicit decompression of downloaded artifacts, i.e. a Compress=no option in the transfer definitions + - download multiple arbitrary patterns from same source + - SHA256SUMS format with bearer tokens for each resource to download + - decrypt SHA256SUMS with key from tpm + - clean up stuff on disk that disappears from SHA256SUMS + - turn http backend stuff int plugin via varlink + - for each transfer support looking at multiple sources, + pick source with newest entry. If multiple sources have the same entry, use + first configured source. Usecase: "sideload" components from local dirs, + without disabling remote sources. + - support "revoked" items, which cause the client to + downgrade/upgrade + - introduce per-user version that can update per-user installed dDIs + - make transport pluggable, so people can plug casync or + similar behind it, instead of http. * in sd-id128: also parse UUIDs in RFC4122 URN syntax (i.e. chop off urn:uuid: prefix) @@ -1807,31 +1577,51 @@ Features: wireguard) - make gatewayd/remote read key via creds logic - add sd_notify() command for flushing out creds not needed anymore + - if we ever acquire a secure way to derive cgroup id of socket + peers (i.e. SO_PEERCGROUPID), then extend the "scoped" credential logic to + allow cgroup-scoped (i.e. app or service scoped) credentials. Then, as next + step use this to implement per-app/per-service encrypted directories, where + we set up fscrypt on the StateDirectory= with a randomized key which is + stored as xattr on the directory, encrypted as a credential. + - optionally include a per-user secret in scoped user-credential + encryption keys. should come from homed in some way, derived from the luks + volume key or fscrypt directory key. + - add a flag to the scoped credentials that if set require PK + reauthentication when unlocking a secret. + - rework docs. The list in + https://systemd.io/CREDENTIALS/#well-known-credentials is very stale. + Document credentials in individual man pages, generate list as in + systemd.directives. * TPM2: auto-reenroll in cryptsetup, as fallback for hosed firmware upgrades and such * introduce a new group to own TPM devices -* cryptsetup: add option for automatically removing empty password slot on boot - -* cryptsetup: optionally, when run during boot-up and password is never - entered, and we are on battery power (or so), power off machine again - -* cryptsetup: when waiting for FIDO2/PKCS#11 token, tell plymouth that, and - allow plymouth to abort the waiting and enter pw instead - * make cryptsetup lower --iter-time -* cryptsetup: allow encoding key directly in /etc/crypttab, maybe with a - "base64:" prefix. Useful in particular for pkcs11 mode. - -* cryptsetup: reimplement the mkswap/mke2fs in cryptsetup-generator to use - systemd-makefs.service instead. - * cryptsetup: - cryptsetup-generator: allow specification of passwords in crypttab itself - support rd.luks.allow-discards= kernel cmdline params in cryptsetup generator + - add boolean for disabling use of any password/recovery key slots. + (i.e. that we can operate in a tpm-only mode, and thus protect us from rogue + root disks) + - new crypttab option to auto-grow a luks device to its backing + partition size. new crypttab option to reencrypt a luks device with a new + volume key. + - a mechanism that allows signing a volume key with some key that + has to be present in the kernel keyring, or similar, to ensure that confext + DDIs can be encrypted against the local SRK but signed with the admin's key + and thus can authenticated locally before they are decrypted. + - add option for automatically removing empty password slot on boot + - optionally, when run during boot-up and password is never + entered, and we are on battery power (or so), power off machine again + - when waiting for FIDO2/PKCS#11 token, tell plymouth that, and + allow plymouth to abort the waiting and enter pw instead + - allow encoding key directly in /etc/crypttab, maybe with a + "base64:" prefix. Useful in particular for pkcs11 mode. + - reimplement the mkswap/mke2fs in cryptsetup-generator to use + systemd-makefs.service instead. * systemd-analyze netif that explains predictable interface (or networkctl) @@ -1850,20 +1640,16 @@ Features: * if /usr/bin/swapoff fails due to OOM, log a friendly explanatory message about it -* pid1: also remove PID files of a service when the service starts, not just - when it exits - -* seccomp: maybe use seccomp_merge() to merge our filters per-arch if we can. - Apparently kernel performance is much better with fewer larger seccomp - filters than with more smaller seccomp filters. - * systemd-path: Add "private" runtime/state/cache dir enum, mapping to $RUNTIME_DIRECTORY, $STATE_DIRECTORY and such -* seccomp: by default mask x32 ABI system wide on x86-64. it's on its way out - -* seccomp: don't install filters for ABIs that are masked anyway for the - specific service +* seccomp: + - maybe use seccomp_merge() to merge our filters per-arch if we can. + Apparently kernel performance is much better with fewer larger seccomp + filters than with more smaller seccomp filters. + - by default mask x32 ABI system wide on x86-64. it's on its way out + - don't install filters for ABIs that are masked anyway for the + specific service * busctl: maybe expose a verb "ping" for pinging a dbus service to see if it exists and responds. @@ -1883,19 +1669,10 @@ Features: * userdb: allow existence checks -* pid1: activation by journal search expression - * when switching root from initrd to host, set the machine_id env var so that if the host has no machine ID set yet we continue to use the random one the initrd had set. -* sd-event: add native support for P_ALL waitid() watching, then move PID 1 to - it for reaping assigned but unknown children. This needs to some special care - to operate somewhat sensibly in light of priorities: P_ALL will return - arbitrary processes, regardless of the priority we want to watch them with, - hence on each event loop iteration check all processes which we shall watch - with higher prio explicitly, and then watch the entire rest with P_ALL. - * tweak sd-event's child watching: keep a prioq of children to watch and use waitid() only on the children with the highest priority until one is waitable and ignore all lower-prio ones from that point on @@ -1959,8 +1736,27 @@ Features: * optionally: turn on cgroup delegation for per-session scope units -* sd-boot: optionally, show boot menu when previous default boot item has - non-zero "tries done" count +* sd-boot: + - do something useful if we find exactly zero entries (ignoring items + such as reboot/poweroff/factory reset). Show a help text or so. + - optionally ask for confirmation before executing certain operations + (e.g. factory resets, storagetm with world access, and so on) + - for each installed OS, grey out older entries (i.e. all but the + newest), to indicate they are obsolete + - we probably should include all BootXY EFI variable defined boot + entries in our menu, and then suppress ourselves. Benefit: instant + compatibility with all other OSes which register things there, in particular + on other disks. Always boot into them via NextBoot EFI variable, to not + affect PCR values. + - should look for information what to boot in SMBIOS, too, so that VM + managers can tell sd-boot what to boot into and suchlike + - instead of unconditionally deriving the ESP to search boot loader + spec entries in from the paths of sd-boot binary, let's optionally allow it + to be configured on sd-boot cmdline + efi var. Use case: embed sd-boot in the + UEFI firmware (for example, ovmf supports that via qemu cmdline option), and + use it to load stuff from the ESP. + - optionally, show boot menu when previous default boot item has + non-zero "tries done" count * augment CODE_FILE=, CODE_LINE= with something like CODE_BASE= or so which contains some identifier for the project, which allows us to include @@ -2004,8 +1800,6 @@ Features: * systemctl, machinectl, loginctl: port "status" commands over to format-table.c's vertical output logic. -* pid1: lock image configured with RootDirectory=/RootImage= using the usual nspawn semantics while the unit is up - * add --vacuum-xyz options to coredumpctl, matching those journalctl already has. * add CopyFile= or so as unit file setting that may be used to copy files or @@ -2031,12 +1825,6 @@ Features: * calenderspec: add support for week numbers and day numbers within a year. This would allow us to define "bi-weekly" triggers safely. -* sd-bus: add vtable flag, that may be used to request client creds implicitly - and asynchronously before dispatching the operation - -* sd-bus: parse addresses given in sd_bus_set_addresses immediately and not - only when used. Add unit tests. - * make use of ethtool veth peer info in machined, for automatically finding out host-side interface pointing to the container. @@ -2147,9 +1935,6 @@ Features: * cache sd_event_now() result from before the first iteration... -* PID1: find a way how we can reload unit file configuration for - specific units only, without reloading the whole of systemd - * add an explicit parser for LimitRTPRIO= that verifies the specified range and generates sane error messages for incorrect specifications. @@ -2215,6 +2000,13 @@ Features: names, so that for the container case we can establish the same name (maybe "host") for referencing the server, everywhere. - allow clients to request DNSSEC for a single lookup even if DNSSEC is off (?) + - make resolved process DNR DHCP info + - report ttl in resolution replies if we know it. This data is useful + for tools such as wireguard which want to periodically re-resolve DNS names, + and might want to use the TTL has hint for that. + - take possession of some IPv6 ULA address (let's say + fd00:5353:5353:5353:5353:5353:5353:5353), and listen on port 53 on it for the + local stubs, so that we can make the stub available via ipv6 too. * refcounting in sd-resolve is borked @@ -2321,6 +2113,12 @@ Features: system-wide confext/sysext should support this too. - Pin the mount namespace via FD by sending it back from sd-exec to the manager, and use it for live mounting, instead of doing it via PID + - also remove PID files of a service when the service starts, not just + when it exits + - activation by journal search expression + - lock image configured with RootDirectory=/RootImage= using the usual nspawn semantics while the unit is up + - find a way how we can reload unit file configuration for + specific units only, without reloading the whole of systemd * unit files: - allow port=0 in .socket units @@ -2403,6 +2201,10 @@ Features: - NameLost/NameAcquired obsolete - path escaping - update systemd.special(7) to mention that dbus.socket is only about the compatibility socket now + - add vtable flag, that may be used to request client creds implicitly + and asynchronously before dispatching the operation + - parse addresses given in sd_bus_set_addresses immediately and not + only when used. Add unit tests. * sd-event: - allow multiple signal handlers per signal? @@ -2412,6 +2214,36 @@ Features: operations instead of IO ready events into event loops. See considerations here: http://blog.vmsplice.net/2020/07/rethinking-event-loop-integration-for.html + - add ability to "chain" event sources. Specifically, add a call + sd_event_source_chain(x, y), which will automatically enable event source y + in oneshot mode once x is triggered. Use case: in src/core/mount.c implement + the /proc/self/mountinfo rescan on SIGCHLD with this: whenever a SIGCHLD is + seen, trigger the rescan defer event source automatically, and allow it to be + dispatched *before* the SIGCHLD is handled (based on priorities). Benefit: + dispatch order is strictly controlled by priorities again. (next step: chain + event sources to the ratelimit being over) + - compat wd reuse in inotify code: keep a set of removed watch + descriptors, and clear this set piecemeal when we see the IN_IGNORED event + for it, or when read() returns EAGAIN or on IN_Q_OVERFLOW. Then, whenever we + see an inotify wd event check against this set, and if it is contained ignore + the event. (to be fully correct this would have to count the occurrences, in + case the same wd is reused multiple times before we start processing + IN_IGNORED again) + - optionally, if per-event source rate limit is hit, downgrade + priority, but leave enabled, and once ratelimit window is over, upgrade + priority again. That way we can combat event source starvation without + stopping processing events from one source entirely. + - similar to existing inotify support add fanotify support (given + that apparently new features in this area are only going to be added to the + latter). + - add 1st class event source for clock changes + - add 1st class event source for timezone changes + - add native support for P_ALL waitid() watching, then move PID 1 to + it for reaping assigned but unknown children. This needs to some special care + to operate somewhat sensibly in light of priorities: P_ALL will return + arbitrary processes, regardless of the priority we want to watch them with, + hence on each event loop iteration check all processes which we shall watch + with higher prio explicitly, and then watch the entire rest with P_ALL. * dbus: when a unit failed to load (i.e. is in UNIT_ERROR state), we should be able to safely try another attempt when the bus call LoadUnit() is invoked. @@ -2429,8 +2261,10 @@ Features: - honor timezone efi variables for default timezone selection (if there are any?) * bootctl: - recognize the case when not booted on EFI - -* bootctl: + - add tool for registering BootXXX entry that boots from some http + server of your choice (i.e. like kernel-bootcfg --add-uri=) + - add reboot-to-disk which takes a block device name, and + automatically sets things up so that system reboots into that device next. - show whether UEFI audit mode is available - teach it to prepare an ESP wholesale, i.e. with mkfs.vfat invocation - teach it to copy in unified kernel images and maybe type #1 boot loader spec entries from host @@ -2460,6 +2294,14 @@ Features: - follow PropertiesChanged state more closely, to deal with quick logouts and relogins - (optionally?) spawn seat-manager@$SEAT.service whenever a seat shows up that as CanGraphical set + - invoke a service manager for "area" logins too. i.e. instantiate + user@.service also for logins where XDG_AREA is set, in per-area fashion, and + ref count it properly. Benefit: graphical logins should start working with + the area logic. + - when logging in, always take an fd to the home dir, to keep the dir + busy, so that autofs release can never happen. (this is generally a good + idea, and specifically works around the fact the autofs ignores busy by mount + namespaces) * move multiseat vid/pid matches from logind udev rule to hwdb @@ -2528,9 +2370,38 @@ Features: Benefit: nspawn --ephemeral would start working nicely with the journal. - assign MESSAGE_ID to log messages about failed services - check if loop in decompress_blob_xz() is necessary - -* journald: support RFC3164 fully for the incoming syslog transport, see - https://github.com/systemd/systemd/issues/19251#issuecomment-816601955 + - log pidfid as another field, i.e. _PIDFDID= + - beef up ClientContext logic to store pidfd_id of peer, to validate + we really use the right cache entry + - log client's pidfd id as a new automatic field _PIDFDID= or so. + - split up ClientContext cache in two: one cache keyed by pid/pidfdid + with process information, and another one keyed by cgroup path/cgroupid with + cgroup information. This way if a service consisting of many logging + processes can take benefit of the cgroup caching. + - support RFC3164 fully for the incoming syslog transport, see + https://github.com/systemd/systemd/issues/19251#issuecomment-816601955 + - add varlink service that allows subscribing to certain log events, + for example matching by message ID, or log level returns a list of journal + cursors as they happen. + - also collect CLOCK_BOOTTIME timestamps per log entry. Then, derive + "corrected" CLOCK_REALTIME information on display from that and the timestamp + info of the newest entry of the specific boot (as identified by the boot + ID). This way, if a system comes up without a valid clock but acquires a + better clock later, we can "fix" older entry timestamps on display, by + calculating backwards. We cannot use CLOCK_MONOTONIC for this, since it does + not account for suspend phases. This would then also enable us to correct the + kmsg timestamping we consume (where we erroneously assume the clock was in + CLOCK_MONOTONIC, but it actually is CLOCK_BOOTTIME as per kernel). + - generate recognizable log events whenever we shutdown journald + cleanly, and when we migrate run → var. This way tools can verify that a + previous boot terminated cleanly, because either of these two messages must + be safely written to disk, then. + - do journal file writing out-of-process, with one writer process per + client UID, so that synthetic hash table collisions can slow down a specific + user's journal stream down but not the others. + - make sure -f ends when the container indicated by -M terminates + - sigbus API via a signal-handler safe function that people may call + from the SIGBUS handler * Hook up journald's FSS logic with TPM2: seal the verification disk by time-based policy, so that the verification key can remain on host and ve @@ -2540,20 +2411,6 @@ Features: fd of the relevant journal dirs in the container with uidmapping applied to allow the host to read it, while making everything read-only. -* journald: add varlink service that allows subscribing to certain log events, - for example matching by message ID, or log level returns a list of journal - cursors as they happen. - -* journald: also collect CLOCK_BOOTTIME timestamps per log entry. Then, derive - "corrected" CLOCK_REALTIME information on display from that and the timestamp - info of the newest entry of the specific boot (as identified by the boot - ID). This way, if a system comes up without a valid clock but acquires a - better clock later, we can "fix" older entry timestamps on display, by - calculating backwards. We cannot use CLOCK_MONOTONIC for this, since it does - not account for suspend phases. This would then also enable us to correct the - kmsg timestamping we consume (where we erroneously assume the clock was in - CLOCK_MONOTONIC, but it actually is CLOCK_BOOTTIME as per kernel). - * in journald, write out a recognizable log record whenever the system clock is changed ("stepped"), and in timesyncd whenever we acquire an NTP fix ("slewing"). Then, in journalctl for each boot time we come across, find @@ -2566,11 +2423,6 @@ Features: and new ID. Then, when displaying log stream in journalctl look for these records, to be able to order them. -* journald: generate recognizable log events whenever we shutdown journald - cleanly, and when we migrate run → var. This way tools can verify that a - previous boot terminated cleanly, because either of these two messages must - be safely written to disk, then. - * hook up journald with TPMs? measure new journal records to the TPM in regular intervals, validate the journal against current TPM state with that. (taking inspiration from IMA log) @@ -2599,10 +2451,6 @@ Features: * introduce per-unit (i.e. per-slice, per-service) journal log size limits. -* journald: do journal file writing out-of-process, with one writer process per - client UID, so that synthetic hash table collisions can slow down a specific - user's journal stream down but not the others. - * tweak journald context caching. In addition to caching per-process attributes keyed by PID, cache per-cgroup attributes (i.e. the various xattrs we read) keyed by cgroup path, and guarded by ctime changes. This should provide us @@ -2614,11 +2462,6 @@ Features: O_NONBLOCK on it. That way people can control if and when to block for logging. -* journalctl: make sure -f ends when the container indicated by -M terminates - -* journald: sigbus API via a signal-handler safe function that people may call - from the SIGBUS handler - * add a test if all entries in the catalog are properly formatted. (Adding dashes in a catalog entry currently results in the catalog entry being silently skipped. journalctl --update-catalog must warn about this, @@ -2674,59 +2517,116 @@ Features: login in discard mode, then immediately rebalance, then turn off discard - add "homectl unbind" command to remove local user record of an inactive home dir + - use systemd-storagetm to expose home dirs via nvme-tcp. Then, + teach homed/pam_systemd_homed with a user name such as + lennart%nvme_tcp_192.168.100.77_8787 to log in from any linux host with the + same home dir. Similar maybe for nbd, iscsi? this should then first ask for + the local root pw, to authenticate that logging in like this is ok, and would + then be followed by another password prompt asking for the user's own + password. Also, do something similar for CIFS: if you log in via + lennart%cifs-someserver_someshare, then set up the homed dir for it + automatically. The PAM module should update the user name used for login to + the short version once it set up the user. Some care should be taken, so that + the long version can be still be resolved via NSS afterwards, to deal with + PAM clients that do not support PAM sessions where PAM_USER changes half-way. + - add a basic form of secrets management to homed, that stores + secrets in $HOME somewhere, is protected by the accounts own authentication + mechanisms. Should implement something PKCS#11-like that can be used to + implement emulated FIDO2 in unpriv userspace on top (which should happen + outside of homed), emulated PKCS11, and libsecrets support. Operate with a + 2nd key derived from volume key of the user, with which to wrap all + keys. maintain keys in kernel keyring if possible. + - when resizing an fs don't sync identity beforehand there might simply + not be enough disk space for that. try to be defensive and sync only after + resize. + - if for some reason the partition ended up being much smaller than + whole disk, recover from that, and grow it again. + - if the homed shell fallback thing has access to an SSH agent, try to + use it to unlock home dir (if ssh-agent forwarding is enabled). We + could implement SSH unlocking of a homedir with that: when enrolling a new + ssh pubkey in a user record we'd ask the ssh-agent to sign some random value + with the privkey, then use that as luks key to unlock the home dir. Will not + work for ECDSA keys since their signatures contain a random component, but + will work for RSA and Ed25519 keys. * add a new switch --auto-definitions=yes/no or so to systemd-repart. If specified, synthesize a definition automatically if we can: enlarge last partition on disk, but only if it is marked for growing and not read-only. -* systemd-repart: read LUKS encryption key from $CREDENTIALS_DIRECTORY - -* systemd-repart: support setting up dm-integrity with HMAC - -* systemd-repart: maybe remove half-initialized image on failure. It fails - if the output file exists, so a repeated invocation will usually fail if - something goes wrong on the way. - -* systemd-repart: by default generate minimized partition tables (i.e. tables - that only cover the space actually used, excluding any free space at the - end), in order to maximize dd'ability. Requires libfdisk work, see - https://github.com/karelzak/util-linux/issues/907 - -* systemd-repart: MBR partition table support. Care needs to be taken regarding - Type=, so that partition definitions can sanely apply to both the GPT and the - MBR case. Idea: accept syntax "Type=gpt:home mbr:0x83" for setting the types - for the two partition types explicitly. And provide an internal mapping so - that "Type=linux-generic" maps to the right types for both partition tables - automatically. - -* systemd-repart: allow sizing partitions as factor of available RAM, so that - we can reasonably size swap partitions for hibernation. - -* systemd-repart: allow boolean option that ensures that if existing partition - doesn't exist within the configured size bounds the whole command fails. This - is useful to implement ESP vs. XBOOTLDR schemes in installers: have one set - of repart files for the case where ESP is large enough and one where it isn't - and XBOOTLDR is added in instead. Then apply the former first, and if it - fails to apply use the latter. - -* systemd-repart: add per-partition option to never reuse existing partition - and always create anew even if matching partition already exists. - -* systemd-repart: add per-partition option to fail if partition already exist, - i.e. is not added new. Similar, add option to fail if partition does not exist yet. - -* systemd-repart: allow disabling growing of specific partitions, or making - them (think ESP: we don't ever want to grow it, since we cannot resize vfat) - Also add option to disable operation via kernel command line. - -* systemd-repart: make it a static checker during early boot for existence and - absence of other partitions for trusted boot environments - -* systemd-repart: add support for SD_GPT_FLAG_GROWFS also on real systems, i.e. - generate some unit to actually enlarge the fs after growing the partition - during boot. - -* systemd-repart: do not print "Successfully resized …" when no change was done. +* systemd-repart: + - implement Integrity=data/meta and Integrity=inline for non-LUKS + case. Currently, only Integrity=inline combined with Encrypt= is implemented + and uses libcryptsetup features. Add support for plain dm-integrity setups when + integrity tags are stored by the device (inline), interleaved with data (data), + and on a separate device (meta). + - add --ghost, that creates file systems, updates the kernel's + partition table but does *not* update partition table on disk. This way, we + have disk backed file systems that go effectively disappear on reboot. This + is useful when booting from a "live" usb stick that is writable, as it means + we do not have to place everything in memory. Moreover, we could then migrate + the file systems to disk later (using btrfs device replacement), if needed as + part of an installer logic. + - make useful to duplicate current OS onto a second disk, so + that we can sanely copy ESP contents, /usr/ images, and then set up btrfs + raid for the root fs to extend/mirror the existing install. This would be + very similar to the concept of live-install-through-btrfs-migration. + - add --installer or so, that will interactively ask for a + target disk, maybe ask for confirmation, and install something on disk. Then, + hook that into installer.target or so, so that it can be used to + install/replicate installs + - should probably enable btrfs' "temp_fsid" feature for all file + systems it creates, as we have no interest in RAID for repart, and it should + make sure that we can mount them trivially everywhere. + - add support for formatting dm-crypt + dm-integrity file + systems. + - also derive the volume key from the seed value, for the + aforementioned purpose. + - add support for generating ISO9660 images + - in addition to the existing "factory reset" mode (which + simply empties existing partitions marked for that). add a mode where + partitions marked for it are entirely removed. Use case: remove secondary OS + copy, and redundant partitions entirely, and recreate them anew. + - if the GPT *disk* UUID (i.e. the one global for the entire + disk) is set to all FFFFF then use this as trigger for factory reset, in + addition to the existing mechanisms via EFI variables and kernel command + line. Benefit: works also on non-EFI systems, and can be requested on one + boot, for the next. + - read LUKS encryption key from $CREDENTIALS_DIRECTORY + - support setting up dm-integrity with HMAC + - maybe remove half-initialized image on failure. It fails + if the output file exists, so a repeated invocation will usually fail if + something goes wrong on the way. + - by default generate minimized partition tables (i.e. tables + that only cover the space actually used, excluding any free space at the + end), in order to maximize dd'ability. Requires libfdisk work, see + https://github.com/karelzak/util-linux/issues/907 + - MBR partition table support. Care needs to be taken regarding + Type=, so that partition definitions can sanely apply to both the GPT and the + MBR case. Idea: accept syntax "Type=gpt:home mbr:0x83" for setting the types + for the two partition types explicitly. And provide an internal mapping so + that "Type=linux-generic" maps to the right types for both partition tables + automatically. + - allow sizing partitions as factor of available RAM, so that + we can reasonably size swap partitions for hibernation. + - allow boolean option that ensures that if existing partition + doesn't exist within the configured size bounds the whole command fails. This + is useful to implement ESP vs. XBOOTLDR schemes in installers: have one set + of repart files for the case where ESP is large enough and one where it isn't + and XBOOTLDR is added in instead. Then apply the former first, and if it + fails to apply use the latter. + - add per-partition option to never reuse existing partition + and always create anew even if matching partition already exists. + - add per-partition option to fail if partition already exist, + i.e. is not added new. Similar, add option to fail if partition does not exist yet. + - allow disabling growing of specific partitions, or making + them (think ESP: we don't ever want to grow it, since we cannot resize vfat) + Also add option to disable operation via kernel command line. + - make it a static checker during early boot for existence and + absence of other partitions for trusted boot environments + - add support for SD_GPT_FLAG_GROWFS also on real systems, i.e. + generate some unit to actually enlarge the fs after growing the partition + during boot. + - do not print "Successfully resized …" when no change was done. * document: - document that deps in [Unit] sections ignore Alias= fields in @@ -2748,6 +2648,7 @@ Features: - add new command to systemctl: "systemctl system-reexec" which reexecs as many daemons as virtually possible - systemctl enable: fail if target to alias into does not exist? maybe show how many units are enabled afterwards? - systemctl: "Journal has been rotated since unit was started." message is misleading + - if some operation fails, show log output? * introduce an option (or replacement) for "systemctl show" that outputs all properties as JSON, similar to busctl's new JSON output. In contrast to that @@ -2769,8 +2670,6 @@ Features: ensure deterministic behaviour if two unit files conflict (like DMs do, for example) -* systemctl: if some operation fails, show log output? - * Add a new verb "systemctl top" * unit install: @@ -2826,6 +2725,8 @@ Features: investigate whether creating the inner child with CLONE_PARENT isn't better. - Reduce the number of sockets that are currently in use and just rely on one or two sockets. + - map foreign UID range through 1:1 + - d-nspawn should get the same SSH key support that vmspawn now has. * machined: - add an API so that libvirt-lxc can inform us about network interfaces being @@ -2840,6 +2741,9 @@ Features: - "machinectl diff" - "machinectl commit" that takes a writable snapshot of a tree, invokes a shell in it, and marks it read-only after use + - gc for OCI layers that are not referenced anymore by any .mstack/ links. + - optionally track nspawn unix-export/ runtime for each machined, and + then update systemd-ssh-proxy so that it can connect to that. * udev: - move to LGPL @@ -2868,6 +2772,9 @@ Features: - add new line type for setting btrfs subvolume attributes (i.e. rw/ro) - tmpfiles: add new line type for setting fcaps - add -n as shortcut for --dry-run in tmpfiles & sysusers & possibly other places + - add new line type for moving files from some source dir to some + target dir. then use that to move sysexts/confexts and stuff from initrd + tmpfs to /run/, so that host can pick things up. * udev-link-config: - Make sure ID_PATH is always exported and complete for From a0039450f2e334349cb614bb03011695324f6d95 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 27 Mar 2026 13:41:43 +0100 Subject: [PATCH 0605/1296] TODO: sort Features entries alphabetically Signed-off-by: Christian Brauner --- TODO | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TODO b/TODO index c238794c2f404..2389514e7861f 100644 --- a/TODO +++ b/TODO @@ -585,9 +585,9 @@ Features: policy bits into one structure, i.e. public key info, pcr masks, pcrlock stuff, pin and so on. Then pass that around in tpm2_seal() and tpm2_unseal(). -* look at nsresourced, mountfsd, homed, importd, and try to come up with a way - how the forked off worker processes can be moved into transient services with - sandboxing, without breaking notify socket stuff and so on. +* look at nsresourced, mountfsd, homed, importd, portabled, and try to come up + with a way how the forked off worker processes can be moved into transient + services with sandboxing, without breaking notify socket stuff and so on. * replace all \x1b, \x1B, \033 C string escape sequences in our codebase with a more readable \e. It's a GNU extension, but a ton more readable than the From 38c2209e8fd5fb5886cdb9fd5b8edbbdfb795f30 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 27 Mar 2026 13:46:38 +0100 Subject: [PATCH 0606/1296] TODO: convert to markdown Rename TODO to TODO.md and convert to proper markdown format: section headers become ## headings and bullet items use - instead of *. Signed-off-by: Christian Brauner --- TODO => TODO.md | 1120 ++++++++++++++++++++++++----------------------- 1 file changed, 561 insertions(+), 559 deletions(-) rename TODO => TODO.md (78%) diff --git a/TODO b/TODO.md similarity index 78% rename from TODO rename to TODO.md index 2389514e7861f..f833a3f9982c7 100644 --- a/TODO +++ b/TODO.md @@ -1,39 +1,41 @@ -Bugfixes: +# TODO -* Many manager configuration settings that are only applicable to user +## Bugfixes + +- Many manager configuration settings that are only applicable to user manager or system manager can be always set. It would be better to reject them when parsing config. -* Jun 01 09:43:02 krowka systemd[1]: Unit user@1000.service has alias user@.service. +- Jun 01 09:43:02 krowka systemd[1]: Unit user@1000.service has alias user@.service. Jun 01 09:43:02 krowka systemd[1]: Unit user@6.service has alias user@.service. Jun 01 09:43:02 krowka systemd[1]: Unit user-runtime-dir@6.service has alias user-runtime-dir@.service. -External: +## External -* Fedora: add an rpmlint check that verifies that all unit files in the RPM are listed in %systemd_post macros. +- Fedora: add an rpmlint check that verifies that all unit files in the RPM are listed in %systemd_post macros. -* dbus: +- dbus: - natively watch for dbus-*.service symlinks (PENDING) - teach dbus to activate all services it finds in /etc/systemd/services/org-*.service -* fedora: suggest auto-restart on failure, but not on success and not on coredump. also, ask people to think about changing the start limit logic. Also point people to RestartPreventExitStatus=, SuccessExitStatus= +- fedora: suggest auto-restart on failure, but not on success and not on coredump. also, ask people to think about changing the start limit logic. Also point people to RestartPreventExitStatus=, SuccessExitStatus= -* neither pkexec nor sudo initialize environ[] from the PAM environment? +- neither pkexec nor sudo initialize environ[] from the PAM environment? -* fedora: update policy to declare access mode and ownership of unit files to root:root 0644, and add an rpmlint check for it +- fedora: update policy to declare access mode and ownership of unit files to root:root 0644, and add an rpmlint check for it -* missing shell completions: +- missing shell completions: -* zsh shell completions: +- zsh shell completions: - - should complete options, but currently does not - systemctl add-wants,add-requires - systemctl reboot --boot-loader-entry= -* systemctl status should know about 'systemd-analyze calendar ... --iterations=' -* If timer has just OnInactiveSec=..., it should fire after a specified time +- systemctl status should know about 'systemd-analyze calendar ... --iterations=' +- If timer has just OnInactiveSec=..., it should fire after a specified time after being started. -* write blog stories about: +- write blog stories about: - hwdb: what belongs into it, lsusb - enabling dbus services - how to make changes to sysctl and sysfs attributes @@ -51,104 +53,104 @@ External: - instantiated apache, dovecot and so on - hooking a script into various stages of shutdown/early boot -Regularly: +## Regularly -* look for close() vs. close_nointr() vs. close_nointr_nofail() +- look for close() vs. close_nointr() vs. close_nointr_nofail() -* check for strerror(r) instead of strerror(-r) +- check for strerror(r) instead of strerror(-r) -* pahole +- pahole -* set_put(), hashmap_put() return values check. i.e. == 0 does not free()! +- set_put(), hashmap_put() return values check. i.e. == 0 does not free()! -* link up selected blog stories from man pages and unit files Documentation= fields +- link up selected blog stories from man pages and unit files Documentation= fields -Janitorial Cleanups: +## Janitorial Cleanups -* machined: make remaining machine bus calls compatible with unpriv machined + +- machined: make remaining machine bus calls compatible with unpriv machined + unpriv npsawn: GetAddresses(), GetSSHInfo(), GetOSRelease(), OpenPTY(), OpenLogin(), OpenShell(), BindMount(), CopyFrom(), CopyTo(), OpenRootDirectory(). Similar for images: GetHostname(), GetMachineID(), GetMachineInfo(), GetOSRelease(). -* rework mount.c and swap.c to follow proper state enumeration/deserialization +- rework mount.c and swap.c to follow proper state enumeration/deserialization semantics, like we do for device.c now -* Replace our fstype_is_network() with a call to libmount's mnt_fstype_is_netfs()? +- Replace our fstype_is_network() with a call to libmount's mnt_fstype_is_netfs()? Having two lists is not nice, but maybe it's now worth making a dependency on libmount for something so trivial. -* drop set_free_free() and switch things over from string_hash_ops to +- drop set_free_free() and switch things over from string_hash_ops to string_hash_ops_free everywhere, so that destruction is implicit rather than explicit. Similar, for other special hashmap/set/ordered_hashmap destructors. -* generators sometimes apply C escaping and sometimes specifier escaping to +- generators sometimes apply C escaping and sometimes specifier escaping to paths and similar strings they write out. Sometimes both. We should clean this up, and should probably always apply both, i.e. introduce unit_file_escape() or so, which applies both. -* xopenat() should pin the parent dir of the inode it creates before doing its +- xopenat() should pin the parent dir of the inode it creates before doing its thing, so that it can create, open, label somewhat atomically. -* use CHASE_MUST_BE_DIRECTORY and CHASE_MUST_BE_REGULAR at more places (the +- use CHASE_MUST_BE_DIRECTORY and CHASE_MUST_BE_REGULAR at more places (the majority of places that currently employ chase() probably should use this) -Deprecations and Removals: +## Deprecations and Removals -* Remove any support for booting without /usr pre-mounted in the initrd entirely. +- Remove any support for booting without /usr pre-mounted in the initrd entirely. Update INITRD_INTERFACE.md accordingly. -* remove cgroups v1 support EOY 2023. As per +- remove cgroups v1 support EOY 2023. As per https://lists.freedesktop.org/archives/systemd-devel/2022-July/048120.html and then rework cgroupsv2 support around fds, i.e. keep one fd per active unit around, and always operate on that, instead of cgroup fs paths. -* drop support for LOOP_CONFIGURE-less loopback block devices, once kernel +- drop support for LOOP_CONFIGURE-less loopback block devices, once kernel baseline is 5.8. -* Remove /dev/mem ACPI FPDT parsing when /sys/firmware/acpi/fpdt is ubiquitous. +- Remove /dev/mem ACPI FPDT parsing when /sys/firmware/acpi/fpdt is ubiquitous. That requires distros to enable CONFIG_ACPI_FPDT, and have kernels v5.12 for x86 and v6.2 for arm. -* In v260: remove support for deprecated FactoryReset EFI variable in +- In v260: remove support for deprecated FactoryReset EFI variable in systemd-repart, replaced by FactoryResetRequest. -* Consider removing root=gpt-auto, and push people to use root=dissect instead. +- Consider removing root=gpt-auto, and push people to use root=dissect instead. -* remove any trace of "cpuacct" cgroup controller, it's a cgroupv1 thing. +- remove any trace of "cpuacct" cgroup controller, it's a cgroupv1 thing. similar "devices" -Features: +## Features -* crypttab/gpt-auto-generator: allow explicit control over which unlock mechs +- crypttab/gpt-auto-generator: allow explicit control over which unlock mechs to permit, and maybe have a global headless kernel cmdline option -* start making use of the new --graceful switch to util-linux' umount command +- start making use of the new --graceful switch to util-linux' umount command -* sysusers: allow specifying a path to an inode *and* a literal UID in the UID +- sysusers: allow specifying a path to an inode *and* a literal UID in the UID column, so that if the inode exists it is used, and if not the literal UID is used. Use this for services such as the imds one, which run under their own UID in the initrd, and whose data should survive to the host, properly owned. -* add service file setting to force the fwmark (a la SO_MARK) to some value, so +- add service file setting to force the fwmark (a la SO_MARK) to some value, so that we can allowlist certain services for imds this way. -* lock down swtpm a bit to make it harder to extract keys from it as it is +- lock down swtpm a bit to make it harder to extract keys from it as it is running. i.e. make ptracing + termination hard from the outside. also run swtpm as unpriv user (not trivial, probably requires patch swtpm, as it needs to allocate vtpm device), to lock it down from the inside. -* once swtpm's sd_notify() support has landed in the distributions, remove the +- once swtpm's sd_notify() support has landed in the distributions, remove the invocation in tpm2-swtpm.c and let swtpm handle it. -* make systemd work nicely without /bin/sh, logins and associated shell tools around +- make systemd work nicely without /bin/sh, logins and associated shell tools around - make sure debug shell service (sushell) has a nice failure mode, prints a message and reboots - varlink interface for "systemctl start" and friends - https://github.com/util-linux/util-linux/issues/4117 -* imds: maybe do smarter api version handling +- imds: maybe do smarter api version handling -* drop NV_ORDERLY flag from the product uuid nvpcr. Effect of the flag is that +- drop NV_ORDERLY flag from the product uuid nvpcr. Effect of the flag is that it pushes the thing into TPM RAM, but a TPM usually has very little of that, less than NVRAM. hence setting the flag amplifies space issues. Unsetting the flag increases wear issues on the NVRAM, however, but this should be limited @@ -158,28 +160,28 @@ Features: possibly. (also, NV_ORDERLY makes stuff faster, and dropping it costs possibly up to 100ms supposedly) -* instead of going directly for DefineSpace when initializing nvpcrs, check if +- instead of going directly for DefineSpace when initializing nvpcrs, check if they exist first. apparently DefineSpace is broken on some tpms, and also creates log spam if the nvindex already exists. -* on first login of a user, measure its identity to some nvpcr +- on first login of a user, measure its identity to some nvpcr -* sd-lldp: pick up 802.3 maximum frame size/mtu, to be able to detect jumbo +- sd-lldp: pick up 802.3 maximum frame size/mtu, to be able to detect jumbo frame capable networks -* networkd: maintain a file in /run/ that can be symlinked into /run/issue.d/ +- networkd: maintain a file in /run/ that can be symlinked into /run/issue.d/ that always shows the current primary IP address -* oci: add support for blake hashes for layers +- oci: add support for blake hashes for layers -* oci: add support for "importctl import-oci" which implements the "OCI layout" +- oci: add support for "importctl import-oci" which implements the "OCI layout" spec (i.e. acquiring via local fs access), as opposed to the current "importctl pull-oci" which focusses on the "OCI image spec", i.e. downloads from the web (i.e. acquiring via URLs). -* oci: support "data" in any OCI descriptor, not just manifest config. +- oci: support "data" in any OCI descriptor, not just manifest config. -* report: +- report: - plug "facts" into systemd-report too, i.e. stuff that is more static, such as hostnames, ssh keys and so on. - pass filtering hints to services, so that they can also be applied server-side, not just client side - metrics from pid1: suppress metrics form units that are inactive and have nothing to report @@ -187,29 +189,29 @@ Features: - add "hint-object" parameter (which only queries info about certain object) - make systemd-report a varlink service -* implement a varlink registry service, similar to the one of the reference +- implement a varlink registry service, similar to the one of the reference implementation, backed by /run/varlink/registry/. Then, also implement connect-via-registry-resolution in sd-varlink and varlinkctl. Care needs to be taken to do the resolution asynchronousy. Also, note that the Varlink reference implementation uses a different address syntax, which needs to be taken into account. -* have a signal that reloads every unit that supports reloading +- have a signal that reloads every unit that supports reloading -* systemd: add storage API via varlink, where everyone can drop a socket in a +- systemd: add storage API via varlink, where everyone can drop a socket in a dir, similar, do the same thing for networking -* do a console daemon that takes stdio fds for services and allows to reconnect +- do a console daemon that takes stdio fds for services and allows to reconnect to them later -* report: have something that requests cloud workload identity bearer tokens +- report: have something that requests cloud workload identity bearer tokens and includes it in the report -* add new tool that can be used in debug mode runs in very early boot, +- add new tool that can be used in debug mode runs in very early boot, generates a random password, passes it as credential to sysusers for the root user, then displays it on screen. people can use this to remotely log in. -* Maybe introduce an InodeRef structure inspired by PidRef, which references a +- Maybe introduce an InodeRef structure inspired by PidRef, which references a specific inode, and combines: a path, an O_PATH fd, and possibly a FID into one. Why? We often pass around path and fd separately in chaseat() and similar calls. Because passing around both separately is cumbersome we sometimes only @@ -218,52 +220,52 @@ Features: InodeRef which *both* pins the inode via an fd, *and* gives us a friendly name for it. -* portable services: attach not only unit files to host, but also simple +- portable services: attach not only unit files to host, but also simple binaries to a tmpfs path in $PATH. -* systemd-sysext: add "exec" command or so that is a bit like "refresh" but +- systemd-sysext: add "exec" command or so that is a bit like "refresh" but runs it in a new namespace and then just executes the selected binary within it. Could be useful to run one-off binaries inside a sysext as a CLI tool. -* homed/pam_systemd: allow authentication by ssh-agent, so that run0/polkit can +- homed/pam_systemd: allow authentication by ssh-agent, so that run0/polkit can be allowed if caller comes with the right ssh-agent keys. -* pull-oci: progress notification +- pull-oci: progress notification -* networkd/machined: implement reverse name lookups in the resolved hook +- networkd/machined: implement reverse name lookups in the resolved hook -* networkd's resolved hook: optionally map all lease IP addresses handed out to +- networkd's resolved hook: optionally map all lease IP addresses handed out to the same hostname which is configured on the .network file. Optionally, even derive this single name from the network interface name (i.e. probably altname or so). This way, when spawning a VM the host could pick the hostname for it and the client gets no say. -* measure all log-in attempts into a new nvpcr +- measure all log-in attempts into a new nvpcr -* maybe rework systemd-modules-load to be a generator that just instantiates +- maybe rework systemd-modules-load to be a generator that just instantiates modprobe@.service a bunch of times -* Split vconsole-setup in two, of which the second is started via udev (instead +- Split vconsole-setup in two, of which the second is started via udev (instead of the "restart" job it currently fires). That way, boot becomes purely positive again, and we can nicely order the two against each other. -* Add ELF section to make systemd main binary recognizable cleanly, the same +- Add ELF section to make systemd main binary recognizable cleanly, the same way as we make sd-boot recognizable via PE section. -* Add knob to cryptsetup, to trigger automatic reboot on failure to unlock +- Add knob to cryptsetup, to trigger automatic reboot on failure to unlock disk. Enable this by default for rootfs, also in gpt-auto-generator -* Add RebootUptimeMinSec= knob to PID 1, that makes systemd-shutdown sleep +- Add RebootUptimeMinSec= knob to PID 1, that makes systemd-shutdown sleep until the specified uptime has passed, to lengthen tight boot loops. -* replace bootctl's PE version check to actually use APIs from pe-binary.[ch] +- replace bootctl's PE version check to actually use APIs from pe-binary.[ch] to find binary version. -* replace symlink_label(), mknodat_label(), btrfs_subvol_make_label(), +- replace symlink_label(), mknodat_label(), btrfs_subvol_make_label(), mkdir_label() and related calls by flags-based calls that use label_ops_pre()/label_ops_post(). -* maybe reconsider whether virtualization consoles (hvc1) are considered local +- maybe reconsider whether virtualization consoles (hvc1) are considered local or remote. i.e. are they more like an ssh login, or more like a /dev/tty1 login? Lennart used to believe the former, but maybe the latter is more appropriate? This has effect on polkit interactivity, since it would mean @@ -271,77 +273,77 @@ Features: also raises the question whether such sessions shall be considered active or not -* automatically reset specific EFI vars on factory reset (make this generic +- automatically reset specific EFI vars on factory reset (make this generic enough so that infra can be used to erase shim's mok vars?) -* similar: add a plugin for factory reset logic that erases certain parts of +- similar: add a plugin for factory reset logic that erases certain parts of the ESP, but leaves others in place. -* flush_fd() should probably try to be smart and stop reading once we know that +- flush_fd() should probably try to be smart and stop reading once we know that all further queued data was enqueued after flush_fd() was originally called. For that, try SIOCINQ if fd refers to stream socket, and look at timestamps for datagram sockets. -* Similar flush_accept() should look at sockdiag queued sockets count and exit +- Similar flush_accept() should look at sockdiag queued sockets count and exit once we flushed out the specified number of connections. -* maybe introduce a new per-unit drop-in directory .confext.d/ that may contain +- maybe introduce a new per-unit drop-in directory .confext.d/ that may contain symlinks to confext images to enable for the unit. -* a small tool that can do basic btrfs raid policy mgmt. i.e. gets started as +- a small tool that can do basic btrfs raid policy mgmt. i.e. gets started as part of the initial transaction for some btrfs raid fs, waits for some time, then puts message on screen (plymouth, console) that some devices apparently are not showing up, then counts down, eventually set a flag somewhere, and retriggers the fs is was invoked for, which causes the udev rules to rerun that assemble the btrfs raid, but this time force degraded assembly. -* introduce /etc/boottab or so which lists block devices that bootctl + +- introduce /etc/boottab or so which lists block devices that bootctl + kernel-install shall update the ESPs on (and register in EFI BootXYZ variables), in addition to whatever is currently the booted /usr/. systemd-sysupdate should also take it into consideration and update the /usr/ images on all listed devices. -* replace all uses of fopen_temporary() by fopen_tmpfile_linkable() + +- replace all uses of fopen_temporary() by fopen_tmpfile_linkable() + flink_tmpfile() and then get rid of fopen_temporary(). Benefit: use O_TMPFILE pervasively, and avoid rename() wherever we can. -* loginctl: show argv[] of "leader" process in tabular list-sessions output +- loginctl: show argv[] of "leader" process in tabular list-sessions output -* loginctl: show "service identifier" in tabular list-sessions output, to make +- loginctl: show "service identifier" in tabular list-sessions output, to make run0 sessions easily visible. -* run0: maybe enable utmp for run0 sessions, so that they are easily visible. +- run0: maybe enable utmp for run0 sessions, so that they are easily visible. -* maybe beef up sd-event: optionally, allow sd-event to query the timestamp of +- maybe beef up sd-event: optionally, allow sd-event to query the timestamp of next pending datagram inside a SOCK_DGRAM IO fd, and order event source dispatching by that. Enable this on the native + syslog sockets in journald, so that we add correct ordering between the two. Use MSG_PEEK + SCM_TIMESTAMP for this. -* bsod: add target "bsod.target" or so, which invokes systemd-bsod.target and +- bsod: add target "bsod.target" or so, which invokes systemd-bsod.target and waits and then reboots. Then use OnFailure=bsod.target from various jobs that should result in system reboots, such as TPM tamper detection cases. -* honour validatefs xattrs in dissect-image.c too +- honour validatefs xattrs in dissect-image.c too -* pcrextend: maybe add option to disable measurements entirely via kernel cmdline +- pcrextend: maybe add option to disable measurements entirely via kernel cmdline -* tpm2-setup: reboot if we detect SRK changed +- tpm2-setup: reboot if we detect SRK changed -* validatefs: validate more things: check if image id + os id of initrd match +- validatefs: validate more things: check if image id + os id of initrd match target mount, so that we refuse early any attempts to boot into different images with the wrong kernels. check min/max kernel version too. all encoded via xattrs in the target fs. -* pcrextend: when we fail to measure, reboot the system (at least optionally). +- pcrextend: when we fail to measure, reboot the system (at least optionally). important because certain measurements are supposed to "destroy" tpm object access. -* pcrextend: after measuring get an immediate quote from the TPM, and validate +- pcrextend: after measuring get an immediate quote from the TPM, and validate it. if it doesn't check out, i.e. the measurement we made doesn't appear in the PCR then also reboot. -* complete varlink introspection comments: +- complete varlink introspection comments: - io.systemd.Hostname - io.systemd.ManagedOOM - io.systemd.Network @@ -351,7 +353,7 @@ Features: - io.systemd.oom - io.systemd.sysext -* maybe define a /etc/machine-info field for the ANSI color to associate with a +- maybe define a /etc/machine-info field for the ANSI color to associate with a hostname. Then use it for the shell prompt to highlight the hostname. If no color is explicitly set, hash a color automatically from the hostname as a fallback, in a reasonable way. Take inspiration from the ANSI_COLOR= field @@ -363,19 +365,19 @@ Features: identity. This code should be placed in hostnamed, so that clients can query the color via varlink or dbus. -* unify how blockdev_get_root() and sysupdate find the default root block device +- unify how blockdev_get_root() and sysupdate find the default root block device -* Maybe rename pkcs7 and public verbs of systemd-keyutil to be more verb like. +- Maybe rename pkcs7 and public verbs of systemd-keyutil to be more verb like. -* maybe extend the capsule concept to the per-user instance too: invokes a +- maybe extend the capsule concept to the per-user instance too: invokes a systemd --user instance with a subdir of $HOME as $HOME, and a subdir of $XDG_RUNTIME_DIR as $XDG_RUNTIME_DIR. -* add "homectl export" and "homectl import" that gets you an "atomic" snapshot +- add "homectl export" and "homectl import" that gets you an "atomic" snapshot of your homedir, i.e. either a tarball or a snapshot of the underlying disk (use FREEZE/THAW to make it consistent, btrfs snapshots) -* maybe introduce a new partition that we can store debug logs and similar at +- maybe introduce a new partition that we can store debug logs and similar at the very last moment of shutdown. idea would be to store reference to block device (major + minor + partition id + diskseq?) in /run somewhere, than use that from systemd-shutdown, just write a raw JSON blob into the partition. @@ -384,18 +386,18 @@ Features: also use this to detect unclean shutdowns, boot into special target if detected -* fix homed/homectl confusion around terminology, i.e. "home directory" +- fix homed/homectl confusion around terminology, i.e. "home directory" vs. "home" vs. "home area". Stick to one term for the concept, and it probably shouldn't contain "area". -* add field to bls type 1 and type 2 profiles that ensures an item is never +- add field to bls type 1 and type 2 profiles that ensures an item is never considered for automatic selection -* add "conditions" for bls type 1 and type 2 profiles that allow suppressing +- add "conditions" for bls type 1 and type 2 profiles that allow suppressing them under various conditions: 1. if tpm2 is available or not available; 2. if sb is on or off; 3. if we are netbooted or not; … -* repart: introduce concept of "ghost" partitions, that we setup in almost all +- repart: introduce concept of "ghost" partitions, that we setup in almost all ways like other partitions, but do not actually register in the actual gpt table, but only tell the kernel about via BLKPG ioctl. These partitions are disk backed (hence can be large), but not persistent (as they are invisible @@ -403,20 +405,20 @@ Features: but automatically start at zero on each boot. There should also be a way to make ghost partitions properly persistent on request. -* repart: introduce MigrateFileSystem= or so which is a bit like +- repart: introduce MigrateFileSystem= or so which is a bit like CopyFiles=/CopyBlocks= but operates via btrfs device logic: adds target as new device then removes source from btrfs. Usecase: a live medium which uses "ghost" partitions as suggested above, which can become persistent on request on another device. -* make nspawn containers, portable services and vmspawn VMs optionally survive +- make nspawn containers, portable services and vmspawn VMs optionally survive soft reboot wholesale. -* Turn systemd-networkd-wait-online into a small varlink service that people +- Turn systemd-networkd-wait-online into a small varlink service that people can talk to and specify exactly what to wait for via a method call, and get a response back once that level of "online" is reached. -* introduce a small "systemd-installer" tool or so, that glues +- introduce a small "systemd-installer" tool or so, that glues systemd-repart-as-installer and bootctl-install into one. Would just interactively ask user for target disk (with completion and so on), and then do two varlink calls to the the two tools with the right parameters. To support @@ -424,18 +426,18 @@ Features: processes with varlink communication over socketpair(). This all should be useful as blueprint for graphical installers which should do the same. -* Make run0 forward various signals to the forked process so that sending +- Make run0 forward various signals to the forked process so that sending signals to a child process works roughly the same regardless of whether the child process is spawned via run0 or not. -* write a document explaining how to write correct udev rules. Mention things +- write a document explaining how to write correct udev rules. Mention things such as: 1. do not do lists of vid/pid matches, use hwdb for that 2. add|change action matches are typically wrong, should be != remove 3. use GOTO, make rules short 4. people shouldn't try to make rules file non-world-readable -* make killing more debuggable: when we kill a service do so setting the +- make killing more debuggable: when we kill a service do so setting the .si_code field with a little bit of info. Specifically, we can set a recognizable value to first of all indicate that it's systemd that did the killing. Secondly, we can give a reason for the killing, i.e. OOM or so, and @@ -444,17 +446,17 @@ Features: Net result: people who try to debug why their process gets killed should have some minimal, nice metadata directly on the signal event. -* sd-boot/sd-stub: install a uefi "handle" to a sidecar dir of bls type #1 +- sd-boot/sd-stub: install a uefi "handle" to a sidecar dir of bls type #1 entries with an "uki" or "uki-url" stanza, and make sd-stub look for that. That way we can parameterize type #1 entries nicely. -* add a system-wide seccomp filter list for syscalls, kill "acct()" "@obsolete" +- add a system-wide seccomp filter list for syscalls, kill "acct()" "@obsolete" and a few other legacy syscalls that way. -* maybe introduce "@icky" as a seccomp filter group, which contains acct() and +- maybe introduce "@icky" as a seccomp filter group, which contains acct() and certain other syscalls that aren't quite obsolete, but certainly icky. -* revisit how we pass fs images and initrd to the kernel. take uefi http boot +- revisit how we pass fs images and initrd to the kernel. take uefi http boot ramdisks as inspiration: for any confext/sysext/initrd erofs/DDI image simply generate a fake pmem region in the UEFI memory tables, that Linux then turns into /dev/pmemX. Then turn of cpio-based initrd logic in linux kernel, @@ -464,40 +466,40 @@ Features: PE section .ramdisk that just synthesizes pmem devices from arbitrary blobs. Could be particularly useful in add-ons) -* also parse out primary GPT disk label uuid from gpt partition device path at +- also parse out primary GPT disk label uuid from gpt partition device path at boot and pass it as efi var to OS. -* storagetm: maybe also serve the specified disk via HTTP? we have glue for +- storagetm: maybe also serve the specified disk via HTTP? we have glue for microhttpd anyway already. Idea would also be serve currently booted UKI as separate HTTP resource, so that EFI http boot on another system could directly boot from our system, with full access to the hdd. -* support specifying download hash sum in systemd-import-generator expression +- support specifying download hash sum in systemd-import-generator expression to pin image/tarball. -* support boot into nvme-over-tcp: add generator that allows specifying nvme +- support boot into nvme-over-tcp: add generator that allows specifying nvme devices on kernel cmdline + credentials. Also maybe add interactive mode (where the user is prompted for nvme info), in order to boot from other system's HDD. -* ptyfwd: use osc context information in vmspawn/nspawn/… to optionally only +- ptyfwd: use osc context information in vmspawn/nspawn/… to optionally only listen to ^]]] key when no further vmspawn/nspawn context is allocated -* ptyfwd: usec osc context information to propagate status messages from +- ptyfwd: usec osc context information to propagate status messages from vmspawn/nspawn to service manager's "status" string, reporting what is currently in the fg -* nspawn/vmspawn: define hotkey that one can hit on the primary interface to +- nspawn/vmspawn: define hotkey that one can hit on the primary interface to ask for a friendly, acpi style shutdown. -* for better compat with major clouds: implement simple PTP device support in +- for better compat with major clouds: implement simple PTP device support in timesyncd -* for better compat with major clouds: recognize clouds via hwdb on DMI device, +- for better compat with major clouds: recognize clouds via hwdb on DMI device, and add udev properties to it that help with handling IMDS, i.e. entrypoint URL, which fields to find ip hostname, ssh key, … -* for better compat with major clouds: introduce imds mini client service that +- for better compat with major clouds: introduce imds mini client service that sets up primary netif in a private netns (ipvlan?) to query imds without affecting rest of the host. pick up literal credentials from there plus the fields the hwdb reports for the other fields and turn them into credentials. @@ -505,71 +507,71 @@ Features: service into the early boot, waiting for the DMI and network device to show up. -* Add UKI profile conditioning so that profiles are only available if secure +- Add UKI profile conditioning so that profiles are only available if secure boot is turned off, or only on. similar, add conditions on TPM availability, network boot, and other conditions. -* fix bug around run0 background color on ls in fresh terminal +- fix bug around run0 background color on ls in fresh terminal -* Reset TPM2 DA bit on each successful boot +- Reset TPM2 DA bit on each successful boot -* systemd-cryptenroll: add --firstboot or so, that will interactively ask user +- systemd-cryptenroll: add --firstboot or so, that will interactively ask user whether recovery key shall be enrolled and do so -* maybe introduce container-shell@.service or so, to match +- maybe introduce container-shell@.service or so, to match container-getty.service but skips authentication, so you get a shell prompt directly. Usecase: wsl-like stuff (they have something pretty much like that). Question: how to pick user for this. Instance parameter? somehow from credential (would probably require some binary that converts credential to User= parameter? -* systemd-firstboot: optionally install an ssh key for root for offline use. +- systemd-firstboot: optionally install an ssh key for root for offline use. -* Allocate UIDs/GIDs automatically in userdbctl load-credentials if none are +- Allocate UIDs/GIDs automatically in userdbctl load-credentials if none are included in the user/group record credentials -* introduce new ANSI sequence for communicating log level and structured error +- introduce new ANSI sequence for communicating log level and structured error metadata to terminals. -* in pid1: include ExecStart= cmdlines (and other Exec*= cmdlines) in polkit +- in pid1: include ExecStart= cmdlines (and other Exec*= cmdlines) in polkit request, so that policies can match against command lines. -* allow dynamic modifications of ConcurrencyHardMax= and ConcurrencySoftMax= +- allow dynamic modifications of ConcurrencyHardMax= and ConcurrencySoftMax= via DBus (and with that also by daemon-reload) -* portabled: similar +- portabled: similar -* maybe introduce an OSC sequence that signals when we ask for a password, so +- maybe introduce an OSC sequence that signals when we ask for a password, so that terminal emulators can maybe connect a password manager or so, and highlight things specially. -* start using STATX_SUBVOL in btrfs_is_subvol(). Also, make use of it +- start using STATX_SUBVOL in btrfs_is_subvol(). Also, make use of it generically, so that image discovery recognizes bcachefs subvols too. -* foreign uid: +- foreign uid: - add support to export-fs, import-fs - systemd-dissect should learn mappings, too, when doing mtree and such -* system LSFMMBPF policy that prohibits creating files owned by "nobody" +- system LSFMMBPF policy that prohibits creating files owned by "nobody" system-wide -* system LSFMMBPF policy that prohibits creating or opening device nodes outside +- system LSFMMBPF policy that prohibits creating or opening device nodes outside of devtmpfs/tmpfs, except if they are the pseudo-devices /dev/null, /dev/zero, /dev/urandom and so on. -* system LSFMMBPF policy that enforces that block device backed mounts may only +- system LSFMMBPF policy that enforces that block device backed mounts may only be established on top of dm-crypt or dm-verity devices, or an allowlist of file systems (which should probably include vfat, for compat with the ESP) -* $SYSTEMD_EXECPID that the service manager sets should +- $SYSTEMD_EXECPID that the service manager sets should be augmented with $SYSTEMD_EXECPIDFD (and similar for other env vars we might send). -* port copy.c over to use LabelOps for all labelling. +- port copy.c over to use LabelOps for all labelling. -* get rid of compat with libbpf.so.0 (retainly only for libbpf.so.1) +- get rid of compat with libbpf.so.0 (retainly only for libbpf.so.1) -* define a generic "report" varlink interface, which services can implement to +- define a generic "report" varlink interface, which services can implement to provide health/statistics data about themselves. then define a dir somewhere in /run/ where components can bind such sockets. Then make journald, logind, and pid1 itself implement this and expose various stats on things there. Then @@ -578,57 +580,57 @@ Features: quote. tpm quote should protect the json doc via the nonce field studd. Allow shipping this off elsewhere for analyze. -* The bind(AF_UNSPEC) construct (for resetting sockets to their initial state) +- The bind(AF_UNSPEC) construct (for resetting sockets to their initial state) should be blocked in many cases because it punches holes in many sandboxes. -* introduce new structure Tpm2CombinedPolicy, that combines the various TPm2 +- introduce new structure Tpm2CombinedPolicy, that combines the various TPm2 policy bits into one structure, i.e. public key info, pcr masks, pcrlock stuff, pin and so on. Then pass that around in tpm2_seal() and tpm2_unseal(). -* look at nsresourced, mountfsd, homed, importd, portabled, and try to come up +- look at nsresourced, mountfsd, homed, importd, portabled, and try to come up with a way how the forked off worker processes can be moved into transient services with sandboxing, without breaking notify socket stuff and so on. -* replace all \x1b, \x1B, \033 C string escape sequences in our codebase with a +- replace all \x1b, \x1B, \033 C string escape sequences in our codebase with a more readable \e. It's a GNU extension, but a ton more readable than the others, and most importantly it doesn't result in confusing errors if you suffix the escape sequence with one more decimal digit, because compilers think you might actually specify a value outside the 8bit range with that. -* confext/sysext: instead of mounting the overlayfs directly on /etc/ + /usr/, +- confext/sysext: instead of mounting the overlayfs directly on /etc/ + /usr/, insert an intermediary bind mount on itself there. This has the benefit that services where mount propagation from the root fs is off, an still have confext/sysext propagated in. -* generic interface for varlink for setting log level and stuff that all our daemons can implement +- generic interface for varlink for setting log level and stuff that all our daemons can implement -* maybe teach repart.d/ dropins a new setting MakeMountNodes= or so, which is +- maybe teach repart.d/ dropins a new setting MakeMountNodes= or so, which is just like MakeDirectories=, but uses an access mode of 0000 and sets the +i chattr bit. This is useful as protection against early uses of /var/ or /tmp/ before their contents is mounted. -* go through all uses of table_new() in our codebase, and make sure we support +- go through all uses of table_new() in our codebase, and make sure we support all three of: 1. --no-legend properly 2. --json= properly 3. --no-pager properly -* go through all --help texts in our codebases, and make sure: +- go through all --help texts in our codebases, and make sure: 1. the one sentence description of the tool is highlighted via ANSI how we usually do it 2. If more than one or two commands are supported (as opposed to switches), separate commands + switches from each other, using underlined --help sections. 3. If there are many switches, consider adding additional --help sections. -* go through our codebase, and convert "vertical tables" (i.e. things such as +- go through our codebase, and convert "vertical tables" (i.e. things such as "systemctl status") to use table_new_vertical() for output -* pcrlock: add support for multi-profile UKIs +- pcrlock: add support for multi-profile UKIs -* initrd: when transitioning from initrd to host, validate that +- initrd: when transitioning from initrd to host, validate that /lib/modules/`uname -r` exists, refuse otherwise -* signed bpf loading: to address need for signature verification for bpf +- signed bpf loading: to address need for signature verification for bpf programs when they are loaded, and given the bpf folks don't think this is realistic in kernel space, maybe add small daemon that facilitates this loading on request of clients, validates signatures and then loads the @@ -640,45 +642,45 @@ Features: PR_SET_DUMPABLE so that it cannot be ptraced from the host. Should have CAP_SYS_BPF as only service around. -* add a mechanism we can drop capabilities from pid1 *before* transitioning +- add a mechanism we can drop capabilities from pid1 *before* transitioning from initrd to host. i.e. before we transition into the slightly lower trust domain that is the host systems we might want to get rid of some caps. Example: CAP_SYS_BPF in the signed bpf loading logic above. (We already have CapabilityBoundingSet= in system.conf, but that is enforced when pid 1 initializes, rather then when it transitions to the next.) -* maybe add a new standard slice where process that are started in the initrd +- maybe add a new standard slice where process that are started in the initrd and stick around for the whole system runtime (i.e. root fs storage daemons, the bpf loader daemon discussed above, and such) are placed. maybe protected.slice or so? Then write docs that suggest that services like this set Slice=protected.sice, RefuseManualStart=yes, RefuseManualStop=yes and a couple of other things. -* rough proposed implementation design for remote attestation infra: add a tool +- rough proposed implementation design for remote attestation infra: add a tool that generates a quote of local PCRs and NvPCRs, along with synchronous log snapshot. use "audit session" logic for that, so that we get read-outs and signature in one step. Then turn this into a JSON object. Use the "TCG TSS 2.0 JSON Data Types and Policy Language" format to encode the signature. And CEL for the measurement log. -* creds: add a new cred format that reused the JSON structures we use in the +- creds: add a new cred format that reused the JSON structures we use in the LUKS header, so that we get the various newer policies for free. -* systemd-analyze: port "pcrs" verb to talk directly to TPM device, instead of +- systemd-analyze: port "pcrs" verb to talk directly to TPM device, instead of using sysfs interface (well, or maybe not, as that would require privileges?) -* pcrextend/tpm2-util: add a concept of "rotation" to event log. i.e. allow +- pcrextend/tpm2-util: add a concept of "rotation" to event log. i.e. allow trailing parts of the logs if time or disk space limit is hit. Protect the boot-time measurements however (i.e. up to some point where things are settled), since we need those for pcrlock measurements and similar. When deleting entries for rotation, place an event that declares how many items have been dropped, and what the hash before and after that. -* use name_to_handle_at() with AT_HANDLE_FID instead of .st_ino (inode +- use name_to_handle_at() with AT_HANDLE_FID instead of .st_ino (inode number) for identifying inodes, for example in copy.c when finding hard links, or loop-util.c for tracking backing files, and other places. -* cryptenroll/cryptsetup/homed: add unlock mechanism that combines tpm2 and +- cryptenroll/cryptsetup/homed: add unlock mechanism that combines tpm2 and fido2, as well as tpm2 + ssh-agent, inspired by ChromeOS' logic: encrypt the volume key with the TPM, with a policy that insists that a nonce is signed by the fido2 device's key or ssh-agent key. Thus, add unlock/login time the TPM @@ -686,44 +688,44 @@ Features: returns a signature which is handed to the tpm, which then reveals the volume key to the PC. -* cryptenroll/cryptsetup/homed: similar to this, implement TOTP backed by TPM. +- cryptenroll/cryptsetup/homed: similar to this, implement TOTP backed by TPM. -* expose the handoff timestamp fully via the D-Bus properties that contain +- expose the handoff timestamp fully via the D-Bus properties that contain ExecStatus information -* properly serialize the ExecStatus data from all ExecCommand objects +- properly serialize the ExecStatus data from all ExecCommand objects associated with services, sockets, mounts and swaps. Currently, the data is flushed out on reload, which is quite a limitation. -* Clean up "reboot argument" handling, i.e. set it through some IPC service +- Clean up "reboot argument" handling, i.e. set it through some IPC service instead of directly via /run/, so that it can be sensible set remotely. -* systemd-tpm2-support: add a some logic that detects if system is in DA +- systemd-tpm2-support: add a some logic that detects if system is in DA lockout mode, and queries the user for TPM recovery PIN then. -* move documentation about our common env vars (SYSTEMD_LOG_LEVEL, +- move documentation about our common env vars (SYSTEMD_LOG_LEVEL, SYSTEMD_PAGER, …) into a man page of its own, and just link it from our various man pages that so far embed the whole list again and again, in an attempt to reduce clutter and noise a bid. -* vmspawn switch default swtpm PCR bank to SHA384-only (away from SHA256), at +- vmspawn switch default swtpm PCR bank to SHA384-only (away from SHA256), at least on 64bit archs, simply because SHA384 is typically double the hashing speed than SHA256 on 64bit archs (since based on 64bit words unlike SHA256 which uses 32bit words). -* In vmspawn/nspawn/machined wait for X_SYSTEMD_UNIT_ACTIVE=ssh-active.target +- In vmspawn/nspawn/machined wait for X_SYSTEMD_UNIT_ACTIVE=ssh-active.target and X_SYSTEMD_SIGNALS_LEVEL=2 as indication whether/when SSH and the POSIX signals are available. Similar for D-Bus (but just use sockets.target for that). Report as property for the machine. -* teach nspawn/machined a new bus call/verb that gets you a +- teach nspawn/machined a new bus call/verb that gets you a shell in containers that have no sensible pid1, via joining the container, and invoking a shell directly. Then provide another new bus call/vern that is somewhat automatic: if we detect that pid1 is running and fully booted up we provide a proper login shell, otherwise just a joined shell. Then expose that as primary way into the container. -* make vmspawn/nspawn/importd/machined a bit more usable in a WSL-like +- make vmspawn/nspawn/importd/machined a bit more usable in a WSL-like fashion. i.e. teach unpriv systemd-vmspawn/systemd-nspawn a reasonable --bind-user= behaviour that mounts the calling user through into the machine. Then, ship importd with a small database of well known distro images @@ -732,47 +734,47 @@ Features: the bg via vmspawn/nspawn if not done so yet and then requests a shell inside it for the invoking user. -* add a new specifier to unit files that figures out the DDI the unit file is +- add a new specifier to unit files that figures out the DDI the unit file is from, tracing through overlayfs, DM, loopback block device. -* importd/importctl: +- importd/importctl: - complete varlink interface - download images into .v/ dirs -* in os-release define a field that can be initialized at build time from +- in os-release define a field that can be initialized at build time from SOURCE_DATE_EPOCH (maybe even under that name?). Would then be used to initialize the timestamp logic of ConditionNeedsUpdate=. -* nspawn/vmspawn/pid1: add ability to easily insert fully booted VMs/FOSC into +- nspawn/vmspawn/pid1: add ability to easily insert fully booted VMs/FOSC into shell pipelines, i.e. add easy to use switch that turns off console status output, and generates the right credentials for systemd-run-generator so that a program is invoked, and its output captured, with correct EOF handling and exit code propagation -* Introduce a CGroupRef structure, inspired by PidRef. Should contain cgroup +- Introduce a CGroupRef structure, inspired by PidRef. Should contain cgroup path, cgroup id, and cgroup fd. Use it to continuously pin all v2 cgroups via a cgroup_ref field in the CGroupRuntime structure. Eventually switch things over to do all cgroupfs access only via that structure's fd. -* Get rid of the symlinks in /run/systemd/units/* and exclusively use cgroupfs +- Get rid of the symlinks in /run/systemd/units/* and exclusively use cgroupfs xattrs to convey info about invocation ids, logging settings and so on. support for cgroupfs xattrs in the "trusted." namespace was added in linux 3.7, i.e. which we don't pretend to support anymore. -* rewrite bpf-devices in libbpf/C code, rather than home-grown BPF assembly, to +- rewrite bpf-devices in libbpf/C code, rather than home-grown BPF assembly, to match bpf-restrict-fs, bpf-restrict-ifaces, bpf-socket-bind -* ditto: rewrite bpf-firewall in libbpf/C code +- ditto: rewrite bpf-firewall in libbpf/C code -* extend the smbios11 logic for passing credentials so that instead of passing +- extend the smbios11 logic for passing credentials so that instead of passing the credential data literally it can also just reference an AF_VSOCK CID/port to read them from. This way the data doesn't remain in the SMBIOS blob during runtime, but only in the credentials fs. -* introduce mntid_t, and make it 64bit, as apparently the kernel switched to +- introduce mntid_t, and make it 64bit, as apparently the kernel switched to 64bit mount ids -* mountfsd/nsresourced: +- mountfsd/nsresourced: - userdb: maybe allow callers to map one uid to their own uid - bpflsm: allow writes if resulting UID on disk would be userns' owner UID - make encrypted DDIs work (password…) @@ -784,12 +786,12 @@ Features: - port: tmpfiles, sysusers and similar - lets see if we can make runtime bind mounts into unpriv nspawn work -* add a kernel cmdline switch (and cred?) for marking a system to be +- add a kernel cmdline switch (and cred?) for marking a system to be "headless", in which case we never open /dev/console for reading, only for writing. This would then mean: systemd-firstboot would process creds but not ask interactively, getty would not be started and so on. -* we probably should have some infrastructure to acquire sysexts with +- we probably should have some infrastructure to acquire sysexts with drivers/firmware for local hardware automatically. Idea: reuse the modalias logic of the kernel for this: make the main OS image install a hwdb file that matches against local modalias strings, and adds properties to relevant @@ -797,23 +799,23 @@ Features: tool that goes through all devices and tries to acquire/download the specified images. -* repart + cryptsetup: support file systems that are encrypted and use verity +- repart + cryptsetup: support file systems that are encrypted and use verity on top. Usecase: confexts that shall be signed by the admin but also be confidential. Then, add a new --make-ddi=confext-encrypted for this. -* tiny varlink service that takes a fd passed in and serves it via http. Then +- tiny varlink service that takes a fd passed in and serves it via http. Then make use of that in networkd, and expose some EFI binary of choice for DHCP/HTTP base EFI boot. -* maybe: in PID1, when we detect we run in an initrd, make superblock read-only +- maybe: in PID1, when we detect we run in an initrd, make superblock read-only early on, but provide opt-out via kernel cmdline. -* systemd-pcrextend: +- systemd-pcrextend: - once we have that start measuring every sysext we apply, every confext, every RootImage= we apply, every nspawn and so on. All in separate fake PCRs. -* vmspawn: +- vmspawn: - --ephemeral support - --read-only support - automatically suspend/resume the VM if the host suspends. Use logind @@ -823,18 +825,18 @@ Features: - translate SIGTERM to clean ACPI shutdown event - implement hotkeys ^]^]r and ^]^]p like nspawn -* storagetm: +- storagetm: - add USB mass storage device logic, so that all local disks are also exposed as mass storage devices on systems that have a USB controller that can operate in device mode - add NVMe authentication -* add support for activating nvme-oF devices at boot automatically via kernel +- add support for activating nvme-oF devices at boot automatically via kernel cmdline, and maybe even support a syntax such as root=nvme::::: to boot directly from nvme-oF -* pcrlock: +- pcrlock: - add kernel-install plugin that automatically creates UKI .pcrlock file when UKI is installed, and removes it when it is removed again - automatically install PE measurement of sd-boot on "bootctl install" @@ -847,22 +849,22 @@ Features: policy from currently booted kernel/event log, to close gap for first boot for pre-built images -* in sd-boot and sd-stub measure the SMBIOS vendor strings to some PCR (at +- in sd-boot and sd-stub measure the SMBIOS vendor strings to some PCR (at least some subset of them that look like systemd stuff), because apparently some firmware does not, but systemd honours it. avoid duplicate measurement by sd-boot and sd-stub by adding LoaderFeatures/StubFeatures flag for this, so that sd-stub can avoid it if sd-boot already did it. -* image policy should be extended to allow dictating *how* a disk is unlocked, +- image policy should be extended to allow dictating *how* a disk is unlocked, i.e. root=encrypted-tpm2+encrypted-fido2 would mean "root fs must be encrypted and unlocked via fido2 or tpm2, but not otherwise" -* redefine /var/lib/extensions/ as the dir one can place all three of sysext, +- redefine /var/lib/extensions/ as the dir one can place all three of sysext, confext as well is multi-modal DDIs that qualify as both. Then introduce /var/lib/sysexts/ which can be used to place only DDIs that shall be used as sysext -* Varlinkification of the following command line tools, to open them up to +- Varlinkification of the following command line tools, to open them up to other programs via IPC: - coredumpcl - systemd-bless-boot @@ -874,38 +876,38 @@ Features: - kernel-install - systemd-mount (with PK so that desktop environments could use it to mount disks) -* enumerate virtiofs devices during boot-up in a generator, and synthesize +- enumerate virtiofs devices during boot-up in a generator, and synthesize mounts for rootfs, /usr/, /home/, /srv/ and some others from it, depending on the "tag". (waits for: https://gitlab.com/virtio-fs/virtiofsd/-/issues/128) -* automatically mount one virtiofs during early boot phase to /run/host/, +- automatically mount one virtiofs during early boot phase to /run/host/, similar to how we do that for nspawn, based on some clear tag. -* add some service that makes an atomic snapshot of PCR state and event log up +- add some service that makes an atomic snapshot of PCR state and event log up to that point available, possibly even with quote by the TPM. -* encode type1 entries in some UKI section to add additional entries to the +- encode type1 entries in some UKI section to add additional entries to the menu. -* Add ACL-based access management to .socket units. i.e. add AllowPeerUser= + +- Add ACL-based access management to .socket units. i.e. add AllowPeerUser= + AllowPeerGroup= that installs additional user/group ACL entries on AF_UNIX sockets. -* systemd-tpm2-setup should support a mode where we refuse booting if the SRK +- systemd-tpm2-setup should support a mode where we refuse booting if the SRK changed. (Must be opt-in, to not break systems which are supposed to be migratable between PCs) -* when systemd-sysext learns mutable /usr/ (and systemd-confext mutable /etc/) +- when systemd-sysext learns mutable /usr/ (and systemd-confext mutable /etc/) then allow them to store the result in a .v/ versioned subdir, for some basic snapshot logic -* add a new PE binary section ".mokkeys" or so which sd-stub will insert into +- add a new PE binary section ".mokkeys" or so which sd-stub will insert into Mok keyring, by overriding/extending whatever shim sets in the EFI var. Benefit: we can extend the kernel module keyring at ukify time, i.e. without recompiling the kernel, taking an upstream OS' kernel and adding a local key to it. -* PidRef conversion work: +- PidRef conversion work: - cg_pid_get_xyz() - pid_from_same_root_fs() - get_ctty_devnr() @@ -915,30 +917,30 @@ Features: - cg_attach() – requires new kernel feature - journald's process cache -* ddi must be listed as block device fstype +- ddi must be listed as block device fstype -* measure some string via pcrphase whenever we end up booting into emergency +- measure some string via pcrphase whenever we end up booting into emergency mode. -* similar, measure some string via pcrphase whenever we resume from hibernate +- similar, measure some string via pcrphase whenever we resume from hibernate -* use sd-event ratelimit feature optionally for journal stream clients that log +- use sd-event ratelimit feature optionally for journal stream clients that log too much -* systemd-mount should only consider modern file systems when mounting, similar +- systemd-mount should only consider modern file systems when mounting, similar to systemd-dissect -* add another PE section ".fname" or so that encodes the intended filename for +- add another PE section ".fname" or so that encodes the intended filename for PE file, and validate that when loading add-ons and similar before using it. This is particularly relevant when we load multiple add-ons and want to sort them to apply them in a define order. The order should not be under control of the attacker. -* also include packaging metadata (á la +- also include packaging metadata (á la https://systemd.io/PACKAGE_METADATA_FOR_EXECUTABLE_FILES/) in our UEFI PE binaries, using the same JSON format. -* make "bootctl install" + "bootctl update" useful for installing shim too. For +- make "bootctl install" + "bootctl update" useful for installing shim too. For that introduce new dir /usr/lib/systemd/efi/extra/ which we copy mostly 1:1 into the ESP at install time. Then make the logic smart enough so that we don't overwrite bootx64.efi with our own if the extra tree already contains @@ -949,7 +951,7 @@ Features: above) for version info in all *.EFI files, and use it to only update if newer. -* in sd-stub: optionally add support for a new PE section .keyring or so that +- in sd-stub: optionally add support for a new PE section .keyring or so that contains additional certificates to include in the Mok keyring, extending what shim might have placed there. why? let's say I use "ukify" to build + sign my own fedora-based UKIs, and only enroll my personal lennart key via @@ -958,54 +960,54 @@ Features: also mean that the key would be in effect whenever I boot an archlinux UKI built the same way, signed with the same lennart key. -* Maybe add SwitchRootEx() as new bus call that takes env vars to set for new +- Maybe add SwitchRootEx() as new bus call that takes env vars to set for new PID 1 as argument. When adding SwitchRootEx() we should maybe also add a flags param that allows disabling and enabling whether serialization is requested during switch root. -* introduce a .acpitable section for early ACPI table override +- introduce a .acpitable section for early ACPI table override -* add proper .osrel matching for PE addons. i.e. refuse applying an addon +- add proper .osrel matching for PE addons. i.e. refuse applying an addon intended for a different OS. Take inspiration from how confext/sysext are matched against OS. -* figure out what to do about credentials sealed to PCRs in kexec + soft-reboot +- figure out what to do about credentials sealed to PCRs in kexec + soft-reboot scenarios. Maybe insist sealing is done additionally against some keypair in the TPM to which access is updated on each boot, for the next, or so? -* mount most file systems with a restrictive uidmap. e.g. mount /usr/ with a +- mount most file systems with a restrictive uidmap. e.g. mount /usr/ with a uidmap that blocks out anything outside 0…1000 (i.e. system users) and similar. -* mount the root fs with MS_NOSUID by default, and then mount /usr/ without +- mount the root fs with MS_NOSUID by default, and then mount /usr/ without both so that suid executables can only be placed there. Do this already in the initrd. If /usr/ is not split out create a bind mount automatically. -* fix our various hwdb lookup keys to end with ":" again. The original idea was +- fix our various hwdb lookup keys to end with ":" again. The original idea was that hwdb patterns can match arbitrary fields with expressions like "*:foobar:*", to wildcard match both the start and the end of the string. This only works safely for later extensions of the string if the strings always end in a colon. This requires updating our udev rules, as well as checking if the various hwdb files are fine with that. -* mount /tmp/ and /var/tmp with a uidmap applied that blocks out "nobody" user +- mount /tmp/ and /var/tmp with a uidmap applied that blocks out "nobody" user among other things such as dynamic uid ranges for containers and so on. That way no one can create files there with these uids and we enforce they are only used transiently, never persistently. -* rework loopback support in fstab: when "loop" option is used, then +- rework loopback support in fstab: when "loop" option is used, then instantiate a new systemd-loop@.service for the source path, set the lo_file_name field for it to something recognizable derived from the fstab line, and then generate a mount unit for it using a udev generated symlink based on lo_file_name. -* teach systemd-nspawn the boot assessment logic: hook up vpick's try counters +- teach systemd-nspawn the boot assessment logic: hook up vpick's try counters with success notifications from nspawn payloads. When this is enabled, automatically support reverting back to older OS version images if newer ones fail to boot. -* remove tomoyo support, it's obsolete and unmaintained apparently +- remove tomoyo support, it's obsolete and unmaintained apparently -* In .socket units, add ConnectStream=, ConnectDatagram=, +- In .socket units, add ConnectStream=, ConnectDatagram=, ConnectSequentialPacket= that create a socket, and then *connect to* rather than listen on some socket. Then, add a new setting WriteData= that takes some base64 data that systemd will write into the socket early on. This can then @@ -1014,33 +1016,33 @@ Features: aforementioned journald subscription varlink service, to enable activation-by-message id and similar. -* .service with invalid Sockets= starts successfully. +- .service with invalid Sockets= starts successfully. -* landlock: lock down RuntimeDirectory= via landlock, so that services lose +- landlock: lock down RuntimeDirectory= via landlock, so that services lose ability to write anywhere else below /run/. Similar for StateDirectory=. Benefit would be clear delegation via unit files: services get the directories they get, and nothing else even if they wanted to. -* landlock: for unprivileged systemd (i.e. systemd --user), use landlock to +- landlock: for unprivileged systemd (i.e. systemd --user), use landlock to implement ProtectSystem=, ProtectHome= and so on. Landlock does not require privs, and we can implement pretty similar behaviour. Also, maybe add a mode where ProtectSystem= combined with an explicit PrivateMounts=no could request similar behaviour for system services, too. -* Add systemd-mount@.service which is instantiated for a block device and +- Add systemd-mount@.service which is instantiated for a block device and invokes systemd-mount and exits. This is then useful to use in ENV{SYSTEMD_WANTS} in udev rules, and a bit prettier than using RUN+= -* udevd: extend memory pressure logic: also kill any idle worker processes +- udevd: extend memory pressure logic: also kill any idle worker processes -* udevadm: to make symlink querying with udevadm nicer: +- udevadm: to make symlink querying with udevadm nicer: - do not enable the pager for queries like 'udevadm info -q symlink -r' - add mode with newlines instead of spaces (for grep)? -* SIGRTMIN+18 and memory pressure handling should still be added to: hostnamed, +- SIGRTMIN+18 and memory pressure handling should still be added to: hostnamed, localed, oomd, timedated. -* repart/gpt-auto/DDIs: maybe introduce a concept of "extension" partitions, +- repart/gpt-auto/DDIs: maybe introduce a concept of "extension" partitions, that have a new type uuid and can "extend" earlier partitions, to work around the fact that systemd-repart can only grow the last partition defined. During activation we'd simply set up a dm-linear mapping to merge them again. A @@ -1054,16 +1056,16 @@ Features: grow exponentially in size to ensure O(log(n)) time for finding them on access. -* Make nspawn to a frontend for systemd-executor, so that we have to ways into +- Make nspawn to a frontend for systemd-executor, so that we have to ways into the executor: via unit files/dbus/varlink through PID1 and via cmdline/OCI through nspawn. -* add a utility that can be used with the kernel's +- add a utility that can be used with the kernel's CONFIG_STATIC_USERMODEHELPER_PATH and then handles them within pid1 so that security, resource management and cgroup settings can be enforced properly for all umh processes. -* timesyncd: when saving/restoring clock try to take boot time into account. +- timesyncd: when saving/restoring clock try to take boot time into account. Specifically, along with the saved clock, store the current boot ID. When starting, check if the boot id matches. If so, don't do anything (we are on the same boot and clock just kept running anyway). If not, then read @@ -1073,32 +1075,32 @@ Features: miss the time spent during shutdown after timesync stopped and before the system actually reset. -* systemd-stub: maybe store a "boot counter" in the ESP, and pass it down to +- systemd-stub: maybe store a "boot counter" in the ESP, and pass it down to userspace to allow ordering boots (for example in journalctl). The counter would be monotonically increased on every boot. -* pam_systemd_home: add module parameter to control whether to only accept +- pam_systemd_home: add module parameter to control whether to only accept only password or only pcks11/fido2 auth, and then use this to hook nicely into two of the three PAM stacks gdm provides. See discussion at https://github.com/authselect/authselect/pull/311 -* maybe prohibit setuid() to the nobody user, to lock things down, via seccomp. +- maybe prohibit setuid() to the nobody user, to lock things down, via seccomp. the nobody is not a user any code should run under, ever, as that user would possibly get a lot of access to resources it really shouldn't be getting access to due to the userns + nfs semantics of the user. Alternatively: use the seccomp log action, and allow it. -* systemd-gpt-auto-generator: add kernel cmdline option to override block +- systemd-gpt-auto-generator: add kernel cmdline option to override block device to dissect. also support dissecting a regular file. useccase: include encrypted/verity root fs in UKI. -* sd-stub: +- sd-stub: - detect if we are running with uefi console output on serial, and if so automatically add console= to kernel cmdline matching the same port. - add ".bootcfg" section for kernel bootconfig data (as per https://docs.kernel.org/admin-guide/bootconfig.html) -* tpm2: add (optional) support for generating a local signing key from PCR 15 +- tpm2: add (optional) support for generating a local signing key from PCR 15 state. use private key part to sign PCR 7+14 policies. stash signatures for expected PCR7+14 policies in EFI var. use public key part in disk encryption. generate new sigs whenever db/dbx/mok/mokx gets updated. that way we can @@ -1107,19 +1109,19 @@ Features: update, but that should be robust/idempotent). needs rollback protection, as usual. -* Lennart: big blog story about DDIs +- Lennart: big blog story about DDIs -* Lennart: big blog story about building initrds +- Lennart: big blog story about building initrds -* Lennart: big blog story about "why systemd-boot" +- Lennart: big blog story about "why systemd-boot" -* bpf: see if we can use BPF to solve the syslog message cgroup source problem: +- bpf: see if we can use BPF to solve the syslog message cgroup source problem: one idea would be to patch source sockaddr of all AF_UNIX/SOCK_DGRAM to implicitly contain the source cgroup id. Another idea would be to patch sendto()/connect()/sendmsg() sockaddr on-the-fly to use a different target sockaddr. -* bpf: see if we can address opportunistic inode sharing of immutable fs images +- bpf: see if we can address opportunistic inode sharing of immutable fs images with BPF. i.e. if bpf gives us power to hook into openat() and return a different inode than is requested for which we however it has same contents then we can use that to implement opportunistic inode sharing among DDIs: @@ -1129,62 +1131,62 @@ Features: xattr set, check bpf table to find dirs with hashes for other prior DDIs and try to use inode from there. -* extend the verity signature partition to permit multiple signatures for the +- extend the verity signature partition to permit multiple signatures for the same root hash, so that people can sign a single image with multiple keys. -* consider adding a new partition type, just for /opt/ for usage in system +- consider adding a new partition type, just for /opt/ for usage in system extensions -* dissection policy should enforce that unlocking can only take place by +- dissection policy should enforce that unlocking can only take place by certain means, i.e. only via pw, only via tpm2, or only via fido, or a combination thereof. -* make the systemd-repart "seed" value provisionable via credentials, so that +- make the systemd-repart "seed" value provisionable via credentials, so that confidential computing environments can set it and deterministically enforce the uuids for partitions created, so that they can calculate PCR 15 ahead of time. -* in the initrd: derive the default machine ID to pass to the host PID 1 via +- in the initrd: derive the default machine ID to pass to the host PID 1 via $machine_id from the same seed credential. -* Add systemd-sysupdate-initrd.service or so that runs systemd-sysupdate in the +- Add systemd-sysupdate-initrd.service or so that runs systemd-sysupdate in the initrd to bootstrap the initrd to populate the initial partitions. Some things to figure out: - Should it run on firstboot or on every boot? - If run on every boot, should it use the sysupdate config from the host on subsequent boots? -* To mimic the new tpm2-measure-pcr= crypttab option and tpm2-measure-nvpcr= +- To mimic the new tpm2-measure-pcr= crypttab option and tpm2-measure-nvpcr= veritytab option, add the same to integritytab (measuring the HMAC key if one is used) -* We should start measuring all services, containers, and system extensions we +- We should start measuring all services, containers, and system extensions we activate. probably into PCR 13. i.e. add --tpm2-measure-pcr= or so to systemd-nspawn, and MeasurePCR= to unit files. Should contain a measurement of the activated configuration and the image that is being activated (in case verity is used, hash of the root hash). -* bootspec: permit graceful "update" from type #2 to type #1. If both a type #1 +- bootspec: permit graceful "update" from type #2 to type #1. If both a type #1 and a type #2 entry exist under otherwise the exact same name, then use the type #1 entry, and ignore the type #2 entry. This way, people can "upgrade" from the UKI with all parameters baked in to a Type #1 .conf file with manual parametrization, if needed. This matches our usual rule that admin config should win over vendor defaults. -* automatic boot assessment: add one more default success check that just waits +- automatic boot assessment: add one more default success check that just waits for a bit after boot, and blesses the boot if the system stayed up that long. -* systemd-boot: maybe add support for collapsing menu entries of the same OS +- systemd-boot: maybe add support for collapsing menu entries of the same OS into one item that can be opened (like in a "tree view" UI element) or collapsed. If only a single OS is installed, disable this mode, but if multiple OSes are installed might make sense to default to it, so that user is not immediately bombarded with a multitude of Linux kernel versions but only one for each OS. -* systemd-tmpfiles: add concept for conditionalizing lines on factory reset +- systemd-tmpfiles: add concept for conditionalizing lines on factory reset boot, or on first boot. -* we probably needs .pcrpkeyrd or so as additional PE section in UKIs, +- we probably needs .pcrpkeyrd or so as additional PE section in UKIs, which contains a separate public key for PCR values that only apply in the initrd, i.e. in the boot phase "enter-initrd". Then, consumers in userspace can easily bind resources to just the initrd. Similar, maybe one more for @@ -1196,10 +1198,10 @@ Features: .pcrpkeyrd (for coverage in the initrd only) and .pcrpkeybt (for coverage until users are allowed to log in). -* Once the root fs LUKS volume key is measured into PCR 15, default to binding +- Once the root fs LUKS volume key is measured into PCR 15, default to binding credentials to PCR 15 in "systemd-creds" -* add support for asymmetric LUKS2 TPM based encryption. i.e. allow preparing +- add support for asymmetric LUKS2 TPM based encryption. i.e. allow preparing an encrypted image on some host given a public key belonging to a specific other host, so that only hosts possessing the private key in the TPM2 chip can decrypt the volume key and activate the volume. Use case: systemd-confext @@ -1211,7 +1213,7 @@ Features: runs a specific software in a specific time window. confext would be automatically invalidated outside of it. -* maybe add a "systemd-report" tool, that generates a TPM2-backed "report" of +- maybe add a "systemd-report" tool, that generates a TPM2-backed "report" of current system state, i.e. a combination of PCR information, local system time and TPM clock, running services, recent high-priority log messages/coredumps, system load/PSI, signed by the local TPM chip, to form an @@ -1233,70 +1235,70 @@ Features: and via the time window TPM logic invalidated if node doesn't keep itself updated, or becomes corrupted in some way. -* in the initrd, once the rootfs encryption key has been measured to PCR 15, +- in the initrd, once the rootfs encryption key has been measured to PCR 15, derive default machine ID to use from it, and pass it to host PID 1. -* automatically propagate LUKS password credential into cryptsetup from host +- automatically propagate LUKS password credential into cryptsetup from host (i.e. SMBIOS type #11, …), so that one can unlock LUKS via VM hypervisor supplied password. -* add ability to path_is_valid() to classify paths that refer to a dir from +- add ability to path_is_valid() to classify paths that refer to a dir from those which may refer to anything, and use that in various places to filter early. i.e. stuff ending in "/", "/." and "/.." definitely refers to a directory, and paths ending that way can be refused early in many contexts. -* systemd-measure: add --pcrpkey-auto as an alternative to --pcrpkey=, where it +- systemd-measure: add --pcrpkey-auto as an alternative to --pcrpkey=, where it would just use the same public key specified with --public-key= (or the one automatically derived from --private-key=). -* Add "purpose" flag to partition flags in discoverable partition spec that +- Add "purpose" flag to partition flags in discoverable partition spec that indicate if partition is intended for sysext, for portable service, for booting and so on. Then, when dissecting DDI allow specifying a purpose to use as additional search condition. Use case: images that combined a sysext partition with a portable service partition in one. -* On boot, auto-generate an asymmetric key pair from the TPM, +- On boot, auto-generate an asymmetric key pair from the TPM, and use it for validating DDIs and credentials. Maybe upload it to the kernel keyring, so that the kernel does this validation for us for verity and kernel modules -* lock down acceptable encrypted credentials at boot, via simple allowlist, +- lock down acceptable encrypted credentials at boot, via simple allowlist, maybe on kernel command line: systemd.import_encrypted_creds=foobar.waldo,tmpfiles.extra to protect locked down kernels from credentials generated on the host with a weak kernel -* Merge systemd-creds options --uid= (which accepts user names) and --user. +- Merge systemd-creds options --uid= (which accepts user names) and --user. -* Add support for extra verity configuration options to systemd-repart (FEC, +- Add support for extra verity configuration options to systemd-repart (FEC, hash type, etc) -* chase(): take inspiration from path_extract_filename() and return +- chase(): take inspiration from path_extract_filename() and return O_DIRECTORY if input path contains trailing slash. -* measure credentials picked up from SMBIOS to some suitable PCR +- measure credentials picked up from SMBIOS to some suitable PCR -* measure GPT and LUKS headers somewhere when we use them (i.e. in +- measure GPT and LUKS headers somewhere when we use them (i.e. in systemd-gpt-auto-generator/systemd-repart and in systemd-cryptsetup?) -* pick up creds from EFI vars +- pick up creds from EFI vars -* Add and pickup tpm2 metadata for creds structure. +- Add and pickup tpm2 metadata for creds structure. -* systemd-measure tool: +- systemd-measure tool: - pre-calculate PCR 12 (command line) + PCR 13 (sysext) the same way we can precalculate PCR 11 -* maybe add new flags to gpt partition tables for rootfs and usrfs indicating +- maybe add new flags to gpt partition tables for rootfs and usrfs indicating purpose, i.e. whether something is supposed to be bootable in a VM, on baremetal, on an nspawn-style container, if it is a portable service image, or a sysext for initrd, for host os, or for portable container. Then hook portabled/… up to udev to watch block devices coming up with the flags set, and use it. -* add "systemd-sysext identify" verb, that you can point on any file in /usr/ +- add "systemd-sysext identify" verb, that you can point on any file in /usr/ and that determines from which overlayfs layer it originates, which image, and with what it was signed. -* systemd-creds: extend encryption logic to support asymmetric +- systemd-creds: extend encryption logic to support asymmetric encryption/authentication. Idea: add new verb "systemd-creds public-key" which generates a priv/pub key pair on the TPM2 and stores the priv key locally in /var. It then outputs a certificate for the pub part to stdout. @@ -1309,10 +1311,10 @@ Features: the dropped in certs and encrypted with machine pubkey, and pass to machine. Machine is then able to authenticate you, and confidentiality is guaranteed. -* building on top of the above, the pub/priv key pair generated on the TPM2 +- building on top of the above, the pub/priv key pair generated on the TPM2 should probably also one you can use to get a remote attestation quote. -* Process credentials in: +- Process credentials in: - crypttab-generator: allow defining additional crypttab-like volumes via credentials (similar: verity-generator, integrity-generator). Use fstab-generator logic as inspiration. @@ -1330,7 +1332,7 @@ Features: sd-stub credentials. That way, we can support parallel OS installations with pre-built kernels. -* define a JSON format for units, separating out unit definitions from unit +- define a JSON format for units, separating out unit definitions from unit runtime state. Then, expose it: 1. Add Describe() method to Unit D-Bus object that returns a JSON object @@ -1342,33 +1344,33 @@ Features: forked-but-not-exec'ed children 4. Add varlink API to run transient units based on provided JSON definitions -* Add SUPPORT_END_URL= field to os-release with more *actionable* information +- Add SUPPORT_END_URL= field to os-release with more *actionable* information what to do if support ended -* pam_systemd: on interactive logins, maybe show SUPPORT_END information at +- pam_systemd: on interactive logins, maybe show SUPPORT_END information at login time, à la motd -* mount /var/ from initrd, so that we can apply sysext and stuff before the +- mount /var/ from initrd, so that we can apply sysext and stuff before the initrd transition. Specifically: 1. There should be a var= kernel cmdline option, matching root= and usr= 2. systemd-gpt-auto-generator should auto-mount /var if it finds it on disk 3. mount.x-initrd mount option in fstab should be implied for /var -* make persistent restarts easier by adding a new setting OpenPersistentFile= +- make persistent restarts easier by adding a new setting OpenPersistentFile= or so, which allows opening one or more files that is "persistent" across service restarts, hot reboot, cold reboots (depending on configuration): the files are created empty on first invocation, and on subsequent invocations the files are reboot. The files would be backed by tmpfs, pmem or /var depending on desired level of persistency. -* if we fork of a service with StandardOutput=journal, and it forks off a +- if we fork of a service with StandardOutput=journal, and it forks off a subprocess that quickly dies, we might not be able to identify the cgroup it comes from, but we can still derive that from the stdin socket its output came from. We apparently don't do that right now. -* add PR_SET_DUMPABLE service setting +- add PR_SET_DUMPABLE service setting -* homed/userdb: maybe define a "companion" dir for home directories where apps +- homed/userdb: maybe define a "companion" dir for home directories where apps can safely put privileged stuff in. Would not be writable by the user, but still conceptually belong to the user. Would be included in user's quota if possible, even if files are not owned by UID of user. Use case: container @@ -1382,12 +1384,12 @@ Features: file to move there, since it is managed by privileged code (i.e. homed) and not unprivileged code. -* maybe add support for binding and connecting AF_UNIX sockets in the file +- maybe add support for binding and connecting AF_UNIX sockets in the file system outside of the 108ch limit. When connecting, open O_PATH fd to socket inode first, then connect to /proc/self/fd/XYZ. When binding, create symlink to target dir in /tmp, and bind through it. -* add a proper concept of a "developer" mode, i.e. where cryptographic +- add a proper concept of a "developer" mode, i.e. where cryptographic protections of the root OS are weakened after interactive confirmation, to allow hackers to allow their own stuff. idea: allow entering developer mode only via explicit choice in boot menu: i.e. add explicit boot menu item for @@ -1397,16 +1399,16 @@ Features: TPM2. Ensure that boot menu item is the only way to enter developer mode, by binding it to locality/PCRs so that keys cannot be generated otherwise. -* services: add support for cryptographically unlocking per-service directories +- services: add support for cryptographically unlocking per-service directories via TPM2. Specifically, for StateDirectory= (and related dirs) use fscrypt to set up the directory so that it can only be accessed if host and app are in order. -* update HACKING.md to suggest developing systemd with the ideas from: +- update HACKING.md to suggest developing systemd with the ideas from: https://0pointer.net/blog/testing-my-system-code-in-usr-without-modifying-usr.html https://0pointer.net/blog/running-an-container-off-the-host-usr.html -* for vendor-built signed initrds: +- for vendor-built signed initrds: - kernel-install should be able to install encrypted creds automatically for machine id, root pw, rootfs uuid, resume partition uuid, and place next to EFI kernel, for sd-stub to pick them up. These creds should be locked to @@ -1416,18 +1418,18 @@ Features: - systemd-fstab-generator should look for rootfs device to mount in creds - systemd-resume-generator should look for resume partition uuid in creds -* Maybe extend the service protocol to support handling of some specific SIGRT +- Maybe extend the service protocol to support handling of some specific SIGRT signal for setting service log level, that carries the level via the sigqueue() data parameter. Enable this via unit file setting. -* sd_notify/vsock: maybe support binding to AF_VSOCK in Type=notify services, +- sd_notify/vsock: maybe support binding to AF_VSOCK in Type=notify services, then passing $NOTIFY_SOCKET and $NOTIFY_GUESTCID with PID1's cid (typically fixed to "2", i.e. the official host cid) and the expected guest cid, for the two sides of the channel. The latter env var could then be used in an appropriate qemu cmdline. That way qemu payloads could talk sd_notify() directly to host service manager. -* sd-device: +- sd-device: - add an API for acquiring list of child devices, given a device objects (i.e. all child dirents that dirs or symlinks to dirs) - maybe pin the sysfs dir with an fd, during the entire runtime of @@ -1436,15 +1438,15 @@ Features: sd_device object, so that data passed into sd_device_new_from_devnum() can also be queried. -* sysext: measure all activated sysext into a TPM PCR +- sysext: measure all activated sysext into a TPM PCR -* systemd-dissect: show available versions inside of a disk image, i.e. if +- systemd-dissect: show available versions inside of a disk image, i.e. if multiple versions are around of the same resource, show which ones. (in other words: show partition labels). -* systemd-dissect: add --cat switch for dumping files such as /etc/os-release +- systemd-dissect: add --cat switch for dumping files such as /etc/os-release -* per-service sandboxing option: ProtectIds=. If used, will overmount +- per-service sandboxing option: ProtectIds=. If used, will overmount /etc/machine-id and /proc/sys/kernel/random/boot_id with synthetic files, to make it harder for the service to identify the host. Depending on the user setting it should be fully randomized at invocation time, or a hash of the @@ -1455,60 +1457,60 @@ Features: uses RootDirectory= or RootImage=. (Might also over-mount /sys/class/dmi/id/*{uuid,serial} with /dev/null). -* doc: prep a document explaining resolved's internal objects, i.e. Query +- doc: prep a document explaining resolved's internal objects, i.e. Query vs. Question vs. Transaction vs. Stream and so on. -* doc: prep a document explaining PID 1's internal logic, i.e. transactions, +- doc: prep a document explaining PID 1's internal logic, i.e. transactions, jobs, units -* automatically ignore threaded cgroups in cg_xyz(). +- automatically ignore threaded cgroups in cg_xyz(). -* add linker script that implicitly adds symbol for build ID and new coredump +- add linker script that implicitly adds symbol for build ID and new coredump json package metadata, and use that when logging -* Enable RestrictFileSystems= for all our long-running services (similar: +- Enable RestrictFileSystems= for all our long-running services (similar: RestrictNetworkInterfaces=) -* Add systemd-analyze security checks for RestrictFileSystems= and +- Add systemd-analyze security checks for RestrictFileSystems= and RestrictNetworkInterfaces= -* cryptsetup/homed: implement TOTP authentication backed by TPM2 and its +- cryptsetup/homed: implement TOTP authentication backed by TPM2 and its internal clock. -* man: rework os-release(5), and clearly separate our extension-release.d/ and +- man: rework os-release(5), and clearly separate our extension-release.d/ and initrd-release parts, i.e. list explicitly which fields are about what. -* sysext: before applying a sysext, do a superficial validation run so that +- sysext: before applying a sysext, do a superficial validation run so that things are not rearranged to wildy. I.e. protect against accidental fuckups, such as masking out /usr/lib/ or so. We should probably refuse if existing inodes are replaced by other types of inodes or so. -* userdb: when synthesizing NSS records, pick "best" password from defined +- userdb: when synthesizing NSS records, pick "best" password from defined passwords, not just the first. i.e. if there are multiple defined, prefer unlocked over locked and prefer non-empty over empty. -* userdbd: implement an additional varlink service socket that provides the +- userdbd: implement an additional varlink service socket that provides the host user db in restricted form, then allow this to be bind mounted into sandboxed environments that want the host database in minimal form. All records would be stripped of all meta info, except the basic UID/name info. Then use this in portabled environments that do not use PrivateUsers=1. -* portabled: when extracting unit files and copying to system.attached, if a +- portabled: when extracting unit files and copying to system.attached, if a .p7s is available in the image, use it to protect the system.attached copy with fs-verity, so that it cannot be tampered with -* /etc/veritytab: allow that the roothash column can be specified as fs path +- /etc/veritytab: allow that the roothash column can be specified as fs path including a path to an AF_UNIX path, similar to how we do things with the keys of /etc/crypttab. That way people can store/provide the roothash externally and provide to us on demand only. -* rework recursive read-only remount to use new mount API +- rework recursive read-only remount to use new mount API -* when mounting disk images: if IMAGE_ID/IMAGE_VERSION is set in os-release +- when mounting disk images: if IMAGE_ID/IMAGE_VERSION is set in os-release data in the image, make sure the image filename actually matches this, so that images cannot be misused. -* sysupdate: +- sysupdate: - add fuzzing to the pattern parser - support casync as download mechanism - "systemd-sysupdate update --all" support, that iterates through all components @@ -1533,40 +1535,40 @@ Features: - make transport pluggable, so people can plug casync or similar behind it, instead of http. -* in sd-id128: also parse UUIDs in RFC4122 URN syntax (i.e. chop off urn:uuid: prefix) +- in sd-id128: also parse UUIDs in RFC4122 URN syntax (i.e. chop off urn:uuid: prefix) -* systemd-sysext: optionally, run it in initrd already, before transitioning +- systemd-sysext: optionally, run it in initrd already, before transitioning into host, to open up possibility for services shipped like that. -* whenever we receive fds via SCM_RIGHTS make sure none got dropped due to the +- whenever we receive fds via SCM_RIGHTS make sure none got dropped due to the reception limit the kernel silently enforces. -* Add service unit setting ConnectStream= which takes IP addresses and connects to them. +- Add service unit setting ConnectStream= which takes IP addresses and connects to them. -* Similar, Load= which takes literal data in text or base64 format, and puts it +- Similar, Load= which takes literal data in text or base64 format, and puts it into a memfd, and passes that. This enables some fun stuff, such as embedding bash scripts in unit files, by combining Load= with ExecStart=/bin/bash /proc/self/fd/3 -* add a ConnectSocket= setting to service unit files, that may reference a +- add a ConnectSocket= setting to service unit files, that may reference a socket unit, and which will connect to the socket defined therein, and pass the resulting fd to the service program via socket activation proto. -* Add a concept of ListenStream=anonymous to socket units: listen on a socket +- Add a concept of ListenStream=anonymous to socket units: listen on a socket that is deleted in the fs. Use case would be with ConnectSocket= above. -* importd: support image signature verification with PKCS#7 + OpenBSD signify +- importd: support image signature verification with PKCS#7 + OpenBSD signify logic, as alternative to crummy gpg -* add "systemd-analyze debug" + AttachDebugger= in unit files: The former +- add "systemd-analyze debug" + AttachDebugger= in unit files: The former specifies a command to execute; the latter specifies that an already running "systemd-analyze debug" instance shall be contacted and execution paused until it gives an OK. That way, tools like gdb or strace can be safely be invoked on processes forked off PID 1. -* expose MS_NOSYMFOLLOW in various places +- expose MS_NOSYMFOLLOW in various places -* credentials system: +- credentials system: - acquire from EFI variable? - acquire via ask-password? - acquire creds via keyring? @@ -1593,14 +1595,14 @@ Features: Document credentials in individual man pages, generate list as in systemd.directives. -* TPM2: auto-reenroll in cryptsetup, as fallback for hosed firmware upgrades +- TPM2: auto-reenroll in cryptsetup, as fallback for hosed firmware upgrades and such -* introduce a new group to own TPM devices +- introduce a new group to own TPM devices -* make cryptsetup lower --iter-time +- make cryptsetup lower --iter-time -* cryptsetup: +- cryptsetup: - cryptsetup-generator: allow specification of passwords in crypttab itself - support rd.luks.allow-discards= kernel cmdline params in cryptsetup generator - add boolean for disabling use of any password/recovery key slots. @@ -1623,27 +1625,27 @@ Features: - reimplement the mkswap/mke2fs in cryptsetup-generator to use systemd-makefs.service instead. -* systemd-analyze netif that explains predictable interface (or networkctl) +- systemd-analyze netif that explains predictable interface (or networkctl) -* systemd-analyze inspect-elf should show other notes too, at least build-id. +- systemd-analyze inspect-elf should show other notes too, at least build-id. -* Figure out naming of verbs in systemd-analyze: we have (singular) capability, +- Figure out naming of verbs in systemd-analyze: we have (singular) capability, exit-status, but (plural) filesystems, architectures. -* special case some calls of chase() to use openat2() internally, so +- special case some calls of chase() to use openat2() internally, so that the kernel does what we otherwise do. -* add a new flag to chase() that stops chasing once the first missing +- add a new flag to chase() that stops chasing once the first missing component is found and then allows the caller to create the rest. -* make use of new glibc 2.32 APIs sigabbrev_np(). +- make use of new glibc 2.32 APIs sigabbrev_np(). -* if /usr/bin/swapoff fails due to OOM, log a friendly explanatory message about it +- if /usr/bin/swapoff fails due to OOM, log a friendly explanatory message about it -* systemd-path: Add "private" runtime/state/cache dir enum, mapping to +- systemd-path: Add "private" runtime/state/cache dir enum, mapping to $RUNTIME_DIRECTORY, $STATE_DIRECTORY and such -* seccomp: +- seccomp: - maybe use seccomp_merge() to merge our filters per-arch if we can. Apparently kernel performance is much better with fewer larger seccomp filters than with more smaller seccomp filters. @@ -1651,92 +1653,92 @@ Features: - don't install filters for ABIs that are masked anyway for the specific service -* busctl: maybe expose a verb "ping" for pinging a dbus service to see if it +- busctl: maybe expose a verb "ping" for pinging a dbus service to see if it exists and responds. -* socket units: allow creating a udev monitor socket with ListenDevices= or so, +- socket units: allow creating a udev monitor socket with ListenDevices= or so, with matches, then activate app through that passing socket over -* unify on openssl: +- unify on openssl: - figure out what to do about libmicrohttpd: - 1.x is stable and has a hard dependency on gnutls - 2.x is in development and has openssl support - Worth testing against 2.x in our CI? - port fsprg over to openssl -* add growvol and makevol options for /etc/crypttab, similar to +- add growvol and makevol options for /etc/crypttab, similar to x-systemd.growfs and x-systemd-makefs. -* userdb: allow existence checks +- userdb: allow existence checks -* when switching root from initrd to host, set the machine_id env var so that +- when switching root from initrd to host, set the machine_id env var so that if the host has no machine ID set yet we continue to use the random one the initrd had set. -* tweak sd-event's child watching: keep a prioq of children to watch and use +- tweak sd-event's child watching: keep a prioq of children to watch and use waitid() only on the children with the highest priority until one is waitable and ignore all lower-prio ones from that point on -* maybe introduce xattrs that can be set on the root dir of the root fs +- maybe introduce xattrs that can be set on the root dir of the root fs partition that declare the volatility mode to use the image in. Previously I thought marking this via GPT partition flags but that's not ideal since that's outside of the LUKS encryption/verity verification, and we probably shouldn't operate in a volatile mode unless we got told so from a trusted source. -* coredump: maybe when coredumping read a new xattr from /proc/$PID/exe that +- coredump: maybe when coredumping read a new xattr from /proc/$PID/exe that may be used to mark a whole binary as non-coredumpable. Would fix: https://bugs.freedesktop.org/show_bug.cgi?id=69447 -* teach parse_timestamp() timezones like the calendar spec already knows it +- teach parse_timestamp() timezones like the calendar spec already knows it -* We should probably replace /etc/rc.d/README with a symlink to doc +- We should probably replace /etc/rc.d/README with a symlink to doc content. After all it is constant vendor data. -* maybe add kernel cmdline params: to force random seed crediting +- maybe add kernel cmdline params: to force random seed crediting -* let's not GC a unit while its ratelimits are still pending +- let's not GC a unit while its ratelimits are still pending -* when killing due to service watchdog timeout maybe detect whether target +- when killing due to service watchdog timeout maybe detect whether target process is under ptracing and then log loudly and continue instead. -* make rfkill uaccess controllable by default, i.e. steal rule from +- make rfkill uaccess controllable by default, i.e. steal rule from gnome-bluetooth and friends -* make MAINPID= message reception checks even stricter: if service uses User=, +- make MAINPID= message reception checks even stricter: if service uses User=, then check sending UID and ignore message if it doesn't match the user or root. -* maybe trigger a uevent "change" on a device if "systemctl reload xyz.device" +- maybe trigger a uevent "change" on a device if "systemctl reload xyz.device" is issued. -* when importing an fs tree with machined, optionally apply userns-rec-chown +- when importing an fs tree with machined, optionally apply userns-rec-chown -* when importing an fs tree with machined, complain if image is not an OS +- when importing an fs tree with machined, complain if image is not an OS -* Maybe introduce a helper safe_exec() or so, which is to execve() which +- Maybe introduce a helper safe_exec() or so, which is to execve() which safe_fork() is to fork(). And then make revert the RLIMIT_NOFILE soft limit to 1K implicitly, unless explicitly opted-out. -* rework seccomp/nnp logic that even if User= is used in combination with +- rework seccomp/nnp logic that even if User= is used in combination with a seccomp option we don't have to set NNP. For that, change uid first while keeping CAP_SYS_ADMIN, then apply seccomp, the drop cap. -* when no locale is configured, default to UEFI's PlatformLang variable +- when no locale is configured, default to UEFI's PlatformLang variable -* add a new syscall group "@esoteric" for more esoteric stuff such as bpf() and +- add a new syscall group "@esoteric" for more esoteric stuff such as bpf() and usefaultd() and make systemd-analyze check for it. -* paranoia: whenever we process passwords, call mlock() on the memory +- paranoia: whenever we process passwords, call mlock() on the memory first. i.e. look for all places we use free_and_erasep() and augment them with mlock(). Also use MADV_DONTDUMP. Alternatively (preferably?) use memfd_secret(). -* Move RestrictAddressFamily= to the new cgroup create socket +- Move RestrictAddressFamily= to the new cgroup create socket -* optionally: turn on cgroup delegation for per-session scope units +- optionally: turn on cgroup delegation for per-session scope units -* sd-boot: +- sd-boot: - do something useful if we find exactly zero entries (ignoring items such as reboot/poweroff/factory reset). Show a help text or so. - optionally ask for confirmation before executing certain operations @@ -1758,7 +1760,7 @@ Features: - optionally, show boot menu when previous default boot item has non-zero "tries done" count -* augment CODE_FILE=, CODE_LINE= with something like CODE_BASE= or so which +- augment CODE_FILE=, CODE_LINE= with something like CODE_BASE= or so which contains some identifier for the project, which allows us to include clickable links to source files generating these log messages. The identifier could be some abbreviated URL prefix or so (taking inspiration from Go @@ -1767,53 +1769,53 @@ Features: sufficient to build a link by prefixing "http://" and suffixing the CODE_FILE. -* Augment MESSAGE_ID with MESSAGE_BASE, in a similar fashion so that we can +- Augment MESSAGE_ID with MESSAGE_BASE, in a similar fashion so that we can make clickable links from log messages carrying a MESSAGE_ID, that lead to some explanatory text online. -* maybe extend .path units to expose fanotify() per-mount change events +- maybe extend .path units to expose fanotify() per-mount change events -* hibernate/s2h: if swap is on weird storage and refuse if so +- hibernate/s2h: if swap is on weird storage and refuse if so -* cgroups: use inotify to get notified when somebody else modifies cgroups +- cgroups: use inotify to get notified when somebody else modifies cgroups owned by us, then log a friendly warning. -* beef up log.c with support for stripping ANSI sequences from strings, so that +- beef up log.c with support for stripping ANSI sequences from strings, so that it is OK to include them in log strings. This would be particularly useful so that our log messages could contain clickable links for example for unit files and suchlike we operate on. -* add support for "portablectl attach http://foobar.com/waaa.raw (i.e. importd integration) +- add support for "portablectl attach http://foobar.com/waaa.raw (i.e. importd integration) -* sync dynamic uids/gids between host+portable service (i.e. if DynamicUser=1 is set for a service, make sure that the +- sync dynamic uids/gids between host+portable service (i.e. if DynamicUser=1 is set for a service, make sure that the selected user is resolvable in the service even if it ships its own /etc/passwd) -* Fix DECIMAL_STR_MAX or DECIMAL_STR_WIDTH. One includes a trailing NUL, the +- Fix DECIMAL_STR_MAX or DECIMAL_STR_WIDTH. One includes a trailing NUL, the other doesn't. What a disaster. Probably to exclude it. -* Check that users of inotify's IN_DELETE_SELF flag are using it properly, as +- Check that users of inotify's IN_DELETE_SELF flag are using it properly, as usually IN_ATTRIB is the right way to watch deleted files, as the former only fires when a file is actually removed from disk, i.e. the link count drops to zero and is not open anymore, while the latter happens when a file is unlinked from any dir. -* systemctl, machinectl, loginctl: port "status" commands over to +- systemctl, machinectl, loginctl: port "status" commands over to format-table.c's vertical output logic. -* add --vacuum-xyz options to coredumpctl, matching those journalctl already has. +- add --vacuum-xyz options to coredumpctl, matching those journalctl already has. -* add CopyFile= or so as unit file setting that may be used to copy files or +- add CopyFile= or so as unit file setting that may be used to copy files or directory trees from the host to the services RootImage= and RootDirectory= environment. Which we can use for /etc/machine-id and in particular /etc/resolv.conf. Should be smart and do something useful on read-only images, for example fall back to read-only bind mounting the file instead. -* bypass SIGTERM state in unit files if KillSignal is SIGKILL +- bypass SIGTERM state in unit files if KillSignal is SIGKILL -* add proper dbus APIs for the various sd_notify() commands, such as MAINPID=1 +- add proper dbus APIs for the various sd_notify() commands, such as MAINPID=1 and so on, which would mean we could report errors and such. -* introduce DefaultSlice= or so in system.conf that allows changing where we +- introduce DefaultSlice= or so in system.conf that allows changing where we place our units by default, i.e. change system.slice to something else. Similar, ManagerSlice= should exist so that PID1's own scope unit could be moved somewhere else too. Finally machined and logind should get similar @@ -1822,175 +1824,175 @@ Features: the entire system, with the exception of one specific service. See: https://lists.freedesktop.org/archives/systemd-devel/2018-February/040369.html -* calenderspec: add support for week numbers and day numbers within a +- calenderspec: add support for week numbers and day numbers within a year. This would allow us to define "bi-weekly" triggers safely. -* make use of ethtool veth peer info in machined, for automatically finding out +- make use of ethtool veth peer info in machined, for automatically finding out host-side interface pointing to the container. -* add some special mode to LogsDirectory=/StateDirectory=… that allows +- add some special mode to LogsDirectory=/StateDirectory=… that allows declaring these directories without necessarily pulling in deps for them, or creating them when starting up. That way, we could declare that systemd-journald writes to /var/log/journal, which could be useful when we doing disk usage calculations and so on. -* deprecate RootDirectoryStartOnly= in favour of a new ExecStart= prefix char +- deprecate RootDirectoryStartOnly= in favour of a new ExecStart= prefix char -* support projid-based quota in machinectl for containers +- support projid-based quota in machinectl for containers -* add a way to lock down cgroup migration: a boolean, which when set for a unit +- add a way to lock down cgroup migration: a boolean, which when set for a unit makes sure the processes in it can never migrate out of it -* blog about fd store and restartable services +- blog about fd store and restartable services -* document Environment=SYSTEMD_LOG_LEVEL=debug drop-in in debugging document +- document Environment=SYSTEMD_LOG_LEVEL=debug drop-in in debugging document -* rework ExecOutput and ExecInput enums so that EXEC_OUTPUT_NULL loses its +- rework ExecOutput and ExecInput enums so that EXEC_OUTPUT_NULL loses its magic meaning and is no longer upgraded to something else if set explicitly. -* in the long run: permit a system with /etc/machine-id linked to /dev/null, to +- in the long run: permit a system with /etc/machine-id linked to /dev/null, to make it lose its identity, i.e. be anonymous. For this we'd have to patch through the whole tree to make all code deal with the case where no machine ID is available. -* optionally, collect cgroup resource data, and store it in per-unit RRD files, +- optionally, collect cgroup resource data, and store it in per-unit RRD files, suitable for processing with rrdtool. Add bus API to access this data, and possibly implement a CPULoad property based on it. -* beef up pam_systemd to take unit file settings such as cgroups properties as +- beef up pam_systemd to take unit file settings such as cgroups properties as parameters -* In DynamicUser= mode: before selecting a UID, use disk quota APIs on relevant +- In DynamicUser= mode: before selecting a UID, use disk quota APIs on relevant disks to see if the UID is already in use. -* Add AddUser= setting to unit files, similar to DynamicUser=1 which however +- Add AddUser= setting to unit files, similar to DynamicUser=1 which however creates a static, persistent user rather than a dynamic, transient user. We can leverage code from sysusers.d for this. -* add some optional flag to ReadWritePaths= and friends, that has the effect +- add some optional flag to ReadWritePaths= and friends, that has the effect that we create the dir in question when the service is started. Example: ReadWritePaths=:/var/lib/foobar -* Add ExecMonitor= setting. May be used multiple times. Forks off a process in +- Add ExecMonitor= setting. May be used multiple times. Forks off a process in the service cgroup, which is supposed to monitor the service, and when it exits the service is considered failed by its monitor. -* track the per-service PAM process properly (i.e. as an additional control +- track the per-service PAM process properly (i.e. as an additional control process), so that it may be queried on the bus and everything. -* add a new "debug" job mode, that is propagated to unit_start() and for +- add a new "debug" job mode, that is propagated to unit_start() and for services results in two things: we raise SIGSTOP right before invoking execve() and turn off watchdog support. Then, use that to implement "systemd-gdb" for attaching to the start-up of any system service in its natural habitat. -* add a percentage syntax for TimeoutStopSec=, e.g. TimeoutStopSec=150%, and +- add a percentage syntax for TimeoutStopSec=, e.g. TimeoutStopSec=150%, and then use that for the setting used in user@.service. It should be understood relative to the configured default value. -* enable LockMLOCK to take a percentage value relative to physical memory +- enable LockMLOCK to take a percentage value relative to physical memory -* Permit masking specific netlink APIs with RestrictAddressFamily= +- Permit masking specific netlink APIs with RestrictAddressFamily= -* define gpt header bits to select volatility mode +- define gpt header bits to select volatility mode -* ProtectClock= (drops CAP_SYS_TIMES, adds seccomp filters for settimeofday, adjtimex), sets DeviceAllow o /dev/rtc +- ProtectClock= (drops CAP_SYS_TIMES, adds seccomp filters for settimeofday, adjtimex), sets DeviceAllow o /dev/rtc -* ProtectTracing= (drops CAP_SYS_PTRACE, blocks ptrace syscall, makes /sys/kernel/tracing go away) +- ProtectTracing= (drops CAP_SYS_PTRACE, blocks ptrace syscall, makes /sys/kernel/tracing go away) -* ProtectMount= (drop mount/umount/pivot_root from seccomp, disallow fuse via DeviceAllow, imply Mountflags=slave) +- ProtectMount= (drop mount/umount/pivot_root from seccomp, disallow fuse via DeviceAllow, imply Mountflags=slave) -* ProtectKeyRing= to take keyring calls away +- ProtectKeyRing= to take keyring calls away -* RemoveKeyRing= to remove all keyring entries of the specified user +- RemoveKeyRing= to remove all keyring entries of the specified user -* ProtectReboot= that masks reboot() and kexec_load() syscalls, prohibits kill +- ProtectReboot= that masks reboot() and kexec_load() syscalls, prohibits kill on PID 1 with the relevant signals, and makes relevant files in /sys and /proc (such as the sysrq stuff) unavailable -* Support ReadWritePaths/ReadOnlyPaths/InaccessiblePaths in systemd --user instances +- Support ReadWritePaths/ReadOnlyPaths/InaccessiblePaths in systemd --user instances via the new unprivileged Landlock LSM (https://landlock.io) -* make sure the ratelimit object can deal with USEC_INFINITY as way to turn off things +- make sure the ratelimit object can deal with USEC_INFINITY as way to turn off things -* in nss-systemd, if we run inside of RootDirectory= with PrivateUsers= set, +- in nss-systemd, if we run inside of RootDirectory= with PrivateUsers= set, find a way to map the User=/Group= of the service to the right name. This way a user/group for a service only has to exist on the host for the right mapping to work. -* add bus API for creating unit files in /etc, reusing the code for transient units +- add bus API for creating unit files in /etc, reusing the code for transient units -* add bus API to remove unit files from /etc +- add bus API to remove unit files from /etc -* add bus API to retrieve current unit file contents (i.e. implement "systemctl cat" on the bus only) +- add bus API to retrieve current unit file contents (i.e. implement "systemctl cat" on the bus only) -* rework fopen_temporary() to make use of open_tmpfile_linkable() (problem: the +- rework fopen_temporary() to make use of open_tmpfile_linkable() (problem: the kernel doesn't support linkat() that replaces existing files, currently) -* transient units: don't bother with actually setting unit properties, we +- transient units: don't bother with actually setting unit properties, we reload the unit file anyway -* optionally, also require WATCHDOG=1 notifications during service start-up and shutdown +- optionally, also require WATCHDOG=1 notifications during service start-up and shutdown -* cache sd_event_now() result from before the first iteration... +- cache sd_event_now() result from before the first iteration... -* add an explicit parser for LimitRTPRIO= that verifies +- add an explicit parser for LimitRTPRIO= that verifies the specified range and generates sane error messages for incorrect specifications. -* when we detect that there are waiting jobs but no running jobs, do something +- when we detect that there are waiting jobs but no running jobs, do something -* PID 1 should send out sd_notify("WATCHDOG=1") messages (for usage in the --user mode, and when run via nspawn) +- PID 1 should send out sd_notify("WATCHDOG=1") messages (for usage in the --user mode, and when run via nspawn) -* there's probably something wrong with having user mounts below /sys, +- there's probably something wrong with having user mounts below /sys, as we have for debugfs. for example, src/core/mount.c handles mounts prefixed with /sys generally special. https://lists.freedesktop.org/archives/systemd-devel/2015-June/032962.html -* fstab-generator: default to tmpfs-as-root if only usr= is specified on the kernel cmdline +- fstab-generator: default to tmpfs-as-root if only usr= is specified on the kernel cmdline -* docs: bring https://systemd.io/MY_SERVICE_CANT_GET_REALTIME up to date +- docs: bring https://systemd.io/MY_SERVICE_CANT_GET_REALTIME up to date -* add a job mode that will fail if a transaction would mean stopping +- add a job mode that will fail if a transaction would mean stopping running units. Use this in timedated to manage the NTP service state. https://lists.freedesktop.org/archives/systemd-devel/2015-April/030229.html -* The udev blkid built-in should expose a property that reflects +- The udev blkid built-in should expose a property that reflects whether media was sensed in USB CF/SD card readers. This should then be used to control SYSTEMD_READY=1/0 so that USB card readers aren't picked up by systemd unless they contain a medium. This would mirror the behaviour we already have for CD drives. -* hostnamectl: show root image uuid +- hostnamectl: show root image uuid -* Find a solution for SMACK capabilities stuff: +- Find a solution for SMACK capabilities stuff: https://lists.freedesktop.org/archives/systemd-devel/2014-December/026188.html -* synchronize console access with BSD locks: +- synchronize console access with BSD locks: https://lists.freedesktop.org/archives/systemd-devel/2014-October/024582.html -* as soon as we have sender timestamps, revisit coalescing multiple parallel daemon reloads: +- as soon as we have sender timestamps, revisit coalescing multiple parallel daemon reloads: https://lists.freedesktop.org/archives/systemd-devel/2014-December/025862.html -* figure out when we can use the coarse timers +- figure out when we can use the coarse timers -* maybe allow timer units with an empty Units= setting, so that they +- maybe allow timer units with an empty Units= setting, so that they can be used for resuming the system but nothing else. -* what to do about udev db binary stability for apps? (raw access is not an option) +- what to do about udev db binary stability for apps? (raw access is not an option) -* exponential backoff in timesyncd when we cannot reach a server +- exponential backoff in timesyncd when we cannot reach a server -* timesyncd: add ugly bus calls to set NTP servers per-interface, for usage by NM +- timesyncd: add ugly bus calls to set NTP servers per-interface, for usage by NM -* add systemd.abort_on_kill or some other such flag to send SIGABRT instead of SIGKILL +- add systemd.abort_on_kill or some other such flag to send SIGABRT instead of SIGKILL (throughout the codebase, not only PID1) -* drop nss-myhostname in favour of nss-resolve? +- drop nss-myhostname in favour of nss-resolve? -* resolved: +- resolved: - mDNS/DNS-SD - service registration - service/domain/types browsing @@ -2008,35 +2010,35 @@ Features: fd00:5353:5353:5353:5353:5353:5353:5353), and listen on port 53 on it for the local stubs, so that we can make the stub available via ipv6 too. -* refcounting in sd-resolve is borked +- refcounting in sd-resolve is borked -* add new gpt type for btrfs volumes +- add new gpt type for btrfs volumes -* generator that automatically discovers btrfs subvolumes, identifies their purpose based on some xattr on them. +- generator that automatically discovers btrfs subvolumes, identifies their purpose based on some xattr on them. -* a way for container managers to turn off getty starting via $container_headless= or so... +- a way for container managers to turn off getty starting via $container_headless= or so... -* figure out a nice way how we can let the admin know what child/sibling unit causes cgroup membership for a specific unit +- figure out a nice way how we can let the admin know what child/sibling unit causes cgroup membership for a specific unit -* For timer units: add some mechanisms so that timer units that trigger immediately on boot do not have the services +- For timer units: add some mechanisms so that timer units that trigger immediately on boot do not have the services they run added to the initial transaction and thus confuse Type=idle. -* add bus api to query unit file's X fields. +- add bus api to query unit file's X fields. -* gpt-auto-generator: +- gpt-auto-generator: - Make /home automount rather than mount? -* add generator that pulls in systemd-network from containers when +- add generator that pulls in systemd-network from containers when CAP_NET_ADMIN is set, more than the loopback device is defined, even when it is otherwise off -* MessageQueueMessageSize= (and suchlike) should use parse_iec_size(). +- MessageQueueMessageSize= (and suchlike) should use parse_iec_size(). -* implement Distribute= in socket units to allow running multiple +- implement Distribute= in socket units to allow running multiple service instances processing the listening socket, and open this up for ReusePort= -* cgroups: +- cgroups: - implement per-slice CPUFairScheduling=1 switch - introduce high-level settings for RT budget, swappiness - how to reset dynamically changed unit cgroup attributes sanely? @@ -2046,46 +2048,46 @@ Features: - add settings for cgroup.max.descendants and cgroup.max.depth, maybe use them for user@.service -* transient units: +- transient units: - add field to transient units that indicate whether systemd or somebody else saves/restores its settings, for integration with libvirt -* libsystemd-journal, libsystemd-login, libudev: add calls to easily attach these objects to sd-event event loops +- libsystemd-journal, libsystemd-login, libudev: add calls to easily attach these objects to sd-event event loops -* be more careful what we export on the bus as (usec_t) 0 and (usec_t) -1 +- be more careful what we export on the bus as (usec_t) 0 and (usec_t) -1 -* rfkill,backlight: we probably should run the load tools inside of the udev rules so that the state is properly initialized by the time other software sees it +- rfkill,backlight: we probably should run the load tools inside of the udev rules so that the state is properly initialized by the time other software sees it -* If we try to find a unit via a dangling symlink, generate a clean +- If we try to find a unit via a dangling symlink, generate a clean error. Currently, we just ignore it and read the unit from the search path anyway. -* refuse boot if /usr/lib/os-release is missing or /etc/machine-id cannot be set up +- refuse boot if /usr/lib/os-release is missing or /etc/machine-id cannot be set up -* man: the documentation of Restart= currently is very misleading and suggests the tools from ExecStartPre= might get restarted. +- man: the documentation of Restart= currently is very misleading and suggests the tools from ExecStartPre= might get restarted. -* There's currently no way to cancel fsck (used to be possible via C-c or c on the console) +- There's currently no way to cancel fsck (used to be possible via C-c or c on the console) -* add option to sockets to avoid activation. Instead just drop packets/connections, see http://cyberelk.net/tim/2012/02/15/portreserve-systemd-solution/ +- add option to sockets to avoid activation. Instead just drop packets/connections, see http://cyberelk.net/tim/2012/02/15/portreserve-systemd-solution/ -* make sure systemd-ask-password-wall does not shutdown systemd-ask-password-console too early +- make sure systemd-ask-password-wall does not shutdown systemd-ask-password-console too early -* verify that the AF_UNIX sockets of a service in the fs still exist +- verify that the AF_UNIX sockets of a service in the fs still exist when we start a service in order to avoid confusion when a user assumes starting a service is enough to make it accessible -* Make it possible to set the keymap independently from the font on +- Make it possible to set the keymap independently from the font on the kernel cmdline. Right now setting one resets also the other. -* and a dbus call to generate target from current state +- and a dbus call to generate target from current state -* investigate whether the gnome pty helper should be moved into systemd, to provide cgroup support. +- investigate whether the gnome pty helper should be moved into systemd, to provide cgroup support. -* dot output for --test showing the 'initial transaction' +- dot output for --test showing the 'initial transaction' -* be able to specify a forced restart of service A where service B depends on, in case B +- be able to specify a forced restart of service A where service B depends on, in case B needs to be auto-respawned? -* pid1: +- pid1: - When logging about multiple units (stopping BoundTo units, conflicts, etc.), log both units as UNIT=, so that journalctl -u triggers on both. - generate better errors when people try to set transient properties @@ -2120,7 +2122,7 @@ Features: - find a way how we can reload unit file configuration for specific units only, without reloading the whole of systemd -* unit files: +- unit files: - allow port=0 in .socket units - maybe introduce ExecRestartPre= - implement Register= switch in .socket units to enable registration @@ -2132,64 +2134,64 @@ Features: - Allow multiple ExecStart= for all Type= settings, so that we can cover rescue.service nicely - add verification of [Install] section to systemd-analyze verify -* timer units: +- timer units: - timer units should get the ability to trigger when DST changes - Modulate timer frequency based on battery state -* clean up date formatting and parsing so that all absolute/relative timestamps we format can also be parsed +- clean up date formatting and parsing so that all absolute/relative timestamps we format can also be parsed -* on shutdown: move utmp, wall, audit logic all into PID 1 (or logind?) +- on shutdown: move utmp, wall, audit logic all into PID 1 (or logind?) -* make repeated alt-ctrl-del presses printing a dump +- make repeated alt-ctrl-del presses printing a dump -* currently x-systemd.timeout is lost in the initrd, since crypttab is copied into dracut, but fstab is not +- currently x-systemd.timeout is lost in the initrd, since crypttab is copied into dracut, but fstab is not -* add a pam module that on password changes updates any LUKS slot where the password matches +- add a pam module that on password changes updates any LUKS slot where the password matches -* test/: +- test/: - add unit tests for config_parse_device_allow() -* seems that when we follow symlinks to units we prefer the symlink +- seems that when we follow symlinks to units we prefer the symlink destination path over /etc and /usr. We should not do that. Instead /etc should always override /run+/usr and also any symlink destination. -* when isolating, try to figure out a way how we implicitly can order +- when isolating, try to figure out a way how we implicitly can order all units we stop before the isolating unit... -* teach ConditionKernelCommandLine= globs or regexes (in order to match foobar={no,0,off}) +- teach ConditionKernelCommandLine= globs or regexes (in order to match foobar={no,0,off}) -* Add ConditionDirectoryNotEmpty= handle non-absoute paths as a search path or add +- Add ConditionDirectoryNotEmpty= handle non-absoute paths as a search path or add ConditionConfigSearchPathNotEmpty= or different syntax? See the discussion starting at https://github.com/systemd/systemd/pull/15109#issuecomment-607740136. -* BootLoaderSpec: define a way how an installer can figure out whether a BLS +- BootLoaderSpec: define a way how an installer can figure out whether a BLS compliant boot loader is installed. -* BootLoaderSpec: document @saved pseudo-entry, update mention in BLI +- BootLoaderSpec: document @saved pseudo-entry, update mention in BLI -* think about requeuing jobs when daemon-reload is issued? use case: +- think about requeuing jobs when daemon-reload is issued? use case: the initrd issues a reload after fstab from the host is accessible and we might want to requeue the mounts local-fs acquired through that automatically. -* systemd-inhibit: make taking delay locks useful: support sending SIGINT or SIGTERM on PrepareForSleep() +- systemd-inhibit: make taking delay locks useful: support sending SIGINT or SIGTERM on PrepareForSleep() -* remove any syslog support from log.c — we probably cannot do this before split-off udev is gone for good +- remove any syslog support from log.c — we probably cannot do this before split-off udev is gone for good -* shutdown logging: store to EFI var, and store to USB stick? +- shutdown logging: store to EFI var, and store to USB stick? -* merge unit_kill_common() and unit_kill_context() +- merge unit_kill_common() and unit_kill_context() -* add a dependency on standard-conf.xml and other included files to man pages +- add a dependency on standard-conf.xml and other included files to man pages -* MountFlags=shared acts as MountFlags=slave right now. +- MountFlags=shared acts as MountFlags=slave right now. -* properly handle loop back mounts via fstab, especially regards to fsck/passno +- properly handle loop back mounts via fstab, especially regards to fsck/passno -* initialize the hostname from the fs label of /, if /etc/hostname does not exist? +- initialize the hostname from the fs label of /, if /etc/hostname does not exist? -* sd-bus: +- sd-bus: - EBADSLT handling - GetAllProperties() on a non-existing object does not result in a failure currently - port to sd-resolve for connecting to TCP dbus servers @@ -2206,7 +2208,7 @@ Features: - parse addresses given in sd_bus_set_addresses immediately and not only when used. Add unit tests. -* sd-event: +- sd-event: - allow multiple signal handlers per signal? - document chaining of signal handler for SIGCHLD and child handlers - define more intervals where we will shift wakeup intervals around in, 1h, 6h, 24h, ... @@ -2245,21 +2247,21 @@ Features: hence on each event loop iteration check all processes which we shall watch with higher prio explicitly, and then watch the entire rest with P_ALL. -* dbus: when a unit failed to load (i.e. is in UNIT_ERROR state), we +- dbus: when a unit failed to load (i.e. is in UNIT_ERROR state), we should be able to safely try another attempt when the bus call LoadUnit() is invoked. -* document org.freedesktop.MemoryAllocation1 +- document org.freedesktop.MemoryAllocation1 -* maybe do not install getty@tty1.service symlink in /etc but in /usr? +- maybe do not install getty@tty1.service symlink in /etc but in /usr? -* print a nicer explanation if people use variable/specifier expansion in ExecStart= for the first word +- print a nicer explanation if people use variable/specifier expansion in ExecStart= for the first word -* mount: turn dependency information from /proc/self/mountinfo into dependency information between systemd units. +- mount: turn dependency information from /proc/self/mountinfo into dependency information between systemd units. -* EFI: +- EFI: - honor language efi variables for default language selection (if there are any?) - honor timezone efi variables for default timezone selection (if there are any?) -* bootctl: +- bootctl: - recognize the case when not booted on EFI - add tool for registering BootXXX entry that boots from some http server of your choice (i.e. like kernel-bootcfg --add-uri=) @@ -2269,7 +2271,7 @@ Features: - teach it to prepare an ESP wholesale, i.e. with mkfs.vfat invocation - teach it to copy in unified kernel images and maybe type #1 boot loader spec entries from host -* logind: +- logind: - logind: optionally, ignore idle-hint logic for autosuspend, block suspend as long as a session is around - logind: wakelock/opportunistic suspend support - Add pretty name for seats in logind @@ -2303,12 +2305,12 @@ Features: idea, and specifically works around the fact the autofs ignores busy by mount namespaces) -* move multiseat vid/pid matches from logind udev rule to hwdb +- move multiseat vid/pid matches from logind udev rule to hwdb -* delay activation of logind until somebody logs in, or when /dev/tty0 pulls it +- delay activation of logind until somebody logs in, or when /dev/tty0 pulls it in or lingering is on (so that containers don't bother with it until PAM is used). also exit-on-idle -* journal: +- journal: - consider introducing implicit _TTY= + _PPID= + _EUID= + _EGID= + _FSUID= + _FSGID= fields - journald: also get thread ID from client, plus thread name - journal: when waiting for journal additions in the client always sleep at least 1s or so, in order to minimize wakeups @@ -2403,42 +2405,42 @@ Features: - sigbus API via a signal-handler safe function that people may call from the SIGBUS handler -* Hook up journald's FSS logic with TPM2: seal the verification disk by +- Hook up journald's FSS logic with TPM2: seal the verification disk by time-based policy, so that the verification key can remain on host and ve validated via TPM. -* rework journalctl -M to be based on a machined method that generates a mount +- rework journalctl -M to be based on a machined method that generates a mount fd of the relevant journal dirs in the container with uidmapping applied to allow the host to read it, while making everything read-only. -* in journald, write out a recognizable log record whenever the system clock is +- in journald, write out a recognizable log record whenever the system clock is changed ("stepped"), and in timesyncd whenever we acquire an NTP fix ("slewing"). Then, in journalctl for each boot time we come across, find these records, and use the structured info they include to display "corrected" wallclock time, as calculated from the monotonic timestamp in the log record, adjusted by the delta declared in the structured log record. -* in journald: whenever we start a new journal file because the boot ID +- in journald: whenever we start a new journal file because the boot ID changed, let's generate a recognizable log record containing info about old and new ID. Then, when displaying log stream in journalctl look for these records, to be able to order them. -* hook up journald with TPMs? measure new journal records to the TPM in regular +- hook up journald with TPMs? measure new journal records to the TPM in regular intervals, validate the journal against current TPM state with that. (taking inspiration from IMA log) -* sd-journal puts a limit on parallel journal files to view at once. journald +- sd-journal puts a limit on parallel journal files to view at once. journald should probably honour that same limit (JOURNAL_FILES_MAX) when vacuuming to ensure we never generate more files than we can actually view. -* bsod: maybe use graphical mode. Use DRM APIs directly, see +- bsod: maybe use graphical mode. Use DRM APIs directly, see https://github.com/dvdhrm/docs/blob/master/drm-howto/modeset.c for an example for doing that. -* maybe implicitly attach monotonic+realtime timestamps to outgoing messages in +- maybe implicitly attach monotonic+realtime timestamps to outgoing messages in log.c and sd-journal-send -* journalctl/timesyncd: whenever timesyncd acquires a synchronization from NTP, +- journalctl/timesyncd: whenever timesyncd acquires a synchronization from NTP, create a structured log entry that contains boot ID, monotonic clock and realtime clock (I mean, this requires no special work, as these three fields are implicit). Then in journalctl when attempting to display the realtime @@ -2449,28 +2451,28 @@ Features: without RTC, i.e. where initially wallclock timestamps carry rubbish, until an NTP sync is acquired. -* introduce per-unit (i.e. per-slice, per-service) journal log size limits. +- introduce per-unit (i.e. per-slice, per-service) journal log size limits. -* tweak journald context caching. In addition to caching per-process attributes +- tweak journald context caching. In addition to caching per-process attributes keyed by PID, cache per-cgroup attributes (i.e. the various xattrs we read) keyed by cgroup path, and guarded by ctime changes. This should provide us with a nice speed-up on services that have many processes running in the same cgroup. -* maybe add call sd_journal_set_block_timeout() or so to set SO_SNDTIMEO for +- maybe add call sd_journal_set_block_timeout() or so to set SO_SNDTIMEO for the sd-journal logging socket, and, if the timeout is set to 0, sets O_NONBLOCK on it. That way people can control if and when to block for logging. -* add a test if all entries in the catalog are properly formatted. +- add a test if all entries in the catalog are properly formatted. (Adding dashes in a catalog entry currently results in the catalog entry being silently skipped. journalctl --update-catalog must warn about this, and we should also have a unit test to check that all our message are OK.) -* build short web pages out of each catalog entry, build them along with man +- build short web pages out of each catalog entry, build them along with man pages, and include hyperlinks to them in the journal output -* homed: +- homed: - when user tries to log into record signed by unrecognized key, automatically add key to our chain after polkit auth - rollback when resize fails mid-operation - GNOME's side for forget key on suspend (requires rework so that lock screen runs outside of uid) @@ -2549,11 +2551,11 @@ Features: work for ECDSA keys since their signatures contain a random component, but will work for RSA and Ed25519 keys. -* add a new switch --auto-definitions=yes/no or so to systemd-repart. If +- add a new switch --auto-definitions=yes/no or so to systemd-repart. If specified, synthesize a definition automatically if we can: enlarge last partition on disk, but only if it is marked for growing and not read-only. -* systemd-repart: +- systemd-repart: - implement Integrity=data/meta and Integrity=inline for non-LUKS case. Currently, only Integrity=inline combined with Encrypt= is implemented and uses libcryptsetup features. Add support for plain dm-integrity setups when @@ -2628,7 +2630,7 @@ Features: during boot. - do not print "Successfully resized …" when no change was done. -* document: +- document: - document that deps in [Unit] sections ignore Alias= fields in [Install] units of other units, unless those units are disabled - document that service reload may be implemented as service reexec @@ -2641,7 +2643,7 @@ Features: - man: maybe sort directives in man pages, and take sections from --help and apply them to man too - document root=gpt-auto properly -* systemctl: +- systemctl: - add systemctl switch to dump transaction without executing it - Add a verbose mode to "systemctl start" and friends that explains what is being done or not done - print nice message from systemctl --failed if there are no entries shown, and hook that into ExecStartPre of rescue.service/emergency.service @@ -2650,33 +2652,33 @@ Features: - systemctl: "Journal has been rotated since unit was started." message is misleading - if some operation fails, show log output? -* introduce an option (or replacement) for "systemctl show" that outputs all +- introduce an option (or replacement) for "systemctl show" that outputs all properties as JSON, similar to busctl's new JSON output. In contrast to that it should skip the variant type string though. -* Add a "systemctl list-units --by-slice" mode or so, which rearranges the +- Add a "systemctl list-units --by-slice" mode or so, which rearranges the output of "systemctl list-units" slightly by showing the tree structure of the slices, and the units attached to them. -* add "systemctl wait" or so, which does what "systemd-run --wait" does, but +- add "systemctl wait" or so, which does what "systemd-run --wait" does, but for all units. It should be both a way to pin units into memory as well as a wait to retrieve their exit data. -* show whether a service has out-of-date configuration in "systemctl status" by +- show whether a service has out-of-date configuration in "systemctl status" by using mtime data of ConfigurationDirectory=. -* "systemctl preset-all" should probably order the unit files it +- "systemctl preset-all" should probably order the unit files it operates on lexicographically before starting to work, in order to ensure deterministic behaviour if two unit files conflict (like DMs do, for example) -* Add a new verb "systemctl top" +- Add a new verb "systemctl top" -* unit install: +- unit install: - "systemctl mask" should find all names by which a unit is accessible (i.e. by scanning for symlinks to it) and link them all to /dev/null -* nspawn: +- nspawn: - emulate /dev/kmsg using CUSE and turn off the syslog syscall with seccomp. That should provide us with a useful log buffer that systemd can log to during early boot, and disconnect container logs @@ -2728,7 +2730,7 @@ Features: - map foreign UID range through 1:1 - d-nspawn should get the same SSH key support that vmspawn now has. -* machined: +- machined: - add an API so that libvirt-lxc can inform us about network interfaces being removed or added to an existing machine - "machinectl migrate" or similar to copy a container from or to a @@ -2745,7 +2747,7 @@ Features: - optionally track nspawn unix-export/ runtime for each machined, and then update systemd-ssh-proxy so that it can connect to that. -* udev: +- udev: - move to LGPL - kill scsi_id - add trigger --subsystem-match=usb/usb_device device @@ -2753,14 +2755,14 @@ Features: - re-enable ProtectClock= once only cgroupsv2 is supported. See f562abe2963bad241d34e0b308e48cf114672c84. -* coredump: +- coredump: - save coredump in Windows/Mozilla minidump format - when truncating coredumps, also log the full size that the process had, and make a metadata field so we can report truncated coredumps - add examples for other distros in PACKAGE_METADATA_FOR_EXECUTABLE_FILES -* support crash reporting operation modes (https://live.gnome.org/GnomeOS/Design/Whiteboards/ProblemReporting) +- support crash reporting operation modes (https://live.gnome.org/GnomeOS/Design/Whiteboards/ProblemReporting) -* tmpfiles: +- tmpfiles: - allow time-based cleanup in r and R too - instead of ignoring unknown fields, reject them. - creating new directories/subvolumes/fifos/device nodes @@ -2776,16 +2778,16 @@ Features: target dir. then use that to move sysexts/confexts and stuff from initrd tmpfs to /run/, so that host can pick things up. -* udev-link-config: +- udev-link-config: - Make sure ID_PATH is always exported and complete for network devices where possible, so we can safely rely on Path= matching -* sd-rtnl: +- sd-rtnl: - add support for more attribute types - inbuilt piping support (essentially degenerate async)? see loopback-setup.c and other places -* networkd: +- networkd: - add more keys to [Route] and [Address] sections - add support for more DHCPv4 options (and, longer term, other kinds of dynamic config) - add reduced [Link] support to .network files @@ -2805,14 +2807,14 @@ Features: support Name=foo*|bar*|baz ? - whenever uplink info changes, make DHCP server send out FORCERENEW -* in networkd, when matching device types, fix up DEVTYPE rubbish the kernel passes to us +- in networkd, when matching device types, fix up DEVTYPE rubbish the kernel passes to us -* Figure out how to do unittests of networkd's state serialization +- Figure out how to do unittests of networkd's state serialization -* dhcp: +- dhcp: - figure out how much we can increase Maximum Message Size -* dhcp6: +- dhcp6: - add functions to set previously stored IPv6 addresses on startup and get them at shutdown; store them in client->ia_na - write more test cases @@ -2826,9 +2828,9 @@ Features: this behavior - RouteTable= ? -* shared/wall: Once more programs are taught to prefer sd-login over utmp, +- shared/wall: Once more programs are taught to prefer sd-login over utmp, switch the default wall implementation to wall_logind (https://github.com/systemd/systemd/pull/29051#issuecomment-1704917074) -* Hook up systemd-journal-upload with RESTART_RESET=1 logic (should probably +- Hook up systemd-journal-upload with RESTART_RESET=1 logic (should probably be conditioned on the num of successfully uploaded entries?) From 4779aafa8bfd5e336d4b7be3fea56d33a9bfab95 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 27 Mar 2026 13:46:59 +0100 Subject: [PATCH 0607/1296] TODO: add symlink to TODO.md Signed-off-by: Christian Brauner --- TODO | 1 + 1 file changed, 1 insertion(+) create mode 120000 TODO diff --git a/TODO b/TODO new file mode 120000 index 0000000000000..cde952be38688 --- /dev/null +++ b/TODO @@ -0,0 +1 @@ +TODO.md \ No newline at end of file From ad8fdfd60221fa1a0687bf0b870912a8f82eddbd Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 27 Mar 2026 13:51:53 +0100 Subject: [PATCH 0608/1296] TODO: add frontmatter and improve formatting Add YAML frontmatter matching the style of other docs in the tree. Bold grouped section topic names to make them visually distinct from standalone items. Escape angle-bracket placeholders so GitHub does not swallow them as HTML. Signed-off-by: Christian Brauner --- TODO.md | 4281 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 2143 insertions(+), 2138 deletions(-) diff --git a/TODO.md b/TODO.md index f833a3f9982c7..a293c792f89ba 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,10 @@ +--- +title: TODO +category: Contributing +layout: default +SPDX-License-Identifier: LGPL-2.1-or-later +--- + # TODO ## Bugfixes @@ -14,7 +21,7 @@ - Fedora: add an rpmlint check that verifies that all unit files in the RPM are listed in %systemd_post macros. -- dbus: +- **dbus:** - natively watch for dbus-*.service symlinks (PENDING) - teach dbus to activate all services it finds in /etc/systemd/services/org-*.service @@ -24,18 +31,17 @@ - fedora: update policy to declare access mode and ownership of unit files to root:root 0644, and add an rpmlint check for it -- missing shell completions: - -- zsh shell completions: - - - should complete options, but currently does not - - systemctl add-wants,add-requires - - systemctl reboot --boot-loader-entry= +- **missing shell completions:** + - **zsh:** + - ` -` should complete options, but currently does not + - systemctl add-wants,add-requires + - systemctl reboot --boot-loader-entry= - systemctl status should know about 'systemd-analyze calendar ... --iterations=' - If timer has just OnInactiveSec=..., it should fire after a specified time after being started. -- write blog stories about: +- **write blog stories about:** - hwdb: what belongs into it, lsusb - enabling dbus services - how to make changes to sysctl and sysfs attributes @@ -100,7 +106,7 @@ - Remove any support for booting without /usr pre-mounted in the initrd entirely. Update INITRD_INTERFACE.md accordingly. -- remove cgroups v1 support EOY 2023. As per +- remove cgroups v1 support (overdue since EOY 2023). As per https://lists.freedesktop.org/archives/systemd-devel/2022-July/048120.html and then rework cgroupsv2 support around fds, i.e. keep one fd per active unit around, and always operate on that, instead of cgroup fs paths. @@ -112,8 +118,8 @@ That requires distros to enable CONFIG_ACPI_FPDT, and have kernels v5.12 for x86 and v6.2 for arm. -- In v260: remove support for deprecated FactoryReset EFI variable in - systemd-repart, replaced by FactoryResetRequest. +- Remove support for deprecated FactoryReset EFI variable in + systemd-repart, replaced by FactoryResetRequest (was planned for v260). - Consider removing root=gpt-auto, and push people to use root=dissect instead. @@ -122,890 +128,923 @@ ## Features -- crypttab/gpt-auto-generator: allow explicit control over which unlock mechs - to permit, and maybe have a global headless kernel cmdline option +- a small tool that can do basic btrfs raid policy mgmt. i.e. gets started as + part of the initial transaction for some btrfs raid fs, waits for some time, + then puts message on screen (plymouth, console) that some devices apparently + are not showing up, then counts down, eventually set a flag somewhere, and + retriggers the fs is was invoked for, which causes the udev rules to rerun + that assemble the btrfs raid, but this time force degraded assembly. -- start making use of the new --graceful switch to util-linux' umount command +- a way for container managers to turn off getty starting via $container_headless= or so... -- sysusers: allow specifying a path to an inode *and* a literal UID in the UID - column, so that if the inode exists it is used, and if not the literal UID is - used. Use this for services such as the imds one, which run under their own - UID in the initrd, and whose data should survive to the host, properly owned. +- add "conditions" for bls type 1 and type 2 profiles that allow suppressing + them under various conditions: 1. if tpm2 is available or not available; + 2. if sb is on or off; 3. if we are netbooted or not; … -- add service file setting to force the fwmark (a la SO_MARK) to some value, so - that we can allowlist certain services for imds this way. +- add "homectl export" and "homectl import" that gets you an "atomic" snapshot + of your homedir, i.e. either a tarball or a snapshot of the underlying disk + (use FREEZE/THAW to make it consistent, btrfs snapshots) -- lock down swtpm a bit to make it harder to extract keys from it as it is - running. i.e. make ptracing + termination hard from the outside. also run - swtpm as unpriv user (not trivial, probably requires patch swtpm, as it needs - to allocate vtpm device), to lock it down from the inside. +- Add "purpose" flag to partition flags in discoverable partition spec that + indicate if partition is intended for sysext, for portable service, for + booting and so on. Then, when dissecting DDI allow specifying a purpose to + use as additional search condition. Use case: images that combined a sysext + partition with a portable service partition in one. -- once swtpm's sd_notify() support has landed in the distributions, remove the - invocation in tpm2-swtpm.c and let swtpm handle it. +- add "systemctl wait" or so, which does what "systemd-run --wait" does, but + for all units. It should be both a way to pin units into memory as well as a + wait to retrieve their exit data. -- make systemd work nicely without /bin/sh, logins and associated shell tools around - - make sure debug shell service (sushell) has a nice failure mode, prints a message and reboots - - varlink interface for "systemctl start" and friends - - https://github.com/util-linux/util-linux/issues/4117 +- add "systemd-analyze debug" + AttachDebugger= in unit files: The former + specifies a command to execute; the latter specifies that an already running + "systemd-analyze debug" instance shall be contacted and execution paused + until it gives an OK. That way, tools like gdb or strace can be safely be + invoked on processes forked off PID 1. -- imds: maybe do smarter api version handling +- add "systemd-sysext identify" verb, that you can point on any file in /usr/ + and that determines from which overlayfs layer it originates, which image, and with + what it was signed. -- drop NV_ORDERLY flag from the product uuid nvpcr. Effect of the flag is that - it pushes the thing into TPM RAM, but a TPM usually has very little of that, - less than NVRAM. hence setting the flag amplifies space issues. Unsetting the - flag increases wear issues on the NVRAM, however, but this should be limited - for the product uuid nvpcr, since its only changed once per boot. this needs - to be configurable by nvpcr however, as other nvpcrs are different, - i.e. verity one receives many writes during system uptime quite - possibly. (also, NV_ORDERLY makes stuff faster, and dropping it costs - possibly up to 100ms supposedly) +- add --vacuum-xyz options to coredumpctl, matching those journalctl already has. -- instead of going directly for DefineSpace when initializing nvpcrs, check if - they exist first. apparently DefineSpace is broken on some tpms, and also - creates log spam if the nvindex already exists. +- Add a "systemctl list-units --by-slice" mode or so, which rearranges the + output of "systemctl list-units" slightly by showing the tree structure of + the slices, and the units attached to them. -- on first login of a user, measure its identity to some nvpcr +- Add a concept of ListenStream=anonymous to socket units: listen on a socket + that is deleted in the fs. Use case would be with ConnectSocket= above. -- sd-lldp: pick up 802.3 maximum frame size/mtu, to be able to detect jumbo - frame capable networks +- add a ConnectSocket= setting to service unit files, that may reference a + socket unit, and which will connect to the socket defined therein, and pass + the resulting fd to the service program via socket activation proto. -- networkd: maintain a file in /run/ that can be symlinked into /run/issue.d/ - that always shows the current primary IP address +- add a dbus call to generate target from current state -- oci: add support for blake hashes for layers +- add a dependency on standard-conf.xml and other included files to man pages -- oci: add support for "importctl import-oci" which implements the "OCI layout" - spec (i.e. acquiring via local fs access), as opposed to the current - "importctl pull-oci" which focusses on the "OCI image spec", i.e. downloads - from the web (i.e. acquiring via URLs). +- add a job mode that will fail if a transaction would mean stopping + running units. Use this in timedated to manage the NTP service + state. + https://lists.freedesktop.org/archives/systemd-devel/2015-April/030229.html -- oci: support "data" in any OCI descriptor, not just manifest config. +- add a kernel cmdline switch (and cred?) for marking a system to be + "headless", in which case we never open /dev/console for reading, only for + writing. This would then mean: systemd-firstboot would process creds but not + ask interactively, getty would not be started and so on. -- report: - - plug "facts" into systemd-report too, i.e. stuff that is more static, such as hostnames, ssh keys and so on. - - pass filtering hints to services, so that they can also be applied server-side, not just client side - - metrics from pid1: suppress metrics form units that are inactive and have nothing to report - - add "hint-suppress-zero" flag (which suppresses all metrics which are zero) - - add "hint-object" parameter (which only queries info about certain object) - - make systemd-report a varlink service +- add a Load= setting which takes literal data in text or base64 format, and + puts it into a memfd, and passes that. This enables some fun stuff, such as + embedding bash scripts in unit files, by combining Load= with + ExecStart=/bin/bash /proc/self/fd/3 -- implement a varlink registry service, similar to the one of the reference - implementation, backed by /run/varlink/registry/. Then, also implement - connect-via-registry-resolution in sd-varlink and varlinkctl. Care needs to - be taken to do the resolution asynchronousy. Also, note that the Varlink - reference implementation uses a different address syntax, which needs to be - taken into account. +- add a mechanism we can drop capabilities from pid1 *before* transitioning + from initrd to host. i.e. before we transition into the slightly lower trust + domain that is the host systems we might want to get rid of some caps. + Example: CAP_SYS_BPF in the signed bpf loading logic above. (We already have + CapabilityBoundingSet= in system.conf, but that is enforced when pid 1 + initializes, rather then when it transitions to the next.) -- have a signal that reloads every unit that supports reloading +- add a new "debug" job mode, that is propagated to unit_start() and for + services results in two things: we raise SIGSTOP right before invoking + execve() and turn off watchdog support. Then, use that to implement + "systemd-gdb" for attaching to the start-up of any system service in its + natural habitat. -- systemd: add storage API via varlink, where everyone can drop a socket in a - dir, similar, do the same thing for networking +- add a new flag to chase() that stops chasing once the first missing + component is found and then allows the caller to create the rest. -- do a console daemon that takes stdio fds for services and allows to reconnect - to them later +- add a new PE binary section ".mokkeys" or so which sd-stub will insert into + Mok keyring, by overriding/extending whatever shim sets in the EFI + var. Benefit: we can extend the kernel module keyring at ukify time, + i.e. without recompiling the kernel, taking an upstream OS' kernel and adding + a local key to it. -- report: have something that requests cloud workload identity bearer tokens - and includes it in the report +- add a new specifier to unit files that figures out the DDI the unit file is + from, tracing through overlayfs, DM, loopback block device. -- add new tool that can be used in debug mode runs in very early boot, - generates a random password, passes it as credential to sysusers for the root - user, then displays it on screen. people can use this to remotely log in. +- add a new switch --auto-definitions=yes/no or so to systemd-repart. If + specified, synthesize a definition automatically if we can: enlarge last + partition on disk, but only if it is marked for growing and not read-only. -- Maybe introduce an InodeRef structure inspired by PidRef, which references a - specific inode, and combines: a path, an O_PATH fd, and possibly a FID into - one. Why? We often pass around path and fd separately in chaseat() and similar - calls. Because passing around both separately is cumbersome we sometimes only - one pass one, once the other and sometimes both. It would make the code a lot - simpler if we could path both around at the same time in a simple way, via an - InodeRef which *both* pins the inode via an fd, *and* gives us a friendly - name for it. +- add a new syscall group "@esoteric" for more esoteric stuff such as bpf() and + usefaultd() and make systemd-analyze check for it. -- portable services: attach not only unit files to host, but also simple - binaries to a tmpfs path in $PATH. +- Add a new verb "systemctl top" -- systemd-sysext: add "exec" command or so that is a bit like "refresh" but - runs it in a new namespace and then just executes the selected binary within - it. Could be useful to run one-off binaries inside a sysext as a CLI tool. +- add a pam module that on password changes updates any LUKS slot where the password matches -- homed/pam_systemd: allow authentication by ssh-agent, so that run0/polkit can - be allowed if caller comes with the right ssh-agent keys. +- add a percentage syntax for TimeoutStopSec=, e.g. TimeoutStopSec=150%, and + then use that for the setting used in user@.service. It should be understood + relative to the configured default value. -- pull-oci: progress notification +- add a plugin for factory reset logic that erases certain parts of the ESP, + but leaves others in place. -- networkd/machined: implement reverse name lookups in the resolved hook +- add a proper concept of a "developer" mode, i.e. where cryptographic + protections of the root OS are weakened after interactive confirmation, to + allow hackers to allow their own stuff. idea: allow entering developer mode + only via explicit choice in boot menu: i.e. add explicit boot menu item for + it. When developer mode is entered, generate a key pair in the TPM2, and add + the public part of it automatically to keychain of valid code signature keys + on subsequent boots. Then provide a tool to sign code with the key in the + TPM2. Ensure that boot menu item is the only way to enter developer mode, by + binding it to locality/PCRs so that keys cannot be generated otherwise. -- networkd's resolved hook: optionally map all lease IP addresses handed out to - the same hostname which is configured on the .network file. Optionally, even - derive this single name from the network interface name (i.e. probably - altname or so). This way, when spawning a VM the host could pick the hostname - for it and the client gets no say. +- add a system-wide seccomp filter list for syscalls, kill "acct()" "@obsolete" + and a few other legacy syscalls that way. -- measure all log-in attempts into a new nvpcr +- add a test if all entries in the catalog are properly formatted. + (Adding dashes in a catalog entry currently results in the catalog entry + being silently skipped. journalctl --update-catalog must warn about this, + and we should also have a unit test to check that all our message are OK.) -- maybe rework systemd-modules-load to be a generator that just instantiates - modprobe@.service a bunch of times +- add a utility that can be used with the kernel's + CONFIG_STATIC_USERMODEHELPER_PATH and then handles them within pid1 so that + security, resource management and cgroup settings can be enforced properly + for all umh processes. -- Split vconsole-setup in two, of which the second is started via udev (instead - of the "restart" job it currently fires). That way, boot becomes purely - positive again, and we can nicely order the two against each other. +- add a way to lock down cgroup migration: a boolean, which when set for a unit + makes sure the processes in it can never migrate out of it -- Add ELF section to make systemd main binary recognizable cleanly, the same - way as we make sd-boot recognizable via PE section. +- add ability to path_is_valid() to classify paths that refer to a dir from + those which may refer to anything, and use that in various places to filter + early. i.e. stuff ending in "/", "/." and "/.." definitely refers to a + directory, and paths ending that way can be refused early in many contexts. -- Add knob to cryptsetup, to trigger automatic reboot on failure to unlock - disk. Enable this by default for rootfs, also in gpt-auto-generator +- Add ACL-based access management to .socket units. i.e. add AllowPeerUser= + + AllowPeerGroup= that installs additional user/group ACL entries on AF_UNIX + sockets. -- Add RebootUptimeMinSec= knob to PID 1, that makes systemd-shutdown sleep - until the specified uptime has passed, to lengthen tight boot loops. +- Add AddUser= setting to unit files, similar to DynamicUser=1 which however + creates a static, persistent user rather than a dynamic, transient user. We + can leverage code from sysusers.d for this. -- replace bootctl's PE version check to actually use APIs from pe-binary.[ch] - to find binary version. +- add an explicit parser for LimitRTPRIO= that verifies + the specified range and generates sane error messages for incorrect + specifications. -- replace symlink_label(), mknodat_label(), btrfs_subvol_make_label(), - mkdir_label() and related calls by flags-based calls that use - label_ops_pre()/label_ops_post(). +- Add and pickup tpm2 metadata for creds structure. -- maybe reconsider whether virtualization consoles (hvc1) are considered local - or remote. i.e. are they more like an ssh login, or more like a /dev/tty1 - login? Lennart used to believe the former, but maybe the latter is more - appropriate? This has effect on polkit interactivity, since it would mean - questions via hvc0 would suddenly use the local polkit property. But this - also raises the question whether such sessions shall be considered active or - not +- add another PE section ".fname" or so that encodes the intended filename for + PE file, and validate that when loading add-ons and similar before using + it. This is particularly relevant when we load multiple add-ons and want to + sort them to apply them in a define order. The order should not be under + control of the attacker. -- automatically reset specific EFI vars on factory reset (make this generic - enough so that infra can be used to erase shim's mok vars?) +- add bus API for creating unit files in /etc, reusing the code for transient units -- similar: add a plugin for factory reset logic that erases certain parts of - the ESP, but leaves others in place. +- add bus api to query unit file's X fields. -- flush_fd() should probably try to be smart and stop reading once we know that - all further queued data was enqueued after flush_fd() was originally - called. For that, try SIOCINQ if fd refers to stream socket, and look at - timestamps for datagram sockets. +- add bus API to remove unit files from /etc -- Similar flush_accept() should look at sockdiag queued sockets count and exit - once we flushed out the specified number of connections. +- add bus API to retrieve current unit file contents (i.e. implement "systemctl cat" on the bus only) -- maybe introduce a new per-unit drop-in directory .confext.d/ that may contain - symlinks to confext images to enable for the unit. +- Add ConditionDirectoryNotEmpty= handle non-absoute paths as a search path or add + ConditionConfigSearchPathNotEmpty= or different syntax? See the discussion starting at + https://github.com/systemd/systemd/pull/15109#issuecomment-607740136. -- a small tool that can do basic btrfs raid policy mgmt. i.e. gets started as - part of the initial transaction for some btrfs raid fs, waits for some time, - then puts message on screen (plymouth, console) that some devices apparently - are not showing up, then counts down, eventually set a flag somewhere, and - retriggers the fs is was invoked for, which causes the udev rules to rerun - that assemble the btrfs raid, but this time force degraded assembly. +- add CopyFile= or so as unit file setting that may be used to copy files or + directory trees from the host to the services RootImage= and RootDirectory= + environment. Which we can use for /etc/machine-id and in particular + /etc/resolv.conf. Should be smart and do something useful on read-only + images, for example fall back to read-only bind mounting the file instead. -- introduce /etc/boottab or so which lists block devices that bootctl + - kernel-install shall update the ESPs on (and register in EFI BootXYZ - variables), in addition to whatever is currently the booted /usr/. - systemd-sysupdate should also take it into consideration and update the - /usr/ images on all listed devices. - -- replace all uses of fopen_temporary() by fopen_tmpfile_linkable() + - flink_tmpfile() and then get rid of fopen_temporary(). Benefit: use O_TMPFILE - pervasively, and avoid rename() wherever we can. - -- loginctl: show argv[] of "leader" process in tabular list-sessions output +- Add ELF section to make systemd main binary recognizable cleanly, the same + way as we make sd-boot recognizable via PE section. -- loginctl: show "service identifier" in tabular list-sessions output, to make - run0 sessions easily visible. +- Add ExecMonitor= setting. May be used multiple times. Forks off a process in + the service cgroup, which is supposed to monitor the service, and when it + exits the service is considered failed by its monitor. -- run0: maybe enable utmp for run0 sessions, so that they are easily visible. +- add field to bls type 1 and type 2 profiles that ensures an item is never + considered for automatic selection -- maybe beef up sd-event: optionally, allow sd-event to query the timestamp of - next pending datagram inside a SOCK_DGRAM IO fd, and order event source - dispatching by that. Enable this on the native + syslog sockets in journald, - so that we add correct ordering between the two. Use MSG_PEEK + SCM_TIMESTAMP - for this. +- add generator that pulls in systemd-network from containers when + CAP_NET_ADMIN is set, more than the loopback device is defined, even + when it is otherwise off -- bsod: add target "bsod.target" or so, which invokes systemd-bsod.target and - waits and then reboots. Then use OnFailure=bsod.target from various jobs that - should result in system reboots, such as TPM tamper detection cases. +- add growvol and makevol options for /etc/crypttab, similar to + x-systemd.growfs and x-systemd-makefs. -- honour validatefs xattrs in dissect-image.c too +- Add knob to cryptsetup, to trigger automatic reboot on failure to unlock + disk. Enable this by default for rootfs, also in gpt-auto-generator -- pcrextend: maybe add option to disable measurements entirely via kernel cmdline +- add linker script that implicitly adds symbol for build ID and new coredump + json package metadata, and use that when logging -- tpm2-setup: reboot if we detect SRK changed +- add new gpt type for btrfs volumes -- validatefs: validate more things: check if image id + os id of initrd match - target mount, so that we refuse early any attempts to boot into different - images with the wrong kernels. check min/max kernel version too. all encoded - via xattrs in the target fs. +- add new tool that can be used in debug mode runs in very early boot, + generates a random password, passes it as credential to sysusers for the root + user, then displays it on screen. people can use this to remotely log in. -- pcrextend: when we fail to measure, reboot the system (at least optionally). - important because certain measurements are supposed to "destroy" tpm object - access. +- add option to sockets to avoid activation. Instead just drop packets/connections, see http://cyberelk.net/tim/2012/02/15/portreserve-systemd-solution/ -- pcrextend: after measuring get an immediate quote from the TPM, and validate - it. if it doesn't check out, i.e. the measurement we made doesn't appear in - the PCR then also reboot. +- add PR_SET_DUMPABLE service setting -- complete varlink introspection comments: - - io.systemd.Hostname - - io.systemd.ManagedOOM - - io.systemd.Network - - io.systemd.PCRLock - - io.systemd.Resolve.Monitor - - io.systemd.Resolve - - io.systemd.oom - - io.systemd.sysext +- add proper .osrel matching for PE addons. i.e. refuse applying an addon + intended for a different OS. Take inspiration from how confext/sysext are + matched against OS. -- maybe define a /etc/machine-info field for the ANSI color to associate with a - hostname. Then use it for the shell prompt to highlight the hostname. If no - color is explicitly set, hash a color automatically from the hostname as a - fallback, in a reasonable way. Take inspiration from the ANSI_COLOR= field - that already exists in /etc/os-release, i.e. use the same field name and - syntax. When hashing the color, use the hsv_to_rgb() helper we already have, - fixate S and V to something reasonable and constant, and derive the H from - the hostname. Ultimate goal with this: give people a visual hint about the - system they are on if the have many to deal with, by giving each a color - identity. This code should be placed in hostnamed, so that clients can query - the color via varlink or dbus. +- add proper dbus APIs for the various sd_notify() commands, such as MAINPID=1 + and so on, which would mean we could report errors and such. -- unify how blockdev_get_root() and sysupdate find the default root block device +- Add RebootUptimeMinSec= knob to PID 1, that makes systemd-shutdown sleep + until the specified uptime has passed, to lengthen tight boot loops. -- Maybe rename pkcs7 and public verbs of systemd-keyutil to be more verb like. +- add service file setting to force the fwmark (a la SO_MARK) to some value, so + that we can allowlist certain services for imds this way. -- maybe extend the capsule concept to the per-user instance too: invokes a - systemd --user instance with a subdir of $HOME as $HOME, and a subdir of - $XDG_RUNTIME_DIR as $XDG_RUNTIME_DIR. +- Add service unit setting ConnectStream= which takes IP addresses and connects to them. -- add "homectl export" and "homectl import" that gets you an "atomic" snapshot - of your homedir, i.e. either a tarball or a snapshot of the underlying disk - (use FREEZE/THAW to make it consistent, btrfs snapshots) +- add some optional flag to ReadWritePaths= and friends, that has the effect + that we create the dir in question when the service is started. Example: -- maybe introduce a new partition that we can store debug logs and similar at - the very last moment of shutdown. idea would be to store reference to block - device (major + minor + partition id + diskseq?) in /run somewhere, than use - that from systemd-shutdown, just write a raw JSON blob into the partition. - Include timestamp, boot id and such, plus kmsg. on next boot immediately - import into journal. maybe use timestamp for making clock more monotonic. - also use this to detect unclean shutdowns, boot into special target if - detected + ReadWritePaths=:/var/lib/foobar -- fix homed/homectl confusion around terminology, i.e. "home directory" - vs. "home" vs. "home area". Stick to one term for the concept, and it - probably shouldn't contain "area". +- add some service that makes an atomic snapshot of PCR state and event log up + to that point available, possibly even with quote by the TPM. -- add field to bls type 1 and type 2 profiles that ensures an item is never - considered for automatic selection +- add some special mode to LogsDirectory=/StateDirectory=… that allows + declaring these directories without necessarily pulling in deps for them, or + creating them when starting up. That way, we could declare that + systemd-journald writes to /var/log/journal, which could be useful when we + doing disk usage calculations and so on. -- add "conditions" for bls type 1 and type 2 profiles that allow suppressing - them under various conditions: 1. if tpm2 is available or not available; - 2. if sb is on or off; 3. if we are netbooted or not; … +- add support for "portablectl attach http://foobar.com/waaa.raw (i.e. importd integration) -- repart: introduce concept of "ghost" partitions, that we setup in almost all - ways like other partitions, but do not actually register in the actual gpt - table, but only tell the kernel about via BLKPG ioctl. These partitions are - disk backed (hence can be large), but not persistent (as they are invisible - on next boot). Could be used by live media and similar, to boot up as usual - but automatically start at zero on each boot. There should also be a way to - make ghost partitions properly persistent on request. +- add support for activating nvme-oF devices at boot automatically via kernel + cmdline, and maybe even support a syntax such as + root=nvme:\:\:\:\:\ to boot directly from + nvme-oF -- repart: introduce MigrateFileSystem= or so which is a bit like - CopyFiles=/CopyBlocks= but operates via btrfs device logic: adds target as - new device then removes source from btrfs. Usecase: a live medium which uses - "ghost" partitions as suggested above, which can become persistent on request - on another device. +- add support for asymmetric LUKS2 TPM based encryption. i.e. allow preparing + an encrypted image on some host given a public key belonging to a specific + other host, so that only hosts possessing the private key in the TPM2 chip + can decrypt the volume key and activate the volume. Use case: systemd-confext + for a central orchestrator to generate confext images securely that can only + be activated on one specific host (which can be used for installing a bunch + of creds in /etc/credstore/ for example). Extending on this: allow binding + LUKS2 TPM based encryption also to the TPM2 internal clock. Net result: + prepare a confext image that can only be activated on a specific host that + runs a specific software in a specific time window. confext would be + automatically invalidated outside of it. -- make nspawn containers, portable services and vmspawn VMs optionally survive - soft reboot wholesale. +- Add support for extra verity configuration options to systemd-repart (FEC, + hash type, etc) -- Turn systemd-networkd-wait-online into a small varlink service that people - can talk to and specify exactly what to wait for via a method call, and get a - response back once that level of "online" is reached. +- Add SUPPORT_END_URL= field to os-release with more *actionable* information + what to do if support ended -- introduce a small "systemd-installer" tool or so, that glues - systemd-repart-as-installer and bootctl-install into one. Would just - interactively ask user for target disk (with completion and so on), and then do - two varlink calls to the the two tools with the right parameters. To support - "offline" operation, optionally invoke the two tools directly as child - processes with varlink communication over socketpair(). This all should be - useful as blueprint for graphical installers which should do the same. +- Add systemd-analyze security checks for RestrictFileSystems= and + RestrictNetworkInterfaces= -- Make run0 forward various signals to the forked process so that sending - signals to a child process works roughly the same regardless of whether the - child process is spawned via run0 or not. +- Add systemd-mount@.service which is instantiated for a block device and + invokes systemd-mount and exits. This is then useful to use in + ENV{SYSTEMD_WANTS} in udev rules, and a bit prettier than using RUN+= -- write a document explaining how to write correct udev rules. Mention things - such as: - 1. do not do lists of vid/pid matches, use hwdb for that - 2. add|change action matches are typically wrong, should be != remove - 3. use GOTO, make rules short - 4. people shouldn't try to make rules file non-world-readable +- Add systemd-sysupdate-initrd.service or so that runs systemd-sysupdate in the + initrd to bootstrap the initrd to populate the initial partitions. Some things + to figure out: + - Should it run on firstboot or on every boot? + - If run on every boot, should it use the sysupdate config from the host on + subsequent boots? -- make killing more debuggable: when we kill a service do so setting the - .si_code field with a little bit of info. Specifically, we can set a - recognizable value to first of all indicate that it's systemd that did the - killing. Secondly, we can give a reason for the killing, i.e. OOM or so, and - also the phase we are in, and which process we think we are killing (i.e. - main vs control process, useful in case of sd_notify() MAINPID= debugging). - Net result: people who try to debug why their process gets killed should have - some minimal, nice metadata directly on the signal event. +- add systemd.abort_on_kill or some other such flag to send SIGABRT instead of SIGKILL + (throughout the codebase, not only PID1) -- sd-boot/sd-stub: install a uefi "handle" to a sidecar dir of bls type #1 - entries with an "uki" or "uki-url" stanza, and make sd-stub look for - that. That way we can parameterize type #1 entries nicely. +- Add UKI profile conditioning so that profiles are only available if secure + boot is turned off, or only on. similar, add conditions on TPM availability, + network boot, and other conditions. -- add a system-wide seccomp filter list for syscalls, kill "acct()" "@obsolete" - and a few other legacy syscalls that way. +- Allocate UIDs/GIDs automatically in userdbctl load-credentials if none are + included in the user/group record credentials -- maybe introduce "@icky" as a seccomp filter group, which contains acct() and - certain other syscalls that aren't quite obsolete, but certainly icky. +- allow dynamic modifications of ConcurrencyHardMax= and ConcurrencySoftMax= + via DBus (and with that also by daemon-reload) -- revisit how we pass fs images and initrd to the kernel. take uefi http boot - ramdisks as inspiration: for any confext/sysext/initrd erofs/DDI image simply - generate a fake pmem region in the UEFI memory tables, that Linux then turns - into /dev/pmemX. Then turn of cpio-based initrd logic in linux kernel, - instead let kernel boot directly into /dev/pmem0. In order to allow our usual - cpio-based parameterization, teach PID 1 to just uncompress cpio ourselves - early on, from another pmem device. (Related to this, maybe introduce a new - PE section .ramdisk that just synthesizes pmem devices from arbitrary - blobs. Could be particularly useful in add-ons) +- also include packaging metadata (á la + https://systemd.io/PACKAGE_METADATA_FOR_EXECUTABLE_FILES/) in our UEFI PE + binaries, using the same JSON format. - also parse out primary GPT disk label uuid from gpt partition device path at boot and pass it as efi var to OS. -- storagetm: maybe also serve the specified disk via HTTP? we have glue for - microhttpd anyway already. Idea would also be serve currently booted UKI as - separate HTTP resource, so that EFI http boot on another system could - directly boot from our system, with full access to the hdd. +- as soon as we have sender timestamps, revisit coalescing multiple parallel daemon reloads: + https://lists.freedesktop.org/archives/systemd-devel/2014-December/025862.html -- support specifying download hash sum in systemd-import-generator expression - to pin image/tarball. +- augment CODE_FILE=, CODE_LINE= with something like CODE_BASE= or so which + contains some identifier for the project, which allows us to include + clickable links to source files generating these log messages. The identifier + could be some abbreviated URL prefix or so (taking inspiration from Go + imports). For example, for systemd we could use + CODE_BASE=github.com/systemd/systemd/blob/98b0b1123cc or so which is + sufficient to build a link by prefixing "http://" and suffixing the + CODE_FILE. -- support boot into nvme-over-tcp: add generator that allows specifying nvme - devices on kernel cmdline + credentials. Also maybe add interactive mode - (where the user is prompted for nvme info), in order to boot from other - system's HDD. +- Augment MESSAGE_ID with MESSAGE_BASE, in a similar fashion so that we can + make clickable links from log messages carrying a MESSAGE_ID, that lead to + some explanatory text online. -- ptyfwd: use osc context information in vmspawn/nspawn/… to optionally only - listen to ^]]] key when no further vmspawn/nspawn context is allocated +- automatic boot assessment: add one more default success check that just waits + for a bit after boot, and blesses the boot if the system stayed up that long. -- ptyfwd: usec osc context information to propagate status messages from - vmspawn/nspawn to service manager's "status" string, reporting what is - currently in the fg +- automatically ignore threaded cgroups in cg_xyz(). -- nspawn/vmspawn: define hotkey that one can hit on the primary interface to - ask for a friendly, acpi style shutdown. +- automatically mount one virtiofs during early boot phase to /run/host/, + similar to how we do that for nspawn, based on some clear tag. -- for better compat with major clouds: implement simple PTP device support in - timesyncd +- automatically propagate LUKS password credential into cryptsetup from host + (i.e. SMBIOS type #11, …), so that one can unlock LUKS via VM hypervisor + supplied password. -- for better compat with major clouds: recognize clouds via hwdb on DMI device, - and add udev properties to it that help with handling IMDS, i.e. entrypoint - URL, which fields to find ip hostname, ssh key, … - -- for better compat with major clouds: introduce imds mini client service that - sets up primary netif in a private netns (ipvlan?) to query imds without - affecting rest of the host. pick up literal credentials from there plus the - fields the hwdb reports for the other fields and turn them into credentials. - then write generator that used detected virtualization info and plugs this - service into the early boot, waiting for the DMI and network device to show - up. +- automatically reset specific EFI vars on factory reset (make this generic + enough so that infra can be used to erase shim's mok vars?) -- Add UKI profile conditioning so that profiles are only available if secure - boot is turned off, or only on. similar, add conditions on TPM availability, - network boot, and other conditions. +- be able to specify a forced restart of service A where service B depends on, in case B + needs to be auto-respawned? -- fix bug around run0 background color on ls in fresh terminal +- be more careful what we export on the bus as (usec_t) 0 and (usec_t) -1 -- Reset TPM2 DA bit on each successful boot +- beef up log.c with support for stripping ANSI sequences from strings, so that + it is OK to include them in log strings. This would be particularly useful so + that our log messages could contain clickable links for example for unit + files and suchlike we operate on. -- systemd-cryptenroll: add --firstboot or so, that will interactively ask user - whether recovery key shall be enrolled and do so +- beef up pam_systemd to take unit file settings such as cgroups properties as + parameters -- maybe introduce container-shell@.service or so, to match - container-getty.service but skips authentication, so you get a shell prompt - directly. Usecase: wsl-like stuff (they have something pretty much like - that). Question: how to pick user for this. Instance parameter? somehow from - credential (would probably require some binary that converts credential to - User= parameter? +- blog about fd store and restartable services -- systemd-firstboot: optionally install an ssh key for root for offline use. +- **bootctl:** + - recognize the case when not booted on EFI + - add tool for registering BootXXX entry that boots from some http + server of your choice (i.e. like kernel-bootcfg --add-uri=) + - add reboot-to-disk which takes a block device name, and + automatically sets things up so that system reboots into that device next. + - show whether UEFI audit mode is available + - teach it to prepare an ESP wholesale, i.e. with mkfs.vfat invocation + - teach it to copy in unified kernel images and maybe type #1 boot loader spec entries from host -- Allocate UIDs/GIDs automatically in userdbctl load-credentials if none are - included in the user/group record credentials +- BootLoaderSpec: define a way how an installer can figure out whether a BLS + compliant boot loader is installed. -- introduce new ANSI sequence for communicating log level and structured error - metadata to terminals. +- BootLoaderSpec: document @saved pseudo-entry, update mention in BLI -- in pid1: include ExecStart= cmdlines (and other Exec*= cmdlines) in polkit - request, so that policies can match against command lines. +- bootspec: permit graceful "update" from type #2 to type #1. If both a type #1 + and a type #2 entry exist under otherwise the exact same name, then use the + type #1 entry, and ignore the type #2 entry. This way, people can "upgrade" + from the UKI with all parameters baked in to a Type #1 .conf file with manual + parametrization, if needed. This matches our usual rule that admin config + should win over vendor defaults. -- allow dynamic modifications of ConcurrencyHardMax= and ConcurrencySoftMax= - via DBus (and with that also by daemon-reload) +- bpf: see if we can address opportunistic inode sharing of immutable fs images + with BPF. i.e. if bpf gives us power to hook into openat() and return a + different inode than is requested for which we however it has same contents + then we can use that to implement opportunistic inode sharing among DDIs: + make all DDIs ship xattr on all reg files with a SHA256 hash. Then, also + dictate that DDIs should come with a top-level subdir where all reg files are + linked into by their SHA256 sum. Then, whenever an inode is opened with the + xattr set, check bpf table to find dirs with hashes for other prior DDIs and + try to use inode from there. -- portabled: similar +- bpf: see if we can use BPF to solve the syslog message cgroup source problem: + one idea would be to patch source sockaddr of all AF_UNIX/SOCK_DGRAM to + implicitly contain the source cgroup id. Another idea would be to patch + sendto()/connect()/sendmsg() sockaddr on-the-fly to use a different target + sockaddr. -- maybe introduce an OSC sequence that signals when we ask for a password, so - that terminal emulators can maybe connect a password manager or so, and - highlight things specially. +- bsod: add target "bsod.target" or so, which invokes systemd-bsod.target and + waits and then reboots. Then use OnFailure=bsod.target from various jobs that + should result in system reboots, such as TPM tamper detection cases. -- start using STATX_SUBVOL in btrfs_is_subvol(). Also, make use of it - generically, so that image discovery recognizes bcachefs subvols too. +- bsod: maybe use graphical mode. Use DRM APIs directly, see + https://github.com/dvdhrm/docs/blob/master/drm-howto/modeset.c for an example + for doing that. -- foreign uid: - - add support to export-fs, import-fs - - systemd-dissect should learn mappings, too, when doing mtree and such +- build short web pages out of each catalog entry, build them along with man + pages, and include hyperlinks to them in the journal output -- system LSFMMBPF policy that prohibits creating files owned by "nobody" - system-wide +- busctl: maybe expose a verb "ping" for pinging a dbus service to see if it + exists and responds. -- system LSFMMBPF policy that prohibits creating or opening device nodes outside - of devtmpfs/tmpfs, except if they are the pseudo-devices /dev/null, - /dev/zero, /dev/urandom and so on. +- bypass SIGTERM state in unit files if KillSignal is SIGKILL -- system LSFMMBPF policy that enforces that block device backed mounts may only - be established on top of dm-crypt or dm-verity devices, or an allowlist of - file systems (which should probably include vfat, for compat with the ESP) +- cache sd_event_now() result from before the first iteration... -- $SYSTEMD_EXECPID that the service manager sets should - be augmented with $SYSTEMD_EXECPIDFD (and similar for - other env vars we might send). +- calenderspec: add support for week numbers and day numbers within a + year. This would allow us to define "bi-weekly" triggers safely. -- port copy.c over to use LabelOps for all labelling. +- cgroups: use inotify to get notified when somebody else modifies cgroups + owned by us, then log a friendly warning. -- get rid of compat with libbpf.so.0 (retainly only for libbpf.so.1) +- **cgroups:** + - implement per-slice CPUFairScheduling=1 switch + - introduce high-level settings for RT budget, swappiness + - how to reset dynamically changed unit cgroup attributes sanely? + - when reloading configuration, apply new cgroup configuration + - when recursively showing the cgroup hierarchy, optionally also show + the hierarchies of child processes + - add settings for cgroup.max.descendants and cgroup.max.depth, + maybe use them for user@.service -- define a generic "report" varlink interface, which services can implement to - provide health/statistics data about themselves. then define a dir somewhere - in /run/ where components can bind such sockets. Then make journald, logind, - and pid1 itself implement this and expose various stats on things there. Then - issue parallel calls to these interfaces from the systemd-report tool, - combine into one json document, and include measurement logs and tpm - quote. tpm quote should protect the json doc via the nonce field - studd. Allow shipping this off elsewhere for analyze. +- chase(): take inspiration from path_extract_filename() and return + O_DIRECTORY if input path contains trailing slash. -- The bind(AF_UNSPEC) construct (for resetting sockets to their initial state) - should be blocked in many cases because it punches holes in many sandboxes. +- Check that users of inotify's IN_DELETE_SELF flag are using it properly, as + usually IN_ATTRIB is the right way to watch deleted files, as the former only + fires when a file is actually removed from disk, i.e. the link count drops to + zero and is not open anymore, while the latter happens when a file is + unlinked from any dir. -- introduce new structure Tpm2CombinedPolicy, that combines the various TPm2 - policy bits into one structure, i.e. public key info, pcr masks, pcrlock - stuff, pin and so on. Then pass that around in tpm2_seal() and tpm2_unseal(). +- Clean up "reboot argument" handling, i.e. set it through some IPC service + instead of directly via /run/, so that it can be sensible set remotely. -- look at nsresourced, mountfsd, homed, importd, portabled, and try to come up - with a way how the forked off worker processes can be moved into transient - services with sandboxing, without breaking notify socket stuff and so on. +- clean up date formatting and parsing so that all absolute/relative timestamps we format can also be parsed -- replace all \x1b, \x1B, \033 C string escape sequences in our codebase with a - more readable \e. It's a GNU extension, but a ton more readable than the - others, and most importantly it doesn't result in confusing errors if you - suffix the escape sequence with one more decimal digit, because compilers - think you might actually specify a value outside the 8bit range with that. +- **complete varlink introspection comments:** + - io.systemd.Hostname + - io.systemd.ManagedOOM + - io.systemd.Network + - io.systemd.PCRLock + - io.systemd.Resolve.Monitor + - io.systemd.Resolve + - io.systemd.oom + - io.systemd.sysext - confext/sysext: instead of mounting the overlayfs directly on /etc/ + /usr/, insert an intermediary bind mount on itself there. This has the benefit that services where mount propagation from the root fs is off, an still have confext/sysext propagated in. -- generic interface for varlink for setting log level and stuff that all our daemons can implement - -- maybe teach repart.d/ dropins a new setting MakeMountNodes= or so, which is - just like MakeDirectories=, but uses an access mode of 0000 and sets the +i - chattr bit. This is useful as protection against early uses of /var/ or /tmp/ - before their contents is mounted. - -- go through all uses of table_new() in our codebase, and make sure we support - all three of: - 1. --no-legend properly - 2. --json= properly - 3. --no-pager properly +- consider adding a new partition type, just for /opt/ for usage in system + extensions -- go through all --help texts in our codebases, and make sure: - 1. the one sentence description of the tool is highlighted via ANSI how we - usually do it - 2. If more than one or two commands are supported (as opposed to switches), - separate commands + switches from each other, using underlined --help sections. - 3. If there are many switches, consider adding additional --help sections. +- coredump: maybe when coredumping read a new xattr from /proc/$PID/exe that + may be used to mark a whole binary as non-coredumpable. Would fix: + https://bugs.freedesktop.org/show_bug.cgi?id=69447 -- go through our codebase, and convert "vertical tables" (i.e. things such as - "systemctl status") to use table_new_vertical() for output +- **coredump:** + - save coredump in Windows/Mozilla minidump format + - when truncating coredumps, also log the full size that the process had, and make a metadata field so we can report truncated coredumps + - add examples for other distros in PACKAGE_METADATA_FOR_EXECUTABLE_FILES -- pcrlock: add support for multi-profile UKIs +- **credentials system:** + - acquire from EFI variable? + - acquire via ask-password? + - acquire creds via keyring? + - pass creds via keyring? + - pass creds via memfd? + - acquire + decrypt creds from pkcs11? + - make macsec code in networkd read key via creds logic (copy logic from + wireguard) + - make gatewayd/remote read key via creds logic + - add sd_notify() command for flushing out creds not needed anymore + - if we ever acquire a secure way to derive cgroup id of socket + peers (i.e. SO_PEERCGROUPID), then extend the "scoped" credential logic to + allow cgroup-scoped (i.e. app or service scoped) credentials. Then, as next + step use this to implement per-app/per-service encrypted directories, where + we set up fscrypt on the StateDirectory= with a randomized key which is + stored as xattr on the directory, encrypted as a credential. + - optionally include a per-user secret in scoped user-credential + encryption keys. should come from homed in some way, derived from the luks + volume key or fscrypt directory key. + - add a flag to the scoped credentials that if set require PK + reauthentication when unlocking a secret. + - rework docs. The list in + https://systemd.io/CREDENTIALS/#well-known-credentials is very stale. + Document credentials in individual man pages, generate list as in + systemd.directives. -- initrd: when transitioning from initrd to host, validate that - /lib/modules/`uname -r` exists, refuse otherwise +- creds: add a new cred format that reused the JSON structures we use in the + LUKS header, so that we get the various newer policies for free. -- signed bpf loading: to address need for signature verification for bpf - programs when they are loaded, and given the bpf folks don't think this is - realistic in kernel space, maybe add small daemon that facilitates this - loading on request of clients, validates signatures and then loads the - programs. This daemon should be the only daemon with privs to do load BPF on - the system. It might be a good idea to run this daemon already in the initrd, - and leave it around during the initrd transition, to continue serve requests. - Should then live in its own fs namespace that inherits from the initrd's - fs tree, not from the host, to isolate it properly. Should set - PR_SET_DUMPABLE so that it cannot be ptraced from the host. Should have - CAP_SYS_BPF as only service around. +- cryptenroll/cryptsetup/homed: add unlock mechanism that combines tpm2 and + fido2, as well as tpm2 + ssh-agent, inspired by ChromeOS' logic: encrypt the + volume key with the TPM, with a policy that insists that a nonce is signed by + the fido2 device's key or ssh-agent key. Thus, add unlock/login time the TPM + generates a nonce, which is sent as a challenge to the fido2/ssh-agent, which + returns a signature which is handed to the tpm, which then reveals the volume + key to the PC. -- add a mechanism we can drop capabilities from pid1 *before* transitioning - from initrd to host. i.e. before we transition into the slightly lower trust - domain that is the host systems we might want to get rid of some caps. - Example: CAP_SYS_BPF in the signed bpf loading logic above. (We already have - CapabilityBoundingSet= in system.conf, but that is enforced when pid 1 - initializes, rather then when it transitions to the next.) +- cryptenroll/cryptsetup/homed: similar to this, implement TOTP backed by TPM. -- maybe add a new standard slice where process that are started in the initrd - and stick around for the whole system runtime (i.e. root fs storage daemons, - the bpf loader daemon discussed above, and such) are placed. maybe - protected.slice or so? Then write docs that suggest that services like this - set Slice=protected.sice, RefuseManualStart=yes, RefuseManualStop=yes and a - couple of other things. +- cryptsetup/homed: implement TOTP authentication backed by TPM2 and its + internal clock. -- rough proposed implementation design for remote attestation infra: add a tool - that generates a quote of local PCRs and NvPCRs, along with synchronous log - snapshot. use "audit session" logic for that, so that we get read-outs and - signature in one step. Then turn this into a JSON object. Use the "TCG TSS 2.0 - JSON Data Types and Policy Language" format to encode the signature. And CEL - for the measurement log. +- **cryptsetup:** + - cryptsetup-generator: allow specification of passwords in crypttab itself + - support rd.luks.allow-discards= kernel cmdline params in cryptsetup generator + - add boolean for disabling use of any password/recovery key slots. + (i.e. that we can operate in a tpm-only mode, and thus protect us from rogue + root disks) + - new crypttab option to auto-grow a luks device to its backing + partition size. new crypttab option to reencrypt a luks device with a new + volume key. + - a mechanism that allows signing a volume key with some key that + has to be present in the kernel keyring, or similar, to ensure that confext + DDIs can be encrypted against the local SRK but signed with the admin's key + and thus can authenticated locally before they are decrypted. + - add option for automatically removing empty password slot on boot + - optionally, when run during boot-up and password is never + entered, and we are on battery power (or so), power off machine again + - when waiting for FIDO2/PKCS#11 token, tell plymouth that, and + allow plymouth to abort the waiting and enter pw instead + - allow encoding key directly in /etc/crypttab, maybe with a + "base64:" prefix. Useful in particular for pkcs11 mode. + - reimplement the mkswap/mke2fs in cryptsetup-generator to use + systemd-makefs.service instead. -- creds: add a new cred format that reused the JSON structures we use in the - LUKS header, so that we get the various newer policies for free. +- crypttab/gpt-auto-generator: allow explicit control over which unlock mechs + to permit, and maybe have a global headless kernel cmdline option -- systemd-analyze: port "pcrs" verb to talk directly to TPM device, instead of - using sysfs interface (well, or maybe not, as that would require privileges?) +- currently x-systemd.timeout is lost in the initrd, since crypttab is copied into dracut, but fstab is not -- pcrextend/tpm2-util: add a concept of "rotation" to event log. i.e. allow - trailing parts of the logs if time or disk space limit is hit. Protect the - boot-time measurements however (i.e. up to some point where things are - settled), since we need those for pcrlock measurements and similar. When - deleting entries for rotation, place an event that declares how many items - have been dropped, and what the hash before and after that. +- dbus: when a unit failed to load (i.e. is in UNIT_ERROR state), we + should be able to safely try another attempt when the bus call LoadUnit() is invoked. -- use name_to_handle_at() with AT_HANDLE_FID instead of .st_ino (inode - number) for identifying inodes, for example in copy.c when finding hard - links, or loop-util.c for tracking backing files, and other places. +- ddi must be listed as block device fstype -- cryptenroll/cryptsetup/homed: add unlock mechanism that combines tpm2 and - fido2, as well as tpm2 + ssh-agent, inspired by ChromeOS' logic: encrypt the - volume key with the TPM, with a policy that insists that a nonce is signed by - the fido2 device's key or ssh-agent key. Thus, add unlock/login time the TPM - generates a nonce, which is sent as a challenge to the fido2/ssh-agent, which - returns a signature which is handed to the tpm, which then reveals the volume - key to the PC. +- define a generic "report" varlink interface, which services can implement to + provide health/statistics data about themselves. then define a dir somewhere + in /run/ where components can bind such sockets. Then make journald, logind, + and pid1 itself implement this and expose various stats on things there. Then + issue parallel calls to these interfaces from the systemd-report tool, + combine into one json document, and include measurement logs and tpm + quote. tpm quote should protect the json doc via the nonce field + studd. Allow shipping this off elsewhere for analyze. -- cryptenroll/cryptsetup/homed: similar to this, implement TOTP backed by TPM. +- define a JSON format for units, separating out unit definitions from unit + runtime state. Then, expose it: -- expose the handoff timestamp fully via the D-Bus properties that contain - ExecStatus information + 1. Add Describe() method to Unit D-Bus object that returns a JSON object + about the unit. + 2. Expose this natively via Varlink, in similar style + 3. Use it when invoking binaries (i.e. make PID 1 fork off systemd-executor + binary which reads the JSON definition and runs it), to address the cow + trap issue and the fact that NSS is actually forbidden in + forked-but-not-exec'ed children + 4. Add varlink API to run transient units based on provided JSON definitions -- properly serialize the ExecStatus data from all ExecCommand objects - associated with services, sockets, mounts and swaps. Currently, the data is - flushed out on reload, which is quite a limitation. +- define gpt header bits to select volatility mode -- Clean up "reboot argument" handling, i.e. set it through some IPC service - instead of directly via /run/, so that it can be sensible set remotely. +- delay activation of logind until somebody logs in, or when /dev/tty0 pulls it + in or lingering is on (so that containers don't bother with it until PAM is used). also exit-on-idle -- systemd-tpm2-support: add a some logic that detects if system is in DA - lockout mode, and queries the user for TPM recovery PIN then. +- deprecate RootDirectoryStartOnly= in favour of a new ExecStart= prefix char -- move documentation about our common env vars (SYSTEMD_LOG_LEVEL, - SYSTEMD_PAGER, …) into a man page of its own, and just link it from our - various man pages that so far embed the whole list again and again, in an - attempt to reduce clutter and noise a bid. +- **dhcp6:** + - add functions to set previously stored IPv6 addresses on startup and get + them at shutdown; store them in client->ia_na + - write more test cases + - implement reconfigure support, see 5.3., 15.11. and 22.20. + - implement support for temporary addresses (IA_TA) + - implement dhcpv6 authentication + - investigate the usefulness of Confirm messages; i.e. are there any + situations where the link changes without any loss in carrier detection + or interface down + - some servers don't do rapid commit without a filled in IA_NA, verify + this behavior + - RouteTable= ? -- vmspawn switch default swtpm PCR bank to SHA384-only (away from SHA256), at - least on 64bit archs, simply because SHA384 is typically double the hashing - speed than SHA256 on 64bit archs (since based on 64bit words unlike SHA256 - which uses 32bit words). +- **dhcp:** + - figure out how much we can increase Maximum Message Size -- In vmspawn/nspawn/machined wait for X_SYSTEMD_UNIT_ACTIVE=ssh-active.target - and X_SYSTEMD_SIGNALS_LEVEL=2 as indication whether/when SSH and the POSIX - signals are available. Similar for D-Bus (but just use sockets.target for - that). Report as property for the machine. +- dissection policy should enforce that unlocking can only take place by + certain means, i.e. only via pw, only via tpm2, or only via fido, or a + combination thereof. -- teach nspawn/machined a new bus call/verb that gets you a - shell in containers that have no sensible pid1, via joining the container, - and invoking a shell directly. Then provide another new bus call/vern that is - somewhat automatic: if we detect that pid1 is running and fully booted up we - provide a proper login shell, otherwise just a joined shell. Then expose that - as primary way into the container. +- do a console daemon that takes stdio fds for services and allows to reconnect + to them later -- make vmspawn/nspawn/importd/machined a bit more usable in a WSL-like - fashion. i.e. teach unpriv systemd-vmspawn/systemd-nspawn a reasonable - --bind-user= behaviour that mounts the calling user through into the - machine. Then, ship importd with a small database of well known distro images - along with their pinned signature keys. Then add some minimal glue that binds - this together: downloads a suitable image if not done so yet, starts it in - the bg via vmspawn/nspawn if not done so yet and then requests a shell inside - it for the invoking user. +- doc: prep a document explaining PID 1's internal logic, i.e. transactions, + jobs, units -- add a new specifier to unit files that figures out the DDI the unit file is - from, tracing through overlayfs, DM, loopback block device. +- doc: prep a document explaining resolved's internal objects, i.e. Query + vs. Question vs. Transaction vs. Stream and so on. -- importd/importctl: - - complete varlink interface - - download images into .v/ dirs +- docs: bring https://systemd.io/MY_SERVICE_CANT_GET_REALTIME up to date -- in os-release define a field that can be initialized at build time from - SOURCE_DATE_EPOCH (maybe even under that name?). Would then be used to - initialize the timestamp logic of ConditionNeedsUpdate=. +- document Environment=SYSTEMD_LOG_LEVEL=debug drop-in in debugging document -- nspawn/vmspawn/pid1: add ability to easily insert fully booted VMs/FOSC into - shell pipelines, i.e. add easy to use switch that turns off console status - output, and generates the right credentials for systemd-run-generator so that - a program is invoked, and its output captured, with correct EOF handling and - exit code propagation +- document org.freedesktop.MemoryAllocation1 -- Introduce a CGroupRef structure, inspired by PidRef. Should contain cgroup - path, cgroup id, and cgroup fd. Use it to continuously pin all v2 cgroups via - a cgroup_ref field in the CGroupRuntime structure. Eventually switch things - over to do all cgroupfs access only via that structure's fd. +- **document:** + - document that deps in [Unit] sections ignore Alias= fields in + [Install] units of other units, unless those units are disabled + - document that service reload may be implemented as service reexec + - add a man page containing packaging guidelines and recommending usage of things like Documentation=, PrivateTmp=, PrivateNetwork= and ReadOnlyDirectories=/etc /usr. + - document systemd-journal-flush.service properly + - documentation: recommend to connect the timer units of a service to the service via Also= in [Install] + - man: document the very specific env the shutdown drop-in tools live in + - man: add more examples to man pages, + - in particular an example how to do the equivalent of switching runlevels + - man: maybe sort directives in man pages, and take sections from --help and apply them to man too + - document root=gpt-auto properly -- Get rid of the symlinks in /run/systemd/units/* and exclusively use cgroupfs - xattrs to convey info about invocation ids, logging settings and so on. - support for cgroupfs xattrs in the "trusted." namespace was added in linux - 3.7, i.e. which we don't pretend to support anymore. +- dot output for --test showing the 'initial transaction' -- rewrite bpf-devices in libbpf/C code, rather than home-grown BPF assembly, to - match bpf-restrict-fs, bpf-restrict-ifaces, bpf-socket-bind +- drop nss-myhostname in favour of nss-resolve? + +- drop NV_ORDERLY flag from the product uuid nvpcr. Effect of the flag is that + it pushes the thing into TPM RAM, but a TPM usually has very little of that, + less than NVRAM. hence setting the flag amplifies space issues. Unsetting the + flag increases wear issues on the NVRAM, however, but this should be limited + for the product uuid nvpcr, since its only changed once per boot. this needs + to be configurable by nvpcr however, as other nvpcrs are different, + i.e. verity one receives many writes during system uptime quite + possibly. (also, NV_ORDERLY makes stuff faster, and dropping it costs + possibly up to 100ms supposedly) + +- **EFI:** + - honor language efi variables for default language selection (if there are any?) + - honor timezone efi variables for default timezone selection (if there are any?) + +- enable LockMLOCK to take a percentage value relative to physical memory + +- Enable RestrictFileSystems= for all our long-running services (similar: + RestrictNetworkInterfaces=) + +- encode type1 entries in some UKI section to add additional entries to the + menu. + +- enumerate virtiofs devices during boot-up in a generator, and synthesize + mounts for rootfs, /usr/, /home/, /srv/ and some others from it, depending on + the "tag". (waits for: https://gitlab.com/virtio-fs/virtiofsd/-/issues/128) + +- /etc/veritytab: allow that the roothash column can be specified as fs path + including a path to an AF_UNIX path, similar to how we do things with the + keys of /etc/crypttab. That way people can store/provide the roothash + externally and provide to us on demand only. + +- exponential backoff in timesyncd when we cannot reach a server + +- expose MS_NOSYMFOLLOW in various places -- ditto: rewrite bpf-firewall in libbpf/C code +- expose the handoff timestamp fully via the D-Bus properties that contain + ExecStatus information - extend the smbios11 logic for passing credentials so that instead of passing the credential data literally it can also just reference an AF_VSOCK CID/port to read them from. This way the data doesn't remain in the SMBIOS blob during runtime, but only in the credentials fs. -- introduce mntid_t, and make it 64bit, as apparently the kernel switched to - 64bit mount ids +- extend the verity signature partition to permit multiple signatures for the + same root hash, so that people can sign a single image with multiple keys. -- mountfsd/nsresourced: - - userdb: maybe allow callers to map one uid to their own uid - - bpflsm: allow writes if resulting UID on disk would be userns' owner UID - - make encrypted DDIs work (password…) - - add API for creating a new file system from scratch (together with some - dm-integrity/HMAC key). Should probably work using systemd-repart (access - via varlink). - - add api to make an existing file "trusted" via dm-integry/HMAC key - - port: portabled - - port: tmpfiles, sysusers and similar - - lets see if we can make runtime bind mounts into unpriv nspawn work +- figure out a nice way how we can let the admin know what child/sibling unit causes cgroup membership for a specific unit -- add a kernel cmdline switch (and cred?) for marking a system to be - "headless", in which case we never open /dev/console for reading, only for - writing. This would then mean: systemd-firstboot would process creds but not - ask interactively, getty would not be started and so on. +- Figure out how to do unittests of networkd's state serialization -- we probably should have some infrastructure to acquire sysexts with - drivers/firmware for local hardware automatically. Idea: reuse the modalias - logic of the kernel for this: make the main OS image install a hwdb file - that matches against local modalias strings, and adds properties to relevant - devices listing names of sysexts needed to support the hw. Then provide some - tool that goes through all devices and tries to acquire/download the - specified images. +- Figure out naming of verbs in systemd-analyze: we have (singular) capability, + exit-status, but (plural) filesystems, architectures. -- repart + cryptsetup: support file systems that are encrypted and use verity - on top. Usecase: confexts that shall be signed by the admin but also be - confidential. Then, add a new --make-ddi=confext-encrypted for this. +- figure out what to do about credentials sealed to PCRs in kexec + soft-reboot + scenarios. Maybe insist sealing is done additionally against some keypair in + the TPM to which access is updated on each boot, for the next, or so? -- tiny varlink service that takes a fd passed in and serves it via http. Then - make use of that in networkd, and expose some EFI binary of choice for - DHCP/HTTP base EFI boot. +- figure out when we can use the coarse timers -- maybe: in PID1, when we detect we run in an initrd, make superblock read-only - early on, but provide opt-out via kernel cmdline. +- Find a solution for SMACK capabilities stuff: + https://lists.freedesktop.org/archives/systemd-devel/2014-December/026188.html -- systemd-pcrextend: - - once we have that start measuring every sysext we apply, every confext, - every RootImage= we apply, every nspawn and so on. All in separate fake - PCRs. +- fix bug around run0 background color on ls in fresh terminal -- vmspawn: - - --ephemeral support - - --read-only support - - automatically suspend/resume the VM if the host suspends. Use logind - suspend inhibitor to implement this. request clean suspend by generating - suspend key presses. - - support for "real" networking via "-n" and --network-bridge= - - translate SIGTERM to clean ACPI shutdown event - - implement hotkeys ^]^]r and ^]^]p like nspawn +- Fix DECIMAL_STR_MAX or DECIMAL_STR_WIDTH. One includes a trailing NUL, the + other doesn't. What a disaster. Probably to exclude it. -- storagetm: - - add USB mass storage device logic, so that all local disks are also exposed - as mass storage devices on systems that have a USB controller that can - operate in device mode - - add NVMe authentication +- fix homed/homectl confusion around terminology, i.e. "home directory" + vs. "home" vs. "home area". Stick to one term for the concept, and it + probably shouldn't contain "area". -- add support for activating nvme-oF devices at boot automatically via kernel - cmdline, and maybe even support a syntax such as - root=nvme::::: to boot directly from - nvme-oF +- fix our various hwdb lookup keys to end with ":" again. The original idea was + that hwdb patterns can match arbitrary fields with expressions like + "*:foobar:*", to wildcard match both the start and the end of the string. + This only works safely for later extensions of the string if the strings + always end in a colon. This requires updating our udev rules, as well as + checking if the various hwdb files are fine with that. -- pcrlock: - - add kernel-install plugin that automatically creates UKI .pcrlock file when - UKI is installed, and removes it when it is removed again - - automatically install PE measurement of sd-boot on "bootctl install" - - pre-calc sysext + kernel cmdline measurements - - pre-calc cryptsetup root key measurement - - maybe make systemd-repart generate .pcrlock for old and new GPT header in - /run? - - Add support for more than 8 branches per PCR OR - - add "systemd-pcrlock lock-kernel-current" or so which synthesizes .pcrlock - policy from currently booted kernel/event log, to close gap for first boot - for pre-built images +- flush_accept() should look at sockdiag queued sockets count and exit once we + flushed out the specified number of connections. -- in sd-boot and sd-stub measure the SMBIOS vendor strings to some PCR (at - least some subset of them that look like systemd stuff), because apparently - some firmware does not, but systemd honours it. avoid duplicate measurement - by sd-boot and sd-stub by adding LoaderFeatures/StubFeatures flag for this, - so that sd-stub can avoid it if sd-boot already did it. +- flush_fd() should probably try to be smart and stop reading once we know that + all further queued data was enqueued after flush_fd() was originally + called. For that, try SIOCINQ if fd refers to stream socket, and look at + timestamps for datagram sockets. -- image policy should be extended to allow dictating *how* a disk is unlocked, - i.e. root=encrypted-tpm2+encrypted-fido2 would mean "root fs must be - encrypted and unlocked via fido2 or tpm2, but not otherwise" +- for better compat with major clouds: implement simple PTP device support in + timesyncd -- redefine /var/lib/extensions/ as the dir one can place all three of sysext, - confext as well is multi-modal DDIs that qualify as both. Then introduce - /var/lib/sysexts/ which can be used to place only DDIs that shall be used as - sysext +- for better compat with major clouds: introduce imds mini client service that + sets up primary netif in a private netns (ipvlan?) to query imds without + affecting rest of the host. pick up literal credentials from there plus the + fields the hwdb reports for the other fields and turn them into credentials. + then write generator that used detected virtualization info and plugs this + service into the early boot, waiting for the DMI and network device to show + up. -- Varlinkification of the following command line tools, to open them up to - other programs via IPC: - - coredumpcl - - systemd-bless-boot - - systemd-measure - - systemd-cryptenroll (to allow UIs to enroll FIDO2 keys and such) - - systemd-dissect - - systemd-sysupdate - - systemd-analyze - - kernel-install - - systemd-mount (with PK so that desktop environments could use it to mount disks) +- for better compat with major clouds: recognize clouds via hwdb on DMI device, + and add udev properties to it that help with handling IMDS, i.e. entrypoint + URL, which fields to find ip hostname, ssh key, … -- enumerate virtiofs devices during boot-up in a generator, and synthesize - mounts for rootfs, /usr/, /home/, /srv/ and some others from it, depending on - the "tag". (waits for: https://gitlab.com/virtio-fs/virtiofsd/-/issues/128) +- For timer units: add some mechanisms so that timer units that trigger immediately on boot do not have the services + they run added to the initial transaction and thus confuse Type=idle. -- automatically mount one virtiofs during early boot phase to /run/host/, - similar to how we do that for nspawn, based on some clear tag. +- **for vendor-built signed initrds:** + - kernel-install should be able to install encrypted creds automatically for + machine id, root pw, rootfs uuid, resume partition uuid, and place next to + EFI kernel, for sd-stub to pick them up. These creds should be locked to + the TPM, and bind to the right PCR the kernel is measured to. + - kernel-install should be able to pick up initrd sysexts automatically and + place them next to EFI kernel, for sd-stub to pick them up. + - systemd-fstab-generator should look for rootfs device to mount in creds + - systemd-resume-generator should look for resume partition uuid in creds -- add some service that makes an atomic snapshot of PCR state and event log up - to that point available, possibly even with quote by the TPM. +- **foreign uid:** + - add support to export-fs, import-fs + - systemd-dissect should learn mappings, too, when doing mtree and such -- encode type1 entries in some UKI section to add additional entries to the - menu. +- fstab-generator: default to tmpfs-as-root if only usr= is specified on the kernel cmdline -- Add ACL-based access management to .socket units. i.e. add AllowPeerUser= + - AllowPeerGroup= that installs additional user/group ACL entries on AF_UNIX - sockets. +- generator that automatically discovers btrfs subvolumes, identifies their purpose based on some xattr on them. -- systemd-tpm2-setup should support a mode where we refuse booting if the SRK - changed. (Must be opt-in, to not break systems which are supposed to be - migratable between PCs) +- generic interface for varlink for setting log level and stuff that all our daemons can implement -- when systemd-sysext learns mutable /usr/ (and systemd-confext mutable /etc/) - then allow them to store the result in a .v/ versioned subdir, for some basic - snapshot logic +- get rid of compat with libbpf.so.0 (retainly only for libbpf.so.1) -- add a new PE binary section ".mokkeys" or so which sd-stub will insert into - Mok keyring, by overriding/extending whatever shim sets in the EFI - var. Benefit: we can extend the kernel module keyring at ukify time, - i.e. without recompiling the kernel, taking an upstream OS' kernel and adding - a local key to it. +- Get rid of the symlinks in /run/systemd/units/* and exclusively use cgroupfs + xattrs to convey info about invocation ids, logging settings and so on. + support for cgroupfs xattrs in the "trusted." namespace was added in linux + 3.7, i.e. which we don't pretend to support anymore. -- PidRef conversion work: - - cg_pid_get_xyz() - - pid_from_same_root_fs() - - get_ctty_devnr() - - actually wait for POLLIN on pidref's pidfd in service logic - - openpt_allocate_in_namespace() - - unit_attach_pid_to_cgroup_via_bus() - - cg_attach() – requires new kernel feature - - journald's process cache +- go through all --help texts in our codebases, and make sure: + 1. the one sentence description of the tool is highlighted via ANSI how we + usually do it + 2. If more than one or two commands are supported (as opposed to switches), + separate commands + switches from each other, using underlined --help sections. + 3. If there are many switches, consider adding additional --help sections. -- ddi must be listed as block device fstype +- go through all uses of table_new() in our codebase, and make sure we support + all three of: + 1. --no-legend properly + 2. --json= properly + 3. --no-pager properly -- measure some string via pcrphase whenever we end up booting into emergency - mode. +- go through our codebase, and convert "vertical tables" (i.e. things such as + "systemctl status") to use table_new_vertical() for output -- similar, measure some string via pcrphase whenever we resume from hibernate +- **gpt-auto-generator:** + - Make /home automount rather than mount? -- use sd-event ratelimit feature optionally for journal stream clients that log - too much +- have a signal that reloads every unit that supports reloading -- systemd-mount should only consider modern file systems when mounting, similar - to systemd-dissect +- hibernate/s2h: if swap is on weird storage and refuse if so -- add another PE section ".fname" or so that encodes the intended filename for - PE file, and validate that when loading add-ons and similar before using - it. This is particularly relevant when we load multiple add-ons and want to - sort them to apply them in a define order. The order should not be under - control of the attacker. +- homed/pam_systemd: allow authentication by ssh-agent, so that run0/polkit can + be allowed if caller comes with the right ssh-agent keys. -- also include packaging metadata (á la - https://systemd.io/PACKAGE_METADATA_FOR_EXECUTABLE_FILES/) in our UEFI PE - binaries, using the same JSON format. +- homed/userdb: maybe define a "companion" dir for home directories where apps + can safely put privileged stuff in. Would not be writable by the user, but + still conceptually belong to the user. Would be included in user's quota if + possible, even if files are not owned by UID of user. Use case: container + images that owned by arbitrary UIDs, and are owned/managed by the users, but + are not directly belonging to the user's UID. Goal: we shouldn't place more + privileged dirs inside of unprivileged dirs, and thus containers really + should not be placed inside of traditional UNIX home dirs (which are owned by + users themselves) but somewhere else, that is separate, but still close + by. Inform user code about path to this companion dir via env var, so that + container managers find it. the ~/.identity file is also a candidate for a + file to move there, since it is managed by privileged code (i.e. homed) and + not unprivileged code. -- make "bootctl install" + "bootctl update" useful for installing shim too. For - that introduce new dir /usr/lib/systemd/efi/extra/ which we copy mostly 1:1 - into the ESP at install time. Then make the logic smart enough so that we - don't overwrite bootx64.efi with our own if the extra tree already contains - one. Also, follow symlinks when copying, so that shim rpm can symlink their - stuff into our dir (which is safe since the target ESP is generally VFAT and - thus does not have symlinks anyway). Later, teach the update logic to look at - the ELF package metadata (which we also should include in all PE files, see - above) for version info in all *.EFI files, and use it to only update if - newer. +- **homed:** + - when user tries to log into record signed by unrecognized key, automatically add key to our chain after polkit auth + - rollback when resize fails mid-operation + - GNOME's side for forget key on suspend (requires rework so that lock screen runs outside of uid) + - update LUKS password on login if we find there's a password that unlocks the JSON record but not the LUKS device. + - create on activate? + - properties: icon url?, administrator bool (which translates to 'wheel' membership)?, address?, telephone?, vcard?, samba stuff?, parental controls? + - communicate clearly when usb stick is safe to remove. probably involves + beefing up logind to make pam session close hook synchronous and wait until + systemd --user is shut down. + - logind: maybe keep a "busy fd" as long as there's a non-released session around or the user@.service + - maybe make automatic, read-only, time-based reflink-copies of LUKS disk + images (and btrfs snapshots of subvolumes) (think: time machine) + - distinguish destroy / remove (i.e. currently we can unregister a user, unregister+remove their home directory, but not just remove their home directory) + - fingerprint authentication, pattern authentication, … + - make sure "classic" user records can also be managed by homed + - make size of $XDG_RUNTIME_DIR configurable in user record + - move acct mgmt stuff from pam_systemd_home to pam_systemd? + - when "homectl --pkcs11-token-uri=" is used, synthesize ssh-authorized-keys records for all keys we have private keys on the stick for + - make slice for users configurable (requires logind rework) + - logind: populate auto-login list bus property from PKCS#11 token + - when determining state of a LUKS home directory, check DM suspended sysfs file + - when homed is in use, maybe start the user session manager in a mount namespace with MS_SLAVE, + so that mounts propagate down but not up - eg, user A setting up a backup volume + doesn't mean user B sees it + - use credentials logic/TPM2 logic to store homed signing key + - permit multiple user record signing keys to be used locally, and pick + the right one for signing records automatically depending on a pre-existing + signature + - add a way to "take possession" of a home directory, i.e. strip foreign signatures + and insert a local signature instead. + - as an extension to the directory+subvolume backend: if located on + especially marked fs, then sync down password into LUKS header of that fs, + and always verify passwords against it too. Bootstrapping is a problem + though: if no one is logged in (or no other user even exists yet), how do you + unlock the volume in order to create the first user and add the first pw. + - support new FS_IOC_ADD_ENCRYPTION_KEY ioctl for setting up fscrypt + - maybe pre-create ~/.cache as subvol so that it can have separate quota + easily? + - store PKCS#11 + FIDO2 token info in LUKS2 header, compatible with + systemd-cryptsetup, so that it can unlock homed volumes + - maybe make all *.home files owned by `systemd-home` user or so, so that we + can easily set overall quota for all users + - on login, if we can't fallocate initially, but rebalance is on, then allow + login in discard mode, then immediately rebalance, then turn off discard + - add "homectl unbind" command to remove local user record of an inactive + home dir + - use systemd-storagetm to expose home dirs via nvme-tcp. Then, + teach homed/pam_systemd_homed with a user name such as + lennart%nvme_tcp_192.168.100.77_8787 to log in from any linux host with the + same home dir. Similar maybe for nbd, iscsi? this should then first ask for + the local root pw, to authenticate that logging in like this is ok, and would + then be followed by another password prompt asking for the user's own + password. Also, do something similar for CIFS: if you log in via + lennart%cifs-someserver_someshare, then set up the homed dir for it + automatically. The PAM module should update the user name used for login to + the short version once it set up the user. Some care should be taken, so that + the long version can be still be resolved via NSS afterwards, to deal with + PAM clients that do not support PAM sessions where PAM_USER changes half-way. + - add a basic form of secrets management to homed, that stores + secrets in $HOME somewhere, is protected by the accounts own authentication + mechanisms. Should implement something PKCS#11-like that can be used to + implement emulated FIDO2 in unpriv userspace on top (which should happen + outside of homed), emulated PKCS11, and libsecrets support. Operate with a + 2nd key derived from volume key of the user, with which to wrap all + keys. maintain keys in kernel keyring if possible. + - when resizing an fs don't sync identity beforehand there might simply + not be enough disk space for that. try to be defensive and sync only after + resize. + - if for some reason the partition ended up being much smaller than + whole disk, recover from that, and grow it again. + - if the homed shell fallback thing has access to an SSH agent, try to + use it to unlock home dir (if ssh-agent forwarding is enabled). We + could implement SSH unlocking of a homedir with that: when enrolling a new + ssh pubkey in a user record we'd ask the ssh-agent to sign some random value + with the privkey, then use that as luks key to unlock the home dir. Will not + work for ECDSA keys since their signatures contain a random component, but + will work for RSA and Ed25519 keys. -- in sd-stub: optionally add support for a new PE section .keyring or so that - contains additional certificates to include in the Mok keyring, extending - what shim might have placed there. why? let's say I use "ukify" to build + - sign my own fedora-based UKIs, and only enroll my personal lennart key via - shim. Then, I want to include the fedora keyring in it, so that kmods work. - But I might not want to enroll the fedora key in shim, because this would - also mean that the key would be in effect whenever I boot an archlinux UKI - built the same way, signed with the same lennart key. +- honour validatefs xattrs in dissect-image.c too -- Maybe add SwitchRootEx() as new bus call that takes env vars to set for new - PID 1 as argument. When adding SwitchRootEx() we should maybe also add a - flags param that allows disabling and enabling whether serialization is - requested during switch root. +- hook up journald with TPMs? measure new journal records to the TPM in regular + intervals, validate the journal against current TPM state with that. (taking + inspiration from IMA log) -- introduce a .acpitable section for early ACPI table override +- Hook up journald's FSS logic with TPM2: seal the verification disk by + time-based policy, so that the verification key can remain on host and ve + validated via TPM. -- add proper .osrel matching for PE addons. i.e. refuse applying an addon - intended for a different OS. Take inspiration from how confext/sysext are - matched against OS. +- Hook up systemd-journal-upload with RESTART_RESET=1 logic (should probably + be conditioned on the num of successfully uploaded entries?) -- figure out what to do about credentials sealed to PCRs in kexec + soft-reboot - scenarios. Maybe insist sealing is done additionally against some keypair in - the TPM to which access is updated on each boot, for the next, or so? +- hostnamectl: show root image uuid -- mount most file systems with a restrictive uidmap. e.g. mount /usr/ with a - uidmap that blocks out anything outside 0…1000 (i.e. system users) and similar. +- if /usr/bin/swapoff fails due to OOM, log a friendly explanatory message about it -- mount the root fs with MS_NOSUID by default, and then mount /usr/ without - both so that suid executables can only be placed there. Do this already in - the initrd. If /usr/ is not split out create a bind mount automatically. +- if we fork of a service with StandardOutput=journal, and it forks off a + subprocess that quickly dies, we might not be able to identify the cgroup it + comes from, but we can still derive that from the stdin socket its output + came from. We apparently don't do that right now. -- fix our various hwdb lookup keys to end with ":" again. The original idea was - that hwdb patterns can match arbitrary fields with expressions like - "*:foobar:*", to wildcard match both the start and the end of the string. - This only works safely for later extensions of the string if the strings - always end in a colon. This requires updating our udev rules, as well as - checking if the various hwdb files are fine with that. +- If we try to find a unit via a dangling symlink, generate a clean + error. Currently, we just ignore it and read the unit from the search + path anyway. -- mount /tmp/ and /var/tmp with a uidmap applied that blocks out "nobody" user - among other things such as dynamic uid ranges for containers and so on. That - way no one can create files there with these uids and we enforce they are only - used transiently, never persistently. +- image policy should be extended to allow dictating *how* a disk is unlocked, + i.e. root=encrypted-tpm2+encrypted-fido2 would mean "root fs must be + encrypted and unlocked via fido2 or tpm2, but not otherwise" -- rework loopback support in fstab: when "loop" option is used, then - instantiate a new systemd-loop@.service for the source path, set the - lo_file_name field for it to something recognizable derived from the fstab - line, and then generate a mount unit for it using a udev generated symlink - based on lo_file_name. +- imds: maybe do smarter api version handling -- teach systemd-nspawn the boot assessment logic: hook up vpick's try counters - with success notifications from nspawn payloads. When this is enabled, - automatically support reverting back to older OS version images if newer ones - fail to boot. +- implement a varlink registry service, similar to the one of the reference + implementation, backed by /run/varlink/registry/. Then, also implement + connect-via-registry-resolution in sd-varlink and varlinkctl. Care needs to + be taken to do the resolution asynchronousy. Also, note that the Varlink + reference implementation uses a different address syntax, which needs to be + taken into account. -- remove tomoyo support, it's obsolete and unmaintained apparently +- implement Distribute= in socket units to allow running multiple + service instances processing the listening socket, and open this up + for ReusePort= + +- **importd/importctl:** + - complete varlink interface + - download images into .v/ dirs + +- importd: support image signature verification with PKCS#7 + OpenBSD signify + logic, as alternative to crummy gpg - In .socket units, add ConnectStream=, ConnectDatagram=, ConnectSequentialPacket= that create a socket, and then *connect to* rather than @@ -1016,345 +1055,359 @@ aforementioned journald subscription varlink service, to enable activation-by-message id and similar. -- .service with invalid Sockets= starts successfully. +- In DynamicUser= mode: before selecting a UID, use disk quota APIs on relevant + disks to see if the UID is already in use. -- landlock: lock down RuntimeDirectory= via landlock, so that services lose - ability to write anywhere else below /run/. Similar for - StateDirectory=. Benefit would be clear delegation via unit files: services - get the directories they get, and nothing else even if they wanted to. +- in journald, write out a recognizable log record whenever the system clock is + changed ("stepped"), and in timesyncd whenever we acquire an NTP fix + ("slewing"). Then, in journalctl for each boot time we come across, find + these records, and use the structured info they include to display + "corrected" wallclock time, as calculated from the monotonic timestamp in the + log record, adjusted by the delta declared in the structured log record. -- landlock: for unprivileged systemd (i.e. systemd --user), use landlock to - implement ProtectSystem=, ProtectHome= and so on. Landlock does not require - privs, and we can implement pretty similar behaviour. Also, maybe add a mode - where ProtectSystem= combined with an explicit PrivateMounts=no could request - similar behaviour for system services, too. +- in journald: whenever we start a new journal file because the boot ID + changed, let's generate a recognizable log record containing info about old + and new ID. Then, when displaying log stream in journalctl look for these + records, to be able to order them. -- Add systemd-mount@.service which is instantiated for a block device and - invokes systemd-mount and exits. This is then useful to use in - ENV{SYSTEMD_WANTS} in udev rules, and a bit prettier than using RUN+= +- in networkd, when matching device types, fix up DEVTYPE rubbish the kernel passes to us -- udevd: extend memory pressure logic: also kill any idle worker processes +- in nss-systemd, if we run inside of RootDirectory= with PrivateUsers= set, + find a way to map the User=/Group= of the service to the right name. This way + a user/group for a service only has to exist on the host for the right + mapping to work. -- udevadm: to make symlink querying with udevadm nicer: - - do not enable the pager for queries like 'udevadm info -q symlink -r' - - add mode with newlines instead of spaces (for grep)? +- in os-release define a field that can be initialized at build time from + SOURCE_DATE_EPOCH (maybe even under that name?). Would then be used to + initialize the timestamp logic of ConditionNeedsUpdate=. -- SIGRTMIN+18 and memory pressure handling should still be added to: hostnamed, - localed, oomd, timedated. +- in pid1: include ExecStart= cmdlines (and other Exec*= cmdlines) in polkit + request, so that policies can match against command lines. -- repart/gpt-auto/DDIs: maybe introduce a concept of "extension" partitions, - that have a new type uuid and can "extend" earlier partitions, to work around - the fact that systemd-repart can only grow the last partition defined. During - activation we'd simply set up a dm-linear mapping to merge them again. A - partition that is to be extended would just set a bit in the partition flags - field to indicate that there's another extension partition to look for. The - identifying UUID of the extension partition would be hashed in counter mode - from the uuid of the original partition it extends. Inspiration for this is - the "dynamic partitions" concept of new Android. This would be a minimalistic - concept of a volume manager, with the extents it manages being exposes as GPT - partitions. I a partition is extended multiple times they should probably - grow exponentially in size to ensure O(log(n)) time for finding them on - access. +- in sd-boot and sd-stub measure the SMBIOS vendor strings to some PCR (at + least some subset of them that look like systemd stuff), because apparently + some firmware does not, but systemd honours it. avoid duplicate measurement + by sd-boot and sd-stub by adding LoaderFeatures/StubFeatures flag for this, + so that sd-stub can avoid it if sd-boot already did it. -- Make nspawn to a frontend for systemd-executor, so that we have to ways into - the executor: via unit files/dbus/varlink through PID1 and via cmdline/OCI - through nspawn. +- in sd-id128: also parse UUIDs in RFC4122 URN syntax (i.e. chop off urn:uuid: prefix) -- add a utility that can be used with the kernel's - CONFIG_STATIC_USERMODEHELPER_PATH and then handles them within pid1 so that - security, resource management and cgroup settings can be enforced properly - for all umh processes. +- in sd-stub: optionally add support for a new PE section .keyring or so that + contains additional certificates to include in the Mok keyring, extending + what shim might have placed there. why? let's say I use "ukify" to build + + sign my own fedora-based UKIs, and only enroll my personal lennart key via + shim. Then, I want to include the fedora keyring in it, so that kmods work. + But I might not want to enroll the fedora key in shim, because this would + also mean that the key would be in effect whenever I boot an archlinux UKI + built the same way, signed with the same lennart key. -- timesyncd: when saving/restoring clock try to take boot time into account. - Specifically, along with the saved clock, store the current boot ID. When - starting, check if the boot id matches. If so, don't do anything (we are on - the same boot and clock just kept running anyway). If not, then read - CLOCK_BOOTTIME (which started at boot), and add it to the saved clock - timestamp, to compensate for the time we spent booting. If EFI timestamps are - available, also include that in the calculation. With this we'll then only - miss the time spent during shutdown after timesync stopped and before the - system actually reset. +- in the initrd, once the rootfs encryption key has been measured to PCR 15, + derive default machine ID to use from it, and pass it to host PID 1. -- systemd-stub: maybe store a "boot counter" in the ESP, and pass it down to - userspace to allow ordering boots (for example in journalctl). The counter - would be monotonically increased on every boot. +- in the initrd: derive the default machine ID to pass to the host PID 1 via + $machine_id from the same seed credential. -- pam_systemd_home: add module parameter to control whether to only accept - only password or only pcks11/fido2 auth, and then use this to hook nicely - into two of the three PAM stacks gdm provides. - See discussion at https://github.com/authselect/authselect/pull/311 +- in the long run: permit a system with /etc/machine-id linked to /dev/null, to + make it lose its identity, i.e. be anonymous. For this we'd have to patch + through the whole tree to make all code deal with the case where no machine + ID is available. -- maybe prohibit setuid() to the nobody user, to lock things down, via seccomp. - the nobody is not a user any code should run under, ever, as that user would - possibly get a lot of access to resources it really shouldn't be getting - access to due to the userns + nfs semantics of the user. Alternatively: use - the seccomp log action, and allow it. +- In vmspawn/nspawn/machined wait for X_SYSTEMD_UNIT_ACTIVE=ssh-active.target + and X_SYSTEMD_SIGNALS_LEVEL=2 as indication whether/when SSH and the POSIX + signals are available. Similar for D-Bus (but just use sockets.target for + that). Report as property for the machine. -- systemd-gpt-auto-generator: add kernel cmdline option to override block - device to dissect. also support dissecting a regular file. useccase: include - encrypted/verity root fs in UKI. +- initialize the hostname from the fs label of /, if /etc/hostname does not exist? -- sd-stub: - - detect if we are running with uefi console output on serial, and if so - automatically add console= to kernel cmdline matching the same port. - - add ".bootcfg" section for kernel bootconfig data (as per - https://docs.kernel.org/admin-guide/bootconfig.html) +- initrd: when transitioning from initrd to host, validate that + /lib/modules/`uname -r` exists, refuse otherwise -- tpm2: add (optional) support for generating a local signing key from PCR 15 - state. use private key part to sign PCR 7+14 policies. stash signatures for - expected PCR7+14 policies in EFI var. use public key part in disk encryption. - generate new sigs whenever db/dbx/mok/mokx gets updated. that way we can - securely bind against SecureBoot/shim state, without having to renroll - everything on each update (but we still have to generate one sig on each - update, but that should be robust/idempotent). needs rollback protection, as - usual. +- instead of going directly for DefineSpace when initializing nvpcrs, check if + they exist first. apparently DefineSpace is broken on some tpms, and also + creates log spam if the nvindex already exists. -- Lennart: big blog story about DDIs +- introduce /etc/boottab or so which lists block devices that bootctl + + kernel-install shall update the ESPs on (and register in EFI BootXYZ + variables), in addition to whatever is currently the booted /usr/. + systemd-sysupdate should also take it into consideration and update the + /usr/ images on all listed devices. -- Lennart: big blog story about building initrds +- introduce a .acpitable section for early ACPI table override -- Lennart: big blog story about "why systemd-boot" +- Introduce a CGroupRef structure, inspired by PidRef. Should contain cgroup + path, cgroup id, and cgroup fd. Use it to continuously pin all v2 cgroups via + a cgroup_ref field in the CGroupRuntime structure. Eventually switch things + over to do all cgroupfs access only via that structure's fd. -- bpf: see if we can use BPF to solve the syslog message cgroup source problem: - one idea would be to patch source sockaddr of all AF_UNIX/SOCK_DGRAM to - implicitly contain the source cgroup id. Another idea would be to patch - sendto()/connect()/sendmsg() sockaddr on-the-fly to use a different target - sockaddr. +- introduce a new group to own TPM devices -- bpf: see if we can address opportunistic inode sharing of immutable fs images - with BPF. i.e. if bpf gives us power to hook into openat() and return a - different inode than is requested for which we however it has same contents - then we can use that to implement opportunistic inode sharing among DDIs: - make all DDIs ship xattr on all reg files with a SHA256 hash. Then, also - dictate that DDIs should come with a top-level subdir where all reg files are - linked into by their SHA256 sum. Then, whenever an inode is opened with the - xattr set, check bpf table to find dirs with hashes for other prior DDIs and - try to use inode from there. +- introduce a small "systemd-installer" tool or so, that glues + systemd-repart-as-installer and bootctl-install into one. Would just + interactively ask user for target disk (with completion and so on), and then do + two varlink calls to the the two tools with the right parameters. To support + "offline" operation, optionally invoke the two tools directly as child + processes with varlink communication over socketpair(). This all should be + useful as blueprint for graphical installers which should do the same. -- extend the verity signature partition to permit multiple signatures for the - same root hash, so that people can sign a single image with multiple keys. +- introduce an option (or replacement) for "systemctl show" that outputs all + properties as JSON, similar to busctl's new JSON output. In contrast to that + it should skip the variant type string though. -- consider adding a new partition type, just for /opt/ for usage in system - extensions +- introduce DefaultSlice= or so in system.conf that allows changing where we + place our units by default, i.e. change system.slice to something + else. Similar, ManagerSlice= should exist so that PID1's own scope unit could + be moved somewhere else too. Finally machined and logind should get similar + options so that it is possible to move user session scopes and machines to a + different slice too by default. Use case: people who want to put resources on + the entire system, with the exception of one specific service. See: + https://lists.freedesktop.org/archives/systemd-devel/2018-February/040369.html -- dissection policy should enforce that unlocking can only take place by - certain means, i.e. only via pw, only via tpm2, or only via fido, or a - combination thereof. - -- make the systemd-repart "seed" value provisionable via credentials, so that - confidential computing environments can set it and deterministically - enforce the uuids for partitions created, so that they can calculate PCR 15 - ahead of time. - -- in the initrd: derive the default machine ID to pass to the host PID 1 via - $machine_id from the same seed credential. - -- Add systemd-sysupdate-initrd.service or so that runs systemd-sysupdate in the - initrd to bootstrap the initrd to populate the initial partitions. Some things - to figure out: - - Should it run on firstboot or on every boot? - - If run on every boot, should it use the sysupdate config from the host on - subsequent boots? - -- To mimic the new tpm2-measure-pcr= crypttab option and tpm2-measure-nvpcr= - veritytab option, add the same to integritytab (measuring the HMAC key if one - is used) - -- We should start measuring all services, containers, and system extensions we - activate. probably into PCR 13. i.e. add --tpm2-measure-pcr= or so to - systemd-nspawn, and MeasurePCR= to unit files. Should contain a measurement - of the activated configuration and the image that is being activated (in case - verity is used, hash of the root hash). - -- bootspec: permit graceful "update" from type #2 to type #1. If both a type #1 - and a type #2 entry exist under otherwise the exact same name, then use the - type #1 entry, and ignore the type #2 entry. This way, people can "upgrade" - from the UKI with all parameters baked in to a Type #1 .conf file with manual - parametrization, if needed. This matches our usual rule that admin config - should win over vendor defaults. +- introduce mntid_t, and make it 64bit, as apparently the kernel switched to + 64bit mount ids -- automatic boot assessment: add one more default success check that just waits - for a bit after boot, and blesses the boot if the system stayed up that long. +- introduce new ANSI sequence for communicating log level and structured error + metadata to terminals. -- systemd-boot: maybe add support for collapsing menu entries of the same OS - into one item that can be opened (like in a "tree view" UI element) or - collapsed. If only a single OS is installed, disable this mode, but if - multiple OSes are installed might make sense to default to it, so that user - is not immediately bombarded with a multitude of Linux kernel versions but - only one for each OS. +- introduce new structure Tpm2CombinedPolicy, that combines the various TPm2 + policy bits into one structure, i.e. public key info, pcr masks, pcrlock + stuff, pin and so on. Then pass that around in tpm2_seal() and tpm2_unseal(). -- systemd-tmpfiles: add concept for conditionalizing lines on factory reset - boot, or on first boot. +- introduce per-unit (i.e. per-slice, per-service) journal log size limits. -- we probably needs .pcrpkeyrd or so as additional PE section in UKIs, - which contains a separate public key for PCR values that only apply in the - initrd, i.e. in the boot phase "enter-initrd". Then, consumers in userspace - can easily bind resources to just the initrd. Similar, maybe one more for - "enter-initrd:leave-initrd" for resources that shall be accessible only - before unprivileged user code is allowed. (we only need this for .pcrpkey, - not for .pcrsig, since the latter is a list of signatures anyway). With that, - when you enroll a LUKS volume or similar, pick either the .pcrkey (for - coverage through all phases of the boot, but excluding shutdown), the - .pcrpkeyrd (for coverage in the initrd only) and .pcrpkeybt (for coverage - until users are allowed to log in). +- investigate whether the gnome pty helper should be moved into systemd, to provide cgroup support. -- Once the root fs LUKS volume key is measured into PCR 15, default to binding - credentials to PCR 15 in "systemd-creds" +- **journal:** + - consider introducing implicit _TTY= + _PPID= + _EUID= + _EGID= + _FSUID= + _FSGID= fields + - journald: also get thread ID from client, plus thread name + - journal: when waiting for journal additions in the client always sleep at least 1s or so, in order to minimize wakeups + - add API to close/reopen/get fd for journal client fd in libsystemd-journal. + - fall back to /dev/log based logging in libsystemd-journal, if we cannot log natively? + - declare the local journal protocol stable in the wiki interface chart + - sd-journal: speed up sd_journal_get_data() with transparent hash table in bg + - journald: when dropping msgs due to ratelimit make sure to write + "dropped %u messages" not only when we are about to print the next + message that works, but already after a short timeout + - check if we can make journalctl by default use --follow mode inside of less if called without args? + - maybe add API to send pairs of iovecs via sd_journal_send + - journal: add a setgid "systemd-journal" utility to invoke from libsystemd-journal, which passes fds via STDOUT and does PK access + - journalctl: support negative filtering, i.e. FOOBAR!="waldo", + and !FOOBAR for events without FOOBAR. + - journal: store timestamp of journal_file_set_offline() in the header, + so it is possible to display when the file was last synced. + - journal-send.c, log.c: when the log socket is clogged, and we drop, count this and write a message about this when it gets unclogged again. + - journal: find a way to allow dropping history early, based on priority, other rules + - journal: When used on NFS, check payload hashes + - journald: add kernel cmdline option to disable ratelimiting for debug purposes + - refuse taking lower-case variable names in sd_journal_send() and friends. + - journald: we currently rotate only after MaxUse+MaxFilesize has been reached. + - journal: deal nicely with byte-by-byte copied files, especially regards header + - journal: sanely deal with entries which are larger than the individual file size, but where the components would fit + - Replace utmp, wtmp, btmp, and lastlog completely with journal + - journalctl: instead --after-cursor= maybe have a --cursor=XYZ+1 syntax? + - when a kernel driver logs in a tight loop, we should ratelimit that too. + - journald: optionally, log debug messages to /run but everything else to /var + - journald: when we drop syslog messages because the syslog socket is + full, make sure to write how many messages are lost as first thing + to syslog when it works again. + - journald: allow per-priority and per-service retention times when rotating/vacuuming + - journald: make use of uid-range.h to manage uid ranges to split + journals in. + - journalctl: add the ability to look for the most recent process of a binary. + journalctl /usr/bin/X11 --invocation=-1 + - systemctl: change 'status' to show logs for the last invocation, not a fixed + number of lines + - improve journalctl performance by loading journal files + lazily. Encode just enough information in the file name, so that we + do not have to open it to know that it is not interesting for us, for + the most common operations. + - man: document that corrupted journal files is nothing to act on + - rework journald sigbus stuff to use mutex + - Set RLIMIT_NPROC for systemd-journal-xyz, and all other of our + services that run under their own user ids, and use User= (but only + in a world where userns is ubiquitous since otherwise we cannot + invoke those daemons on the host AND in a container anymore). Also, + if LimitNPROC= is used without User= we should warn and refuse + operation. + - journalctl --verify: don't show files that are currently being + written to as FAIL, but instead show that they are being written to. + - add journalctl -H that talks via ssh to a remote peer and passes through + binary logs data + - add a version of --merge which also merges /var/log/journal/remote + - journalctl: -m should access container journals directly by enumerating + them via machined, and also watch containers coming and going. + Benefit: nspawn --ephemeral would start working nicely with the journal. + - assign MESSAGE_ID to log messages about failed services + - check if loop in decompress_blob_xz() is necessary + - log pidfid as another field, i.e. _PIDFDID= + - beef up ClientContext logic to store pidfd_id of peer, to validate + we really use the right cache entry + - log client's pidfd id as a new automatic field _PIDFDID= or so. + - split up ClientContext cache in two: one cache keyed by pid/pidfdid + with process information, and another one keyed by cgroup path/cgroupid with + cgroup information. This way if a service consisting of many logging + processes can take benefit of the cgroup caching. + - support RFC3164 fully for the incoming syslog transport, see + https://github.com/systemd/systemd/issues/19251#issuecomment-816601955 + - add varlink service that allows subscribing to certain log events, + for example matching by message ID, or log level returns a list of journal + cursors as they happen. + - also collect CLOCK_BOOTTIME timestamps per log entry. Then, derive + "corrected" CLOCK_REALTIME information on display from that and the timestamp + info of the newest entry of the specific boot (as identified by the boot + ID). This way, if a system comes up without a valid clock but acquires a + better clock later, we can "fix" older entry timestamps on display, by + calculating backwards. We cannot use CLOCK_MONOTONIC for this, since it does + not account for suspend phases. This would then also enable us to correct the + kmsg timestamping we consume (where we erroneously assume the clock was in + CLOCK_MONOTONIC, but it actually is CLOCK_BOOTTIME as per kernel). + - generate recognizable log events whenever we shutdown journald + cleanly, and when we migrate run → var. This way tools can verify that a + previous boot terminated cleanly, because either of these two messages must + be safely written to disk, then. + - do journal file writing out-of-process, with one writer process per + client UID, so that synthetic hash table collisions can slow down a specific + user's journal stream down but not the others. + - make sure -f ends when the container indicated by -M terminates + - sigbus API via a signal-handler safe function that people may call + from the SIGBUS handler -- add support for asymmetric LUKS2 TPM based encryption. i.e. allow preparing - an encrypted image on some host given a public key belonging to a specific - other host, so that only hosts possessing the private key in the TPM2 chip - can decrypt the volume key and activate the volume. Use case: systemd-confext - for a central orchestrator to generate confext images securely that can only - be activated on one specific host (which can be used for installing a bunch - of creds in /etc/credstore/ for example). Extending on this: allow binding - LUKS2 TPM based encryption also to the TPM2 internal clock. Net result: - prepare a confext image that can only be activated on a specific host that - runs a specific software in a specific time window. confext would be - automatically invalidated outside of it. +- journalctl/timesyncd: whenever timesyncd acquires a synchronization from NTP, + create a structured log entry that contains boot ID, monotonic clock and + realtime clock (I mean, this requires no special work, as these three fields + are implicit). Then in journalctl when attempting to display the realtime + timestamp of a log entry, first search for the closest later log entry + of this kinda that has a matching boot id, and convert the monotonic clock + timestamp of the entry to the realtime clock using this info. This way we can + retroactively correct the wallclock timestamps, in particular for systems + without RTC, i.e. where initially wallclock timestamps carry rubbish, until + an NTP sync is acquired. -- maybe add a "systemd-report" tool, that generates a TPM2-backed "report" of - current system state, i.e. a combination of PCR information, local system - time and TPM clock, running services, recent high-priority log - messages/coredumps, system load/PSI, signed by the local TPM chip, to form an - enhanced remote attestation quote. Use case: a simple orchestrator could use - this: have the report tool upload these reports every 3min somewhere. Then - have the orchestrator collect these reports centrally over a 3min time - window, and use them to determine what which node should now start/stop what, - and generate a small confext for each node, that uses Uphold= to pin services - on each node. The confext would be encrypted using the asymmetric encryption - proposed above, so that it can only be activated on the specific host, if the - software is in a good state, and within a specific time frame. Then run a - loop on each node that sends report to orchestrator and then sysupdate to - update confext. Orchestrator would be stateless, i.e. operate on desired - config and collected reports in the last 3min time window only, and thus can - be trivially scaled up since all instances of the orchestrator should come to - the same conclusions given the same inputs of reports/desired workload info. - Could also be used to deliver Wireguard secrets and thus to clients, thus - permitting zero-trust networking: secrets are rolled over via confext updates, - and via the time window TPM logic invalidated if node doesn't keep itself - updated, or becomes corrupted in some way. +- landlock: for unprivileged systemd (i.e. systemd --user), use landlock to + implement ProtectSystem=, ProtectHome= and so on. Landlock does not require + privs, and we can implement pretty similar behaviour. Also, maybe add a mode + where ProtectSystem= combined with an explicit PrivateMounts=no could request + similar behaviour for system services, too. -- in the initrd, once the rootfs encryption key has been measured to PCR 15, - derive default machine ID to use from it, and pass it to host PID 1. +- landlock: lock down RuntimeDirectory= via landlock, so that services lose + ability to write anywhere else below /run/. Similar for + StateDirectory=. Benefit would be clear delegation via unit files: services + get the directories they get, and nothing else even if they wanted to. -- automatically propagate LUKS password credential into cryptsetup from host - (i.e. SMBIOS type #11, …), so that one can unlock LUKS via VM hypervisor - supplied password. +- Lennart: big blog story about "why systemd-boot" -- add ability to path_is_valid() to classify paths that refer to a dir from - those which may refer to anything, and use that in various places to filter - early. i.e. stuff ending in "/", "/." and "/.." definitely refers to a - directory, and paths ending that way can be refused early in many contexts. +- Lennart: big blog story about building initrds -- systemd-measure: add --pcrpkey-auto as an alternative to --pcrpkey=, where it - would just use the same public key specified with --public-key= (or the one - automatically derived from --private-key=). +- Lennart: big blog story about DDIs -- Add "purpose" flag to partition flags in discoverable partition spec that - indicate if partition is intended for sysext, for portable service, for - booting and so on. Then, when dissecting DDI allow specifying a purpose to - use as additional search condition. Use case: images that combined a sysext - partition with a portable service partition in one. +- let's not GC a unit while its ratelimits are still pending -- On boot, auto-generate an asymmetric key pair from the TPM, - and use it for validating DDIs and credentials. Maybe upload it to the kernel - keyring, so that the kernel does this validation for us for verity and kernel - modules +- libsystemd-journal, libsystemd-login, libudev: add calls to easily attach these objects to sd-event event loops - lock down acceptable encrypted credentials at boot, via simple allowlist, maybe on kernel command line: systemd.import_encrypted_creds=foobar.waldo,tmpfiles.extra to protect locked down kernels from credentials generated on the host with a weak kernel -- Merge systemd-creds options --uid= (which accepts user names) and --user. - -- Add support for extra verity configuration options to systemd-repart (FEC, - hash type, etc) - -- chase(): take inspiration from path_extract_filename() and return - O_DIRECTORY if input path contains trailing slash. - -- measure credentials picked up from SMBIOS to some suitable PCR +- lock down swtpm a bit to make it harder to extract keys from it as it is + running. i.e. make ptracing + termination hard from the outside. also run + swtpm as unpriv user (not trivial, probably requires patch swtpm, as it needs + to allocate vtpm device), to lock it down from the inside. -- measure GPT and LUKS headers somewhere when we use them (i.e. in - systemd-gpt-auto-generator/systemd-repart and in systemd-cryptsetup?) +- loginctl: show "service identifier" in tabular list-sessions output, to make + run0 sessions easily visible. -- pick up creds from EFI vars +- loginctl: show argv[] of "leader" process in tabular list-sessions output -- Add and pickup tpm2 metadata for creds structure. - -- systemd-measure tool: - - pre-calculate PCR 12 (command line) + PCR 13 (sysext) the same way we can precalculate PCR 11 - -- maybe add new flags to gpt partition tables for rootfs and usrfs indicating - purpose, i.e. whether something is supposed to be bootable in a VM, on - baremetal, on an nspawn-style container, if it is a portable service image, - or a sysext for initrd, for host os, or for portable container. Then hook - portabled/… up to udev to watch block devices coming up with the flags set, and - use it. +- **logind:** + - logind: optionally, ignore idle-hint logic for autosuspend, block suspend as long as a session is around + - logind: wakelock/opportunistic suspend support + - Add pretty name for seats in logind + - logind: allow showing logout dialog from system? + - add Suspend() bus calls which take timestamps to fix double suspend issues when somebody hits suspend and closes laptop quickly. + - if pam_systemd is invoked by su from a process that is outside of a + any session we should probably just become a NOP, since that's + usually not a real user session but just some system code that just + needs setuid(). + - logind: make the Suspend()/Hibernate() bus calls wait for the for + the job to be completed. before returning, so that clients can wait + for "systemctl suspend" to finish to know when the suspending is + complete. + - logind: when the power button is pressed short, just popup a + logout dialog. If it is pressed for 1s, do the usual + shutdown. Inspiration are Macs here. + - expose "Locked" property on logind session objects + - maybe allow configuration of the StopTimeout for session scopes + - rename session scope so that it includes the UID. THat way + the session scope can be arranged freely in slices and we don't have + make assumptions about their slice anymore. + - follow PropertiesChanged state more closely, to deal with quick logouts and + relogins + - (optionally?) spawn seat-manager@$SEAT.service whenever a seat shows up that as CanGraphical set + - invoke a service manager for "area" logins too. i.e. instantiate + user@.service also for logins where XDG_AREA is set, in per-area fashion, and + ref count it properly. Benefit: graphical logins should start working with + the area logic. + - when logging in, always take an fd to the home dir, to keep the dir + busy, so that autofs release can never happen. (this is generally a good + idea, and specifically works around the fact the autofs ignores busy by mount + namespaces) -- add "systemd-sysext identify" verb, that you can point on any file in /usr/ - and that determines from which overlayfs layer it originates, which image, and with - what it was signed. +- look at nsresourced, mountfsd, homed, importd, portabled, and try to come up + with a way how the forked off worker processes can be moved into transient + services with sandboxing, without breaking notify socket stuff and so on. -- systemd-creds: extend encryption logic to support asymmetric - encryption/authentication. Idea: add new verb "systemd-creds public-key" - which generates a priv/pub key pair on the TPM2 and stores the priv key - locally in /var. It then outputs a certificate for the pub part to stdout. - This can then be copied/taken elsewhere, and can be used for encrypting creds - that only the host on its specific hw can decrypt. Then, support a drop-in - dir with certificates that can be used to authenticate credentials. Flow of - operations is then this: build image with owner certificate, then after - boot up issue "systemd-creds public-key" to acquire pubkey of the machine. - Then, when passing data to the machine, sign with privkey belonging to one of - the dropped in certs and encrypted with machine pubkey, and pass to machine. - Machine is then able to authenticate you, and confidentiality is guaranteed. +- **machined:** + - add an API so that libvirt-lxc can inform us about network interfaces being + removed or added to an existing machine + - "machinectl migrate" or similar to copy a container from or to a + difference host, via ssh + - introduce systemd-nspawn-ephemeral@.service, and hook it into + "machinectl start" with a new --ephemeral switch + - "machinectl status" should also show internal logs of the container in + question + - "machinectl history" + - "machinectl diff" + - "machinectl commit" that takes a writable snapshot of a tree, invokes a + shell in it, and marks it read-only after use + - gc for OCI layers that are not referenced anymore by any .mstack/ links. + - optionally track nspawn unix-export/ runtime for each machined, and + then update systemd-ssh-proxy so that it can connect to that. -- building on top of the above, the pub/priv key pair generated on the TPM2 - should probably also one you can use to get a remote attestation quote. +- make "bootctl install" + "bootctl update" useful for installing shim too. For + that introduce new dir /usr/lib/systemd/efi/extra/ which we copy mostly 1:1 + into the ESP at install time. Then make the logic smart enough so that we + don't overwrite bootx64.efi with our own if the extra tree already contains + one. Also, follow symlinks when copying, so that shim rpm can symlink their + stuff into our dir (which is safe since the target ESP is generally VFAT and + thus does not have symlinks anyway). Later, teach the update logic to look at + the ELF package metadata (which we also should include in all PE files, see + above) for version info in all *.EFI files, and use it to only update if + newer. -- Process credentials in: - - crypttab-generator: allow defining additional crypttab-like volumes via - credentials (similar: verity-generator, integrity-generator). Use - fstab-generator logic as inspiration. - - run-generator: allow defining additional commands to run via a credential - - resolved: allow defining additional /etc/hosts entries via a credential (it - might make sense to then synthesize a new combined /etc/hosts file in /run - and bind mount it on /etc/hosts for other clients that want to read it. - - repart: allow defining additional partitions via credential - - timesyncd: pick NTP server info from credential - - portabled: read a credential "portable.extra" or so, that takes a list of - file system paths to enable on start. - - make systemd-fstab-generator look for a system credential encoding root= or - usr= - - in gpt-auto-generator: check partition uuids against such uuids supplied via - sd-stub credentials. That way, we can support parallel OS installations with - pre-built kernels. +- make cryptsetup lower --iter-time -- define a JSON format for units, separating out unit definitions from unit - runtime state. Then, expose it: +- Make it possible to set the keymap independently from the font on + the kernel cmdline. Right now setting one resets also the other. - 1. Add Describe() method to Unit D-Bus object that returns a JSON object - about the unit. - 2. Expose this natively via Varlink, in similar style - 3. Use it when invoking binaries (i.e. make PID 1 fork off systemd-executor - binary which reads the JSON definition and runs it), to address the cow - trap issue and the fact that NSS is actually forbidden in - forked-but-not-exec'ed children - 4. Add varlink API to run transient units based on provided JSON definitions +- make killing more debuggable: when we kill a service do so setting the + .si_code field with a little bit of info. Specifically, we can set a + recognizable value to first of all indicate that it's systemd that did the + killing. Secondly, we can give a reason for the killing, i.e. OOM or so, and + also the phase we are in, and which process we think we are killing (i.e. + main vs control process, useful in case of sd_notify() MAINPID= debugging). + Net result: people who try to debug why their process gets killed should have + some minimal, nice metadata directly on the signal event. -- Add SUPPORT_END_URL= field to os-release with more *actionable* information - what to do if support ended +- make MAINPID= message reception checks even stricter: if service uses User=, + then check sending UID and ignore message if it doesn't match the user or + root. -- pam_systemd: on interactive logins, maybe show SUPPORT_END information at - login time, à la motd +- make nspawn containers, portable services and vmspawn VMs optionally survive + soft reboot wholesale. -- mount /var/ from initrd, so that we can apply sysext and stuff before the - initrd transition. Specifically: - 1. There should be a var= kernel cmdline option, matching root= and usr= - 2. systemd-gpt-auto-generator should auto-mount /var if it finds it on disk - 3. mount.x-initrd mount option in fstab should be implied for /var +- Make nspawn to a frontend for systemd-executor, so that we have to ways into + the executor: via unit files/dbus/varlink through PID1 and via cmdline/OCI + through nspawn. - make persistent restarts easier by adding a new setting OpenPersistentFile= or so, which allows opening one or more files that is "persistent" across @@ -1363,636 +1416,639 @@ the files are reboot. The files would be backed by tmpfs, pmem or /var depending on desired level of persistency. -- if we fork of a service with StandardOutput=journal, and it forks off a - subprocess that quickly dies, we might not be able to identify the cgroup it - comes from, but we can still derive that from the stdin socket its output - came from. We apparently don't do that right now. +- make repeated alt-ctrl-del presses printing a dump -- add PR_SET_DUMPABLE service setting +- make rfkill uaccess controllable by default, i.e. steal rule from + gnome-bluetooth and friends -- homed/userdb: maybe define a "companion" dir for home directories where apps - can safely put privileged stuff in. Would not be writable by the user, but - still conceptually belong to the user. Would be included in user's quota if - possible, even if files are not owned by UID of user. Use case: container - images that owned by arbitrary UIDs, and are owned/managed by the users, but - are not directly belonging to the user's UID. Goal: we shouldn't place more - privileged dirs inside of unprivileged dirs, and thus containers really - should not be placed inside of traditional UNIX home dirs (which are owned by - users themselves) but somewhere else, that is separate, but still close - by. Inform user code about path to this companion dir via env var, so that - container managers find it. the ~/.identity file is also a candidate for a - file to move there, since it is managed by privileged code (i.e. homed) and - not unprivileged code. +- Make run0 forward various signals to the forked process so that sending + signals to a child process works roughly the same regardless of whether the + child process is spawned via run0 or not. -- maybe add support for binding and connecting AF_UNIX sockets in the file - system outside of the 108ch limit. When connecting, open O_PATH fd to socket - inode first, then connect to /proc/self/fd/XYZ. When binding, create symlink - to target dir in /tmp, and bind through it. +- make sure systemd-ask-password-wall does not shutdown systemd-ask-password-console too early -- add a proper concept of a "developer" mode, i.e. where cryptographic - protections of the root OS are weakened after interactive confirmation, to - allow hackers to allow their own stuff. idea: allow entering developer mode - only via explicit choice in boot menu: i.e. add explicit boot menu item for - it. When developer mode is entered, generate a key pair in the TPM2, and add - the public part of it automatically to keychain of valid code signature keys - on subsequent boots. Then provide a tool to sign code with the key in the - TPM2. Ensure that boot menu item is the only way to enter developer mode, by - binding it to locality/PCRs so that keys cannot be generated otherwise. +- make sure the ratelimit object can deal with USEC_INFINITY as way to turn off things -- services: add support for cryptographically unlocking per-service directories - via TPM2. Specifically, for StateDirectory= (and related dirs) use fscrypt to - set up the directory so that it can only be accessed if host and app are in - order. +- make systemd work nicely without /bin/sh, logins and associated shell tools around + - make sure debug shell service (sushell) has a nice failure mode, prints a message and reboots + - varlink interface for "systemctl start" and friends + - https://github.com/util-linux/util-linux/issues/4117 -- update HACKING.md to suggest developing systemd with the ideas from: - https://0pointer.net/blog/testing-my-system-code-in-usr-without-modifying-usr.html - https://0pointer.net/blog/running-an-container-off-the-host-usr.html +- make the systemd-repart "seed" value provisionable via credentials, so that + confidential computing environments can set it and deterministically + enforce the uuids for partitions created, so that they can calculate PCR 15 + ahead of time. -- for vendor-built signed initrds: - - kernel-install should be able to install encrypted creds automatically for - machine id, root pw, rootfs uuid, resume partition uuid, and place next to - EFI kernel, for sd-stub to pick them up. These creds should be locked to - the TPM, and bind to the right PCR the kernel is measured to. - - kernel-install should be able to pick up initrd sysexts automatically and - place them next to EFI kernel, for sd-stub to pick them up. - - systemd-fstab-generator should look for rootfs device to mount in creds - - systemd-resume-generator should look for resume partition uuid in creds +- make use of ethtool veth peer info in machined, for automatically finding out + host-side interface pointing to the container. -- Maybe extend the service protocol to support handling of some specific SIGRT - signal for setting service log level, that carries the level via the - sigqueue() data parameter. Enable this via unit file setting. +- make use of new glibc 2.32 APIs sigabbrev_np(). -- sd_notify/vsock: maybe support binding to AF_VSOCK in Type=notify services, - then passing $NOTIFY_SOCKET and $NOTIFY_GUESTCID with PID1's cid (typically - fixed to "2", i.e. the official host cid) and the expected guest cid, for the - two sides of the channel. The latter env var could then be used in an - appropriate qemu cmdline. That way qemu payloads could talk sd_notify() - directly to host service manager. +- make vmspawn/nspawn/importd/machined a bit more usable in a WSL-like + fashion. i.e. teach unpriv systemd-vmspawn/systemd-nspawn a reasonable + --bind-user= behaviour that mounts the calling user through into the + machine. Then, ship importd with a small database of well known distro images + along with their pinned signature keys. Then add some minimal glue that binds + this together: downloads a suitable image if not done so yet, starts it in + the bg via vmspawn/nspawn if not done so yet and then requests a shell inside + it for the invoking user. -- sd-device: - - add an API for acquiring list of child devices, given a device - objects (i.e. all child dirents that dirs or symlinks to dirs) - - maybe pin the sysfs dir with an fd, during the entire runtime of - an sd_device, then always work based on that. - - should return the devnum type (i.e. 'b' or 'c') via some API for an - sd_device object, so that data passed into sd_device_new_from_devnum() can - also be queried. +- man: rework os-release(5), and clearly separate our extension-release.d/ and + initrd-release parts, i.e. list explicitly which fields are about what. -- sysext: measure all activated sysext into a TPM PCR +- man: the documentation of Restart= currently is very misleading and suggests the tools from ExecStartPre= might get restarted. -- systemd-dissect: show available versions inside of a disk image, i.e. if - multiple versions are around of the same resource, show which ones. (in other - words: show partition labels). +- maybe add a "systemd-report" tool, that generates a TPM2-backed "report" of + current system state, i.e. a combination of PCR information, local system + time and TPM clock, running services, recent high-priority log + messages/coredumps, system load/PSI, signed by the local TPM chip, to form an + enhanced remote attestation quote. Use case: a simple orchestrator could use + this: have the report tool upload these reports every 3min somewhere. Then + have the orchestrator collect these reports centrally over a 3min time + window, and use them to determine what which node should now start/stop what, + and generate a small confext for each node, that uses Uphold= to pin services + on each node. The confext would be encrypted using the asymmetric encryption + proposed above, so that it can only be activated on the specific host, if the + software is in a good state, and within a specific time frame. Then run a + loop on each node that sends report to orchestrator and then sysupdate to + update confext. Orchestrator would be stateless, i.e. operate on desired + config and collected reports in the last 3min time window only, and thus can + be trivially scaled up since all instances of the orchestrator should come to + the same conclusions given the same inputs of reports/desired workload info. + Could also be used to deliver Wireguard secrets and thus to clients, thus + permitting zero-trust networking: secrets are rolled over via confext updates, + and via the time window TPM logic invalidated if node doesn't keep itself + updated, or becomes corrupted in some way. -- systemd-dissect: add --cat switch for dumping files such as /etc/os-release +- maybe add a new standard slice where process that are started in the initrd + and stick around for the whole system runtime (i.e. root fs storage daemons, + the bpf loader daemon discussed above, and such) are placed. maybe + protected.slice or so? Then write docs that suggest that services like this + set Slice=protected.sice, RefuseManualStart=yes, RefuseManualStop=yes and a + couple of other things. -- per-service sandboxing option: ProtectIds=. If used, will overmount - /etc/machine-id and /proc/sys/kernel/random/boot_id with synthetic files, to - make it harder for the service to identify the host. Depending on the user - setting it should be fully randomized at invocation time, or a hash of the - real thing, keyed by the unit name or so. Of course, there are other ways to - get these IDs (e.g. journal) or similar ids (e.g. MAC addresses, DMI ids, CPU - ids), so this knob would only be useful in combination with other lockdown - options. Particularly useful for portable services, and anything else that - uses RootDirectory= or RootImage=. (Might also over-mount - /sys/class/dmi/id/*{uuid,serial} with /dev/null). +- maybe add call sd_journal_set_block_timeout() or so to set SO_SNDTIMEO for + the sd-journal logging socket, and, if the timeout is set to 0, sets + O_NONBLOCK on it. That way people can control if and when to block for + logging. -- doc: prep a document explaining resolved's internal objects, i.e. Query - vs. Question vs. Transaction vs. Stream and so on. +- maybe add kernel cmdline params: to force random seed crediting -- doc: prep a document explaining PID 1's internal logic, i.e. transactions, - jobs, units +- maybe add new flags to gpt partition tables for rootfs and usrfs indicating + purpose, i.e. whether something is supposed to be bootable in a VM, on + baremetal, on an nspawn-style container, if it is a portable service image, + or a sysext for initrd, for host os, or for portable container. Then hook + portabled/… up to udev to watch block devices coming up with the flags set, and + use it. -- automatically ignore threaded cgroups in cg_xyz(). +- maybe add support for binding and connecting AF_UNIX sockets in the file + system outside of the 108ch limit. When connecting, open O_PATH fd to socket + inode first, then connect to /proc/self/fd/XYZ. When binding, create symlink + to target dir in /tmp, and bind through it. -- add linker script that implicitly adds symbol for build ID and new coredump - json package metadata, and use that when logging +- Maybe add SwitchRootEx() as new bus call that takes env vars to set for new + PID 1 as argument. When adding SwitchRootEx() we should maybe also add a + flags param that allows disabling and enabling whether serialization is + requested during switch root. -- Enable RestrictFileSystems= for all our long-running services (similar: - RestrictNetworkInterfaces=) +- maybe allow timer units with an empty Units= setting, so that they + can be used for resuming the system but nothing else. -- Add systemd-analyze security checks for RestrictFileSystems= and - RestrictNetworkInterfaces= +- maybe beef up sd-event: optionally, allow sd-event to query the timestamp of + next pending datagram inside a SOCK_DGRAM IO fd, and order event source + dispatching by that. Enable this on the native + syslog sockets in journald, + so that we add correct ordering between the two. Use MSG_PEEK + SCM_TIMESTAMP + for this. -- cryptsetup/homed: implement TOTP authentication backed by TPM2 and its - internal clock. +- maybe define a /etc/machine-info field for the ANSI color to associate with a + hostname. Then use it for the shell prompt to highlight the hostname. If no + color is explicitly set, hash a color automatically from the hostname as a + fallback, in a reasonable way. Take inspiration from the ANSI_COLOR= field + that already exists in /etc/os-release, i.e. use the same field name and + syntax. When hashing the color, use the hsv_to_rgb() helper we already have, + fixate S and V to something reasonable and constant, and derive the H from + the hostname. Ultimate goal with this: give people a visual hint about the + system they are on if the have many to deal with, by giving each a color + identity. This code should be placed in hostnamed, so that clients can query + the color via varlink or dbus. -- man: rework os-release(5), and clearly separate our extension-release.d/ and - initrd-release parts, i.e. list explicitly which fields are about what. +- maybe do not install getty@tty1.service symlink in /etc but in /usr? -- sysext: before applying a sysext, do a superficial validation run so that - things are not rearranged to wildy. I.e. protect against accidental fuckups, - such as masking out /usr/lib/ or so. We should probably refuse if existing - inodes are replaced by other types of inodes or so. +- maybe extend .path units to expose fanotify() per-mount change events -- userdb: when synthesizing NSS records, pick "best" password from defined - passwords, not just the first. i.e. if there are multiple defined, prefer - unlocked over locked and prefer non-empty over empty. +- maybe extend the capsule concept to the per-user instance too: invokes a + systemd --user instance with a subdir of $HOME as $HOME, and a subdir of + $XDG_RUNTIME_DIR as $XDG_RUNTIME_DIR. -- userdbd: implement an additional varlink service socket that provides the - host user db in restricted form, then allow this to be bind mounted into - sandboxed environments that want the host database in minimal form. All - records would be stripped of all meta info, except the basic UID/name - info. Then use this in portabled environments that do not use PrivateUsers=1. +- Maybe extend the service protocol to support handling of some specific SIGRT + signal for setting service log level, that carries the level via the + sigqueue() data parameter. Enable this via unit file setting. -- portabled: when extracting unit files and copying to system.attached, if a - .p7s is available in the image, use it to protect the system.attached copy - with fs-verity, so that it cannot be tampered with +- maybe implicitly attach monotonic+realtime timestamps to outgoing messages in + log.c and sd-journal-send -- /etc/veritytab: allow that the roothash column can be specified as fs path - including a path to an AF_UNIX path, similar to how we do things with the - keys of /etc/crypttab. That way people can store/provide the roothash - externally and provide to us on demand only. +- maybe introduce "@icky" as a seccomp filter group, which contains acct() and + certain other syscalls that aren't quite obsolete, but certainly icky. -- rework recursive read-only remount to use new mount API +- Maybe introduce a helper safe_exec() or so, which is to execve() which + safe_fork() is to fork(). And then make revert the RLIMIT_NOFILE soft limit + to 1K implicitly, unless explicitly opted-out. -- when mounting disk images: if IMAGE_ID/IMAGE_VERSION is set in os-release - data in the image, make sure the image filename actually matches this, so - that images cannot be misused. +- maybe introduce a new partition that we can store debug logs and similar at + the very last moment of shutdown. idea would be to store reference to block + device (major + minor + partition id + diskseq?) in /run somewhere, than use + that from systemd-shutdown, just write a raw JSON blob into the partition. + Include timestamp, boot id and such, plus kmsg. on next boot immediately + import into journal. maybe use timestamp for making clock more monotonic. + also use this to detect unclean shutdowns, boot into special target if + detected -- sysupdate: - - add fuzzing to the pattern parser - - support casync as download mechanism - - "systemd-sysupdate update --all" support, that iterates through all components - defined on the host, plus all images installed into /var/lib/machines/, - /var/lib/portable/ and so on. - - Allow invocation with a single transfer definition, i.e. with - --definitions= pointing to a file rather than a dir. - - add ability to disable implicit decompression of downloaded artifacts, - i.e. a Compress=no option in the transfer definitions - - download multiple arbitrary patterns from same source - - SHA256SUMS format with bearer tokens for each resource to download - - decrypt SHA256SUMS with key from tpm - - clean up stuff on disk that disappears from SHA256SUMS - - turn http backend stuff int plugin via varlink - - for each transfer support looking at multiple sources, - pick source with newest entry. If multiple sources have the same entry, use - first configured source. Usecase: "sideload" components from local dirs, - without disabling remote sources. - - support "revoked" items, which cause the client to - downgrade/upgrade - - introduce per-user version that can update per-user installed dDIs - - make transport pluggable, so people can plug casync or - similar behind it, instead of http. +- maybe introduce a new per-unit drop-in directory .confext.d/ that may contain + symlinks to confext images to enable for the unit. -- in sd-id128: also parse UUIDs in RFC4122 URN syntax (i.e. chop off urn:uuid: prefix) +- Maybe introduce an InodeRef structure inspired by PidRef, which references a + specific inode, and combines: a path, an O_PATH fd, and possibly a FID into + one. Why? We often pass around path and fd separately in chaseat() and similar + calls. Because passing around both separately is cumbersome we sometimes only + one pass one, once the other and sometimes both. It would make the code a lot + simpler if we could path both around at the same time in a simple way, via an + InodeRef which *both* pins the inode via an fd, *and* gives us a friendly + name for it. -- systemd-sysext: optionally, run it in initrd already, before transitioning - into host, to open up possibility for services shipped like that. +- maybe introduce an OSC sequence that signals when we ask for a password, so + that terminal emulators can maybe connect a password manager or so, and + highlight things specially. -- whenever we receive fds via SCM_RIGHTS make sure none got dropped due to the - reception limit the kernel silently enforces. +- maybe introduce container-shell@.service or so, to match + container-getty.service but skips authentication, so you get a shell prompt + directly. Usecase: wsl-like stuff (they have something pretty much like + that). Question: how to pick user for this. Instance parameter? somehow from + credential (would probably require some binary that converts credential to + User= parameter? -- Add service unit setting ConnectStream= which takes IP addresses and connects to them. +- maybe introduce xattrs that can be set on the root dir of the root fs + partition that declare the volatility mode to use the image in. Previously I + thought marking this via GPT partition flags but that's not ideal since + that's outside of the LUKS encryption/verity verification, and we probably + shouldn't operate in a volatile mode unless we got told so from a trusted + source. -- Similar, Load= which takes literal data in text or base64 format, and puts it - into a memfd, and passes that. This enables some fun stuff, such as embedding - bash scripts in unit files, by combining Load= with ExecStart=/bin/bash - /proc/self/fd/3 +- maybe prohibit setuid() to the nobody user, to lock things down, via seccomp. + the nobody is not a user any code should run under, ever, as that user would + possibly get a lot of access to resources it really shouldn't be getting + access to due to the userns + nfs semantics of the user. Alternatively: use + the seccomp log action, and allow it. -- add a ConnectSocket= setting to service unit files, that may reference a - socket unit, and which will connect to the socket defined therein, and pass - the resulting fd to the service program via socket activation proto. +- maybe reconsider whether virtualization consoles (hvc1) are considered local + or remote. i.e. are they more like an ssh login, or more like a /dev/tty1 + login? Lennart used to believe the former, but maybe the latter is more + appropriate? This has effect on polkit interactivity, since it would mean + questions via hvc0 would suddenly use the local polkit property. But this + also raises the question whether such sessions shall be considered active or + not -- Add a concept of ListenStream=anonymous to socket units: listen on a socket - that is deleted in the fs. Use case would be with ConnectSocket= above. +- Maybe rename pkcs7 and public verbs of systemd-keyutil to be more verb like. -- importd: support image signature verification with PKCS#7 + OpenBSD signify - logic, as alternative to crummy gpg +- maybe rework systemd-modules-load to be a generator that just instantiates + modprobe@.service a bunch of times -- add "systemd-analyze debug" + AttachDebugger= in unit files: The former - specifies a command to execute; the latter specifies that an already running - "systemd-analyze debug" instance shall be contacted and execution paused - until it gives an OK. That way, tools like gdb or strace can be safely be - invoked on processes forked off PID 1. +- maybe teach repart.d/ dropins a new setting MakeMountNodes= or so, which is + just like MakeDirectories=, but uses an access mode of 0000 and sets the +i + chattr bit. This is useful as protection against early uses of /var/ or /tmp/ + before their contents is mounted. -- expose MS_NOSYMFOLLOW in various places +- maybe trigger a uevent "change" on a device if "systemctl reload xyz.device" + is issued. -- credentials system: - - acquire from EFI variable? - - acquire via ask-password? - - acquire creds via keyring? - - pass creds via keyring? - - pass creds via memfd? - - acquire + decrypt creds from pkcs11? - - make macsec code in networkd read key via creds logic (copy logic from - wireguard) - - make gatewayd/remote read key via creds logic - - add sd_notify() command for flushing out creds not needed anymore - - if we ever acquire a secure way to derive cgroup id of socket - peers (i.e. SO_PEERCGROUPID), then extend the "scoped" credential logic to - allow cgroup-scoped (i.e. app or service scoped) credentials. Then, as next - step use this to implement per-app/per-service encrypted directories, where - we set up fscrypt on the StateDirectory= with a randomized key which is - stored as xattr on the directory, encrypted as a credential. - - optionally include a per-user secret in scoped user-credential - encryption keys. should come from homed in some way, derived from the luks - volume key or fscrypt directory key. - - add a flag to the scoped credentials that if set require PK - reauthentication when unlocking a secret. - - rework docs. The list in - https://systemd.io/CREDENTIALS/#well-known-credentials is very stale. - Document credentials in individual man pages, generate list as in - systemd.directives. +- maybe: in PID1, when we detect we run in an initrd, make superblock read-only + early on, but provide opt-out via kernel cmdline. -- TPM2: auto-reenroll in cryptsetup, as fallback for hosed firmware upgrades - and such +- measure all log-in attempts into a new nvpcr -- introduce a new group to own TPM devices +- measure credentials picked up from SMBIOS to some suitable PCR -- make cryptsetup lower --iter-time +- measure GPT and LUKS headers somewhere when we use them (i.e. in + systemd-gpt-auto-generator/systemd-repart and in systemd-cryptsetup?) -- cryptsetup: - - cryptsetup-generator: allow specification of passwords in crypttab itself - - support rd.luks.allow-discards= kernel cmdline params in cryptsetup generator - - add boolean for disabling use of any password/recovery key slots. - (i.e. that we can operate in a tpm-only mode, and thus protect us from rogue - root disks) - - new crypttab option to auto-grow a luks device to its backing - partition size. new crypttab option to reencrypt a luks device with a new - volume key. - - a mechanism that allows signing a volume key with some key that - has to be present in the kernel keyring, or similar, to ensure that confext - DDIs can be encrypted against the local SRK but signed with the admin's key - and thus can authenticated locally before they are decrypted. - - add option for automatically removing empty password slot on boot - - optionally, when run during boot-up and password is never - entered, and we are on battery power (or so), power off machine again - - when waiting for FIDO2/PKCS#11 token, tell plymouth that, and - allow plymouth to abort the waiting and enter pw instead - - allow encoding key directly in /etc/crypttab, maybe with a - "base64:" prefix. Useful in particular for pkcs11 mode. - - reimplement the mkswap/mke2fs in cryptsetup-generator to use - systemd-makefs.service instead. +- measure some string via pcrphase whenever we end up booting into emergency + mode. -- systemd-analyze netif that explains predictable interface (or networkctl) +- measure some string via pcrphase whenever we resume from hibernate -- systemd-analyze inspect-elf should show other notes too, at least build-id. +- Merge systemd-creds options --uid= (which accepts user names) and --user. -- Figure out naming of verbs in systemd-analyze: we have (singular) capability, - exit-status, but (plural) filesystems, architectures. +- merge unit_kill_common() and unit_kill_context() -- special case some calls of chase() to use openat2() internally, so - that the kernel does what we otherwise do. +- MessageQueueMessageSize= (and suchlike) should use parse_iec_size(). -- add a new flag to chase() that stops chasing once the first missing - component is found and then allows the caller to create the rest. +- mount /tmp/ and /var/tmp with a uidmap applied that blocks out "nobody" user + among other things such as dynamic uid ranges for containers and so on. That + way no one can create files there with these uids and we enforce they are only + used transiently, never persistently. -- make use of new glibc 2.32 APIs sigabbrev_np(). +- mount /var/ from initrd, so that we can apply sysext and stuff before the + initrd transition. Specifically: + 1. There should be a var= kernel cmdline option, matching root= and usr= + 2. systemd-gpt-auto-generator should auto-mount /var if it finds it on disk + 3. mount.x-initrd mount option in fstab should be implied for /var -- if /usr/bin/swapoff fails due to OOM, log a friendly explanatory message about it +- mount most file systems with a restrictive uidmap. e.g. mount /usr/ with a + uidmap that blocks out anything outside 0…1000 (i.e. system users) and similar. -- systemd-path: Add "private" runtime/state/cache dir enum, mapping to - $RUNTIME_DIRECTORY, $STATE_DIRECTORY and such +- mount the root fs with MS_NOSUID by default, and then mount /usr/ without + both so that suid executables can only be placed there. Do this already in + the initrd. If /usr/ is not split out create a bind mount automatically. -- seccomp: - - maybe use seccomp_merge() to merge our filters per-arch if we can. - Apparently kernel performance is much better with fewer larger seccomp - filters than with more smaller seccomp filters. - - by default mask x32 ABI system wide on x86-64. it's on its way out - - don't install filters for ABIs that are masked anyway for the - specific service +- mount: turn dependency information from /proc/self/mountinfo into dependency information between systemd units. -- busctl: maybe expose a verb "ping" for pinging a dbus service to see if it - exists and responds. +- MountFlags=shared acts as MountFlags=slave right now. -- socket units: allow creating a udev monitor socket with ListenDevices= or so, - with matches, then activate app through that passing socket over +- **mountfsd/nsresourced:** + - userdb: maybe allow callers to map one uid to their own uid + - bpflsm: allow writes if resulting UID on disk would be userns' owner UID + - make encrypted DDIs work (password…) + - add API for creating a new file system from scratch (together with some + dm-integrity/HMAC key). Should probably work using systemd-repart (access + via varlink). + - add api to make an existing file "trusted" via dm-integry/HMAC key + - port: portabled + - port: tmpfiles, sysusers and similar + - lets see if we can make runtime bind mounts into unpriv nspawn work -- unify on openssl: - - figure out what to do about libmicrohttpd: - - 1.x is stable and has a hard dependency on gnutls - - 2.x is in development and has openssl support - - Worth testing against 2.x in our CI? - - port fsprg over to openssl +- move documentation about our common env vars (SYSTEMD_LOG_LEVEL, + SYSTEMD_PAGER, …) into a man page of its own, and just link it from our + various man pages that so far embed the whole list again and again, in an + attempt to reduce clutter and noise a bid. -- add growvol and makevol options for /etc/crypttab, similar to - x-systemd.growfs and x-systemd-makefs. +- move multiseat vid/pid matches from logind udev rule to hwdb -- userdb: allow existence checks +- Move RestrictAddressFamily= to the new cgroup create socket -- when switching root from initrd to host, set the machine_id env var so that - if the host has no machine ID set yet we continue to use the random one the - initrd had set. +- networkd's resolved hook: optionally map all lease IP addresses handed out to + the same hostname which is configured on the .network file. Optionally, even + derive this single name from the network interface name (i.e. probably + altname or so). This way, when spawning a VM the host could pick the hostname + for it and the client gets no say. -- tweak sd-event's child watching: keep a prioq of children to watch and use - waitid() only on the children with the highest priority until one is waitable - and ignore all lower-prio ones from that point on +- networkd/machined: implement reverse name lookups in the resolved hook -- maybe introduce xattrs that can be set on the root dir of the root fs - partition that declare the volatility mode to use the image in. Previously I - thought marking this via GPT partition flags but that's not ideal since - that's outside of the LUKS encryption/verity verification, and we probably - shouldn't operate in a volatile mode unless we got told so from a trusted - source. +- networkd: maintain a file in /run/ that can be symlinked into /run/issue.d/ + that always shows the current primary IP address -- coredump: maybe when coredumping read a new xattr from /proc/$PID/exe that - may be used to mark a whole binary as non-coredumpable. Would fix: - https://bugs.freedesktop.org/show_bug.cgi?id=69447 +- **networkd:** + - add more keys to [Route] and [Address] sections + - add support for more DHCPv4 options (and, longer term, other kinds of dynamic config) + - add reduced [Link] support to .network files + - properly handle routerless dhcp leases + - work with non-Ethernet devices + - dhcp: do we allow configuring dhcp routes on interfaces that are not the one we got the dhcp info from? + - the DHCP lease data (such as NTP/DNS) is still made available when + a carrier is lost on a link. It should be removed instantly. + - expose in the API the following bits: + - option 15, domain name + - option 12, hostname and/or option 81, fqdn + - option 123, 144, geolocation + - option 252, configure http proxy (PAC/wpad) + - provide a way to define a per-network interface default metric value + for all routes to it. possibly a second default for DHCP routes. + - allow Name= to be specified repeatedly in the [Match] section. Maybe also + support Name=foo*|bar*|baz ? + - whenever uplink info changes, make DHCP server send out FORCERENEW -- teach parse_timestamp() timezones like the calendar spec already knows it +- nspawn/vmspawn/pid1: add ability to easily insert fully booted VMs/FOSC into + shell pipelines, i.e. add easy to use switch that turns off console status + output, and generates the right credentials for systemd-run-generator so that + a program is invoked, and its output captured, with correct EOF handling and + exit code propagation -- We should probably replace /etc/rc.d/README with a symlink to doc - content. After all it is constant vendor data. +- nspawn/vmspawn: define hotkey that one can hit on the primary interface to + ask for a friendly, acpi style shutdown. -- maybe add kernel cmdline params: to force random seed crediting +- **nspawn:** + - emulate /dev/kmsg using CUSE and turn off the syslog syscall + with seccomp. That should provide us with a useful log buffer that + systemd can log to during early boot, and disconnect container logs + from the kernel's logs. + - as soon as networkd has a bus interface, hook up --network-interface=, + --network-bridge= with networkd, to trigger netdev creation should an + interface be missing + - a nice way to boot up without machine id set, so that it is set at boot + automatically for supporting --ephemeral. Maybe hash the host machine id + together with the machine name to generate the machine id for the container + - fix logic always print a final newline on output. + https://github.com/systemd/systemd/pull/272#issuecomment-113153176 + - should optionally support receiving WATCHDOG=1 messages from its payload + PID 1... + - optionally automatically add FORWARD rules to iptables whenever nspawn is + running, remove them when shut down. + - add support for sysext extensions, too. i.e. a new --extension= switch that + takes one or more arguments, and applies the extensions already during + startup. + - when main nspawn supervisor process gets suspended due to SIGSTOP/SIGTTOU + or so, freeze the payload too. + - support time namespaces + - on cgroupsv1 issue cgroup empty handler process based on host events, so + that we make cgroup agent logic safe + - add API to invoke binary in container, then use that as fallback in + "machinectl shell" + - make nspawn suitable for shell pipelines: instead of triggering a hangup + when input is finished, send ^D, which synthesizes an EOF. Then wait for + hangup or ^D before passing on the EOF. + - greater control over selinux label? + - support that /proc, /sys/, /dev are pre-mounted + - maybe allow TPM passthrough, backed by swtpm, and measure --image= hash + into its PCR 11, so that nspawn instances can be TPM enabled, and partake + in measurements/remote attestation and such. swtpm would run outside of + control of container, and ideally would itself bind its encryption keys to + host TPM. + - make boot assessment do something sensible in a container. i.e send an + sd_notify() from payload to container manager once boot-up is completed + successfully, and use that in nspawn for dealing with boot counting, + implemented in the partition table labels and directory names. + - optionally set up nftables/iptables routes that forward UDP/TCP traffic on + port 53 to resolved stub 127.0.0.54 + - maybe optionally insert .nspawn file as GPT partition into images, so that + such container images are entirely stand-alone and can be updated as one. + - The subreaper logic we currently have seems overly complex. We should + investigate whether creating the inner child with CLONE_PARENT isn't better. + - Reduce the number of sockets that are currently in use and just rely on one + or two sockets. + - map foreign UID range through 1:1 + - d-nspawn should get the same SSH key support that vmspawn now has. -- let's not GC a unit while its ratelimits are still pending +- oci: add support for "importctl import-oci" which implements the "OCI layout" + spec (i.e. acquiring via local fs access), as opposed to the current + "importctl pull-oci" which focusses on the "OCI image spec", i.e. downloads + from the web (i.e. acquiring via URLs). -- when killing due to service watchdog timeout maybe detect whether target - process is under ptracing and then log loudly and continue instead. +- oci: add support for blake hashes for layers -- make rfkill uaccess controllable by default, i.e. steal rule from - gnome-bluetooth and friends +- oci: support "data" in any OCI descriptor, not just manifest config. -- make MAINPID= message reception checks even stricter: if service uses User=, - then check sending UID and ignore message if it doesn't match the user or - root. +- On boot, auto-generate an asymmetric key pair from the TPM, + and use it for validating DDIs and credentials. Maybe upload it to the kernel + keyring, so that the kernel does this validation for us for verity and kernel + modules -- maybe trigger a uevent "change" on a device if "systemctl reload xyz.device" - is issued. +- on first login of a user, measure its identity to some nvpcr -- when importing an fs tree with machined, optionally apply userns-rec-chown +- on shutdown: move utmp, wall, audit logic all into PID 1 (or logind?) -- when importing an fs tree with machined, complain if image is not an OS +- once swtpm's sd_notify() support has landed in the distributions, remove the + invocation in tpm2-swtpm.c and let swtpm handle it. -- Maybe introduce a helper safe_exec() or so, which is to execve() which - safe_fork() is to fork(). And then make revert the RLIMIT_NOFILE soft limit - to 1K implicitly, unless explicitly opted-out. +- Once the root fs LUKS volume key is measured into PCR 15, default to binding + credentials to PCR 15 in "systemd-creds" -- rework seccomp/nnp logic that even if User= is used in combination with - a seccomp option we don't have to set NNP. For that, change uid first while - keeping CAP_SYS_ADMIN, then apply seccomp, the drop cap. +- optionally, also require WATCHDOG=1 notifications during service start-up and shutdown -- when no locale is configured, default to UEFI's PlatformLang variable +- optionally, collect cgroup resource data, and store it in per-unit RRD files, + suitable for processing with rrdtool. Add bus API to access this data, and + possibly implement a CPULoad property based on it. -- add a new syscall group "@esoteric" for more esoteric stuff such as bpf() and - usefaultd() and make systemd-analyze check for it. +- optionally: turn on cgroup delegation for per-session scope units + +- pam_systemd: on interactive logins, maybe show SUPPORT_END information at + login time, à la motd + +- pam_systemd_home: add module parameter to control whether to only accept + only password or only pcks11/fido2 auth, and then use this to hook nicely + into two of the three PAM stacks gdm provides. + See discussion at https://github.com/authselect/authselect/pull/311 - paranoia: whenever we process passwords, call mlock() on the memory first. i.e. look for all places we use free_and_erasep() and augment them with mlock(). Also use MADV_DONTDUMP. Alternatively (preferably?) use memfd_secret(). -- Move RestrictAddressFamily= to the new cgroup create socket +- pcrextend/tpm2-util: add a concept of "rotation" to event log. i.e. allow + trailing parts of the logs if time or disk space limit is hit. Protect the + boot-time measurements however (i.e. up to some point where things are + settled), since we need those for pcrlock measurements and similar. When + deleting entries for rotation, place an event that declares how many items + have been dropped, and what the hash before and after that. -- optionally: turn on cgroup delegation for per-session scope units +- pcrextend: after measuring get an immediate quote from the TPM, and validate + it. if it doesn't check out, i.e. the measurement we made doesn't appear in + the PCR then also reboot. -- sd-boot: - - do something useful if we find exactly zero entries (ignoring items - such as reboot/poweroff/factory reset). Show a help text or so. - - optionally ask for confirmation before executing certain operations - (e.g. factory resets, storagetm with world access, and so on) - - for each installed OS, grey out older entries (i.e. all but the - newest), to indicate they are obsolete - - we probably should include all BootXY EFI variable defined boot - entries in our menu, and then suppress ourselves. Benefit: instant - compatibility with all other OSes which register things there, in particular - on other disks. Always boot into them via NextBoot EFI variable, to not - affect PCR values. - - should look for information what to boot in SMBIOS, too, so that VM - managers can tell sd-boot what to boot into and suchlike - - instead of unconditionally deriving the ESP to search boot loader - spec entries in from the paths of sd-boot binary, let's optionally allow it - to be configured on sd-boot cmdline + efi var. Use case: embed sd-boot in the - UEFI firmware (for example, ovmf supports that via qemu cmdline option), and - use it to load stuff from the ESP. - - optionally, show boot menu when previous default boot item has - non-zero "tries done" count +- pcrextend: maybe add option to disable measurements entirely via kernel cmdline -- augment CODE_FILE=, CODE_LINE= with something like CODE_BASE= or so which - contains some identifier for the project, which allows us to include - clickable links to source files generating these log messages. The identifier - could be some abbreviated URL prefix or so (taking inspiration from Go - imports). For example, for systemd we could use - CODE_BASE=github.com/systemd/systemd/blob/98b0b1123cc or so which is - sufficient to build a link by prefixing "http://" and suffixing the - CODE_FILE. +- pcrextend: when we fail to measure, reboot the system (at least optionally). + important because certain measurements are supposed to "destroy" tpm object + access. -- Augment MESSAGE_ID with MESSAGE_BASE, in a similar fashion so that we can - make clickable links from log messages carrying a MESSAGE_ID, that lead to - some explanatory text online. +- pcrlock: add support for multi-profile UKIs -- maybe extend .path units to expose fanotify() per-mount change events +- **pcrlock:** + - add kernel-install plugin that automatically creates UKI .pcrlock file when + UKI is installed, and removes it when it is removed again + - automatically install PE measurement of sd-boot on "bootctl install" + - pre-calc sysext + kernel cmdline measurements + - pre-calc cryptsetup root key measurement + - maybe make systemd-repart generate .pcrlock for old and new GPT header in + /run? + - Add support for more than 8 branches per PCR OR + - add "systemd-pcrlock lock-kernel-current" or so which synthesizes .pcrlock + policy from currently booted kernel/event log, to close gap for first boot + for pre-built images -- hibernate/s2h: if swap is on weird storage and refuse if so +- per-service sandboxing option: ProtectIds=. If used, will overmount + /etc/machine-id and /proc/sys/kernel/random/boot_id with synthetic files, to + make it harder for the service to identify the host. Depending on the user + setting it should be fully randomized at invocation time, or a hash of the + real thing, keyed by the unit name or so. Of course, there are other ways to + get these IDs (e.g. journal) or similar ids (e.g. MAC addresses, DMI ids, CPU + ids), so this knob would only be useful in combination with other lockdown + options. Particularly useful for portable services, and anything else that + uses RootDirectory= or RootImage=. (Might also over-mount + /sys/class/dmi/id/*{uuid,serial} with /dev/null). -- cgroups: use inotify to get notified when somebody else modifies cgroups - owned by us, then log a friendly warning. +- Permit masking specific netlink APIs with RestrictAddressFamily= -- beef up log.c with support for stripping ANSI sequences from strings, so that - it is OK to include them in log strings. This would be particularly useful so - that our log messages could contain clickable links for example for unit - files and suchlike we operate on. +- pick up creds from EFI vars -- add support for "portablectl attach http://foobar.com/waaa.raw (i.e. importd integration) +- PID 1 should send out sd_notify("WATCHDOG=1") messages (for usage in the --user mode, and when run via nspawn) -- sync dynamic uids/gids between host+portable service (i.e. if DynamicUser=1 is set for a service, make sure that the - selected user is resolvable in the service even if it ships its own /etc/passwd) +- **pid1:** + - When logging about multiple units (stopping BoundTo units, conflicts, etc.), + log both units as UNIT=, so that journalctl -u triggers on both. + - generate better errors when people try to set transient properties + that are not supported... + https://lists.freedesktop.org/archives/systemd-devel/2015-February/028076.html + - recreate systemd's D-Bus private socket file on SIGUSR2 + - when we automatically restart a service, ensure we restart its rdeps, too. + - hide PAM options in fragment parser when compile time disabled + - Support --test based on current system state + - If we show an error about a unit (such as not showing up) and it has no Description string, then show a description string generated form the reverse of unit_name_mangle(). + - after deserializing sockets in socket.c we should reapply sockopts and things + - drop PID 1 reloading, only do reexecing (difficult: Reload() + currently is properly synchronous, Reexec() is weird, because we + cannot delay the response properly until we are back, so instead of + being properly synchronous we just keep open the fd and close it + when done. That means clients do not get a successful method reply, + but much rather a disconnect on success. + - when breaking cycles drop services from /run first, then from /etc, then from /usr + - when a bus name of a service disappears from the bus make sure to queue further activation requests + - maybe introduce CoreScheduling=yes/no to optionally set a PR_SCHED_CORE cookie, so that all + processes in a service's cgroup share the same cookie and are guaranteed not to share SMT cores + with other units https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/hw-vuln/core-scheduling.rst + - ExtensionImages= deduplication for services is currently only applied to disk images without GPT envelope. + This should be extended to work with proper DDIs too, as well as directory confext/sysext. Moreover, + system-wide confext/sysext should support this too. + - Pin the mount namespace via FD by sending it back from sd-exec to the manager, and use it + for live mounting, instead of doing it via PID + - also remove PID files of a service when the service starts, not just + when it exits + - activation by journal search expression + - lock image configured with RootDirectory=/RootImage= using the usual nspawn semantics while the unit is up + - find a way how we can reload unit file configuration for + specific units only, without reloading the whole of systemd -- Fix DECIMAL_STR_MAX or DECIMAL_STR_WIDTH. One includes a trailing NUL, the - other doesn't. What a disaster. Probably to exclude it. +- **PidRef conversion work:** + - cg_pid_get_xyz() + - pid_from_same_root_fs() + - get_ctty_devnr() + - actually wait for POLLIN on pidref's pidfd in service logic + - openpt_allocate_in_namespace() + - unit_attach_pid_to_cgroup_via_bus() + - cg_attach() – requires new kernel feature + - journald's process cache -- Check that users of inotify's IN_DELETE_SELF flag are using it properly, as - usually IN_ATTRIB is the right way to watch deleted files, as the former only - fires when a file is actually removed from disk, i.e. the link count drops to - zero and is not open anymore, while the latter happens when a file is - unlinked from any dir. +- port copy.c over to use LabelOps for all labelling. -- systemctl, machinectl, loginctl: port "status" commands over to - format-table.c's vertical output logic. +- portable services: attach not only unit files to host, but also simple + binaries to a tmpfs path in $PATH. -- add --vacuum-xyz options to coredumpctl, matching those journalctl already has. +- portabled: when extracting unit files and copying to system.attached, if a + .p7s is available in the image, use it to protect the system.attached copy + with fs-verity, so that it cannot be tampered with -- add CopyFile= or so as unit file setting that may be used to copy files or - directory trees from the host to the services RootImage= and RootDirectory= - environment. Which we can use for /etc/machine-id and in particular - /etc/resolv.conf. Should be smart and do something useful on read-only - images, for example fall back to read-only bind mounting the file instead. +- print a nicer explanation if people use variable/specifier expansion in ExecStart= for the first word -- bypass SIGTERM state in unit files if KillSignal is SIGKILL +- **Process credentials in:** + - crypttab-generator: allow defining additional crypttab-like volumes via + credentials (similar: verity-generator, integrity-generator). Use + fstab-generator logic as inspiration. + - run-generator: allow defining additional commands to run via a credential + - resolved: allow defining additional /etc/hosts entries via a credential (it + might make sense to then synthesize a new combined /etc/hosts file in /run + and bind mount it on /etc/hosts for other clients that want to read it. + - repart: allow defining additional partitions via credential + - timesyncd: pick NTP server info from credential + - portabled: read a credential "portable.extra" or so, that takes a list of + file system paths to enable on start. + - make systemd-fstab-generator look for a system credential encoding root= or + usr= + - in gpt-auto-generator: check partition uuids against such uuids supplied via + sd-stub credentials. That way, we can support parallel OS installations with + pre-built kernels. -- add proper dbus APIs for the various sd_notify() commands, such as MAINPID=1 - and so on, which would mean we could report errors and such. +- properly handle loop back mounts via fstab, especially regards to fsck/passno -- introduce DefaultSlice= or so in system.conf that allows changing where we - place our units by default, i.e. change system.slice to something - else. Similar, ManagerSlice= should exist so that PID1's own scope unit could - be moved somewhere else too. Finally machined and logind should get similar - options so that it is possible to move user session scopes and machines to a - different slice too by default. Use case: people who want to put resources on - the entire system, with the exception of one specific service. See: - https://lists.freedesktop.org/archives/systemd-devel/2018-February/040369.html +- properly serialize the ExecStatus data from all ExecCommand objects + associated with services, sockets, mounts and swaps. Currently, the data is + flushed out on reload, which is quite a limitation. -- calenderspec: add support for week numbers and day numbers within a - year. This would allow us to define "bi-weekly" triggers safely. +- ProtectClock= (drops CAP_SYS_TIMES, adds seccomp filters for settimeofday, adjtimex), sets DeviceAllow o /dev/rtc -- make use of ethtool veth peer info in machined, for automatically finding out - host-side interface pointing to the container. +- ProtectKeyRing= to take keyring calls away -- add some special mode to LogsDirectory=/StateDirectory=… that allows - declaring these directories without necessarily pulling in deps for them, or - creating them when starting up. That way, we could declare that - systemd-journald writes to /var/log/journal, which could be useful when we - doing disk usage calculations and so on. +- ProtectMount= (drop mount/umount/pivot_root from seccomp, disallow fuse via DeviceAllow, imply Mountflags=slave) -- deprecate RootDirectoryStartOnly= in favour of a new ExecStart= prefix char +- ProtectReboot= that masks reboot() and kexec_load() syscalls, prohibits kill + on PID 1 with the relevant signals, and makes relevant files in /sys and + /proc (such as the sysrq stuff) unavailable -- support projid-based quota in machinectl for containers +- ProtectTracing= (drops CAP_SYS_PTRACE, blocks ptrace syscall, makes /sys/kernel/tracing go away) -- add a way to lock down cgroup migration: a boolean, which when set for a unit - makes sure the processes in it can never migrate out of it +- ptyfwd: use osc context information in vmspawn/nspawn/… to optionally only + listen to ^]]] key when no further vmspawn/nspawn context is allocated -- blog about fd store and restartable services +- ptyfwd: usec osc context information to propagate status messages from + vmspawn/nspawn to service manager's "status" string, reporting what is + currently in the fg -- document Environment=SYSTEMD_LOG_LEVEL=debug drop-in in debugging document +- pull-oci: progress notification -- rework ExecOutput and ExecInput enums so that EXEC_OUTPUT_NULL loses its - magic meaning and is no longer upgraded to something else if set explicitly. +- redefine /var/lib/extensions/ as the dir one can place all three of sysext, + confext as well is multi-modal DDIs that qualify as both. Then introduce + /var/lib/sysexts/ which can be used to place only DDIs that shall be used as + sysext -- in the long run: permit a system with /etc/machine-id linked to /dev/null, to - make it lose its identity, i.e. be anonymous. For this we'd have to patch - through the whole tree to make all code deal with the case where no machine - ID is available. +- refcounting in sd-resolve is borked -- optionally, collect cgroup resource data, and store it in per-unit RRD files, - suitable for processing with rrdtool. Add bus API to access this data, and - possibly implement a CPULoad property based on it. +- refuse boot if /usr/lib/os-release is missing or /etc/machine-id cannot be set up -- beef up pam_systemd to take unit file settings such as cgroups properties as - parameters +- remove any syslog support from log.c — we probably cannot do this before split-off udev is gone for good -- In DynamicUser= mode: before selecting a UID, use disk quota APIs on relevant - disks to see if the UID is already in use. +- remove tomoyo support, it's obsolete and unmaintained apparently -- Add AddUser= setting to unit files, similar to DynamicUser=1 which however - creates a static, persistent user rather than a dynamic, transient user. We - can leverage code from sysusers.d for this. +- RemoveKeyRing= to remove all keyring entries of the specified user -- add some optional flag to ReadWritePaths= and friends, that has the effect - that we create the dir in question when the service is started. Example: +- repart + cryptsetup: support file systems that are encrypted and use verity + on top. Usecase: confexts that shall be signed by the admin but also be + confidential. Then, add a new --make-ddi=confext-encrypted for this. - ReadWritePaths=:/var/lib/foobar +- repart/gpt-auto/DDIs: maybe introduce a concept of "extension" partitions, + that have a new type uuid and can "extend" earlier partitions, to work around + the fact that systemd-repart can only grow the last partition defined. During + activation we'd simply set up a dm-linear mapping to merge them again. A + partition that is to be extended would just set a bit in the partition flags + field to indicate that there's another extension partition to look for. The + identifying UUID of the extension partition would be hashed in counter mode + from the uuid of the original partition it extends. Inspiration for this is + the "dynamic partitions" concept of new Android. This would be a minimalistic + concept of a volume manager, with the extents it manages being exposes as GPT + partitions. I a partition is extended multiple times they should probably + grow exponentially in size to ensure O(log(n)) time for finding them on + access. -- Add ExecMonitor= setting. May be used multiple times. Forks off a process in - the service cgroup, which is supposed to monitor the service, and when it - exits the service is considered failed by its monitor. +- repart: introduce concept of "ghost" partitions, that we setup in almost all + ways like other partitions, but do not actually register in the actual gpt + table, but only tell the kernel about via BLKPG ioctl. These partitions are + disk backed (hence can be large), but not persistent (as they are invisible + on next boot). Could be used by live media and similar, to boot up as usual + but automatically start at zero on each boot. There should also be a way to + make ghost partitions properly persistent on request. -- track the per-service PAM process properly (i.e. as an additional control - process), so that it may be queried on the bus and everything. +- repart: introduce MigrateFileSystem= or so which is a bit like + CopyFiles=/CopyBlocks= but operates via btrfs device logic: adds target as + new device then removes source from btrfs. Usecase: a live medium which uses + "ghost" partitions as suggested above, which can become persistent on request + on another device. -- add a new "debug" job mode, that is propagated to unit_start() and for - services results in two things: we raise SIGSTOP right before invoking - execve() and turn off watchdog support. Then, use that to implement - "systemd-gdb" for attaching to the start-up of any system service in its - natural habitat. +- replace all \x1b, \x1B, \033 C string escape sequences in our codebase with a + more readable \e. It's a GNU extension, but a ton more readable than the + others, and most importantly it doesn't result in confusing errors if you + suffix the escape sequence with one more decimal digit, because compilers + think you might actually specify a value outside the 8bit range with that. -- add a percentage syntax for TimeoutStopSec=, e.g. TimeoutStopSec=150%, and - then use that for the setting used in user@.service. It should be understood - relative to the configured default value. +- replace all uses of fopen_temporary() by fopen_tmpfile_linkable() + + flink_tmpfile() and then get rid of fopen_temporary(). Benefit: use O_TMPFILE + pervasively, and avoid rename() wherever we can. -- enable LockMLOCK to take a percentage value relative to physical memory +- replace bootctl's PE version check to actually use APIs from pe-binary.[ch] + to find binary version. -- Permit masking specific netlink APIs with RestrictAddressFamily= +- replace symlink_label(), mknodat_label(), btrfs_subvol_make_label(), + mkdir_label() and related calls by flags-based calls that use + label_ops_pre()/label_ops_post(). -- define gpt header bits to select volatility mode +- report: have something that requests cloud workload identity bearer tokens + and includes it in the report -- ProtectClock= (drops CAP_SYS_TIMES, adds seccomp filters for settimeofday, adjtimex), sets DeviceAllow o /dev/rtc - -- ProtectTracing= (drops CAP_SYS_PTRACE, blocks ptrace syscall, makes /sys/kernel/tracing go away) - -- ProtectMount= (drop mount/umount/pivot_root from seccomp, disallow fuse via DeviceAllow, imply Mountflags=slave) - -- ProtectKeyRing= to take keyring calls away - -- RemoveKeyRing= to remove all keyring entries of the specified user - -- ProtectReboot= that masks reboot() and kexec_load() syscalls, prohibits kill - on PID 1 with the relevant signals, and makes relevant files in /sys and - /proc (such as the sysrq stuff) unavailable - -- Support ReadWritePaths/ReadOnlyPaths/InaccessiblePaths in systemd --user instances - via the new unprivileged Landlock LSM (https://landlock.io) - -- make sure the ratelimit object can deal with USEC_INFINITY as way to turn off things - -- in nss-systemd, if we run inside of RootDirectory= with PrivateUsers= set, - find a way to map the User=/Group= of the service to the right name. This way - a user/group for a service only has to exist on the host for the right - mapping to work. - -- add bus API for creating unit files in /etc, reusing the code for transient units - -- add bus API to remove unit files from /etc - -- add bus API to retrieve current unit file contents (i.e. implement "systemctl cat" on the bus only) - -- rework fopen_temporary() to make use of open_tmpfile_linkable() (problem: the - kernel doesn't support linkat() that replaces existing files, currently) - -- transient units: don't bother with actually setting unit properties, we - reload the unit file anyway - -- optionally, also require WATCHDOG=1 notifications during service start-up and shutdown - -- cache sd_event_now() result from before the first iteration... - -- add an explicit parser for LimitRTPRIO= that verifies - the specified range and generates sane error messages for incorrect - specifications. - -- when we detect that there are waiting jobs but no running jobs, do something - -- PID 1 should send out sd_notify("WATCHDOG=1") messages (for usage in the --user mode, and when run via nspawn) - -- there's probably something wrong with having user mounts below /sys, - as we have for debugfs. for example, src/core/mount.c handles mounts - prefixed with /sys generally special. - https://lists.freedesktop.org/archives/systemd-devel/2015-June/032962.html - -- fstab-generator: default to tmpfs-as-root if only usr= is specified on the kernel cmdline - -- docs: bring https://systemd.io/MY_SERVICE_CANT_GET_REALTIME up to date - -- add a job mode that will fail if a transaction would mean stopping - running units. Use this in timedated to manage the NTP service - state. - https://lists.freedesktop.org/archives/systemd-devel/2015-April/030229.html - -- The udev blkid built-in should expose a property that reflects - whether media was sensed in USB CF/SD card readers. This should then - be used to control SYSTEMD_READY=1/0 so that USB card readers aren't - picked up by systemd unless they contain a medium. This would mirror - the behaviour we already have for CD drives. - -- hostnamectl: show root image uuid - -- Find a solution for SMACK capabilities stuff: - https://lists.freedesktop.org/archives/systemd-devel/2014-December/026188.html - -- synchronize console access with BSD locks: - https://lists.freedesktop.org/archives/systemd-devel/2014-October/024582.html - -- as soon as we have sender timestamps, revisit coalescing multiple parallel daemon reloads: - https://lists.freedesktop.org/archives/systemd-devel/2014-December/025862.html - -- figure out when we can use the coarse timers - -- maybe allow timer units with an empty Units= setting, so that they - can be used for resuming the system but nothing else. - -- what to do about udev db binary stability for apps? (raw access is not an option) - -- exponential backoff in timesyncd when we cannot reach a server - -- timesyncd: add ugly bus calls to set NTP servers per-interface, for usage by NM - -- add systemd.abort_on_kill or some other such flag to send SIGABRT instead of SIGKILL - (throughout the codebase, not only PID1) +- **report:** + - plug "facts" into systemd-report too, i.e. stuff that is more static, such as hostnames, ssh keys and so on. + - pass filtering hints to services, so that they can also be applied server-side, not just client side + - metrics from pid1: suppress metrics form units that are inactive and have nothing to report + - add "hint-suppress-zero" flag (which suppresses all metrics which are zero) + - add "hint-object" parameter (which only queries info about certain object) + - make systemd-report a varlink service -- drop nss-myhostname in favour of nss-resolve? +- Reset TPM2 DA bit on each successful boot -- resolved: +- **resolved:** - mDNS/DNS-SD - service registration - service/domain/types browsing @@ -2010,552 +2066,358 @@ fd00:5353:5353:5353:5353:5353:5353:5353), and listen on port 53 on it for the local stubs, so that we can make the stub available via ipv6 too. -- refcounting in sd-resolve is borked +- revisit how we pass fs images and initrd to the kernel. take uefi http boot + ramdisks as inspiration: for any confext/sysext/initrd erofs/DDI image simply + generate a fake pmem region in the UEFI memory tables, that Linux then turns + into /dev/pmemX. Then turn of cpio-based initrd logic in linux kernel, + instead let kernel boot directly into /dev/pmem0. In order to allow our usual + cpio-based parameterization, teach PID 1 to just uncompress cpio ourselves + early on, from another pmem device. (Related to this, maybe introduce a new + PE section .ramdisk that just synthesizes pmem devices from arbitrary + blobs. Could be particularly useful in add-ons) -- add new gpt type for btrfs volumes +- rework ExecOutput and ExecInput enums so that EXEC_OUTPUT_NULL loses its + magic meaning and is no longer upgraded to something else if set explicitly. -- generator that automatically discovers btrfs subvolumes, identifies their purpose based on some xattr on them. +- rework fopen_temporary() to make use of open_tmpfile_linkable() (problem: the + kernel doesn't support linkat() that replaces existing files, currently) -- a way for container managers to turn off getty starting via $container_headless= or so... +- rework journalctl -M to be based on a machined method that generates a mount + fd of the relevant journal dirs in the container with uidmapping applied to + allow the host to read it, while making everything read-only. -- figure out a nice way how we can let the admin know what child/sibling unit causes cgroup membership for a specific unit +- rework loopback support in fstab: when "loop" option is used, then + instantiate a new systemd-loop@.service for the source path, set the + lo_file_name field for it to something recognizable derived from the fstab + line, and then generate a mount unit for it using a udev generated symlink + based on lo_file_name. -- For timer units: add some mechanisms so that timer units that trigger immediately on boot do not have the services - they run added to the initial transaction and thus confuse Type=idle. +- rework recursive read-only remount to use new mount API -- add bus api to query unit file's X fields. +- rework seccomp/nnp logic that even if User= is used in combination with + a seccomp option we don't have to set NNP. For that, change uid first while + keeping CAP_SYS_ADMIN, then apply seccomp, the drop cap. -- gpt-auto-generator: - - Make /home automount rather than mount? +- rewrite bpf-devices in libbpf/C code, rather than home-grown BPF assembly, to + match bpf-restrict-fs, bpf-restrict-ifaces, bpf-socket-bind -- add generator that pulls in systemd-network from containers when - CAP_NET_ADMIN is set, more than the loopback device is defined, even - when it is otherwise off +- rewrite bpf-firewall in libbpf/C code -- MessageQueueMessageSize= (and suchlike) should use parse_iec_size(). +- rfkill,backlight: we probably should run the load tools inside of the udev rules so that the state is properly initialized by the time other software sees it -- implement Distribute= in socket units to allow running multiple - service instances processing the listening socket, and open this up - for ReusePort= +- rough proposed implementation design for remote attestation infra: add a tool + that generates a quote of local PCRs and NvPCRs, along with synchronous log + snapshot. use "audit session" logic for that, so that we get read-outs and + signature in one step. Then turn this into a JSON object. Use the "TCG TSS 2.0 + JSON Data Types and Policy Language" format to encode the signature. And CEL + for the measurement log. -- cgroups: - - implement per-slice CPUFairScheduling=1 switch - - introduce high-level settings for RT budget, swappiness - - how to reset dynamically changed unit cgroup attributes sanely? - - when reloading configuration, apply new cgroup configuration - - when recursively showing the cgroup hierarchy, optionally also show - the hierarchies of child processes - - add settings for cgroup.max.descendants and cgroup.max.depth, - maybe use them for user@.service +- run0: maybe enable utmp for run0 sessions, so that they are easily visible. -- transient units: - - add field to transient units that indicate whether systemd or somebody else saves/restores its settings, for integration with libvirt +- sd-boot/sd-stub: install a uefi "handle" to a sidecar dir of bls type #1 + entries with an "uki" or "uki-url" stanza, and make sd-stub look for + that. That way we can parameterize type #1 entries nicely. -- libsystemd-journal, libsystemd-login, libudev: add calls to easily attach these objects to sd-event event loops +- **sd-boot:** + - do something useful if we find exactly zero entries (ignoring items + such as reboot/poweroff/factory reset). Show a help text or so. + - optionally ask for confirmation before executing certain operations + (e.g. factory resets, storagetm with world access, and so on) + - for each installed OS, grey out older entries (i.e. all but the + newest), to indicate they are obsolete + - we probably should include all BootXY EFI variable defined boot + entries in our menu, and then suppress ourselves. Benefit: instant + compatibility with all other OSes which register things there, in particular + on other disks. Always boot into them via NextBoot EFI variable, to not + affect PCR values. + - should look for information what to boot in SMBIOS, too, so that VM + managers can tell sd-boot what to boot into and suchlike + - instead of unconditionally deriving the ESP to search boot loader + spec entries in from the paths of sd-boot binary, let's optionally allow it + to be configured on sd-boot cmdline + efi var. Use case: embed sd-boot in the + UEFI firmware (for example, ovmf supports that via qemu cmdline option), and + use it to load stuff from the ESP. + - optionally, show boot menu when previous default boot item has + non-zero "tries done" count -- be more careful what we export on the bus as (usec_t) 0 and (usec_t) -1 +- **sd-bus:** + - EBADSLT handling + - GetAllProperties() on a non-existing object does not result in a failure currently + - port to sd-resolve for connecting to TCP dbus servers + - see if we can introduce a new sd_bus_get_owner_machine_id() call to retrieve the machine ID of the machine of the bus itself + - see if we can drop more message validation on the sending side + - add API to clone sd_bus_message objects + - longer term: priority inheritance + - dbus spec updates: + - NameLost/NameAcquired obsolete + - path escaping + - update systemd.special(7) to mention that dbus.socket is only about the compatibility socket now + - add vtable flag, that may be used to request client creds implicitly + and asynchronously before dispatching the operation + - parse addresses given in sd_bus_set_addresses immediately and not + only when used. Add unit tests. -- rfkill,backlight: we probably should run the load tools inside of the udev rules so that the state is properly initialized by the time other software sees it +- **sd-device:** + - add an API for acquiring list of child devices, given a device + objects (i.e. all child dirents that dirs or symlinks to dirs) + - maybe pin the sysfs dir with an fd, during the entire runtime of + an sd_device, then always work based on that. + - should return the devnum type (i.e. 'b' or 'c') via some API for an + sd_device object, so that data passed into sd_device_new_from_devnum() can + also be queried. -- If we try to find a unit via a dangling symlink, generate a clean - error. Currently, we just ignore it and read the unit from the search - path anyway. +- **sd-event:** + - allow multiple signal handlers per signal? + - document chaining of signal handler for SIGCHLD and child handlers + - define more intervals where we will shift wakeup intervals around in, 1h, 6h, 24h, ... + - maybe support iouring as backend, so that we allow hooking read and write + operations instead of IO ready events into event loops. See considerations + here: + http://blog.vmsplice.net/2020/07/rethinking-event-loop-integration-for.html + - add ability to "chain" event sources. Specifically, add a call + sd_event_source_chain(x, y), which will automatically enable event source y + in oneshot mode once x is triggered. Use case: in src/core/mount.c implement + the /proc/self/mountinfo rescan on SIGCHLD with this: whenever a SIGCHLD is + seen, trigger the rescan defer event source automatically, and allow it to be + dispatched *before* the SIGCHLD is handled (based on priorities). Benefit: + dispatch order is strictly controlled by priorities again. (next step: chain + event sources to the ratelimit being over) + - compat wd reuse in inotify code: keep a set of removed watch + descriptors, and clear this set piecemeal when we see the IN_IGNORED event + for it, or when read() returns EAGAIN or on IN_Q_OVERFLOW. Then, whenever we + see an inotify wd event check against this set, and if it is contained ignore + the event. (to be fully correct this would have to count the occurrences, in + case the same wd is reused multiple times before we start processing + IN_IGNORED again) + - optionally, if per-event source rate limit is hit, downgrade + priority, but leave enabled, and once ratelimit window is over, upgrade + priority again. That way we can combat event source starvation without + stopping processing events from one source entirely. + - similar to existing inotify support add fanotify support (given + that apparently new features in this area are only going to be added to the + latter). + - add 1st class event source for clock changes + - add 1st class event source for timezone changes + - add native support for P_ALL waitid() watching, then move PID 1 to + it for reaping assigned but unknown children. This needs to some special care + to operate somewhat sensibly in light of priorities: P_ALL will return + arbitrary processes, regardless of the priority we want to watch them with, + hence on each event loop iteration check all processes which we shall watch + with higher prio explicitly, and then watch the entire rest with P_ALL. -- refuse boot if /usr/lib/os-release is missing or /etc/machine-id cannot be set up +- sd-journal puts a limit on parallel journal files to view at once. journald + should probably honour that same limit (JOURNAL_FILES_MAX) when vacuuming to + ensure we never generate more files than we can actually view. -- man: the documentation of Restart= currently is very misleading and suggests the tools from ExecStartPre= might get restarted. +- sd-lldp: pick up 802.3 maximum frame size/mtu, to be able to detect jumbo + frame capable networks -- There's currently no way to cancel fsck (used to be possible via C-c or c on the console) - -- add option to sockets to avoid activation. Instead just drop packets/connections, see http://cyberelk.net/tim/2012/02/15/portreserve-systemd-solution/ - -- make sure systemd-ask-password-wall does not shutdown systemd-ask-password-console too early - -- verify that the AF_UNIX sockets of a service in the fs still exist - when we start a service in order to avoid confusion when a user - assumes starting a service is enough to make it accessible - -- Make it possible to set the keymap independently from the font on - the kernel cmdline. Right now setting one resets also the other. - -- and a dbus call to generate target from current state - -- investigate whether the gnome pty helper should be moved into systemd, to provide cgroup support. - -- dot output for --test showing the 'initial transaction' +- **sd-rtnl:** + - add support for more attribute types + - inbuilt piping support (essentially degenerate async)? see loopback-setup.c and other places -- be able to specify a forced restart of service A where service B depends on, in case B - needs to be auto-respawned? +- **sd-stub:** + - detect if we are running with uefi console output on serial, and if so + automatically add console= to kernel cmdline matching the same port. + - add ".bootcfg" section for kernel bootconfig data (as per + https://docs.kernel.org/admin-guide/bootconfig.html) -- pid1: - - When logging about multiple units (stopping BoundTo units, conflicts, etc.), - log both units as UNIT=, so that journalctl -u triggers on both. - - generate better errors when people try to set transient properties - that are not supported... - https://lists.freedesktop.org/archives/systemd-devel/2015-February/028076.html - - recreate systemd's D-Bus private socket file on SIGUSR2 - - when we automatically restart a service, ensure we restart its rdeps, too. - - hide PAM options in fragment parser when compile time disabled - - Support --test based on current system state - - If we show an error about a unit (such as not showing up) and it has no Description string, then show a description string generated form the reverse of unit_name_mangle(). - - after deserializing sockets in socket.c we should reapply sockopts and things - - drop PID 1 reloading, only do reexecing (difficult: Reload() - currently is properly synchronous, Reexec() is weird, because we - cannot delay the response properly until we are back, so instead of - being properly synchronous we just keep open the fd and close it - when done. That means clients do not get a successful method reply, - but much rather a disconnect on success. - - when breaking cycles drop services from /run first, then from /etc, then from /usr - - when a bus name of a service disappears from the bus make sure to queue further activation requests - - maybe introduce CoreScheduling=yes/no to optionally set a PR_SCHED_CORE cookie, so that all - processes in a service's cgroup share the same cookie and are guaranteed not to share SMT cores - with other units https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/hw-vuln/core-scheduling.rst - - ExtensionImages= deduplication for services is currently only applied to disk images without GPT envelope. - This should be extended to work with proper DDIs too, as well as directory confext/sysext. Moreover, - system-wide confext/sysext should support this too. - - Pin the mount namespace via FD by sending it back from sd-exec to the manager, and use it - for live mounting, instead of doing it via PID - - also remove PID files of a service when the service starts, not just - when it exits - - activation by journal search expression - - lock image configured with RootDirectory=/RootImage= using the usual nspawn semantics while the unit is up - - find a way how we can reload unit file configuration for - specific units only, without reloading the whole of systemd +- sd_notify/vsock: maybe support binding to AF_VSOCK in Type=notify services, + then passing $NOTIFY_SOCKET and $NOTIFY_GUESTCID with PID1's cid (typically + fixed to "2", i.e. the official host cid) and the expected guest cid, for the + two sides of the channel. The latter env var could then be used in an + appropriate qemu cmdline. That way qemu payloads could talk sd_notify() + directly to host service manager. -- unit files: - - allow port=0 in .socket units - - maybe introduce ExecRestartPre= - - implement Register= switch in .socket units to enable registration - in Avahi, RPC and other socket registration services. - - allow Type=simple with PIDFile= - https://bugzilla.redhat.com/show_bug.cgi?id=723942 - - allow writing multiple conditions in unit files on one line - - add a concept of RemainAfterExit= to scope units - - Allow multiple ExecStart= for all Type= settings, so that we can cover rescue.service nicely - - add verification of [Install] section to systemd-analyze verify +- **seccomp:** + - maybe use seccomp_merge() to merge our filters per-arch if we can. + Apparently kernel performance is much better with fewer larger seccomp + filters than with more smaller seccomp filters. + - by default mask x32 ABI system wide on x86-64. it's on its way out + - don't install filters for ABIs that are masked anyway for the + specific service -- timer units: - - timer units should get the ability to trigger when DST changes - - Modulate timer frequency based on battery state +- seems that when we follow symlinks to units we prefer the symlink + destination path over /etc and /usr. We should not do that. Instead + /etc should always override /run+/usr and also any symlink + destination. -- clean up date formatting and parsing so that all absolute/relative timestamps we format can also be parsed +- .service with invalid Sockets= starts successfully. -- on shutdown: move utmp, wall, audit logic all into PID 1 (or logind?) +- services: add support for cryptographically unlocking per-service directories + via TPM2. Specifically, for StateDirectory= (and related dirs) use fscrypt to + set up the directory so that it can only be accessed if host and app are in + order. -- make repeated alt-ctrl-del presses printing a dump +- shared/wall: Once more programs are taught to prefer sd-login over utmp, + switch the default wall implementation to wall_logind + (https://github.com/systemd/systemd/pull/29051#issuecomment-1704917074) -- currently x-systemd.timeout is lost in the initrd, since crypttab is copied into dracut, but fstab is not +- show whether a service has out-of-date configuration in "systemctl status" by + using mtime data of ConfigurationDirectory=. -- add a pam module that on password changes updates any LUKS slot where the password matches +- shutdown logging: store to EFI var, and store to USB stick? -- test/: - - add unit tests for config_parse_device_allow() +- signed bpf loading: to address need for signature verification for bpf + programs when they are loaded, and given the bpf folks don't think this is + realistic in kernel space, maybe add small daemon that facilitates this + loading on request of clients, validates signatures and then loads the + programs. This daemon should be the only daemon with privs to do load BPF on + the system. It might be a good idea to run this daemon already in the initrd, + and leave it around during the initrd transition, to continue serve requests. + Should then live in its own fs namespace that inherits from the initrd's + fs tree, not from the host, to isolate it properly. Should set + PR_SET_DUMPABLE so that it cannot be ptraced from the host. Should have + CAP_SYS_BPF as only service around. -- seems that when we follow symlinks to units we prefer the symlink - destination path over /etc and /usr. We should not do that. Instead - /etc should always override /run+/usr and also any symlink - destination. +- SIGRTMIN+18 and memory pressure handling should still be added to: hostnamed, + localed, oomd, timedated. -- when isolating, try to figure out a way how we implicitly can order - all units we stop before the isolating unit... +- socket units: allow creating a udev monitor socket with ListenDevices= or so, + with matches, then activate app through that passing socket over -- teach ConditionKernelCommandLine= globs or regexes (in order to match foobar={no,0,off}) +- special case some calls of chase() to use openat2() internally, so + that the kernel does what we otherwise do. -- Add ConditionDirectoryNotEmpty= handle non-absoute paths as a search path or add - ConditionConfigSearchPathNotEmpty= or different syntax? See the discussion starting at - https://github.com/systemd/systemd/pull/15109#issuecomment-607740136. +- Split vconsole-setup in two, of which the second is started via udev (instead + of the "restart" job it currently fires). That way, boot becomes purely + positive again, and we can nicely order the two against each other. -- BootLoaderSpec: define a way how an installer can figure out whether a BLS - compliant boot loader is installed. +- start making use of the new --graceful switch to util-linux' umount command -- BootLoaderSpec: document @saved pseudo-entry, update mention in BLI +- start using STATX_SUBVOL in btrfs_is_subvol(). Also, make use of it + generically, so that image discovery recognizes bcachefs subvols too. -- think about requeuing jobs when daemon-reload is issued? use case: - the initrd issues a reload after fstab from the host is accessible - and we might want to requeue the mounts local-fs acquired through - that automatically. +- storagetm: maybe also serve the specified disk via HTTP? we have glue for + microhttpd anyway already. Idea would also be serve currently booted UKI as + separate HTTP resource, so that EFI http boot on another system could + directly boot from our system, with full access to the hdd. -- systemd-inhibit: make taking delay locks useful: support sending SIGINT or SIGTERM on PrepareForSleep() +- **storagetm:** + - add USB mass storage device logic, so that all local disks are also exposed + as mass storage devices on systems that have a USB controller that can + operate in device mode + - add NVMe authentication -- remove any syslog support from log.c — we probably cannot do this before split-off udev is gone for good +- support boot into nvme-over-tcp: add generator that allows specifying nvme + devices on kernel cmdline + credentials. Also maybe add interactive mode + (where the user is prompted for nvme info), in order to boot from other + system's HDD. -- shutdown logging: store to EFI var, and store to USB stick? +- support crash reporting operation modes (https://live.gnome.org/GnomeOS/Design/Whiteboards/ProblemReporting) -- merge unit_kill_common() and unit_kill_context() +- support projid-based quota in machinectl for containers -- add a dependency on standard-conf.xml and other included files to man pages +- Support ReadWritePaths/ReadOnlyPaths/InaccessiblePaths in systemd --user instances + via the new unprivileged Landlock LSM (https://landlock.io) -- MountFlags=shared acts as MountFlags=slave right now. +- support specifying download hash sum in systemd-import-generator expression + to pin image/tarball. -- properly handle loop back mounts via fstab, especially regards to fsck/passno +- sync dynamic uids/gids between host+portable service (i.e. if DynamicUser=1 is set for a service, make sure that the + selected user is resolvable in the service even if it ships its own /etc/passwd) -- initialize the hostname from the fs label of /, if /etc/hostname does not exist? +- synchronize console access with BSD locks: + https://lists.freedesktop.org/archives/systemd-devel/2014-October/024582.html -- sd-bus: - - EBADSLT handling - - GetAllProperties() on a non-existing object does not result in a failure currently - - port to sd-resolve for connecting to TCP dbus servers - - see if we can introduce a new sd_bus_get_owner_machine_id() call to retrieve the machine ID of the machine of the bus itself - - see if we can drop more message validation on the sending side - - add API to clone sd_bus_message objects - - longer term: priority inheritance - - dbus spec updates: - - NameLost/NameAcquired obsolete - - path escaping - - update systemd.special(7) to mention that dbus.socket is only about the compatibility socket now - - add vtable flag, that may be used to request client creds implicitly - and asynchronously before dispatching the operation - - parse addresses given in sd_bus_set_addresses immediately and not - only when used. Add unit tests. +- sysext: before applying a sysext, do a superficial validation run so that + things are not rearranged to wildy. I.e. protect against accidental fuckups, + such as masking out /usr/lib/ or so. We should probably refuse if existing + inodes are replaced by other types of inodes or so. -- sd-event: - - allow multiple signal handlers per signal? - - document chaining of signal handler for SIGCHLD and child handlers - - define more intervals where we will shift wakeup intervals around in, 1h, 6h, 24h, ... - - maybe support iouring as backend, so that we allow hooking read and write - operations instead of IO ready events into event loops. See considerations - here: - http://blog.vmsplice.net/2020/07/rethinking-event-loop-integration-for.html - - add ability to "chain" event sources. Specifically, add a call - sd_event_source_chain(x, y), which will automatically enable event source y - in oneshot mode once x is triggered. Use case: in src/core/mount.c implement - the /proc/self/mountinfo rescan on SIGCHLD with this: whenever a SIGCHLD is - seen, trigger the rescan defer event source automatically, and allow it to be - dispatched *before* the SIGCHLD is handled (based on priorities). Benefit: - dispatch order is strictly controlled by priorities again. (next step: chain - event sources to the ratelimit being over) - - compat wd reuse in inotify code: keep a set of removed watch - descriptors, and clear this set piecemeal when we see the IN_IGNORED event - for it, or when read() returns EAGAIN or on IN_Q_OVERFLOW. Then, whenever we - see an inotify wd event check against this set, and if it is contained ignore - the event. (to be fully correct this would have to count the occurrences, in - case the same wd is reused multiple times before we start processing - IN_IGNORED again) - - optionally, if per-event source rate limit is hit, downgrade - priority, but leave enabled, and once ratelimit window is over, upgrade - priority again. That way we can combat event source starvation without - stopping processing events from one source entirely. - - similar to existing inotify support add fanotify support (given - that apparently new features in this area are only going to be added to the - latter). - - add 1st class event source for clock changes - - add 1st class event source for timezone changes - - add native support for P_ALL waitid() watching, then move PID 1 to - it for reaping assigned but unknown children. This needs to some special care - to operate somewhat sensibly in light of priorities: P_ALL will return - arbitrary processes, regardless of the priority we want to watch them with, - hence on each event loop iteration check all processes which we shall watch - with higher prio explicitly, and then watch the entire rest with P_ALL. +- sysext: measure all activated sysext into a TPM PCR -- dbus: when a unit failed to load (i.e. is in UNIT_ERROR state), we - should be able to safely try another attempt when the bus call LoadUnit() is invoked. +- system LSFMMBPF policy that enforces that block device backed mounts may only + be established on top of dm-crypt or dm-verity devices, or an allowlist of + file systems (which should probably include vfat, for compat with the ESP) -- document org.freedesktop.MemoryAllocation1 +- system LSFMMBPF policy that prohibits creating files owned by "nobody" + system-wide -- maybe do not install getty@tty1.service symlink in /etc but in /usr? +- system LSFMMBPF policy that prohibits creating or opening device nodes outside + of devtmpfs/tmpfs, except if they are the pseudo-devices /dev/null, + /dev/zero, /dev/urandom and so on. -- print a nicer explanation if people use variable/specifier expansion in ExecStart= for the first word +- "systemctl preset-all" should probably order the unit files it + operates on lexicographically before starting to work, in order to + ensure deterministic behaviour if two unit files conflict (like DMs + do, for example) -- mount: turn dependency information from /proc/self/mountinfo into dependency information between systemd units. +- systemctl, machinectl, loginctl: port "status" commands over to + format-table.c's vertical output logic. -- EFI: - - honor language efi variables for default language selection (if there are any?) - - honor timezone efi variables for default timezone selection (if there are any?) -- bootctl: - - recognize the case when not booted on EFI - - add tool for registering BootXXX entry that boots from some http - server of your choice (i.e. like kernel-bootcfg --add-uri=) - - add reboot-to-disk which takes a block device name, and - automatically sets things up so that system reboots into that device next. - - show whether UEFI audit mode is available - - teach it to prepare an ESP wholesale, i.e. with mkfs.vfat invocation - - teach it to copy in unified kernel images and maybe type #1 boot loader spec entries from host - -- logind: - - logind: optionally, ignore idle-hint logic for autosuspend, block suspend as long as a session is around - - logind: wakelock/opportunistic suspend support - - Add pretty name for seats in logind - - logind: allow showing logout dialog from system? - - add Suspend() bus calls which take timestamps to fix double suspend issues when somebody hits suspend and closes laptop quickly. - - if pam_systemd is invoked by su from a process that is outside of a - any session we should probably just become a NOP, since that's - usually not a real user session but just some system code that just - needs setuid(). - - logind: make the Suspend()/Hibernate() bus calls wait for the for - the job to be completed. before returning, so that clients can wait - for "systemctl suspend" to finish to know when the suspending is - complete. - - logind: when the power button is pressed short, just popup a - logout dialog. If it is pressed for 1s, do the usual - shutdown. Inspiration are Macs here. - - expose "Locked" property on logind session objects - - maybe allow configuration of the StopTimeout for session scopes - - rename session scope so that it includes the UID. THat way - the session scope can be arranged freely in slices and we don't have - make assumptions about their slice anymore. - - follow PropertiesChanged state more closely, to deal with quick logouts and - relogins - - (optionally?) spawn seat-manager@$SEAT.service whenever a seat shows up that as CanGraphical set - - invoke a service manager for "area" logins too. i.e. instantiate - user@.service also for logins where XDG_AREA is set, in per-area fashion, and - ref count it properly. Benefit: graphical logins should start working with - the area logic. - - when logging in, always take an fd to the home dir, to keep the dir - busy, so that autofs release can never happen. (this is generally a good - idea, and specifically works around the fact the autofs ignores busy by mount - namespaces) - -- move multiseat vid/pid matches from logind udev rule to hwdb - -- delay activation of logind until somebody logs in, or when /dev/tty0 pulls it - in or lingering is on (so that containers don't bother with it until PAM is used). also exit-on-idle - -- journal: - - consider introducing implicit _TTY= + _PPID= + _EUID= + _EGID= + _FSUID= + _FSGID= fields - - journald: also get thread ID from client, plus thread name - - journal: when waiting for journal additions in the client always sleep at least 1s or so, in order to minimize wakeups - - add API to close/reopen/get fd for journal client fd in libsystemd-journal. - - fall back to /dev/log based logging in libsystemd-journal, if we cannot log natively? - - declare the local journal protocol stable in the wiki interface chart - - sd-journal: speed up sd_journal_get_data() with transparent hash table in bg - - journald: when dropping msgs due to ratelimit make sure to write - "dropped %u messages" not only when we are about to print the next - message that works, but already after a short timeout - - check if we can make journalctl by default use --follow mode inside of less if called without args? - - maybe add API to send pairs of iovecs via sd_journal_send - - journal: add a setgid "systemd-journal" utility to invoke from libsystemd-journal, which passes fds via STDOUT and does PK access - - journalctl: support negative filtering, i.e. FOOBAR!="waldo", - and !FOOBAR for events without FOOBAR. - - journal: store timestamp of journal_file_set_offline() in the header, - so it is possible to display when the file was last synced. - - journal-send.c, log.c: when the log socket is clogged, and we drop, count this and write a message about this when it gets unclogged again. - - journal: find a way to allow dropping history early, based on priority, other rules - - journal: When used on NFS, check payload hashes - - journald: add kernel cmdline option to disable ratelimiting for debug purposes - - refuse taking lower-case variable names in sd_journal_send() and friends. - - journald: we currently rotate only after MaxUse+MaxFilesize has been reached. - - journal: deal nicely with byte-by-byte copied files, especially regards header - - journal: sanely deal with entries which are larger than the individual file size, but where the components would fit - - Replace utmp, wtmp, btmp, and lastlog completely with journal - - journalctl: instead --after-cursor= maybe have a --cursor=XYZ+1 syntax? - - when a kernel driver logs in a tight loop, we should ratelimit that too. - - journald: optionally, log debug messages to /run but everything else to /var - - journald: when we drop syslog messages because the syslog socket is - full, make sure to write how many messages are lost as first thing - to syslog when it works again. - - journald: allow per-priority and per-service retention times when rotating/vacuuming - - journald: make use of uid-range.h to manage uid ranges to split - journals in. - - journalctl: add the ability to look for the most recent process of a binary. - journalctl /usr/bin/X11 --invocation=-1 - - systemctl: change 'status' to show logs for the last invocation, not a fixed - number of lines - - improve journalctl performance by loading journal files - lazily. Encode just enough information in the file name, so that we - do not have to open it to know that it is not interesting for us, for - the most common operations. - - man: document that corrupted journal files is nothing to act on - - rework journald sigbus stuff to use mutex - - Set RLIMIT_NPROC for systemd-journal-xyz, and all other of our - services that run under their own user ids, and use User= (but only - in a world where userns is ubiquitous since otherwise we cannot - invoke those daemons on the host AND in a container anymore). Also, - if LimitNPROC= is used without User= we should warn and refuse - operation. - - journalctl --verify: don't show files that are currently being - written to as FAIL, but instead show that they are being written to. - - add journalctl -H that talks via ssh to a remote peer and passes through - binary logs data - - add a version of --merge which also merges /var/log/journal/remote - - journalctl: -m should access container journals directly by enumerating - them via machined, and also watch containers coming and going. - Benefit: nspawn --ephemeral would start working nicely with the journal. - - assign MESSAGE_ID to log messages about failed services - - check if loop in decompress_blob_xz() is necessary - - log pidfid as another field, i.e. _PIDFDID= - - beef up ClientContext logic to store pidfd_id of peer, to validate - we really use the right cache entry - - log client's pidfd id as a new automatic field _PIDFDID= or so. - - split up ClientContext cache in two: one cache keyed by pid/pidfdid - with process information, and another one keyed by cgroup path/cgroupid with - cgroup information. This way if a service consisting of many logging - processes can take benefit of the cgroup caching. - - support RFC3164 fully for the incoming syslog transport, see - https://github.com/systemd/systemd/issues/19251#issuecomment-816601955 - - add varlink service that allows subscribing to certain log events, - for example matching by message ID, or log level returns a list of journal - cursors as they happen. - - also collect CLOCK_BOOTTIME timestamps per log entry. Then, derive - "corrected" CLOCK_REALTIME information on display from that and the timestamp - info of the newest entry of the specific boot (as identified by the boot - ID). This way, if a system comes up without a valid clock but acquires a - better clock later, we can "fix" older entry timestamps on display, by - calculating backwards. We cannot use CLOCK_MONOTONIC for this, since it does - not account for suspend phases. This would then also enable us to correct the - kmsg timestamping we consume (where we erroneously assume the clock was in - CLOCK_MONOTONIC, but it actually is CLOCK_BOOTTIME as per kernel). - - generate recognizable log events whenever we shutdown journald - cleanly, and when we migrate run → var. This way tools can verify that a - previous boot terminated cleanly, because either of these two messages must - be safely written to disk, then. - - do journal file writing out-of-process, with one writer process per - client UID, so that synthetic hash table collisions can slow down a specific - user's journal stream down but not the others. - - make sure -f ends when the container indicated by -M terminates - - sigbus API via a signal-handler safe function that people may call - from the SIGBUS handler - -- Hook up journald's FSS logic with TPM2: seal the verification disk by - time-based policy, so that the verification key can remain on host and ve - validated via TPM. - -- rework journalctl -M to be based on a machined method that generates a mount - fd of the relevant journal dirs in the container with uidmapping applied to - allow the host to read it, while making everything read-only. - -- in journald, write out a recognizable log record whenever the system clock is - changed ("stepped"), and in timesyncd whenever we acquire an NTP fix - ("slewing"). Then, in journalctl for each boot time we come across, find - these records, and use the structured info they include to display - "corrected" wallclock time, as calculated from the monotonic timestamp in the - log record, adjusted by the delta declared in the structured log record. - -- in journald: whenever we start a new journal file because the boot ID - changed, let's generate a recognizable log record containing info about old - and new ID. Then, when displaying log stream in journalctl look for these - records, to be able to order them. - -- hook up journald with TPMs? measure new journal records to the TPM in regular - intervals, validate the journal against current TPM state with that. (taking - inspiration from IMA log) - -- sd-journal puts a limit on parallel journal files to view at once. journald - should probably honour that same limit (JOURNAL_FILES_MAX) when vacuuming to - ensure we never generate more files than we can actually view. - -- bsod: maybe use graphical mode. Use DRM APIs directly, see - https://github.com/dvdhrm/docs/blob/master/drm-howto/modeset.c for an example - for doing that. - -- maybe implicitly attach monotonic+realtime timestamps to outgoing messages in - log.c and sd-journal-send - -- journalctl/timesyncd: whenever timesyncd acquires a synchronization from NTP, - create a structured log entry that contains boot ID, monotonic clock and - realtime clock (I mean, this requires no special work, as these three fields - are implicit). Then in journalctl when attempting to display the realtime - timestamp of a log entry, first search for the closest later log entry - of this kinda that has a matching boot id, and convert the monotonic clock - timestamp of the entry to the realtime clock using this info. This way we can - retroactively correct the wallclock timestamps, in particular for systems - without RTC, i.e. where initially wallclock timestamps carry rubbish, until - an NTP sync is acquired. - -- introduce per-unit (i.e. per-slice, per-service) journal log size limits. - -- tweak journald context caching. In addition to caching per-process attributes - keyed by PID, cache per-cgroup attributes (i.e. the various xattrs we read) - keyed by cgroup path, and guarded by ctime changes. This should provide us - with a nice speed-up on services that have many processes running in the same - cgroup. - -- maybe add call sd_journal_set_block_timeout() or so to set SO_SNDTIMEO for - the sd-journal logging socket, and, if the timeout is set to 0, sets - O_NONBLOCK on it. That way people can control if and when to block for - logging. - -- add a test if all entries in the catalog are properly formatted. - (Adding dashes in a catalog entry currently results in the catalog entry - being silently skipped. journalctl --update-catalog must warn about this, - and we should also have a unit test to check that all our message are OK.) - -- build short web pages out of each catalog entry, build them along with man - pages, and include hyperlinks to them in the journal output - -- homed: - - when user tries to log into record signed by unrecognized key, automatically add key to our chain after polkit auth - - rollback when resize fails mid-operation - - GNOME's side for forget key on suspend (requires rework so that lock screen runs outside of uid) - - update LUKS password on login if we find there's a password that unlocks the JSON record but not the LUKS device. - - create on activate? - - properties: icon url?, administrator bool (which translates to 'wheel' membership)?, address?, telephone?, vcard?, samba stuff?, parental controls? - - communicate clearly when usb stick is safe to remove. probably involves - beefing up logind to make pam session close hook synchronous and wait until - systemd --user is shut down. - - logind: maybe keep a "busy fd" as long as there's a non-released session around or the user@.service - - maybe make automatic, read-only, time-based reflink-copies of LUKS disk - images (and btrfs snapshots of subvolumes) (think: time machine) - - distinguish destroy / remove (i.e. currently we can unregister a user, unregister+remove their home directory, but not just remove their home directory) - - fingerprint authentication, pattern authentication, … - - make sure "classic" user records can also be managed by homed - - make size of $XDG_RUNTIME_DIR configurable in user record - - move acct mgmt stuff from pam_systemd_home to pam_systemd? - - when "homectl --pkcs11-token-uri=" is used, synthesize ssh-authorized-keys records for all keys we have private keys on the stick for - - make slice for users configurable (requires logind rework) - - logind: populate auto-login list bus property from PKCS#11 token - - when determining state of a LUKS home directory, check DM suspended sysfs file - - when homed is in use, maybe start the user session manager in a mount namespace with MS_SLAVE, - so that mounts propagate down but not up - eg, user A setting up a backup volume - doesn't mean user B sees it - - use credentials logic/TPM2 logic to store homed signing key - - permit multiple user record signing keys to be used locally, and pick - the right one for signing records automatically depending on a pre-existing - signature - - add a way to "take possession" of a home directory, i.e. strip foreign signatures - and insert a local signature instead. - - as an extension to the directory+subvolume backend: if located on - especially marked fs, then sync down password into LUKS header of that fs, - and always verify passwords against it too. Bootstrapping is a problem - though: if no one is logged in (or no other user even exists yet), how do you - unlock the volume in order to create the first user and add the first pw. - - support new FS_IOC_ADD_ENCRYPTION_KEY ioctl for setting up fscrypt - - maybe pre-create ~/.cache as subvol so that it can have separate quota - easily? - - store PKCS#11 + FIDO2 token info in LUKS2 header, compatible with - systemd-cryptsetup, so that it can unlock homed volumes - - maybe make all *.home files owned by `systemd-home` user or so, so that we - can easily set overall quota for all users - - on login, if we can't fallocate initially, but rebalance is on, then allow - login in discard mode, then immediately rebalance, then turn off discard - - add "homectl unbind" command to remove local user record of an inactive - home dir - - use systemd-storagetm to expose home dirs via nvme-tcp. Then, - teach homed/pam_systemd_homed with a user name such as - lennart%nvme_tcp_192.168.100.77_8787 to log in from any linux host with the - same home dir. Similar maybe for nbd, iscsi? this should then first ask for - the local root pw, to authenticate that logging in like this is ok, and would - then be followed by another password prompt asking for the user's own - password. Also, do something similar for CIFS: if you log in via - lennart%cifs-someserver_someshare, then set up the homed dir for it - automatically. The PAM module should update the user name used for login to - the short version once it set up the user. Some care should be taken, so that - the long version can be still be resolved via NSS afterwards, to deal with - PAM clients that do not support PAM sessions where PAM_USER changes half-way. - - add a basic form of secrets management to homed, that stores - secrets in $HOME somewhere, is protected by the accounts own authentication - mechanisms. Should implement something PKCS#11-like that can be used to - implement emulated FIDO2 in unpriv userspace on top (which should happen - outside of homed), emulated PKCS11, and libsecrets support. Operate with a - 2nd key derived from volume key of the user, with which to wrap all - keys. maintain keys in kernel keyring if possible. - - when resizing an fs don't sync identity beforehand there might simply - not be enough disk space for that. try to be defensive and sync only after - resize. - - if for some reason the partition ended up being much smaller than - whole disk, recover from that, and grow it again. - - if the homed shell fallback thing has access to an SSH agent, try to - use it to unlock home dir (if ssh-agent forwarding is enabled). We - could implement SSH unlocking of a homedir with that: when enrolling a new - ssh pubkey in a user record we'd ask the ssh-agent to sign some random value - with the privkey, then use that as luks key to unlock the home dir. Will not - work for ECDSA keys since their signatures contain a random component, but - will work for RSA and Ed25519 keys. +- **systemctl:** + - add systemctl switch to dump transaction without executing it + - Add a verbose mode to "systemctl start" and friends that explains what is being done or not done + - print nice message from systemctl --failed if there are no entries shown, and hook that into ExecStartPre of rescue.service/emergency.service + - add new command to systemctl: "systemctl system-reexec" which reexecs as many daemons as virtually possible + - systemctl enable: fail if target to alias into does not exist? maybe show how many units are enabled afterwards? + - systemctl: "Journal has been rotated since unit was started." message is misleading + - if some operation fails, show log output? -- add a new switch --auto-definitions=yes/no or so to systemd-repart. If - specified, synthesize a definition automatically if we can: enlarge last - partition on disk, but only if it is marked for growing and not read-only. +- systemd-analyze inspect-elf should show other notes too, at least build-id. + +- systemd-analyze netif that explains predictable interface (or networkctl) + +- systemd-analyze: port "pcrs" verb to talk directly to TPM device, instead of + using sysfs interface (well, or maybe not, as that would require privileges?) + +- systemd-boot: maybe add support for collapsing menu entries of the same OS + into one item that can be opened (like in a "tree view" UI element) or + collapsed. If only a single OS is installed, disable this mode, but if + multiple OSes are installed might make sense to default to it, so that user + is not immediately bombarded with a multitude of Linux kernel versions but + only one for each OS. + +- systemd-creds: extend encryption logic to support asymmetric + encryption/authentication. Idea: add new verb "systemd-creds public-key" + which generates a priv/pub key pair on the TPM2 and stores the priv key + locally in /var. It then outputs a certificate for the pub part to stdout. + This can then be copied/taken elsewhere, and can be used for encrypting creds + that only the host on its specific hw can decrypt. Then, support a drop-in + dir with certificates that can be used to authenticate credentials. Flow of + operations is then this: build image with owner certificate, then after + boot up issue "systemd-creds public-key" to acquire pubkey of the machine. + Then, when passing data to the machine, sign with privkey belonging to one of + the dropped in certs and encrypted with machine pubkey, and pass to machine. + Machine is then able to authenticate you, and confidentiality is guaranteed. + +- systemd-cryptenroll: add --firstboot or so, that will interactively ask user + whether recovery key shall be enrolled and do so + +- systemd-dissect: add --cat switch for dumping files such as /etc/os-release + +- systemd-dissect: show available versions inside of a disk image, i.e. if + multiple versions are around of the same resource, show which ones. (in other + words: show partition labels). + +- systemd-firstboot: optionally install an ssh key for root for offline use. + +- systemd-gpt-auto-generator: add kernel cmdline option to override block + device to dissect. also support dissecting a regular file. useccase: include + encrypted/verity root fs in UKI. + +- systemd-inhibit: make taking delay locks useful: support sending SIGINT or SIGTERM on PrepareForSleep() + +- **systemd-measure tool:** + - pre-calculate PCR 12 (command line) + PCR 13 (sysext) the same way we can precalculate PCR 11 + +- systemd-measure: add --pcrpkey-auto as an alternative to --pcrpkey=, where it + would just use the same public key specified with --public-key= (or the one + automatically derived from --private-key=). + +- systemd-mount should only consider modern file systems when mounting, similar + to systemd-dissect + +- systemd-path: Add "private" runtime/state/cache dir enum, mapping to + $RUNTIME_DIRECTORY, $STATE_DIRECTORY and such -- systemd-repart: +- **systemd-pcrextend:** + - once we have that start measuring every sysext we apply, every confext, + every RootImage= we apply, every nspawn and so on. All in separate fake + PCRs. + +- **systemd-repart:** - implement Integrity=data/meta and Integrity=inline for non-LUKS case. Currently, only Integrity=inline combined with Encrypt= is implemented and uses libcryptsetup features. Add support for plain dm-integrity setups when @@ -2630,139 +2492,128 @@ during boot. - do not print "Successfully resized …" when no change was done. -- document: - - document that deps in [Unit] sections ignore Alias= fields in - [Install] units of other units, unless those units are disabled - - document that service reload may be implemented as service reexec - - add a man page containing packaging guidelines and recommending usage of things like Documentation=, PrivateTmp=, PrivateNetwork= and ReadOnlyDirectories=/etc /usr. - - document systemd-journal-flush.service properly - - documentation: recommend to connect the timer units of a service to the service via Also= in [Install] - - man: document the very specific env the shutdown drop-in tools live in - - man: add more examples to man pages, - - in particular an example how to do the equivalent of switching runlevels - - man: maybe sort directives in man pages, and take sections from --help and apply them to man too - - document root=gpt-auto properly +- systemd-stub: maybe store a "boot counter" in the ESP, and pass it down to + userspace to allow ordering boots (for example in journalctl). The counter + would be monotonically increased on every boot. -- systemctl: - - add systemctl switch to dump transaction without executing it - - Add a verbose mode to "systemctl start" and friends that explains what is being done or not done - - print nice message from systemctl --failed if there are no entries shown, and hook that into ExecStartPre of rescue.service/emergency.service - - add new command to systemctl: "systemctl system-reexec" which reexecs as many daemons as virtually possible - - systemctl enable: fail if target to alias into does not exist? maybe show how many units are enabled afterwards? - - systemctl: "Journal has been rotated since unit was started." message is misleading - - if some operation fails, show log output? +- systemd-sysext: add "exec" command or so that is a bit like "refresh" but + runs it in a new namespace and then just executes the selected binary within + it. Could be useful to run one-off binaries inside a sysext as a CLI tool. -- introduce an option (or replacement) for "systemctl show" that outputs all - properties as JSON, similar to busctl's new JSON output. In contrast to that - it should skip the variant type string though. +- systemd-sysext: optionally, run it in initrd already, before transitioning + into host, to open up possibility for services shipped like that. -- Add a "systemctl list-units --by-slice" mode or so, which rearranges the - output of "systemctl list-units" slightly by showing the tree structure of - the slices, and the units attached to them. +- systemd-tmpfiles: add concept for conditionalizing lines on factory reset + boot, or on first boot. -- add "systemctl wait" or so, which does what "systemd-run --wait" does, but - for all units. It should be both a way to pin units into memory as well as a - wait to retrieve their exit data. +- systemd-tpm2-setup should support a mode where we refuse booting if the SRK + changed. (Must be opt-in, to not break systems which are supposed to be + migratable between PCs) -- show whether a service has out-of-date configuration in "systemctl status" by - using mtime data of ConfigurationDirectory=. +- systemd-tpm2-support: add a some logic that detects if system is in DA + lockout mode, and queries the user for TPM recovery PIN then. -- "systemctl preset-all" should probably order the unit files it - operates on lexicographically before starting to work, in order to - ensure deterministic behaviour if two unit files conflict (like DMs - do, for example) +- systemd: add storage API via varlink, where everyone can drop a socket in a + dir, similar, do the same thing for networking -- Add a new verb "systemctl top" +- $SYSTEMD_EXECPID that the service manager sets should + be augmented with $SYSTEMD_EXECPIDFD (and similar for + other env vars we might send). -- unit install: - - "systemctl mask" should find all names by which a unit is accessible - (i.e. by scanning for symlinks to it) and link them all to /dev/null +- **sysupdate:** + - add fuzzing to the pattern parser + - support casync as download mechanism + - "systemd-sysupdate update --all" support, that iterates through all components + defined on the host, plus all images installed into /var/lib/machines/, + /var/lib/portable/ and so on. + - Allow invocation with a single transfer definition, i.e. with + --definitions= pointing to a file rather than a dir. + - add ability to disable implicit decompression of downloaded artifacts, + i.e. a Compress=no option in the transfer definitions + - download multiple arbitrary patterns from same source + - SHA256SUMS format with bearer tokens for each resource to download + - decrypt SHA256SUMS with key from tpm + - clean up stuff on disk that disappears from SHA256SUMS + - turn http backend stuff int plugin via varlink + - for each transfer support looking at multiple sources, + pick source with newest entry. If multiple sources have the same entry, use + first configured source. Usecase: "sideload" components from local dirs, + without disabling remote sources. + - support "revoked" items, which cause the client to + downgrade/upgrade + - introduce per-user version that can update per-user installed dDIs + - make transport pluggable, so people can plug casync or + similar behind it, instead of http. -- nspawn: - - emulate /dev/kmsg using CUSE and turn off the syslog syscall - with seccomp. That should provide us with a useful log buffer that - systemd can log to during early boot, and disconnect container logs - from the kernel's logs. - - as soon as networkd has a bus interface, hook up --network-interface=, - --network-bridge= with networkd, to trigger netdev creation should an - interface be missing - - a nice way to boot up without machine id set, so that it is set at boot - automatically for supporting --ephemeral. Maybe hash the host machine id - together with the machine name to generate the machine id for the container - - fix logic always print a final newline on output. - https://github.com/systemd/systemd/pull/272#issuecomment-113153176 - - should optionally support receiving WATCHDOG=1 messages from its payload - PID 1... - - optionally automatically add FORWARD rules to iptables whenever nspawn is - running, remove them when shut down. - - add support for sysext extensions, too. i.e. a new --extension= switch that - takes one or more arguments, and applies the extensions already during - startup. - - when main nspawn supervisor process gets suspended due to SIGSTOP/SIGTTOU - or so, freeze the payload too. - - support time namespaces - - on cgroupsv1 issue cgroup empty handler process based on host events, so - that we make cgroup agent logic safe - - add API to invoke binary in container, then use that as fallback in - "machinectl shell" - - make nspawn suitable for shell pipelines: instead of triggering a hangup - when input is finished, send ^D, which synthesizes an EOF. Then wait for - hangup or ^D before passing on the EOF. - - greater control over selinux label? - - support that /proc, /sys/, /dev are pre-mounted - - maybe allow TPM passthrough, backed by swtpm, and measure --image= hash - into its PCR 11, so that nspawn instances can be TPM enabled, and partake - in measurements/remote attestation and such. swtpm would run outside of - control of container, and ideally would itself bind its encryption keys to - host TPM. - - make boot assessment do something sensible in a container. i.e send an - sd_notify() from payload to container manager once boot-up is completed - successfully, and use that in nspawn for dealing with boot counting, - implemented in the partition table labels and directory names. - - optionally set up nftables/iptables routes that forward UDP/TCP traffic on - port 53 to resolved stub 127.0.0.54 - - maybe optionally insert .nspawn file as GPT partition into images, so that - such container images are entirely stand-alone and can be updated as one. - - The subreaper logic we currently have seems overly complex. We should - investigate whether creating the inner child with CLONE_PARENT isn't better. - - Reduce the number of sockets that are currently in use and just rely on one - or two sockets. - - map foreign UID range through 1:1 - - d-nspawn should get the same SSH key support that vmspawn now has. +- sysusers: allow specifying a path to an inode *and* a literal UID in the UID + column, so that if the inode exists it is used, and if not the literal UID is + used. Use this for services such as the imds one, which run under their own + UID in the initrd, and whose data should survive to the host, properly owned. -- machined: - - add an API so that libvirt-lxc can inform us about network interfaces being - removed or added to an existing machine - - "machinectl migrate" or similar to copy a container from or to a - difference host, via ssh - - introduce systemd-nspawn-ephemeral@.service, and hook it into - "machinectl start" with a new --ephemeral switch - - "machinectl status" should also show internal logs of the container in - question - - "machinectl history" - - "machinectl diff" - - "machinectl commit" that takes a writable snapshot of a tree, invokes a - shell in it, and marks it read-only after use - - gc for OCI layers that are not referenced anymore by any .mstack/ links. - - optionally track nspawn unix-export/ runtime for each machined, and - then update systemd-ssh-proxy so that it can connect to that. +- teach ConditionKernelCommandLine= globs or regexes (in order to match foobar={no,0,off}) + +- teach nspawn/machined a new bus call/verb that gets you a + shell in containers that have no sensible pid1, via joining the container, + and invoking a shell directly. Then provide another new bus call/vern that is + somewhat automatic: if we detect that pid1 is running and fully booted up we + provide a proper login shell, otherwise just a joined shell. Then expose that + as primary way into the container. + +- teach parse_timestamp() timezones like the calendar spec already knows it + +- teach systemd-nspawn the boot assessment logic: hook up vpick's try counters + with success notifications from nspawn payloads. When this is enabled, + automatically support reverting back to older OS version images if newer ones + fail to boot. + +- **test/:** + - add unit tests for config_parse_device_allow() + +- The bind(AF_UNSPEC) construct (for resetting sockets to their initial state) + should be blocked in many cases because it punches holes in many sandboxes. + +- the pub/priv key pair generated on the TPM2 should probably also be one you + can use to get a remote attestation quote. + +- The udev blkid built-in should expose a property that reflects + whether media was sensed in USB CF/SD card readers. This should then + be used to control SYSTEMD_READY=1/0 so that USB card readers aren't + picked up by systemd unless they contain a medium. This would mirror + the behaviour we already have for CD drives. + +- There's currently no way to cancel fsck (used to be possible via C-c or c on the console) + +- there's probably something wrong with having user mounts below /sys, + as we have for debugfs. for example, src/core/mount.c handles mounts + prefixed with /sys generally special. + https://lists.freedesktop.org/archives/systemd-devel/2015-June/032962.html + +- think about requeuing jobs when daemon-reload is issued? use case: + the initrd issues a reload after fstab from the host is accessible + and we might want to requeue the mounts local-fs acquired through + that automatically. + +- **timer units:** + - timer units should get the ability to trigger when DST changes + - Modulate timer frequency based on battery state -- udev: - - move to LGPL - - kill scsi_id - - add trigger --subsystem-match=usb/usb_device device - - reimport udev db after MOVE events for devices without dev_t - - re-enable ProtectClock= once only cgroupsv2 is supported. - See f562abe2963bad241d34e0b308e48cf114672c84. +- timesyncd: add ugly bus calls to set NTP servers per-interface, for usage by NM -- coredump: - - save coredump in Windows/Mozilla minidump format - - when truncating coredumps, also log the full size that the process had, and make a metadata field so we can report truncated coredumps - - add examples for other distros in PACKAGE_METADATA_FOR_EXECUTABLE_FILES +- timesyncd: when saving/restoring clock try to take boot time into account. + Specifically, along with the saved clock, store the current boot ID. When + starting, check if the boot id matches. If so, don't do anything (we are on + the same boot and clock just kept running anyway). If not, then read + CLOCK_BOOTTIME (which started at boot), and add it to the saved clock + timestamp, to compensate for the time we spent booting. If EFI timestamps are + available, also include that in the calculation. With this we'll then only + miss the time spent during shutdown after timesync stopped and before the + system actually reset. -- support crash reporting operation modes (https://live.gnome.org/GnomeOS/Design/Whiteboards/ProblemReporting) +- tiny varlink service that takes a fd passed in and serves it via http. Then + make use of that in networkd, and expose some EFI binary of choice for + DHCP/HTTP base EFI boot. -- tmpfiles: +- **tmpfiles:** - allow time-based cleanup in r and R too - instead of ignoring unknown fields, reject them. - creating new directories/subvolumes/fifos/device nodes @@ -2778,59 +2629,213 @@ target dir. then use that to move sysexts/confexts and stuff from initrd tmpfs to /run/, so that host can pick things up. -- udev-link-config: +- To mimic the new tpm2-measure-pcr= crypttab option and tpm2-measure-nvpcr= + veritytab option, add the same to integritytab (measuring the HMAC key if one + is used) + +- tpm2-setup: reboot if we detect SRK changed + +- tpm2: add (optional) support for generating a local signing key from PCR 15 + state. use private key part to sign PCR 7+14 policies. stash signatures for + expected PCR7+14 policies in EFI var. use public key part in disk encryption. + generate new sigs whenever db/dbx/mok/mokx gets updated. that way we can + securely bind against SecureBoot/shim state, without having to renroll + everything on each update (but we still have to generate one sig on each + update, but that should be robust/idempotent). needs rollback protection, as + usual. + +- TPM2: auto-reenroll in cryptsetup, as fallback for hosed firmware upgrades + and such + +- track the per-service PAM process properly (i.e. as an additional control + process), so that it may be queried on the bus and everything. + +- transient units: don't bother with actually setting unit properties, we + reload the unit file anyway + +- **transient units:** + - add field to transient units that indicate whether systemd or somebody else saves/restores its settings, for integration with libvirt + +- Turn systemd-networkd-wait-online into a small varlink service that people + can talk to and specify exactly what to wait for via a method call, and get a + response back once that level of "online" is reached. + +- tweak journald context caching. In addition to caching per-process attributes + keyed by PID, cache per-cgroup attributes (i.e. the various xattrs we read) + keyed by cgroup path, and guarded by ctime changes. This should provide us + with a nice speed-up on services that have many processes running in the same + cgroup. + +- tweak sd-event's child watching: keep a prioq of children to watch and use + waitid() only on the children with the highest priority until one is waitable + and ignore all lower-prio ones from that point on + +- **udev-link-config:** - Make sure ID_PATH is always exported and complete for network devices where possible, so we can safely rely on Path= matching -- sd-rtnl: - - add support for more attribute types - - inbuilt piping support (essentially degenerate async)? see loopback-setup.c and other places +- **udev:** + - move to LGPL + - kill scsi_id + - add trigger --subsystem-match=usb/usb_device device + - reimport udev db after MOVE events for devices without dev_t + - re-enable ProtectClock= once only cgroupsv2 is supported. + See f562abe2963bad241d34e0b308e48cf114672c84. -- networkd: - - add more keys to [Route] and [Address] sections - - add support for more DHCPv4 options (and, longer term, other kinds of dynamic config) - - add reduced [Link] support to .network files - - properly handle routerless dhcp leases - - work with non-Ethernet devices - - dhcp: do we allow configuring dhcp routes on interfaces that are not the one we got the dhcp info from? - - the DHCP lease data (such as NTP/DNS) is still made available when - a carrier is lost on a link. It should be removed instantly. - - expose in the API the following bits: - - option 15, domain name - - option 12, hostname and/or option 81, fqdn - - option 123, 144, geolocation - - option 252, configure http proxy (PAC/wpad) - - provide a way to define a per-network interface default metric value - for all routes to it. possibly a second default for DHCP routes. - - allow Name= to be specified repeatedly in the [Match] section. Maybe also - support Name=foo*|bar*|baz ? - - whenever uplink info changes, make DHCP server send out FORCERENEW +- **udevadm: to make symlink querying with udevadm nicer:** + - do not enable the pager for queries like 'udevadm info -q symlink -r' + - add mode with newlines instead of spaces (for grep)? -- in networkd, when matching device types, fix up DEVTYPE rubbish the kernel passes to us +- udevd: extend memory pressure logic: also kill any idle worker processes -- Figure out how to do unittests of networkd's state serialization +- unify how blockdev_get_root() and sysupdate find the default root block device -- dhcp: - - figure out how much we can increase Maximum Message Size +- **unify on openssl:** + - figure out what to do about libmicrohttpd: + - 1.x is stable and has a hard dependency on gnutls + - 2.x is in development and has openssl support + - Worth testing against 2.x in our CI? + - port fsprg over to openssl -- dhcp6: - - add functions to set previously stored IPv6 addresses on startup and get - them at shutdown; store them in client->ia_na - - write more test cases - - implement reconfigure support, see 5.3., 15.11. and 22.20. - - implement support for temporary addresses (IA_TA) - - implement dhcpv6 authentication - - investigate the usefulness of Confirm messages; i.e. are there any - situations where the link changes without any loss in carrier detection - or interface down - - some servers don't do rapid commit without a filled in IA_NA, verify - this behavior - - RouteTable= ? +- **unit files:** + - allow port=0 in .socket units + - maybe introduce ExecRestartPre= + - implement Register= switch in .socket units to enable registration + in Avahi, RPC and other socket registration services. + - allow Type=simple with PIDFile= + https://bugzilla.redhat.com/show_bug.cgi?id=723942 + - allow writing multiple conditions in unit files on one line + - add a concept of RemainAfterExit= to scope units + - Allow multiple ExecStart= for all Type= settings, so that we can cover rescue.service nicely + - add verification of [Install] section to systemd-analyze verify -- shared/wall: Once more programs are taught to prefer sd-login over utmp, - switch the default wall implementation to wall_logind - (https://github.com/systemd/systemd/pull/29051#issuecomment-1704917074) +- **unit install:** + - "systemctl mask" should find all names by which a unit is accessible + (i.e. by scanning for symlinks to it) and link them all to /dev/null -- Hook up systemd-journal-upload with RESTART_RESET=1 logic (should probably - be conditioned on the num of successfully uploaded entries?) +- update HACKING.md to suggest developing systemd with the ideas from: + https://0pointer.net/blog/testing-my-system-code-in-usr-without-modifying-usr.html + https://0pointer.net/blog/running-an-container-off-the-host-usr.html + +- use name_to_handle_at() with AT_HANDLE_FID instead of .st_ino (inode + number) for identifying inodes, for example in copy.c when finding hard + links, or loop-util.c for tracking backing files, and other places. + +- use sd-event ratelimit feature optionally for journal stream clients that log + too much + +- userdb: allow existence checks + +- userdb: when synthesizing NSS records, pick "best" password from defined + passwords, not just the first. i.e. if there are multiple defined, prefer + unlocked over locked and prefer non-empty over empty. + +- userdbd: implement an additional varlink service socket that provides the + host user db in restricted form, then allow this to be bind mounted into + sandboxed environments that want the host database in minimal form. All + records would be stripped of all meta info, except the basic UID/name + info. Then use this in portabled environments that do not use PrivateUsers=1. + +- validatefs: validate more things: check if image id + os id of initrd match + target mount, so that we refuse early any attempts to boot into different + images with the wrong kernels. check min/max kernel version too. all encoded + via xattrs in the target fs. + +- Varlinkification of the following command line tools, to open them up to + other programs via IPC: + - coredumpcl + - systemd-bless-boot + - systemd-measure + - systemd-cryptenroll (to allow UIs to enroll FIDO2 keys and such) + - systemd-dissect + - systemd-sysupdate + - systemd-analyze + - kernel-install + - systemd-mount (with PK so that desktop environments could use it to mount disks) + +- verify that the AF_UNIX sockets of a service in the fs still exist + when we start a service in order to avoid confusion when a user + assumes starting a service is enough to make it accessible + +- vmspawn switch default swtpm PCR bank to SHA384-only (away from SHA256), at + least on 64bit archs, simply because SHA384 is typically double the hashing + speed than SHA256 on 64bit archs (since based on 64bit words unlike SHA256 + which uses 32bit words). + +- **vmspawn:** + - --ephemeral support + - --read-only support + - automatically suspend/resume the VM if the host suspends. Use logind + suspend inhibitor to implement this. request clean suspend by generating + suspend key presses. + - support for "real" networking via "-n" and --network-bridge= + - translate SIGTERM to clean ACPI shutdown event + - implement hotkeys ^]^]r and ^]^]p like nspawn + +- we probably needs .pcrpkeyrd or so as additional PE section in UKIs, + which contains a separate public key for PCR values that only apply in the + initrd, i.e. in the boot phase "enter-initrd". Then, consumers in userspace + can easily bind resources to just the initrd. Similar, maybe one more for + "enter-initrd:leave-initrd" for resources that shall be accessible only + before unprivileged user code is allowed. (we only need this for .pcrpkey, + not for .pcrsig, since the latter is a list of signatures anyway). With that, + when you enroll a LUKS volume or similar, pick either the .pcrkey (for + coverage through all phases of the boot, but excluding shutdown), the + .pcrpkeyrd (for coverage in the initrd only) and .pcrpkeybt (for coverage + until users are allowed to log in). + +- we probably should have some infrastructure to acquire sysexts with + drivers/firmware for local hardware automatically. Idea: reuse the modalias + logic of the kernel for this: make the main OS image install a hwdb file + that matches against local modalias strings, and adds properties to relevant + devices listing names of sysexts needed to support the hw. Then provide some + tool that goes through all devices and tries to acquire/download the + specified images. + +- We should probably replace /etc/rc.d/README with a symlink to doc + content. After all it is constant vendor data. + +- We should start measuring all services, containers, and system extensions we + activate. probably into PCR 13. i.e. add --tpm2-measure-pcr= or so to + systemd-nspawn, and MeasurePCR= to unit files. Should contain a measurement + of the activated configuration and the image that is being activated (in case + verity is used, hash of the root hash). + +- what to do about udev db binary stability for apps? (raw access is not an option) + +- when importing an fs tree with machined, complain if image is not an OS + +- when importing an fs tree with machined, optionally apply userns-rec-chown + +- when isolating, try to figure out a way how we implicitly can order + all units we stop before the isolating unit... + +- when killing due to service watchdog timeout maybe detect whether target + process is under ptracing and then log loudly and continue instead. + +- when mounting disk images: if IMAGE_ID/IMAGE_VERSION is set in os-release + data in the image, make sure the image filename actually matches this, so + that images cannot be misused. + +- when no locale is configured, default to UEFI's PlatformLang variable + +- when switching root from initrd to host, set the machine_id env var so that + if the host has no machine ID set yet we continue to use the random one the + initrd had set. + +- when systemd-sysext learns mutable /usr/ (and systemd-confext mutable /etc/) + then allow them to store the result in a .v/ versioned subdir, for some basic + snapshot logic + +- when we detect that there are waiting jobs but no running jobs, do something + +- whenever we receive fds via SCM_RIGHTS make sure none got dropped due to the + reception limit the kernel silently enforces. + +- write a document explaining how to write correct udev rules. Mention things + such as: + 1. do not do lists of vid/pid matches, use hwdb for that + 2. add|change action matches are typically wrong, should be != remove + 3. use GOTO, make rules short + 4. people shouldn't try to make rules file non-world-readable From 4ff8fb899ba94e7b197f8c55a7797adb648791b4 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 20:59:35 +0000 Subject: [PATCH 0609/1296] journal: add assert for max_size overflow safety Coverity flags max_size*2 as a potential overflow. The value is bounded by MAX_SIZE_UPPER (128 MiB) or JOURNAL_COMPACT_SIZE_MAX (4 GiB), so doubling is safe within uint64_t. Add an assert to document this. CID#1548019 Follow-up for 8580d1f73db36e9383e674e388b4fb55828c0c66 --- src/libsystemd/sd-journal/journal-file.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index de5d7075e0cca..b4efcc050eaae 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -4058,6 +4058,8 @@ static void journal_default_metrics(JournalMetrics *m, int fd, bool compact) { if (m->max_size < JOURNAL_FILE_SIZE_MIN) m->max_size = JOURNAL_FILE_SIZE_MIN; + /* Silence static analyzers */ + assert(m->max_size <= UINT64_MAX / 2); if (m->max_use != 0 && m->max_size*2 > m->max_use) m->max_use = m->max_size*2; } From a702b9b6ea9bde91f21f218ec18305f5b7c56f38 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 21:01:34 +0000 Subject: [PATCH 0610/1296] sd-json: silence false positive in sd_json_variant_filter Same pattern as the fix for sd_json_variant_unset_field in 9b3715d529e4eba79e19c87e85583f7be5ee2c95: cache the element count in a local variable and assert it is at least 2 before subtracting. CID#1548029 Follow-up for f2ff34ff2aaafd313a5c62b4b9f13ba6777731e5 --- src/libsystemd/sd-json/sd-json.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index b959fe16286ac..03557d3ac6822 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -1940,7 +1940,7 @@ _public_ int sd_json_variant_filter(sd_json_variant **v, char **to_remove) { if (strv_isempty(to_remove)) return 0; - for (size_t i = 0; i < sd_json_variant_elements(*v); i += 2) { + for (size_t i = 0, m = sd_json_variant_elements(*v); i < m; i += 2) { sd_json_variant *p; p = sd_json_variant_by_index(*v, i); @@ -1949,7 +1949,9 @@ _public_ int sd_json_variant_filter(sd_json_variant **v, char **to_remove) { if (strv_contains(to_remove, sd_json_variant_string(p))) { if (!array) { - array = new(sd_json_variant*, sd_json_variant_elements(*v) - 2); + /* Silence static analyzers */ + assert(m >= 2); + array = new(sd_json_variant*, m - 2); if (!array) return -ENOMEM; From 56df6e5669a8c9a94ff1026f60db1b39033eb469 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 21:03:14 +0000 Subject: [PATCH 0611/1296] sd-daemon: add assert before CMSG_SPACE subtraction Coverity flags the subtraction from msg_controllen as a potential underflow. The CMSG_SPACE was added when send_ucred was set, and the subtraction only runs when send_ucred was true, so it is safe. Add an assert to document this invariant. CID#1548074 Follow-up for 64144440a5d2d94482f882b992fd2a4e0dca7a05 --- src/libsystemd/sd-daemon/sd-daemon.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libsystemd/sd-daemon/sd-daemon.c b/src/libsystemd/sd-daemon/sd-daemon.c index 2ab50287b4ffa..2937ac569c321 100644 --- a/src/libsystemd/sd-daemon/sd-daemon.c +++ b/src/libsystemd/sd-daemon/sd-daemon.c @@ -603,6 +603,8 @@ static int pid_notify_with_fds_internal( return log_debug_errno(errno, "Failed to send notify message to '%s': %m", e); /* If that failed, try with our own ucred instead */ + /* Silence static analyzers */ + assert(msghdr.msg_controllen >= CMSG_SPACE(sizeof(struct ucred))); msghdr.msg_controllen -= CMSG_SPACE(sizeof(struct ucred)); if (msghdr.msg_controllen == 0) msghdr.msg_control = NULL; From 55354d5930fd0b7952d649d9ad5a850279fc73e1 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 21:11:48 +0000 Subject: [PATCH 0612/1296] sd-bus: add asserts for message size overflow safety Coverity flags arithmetic in BUS_MESSAGE_SIZE(), BUS_MESSAGE_BODY_BEGIN() and message_from_header() as potential overflows. The values are validated at message creation time, but add asserts to make the invariants explicit for static analyzers. CID#1548023 CID#1548030 CID#1548046 Follow-up for 6629161f827c82889cf45cfcdce62dcb543eda23 --- src/libsystemd/sd-bus/bus-message.c | 2 ++ src/libsystemd/sd-bus/bus-message.h | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/src/libsystemd/sd-bus/bus-message.c b/src/libsystemd/sd-bus/bus-message.c index c300d511ab1c3..1f278db54e57e 100644 --- a/src/libsystemd/sd-bus/bus-message.c +++ b/src/libsystemd/sd-bus/bus-message.c @@ -362,6 +362,8 @@ static int message_from_header( if (label) { label_sz = strlen(label); + /* Silence static analyzers */ + assert(label_sz <= SIZE_MAX - ALIGN(sizeof(sd_bus_message)) - 1); a += label_sz + 1; } diff --git a/src/libsystemd/sd-bus/bus-message.h b/src/libsystemd/sd-bus/bus-message.h index c15e947fbf8d1..fe9679393ecea 100644 --- a/src/libsystemd/sd-bus/bus-message.h +++ b/src/libsystemd/sd-bus/bus-message.h @@ -153,6 +153,9 @@ static inline uint64_t BUS_MESSAGE_COOKIE(sd_bus_message *m) { } static inline size_t BUS_MESSAGE_SIZE(sd_bus_message *m) { + /* Silence static analyzers */ + assert(ALIGN8(m->fields_size) <= SIZE_MAX - sizeof(BusMessageHeader)); + assert(m->body_size <= SIZE_MAX - sizeof(BusMessageHeader) - ALIGN8(m->fields_size)); return sizeof(BusMessageHeader) + ALIGN8(m->fields_size) + @@ -160,6 +163,8 @@ static inline size_t BUS_MESSAGE_SIZE(sd_bus_message *m) { } static inline size_t BUS_MESSAGE_BODY_BEGIN(sd_bus_message *m) { + /* Silence static analyzers */ + assert(ALIGN8(m->fields_size) <= SIZE_MAX - sizeof(BusMessageHeader)); return sizeof(BusMessageHeader) + ALIGN8(m->fields_size); From 02f848b3752c9de4d00954e77b9069ae717d82fe Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 21:12:31 +0000 Subject: [PATCH 0613/1296] user-util: add asserts for buffer allocation overflow safety Coverity flags ALIGN(sizeof(struct passwd/group)) + bufsize as potential overflows in the getpw/getgr helpers. Add asserts to make the bounds explicit for static analyzers. CID#1548047 CID#1548049 CID#1548069 CID#1548070 Follow-up for 75673cd8aee5c6174538e71dd36c7a353c836973 --- src/basic/user-util.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/basic/user-util.c b/src/basic/user-util.c index a4ae020c2c6bc..93a3852879b29 100644 --- a/src/basic/user-util.c +++ b/src/basic/user-util.c @@ -1113,6 +1113,8 @@ int getpwnam_malloc(const char *name, struct passwd **ret) { for (;;) { _cleanup_free_ void *buf = NULL; + /* Silence static analyzers */ + assert(bufsize <= SIZE_MAX - ALIGN(sizeof(struct passwd))); buf = malloc0(ALIGN(sizeof(struct passwd)) + bufsize); if (!buf) return -ENOMEM; @@ -1154,6 +1156,8 @@ int getpwuid_malloc(uid_t uid, struct passwd **ret) { for (;;) { _cleanup_free_ void *buf = NULL; + /* Silence static analyzers */ + assert(bufsize <= SIZE_MAX - ALIGN(sizeof(struct passwd))); buf = malloc0(ALIGN(sizeof(struct passwd)) + bufsize); if (!buf) return -ENOMEM; @@ -1198,6 +1202,8 @@ int getgrnam_malloc(const char *name, struct group **ret) { for (;;) { _cleanup_free_ void *buf = NULL; + /* Silence static analyzers */ + assert(bufsize <= SIZE_MAX - ALIGN(sizeof(struct group))); buf = malloc0(ALIGN(sizeof(struct group)) + bufsize); if (!buf) return -ENOMEM; @@ -1237,6 +1243,8 @@ int getgrgid_malloc(gid_t gid, struct group **ret) { for (;;) { _cleanup_free_ void *buf = NULL; + /* Silence static analyzers */ + assert(bufsize <= SIZE_MAX - ALIGN(sizeof(struct group))); buf = malloc0(ALIGN(sizeof(struct group)) + bufsize); if (!buf) return -ENOMEM; From 09bb6448ae221c09a00d1f4a9b45ce8535003319 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 21:14:07 +0000 Subject: [PATCH 0614/1296] limits-util: add assert for physical memory calculation overflow Coverity flags (uint64_t) sc * (uint64_t) ps as a potential overflow. Add an assert to make the bounds explicit for static analyzers. CID#1548042 Follow-up for eefc66aa8f77c96a13a78d6c40c79ed7f3d6dc9d --- src/basic/limits-util.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/basic/limits-util.c b/src/basic/limits-util.c index d48d67dbf7785..02fbe92cc7712 100644 --- a/src/basic/limits-util.c +++ b/src/basic/limits-util.c @@ -28,6 +28,8 @@ uint64_t physical_memory(void) { assert(sc > 0); ps = page_size(); + /* Silence static analyzers */ + assert((uint64_t) sc <= UINT64_MAX / (uint64_t) ps); mem = (uint64_t) sc * (uint64_t) ps; r = cg_get_root_path(&root); From 21cc3216a7c572c6689a7406e37ec0f6915ad3fc Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 21:14:35 +0000 Subject: [PATCH 0615/1296] dns-packet: add asserts for allocation overflow safety Coverity flags ALIGN(sizeof(DnsPacket)) + size calculations in dns_packet_new() and dns_packet_dup() as potential overflows. The sizes are bounded by DNS_PACKET_SIZE_MAX but add asserts to make this explicit for static analyzers. CID#1548058 CID#1548076 Follow-up for c73ce96b569e2f10dff64b7dc0bd271972674c2a --- src/shared/dns-packet.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/shared/dns-packet.c b/src/shared/dns-packet.c index cdd56d513faba..d333b255eefbe 100644 --- a/src/shared/dns-packet.c +++ b/src/shared/dns-packet.c @@ -120,6 +120,8 @@ int dns_packet_new( a = min_alloc_dsize; /* round up to next page size */ + /* Silence static analyzers */ + assert(a <= SIZE_MAX - ALIGN(sizeof(DnsPacket))); a = PAGE_ALIGN(ALIGN(sizeof(DnsPacket)) + a) - ALIGN(sizeof(DnsPacket)); /* make sure we never allocate more than useful */ @@ -226,6 +228,8 @@ int dns_packet_dup(DnsPacket **ret, DnsPacket *p) { if (r < 0) return r; + /* Silence static analyzers */ + assert(p->size <= SIZE_MAX - ALIGN(sizeof(DnsPacket))); c = malloc(ALIGN(sizeof(DnsPacket)) + p->size); if (!c) return -ENOMEM; From 3fd6774633d05850de78cc9c095f377e922609ac Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 21:19:14 +0000 Subject: [PATCH 0616/1296] networkd-ndisc: add assert for DNSSL allocation overflow safety Coverity flags ALIGN(sizeof(NDiscDNSSL)) + strlen(*j) + 1 as a potential overflow. Domain names are protocol-bounded but add an assert to make this explicit for static analyzers. CID#1548066 Follow-up for 1e7a0e21c97ac1bbc743009e5ec8c12bc6200e19 --- src/network/networkd-ndisc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 93965f52536ec..e6b03c0826cad 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -1909,6 +1909,8 @@ static int ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt, bool zero _cleanup_free_ NDiscDNSSL *s = NULL; NDiscDNSSL *dnssl; + /* Silence static analyzers */ + assert(strlen(*j) <= SIZE_MAX - ALIGN(sizeof(NDiscDNSSL)) - 1); s = malloc0(ALIGN(sizeof(NDiscDNSSL)) + strlen(*j) + 1); if (!s) return log_oom(); From 96b085c4beb48fe5e3fbed0e13462ca302d0a283 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 21:20:39 +0000 Subject: [PATCH 0617/1296] repart: add assert for offset + current_size overflow safety Coverity flags a->after->offset + a->after->current_size as a potential overflow. Both values are validated as not UINT64_MAX by existing asserts, add an explicit overflow check to document the invariant for static analyzers. CID#1548063 Follow-up for e594a3b154bd06c535a934a1cc7231b1ef76df73 --- src/repart/repart.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/repart/repart.c b/src/repart/repart.c index d672db6d266b4..7a8bc00919e85 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -1247,6 +1247,8 @@ static uint64_t free_area_current_end(Context *context, const FreeArea *a) { assert(a->after->offset != UINT64_MAX); assert(a->after->current_size != UINT64_MAX); + /* Silence static analyzers */ + assert(a->after->current_size <= UINT64_MAX - a->after->offset); /* Calculate where the free area ends, based on the offset of the partition preceding it. */ return round_up_size(a->after->offset + a->after->current_size, context->grain_size) + free_area_available(a); From 331461a5a2ffe323190c4ca6b7bcd35944e36f92 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 21:28:56 +0000 Subject: [PATCH 0618/1296] tree-wide: add assert_cc for time constant multiplications Coverity flags compile-time constant multiplications of USEC_PER_SEC, USEC_PER_MSEC, and USEC_PER_HOUR as potential overflows. Add assert_cc() to prove they fit at build time. CID#1548025 CID#1548048 CID#1548055 CID#1548059 Follow-up for 500727c220354b81b68ed6667d9a6f0fafe3ba19 Follow-up for 27d340c772fb1b251085dba7bd5420484f7c5892 Follow-up for e537352b9bfffe6f6286483bff2c7601c78407e3 Follow-up for 1007ec60e664da03b7aea4803c643d991fcf6530 --- src/fsck/fsck.c | 1 + src/shared/utmp-wtmp.c | 2 ++ src/test/test-path.c | 2 ++ src/test/test-time-util.c | 2 ++ 4 files changed, 7 insertions(+) diff --git a/src/fsck/fsck.c b/src/fsck/fsck.c index 9767568724faf..43cc208e598a2 100644 --- a/src/fsck/fsck.c +++ b/src/fsck/fsck.c @@ -218,6 +218,7 @@ static int process_progress(int fd, FILE* console) { /* Only update once every 50ms */ t = now(CLOCK_MONOTONIC); + assert_cc(50 * USEC_PER_MSEC <= USEC_INFINITY); if (last + 50 * USEC_PER_MSEC > t) continue; diff --git a/src/shared/utmp-wtmp.c b/src/shared/utmp-wtmp.c index 6d150e7dcf3ef..cfc6ae5a3ddba 100644 --- a/src/shared/utmp-wtmp.c +++ b/src/shared/utmp-wtmp.c @@ -17,6 +17,8 @@ static void init_timestamp(struct utmpx *store, usec_t t) { if (t <= 0) t = now(CLOCK_REALTIME); + /* Silence static analyzers */ + assert_cc(USEC_PER_SEC > 0); store->ut_tv.tv_sec = t / USEC_PER_SEC; store->ut_tv.tv_usec = t % USEC_PER_SEC; } diff --git a/src/test/test-path.c b/src/test/test-path.c index d282cfbf89113..82b1f27ed7550 100644 --- a/src/test/test-path.c +++ b/src/test/test-path.c @@ -78,6 +78,8 @@ static int _check_states(unsigned line, assert_se(m); assert_se(service); + /* Silence static analyzers */ + assert_cc(30 * USEC_PER_SEC <= USEC_INFINITY); usec_t end = now(CLOCK_MONOTONIC) + 30 * USEC_PER_SEC; while (path->state != path_state || service->state != service_state || diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c index 04da9891cb73a..8250a03e29876 100644 --- a/src/test/test-time-util.c +++ b/src/test/test-time-util.c @@ -1113,6 +1113,8 @@ TEST(usec_shift_clock) { assert_se(usec_shift_clock(USEC_INFINITY, CLOCK_REALTIME, CLOCK_MONOTONIC) == USEC_INFINITY); + /* Silence static analyzers */ + assert_cc(9 * USEC_PER_HOUR <= USEC_INFINITY); assert_similar(usec_shift_clock(rt + USEC_PER_HOUR, CLOCK_REALTIME, CLOCK_MONOTONIC), mn + USEC_PER_HOUR); assert_similar(usec_shift_clock(rt + 2*USEC_PER_HOUR, CLOCK_REALTIME, CLOCK_BOOTTIME), bt + 2*USEC_PER_HOUR); assert_se(usec_shift_clock(rt + 3*USEC_PER_HOUR, CLOCK_REALTIME, CLOCK_REALTIME_ALARM) == rt + 3*USEC_PER_HOUR); From a05483a921a518fd283e7cb32dc8c8e816b2ab2c Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 21:29:58 +0000 Subject: [PATCH 0619/1296] nss-myhostname: add asserts for buffer index accumulation Coverity flags idx += 2*sizeof(char*) and idx += sizeof(char*) as potential overflows. The idx is bounded by the ms buffer size calculation, add asserts to document this. CID#1548028 Follow-up for e8a7a315391a6a07897122725cd707f4e9ce63d7 --- src/nss-myhostname/nss-myhostname.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/nss-myhostname/nss-myhostname.c b/src/nss-myhostname/nss-myhostname.c index ed470ed298cc4..6a016a1f5cc12 100644 --- a/src/nss-myhostname/nss-myhostname.c +++ b/src/nss-myhostname/nss-myhostname.c @@ -238,9 +238,13 @@ static enum nss_status fill_in_hostent( if (additional) { ((char**) r_aliases)[0] = r_alias; ((char**) r_aliases)[1] = NULL; + /* Silence static analyzers */ + assert(idx <= buflen - 2 * sizeof(char*)); idx += 2*sizeof(char*); } else { ((char**) r_aliases)[0] = NULL; + /* Silence static analyzers */ + assert(idx <= buflen - sizeof(char*)); idx += sizeof(char*); } From aa2cc18c762af5c292fdd22357dbbc2c90c66db5 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 21:37:47 +0000 Subject: [PATCH 0620/1296] sd-bus: use usec_add() for auth timeout calculation Use the overflow-safe usec_add() instead of raw addition for computing the authentication timeout. CID#1548036 Follow-up for e3017af97310da024ffb378ed155bc1676922ce7 --- src/libsystemd/sd-bus/bus-socket.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libsystemd/sd-bus/bus-socket.c b/src/libsystemd/sd-bus/bus-socket.c index 3c6a2b2747fb9..fdbf557f137cf 100644 --- a/src/libsystemd/sd-bus/bus-socket.c +++ b/src/libsystemd/sd-bus/bus-socket.c @@ -788,7 +788,7 @@ int bus_socket_start_auth(sd_bus *b) { bus_get_peercred(b); bus_set_state(b, BUS_AUTHENTICATING); - b->auth_timeout = now(CLOCK_MONOTONIC) + BUS_AUTH_TIMEOUT; + b->auth_timeout = usec_add(now(CLOCK_MONOTONIC), BUS_AUTH_TIMEOUT); if (sd_is_socket(b->input_fd, AF_UNIX, 0, 0) <= 0) b->accept_fd = false; From 4f5b28b72c7ff78c7eabcce7ad4f0eaebfd5545d Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 21:41:02 +0000 Subject: [PATCH 0621/1296] sd-bus: add assert_cc for message allocation size Use CONST_ALIGN_TO to express the compile-time overflow check for the ALIGN(sizeof(sd_bus_message)) + sizeof(BusMessageHeader) allocation, since ALIGN() is not constexpr. CID#1548031 Follow-up for de1c301ed165eb4d04a0c9d4babe97912b5233bb --- src/libsystemd/sd-bus/bus-message.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libsystemd/sd-bus/bus-message.c b/src/libsystemd/sd-bus/bus-message.c index 1f278db54e57e..507c5d7ff4060 100644 --- a/src/libsystemd/sd-bus/bus-message.c +++ b/src/libsystemd/sd-bus/bus-message.c @@ -464,6 +464,8 @@ _public_ int sd_bus_message_new( /* Creation of messages with _SD_BUS_MESSAGE_TYPE_INVALID is allowed. */ assert_return(type < _SD_BUS_MESSAGE_TYPE_MAX, -EINVAL); + /* Silence static analyzers */ + assert_cc(sizeof(sd_bus_message) + sizeof(void*) + sizeof(BusMessageHeader) <= SIZE_MAX); sd_bus_message *t = malloc0(ALIGN(sizeof(sd_bus_message)) + sizeof(BusMessageHeader)); if (!t) return -ENOMEM; From ded2e6e9761ef0838c6913f7acd21e6bf2f05075 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 21:47:08 +0000 Subject: [PATCH 0622/1296] test-strv: avoid unsigned wraparound in backwards iteration Use pre-decrement starting from 3 instead of post-decrement starting from 2, so that the unsigned counter does not wrap past zero on the final iteration. CID#1548035 Follow-up for 02f19706a9fd96e05c9ed16aa55ba3d03d008167 --- src/test/test-strv.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/test-strv.c b/src/test/test-strv.c index b3468b1cfac62..283ae5c865ace 100644 --- a/src/test/test-strv.c +++ b/src/test/test-strv.c @@ -799,14 +799,14 @@ TEST(strv_foreach) { TEST(strv_foreach_backwards) { _cleanup_strv_free_ char **a; - unsigned i = 2; + unsigned i = 3; a = strv_new("one", "two", "three"); assert_se(a); STRV_FOREACH_BACKWARDS(check, a) - ASSERT_STREQ(*check, input_table_multiple[i--]); + ASSERT_STREQ(*check, input_table_multiple[--i]); STRV_FOREACH_BACKWARDS(check, (char**) NULL) assert_not_reached(); From 1eb2bd5aaf14650a3433767fc44ffaef85407b21 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 21:52:57 +0000 Subject: [PATCH 0623/1296] repart: use INC_SAFE for partition min size accumulation Use overflow-safe INC_SAFE() instead of raw addition when accumulating partition minimum size components. CID#1548041 Follow-up for 170c98234530af6af487d37057b6e687569f8f91 --- src/repart/repart.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index 7a8bc00919e85..1cdc0a051f6bf 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -1133,16 +1133,16 @@ static uint64_t partition_min_size(const Context *context, const Partition *p) { uint64_t d = 0; if (p->encrypt != ENCRYPT_OFF) - d += round_up_size(LUKS2_METADATA_KEEP_FREE, context->grain_size); + assert_se(INC_SAFE(&d, round_up_size(LUKS2_METADATA_KEEP_FREE, context->grain_size))); if (p->copy_blocks_size != UINT64_MAX) - d += round_up_size(p->copy_blocks_size, context->grain_size); + assert_se(INC_SAFE(&d, round_up_size(p->copy_blocks_size, context->grain_size))); else if (p->format || p->encrypt != ENCRYPT_OFF) { uint64_t f; /* If we shall synthesize a file system, take minimal fs size into account (assumed to be 4K if not known) */ f = partition_fstype_min_size(context, p); - d += f == UINT64_MAX ? context->grain_size : round_up_size(f, context->grain_size); + assert_se(INC_SAFE(&d, f == UINT64_MAX ? context->grain_size : round_up_size(f, context->grain_size))); } if (d > sz) From de2a7614f1703cbba6d978333900a6cdbc401a84 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 21:56:41 +0000 Subject: [PATCH 0624/1296] calendarspec: use ADD_SAFE for repeat offset calculation Use overflow-safe ADD_SAFE() instead of raw addition when computing the next matching calendar component with repeat. On overflow, skip the component instead of using a bogus value. CID#1548052 Follow-up for a2eb5ea79c53620cfcf616e83bfac0c431247f86 --- src/shared/calendarspec.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/shared/calendarspec.c b/src/shared/calendarspec.c index dc13ae0f77994..df371ac4f8cc7 100644 --- a/src/shared/calendarspec.c +++ b/src/shared/calendarspec.c @@ -1149,9 +1149,8 @@ static int find_matching_component( } else if (c->repeat > 0) { int k; - k = start + ROUND_UP(*val - start, c->repeat); - - if ((!d_set || k < d) && (stop < 0 || k <= stop)) { + if (ADD_SAFE(&k, start, ROUND_UP(*val - start, c->repeat)) && + (!d_set || k < d) && (stop < 0 || k <= stop)) { d = k; d_set = true; } From 24bdc49c23be1b71f72ebf20586cce07147c9d3e Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 22:00:25 +0000 Subject: [PATCH 0625/1296] creds-util: add assert for output buffer size overflow safety Coverity flags the multi-term output.iov_len accumulation as a potential overflow. Add an assert after the calculation to verify the result is at least as large as the input, catching wraparound. CID#1548068 Follow-up for 21bc0b6fa1de44b520353b935bf14160f9f70591 --- src/shared/creds-util.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index e7db1ff7eff33..6fedc500dc3ef 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -1068,6 +1068,8 @@ int encrypt_credential_and_warn( ALIGN8(offsetof(struct metadata_credential_header, name) + strlen_ptr(name)) + input->iov_len + 2U * (size_t) bsz + tsz; + /* Silence static analyzers */ + assert(output.iov_len >= input->iov_len); output.iov_base = malloc0(output.iov_len); if (!output.iov_base) From a770fb3e14d434ef25f092c2f3293a43448c92cd Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 22:06:51 +0000 Subject: [PATCH 0626/1296] boot: clamp setup header copy size to sizeof(SetupHeader) The setup_size field from the kernel image header is used as part of the memcpy size. Clamp it to sizeof(SetupHeader) to ensure the copy does not read beyond the struct bounds even if the kernel image header contains an unexpected value. CID#1549197 Follow-up for d62c1777568ff69034fd5b5d582a2889229f7e20 --- src/boot/linux_x86.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/boot/linux_x86.c b/src/boot/linux_x86.c index cf9707a6cfd7a..349e3fb26c01b 100644 --- a/src/boot/linux_x86.c +++ b/src/boot/linux_x86.c @@ -195,9 +195,14 @@ EFI_STATUS linux_exec_efi_handover( /* Setup size is determined by offset 0x0202 + byte value at offset 0x0201, which is the same as * offset of the header field and the target from the jump field (which we split for this reason). */ + size_t setup_hdr_len; + if (!ADD_SAFE(&setup_hdr_len, offsetof(SetupHeader, header), image_params->hdr.setup_size)) + setup_hdr_len = sizeof(SetupHeader); + else + setup_hdr_len = MIN(setup_hdr_len, sizeof(SetupHeader)); memcpy(&boot_params->hdr, &image_params->hdr, - offsetof(SetupHeader, header) + image_params->hdr.setup_size); + setup_hdr_len); boot_params->hdr.type_of_loader = 0xff; From 569c849be4ea60d9a83ea346bbb3af4634d7cf25 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 22:15:56 +0000 Subject: [PATCH 0627/1296] nspawn-oci: add asserts for UID/GID validity after dispatch Coverity flags UINT32_MAX - data.container_id as an underflow when container_id could be UID_INVALID (UINT32_MAX). After successful sd_json_dispatch_uid_gid(), the values are guaranteed valid, but Coverity cannot trace through the callback. Add asserts to document this invariant. CID#1548072 Follow-up for 91c4d1affdba02a323dc2c7caccabe240ccb8302 --- src/nspawn/nspawn-oci.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/nspawn/nspawn-oci.c b/src/nspawn/nspawn-oci.c index 29091bd82c8f5..1fde98a9d9e5d 100644 --- a/src/nspawn/nspawn-oci.c +++ b/src/nspawn/nspawn-oci.c @@ -22,6 +22,7 @@ #include "string-util.h" #include "strv.h" #include "time-util.h" +#include "user-util.h" /* TODO: * OCI runtime tool implementation @@ -685,6 +686,10 @@ static int oci_uid_gid_mappings(const char *name, sd_json_variant *v, sd_json_di if (r < 0) return r; + /* Silence static analyzers, sd_json_dispatch_uid_gid() already validates */ + assert(uid_is_valid(data.host_id)); + assert(uid_is_valid(data.container_id)); + if (data.range > UINT32_MAX - data.host_id || data.range > UINT32_MAX - data.container_id) return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL), From ad7813844a9ff4c5936f4465039cd66c890a5702 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Sat, 28 Mar 2026 09:36:32 +0100 Subject: [PATCH 0628/1296] mkosi: add coccinelle to the debian tools tree too It is already part of the fedora/opensues tools tree. It must have slipped through for Debian so lets add it. --- mkosi/mkosi.tools.conf/mkosi.conf.d/debian-ubuntu.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/mkosi/mkosi.tools.conf/mkosi.conf.d/debian-ubuntu.conf b/mkosi/mkosi.tools.conf/mkosi.conf.d/debian-ubuntu.conf index 4bd4c12fd94de..613d9d87d917f 100644 --- a/mkosi/mkosi.tools.conf/mkosi.conf.d/debian-ubuntu.conf +++ b/mkosi/mkosi.tools.conf/mkosi.conf.d/debian-ubuntu.conf @@ -8,6 +8,7 @@ Distribution=|ubuntu PrepareScripts=%D/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.prepare Packages= clang-tidy + coccinelle lcov mypy shellcheck From 69d54d521330f40d4fe88107760b5d878fdf4715 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Sat, 28 Mar 2026 09:46:33 +0100 Subject: [PATCH 0629/1296] cleanup: address review feedback from claude Trivial ordering/modernizing change that got highlighted by claude and refined by keszybz to move to the modern systemd style. Thanks to keszybz for suggesting this. --- src/import/qcow2-util.c | 4 +--- src/resolve/resolved-dns-stub.c | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/import/qcow2-util.c b/src/import/qcow2-util.c index dd5c3c23ecb42..fe0c8a26209e0 100644 --- a/src/import/qcow2-util.c +++ b/src/import/qcow2-util.c @@ -154,9 +154,7 @@ static int normalize_offset( POINTER_MAY_BE_NULL(compressed); POINTER_MAY_BE_NULL(compressed_size); - uint64_t q; - - q = be64toh(p); + uint64_t q = be64toh(p); if (q & QCOW2_COMPRESSED) { uint64_t sz, csize_shift, csize_mask; diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c index 298db3ae78faa..91ea30e0e2f4c 100644 --- a/src/resolve/resolved-dns-stub.c +++ b/src/resolve/resolved-dns-stub.c @@ -83,9 +83,7 @@ int dns_stub_listener_extra_new( assert(ret); - DnsStubListenerExtra *l; - - l = new(DnsStubListenerExtra, 1); + DnsStubListenerExtra *l = new(DnsStubListenerExtra, 1); if (!l) return -ENOMEM; From f3c49a597f25768da105f600a80f00ac6c3c5412 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Sat, 28 Mar 2026 10:24:18 +0100 Subject: [PATCH 0630/1296] nspawn: make check-pointer-deref clean Add the needed assert changes to make the code clean for the new check-pointer-deref script. --- meson.build | 1 - src/nspawn/nspawn-oci.c | 11 ++++++++--- src/nspawn/nspawn.c | 3 +++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/meson.build b/meson.build index 5c02ab8664063..deeb90a37eb70 100644 --- a/meson.build +++ b/meson.build @@ -2982,7 +2982,6 @@ if spatch.found() 'src/journal/', 'src/libsystemd/', 'src/network/', - 'src/nspawn/', 'src/shared/', # libc/ has no assert() or systemd-headers so leave it 'src/libc/', diff --git a/src/nspawn/nspawn-oci.c b/src/nspawn/nspawn-oci.c index 29091bd82c8f5..93fd21432979e 100644 --- a/src/nspawn/nspawn-oci.c +++ b/src/nspawn/nspawn-oci.c @@ -1489,6 +1489,8 @@ static int oci_resources(const char *name, sd_json_variant *v, sd_json_dispatch_ static bool sysctl_key_valid(const char *s) { bool dot = true; + POINTER_MAY_BE_NULL(s); + /* Note that we are a bit stricter here than in systemd-sysctl, as that inherited semantics from the old sysctl * tool, which were really weird (as it swaps / and . in both ways) */ @@ -1546,7 +1548,6 @@ static int oci_sysctl(const char *name, sd_json_variant *v, sd_json_dispatch_fla #if HAVE_SECCOMP static int oci_seccomp_action_from_string(const char *name, uint32_t *ret) { - static const struct { const char *name; uint32_t action; @@ -1563,6 +1564,8 @@ static int oci_seccomp_action_from_string(const char *name, uint32_t *ret) { * here */ }; + assert(ret); + FOREACH_ELEMENT(i, table) if (streq_ptr(name, i->name)) { *ret = i->action; @@ -1573,7 +1576,6 @@ static int oci_seccomp_action_from_string(const char *name, uint32_t *ret) { } static int oci_seccomp_arch_from_string(const char *name, uint32_t *ret) { - static const struct { const char *name; uint32_t arch; @@ -1605,6 +1607,8 @@ static int oci_seccomp_arch_from_string(const char *name, uint32_t *ret) { { "SCMP_ARCH_X86_64", SCMP_ARCH_X86_64 }, }; + assert(ret); + FOREACH_ELEMENT(i, table) if (streq_ptr(i->name, name)) { *ret = i->arch; @@ -1615,7 +1619,6 @@ static int oci_seccomp_arch_from_string(const char *name, uint32_t *ret) { } static int oci_seccomp_compare_from_string(const char *name, enum scmp_compare *ret) { - static const struct { const char *name; enum scmp_compare op; @@ -1629,6 +1632,8 @@ static int oci_seccomp_compare_from_string(const char *name, enum scmp_compare * { "SCMP_CMP_MASKED_EQ", SCMP_CMP_MASKED_EQ }, }; + assert(ret); + FOREACH_ELEMENT(i, table) if (streq_ptr(i->name, name)) { *ret = i->op; diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 84e94e845a6b5..1740ab4d6eb18 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -2981,6 +2981,7 @@ static int wait_for_container(PidRef *pid, ContainerStatus *container) { int r; assert(pidref_is_set(pid)); + assert(container); r = pidref_wait_for_terminate(pid, &status); if (r < 0) @@ -4723,6 +4724,8 @@ static int nspawn_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t r static int setup_notify_parent(sd_event *event, int fd, PidRef *inner_child_pid, sd_event_source **notify_event_source) { int r; + assert(notify_event_source); + if (fd < 0) return 0; From 162613025445b96398f73574aca1cf70681da39a Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Sat, 28 Mar 2026 10:48:27 +0100 Subject: [PATCH 0631/1296] journal: make check-pointer-deref clean Add the needed assert changes to make the code clean for the new check-pointer-deref script. --- meson.build | 1 - src/journal/journald-audit.c | 2 ++ src/journal/journald-manager.c | 2 ++ src/journal/journald-native.c | 6 ++++++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index deeb90a37eb70..bb4a2fbeea6a2 100644 --- a/meson.build +++ b/meson.build @@ -2979,7 +2979,6 @@ if spatch.found() coccinelle_exclude = [ 'src/basic/', 'src/core/', - 'src/journal/', 'src/libsystemd/', 'src/network/', 'src/shared/', diff --git a/src/journal/journald-audit.c b/src/journal/journald-audit.c index fa6ba50b37708..173fe9944af47 100644 --- a/src/journal/journald-audit.c +++ b/src/journal/journald-audit.c @@ -158,6 +158,8 @@ static int map_generic_field( char *c, *t; int r; + assert(p); + /* Implements fallback mappings for all fields we don't know */ for (e = *p; e < *p + 16; e++) { diff --git a/src/journal/journald-manager.c b/src/journal/journald-manager.c index 8d95280fc61c7..3abc0d3869a62 100644 --- a/src/journal/journald-manager.c +++ b/src/journal/journald-manager.c @@ -135,6 +135,8 @@ static int manager_determine_path_usage( } static void cache_space_invalidate(JournalStorageSpace *space) { + assert(space); + zero(*space); } diff --git a/src/journal/journald-native.c b/src/journal/journald-native.c index d61e8f5a743f7..1e2a872c6ed59 100644 --- a/src/journal/journald-native.c +++ b/src/journal/journald-native.c @@ -45,6 +45,10 @@ static void manager_process_entry_meta( char **message, pid_t *object_pid) { + assert(priority); + assert(identifier); + assert(message); + /* We need to determine the priority of this entry for the rate limiting logic */ if (l == 10 && @@ -113,6 +117,8 @@ static int manager_process_entry( const char *p; int r = 1; + assert(remaining); + p = buffer; while (*remaining > 0) { From 3e9eaf45f022d116d31bd7906b1bcfdba2a88ecc Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Sat, 28 Mar 2026 12:02:32 +0100 Subject: [PATCH 0632/1296] network: make check-pointer-deref clean Add the needed assert changes to make the code clean for the new check-pointer-deref script. --- meson.build | 1 - src/network/generator/network-generator.c | 1 + src/network/netdev/macsec.c | 1 + src/network/netdev/netdev.c | 1 + src/network/networkctl-status-system.c | 3 +++ src/network/networkd-dhcp-common.c | 1 + src/network/networkd-dhcp-prefix-delegation.c | 1 + src/network/networkd-dhcp-server.c | 1 + src/network/networkd-link.c | 1 + src/network/networkd-radv.c | 1 + src/network/networkd-routing-policy-rule.c | 2 ++ src/network/networkd-wwan.c | 1 + 12 files changed, 14 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index bb4a2fbeea6a2..674c2fef64f11 100644 --- a/meson.build +++ b/meson.build @@ -2980,7 +2980,6 @@ if spatch.found() 'src/basic/', 'src/core/', 'src/libsystemd/', - 'src/network/', 'src/shared/', # libc/ has no assert() or systemd-headers so leave it 'src/libc/', diff --git a/src/network/generator/network-generator.c b/src/network/generator/network-generator.c index d0204366fb6ab..2c3a8bf6aaccf 100644 --- a/src/network/generator/network-generator.c +++ b/src/network/generator/network-generator.c @@ -119,6 +119,7 @@ static int address_new( assert(network); assert(IN_SET(family, AF_INET, AF_INET6)); assert(addr); + POINTER_MAY_BE_NULL(peer); address = new(Address, 1); if (!address) diff --git a/src/network/netdev/macsec.c b/src/network/netdev/macsec.c index 0b999b4b8a6c0..9f3ddcc2b1937 100644 --- a/src/network/netdev/macsec.c +++ b/src/network/netdev/macsec.c @@ -125,6 +125,7 @@ static int macsec_receive_channel_new(MACsec *s, uint64_t sci, ReceiveChannel ** ReceiveChannel *c; assert(s); + assert(ret); c = new(ReceiveChannel, 1); if (!c) diff --git a/src/network/netdev/netdev.c b/src/network/netdev/netdev.c index 393114aa7bae4..bde8d2db05fb3 100644 --- a/src/network/netdev/netdev.c +++ b/src/network/netdev/netdev.c @@ -277,6 +277,7 @@ static int netdev_attach_name_full(NetDev *netdev, const char *name, Hashmap **n assert(netdev); assert(name); + assert(netdevs); r = hashmap_ensure_put(netdevs, &string_hash_ops, name, netdev); if (r == -ENOMEM) diff --git a/src/network/networkctl-status-system.c b/src/network/networkctl-status-system.c index bb1b5d1377bfa..20a4c2be9186f 100644 --- a/src/network/networkctl-status-system.c +++ b/src/network/networkctl-status-system.c @@ -20,6 +20,9 @@ static int ifindex_str_compare_func(char * const *a, char * const *b) { size_t al, bl; int r; + assert(a); + assert(b); + al = strlen_ptr(*a); bl = strlen_ptr(*b); diff --git a/src/network/networkd-dhcp-common.c b/src/network/networkd-dhcp-common.c index 619c2f6377093..3e7cca991b392 100644 --- a/src/network/networkd-dhcp-common.c +++ b/src/network/networkd-dhcp-common.c @@ -276,6 +276,7 @@ int link_get_captive_portal(Link *link, const char **ret) { int r; assert(link); + assert(ret); if (!link->network) { *ret = NULL; diff --git a/src/network/networkd-dhcp-prefix-delegation.c b/src/network/networkd-dhcp-prefix-delegation.c index f53d7d1c5aada..e734b0171d37a 100644 --- a/src/network/networkd-dhcp-prefix-delegation.c +++ b/src/network/networkd-dhcp-prefix-delegation.c @@ -557,6 +557,7 @@ static int dhcp_pd_get_preferred_subnet_prefix( assert(link->manager); assert(link->network); assert(pd_prefix); + assert(ret); if (link->network->dhcp_pd_subnet_id >= 0) { /* If the link has a preference for a particular subnet id try to allocate that */ diff --git a/src/network/networkd-dhcp-server.c b/src/network/networkd-dhcp-server.c index 24ae1bbe89087..23ee3024e902a 100644 --- a/src/network/networkd-dhcp-server.c +++ b/src/network/networkd-dhcp-server.c @@ -326,6 +326,7 @@ void manager_toggle_dhcp4_server_state(Manager *manager, bool start) { static int dhcp_server_find_uplink(Link *link, Link **ret) { assert(link); + assert(ret); if (link->network->dhcp_server_uplink_name) return link_get_by_name(link->manager, link->network->dhcp_server_uplink_name, ret); diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 57b074e1be235..c6bc8fc4b1c4d 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -862,6 +862,7 @@ static int link_put_carrier(Link *link, Link *carrier, Hashmap **h) { assert(link); assert(carrier); + assert(h); if (link == carrier) return 0; diff --git a/src/network/networkd-radv.c b/src/network/networkd-radv.c index 0ef292bdf22f7..a71e01dfbf474 100644 --- a/src/network/networkd-radv.c +++ b/src/network/networkd-radv.c @@ -423,6 +423,7 @@ static int radv_find_uplink(Link *link, Link **ret) { int r; assert(link); + assert(ret); if (link->network->router_uplink_name) return link_get_by_name(link->manager, link->network->router_uplink_name, ret); diff --git a/src/network/networkd-routing-policy-rule.c b/src/network/networkd-routing-policy-rule.c index eb60d315bd200..1cefad29de50e 100644 --- a/src/network/networkd-routing-policy-rule.c +++ b/src/network/networkd-routing-policy-rule.c @@ -99,6 +99,8 @@ DEFINE_SECTION_CLEANUP_FUNCTIONS(RoutingPolicyRule, routing_policy_rule_unref); static int routing_policy_rule_new(RoutingPolicyRule **ret) { RoutingPolicyRule *rule; + assert(ret); + rule = new(RoutingPolicyRule, 1); if (!rule) return -ENOMEM; diff --git a/src/network/networkd-wwan.c b/src/network/networkd-wwan.c index b84ace130102a..525660ad8fce6 100644 --- a/src/network/networkd-wwan.c +++ b/src/network/networkd-wwan.c @@ -235,6 +235,7 @@ int link_get_modem(Link *link, Modem **ret) { assert(link); assert(link->manager); assert(link->ifname); + assert(ret); HASHMAP_FOREACH(modem, link->manager->modems_by_path) if (modem->port_name && streq(modem->port_name, link->ifname)) { From 7e54cc88d02fe964626000eacce12749588c8d2c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 30 Mar 2026 13:51:07 +0200 Subject: [PATCH 0633/1296] TODO: fix typos and restore dropped item MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix "d-nspawn" → "systemd-nspawn" (prefix was incorrectly stripped when merging into the nspawn grouped section) - Fix "LSFMMBPF" → "LSM BPF" (the original informal abbreviations "lsmbpf"/"lsmpbf" were incorrectly "corrected") - Restore the dropped "portabled: similar" reference by folding it into the ConcurrencyHardMax=/ConcurrencySoftMax= item Signed-off-by: Christian Brauner --- TODO.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TODO.md b/TODO.md index a293c792f89ba..0cd44bd57d9b5 100644 --- a/TODO.md +++ b/TODO.md @@ -421,7 +421,7 @@ SPDX-License-Identifier: LGPL-2.1-or-later included in the user/group record credentials - allow dynamic modifications of ConcurrencyHardMax= and ConcurrencySoftMax= - via DBus (and with that also by daemon-reload) + via DBus (and with that also by daemon-reload). Similar for portabled. - also include packaging metadata (á la https://systemd.io/PACKAGE_METADATA_FOR_EXECUTABLE_FILES/) in our UEFI PE @@ -1774,7 +1774,7 @@ SPDX-License-Identifier: LGPL-2.1-or-later - Reduce the number of sockets that are currently in use and just rely on one or two sockets. - map foreign UID range through 1:1 - - d-nspawn should get the same SSH key support that vmspawn now has. + - systemd-nspawn should get the same SSH key support that vmspawn now has. - oci: add support for "importctl import-oci" which implements the "OCI layout" spec (i.e. acquiring via local fs access), as opposed to the current @@ -2327,14 +2327,14 @@ SPDX-License-Identifier: LGPL-2.1-or-later - sysext: measure all activated sysext into a TPM PCR -- system LSFMMBPF policy that enforces that block device backed mounts may only +- system BPF LSM policy that enforces that block device backed mounts may only be established on top of dm-crypt or dm-verity devices, or an allowlist of file systems (which should probably include vfat, for compat with the ESP) -- system LSFMMBPF policy that prohibits creating files owned by "nobody" +- system BPF LSM policy that prohibits creating files owned by "nobody" system-wide -- system LSFMMBPF policy that prohibits creating or opening device nodes outside +- system BPF LSM policy that prohibits creating or opening device nodes outside of devtmpfs/tmpfs, except if they are the pseudo-devices /dev/null, /dev/zero, /dev/urandom and so on. From 7f133c996c8b1ea9219540ec8f966b64b58d30a6 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 22:32:06 +0000 Subject: [PATCH 0634/1296] test-json: avoid divide-by-zero coverity warning for index 9 Same fix as d0a066a1a4a391f629f7f52b5005103f8daf411f did for index 10: add iszero_safe() check before dividing by the json variant real value. CID#1587762 Follow-up for d0a066a1a4a391f629f7f52b5005103f8daf411f --- src/test/test-json.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/test-json.c b/src/test/test-json.c index d6308b23e7dba..016416dc4b728 100644 --- a/src/test/test-json.c +++ b/src/test/test-json.c @@ -665,6 +665,7 @@ static void test_float_match(sd_json_variant *v) { sd_json_variant_integer(sd_json_variant_by_index(v, 8)) == -10); assert_se(sd_json_variant_is_real(sd_json_variant_by_index(v, 9)) && !sd_json_variant_is_integer(sd_json_variant_by_index(v, 9))); + assert_se(!iszero_safe(sd_json_variant_real(sd_json_variant_by_index(v, 9)))); assert_se(fabs(1.0 - (DBL_MIN / 2 / sd_json_variant_real(sd_json_variant_by_index(v, 9)))) <= delta); assert_se(sd_json_variant_is_real(sd_json_variant_by_index(v, 10)) && !sd_json_variant_is_integer(sd_json_variant_by_index(v, 10))); From ea1e81e1179d0a50ff4348b40d64a15870ccdff8 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 28 Mar 2026 22:46:35 +0000 Subject: [PATCH 0635/1296] journald: add assert for allocated buffer size Coverity flags allocated - 1 as a potential underflow when allocated is 0. After GREEDY_REALLOC succeeds the buffer is guaranteed non-empty, but Coverity cannot trace through the conditional. Add an assert to document this. CID#1548053 Follow-up for ec20fe5ffb8a00469bab209fff6c069bb93c6db2 --- src/journal/journald-stream.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/journal/journald-stream.c b/src/journal/journald-stream.c index 677fb93b7597f..ee903755cf7a4 100644 --- a/src/journal/journald-stream.c +++ b/src/journal/journald-stream.c @@ -557,6 +557,8 @@ static int stdout_stream_process(sd_event_source *es, int fd, uint32_t revents, /* Try to make use of the allocated buffer in full, but never read more than the configured line size. Also, * always leave room for a terminating NUL we might need to add. */ + /* Silence static analyzers, GREEDY_REALLOC above ensures allocated > 0 */ + assert(allocated > 0); limit = MIN(allocated - 1, MAX(s->manager->config.line_max, STDOUT_STREAM_SETUP_PROTOCOL_LINE_MAX)); assert(s->length <= limit); iovec = IOVEC_MAKE(s->buffer + s->length, limit - s->length); From 57e5c9eea600913173e24ee40c583912e52cfee0 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Sat, 28 Mar 2026 12:35:54 +0100 Subject: [PATCH 0636/1296] core: make check-pointer-deref clean Add the needed assert changes to make the code clean for the new check-pointer-deref script. --- meson.build | 1 - src/core/bpf-firewall.c | 2 ++ src/core/cgroup.c | 4 ++++ src/core/emergency-action.c | 2 ++ src/core/job.c | 7 +++++++ src/core/load-fragment.c | 6 ++++++ src/core/manager.c | 2 ++ src/core/mount.c | 2 ++ src/core/path.c | 1 + src/core/scope.c | 2 ++ src/core/smack-setup.c | 3 +++ src/core/socket.c | 2 ++ src/core/swap.c | 2 ++ src/core/timer.c | 1 + src/core/unit.c | 1 + src/core/varlink-execute.c | 3 +++ 16 files changed, 40 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 674c2fef64f11..c9e96b2591495 100644 --- a/meson.build +++ b/meson.build @@ -2978,7 +2978,6 @@ if spatch.found() # Remove directories from this list as they are cleaned up. coccinelle_exclude = [ 'src/basic/', - 'src/core/', 'src/libsystemd/', 'src/shared/', # libc/ has no assert() or systemd-headers so leave it diff --git a/src/core/bpf-firewall.c b/src/core/bpf-firewall.c index b0c54d3134723..bc5d7f0351dcd 100644 --- a/src/core/bpf-firewall.c +++ b/src/core/bpf-firewall.c @@ -661,6 +661,8 @@ static int attach_custom_bpf_progs(Unit *u, const char *path, int attach_type, S int r; assert(u); + assert(set); + assert(set_installed); set_clear(*set_installed); r = set_ensure_allocated(set_installed, &bpf_program_hash_ops); diff --git a/src/core/cgroup.c b/src/core/cgroup.c index 514dabf371b7f..7bcd6777df244 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -313,6 +313,8 @@ static int unit_compare_memory_limit(Unit *u, const char *property_name, uint64_ * - ret_kernel_value will contain the actual value presented by the kernel. */ assert(u); + assert(ret_unit_value); + assert(ret_kernel_value); /* The root slice doesn't have any controller files, so we can't compare anything. */ if (unit_has_name(u, SPECIAL_ROOT_SLICE)) @@ -3189,6 +3191,8 @@ static int cg_bpf_mask_supported(CGroupMask *ret) { CGroupMask mask = 0; int r; + assert(ret); + /* BPF-based firewall, device access control, and pinned foreign prog */ if (bpf_program_supported() > 0) mask |= CGROUP_MASK_BPF_FIREWALL | diff --git a/src/core/emergency-action.c b/src/core/emergency-action.c index 439228c8995ff..b9a5c66ebff87 100644 --- a/src/core/emergency-action.c +++ b/src/core/emergency-action.c @@ -240,6 +240,8 @@ int parse_emergency_action( EmergencyAction x; + assert(ret); + x = emergency_action_from_string(value); if (x < 0) return -EINVAL; diff --git a/src/core/job.c b/src/core/job.c index 1cac09bd06607..638e6e759e5b2 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -510,6 +510,8 @@ JobType job_type_collapse(JobType t, Unit *u) { int job_type_merge_and_collapse(JobType *a, JobType b, Unit *u) { JobType t; + assert(a); + t = job_type_lookup_merge(*a, b); if (t < 0) return -EEXIST; @@ -1523,6 +1525,11 @@ void job_add_to_gc_queue(Job *j) { } static int job_compare_id(Job * const *a, Job * const *b) { + assert(a); + assert(b); + assert(*a); + assert(*b); + return CMP((*a)->id, (*b)->id); } diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index cef01ab776365..840804fcf8dde 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -88,6 +88,8 @@ static int parse_socket_protocol(const char *s) { int parse_crash_chvt(const char *value, int *data) { int b; + assert(data); + if (safe_atoi(value, data) >= 0) return 0; @@ -107,6 +109,8 @@ int parse_confirm_spawn(const char *value, char **console) { char *s; int r; + assert(console); + r = value ? parse_boolean(value) : 1; if (r == 0) { *console = NULL; @@ -565,6 +569,8 @@ static int patch_var_run( const char *e; char *z; + assert(path); + e = path_startswith(*path, "/var/run/"); if (!e) return 0; diff --git a/src/core/manager.c b/src/core/manager.c index e8c5f00895847..a5af434e5ef81 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -1982,6 +1982,8 @@ Manager* manager_reloading_start(Manager *m) { } void manager_reloading_stopp(Manager **m) { + assert(m); + if (*m) { assert((*m)->n_reloading > 0); (*m)->n_reloading--; diff --git a/src/core/mount.c b/src/core/mount.c index 680e376febfc9..46e157af206c1 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -2003,6 +2003,8 @@ static int mount_get_timeout(Unit *u, usec_t *timeout) { usec_t t; int r; + assert(timeout); + if (!m->timer_event_source) return 0; diff --git a/src/core/path.c b/src/core/path.c index 789ef9e25d6e9..18a5e140f1442 100644 --- a/src/core/path.c +++ b/src/core/path.c @@ -931,6 +931,7 @@ static int activation_details_path_deserialize(const char *key, const char *valu assert(key); assert(value); + POINTER_MAY_BE_NULL(details); if (!details || !*details) return -EINVAL; diff --git a/src/core/scope.c b/src/core/scope.c index d36b27c537dfc..21520d9d1943b 100644 --- a/src/core/scope.c +++ b/src/core/scope.c @@ -483,6 +483,8 @@ static int scope_get_timeout(Unit *u, usec_t *timeout) { usec_t t; int r; + assert(timeout); + if (!s->timer_event_source) return 0; diff --git a/src/core/smack-setup.c b/src/core/smack-setup.c index 1e8e2b54e53d2..46c7d0a6e88d2 100644 --- a/src/core/smack-setup.c +++ b/src/core/smack-setup.c @@ -26,6 +26,9 @@ static int fdopen_unlocked_at(int dfd, const char *dir, const char *name, int *s int fd, r; FILE *f; + assert(status); + assert(ret_file); + fd = openat(dfd, name, O_RDONLY|O_CLOEXEC); if (fd < 0) { if (*status == 0) diff --git a/src/core/socket.c b/src/core/socket.c index 43f61e456dcf2..f911f1758fb96 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -3525,6 +3525,8 @@ static int socket_get_timeout(Unit *u, usec_t *timeout) { usec_t t; int r; + assert(timeout); + if (!s->timer_event_source) return 0; diff --git a/src/core/swap.c b/src/core/swap.c index 5de1dccf42779..960d831c5cc3d 100644 --- a/src/core/swap.c +++ b/src/core/swap.c @@ -1427,6 +1427,8 @@ static int swap_get_timeout(Unit *u, usec_t *timeout) { usec_t t; int r; + assert(timeout); + if (!s->timer_event_source) return 0; diff --git a/src/core/timer.c b/src/core/timer.c index c591fcd469c7a..510d8e1995774 100644 --- a/src/core/timer.c +++ b/src/core/timer.c @@ -940,6 +940,7 @@ static int activation_details_timer_deserialize(const char *key, const char *val assert(key); assert(value); + POINTER_MAY_BE_NULL(details); if (!details || !*details) return -EINVAL; diff --git a/src/core/unit.c b/src/core/unit.c index 9af7fb51405e0..41f536ce1f15d 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -6377,6 +6377,7 @@ int unit_clean(Unit *u, ExecCleanMask mask) { int unit_can_clean(Unit *u, ExecCleanMask *ret) { assert(u); + assert(ret); if (!UNIT_VTABLE(u)->clean || u->load_state != UNIT_LOADED) { diff --git a/src/core/varlink-execute.c b/src/core/varlink-execute.c index e6efd5989597b..ccb454c8c245b 100644 --- a/src/core/varlink-execute.c +++ b/src/core/varlink-execute.c @@ -781,6 +781,9 @@ static int set_credential_build_json(sd_json_variant **ret, const char *name, vo int unit_exec_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { Unit *u = ASSERT_PTR(userdata); ExecContext *c = unit_get_exec_context(u); + + assert(ret); + if (!c) { *ret = NULL; return 0; From caeec0de6d460c1c870dc7a981c46813ceca1d57 Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Thu, 19 Mar 2026 11:12:06 -0400 Subject: [PATCH 0637/1296] ether-addr-util: introduce hw_addr_is_valid() Take this from udev, and adapt it to make it re-usable elsewhere. --- src/basic/ether-addr-util.c | 24 ++++++++++++++++++++++++ src/basic/ether-addr-util.h | 1 + src/udev/net/link-config.c | 24 ++---------------------- 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/basic/ether-addr-util.c b/src/basic/ether-addr-util.c index 375c044415738..2a8196d5303a1 100644 --- a/src/basic/ether-addr-util.c +++ b/src/basic/ether-addr-util.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include #include "ether-addr-util.h" @@ -68,6 +69,29 @@ bool hw_addr_is_null(const struct hw_addr_data *addr) { return addr->length == 0 || memeqzero(addr->bytes, addr->length); } +bool hw_addr_is_valid(const struct hw_addr_data *addr, uint16_t iftype) { + assert(addr); + + switch (iftype) { + case ARPHRD_ETHER: + /* Refuse all zero and all 0xFF. */ + if (addr->length != ETH_ALEN) + return false; + + return !ether_addr_is_null(&addr->ether) && !ether_addr_is_broadcast(&addr->ether); + + case ARPHRD_INFINIBAND: + /* The last 8 bytes cannot be zero. */ + if (addr->length != INFINIBAND_ALEN) + return false; + + return !memeqzero(addr->bytes + INFINIBAND_ALEN - 8, 8); + + default: + return false; + } +} + DEFINE_HASH_OPS(hw_addr_hash_ops, struct hw_addr_data, hw_addr_hash_func, hw_addr_compare); DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(hw_addr_hash_ops_free, struct hw_addr_data, hw_addr_hash_func, hw_addr_compare, free); diff --git a/src/basic/ether-addr-util.h b/src/basic/ether-addr-util.h index cf014a95273e8..d342b5621e07e 100644 --- a/src/basic/ether-addr-util.h +++ b/src/basic/ether-addr-util.h @@ -57,6 +57,7 @@ static inline bool hw_addr_equal(const struct hw_addr_data *a, const struct hw_a return hw_addr_compare(a, b) == 0; } bool hw_addr_is_null(const struct hw_addr_data *addr) _pure_; +bool hw_addr_is_valid(const struct hw_addr_data *addr, uint16_t iftype); extern const struct hash_ops hw_addr_hash_ops; extern const struct hash_ops hw_addr_hash_ops_free; diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c index 704a38831e13d..3c4b886e6759c 100644 --- a/src/udev/net/link-config.c +++ b/src/udev/net/link-config.c @@ -558,26 +558,6 @@ static int link_apply_ethtool_settings(Link *link, int *ethtool_fd) { return 0; } -static bool hw_addr_is_valid(Link *link, const struct hw_addr_data *hw_addr) { - assert(link); - assert(hw_addr); - - switch (link->iftype) { - case ARPHRD_ETHER: - /* Refuse all zero and all 0xFF. */ - assert(hw_addr->length == ETH_ALEN); - return !ether_addr_is_null(&hw_addr->ether) && !ether_addr_is_broadcast(&hw_addr->ether); - - case ARPHRD_INFINIBAND: - /* The last 8 bytes cannot be zero. */ - assert(hw_addr->length == INFINIBAND_ALEN); - return !memeqzero(hw_addr->bytes + INFINIBAND_ALEN - 8, 8); - - default: - assert_not_reached(); - } -} - static int link_generate_new_hw_addr(Link *link, struct hw_addr_data *ret) { struct hw_addr_data hw_addr = HW_ADDR_NULL; bool is_static = false; @@ -647,7 +627,7 @@ static int link_generate_new_hw_addr(Link *link, struct hw_addr_data *ret) { * systems booting up at the very same time. */ for (;;) { random_bytes(p, len); - if (hw_addr_is_valid(link, &hw_addr)) + if (hw_addr_is_valid(&hw_addr, link->iftype)) break; } @@ -662,7 +642,7 @@ static int link_generate_new_hw_addr(Link *link, struct hw_addr_data *ret) { assert(len <= sizeof(result)); memcpy(p, &result, len); - if (!hw_addr_is_valid(link, &hw_addr)) + if (!hw_addr_is_valid(&hw_addr, link->iftype)) return log_link_warning_errno(link, SYNTHETIC_ERRNO(EINVAL), "Could not generate valid persistent MAC address."); } From cceddc41fd2511db52456835f24ea7e6381fab58 Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Tue, 24 Feb 2026 14:05:05 -0500 Subject: [PATCH 0638/1296] network-generator: support BOOTIF= and rd.bootif=0 options The network generator currently supports many of the options described by dracut.cmdline(7), but not everything. This commit adds support for the BOOTIF= option (and the related rd.bootif= option) used in PXE setups. This is implemented by treating BOOTIF as a special name/placeholder when used as an interface name, and expecting a MAC address to be set in the BOOTIF= parameter. The resulting .network file then uses MACAddress= in the [Match] section, instead of Name=. --- man/systemd-network-generator.service.xml | 19 +++- .../generator/network-generator-main.c | 2 + src/network/generator/network-generator.c | 97 ++++++++++++++++++- src/network/generator/network-generator.h | 6 ++ .../generator/test-network-generator.c | 83 ++++++++++++++++ 5 files changed, 204 insertions(+), 3 deletions(-) diff --git a/man/systemd-network-generator.service.xml b/man/systemd-network-generator.service.xml index ccdb57b62b270..7b20f8516d0b5 100644 --- a/man/systemd-network-generator.service.xml +++ b/man/systemd-network-generator.service.xml @@ -100,10 +100,27 @@ + + BOOTIF= + rd.bootif= + + When BOOTIF is specified in the interface field of ip=, it is treated + as a special placeholder rather than a real interface name. Then, in combination with the MAC address provided + by BOOTIF=, this is translated into a + systemd.network5 file + which matches on the the provided MAC address. When rd.bootif=0 is passed, this functionality + is disabled, and the BOOTIF= option is ignored. + + See dracut.cmdline7 + for details on the usage of these options. + + + + + - - ### Must fix - - [ ] **short title** — `path:line` — brief explanation - - ### Suggestions - - [ ] **short title** — `path:line` — brief explanation - - ### Nits - - [ ] **short title** — `path:line` — brief explanation - ``` - - Omit empty sections. Each checkbox item must correspond to an entry in `comments`. - If there are no issues at all, write a short message saying the PR looks good. - - **If an existing tracking comment was found (subsequent run):** - Use the existing comment as the starting point. Preserve the order and wording - of all existing items. Then apply these updates: - - Update the HEAD SHA in the header line. - - For each existing item, re-check whether the issue is still present in the - current diff. If it has been fixed, mark it checked: `- [x]`. - - If the PR author replied dismissing an item, mark it: - `- [x] ~~short title~~ (dismissed)`. - - Preserve checkbox state that was already set by previous runs or by hand. - - Append any NEW issues found in this run that aren't already listed, - in the appropriate severity section, after the existing items. - - Do NOT reorder, reword, or remove existing items. - - ## Error tracking - - If any errors prevented you from doing your job fully (tools that were - not available, git commands that failed, etc.), append a `### Errors` - section to the summary listing each failed action and the error message. - - ## Output formatting - - Do NOT escape characters in `body` or `summary`. Write plain markdown — no - backslash escaping of `!` or other characters. In particular, HTML comments - like `` must be written verbatim, never as `<\!-- ... -->`. - - ## CRITICAL: Write review result to file - - Your FINAL action must be to write `review-result.json` in the repo - root. The file must contain a JSON object with the following schema: - - ```json - { - "type": "object", - "required": ["summary", "comments"], - "properties": { - "summary": { "type": "string", "description": "Markdown summary for the tracking comment" }, - "comments": { "description": "Array of review comments (same schema as the reviewer output above)" }, - "resolve": { "type": "array", "items": { "type": "integer" }, "description": "REST API IDs of review comment threads to resolve" } - } - } + + + ### Must fix + - [ ] **short title** — `path:line` — brief explanation + + ### Suggestions + - [ ] **short title** — `path:line` — brief explanation + + ### Nits + - [ ] **short title** — `path:line` — brief explanation ``` - Do NOT attempt to post comments or use any MCP tools to modify the PR. + Omit empty sections. Each checkbox item must correspond to an entry in `comments`. + If there are no issues at all, write a short message saying the PR looks good. + + **If an existing tracking comment was found (subsequent run):** + Use the existing comment as the starting point. Preserve the order and wording + of all existing items. Then apply these updates: + - Update the HEAD SHA in the header line. + - For each existing item, re-check whether the issue is still present in the + current diff. If it has been fixed, mark it checked: `- [x]`. + - If the PR author replied dismissing an item, mark it: + `- [x] ~~short title~~ (dismissed)`. + - Preserve checkbox state that was already set by previous runs or by hand. + - Append any new issues found in this run that aren't already listed, + in the appropriate severity section, after the existing items. + - Do not reorder, reword, or remove existing items. + + ## Error tracking + + If any errors prevented you from doing your job fully (tools that were + not available, git commands that failed, etc.), append a `### Errors` + section to the summary listing each failed action and the error message. + + ## Review result + + Produce your review result as structured output. The fields are: + - `summary`: Markdown summary for the tracking comment. + - `comments`: Array of review comments (same schema as the reviewer output above). + - `resolve`: REST API IDs of review comment threads to resolve. + PROMPT + + claude \ + --model us.anthropic.claude-opus-4-6-v1 \ + --effort max \ + --max-turns 200 \ + --setting-sources user \ + --output-format stream-json \ + --json-schema "$(cat review-schema.json)" \ + --verbose \ + -p "$(cat /tmp/review-prompt.txt)" \ + | tee claude.json + + jq '.structured_output | select(. != null)' claude.json > review-result.json - name: Upload review result if: always() From 51164fd9fcce758a3597c43d89e56a36bc5a01de Mon Sep 17 00:00:00 2001 From: Tobias Heider Date: Wed, 1 Apr 2026 11:17:16 +0200 Subject: [PATCH 0662/1296] hwdb: Silence spurrious F23 key-press from Fn key on Thinkpad T14s The Thinkpad T14s Gen 6 (Snapdragon) emits a F23 key press when pressing the Fn key. Silence them since the keyboard doesn't actually have a F23 key. --- hwdb.d/60-keyboard.hwdb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hwdb.d/60-keyboard.hwdb b/hwdb.d/60-keyboard.hwdb index ce3750a36af7f..a83c7e4b94d89 100644 --- a/hwdb.d/60-keyboard.hwdb +++ b/hwdb.d/60-keyboard.hwdb @@ -1214,6 +1214,11 @@ evdev:name:AT Raw Set 2 keyboard:dmi:*:svnLENOVO:pn83N0:* evdev:name:AT Raw Set 2 keyboard:dmi:*:svnLENOVO:pn83N1:* KEYBOARD_KEY_20=f16 # Power button long press +# Lenovo Thinkpad T14s Gen 6 (Snapdragon) +evdev:name:hid-over-i2c 04F3:000D Keyboard:dmi:bvn*:bvr*:bd*:svnLENOVO:pn21N1*:* +evdev:name:hid-over-i2c 04F3:000D Keyboard:dmi:bvn*:bvr*:bd*:svnLENOVO:pn21N2*:* + KEYBOARD_KEY_70072=unknown # Silence spurious F23 key-press report from Fn key + ########################################################### # LG ########################################################### From af5126568af6080b5908f31fe4b39de278a02e5c Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 10 Mar 2026 14:10:05 +0100 Subject: [PATCH 0663/1296] nspawn: keep backing files for boot_id and kmsg bind mounts alive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both setup_boot_id() and setup_kmsg() previously created temporary files in /run, bind mounted them over their respective /proc targets, and then immediately unlinked the backing files. While the bind mount keeps the inode alive, the kernel marks the dentry as deleted. This is a problem because bind mounts backed by unlinked files cannot be replicated: both the old mount API (mount(MS_BIND)) and the new mount API (open_tree(OPEN_TREE_CLONE) + move_mount()) fail with ENOENT when the source mount references a deleted dentry. This affects mount_private_apivfs() in namespace.c, which needs to replicate these submounts when setting up a fresh /proc instance for services with ProtectProc= or similar sandboxing options — with an unlinked backing file, the boot_id submount simply gets lost. Fix this by using fixed paths (/run/proc-sys-kernel-random-boot-id and /run/proc-kmsg) instead of randomized tempfiles, and not unlinking them after the bind mount. The files live in /run which is cleaned up on shutdown anyway. Co-Authored-By: Claude Opus 4.6 --- src/nspawn/nspawn.c | 60 +++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index a778a25aa5e0e..f3e072b0db29e 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -2124,34 +2124,42 @@ static int setup_resolv_conf(const char *dest) { } static int setup_boot_id(void) { - _cleanup_(unlink_and_freep) char *from = NULL; - _cleanup_free_ char *path = NULL; sd_id128_t rnd = SD_ID128_NULL; - const char *to; int r; /* Generate a new randomized boot ID, so that each boot-up of the container gets a new one */ - r = tempfn_random_child("/run", "proc-sys-kernel-random-boot-id", &path); - if (r < 0) - return log_error_errno(r, "Failed to generate random boot ID path: %m"); - r = sd_id128_randomize(&rnd); if (r < 0) return log_error_errno(r, "Failed to generate random boot id: %m"); - r = id128_write(path, ID128_FORMAT_UUID, rnd); + r = id128_write("/run/.proc-sys-kernel-random-boot-id", ID128_FORMAT_UUID, rnd); if (r < 0) return log_error_errno(r, "Failed to write boot id: %m"); - from = TAKE_PTR(path); - to = "/proc/sys/kernel/random/boot_id"; - - r = mount_nofollow_verbose(LOG_ERR, from, to, NULL, MS_BIND, NULL); + r = mount_nofollow_verbose( + LOG_ERR, + "/run/.proc-sys-kernel-random-boot-id", + "/proc/sys/kernel/random/boot_id", + /* fstype= */ NULL, + MS_BIND, + /* options= */ NULL); if (r < 0) return r; - return mount_nofollow_verbose(LOG_ERR, NULL, to, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL); + /* NB: We intentionally do not unlink the backing file. Bind mounts of unlinked files cannot be + * replicated to other mount namespaces (both the old and new mount APIs fail with ENOENT). Since + * mount_private_apivfs() needs to replicate submounts like boot_id when setting up a fresh /proc + * instance, the backing file must remain on disk. It lives in /run which is cleaned up on + * shutdown anyway. */ + + return mount_nofollow_verbose( + LOG_ERR, + /* what= */ NULL, + "/proc/sys/kernel/random/boot_id", + /* fstype= */ NULL, + MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, + /* options= */ NULL); } static int bind_mount_devnode(const char *from, const char *to) { @@ -2532,8 +2540,6 @@ static int setup_credentials(const char *root) { } static int setup_kmsg(int fd_inner_socket) { - _cleanup_(unlink_and_freep) char *from = NULL; - _cleanup_free_ char *fifo = NULL; _cleanup_close_ int fd = -EBADF; int r; @@ -2541,28 +2547,24 @@ static int setup_kmsg(int fd_inner_socket) { BLOCK_WITH_UMASK(0000); - /* We create the kmsg FIFO as a temporary file in /run, but immediately delete it after bind mounting it to - * /proc/kmsg. While FIFOs on the reading side behave very similar to /proc/kmsg, their writing side behaves - * differently from /dev/kmsg in that writing blocks when nothing is reading. In order to avoid any problems - * with containers deadlocking due to this we simply make /dev/kmsg unavailable to the container. */ + /* We create the kmsg FIFO in /run, and bind mount it to /proc/kmsg. While FIFOs on the reading + * side behave very similar to /proc/kmsg, their writing side behaves differently from /dev/kmsg in + * that writing blocks when nothing is reading. In order to avoid any problems with containers + * deadlocking due to this we simply make /dev/kmsg unavailable to the container. */ - r = tempfn_random_child("/run", "proc-kmsg", &fifo); - if (r < 0) - return log_error_errno(r, "Failed to generate kmsg path: %m"); - - if (mkfifo(fifo, 0600) < 0) - return log_error_errno(errno, "mkfifo() for /run/kmsg failed: %m"); + if (mkfifo("/run/.proc-kmsg", 0600) < 0) + return log_error_errno(errno, "mkfifo() for /run/.proc-kmsg failed: %m"); - from = TAKE_PTR(fifo); - - r = mount_nofollow_verbose(LOG_ERR, from, "/proc/kmsg", NULL, MS_BIND, NULL); + r = mount_nofollow_verbose(LOG_ERR, "/run/.proc-kmsg", "/proc/kmsg", NULL, MS_BIND, NULL); if (r < 0) return r; - fd = open(from, O_RDWR|O_NONBLOCK|O_CLOEXEC); + fd = open("/run/.proc-kmsg", O_RDWR|O_NONBLOCK|O_CLOEXEC); if (fd < 0) return log_error_errno(errno, "Failed to open fifo: %m"); + /* NB: We intentionally do not unlink the backing FIFO. See setup_boot_id() for details. */ + /* Store away the fd in the socket, so that it stays open as long as we run the child */ r = send_one_fd(fd_inner_socket, fd, 0); if (r < 0) From d3cb7a4e0fc95eaa0fefe1b750e3a2612aeee97f Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 30 Mar 2026 08:43:38 +0000 Subject: [PATCH 0664/1296] loop-util: work around kernel loop driver partition scan race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The kernel loop driver has a race condition in LOOP_CONFIGURE when LO_FLAGS_PARTSCAN is set: it sends a KOBJ_CHANGE uevent (with GD_NEED_PART_SCAN set) before calling loop_reread_partitions(). If udev opens the device in response to the uevent before loop_reread_partitions() runs, the kernel's blkdev_get_whole() sees GD_NEED_PART_SCAN and triggers a first partition scan. Then loop_reread_partitions() runs a second scan that drops all partitions from the first scan (via blk_drop_partitions()) before re-adding them. This causes partition devices to briefly disappear (plugged -> dead -> plugged), which breaks systemd units with BindsTo= on the partition device: systemd observes the dead transition, fails the dependent units with 'dependency', and does not retry when the device reappears. Work around this in loop_device_make_internal() by splitting the loop device setup into two steps: first LOOP_CONFIGURE without LO_FLAGS_PARTSCAN, then LOOP_SET_STATUS64 to enable partscan. This avoids the race because: 1. LOOP_CONFIGURE without partscan: disk_force_media_change() sets GD_NEED_PART_SCAN, but GD_SUPPRESS_PART_SCAN remains set. If udev opens the device, blkdev_get_whole() calls bdev_disk_changed() which clears GD_NEED_PART_SCAN, but blk_add_partitions() returns early because disk_has_partscan() is false — no partitions appear, the flag is drained harmlessly. 2. Between the two ioctls, we open and close the device to ensure GD_NEED_PART_SCAN is drained regardless of whether udev processed the uevent yet. 3. LOOP_SET_STATUS64 with LO_FLAGS_PARTSCAN: clears GD_SUPPRESS_PART_SCAN and calls loop_reread_partitions() for a single clean scan. Crucially, loop_set_status() does not call disk_force_media_change(), so GD_NEED_PART_SCAN is never set again. A proper kernel fix has been submitted: https://lore.kernel.org/linux-block/20260330081819.652890-1-daan@amutable.com/T/#u This workaround should be dropped once the fix is widely available. Co-developed-by: Claude Opus 4.6 --- src/shared/loop-util.c | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c index fbed79bb2ec16..4a759f4a7e24e 100644 --- a/src/shared/loop-util.c +++ b/src/shared/loop-util.c @@ -541,12 +541,26 @@ static int loop_device_make_internal( return r; } + /* Strip LO_FLAGS_PARTSCAN from LOOP_CONFIGURE and enable it afterwards via + * LOOP_SET_STATUS64 to work around a kernel race: LOOP_CONFIGURE sends a uevent with + * GD_NEED_PART_SCAN set before calling loop_reread_partitions(). If udev opens the device in + * response, blkdev_get_whole() triggers a first scan, then loop_reread_partitions() does a + * second scan that briefly drops all partitions. By configuring without partscan, + * GD_SUPPRESS_PART_SCAN stays set, making any concurrent open harmless. LOOP_SET_STATUS64 + * doesn't call disk_force_media_change() so it doesn't set GD_NEED_PART_SCAN. + * + * See: https://lore.kernel.org/linux-block/20260330081819.652890-1-daan@amutable.com/T/#u + * Drop this workaround once the kernel fix is widely available. */ + bool deferred_partscan = FLAGS_SET(loop_flags, LO_FLAGS_PARTSCAN); + config = (struct loop_config) { .fd = fd, .block_size = sector_size, .info = { /* Use the specified flags, but configure the read-only flag from the open flags, and force autoclear */ - .lo_flags = (loop_flags & ~LO_FLAGS_READ_ONLY) | ((open_flags & O_ACCMODE_STRICT) == O_RDONLY ? LO_FLAGS_READ_ONLY : 0) | LO_FLAGS_AUTOCLEAR, + .lo_flags = ((loop_flags & ~(LO_FLAGS_READ_ONLY|LO_FLAGS_PARTSCAN)) | + ((open_flags & O_ACCMODE_STRICT) == O_RDONLY ? LO_FLAGS_READ_ONLY : 0) | + LO_FLAGS_AUTOCLEAR), .lo_offset = offset, .lo_sizelimit = size == UINT64_MAX ? 0 : size, }, @@ -638,6 +652,24 @@ static int loop_device_make_internal( } } + if (deferred_partscan) { + /* Open+close to drain GD_NEED_PART_SCAN harmlessly (GD_SUPPRESS_PART_SCAN is still + * set so no partitions appear). Then enable partscan via LOOP_SET_STATUS64. */ + int tmp_fd = fd_reopen(d->fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK); + if (tmp_fd < 0) + return log_debug_errno(tmp_fd, "Failed to reopen loop device to drain partscan flag: %m"); + safe_close(tmp_fd); + + struct loop_info64 info; + if (ioctl(d->fd, LOOP_GET_STATUS64, &info) < 0) + return log_debug_errno(errno, "Failed to get loop device status: %m"); + + info.lo_flags |= LO_FLAGS_PARTSCAN; + + if (ioctl(d->fd, LOOP_SET_STATUS64, &info) < 0) + return log_debug_errno(errno, "Failed to enable partscan on loop device: %m"); + } + d->backing_file = TAKE_PTR(backing_file); d->backing_inode = st.st_ino; d->backing_devno = st.st_dev; From 0ffefb8a4da960ec58b2aa711139898118be9f06 Mon Sep 17 00:00:00 2001 From: Michal Rybecky Date: Wed, 1 Apr 2026 10:00:14 +0200 Subject: [PATCH 0665/1296] hmac: erase key-derived stack buffers before returning hmac_sha256() leaves four stack buffers containing key-derived material (inner_padding, outer_padding, replacement_key, hash state) on the stack after returning. The inner_padding and outer_padding arrays contain key XOR 0x36 and key XOR 0x5c respectively, which are trivially reversible to recover the original HMAC key. This function is called with security-sensitive keys including the LUKS volume key (cryptsetup-util.c), TPM2 PIN (tpm2-util.c), and boot secret (tpm2-swtpm.c). The key material persists on the stack until overwritten by later unrelated function calls. Add CLEANUP_ERASE() to all four local buffers, following the same pattern applied to tpm2-util.c in commit 6c80ce6 (PR #41394). --- src/basic/hmac.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/basic/hmac.c b/src/basic/hmac.c index d70b874e2cd36..3697fe2252460 100644 --- a/src/basic/hmac.c +++ b/src/basic/hmac.c @@ -3,6 +3,7 @@ #include #include "hmac.h" +#include "memory-util.h" #include "sha256.h" #define HMAC_BLOCK_SIZE 64 @@ -16,9 +17,13 @@ void hmac_sha256(const void *key, uint8_t res[static SHA256_DIGEST_SIZE]) { uint8_t inner_padding[HMAC_BLOCK_SIZE] = { }; + CLEANUP_ERASE(inner_padding); uint8_t outer_padding[HMAC_BLOCK_SIZE] = { }; + CLEANUP_ERASE(outer_padding); uint8_t replacement_key[SHA256_DIGEST_SIZE]; + CLEANUP_ERASE(replacement_key); struct sha256_ctx hash; + CLEANUP_ERASE(hash); assert(key); assert(key_size > 0); From b67558286134ceb520511c5d2638b1be023bc612 Mon Sep 17 00:00:00 2001 From: Michal Rybecky Date: Wed, 1 Apr 2026 13:16:42 +0200 Subject: [PATCH 0666/1296] hmac: add comments explaining why each buffer needs erasing As requested in review: clarify that the padding arrays carry key material (key XOR fixed constant, trivially reversible), not just padding bytes. --- src/basic/hmac.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/basic/hmac.c b/src/basic/hmac.c index 3697fe2252460..72cf59d468f23 100644 --- a/src/basic/hmac.c +++ b/src/basic/hmac.c @@ -17,13 +17,13 @@ void hmac_sha256(const void *key, uint8_t res[static SHA256_DIGEST_SIZE]) { uint8_t inner_padding[HMAC_BLOCK_SIZE] = { }; - CLEANUP_ERASE(inner_padding); + CLEANUP_ERASE(inner_padding); /* carries key ^ 0x36, trivially reversible to the original key */ uint8_t outer_padding[HMAC_BLOCK_SIZE] = { }; - CLEANUP_ERASE(outer_padding); + CLEANUP_ERASE(outer_padding); /* carries key ^ 0x5c, trivially reversible to the original key */ uint8_t replacement_key[SHA256_DIGEST_SIZE]; - CLEANUP_ERASE(replacement_key); + CLEANUP_ERASE(replacement_key); /* SHA-256 of the key when key_size > block size */ struct sha256_ctx hash; - CLEANUP_ERASE(hash); + CLEANUP_ERASE(hash); /* intermediate state derived from key material */ assert(key); assert(key_size > 0); From ac725eb953369e3fb24beff5ce45888e8532948b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:53:51 +0000 Subject: [PATCH 0667/1296] build(deps): bump the actions group with 3 updates Bumps the actions group with 3 updates: [actions/upload-artifact](https://github.com/actions/upload-artifact), [redhat-plumbers-in-action/download-artifact](https://github.com/redhat-plumbers-in-action/download-artifact) and [softprops/action-gh-release](https://github.com/softprops/action-gh-release). Updates `actions/upload-artifact` from 6 to 7 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v6...v7) Updates `redhat-plumbers-in-action/download-artifact` from 1.1.5 to 1.1.6 - [Release notes](https://github.com/redhat-plumbers-in-action/download-artifact/releases) - [Commits](https://github.com/redhat-plumbers-in-action/download-artifact/compare/103e5f882470b59e9d71c80ecb2d0a0b91a7c43b...03d5b806a9dca9928eb5628833fe81a0558f23bb) Updates `softprops/action-gh-release` from 2.5.0 to 2.6.1 - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/a06a81a03ee405af7f2048a818ed3f03bbf83c7b...153bb8e04406b158c6c84fc1615b65b24149a1fe) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions - dependency-name: redhat-plumbers-in-action/download-artifact dependency-version: 1.1.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions - dependency-name: softprops/action-gh-release dependency-version: 2.6.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions ... Signed-off-by: dependabot[bot] --- .github/workflows/cifuzz.yml | 2 +- .github/workflows/coverage.yml | 2 +- .github/workflows/development-freeze.yml | 2 +- .github/workflows/gather-pr-metadata.yml | 2 +- .github/workflows/make-release.yml | 2 +- .github/workflows/mkosi.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index bb301793ca1b7..d352b2c7b4028 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -60,7 +60,7 @@ jobs: sanitizer: ${{ matrix.sanitizer }} output-sarif: true - name: Upload Crash - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f if: failure() && steps.build.outcome == 'success' with: name: ${{ matrix.sanitizer }}-${{ matrix.architecture }}-artifacts diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 7ebb7491506a7..c2b9493f6d8ba 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -130,7 +130,7 @@ jobs: --quiet - name: Archive failed test journals - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 if: failure() && (github.repository == 'systemd/systemd' || github.repository == 'systemd/systemd-stable') with: name: ci-coverage-${{ github.run_id }}-${{ github.run_attempt }}-arch-rolling-failed-test-journals diff --git a/.github/workflows/development-freeze.yml b/.github/workflows/development-freeze.yml index be75a2c421c58..25f6c1e92cfb9 100644 --- a/.github/workflows/development-freeze.yml +++ b/.github/workflows/development-freeze.yml @@ -25,7 +25,7 @@ jobs: steps: - id: artifact name: Download Pull Request Metadata artifact - uses: redhat-plumbers-in-action/download-artifact@103e5f882470b59e9d71c80ecb2d0a0b91a7c43b + uses: redhat-plumbers-in-action/download-artifact@03d5b806a9dca9928eb5628833fe81a0558f23bb with: name: Pull Request Metadata diff --git a/.github/workflows/gather-pr-metadata.yml b/.github/workflows/gather-pr-metadata.yml index f9cfd9154e61c..2ae9a098a6949 100644 --- a/.github/workflows/gather-pr-metadata.yml +++ b/.github/workflows/gather-pr-metadata.yml @@ -25,7 +25,7 @@ jobs: uses: redhat-plumbers-in-action/gather-pull-request-metadata@b86d1eaf7038cf88a56b26ba3e504f10e07b0ce5 - name: Upload Pull Request Metadata artifact - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f with: name: Pull Request Metadata path: ${{ steps.metadata.outputs.metadata-file }} diff --git a/.github/workflows/make-release.yml b/.github/workflows/make-release.yml index 72daed60ef293..3aa169f55ad5c 100644 --- a/.github/workflows/make-release.yml +++ b/.github/workflows/make-release.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Release - uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b + uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe with: prerelease: ${{ contains(github.ref_name, '-rc') }} draft: ${{ github.repository == 'systemd/systemd' }} diff --git a/.github/workflows/mkosi.yml b/.github/workflows/mkosi.yml index c83be9c78dfea..5fd1469ba3535 100644 --- a/.github/workflows/mkosi.yml +++ b/.github/workflows/mkosi.yml @@ -324,7 +324,7 @@ jobs: "${MAX_LINES[@]}" - name: Archive failed test journals - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 if: failure() && (github.repository == 'systemd/systemd' || github.repository == 'systemd/systemd-stable') with: name: ci-mkosi-${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.distro }}-${{ matrix.release }}-${{ matrix.runner }}-failed-test-journals From e44f88f275ee99d8ed349f0cdfb37520aca3d8a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:50:14 +0000 Subject: [PATCH 0668/1296] build(deps): bump meson from 1.10.1 to 1.10.2 in /.github/workflows Bumps [meson](https://github.com/mesonbuild/meson) from 1.10.1 to 1.10.2. - [Release notes](https://github.com/mesonbuild/meson/releases) - [Commits](https://github.com/mesonbuild/meson/compare/1.10.1...1.10.2) --- updated-dependencies: - dependency-name: meson dependency-version: 1.10.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/requirements.txt b/.github/workflows/requirements.txt index 0334f8612219a..95f1bf1a6a5ad 100644 --- a/.github/workflows/requirements.txt +++ b/.github/workflows/requirements.txt @@ -1,6 +1,6 @@ -meson==1.10.1 \ - --hash=sha256:c42296f12db316a4515b9375a5df330f2e751ccdd4f608430d41d7d6210e4317 \ - --hash=sha256:fe43d1cc2e6de146fbea78f3a062194bcc0e779efc8a0f0d7c35544dfb86731f +meson==1.10.2 \ + --hash=sha256:5f84ef186e6e788d9154db63620fc61b3ece69f643b94b43c8b9203c43d89b36 \ + --hash=sha256:7890287d911dd4ee1ebd0efb61ed0321bfcd87c725df923a837cf90c6508f96b ninja==1.13.0 \ --hash=sha256:11be2d22027bde06f14c343f01d31446747dbb51e72d00decca2eb99be911e2f \ --hash=sha256:1c97223cdda0417f414bf864cfb73b72d8777e57ebb279c5f6de368de0062988 \ From 45e4df9a331208d20ecb9f5ead8110eb50a5b86d Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 27 Mar 2026 18:48:28 +0000 Subject: [PATCH 0669/1296] stub: auto-detect console device and append console= to kernel command line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Linux kernel does not reliably auto-detect serial consoles on headless systems. While the docs claim serial is used as a fallback when no VGA card is found, in practice CONFIG_VT's dummy console (dummycon) registers early and satisfies the kernel's console requirement, preventing the serial fallback from ever triggering. The ACPI SPCR table can help on ARM/RISC-V where QEMU generates it, but x86 QEMU does not produce SPCR, and SPCR cannot describe virtio consoles at all. This means UKIs booted via sd-stub in headless VMs produce no visible console output unless console= is explicitly passed on the kernel command line. Fix this by having sd-stub auto-detect the console type and append an appropriate console= argument when one isn't already present. Detection priority: 1. VirtIO console PCI device (vendor 0x1AF4, device 0x1003): if exactly one is found, append console=hvc0. This takes highest priority since a VirtIO console is explicitly configured by the VMM (e.g. systemd-vmspawn's virtconsole device). If multiple VirtIO console devices exist, we cannot determine which hvc index is correct, so we skip this path entirely. 2. EFI Graphics Output Protocol (GOP): if present, don't add any console= argument. The kernel will use the framebuffer console by default, and adding a serial console= would redirect the primary console away from the display. 3. Serial console: first, we count the total number of serial devices via EFI_SERIAL_IO_PROTOCOL. If there are zero or more than one, we bail out — with multiple UARTs, the kernel assigns ttyS indices based on its own enumeration order and we cannot determine which index the console UART will receive. Only when exactly one serial device exists (guaranteeing it will be ttyS0) do we proceed to verify it's actually used as a console by checking for UART device path nodes (MESSAGING_DEVICE_PATH + MSG_UART_DP). The firmware's ConOut handle is checked first; if it has no device path (common with OVMF's ConSplitter virtual handle when using -nographic -nodefaults), we fall back to enumerating all EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL handles and checking each one's device path. The architecture-specific console argument is then appended: - x86: console=ttyS0 - ARM: console=ttyAMA0 - Others: console=ttyS0 (RISC-V, LoongArch, MIPS all use ttyS0) Note on OVMF's VirtioSerialDxe: it exposes virtio serial ports with the same UART device path nodes as real serial ports (ACPI PNP 0x0501 + MSG_UART_DP), making them indistinguishable from real UARTs via device path inspection alone. This is why we check for the VirtIO console PCI device via EFI_PCI_IO_PROTOCOL before falling back to device path analysis. Also add a minimal EFI_PCI_IO_PROTOCOL definition (proto/pci-io.h) with just enough to call Pci.Read for vendor/device ID enumeration, and add the MSG_UART_DP subtype to the device path header. Co-developed-by: Claude Opus 4.6 --- src/boot/proto/device-path.h | 1 + src/boot/proto/pci-io.h | 43 +++++++ src/boot/proto/serial-io.h | 7 ++ src/boot/stub.c | 224 +++++++++++++++++++++++++++++++++++ 4 files changed, 275 insertions(+) create mode 100644 src/boot/proto/pci-io.h create mode 100644 src/boot/proto/serial-io.h diff --git a/src/boot/proto/device-path.h b/src/boot/proto/device-path.h index b56c217082dd8..531ff3d003bdc 100644 --- a/src/boot/proto/device-path.h +++ b/src/boot/proto/device-path.h @@ -33,6 +33,7 @@ enum { MEDIA_PIWG_FW_FILE_DP = 0x06, MEDIA_PIWG_FW_VOL_DP = 0x07, + MSG_UART_DP = 0x0e, MSG_URI_DP = 24, }; diff --git a/src/boot/proto/pci-io.h b/src/boot/proto/pci-io.h new file mode 100644 index 0000000000000..d05f0a683ce9e --- /dev/null +++ b/src/boot/proto/pci-io.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "efi.h" + +#define EFI_PCI_IO_PROTOCOL_GUID \ + GUID_DEF(0x4cf5b200, 0x68b8, 0x4ca5, 0x9e, 0xec, 0xb2, 0x3e, 0x3f, 0x50, 0x02, 0x9a) + +typedef enum { + EfiPciIoWidthUint8 = 0, + EfiPciIoWidthUint16, + EfiPciIoWidthUint32, + EfiPciIoWidthUint64, +} EFI_PCI_IO_PROTOCOL_WIDTH; + +typedef struct EFI_PCI_IO_PROTOCOL EFI_PCI_IO_PROTOCOL; + +typedef EFI_STATUS (EFIAPI *EFI_PCI_IO_PROTOCOL_CONFIG)( + EFI_PCI_IO_PROTOCOL *This, + EFI_PCI_IO_PROTOCOL_WIDTH Width, + uint32_t Offset, + size_t Count, + void *Buffer); + +typedef struct { + EFI_PCI_IO_PROTOCOL_CONFIG Read; + EFI_PCI_IO_PROTOCOL_CONFIG Write; +} EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS; + +/* Minimal definition — only Pci.Read is used. Fields before Pci must be correctly sized + * (one function pointer each for PollMem/PollIo, two for Mem.Read/Write, two for Io.Read/Write) + * to ensure Pci is at the right offset. */ +struct EFI_PCI_IO_PROTOCOL { + void *PollMem; + void *PollIo; + EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS Mem; + EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS Io; + EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS Pci; + /* remaining fields omitted */ +}; + +#define PCI_VENDOR_ID_REDHAT 0x1af4U +#define PCI_DEVICE_ID_VIRTIO_CONSOLE 0x1003U diff --git a/src/boot/proto/serial-io.h b/src/boot/proto/serial-io.h new file mode 100644 index 0000000000000..98690c108c288 --- /dev/null +++ b/src/boot/proto/serial-io.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "efi.h" + +#define EFI_SERIAL_IO_PROTOCOL_GUID \ + GUID_DEF(0xbb25cf6f, 0xf1d4, 0x11d2, 0x9a, 0x0c, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0xfd) diff --git a/src/boot/stub.c b/src/boot/stub.c index 66b20805d5389..6b5ae0a68786c 100644 --- a/src/boot/stub.c +++ b/src/boot/stub.c @@ -6,6 +6,7 @@ #include "devicetree.h" #include "efi-efivars.h" #include "efi-log.h" +#include "efi-string.h" #include "export-vars.h" #include "graphics.h" #include "iovec-util-fundamental.h" @@ -14,6 +15,9 @@ #include "memory-util-fundamental.h" #include "part-discovery.h" #include "pe.h" +#include "proto/graphics-output.h" +#include "proto/pci-io.h" +#include "proto/serial-io.h" /* IWYU pragma: keep */ #include "proto/shell-parameters.h" #include "random-seed.h" #include "sbat.h" @@ -1242,6 +1246,220 @@ static void measure_profile(unsigned profile, int *parameters_measured) { combine_measured_flag(parameters_measured, m); } +static bool has_virtio_console_pci_device(void) { + _cleanup_free_ EFI_HANDLE *handles = NULL; + size_t n_handles = 0; + + EFI_STATUS err = BS->LocateHandleBuffer( + ByProtocol, + MAKE_GUID_PTR(EFI_PCI_IO_PROTOCOL), + NULL, + &n_handles, + &handles); + if (err != EFI_SUCCESS) { + log_debug_status(err, "Failed to locate PCI I/O protocol handles, assuming no VirtIO console: %m"); + return false; + } + + if (n_handles == 0) { + log_debug("No PCI devices found, not scanning for VirtIO console."); + return false; + } + + log_debug("Found %zu PCI devices, scanning for VirtIO console...", n_handles); + + size_t n_virtio_console = 0; + + for (size_t i = 0; i < n_handles; i++) { + EFI_PCI_IO_PROTOCOL *pci_io = NULL; + + if (BS->HandleProtocol(handles[i], MAKE_GUID_PTR(EFI_PCI_IO_PROTOCOL), (void **) &pci_io) != EFI_SUCCESS) + continue; + + uint16_t vendor_id = 0, device_id = 0; + if (pci_io->Pci.Read(pci_io, EfiPciIoWidthUint16, 0x00, 1, &vendor_id) != EFI_SUCCESS) + continue; + if (pci_io->Pci.Read(pci_io, EfiPciIoWidthUint16, 0x02, 1, &device_id) != EFI_SUCCESS) + continue; + + log_debug("PCI device %zu: vendor=%04x device=%04x", i, vendor_id, device_id); + + if (vendor_id == PCI_VENDOR_ID_REDHAT && device_id == PCI_DEVICE_ID_VIRTIO_CONSOLE) + n_virtio_console++; + + if (n_virtio_console > 1) { + log_debug("There is more than one VirtIO console PCI device, cannot determine which one is the console."); + return false; + } + } + + if (n_virtio_console == 0) { + log_debug("No VirtIO console PCI device found."); + return false; + } + + log_debug("Found exactly one VirtIO console PCI device."); + return true; +} + +static bool device_path_has_uart(const EFI_DEVICE_PATH *dp) { + for (const EFI_DEVICE_PATH *node = dp; !device_path_is_end(node); node = device_path_next_node(node)) + if (node->Type == MESSAGING_DEVICE_PATH && node->SubType == MSG_UART_DP) + return true; + + return false; +} + +static size_t count_serial_devices(void) { + _cleanup_free_ EFI_HANDLE *handles = NULL; + size_t n_handles = 0; + + if (BS->LocateHandleBuffer( + ByProtocol, + MAKE_GUID_PTR(EFI_SERIAL_IO_PROTOCOL), + NULL, + &n_handles, + &handles) != EFI_SUCCESS) + return 0; + + log_debug("Found %zu serial I/O devices in total.", n_handles); + return n_handles; +} + +static bool has_single_serial_console(void) { + /* Even if we find exactly one serial console, we can only confidently map it to ttyS0 + * if there's only one serial device in the entire system. With multiple UARTs, the + * kernel assigns ttyS indices based on its own discovery order, so the console UART + * might end up as ttyS1 or higher. */ + size_t n_serial_devices = count_serial_devices(); + if (n_serial_devices == 0) { + log_debug("No serial I/O devices found."); + return false; + } + if (n_serial_devices > 1) { + log_debug("Found %zu serial I/O devices, cannot determine ttyS index.", n_serial_devices); + return false; + } + + /* Exactly one serial device in the system. Verify it's actually used as a console + * by checking if ConOut or any text output handle has a UART device path. */ + + /* First try the ConOut handle directly */ + EFI_DEVICE_PATH *dp = NULL; + if (BS->HandleProtocol(ST->ConsoleOutHandle, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &dp) == EFI_SUCCESS) { + _cleanup_free_ char16_t *dp_str = NULL; + (void) device_path_to_str(dp, &dp_str); + log_debug("ConOut device path: %ls", strempty(dp_str)); + + if (device_path_has_uart(dp)) { + log_debug("ConOut device path contains UART node."); + return true; + } + + log_debug("ConOut device path does not contain UART node."); + return false; + } + + /* ConOut handle has no device path (e.g. ConSplitter virtual handle). Enumerate all + * text output handles and check if any of them is a serial console. */ + log_debug("ConOut handle has no device path, enumerating text output handles..."); + + _cleanup_free_ EFI_HANDLE *handles = NULL; + size_t n_handles = 0; + if (BS->LocateHandleBuffer( + ByProtocol, + MAKE_GUID_PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL), + NULL, + &n_handles, + &handles) != EFI_SUCCESS) { + log_debug("Failed to enumerate text output handles."); + return false; + } + + for (size_t i = 0; i < n_handles; i++) { + dp = NULL; + if (BS->HandleProtocol(handles[i], MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &dp) != EFI_SUCCESS) + continue; + + _cleanup_free_ char16_t *dp_str = NULL; + (void) device_path_to_str(dp, &dp_str); + log_debug("Text output handle %zu device path: %ls", i, strempty(dp_str)); + + if (device_path_has_uart(dp)) { + log_debug("Text output handle %zu is a serial console.", i); + return true; + } + } + + log_debug("No serial console found among text output handles."); + return false; +} + +static bool has_graphics_output(void) { + EFI_GRAPHICS_OUTPUT_PROTOCOL *gop = NULL; + EFI_STATUS err; + + err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_GRAPHICS_OUTPUT_PROTOCOL), NULL, (void **) &gop); + if (err != EFI_SUCCESS) { + log_debug_status(err, "No EFI Graphics Output Protocol found: %m"); + return false; + } + + log_debug("EFI Graphics Output Protocol found."); + return true; +} + +static const char16_t *serial_console_arg(void) { +#if defined(__arm__) || defined(__aarch64__) + return u"console=ttyAMA0"; +#else + return u"console=ttyS0"; +#endif +} + +/* If there's no console= in the command line yet, try to detect the appropriate console device. + * + * Detection order: + * 1. If exactly one VirtIO console PCI device exists → console=hvc0 + * 2. If there's graphical output (GOP) → don't add console=, the kernel defaults are fine + * 3. If exactly one serial console exists → arch-specific serial (ttyS0, ttyAMA0, etc.) + * 4. Otherwise → don't add console=, let the user handle it + * + * VirtIO console takes priority since it's explicitly configured by the VMM. Graphics is + * checked before serial to avoid accidentally redirecting output away from a graphical + * console by adding a serial console= argument. */ +static void cmdline_append_console(char16_t **cmdline) { + assert(cmdline); + + if (*cmdline && (efi_fnmatch(u"console=*", *cmdline) || efi_fnmatch(u"* console=*", *cmdline))) { + log_debug("Kernel command line already contains console=, not adding one."); + return; + } + + const char16_t *console_arg = NULL; + + if (has_virtio_console_pci_device()) + console_arg = u"console=hvc0"; + else if (has_graphics_output()) { + log_debug("Graphical output available, not adding console= to kernel command line."); + return; + } else if (has_single_serial_console()) + console_arg = serial_console_arg(); + + if (!console_arg) { + log_debug("Cannot determine console type, not adding console= to kernel command line."); + return; + } + + log_debug("Appending %ls to kernel command line.", console_arg); + + _cleanup_free_ char16_t *old = TAKE_PTR(*cmdline); + if (isempty(old)) + *cmdline = xstrdup16(console_arg); + else + *cmdline = xasprintf("%ls %ls", old, console_arg); +} + static EFI_STATUS run(EFI_HANDLE image) { int sections_measured = -1, parameters_measured = -1, sysext_measured = -1, confext_measured = -1; _cleanup_(devicetree_cleanup) struct devicetree_state dt_state = {}; @@ -1304,6 +1522,12 @@ static EFI_STATUS run(EFI_HANDLE image) { cmdline_append_and_measure_addons(cmdline_addons, &cmdline, ¶meters_measured); cmdline_append_and_measure_smbios(&cmdline, ¶meters_measured); + /* Console auto-detection is intentionally not TPM-measured. The value is deterministically + * derived from firmware-reported hardware state (PCI device enumeration, GOP presence, serial + * device paths), so it doesn't represent an independent input that could be manipulated + * without also changing the firmware environment that TPM already captures. */ + cmdline_append_console(&cmdline); + export_common_variables(loaded_image); export_stub_variables(loaded_image, profile); From e11158af384ccc2e811cbc6d8f8ca413c057bad7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 5 Mar 2026 12:44:43 +0100 Subject: [PATCH 0670/1296] report: downgrade message for org.varlink.service.MethodNotFound We now have three kinds of endpoints under /run/systemd/report/: those that implement facts, those that implement metrics, and those that implement both. Let's downgrade the message to avoid pointless warnings. --- src/report/report.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/report/report.c b/src/report/report.c index f93b3076c6f81..be7b89b821472 100644 --- a/src/report/report.c +++ b/src/report/report.c @@ -226,7 +226,10 @@ static int metrics_on_query_reply( Context *context = ASSERT_PTR(li->context); if (error_id) { - if (streq(error_id, SD_VARLINK_ERROR_DISCONNECTED)) + if (STR_IN_SET(error_id, SD_VARLINK_ERROR_METHOD_NOT_FOUND, + SD_VARLINK_ERROR_METHOD_NOT_IMPLEMENTED)) + log_debug("Ignoring Varlink endpoint '%s': %s", li->name, error_id); + else if (streq(error_id, SD_VARLINK_ERROR_DISCONNECTED)) log_warning("Varlink connection to '%s' disconnected prematurely, ignoring.", li->name); else if (streq(error_id, SD_VARLINK_ERROR_TIMEOUT)) log_warning("Varlink connection to '%s' timed out, ignoring.", li->name); From f60d5985e5c4c69ffba4b137c23f44d893aa5656 Mon Sep 17 00:00:00 2001 From: Yaping Li <202858510+YapingLi04@users.noreply.github.com> Date: Mon, 2 Mar 2026 22:41:43 -0800 Subject: [PATCH 0671/1296] report: implement facts interface --- src/core/meson.build | 1 + src/core/varlink-facts.c | 142 +++++++++++++++ src/core/varlink-facts.h | 9 + src/core/varlink.c | 17 +- src/report/report.c | 235 +++++++++++++++++++++++-- src/shared/facts.c | 150 ++++++++++++++++ src/shared/facts.h | 31 ++++ src/shared/meson.build | 2 + src/shared/varlink-io.systemd.Facts.c | 36 ++++ src/shared/varlink-io.systemd.Facts.h | 6 + test/units/TEST-74-AUX-UTILS.report.sh | 20 +++ 11 files changed, 636 insertions(+), 13 deletions(-) create mode 100644 src/core/varlink-facts.c create mode 100644 src/core/varlink-facts.h create mode 100644 src/shared/facts.c create mode 100644 src/shared/facts.h create mode 100644 src/shared/varlink-io.systemd.Facts.c create mode 100644 src/shared/varlink-io.systemd.Facts.h diff --git a/src/core/meson.build b/src/core/meson.build index 391dc45a6b294..353854dafd9a8 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -70,6 +70,7 @@ libcore_sources = files( 'varlink-execute.c', 'varlink-manager.c', 'varlink-metrics.c', + 'varlink-facts.c', 'varlink-unit.c', ) diff --git a/src/core/varlink-facts.c b/src/core/varlink-facts.c new file mode 100644 index 0000000000000..695080c819c60 --- /dev/null +++ b/src/core/varlink-facts.c @@ -0,0 +1,142 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-id128.h" +#include "sd-json.h" +#include "sd-varlink.h" + +#include "alloc-util.h" +#include "architecture.h" +#include "facts.h" +#include "hostname-setup.h" +#include "varlink-facts.h" +#include "virt.h" + +static int architecture_generate(FactFamilyContext *context, void *userdata) { + assert(context); + + return fact_build_send_string( + context, + /* object= */ NULL, + architecture_to_string(uname_architecture())); +} + +static int boot_id_generate(FactFamilyContext *context, void *userdata) { + sd_id128_t id; + int r; + + assert(context); + + r = sd_id128_get_boot(&id); + if (r < 0) + return r; + + return fact_build_send_string( + context, + /* object= */ NULL, + SD_ID128_TO_STRING(id)); +} + +static int hostname_generate(FactFamilyContext *context, void *userdata) { + _cleanup_free_ char *hostname = NULL; + int r; + + assert(context); + + r = gethostname_full(GET_HOSTNAME_ALLOW_LOCALHOST | GET_HOSTNAME_FALLBACK_DEFAULT, &hostname); + if (r < 0) + return r; + + return fact_build_send_string( + context, + /* object= */ NULL, + hostname); +} + +static int kernel_version_generate(FactFamilyContext *context, void *userdata) { + struct utsname u; + + assert(context); + + assert_se(uname(&u) >= 0); + + return fact_build_send_string( + context, + /* object= */ NULL, + u.release); +} + +static int machine_id_generate(FactFamilyContext *context, void *userdata) { + sd_id128_t id; + int r; + + assert(context); + + r = sd_id128_get_machine(&id); + if (r < 0) + return r; + + return fact_build_send_string( + context, + /* object= */ NULL, + SD_ID128_TO_STRING(id)); +} + +static int virtualization_generate(FactFamilyContext *context, void *userdata) { + Virtualization v; + + assert(context); + + v = detect_virtualization(); + if (v < 0) + return v; + + return fact_build_send_string( + context, + /* object= */ NULL, + virtualization_to_string(v)); +} + +const FactFamily fact_family_table[] = { + /* Keep facts ordered alphabetically */ + { + .name = FACT_IO_SYSTEMD_MANAGER_PREFIX "Architecture", + .description = "CPU architecture", + .generate = architecture_generate, + }, + { + .name = FACT_IO_SYSTEMD_MANAGER_PREFIX "BootID", + .description = "Current boot ID", + .generate = boot_id_generate, + }, + { + .name = FACT_IO_SYSTEMD_MANAGER_PREFIX "Hostname", + .description = "System hostname", + .generate = hostname_generate, + }, + { + .name = FACT_IO_SYSTEMD_MANAGER_PREFIX "KernelVersion", + .description = "Kernel version", + .generate = kernel_version_generate, + }, + { + .name = FACT_IO_SYSTEMD_MANAGER_PREFIX "MachineID", + .description = "Machine ID", + .generate = machine_id_generate, + }, + { + .name = FACT_IO_SYSTEMD_MANAGER_PREFIX "Virtualization", + .description = "Virtualization type", + .generate = virtualization_generate, + }, + {} +}; + +int vl_method_describe_facts(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return facts_method_describe(fact_family_table, link, parameters, flags, userdata); +} + +int vl_method_list_facts(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return facts_method_list(fact_family_table, link, parameters, flags, userdata); +} diff --git a/src/core/varlink-facts.h b/src/core/varlink-facts.h new file mode 100644 index 0000000000000..5c303c84558ad --- /dev/null +++ b/src/core/varlink-facts.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" + +#define FACT_IO_SYSTEMD_MANAGER_PREFIX "io.systemd.Manager." + +int vl_method_list_facts(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_describe_facts(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/core/varlink.c b/src/core/varlink.c index 533d1061b8eb7..e4e598a3dae46 100644 --- a/src/core/varlink.c +++ b/src/core/varlink.c @@ -5,6 +5,7 @@ #include "constants.h" #include "errno-util.h" #include "manager.h" +#include "facts.h" #include "metrics.h" #include "path-util.h" #include "pidref.h" @@ -12,6 +13,7 @@ #include "unit.h" #include "varlink.h" #include "varlink-dynamic-user.h" +#include "varlink-facts.h" #include "varlink-io.systemd.ManagedOOM.h" #include "varlink-io.systemd.Manager.h" #include "varlink-io.systemd.Unit.h" @@ -433,16 +435,29 @@ int manager_setup_varlink_server(Manager *m) { } int manager_setup_varlink_metrics_server(Manager *m) { + int r; + assert(m); sd_varlink_server_flags_t flags = SD_VARLINK_SERVER_INHERIT_USERDATA; if (MANAGER_IS_SYSTEM(m)) flags |= SD_VARLINK_SERVER_ACCOUNT_UID; - return metrics_setup_varlink_server(&m->metrics_varlink_server, flags, + r = metrics_setup_varlink_server(&m->metrics_varlink_server, flags, m->event, EVENT_PRIORITY_IPC, vl_method_list_metrics, vl_method_describe_metrics, m); + if (r < 0) + return r; + if (r > 0) { + /* Server newly created — also register facts interface on it */ + int q = facts_add_to_varlink_server(m->metrics_varlink_server, + vl_method_list_facts, vl_method_describe_facts); + if (q < 0) + return q; + } + + return r; } static int varlink_server_listen_many_idempotent_sentinel( diff --git a/src/report/report.c b/src/report/report.c index be7b89b821472..44ed7535334ff 100644 --- a/src/report/report.c +++ b/src/report/report.c @@ -41,6 +41,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_matches, strv_freep); typedef enum Action { ACTION_LIST, ACTION_DESCRIBE, + ACTION_LIST_FACTS, + ACTION_DESCRIBE_FACTS, _ACTION_MAX, _ACTION_INVALID = -EINVAL, } Action; @@ -213,7 +215,7 @@ static Verdict metrics_verdict(LinkInfo *li, sd_json_variant *metric) { return VERDICT_MATCH; } -static int metrics_on_query_reply( +static int on_query_reply( sd_varlink *link, sd_json_variant *parameters, const char *error_id, @@ -288,14 +290,20 @@ static int metrics_call(Context *context, const char *name, const char *path) { if (r < 0) return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); - r = sd_varlink_bind_reply(vl, metrics_on_query_reply); + r = sd_varlink_bind_reply(vl, on_query_reply); if (r < 0) return log_error_errno(r, "Failed to bind reply callback: %m"); - const char *method = context->action == ACTION_LIST ? "io.systemd.Metrics.List" : "io.systemd.Metrics.Describe"; - r = sd_varlink_observe(vl, - method, - /* parameters= */ NULL); + const char *method; + switch (context->action) { + case ACTION_LIST: method = "io.systemd.Metrics.List"; break; + case ACTION_DESCRIBE: method = "io.systemd.Metrics.Describe"; break; + case ACTION_LIST_FACTS: method = "io.systemd.Facts.List"; break; + case ACTION_DESCRIBE_FACTS: method = "io.systemd.Facts.Describe"; break; + default: assert_not_reached(); + } + + r = sd_varlink_observe(vl, method, /* parameters= */ NULL); if (r < 0) return log_error_errno(r, "Failed to issue %s() call: %m", method); @@ -308,7 +316,6 @@ static int metrics_call(Context *context, const char *name, const char *path) { .link = sd_varlink_ref(vl), .name = strdup(name), }; - if (!li->name) return log_oom(); @@ -426,6 +433,105 @@ static int metrics_output_describe(Context *context, Table **ret) { return 0; } +static int facts_output_list(Context *context, Table **ret) { + int r; + + assert(context); + + _cleanup_(table_unrefp) Table *table = table_new("family", "object", "value"); + if (!table) + return log_oom(); + + table_set_ersatz_string(table, TABLE_ERSATZ_DASH); + table_set_sort(table, (size_t) 0, (size_t) 1, (size_t) 2); + + FOREACH_ARRAY(m, context->metrics, context->n_metrics) { + struct { + const char *name; + const char *object; + sd_json_variant *value; + } d = {}; + + static const sd_json_dispatch_field dispatch_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(d, name), SD_JSON_MANDATORY }, + { "object", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(d, object), 0 }, + { "value", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_variant_noref, voffsetof(d, value), SD_JSON_MANDATORY }, + {} + }; + + r = sd_json_dispatch(*m, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &d); + if (r < 0) { + _cleanup_free_ char *t = NULL; + int k = sd_json_variant_format(*m, /* flags= */ 0, &t); + if (k < 0) + return log_error_errno(k, "Failed to format JSON: %m"); + + log_warning_errno(r, "Cannot parse fact, skipping: %s", t); + continue; + } + + r = table_add_many( + table, + TABLE_STRING, d.name, + TABLE_STRING, d.object, + TABLE_JSON, d.value, + TABLE_SET_WEIGHT, 50U); + if (r < 0) + return table_log_add_error(r); + } + + *ret = TAKE_PTR(table); + return 0; +} + +static int facts_output_describe(Context *context, Table **ret) { + int r; + + assert(context); + + _cleanup_(table_unrefp) Table *table = table_new("family", "description"); + if (!table) + return log_oom(); + + table_set_ersatz_string(table, TABLE_ERSATZ_DASH); + table_set_sort(table, (size_t) 0, (size_t) 1); + + FOREACH_ARRAY(m, context->metrics, context->n_metrics) { + struct { + const char *name; + const char *description; + } d = {}; + + static const sd_json_dispatch_field dispatch_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(d, name), SD_JSON_MANDATORY }, + { "description", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(d, description), 0 }, + {} + }; + + r = sd_json_dispatch(*m, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &d); + if (r < 0) { + _cleanup_free_ char *t = NULL; + int k = sd_json_variant_format(*m, /* flags= */ 0, &t); + if (k < 0) + return log_error_errno(k, "Failed to format JSON: %m"); + + log_warning_errno(r, "Cannot parse fact description, skipping: %s", t); + continue; + } + + r = table_add_many( + table, + TABLE_STRING, d.name, + TABLE_STRING, d.description, + TABLE_SET_WEIGHT, 50U); + if (r < 0) + return table_log_add_error(r); + } + + *ret = TAKE_PTR(table); + return 0; +} + static int metrics_output(Context *context) { int r; @@ -444,8 +550,12 @@ static int metrics_output(Context *context) { return log_error_errno(r, "Failed to write JSON: %m"); } - if (context->n_metrics == 0 && arg_legend) - log_info("No metrics collected."); + if (context->n_metrics == 0 && arg_legend) { + if (IN_SET(context->action, ACTION_LIST_FACTS, ACTION_DESCRIBE_FACTS)) + log_info("No facts collected."); + else + log_info("No metrics collected."); + } return 0; } @@ -461,6 +571,14 @@ static int metrics_output(Context *context) { r = metrics_output_describe(context, &table); break; + case ACTION_LIST_FACTS: + r = facts_output_list(context, &table); + break; + + case ACTION_DESCRIBE_FACTS: + r = facts_output_describe(context, &table); + break; + default: assert_not_reached(); } @@ -474,10 +592,12 @@ static int metrics_output(Context *context) { } if (arg_legend && !sd_json_format_enabled(arg_json_format_flags)) { + bool is_facts = IN_SET(context->action, ACTION_LIST_FACTS, ACTION_DESCRIBE_FACTS); + if (table_isempty(table)) - printf("No metrics available.\n"); + printf("No %s available.\n", is_facts ? "facts" : "metrics"); else - printf("\n%zu metrics listed.\n", table_get_rows(table) - 1); + printf("\n%zu %s listed.\n", table_get_rows(table) - 1, is_facts ? "facts" : "metrics"); } return 0; @@ -659,6 +779,92 @@ static int verb_metrics(int argc, char *argv[], uintptr_t data, void *userdata) return 0; } +static int verb_facts(int argc, char *argv[], uintptr_t _data, void *userdata) { + Action action; + int r; + + assert(argc >= 1); + assert(argv); + + /* Enable JSON-SEQ mode here, since we'll dump a large series of JSON objects */ + arg_json_format_flags |= SD_JSON_FORMAT_SEQ; + + if (streq_ptr(argv[0], "facts")) + action = ACTION_LIST_FACTS; + else { + assert(streq_ptr(argv[0], "describe-facts")); + action = ACTION_DESCRIBE_FACTS; + } + + r = parse_metrics_matches(argv + 1); + if (r < 0) + return r; + + _cleanup_(context_done) Context context = { + .action = action, + }; + size_t n_skipped_sources = 0; + + _cleanup_free_ DirectoryEntries *de = NULL; + _cleanup_free_ char *sources_path = NULL; + r = readdir_sources(&sources_path, &de); + if (r < 0) + return r; + if (r > 0) { + r = sd_event_default(&context.event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); + + r = sd_event_set_signal_exit(context.event, true); + if (r < 0) + return log_error_errno(r, "Failed to enable exit on SIGINT/SIGTERM: %m"); + + FOREACH_ARRAY(i, de->entries, de->n_entries) { + struct dirent *d = *i; + + if (set_size(context.link_infos) >= METRICS_LINKS_MAX) { + n_skipped_sources++; + break; + } + + _cleanup_free_ char *p = path_join(sources_path, d->d_name); + if (!p) + return log_oom(); + + (void) metrics_call(&context, d->d_name, p); + } + } + + if (set_isempty(context.link_infos)) { + if (arg_legend) + log_info("No facts sources found."); + } else { + assert(context.event); + + r = sd_event_loop(context.event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + r = metrics_output(&context); + if (r < 0) + return r; + } + + if (n_skipped_sources > 0) + return log_warning_errno(SYNTHETIC_ERRNO(EUCLEAN), + "Too many facts sources, only %u sources contacted, %zu sources skipped.", + set_size(context.link_infos), n_skipped_sources); + if (context.n_invalid_metrics > 0) + return log_warning_errno(SYNTHETIC_ERRNO(EUCLEAN), + "%zu facts are not valid.", + context.n_invalid_metrics); + if (context.n_skipped_metrics > 0) + return log_warning_errno(SYNTHETIC_ERRNO(EUCLEAN), + "Too many facts, only %zu facts collected, %zu facts skipped.", + context.n_metrics, context.n_skipped_metrics); + return 0; +} + static int verb_list_sources(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -723,11 +929,14 @@ static int help(void) { return log_oom(); printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n%5$sAcquire metrics from local sources.%6$s\n" + "\n%5$sAcquire metrics and facts from local sources.%6$s\n" "\n%3$sCommands:%4$s\n" " metrics [MATCH...] Acquire list of metrics and their values\n" " describe-metrics [MATCH...]\n" " Describe available metrics\n" + " facts [MATCH...] Acquire list of facts and their values\n" + " describe-facts [MATCH...]\n" + " Describe available facts\n" " list-sources Show list of known metrics sources\n" "\n%3$sOptions:%4$s\n" " -h --help Show this help\n" @@ -831,6 +1040,8 @@ static int report_main(int argc, char *argv[]) { { "help", VERB_ANY, 1, 0, verb_help }, { "metrics", VERB_ANY, VERB_ANY, 0, verb_metrics, ACTION_LIST }, { "describe-metrics", VERB_ANY, VERB_ANY, 0, verb_metrics, ACTION_DESCRIBE }, + { "facts", VERB_ANY, VERB_ANY, 0, verb_facts, ACTION_LIST_FACTS }, + { "describe-facts", VERB_ANY, VERB_ANY, 0, verb_facts, ACTION_DESCRIBE_FACTS }, { "list-sources", VERB_ANY, 1, 0, verb_list_sources }, {} }; diff --git a/src/shared/facts.c b/src/shared/facts.c new file mode 100644 index 0000000000000..5a6882c6ac80a --- /dev/null +++ b/src/shared/facts.c @@ -0,0 +1,150 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "facts.h" +#include "json-util.h" +#include "log.h" +#include "varlink-io.systemd.Facts.h" +#include "varlink-util.h" + +int facts_add_to_varlink_server( + sd_varlink_server *server, + sd_varlink_method_t vl_method_list_cb, + sd_varlink_method_t vl_method_describe_cb) { + + int r; + + assert(server); + assert(vl_method_list_cb); + assert(vl_method_describe_cb); + + r = sd_varlink_server_add_interface(server, &vl_interface_io_systemd_Facts); + if (r < 0) + return log_debug_errno(r, "Failed to add varlink facts interface to varlink server: %m"); + + r = sd_varlink_server_bind_method_many( + server, + "io.systemd.Facts.List", vl_method_list_cb, + "io.systemd.Facts.Describe", vl_method_describe_cb); + if (r < 0) + return log_debug_errno(r, "Failed to register varlink facts methods: %m"); + + return 0; +} + +static int fact_family_build_json(const FactFamily *ff, sd_json_variant **ret) { + assert(ff); + + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_STRING("name", ff->name), + SD_JSON_BUILD_PAIR_STRING("description", ff->description)); +} + +int facts_method_describe( + const FactFamily fact_family_table[], + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + int r; + + assert(fact_family_table); + assert(link); + assert(parameters); + assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)); + + r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL); + if (r != 0) + return r; + + r = sd_varlink_set_sentinel(link, "io.systemd.Facts.NoSuchFact"); + if (r < 0) + return r; + + for (const FactFamily *ff = fact_family_table; ff && ff->name; ff++) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + r = fact_family_build_json(ff, &v); + if (r < 0) + return log_debug_errno(r, "Failed to describe fact family '%s': %m", ff->name); + + r = sd_varlink_reply(link, v); + if (r < 0) + return log_debug_errno(r, "Failed to send varlink reply: %m"); + } + + return 0; +} + +int facts_method_list( + const FactFamily fact_family_table[], + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + int r; + + assert(fact_family_table); + assert(link); + assert(parameters); + assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)); + + r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL); + if (r != 0) + return r; + + r = sd_varlink_set_sentinel(link, "io.systemd.Facts.NoSuchFact"); + if (r < 0) + return r; + + FactFamilyContext ctx = { .link = link }; + for (const FactFamily *ff = fact_family_table; ff && ff->name; ff++) { + assert(ff->generate); + + ctx.fact_family = ff; + r = ff->generate(&ctx, userdata); + if (r < 0) + return log_debug_errno( + r, "Failed to list facts for fact family '%s': %m", ff->name); + } + + return 0; +} + +static int fact_build_send(FactFamilyContext *context, const char *object, sd_json_variant *value) { + assert(context); + assert(value); + assert(context->link); + assert(context->fact_family); + + return sd_varlink_replybo(context->link, + SD_JSON_BUILD_PAIR_STRING("name", context->fact_family->name), + JSON_BUILD_PAIR_STRING_NON_EMPTY("object", object), + SD_JSON_BUILD_PAIR("value", SD_JSON_BUILD_VARIANT(value))); +} + +int fact_build_send_string(FactFamilyContext *context, const char *object, const char *value) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + int r; + + assert(value); + + r = sd_json_variant_new_string(&v, value); + if (r < 0) + return log_debug_errno(r, "Failed to allocate JSON string: %m"); + + return fact_build_send(context, object, v); +} + +int fact_build_send_unsigned(FactFamilyContext *context, const char *object, uint64_t value) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + int r; + + r = sd_json_variant_new_unsigned(&v, value); + if (r < 0) + return log_debug_errno(r, "Failed to allocate JSON unsigned: %m"); + + return fact_build_send(context, object, v); +} diff --git a/src/shared/facts.h b/src/shared/facts.h new file mode 100644 index 0000000000000..8a8a94cd91f77 --- /dev/null +++ b/src/shared/facts.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +typedef struct FactFamily FactFamily; + +typedef struct FactFamilyContext { + const FactFamily *fact_family; + sd_varlink *link; +} FactFamilyContext; + +typedef int (*fact_family_generate_func_t)(FactFamilyContext *ffc, void *userdata); + +typedef struct FactFamily { + const char *name; + const char *description; + fact_family_generate_func_t generate; +} FactFamily; + +/* Add io.systemd.Facts interface + methods to an existing varlink server */ +int facts_add_to_varlink_server( + sd_varlink_server *server, + sd_varlink_method_t vl_method_list_cb, + sd_varlink_method_t vl_method_describe_cb); + +int facts_method_describe(const FactFamily fact_family_table[], sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int facts_method_list(const FactFamily fact_family_table[], sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); + +int fact_build_send_string(FactFamilyContext *context, const char *object, const char *value); +int fact_build_send_unsigned(FactFamilyContext *context, const char *object, uint64_t value); diff --git a/src/shared/meson.build b/src/shared/meson.build index cdbe763d0137d..e2c1501adb86b 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -77,6 +77,7 @@ shared_sources = files( 'exit-status.c', 'extension-util.c', 'factory-reset.c', + 'facts.c', 'fdset.c', 'fido2-util.c', 'find-esp.c', @@ -205,6 +206,7 @@ shared_sources = files( 'varlink-io.systemd.BootControl.c', 'varlink-io.systemd.Credentials.c', 'varlink-io.systemd.FactoryReset.c', + 'varlink-io.systemd.Facts.c', 'varlink-io.systemd.Hostname.c', 'varlink-io.systemd.Import.c', 'varlink-io.systemd.InstanceMetadata.c', diff --git a/src/shared/varlink-io.systemd.Facts.c b/src/shared/varlink-io.systemd.Facts.c new file mode 100644 index 0000000000000..c7cf10290aa63 --- /dev/null +++ b/src/shared/varlink-io.systemd.Facts.c @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-varlink-idl.h" + +#include "varlink-io.systemd.Facts.h" + +static SD_VARLINK_DEFINE_ERROR(NoSuchFact); + +static SD_VARLINK_DEFINE_METHOD_FULL( + List, + SD_VARLINK_REQUIRES_MORE, + SD_VARLINK_FIELD_COMMENT("Fact family name, e.g. io.systemd.Manager.Hostname"), + SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Fact object name"), + SD_VARLINK_DEFINE_OUTPUT(object, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Fact value"), + SD_VARLINK_DEFINE_OUTPUT(value, SD_VARLINK_ANY, 0)); + +static SD_VARLINK_DEFINE_METHOD_FULL( + Describe, + SD_VARLINK_REQUIRES_MORE, + SD_VARLINK_FIELD_COMMENT("Fact family name"), + SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Fact family description"), + SD_VARLINK_DEFINE_OUTPUT(description, SD_VARLINK_STRING, 0)); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_Facts, + "io.systemd.Facts", + SD_VARLINK_INTERFACE_COMMENT("Facts APIs"), + SD_VARLINK_SYMBOL_COMMENT("Method to get a list of facts and their values"), + &vl_method_List, + SD_VARLINK_SYMBOL_COMMENT("Method to get the fact families"), + &vl_method_Describe, + SD_VARLINK_SYMBOL_COMMENT("No such fact found"), + &vl_error_NoSuchFact); diff --git a/src/shared/varlink-io.systemd.Facts.h b/src/shared/varlink-io.systemd.Facts.h new file mode 100644 index 0000000000000..ce07de32fb9df --- /dev/null +++ b/src/shared/varlink-io.systemd.Facts.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_Facts; diff --git a/test/units/TEST-74-AUX-UTILS.report.sh b/test/units/TEST-74-AUX-UTILS.report.sh index 876c5b2cad49e..4edb67a315063 100755 --- a/test/units/TEST-74-AUX-UTILS.report.sh +++ b/test/units/TEST-74-AUX-UTILS.report.sh @@ -35,3 +35,23 @@ varlinkctl info /run/systemd/report/io.systemd.Network varlinkctl list-methods /run/systemd/report/io.systemd.Network varlinkctl --more call /run/systemd/report/io.systemd.Network io.systemd.Metrics.List {} varlinkctl --more call /run/systemd/report/io.systemd.Network io.systemd.Metrics.Describe {} + +# Test facts verbs +"$REPORT" facts +"$REPORT" facts -j +"$REPORT" facts --no-legend +"$REPORT" describe-facts +"$REPORT" describe-facts -j +"$REPORT" describe-facts --no-legend + +# Test facts with match filters +"$REPORT" facts io +"$REPORT" facts io.systemd piff +"$REPORT" facts piff +"$REPORT" describe-facts io +"$REPORT" describe-facts io.systemd piff +"$REPORT" describe-facts piff + +# Test facts via direct Varlink call on existing socket +varlinkctl --more call /run/systemd/report/io.systemd.Manager io.systemd.Facts.List {} +varlinkctl --more call /run/systemd/report/io.systemd.Manager io.systemd.Facts.Describe {} From 3220a1c57fc4bdab6ccd40dab20d14cdbc949ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 17:03:47 +0100 Subject: [PATCH 0672/1296] report: rename variables/fields, use string table Noop refactoring to make naming more consistent. --- src/report/report.c | 93 ++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 47 deletions(-) diff --git a/src/report/report.c b/src/report/report.c index 44ed7535334ff..108b24e4701e8 100644 --- a/src/report/report.c +++ b/src/report/report.c @@ -20,14 +20,15 @@ #include "runtime-scope.h" #include "set.h" #include "sort-util.h" +#include "string-table.h" #include "string-util.h" #include "strv.h" #include "time-util.h" #include "varlink-idl-util.h" #include "verbs.h" -#define METRICS_MAX 1024U -#define METRICS_LINKS_MAX 128U +#define METRICS_OR_FACTS_MAX 1024U +#define METRICS_OR_FACTS_LINKS_MAX 128U #define TIMEOUT_USEC (30 * USEC_PER_SEC) /* 30 seconds */ static PagerFlags arg_pager_flags = 0; @@ -39,19 +40,21 @@ static char **arg_matches = NULL; STATIC_DESTRUCTOR_REGISTER(arg_matches, strv_freep); typedef enum Action { - ACTION_LIST, - ACTION_DESCRIBE, + ACTION_LIST_METRICS, + ACTION_DESCRIBE_METRICS, ACTION_LIST_FACTS, ACTION_DESCRIBE_FACTS, _ACTION_MAX, _ACTION_INVALID = -EINVAL, } Action; +/* The structure for collected "metrics" or "facts". The fields + * are prefixed with just "metrics" for brevity. */ typedef struct Context { Action action; sd_event *event; Set *link_infos; - sd_json_variant **metrics; /* Collected metrics for sorting */ + sd_json_variant **metrics; /* Collected metrics or facts for sorting */ size_t n_metrics, n_skipped_metrics, n_invalid_metrics; } Context; @@ -85,6 +88,15 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( void, trivial_hash_func, trivial_compare_func, LinkInfo, link_info_free); +static const char* const action_method_table[] = { + [ACTION_LIST_METRICS] = "io.systemd.Metrics.List", + [ACTION_DESCRIBE_METRICS] = "io.systemd.Metrics.Describe", + [ACTION_LIST_FACTS] = "io.systemd.Facts.List", + [ACTION_DESCRIBE_FACTS] = "io.systemd.Facts.Describe", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(action_method, Action); + static int metric_compare(sd_json_variant *const *a, sd_json_variant *const *b) { const char *name_a, *name_b, *object_a, *object_b; sd_json_variant *fields_a, *fields_b; @@ -241,7 +253,7 @@ static int on_query_reply( goto finish; } - if (context->n_metrics >= METRICS_MAX) { + if (context->n_metrics >= METRICS_OR_FACTS_MAX) { context->n_skipped_metrics++; goto finish; } @@ -271,7 +283,7 @@ static int on_query_reply( return 0; } -static int metrics_call(Context *context, const char *name, const char *path) { +static int call_collect(Context *context, const char *name, const char *path) { _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; int r; @@ -294,14 +306,7 @@ static int metrics_call(Context *context, const char *name, const char *path) { if (r < 0) return log_error_errno(r, "Failed to bind reply callback: %m"); - const char *method; - switch (context->action) { - case ACTION_LIST: method = "io.systemd.Metrics.List"; break; - case ACTION_DESCRIBE: method = "io.systemd.Metrics.Describe"; break; - case ACTION_LIST_FACTS: method = "io.systemd.Facts.List"; break; - case ACTION_DESCRIBE_FACTS: method = "io.systemd.Facts.Describe"; break; - default: assert_not_reached(); - } + const char *method = ASSERT_PTR(action_method_to_string(context->action)); r = sd_varlink_observe(vl, method, /* parameters= */ NULL); if (r < 0) @@ -328,7 +333,7 @@ static int metrics_call(Context *context, const char *name, const char *path) { return 0; } -static int metrics_output_list(Context *context, Table **ret) { +static int output_collected_list(Context *context, Table **ret) { int r; assert(context); @@ -382,7 +387,7 @@ static int metrics_output_list(Context *context, Table **ret) { return 0; } -static int metrics_output_describe(Context *context, Table **ret) { +static int output_collected_describe(Context *context, Table **ret) { int r; assert(context); @@ -532,7 +537,7 @@ static int facts_output_describe(Context *context, Table **ret) { return 0; } -static int metrics_output(Context *context) { +static int output_collected(Context *context) { int r; assert(context); @@ -563,12 +568,12 @@ static int metrics_output(Context *context) { _cleanup_(table_unrefp) Table *table = NULL; switch(context->action) { - case ACTION_LIST: - r = metrics_output_list(context, &table); + case ACTION_LIST_METRICS: + r = output_collected_list(context, &table); break; - case ACTION_DESCRIBE: - r = metrics_output_describe(context, &table); + case ACTION_DESCRIBE_METRICS: + r = output_collected_describe(context, &table); break; case ACTION_LIST_FACTS: @@ -592,12 +597,12 @@ static int metrics_output(Context *context) { } if (arg_legend && !sd_json_format_enabled(arg_json_format_flags)) { - bool is_facts = IN_SET(context->action, ACTION_LIST_FACTS, ACTION_DESCRIBE_FACTS); + const char *type = IN_SET(context->action, ACTION_LIST_FACTS, ACTION_DESCRIBE_FACTS) ? "facts" : "metrics"; if (table_isempty(table)) - printf("No %s available.\n", is_facts ? "facts" : "metrics"); + printf("No %s available.\n", type); else - printf("\n%zu %s listed.\n", table_get_rows(table) - 1, is_facts ? "facts" : "metrics"); + printf("\n%zu %s listed.\n", table_get_rows(table) - 1, type); } return 0; @@ -705,7 +710,7 @@ static int verb_metrics(int argc, char *argv[], uintptr_t data, void *userdata) assert(argc >= 1); assert(argv); - assert(IN_SET(action, ACTION_LIST, ACTION_DESCRIBE)); + assert(IN_SET(action, ACTION_LIST_METRICS, ACTION_DESCRIBE_METRICS)); /* Enable JSON-SEQ mode here, since we'll dump a large series of JSON objects */ arg_json_format_flags |= SD_JSON_FORMAT_SEQ; @@ -736,7 +741,7 @@ static int verb_metrics(int argc, char *argv[], uintptr_t data, void *userdata) FOREACH_ARRAY(i, de->entries, de->n_entries) { struct dirent *d = *i; - if (set_size(context.link_infos) >= METRICS_LINKS_MAX) { + if (set_size(context.link_infos) >= METRICS_OR_FACTS_LINKS_MAX) { n_skipped_sources++; break; } @@ -745,7 +750,7 @@ static int verb_metrics(int argc, char *argv[], uintptr_t data, void *userdata) if (!p) return log_oom(); - (void) metrics_call(&context, d->d_name, p); + (void) call_collect(&context, d->d_name, p); } } @@ -759,7 +764,7 @@ static int verb_metrics(int argc, char *argv[], uintptr_t data, void *userdata) if (r < 0) return log_error_errno(r, "Failed to run event loop: %m"); - r = metrics_output(&context); + r = output_collected(&context); if (r < 0) return r; } @@ -779,23 +784,17 @@ static int verb_metrics(int argc, char *argv[], uintptr_t data, void *userdata) return 0; } -static int verb_facts(int argc, char *argv[], uintptr_t _data, void *userdata) { - Action action; +static int verb_facts(int argc, char *argv[], uintptr_t data, void *userdata) { + Action action = data; int r; assert(argc >= 1); assert(argv); + assert(IN_SET(action, ACTION_LIST_FACTS, ACTION_DESCRIBE_FACTS)); /* Enable JSON-SEQ mode here, since we'll dump a large series of JSON objects */ arg_json_format_flags |= SD_JSON_FORMAT_SEQ; - if (streq_ptr(argv[0], "facts")) - action = ACTION_LIST_FACTS; - else { - assert(streq_ptr(argv[0], "describe-facts")); - action = ACTION_DESCRIBE_FACTS; - } - r = parse_metrics_matches(argv + 1); if (r < 0) return r; @@ -822,7 +821,7 @@ static int verb_facts(int argc, char *argv[], uintptr_t _data, void *userdata) { FOREACH_ARRAY(i, de->entries, de->n_entries) { struct dirent *d = *i; - if (set_size(context.link_infos) >= METRICS_LINKS_MAX) { + if (set_size(context.link_infos) >= METRICS_OR_FACTS_LINKS_MAX) { n_skipped_sources++; break; } @@ -831,7 +830,7 @@ static int verb_facts(int argc, char *argv[], uintptr_t _data, void *userdata) { if (!p) return log_oom(); - (void) metrics_call(&context, d->d_name, p); + (void) call_collect(&context, d->d_name, p); } } @@ -845,7 +844,7 @@ static int verb_facts(int argc, char *argv[], uintptr_t _data, void *userdata) { if (r < 0) return log_error_errno(r, "Failed to run event loop: %m"); - r = metrics_output(&context); + r = output_collected(&context); if (r < 0) return r; } @@ -1037,12 +1036,12 @@ static int parse_argv(int argc, char *argv[]) { static int report_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "help", VERB_ANY, 1, 0, verb_help }, - { "metrics", VERB_ANY, VERB_ANY, 0, verb_metrics, ACTION_LIST }, - { "describe-metrics", VERB_ANY, VERB_ANY, 0, verb_metrics, ACTION_DESCRIBE }, - { "facts", VERB_ANY, VERB_ANY, 0, verb_facts, ACTION_LIST_FACTS }, - { "describe-facts", VERB_ANY, VERB_ANY, 0, verb_facts, ACTION_DESCRIBE_FACTS }, - { "list-sources", VERB_ANY, 1, 0, verb_list_sources }, + { "help", VERB_ANY, 1, 0, verb_help }, + { "metrics", VERB_ANY, VERB_ANY, 0, verb_metrics, ACTION_LIST_METRICS }, + { "describe-metrics", VERB_ANY, VERB_ANY, 0, verb_metrics, ACTION_DESCRIBE_METRICS }, + { "facts", VERB_ANY, VERB_ANY, 0, verb_facts, ACTION_LIST_FACTS }, + { "describe-facts", VERB_ANY, VERB_ANY, 0, verb_facts, ACTION_DESCRIBE_FACTS }, + { "list-sources", VERB_ANY, 1, 0, verb_list_sources }, {} }; From c26f432729b4715e46fb047badaeaff0d86a7a44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 24 Mar 2026 16:26:31 +0100 Subject: [PATCH 0673/1296] shared/options: allow output ret_arg to be omitted Sometimes we have a parser which would never use the argument. --- src/shared/options.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/shared/options.c b/src/shared/options.c index c5118f33426c3..853df0d38d2f1 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -72,8 +72,6 @@ int option_parse( const Option **ret_option, const char **ret_arg) { - assert(ret_arg); - /* Check and initialize */ if (state->optind == 0) { if (state->argc < 1 || strv_isempty(state->argv)) @@ -226,7 +224,13 @@ int option_parse( if (ret_option) /* Return the matched Option structure to allow the caller to "know" what was matched */ *ret_option = option; - *ret_arg = optval; + + if (ret_arg) + *ret_arg = optval; + else + /* It's fine to omit ret_arg, but only if no options return a value. */ + assert(!optval); + return option->id; } From bedd902f9a842ec8bd6d1bb8f75db30c24fe8bcc Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 1 Apr 2026 15:46:20 +0200 Subject: [PATCH 0674/1296] ci: Delay instructions to read pr-context.json until 2nd phase The main agent doesn't need to read pr-context.json until all reviews have finished. This should prevent it from passing unnecessary data from pr-context.json in the prompt to its subagents, which can just read that file themselves when needed. --- .github/workflows/claude-review.yml | 57 ++++++++++++++++------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index eea4a5ed33756..63bcbc6b5da45 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -256,17 +256,7 @@ jobs: ## Phase 1: Review commits - Read `pr-context.json` from the repository root. `pr-context.json` contains - PR metadata from the GitHub API. Rules for its `review_comments` field: - - Only re-check your own comments (user.login == "github-actions[bot]" and - body starts with "Claude: "). - - Items checked off in the `tracking_comment` (`- [x]`) are resolved. - - You will need the `id` fields of your own unresolved comments in Phase 2 - to populate the `resolve` array. - - If `tracking_comment` is non-null, use it as the basis for your summary - in Phase 2. - - Then, list the directories in `worktrees/` — there is one per commit. Each + List the directories in `worktrees/` — there is one per commit. Each worktree at `worktrees//` contains the full source tree checked out at that commit, plus `commit.patch` (the diff) and `commit-message.txt` (the commit message). Spawn one @@ -278,7 +268,8 @@ jobs: security implications. Each subagent prompt must include: - - Instructions to read `pr-context.json` in the repository root for context. + - Instructions to read `pr-context.json` in the repository root for additional + context. - Instructions to read `review-schema.json` in the repository root and return a JSON array matching the `comments` items schema from that file. - The worktree path. @@ -289,22 +280,36 @@ jobs: ## Phase 2: Collect, deduplicate, and summarize - After all reviews (yours and any subagents') are done: + After all reviews are done, read `pr-context.json` from the repository root. + It contains PR metadata from the GitHub API. Rules for its `review_comments` + field: + - Only look at your own comments (user.login == "github-actions[bot]" and + body starts with "Claude: "). Ignore all other comments. + - Items checked off in the `tracking_comment` (`- [x]`) are resolved. + - You will need the `id` fields of your own unresolved comments to + populate the `resolve` array. + - If `tracking_comment` is non-null, use it as the basis for your summary. + + Then: 1. Collect all issues. Merge duplicates (same file, lines within 3 of each other, same problem). 2. Drop low-confidence findings. - 3. Check the existing inline review comments fetched in Phase 1. Do NOT include a - comment if one already exists on the same file and line about the same problem. - Also check for author replies that dismiss or reject a previous comment — do NOT - re-raise an issue the PR author has already responded to disagreeing with. - Populate the `resolve` array with the REST API `id` (integer) of existing - review comments whose threads should be resolved. A thread should be resolved if: - - The issue it raised has been addressed in the current PR (i.e. your review - no longer flags it), or - - The PR author (or another reviewer) left a reply disagreeing with or - dismissing the comment. - Only include the `id` of the **first** comment in each thread (the one that - started the conversation). Do not resolve threads for issues that are still - present and unaddressed. + 3. Check the existing inline review comments from `pr-context.json`. Do NOT + include a comment if one already exists on the same file about the same + problem, even if the exact line numbers differ (lines shift between + revisions). Also check for author replies that dismiss or reject a previous + comment — do NOT re-raise an issue the PR author has already responded to + disagreeing with. + Populate the `resolve` array with the REST API `id` (integer) of your own + review comment threads that should be resolved (user.login == "github-actions[bot]" + and body starts with "Claude: "). Do not resolve threads from human reviewers. + A thread should be resolved if: + - The issue it raised has been addressed in the current PR (i.e. your review + no longer flags it), or + - The PR author (or another reviewer) left a reply disagreeing with or + dismissing the comment. + Only include the `id` of the **first** comment in each thread (the one that + started the conversation). Do not resolve threads for issues that are still + present and unaddressed. 4. Write a `summary` field in markdown for a top-level tracking comment. **If no existing tracking comment was found (first run):** From 60b10fa0f1e91c3d4d017c10ad3941ac439ea195 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 1 Apr 2026 15:47:34 +0200 Subject: [PATCH 0675/1296] ci: base64 encode multiline strings in structured output Avoid claude trying to escape characters in the structured JSON by just having it base64 encode the multiline strings in the structured JSON. --- .github/workflows/claude-review.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 63bcbc6b5da45..2f9c76990d245 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -355,8 +355,11 @@ jobs: ## Review result Produce your review result as structured output. The fields are: - - `summary`: Markdown summary for the tracking comment. + - `summary`: The markdown summary for the tracking comment, **base64-encoded**. + Write the summary to a temporary file first, then encode it with + `base64 -w0 /tmp/summary.md` and put the resulting string in this field. - `comments`: Array of review comments (same schema as the reviewer output above). + The `body` field of each comment must also be **base64-encoded** the same way. - `resolve`: REST API IDs of review comment threads to resolve. PROMPT @@ -449,7 +452,7 @@ jobs: if (Array.isArray(review.resolve)) resolveIds = review.resolve; if (typeof review.summary === "string") - summary = review.summary; + summary = Buffer.from(review.summary, "base64").toString("utf-8"); } catch (e) { core.warning(`Failed to parse structured output: ${e.message}`); } @@ -480,7 +483,7 @@ jobs: ...(c.side != null && { side: c.side }), ...(c.start_line != null && { start_line: c.start_line }), ...(c.start_side != null && { start_side: c.start_side }), - body: `Claude: **${c.severity}**: ${c.body}`, + body: `Claude: **${c.severity}**: ${Buffer.from(c.body, "base64").toString("utf-8")}`, }); posted++; } catch (e) { From e1342e063b1a6d8b4d2dc39705e330da837cecff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 1 Apr 2026 15:47:30 +0200 Subject: [PATCH 0676/1296] test-efi-string: add more cases This excercises the patterns used in 45e4df9a331208d20ecb9f5ead8110eb50a5b86d. --- src/boot/test-efi-string.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/boot/test-efi-string.c b/src/boot/test-efi-string.c index 76a891ec1fa69..7633534dd4c07 100644 --- a/src/boot/test-efi-string.c +++ b/src/boot/test-efi-string.c @@ -475,6 +475,12 @@ TEST(efi_fnmatch) { TEST_FNMATCH_ONE_MAY_SKIP_LIBC("[a\\-z]", "b", false); TEST_FNMATCH_ONE("?a*b[.-0]c", "/a/b/c", true); TEST_FNMATCH_ONE("debian-*-*-*.*", "debian-jessie-2018-06-17-kernel-image-5.10.0-16-amd64.efi", true); + TEST_FNMATCH_ONE("console=*", "console=xxx", true); + TEST_FNMATCH_ONE("* console=*", "opt1 console=ttyS0 opt2", true); + TEST_FNMATCH_ONE("console=*", " console=xxx", false); + TEST_FNMATCH_ONE("* console=", "opt1 console=ttyS0 opt2", false); + TEST_FNMATCH_ONE("console=*", "netconsole=@/eth0,@10.0.0.1/", false); + TEST_FNMATCH_ONE("* console=*", "netconsole=@/eth0,@10.0.0.1/", false); /* These would take forever with a backtracking implementation. */ TEST_FNMATCH_ONE( From d0f482d34237523919fd35302f26b11302837138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 1 Apr 2026 15:59:48 +0200 Subject: [PATCH 0677/1296] basic/terminal-util: flush stray input when terminal query fails Follow-up for da69848791d2b32dfb90946264fd632ac1d5c7de. --- src/basic/terminal-util.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index ca871aac18cbe..7a240a4c7ab31 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -2604,16 +2604,24 @@ int terminal_get_size( if (try_csi18) { r = terminal_query_size_by_csi18(nonblock_input_fd, output_fd, ret_rows, ret_columns); - if (!IN_SET(r, -EOPNOTSUPP, -EINVAL) || !try_dsr) + if (r >= 0) return r; - /* CSI 18 query failed. Flush input before trying the DSR fallback — a late CSI 18 response - * may have landed in the input queue and would confuse the DSR response parser. */ + /* Query failed. Flush any outstanding input. */ (void) tcflush(nonblock_input_fd, TCIFLUSH); + + if (!IN_SET(r, -EOPNOTSUPP, -EINVAL)) + return r; } - if (try_dsr) + if (try_dsr) { r = terminal_query_size_by_dsr(nonblock_input_fd, output_fd, ret_rows, ret_columns); + if (r >= 0) + return r; + + /* Query failed. Flush any outstanding input. */ + (void) tcflush(nonblock_input_fd, TCIFLUSH); + } return r; } From b47139e489936916bbd5c5bf57ecf9d5e8cde618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 5 Mar 2026 11:49:11 +0100 Subject: [PATCH 0678/1296] report: move facts generator out of PID1 into a separate varlink service The collection of facts is entirely unprivileged and has very little to do with PID1. PID1 is privileged and single-threaded and a point of contention, so we shouldn't put things in PID1 that don't need to be there. A separate service can be enabled/disabled/started/stopped at will, is easy to sandbox, etc. If it turns out to be necessary to collect some facts through PID1 in the future, we can always add a smaller facts endpoint to PID1 again. --- src/core/meson.build | 1 - src/core/varlink.c | 17 +--- src/report/meson.build | 9 ++ src/report/report-basic-server.c | 97 +++++++++++++++++++ .../varlink-facts.c => report/report-basic.c} | 16 +-- .../varlink-facts.h => report/report-basic.h} | 4 +- src/shared/facts.c | 3 +- src/shared/options.h | 1 + src/shared/varlink-io.systemd.Facts.c | 5 +- test/units/TEST-74-AUX-UTILS.report.sh | 7 +- units/meson.build | 2 + units/systemd-report-basic.socket | 22 +++++ units/systemd-report-basic@.service.in | 13 +++ 13 files changed, 165 insertions(+), 32 deletions(-) create mode 100644 src/report/report-basic-server.c rename src/{core/varlink-facts.c => report/report-basic.c} (88%) rename src/{core/varlink-facts.h => report/report-basic.h} (78%) create mode 100644 units/systemd-report-basic.socket create mode 100644 units/systemd-report-basic@.service.in diff --git a/src/core/meson.build b/src/core/meson.build index 353854dafd9a8..391dc45a6b294 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -70,7 +70,6 @@ libcore_sources = files( 'varlink-execute.c', 'varlink-manager.c', 'varlink-metrics.c', - 'varlink-facts.c', 'varlink-unit.c', ) diff --git a/src/core/varlink.c b/src/core/varlink.c index e4e598a3dae46..533d1061b8eb7 100644 --- a/src/core/varlink.c +++ b/src/core/varlink.c @@ -5,7 +5,6 @@ #include "constants.h" #include "errno-util.h" #include "manager.h" -#include "facts.h" #include "metrics.h" #include "path-util.h" #include "pidref.h" @@ -13,7 +12,6 @@ #include "unit.h" #include "varlink.h" #include "varlink-dynamic-user.h" -#include "varlink-facts.h" #include "varlink-io.systemd.ManagedOOM.h" #include "varlink-io.systemd.Manager.h" #include "varlink-io.systemd.Unit.h" @@ -435,29 +433,16 @@ int manager_setup_varlink_server(Manager *m) { } int manager_setup_varlink_metrics_server(Manager *m) { - int r; - assert(m); sd_varlink_server_flags_t flags = SD_VARLINK_SERVER_INHERIT_USERDATA; if (MANAGER_IS_SYSTEM(m)) flags |= SD_VARLINK_SERVER_ACCOUNT_UID; - r = metrics_setup_varlink_server(&m->metrics_varlink_server, flags, + return metrics_setup_varlink_server(&m->metrics_varlink_server, flags, m->event, EVENT_PRIORITY_IPC, vl_method_list_metrics, vl_method_describe_metrics, m); - if (r < 0) - return r; - if (r > 0) { - /* Server newly created — also register facts interface on it */ - int q = facts_add_to_varlink_server(m->metrics_varlink_server, - vl_method_list_facts, vl_method_describe_facts); - if (q < 0) - return q; - } - - return r; } static int varlink_server_listen_many_idempotent_sentinel( diff --git a/src/report/meson.build b/src/report/meson.build index 2813f9d033b16..26d1bbfdc3e9c 100644 --- a/src/report/meson.build +++ b/src/report/meson.build @@ -6,4 +6,13 @@ executables += [ 'public' : true, 'sources' : files('report.c'), }, + + libexec_template + { + 'name' : 'systemd-report-basic', + 'public' : true, + 'sources' : files( + 'report-basic-server.c', + 'report-basic.c', + ), + }, ] diff --git a/src/report/report-basic-server.c b/src/report/report-basic-server.c new file mode 100644 index 0000000000000..32ec9b035600b --- /dev/null +++ b/src/report/report-basic-server.c @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-varlink.h" + +#include "ansi-color.h" +#include "build.h" +#include "facts.h" +#include "format-table.h" +#include "log.h" +#include "main-func.h" +#include "options.h" +#include "report-basic.h" +#include "varlink-util.h" + +static int vl_server(void) { + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *vs = NULL; + int r; + + r = varlink_server_new(&vs, /* flags= */ 0, /* userdata= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + r = facts_add_to_varlink_server(vs, vl_method_list_facts, vl_method_describe_facts); + if (r < 0) + return log_error_errno(r, "Failed to register Facts varlink interface: %m"); + + r = sd_varlink_server_loop_auto(vs); + if (r < 0) + return log_error_errno(r, "Failed to run Varlink event loop: %m"); + + return 0; +} + +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n" + "\n%sGenerate a report describing the current system%s\n" + "\n%sOptions:%s\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal(), + ansi_underline(), + ansi_normal()); + table_print(options, stdout); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + int r; + + assert(argc >= 0); + assert(argv); + + OptionParser state = { argc, argv }; + + FOREACH_OPTION(&state, c, /* ret_a= */ NULL, /* on_error= */ return c) + switch (c) { + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + } + + if (state.optind < argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "This program takes no arguments."); + + 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) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "This program can only run as a Varlink service."); + return 1; +} + +static int run(int argc, char *argv[]) { + int r; + + log_setup(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + return vl_server(); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/core/varlink-facts.c b/src/report/report-basic.c similarity index 88% rename from src/core/varlink-facts.c rename to src/report/report-basic.c index 695080c819c60..381262dfd4909 100644 --- a/src/core/varlink-facts.c +++ b/src/report/report-basic.c @@ -10,7 +10,7 @@ #include "architecture.h" #include "facts.h" #include "hostname-setup.h" -#include "varlink-facts.h" +#include "report-basic.h" #include "virt.h" static int architecture_generate(FactFamilyContext *context, void *userdata) { @@ -98,35 +98,35 @@ static int virtualization_generate(FactFamilyContext *context, void *userdata) { virtualization_to_string(v)); } -const FactFamily fact_family_table[] = { +static const FactFamily fact_family_table[] = { /* Keep facts ordered alphabetically */ { - .name = FACT_IO_SYSTEMD_MANAGER_PREFIX "Architecture", + .name = FACT_IO_SYSTEMD_BASIC "Architecture", .description = "CPU architecture", .generate = architecture_generate, }, { - .name = FACT_IO_SYSTEMD_MANAGER_PREFIX "BootID", + .name = FACT_IO_SYSTEMD_BASIC "BootID", .description = "Current boot ID", .generate = boot_id_generate, }, { - .name = FACT_IO_SYSTEMD_MANAGER_PREFIX "Hostname", + .name = FACT_IO_SYSTEMD_BASIC "Hostname", .description = "System hostname", .generate = hostname_generate, }, { - .name = FACT_IO_SYSTEMD_MANAGER_PREFIX "KernelVersion", + .name = FACT_IO_SYSTEMD_BASIC "KernelVersion", .description = "Kernel version", .generate = kernel_version_generate, }, { - .name = FACT_IO_SYSTEMD_MANAGER_PREFIX "MachineID", + .name = FACT_IO_SYSTEMD_BASIC "MachineID", .description = "Machine ID", .generate = machine_id_generate, }, { - .name = FACT_IO_SYSTEMD_MANAGER_PREFIX "Virtualization", + .name = FACT_IO_SYSTEMD_BASIC "Virtualization", .description = "Virtualization type", .generate = virtualization_generate, }, diff --git a/src/core/varlink-facts.h b/src/report/report-basic.h similarity index 78% rename from src/core/varlink-facts.h rename to src/report/report-basic.h index 5c303c84558ad..b24613edb62ff 100644 --- a/src/core/varlink-facts.h +++ b/src/report/report-basic.h @@ -1,9 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "core-forward.h" +#include "shared-forward.h" -#define FACT_IO_SYSTEMD_MANAGER_PREFIX "io.systemd.Manager." +#define FACT_IO_SYSTEMD_BASIC "io.systemd.Basic." int vl_method_list_facts(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_describe_facts(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/shared/facts.c b/src/shared/facts.c index 5a6882c6ac80a..7554126e2b808 100644 --- a/src/shared/facts.c +++ b/src/shared/facts.c @@ -1,10 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-varlink.h" + #include "facts.h" #include "json-util.h" #include "log.h" #include "varlink-io.systemd.Facts.h" -#include "varlink-util.h" int facts_add_to_varlink_server( sd_varlink_server *server, diff --git a/src/shared/options.h b/src/shared/options.h index 7980b69448b11..afa17d9e3006f 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "memory-util.h" #include "shared-forward.h" typedef enum OptionFlags { diff --git a/src/shared/varlink-io.systemd.Facts.c b/src/shared/varlink-io.systemd.Facts.c index c7cf10290aa63..dad1271c7248b 100644 --- a/src/shared/varlink-io.systemd.Facts.c +++ b/src/shared/varlink-io.systemd.Facts.c @@ -9,8 +9,9 @@ static SD_VARLINK_DEFINE_ERROR(NoSuchFact); static SD_VARLINK_DEFINE_METHOD_FULL( List, SD_VARLINK_REQUIRES_MORE, - SD_VARLINK_FIELD_COMMENT("Fact family name, e.g. io.systemd.Manager.Hostname"), + SD_VARLINK_FIELD_COMMENT("Fact family name, e.g. io.systemd.Basic.Hostname"), SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0), + /* This is currently an unused placeholder. Add examples when we have them. */ SD_VARLINK_FIELD_COMMENT("Fact object name"), SD_VARLINK_DEFINE_OUTPUT(object, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Fact value"), @@ -19,7 +20,7 @@ static SD_VARLINK_DEFINE_METHOD_FULL( static SD_VARLINK_DEFINE_METHOD_FULL( Describe, SD_VARLINK_REQUIRES_MORE, - SD_VARLINK_FIELD_COMMENT("Fact family name"), + SD_VARLINK_FIELD_COMMENT("Fact family name, e.g. io.systemd.Basic.Hostname"), SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("Fact family description"), SD_VARLINK_DEFINE_OUTPUT(description, SD_VARLINK_STRING, 0)); diff --git a/test/units/TEST-74-AUX-UTILS.report.sh b/test/units/TEST-74-AUX-UTILS.report.sh index 4edb67a315063..0b9006e0590e0 100755 --- a/test/units/TEST-74-AUX-UTILS.report.sh +++ b/test/units/TEST-74-AUX-UTILS.report.sh @@ -36,6 +36,9 @@ varlinkctl list-methods /run/systemd/report/io.systemd.Network varlinkctl --more call /run/systemd/report/io.systemd.Network io.systemd.Metrics.List {} varlinkctl --more call /run/systemd/report/io.systemd.Network io.systemd.Metrics.Describe {} +# Make sure the service for "system facts" is enabled +systemctl start systemd-report-basic.socket + # Test facts verbs "$REPORT" facts "$REPORT" facts -j @@ -53,5 +56,5 @@ varlinkctl --more call /run/systemd/report/io.systemd.Network io.systemd.Metrics "$REPORT" describe-facts piff # Test facts via direct Varlink call on existing socket -varlinkctl --more call /run/systemd/report/io.systemd.Manager io.systemd.Facts.List {} -varlinkctl --more call /run/systemd/report/io.systemd.Manager io.systemd.Facts.Describe {} +varlinkctl --more call /run/systemd/report/io.systemd.Basic io.systemd.Facts.List {} +varlinkctl --more call /run/systemd/report/io.systemd.Basic io.systemd.Facts.Describe {} diff --git a/units/meson.build b/units/meson.build index 774c02c0ac4fd..02c2db074c259 100644 --- a/units/meson.build +++ b/units/meson.build @@ -624,6 +624,8 @@ units = [ 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'], 'symlinks' : ['sysinit.target.wants/'], }, + { 'file' : 'systemd-report-basic.socket' }, + { 'file' : 'systemd-report-basic@.service.in' }, { 'file' : 'systemd-tpm2-clear.service.in', 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'], diff --git a/units/systemd-report-basic.socket b/units/systemd-report-basic.socket new file mode 100644 index 0000000000000..bce9309196895 --- /dev/null +++ b/units/systemd-report-basic.socket @@ -0,0 +1,22 @@ +# 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=Report System Basic Facts Socket +DefaultDependencies=no +Before=sockets.target + +[Socket] +ListenStream=/run/systemd/report/io.systemd.Basic +FileDescriptorName=varlink +SocketMode=0666 +Accept=yes +RemoveOnStop=yes + +[Install] +WantedBy=sockets.target diff --git a/units/systemd-report-basic@.service.in b/units/systemd-report-basic@.service.in new file mode 100644 index 0000000000000..ad4e3fce70857 --- /dev/null +++ b/units/systemd-report-basic@.service.in @@ -0,0 +1,13 @@ +# 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=Report System Basic Facts + +[Service] +ExecStart={{LIBEXECDIR}}/systemd-report-basic From 5d0a9539603399abcc161cb87cdee5fde2715466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 5 Mar 2026 12:17:47 +0100 Subject: [PATCH 0679/1296] report-basic: lock down the service The basic approach is copied from systemd-journal-gatewayd.service, with some additions to lock down unneeded network access. --- units/systemd-report-basic.socket | 1 + units/systemd-report-basic@.service.in | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/units/systemd-report-basic.socket b/units/systemd-report-basic.socket index bce9309196895..bfa4ea72568fe 100644 --- a/units/systemd-report-basic.socket +++ b/units/systemd-report-basic.socket @@ -16,6 +16,7 @@ ListenStream=/run/systemd/report/io.systemd.Basic FileDescriptorName=varlink SocketMode=0666 Accept=yes +MaxConnectionsPerSource=16 RemoveOnStop=yes [Install] diff --git a/units/systemd-report-basic@.service.in b/units/systemd-report-basic@.service.in index ad4e3fce70857..a8a3b76e865c7 100644 --- a/units/systemd-report-basic@.service.in +++ b/units/systemd-report-basic@.service.in @@ -10,4 +10,28 @@ Description=Report System Basic Facts [Service] +CapabilityBoundingSet= +DeviceAllow= +DynamicUser=yes +LockPersonality=yes +MemoryDenyWriteExecute=yes +PrivateDevices=yes +PrivateIPC=yes +PrivateNetwork=yes +PrivateTmp=disconnected +ProtectControlGroups=yes +ProtectHome=yes +ProtectHostname=yes +ProtectKernelLogs=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +ProtectSystem=strict +RestrictAddressFamilies=AF_UNIX +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes +SystemCallArchitectures=native +SystemCallErrorNumber=EPERM +SystemCallFilter=@system-service + ExecStart={{LIBEXECDIR}}/systemd-report-basic From 09b0a6ab4ff5683ec0e52648547e103a85da2f12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 1 Apr 2026 15:08:14 +0200 Subject: [PATCH 0680/1296] units: allow systemd-report-basic@.service to run in early boot --- units/systemd-report-basic@.service.in | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/units/systemd-report-basic@.service.in b/units/systemd-report-basic@.service.in index a8a3b76e865c7..043324b5c3987 100644 --- a/units/systemd-report-basic@.service.in +++ b/units/systemd-report-basic@.service.in @@ -9,6 +9,10 @@ [Unit] Description=Report System Basic Facts +DefaultDependencies=no +Conflicts=shutdown.target +Before=shutdown.target + [Service] CapabilityBoundingSet= DeviceAllow= From c8a68dc7b3bcef37174e6c64a9f61e6ac326c9a8 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 1 Apr 2026 13:12:54 +0000 Subject: [PATCH 0681/1296] nspawn: move boot_id and kmsg backing file creation to outer child Follow-up for af5126568af6 ("nspawn: keep backing files for boot_id and kmsg bind mounts alive"). The backing files for the boot_id and kmsg bind mounts were previously created in the inner child. However, /run/host/ is remounted read-only by mount_all() in the inner child (via the MOUNT_IN_USERNS mount table entry) before setup_boot_id() and setup_kmsg() run, so creating files there would fail with EROFS. Fix this by splitting the file creation into separate functions (setup_boot_id_file() and setup_kmsg_fifo()) that run in the outer child, where /run/host/ is still writable. The bind mounts onto /proc remain in the inner child, since procfs is only mounted there. Also move the backing files from /run/ to /run/host/ and drop the dot prefix, since /run/host/ is the container-manager-owned namespace and there is no need to hide these files there. Additionally, apply userns_lchown() to the created files, matching the convention used by all other outer child functions that create files in the container. --- src/nspawn/nspawn.c | 73 ++++++++++++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 20 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index f3e072b0db29e..98e2de2711056 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -2123,23 +2123,42 @@ static int setup_resolv_conf(const char *dest) { return 0; } -static int setup_boot_id(void) { - sd_id128_t rnd = SD_ID128_NULL; +static int setup_boot_id_file(const char *directory) { + _cleanup_free_ char *p = NULL; + sd_id128_t rnd; int r; - /* Generate a new randomized boot ID, so that each boot-up of the container gets a new one */ + assert(directory); + + /* Generate a new randomized boot ID, so that each boot-up of the container gets a new one. We create + * the backing file here in the outer child already, since /run/host/ is mounted read-only by the time + * the inner child runs. We intentionally do not unlink it: bind mounts of unlinked files cannot be + * replicated to other mount namespaces (both the old and new mount APIs fail with ENOENT). Since + * mount_private_apivfs() needs to replicate submounts like boot_id when setting up a fresh /proc + * instance, the backing file must remain on disk. It lives in /run/host/ which is cleaned up on + * shutdown anyway. */ + + p = path_join(directory, "/run/host/proc-sys-kernel-random-boot-id"); + if (!p) + return log_oom(); r = sd_id128_randomize(&rnd); if (r < 0) return log_error_errno(r, "Failed to generate random boot id: %m"); - r = id128_write("/run/.proc-sys-kernel-random-boot-id", ID128_FORMAT_UUID, rnd); + r = id128_write(p, ID128_FORMAT_UUID, rnd); if (r < 0) return log_error_errno(r, "Failed to write boot id: %m"); + return userns_lchown(p, 0, 0); +} + +static int setup_boot_id(void) { + int r; + r = mount_nofollow_verbose( LOG_ERR, - "/run/.proc-sys-kernel-random-boot-id", + "/run/host/proc-sys-kernel-random-boot-id", "/proc/sys/kernel/random/boot_id", /* fstype= */ NULL, MS_BIND, @@ -2147,12 +2166,6 @@ static int setup_boot_id(void) { if (r < 0) return r; - /* NB: We intentionally do not unlink the backing file. Bind mounts of unlinked files cannot be - * replicated to other mount namespaces (both the old and new mount APIs fail with ENOENT). Since - * mount_private_apivfs() needs to replicate submounts like boot_id when setting up a fresh /proc - * instance, the backing file must remain on disk. It lives in /run which is cleaned up on - * shutdown anyway. */ - return mount_nofollow_verbose( LOG_ERR, /* what= */ NULL, @@ -2539,31 +2552,43 @@ static int setup_credentials(const char *root) { return mount_nofollow_verbose(LOG_ERR, NULL, q, NULL, MS_REMOUNT|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, "mode=0500"); } +static int setup_kmsg_fifo(const char *directory) { + _cleanup_free_ char *p = NULL; + + assert(directory); + + p = path_join(directory, "/run/host/proc-kmsg"); + if (!p) + return log_oom(); + + BLOCK_WITH_UMASK(0000); + + if (mkfifo(p, 0600) < 0) + return log_error_errno(errno, "mkfifo() for /run/host/proc-kmsg failed: %m"); + + return userns_lchown(p, 0, 0); +} + static int setup_kmsg(int fd_inner_socket) { _cleanup_close_ int fd = -EBADF; int r; assert(fd_inner_socket >= 0); - BLOCK_WITH_UMASK(0000); - - /* We create the kmsg FIFO in /run, and bind mount it to /proc/kmsg. While FIFOs on the reading + /* We bind mount the kmsg FIFO (created in the outer child) to /proc/kmsg. While FIFOs on the reading * side behave very similar to /proc/kmsg, their writing side behaves differently from /dev/kmsg in * that writing blocks when nothing is reading. In order to avoid any problems with containers * deadlocking due to this we simply make /dev/kmsg unavailable to the container. */ - if (mkfifo("/run/.proc-kmsg", 0600) < 0) - return log_error_errno(errno, "mkfifo() for /run/.proc-kmsg failed: %m"); - - r = mount_nofollow_verbose(LOG_ERR, "/run/.proc-kmsg", "/proc/kmsg", NULL, MS_BIND, NULL); + r = mount_nofollow_verbose(LOG_ERR, "/run/host/proc-kmsg", "/proc/kmsg", NULL, MS_BIND, NULL); if (r < 0) return r; - fd = open("/run/.proc-kmsg", O_RDWR|O_NONBLOCK|O_CLOEXEC); + fd = open("/run/host/proc-kmsg", O_RDWR|O_NONBLOCK|O_CLOEXEC); if (fd < 0) return log_error_errno(errno, "Failed to open fifo: %m"); - /* NB: We intentionally do not unlink the backing FIFO. See setup_boot_id() for details. */ + /* NB: We intentionally do not unlink the backing FIFO. See setup_boot_id_file() for details. */ /* Store away the fd in the socket, so that it stays open as long as we run the child */ r = send_one_fd(fd_inner_socket, fd, 0); @@ -4378,6 +4403,14 @@ static int outer_child( (void) make_inaccessible_nodes(p, chown_uid, chown_uid); + r = setup_boot_id_file(directory); + if (r < 0) + return r; + + r = setup_kmsg_fifo(directory); + if (r < 0) + return r; + r = setup_unix_export_host_inside(directory, unix_export_path); if (r < 0) return r; From 7ae0a588154ad279deaa98f82c15470684189856 Mon Sep 17 00:00:00 2001 From: Christian Hesse Date: Wed, 1 Apr 2026 16:56:19 +0200 Subject: [PATCH 0682/1296] hwdb/keyboard: fix enter key for X+ piccolo The main enter key gives a code for keypad one... Map it to regular enter key. --- hwdb.d/60-keyboard.hwdb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hwdb.d/60-keyboard.hwdb b/hwdb.d/60-keyboard.hwdb index a83c7e4b94d89..771b7dc43e477 100644 --- a/hwdb.d/60-keyboard.hwdb +++ b/hwdb.d/60-keyboard.hwdb @@ -2202,6 +2202,14 @@ evdev:name:FTSC1000:00 2808:509C Keyboard:dmi:*:svnXiaomiInc:pnMipad2:* evdev:atkbd:dmi:bvnTIMI*:bvr*:bd*:svnTIMI*:pnMiNoteBookPro*:* KEYBOARD_KEY_72=macro +########################################################### +# X+ +########################################################### + +# X+ piccolo series 81X (Intel N305, possibly more) +evdev:input:b0011v0001p0001eAB83* + KEYBOARD_KEY_9c=enter # KP_enter in the main area is wrong + ########################################################### # Zepto ########################################################### From ca347a9494dabc39b1a6900bae60571937a772f2 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 1 Apr 2026 22:52:59 +0200 Subject: [PATCH 0683/1296] vmspawn: Pass extra cmdline via smbios when direct booting a UKI -cmdline doesn't work when direct booting a UKI so use SMBIOS instead. --- src/vmspawn/vmspawn.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 02f2b0df2e08a..a93fb0d456044 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -47,6 +47,7 @@ #include "hostname-setup.h" #include "hostname-util.h" #include "id128-util.h" +#include "kernel-image.h" #include "log.h" #include "machine-bind-user.h" #include "machine-credential.h" @@ -1373,11 +1374,24 @@ static int cmdline_add_kernel_cmdline(char ***cmdline, const char *kernel, const if (strv_isempty(arg_kernel_cmdline_extra)) return 0; + KernelImageType type = _KERNEL_IMAGE_TYPE_INVALID; + if (kernel) { + r = inspect_kernel( + AT_FDCWD, + kernel, + &type, + /* ret_cmdline= */ NULL, + /* ret_uname= */ NULL, + /* ret_pretty_name= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to determine '%s' kernel image type: %m", kernel); + } + _cleanup_free_ char *kcl = strv_join(arg_kernel_cmdline_extra, " "); if (!kcl) return log_oom(); - if (kernel) { + if (kernel && type != KERNEL_IMAGE_TYPE_UKI) { if (strv_extend_many(cmdline, "-append", kcl) < 0) return log_oom(); } else { From 189d5c020b9705ccc7215dacd5bb262e96e7dcc3 Mon Sep 17 00:00:00 2001 From: Michael Ferrari Date: Fri, 27 Mar 2026 22:43:27 +0100 Subject: [PATCH 0684/1296] Support `CopyBlocks=` for `Verity={hash,sig}` This enables deriving the minimum size of the `Verity=hash` partition using the `Verity=` logic when the size of the `Verity=data` partition is bigger than the `CopyBlocks=` target. This enables using `Minimize=true` for an "installer image" and later using sd-repart to install to a system with reserve space for future updates by specifying `Size{Min,Max}Bytes=` only in the `Verity=data` partition, without needing to hardcode the corresponding size for the `Verity=hash` partition. While not strictly necessary for `Verity=signature` partitions (since they have a fixed size) there isn't too much reason to not support it, since then you can still specify `VerityMatchKey=` to indicate that the partition is logically still part of that group of partitions. We ensure that if one of the hash uses `CopyBlocks=` that the data partition does so as well. Similarly if the signature partition does it checks that the hash and data partition do so as well. This is to minimize the chance of accidental misconfiguration of mixing `CopyBlocks=auto` and `CopyBlocks=` and of manually populating partitions while hash/sig partitions are copied from existing sources. --- src/repart/repart.c | 48 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index 7dbcfd6d5824e..fd1f16d4de38e 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -2998,10 +2998,20 @@ static int partition_read_definition( "VerityMatchKey= can only be set if Verity= is not \"%s\".", verity_mode_to_string(p->verity)); - if (IN_SET(p->verity, VERITY_HASH, VERITY_SIG) && (p->copy_blocks_path || p->copy_blocks_auto || p->format || partition_needs_populate(p))) - return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "CopyBlocks=/CopyFiles=/Format=/MakeDirectories=/MakeSymlinks= cannot be used with Verity=%s.", - verity_mode_to_string(p->verity)); + if (IN_SET(p->verity, VERITY_HASH, VERITY_SIG)) { + if (p->format || partition_needs_populate(p)) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "CopyFiles=/Format=/MakeDirectories=/MakeSymlinks= cannot be used with Verity=%s.", + verity_mode_to_string(p->verity)); + + /* Later we check that the same CopyBlocks= type (auto vs path) is used for the entire verity set. + * So we assume that CopyBlocks=auto is going to be correct and just path based blocks might result in + * a broken setup */ + if (p->copy_blocks_path) + log_syntax(NULL, LOG_DEBUG, path, 1, 0, + "CopyBlocks= with Verity=%s bypasses dm-verity hash/signature computation; repart cannot verify the resulting setup is correct.", + verity_mode_to_string(p->verity)); + } if (p->verity != VERITY_OFF && p->encrypt != ENCRYPT_OFF) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), @@ -3494,6 +3504,27 @@ static int context_read_definitions(Context *context) { } } + LIST_FOREACH(partitions, p, context->partitions) { + if (!IN_SET(p->verity, VERITY_HASH, VERITY_SIG)) + continue; + + /* We check all verity siblings up until our current type and ensure that if we are using CopyBlocks= + * the previous ones are using the same type of CopyBlocks=. */ + for (VerityMode mode = VERITY_DATA; mode < p->verity; mode++) { + Partition *q = ASSERT_PTR(p->siblings[mode]); + + if (p->copy_blocks_auto && !q->copy_blocks_auto) + return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL), + "CopyBlocks=auto set with Verity=%s but Verity=%s partition does not set CopyBlocks=auto.", + verity_mode_to_string(p->verity), verity_mode_to_string(mode)); + + if (p->copy_blocks_path && !q->copy_blocks_path) + return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL), + "CopyBlocks= set with Verity=%s but Verity=%s partition does not set CopyBlocks=.", + verity_mode_to_string(p->verity), verity_mode_to_string(mode)); + } + } + LIST_FOREACH(partitions, p, context->partitions) { Partition *dp; @@ -5620,7 +5651,7 @@ static int partition_format_verity_hash( if (PARTITION_EXISTS(p)) /* Never format existing partitions */ return 0; - /* Minimized partitions will use the copy blocks logic so skip those here. */ + /* Either we are minimizing the partition or we were instructed to copy an existing hash block directly. */ if (p->copy_blocks_fd >= 0) return 0; @@ -5794,6 +5825,10 @@ static int partition_format_verity_sig(Context *context, Partition *p) { if (PARTITION_EXISTS(p)) return 0; + /* We were instructed to copy an existing signature block directly */ + if (p->copy_blocks_fd >= 0) + return 0; + assert_se(hp = p->siblings[VERITY_HASH]); assert(!hp->dropped); assert_se(rp = p->siblings[VERITY_DATA]); @@ -8884,6 +8919,9 @@ static int context_minimize(Context *context) { if (PARTITION_EXISTS(p)) /* Never format existing partitions */ continue; + if (p->copy_blocks_fd >= 0) + continue; + if (p->minimize == MINIMIZE_OFF) continue; From adc4757b9e518727920617600de5982b57061662 Mon Sep 17 00:00:00 2001 From: Kit Dallege Date: Fri, 27 Mar 2026 00:18:52 +0100 Subject: [PATCH 0685/1296] docs: fix misleading VM/machined documentation Fix two issues in WRITING_VM_AND_CONTAINER_MANAGERS.md: 1. The Host OS Integration section implied that -M switch and machinectl shell/login work for VMs, but they currently only work for containers. Add a note clarifying this limitation. 2. The Guest OS Integration section said "there's only one" VM integration API (SMBIOS Product UUID), but VM_INTERFACE.md documents five. Replace the outdated single-API description with a reference to VM_INTERFACE.md listing all five. Fixes #40935 Co-developed-by: Claude Opus 4.6 --- docs/WRITING_VM_AND_CONTAINER_MANAGERS.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/WRITING_VM_AND_CONTAINER_MANAGERS.md b/docs/WRITING_VM_AND_CONTAINER_MANAGERS.md index 724d3d6dafb94..e23de1a746d86 100644 --- a/docs/WRITING_VM_AND_CONTAINER_MANAGERS.md +++ b/docs/WRITING_VM_AND_CONTAINER_MANAGERS.md @@ -24,7 +24,8 @@ their own. All virtual machines and containers should be registered with the [machined](https://www.freedesktop.org/software/systemd/man/latest/org.freedesktop.machine1) mini service that is part of systemd. This provides integration into the core OS at various points. For example, tools like ps, cgls, gnome-system-manager use this registration information to show machine information for running processes, as each of the VM's/container's processes can reliably attributed to a registered machine. The various systemd tools (like systemctl, journalctl, loginctl, systemd-run, ...) all support a -M switch that operates on machines registered with machined. -"machinectl" may be used to execute operations on any such machine. +Note that the -M switch and interactive commands like "machinectl shell" and "machinectl login" currently only work for containers, not for VMs. +For VMs, registration with machined still provides process attribution, cgroup placement, and visibility in tools like ps and systemctl. When a machine is registered via machined its processes will automatically be placed in a systemd scope unit (that is located in the machines.slice slice) and thus appear in "systemctl" and similar commands. The scope unit name is based on the machine meta information passed to machined at registration. @@ -34,7 +35,5 @@ For more details on the APIs provided by machine consult [the bus API interface As container virtualization is much less comprehensive, and the guest is less isolated from the host, there are a number of interfaces defined how the container manager can set up the environment for systemd running inside a container. These Interfaces are documented in [Container Interface of systemd](/CONTAINER_INTERFACE). -VM virtualization is more comprehensive and fewer integration APIs are available. -In fact there's only one: a VM manager may initialize the SMBIOS DMI field "Product UUUID" to a UUID uniquely identifying this virtual machine instance. -This is read in the guest via `/sys/class/dmi/id/product_uuid`, and used as configuration source for `/etc/machine-id` if in the guest, if that file is not initialized yet. -Note that this is currently only supported for kvm hosts, but may be extended to other managers as well. +VM virtualization is more comprehensive and fewer integration APIs are available compared to containers. +See [The VM Interface](/VM_INTERFACE) for the full list of integration points, which includes system credentials via SMBIOS Type 11 vendor strings, readiness notification via `AF_VSOCK`, SSH access via `AF_VSOCK`, machine ID initialization from SMBIOS Product UUID, and kernel command line extension. From fd1b84af98585dbfa62c7545edf689e380062f21 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 27 Mar 2026 14:57:40 +0000 Subject: [PATCH 0686/1296] vmspawn: pass --log-level=error and --modcaps=-mknod to virtiofsd Reduce virtiofsd log noise by setting --log-level=error, and drop the unnecessary mknod capability with --modcaps=-mknod, matching mkosi's virtiofsd invocation. Co-developed-by: Claude Opus 4.6 --- src/vmspawn/vmspawn.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index a93fb0d456044..18f2833ae7f31 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -1700,7 +1700,9 @@ static int start_virtiofsd( "--shared-dir", source_uid == FOREIGN_UID_MIN ? "/run/systemd/mount-rootfs" : directory, "--xattr", "--fd", sockstr, - "--no-announce-submounts"); + "--no-announce-submounts", + "--log-level=error", + "--modcaps=-mknod"); if (!argv) return log_oom(); From 73c0a797893f2fb8dcf70d883bbef2b9163f015d Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 27 Mar 2026 14:24:00 +0100 Subject: [PATCH 0687/1296] vmspawn: Use qemu config file for smp and memory Pass -no-user-config while we're at it to avoid loading qemu config from /etc which is more likely to cause hard to debug issues rather than do something useful. --- src/vmspawn/vmspawn.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 18f2833ae7f31..0de314ac183ab 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -2308,6 +2308,16 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return r; } + r = qemu_config_section(config_file, "smp-opts", /* id= */ NULL, + "cpus", arg_cpus ?: "1"); + if (r < 0) + return r; + + r = qemu_config_section(config_file, "memory", /* id= */ NULL, + "size", mem); + if (r < 0) + return r; + r = qemu_config_section(config_file, "object", "rng0", "qom-type", "rng-random", "filename", "/dev/urandom"); @@ -2349,8 +2359,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { /* Start building the cmdline for items that must remain as command line arguments */ cmdline = strv_new(qemu_binary, - "-smp", arg_cpus ?: "1", - "-m", mem); + "-no-user-config"); if (!cmdline) return log_oom(); From f180ff5983b94781c0a03e046fec1cb9e274f114 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 1 Apr 2026 17:29:45 +0200 Subject: [PATCH 0688/1296] sd-varlink: fix fd handling in upgrade code path This commit fixes an issue with the fd handling in sd_varlink_call_and_upgrade() when one direction of the output FDs is unset. Thanks to Lennart for spotting this and suggesting the fix. --- src/libsystemd/sd-varlink/sd-varlink.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index 64606adcea998..4beb785199799 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -2452,26 +2452,30 @@ _public_ int sd_varlink_call_and_upgrade( } } - /* Handle the case where the caller is not interested in one of the fds. We need - * to consider the case when (input_fd == output_fd) just clear the alias - * rather than closing it, since the other branch may hand it out. */ + /* Hand out the fds to the caller. When the caller doesn't want one direction, shut it + * down: but avoid closing the underlying fd if the other direction still needs it + * (i.e. when input_fd == output_fd). */ bool same_fd = v->input_fd == v->output_fd; if (ret_input_fd) *ret_input_fd = TAKE_FD(v->input_fd); - else if (!same_fd) { + else { (void) shutdown(v->input_fd, SHUT_RD); - v->input_fd = safe_close(v->input_fd); - } else - v->input_fd = -EBADF; + if (same_fd && ret_output_fd) + TAKE_FD(v->input_fd); /* don't close yet, output branch needs it */ + else + v->input_fd = safe_close(v->input_fd); + } if (ret_output_fd) *ret_output_fd = TAKE_FD(v->output_fd); - else if (!same_fd) { + else { (void) shutdown(v->output_fd, SHUT_WR); - v->output_fd = safe_close(v->output_fd); - } else - v->output_fd = -EBADF; + if (same_fd && ret_input_fd) + TAKE_FD(v->output_fd); + else + v->output_fd = safe_close(v->output_fd); + } varlink_set_state(v, VARLINK_DISCONNECTED); assert(v->n_pending == 1); From f6c8b3552915e28e1c0a319f3ebe7cbdc70ba571 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 1 Apr 2026 17:47:10 +0200 Subject: [PATCH 0689/1296] varlinkctl: simplify error handling in exec_with_listen_fds Instead of exiting in exec_with_listen_fds() just return an error and do the actual _exit() in the caller. Much nicer this way. Thanks for Lennart for suggesting this. --- src/varlinkctl/varlinkctl.c | 60 +++++++++++++++---------------------- 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index 4495d090dfc0d..c2cdd52b89ea6 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -651,56 +651,42 @@ static int upgrade_forward_done(SocketForward *sf, int error, void *userdata) { return sd_event_exit(event, error < 0 ? error : 0); } -_noreturn_ static void exec_with_listen_fds(char **exec_cmdline, int *fds, size_t n_fds) { - int r; - +/* This will only return if something goes wrong, otherwise the exec_cmdline + * is run and replaces our code. */ +static int exec_with_listen_fds(char **exec_cmdline, int *fds, size_t n_fds) { _cleanup_free_ char *j = quote_command_line(exec_cmdline, SHELL_ESCAPE_EMPTY); - if (!j) { - log_oom(); - _exit(EXIT_FAILURE); - } + if (!j) + return log_oom(); log_close(); log_set_open_when_needed(true); - r = close_all_fds(fds, n_fds); - if (r < 0) { - log_error_errno(r, "Failed to close all remaining file descriptors: %m"); - _exit(EXIT_FAILURE); - } + int r = close_all_fds(fds, n_fds); + if (r < 0) + return log_error_errno(r, "Failed to close all remaining file descriptors: %m"); r = pack_fds(fds, n_fds); - if (r < 0) { - log_error_errno(r, "Failed to rearrange file descriptors: %m"); - _exit(EXIT_FAILURE); - } + if (r < 0) + return log_error_errno(r, "Failed to rearrange file descriptors: %m"); r = fd_cloexec_many(fds, n_fds, false); - if (r < 0) { - log_error_errno(r, "Failed to disable O_CLOEXEC for file descriptors: %m"); - _exit(EXIT_FAILURE); - } + if (r < 0) + return log_error_errno(r, "Failed to disable O_CLOEXEC for file descriptors: %m"); if (n_fds > 0) { r = setenvf("LISTEN_FDS", /* overwrite= */ true, "%zu", n_fds); - if (r < 0) { - log_error_errno(r, "Failed to set $LISTEN_FDS environment variable: %m"); - _exit(EXIT_FAILURE); - } + if (r < 0) + return log_error_errno(r, "Failed to set $LISTEN_FDS environment variable: %m"); r = setenvf("LISTEN_PID", /* overwrite= */ true, PID_FMT, getpid_cached()); - if (r < 0) { - log_error_errno(r, "Failed to set $LISTEN_PID environment variable: %m"); - _exit(EXIT_FAILURE); - } + if (r < 0) + return log_error_errno(r, "Failed to set $LISTEN_PID environment variable: %m"); uint64_t pidfdid; if (pidfd_get_inode_id_self_cached(&pidfdid) >= 0) { r = setenvf("LISTEN_PIDFDID", /* overwrite= */ true, "%" PRIu64, pidfdid); - if (r < 0) { - log_error_errno(r, "Failed to set $LISTEN_PIDFDID environment variable: %m"); - _exit(EXIT_FAILURE); - } + if (r < 0) + return log_error_errno(r, "Failed to set $LISTEN_PIDFDID environment variable: %m"); } } else { (void) unsetenv("LISTEN_FDS"); @@ -712,8 +698,7 @@ _noreturn_ static void exec_with_listen_fds(char **exec_cmdline, int *fds, size_ log_debug("Executing: %s", j); execvp(exec_cmdline[0], exec_cmdline); - log_error_errno(errno, "Failed to execute '%s': %m", j); - _exit(EXIT_FAILURE); + return log_error_errno(errno, "Failed to execute '%s': %m", j); } static int varlink_call_and_upgrade(const char *url, const char *method, sd_json_variant *parameters, char **exec_cmdline) { @@ -778,8 +763,9 @@ static int varlink_call_and_upgrade(const char *url, const char *method, sd_json _exit(EXIT_FAILURE); } - /* We exec and never return here */ - exec_with_listen_fds(exec_cmdline, /* fds= */ NULL, /* n_fds= */ 0); + r = exec_with_listen_fds(exec_cmdline, /* fds= */ NULL, /* n_fds= */ 0); + /* This is only reached on failure, otherwise we continue with exec_cmldine). */ + _exit(EXIT_FAILURE); } /* No --exec: bidirectional proxy between stdin/stdout and the upgraded socket */ @@ -1070,6 +1056,8 @@ static int verb_call(int argc, char *argv[], uintptr_t _data, void *userdata) { } exec_with_listen_fds(exec_cmdline, fd_array, m); + /* This is only reached on failure, otherwise we continue with exec_cmdline. */ + _exit(EXIT_FAILURE); } if (arg_quiet) From ebdc91263abf6a54779a14099d2c966681d758f1 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 1 Apr 2026 17:34:40 +0200 Subject: [PATCH 0690/1296] test: tweak TEST-74-AUX-UTILS.varlinkctl.sh varlink test This commit tweaks the TEST-74-AUX-UTILS.varlinkctl.sh code to use `systemd-notify --fork $UPGRADE_SERVER` instead of the (ugly) timeout. This also fixes a stale comment in around `Test --upgrade with stdin redirected from a regular file`. Thanks to Daan for suggesting this! --- test/units/TEST-74-AUX-UTILS.varlinkctl.sh | 36 ++++++++++------------ 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh index 7f888756ae049..b6d270cfd4703 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -265,11 +265,22 @@ cat >"$UPGRADE_SERVER" <<'PYEOF' Without arguments, speaks over stdin/stdout (for ssh-exec: transport testing).""" import json, os, socket, sys +def sd_notify_ready(): + addr = os.environ.get("NOTIFY_SOCKET") + if not addr: + return + if addr[0] == "@": + addr = "\0" + addr[1:] + s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) + s.connect(addr) + s.sendall(b"READY=1") + s.close() + if len(sys.argv) > 1: sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.bind(sys.argv[1]) sock.listen(1) - print("READY", flush=True) + sd_notify_ready() conn, _ = sock.accept() inp = conn.makefile("rb") out = conn.makefile("wb") @@ -309,12 +320,8 @@ if sock: PYEOF chmod +x "$UPGRADE_SERVER" -# Start the server in the background -python3 "$UPGRADE_SERVER" "$UPGRADE_SOCKET" & -SERVER_PID=$! - -# Wait for server readiness -timeout 5 bash -c "while [ ! -S '$UPGRADE_SOCKET' ]; do sleep 0.1; done" +# Start the server in the background, wait for readiness via sd_notify +systemd-notify --fork -q -- python3 "$UPGRADE_SERVER" "$UPGRADE_SOCKET" # Test proxy mode: pipe data through --upgrade, passing parameters and validate result="$(echo "hello world" | varlinkctl call --upgrade "unix:$UPGRADE_SOCKET" io.systemd.test.Reverse '{"foo":"bar"}')" @@ -322,14 +329,10 @@ echo "$result" | grep "<<< UPGRADED >>>" >/dev/null echo "$result" | grep '"foo": "bar"' >/dev/null echo "$result" | grep "dlrow olleh" >/dev/null -wait "$SERVER_PID" || : - # Test --upgrade with stdin redirected from a regular file (epoll can't poll regular files, -# so this exercises the fork+pipe fallback path) +# so this exercises the sd_event_add_defer fallback path) UPGRADE_SOCKET2="$(mktemp -d)/upgrade.sock" -python3 "$UPGRADE_SERVER" "$UPGRADE_SOCKET2" & -SERVER_PID=$! -timeout 5 bash -c "while [ ! -S '$UPGRADE_SOCKET2' ]; do sleep 0.1; done" +systemd-notify --fork -q -- python3 "$UPGRADE_SERVER" "$UPGRADE_SOCKET2" echo "file input test" > /tmp/test-upgrade-input result="$(varlinkctl call --upgrade "unix:$UPGRADE_SOCKET2" io.systemd.test.Reverse '{"foo":"file"}' < /tmp/test-upgrade-input)" @@ -337,8 +340,6 @@ echo "$result" | grep "<<< UPGRADED >>>" >/dev/null echo "$result" | grep '"foo": "file"' >/dev/null echo "$result" | grep "tset tupni elif" >/dev/null -wait "$SERVER_PID" || : - # Test --upgrade over ssh-exec: transport (pipe pair, not a bidirectional socket). # This exercises the input_fd != output_fd path in sd_varlink_call_and_upgrade(). # Reuse the same server script without a socket argument - it speaks over stdin/stdout. @@ -355,9 +356,7 @@ echo "$result" | grep "tset epip hss" >/dev/null # Start another server for --exec test rm -f "$UPGRADE_SOCKET" -python3 "$UPGRADE_SERVER" "$UPGRADE_SOCKET" & -SERVER_PID=$! -timeout 5 bash -c "while [ ! -S '$UPGRADE_SOCKET' ]; do sleep 0.1; done" +systemd-notify --fork -q -- python3 "$UPGRADE_SERVER" "$UPGRADE_SOCKET" # Test --exec mode: the upgraded socket becomes stdin/stdout of the child. # Since stdout goes to the socket (not the terminal), write results to a file for verification. @@ -369,6 +368,5 @@ grep '"foo": "bar"' "$EXEC_RESULT" >/dev/null grep "dlrow olleh" "$EXEC_RESULT" >/dev/null rm -f "$EXEC_RESULT" -wait "$SERVER_PID" || : rm -f "$UPGRADE_SOCKET" "$UPGRADE_SOCKET2" "$UPGRADE_SERVER" /tmp/test-upgrade-input rm -rf "$(dirname "$UPGRADE_SOCKET")" "$(dirname "$UPGRADE_SOCKET2")" From ecf2329e14f1c554e2a3c240290cb9116546597f Mon Sep 17 00:00:00 2001 From: Michael Ferrari Date: Mon, 30 Mar 2026 02:48:48 +0200 Subject: [PATCH 0691/1296] Initialize roothash when populating sig partition This allows one to specify `CopyBlocks=` on both the `Verity=` data and hash partition and the signature is recreated correctly. --- src/repart/repart.c | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index fd1f16d4de38e..1512c1a0d6c63 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -5788,21 +5788,41 @@ static int sign_verity_roothash( #endif } -static const VeritySettings *lookup_verity_settings_by_uuid_pair(sd_id128_t data_uuid, sd_id128_t hash_uuid) { - uint8_t root_hash_key[sizeof(sd_id128_t) * 2]; +static int iovec_roothash_from_uuid_pair( + sd_id128_t data_uuid, + sd_id128_t hash_uuid, + struct iovec *ret_roothash) { + + uint8_t roothash_bytes[sizeof(sd_id128_t) * 2]; + + assert(ret_roothash); if (sd_id128_is_null(data_uuid) || sd_id128_is_null(hash_uuid)) - return NULL; + return -EINVAL; /* As per the https://uapi-group.org/specifications/specs/discoverable_partitions_specification/ the * UUIDs of the data and verity partitions are respectively the first and second halves of the * dm-verity roothash, so we can use them to match the signature to the right partition. */ - memcpy(root_hash_key, data_uuid.bytes, sizeof(sd_id128_t)); - memcpy(root_hash_key + sizeof(sd_id128_t), hash_uuid.bytes, sizeof(sd_id128_t)); + memcpy(roothash_bytes, data_uuid.bytes, sizeof(sd_id128_t)); + memcpy(roothash_bytes + sizeof(sd_id128_t), hash_uuid.bytes, sizeof(sd_id128_t)); + + if (!iovec_memdup(&IOVEC_MAKE(roothash_bytes, sizeof(roothash_bytes)), ret_roothash)) + return -ENOMEM; + + return 0; +} + +static const VeritySettings *lookup_verity_settings_by_uuid_pair(sd_id128_t data_uuid, sd_id128_t hash_uuid) { + _cleanup_(iovec_done) struct iovec roothash = {}; + int r; + + r = iovec_roothash_from_uuid_pair(data_uuid, hash_uuid, &roothash); + if (r < 0) + return NULL; VeritySettings key = { - .root_hash = IOVEC_MAKE(root_hash_key, sizeof(root_hash_key)), + .root_hash = roothash, }; return set_get(arg_verity_settings, &key); @@ -5834,6 +5854,14 @@ static int partition_format_verity_sig(Context *context, Partition *p) { assert_se(rp = p->siblings[VERITY_DATA]); assert(!rp->dropped); + /* Currently only set while formatting the hash partition. But if this is skipped via CopyBlocks= + * we just derive the roothash from the UUIDs from the data + hash partition. */ + if (!iovec_is_set(&hp->roothash)) { + r = iovec_roothash_from_uuid_pair(rp->new_uuid, hp->new_uuid, &hp->roothash); + if (r < 0) + return log_error_errno(r, "Unable to derive roothash: %m"); + } + verity_settings = lookup_verity_settings_by_uuid_pair(rp->current_uuid, hp->current_uuid); if (!verity_settings) { From 501ece433b701d65c5dd484a22d3026bf872b877 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 2 Apr 2026 12:08:56 +0100 Subject: [PATCH 0692/1296] service: transition unit from SERVICE_DEAD_RESOURCES_PINNED to SERVICE_DEAD when fd store is emptied Follow-up for b9c1883a9cd9b5126fe648f3e198143dc19a222d --- src/core/service.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/core/service.c b/src/core/service.c index 51bba291e8fd4..b511b422ac3d1 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -138,6 +138,8 @@ static void service_enter_reload_by_notify(Service *s); static bool service_can_reload_extensions(Service *s, bool warn); +static void service_set_state(Service *s, ServiceState state); + static bool SERVICE_STATE_WITH_MAIN_PROCESS(ServiceState state) { return IN_SET(state, SERVICE_START, SERVICE_START_POST, @@ -571,15 +573,20 @@ static void service_done(Unit *u) { static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *userdata) { ServiceFDStore *fs = ASSERT_PTR(userdata); + Service *s = fs->service; assert(e); /* If we get either EPOLLHUP or EPOLLERR, it's time to remove this entry from the fd store */ - log_unit_debug(UNIT(fs->service), + log_unit_debug(UNIT(s), "Received %s on stored fd %d (%s), closing.", revents & EPOLLERR ? "EPOLLERR" : "EPOLLHUP", fs->fd, strna(fs->fdname)); service_fd_store_unlink(fs); + + if (s->state == SERVICE_DEAD_RESOURCES_PINNED && !s->fd_store) + service_set_state(s, SERVICE_DEAD); + return 0; } From 5a20987bef4455dc66ddc370e938bccc36b712eb Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 2 Apr 2026 12:10:39 +0100 Subject: [PATCH 0693/1296] service: add macro to check if FD store is empty Follow-up for b9c1883a9cd9b5126fe648f3e198143dc19a222d --- src/core/service.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/core/service.c b/src/core/service.c index b511b422ac3d1..f3e5a5f85b78e 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -60,6 +60,8 @@ #define service_spawn(...) service_spawn_internal(__func__, __VA_ARGS__) +#define SERVICE_FD_STORE_POPULATED(s) (!!(s)->fd_store) + static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = { [SERVICE_DEAD] = UNIT_INACTIVE, [SERVICE_CONDITION] = UNIT_ACTIVATING, @@ -481,12 +483,12 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(ServiceFDStore*, service_fd_store_unlink); static void service_release_fd_store(Service *s) { assert(s); - if (!s->fd_store) + if (!SERVICE_FD_STORE_POPULATED(s)) return; log_unit_debug(UNIT(s), "Releasing all stored fds."); - while (s->fd_store) + while (SERVICE_FD_STORE_POPULATED(s)) service_fd_store_unlink(s->fd_store); assert(s->n_fd_store == 0); @@ -584,7 +586,7 @@ static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *us fs->fd, strna(fs->fdname)); service_fd_store_unlink(fs); - if (s->state == SERVICE_DEAD_RESOURCES_PINNED && !s->fd_store) + if (s->state == SERVICE_DEAD_RESOURCES_PINNED && !SERVICE_FD_STORE_POPULATED(s)) service_set_state(s, SERVICE_DEAD); return 0; @@ -2133,7 +2135,7 @@ static bool service_will_restart(Unit *u) { static ServiceState service_determine_dead_state(Service *s) { assert(s); - return s->fd_store && s->fd_store_preserve_mode == EXEC_PRESERVE_YES ? SERVICE_DEAD_RESOURCES_PINNED : SERVICE_DEAD; + return SERVICE_FD_STORE_POPULATED(s) && s->fd_store_preserve_mode == EXEC_PRESERVE_YES ? SERVICE_DEAD_RESOURCES_PINNED : SERVICE_DEAD; } static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) { @@ -5619,7 +5621,7 @@ static int service_clean(Unit *u, ExecCleanMask mask) { /* If we are done, leave quickly */ if (strv_isempty(l)) { - if (s->state == SERVICE_DEAD_RESOURCES_PINNED && !s->fd_store) + if (s->state == SERVICE_DEAD_RESOURCES_PINNED && !SERVICE_FD_STORE_POPULATED(s)) service_set_state(s, SERVICE_DEAD); return 0; } @@ -5872,7 +5874,7 @@ static void service_release_resources(Unit *u) { if (s->fd_store_preserve_mode != EXEC_PRESERVE_YES) service_release_fd_store(s); - if (s->state == SERVICE_DEAD_RESOURCES_PINNED && !s->fd_store) + if (s->state == SERVICE_DEAD_RESOURCES_PINNED && !SERVICE_FD_STORE_POPULATED(s)) service_set_state(s, SERVICE_DEAD); } From 55327a1fd859e8c168786059242306f1a74d4153 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 2 Apr 2026 12:11:01 +0100 Subject: [PATCH 0694/1296] core: do not GC units that have FDs stored If a unit has FileDescriptorStorePreserve=yes we'll keep its FDs around in case it starts again. But if there are no reverse dependencies referencing it, we'll also GC it and lose all the FDs, which defeats the point of the setting (which is opt-in). Do not GC units that have FDs stored to avoid this. Follow-up for b9c1883a9cd9b5126fe648f3e198143dc19a222d --- man/systemd.service.xml | 7 ++++--- man/systemd.unit.xml | 5 +++++ src/core/service.c | 6 ++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/man/systemd.service.xml b/man/systemd.service.xml index 549f36af1db77..d4a1978523011 100644 --- a/man/systemd.service.xml +++ b/man/systemd.service.xml @@ -1250,9 +1250,10 @@ RestartMaxDelaySec=160s file descriptor store is automatically released when the service is stopped; if restart (the default) it is kept around as long as the unit is neither inactive nor failed, or a job is queued for the service, or the service is expected to be restarted. If - yes the file descriptor store is kept around until the unit is removed from - memory (i.e. is not referenced anymore and inactive). The latter is useful to keep entries in the - file descriptor store pinned until the service manager exits. + yes the file descriptor store is kept around and garbage collection of the unit + is disabled. The latter is useful to keep entries in the file descriptor store pinned until the unit + is removed, the service manager exits, or the file descriptors get EPOLLHUP or + EPOLLERR. Use systemctl clean --what=fdstore … to release the file descriptor store explicitly. diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index 8bbff2f210a7f..47c30d869fcbe 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -1082,6 +1082,11 @@ resources, …) are flushed out immediately after the unit completed, except for what is stored in the logging subsystem. Defaults to . + Since v261, if FileDescriptorStorePreserve= is set to , + and the unit has file descriptors stored, garbage collection will be disabled until the unit is + removed, the service manager exits, or the file descriptors get EPOLLHUP or + EPOLLERR. + diff --git a/src/core/service.c b/src/core/service.c index f3e5a5f85b78e..569a6871d602f 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -3959,8 +3959,10 @@ static bool service_may_gc(Unit *u) { return false; /* Only allow collection of actually dead services, i.e. not those that are in the transitionary - * SERVICE_DEAD_BEFORE_AUTO_RESTART/SERVICE_FAILED_BEFORE_AUTO_RESTART states. */ - if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_DEAD_RESOURCES_PINNED)) + * SERVICE_DEAD_BEFORE_AUTO_RESTART/SERVICE_FAILED_BEFORE_AUTO_RESTART states, and not those + * that still have resources pinned (fd store with FileDescriptorStorePreserve=yes) in case they are + * started again later despite not having any reverse dependency. */ + if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED)) return false; return true; From 08eff23a2dfb3f487e2451bb8cfb43c8fe59a9e0 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Thu, 26 Mar 2026 09:23:29 +0100 Subject: [PATCH 0695/1296] mount-util: restore compat for kernels without MOUNT_ATTR_NOSYMFOLLOW (< 5.14) Follow-up for 6753bd8a2f38bd77a4c8b973174db6ec8bcaf3ab Replaces #41341 --- README | 4 ++-- src/shared/mount-util.c | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/README b/README index 0b2d53de1c895..359db5c3f433f 100644 --- a/README +++ b/README @@ -60,7 +60,7 @@ REQUIREMENTS: Linux kernel ≥ 5.11 for epoll_pwait2() ≥ 5.12 for idmapped mount (mount_setattr()) - ≥ 5.14 for cgroup.kill and quotactl_fd() + ≥ 5.14 for cgroup.kill, quotactl_fd(), and MOUNT_ATTR_NOSYMFOLLOW ⚠️ Kernel versions below 5.14 ("recommended baseline") have significant gaps in functionality and are not recommended for use with this version @@ -77,7 +77,7 @@ REQUIREMENTS: ≥ 6.10 for fcntl(F_DUPFD_QUERY), unprivileged linkat(AT_EMPTY_PATH), and block device 'partscan' sysfs attribute ≥ 6.12 for AT_HANDLE_MNT_ID_UNIQUE - ≥ 6.13 for PIDFD_GET_INFO and {set,remove}xattrat() and + ≥ 6.13 for PIDFD_GET_INFO, {set,remove}xattrat(), and FSCONFIG_SET_FD support for overlayfs layers ≥ 6.16 for coredump pattern '%F' (pidfd) specifier and SO_PASSRIGHTS diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index 382992edf0887..02f63f802a4a8 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -1988,10 +1988,19 @@ int fsmount_credentials_fs(int *ret_fsfd) { if (fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) < 0) return -errno; - int mfd = fsmount(fs_fd, FSMOUNT_CLOEXEC, - ms_flags_to_mount_attr(credentials_fs_mount_flags(/* ro= */ false))); + unsigned mount_attrs = ms_flags_to_mount_attr(credentials_fs_mount_flags(/* ro = */ false)); + + int mfd = RET_NERRNO(fsmount(fs_fd, FSMOUNT_CLOEXEC, mount_attrs)); + if (mfd == -EINVAL) { + /* MS_NOSYMFOLLOW was added in kernel 5.10, but the new mount API counterpart was missing + * until 5.14 (c.f. https://github.com/torvalds/linux/commit/dd8b477f9a3d8edb136207acb3652e1a34a661b7). + * + * TODO: drop this once our baseline is raised to 5.14 */ + assert(FLAGS_SET(mount_attrs, MOUNT_ATTR_NOSYMFOLLOW)); + mfd = RET_NERRNO(fsmount(fs_fd, FSMOUNT_CLOEXEC, mount_attrs & ~MOUNT_ATTR_NOSYMFOLLOW)); + } if (mfd < 0) - return -errno; + return mfd; if (ret_fsfd) *ret_fsfd = TAKE_FD(fs_fd); From 0e187fae072662c1e8a2cea1185daf36380bd725 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 30 Mar 2026 21:50:27 +0200 Subject: [PATCH 0696/1296] shared/gpt: add gpt_probe() for GPT header and partition entry reading Add gpt_probe() which probes for a GPT partition table at various sector sizes (512-4096) and optionally returns the header and partition entries. Returns the detected sector size on success, 0 if no GPT was found, or negative errno on error. Refactor probe_sector_size() in dissect-image.c to be a thin wrapper around gpt_probe(). Co-developed-by: Claude Opus 4.6 --- src/shared/dissect-image.c | 56 +++---------- src/shared/gpt.c | 85 ++++++++++++++++++++ src/shared/gpt.h | 7 ++ src/test/test-gpt.c | 159 +++++++++++++++++++++++++++++++++++-- 4 files changed, 257 insertions(+), 50 deletions(-) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 94d6f0545d4e1..2bef82dd34b53 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -139,54 +139,24 @@ static const char *getenv_fstype(PartitionDesignator d) { int probe_sector_size(int fd, uint32_t *ret) { - /* Disk images might be for 512B or for 4096 sector sizes, let's try to auto-detect that by searching - * for the GPT headers at the relevant byte offsets */ - - assert_cc(sizeof(GptHeader) == 92); - - /* We expect a sector size in the range 512…4096. The GPT header is located in the second - * sector. Hence it could be at byte 512 at the earliest, and at byte 4096 at the latest. And we must - * read with granularity of the largest sector size we care about. Which means 8K. */ - uint8_t sectors[2 * 4096]; - uint32_t found = 0; - ssize_t n; - assert(fd >= 0); assert(ret); - n = pread(fd, sectors, sizeof(sectors), 0); - if (n < 0) - return -errno; - if (n != sizeof(sectors)) /* too short? */ - goto not_found; - - /* Let's see if we find the GPT partition header with various expected sector sizes */ - for (uint32_t sz = 512; sz <= 4096; sz <<= 1) { - const GptHeader *p; - - assert(sizeof(sectors) >= sz * 2); - p = (const GptHeader*) (sectors + sz); - - if (!gpt_header_has_signature(p)) - continue; - - if (found != 0) - return log_debug_errno(SYNTHETIC_ERRNO(ENOTUNIQ), - "Detected valid partition table at offsets matching multiple sector sizes, refusing."); - - found = sz; - } - - if (found != 0) { - log_debug("Determined sector size %" PRIu32 " based on discovered partition table.", found); - *ret = found; - return 1; /* indicate we *did* find it */ + ssize_t ssz = gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL); + if (ssz == -ENOTUNIQ) + return log_debug_errno(ssz, + "Detected valid partition table at offsets matching multiple sector sizes, refusing."); + if (ssz < 0) + return ssz; + if (ssz == 0) { + log_debug("Couldn't find any partition table to derive sector size of."); + *ret = 512; /* pick the traditional default */ + return 0; /* indicate we didn't find it */ } -not_found: - log_debug("Couldn't find any partition table to derive sector size of."); - *ret = 512; /* pick the traditional default */ - return 0; /* indicate we didn't find it */ + log_debug("Determined sector size %" PRIu32 " based on discovered partition table.", (uint32_t) ssz); + *ret = ssz; + return 1; /* indicate we *did* find it */ } int probe_sector_size_prefer_ioctl(int fd, uint32_t *ret) { diff --git a/src/shared/gpt.c b/src/shared/gpt.c index 9308159ebe9f0..d6f264fbe980b 100644 --- a/src/shared/gpt.c +++ b/src/shared/gpt.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "alloc-util.h" #include "gpt.h" #include "string-table.h" @@ -391,3 +393,86 @@ bool gpt_header_has_signature(const GptHeader *p) { return true; } + +ssize_t gpt_probe( + int fd, + GptHeader *ret_header, + void **ret_entries, + uint32_t *ret_n_entries, + uint32_t *ret_entry_size) { + + assert(fd >= 0); + + /* Disk images might be for 512B or for 4096 sector sizes, let's try to auto-detect that by searching + * for the GPT headers at the relevant byte offsets. */ + + assert_cc(sizeof(GptHeader) == 92); + + /* We expect a sector size in the range 512…4096. The GPT header is located in the second + * sector. Hence it could be at byte 512 at the earliest, and at byte 4096 at the latest. And we must + * read with granularity of the largest sector size we care about. Which means 8K. */ + uint8_t sectors[2 * 4096]; + + ssize_t n = pread(fd, sectors, sizeof(sectors), 0); + if (n < 0) + return -errno; + if ((size_t) n < sizeof(sectors)) + return 0; /* too short */ + + /* Let's see if we find the GPT partition header with various expected sector sizes */ + uint32_t found = 0; + for (uint32_t sz = 512; sz <= 4096; sz <<= 1) { + const GptHeader *p = (const GptHeader *) (sectors + sz); + + if (!gpt_header_has_signature(p)) + continue; + + if (found != 0) + return -ENOTUNIQ; + + found = sz; + } + + if (found == 0) + return 0; + + const GptHeader *h = (const GptHeader *) (sectors + found); + + uint32_t entry_sz = le32toh(h->size_of_partition_entry); + uint32_t entry_count = le32toh(h->number_of_partition_entries); + + if (ret_entries) { + uint64_t entry_lba = le64toh(h->partition_entry_lba); + if (entry_lba > (uint64_t) INT64_MAX / found) + return -EBADMSG; + + uint64_t entry_offset = entry_lba * found; + + if (entry_sz < sizeof(GptPartitionEntry) || entry_count == 0 || entry_count > 1024) + return -EBADMSG; + if (entry_sz > SIZE_MAX / entry_count) + return -EBADMSG; + + size_t entries_size = (size_t) entry_sz * entry_count; + _cleanup_free_ void *entries = malloc(entries_size); + if (!entries) + return -ENOMEM; + + n = pread(fd, entries, entries_size, entry_offset); + if (n < 0) + return -errno; + if ((size_t) n < entries_size) + return -EBADMSG; + + *ret_entries = TAKE_PTR(entries); + } + + if (ret_header) + *ret_header = *h; + if (ret_n_entries) + *ret_n_entries = entry_count; + if (ret_entry_size) + *ret_entry_size = entry_sz; + + return found; /* sector size */ +} diff --git a/src/shared/gpt.h b/src/shared/gpt.h index f59f2da29fdcb..18d665441c679 100644 --- a/src/shared/gpt.h +++ b/src/shared/gpt.h @@ -113,3 +113,10 @@ typedef struct { } _packed_ GptHeader; bool gpt_header_has_signature(const GptHeader *p) _pure_; + +ssize_t gpt_probe( + int fd, + GptHeader *ret_header, + void **ret_entries, + uint32_t *ret_n_entries, + uint32_t *ret_entry_size); diff --git a/src/test/test-gpt.c b/src/test/test-gpt.c index 6772d46ef64bd..430f8e07fdd72 100644 --- a/src/test/test-gpt.c +++ b/src/test/test-gpt.c @@ -1,8 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "architecture.h" +#include "fd-util.h" #include "gpt.h" #include "log.h" +#include "memfd-util.h" +#include "memory-util.h" #include "pretty-print.h" #include "strv.h" #include "tests.h" @@ -49,19 +54,19 @@ TEST(verity_mappings) { PartitionDesignator q; q = partition_verity_hash_of(p); - assert_se(q < 0 || partition_verity_hash_to_data(q) == p); + ASSERT_TRUE(q < 0 || partition_verity_hash_to_data(q) == p); q = partition_verity_sig_of(p); - assert_se(q < 0 || partition_verity_sig_to_data(q) == p); + ASSERT_TRUE(q < 0 || partition_verity_sig_to_data(q) == p); q = partition_verity_hash_to_data(p); - assert_se(q < 0 || partition_verity_hash_of(q) == p); + ASSERT_TRUE(q < 0 || partition_verity_hash_of(q) == p); q = partition_verity_sig_to_data(p); - assert_se(q < 0 || partition_verity_sig_of(q) == p); + ASSERT_TRUE(q < 0 || partition_verity_sig_of(q) == p); q = partition_verity_to_data(p); - assert_se(q < 0 || partition_verity_hash_of(q) == p || partition_verity_sig_of(q) == p); + ASSERT_TRUE(q < 0 || partition_verity_hash_of(q) == p || partition_verity_sig_of(q) == p); } } @@ -94,7 +99,7 @@ TEST(override_architecture) { x = gpt_partition_type_override_architecture(x, ARCHITECTURE_ARM64); ASSERT_EQ(x.arch, y.arch); ASSERT_EQ(x.designator, y.designator); - assert_se(sd_id128_equal(x.uuid, y.uuid)); + ASSERT_EQ_ID128(x.uuid, y.uuid); ASSERT_STREQ(x.name, y.name); /* If the partition type does not have an architecture, nothing should change. */ @@ -105,8 +110,148 @@ TEST(override_architecture) { x = gpt_partition_type_override_architecture(x, ARCHITECTURE_ARM64); ASSERT_EQ(x.arch, y.arch); ASSERT_EQ(x.designator, y.designator); - assert_se(sd_id128_equal(x.uuid, y.uuid)); + ASSERT_EQ_ID128(x.uuid, y.uuid); ASSERT_STREQ(x.name, y.name); } +static void make_gpt(int fd, uint32_t sector_size, const GptPartitionEntry *part_entries, size_t n_entries) { + /* Zero-fill enough for header probing (gpt_probe reads 2*4096 = 8KB) */ + static const uint8_t zeros[2 * 4096] = {}; + ASSERT_OK_EQ_ERRNO(pwrite(fd, zeros, sizeof(zeros), 0), (ssize_t) sizeof(zeros)); + + GptHeader h = { + .signature = { 'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T' }, + .revision = htole32(UINT32_C(0x00010000)), + .header_size = htole32(sizeof(GptHeader)), + .my_lba = htole64(1), + .partition_entry_lba = htole64(2), + .number_of_partition_entries = htole32(n_entries), + .size_of_partition_entry = htole32(sizeof(GptPartitionEntry)), + }; + ASSERT_OK_EQ_ERRNO(pwrite(fd, &h, sizeof(h), sector_size), (ssize_t) sizeof(h)); + + if (n_entries > 0) { + size_t entries_size = n_entries * sizeof(GptPartitionEntry); + ASSERT_OK_EQ_ERRNO(pwrite(fd, part_entries, entries_size, 2 * sector_size), (ssize_t) entries_size); + } +} + +TEST(gpt_probe_empty) { + _cleanup_close_ int fd = -EBADF; + + fd = ASSERT_OK(memfd_new("test-gpt-probe")); + ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), (ssize_t) 0); +} + +TEST(gpt_probe_too_short) { + _cleanup_close_ int fd = -EBADF; + static const uint8_t buf[4096] = {}; + + fd = ASSERT_OK(memfd_new("test-gpt-probe")); + ASSERT_OK_EQ_ERRNO(pwrite(fd, buf, sizeof(buf), 0), (ssize_t) sizeof(buf)); + ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), (ssize_t) 0); +} + +TEST(gpt_probe_no_signature) { + _cleanup_close_ int fd = -EBADF; + static const uint8_t buf[2 * 4096] = {}; + + fd = ASSERT_OK(memfd_new("test-gpt-probe")); + ASSERT_OK_EQ_ERRNO(pwrite(fd, buf, sizeof(buf), 0), (ssize_t) sizeof(buf)); + ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), (ssize_t) 0); +} + +TEST(gpt_probe_sector_512) { + _cleanup_close_ int fd = -EBADF; + + const GptPartitionEntry entries[2] = { + { + .unique_partition_guid = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 }, + .starting_lba = htole64(100), + .ending_lba = htole64(200), + }, + { + .unique_partition_guid = { 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20 }, + .starting_lba = htole64(300), + .ending_lba = htole64(400), + }, + }; + + fd = ASSERT_OK(memfd_new("test-gpt-probe")); + make_gpt(fd, 512, entries, 2); + + /* Sector size detection only */ + ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), (ssize_t) 512); + + /* Header return */ + GptHeader h; + ASSERT_OK_EQ(gpt_probe(fd, &h, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), (ssize_t) 512); + ASSERT_EQ(le32toh(h.number_of_partition_entries), 2u); + ASSERT_EQ(le32toh(h.size_of_partition_entry), (uint32_t) sizeof(GptPartitionEntry)); + + /* Full probe with entries */ + _cleanup_free_ void *ret_entries = NULL; + uint32_t n, sz; + ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, &ret_entries, &n, &sz), (ssize_t) 512); + ASSERT_EQ(n, 2u); + ASSERT_EQ(sz, (uint32_t) sizeof(GptPartitionEntry)); + ASSERT_NOT_NULL(ret_entries); + + GptPartitionEntry *e = ret_entries; + ASSERT_EQ(memcmp_nn(e[0].unique_partition_guid, sizeof(e[0].unique_partition_guid), entries[0].unique_partition_guid, sizeof(entries[0].unique_partition_guid)), 0); + ASSERT_EQ(memcmp_nn(e[1].unique_partition_guid, sizeof(e[1].unique_partition_guid), entries[1].unique_partition_guid, sizeof(entries[1].unique_partition_guid)), 0); + ASSERT_EQ(le64toh(e[0].starting_lba), UINT64_C(100)); + ASSERT_EQ(le64toh(e[1].starting_lba), UINT64_C(300)); +} + +TEST(gpt_probe_sector_4096) { + _cleanup_close_ int fd = -EBADF; + + const GptPartitionEntry entry = { + .unique_partition_guid = { 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, + 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99 }, + .starting_lba = htole64(50), + .ending_lba = htole64(100), + }; + + fd = ASSERT_OK(memfd_new("test-gpt-probe")); + make_gpt(fd, 4096, &entry, 1); + + ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), (ssize_t) 4096); + + _cleanup_free_ void *ret_entries = NULL; + uint32_t n, sz; + ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, &ret_entries, &n, &sz), (ssize_t) 4096); + ASSERT_EQ(n, 1u); + + GptPartitionEntry *e = ret_entries; + ASSERT_EQ(memcmp_nn(e[0].unique_partition_guid, sizeof(e[0].unique_partition_guid), entry.unique_partition_guid, sizeof(entry.unique_partition_guid)), 0); + ASSERT_EQ(le64toh(e[0].starting_lba), UINT64_C(50)); +} + +TEST(gpt_probe_ambiguous) { + _cleanup_close_ int fd = -EBADF; + + const GptPartitionEntry entry = {}; + + fd = ASSERT_OK(memfd_new("test-gpt-probe")); + make_gpt(fd, 512, &entry, 1); + + /* Place a second valid header at offset 4096 */ + GptHeader h2 = { + .signature = { 'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T' }, + .revision = htole32(UINT32_C(0x00010000)), + .header_size = htole32(sizeof(GptHeader)), + .my_lba = htole64(1), + .partition_entry_lba = htole64(2), + .number_of_partition_entries = htole32(1), + .size_of_partition_entry = htole32(sizeof(GptPartitionEntry)), + }; + ASSERT_OK_EQ_ERRNO(pwrite(fd, &h2, sizeof(h2), 4096), (ssize_t) sizeof(h2)); + + ASSERT_ERROR(gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), ENOTUNIQ); +} + DEFINE_TEST_MAIN(LOG_INFO); From 353e220a95467532332ec31a90d9e3a6271691f4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 2 Apr 2026 14:06:43 +0200 Subject: [PATCH 0697/1296] update TODO --- TODO | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/TODO b/TODO index 687d15b5ba83b..09eeda07ef958 100644 --- a/TODO +++ b/TODO @@ -120,6 +120,15 @@ Deprecations and removals: Features: +* sd-varlink: add fully async modes of the protocol upgrade stuff + +* sd-varlink: optimize the read-byte-by-byte mode in case upgrade mode is + enabled, via recvmsg() with MSG_SEEK: first read non-destrictively, look for + NUL byte, and only then flush out + +* repart: maybe remove iso9660/eltorito superblock from disk when booting via + gpt, if there is one. + * crypttab/gpt-auto-generator: allow explicit control over which unlock mechs to permit, and maybe have a global headless kernel cmdline option From f9339b90877ff807bf2ed1dbeec08e6141ef765a Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 1 Apr 2026 17:11:05 +0200 Subject: [PATCH 0698/1296] stub: Drop needless enum value assignment Follow up for 45e4df9a33 --- src/boot/proto/pci-io.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/boot/proto/pci-io.h b/src/boot/proto/pci-io.h index d05f0a683ce9e..2e385d4650a47 100644 --- a/src/boot/proto/pci-io.h +++ b/src/boot/proto/pci-io.h @@ -7,7 +7,7 @@ GUID_DEF(0x4cf5b200, 0x68b8, 0x4ca5, 0x9e, 0xec, 0xb2, 0x3e, 0x3f, 0x50, 0x02, 0x9a) typedef enum { - EfiPciIoWidthUint8 = 0, + EfiPciIoWidthUint8, EfiPciIoWidthUint16, EfiPciIoWidthUint32, EfiPciIoWidthUint64, From 1653ac8db5bb79ebfdb902f18b6ba303ad4a18a6 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 1 Apr 2026 17:11:44 +0200 Subject: [PATCH 0699/1296] stub: Do a single PCI read to get the vendor and device ID --- src/boot/stub.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/boot/stub.c b/src/boot/stub.c index 6b5ae0a68786c..02621b68bb928 100644 --- a/src/boot/stub.c +++ b/src/boot/stub.c @@ -1276,15 +1276,14 @@ static bool has_virtio_console_pci_device(void) { if (BS->HandleProtocol(handles[i], MAKE_GUID_PTR(EFI_PCI_IO_PROTOCOL), (void **) &pci_io) != EFI_SUCCESS) continue; - uint16_t vendor_id = 0, device_id = 0; - if (pci_io->Pci.Read(pci_io, EfiPciIoWidthUint16, 0x00, 1, &vendor_id) != EFI_SUCCESS) - continue; - if (pci_io->Pci.Read(pci_io, EfiPciIoWidthUint16, 0x02, 1, &device_id) != EFI_SUCCESS) + /* Read PCI vendor ID and device ID (at offsets 0x00 and 0x02 in PCI config space) */ + uint16_t pci_id[2] = {}; + if (pci_io->Pci.Read(pci_io, EfiPciIoWidthUint16, /* offset= */ 0x00, /* count= */ 2, pci_id) != EFI_SUCCESS) continue; - log_debug("PCI device %zu: vendor=%04x device=%04x", i, vendor_id, device_id); + log_debug("PCI device %zu: vendor=%04x device=%04x", i, pci_id[0], pci_id[1]); - if (vendor_id == PCI_VENDOR_ID_REDHAT && device_id == PCI_DEVICE_ID_VIRTIO_CONSOLE) + if (pci_id[0] == PCI_VENDOR_ID_REDHAT && pci_id[1] == PCI_DEVICE_ID_VIRTIO_CONSOLE) n_virtio_console++; if (n_virtio_console > 1) { From e306f4d1234d55e23f55048167d7554d15d31fbb Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 1 Apr 2026 18:24:01 +0000 Subject: [PATCH 0700/1296] stub: Determine the correct serial console from the ACPI device path Instead of requiring exactly one serial device and assuming ttyS0, extract the COM port index from the ACPI device path and use the uart I/O port address format for the console= kernel argument. On x86, the ACPI UID for PNP0501 (16550 UART) maps directly to the COM port number: UID 0 = COM1 (0x3F8), UID 1 = COM2 (0x2F8), etc. The I/O port addresses are fixed in the kernel (see arch/x86/include/asm/serial.h). Using the console=uart,io, format (see Documentation/admin-guide/kernel-parameters.txt) addresses the UART by I/O port directly rather than relying on ttyS naming, and also provides early console output before the full serial driver loads. Restrict the entire serial console auto-detection to x86. On non-x86 (e.g. ARM with PL011 UARTs), displays may be available without GOP (e.g. simple-framebuffer via device tree), serial device indices are assigned dynamically during probe rather than being fixed to I/O port addresses, and the kernel has its own console auto-detection via DT stdout-path. When ConOut has no device path (ConSplitter), all text output handles are enumerated. If multiple handles have PNP0501 UART nodes with different UIDs, bail out rather than guessing. Add ACPI_DP device path subtype, ACPI_HID_DEVICE_PATH struct, and EISA_PNP_ID() macro to device-path.h for parsing ACPI device path nodes. Remove MSG_UART_DP, device_path_has_uart(), count_serial_devices() and proto/serial-io.h (no longer needed). Move all the console logic to console.c as well. --- src/boot/console.c | 253 +++++++++++++++++++++++++++++++++++ src/boot/console.h | 1 + src/boot/proto/device-path.h | 12 +- src/boot/proto/serial-io.h | 7 - src/boot/stub.c | 221 +----------------------------- 5 files changed, 266 insertions(+), 228 deletions(-) delete mode 100644 src/boot/proto/serial-io.h diff --git a/src/boot/console.c b/src/boot/console.c index 81a5641d40579..8dcd986aa4f15 100644 --- a/src/boot/console.c +++ b/src/boot/console.c @@ -1,8 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "console.h" +#include "device-path-util.h" #include "efi-log.h" +#include "efi-string.h" #include "proto/graphics-output.h" +#include "proto/pci-io.h" +#include "string-util-fundamental.h" +#include "util.h" #define SYSTEM_FONT_WIDTH 8 #define SYSTEM_FONT_HEIGHT 19 @@ -347,3 +352,251 @@ EFI_STATUS console_query_mode(size_t *x_max, size_t *y_max) { return err; } + +static bool has_virtio_console_pci_device(void) { + _cleanup_free_ EFI_HANDLE *handles = NULL; + size_t n_handles = 0; + + EFI_STATUS err = BS->LocateHandleBuffer( + ByProtocol, + MAKE_GUID_PTR(EFI_PCI_IO_PROTOCOL), + NULL, + &n_handles, + &handles); + if (err != EFI_SUCCESS) { + log_debug_status(err, "Failed to locate PCI I/O protocol handles, assuming no VirtIO console: %m"); + return false; + } + + if (n_handles == 0) { + log_debug("No PCI devices found, not scanning for VirtIO console."); + return false; + } + + log_debug("Found %zu PCI devices, scanning for VirtIO console...", n_handles); + + size_t n_virtio_console = 0; + + for (size_t i = 0; i < n_handles; i++) { + EFI_PCI_IO_PROTOCOL *pci_io = NULL; + + if (BS->HandleProtocol(handles[i], MAKE_GUID_PTR(EFI_PCI_IO_PROTOCOL), (void **) &pci_io) != EFI_SUCCESS) + continue; + + /* Read PCI vendor ID and device ID (at offsets 0x00 and 0x02 in PCI config space) */ + uint16_t pci_id[2] = {}; + if (pci_io->Pci.Read(pci_io, EfiPciIoWidthUint16, /* offset= */ 0x00, /* count= */ 2, pci_id) != EFI_SUCCESS) + continue; + + log_debug("PCI device %zu: vendor=%04x device=%04x", i, pci_id[0], pci_id[1]); + + if (pci_id[0] == PCI_VENDOR_ID_REDHAT && pci_id[1] == PCI_DEVICE_ID_VIRTIO_CONSOLE) + n_virtio_console++; + + if (n_virtio_console > 1) { + log_debug("There is more than one VirtIO console PCI device, cannot determine which one is the console."); + return false; + } + } + + if (n_virtio_console == 0) { + log_debug("No VirtIO console PCI device found."); + return false; + } + + log_debug("Found exactly one VirtIO console PCI device."); + return true; +} + +static bool has_graphics_output(void) { + EFI_GRAPHICS_OUTPUT_PROTOCOL *gop = NULL; + EFI_STATUS err; + + err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_GRAPHICS_OUTPUT_PROTOCOL), NULL, (void **) &gop); + if (err != EFI_SUCCESS) { + log_debug_status(err, "No EFI Graphics Output Protocol found: %m"); + return false; + } + + log_debug("EFI Graphics Output Protocol found."); + return true; +} + +#if defined(__i386__) || defined(__x86_64__) + +/* Walk the device path looking for a UART console and determine the COM port index from the + * ACPI device path node. On x86, the Linux kernel assigns fixed ttyS indices based on I/O port + * addresses (see arch/x86/include/asm/serial.h): + * + * ttyS0=0x3F8, ttyS1=0x2F8, ttyS2=0x3E8, ttyS3=0x2E8 + * + * On standard PC firmware, the ACPI UID for PNP0501 (16550 UART) maps directly to the COM port + * index: UID 0 = COM1 (0x3F8) = ttyS0, UID 1 = COM2 (0x2F8) = ttyS1, etc. + * + * Returns EFI_SUCCESS and sets *ret_index on success, or EFI_NOT_FOUND if no PNP0501 UART + * was found. */ +static EFI_STATUS device_path_get_uart_index(const EFI_DEVICE_PATH *dp, uint32_t *ret_index) { + assert(ret_index); + + for (const EFI_DEVICE_PATH *node = dp; !device_path_is_end(node); node = device_path_next_node(node)) + if (node->Type == ACPI_DEVICE_PATH && + node->SubType == ACPI_DP && + node->Length >= sizeof(ACPI_HID_DEVICE_PATH)) { + const ACPI_HID_DEVICE_PATH *acpi = (const ACPI_HID_DEVICE_PATH *) node; + if (acpi->HID == EISA_PNP_ID(0x0501)) { + *ret_index = acpi->UID; + return EFI_SUCCESS; + } + } + + return EFI_NOT_FOUND; +} + +/* Check if the console output is a serial UART. If so, determine the COM port index from the + * ACPI device path so we can pass the correct console= device to the kernel. */ +static EFI_STATUS find_serial_console_index(uint32_t *ret_index) { + assert(ret_index); + + /* First try the ConOut handle directly. */ + EFI_DEVICE_PATH *dp = NULL; + if (BS->HandleProtocol(ST->ConsoleOutHandle, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &dp) == EFI_SUCCESS) { + _cleanup_free_ char16_t *dp_str = NULL; + (void) device_path_to_str(dp, &dp_str); + log_debug("ConOut device path: %ls", strempty(dp_str)); + + if (device_path_get_uart_index(dp, ret_index) == EFI_SUCCESS) { + log_debug("ConOut is a serial console (port index %u).", *ret_index); + return EFI_SUCCESS; + } + + log_debug("ConOut device path does not contain a PNP0501 UART node."); + return EFI_NOT_FOUND; + } + + /* ConOut handle has no device path (e.g. ConSplitter virtual handle). Enumerate all + * text output handles and check if any of them is a serial console. */ + log_debug("ConOut handle has no device path, enumerating text output handles..."); + + _cleanup_free_ EFI_HANDLE *handles = NULL; + size_t n_handles = 0; + if (BS->LocateHandleBuffer( + ByProtocol, + MAKE_GUID_PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL), + NULL, + &n_handles, + &handles) != EFI_SUCCESS) { + log_debug("Failed to enumerate text output handles."); + return EFI_NOT_FOUND; + } + + bool found = false; + + for (size_t i = 0; i < n_handles; i++) { + dp = NULL; + if (BS->HandleProtocol(handles[i], MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &dp) != EFI_SUCCESS) + continue; + + _cleanup_free_ char16_t *dp_str = NULL; + (void) device_path_to_str(dp, &dp_str); + log_debug("Text output handle %zu device path: %ls", i, strempty(dp_str)); + + uint32_t index; + if (device_path_get_uart_index(dp, &index) != EFI_SUCCESS) + continue; + + log_debug("Text output handle %zu is a serial console (port index %u).", i, index); + + if (found && *ret_index != index) { + log_debug("Multiple serial consoles with different port indices found, cannot determine which one to use."); + return EFI_NOT_FOUND; + } + + *ret_index = index; + found = true; + } + + if (!found) { + log_debug("No serial console found among text output handles."); + return EFI_NOT_FOUND; + } + + return EFI_SUCCESS; +} + +static const char16_t *serial_console_arg(uint32_t index) { + /* Use the uart I/O port address format (see Documentation/admin-guide/kernel-parameters.txt) + * instead of ttyS names. This addresses the 8250/16550 UART at the specified I/O port + * directly and switches to the matching ttyS device later. The I/O port addresses for + * the standard COM ports are fixed (see arch/x86/include/asm/serial.h), and the ACPI UID + * for PNP0501 maps directly to the COM port index. */ + static const char16_t *const table[] = { + u"console=uart,io,0x3f8", /* COM1 */ + u"console=uart,io,0x2f8", /* COM2 */ + u"console=uart,io,0x3e8", /* COM3 */ + u"console=uart,io,0x2e8", /* COM4 */ + }; + + if (index >= ELEMENTSOF(table)) + return NULL; + + return table[index]; +} + +#endif /* __i386__ || __x86_64__ */ + +/* If there's no console= in the command line yet, try to detect the appropriate console device. + * + * Detection order: + * 1. If exactly one VirtIO console PCI device exists -> console=hvc0 + * 2. If there's graphical output (GOP) -> don't add console=, the kernel defaults are fine + * 3. On x86, if exactly one serial console exists -> console=uart,io, + * 4. Otherwise -> don't add console=, let the user handle it + * + * VirtIO console takes priority since it's explicitly configured by the VMM. Graphics is + * checked before serial to avoid accidentally redirecting output away from a graphical + * console by adding a serial console= argument. + * + * Serial console auto-detection is restricted to x86 where ACPI PNP0501 UIDs map to fixed + * I/O port addresses for 8250/16550 UARTs. On non-x86 (e.g. ARM), serial device indices are + * assigned dynamically, and the kernel has its own console auto-detection mechanisms + * (DT stdout-path, etc.). + * + * Not TPM-measured because the value is deterministically derived from firmware-reported + * hardware state (PCI device enumeration, GOP presence, serial device paths). */ +void cmdline_append_console(char16_t **cmdline) { + assert(cmdline); + + if (*cmdline && (efi_fnmatch(u"console=*", *cmdline) || efi_fnmatch(u"* console=*", *cmdline))) { + log_debug("Kernel command line already contains console=, not adding one."); + return; + } + + const char16_t *console_arg = NULL; + + if (has_virtio_console_pci_device()) + console_arg = u"console=hvc0"; + else if (has_graphics_output()) { + log_debug("Graphical output available, not adding console= to kernel command line."); + return; + } +#if defined(__i386__) || defined(__x86_64__) + else { + uint32_t serial_index; + if (find_serial_console_index(&serial_index) == EFI_SUCCESS) + console_arg = serial_console_arg(serial_index); + } +#endif + + if (!console_arg) { + log_debug("Cannot determine console type, not adding console= to kernel command line."); + return; + } + + log_debug("Appending %ls to kernel command line.", console_arg); + + _cleanup_free_ char16_t *old = TAKE_PTR(*cmdline); + if (isempty(old)) + *cmdline = xstrdup16(console_arg); + else + *cmdline = xasprintf("%ls %ls", old, console_arg); +} diff --git a/src/boot/console.h b/src/boot/console.h index 4d0d1364d8fa0..3a2bc6391cdce 100644 --- a/src/boot/console.h +++ b/src/boot/console.h @@ -36,3 +36,4 @@ EFI_STATUS console_key_read(uint64_t *ret_key, uint64_t timeout_usec); EFI_STATUS console_set_mode(int64_t mode); EFI_STATUS console_query_mode(size_t *x_max, size_t *y_max); EFI_STATUS query_screen_resolution(uint32_t *ret_width, uint32_t *ret_height); +void cmdline_append_console(char16_t **cmdline); diff --git a/src/boot/proto/device-path.h b/src/boot/proto/device-path.h index 531ff3d003bdc..d81c0e1f8dd17 100644 --- a/src/boot/proto/device-path.h +++ b/src/boot/proto/device-path.h @@ -27,13 +27,14 @@ enum { HW_MEMMAP_DP = 0x03, + ACPI_DP = 0x01, + MEDIA_HARDDRIVE_DP = 0x01, MEDIA_VENDOR_DP = 0x03, MEDIA_FILEPATH_DP = 0x04, MEDIA_PIWG_FW_FILE_DP = 0x06, MEDIA_PIWG_FW_VOL_DP = 0x07, - MSG_UART_DP = 0x0e, MSG_URI_DP = 24, }; @@ -48,6 +49,15 @@ typedef struct { EFI_GUID Guid; } _packed_ VENDOR_DEVICE_PATH; +/* EISA PNP ID encoding: compressed 3-letter vendor + 16-bit product ID. */ +#define EISA_PNP_ID(Id) ((uint32_t) (((Id) << 16) | 0x41D0)) + +typedef struct { + EFI_DEVICE_PATH Header; + uint32_t HID; + uint32_t UID; +} _packed_ ACPI_HID_DEVICE_PATH; + typedef struct { EFI_DEVICE_PATH Header; uint32_t MemoryType; diff --git a/src/boot/proto/serial-io.h b/src/boot/proto/serial-io.h deleted file mode 100644 index 98690c108c288..0000000000000 --- a/src/boot/proto/serial-io.h +++ /dev/null @@ -1,7 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include "efi.h" - -#define EFI_SERIAL_IO_PROTOCOL_GUID \ - GUID_DEF(0xbb25cf6f, 0xf1d4, 0x11d2, 0x9a, 0x0c, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0xfd) diff --git a/src/boot/stub.c b/src/boot/stub.c index 02621b68bb928..3c8318a2adfe4 100644 --- a/src/boot/stub.c +++ b/src/boot/stub.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "boot-secret.h" +#include "console.h" #include "cpio.h" #include "device-path-util.h" #include "devicetree.h" @@ -15,9 +16,6 @@ #include "memory-util-fundamental.h" #include "part-discovery.h" #include "pe.h" -#include "proto/graphics-output.h" -#include "proto/pci-io.h" -#include "proto/serial-io.h" /* IWYU pragma: keep */ #include "proto/shell-parameters.h" #include "random-seed.h" #include "sbat.h" @@ -1246,219 +1244,6 @@ static void measure_profile(unsigned profile, int *parameters_measured) { combine_measured_flag(parameters_measured, m); } -static bool has_virtio_console_pci_device(void) { - _cleanup_free_ EFI_HANDLE *handles = NULL; - size_t n_handles = 0; - - EFI_STATUS err = BS->LocateHandleBuffer( - ByProtocol, - MAKE_GUID_PTR(EFI_PCI_IO_PROTOCOL), - NULL, - &n_handles, - &handles); - if (err != EFI_SUCCESS) { - log_debug_status(err, "Failed to locate PCI I/O protocol handles, assuming no VirtIO console: %m"); - return false; - } - - if (n_handles == 0) { - log_debug("No PCI devices found, not scanning for VirtIO console."); - return false; - } - - log_debug("Found %zu PCI devices, scanning for VirtIO console...", n_handles); - - size_t n_virtio_console = 0; - - for (size_t i = 0; i < n_handles; i++) { - EFI_PCI_IO_PROTOCOL *pci_io = NULL; - - if (BS->HandleProtocol(handles[i], MAKE_GUID_PTR(EFI_PCI_IO_PROTOCOL), (void **) &pci_io) != EFI_SUCCESS) - continue; - - /* Read PCI vendor ID and device ID (at offsets 0x00 and 0x02 in PCI config space) */ - uint16_t pci_id[2] = {}; - if (pci_io->Pci.Read(pci_io, EfiPciIoWidthUint16, /* offset= */ 0x00, /* count= */ 2, pci_id) != EFI_SUCCESS) - continue; - - log_debug("PCI device %zu: vendor=%04x device=%04x", i, pci_id[0], pci_id[1]); - - if (pci_id[0] == PCI_VENDOR_ID_REDHAT && pci_id[1] == PCI_DEVICE_ID_VIRTIO_CONSOLE) - n_virtio_console++; - - if (n_virtio_console > 1) { - log_debug("There is more than one VirtIO console PCI device, cannot determine which one is the console."); - return false; - } - } - - if (n_virtio_console == 0) { - log_debug("No VirtIO console PCI device found."); - return false; - } - - log_debug("Found exactly one VirtIO console PCI device."); - return true; -} - -static bool device_path_has_uart(const EFI_DEVICE_PATH *dp) { - for (const EFI_DEVICE_PATH *node = dp; !device_path_is_end(node); node = device_path_next_node(node)) - if (node->Type == MESSAGING_DEVICE_PATH && node->SubType == MSG_UART_DP) - return true; - - return false; -} - -static size_t count_serial_devices(void) { - _cleanup_free_ EFI_HANDLE *handles = NULL; - size_t n_handles = 0; - - if (BS->LocateHandleBuffer( - ByProtocol, - MAKE_GUID_PTR(EFI_SERIAL_IO_PROTOCOL), - NULL, - &n_handles, - &handles) != EFI_SUCCESS) - return 0; - - log_debug("Found %zu serial I/O devices in total.", n_handles); - return n_handles; -} - -static bool has_single_serial_console(void) { - /* Even if we find exactly one serial console, we can only confidently map it to ttyS0 - * if there's only one serial device in the entire system. With multiple UARTs, the - * kernel assigns ttyS indices based on its own discovery order, so the console UART - * might end up as ttyS1 or higher. */ - size_t n_serial_devices = count_serial_devices(); - if (n_serial_devices == 0) { - log_debug("No serial I/O devices found."); - return false; - } - if (n_serial_devices > 1) { - log_debug("Found %zu serial I/O devices, cannot determine ttyS index.", n_serial_devices); - return false; - } - - /* Exactly one serial device in the system. Verify it's actually used as a console - * by checking if ConOut or any text output handle has a UART device path. */ - - /* First try the ConOut handle directly */ - EFI_DEVICE_PATH *dp = NULL; - if (BS->HandleProtocol(ST->ConsoleOutHandle, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &dp) == EFI_SUCCESS) { - _cleanup_free_ char16_t *dp_str = NULL; - (void) device_path_to_str(dp, &dp_str); - log_debug("ConOut device path: %ls", strempty(dp_str)); - - if (device_path_has_uart(dp)) { - log_debug("ConOut device path contains UART node."); - return true; - } - - log_debug("ConOut device path does not contain UART node."); - return false; - } - - /* ConOut handle has no device path (e.g. ConSplitter virtual handle). Enumerate all - * text output handles and check if any of them is a serial console. */ - log_debug("ConOut handle has no device path, enumerating text output handles..."); - - _cleanup_free_ EFI_HANDLE *handles = NULL; - size_t n_handles = 0; - if (BS->LocateHandleBuffer( - ByProtocol, - MAKE_GUID_PTR(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL), - NULL, - &n_handles, - &handles) != EFI_SUCCESS) { - log_debug("Failed to enumerate text output handles."); - return false; - } - - for (size_t i = 0; i < n_handles; i++) { - dp = NULL; - if (BS->HandleProtocol(handles[i], MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &dp) != EFI_SUCCESS) - continue; - - _cleanup_free_ char16_t *dp_str = NULL; - (void) device_path_to_str(dp, &dp_str); - log_debug("Text output handle %zu device path: %ls", i, strempty(dp_str)); - - if (device_path_has_uart(dp)) { - log_debug("Text output handle %zu is a serial console.", i); - return true; - } - } - - log_debug("No serial console found among text output handles."); - return false; -} - -static bool has_graphics_output(void) { - EFI_GRAPHICS_OUTPUT_PROTOCOL *gop = NULL; - EFI_STATUS err; - - err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_GRAPHICS_OUTPUT_PROTOCOL), NULL, (void **) &gop); - if (err != EFI_SUCCESS) { - log_debug_status(err, "No EFI Graphics Output Protocol found: %m"); - return false; - } - - log_debug("EFI Graphics Output Protocol found."); - return true; -} - -static const char16_t *serial_console_arg(void) { -#if defined(__arm__) || defined(__aarch64__) - return u"console=ttyAMA0"; -#else - return u"console=ttyS0"; -#endif -} - -/* If there's no console= in the command line yet, try to detect the appropriate console device. - * - * Detection order: - * 1. If exactly one VirtIO console PCI device exists → console=hvc0 - * 2. If there's graphical output (GOP) → don't add console=, the kernel defaults are fine - * 3. If exactly one serial console exists → arch-specific serial (ttyS0, ttyAMA0, etc.) - * 4. Otherwise → don't add console=, let the user handle it - * - * VirtIO console takes priority since it's explicitly configured by the VMM. Graphics is - * checked before serial to avoid accidentally redirecting output away from a graphical - * console by adding a serial console= argument. */ -static void cmdline_append_console(char16_t **cmdline) { - assert(cmdline); - - if (*cmdline && (efi_fnmatch(u"console=*", *cmdline) || efi_fnmatch(u"* console=*", *cmdline))) { - log_debug("Kernel command line already contains console=, not adding one."); - return; - } - - const char16_t *console_arg = NULL; - - if (has_virtio_console_pci_device()) - console_arg = u"console=hvc0"; - else if (has_graphics_output()) { - log_debug("Graphical output available, not adding console= to kernel command line."); - return; - } else if (has_single_serial_console()) - console_arg = serial_console_arg(); - - if (!console_arg) { - log_debug("Cannot determine console type, not adding console= to kernel command line."); - return; - } - - log_debug("Appending %ls to kernel command line.", console_arg); - - _cleanup_free_ char16_t *old = TAKE_PTR(*cmdline); - if (isempty(old)) - *cmdline = xstrdup16(console_arg); - else - *cmdline = xasprintf("%ls %ls", old, console_arg); -} - static EFI_STATUS run(EFI_HANDLE image) { int sections_measured = -1, parameters_measured = -1, sysext_measured = -1, confext_measured = -1; _cleanup_(devicetree_cleanup) struct devicetree_state dt_state = {}; @@ -1521,10 +1306,6 @@ static EFI_STATUS run(EFI_HANDLE image) { cmdline_append_and_measure_addons(cmdline_addons, &cmdline, ¶meters_measured); cmdline_append_and_measure_smbios(&cmdline, ¶meters_measured); - /* Console auto-detection is intentionally not TPM-measured. The value is deterministically - * derived from firmware-reported hardware state (PCI device enumeration, GOP presence, serial - * device paths), so it doesn't represent an independent input that could be manipulated - * without also changing the firmware environment that TPM already captures. */ cmdline_append_console(&cmdline); export_common_variables(loaded_image); From b68898530ea1ef356053dad533d4b01d2bb11f61 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 27 Mar 2026 14:23:34 +0100 Subject: [PATCH 0701/1296] ptyfwd: Add transparent flag --- src/shared/ptyfwd.c | 29 ++++++++++++++++------------- src/shared/ptyfwd.h | 3 +++ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/shared/ptyfwd.c b/src/shared/ptyfwd.c index 2e8d77dee1c43..88cfd596d0508 100644 --- a/src/shared/ptyfwd.c +++ b/src/shared/ptyfwd.c @@ -669,19 +669,22 @@ static int do_shovel(PTYForward *f) { f->stdin_event_source = sd_event_source_unref(f->stdin_event_source); } else { - /* Check if ^] has been pressed three times within one second. If we get this we quite - * immediately. */ - RequestOperation q = look_for_escape(f, f->in_buffer + f->in_buffer_full, k); - f->in_buffer_full += (size_t) k; - if (q < 0) - return q; - if (q == REQUEST_EXIT) - return -ECANCELED; - if (q >= REQUEST_HOTKEY_A && q <= REQUEST_HOTKEY_Z && f->hotkey_handler) { - r = f->hotkey_handler(f, q - REQUEST_HOTKEY_BASE, f->hotkey_userdata); - if (r < 0) - return r; - } + if (!FLAGS_SET(f->flags, PTY_FORWARD_TRANSPARENT)) { + /* Check if ^] has been pressed three times within one second. If we get this we quit + * immediately. */ + RequestOperation q = look_for_escape(f, f->in_buffer + f->in_buffer_full, k); + f->in_buffer_full += (size_t) k; + if (q < 0) + return q; + if (q == REQUEST_EXIT) + return -ECANCELED; + if (q >= REQUEST_HOTKEY_A && q <= REQUEST_HOTKEY_Z && f->hotkey_handler) { + r = f->hotkey_handler(f, q - REQUEST_HOTKEY_BASE, f->hotkey_userdata); + if (r < 0) + return r; + } + } else + f->in_buffer_full += (size_t) k; } did_something = true; diff --git a/src/shared/ptyfwd.h b/src/shared/ptyfwd.h index 1c1246f37f163..f92676dabe3e8 100644 --- a/src/shared/ptyfwd.h +++ b/src/shared/ptyfwd.h @@ -17,6 +17,9 @@ typedef enum PTYForwardFlags { /* Don't tint the background, or set window title */ PTY_FORWARD_DUMB_TERMINAL = 1 << 3, + + /* Don't interpret escape sequences (^] exit, hotkeys), just forward everything as-is */ + PTY_FORWARD_TRANSPARENT = 1 << 4, } PTYForwardFlags; typedef int (*PTYForwardHangupHandler)(PTYForward *f, int rcode, void *userdata); From 1b7a42178a7c16d187e105a4f9e524d6ffec369d Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 27 Mar 2026 13:38:47 +0000 Subject: [PATCH 0702/1296] vmspawn: use PTY for native console to avoid QEMU O_NONBLOCK issue QEMU's stdio chardev sets O_NONBLOCK on both stdin and stdout (see chardev/char-stdio.c [1] and chardev/char-fd.c [2]). Since forked processes share file descriptions, and on a terminal all three stdio fds typically reference the same file description, this affects our own stdio too. Avoid this by using a PTY with chardev serial instead of chardev stdio for native console mode, matching the approach already used for interactive and read-only modes. The PTY forwarder shovels bytes transparently between our stdio and QEMU's PTY using the new PTY_FORWARD_DUMB_TERMINAL and PTY_FORWARD_TRANSPARENT flags, which disable terminal decoration (background tinting, window title, OSC context) and escape sequence handling (Ctrl-] exit, hotkeys) respectively. The chardev is configured with mux=on so the QEMU monitor remains accessible via Ctrl-a c. Also dedup CONSOLE_NATIVE, CONSOLE_READ_ONLY, and CONSOLE_INTERACTIVE handling by using fallthrough, with the only differences being the ptyfwd flags, mux setting, and monitor section. [1] https://gitlab.com/qemu-project/qemu/-/blob/master/chardev/char-stdio.c [2] https://gitlab.com/qemu-project/qemu/-/blob/master/chardev/char-fd.c Co-developed-by: Claude Opus 4.6 --- src/vmspawn/vmspawn.c | 91 +++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 50 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 0de314ac183ab..8ad3fb61645b8 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -2557,12 +2557,22 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { PTYForwardFlags ptyfwd_flags = 0; switch (arg_console_mode) { + case CONSOLE_NATIVE: + /* Use a PTY instead of chardev stdio to prevent QEMU from setting O_NONBLOCK on + * our stdio file descriptions (see qemu's chardev/char-stdio.c and char-fd.c). + * Use PTY_FORWARD_DUMB_TERMINAL|PTY_FORWARD_TRANSPARENT so the forwarder just + * shovels bytes without any terminal manipulation or escape sequence handling. */ + ptyfwd_flags |= PTY_FORWARD_DUMB_TERMINAL|PTY_FORWARD_TRANSPARENT; + + _fallthrough_; + case CONSOLE_READ_ONLY: - ptyfwd_flags |= PTY_FORWARD_READ_ONLY; + if (arg_console_mode == CONSOLE_READ_ONLY) + ptyfwd_flags |= PTY_FORWARD_READ_ONLY; _fallthrough_; - case CONSOLE_INTERACTIVE: { + case CONSOLE_INTERACTIVE: { _cleanup_free_ char *pty_path = NULL; master = openpt_allocate(O_RDWR|O_NONBLOCK, &pty_path); @@ -2578,9 +2588,11 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (r < 0) return r; + /* Enable mux for native console so the QEMU monitor is accessible via Ctrl-a c */ r = qemu_config_section(config_file, "chardev", "console", "backend", "serial", - "path", pty_path); + "path", pty_path, + "mux", on_off(arg_console_mode == CONSOLE_NATIVE)); if (r < 0) return r; @@ -2590,6 +2602,13 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (r < 0) return r; + if (arg_console_mode == CONSOLE_NATIVE) { + r = qemu_config_section(config_file, "mon", "mon0", + "chardev", "console"); + if (r < 0) + return r; + } + break; } @@ -2620,36 +2639,6 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { break; - case CONSOLE_NATIVE: - r = strv_extend_many(&cmdline, "-nographic", "-nodefaults"); - if (r < 0) - return log_oom(); - - r = qemu_config_section(config_file, "chardev", "console", - "backend", "stdio", - "mux", "on", - "signal", "off"); - if (r < 0) - return r; - - r = qemu_config_section(config_file, "device", "vmspawn-virtio-serial-pci", - "driver", "virtio-serial-pci"); - if (r < 0) - return r; - - r = qemu_config_section(config_file, "device", "virtconsole0", - "driver", "virtconsole", - "chardev", "console"); - if (r < 0) - return r; - - r = qemu_config_section(config_file, "mon", "mon0", - "chardev", "console"); - if (r < 0) - return r; - - break; - case CONSOLE_HEADLESS: r = strv_extend_many(&cmdline, "-nographic", "-nodefaults"); if (r < 0) @@ -3533,29 +3522,31 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { _cleanup_(osc_context_closep) sd_id128_t osc_context_id = SD_ID128_NULL; _cleanup_(pty_forward_freep) PTYForward *forward = NULL; if (master >= 0) { - if (!terminal_is_dumb()) { - r = osc_context_open_vm(arg_machine, /* ret_seq= */ NULL, &osc_context_id); - if (r < 0) - return r; - } - r = pty_forward_new(event, master, ptyfwd_flags, &forward); if (r < 0) return log_error_errno(r, "Failed to create PTY forwarder: %m"); - if (!arg_background) { - _cleanup_free_ char *bg = NULL; + if (!FLAGS_SET(ptyfwd_flags, PTY_FORWARD_DUMB_TERMINAL)) { + if (!terminal_is_dumb()) { + r = osc_context_open_vm(arg_machine, /* ret_seq= */ NULL, &osc_context_id); + if (r < 0) + return r; + } - r = terminal_tint_color(130 /* green */, &bg); - if (r < 0) - log_debug_errno(r, "Failed to determine terminal background color, not tinting."); - else - (void) pty_forward_set_background_color(forward, bg); - } else if (!isempty(arg_background)) - (void) pty_forward_set_background_color(forward, arg_background); + if (!arg_background) { + _cleanup_free_ char *bg = NULL; - (void) pty_forward_set_window_title(forward, GLYPH_GREEN_CIRCLE, /* hostname= */ NULL, - STRV_MAKE("Virtual Machine", arg_machine)); + r = terminal_tint_color(130 /* green */, &bg); + if (r < 0) + log_debug_errno(r, "Failed to determine terminal background color, not tinting."); + else + (void) pty_forward_set_background_color(forward, bg); + } else if (!isempty(arg_background)) + (void) pty_forward_set_background_color(forward, arg_background); + + (void) pty_forward_set_window_title(forward, GLYPH_GREEN_CIRCLE, /* hostname= */ NULL, + STRV_MAKE("Virtual Machine", arg_machine)); + } } r = sd_event_loop(event); From a77c7a8224447890a304bd857f412c8103f217f1 Mon Sep 17 00:00:00 2001 From: Chris Down Date: Fri, 20 Mar 2026 17:48:49 +0800 Subject: [PATCH 0703/1296] core: Prevent corrupting units from stale alias state on daemon-reload During daemon-reload (or daemon-reexec), when a unit becomes an alias to another unit, deserialising the alias's stale serialised state can corrupt the canonical unit's live runtime state. Consider this scenario: 1. Before reload: - a.service is running - b.service was stopped earlier and is dead - Both exist as independent units 2. User creates an alias to migrate from b -> a: - `ln -s /run/systemd/system/a.service /etc/systemd/system/b.service` 3. daemon-reload triggers serialisation. State file contains both units: - a.service -> state=running, cgroup=/system.slice/a.service, PID=1234, ... - b.service -> state=dead, cgroup=(empty), no PIDs, ... 4. During deserialisation: - Processes a.service: loads Unit A, deserialises -> state=RUNNING - Processes b.service: manager_load_unit() detects symlink, returns Unit A - unit_deserialize_state(Unit A, ...) overwrites with b's dead state 5. The result is that: - Unit A incorrectly shows state=dead despite PID 1234 still running - If a.service has Upholds= dependents, catch-up logic sees a.service should be running but is dead - systemd starts a.service again -> PID 5678 - Two instances run: PID 1234 (left-over) and PID 5678 (new) This bug is deterministic when serialisation orders a.service before b.service. The root cause is that manager_deserialize_one_unit() calls manager_load_unit(name, &u) which resolves aliases via unit_follow_merge(), returning the canonical Unit object. However, the code doesn't distinguish between two cases when u->id differs from the requested name from the state file. In the corruption case, we're deserialising an alias entry and unit_deserialize_state() blindly overwrites the canonical unit's fields with stale data from the old, independent unit. The serialised b.service then overwrites Unit A's correct live state. This commit first scans the serialised unit names, then adds a check after manager_load_unit(): if (!streq(u->id, name) && set_contains(serialized_units, u->id)) ... This detects when the loaded unit's canonical ID (u->id) differs from the serialised name, indicating the name is now an alias for a different unit and the canonical unit also has its own serialised state entry. If the canonical unit does not have its own serialised state entry, we keep the state entry. That handles cases where the old name is really just a rename, and thus the old name is the only serialised state for the unit. In that case there is no bug, because there is no separate canonical state entry for the stale alias entry to overwrite. Skipping is safe because: 1. The canonical unit's own state entry will be correctly deserialised regardless of order. This fix only prevents other stale alias entries from corrupting it. 2. unit_merge() has already transferred the necessary data. When b.service became an alias during unit loading, unit_merge() already migrated dependencies and references to the canonical unit. 3. After merging, the alias doesn't have its own runtime state. The serialised data represents b.service when it was independent, which is now obsolete once the canonical unit also has its own serialised entry. 4. All fields are stale. unit_deserialize_state() would overwrite state, timestamps, cgroup paths, pids, etc. There's no scenario where we want this data applied on top of the canonical unit's own serialised state. This fix also correctly handles unit precedence. For example, imagine this scenario: 1. `b.service` is a valid, running unit defined in `/run`. 2. The sysadmin creates `ln -s .../a.service /etc/.../b.service`. 3. On reload, the new symlink in `/etc` overrides the unit in `/run`. The new perspective from the manager side is that `b.service` is now an alias for `a.service`. In this case, systemd correctly abandons the old b.service unit, because that's the intended general semantics of unit file precedence. We also do that in other cases, like when a unit file in /etc/systemd/system/ masks a vendor-supplied unit file in /lib/systemd/system/, or when an admin uses systemctl mask to explicitly disable a unit. In all these scenarios, the configuration with the highest precedence (in /etc/) is treated as the new source of truth. The old unit's definition is discarded, and its running processes are (correctly) abandoned. In that respect we are not doing anything new here. Some may ask why we shouldn't just ignore the symlink if we think this case will come up. I think there are multiple very strong reasons not to do so: 1. It violates unit precedence. The unit design is built on a strict precedence list. When an admin puts any file in /etc, they are intentionally overriding everything else. If manager_load_unit were to "ignore" this file based on runtime state, it would break this fundamental precedent. 2. It makes daemon-reload stateful. daemon-reload is supposed to be a simple, stateless operation, basically to read the files on disk and apply the new configuration. But doing this would make daemon-reload stateful, because we'd have to read the files on disk, but cross-reference the current runtime state, and... maybe ignore some files. This is complex and unpredictable. 3. It also completely ignores the user intent. The admin clearly has tried to replace the old service with an alias. Ignoring their instruction is the opposite of what they want. Fixes: https://github.com/systemd/systemd/issues/38817 Fixes: https://github.com/systemd/systemd/issues/37482 --- man/systemctl.xml | 11 + src/core/manager-serialize.c | 107 ++++++++- test/units/TEST-07-PID1.alias-corruption.sh | 229 ++++++++++++++++++++ test/units/TEST-07-PID1.alias-rename.sh | 58 +++++ 4 files changed, 402 insertions(+), 3 deletions(-) create mode 100755 test/units/TEST-07-PID1.alias-corruption.sh create mode 100755 test/units/TEST-07-PID1.alias-rename.sh diff --git a/man/systemctl.xml b/man/systemctl.xml index f24a87739512a..2a542ba466a0d 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -1499,6 +1499,17 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err systemd listens on behalf of user configuration will stay accessible. + When unit aliasing is introduced during reload (e.g., converting + b.service to a symlink pointing to + a.service), the running state of the canonical + unit (a.service) is preserved. The old serialized + state of the now-aliased unit is discarded to prevent stale data from + corrupting the canonical unit's live state. Dependencies referencing + the alias name are automatically resolved to the canonical unit, and + the dependency graph is rebuilt from unit files, ensuring consistency. + If the now-aliased unit had running processes, they are abandoned and + will no longer be tracked by the service manager. + This command should not be confused with the reload command. diff --git a/src/core/manager-serialize.c b/src/core/manager-serialize.c index 2357b9277c616..c794888164874 100644 --- a/src/core/manager-serialize.c +++ b/src/core/manager-serialize.c @@ -14,6 +14,7 @@ #include "manager-serialize.h" #include "parse-util.h" #include "serialize.h" +#include "set.h" #include "string-util.h" #include "strv.h" #include "syslog-util.h" @@ -197,7 +198,50 @@ int manager_serialize( return 0; } -static int manager_deserialize_one_unit(Manager *m, const char *name, FILE *f, FDSet *fds) { +static int manager_collect_serialized_unit_names(FILE *f, Set **ret) { + _cleanup_set_free_ Set *serialized_units = NULL; + off_t offset; + int r; + + assert(f); + assert(ret); + + offset = ftello(f); + if (offset < 0) + return log_error_errno(errno, "Failed to determine serialization offset: %m"); + + for (;;) { + _cleanup_free_ char *line = NULL; + + r = read_stripped_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return log_error_errno(r, "Failed to read serialization line: %m"); + if (r == 0) + break; + + r = set_ensure_consume(&serialized_units, &string_hash_ops_free, TAKE_PTR(line)); + if (r < 0) + return log_oom(); + + r = unit_deserialize_state_skip(f); + if (r < 0) + return r; + } + + if (fseeko(f, offset, SEEK_SET) < 0) + return log_error_errno(errno, "Failed to reset serialization offset: %m"); + + *ret = TAKE_PTR(serialized_units); + return 0; +} + +static int manager_deserialize_one_unit( + Manager *m, + const char *name, + FILE *f, + FDSet *fds, + Set *serialized_units) { + Unit *u; int r; @@ -207,18 +251,75 @@ static int manager_deserialize_one_unit(Manager *m, const char *name, FILE *f, F if (r < 0) return log_notice_errno(r, "Failed to load unit \"%s\", skipping deserialization: %m", name); + if (!streq(u->id, name) && + set_contains(serialized_units, u->id)) { + /* + * The unit from the state file (name) resolved to a different canonical unit (u->id), and + * the canonical unit also has its own state entry. + * + * This means the state entry for the unit name is stale. That is, when the state was + * serialized, the name referred to an independent unit, but it now resolves as an alias to + * the canonical unit. Deserializing it would overwrite the canonical unit's own serialized + * state, and thus corrupt its live runtime state. + * + * It is very important to note that this only affects units that were independent when the + * state file was written, but are now aliases (either because a reload created the symlink, + * or the symlink existed but this is the first reload). Normal aliases that were already + * aliases during the most recent serialization are filtered out in manager_serialize(), so + * they never appear in the state file. + * + * If the canonical unit does not have its own state entry, then this is instead a rename or + * canonical ID change, and this state entry is the only state we have for the unit. In that + * case we must preserve it. After doing so, we insert the canonical unit's ID into the set + * so that any further aliases resolving to the same unit are skipped. + * + * The serialized data represents the old, independent unit. Deserializing this stale state + * would corrupt the canonical unit's live state, so we must discard it. + * + * Take as an example, a.service is running. Someone created symlink b.service -> a.service. + * On first reload, the state file still has b.service as an independent dead unit (from + * before the symlink existed), but b.service now resolves to a.service. We must discard + * b.service's stale dead state to preserve a.service's running state. + * + * Note: This log message is checked in TEST-07-PID1.alias-corruption.sh, so the test case + * may need adjustment if the message is changed. + */ + log_warning("Unit file for '%s' was overridden by a symlink to '%s', which also has serialized state. Skipping stale state of old unit. Any processes from the overridden unit are now abandoned!", + name, + u->id); + + return unit_deserialize_state_skip(f); + } + r = unit_deserialize_state(u, f, fds); if (r == -ENOMEM) return log_oom(); if (r < 0) return log_notice_errno(r, "Failed to deserialize unit \"%s\", skipping: %m", name); + /* If this unit was deserialized under an alias name (that is, it is a rename), record the canonical + * ID so that any further aliases pointing to the same unit are correctly skipped. */ + if (!streq(u->id, name)) { + r = set_put_strdup(&serialized_units, u->id); + if (r < 0) + return log_oom(); + } + return 0; } -static int manager_deserialize_units(Manager *m, FILE *f, FDSet *fds) { +static int manager_deserialize_units( + Manager *m, + FILE *f, + FDSet *fds) { + + _cleanup_set_free_ Set *serialized_units = NULL; int r; + r = manager_collect_serialized_unit_names(f, &serialized_units); + if (r < 0) + return r; + for (;;) { _cleanup_free_ char *line = NULL; @@ -229,7 +330,7 @@ static int manager_deserialize_units(Manager *m, FILE *f, FDSet *fds) { if (r == 0) break; - r = manager_deserialize_one_unit(m, line, f, fds); + r = manager_deserialize_one_unit(m, line, f, fds, serialized_units); if (r == -ENOMEM) return r; if (r < 0) { diff --git a/test/units/TEST-07-PID1.alias-corruption.sh b/test/units/TEST-07-PID1.alias-corruption.sh new file mode 100755 index 0000000000000..e2fc00d410f85 --- /dev/null +++ b/test/units/TEST-07-PID1.alias-corruption.sh @@ -0,0 +1,229 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# Verify that stale alias state doesn't overwrite canonical unit state. +# 1. Legit unit is running (PID A). +# 2. Sus units are running (PID B, C, D...). +# 3. We alias sus -> legit. +# 4. If the bug triggers, legit unit's state is overwritten by a sus unit's state. +# 5. Legit unit thinks it is now PID B (or C, or D...). +# 6. We detect this PID change as proof of corruption. + +declare -a abandoned_pids=() + +reap_abandoned_pids() { + local pid attempt + + if (( ${#abandoned_pids[@]} == 0 )); then + return 0 + fi + + echo "Reaping ${#abandoned_pids[@]} abandoned processes..." + + for pid in "${abandoned_pids[@]}"; do + kill "$pid" 2>/dev/null || true + done + + for pid in "${abandoned_pids[@]}"; do + for attempt in $(seq 1 50); do + if ! kill -0 "$pid" 2>/dev/null; then + break + fi + + sleep 0.1 + done + + if kill -0 "$pid" 2>/dev/null; then + kill -KILL "$pid" 2>/dev/null || true + fi + + for attempt in $(seq 1 50); do + if ! kill -0 "$pid" 2>/dev/null; then + break + fi + + sleep 0.1 + done + + if kill -0 "$pid" 2>/dev/null; then + echo "ERROR: Failed to reap abandoned process PID $pid" + return 1 + fi + done + + abandoned_pids=() +} + +run_test() { + local reload_cmd="${1:?}" + local current_pid journal_warnings new_pid orig_pid pid reload_start unit warning_count + + echo "" + echo "=========================================" + echo "Testing with: systemctl $reload_cmd" + echo "=========================================" + + cat >/run/systemd/system/legit.service <<'EOF' +[Service] +Type=simple +ExecStart=/bin/sleep infinity +EOF + + # Create 20 sus units. They must be Type=simple/running so systemd + # CANNOT garbage collect them. If they are dead/stopped, systemd can remove + # them from memory before serialization + echo "Creating 20 sus units..." + for i in $(seq -f "%02g" 1 20); do + cat >/run/systemd/system/sus-"${i}".service <<'EOF' +[Service] +Type=simple +ExecStart=/bin/sleep infinity +EOF + done + + systemctl daemon-reload + + echo "Starting legit unit..." + systemctl start legit.service + + echo "Starting sus units..." + for i in $(seq -f "%02g" 1 20); do + systemctl start sus-"${i}".service + done + + echo "Setup complete: 1 running legit unit, 20 running sus units" + + orig_pid=$(systemctl show -P MainPID legit.service) + echo "Original legit PID: $orig_pid" + + if (( orig_pid == 0 )); then + echo "Error: Legit PID is 0, setup failed." + return 1 + fi + + # Since ordering is not deterministic we should loop 3 times to reduce + # false negative rate (ordering luck). With this it's roughly 0.01% chance + # of falsely passing. Falsely failing does not happen, though. + for attempt in 1 2 3; do + echo "" + echo "--- Attempt $attempt/3 ---" + + unset sus_pids + declare -A sus_pids + for i in $(seq -f "%02g" 1 20); do + pid=$(systemctl show -P MainPID sus-"${i}".service) + if (( pid != 0 )); then + sus_pids["sus-${i}"]=$pid + abandoned_pids+=("$pid") + echo "sus-${i}.service PID: $pid" + fi + done + + echo "Converting sus units to symlinks -> legit.service..." + for i in $(seq -f "%02g" 1 20); do + rm -f /run/systemd/system/sus-"${i}".service + ln -sf /run/systemd/system/legit.service /run/systemd/system/sus-"${i}".service + done + + reload_start=$(date '+%Y-%m-%d %H:%M:%S') + + echo "Running $reload_cmd..." + systemctl "$reload_cmd" + + # If the bug triggered, legit.service deserialized a sus unit's state + # and overwrote its own MainPID with the sus unit's PID. + new_pid=$(systemctl show -P MainPID legit.service) + + if [[ "$new_pid" != "$orig_pid" ]]; then + echo "legit.service PID changed from $orig_pid to $new_pid!" + echo "The stale alias state corrupted the canonical unit." + return 1 + fi + + echo "legit.service PID remains $new_pid. Attempt $attempt passed." + + # Verify that all sus unit processes were abandoned (still running but no longer tracked) + echo "Verifying sus unit processes were abandoned..." + for unit in "${!sus_pids[@]}"; do + pid=${sus_pids[$unit]} + # Process should still be running + if ! kill -0 "$pid" 2>/dev/null; then + echo "ERROR: $unit process (PID $pid) was killed instead of abandoned!" + return 1 + fi + # But the alias should now either be inactive (MainPID=0) or resolve to legit's PID. + current_pid=$(systemctl show -P MainPID "${unit}.service") + if ! (( current_pid == 0 || current_pid == new_pid )); then + echo "ERROR: $unit unexpectedly reports MainPID=$current_pid after aliasing!" + return 1 + fi + echo "$unit process (PID $pid) was correctly abandoned (still running, no longer tracked)" + done + + # Check consistency between journal warnings and abandoned processes + echo "Checking journal for stale state warnings..." + journal_warnings=$(journalctl --since "$reload_start" --no-pager | grep "Skipping stale state" || true) + warning_count=$(echo "$journal_warnings" | grep -c "Skipping stale state" || true) + + echo "Found $warning_count 'Skipping stale state' warnings" + + # Extract unit names from warnings and verify they match our sus units + if (( warning_count > 0 )); then + echo "Verifying warning consistency..." + for unit in "${!sus_pids[@]}"; do + if [[ "$journal_warnings" != *"${unit}.service"* ]]; then + echo "WARNING: Expected journal warning for ${unit}.service but didn't find it" + fi + done + fi + + reap_abandoned_pids + + if (( attempt < 3 )); then + echo "Resetting sus units..." + + # We must fully reset to get independent running units again + for i in $(seq -f "%02g" 1 20); do + rm -f /run/systemd/system/sus-"${i}".service + cat >/run/systemd/system/sus-"${i}".service <<'EOF' +[Service] +Type=simple +ExecStart=/bin/sleep infinity +EOF + done + + systemctl "$reload_cmd" + + # Ensure they are running again (they might have been + # abandoned/killed during the transition) + for i in $(seq -f "%02g" 1 20); do + systemctl start sus-"${i}".service + done + + echo "Reset complete." + fi + done + + echo "legit.service did not become sus through all 3 $reload_cmd cycles" + + echo "$reload_cmd test passed" +} + +cleanup_test_units() { + reap_abandoned_pids || true + systemctl stop legit.service 2>/dev/null || true + for i in $(seq -f "%02g" 1 20); do + systemctl stop sus-"${i}".service 2>/dev/null || true + rm -f /run/systemd/system/sus-"${i}".service + done + rm -f /run/systemd/system/legit.service + systemctl daemon-reload +} + +trap cleanup_test_units EXIT + +run_test daemon-reload +cleanup_test_units +run_test daemon-reexec diff --git a/test/units/TEST-07-PID1.alias-rename.sh b/test/units/TEST-07-PID1.alias-rename.sh new file mode 100755 index 0000000000000..c33408914ce82 --- /dev/null +++ b/test/units/TEST-07-PID1.alias-rename.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +run_test() { + local reload_cmd="${1:?}" + local orig_pid new_pid + + echo "" + echo "=========================================" + echo "Testing rename preservation with: systemctl $reload_cmd" + echo "=========================================" + + cat >/run/systemd/system/rename.service <<'EOF' +[Service] +Type=simple +ExecStart=/bin/sleep infinity +EOF + + systemctl daemon-reload + systemctl start rename.service + + orig_pid=$(systemctl show -P MainPID rename.service) + (( orig_pid != 0 )) + + # The old name becomes an alias to the new canonical unit... + rm -f /run/systemd/system/rename.service + cat >/run/systemd/system/the-unit-formerly-known-as-rename.service <<'EOF' +[Service] +Type=simple +ExecStart=/bin/sleep infinity +EOF + ln -sf /run/systemd/system/the-unit-formerly-known-as-rename.service /run/systemd/system/rename.service + + systemctl "$reload_cmd" + + # ...and the running service must stay tracked across the rename. + new_pid=$(systemctl show -P MainPID the-unit-formerly-known-as-rename.service) + (( new_pid == orig_pid )) + (( $(systemctl show -P MainPID rename.service) == orig_pid )) + [[ "$(systemctl show -P ActiveState the-unit-formerly-known-as-rename.service)" == active ]] + [[ "$(systemctl show -P ActiveState rename.service)" == active ]] +} + +cleanup_test_units() { + systemctl stop the-unit-formerly-known-as-rename.service 2>/dev/null || true + systemctl stop rename.service 2>/dev/null || true + rm -f /run/systemd/system/rename.service + rm -f /run/systemd/system/the-unit-formerly-known-as-rename.service + systemctl daemon-reload +} + +trap cleanup_test_units EXIT + +run_test daemon-reload +cleanup_test_units +run_test daemon-reexec From 2371c9e7981947af16a93ded1775b494443158d3 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 29 Mar 2026 20:22:24 +0000 Subject: [PATCH 0704/1296] vmspawn: add scsi-cd disk type for ISO/CD-ROM image support Add DISK_TYPE_SCSI_CD to support attaching disk images as CD-ROM drives, needed for testing El Torito ISO images built by systemd-repart. When --image-disk-type=scsi-cd is specified, the image is attached with media=cdrom and readonly=on on the drive, using scsi-cd as the device driver on the SCSI bus. This also works for --extra-drive= with the scsi-cd: prefix. The QEMU configuration matches the standard OVMF CD-ROM boot setup: -drive if=none,media=cdrom,format=raw,readonly=on -device virtio-scsi-pci -device scsi-cd When direct kernel booting with scsi-cd, if the kernel command line contains "rw", append "ro" to override it since CD-ROMs are read-only. Co-developed-by: Claude Opus 4.6 --- man/systemd-vmspawn.xml | 5 +- src/vmspawn/vmspawn-settings.c | 7 +-- src/vmspawn/vmspawn-settings.h | 1 + src/vmspawn/vmspawn.c | 84 +++++++++++++++++++++++++--------- 4 files changed, 71 insertions(+), 26 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 28070cfe8f66c..129f5ba14199f 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -131,8 +131,9 @@ Specifies the disk type to use for the root disk passed to . Extra drives added via inherit this disk type unless overridden with an explicit disk type prefix. Takes one of virtio-blk, - virtio-scsi, or nvme. Defaults to - virtio-blk. + virtio-scsi, nvme, or scsi-cd. Defaults to + virtio-blk. When scsi-cd is specified, the disk is attached + as a read-only CD-ROM drive. diff --git a/src/vmspawn/vmspawn-settings.c b/src/vmspawn/vmspawn-settings.c index d19a65d55debf..776b590252ee5 100644 --- a/src/vmspawn/vmspawn-settings.c +++ b/src/vmspawn/vmspawn-settings.c @@ -11,9 +11,10 @@ static const char *const image_format_table[_IMAGE_FORMAT_MAX] = { DEFINE_STRING_TABLE_LOOKUP(image_format, ImageFormat); static const char *const disk_type_table[_DISK_TYPE_MAX] = { - [DISK_TYPE_VIRTIO_BLK] = "virtio-blk", - [DISK_TYPE_VIRTIO_SCSI] = "virtio-scsi", - [DISK_TYPE_NVME] = "nvme", + [DISK_TYPE_VIRTIO_BLK] = "virtio-blk", + [DISK_TYPE_VIRTIO_SCSI] = "virtio-scsi", + [DISK_TYPE_NVME] = "nvme", + [DISK_TYPE_VIRTIO_SCSI_CDROM] = "scsi-cd", }; DEFINE_STRING_TABLE_LOOKUP(disk_type, DiskType); diff --git a/src/vmspawn/vmspawn-settings.h b/src/vmspawn/vmspawn-settings.h index cfcee6fbb61b6..b897f148e131a 100644 --- a/src/vmspawn/vmspawn-settings.h +++ b/src/vmspawn/vmspawn-settings.h @@ -14,6 +14,7 @@ typedef enum DiskType { DISK_TYPE_VIRTIO_BLK, DISK_TYPE_VIRTIO_SCSI, DISK_TYPE_NVME, + DISK_TYPE_VIRTIO_SCSI_CDROM, _DISK_TYPE_MAX, _DISK_TYPE_INVALID = -EINVAL, } DiskType; diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 8ad3fb61645b8..ff76c5e4d30e3 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -96,6 +96,9 @@ #define VM_TAP_HASH_KEY SD_ID128_MAKE(01,d0,c6,4c,2b,df,24,fb,c0,f8,b2,09,7d,59,b2,93) +#define DISK_SERIAL_MAX_LEN_SCSI 30 +#define DISK_SERIAL_MAX_LEN_NVME 20 + /* An enum controlling how auxiliary state for the VM are maintained, i.e. the TPM state and the EFI variable * NVRAM. */ typedef enum StateMode { @@ -221,7 +224,8 @@ static int help(void) { " -i --image=FILE|DEVICE Root file system disk image or device for the VM\n" " --image-format=FORMAT Specify disk image format (raw, qcow2; default: raw)\n" " --image-disk-type=TYPE\n" - " Specify disk type (virtio-blk, virtio-scsi, nvme; default: virtio-blk)\n" + " Specify disk type (virtio-blk, virtio-scsi, nvme,\n" + " scsi-cd; default: virtio-blk)\n" "\n%3$sHost Configuration:%4$s\n" " --cpus=CPUS Configure number of CPUs in guest\n" " --ram=BYTES Configure guest's RAM size\n" @@ -272,7 +276,8 @@ static int help(void) { " --extra-drive=[FORMAT:][DISKTYPE:]PATH\n" " Adds an additional disk to the VM\n" " FORMAT: raw, qcow2\n" - " DISKTYPE: virtio-blk, virtio-scsi, nvme\n" + " DISKTYPE: virtio-blk, virtio-scsi, nvme,\n" + " scsi-cd\n" " --bind-user=NAME Bind user from host to virtual machine\n" " --bind-user-shell=BOOL|PATH\n" " Configure the shell to use for --bind-user= users\n" @@ -2758,11 +2763,11 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } bool need_scsi_controller = - arg_image_disk_type == DISK_TYPE_VIRTIO_SCSI && arg_image; + IN_SET(arg_image_disk_type, DISK_TYPE_VIRTIO_SCSI, DISK_TYPE_VIRTIO_SCSI_CDROM) && arg_image; if (!need_scsi_controller) FOREACH_ARRAY(drive, arg_extra_drives.drives, arg_extra_drives.n_drives) { DiskType dt = drive->disk_type >= 0 ? drive->disk_type : arg_image_disk_type; - if (dt == DISK_TYPE_VIRTIO_SCSI) { + if (IN_SET(dt, DISK_TYPE_VIRTIO_SCSI, DISK_TYPE_VIRTIO_SCSI_CDROM)) { need_scsi_controller = true; break; } @@ -2786,12 +2791,20 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { arg_image); } - r = qemu_config_section(config_file, "drive", "vmspawn", - "if", "none", - "file", arg_image, - "format", image_format_to_string(arg_image_format), - "discard", on_off(arg_discard_disk), - "snapshot", on_off(arg_ephemeral)); + if (arg_image_disk_type == DISK_TYPE_VIRTIO_SCSI_CDROM) + r = qemu_config_section(config_file, "drive", "vmspawn", + "if", "none", + "file", arg_image, + "format", image_format_to_string(arg_image_format), + "media", "cdrom", + "readonly", "on"); + else + r = qemu_config_section(config_file, "drive", "vmspawn", + "if", "none", + "file", arg_image, + "format", image_format_to_string(arg_image_format), + "discard", on_off(arg_discard_disk), + "snapshot", on_off(arg_ephemeral)); if (r < 0) return r; @@ -2812,13 +2825,19 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { break; case DISK_TYPE_VIRTIO_SCSI: disk_driver = "scsi-hd"; - r = disk_serial(image_fn, 30, &serial); + r = disk_serial(image_fn, DISK_SERIAL_MAX_LEN_SCSI, &serial); if (r < 0) return log_oom(); break; case DISK_TYPE_NVME: disk_driver = "nvme"; - r = disk_serial(image_fn, 20, &serial); + r = disk_serial(image_fn, DISK_SERIAL_MAX_LEN_NVME, &serial); + if (r < 0) + return log_oom(); + break; + case DISK_TYPE_VIRTIO_SCSI_CDROM: + disk_driver = "scsi-cd"; + r = disk_serial(image_fn, DISK_SERIAL_MAX_LEN_SCSI, &serial); if (r < 0) return log_oom(); break; @@ -2834,15 +2853,20 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (r < 0) return r; - if (arg_image_disk_type == DISK_TYPE_VIRTIO_SCSI) { + if (IN_SET(arg_image_disk_type, DISK_TYPE_VIRTIO_SCSI, DISK_TYPE_VIRTIO_SCSI_CDROM)) { r = qemu_config_key(config_file, "bus", "vmspawn_scsi.0"); if (r < 0) return r; } - r = grow_image(arg_image, arg_grow_image); - if (r < 0) - return r; + if (arg_image_disk_type != DISK_TYPE_VIRTIO_SCSI_CDROM) { + r = grow_image(arg_image, arg_grow_image); + if (r < 0) + return r; + /* CD-ROMs are read-only, so override any "rw" on the kernel command line. */ + } else if (strv_contains(arg_kernel_cmdline_extra, "rw") && + strv_extend(&arg_kernel_cmdline_extra, "ro") < 0) + return log_oom(); } _cleanup_(sd_event_unrefp) sd_event *event = NULL; @@ -2947,7 +2971,11 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected regular file or block device, not '%s'.", drive->path); - if (strv_extendf(&cmdline, "driver=%s,cache.direct=off,cache.no-flush=on,file.driver=%s,file.filename=%s,node-name=vmspawn_extra_%zu", image_format_to_string(drive->format), driver, escaped_drive, i) < 0) + DiskType dt = drive->disk_type >= 0 ? drive->disk_type : arg_image_disk_type; + + if (strv_extendf(&cmdline, "driver=%s,cache.direct=off,cache.no-flush=on,file.driver=%s,file.filename=%s,node-name=vmspawn_extra_%zu%s", + image_format_to_string(drive->format), driver, escaped_drive, i, + dt == DISK_TYPE_VIRTIO_SCSI_CDROM ? ",read-only=on" : "") < 0) return log_oom(); _cleanup_free_ char *drive_fn = NULL; @@ -2962,8 +2990,6 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (strv_extend(&cmdline, "-device") < 0) return log_oom(); - DiskType dt = drive->disk_type >= 0 ? drive->disk_type : arg_image_disk_type; - switch (dt) { case DISK_TYPE_VIRTIO_BLK: if (strv_extendf(&cmdline, "virtio-blk-pci,drive=vmspawn_extra_%zu,serial=%s", i++, escaped_drive_fn) < 0) @@ -2971,7 +2997,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { break; case DISK_TYPE_VIRTIO_SCSI: { _cleanup_free_ char *serial = NULL; - r = disk_serial(escaped_drive_fn, 30, &serial); + r = disk_serial(escaped_drive_fn, DISK_SERIAL_MAX_LEN_SCSI, &serial); if (r < 0) return log_oom(); if (strv_extendf(&cmdline, "scsi-hd,bus=vmspawn_scsi.0,drive=vmspawn_extra_%zu,serial=%s", i++, serial) < 0) @@ -2980,13 +3006,22 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } case DISK_TYPE_NVME: { _cleanup_free_ char *serial = NULL; - r = disk_serial(escaped_drive_fn, 20, &serial); + r = disk_serial(escaped_drive_fn, DISK_SERIAL_MAX_LEN_NVME, &serial); if (r < 0) return log_oom(); if (strv_extendf(&cmdline, "nvme,drive=vmspawn_extra_%zu,serial=%s", i++, serial) < 0) return log_oom(); break; } + case DISK_TYPE_VIRTIO_SCSI_CDROM: { + _cleanup_free_ char *serial = NULL; + r = disk_serial(escaped_drive_fn, DISK_SERIAL_MAX_LEN_SCSI, &serial); + if (r < 0) + return log_oom(); + if (strv_extendf(&cmdline, "scsi-cd,bus=vmspawn_scsi.0,drive=vmspawn_extra_%zu,serial=%s", i++, serial) < 0) + return log_oom(); + break; + } default: assert_not_reached(); } @@ -3647,6 +3682,13 @@ static int verify_arguments(void) { if (!strv_isempty(arg_initrds) && !arg_linux) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --initrd= cannot be used without --linux=."); + if (arg_image_disk_type == DISK_TYPE_VIRTIO_SCSI_CDROM) { + if (arg_ephemeral) + log_warning("--ephemeral has no effect with --image-disk-type=scsi-cd (CD-ROMs are read-only)."); + if (arg_discard_disk) + log_warning("--discard-disk has no effect with --image-disk-type=scsi-cd (CD-ROMs are read-only)."); + } + return 0; } From e98a26ddc096dc5fe146c85177e1a61a330883d4 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 27 Mar 2026 14:58:35 +0000 Subject: [PATCH 0705/1296] vmspawn: use fstab.extra credential for runtime mounts instead of kernel cmdline Switch runtime virtiofs mount configuration from systemd.mount-extra= kernel command line parameters to the fstab.extra credential. This avoids consuming kernel command line space (which is limited) and matches the approach used by mkosi. Each mount is added as an fstab entry in the format: {tag} {destination} virtiofs {ro|rw},x-initrd.mount If the user already specified a fstab.extra credential via --set-credential= or --load-credential=, the virtiofs mount entries are appended to it rather than conflicting. Co-developed-by: Claude Opus 4.6 --- src/basic/escape.c | 6 +++--- src/basic/escape.h | 5 ++++- src/vmspawn/vmspawn.c | 34 ++++++++++++++++++++++++++++++---- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/basic/escape.c b/src/basic/escape.c index e1771bf432278..9af8efacc7423 100644 --- a/src/basic/escape.c +++ b/src/basic/escape.c @@ -447,10 +447,10 @@ char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFl FLAGS_SET(flags, XESCAPE_FORCE_ELLIPSIS)); } -char* octescape(const char *s, size_t len) { +char* octescape_full(const char *s, size_t len, const char *bad) { char *buf, *t; - /* Escapes \ and " chars, in \nnn style escaping. */ + /* Escapes all chars in bad, in addition to \ and " chars, in \nnn octal style escaping. */ assert(s || len == 0); @@ -467,7 +467,7 @@ char* octescape(const char *s, size_t len) { for (size_t i = 0; i < len; i++) { uint8_t u = (uint8_t) s[i]; - if (u < ' ' || u >= 127 || IN_SET(u, '\\', '"')) { + if (u < ' ' || u >= 127 || IN_SET(u, '\\', '"') || (bad && strchr(bad, u))) { *(t++) = '\\'; *(t++) = '0' + (u >> 6); *(t++) = '0' + ((u >> 3) & 7); diff --git a/src/basic/escape.h b/src/basic/escape.h index a8b68fa75c277..625758f2f4c9f 100644 --- a/src/basic/escape.h +++ b/src/basic/escape.h @@ -59,7 +59,10 @@ char* xescape_full(const char *s, const char *bad, size_t console_width, XEscape static inline char* xescape(const char *s, const char *bad) { return xescape_full(s, bad, SIZE_MAX, 0); } -char* octescape(const char *s, size_t len); +char* octescape_full(const char *s, size_t len, const char *bad); +static inline char* octescape(const char *s, size_t len) { + return octescape_full(s, len, NULL); +} char* decescape(const char *s, size_t len, const char *bad) _nonnull_if_nonzero_(1, 2); char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFlags flags); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index ff76c5e4d30e3..e14d2bb5f36cd 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -3033,6 +3033,8 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return log_oom(); } + _cleanup_free_ char *fstab_extra = NULL; + for (size_t j = 0; j < arg_runtime_mounts.n_mounts; j++) { RuntimeMount *m = arg_runtime_mounts.mounts + j; _cleanup_free_ char *listen_address = NULL; @@ -3079,15 +3081,39 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (r < 0) return r; - _cleanup_free_ char *clean_target = xescape(m->target, "\":"); - if (!clean_target) + /* fstab uses whitespace as field separator, so octal-escape spaces in paths */ + _cleanup_free_ char *escaped_target = octescape_full(m->target, SIZE_MAX, " \t"); + if (!escaped_target) return log_oom(); - if (strv_extendf(&arg_kernel_cmdline_extra, "systemd.mount-extra=\"%s:%s:virtiofs:%s\"", - id, clean_target, m->read_only ? "ro" : "rw") < 0) + if (strextendf(&fstab_extra, "%s %s virtiofs %s,x-initrd.mount\n", + id, escaped_target, m->read_only ? "ro" : "rw") < 0) return log_oom(); } + if (fstab_extra) { + /* If the user already specified a fstab.extra credential, combine it with ours */ + MachineCredential *existing = machine_credential_find(&arg_credentials, "fstab.extra"); + if (existing) { + _cleanup_free_ char *combined = NULL; + + if (existing->size > 0 && existing->data[existing->size - 1] != '\n') + r = asprintf(&combined, "%.*s\n%s", (int) existing->size, existing->data, fstab_extra); + else + r = asprintf(&combined, "%.*s%s", (int) existing->size, existing->data, fstab_extra); + if (r < 0) + return log_oom(); + + erase_and_free(existing->data); + existing->data = TAKE_PTR(combined); + existing->size = r; + } else { + r = machine_credential_add(&arg_credentials, "fstab.extra", fstab_extra, SIZE_MAX); + if (r < 0) + return r; + } + } + _cleanup_(rm_rf_physical_and_freep) char *smbios_dir = NULL; r = mkdtemp_malloc("/var/tmp/vmspawn-smbios-XXXXXX", &smbios_dir); if (r < 0) From ba10d789a899680dc7c3cdc5cd29b66d761eb81e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 2 Apr 2026 08:02:25 +0000 Subject: [PATCH 0706/1296] basic/terminal-util: use non-blocking writes when sending ANSI sequences in terminal_get_size() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit terminal_get_size() writes ANSI escape sequences (CSI 18 and DSR queries) to the output fd to determine terminal dimensions. This is called during early boot via reset_dev_console_fd() and from service execution contexts via exec_context_apply_tty_size(). Previously, these writes used loop_write() on a blocking fd, which could block indefinitely if the terminal is not consuming data — for example on a serial console with flow control asserted, or a disconnected terminal. This is the same problem that was solved for terminal_reset_ansi_seq() in systemd/systemd#32369 by temporarily setting the fd to non-blocking mode with a write timeout. Apply the same pattern here: set the output fd to non-blocking in terminal_get_size() before issuing the queries, and restore blocking mode afterward. Change the loop_write() calls in terminal_query_size_by_dsr() and terminal_query_size_by_csi18() to loop_write_full() with a 100ms timeout so writes fail gracefully instead of hanging. Also introduce the CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC constant for all timeouts used across all ANSI sequence writes and reads (vt_disallocate(), terminal_reset_ansi_seq(), and the two size query functions). 333ms is now used for all timeouts in terminal-util.c. Also introduce a cleanup function for resetting a fd back to blocking mode after it was made non-blocking. --- src/basic/fd-util.c | 7 +++++ src/basic/fd-util.h | 2 ++ src/basic/terminal-util.c | 61 ++++++++++++++++++++++----------------- 3 files changed, 43 insertions(+), 27 deletions(-) diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index 6fb4e78c2f6b6..fbe3e68974d44 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -166,6 +166,13 @@ int fd_nonblock(int fd, bool nonblock) { return 1; } +void nonblock_resetp(int *fd) { + PROTECT_ERRNO; + + if (*fd >= 0) + (void) fd_nonblock(*fd, false); +} + int stdio_disable_nonblock(void) { int ret = 0; diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index 60caa424b4e1d..c15ce7fddde4a 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -112,6 +112,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(DIR*, closedir, NULL); int fd_nonblock(int fd, bool nonblock); int stdio_disable_nonblock(void); +void nonblock_resetp(int *fd); + int fd_cloexec(int fd, bool cloexec); int fd_cloexec_many(const int fds[], size_t n_fds, bool cloexec); diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 7a240a4c7ab31..3fd5c4f8b8bd2 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -44,8 +44,8 @@ #include "time-util.h" #include "utf8.h" -/* How much to wait for a reply to a terminal sequence */ -#define CONSOLE_REPLY_WAIT_USEC (333 * USEC_PER_MSEC) +/* How much to wait when reading/writing ANSI sequences from/to the console */ +#define CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC (333 * USEC_PER_MSEC) static volatile unsigned cached_columns = 0; static volatile unsigned cached_lines = 0; @@ -848,7 +848,7 @@ int vt_disallocate(const char *tty_path) { "\033[3J" /* clear screen including scrollback, requires Linux 2.6.40 */ "\033c", /* reset to initial state */ SIZE_MAX, - 100 * USEC_PER_MSEC); + CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC); } static int vt_default_utf8(void) { @@ -947,7 +947,8 @@ static int terminal_reset_ioctl(int fd, bool switch_to_text) { } int terminal_reset_ansi_seq(int fd) { - int r, k; + _cleanup_(nonblock_resetp) int nonblock_reset = -EBADF; + int r; assert(fd >= 0); @@ -957,8 +958,10 @@ int terminal_reset_ansi_seq(int fd) { r = fd_nonblock(fd, true); if (r < 0) return log_debug_errno(r, "Failed to set terminal to non-blocking mode: %m"); + if (r > 0) + nonblock_reset = fd; - k = loop_write_full(fd, + r = loop_write_full(fd, "\033[!p" /* soft terminal reset */ ANSI_OSC "104" ANSI_ST /* reset color palette via OSC 104 */ ANSI_NORMAL /* reset colors */ @@ -966,17 +969,11 @@ int terminal_reset_ansi_seq(int fd) { "\033[1G" /* place cursor at beginning of current line */ "\033[0J", /* erase till end of screen */ SIZE_MAX, - 100 * USEC_PER_MSEC); - if (k < 0) - log_debug_errno(k, "Failed to reset terminal through ANSI sequences: %m"); - - if (r > 0) { - r = fd_nonblock(fd, false); - if (r < 0) - log_debug_errno(r, "Failed to set terminal back to blocking mode: %m"); - } + CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC); + if (r < 0) + log_debug_errno(r, "Failed to reset terminal through ANSI sequences: %m"); - return k < 0 ? k : r; + return r; } void reset_dev_console_fd(int fd, bool switch_to_text) { @@ -2009,7 +2006,7 @@ int terminal_get_cursor_position( if (r < 0) return r; - usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC); + usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC); char buf[STRLEN("\x1B[1;1R")]; /* The shortest valid reply possible */ size_t buf_full = 0; CursorPositionContext context = {}; @@ -2308,7 +2305,7 @@ int get_default_background_color(double *ret_red, double *ret_green, double *ret if (r < 0) return r; - usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC); + usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC); char buf[STRLEN(ANSI_OSC "11;rgb:0/0/0" ANSI_ST)]; /* shortest possible reply */ size_t buf_full = 0; BackgroundColorContext context = {}; @@ -2379,16 +2376,17 @@ static int terminal_query_size_by_dsr( /* Use DECSC/DECRC to save/restore cursor instead of querying position via DSR. This way the cursor * is always restored — even on timeout — and we only need one DSR response instead of two. */ - r = loop_write(output_fd, - "\x1B" "7" /* DECSC: save cursor position */ - "\x1B[32766;32766H" /* CUP: position cursor far to the right and to the bottom, staying within 16bit signed range */ - "\x1B[6n" /* DSR: request cursor position (CPR) */ - "\x1B" "8", /* DECRC: restore cursor position */ - SIZE_MAX); + r = loop_write_full(output_fd, + "\x1B" "7" /* DECSC: save cursor position */ + "\x1B[32766;32766H" /* CUP: position cursor far to the right and to the bottom, staying within 16bit signed range */ + "\x1B[6n" /* DSR: request cursor position (CPR) */ + "\x1B" "8", /* DECRC: restore cursor position */ + SIZE_MAX, + CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC); if (r < 0) return r; - usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC); + usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC); char buf[STRLEN("\x1B[1;1R")]; /* The shortest valid reply possible */ size_t buf_full = 0; CursorPositionContext context = {}; @@ -2536,11 +2534,11 @@ static int terminal_query_size_by_csi18( assert(nonblock_input_fd >= 0); assert(output_fd >= 0); - r = loop_write(output_fd, CSI18_Q, SIZE_MAX); + r = loop_write_full(output_fd, CSI18_Q, SIZE_MAX, CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC); if (r < 0) return r; - usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC); + usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC); char buf[STRLEN(CSI18_R1)]; size_t bytes = 0; @@ -2591,6 +2589,7 @@ int terminal_get_size( _cleanup_close_ int nonblock_input_fd = -EBADF; struct termios old_termios = TERMIOS_NULL; CLEANUP_TERMIOS_RESET(nonblock_input_fd, old_termios); + _cleanup_(nonblock_resetp) int nonblock_reset = -EBADF; int r; assert(try_dsr || try_csi18); @@ -2599,6 +2598,14 @@ int terminal_get_size( if (r < 0) return r; + /* Put the output fd in non-blocking mode with a write timeout, to avoid blocking indefinitely on + * write if the terminal is not consuming data (e.g. serial console with flow control). */ + r = fd_nonblock(output_fd, true); + if (r < 0) + return log_debug_errno(r, "Failed to set terminal to non-blocking mode: %m"); + if (r > 0) + nonblock_reset = output_fd; + /* Flush any stale input that might confuse the response parsers. */ (void) tcflush(nonblock_input_fd, TCIFLUSH); @@ -2732,7 +2739,7 @@ int terminal_get_terminfo_by_dcs(int fd, char **ret_name) { if (r < 0) return r; - usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC); + usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC); char buf[STRLEN(DCS_TERMINFO_R1) + MAX_TERMINFO_LENGTH + STRLEN(ANSI_ST)]; size_t bytes = 0; From 7d11e8bed7780924175a8fbb293aeec15c8442e4 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 2 Apr 2026 08:08:40 +0000 Subject: [PATCH 0707/1296] basic/terminal-util: use getenv_terminal_is_dumb() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit terminal_prepare_query() is called from terminal_get_size() which operates on an explicitly passed fd — typically /dev/console opened directly by PID 1 via reset_dev_console_fd(), or a service's TTY via exec_context_apply_tty_size(). Using terminal_is_dumb() here is wrong because it additionally checks on_tty(), which tests whether stderr is a tty. PID 1's stderr may not be a tty (e.g. connected to kmsg or the journal), causing terminal_is_dumb() to return true and skip the ANSI query even though the fd we're operating on is a perfectly functional terminal. Use getenv_terminal_is_dumb() instead, which only checks $TERM, matching what terminal_reset_ansi_seq() already does. Also use it in terminal_get_cursor_position(), which also receives fds to operate on. --- src/basic/terminal-util.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 3fd5c4f8b8bd2..09410ccc457fa 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -1973,7 +1973,7 @@ int terminal_get_cursor_position( assert(input_fd >= 0); assert(output_fd >= 0); - if (terminal_is_dumb()) + if (getenv_terminal_is_dumb()) return -EOPNOTSUPP; r = terminal_verify_same(input_fd, output_fd); @@ -2458,7 +2458,11 @@ static int terminal_prepare_query( assert(ret_nonblock_fd); assert(ret_saved_termios); - if (terminal_is_dumb()) + /* Use getenv_terminal_is_dumb() instead of terminal_is_dumb() here since we operate on an + * explicitly passed fd, not on stdio. terminal_is_dumb() additionally checks on_tty() which + * tests whether *stderr* is a tty — that's irrelevant when we're querying a directly opened + * terminal such as /dev/console. */ + if (getenv_terminal_is_dumb()) return -EOPNOTSUPP; r = terminal_verify_same(input_fd, output_fd); From bfdc389ea7a19f5682bf87a4fefd2d6ab6c81f2d Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 2 Apr 2026 10:26:08 +0200 Subject: [PATCH 0708/1296] terminal-util: Protect errno in termios_reset() --- src/basic/terminal-util.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 09410ccc457fa..2da4f1c2fb86c 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -2114,6 +2114,8 @@ static bool termios_is_null(const struct termios *t) { void termios_reset(const TermiosResetContext *c) { assert(c); + PROTECT_ERRNO; + if (c->fd && *c->fd >= 0 && !termios_is_null(c->termios)) (void) tcsetattr(*c->fd, TCSANOW, c->termios); } From 214f7f0b8acea40b721f8b6f9a9c62c124dd07d1 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 2 Apr 2026 16:18:27 +0200 Subject: [PATCH 0709/1296] vmspawn: Warn about --grow-image= in combo with --image-disk-type=scsi-cd --- src/vmspawn/vmspawn.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index e14d2bb5f36cd..73df1cbadd966 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -3713,6 +3713,8 @@ static int verify_arguments(void) { log_warning("--ephemeral has no effect with --image-disk-type=scsi-cd (CD-ROMs are read-only)."); if (arg_discard_disk) log_warning("--discard-disk has no effect with --image-disk-type=scsi-cd (CD-ROMs are read-only)."); + if (arg_grow_image) + log_warning("--grow-image has no effect with --image-disk-type=scsi-cd (CD-ROMs are read-only)."); } return 0; From dcce04e649f3e7f2a290fd35b039784a7cdd6490 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sat, 28 Mar 2026 13:12:25 +0000 Subject: [PATCH 0710/1296] vmspawn: propagate $TERM from host into VM via kernel command line When running in a console mode (interactive, native, or read-only), propagate the host's $TERM into the VM by adding TERM= and systemd.tty.term.hvc0= to the kernel command line. TERM= is picked up by PID 1 and inherited by services on /dev/console (such as emergency.service). systemd.tty.term.hvc0= is used by services directly attached to /dev/hvc0 (such as serial-getty@hvc0.service) which look up $TERM via the systemd.tty.term. kernel command line parameter. While systemd can auto-detect the terminal type via DCS XTGETTCAP, not all terminal emulators implement this, so explicitly propagating $TERM provides a more reliable experience. We skip propagation when $TERM is unset or set to "unknown" (as is the case in GitHub Actions and some other CI environments). Previously this was handled by mkosi synthesizing the corresponding kernel command line parameters externally. Co-developed-by: Claude Opus 4.6 --- src/basic/terminal-util.c | 10 ++++++++++ src/basic/terminal-util.h | 1 + src/vmspawn/vmspawn.c | 18 ++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 2da4f1c2fb86c..ecdc241247286 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -1713,6 +1713,16 @@ static bool on_dev_null(void) { return cached_on_dev_null; } +bool term_env_valid(const char *term) { + /* Checks if the specified $TERM value is suitable for propagation, i.e. is not empty, not set to + * "unknown" (as is common in CI), and only contains characters valid in terminal type names. + * Valid $TERM values are things like "xterm-256color", "linux", "screen.xterm-256color", i.e. + * alphanumeric characters, hyphens, underscores, dots, and plus signs. */ + return !isempty(term) && + !streq(term, "unknown") && + in_charset(term, ALPHANUMERICAL "-_+."); +} + bool getenv_terminal_is_dumb(void) { const char *e; diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index dde1430243cf8..7ac5661104159 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -118,6 +118,7 @@ void columns_lines_cache_reset(int _unused_ signum); void reset_terminal_feature_caches(void); bool on_tty(void); +bool term_env_valid(const char *term); bool getenv_terminal_is_dumb(void); bool terminal_is_dumb(void); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 73df1cbadd966..1891ac8bad742 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -3031,6 +3031,24 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { r = strv_prepend(&arg_kernel_cmdline_extra, "console=hvc0"); if (r < 0) return log_oom(); + + /* Propagate the host's $TERM into the VM via the kernel command line. TERM= is + * picked up by PID 1 and inherited by services on /dev/console, and + * systemd.tty.term.hvc0= is used by services directly attached to /dev/hvc0 (such + * as serial-getty). While systemd can auto-detect the terminal type via DCS + * XTGETTCAP, not all terminal emulators implement this, so let's always propagate + * $TERM if we have it. */ + const char *term = getenv("TERM"); + if (term_env_valid(term)) { + FOREACH_STRING(tty_key, "systemd.tty.term.hvc0", "TERM") { + _cleanup_free_ char *p = strjoin(tty_key, "=", term); + if (!p) + return log_oom(); + + if (strv_consume_prepend(&arg_kernel_cmdline_extra, TAKE_PTR(p)) < 0) + return log_oom(); + } + } } _cleanup_free_ char *fstab_extra = NULL; From d49df940ec7ba43b00c25a1f9e3ee4e29b0788c2 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 30 Mar 2026 20:21:04 +0200 Subject: [PATCH 0711/1296] test-loop-block: Migrate to new assertion macros and framework While we're at it, rename to test-loop-util.c so it matches its source file. We also drop the root check and solely check for CAP_SYS_ADMIN since that's sufficient to run the tests. --- src/test/meson.build | 2 +- .../{test-loop-block.c => test-loop-util.c} | 184 +++++++++--------- test/units/TEST-02-UNITTESTS.sh | 2 +- 3 files changed, 91 insertions(+), 97 deletions(-) rename src/test/{test-loop-block.c => test-loop-util.c} (72%) diff --git a/src/test/meson.build b/src/test/meson.build index d11d853f1d46f..aa6e5b97f44e8 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -580,7 +580,7 @@ executables += [ 'dependencies' : common_test_dependencies, }, core_test_template + { - 'sources' : files('test-loop-block.c'), + 'sources' : files('test-loop-util.c'), 'dependencies' : [threads], 'parallel' : false, }, diff --git a/src/test/test-loop-block.c b/src/test/test-loop-util.c similarity index 72% rename from src/test/test-loop-block.c rename to src/test/test-loop-util.c index 76046f98ada19..98cd9cbaf890b 100644 --- a/src/test/test-loop-block.c +++ b/src/test/test-loop-util.c @@ -9,12 +9,12 @@ #include #include "alloc-util.h" +#include "argv-util.h" #include "capability-util.h" #include "dissect-image.h" #include "fd-util.h" #include "gpt.h" #include "loop-util.h" -#include "main-func.h" #include "mkfs-util.h" #include "mount-util.h" #include "namespace-util.h" @@ -35,14 +35,14 @@ static usec_t arg_timeout = 0; static usec_t end = 0; static void verify_dissected_image(DissectedImage *dissected) { - assert_se(dissected->partitions[PARTITION_ESP].found); - assert_se(dissected->partitions[PARTITION_ESP].node); - assert_se(dissected->partitions[PARTITION_XBOOTLDR].found); - assert_se(dissected->partitions[PARTITION_XBOOTLDR].node); - assert_se(dissected->partitions[PARTITION_ROOT].found); - assert_se(dissected->partitions[PARTITION_ROOT].node); - assert_se(dissected->partitions[PARTITION_HOME].found); - assert_se(dissected->partitions[PARTITION_HOME].node); + ASSERT_TRUE(dissected->partitions[PARTITION_ESP].found); + ASSERT_NOT_NULL(dissected->partitions[PARTITION_ESP].node); + ASSERT_TRUE(dissected->partitions[PARTITION_XBOOTLDR].found); + ASSERT_NOT_NULL(dissected->partitions[PARTITION_XBOOTLDR].node); + ASSERT_TRUE(dissected->partitions[PARTITION_ROOT].found); + ASSERT_NOT_NULL(dissected->partitions[PARTITION_ROOT].node); + ASSERT_TRUE(dissected->partitions[PARTITION_HOME].found); + ASSERT_NOT_NULL(dissected->partitions[PARTITION_HOME].node); } static void verify_dissected_image_harder(DissectedImage *dissected) { @@ -56,7 +56,6 @@ static void verify_dissected_image_harder(DissectedImage *dissected) { static void* thread_func(void *ptr) { int fd = PTR_TO_FD(ptr); - int r; for (unsigned i = 0; i < arg_n_iterations; i++) { _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL; @@ -70,28 +69,22 @@ static void* thread_func(void *ptr) { log_notice("> Thread iteration #%u.", i); - assert_se(mkdtemp_malloc(NULL, &mounted) >= 0); + ASSERT_OK(mkdtemp_malloc(NULL, &mounted)); - r = loop_device_make(fd, O_RDONLY, 0, UINT64_MAX, 0, LO_FLAGS_PARTSCAN, LOCK_SH, &loop); - if (r < 0) - log_error_errno(r, "Failed to allocate loopback device: %m"); - assert_se(r >= 0); - assert_se(loop->dev); - assert_se(loop->backing_file); + ASSERT_OK(loop_device_make(fd, O_RDONLY, 0, UINT64_MAX, 0, LO_FLAGS_PARTSCAN, LOCK_SH, &loop)); + ASSERT_NOT_NULL(loop->dev); + ASSERT_NOT_NULL(loop->backing_file); log_notice("Acquired loop device %s, will mount on %s", loop->node, mounted); - r = dissect_loop_device( + ASSERT_OK(dissect_loop_device( loop, /* verity= */ NULL, /* mount_options= */ NULL, /* image_policy= */ NULL, /* image_filter= */ NULL, DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, - &dissected); - if (r < 0) - log_error_errno(r, "Failed to dissect loopback device %s: %m", loop->node); - assert_se(r >= 0); + &dissected)); log_info("Dissected loop device %s", loop->node); @@ -107,19 +100,17 @@ static void* thread_func(void *ptr) { verify_dissected_image(dissected); - r = dissected_image_mount( + ASSERT_OK(dissected_image_mount( dissected, mounted, /* uid_shift= */ UID_INVALID, /* uid_range= */ UID_INVALID, /* userns_fd= */ -EBADF, - DISSECT_IMAGE_READ_ONLY); - log_notice_errno(r, "Mounted %s → %s: %m", loop->node, mounted); - assert_se(r >= 0); + DISSECT_IMAGE_READ_ONLY)); /* Now the block device is mounted, we don't need no manual lock anymore, the devices are now * pinned by the mounts. */ - assert_se(loop_device_flock(loop, LOCK_UN) >= 0); + ASSERT_OK(loop_device_flock(loop, LOCK_UN)); log_notice("Unmounting %s", mounted); mounted = umount_and_rmdir_and_free(mounted); @@ -147,47 +138,36 @@ static bool have_root_gpt_type(void) { #endif } -static int run(int argc, char *argv[]) { -#if HAVE_BLKID - _cleanup_(dissected_image_unrefp) DissectedImage *dissected = NULL; - _cleanup_(umount_and_rmdir_and_freep) char *mounted = NULL; - pthread_t threads[arg_n_threads]; - sd_id128_t id; -#endif - _cleanup_free_ char *p = NULL, *cmd = NULL; - _cleanup_pclose_ FILE *sfdisk = NULL; - _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL; - _cleanup_close_ int fd = -EBADF; +static int intro(void) { int r; - test_setup_logging(LOG_DEBUG); log_show_tid(true); log_show_time(true); log_show_color(true); - if (argc >= 2) { - r = safe_atou(argv[1], &arg_n_threads); + if (saved_argc >= 2) { + r = safe_atou(saved_argv[1], &arg_n_threads); if (r < 0) - return log_error_errno(r, "Failed to parse first argument (number of threads): %s", argv[1]); + return log_error_errno(r, "Failed to parse first argument (number of threads): %s", saved_argv[1]); if (arg_n_threads <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Number of threads must be at least 1, refusing."); } - if (argc >= 3) { - r = safe_atou(argv[2], &arg_n_iterations); + if (saved_argc >= 3) { + r = safe_atou(saved_argv[2], &arg_n_iterations); if (r < 0) - return log_error_errno(r, "Failed to parse second argument (number of iterations): %s", argv[2]); + return log_error_errno(r, "Failed to parse second argument (number of iterations): %s", saved_argv[2]); if (arg_n_iterations <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Number of iterations must be at least 1, refusing."); } - if (argc >= 4) { - r = parse_sec(argv[3], &arg_timeout); + if (saved_argc >= 4) { + r = parse_sec(saved_argv[3], &arg_timeout); if (r < 0) - return log_error_errno(r, "Failed to parse third argument (timeout): %s", argv[3]); + return log_error_errno(r, "Failed to parse third argument (timeout): %s", saved_argv[3]); } - if (argc >= 5) + if (saved_argc >= 5) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments (expected 3 at max)."); if (!have_root_gpt_type()) @@ -197,13 +177,27 @@ static int run(int argc, char *argv[]) { if (r < 0) return log_tests_skipped_errno(r, "Could not find sfdisk command"); - assert_se(tempfn_random_child("/var/tmp", "sfdisk", &p) >= 0); - fd = open(p, O_CREAT|O_EXCL|O_RDWR|O_CLOEXEC|O_NOFOLLOW, 0666); - assert_se(fd >= 0); - assert_se(ftruncate(fd, 256*1024*1024) >= 0); + return EXIT_SUCCESS; +} + +TEST(loop_block) { +#if HAVE_BLKID + _cleanup_(dissected_image_unrefp) DissectedImage *dissected = NULL; + _cleanup_(umount_and_rmdir_and_freep) char *mounted = NULL; + pthread_t threads[arg_n_threads]; + sd_id128_t id; +#endif + _cleanup_free_ char *p = NULL, *cmd = NULL; + _cleanup_pclose_ FILE *sfdisk = NULL; + _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL; + _cleanup_close_ int fd = -EBADF; + + ASSERT_OK(tempfn_random_child("/var/tmp", "sfdisk", &p)); + fd = ASSERT_OK_ERRNO(open(p, O_CREAT|O_EXCL|O_RDWR|O_CLOEXEC|O_NOFOLLOW, 0666)); + ASSERT_OK_ERRNO(ftruncate(fd, 256*1024*1024)); - assert_se(cmd = strjoin("sfdisk ", p)); - assert_se(sfdisk = popen(cmd, "we")); + cmd = ASSERT_NOT_NULL(strjoin("sfdisk ", p)); + sfdisk = ASSERT_NOT_NULL(popen(cmd, "we")); /* A reasonably complex partition table that fits on a 64K disk */ fputs("label: gpt\n" @@ -221,62 +215,63 @@ static int run(int argc, char *argv[]) { fputs("\n" "size=32M, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915\n", sfdisk); - assert_se(pclose(sfdisk) == 0); + ASSERT_EQ(pclose(sfdisk), 0); sfdisk = NULL; #if HAVE_BLKID - assert_se(dissect_image_file( + ASSERT_OK(dissect_image_file( p, /* verity= */ NULL, /* mount_options= */ NULL, /* image_policy= */ NULL, /* image_filter= */ NULL, /* flags= */ 0, - &dissected) >= 0); + &dissected)); verify_dissected_image(dissected); dissected = dissected_image_unref(dissected); #endif - if (geteuid() != 0 || have_effective_cap(CAP_SYS_ADMIN) <= 0) - return log_tests_skipped("not running privileged"); + if (have_effective_cap(CAP_SYS_ADMIN) <= 0) { + log_tests_skipped("not running privileged"); + return; + } - if (detect_container() != 0 || running_in_chroot() != 0) - return log_tests_skipped("Test not supported in a container/chroot, requires udev/uevent notifications"); + if (detect_container() != 0 || running_in_chroot() != 0) { + log_tests_skipped("Test not supported in a container/chroot, requires udev/uevent notifications"); + return; + } - assert_se(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 0, LO_FLAGS_PARTSCAN, LOCK_EX, &loop) >= 0); + ASSERT_OK(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 0, LO_FLAGS_PARTSCAN, LOCK_EX, &loop)); #if HAVE_BLKID - assert_se(dissect_loop_device( + ASSERT_OK(dissect_loop_device( loop, /* verity= */ NULL, /* mount_options= */ NULL, /* image_policy= */ NULL, /* image_filter= */ NULL, DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, - &dissected) >= 0); + &dissected)); verify_dissected_image(dissected); FOREACH_STRING(fs, "vfat", "ext4") { - r = mkfs_exists(fs); - assert_se(r >= 0); - if (!r) { + if (ASSERT_OK(mkfs_exists(fs)) == 0) { log_tests_skipped("mkfs.{vfat|ext4} not installed"); - return 0; + return; } } - assert_se(r >= 0); - assert_se(sd_id128_randomize(&id) >= 0); - assert_se(make_filesystem(dissected->partitions[PARTITION_ESP].node, "vfat", "EFI", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL) >= 0); + ASSERT_OK(sd_id128_randomize(&id)); + ASSERT_OK(make_filesystem(dissected->partitions[PARTITION_ESP].node, "vfat", "EFI", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL)); - assert_se(sd_id128_randomize(&id) >= 0); - assert_se(make_filesystem(dissected->partitions[PARTITION_XBOOTLDR].node, "vfat", "xbootldr", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL) >= 0); + ASSERT_OK(sd_id128_randomize(&id)); + ASSERT_OK(make_filesystem(dissected->partitions[PARTITION_XBOOTLDR].node, "vfat", "xbootldr", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL)); - assert_se(sd_id128_randomize(&id) >= 0); - assert_se(make_filesystem(dissected->partitions[PARTITION_ROOT].node, "ext4", "root", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL) >= 0); + ASSERT_OK(sd_id128_randomize(&id)); + ASSERT_OK(make_filesystem(dissected->partitions[PARTITION_ROOT].node, "ext4", "root", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL)); - assert_se(sd_id128_randomize(&id) >= 0); - assert_se(make_filesystem(dissected->partitions[PARTITION_HOME].node, "ext4", "home", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL) >= 0); + ASSERT_OK(sd_id128_randomize(&id)); + ASSERT_OK(make_filesystem(dissected->partitions[PARTITION_HOME].node, "ext4", "home", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL)); dissected = dissected_image_unref(dissected); @@ -285,62 +280,62 @@ static int run(int argc, char *argv[]) { * hence what was written via the partition device might not appear on the whole block device * yet. Let's hence explicitly flush the whole block device, so that the read-back definitely * works. */ - assert_se(ioctl(loop->fd, BLKFLSBUF, 0) >= 0); + ASSERT_OK_ERRNO(ioctl(loop->fd, BLKFLSBUF, 0)); /* Try to read once, without pinning or adding partitions, i.e. by only accessing the whole block * device. */ - assert_se(dissect_loop_device( + ASSERT_OK(dissect_loop_device( loop, /* verity= */ NULL, /* mount_options= */ NULL, /* image_policy= */ NULL, /* image_filter= */ NULL, /* flags= */ 0, - &dissected) >= 0); + &dissected)); verify_dissected_image_harder(dissected); dissected = dissected_image_unref(dissected); /* Now go via the loopback device after all, but this time add/pin, because now we want to mount it. */ - assert_se(dissect_loop_device( + ASSERT_OK(dissect_loop_device( loop, /* verity= */ NULL, /* mount_options= */ NULL, /* image_policy= */ NULL, /* image_filter= */ NULL, DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, - &dissected) >= 0); + &dissected)); verify_dissected_image_harder(dissected); - assert_se(mkdtemp_malloc(NULL, &mounted) >= 0); + ASSERT_OK(mkdtemp_malloc(NULL, &mounted)); /* We are particularly correct here, and now downgrade LOCK → LOCK_SH. That's because we are done * with formatting the file systems, so we don't need the exclusive lock anymore. From now on a * shared one is fine. This way udev can now probe the device if it wants, but still won't call * BLKRRPART on it, and that's good, because that would destroy our partition table while we are at * it. */ - assert_se(loop_device_flock(loop, LOCK_SH) >= 0); + ASSERT_OK(loop_device_flock(loop, LOCK_SH)); /* This is a test for the loopback block device setup code and it's use by the image dissection * logic: since the kernel APIs are hard use and prone to races, let's test this in a heavy duty * test: we open a bunch of threads and repeatedly allocate and deallocate loopback block devices in * them in parallel, with an image file with a number of partitions. */ - assert_se(detach_mount_namespace() >= 0); + ASSERT_OK(detach_mount_namespace()); /* This first (writable) mount will initialize the mount point dirs, so that the subsequent read-only ones can work */ - assert_se(dissected_image_mount( + ASSERT_OK(dissected_image_mount( dissected, mounted, /* uid_shift= */ UID_INVALID, /* uid_range= */ UID_INVALID, /* userns_fd= */ -EBADF, - 0) >= 0); + 0)); /* Now we mounted everything, the partitions are pinned. Now it's fine to release the lock * fully. This means udev could now issue BLKRRPART again, but that's OK given this will fail because * we now mounted the device. */ - assert_se(loop_device_flock(loop, LOCK_UN) >= 0); + ASSERT_OK(loop_device_flock(loop, LOCK_UN)); - assert_se(umount_recursive(mounted, 0) >= 0); + ASSERT_OK(umount_recursive(mounted, 0)); loop = loop_device_unref(loop); log_notice("Threads are being started now"); @@ -353,7 +348,7 @@ static int run(int argc, char *argv[]) { if (arg_n_threads > 1) for (unsigned i = 0; i < arg_n_threads; i++) - assert_se(pthread_create(threads + i, NULL, thread_func, FD_TO_PTR(fd)) == 0); + ASSERT_EQ(pthread_create(threads + i, NULL, thread_func, FD_TO_PTR(fd)), 0); log_notice("All threads started now."); @@ -364,8 +359,8 @@ static int run(int argc, char *argv[]) { log_notice("Joining thread #%u.", i); void *k; - assert_se(pthread_join(threads[i], &k) == 0); - assert_se(!k); + ASSERT_EQ(pthread_join(threads[i], &k), 0); + ASSERT_NULL(k); log_notice("Joined thread #%u.", i); } @@ -374,7 +369,6 @@ static int run(int argc, char *argv[]) { #else log_notice("Cutting test short, since we do not have libblkid."); #endif - return 0; } -DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/test/units/TEST-02-UNITTESTS.sh b/test/units/TEST-02-UNITTESTS.sh index ee5eab08d566d..2a38062a41046 100755 --- a/test/units/TEST-02-UNITTESTS.sh +++ b/test/units/TEST-02-UNITTESTS.sh @@ -24,7 +24,7 @@ if [[ -z "${TEST_MATCH_SUBTEST:-}" ]]; then # in QEMU to only those that can't run in a container to avoid running # the same tests again in a, most likely, very slow environment if ! systemd-detect-virt -qc && [[ "${TEST_PREFER_NSPAWN:-0}" -ne 0 ]]; then - TEST_MATCH_SUBTEST="test-loop-block" + TEST_MATCH_SUBTEST="test-loop-util" else TEST_MATCH_SUBTEST="test-*" fi From eb584f164f245b7a779868bed2210625ad27dbff Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 30 Mar 2026 19:23:48 +0200 Subject: [PATCH 0712/1296] loop-util: create loop device for block devices with sector size mismatch Previously, loop_device_make_internal() always used the block device directly (via loop_device_open_from_fd()) for whole-device access, regardless of sector size. This is incorrect when the GPT partition table was written with a different sector size than the device reports, as happens with CD-ROM/ISO boot via El Torito: the device has 2048-byte blocks but the GPT uses 512-byte sectors. Restructure the sector size handling in loop_device_make_internal(): - Move GPT sector size probing (UINT32_MAX case) before the block-vs-regular-file split so both paths share the same logic and O_DIRECT handling. Check f_flags instead of loop_flags for O_DIRECT detection, since we're probing the original fd before any reopening. - For block devices, get the device sector size and compare it against the resolved sector_size. Only use the block device directly when sector sizes match. When they differ (probed GPT mismatch or explicit sector size request), fall through to create a real loop device with the correct sector size. - Default sector_size=0 to the device sector size for block devices (instead of always 512), so "no preference" correctly matches the device's sector size. Co-developed-by: Claude Opus 4.6 --- src/shared/loop-util.c | 105 ++++++++++++++---------- src/test/test-loop-util.c | 167 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 228 insertions(+), 44 deletions(-) diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c index 4a759f4a7e24e..0ab5d5fd8cf8e 100644 --- a/src/shared/loop-util.c +++ b/src/shared/loop-util.c @@ -413,6 +413,33 @@ static int fd_set_max_discard(int fd, uint64_t max_discard) { return write_string_filef(sysfs_path, WRITE_STRING_FILE_DISABLE_BUFFER, "%" PRIu64, max_discard); } +static int probe_sector_size_harder(int fd, uint32_t *ret) { + _cleanup_close_ int non_direct_io_fd = -EBADF; + int probe_fd, f_flags; + + assert(fd >= 0); + assert(ret); + + /* Wraps probe_sector_size() but handles O_DIRECT: if the fd is opened with O_DIRECT there are + * strict alignment requirements for reads, so we temporarily reopen it without O_DIRECT for the + * probing logic. */ + + f_flags = fcntl(fd, F_GETFL); + if (f_flags < 0) + return -errno; + + if (FLAGS_SET(f_flags, O_DIRECT)) { + non_direct_io_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK); + if (non_direct_io_fd < 0) + return non_direct_io_fd; + + probe_fd = non_direct_io_fd; + } else + probe_fd = fd; + + return probe_sector_size(probe_fd, ret); +} + static int loop_device_make_internal( const char *path, int fd, @@ -435,6 +462,11 @@ static int loop_device_make_internal( assert(open_flags < 0 || IN_SET(open_flags, O_RDWR, O_RDONLY)); assert(ret); + /* sector_size interpretation: + * 0 → use device sector size for block devices, 512 for regular files + * UINT32_MAX → probe GPT header to find the right sector size, fall back to 0 behavior + * other → use the specified sector size explicitly */ + f_flags = fcntl(fd, F_GETFL); if (f_flags < 0) return -errno; @@ -449,19 +481,45 @@ static int loop_device_make_internal( return log_debug_errno(SYNTHETIC_ERRNO(EBADFD), "Access mode of image file is write only (?)"); } + if (sector_size == UINT32_MAX) { + /* If sector size is specified as UINT32_MAX, we'll try to probe the right sector size + * by looking for the GPT partition header at various offsets. This of course only works + * if the image already has a disk label. */ + + r = probe_sector_size_harder(fd, §or_size); + if (r < 0) + return r; + if (r == 0) + sector_size = 0; /* If we can't probe anything, use default sector size. */ + } + if (fstat(fd, &st) < 0) return -errno; if (S_ISBLK(st.st_mode)) { - if (offset == 0 && IN_SET(size, 0, UINT64_MAX)) + uint32_t device_ssz; + r = blockdev_get_sector_size(fd, &device_ssz); + if (r < 0) + return r; + + if (sector_size == 0) + sector_size = device_ssz; + + if (offset == 0 && IN_SET(size, 0, UINT64_MAX) && sector_size == device_ssz) /* If this is already a block device and we are supposed to cover the whole of it - * then store an fd to the original open device node — and do not actually create an - * unnecessary loopback device for it. */ + * then store an fd to the original open device node — and do not actually create + * an unnecessary loopback device for it. If an explicit sector size was requested + * that differs from the device sector size, or if the probed GPT sector size + * differs (e.g. CD-ROMs with 2048-byte blocks but a 512-byte sector GPT), create + * a real loop device to change the sector size. */ return loop_device_open_from_fd(fd, open_flags, lock_op, ret); } else { r = stat_verify_regular(&st); if (r < 0) return r; + + if (sector_size == 0) + sector_size = 512; } if (path) { @@ -500,47 +558,6 @@ static int loop_device_make_internal( if (control < 0) return -errno; - if (sector_size == 0) - /* If no sector size is specified, default to the classic default */ - sector_size = 512; - else if (sector_size == UINT32_MAX) { - - if (S_ISBLK(st.st_mode)) - /* If the sector size is specified as UINT32_MAX we'll propagate the sector size of - * the underlying block device. */ - r = blockdev_get_sector_size(fd, §or_size); - else { - _cleanup_close_ int non_direct_io_fd = -EBADF; - int probe_fd; - - assert(S_ISREG(st.st_mode)); - - /* If sector size is specified as UINT32_MAX, we'll try to probe the right sector - * size of the image in question by looking for the GPT partition header at various - * offsets. This of course only works if the image already has a disk label. - * - * So here we actually want to read the file contents ourselves. This is quite likely - * not going to work if we managed to enable O_DIRECT, because in such a case there - * are some pretty strict alignment requirements to offset, size and target, but - * there's no way to query what alignment specifically is actually required. Hence, - * let's avoid the mess, and temporarily open an fd without O_DIRECT for the probing - * logic. */ - - if (FLAGS_SET(loop_flags, LO_FLAGS_DIRECT_IO)) { - non_direct_io_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK); - if (non_direct_io_fd < 0) - return non_direct_io_fd; - - probe_fd = non_direct_io_fd; - } else - probe_fd = fd; - - r = probe_sector_size(probe_fd, §or_size); - } - if (r < 0) - return r; - } - /* Strip LO_FLAGS_PARTSCAN from LOOP_CONFIGURE and enable it afterwards via * LOOP_SET_STATUS64 to work around a kernel race: LOOP_CONFIGURE sends a uevent with * GD_NEED_PART_SCAN set before calling loop_reread_partitions(). If udev opens the device in diff --git a/src/test/test-loop-util.c b/src/test/test-loop-util.c index 98cd9cbaf890b..f90bf0e1998fb 100644 --- a/src/test/test-loop-util.c +++ b/src/test/test-loop-util.c @@ -371,4 +371,171 @@ TEST(loop_block) { #endif } +static int make_test_image(int *ret_fd) { + _cleanup_free_ char *p = NULL, *cmd = NULL; + _cleanup_pclose_ FILE *sfdisk = NULL; + + ASSERT_OK(tempfn_random_child("/var/tmp", "sfdisk", &p)); + int fd = ASSERT_OK_ERRNO(open(p, O_CREAT|O_EXCL|O_RDWR|O_CLOEXEC|O_NOFOLLOW, 0666)); + ASSERT_OK_ERRNO(ftruncate(fd, 256*1024*1024)); + + cmd = ASSERT_NOT_NULL(strjoin("sfdisk ", p)); + sfdisk = ASSERT_NOT_NULL(popen(cmd, "we")); + + fputs("label: gpt\n" + "size=32M, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B\n", sfdisk); + + ASSERT_EQ(pclose(sfdisk), 0); + sfdisk = NULL; + + (void) unlink(p); + + *ret_fd = fd; + return 0; +} + +TEST(sector_size_regular_file) { + _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL; + _cleanup_close_ int fd = -EBADF; + + if (have_effective_cap(CAP_SYS_ADMIN) <= 0) { + log_tests_skipped("not running privileged"); + return; + } + + if (detect_container() != 0 || running_in_chroot() != 0) { + log_tests_skipped("Test not supported in a container/chroot"); + return; + } + + ASSERT_OK(make_test_image(&fd)); + + /* sector_size=0 on regular file: should default to 512 */ + ASSERT_OK(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 0, 0, LOCK_EX, &loop)); + ASSERT_EQ(loop->sector_size, 512u); + loop = loop_device_unref(loop); + + /* sector_size=UINT32_MAX on regular file with GPT: should probe and find 512 */ + ASSERT_OK(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, UINT32_MAX, 0, LOCK_EX, &loop)); + ASSERT_EQ(loop->sector_size, 512u); + loop = loop_device_unref(loop); + + /* Explicit sector_size=512 on regular file */ + ASSERT_OK(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 512, 0, LOCK_EX, &loop)); + ASSERT_EQ(loop->sector_size, 512u); + loop = loop_device_unref(loop); + + /* Explicit sector_size=4096 on regular file */ + ASSERT_OK(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 4096, 0, LOCK_EX, &loop)); + ASSERT_EQ(loop->sector_size, 4096u); + loop = loop_device_unref(loop); +} + +TEST(sector_size_block_device) { + _cleanup_(loop_device_unrefp) LoopDevice *block_loop = NULL, *loop = NULL; + _cleanup_close_ int fd = -EBADF; + + if (have_effective_cap(CAP_SYS_ADMIN) <= 0) { + log_tests_skipped("not running privileged"); + return; + } + + if (detect_container() != 0 || running_in_chroot() != 0) { + log_tests_skipped("Test not supported in a container/chroot, requires udev/uevent notifications"); + return; + } + + ASSERT_OK(make_test_image(&fd)); + + /* Create a loop device to use as our block device */ + ASSERT_OK(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 0, LO_FLAGS_PARTSCAN, LOCK_EX, &block_loop)); + ASSERT_FALSE(LOOP_DEVICE_IS_FOREIGN(block_loop)); + ASSERT_OK(loop_device_flock(block_loop, LOCK_SH)); + + uint32_t device_ssz = block_loop->sector_size; + + /* sector_size=0 on block device: should use device directly */ + ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, 0, 0, LOCK_SH, &loop)); + ASSERT_FALSE(loop->created); + ASSERT_EQ(loop->sector_size, device_ssz); + loop = loop_device_unref(loop); + + /* sector_size=UINT32_MAX on block device: should probe, match device, use directly */ + ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, UINT32_MAX, 0, LOCK_SH, &loop)); + ASSERT_FALSE(loop->created); + ASSERT_EQ(loop->sector_size, device_ssz); + loop = loop_device_unref(loop); + + /* sector_size=UINT32_MAX with LO_FLAGS_PARTSCAN: should probe, match, use directly */ + ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, UINT32_MAX, LO_FLAGS_PARTSCAN, LOCK_SH, &loop)); + ASSERT_FALSE(loop->created); + ASSERT_EQ(loop->sector_size, device_ssz); + loop = loop_device_unref(loop); + + /* Explicit sector_size matching device: should use device directly */ + ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, device_ssz, 0, LOCK_SH, &loop)); + ASSERT_FALSE(loop->created); + ASSERT_EQ(loop->sector_size, device_ssz); + loop = loop_device_unref(loop); + + /* Explicit sector_size=4096 (differs from device 512): should create a real loop device */ + if (device_ssz != 4096) { + ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, 4096, 0, LOCK_SH, &loop)); + ASSERT_TRUE(loop->created); + ASSERT_EQ(loop->sector_size, 4096u); + loop = loop_device_unref(loop); + } +} + +TEST(sector_size_mismatch) { + _cleanup_(loop_device_unrefp) LoopDevice *block_loop = NULL, *loop = NULL; + _cleanup_close_ int fd = -EBADF; + + if (have_effective_cap(CAP_SYS_ADMIN) <= 0) { + log_tests_skipped("not running privileged"); + return; + } + + if (detect_container() != 0 || running_in_chroot() != 0) { + log_tests_skipped("Test not supported in a container/chroot"); + return; + } + + /* Create an image with a GPT written at 512-byte sectors, then create a loop device with + * 4096-byte sectors on top. This simulates the CD-ROM scenario where the device has large + * blocks but the GPT uses 512-byte sectors. */ + ASSERT_OK(make_test_image(&fd)); + + /* Create a loop device with 4096-byte sector size — GPT was written at 512 */ + ASSERT_OK(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 4096, 0, LOCK_EX, &block_loop)); + ASSERT_TRUE(block_loop->created); + ASSERT_EQ(block_loop->sector_size, 4096u); + ASSERT_OK(loop_device_flock(block_loop, LOCK_SH)); + + /* sector_size=0: no preference, should use block device directly despite GPT mismatch */ + ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, 0, 0, LOCK_SH, &loop)); + ASSERT_FALSE(loop->created); + ASSERT_EQ(loop->sector_size, 4096u); + loop = loop_device_unref(loop); + + /* sector_size=UINT32_MAX: should probe GPT at 512, detect mismatch with device 4096, + * and create a new loop device with 512-byte sectors */ + ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, UINT32_MAX, 0, LOCK_SH, &loop)); + ASSERT_TRUE(loop->created); + ASSERT_EQ(loop->sector_size, 512u); + loop = loop_device_unref(loop); + + /* Explicit sector_size=512: differs from device 4096, should create a new loop device */ + ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, 512, 0, LOCK_SH, &loop)); + ASSERT_TRUE(loop->created); + ASSERT_EQ(loop->sector_size, 512u); + loop = loop_device_unref(loop); + + /* Explicit sector_size=4096: matches device, should use directly */ + ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, 4096, 0, LOCK_SH, &loop)); + ASSERT_FALSE(loop->created); + ASSERT_EQ(loop->sector_size, 4096u); + loop = loop_device_unref(loop); +} + DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); From a586648800bf6c3f83c8b64f123d8e9df4857bc4 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 2 Apr 2026 09:57:46 +0000 Subject: [PATCH 0713/1296] vmspawn: Add --console-transport= option to select serial vs virtio-serial Add a --console-transport= option that selects between virtio-serial (the default, appearing as /dev/hvc0) and a regular serial port (appearing as /dev/ttyS0 or /dev/ttyAMA0 depending on architecture). This is primarily useful for testing purposes, for example to test sd-stub's automatic console= kernel command line parameter handling. It allows verifying that the guest OS correctly handles serial console configurations without virtio. When serial transport is selected, -serial chardev:console is used on the QEMU command line to connect the chardev to the platform's default serial device. This cannot be done via the QEMU config file as on some platforms (e.g. ARM) the serial device is a sysbus device that can only be connected via serial_hd() which is populated by -serial. --- man/systemd-vmspawn.xml | 14 ++++++++++ src/vmspawn/vmspawn-settings.c | 7 +++++ src/vmspawn/vmspawn-settings.h | 8 ++++++ src/vmspawn/vmspawn-util.h | 6 ++++ src/vmspawn/vmspawn.c | 50 ++++++++++++++++++++++++++-------- 5 files changed, 73 insertions(+), 12 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 129f5ba14199f..9feb7407ca6ee 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -712,6 +712,20 @@ + + + + Configures the transport to use for the VM console. Takes one of + virtio or serial. Defaults to virtio. + virtio uses a virtio-serial device, which appears as + /dev/hvc0 in the VM. serial uses a regular serial port, + which appears as /dev/ttyS0 (or /dev/ttyAMA0 on ARM) in the VM. This option only has an effect in + , , and + modes. + + + + diff --git a/src/vmspawn/vmspawn-settings.c b/src/vmspawn/vmspawn-settings.c index 776b590252ee5..56a07b3f6f01d 100644 --- a/src/vmspawn/vmspawn-settings.c +++ b/src/vmspawn/vmspawn-settings.c @@ -37,3 +37,10 @@ static const char *const console_mode_table[_CONSOLE_MODE_MAX] = { }; DEFINE_STRING_TABLE_LOOKUP(console_mode, ConsoleMode); + +static const char *const console_transport_table[_CONSOLE_TRANSPORT_MAX] = { + [CONSOLE_TRANSPORT_VIRTIO] = "virtio", + [CONSOLE_TRANSPORT_SERIAL] = "serial", +}; + +DEFINE_STRING_TABLE_LOOKUP(console_transport, ConsoleTransport); diff --git a/src/vmspawn/vmspawn-settings.h b/src/vmspawn/vmspawn-settings.h index b897f148e131a..83d28725359df 100644 --- a/src/vmspawn/vmspawn-settings.h +++ b/src/vmspawn/vmspawn-settings.h @@ -42,6 +42,13 @@ typedef enum ConsoleMode { _CONSOLE_MODE_INVALID = -EINVAL, } ConsoleMode; +typedef enum ConsoleTransport { + CONSOLE_TRANSPORT_VIRTIO, /* virtio-serial (hvc0) */ + CONSOLE_TRANSPORT_SERIAL, /* regular serial port (ttyS0/ttyAMA0) */ + _CONSOLE_TRANSPORT_MAX, + _CONSOLE_TRANSPORT_INVALID = -EINVAL, +} ConsoleTransport; + typedef enum SettingsMask { SETTING_START_MODE = UINT64_C(1) << 0, SETTING_MACHINE_ID = UINT64_C(1) << 6, @@ -53,5 +60,6 @@ typedef enum SettingsMask { } SettingsMask; DECLARE_STRING_TABLE_LOOKUP(console_mode, ConsoleMode); +DECLARE_STRING_TABLE_LOOKUP(console_transport, ConsoleTransport); DECLARE_STRING_TABLE_LOOKUP(disk_type, DiskType); DECLARE_STRING_TABLE_LOOKUP(image_format, ImageFormat); diff --git a/src/vmspawn/vmspawn-util.h b/src/vmspawn/vmspawn-util.h index 90efd93661224..9fec6641aa3d0 100644 --- a/src/vmspawn/vmspawn-util.h +++ b/src/vmspawn/vmspawn-util.h @@ -49,6 +49,12 @@ # define QEMU_MACHINE_TYPE "none" #endif +#if defined(__arm__) || defined(__aarch64__) +# define QEMU_SERIAL_CONSOLE_NAME "ttyAMA0" +#else +# define QEMU_SERIAL_CONSOLE_NAME "ttyS0" +#endif + typedef struct OvmfConfig { char *path; char *format; diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 1891ac8bad742..85165e1af7a98 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -137,6 +137,7 @@ static int arg_tpm = -1; static char *arg_linux = NULL; static char **arg_initrds = NULL; static ConsoleMode arg_console_mode = CONSOLE_INTERACTIVE; +static ConsoleTransport arg_console_transport = CONSOLE_TRANSPORT_VIRTIO; static NetworkStack arg_network_stack = NETWORK_STACK_NONE; static MachineCredentialContext arg_credentials = {}; static uid_t arg_uid_shift = UID_INVALID, arg_uid_range = 0x10000U; @@ -291,6 +292,8 @@ static int help(void) { "\n%3$sInput/Output:%4$s\n" " --console=MODE Console mode (interactive, native, gui, read-only\n" " or headless)\n" + " --console-transport=TRANSPORT\n" + " Console transport (virtio or serial)\n" " --background=COLOR Set ANSI color for background\n" "\n%3$sCredentials:%4$s\n" " --set-credential=ID:VALUE\n" @@ -375,6 +378,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_USER, ARG_IMAGE_FORMAT, ARG_IMAGE_DISK_TYPE, + ARG_CONSOLE_TRANSPORT, }; static const struct option options[] = { @@ -402,6 +406,7 @@ static int parse_argv(int argc, char *argv[]) { { "linux", required_argument, NULL, ARG_LINUX }, { "initrd", required_argument, NULL, ARG_INITRD }, { "console", required_argument, NULL, ARG_CONSOLE }, + { "console-transport", required_argument, NULL, ARG_CONSOLE_TRANSPORT }, { "qemu-gui", no_argument, NULL, ARG_QEMU_GUI }, /* compat option */ { "network-tap", no_argument, NULL, 'n' }, { "network-user-mode", no_argument, NULL, ARG_NETWORK_USER_MODE }, @@ -578,6 +583,13 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_CONSOLE_TRANSPORT: + arg_console_transport = console_transport_from_string(optarg); + if (arg_console_transport < 0) + return log_error_errno(arg_console_transport, "Failed to parse specified console transport: %s", optarg); + + break; + case ARG_QEMU_GUI: arg_console_mode = CONSOLE_GUI; break; @@ -2588,11 +2600,6 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (r < 0) return log_oom(); - r = qemu_config_section(config_file, "device", "vmspawn-virtio-serial-pci", - "driver", "virtio-serial-pci"); - if (r < 0) - return r; - /* Enable mux for native console so the QEMU monitor is accessible via Ctrl-a c */ r = qemu_config_section(config_file, "chardev", "console", "backend", "serial", @@ -2601,12 +2608,6 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (r < 0) return r; - r = qemu_config_section(config_file, "device", "virtconsole0", - "driver", "virtconsole", - "chardev", "console"); - if (r < 0) - return r; - if (arg_console_mode == CONSOLE_NATIVE) { r = qemu_config_section(config_file, "mon", "mon0", "chardev", "console"); @@ -2655,6 +2656,29 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { assert_not_reached(); } + if (!IN_SET(arg_console_mode, CONSOLE_GUI, CONSOLE_HEADLESS)) { + if (arg_console_transport == CONSOLE_TRANSPORT_SERIAL) { + /* Use -serial to connect the chardev to the platform's default serial + * device (e.g. isa-serial on x86, PL011 on ARM). On some platforms the + * serial device is a sysbus device that can only be connected via + * serial_hd() which is populated by -serial, not via the config file. */ + r = strv_extend_many(&cmdline, "-serial", "chardev:console"); + if (r < 0) + return log_oom(); + } else { + r = qemu_config_section(config_file, "device", "vmspawn-virtio-serial-pci", + "driver", "virtio-serial-pci"); + if (r < 0) + return r; + + r = qemu_config_section(config_file, "device", "virtconsole0", + "driver", "virtconsole", + "chardev", "console"); + if (r < 0) + return r; + } + } + r = qemu_config_section(config_file, "drive", "ovmf-code", "if", "pflash", "format", ovmf_config_format(ovmf_config), @@ -3028,7 +3052,9 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } if (!IN_SET(arg_console_mode, CONSOLE_GUI, CONSOLE_HEADLESS)) { - r = strv_prepend(&arg_kernel_cmdline_extra, "console=hvc0"); + r = strv_prepend(&arg_kernel_cmdline_extra, + arg_console_transport == CONSOLE_TRANSPORT_SERIAL ? + "console=" QEMU_SERIAL_CONSOLE_NAME : "console=hvc0"); if (r < 0) return log_oom(); From 1f6935145dd181cd7ce33b94e5aa0a90fa468c2d Mon Sep 17 00:00:00 2001 From: Nikolas Kyx <4662868-nyx23@users.noreply.gitlab.com> Date: Tue, 31 Mar 2026 15:48:58 +0300 Subject: [PATCH 0714/1296] manager: Add DefaultMemoryZSwapWriteback Allow setting system-wide MemoryZSwapWriteback in system.conf Resolves: #41320 --- man/org.freedesktop.systemd1.xml | 7 ++ man/systemd-system.conf.xml | 12 ++ man/systemd.resource-control.xml | 11 +- src/core/dbus-manager.c | 1 + src/core/main.c | 152 ++++++++++++------------ src/core/manager.c | 4 + src/core/manager.h | 2 + src/core/system.conf.in | 1 + src/core/unit.c | 2 + src/core/varlink-manager.c | 1 + src/shared/varlink-io.systemd.Manager.c | 4 +- 11 files changed, 116 insertions(+), 81 deletions(-) diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index f4a06901b0368..cbeb25efcd767 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -564,6 +564,8 @@ node /org/freedesktop/systemd1 { readonly s CtrlAltDelBurstAction = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly u SoftRebootsCount = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly b DefaultMemoryZSwapWriteback = ...; }; interface org.freedesktop.DBus.Peer { ... }; interface org.freedesktop.DBus.Introspectable { ... }; @@ -801,6 +803,8 @@ node /org/freedesktop/systemd1 { + + @@ -1251,6 +1255,8 @@ node /org/freedesktop/systemd1 { + + @@ -12469,6 +12475,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ RemoveSubgroupFromUnit(), and KillUnitSubgroup() were added in version 258. TransactionsWithOrderingCycle was added in version 259. + DefaultMemoryZSwapWriteback was added in version 261. Unit Objects diff --git a/man/systemd-system.conf.xml b/man/systemd-system.conf.xml index b7fe53dc9cf38..172657de65cbf 100644 --- a/man/systemd-system.conf.xml +++ b/man/systemd-system.conf.xml @@ -688,6 +688,18 @@ + + + DefaultMemoryZSwapWriteback= + + Takes a boolean argument. Defaults to true if unspecified. This is used as a default + for units which lack an explicit definition for MemoryZSwapWriteback=. + See systemd.resource-control5 + for the details. + + + + diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml index 12a3c0e644eba..ac31971e54f6e 100644 --- a/man/systemd.resource-control.xml +++ b/man/systemd.resource-control.xml @@ -481,13 +481,16 @@ CPUWeight=20 DisableControllers=cpu / \ This setting controls the controller in the unified hierarchy. - Takes a boolean argument. When true, pages stored in the Zswap cache are permitted to be - written to the backing storage, false otherwise. Defaults to true. This allows disabling - writeback of swap pages for IO-intensive applications, while retaining the ability to store - compressed pages in Zswap. See the kernel's + Takes a boolean argument. Defaults to true if DefaultMemoryZSwapWriteback= + is not set. When true, pages stored in the Zswap cache are permitted to be + written to the backing storage, false otherwise. This allows disabling writeback of swap pages for + IO-intensive applications, while retaining the ability to store compressed pages in Zswap. See the kernel's Zswap documentation for more details. + The system default for this setting may be controlled with DefaultMemoryZSwapWriteback= + in systemd-system.conf5. + diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 088d6c508ee91..749e2261af7a9 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -2988,6 +2988,7 @@ const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_PROPERTY("DefaultRestrictSUIDSGID", "b", bus_property_get_bool, offsetof(Manager, defaults.restrict_suid_sgid), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("CtrlAltDelBurstAction", "s", bus_property_get_emergency_action, offsetof(Manager, cad_burst_action), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SoftRebootsCount", "u", bus_property_get_unsigned, offsetof(Manager, soft_reboots_count), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultMemoryZSwapWriteback", "b", bus_property_get_bool, offsetof(Manager, defaults.memory_zswap_writeback), SD_BUS_VTABLE_PROPERTY_CONST), /* deprecated cgroup v1 property */ SD_BUS_PROPERTY("DefaultBlockIOAccounting", "b", bus_property_get_bool_false, 0, SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_DEPRECATED|SD_BUS_VTABLE_HIDDEN), diff --git a/src/core/main.c b/src/core/main.c index 3a6284b456bc3..bd065c351d23a 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -735,88 +735,88 @@ static int config_parse_crash_reboot( static int parse_config_file(void) { const ConfigTableItem items[] = { - { "Manager", "LogLevel", config_parse_level2, 0, NULL }, - { "Manager", "LogTarget", config_parse_target, 0, NULL }, - { "Manager", "LogColor", config_parse_color, 0, NULL }, - { "Manager", "LogLocation", config_parse_location, 0, NULL }, - { "Manager", "LogTime", config_parse_time, 0, NULL }, - { "Manager", "DumpCore", config_parse_bool, 0, &arg_dump_core }, - { "Manager", "CrashChVT", /* legacy */ config_parse_crash_chvt, 0, &arg_crash_chvt }, - { "Manager", "CrashChangeVT", config_parse_crash_chvt, 0, &arg_crash_chvt }, - { "Manager", "CrashShell", config_parse_bool, 0, &arg_crash_shell }, - { "Manager", "CrashReboot", config_parse_crash_reboot, 0, &arg_crash_action }, - { "Manager", "CrashAction", config_parse_crash_action, 0, &arg_crash_action }, - { "Manager", "ShowStatus", config_parse_show_status, 0, &arg_show_status }, - { "Manager", "StatusUnitFormat", config_parse_status_unit_format, 0, &arg_status_unit_format }, - { "Manager", "CPUAffinity", config_parse_cpu_set, 0, &arg_cpu_affinity }, - { "Manager", "NUMAPolicy", config_parse_numa_policy, 0, &arg_numa_policy.type }, - { "Manager", "NUMAMask", config_parse_numa_mask, 0, &arg_numa_policy.nodes }, - { "Manager", "JoinControllers", config_parse_warn_compat, DISABLED_LEGACY, NULL }, - { "Manager", "RuntimeWatchdogSec", config_parse_watchdog_sec, 0, &arg_runtime_watchdog }, - { "Manager", "RuntimeWatchdogPreSec", config_parse_watchdog_sec, 0, &arg_pretimeout_watchdog }, - { "Manager", "RebootWatchdogSec", config_parse_watchdog_sec, 0, &arg_reboot_watchdog }, - { "Manager", "ShutdownWatchdogSec", config_parse_watchdog_sec, 0, &arg_reboot_watchdog }, /* obsolete alias */ - { "Manager", "KExecWatchdogSec", config_parse_watchdog_sec, 0, &arg_kexec_watchdog }, - { "Manager", "WatchdogDevice", config_parse_path, 0, &arg_watchdog_device }, - { "Manager", "RuntimeWatchdogPreGovernor", config_parse_string, CONFIG_PARSE_STRING_SAFE, &arg_watchdog_pretimeout_governor }, - { "Manager", "CapabilityBoundingSet", config_parse_capability_set, 0, &arg_capability_bounding_set }, - { "Manager", "NoNewPrivileges", config_parse_bool, 0, &arg_no_new_privs }, - { "Manager", "ProtectSystem", config_parse_protect_system_pid1, 0, &arg_protect_system }, + { "Manager", "LogLevel", config_parse_level2, 0, NULL }, + { "Manager", "LogTarget", config_parse_target, 0, NULL }, + { "Manager", "LogColor", config_parse_color, 0, NULL }, + { "Manager", "LogLocation", config_parse_location, 0, NULL }, + { "Manager", "LogTime", config_parse_time, 0, NULL }, + { "Manager", "DumpCore", config_parse_bool, 0, &arg_dump_core }, + { "Manager", "CrashChVT", /* legacy */ config_parse_crash_chvt, 0, &arg_crash_chvt }, + { "Manager", "CrashChangeVT", config_parse_crash_chvt, 0, &arg_crash_chvt }, + { "Manager", "CrashShell", config_parse_bool, 0, &arg_crash_shell }, + { "Manager", "CrashReboot", config_parse_crash_reboot, 0, &arg_crash_action }, + { "Manager", "CrashAction", config_parse_crash_action, 0, &arg_crash_action }, + { "Manager", "ShowStatus", config_parse_show_status, 0, &arg_show_status }, + { "Manager", "StatusUnitFormat", config_parse_status_unit_format, 0, &arg_status_unit_format }, + { "Manager", "CPUAffinity", config_parse_cpu_set, 0, &arg_cpu_affinity }, + { "Manager", "NUMAPolicy", config_parse_numa_policy, 0, &arg_numa_policy.type }, + { "Manager", "NUMAMask", config_parse_numa_mask, 0, &arg_numa_policy.nodes }, + { "Manager", "JoinControllers", config_parse_warn_compat, DISABLED_LEGACY, NULL }, + { "Manager", "RuntimeWatchdogSec", config_parse_watchdog_sec, 0, &arg_runtime_watchdog }, + { "Manager", "RuntimeWatchdogPreSec", config_parse_watchdog_sec, 0, &arg_pretimeout_watchdog }, + { "Manager", "RebootWatchdogSec", config_parse_watchdog_sec, 0, &arg_reboot_watchdog }, + { "Manager", "ShutdownWatchdogSec", config_parse_watchdog_sec, 0, &arg_reboot_watchdog }, /* obsolete alias */ + { "Manager", "KExecWatchdogSec", config_parse_watchdog_sec, 0, &arg_kexec_watchdog }, + { "Manager", "WatchdogDevice", config_parse_path, 0, &arg_watchdog_device }, + { "Manager", "RuntimeWatchdogPreGovernor", config_parse_string, CONFIG_PARSE_STRING_SAFE, &arg_watchdog_pretimeout_governor }, + { "Manager", "CapabilityBoundingSet", config_parse_capability_set, 0, &arg_capability_bounding_set }, + { "Manager", "NoNewPrivileges", config_parse_bool, 0, &arg_no_new_privs }, + { "Manager", "ProtectSystem", config_parse_protect_system_pid1, 0, &arg_protect_system }, #if HAVE_SECCOMP - { "Manager", "SystemCallArchitectures", config_parse_syscall_archs, 0, &arg_syscall_archs }, + { "Manager", "SystemCallArchitectures", config_parse_syscall_archs, 0, &arg_syscall_archs }, #else - { "Manager", "SystemCallArchitectures", config_parse_warn_compat, DISABLED_CONFIGURATION, NULL }, - + { "Manager", "SystemCallArchitectures", config_parse_warn_compat, DISABLED_CONFIGURATION, NULL }, #endif - { "Manager", "TimerSlackNSec", config_parse_nsec, 0, &arg_timer_slack_nsec }, - { "Manager", "DefaultTimerAccuracySec", config_parse_sec, 0, &arg_defaults.timer_accuracy_usec }, - { "Manager", "DefaultStandardOutput", config_parse_output_restricted, 0, &arg_defaults.std_output }, - { "Manager", "DefaultStandardError", config_parse_output_restricted, 0, &arg_defaults.std_error }, - { "Manager", "DefaultTimeoutStartSec", config_parse_sec, 0, &arg_defaults.timeout_start_usec }, - { "Manager", "DefaultTimeoutStopSec", config_parse_sec, 0, &arg_defaults.timeout_stop_usec }, - { "Manager", "DefaultTimeoutAbortSec", config_parse_default_timeout_abort, 0, NULL }, - { "Manager", "DefaultDeviceTimeoutSec", config_parse_sec, 0, &arg_defaults.device_timeout_usec }, - { "Manager", "DefaultRestartSec", config_parse_sec, 0, &arg_defaults.restart_usec }, - { "Manager", "DefaultStartLimitInterval", config_parse_sec, 0, &arg_defaults.start_limit.interval}, /* obsolete alias */ - { "Manager", "DefaultStartLimitIntervalSec", config_parse_sec, 0, &arg_defaults.start_limit.interval}, - { "Manager", "DefaultStartLimitBurst", config_parse_unsigned, 0, &arg_defaults.start_limit.burst }, - { "Manager", "DefaultRestrictSUIDSGID", config_parse_bool, 0, &arg_defaults.restrict_suid_sgid }, - { "Manager", "DefaultEnvironment", config_parse_environ, arg_runtime_scope, &arg_default_environment }, - { "Manager", "ManagerEnvironment", config_parse_environ, arg_runtime_scope, &arg_manager_environment }, - { "Manager", "DefaultLimitCPU", config_parse_rlimit, RLIMIT_CPU, arg_defaults.rlimit }, - { "Manager", "DefaultLimitFSIZE", config_parse_rlimit, RLIMIT_FSIZE, arg_defaults.rlimit }, - { "Manager", "DefaultLimitDATA", config_parse_rlimit, RLIMIT_DATA, arg_defaults.rlimit }, - { "Manager", "DefaultLimitSTACK", config_parse_rlimit, RLIMIT_STACK, arg_defaults.rlimit }, - { "Manager", "DefaultLimitCORE", config_parse_rlimit, RLIMIT_CORE, arg_defaults.rlimit }, - { "Manager", "DefaultLimitRSS", config_parse_rlimit, RLIMIT_RSS, arg_defaults.rlimit }, - { "Manager", "DefaultLimitNOFILE", config_parse_rlimit, RLIMIT_NOFILE, arg_defaults.rlimit }, - { "Manager", "DefaultLimitAS", config_parse_rlimit, RLIMIT_AS, arg_defaults.rlimit }, - { "Manager", "DefaultLimitNPROC", config_parse_rlimit, RLIMIT_NPROC, arg_defaults.rlimit }, - { "Manager", "DefaultLimitMEMLOCK", config_parse_rlimit, RLIMIT_MEMLOCK, arg_defaults.rlimit }, - { "Manager", "DefaultLimitLOCKS", config_parse_rlimit, RLIMIT_LOCKS, arg_defaults.rlimit }, - { "Manager", "DefaultLimitSIGPENDING", config_parse_rlimit, RLIMIT_SIGPENDING, arg_defaults.rlimit }, - { "Manager", "DefaultLimitMSGQUEUE", config_parse_rlimit, RLIMIT_MSGQUEUE, arg_defaults.rlimit }, - { "Manager", "DefaultLimitNICE", config_parse_rlimit, RLIMIT_NICE, arg_defaults.rlimit }, - { "Manager", "DefaultLimitRTPRIO", config_parse_rlimit, RLIMIT_RTPRIO, arg_defaults.rlimit }, - { "Manager", "DefaultLimitRTTIME", config_parse_rlimit, RLIMIT_RTTIME, arg_defaults.rlimit }, - { "Manager", "DefaultCPUAccounting", config_parse_warn_compat, DISABLED_LEGACY, NULL }, - { "Manager", "DefaultIOAccounting", config_parse_bool, 0, &arg_defaults.io_accounting }, - { "Manager", "DefaultIPAccounting", config_parse_bool, 0, &arg_defaults.ip_accounting }, - { "Manager", "DefaultBlockIOAccounting", config_parse_warn_compat, DISABLED_LEGACY, NULL }, - { "Manager", "DefaultMemoryAccounting", config_parse_bool, 0, &arg_defaults.memory_accounting }, - { "Manager", "DefaultTasksAccounting", config_parse_bool, 0, &arg_defaults.tasks_accounting }, - { "Manager", "DefaultTasksMax", config_parse_tasks_max, 0, &arg_defaults.tasks_max }, + { "Manager", "TimerSlackNSec", config_parse_nsec, 0, &arg_timer_slack_nsec }, + { "Manager", "DefaultTimerAccuracySec", config_parse_sec, 0, &arg_defaults.timer_accuracy_usec }, + { "Manager", "DefaultStandardOutput", config_parse_output_restricted, 0, &arg_defaults.std_output }, + { "Manager", "DefaultStandardError", config_parse_output_restricted, 0, &arg_defaults.std_error }, + { "Manager", "DefaultTimeoutStartSec", config_parse_sec, 0, &arg_defaults.timeout_start_usec }, + { "Manager", "DefaultTimeoutStopSec", config_parse_sec, 0, &arg_defaults.timeout_stop_usec }, + { "Manager", "DefaultTimeoutAbortSec", config_parse_default_timeout_abort, 0, NULL }, + { "Manager", "DefaultDeviceTimeoutSec", config_parse_sec, 0, &arg_defaults.device_timeout_usec }, + { "Manager", "DefaultRestartSec", config_parse_sec, 0, &arg_defaults.restart_usec }, + { "Manager", "DefaultStartLimitInterval", config_parse_sec, 0, &arg_defaults.start_limit.interval }, /* obsolete alias */ + { "Manager", "DefaultStartLimitIntervalSec", config_parse_sec, 0, &arg_defaults.start_limit.interval }, + { "Manager", "DefaultStartLimitBurst", config_parse_unsigned, 0, &arg_defaults.start_limit.burst }, + { "Manager", "DefaultRestrictSUIDSGID", config_parse_bool, 0, &arg_defaults.restrict_suid_sgid }, + { "Manager", "DefaultEnvironment", config_parse_environ, arg_runtime_scope, &arg_default_environment }, + { "Manager", "ManagerEnvironment", config_parse_environ, arg_runtime_scope, &arg_manager_environment }, + { "Manager", "DefaultLimitCPU", config_parse_rlimit, RLIMIT_CPU, arg_defaults.rlimit }, + { "Manager", "DefaultLimitFSIZE", config_parse_rlimit, RLIMIT_FSIZE, arg_defaults.rlimit }, + { "Manager", "DefaultLimitDATA", config_parse_rlimit, RLIMIT_DATA, arg_defaults.rlimit }, + { "Manager", "DefaultLimitSTACK", config_parse_rlimit, RLIMIT_STACK, arg_defaults.rlimit }, + { "Manager", "DefaultLimitCORE", config_parse_rlimit, RLIMIT_CORE, arg_defaults.rlimit }, + { "Manager", "DefaultLimitRSS", config_parse_rlimit, RLIMIT_RSS, arg_defaults.rlimit }, + { "Manager", "DefaultLimitNOFILE", config_parse_rlimit, RLIMIT_NOFILE, arg_defaults.rlimit }, + { "Manager", "DefaultLimitAS", config_parse_rlimit, RLIMIT_AS, arg_defaults.rlimit }, + { "Manager", "DefaultLimitNPROC", config_parse_rlimit, RLIMIT_NPROC, arg_defaults.rlimit }, + { "Manager", "DefaultLimitMEMLOCK", config_parse_rlimit, RLIMIT_MEMLOCK, arg_defaults.rlimit }, + { "Manager", "DefaultLimitLOCKS", config_parse_rlimit, RLIMIT_LOCKS, arg_defaults.rlimit }, + { "Manager", "DefaultLimitSIGPENDING", config_parse_rlimit, RLIMIT_SIGPENDING, arg_defaults.rlimit }, + { "Manager", "DefaultLimitMSGQUEUE", config_parse_rlimit, RLIMIT_MSGQUEUE, arg_defaults.rlimit }, + { "Manager", "DefaultLimitNICE", config_parse_rlimit, RLIMIT_NICE, arg_defaults.rlimit }, + { "Manager", "DefaultLimitRTPRIO", config_parse_rlimit, RLIMIT_RTPRIO, arg_defaults.rlimit }, + { "Manager", "DefaultLimitRTTIME", config_parse_rlimit, RLIMIT_RTTIME, arg_defaults.rlimit }, + { "Manager", "DefaultCPUAccounting", config_parse_warn_compat, DISABLED_LEGACY, NULL }, + { "Manager", "DefaultIOAccounting", config_parse_bool, 0, &arg_defaults.io_accounting }, + { "Manager", "DefaultIPAccounting", config_parse_bool, 0, &arg_defaults.ip_accounting }, + { "Manager", "DefaultBlockIOAccounting", config_parse_warn_compat, DISABLED_LEGACY, NULL }, + { "Manager", "DefaultMemoryAccounting", config_parse_bool, 0, &arg_defaults.memory_accounting }, + { "Manager", "DefaultTasksAccounting", config_parse_bool, 0, &arg_defaults.tasks_accounting }, + { "Manager", "DefaultTasksMax", config_parse_tasks_max, 0, &arg_defaults.tasks_max }, { "Manager", "DefaultMemoryPressureThresholdSec", config_parse_sec, 0, &arg_defaults.memory_pressure_threshold_usec }, - { "Manager", "DefaultMemoryPressureWatch", config_parse_memory_pressure_watch, 0, &arg_defaults.memory_pressure_watch }, - { "Manager", "CtrlAltDelBurstAction", config_parse_emergency_action, arg_runtime_scope, &arg_cad_burst_action }, - { "Manager", "DefaultOOMPolicy", config_parse_oom_policy, 0, &arg_defaults.oom_policy }, - { "Manager", "DefaultOOMScoreAdjust", config_parse_oom_score_adjust, 0, NULL }, - { "Manager", "ReloadLimitIntervalSec", config_parse_sec, 0, &arg_reload_limit_interval_sec }, - { "Manager", "ReloadLimitBurst", config_parse_unsigned, 0, &arg_reload_limit_burst }, + { "Manager", "DefaultMemoryPressureWatch", config_parse_memory_pressure_watch, 0, &arg_defaults.memory_pressure_watch }, + { "Manager", "CtrlAltDelBurstAction", config_parse_emergency_action, arg_runtime_scope, &arg_cad_burst_action }, + { "Manager", "DefaultOOMPolicy", config_parse_oom_policy, 0, &arg_defaults.oom_policy }, + { "Manager", "DefaultOOMScoreAdjust", config_parse_oom_score_adjust, 0, NULL }, + { "Manager", "ReloadLimitIntervalSec", config_parse_sec, 0, &arg_reload_limit_interval_sec }, + { "Manager", "ReloadLimitBurst", config_parse_unsigned, 0, &arg_reload_limit_burst }, + { "Manager", "DefaultMemoryZSwapWriteback", config_parse_bool, 0, &arg_defaults.memory_zswap_writeback }, #if ENABLE_SMACK - { "Manager", "DefaultSmackProcessLabel", config_parse_string, 0, &arg_defaults.smack_process_label }, + { "Manager", "DefaultSmackProcessLabel", config_parse_string, 0, &arg_defaults.smack_process_label }, #else - { "Manager", "DefaultSmackProcessLabel", config_parse_warn_compat, DISABLED_CONFIGURATION, NULL }, + { "Manager", "DefaultSmackProcessLabel", config_parse_warn_compat, DISABLED_CONFIGURATION, NULL }, #endif {} }; diff --git a/src/core/manager.c b/src/core/manager.c index a5af434e5ef81..85b68b86d2bf6 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -4303,6 +4303,8 @@ int manager_set_unit_defaults(Manager *m, const UnitDefaults *defaults) { m->defaults.memory_pressure_watch = defaults->memory_pressure_watch; m->defaults.memory_pressure_threshold_usec = defaults->memory_pressure_threshold_usec; + m->defaults.memory_zswap_writeback = defaults->memory_zswap_writeback; + free_and_replace(m->defaults.smack_process_label, label); rlimit_free_all(m->defaults.rlimit); memcpy(m->defaults.rlimit, rlimit, sizeof(struct rlimit*) * _RLIMIT_MAX); @@ -5198,6 +5200,8 @@ void unit_defaults_init(UnitDefaults *defaults, RuntimeScope scope) { .oom_policy = OOM_STOP, .oom_score_adjust_set = false, + + .memory_zswap_writeback = true, }; } diff --git a/src/core/manager.h b/src/core/manager.h index 2df606005dbb1..1c04deabee9ef 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -147,6 +147,8 @@ typedef struct UnitDefaults { int oom_score_adjust; bool oom_score_adjust_set; + bool memory_zswap_writeback; + CGroupPressureWatch memory_pressure_watch; usec_t memory_pressure_threshold_usec; diff --git a/src/core/system.conf.in b/src/core/system.conf.in index 54196e84894df..6000d1702e097 100644 --- a/src/core/system.conf.in +++ b/src/core/system.conf.in @@ -82,3 +82,4 @@ #DefaultRestrictSUIDSGID= #ReloadLimitIntervalSec= #ReloadLimitBurst= +#DefaultMemoryZSwapWriteback=yes diff --git a/src/core/unit.c b/src/core/unit.c index 41f536ce1f15d..6dd5599f0a773 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -180,6 +180,8 @@ static void unit_init(Unit *u) { cc->memory_pressure_watch = u->manager->defaults.memory_pressure_watch; cc->memory_pressure_threshold_usec = u->manager->defaults.memory_pressure_threshold_usec; + + cc->memory_zswap_writeback = u->manager->defaults.memory_zswap_writeback; } ec = unit_get_exec_context(u); diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index 0cbe26d5d588f..c039ea8e53610 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -119,6 +119,7 @@ static int manager_context_build_json(sd_json_variant **ret, const char *name, v SD_JSON_BUILD_PAIR_INTEGER("DefaultOOMScoreAdjust", m->defaults.oom_score_adjust), SD_JSON_BUILD_PAIR_BOOLEAN("DefaultRestrictSUIDSGID", m->defaults.restrict_suid_sgid), SD_JSON_BUILD_PAIR_STRING("CtrlAltDelBurstAction", emergency_action_to_string(m->cad_burst_action)), + SD_JSON_BUILD_PAIR_BOOLEAN("DefaultMemoryZSwapWriteback", m->defaults.memory_zswap_writeback), JSON_BUILD_PAIR_STRING_NON_EMPTY("ConfirmSpawn", manager_get_confirm_spawn(m)), JSON_BUILD_PAIR_STRING_NON_EMPTY("ControlGroup", m->cgroup_root)); } diff --git a/src/shared/varlink-io.systemd.Manager.c b/src/shared/varlink-io.systemd.Manager.c index f33cab34b3de9..ddf15b173ecc6 100644 --- a/src/shared/varlink-io.systemd.Manager.c +++ b/src/shared/varlink-io.systemd.Manager.c @@ -89,7 +89,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The console on which systemd asks for confirmation when spawning processes"), SD_VARLINK_DEFINE_FIELD(ConfirmSpawn, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Root of the control group hierarchy that the manager is running in"), - SD_VARLINK_DEFINE_FIELD(ControlGroup, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + SD_VARLINK_DEFINE_FIELD(ControlGroup, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultMemoryZSwapWriteback="), + SD_VARLINK_DEFINE_FIELD(DefaultMemoryZSwapWriteback, SD_VARLINK_BOOL, 0)); static SD_VARLINK_DEFINE_STRUCT_TYPE( ManagerRuntime, From 0508f15b7fc6c6f3c6760e9f79b6d8de344e56ef Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 2 Apr 2026 20:42:04 +0200 Subject: [PATCH 0715/1296] ci: Drop base64 encoding in claude review workflow Doesn't seem to work nearly as good as the previous solution which just told claude not to escape stuff. --- .github/workflows/claude-review.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 2f9c76990d245..a079cf1164265 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -352,14 +352,17 @@ jobs: not available, git commands that failed, etc.), append a `### Errors` section to the summary listing each failed action and the error message. + ## Output formatting + + Do NOT escape characters in `body` or `summary`. Write plain markdown — no + backslash escaping of `!` or other characters. In particular, HTML comments + like `` must be written verbatim, never as `<\!-- ... -->`. + ## Review result Produce your review result as structured output. The fields are: - - `summary`: The markdown summary for the tracking comment, **base64-encoded**. - Write the summary to a temporary file first, then encode it with - `base64 -w0 /tmp/summary.md` and put the resulting string in this field. + - `summary`: The markdown summary for the tracking comment. - `comments`: Array of review comments (same schema as the reviewer output above). - The `body` field of each comment must also be **base64-encoded** the same way. - `resolve`: REST API IDs of review comment threads to resolve. PROMPT @@ -452,7 +455,7 @@ jobs: if (Array.isArray(review.resolve)) resolveIds = review.resolve; if (typeof review.summary === "string") - summary = Buffer.from(review.summary, "base64").toString("utf-8"); + summary = review.summary; } catch (e) { core.warning(`Failed to parse structured output: ${e.message}`); } @@ -483,7 +486,7 @@ jobs: ...(c.side != null && { side: c.side }), ...(c.start_line != null && { start_line: c.start_line }), ...(c.start_side != null && { start_side: c.start_side }), - body: `Claude: **${c.severity}**: ${Buffer.from(c.body, "base64").toString("utf-8")}`, + body: `Claude: **${c.severity}**: ${c.body}`, }); posted++; } catch (e) { From c0b64eef37b2d9ab5c74d56c087e4743cfd217b8 Mon Sep 17 00:00:00 2001 From: Yaping Li <202858510+YapingLi04@users.noreply.github.com> Date: Thu, 26 Mar 2026 19:57:46 -0700 Subject: [PATCH 0716/1296] report: add manager-level metrics to varlink Metrics API Added these metrics: - JobsQueued: number of jobs currently queued - SystemState: overall system state (running, degraded, etc.) - UnitsByLoadStateTotal: unit counts broken down by load state - UnitsTotal: total number of units Also bump METRICS_MAX from 1024 to 4096 to accommodate the new per-unit metrics that are now collected. --- src/core/varlink-metrics.c | 109 ++++++++++++++++++++++++++++++++++++- src/report/report.c | 2 +- 2 files changed, 109 insertions(+), 2 deletions(-) diff --git a/src/core/varlink-metrics.c b/src/core/varlink-metrics.c index 00af452d7776c..68560387f3032 100644 --- a/src/core/varlink-metrics.c +++ b/src/core/varlink-metrics.c @@ -108,7 +108,7 @@ static int units_by_type_total_build_json(MetricFamilyContext *context, void *us static int units_by_state_total_build_json(MetricFamilyContext *context, void *userdata) { Manager *manager = ASSERT_PTR(userdata); - UnitActiveState counters[_UNIT_ACTIVE_STATE_MAX] = {}; + uint64_t counters[_UNIT_ACTIVE_STATE_MAX] = {}; Unit *unit; char *key; int r; @@ -143,14 +143,109 @@ static int units_by_state_total_build_json(MetricFamilyContext *context, void *u return 0; } +static int jobs_queued_build_json(MetricFamilyContext *context, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + + assert(context); + + return metric_build_send_unsigned( + context, + /* object= */ NULL, + hashmap_size(manager->jobs), + /* fields= */ NULL); +} + +static int system_state_build_json(MetricFamilyContext *context, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + + assert(context); + + return metric_build_send_string( + context, + /* object= */ NULL, + manager_state_to_string(manager_state(manager)), + /* fields= */ NULL); +} + +static int units_by_load_state_total_build_json(MetricFamilyContext *context, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + uint64_t counters[_UNIT_LOAD_STATE_MAX] = {}; + Unit *unit; + char *key; + int r; + + assert(context); + + HASHMAP_FOREACH_KEY(unit, key, manager->units) { + /* ignore aliases */ + if (key != unit->id) + continue; + + counters[unit->load_state]++; + } + + for (UnitLoadState state = 0; state < _UNIT_LOAD_STATE_MAX; state++) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *fields = NULL; + + r = sd_json_buildo(&fields, SD_JSON_BUILD_PAIR_STRING("load_state", unit_load_state_to_string(state))); + if (r < 0) + return r; + + r = metric_build_send_unsigned( + context, + /* object= */ NULL, + counters[state], + fields); + if (r < 0) + return r; + } + + return 0; +} + +static int units_total_build_json(MetricFamilyContext *context, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + uint64_t count = 0; + Unit *unit; + char *key; + + assert(context); + + HASHMAP_FOREACH_KEY(unit, key, manager->units) { + /* ignore aliases */ + if (key != unit->id) + continue; + + count++; + } + + return metric_build_send_unsigned( + context, + /* object= */ NULL, + count, + /* fields= */ NULL); +} + static const MetricFamily metric_family_table[] = { /* Keep metrics ordered alphabetically */ + { + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "JobsQueued", + .description = "Number of jobs currently queued", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = jobs_queued_build_json, + }, { .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "NRestarts", .description = "Per unit metric: number of restarts", .type = METRIC_FAMILY_TYPE_COUNTER, .generate = nrestarts_build_json, }, + { + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "SystemState", + .description = "Overall system state", + .type = METRIC_FAMILY_TYPE_STRING, + .generate = system_state_build_json, + }, { .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UnitActiveState", .description = "Per unit metric: active state", @@ -163,6 +258,12 @@ static const MetricFamily metric_family_table[] = { .type = METRIC_FAMILY_TYPE_STRING, .generate = unit_load_state_build_json, }, + { + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UnitsByLoadStateTotal", + .description = "Total number of units by load state", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = units_by_load_state_total_build_json, + }, { .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UnitsByStateTotal", .description = "Total number of units of different state", @@ -175,6 +276,12 @@ static const MetricFamily metric_family_table[] = { .type = METRIC_FAMILY_TYPE_GAUGE, .generate = units_by_type_total_build_json, }, + { + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UnitsTotal", + .description = "Total number of units", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = units_total_build_json, + }, {} }; diff --git a/src/report/report.c b/src/report/report.c index 108b24e4701e8..ca169c94a8f07 100644 --- a/src/report/report.c +++ b/src/report/report.c @@ -27,7 +27,7 @@ #include "varlink-idl-util.h" #include "verbs.h" -#define METRICS_OR_FACTS_MAX 1024U +#define METRICS_OR_FACTS_MAX 4096U #define METRICS_OR_FACTS_LINKS_MAX 128U #define TIMEOUT_USEC (30 * USEC_PER_SEC) /* 30 seconds */ From f83e47bd57edbf969dec1dc83ea8a7dbe4892c11 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 2 Apr 2026 23:02:40 +0200 Subject: [PATCH 0717/1296] vmspawn: add a bunch of func param assert()s --- src/vmspawn/vmspawn.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 85165e1af7a98..85104f9ea9e1e 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -1142,6 +1142,8 @@ static int vmspawn_dispatch_vsock_connections(sd_event_source *source, int fd, u sd_event *event; int r; + assert(source); + assert(fd >= 0); assert(userdata); if (revents != EPOLLIN) { @@ -1328,6 +1330,7 @@ static int shutdown_vm_graceful(sd_event_source *s, const struct signalfd_siginf } static int on_child_exit(sd_event_source *s, const siginfo_t *si, void *userdata) { + assert(s); assert(si); /* Let's first do some logging about the exit status of the child. */ @@ -1364,6 +1367,9 @@ static int on_child_exit(sd_event_source *s, const siginfo_t *si, void *userdata static int cmdline_add_vsock(char ***cmdline, int vsock_fd) { int r; + assert(cmdline); + assert(vsock_fd >= 0); + r = strv_extend(cmdline, "-smbios"); if (r < 0) return r; @@ -1387,6 +1393,7 @@ static int cmdline_add_kernel_cmdline(char ***cmdline, const char *kernel, const int r; assert(cmdline); + assert(smbios_dir); if (strv_isempty(arg_kernel_cmdline_extra)) return 0; @@ -1491,6 +1498,7 @@ static int start_tpm( assert(scope); assert(swtpm); assert(runtime_dir); + assert(sd_socket_activate); _cleanup_free_ char *scope_prefix = NULL; r = unit_name_to_prefix(scope, &scope_prefix); @@ -1558,6 +1566,7 @@ static int start_systemd_journal_remote( int r; assert(scope); + assert(sd_socket_activate); _cleanup_free_ char *scope_prefix = NULL; r = unit_name_to_prefix(scope, &scope_prefix); From 275e31f44cb5b187c14e28eed01916f7b06e3d59 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 2 Apr 2026 23:02:56 +0200 Subject: [PATCH 0718/1296] vmspawn: shorten find_virtiofsd() a bit --- src/vmspawn/vmspawn.c | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 85104f9ea9e1e..4061c6c9eedaa 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -1642,33 +1642,30 @@ static int discover_root(char **ret) { static int find_virtiofsd(char **ret) { int r; - _cleanup_free_ char *virtiofsd = NULL; assert(ret); - r = find_executable("virtiofsd", &virtiofsd); - if (r < 0 && r != -ENOENT) + r = find_executable("virtiofsd", ret); + if (r >= 0) + return 0; + if (r != -ENOENT) return log_error_errno(r, "Error while searching for virtiofsd: %m"); - if (!virtiofsd) { - FOREACH_STRING(file, "/usr/libexec/virtiofsd", "/usr/lib/virtiofsd") { - if (access(file, X_OK) >= 0) { - virtiofsd = strdup(file); - if (!virtiofsd) - return log_oom(); - break; - } + FOREACH_STRING(file, "/usr/libexec/virtiofsd", "/usr/lib/virtiofsd") { + if (access(file, X_OK) >= 0) { + _cleanup_free_ char *copy = strdup(file); + if (!copy) + return log_oom(); - if (!IN_SET(errno, ENOENT, EACCES)) - return log_error_errno(errno, "Error while searching for virtiofsd: %m"); + *ret = TAKE_PTR(copy); + return 0; } - } - if (!virtiofsd) - return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Failed to find virtiofsd binary."); + if (!IN_SET(errno, ENOENT, EACCES)) + return log_error_errno(errno, "Error while searching for virtiofsd: %m"); + } - *ret = TAKE_PTR(virtiofsd); - return 0; + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Failed to find virtiofsd binary."); } static int start_virtiofsd( From 7f5bea5b78d8ccd50c4780aeb46389662fbb9072 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 2 Apr 2026 23:03:11 +0200 Subject: [PATCH 0719/1296] vmspawn: simplify kernel_cmdline_maybe_append_root() --- src/vmspawn/vmspawn.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 4061c6c9eedaa..5d0962daf6104 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -1876,22 +1876,20 @@ static int bind_user_setup( static int kernel_cmdline_maybe_append_root(void) { int r; - bool cmdline_contains_root = strv_find_startswith(arg_kernel_cmdline_extra, "root=") - || strv_find_startswith(arg_kernel_cmdline_extra, "mount.usr="); - if (!cmdline_contains_root) { - _cleanup_free_ char *root = NULL; + if (strv_find_startswith(arg_kernel_cmdline_extra, "root=") || + strv_find_startswith(arg_kernel_cmdline_extra, "mount.usr=")) + return 0; - r = discover_root(&root); - if (r < 0) - return r; + _cleanup_free_ char *root = NULL; + r = discover_root(&root); + if (r < 0) + return r; - log_debug("Determined root file system %s from dissected image", root); + log_debug("Determined root file system '%s' from dissected image", root); - r = strv_consume(&arg_kernel_cmdline_extra, TAKE_PTR(root)); - if (r < 0) - return log_oom(); - } + if (strv_consume(&arg_kernel_cmdline_extra, TAKE_PTR(root)) < 0) + return log_oom(); return 0; } From 4ec0520d9a06a9f2a162df22103d0117ded3b4ad Mon Sep 17 00:00:00 2001 From: Yaping Li <202858510+YapingLi04@users.noreply.github.com> Date: Thu, 19 Mar 2026 14:10:49 -0700 Subject: [PATCH 0720/1296] report: add per-service metrics to the varlink Metrics API Added these metrics: - ActiveTimestamp: active state transition timestamps (enter/exit) - InactiveExitTimestamp: when the unit last left inactive state - NRestarts: restart count - StateChangeTimestamp: last state change timestamp - StatusErrno: service errno status Per-service cgroup metrics (CpuUsage, MemoryUsage, IOReadBytes, IOReadOperations, TasksCurrent) are not included here as they are gathered by the kernel and will be served by a separate process that reads cgroup files directly, minimizing PID1 involvement. --- src/core/varlink-metrics.c | 136 +++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/src/core/varlink-metrics.c b/src/core/varlink-metrics.c index 68560387f3032..82bc3cf4cba15 100644 --- a/src/core/varlink-metrics.c +++ b/src/core/varlink-metrics.c @@ -11,6 +11,118 @@ #include "unit.h" #include "varlink-metrics.h" +static int active_timestamp_build_json(MetricFamilyContext *context, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + Unit *unit; + char *key; + int r; + + assert(context); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *enter_fields = NULL; + r = sd_json_buildo(&enter_fields, SD_JSON_BUILD_PAIR_STRING("event", "enter")); + if (r < 0) + return r; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *exit_fields = NULL; + r = sd_json_buildo(&exit_fields, SD_JSON_BUILD_PAIR_STRING("event", "exit")); + if (r < 0) + return r; + + HASHMAP_FOREACH_KEY(unit, key, manager->units) { + /* ignore aliases */ + if (key != unit->id) + continue; + + r = metric_build_send_unsigned( + context, + unit->id, + unit->active_enter_timestamp.realtime, + enter_fields); + if (r < 0) + return r; + + r = metric_build_send_unsigned( + context, + unit->id, + unit->active_exit_timestamp.realtime, + exit_fields); + if (r < 0) + return r; + } + + return 0; +} + +static int inactive_exit_timestamp_build_json(MetricFamilyContext *context, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + Unit *unit; + char *key; + int r; + + assert(context); + + HASHMAP_FOREACH_KEY(unit, key, manager->units) { + /* ignore aliases */ + if (key != unit->id) + continue; + + r = metric_build_send_unsigned( + context, + unit->id, + unit->inactive_exit_timestamp.realtime, + /* fields= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + +static int state_change_timestamp_build_json(MetricFamilyContext *context, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + Unit *unit; + char *key; + int r; + + assert(context); + + HASHMAP_FOREACH_KEY(unit, key, manager->units) { + /* ignore aliases */ + if (key != unit->id) + continue; + + r = metric_build_send_unsigned( + context, + unit->id, + unit->state_change_timestamp.realtime, + /* fields= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + +static int status_errno_build_json(MetricFamilyContext *context, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + int r; + + assert(context); + + LIST_FOREACH(units_by_type, unit, manager->units_by_type[UNIT_SERVICE]) { + r = metric_build_send_unsigned( + context, + unit->id, + (uint64_t) SERVICE(unit)->status_errno, + /* fields= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + static int unit_active_state_build_json(MetricFamilyContext *context, void *userdata) { Manager *manager = ASSERT_PTR(userdata); Unit *unit; @@ -228,6 +340,18 @@ static int units_total_build_json(MetricFamilyContext *context, void *userdata) static const MetricFamily metric_family_table[] = { /* Keep metrics ordered alphabetically */ + { + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "ActiveTimestamp", + .description = "Per unit metric: timestamp of active state transitions in microseconds; 0 indicates the transition has not occurred", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = active_timestamp_build_json, + }, + { + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "InactiveExitTimestamp", + .description = "Per unit metric: timestamp when the unit last exited the inactive state in microseconds; 0 indicates the transition has not occurred", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = inactive_exit_timestamp_build_json, + }, { .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "JobsQueued", .description = "Number of jobs currently queued", @@ -240,6 +364,18 @@ static const MetricFamily metric_family_table[] = { .type = METRIC_FAMILY_TYPE_COUNTER, .generate = nrestarts_build_json, }, + { + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "StateChangeTimestamp", + .description = "Per unit metric: timestamp of the last state change in microseconds; 0 indicates no state change has occurred", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = state_change_timestamp_build_json, + }, + { + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "StatusErrno", + .description = "Per service metric: errno status of the service", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = status_errno_build_json, + }, { .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "SystemState", .description = "Overall system state", From 39ce1ee990fa9a703d557592c2a79753c723e8cc Mon Sep 17 00:00:00 2001 From: Yaping Li <202858510+YapingLi04@users.noreply.github.com> Date: Thu, 19 Mar 2026 14:10:59 -0700 Subject: [PATCH 0721/1296] report: follow up to review comments on #40619 Address review comments on https://github.com/systemd/systemd/pull/40619#discussion_r2810892362 --- src/network/networkd-varlink-metrics.c | 57 ++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/network/networkd-varlink-metrics.c b/src/network/networkd-varlink-metrics.c index d37f65b43d2a1..50aacebbf8077 100644 --- a/src/network/networkd-varlink-metrics.c +++ b/src/network/networkd-varlink-metrics.c @@ -1,7 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-json.h" #include "sd-varlink.h" +#include "alloc-util.h" #include "argv-util.h" #include "errno-util.h" #include "fd-util.h" @@ -99,6 +101,55 @@ static int managed_interfaces_build_json(MetricFamilyContext *context, void *use return metric_build_send_unsigned(context, /* object= */ NULL, count, /* fields= */ NULL); } +static int required_for_online_build_json(MetricFamilyContext *context, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + Link *link; + int r; + + assert(context); + + HASHMAP_FOREACH(link, manager->links_by_index) { + if (!link->network) + continue; + + if (link->network->required_for_online == 0) { + r = metric_build_send_string( + context, + link->ifname, + "no", + /* fields= */ NULL); + } else { + LinkOperationalStateRange range; + link_required_operstate_for_online(link, &range); + + const char *min_str = link_operstate_to_string(range.min); + const char *max_str = link_operstate_to_string(range.max); + + if (range.min == range.max) + r = metric_build_send_string( + context, + link->ifname, + min_str, + /* fields= */ NULL); + else { + _cleanup_free_ char *value = NULL; + if (asprintf(&value, "%s:%s", min_str, max_str) < 0) + return -ENOMEM; + + r = metric_build_send_string( + context, + link->ifname, + value, + /* fields= */ NULL); + } + } + if (r < 0) + return r; + } + + return 0; +} + /* Keep metrics ordered alphabetically */ static const MetricFamily network_metric_family_table[] = { { @@ -143,6 +194,12 @@ static const MetricFamily network_metric_family_table[] = { .type = METRIC_FAMILY_TYPE_STRING, .generate = link_oper_state_build_json, }, + { + .name = METRIC_IO_SYSTEMD_NETWORK_PREFIX "RequiredForOnline", + .description = "Per interface metric: required operational state for online, or 'no' if not required", + .type = METRIC_FAMILY_TYPE_STRING, + .generate = required_for_online_build_json, + }, {} }; From d90d0a1f639efdc6e7ea33a3671266f74c0c25b3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 10 Feb 2026 15:39:54 +0100 Subject: [PATCH 0722/1296] kernel-image: minor refactoring to inspect_kernel() Let's add make three arguments optional, by splitting inspect_kernel() from inspect_kernel_full(). Let's also downgrade logging to debug, so that this becomes more library-like. Let's log on the call-site instead. --- src/bootctl/bootctl-uki.c | 9 +++++---- src/kernel-install/kernel-install.c | 11 ++++++++++- src/shared/kernel-image.c | 13 ++++++------- src/shared/kernel-image.h | 9 ++++++++- src/vmspawn/vmspawn.c | 8 +------- 5 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/bootctl/bootctl-uki.c b/src/bootctl/bootctl-uki.c index 1b52252210036..7c37081cbfdd7 100644 --- a/src/bootctl/bootctl-uki.c +++ b/src/bootctl/bootctl-uki.c @@ -5,14 +5,15 @@ #include "alloc-util.h" #include "bootctl-uki.h" #include "kernel-image.h" +#include "log.h" int verb_kernel_identify(int argc, char *argv[], uintptr_t _data, void *userdata) { KernelImageType t; int r; - r = inspect_kernel(AT_FDCWD, argv[1], &t, NULL, NULL, NULL); + r = inspect_kernel(AT_FDCWD, argv[1], &t); if (r < 0) - return r; + return log_error_errno(r, "Failed to inspect '%s': %m", argv[1]); puts(kernel_image_type_to_string(t)); return 0; @@ -23,9 +24,9 @@ int verb_kernel_inspect(int argc, char *argv[], uintptr_t _data, void *userdata) KernelImageType t; int r; - r = inspect_kernel(AT_FDCWD, argv[1], &t, &cmdline, &uname, &pname); + r = inspect_kernel_full(AT_FDCWD, argv[1], &t, &cmdline, &uname, &pname); if (r < 0) - return r; + return log_error_errno(r, "Failed to inspect '%s': %m", argv[1]); printf("Kernel Type: %s\n", kernel_image_type_to_string(t)); if (cmdline) diff --git a/src/kernel-install/kernel-install.c b/src/kernel-install/kernel-install.c index 001e9e20e2f8a..740791bba3562 100644 --- a/src/kernel-install/kernel-install.c +++ b/src/kernel-install/kernel-install.c @@ -750,12 +750,21 @@ static int context_from_cmdline(Context *c, Action action) { } static int context_inspect_kernel(Context *c) { + int r; + assert(c); if (!c->kernel) return 0; - return inspect_kernel(c->rfd, c->kernel, &c->kernel_image_type, NULL, NULL, NULL); + r = inspect_kernel( + c->rfd, + c->kernel, + &c->kernel_image_type); + if (r < 0) + return log_error_errno(r, "Failed to inspect kernel image '%s': %m", c->kernel); + + return 0; } static int context_ensure_layout(Context *c) { diff --git a/src/shared/kernel-image.c b/src/shared/kernel-image.c index 0f2a646da5eb1..e2db555cb4491 100644 --- a/src/shared/kernel-image.c +++ b/src/shared/kernel-image.c @@ -55,7 +55,7 @@ static int uki_read_pretty_name( "PRETTY_NAME", &pname, "NAME", &name); if (r < 0) - return log_error_errno(r, "Failed to parse embedded os-release file: %m"); + return log_debug_errno(r, "Failed to parse embedded os-release file: %m"); /* follow the same logic as os_release_pretty_name() */ if (!isempty(pname)) @@ -65,7 +65,7 @@ static int uki_read_pretty_name( else { char *n = strdup("Linux"); if (!n) - return log_oom(); + return -ENOMEM; *ret = n; } @@ -115,7 +115,7 @@ static int inspect_uki( return 0; } -int inspect_kernel( +int inspect_kernel_full( int dir_fd, const char *filename, KernelImageType *ret_type, @@ -131,23 +131,22 @@ int inspect_kernel( int r; assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); - assert(filename); fd = xopenat(dir_fd, filename, O_RDONLY|O_CLOEXEC); if (fd < 0) - return log_error_errno(fd, "Failed to open kernel image file '%s': %m", filename); + return log_debug_errno(fd, "Failed to open kernel image file '%s': %m", strna(filename)); r = pe_load_headers(fd, &dos_header, &pe_header); if (r == -EBADMSG) /* not a valid PE file */ goto not_uki; if (r < 0) - return log_error_errno(r, "Failed to parse kernel image file '%s': %m", filename); + return log_debug_errno(r, "Failed to parse kernel image file '%s': %m", strna(filename)); r = pe_load_sections(fd, dos_header, pe_header, §ions); if (r == -EBADMSG) /* not a valid PE file */ goto not_uki; if (r < 0) - return log_error_errno(r, "Failed to load PE sections from kernel image file '%s': %m", filename); + return log_debug_errno(r, "Failed to load PE sections from kernel image file '%s': %m", strna(filename)); if (pe_is_uki(pe_header, sections)) { r = inspect_uki(fd, pe_header, sections, ret_cmdline, ret_uname, ret_pretty_name); diff --git a/src/shared/kernel-image.h b/src/shared/kernel-image.h index 85a3308986c5c..c0b8847cafb54 100644 --- a/src/shared/kernel-image.h +++ b/src/shared/kernel-image.h @@ -14,10 +14,17 @@ typedef enum KernelImageType { DECLARE_STRING_TABLE_LOOKUP_TO_STRING(kernel_image_type, KernelImageType); -int inspect_kernel( +int inspect_kernel_full( int dir_fd, const char *filename, KernelImageType *ret_type, char **ret_cmdline, char **ret_uname, char **ret_pretty_name); + +static inline int inspect_kernel( + int dir_fd, + const char *filename, + KernelImageType *ret_type) { + return inspect_kernel_full(dir_fd, filename, ret_type, NULL, NULL, NULL); +} diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 5d0962daf6104..8777948626356 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -1400,13 +1400,7 @@ static int cmdline_add_kernel_cmdline(char ***cmdline, const char *kernel, const KernelImageType type = _KERNEL_IMAGE_TYPE_INVALID; if (kernel) { - r = inspect_kernel( - AT_FDCWD, - kernel, - &type, - /* ret_cmdline= */ NULL, - /* ret_uname= */ NULL, - /* ret_pretty_name= */ NULL); + r = inspect_kernel(AT_FDCWD, kernel, &type); if (r < 0) return log_error_errno(r, "Failed to determine '%s' kernel image type: %m", kernel); } From 0ef9bf97233481991b785d128038df89dae194b2 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 2 Apr 2026 21:23:01 +0000 Subject: [PATCH 0723/1296] vmspawn: drop ICH9-LPC S3 disable and guard cfi.pflash01 for x86 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ICH9-LPC disable_s3 global QEMU config was a workaround for an OVMF limitation where S3 resume didn't work with X64 PEI + SMM. SMM is required for secure boot as it prevents the guest from writing directly to the pflash, bypassing UEFI variable protections. With X64 PEI + SMM enabled and S3 advertised, OVMF would hang on S3 resume. The workaround was to tell QEMU not to advertise S3 support. This limitation has been resolved in edk2 — the S3Verification() check was removed in edk2 commit 098c5570 ("OvmfPkg/PlatformPei: drop S3Verification()") after edk2 gained native X64 PEI + SMM + S3 resume support. See https://github.com/tianocore/edk2/commit/098c5570. Drop the now-unnecessary ICH9-LPC disable_s3 config entirely, and guard the cfi.pflash01 secure=on setting with an x86 architecture check since SMM is x86-specific and this option is invalid on ARM. --- src/vmspawn/vmspawn.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 8777948626356..1b1e31bb5b103 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -2748,19 +2748,18 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { destroy_path = mfree(destroy_path); /* disarm auto-destroy */ - r = qemu_config_section(config_file, "global", /* id= */ NULL, - "driver", "ICH9-LPC", - "property", "disable_s3", - "value", "1"); - if (r < 0) - return r; - - r = qemu_config_section(config_file, "global", /* id= */ NULL, - "driver", "cfi.pflash01", - "property", "secure", - "value", "on"); - if (r < 0) - return r; + /* Mark the UEFI variable store pflash as requiring SMM access. This + * prevents the guest OS from writing to pflash directly, ensuring all + * variable updates go through the firmware's validation checks. Without + * this, secure boot keys could be overwritten by the OS. */ + if (ARCHITECTURE_SUPPORTS_SMM) { + r = qemu_config_section(config_file, "global", /* id= */ NULL, + "driver", "cfi.pflash01", + "property", "secure", + "value", "on"); + if (r < 0) + return r; + } r = qemu_config_section(config_file, "drive", "ovmf-vars", "file", state, From 805d3f5c53298dd14770f40d4315f6c0d5ebbadb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 27 Mar 2026 09:17:51 +0100 Subject: [PATCH 0724/1296] shared/verbs: extend comment This was suggested in one of the reviews but I forgot to push the change. --- src/shared/verbs.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/shared/verbs.h b/src/shared/verbs.h index 6b380041b81e5..cd18359cd75ac 100644 --- a/src/shared/verbs.h +++ b/src/shared/verbs.h @@ -52,7 +52,8 @@ typedef struct { #define VERB_NOARG(d, v, h) \ VERB(d, v, /* a= */ NULL, /* amin= */ VERB_ANY, /* amax= */ 1, /* f= */ 0, h) -/* Magic entry in the table (which will not be returned) that designates the start of the group . */ +/* Magic entry in the table (which will not be returned) that designates the start of the group . + * The macro works as a separator between groups and must be between other VERB* stanzas. */ #define VERB_GROUP(gr) \ _VERB_DATA(/* d= */ NULL, /* v= */ gr, /* a= */ NULL, /* amin= */ 0, /* amax= */ 0, \ /* f= */ VERB_GROUP_MARKER, /* dat= */ 0, /* h= */ NULL) From b4e0ff0874fb6128fc4bd0610e94029ce93b9454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 1 Apr 2026 17:31:18 +0200 Subject: [PATCH 0725/1296] shared/options: extend comment --- src/shared/options.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/shared/options.c b/src/shared/options.c index 853df0d38d2f1..e6b82fe34d07c 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -238,7 +238,8 @@ char** option_parser_get_args(const OptionParser *state) { /* Returns positional args as a strv. * If "--" was found, it has been moved before state->positional_offset. * The array is only valid, i.e. clean without any options, after parsing - * has naturally finished. */ + * has naturally finished. The array that is returned is a slice of the + * original argv array, so it must not be freed or modified. */ assert(state->optind > 0); assert(state->optind == state->argc || state->parsing_stopped); From f833b092d8075e3c0d03a05f818f454e6fbd6936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 3 Apr 2026 10:27:43 +0200 Subject: [PATCH 0726/1296] analyze: use table_print_with_pager in one more place I guess this wasn't converted previously because verb_blame doesn't support json output, the flags that are passed atm cannot contain real json flags. That's OK, we can still use table_print_with_pager. --- src/analyze/analyze-blame.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/analyze/analyze-blame.c b/src/analyze/analyze-blame.c index 8651f2586a4b9..24b29a26a1ff1 100644 --- a/src/analyze/analyze-blame.c +++ b/src/analyze/analyze-blame.c @@ -28,8 +28,6 @@ int verb_blame(int argc, char *argv[], uintptr_t _data, void *userdata) { if (!table) return log_oom(); - table_set_header(table, false); - assert_se(cell = table_get_cell(table, 0, 0)); r = table_set_ellipsize_percent(table, cell, 100); if (r < 0) @@ -63,11 +61,5 @@ int verb_blame(int argc, char *argv[], uintptr_t _data, void *userdata) { return table_log_add_error(r); } - pager_open(arg_pager_flags); - - r = table_print(table, NULL); - if (r < 0) - return r; - - return 0; + return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, /* show_header= */ false); } From 16a43412b9fbe3a4af8cafff40a0c2fedf34efc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 3 Apr 2026 10:53:53 +0200 Subject: [PATCH 0727/1296] busctl: use table_print_with_pager in one more place --- src/busctl/busctl.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c index aa04c9bbf0e5d..a895c3fe91edd 100644 --- a/src/busctl/busctl.c +++ b/src/busctl/busctl.c @@ -1188,13 +1188,7 @@ static int verb_introspect(int argc, char *argv[], uintptr_t _data, void *userda return table_log_add_error(r); } - pager_open(arg_pager_flags); - - r = table_print(table, NULL); - if (r < 0) - return table_log_print_error(r); - - return 0; + return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend); } static int message_dump(sd_bus_message *m, FILE *f) { From 0ce5a8fb4ee182da1967c8d99ce100f1c484ad15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 3 Apr 2026 10:59:18 +0200 Subject: [PATCH 0728/1296] tree-wide: drop flush&check step after table printing Almost all callers of table_print() specify stdout or NULL (equivalent to stdout) as the output stream. Simplify things by not requiring the the stream to be specified. In almost all cases, the printing of the table is surrounded by normal printfs() that don't do explicit flushing and for which we don't check the output stream status. Let's simplify most callers and skip this step. The reason is not so much to avoid the extra step itself, but instead to avoid the _handling_ of the potential failure. We generally only want to print an error message for ENOMEM and other "internal" errors, so strictly speaking we should filter out the errors from the stream. By skipping the flush&check step we implicitly do this. --- src/ac-power/ac-power.c | 2 +- src/analyze/analyze-calendar.c | 2 +- src/analyze/analyze-image-policy.c | 2 +- src/analyze/analyze-inspect-elf.c | 2 +- src/analyze/analyze-plot.c | 2 +- src/analyze/analyze-timespan.c | 2 +- src/analyze/analyze-timestamp.c | 2 +- src/ask-password/ask-password.c | 2 +- src/binfmt/binfmt.c | 2 +- src/bless-boot/bless-boot.c | 4 ++-- src/cryptenroll/cryptenroll-list.c | 2 +- src/detect-virt/detect-virt.c | 2 +- src/dissect/dissect.c | 6 +++--- src/factory-reset/factory-reset-tool.c | 4 ++-- src/hostname/hostnamectl.c | 6 +++--- src/id128/id128.c | 4 ++-- src/imds/imds-tool.c | 2 +- src/locale/localectl.c | 2 +- src/login/loginctl.c | 6 +++--- src/machine/machinectl.c | 2 +- src/network/networkctl-address-label.c | 2 +- src/network/networkctl-list.c | 2 +- src/network/networkctl-lldp.c | 2 +- src/network/networkctl-status-link.c | 2 +- src/network/networkctl-status-system.c | 2 +- src/notify/notify.c | 2 +- src/portable/portablectl.c | 2 +- src/report/report-basic-server.c | 2 +- src/resolve/resolvectl.c | 6 +++--- src/run/run.c | 2 +- src/shared/format-table.c | 9 ++++++--- src/shared/format-table.h | 6 +++++- src/shared/libfido2-util.c | 2 +- src/shared/parse-argument.c | 2 +- src/shared/pkcs11-util.c | 2 +- src/shared/tpm2-util.c | 2 +- src/systemctl/systemctl-list-jobs.c | 2 +- src/systemctl/systemctl-util.c | 2 +- src/test/test-format-table.c | 2 +- src/timedate/timedatectl.c | 8 ++++---- src/update-done/update-done.c | 2 +- src/validatefs/validatefs.c | 2 +- src/varlinkctl/varlinkctl.c | 2 +- src/vpick/vpick-tool.c | 2 +- 44 files changed, 67 insertions(+), 60 deletions(-) diff --git a/src/ac-power/ac-power.c b/src/ac-power/ac-power.c index 1ca1048c5a4e9..b153194cdbf7e 100644 --- a/src/ac-power/ac-power.c +++ b/src/ac-power/ac-power.c @@ -37,7 +37,7 @@ static int help(void) { program_invocation_short_name, ansi_highlight(), ansi_normal()); - table_print(options, stdout); + table_print(options); printf("\nSee the %s for details.\n", link); return 0; diff --git a/src/analyze/analyze-calendar.c b/src/analyze/analyze-calendar.c index ac0b2da7d8286..ea99f3871e85f 100644 --- a/src/analyze/analyze-calendar.c +++ b/src/analyze/analyze-calendar.c @@ -119,7 +119,7 @@ static int test_calendar_one(usec_t n, const char *p) { n = next; } - return table_print(table, NULL); + return table_print(table); } int verb_calendar(int argc, char *argv[], uintptr_t _data, void *userdata) { diff --git a/src/analyze/analyze-image-policy.c b/src/analyze/analyze-image-policy.c index 93777c91a1f56..16e69414c1250 100644 --- a/src/analyze/analyze-image-policy.c +++ b/src/analyze/analyze-image-policy.c @@ -157,7 +157,7 @@ int verb_image_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { putc('\n', stdout); - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return r; } diff --git a/src/analyze/analyze-inspect-elf.c b/src/analyze/analyze-inspect-elf.c index 41dabd051c822..f4fcc3bd57089 100644 --- a/src/analyze/analyze-inspect-elf.c +++ b/src/analyze/analyze-inspect-elf.c @@ -118,7 +118,7 @@ static int analyze_elf(char **filenames, sd_json_format_flags_t json_flags) { if (sd_json_format_enabled(json_flags)) sd_json_variant_dump(package_metadata, json_flags, stdout, NULL); else { - r = table_print(t, NULL); + r = table_print(t); if (r < 0) return table_log_print_error(r); } diff --git a/src/analyze/analyze-plot.c b/src/analyze/analyze-plot.c index 8460757b8ac89..7f92c1c6bb23e 100644 --- a/src/analyze/analyze-plot.c +++ b/src/analyze/analyze-plot.c @@ -429,7 +429,7 @@ static int show_table(Table *table, const char *word) { if (sd_json_format_enabled(arg_json_format_flags)) r = table_print_json(table, NULL, arg_json_format_flags | SD_JSON_FORMAT_COLOR_AUTO); else - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); } diff --git a/src/analyze/analyze-timespan.c b/src/analyze/analyze-timespan.c index fb077617d476e..b6ca7bb4af4df 100644 --- a/src/analyze/analyze-timespan.c +++ b/src/analyze/analyze-timespan.c @@ -55,7 +55,7 @@ int verb_timespan(int argc, char *argv[], uintptr_t _data, void *userdata) { if (r < 0) return table_log_add_error(r); - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return r; diff --git a/src/analyze/analyze-timestamp.c b/src/analyze/analyze-timestamp.c index e7ba6e1bcc1b9..d998ca830a20a 100644 --- a/src/analyze/analyze-timestamp.c +++ b/src/analyze/analyze-timestamp.c @@ -73,7 +73,7 @@ static int test_timestamp_one(const char *p) { if (r < 0) return table_log_add_error(r); - return table_print(table, NULL); + return table_print(table); } int verb_timestamp(int argc, char *argv[], uintptr_t _data, void *userdata) { diff --git a/src/ask-password/ask-password.c b/src/ask-password/ask-password.c index 2c032c1afbc7f..2ed2be8afee31 100644 --- a/src/ask-password/ask-password.c +++ b/src/ask-password/ask-password.c @@ -57,7 +57,7 @@ static int help(void) { program_invocation_short_name, ansi_highlight(), ansi_normal()); - table_print(options, stdout); + table_print(options); printf("\nSee the %s for details.\n", link); return 0; diff --git a/src/binfmt/binfmt.c b/src/binfmt/binfmt.c index 23c09fe3496e3..06fff811bfb2f 100644 --- a/src/binfmt/binfmt.c +++ b/src/binfmt/binfmt.c @@ -126,7 +126,7 @@ static int help(void) { program_invocation_short_name, ansi_highlight(), ansi_normal()); - table_print(options, stdout); + table_print(options); printf("\nSee the %s for details.\n", link); return 0; diff --git a/src/bless-boot/bless-boot.c b/src/bless-boot/bless-boot.c index b82be92dbdf05..67da021f29660 100644 --- a/src/bless-boot/bless-boot.c +++ b/src/bless-boot/bless-boot.c @@ -58,10 +58,10 @@ static int help(void) { program_invocation_short_name, ansi_highlight(), ansi_normal()); - table_print(verbs, stdout); + table_print(verbs); printf("\nOptions:\n"); - table_print(options, stdout); + table_print(options); printf("\nSee the %s for details.\n", link); return 0; diff --git a/src/cryptenroll/cryptenroll-list.c b/src/cryptenroll/cryptenroll-list.c index 32e8c9cf2a32b..bf9f2a130e94a 100644 --- a/src/cryptenroll/cryptenroll-list.c +++ b/src/cryptenroll/cryptenroll-list.c @@ -125,7 +125,7 @@ int list_enrolled(struct crypt_device *cd) { return 0; } - r = table_print(t, stdout); + r = table_print(t); if (r < 0) return log_error_errno(r, "Failed to show slot table: %m"); diff --git a/src/detect-virt/detect-virt.c b/src/detect-virt/detect-virt.c index 912f6fbdd67bb..a8b9739a925d3 100644 --- a/src/detect-virt/detect-virt.c +++ b/src/detect-virt/detect-virt.c @@ -41,7 +41,7 @@ static int help(void) { program_invocation_short_name, ansi_highlight(), ansi_normal()); - table_print(options, stdout); + table_print(options); printf("\nSee the %s for details.\n", link); return 0; diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 26fa1aa1a383e..91ad3a4502676 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -165,11 +165,11 @@ static int help(void) { ansi_underline(), ansi_normal()); - table_print(options, stdout); + table_print(options); printf("\n%sCommands:%s\n", ansi_underline(), ansi_normal()); - table_print(commands, stdout); + table_print(commands); printf("\nSee the %s for details.\n", link); return 0; @@ -1082,7 +1082,7 @@ static int action_dissect( if (!sd_json_format_enabled(arg_json_format_flags)) { table_set_header(t, arg_legend); - r = table_print(t, NULL); + r = table_print(t); if (r < 0) return table_log_print_error(r); } else { diff --git a/src/factory-reset/factory-reset-tool.c b/src/factory-reset/factory-reset-tool.c index e7eabd8757b95..c09369e22936a 100644 --- a/src/factory-reset/factory-reset-tool.c +++ b/src/factory-reset/factory-reset-tool.c @@ -51,10 +51,10 @@ static int help(void) { program_invocation_short_name, ansi_highlight(), ansi_normal()); - table_print(verbs, stdout); + table_print(verbs); printf("\nOptions:\n"); - table_print(options, stdout); + table_print(options); printf("\nSee the %s for details.\n", link); return 0; diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index 52fa3319d7070..52b31a58679ed 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -375,7 +375,7 @@ static int print_status_info(StatusInfo *i) { } } - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); @@ -748,10 +748,10 @@ static int help(void) { program_invocation_short_name, ansi_highlight(), ansi_normal()); - table_print(verbs, stdout); + table_print(verbs); printf("\nOptions:\n"); - table_print(options, stdout); + table_print(options); printf("\nSee the %s for details.\n", link); return 0; diff --git a/src/id128/id128.c b/src/id128/id128.c index f23403b3f811b..eda117aec0c4e 100644 --- a/src/id128/id128.c +++ b/src/id128/id128.c @@ -216,10 +216,10 @@ static int help(void) { program_invocation_short_name, ansi_highlight(), ansi_normal()); - table_print(verbs, stdout); + table_print(verbs); printf("\nOptions:\n"); - table_print(options, stdout); + table_print(options); printf("\nSee the %s for details.\n", link); return 0; diff --git a/src/imds/imds-tool.c b/src/imds/imds-tool.c index 4ae8dbb33cec9..61fc82014e807 100644 --- a/src/imds/imds-tool.c +++ b/src/imds/imds-tool.c @@ -381,7 +381,7 @@ static int action_summary(sd_varlink *link) { if (table_isempty(table)) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "No well-known IMDS data available."); - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); diff --git a/src/locale/localectl.c b/src/locale/localectl.c index 65756d26f0d30..b67a67e73b7d1 100644 --- a/src/locale/localectl.c +++ b/src/locale/localectl.c @@ -144,7 +144,7 @@ static int print_status_info(StatusInfo *i) { return table_log_add_error(r); } - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); diff --git a/src/login/loginctl.c b/src/login/loginctl.c index a921a4a6771c9..fa1c02448124b 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -716,7 +716,7 @@ static int print_session_status_info(sd_bus *bus, const char *path) { /* We don't use the table to show the header, in order to make the width of the column stable. */ printf("%s%s - %s (" UID_FMT ")%s\n", ansi_highlight(), i.id, i.name, i.uid, ansi_normal()); - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); @@ -821,7 +821,7 @@ static int print_user_status_info(sd_bus *bus, const char *path) { printf("%s%s (" UID_FMT ")%s\n", ansi_highlight(), i.name, i.uid, ansi_normal()); - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); @@ -896,7 +896,7 @@ static int print_seat_status_info(sd_bus *bus, const char *path) { printf("%s%s%s\n", ansi_highlight(), i.id, ansi_normal()); - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 733b1a19ef100..6c684149cb957 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -262,7 +262,7 @@ static int show_table(Table *table, const char *word) { if (OUTPUT_MODE_IS_JSON(arg_output)) r = table_print_json(table, NULL, output_mode_to_json_format_flags(arg_output) | SD_JSON_FORMAT_COLOR_AUTO); else - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); } diff --git a/src/network/networkctl-address-label.c b/src/network/networkctl-address-label.c index f587d0cfbb542..04b6d4d236614 100644 --- a/src/network/networkctl-address-label.c +++ b/src/network/networkctl-address-label.c @@ -82,7 +82,7 @@ static int dump_address_labels(sd_netlink *rtnl) { return table_log_add_error(r); } - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); diff --git a/src/network/networkctl-list.c b/src/network/networkctl-list.c index 1ef38a0b85452..c30be4a1dee91 100644 --- a/src/network/networkctl-list.c +++ b/src/network/networkctl-list.c @@ -79,7 +79,7 @@ int verb_list_links(int argc, char *argv[], uintptr_t _data, void *userdata) { return table_log_add_error(r); } - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); diff --git a/src/network/networkctl-lldp.c b/src/network/networkctl-lldp.c index ddce26e5c4268..c91d8fea00715 100644 --- a/src/network/networkctl-lldp.c +++ b/src/network/networkctl-lldp.c @@ -303,7 +303,7 @@ int verb_link_lldp_status(int argc, char *argv[], uintptr_t _data, void *userdat } } - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); diff --git a/src/network/networkctl-status-link.c b/src/network/networkctl-status-link.c index f63ee2d4175d7..9cbf3efb33210 100644 --- a/src/network/networkctl-status-link.c +++ b/src/network/networkctl-status-link.c @@ -900,7 +900,7 @@ static int link_status_one( on_color_operational, glyph(GLYPH_BLACK_CIRCLE), off_color_operational, info->ifindex, info->name); - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); diff --git a/src/network/networkctl-status-system.c b/src/network/networkctl-status-system.c index 20a4c2be9186f..ce403d60624e3 100644 --- a/src/network/networkctl-status-system.c +++ b/src/network/networkctl-status-system.c @@ -126,7 +126,7 @@ int system_status(sd_netlink *rtnl, sd_hwdb *hwdb) { on_color_operational, glyph(GLYPH_BLACK_CIRCLE), off_color_operational, strna(netifs_joined)); - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); diff --git a/src/notify/notify.c b/src/notify/notify.c index a06f5ce7734e3..e425a125ac3ae 100644 --- a/src/notify/notify.c +++ b/src/notify/notify.c @@ -77,7 +77,7 @@ static int help(void) { ansi_highlight(), ansi_normal()); - table_print(options, stdout); + table_print(options); printf("\nSee the %s for details.\n", link); return 0; diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index 0cd461a997e23..f5ce3db9c41e3 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -1075,7 +1075,7 @@ static int verb_list_images(int argc, char *argv[], uintptr_t _data, void *userd table_set_header(table, arg_legend); - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); } diff --git a/src/report/report-basic-server.c b/src/report/report-basic-server.c index 32ec9b035600b..dc482a37ff0ba 100644 --- a/src/report/report-basic-server.c +++ b/src/report/report-basic-server.c @@ -47,7 +47,7 @@ static int help(void) { ansi_normal(), ansi_underline(), ansi_normal()); - table_print(options, stdout); + table_print(options); return 0; } diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index e4705a6bccc76..fe2b676c7c0dc 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -1321,7 +1321,7 @@ static int verb_show_statistics(int argc, char *argv[], uintptr_t _data, void *u if (r < 0) return table_log_add_error(r); - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); @@ -1890,7 +1890,7 @@ static int print_configuration(DNSConfiguration *configuration, StatusMode mode, return table_log_add_error(r); } - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); @@ -3097,7 +3097,7 @@ static int dump_server_state(sd_json_variant *server) { if (r < 0) return table_log_add_error(r); - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); diff --git a/src/run/run.c b/src/run/run.c index f1bbbe1f393c2..88a9f41d69a3a 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -2437,7 +2437,7 @@ static int run_context_show_result(RunContext *c) { return table_log_add_error(r); } - r = table_print(t, stderr); + r = table_print_full(t, stderr, /* flush= */ true); if (r < 0) return table_log_print_error(r); diff --git a/src/shared/format-table.c b/src/shared/format-table.c index 04552b5b56776..718d5c09b860a 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -2217,7 +2217,7 @@ int _table_sync_column_widths(size_t column, Table *a, ...) { return r; } -int table_print(Table *t, FILE *f) { +int table_print_full(Table *t, FILE *f, bool flush) { size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width, table_minimum_width, table_maximum_width, table_requested_width, table_effective_width, *width = NULL; @@ -2637,6 +2637,9 @@ int table_print(Table *t, FILE *f) { } while (more_sublines); } + if (!flush) + return 0; + return fflush_and_check(f); } @@ -2652,7 +2655,7 @@ int table_format(Table *t, char **ret) { if (!f) return -ENOMEM; - r = table_print(t, f); + r = table_print_full(t, f, /* flush= */ true); if (r < 0) return r; @@ -3141,7 +3144,7 @@ int table_print_json(Table *t, FILE *f, sd_json_format_flags_t flags) { assert(t); if (!sd_json_format_enabled(flags)) /* If JSON output is turned off, use regular output */ - return table_print(t, f); + return table_print_full(t, f, /* flush= */ true); if (!f) f = stdout; diff --git a/src/shared/format-table.h b/src/shared/format-table.h index bb5a68b7e9fa3..ee4007394ef0f 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -147,7 +147,11 @@ int table_set_column_width(Table *t, size_t column, size_t width); int _table_sync_column_widths(size_t column, Table *a, ...); #define table_sync_column_widths(column, a, ...) _table_sync_column_widths(column, a, __VA_ARGS__, NULL) -int table_print(Table *t, FILE *f); +int table_print_full(Table *t, FILE *f, bool flush); +static inline int table_print(Table *t) { + return table_print_full(t, /* f= */ NULL, /* flush= */ false); +} + int table_format(Table *t, char **ret); static inline TableCell* TABLE_HEADER_CELL(size_t i) { diff --git a/src/shared/libfido2-util.c b/src/shared/libfido2-util.c index f4c8ad5c8616e..7e50d0dd2df57 100644 --- a/src/shared/libfido2-util.c +++ b/src/shared/libfido2-util.c @@ -1241,7 +1241,7 @@ int fido2_list_devices(void) { } } - r = table_print(t, stdout); + r = table_print(t); if (r < 0) { log_error_errno(r, "Failed to show device table: %m"); goto finish; diff --git a/src/shared/parse-argument.c b/src/shared/parse-argument.c index 39e5328e7037e..6ac42c4d88407 100644 --- a/src/shared/parse-argument.c +++ b/src/shared/parse-argument.c @@ -128,7 +128,7 @@ int parse_signal_argument(const char *s, int *ret) { return table_log_add_error(r); } - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); diff --git a/src/shared/pkcs11-util.c b/src/shared/pkcs11-util.c index 1fa0d77d6d004..2d8ce29b5803e 100644 --- a/src/shared/pkcs11-util.c +++ b/src/shared/pkcs11-util.c @@ -1816,7 +1816,7 @@ int pkcs11_list_tokens(void) { return 0; } - r = table_print(t, stdout); + r = table_print(t); if (r < 0) return log_error_errno(r, "Failed to show device table: %m"); diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index dc062117b414a..a1f05d6397c5e 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -6584,7 +6584,7 @@ int tpm2_list_devices(bool legend, bool quiet) { return 0; } - r = table_print(t, stdout); + r = table_print(t); if (r < 0) return log_error_errno(r, "Failed to show device table: %m"); diff --git a/src/systemctl/systemctl-list-jobs.c b/src/systemctl/systemctl-list-jobs.c index 5804d32518c6e..bf3f5ed8662aa 100644 --- a/src/systemctl/systemctl-list-jobs.c +++ b/src/systemctl/systemctl-list-jobs.c @@ -115,7 +115,7 @@ static int output_jobs_list(sd_bus *bus, const struct job_info* jobs, unsigned n output_waiting_jobs(bus, table, j->id, "GetJobBefore", "\twaiting for job"); } - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return log_error_errno(r, "Failed to print the table: %m"); diff --git a/src/systemctl/systemctl-util.c b/src/systemctl/systemctl-util.c index ef7bce9e7f1cc..b278f784ba3ec 100644 --- a/src/systemctl/systemctl-util.c +++ b/src/systemctl/systemctl-util.c @@ -919,7 +919,7 @@ int output_table(Table *table) { if (OUTPUT_MODE_IS_JSON(arg_output)) r = table_print_json(table, NULL, output_mode_to_json_format_flags(arg_output) | SD_JSON_FORMAT_COLOR_AUTO); else - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); diff --git a/src/test/test-format-table.c b/src/test/test-format-table.c index 4305f77224e66..677adaa9c964d 100644 --- a/src/test/test-format-table.c +++ b/src/test/test-format-table.c @@ -890,7 +890,7 @@ TEST(table_ansi) { "FOO BAR BAZ KKK\n" "hallo knuerzredgreen noansi thisisgrey\n"); - ASSERT_OK(table_print(table, /* f= */ NULL)); + ASSERT_OK(table_print(table)); _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL, *jj = NULL; diff --git a/src/timedate/timedatectl.c b/src/timedate/timedatectl.c index cec4363affbbb..93a5dd6da359b 100644 --- a/src/timedate/timedatectl.c +++ b/src/timedate/timedatectl.c @@ -163,7 +163,7 @@ static int print_status_info(const StatusInfo *i) { if (r < 0) return table_log_add_error(r); - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); @@ -443,7 +443,7 @@ static int print_ntp_status_info(NTPStatusInfo *i) { if (r < 0) return table_log_add_error(r); - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); @@ -452,7 +452,7 @@ static int print_ntp_status_info(NTPStatusInfo *i) { if (i->dest < i->origin || i->trans < i->recv || i->dest - i->origin < i->trans - i->recv) { log_error("Invalid NTP response"); - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); @@ -536,7 +536,7 @@ static int print_ntp_status_info(NTPStatusInfo *i) { return table_log_add_error(r); } - r = table_print(table, NULL); + r = table_print(table); if (r < 0) return table_log_print_error(r); diff --git a/src/update-done/update-done.c b/src/update-done/update-done.c index b3c45c352b98d..f076f263a6880 100644 --- a/src/update-done/update-done.c +++ b/src/update-done/update-done.c @@ -82,7 +82,7 @@ static int help(void) { ansi_normal(), ansi_underline(), ansi_normal()); - table_print(options, stdout); + table_print(options); printf("\nSee the %s for details.\n", link); return 0; diff --git a/src/validatefs/validatefs.c b/src/validatefs/validatefs.c index 9645fd187fe50..0608a1489520b 100644 --- a/src/validatefs/validatefs.c +++ b/src/validatefs/validatefs.c @@ -50,7 +50,7 @@ static int help(void) { program_invocation_short_name, ansi_highlight(), ansi_normal()); - table_print(options, stdout); + table_print(options); printf("\nSee the %s for details.\n", link); return 0; diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index c2cdd52b89ea6..2d610db4e5500 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -439,7 +439,7 @@ static int verb_info(int argc, char *argv[], uintptr_t _data, void *userdata) { if (r < 0) return table_log_add_error(r); - r = table_print(t, NULL); + r = table_print(t); if (r < 0) return table_log_print_error(r); } diff --git a/src/vpick/vpick-tool.c b/src/vpick/vpick-tool.c index c20994d115dd5..1f3277da45e6d 100644 --- a/src/vpick/vpick-tool.c +++ b/src/vpick/vpick-tool.c @@ -337,7 +337,7 @@ static int run(int argc, char *argv[]) { return table_log_add_error(r); } - r = table_print(t, stdout); + r = table_print(t); if (r < 0) return table_log_print_error(r); From 1008583c91474091eae5ebd3a55ddd86d41a6e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 3 Apr 2026 11:49:30 +0200 Subject: [PATCH 0729/1296] tree-wide: print error if table_print() fails We generally want to print an error message if table_print() fails. Add a helper function for this and use it consistently. This does one of the three things depending on the call site: - a no-change reformatting of the code - change from a custom message to the generic one - addition of the error message where previously none was printed In the third case, the actual use impact is very small, since the table formatting is very unlikely to fail. But if it did, we would often return an error without any message whatsoever, which we never want to do. --- src/ac-power/ac-power.c | 4 +++- src/analyze/analyze-calendar.c | 2 +- src/analyze/analyze-image-policy.c | 2 +- src/analyze/analyze-inspect-elf.c | 4 ++-- src/analyze/analyze-timespan.c | 2 +- src/analyze/analyze-timestamp.c | 2 +- src/ask-password/ask-password.c | 4 +++- src/binfmt/binfmt.c | 4 +++- src/bless-boot/bless-boot.c | 8 ++++++-- src/cryptenroll/cryptenroll-list.c | 6 +----- src/detect-virt/detect-virt.c | 4 +++- src/dissect/dissect.c | 12 ++++++++---- src/factory-reset/factory-reset-tool.c | 8 ++++++-- src/hostname/hostnamectl.c | 14 +++++++------- src/id128/id128.c | 8 ++++++-- src/imds/imds-tool.c | 8 ++------ src/locale/localectl.c | 6 +----- src/login/loginctl.c | 12 ++++++------ src/network/networkctl-address-label.c | 6 +----- src/network/networkctl-list.c | 4 ++-- src/network/networkctl-lldp.c | 4 ++-- src/network/networkctl-status-link.c | 4 ++-- src/network/networkctl-status-system.c | 4 ++-- src/notify/notify.c | 4 +++- src/portable/portablectl.c | 4 ++-- src/report/report-basic-server.c | 3 +-- src/resolve/resolvectl.c | 16 ++++------------ src/shared/format-table.c | 9 +++++++++ src/shared/format-table.h | 1 + src/shared/libfido2-util.c | 6 ++---- src/shared/parse-argument.c | 6 +----- src/shared/pkcs11-util.c | 6 +----- src/shared/tpm2-util.c | 6 +----- src/systemctl/systemctl-list-jobs.c | 4 ++-- src/timedate/timedatectl.c | 22 +++++----------------- src/update-done/update-done.c | 4 +++- src/validatefs/validatefs.c | 4 +++- src/varlinkctl/varlinkctl.c | 14 +++++--------- src/vpick/vpick-tool.c | 4 ++-- 39 files changed, 115 insertions(+), 130 deletions(-) diff --git a/src/ac-power/ac-power.c b/src/ac-power/ac-power.c index b153194cdbf7e..dad4384ada74c 100644 --- a/src/ac-power/ac-power.c +++ b/src/ac-power/ac-power.c @@ -37,7 +37,9 @@ static int help(void) { program_invocation_short_name, ansi_highlight(), ansi_normal()); - table_print(options); + r = table_print_or_warn(options); + if (r < 0) + return r; printf("\nSee the %s for details.\n", link); return 0; diff --git a/src/analyze/analyze-calendar.c b/src/analyze/analyze-calendar.c index ea99f3871e85f..c1427f25aabf2 100644 --- a/src/analyze/analyze-calendar.c +++ b/src/analyze/analyze-calendar.c @@ -119,7 +119,7 @@ static int test_calendar_one(usec_t n, const char *p) { n = next; } - return table_print(table); + return table_print_or_warn(table); } int verb_calendar(int argc, char *argv[], uintptr_t _data, void *userdata) { diff --git a/src/analyze/analyze-image-policy.c b/src/analyze/analyze-image-policy.c index 16e69414c1250..220716878a524 100644 --- a/src/analyze/analyze-image-policy.c +++ b/src/analyze/analyze-image-policy.c @@ -157,7 +157,7 @@ int verb_image_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { putc('\n', stdout); - r = table_print(table); + r = table_print_or_warn(table); if (r < 0) return r; } diff --git a/src/analyze/analyze-inspect-elf.c b/src/analyze/analyze-inspect-elf.c index f4fcc3bd57089..c664aea588673 100644 --- a/src/analyze/analyze-inspect-elf.c +++ b/src/analyze/analyze-inspect-elf.c @@ -118,9 +118,9 @@ static int analyze_elf(char **filenames, sd_json_format_flags_t json_flags) { if (sd_json_format_enabled(json_flags)) sd_json_variant_dump(package_metadata, json_flags, stdout, NULL); else { - r = table_print(t); + r = table_print_or_warn(t); if (r < 0) - return table_log_print_error(r); + return r; } } diff --git a/src/analyze/analyze-timespan.c b/src/analyze/analyze-timespan.c index b6ca7bb4af4df..31a201c5f6c5d 100644 --- a/src/analyze/analyze-timespan.c +++ b/src/analyze/analyze-timespan.c @@ -55,7 +55,7 @@ int verb_timespan(int argc, char *argv[], uintptr_t _data, void *userdata) { if (r < 0) return table_log_add_error(r); - r = table_print(table); + r = table_print_or_warn(table); if (r < 0) return r; diff --git a/src/analyze/analyze-timestamp.c b/src/analyze/analyze-timestamp.c index d998ca830a20a..5d4fa2f6250f5 100644 --- a/src/analyze/analyze-timestamp.c +++ b/src/analyze/analyze-timestamp.c @@ -73,7 +73,7 @@ static int test_timestamp_one(const char *p) { if (r < 0) return table_log_add_error(r); - return table_print(table); + return table_print_or_warn(table); } int verb_timestamp(int argc, char *argv[], uintptr_t _data, void *userdata) { diff --git a/src/ask-password/ask-password.c b/src/ask-password/ask-password.c index 2ed2be8afee31..4bd618b2a7f09 100644 --- a/src/ask-password/ask-password.c +++ b/src/ask-password/ask-password.c @@ -57,7 +57,9 @@ static int help(void) { program_invocation_short_name, ansi_highlight(), ansi_normal()); - table_print(options); + r = table_print_or_warn(options); + if (r < 0) + return r; printf("\nSee the %s for details.\n", link); return 0; diff --git a/src/binfmt/binfmt.c b/src/binfmt/binfmt.c index 06fff811bfb2f..f8c2b55595e07 100644 --- a/src/binfmt/binfmt.c +++ b/src/binfmt/binfmt.c @@ -126,7 +126,9 @@ static int help(void) { program_invocation_short_name, ansi_highlight(), ansi_normal()); - table_print(options); + r = table_print_or_warn(options); + if (r < 0) + return r; printf("\nSee the %s for details.\n", link); return 0; diff --git a/src/bless-boot/bless-boot.c b/src/bless-boot/bless-boot.c index 67da021f29660..86525f359a102 100644 --- a/src/bless-boot/bless-boot.c +++ b/src/bless-boot/bless-boot.c @@ -58,10 +58,14 @@ static int help(void) { program_invocation_short_name, ansi_highlight(), ansi_normal()); - table_print(verbs); + r = table_print_or_warn(verbs); + if (r < 0) + return r; printf("\nOptions:\n"); - table_print(options); + r = table_print_or_warn(options); + if (r < 0) + return r; printf("\nSee the %s for details.\n", link); return 0; diff --git a/src/cryptenroll/cryptenroll-list.c b/src/cryptenroll/cryptenroll-list.c index bf9f2a130e94a..bca9f74c3ab02 100644 --- a/src/cryptenroll/cryptenroll-list.c +++ b/src/cryptenroll/cryptenroll-list.c @@ -125,9 +125,5 @@ int list_enrolled(struct crypt_device *cd) { return 0; } - r = table_print(t); - if (r < 0) - return log_error_errno(r, "Failed to show slot table: %m"); - - return 0; + return table_print_or_warn(t); } diff --git a/src/detect-virt/detect-virt.c b/src/detect-virt/detect-virt.c index a8b9739a925d3..3c5b3dd39cb34 100644 --- a/src/detect-virt/detect-virt.c +++ b/src/detect-virt/detect-virt.c @@ -41,7 +41,9 @@ static int help(void) { program_invocation_short_name, ansi_highlight(), ansi_normal()); - table_print(options); + r = table_print_or_warn(options); + if (r < 0) + return r; printf("\nSee the %s for details.\n", link); return 0; diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 91ad3a4502676..ceaedb262fd71 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -165,11 +165,15 @@ static int help(void) { ansi_underline(), ansi_normal()); - table_print(options); + r = table_print_or_warn(options); + if (r < 0) + return r; printf("\n%sCommands:%s\n", ansi_underline(), ansi_normal()); - table_print(commands); + r = table_print_or_warn(commands); + if (r < 0) + return r; printf("\nSee the %s for details.\n", link); return 0; @@ -1082,9 +1086,9 @@ static int action_dissect( if (!sd_json_format_enabled(arg_json_format_flags)) { table_set_header(t, arg_legend); - r = table_print(t); + r = table_print_or_warn(t); if (r < 0) - return table_log_print_error(r); + return r; } else { _cleanup_(sd_json_variant_unrefp) sd_json_variant *jt = NULL; diff --git a/src/factory-reset/factory-reset-tool.c b/src/factory-reset/factory-reset-tool.c index c09369e22936a..ec3f7e43c492b 100644 --- a/src/factory-reset/factory-reset-tool.c +++ b/src/factory-reset/factory-reset-tool.c @@ -51,10 +51,14 @@ static int help(void) { program_invocation_short_name, ansi_highlight(), ansi_normal()); - table_print(verbs); + r = table_print_or_warn(verbs); + if (r < 0) + return r; printf("\nOptions:\n"); - table_print(options); + r = table_print_or_warn(options); + if (r < 0) + return r; printf("\nSee the %s for details.\n", link); return 0; diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index 52b31a58679ed..67e82c1e7c028 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -375,11 +375,7 @@ static int print_status_info(StatusInfo *i) { } } - r = table_print(table); - if (r < 0) - return table_log_print_error(r); - - return 0; + return table_print_or_warn(table); } static int get_one_name(sd_bus *bus, const char* attr, char **ret) { @@ -748,10 +744,14 @@ static int help(void) { program_invocation_short_name, ansi_highlight(), ansi_normal()); - table_print(verbs); + r = table_print_or_warn(verbs); + if (r < 0) + return r; printf("\nOptions:\n"); - table_print(options); + r = table_print_or_warn(options); + if (r < 0) + return r; printf("\nSee the %s for details.\n", link); return 0; diff --git a/src/id128/id128.c b/src/id128/id128.c index eda117aec0c4e..1616bfea65bbe 100644 --- a/src/id128/id128.c +++ b/src/id128/id128.c @@ -216,10 +216,14 @@ static int help(void) { program_invocation_short_name, ansi_highlight(), ansi_normal()); - table_print(verbs); + r = table_print_or_warn(verbs); + if (r < 0) + return r; printf("\nOptions:\n"); - table_print(options); + r = table_print_or_warn(options); + if (r < 0) + return r; printf("\nSee the %s for details.\n", link); return 0; diff --git a/src/imds/imds-tool.c b/src/imds/imds-tool.c index 61fc82014e807..ff5a9b317af8a 100644 --- a/src/imds/imds-tool.c +++ b/src/imds/imds-tool.c @@ -381,14 +381,10 @@ static int action_summary(sd_varlink *link) { if (table_isempty(table)) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "No well-known IMDS data available."); - r = table_print(table); - if (r < 0) - return table_log_print_error(r); - - return 0; + return table_print_or_warn(table); } -static const char *detect_json_object(const char *text) { +static const char* detect_json_object(const char *text) { assert(text); /* Checks if the provided text looks like a JSON object. It checks if the first non-whitespace diff --git a/src/locale/localectl.c b/src/locale/localectl.c index b67a67e73b7d1..e80cd96c86ee8 100644 --- a/src/locale/localectl.c +++ b/src/locale/localectl.c @@ -144,11 +144,7 @@ static int print_status_info(StatusInfo *i) { return table_log_add_error(r); } - r = table_print(table); - if (r < 0) - return table_log_print_error(r); - - return 0; + return table_print_or_warn(table); } static int verb_show_status(int argc, char *argv[], uintptr_t _data, void *userdata) { diff --git a/src/login/loginctl.c b/src/login/loginctl.c index fa1c02448124b..cdffd79d8ca12 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -716,9 +716,9 @@ static int print_session_status_info(sd_bus *bus, const char *path) { /* We don't use the table to show the header, in order to make the width of the column stable. */ printf("%s%s - %s (" UID_FMT ")%s\n", ansi_highlight(), i.id, i.name, i.uid, ansi_normal()); - r = table_print(table); + r = table_print_or_warn(table); if (r < 0) - return table_log_print_error(r); + return r; if (i.scope) { show_unit_cgroup(bus, i.scope, i.leader, /* prefix= */ strrepa(" ", STRLEN("Display: "))); @@ -821,9 +821,9 @@ static int print_user_status_info(sd_bus *bus, const char *path) { printf("%s%s (" UID_FMT ")%s\n", ansi_highlight(), i.name, i.uid, ansi_normal()); - r = table_print(table); + r = table_print_or_warn(table); if (r < 0) - return table_log_print_error(r); + return r; if (i.slice) { show_unit_cgroup(bus, i.slice, /* leader= */ 0, /* prefix= */ strrepa(" ", STRLEN("Sessions: "))); @@ -896,9 +896,9 @@ static int print_seat_status_info(sd_bus *bus, const char *path) { printf("%s%s%s\n", ansi_highlight(), i.id, ansi_normal()); - r = table_print(table); + r = table_print_or_warn(table); if (r < 0) - return table_log_print_error(r); + return r; if (arg_transport == BUS_TRANSPORT_LOCAL) { unsigned c = MAX(LESS_BY(columns(), 21U), 10U); diff --git a/src/network/networkctl-address-label.c b/src/network/networkctl-address-label.c index 04b6d4d236614..c1e2cc0920278 100644 --- a/src/network/networkctl-address-label.c +++ b/src/network/networkctl-address-label.c @@ -82,11 +82,7 @@ static int dump_address_labels(sd_netlink *rtnl) { return table_log_add_error(r); } - r = table_print(table); - if (r < 0) - return table_log_print_error(r); - - return 0; + return table_print_or_warn(table); } int verb_list_address_labels(int argc, char *argv[], uintptr_t _data, void *userdata) { diff --git a/src/network/networkctl-list.c b/src/network/networkctl-list.c index c30be4a1dee91..6f3379e788d80 100644 --- a/src/network/networkctl-list.c +++ b/src/network/networkctl-list.c @@ -79,9 +79,9 @@ int verb_list_links(int argc, char *argv[], uintptr_t _data, void *userdata) { return table_log_add_error(r); } - r = table_print(table); + r = table_print_or_warn(table); if (r < 0) - return table_log_print_error(r); + return r; if (arg_legend) printf("\n%i links listed.\n", c); diff --git a/src/network/networkctl-lldp.c b/src/network/networkctl-lldp.c index c91d8fea00715..009f70e4d7fc0 100644 --- a/src/network/networkctl-lldp.c +++ b/src/network/networkctl-lldp.c @@ -303,9 +303,9 @@ int verb_link_lldp_status(int argc, char *argv[], uintptr_t _data, void *userdat } } - r = table_print(table); + r = table_print_or_warn(table); if (r < 0) - return table_log_print_error(r); + return r; if (arg_legend) { lldp_capabilities_legend(all); diff --git a/src/network/networkctl-status-link.c b/src/network/networkctl-status-link.c index 9cbf3efb33210..15c9c46336b3a 100644 --- a/src/network/networkctl-status-link.c +++ b/src/network/networkctl-status-link.c @@ -900,9 +900,9 @@ static int link_status_one( on_color_operational, glyph(GLYPH_BLACK_CIRCLE), off_color_operational, info->ifindex, info->name); - r = table_print(table); + r = table_print_or_warn(table); if (r < 0) - return table_log_print_error(r); + return r; return show_logs(info->ifindex, info->name); } diff --git a/src/network/networkctl-status-system.c b/src/network/networkctl-status-system.c index ce403d60624e3..a03004b882aad 100644 --- a/src/network/networkctl-status-system.c +++ b/src/network/networkctl-status-system.c @@ -126,9 +126,9 @@ int system_status(sd_netlink *rtnl, sd_hwdb *hwdb) { on_color_operational, glyph(GLYPH_BLACK_CIRCLE), off_color_operational, strna(netifs_joined)); - r = table_print(table); + r = table_print_or_warn(table); if (r < 0) - return table_log_print_error(r); + return r; return show_logs(0, NULL); } diff --git a/src/notify/notify.c b/src/notify/notify.c index e425a125ac3ae..2ac0129bae105 100644 --- a/src/notify/notify.c +++ b/src/notify/notify.c @@ -77,7 +77,9 @@ static int help(void) { ansi_highlight(), ansi_normal()); - table_print(options); + r = table_print_or_warn(options); + if (r < 0) + return r; printf("\nSee the %s for details.\n", link); return 0; diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index f5ce3db9c41e3..7c2cbf3e52b3a 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -1075,9 +1075,9 @@ static int verb_list_images(int argc, char *argv[], uintptr_t _data, void *userd table_set_header(table, arg_legend); - r = table_print(table); + r = table_print_or_warn(table); if (r < 0) - return table_log_print_error(r); + return r; } if (arg_legend) { diff --git a/src/report/report-basic-server.c b/src/report/report-basic-server.c index dc482a37ff0ba..ea82dbd42fe26 100644 --- a/src/report/report-basic-server.c +++ b/src/report/report-basic-server.c @@ -47,9 +47,8 @@ static int help(void) { ansi_normal(), ansi_underline(), ansi_normal()); - table_print(options); - return 0; + return table_print_or_warn(options); } static int parse_argv(int argc, char *argv[]) { diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index fe2b676c7c0dc..8cc9e73f93cd5 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -1321,11 +1321,7 @@ static int verb_show_statistics(int argc, char *argv[], uintptr_t _data, void *u if (r < 0) return table_log_add_error(r); - r = table_print(table); - if (r < 0) - return table_log_print_error(r); - - return 0; + return table_print_or_warn(table); } static int verb_reset_statistics(int argc, char *argv[], uintptr_t _data, void *userdata) { @@ -1890,9 +1886,9 @@ static int print_configuration(DNSConfiguration *configuration, StatusMode mode, return table_log_add_error(r); } - r = table_print(table); + r = table_print_or_warn(table); if (r < 0) - return table_log_print_error(r); + return r; if (empty_line) *empty_line = true; @@ -3097,11 +3093,7 @@ static int dump_server_state(sd_json_variant *server) { if (r < 0) return table_log_add_error(r); - r = table_print(table); - if (r < 0) - return table_log_print_error(r); - - return 0; + return table_print_or_warn(table); } static int verb_show_server_state(int argc, char *argv[], uintptr_t _data, void *userdata) { diff --git a/src/shared/format-table.c b/src/shared/format-table.c index 718d5c09b860a..8c8bcb1bdcbfe 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -2643,6 +2643,15 @@ int table_print_full(Table *t, FILE *f, bool flush) { return fflush_and_check(f); } +int table_print_or_warn(Table *t) { + int r; + + r = table_print(t); + if (r < 0) + return table_log_print_error(r); + return 0; +} + int table_format(Table *t, char **ret) { _cleanup_(memstream_done) MemStream m = {}; FILE *f; diff --git a/src/shared/format-table.h b/src/shared/format-table.h index ee4007394ef0f..0f52f80293d2e 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -151,6 +151,7 @@ int table_print_full(Table *t, FILE *f, bool flush); static inline int table_print(Table *t) { return table_print_full(t, /* f= */ NULL, /* flush= */ false); } +int table_print_or_warn(Table *t); int table_format(Table *t, char **ret); diff --git a/src/shared/libfido2-util.c b/src/shared/libfido2-util.c index 7e50d0dd2df57..6224ad4b1d2c6 100644 --- a/src/shared/libfido2-util.c +++ b/src/shared/libfido2-util.c @@ -1241,11 +1241,9 @@ int fido2_list_devices(void) { } } - r = table_print(t); - if (r < 0) { - log_error_errno(r, "Failed to show device table: %m"); + r = table_print_or_warn(t); + if (r < 0) goto finish; - } if (!table_isempty(t)) printf("\n" diff --git a/src/shared/parse-argument.c b/src/shared/parse-argument.c index 6ac42c4d88407..e85c78a47694d 100644 --- a/src/shared/parse-argument.c +++ b/src/shared/parse-argument.c @@ -128,11 +128,7 @@ int parse_signal_argument(const char *s, int *ret) { return table_log_add_error(r); } - r = table_print(table); - if (r < 0) - return table_log_print_error(r); - - return 0; + return table_print_or_warn(table); } r = signal_from_string(s); diff --git a/src/shared/pkcs11-util.c b/src/shared/pkcs11-util.c index 2d8ce29b5803e..3144d0b3da90c 100644 --- a/src/shared/pkcs11-util.c +++ b/src/shared/pkcs11-util.c @@ -1816,11 +1816,7 @@ int pkcs11_list_tokens(void) { return 0; } - r = table_print(t); - if (r < 0) - return log_error_errno(r, "Failed to show device table: %m"); - - return 0; + return table_print_or_warn(t); #else return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "PKCS#11 tokens not supported on this build."); diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index a1f05d6397c5e..78ff47afe8744 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -6584,11 +6584,7 @@ int tpm2_list_devices(bool legend, bool quiet) { return 0; } - r = table_print(t); - if (r < 0) - return log_error_errno(r, "Failed to show device table: %m"); - - return 0; + return table_print_or_warn(t); #else return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 not supported on this build."); diff --git a/src/systemctl/systemctl-list-jobs.c b/src/systemctl/systemctl-list-jobs.c index bf3f5ed8662aa..e5ad25234f628 100644 --- a/src/systemctl/systemctl-list-jobs.c +++ b/src/systemctl/systemctl-list-jobs.c @@ -115,9 +115,9 @@ static int output_jobs_list(sd_bus *bus, const struct job_info* jobs, unsigned n output_waiting_jobs(bus, table, j->id, "GetJobBefore", "\twaiting for job"); } - r = table_print(table); + r = table_print_or_warn(table); if (r < 0) - return log_error_errno(r, "Failed to print the table: %m"); + return r; if (arg_legend != 0) { on = ansi_highlight(); diff --git a/src/timedate/timedatectl.c b/src/timedate/timedatectl.c index 93a5dd6da359b..d02cb80bb1dab 100644 --- a/src/timedate/timedatectl.c +++ b/src/timedate/timedatectl.c @@ -163,9 +163,9 @@ static int print_status_info(const StatusInfo *i) { if (r < 0) return table_log_add_error(r); - r = table_print(table); + r = table_print_or_warn(table); if (r < 0) - return table_log_print_error(r); + return r; if (i->rtc_local) { fflush(stdout); @@ -443,20 +443,12 @@ static int print_ntp_status_info(NTPStatusInfo *i) { if (r < 0) return table_log_add_error(r); - r = table_print(table); - if (r < 0) - return table_log_print_error(r); - - return 0; + return table_print_or_warn(table); } if (i->dest < i->origin || i->trans < i->recv || i->dest - i->origin < i->trans - i->recv) { log_error("Invalid NTP response"); - r = table_print(table); - if (r < 0) - return table_log_print_error(r); - - return 0; + return table_print_or_warn(table); } delay = (i->dest - i->origin) - (i->trans - i->recv); @@ -536,11 +528,7 @@ static int print_ntp_status_info(NTPStatusInfo *i) { return table_log_add_error(r); } - r = table_print(table); - if (r < 0) - return table_log_print_error(r); - - return 0; + return table_print_or_warn(table); } static int map_server_address(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { diff --git a/src/update-done/update-done.c b/src/update-done/update-done.c index f076f263a6880..bc678b8f41988 100644 --- a/src/update-done/update-done.c +++ b/src/update-done/update-done.c @@ -82,7 +82,9 @@ static int help(void) { ansi_normal(), ansi_underline(), ansi_normal()); - table_print(options); + r = table_print_or_warn(options); + if (r < 0) + return r; printf("\nSee the %s for details.\n", link); return 0; diff --git a/src/validatefs/validatefs.c b/src/validatefs/validatefs.c index 0608a1489520b..1106eaf1fe991 100644 --- a/src/validatefs/validatefs.c +++ b/src/validatefs/validatefs.c @@ -50,7 +50,9 @@ static int help(void) { program_invocation_short_name, ansi_highlight(), ansi_normal()); - table_print(options); + r = table_print_or_warn(options); + if (r < 0) + return r; printf("\nSee the %s for details.\n", link); return 0; diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index 2d610db4e5500..4124d0d570726 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -416,6 +416,8 @@ static int verb_info(int argc, char *argv[], uintptr_t _data, void *userdata) { if (streq_ptr(argv[0], "list-interfaces")) { STRV_FOREACH(i, data.interfaces) puts(*i); + + return 0; } else { _cleanup_(table_unrefp) Table *t = NULL; @@ -439,20 +441,14 @@ static int verb_info(int argc, char *argv[], uintptr_t _data, void *userdata) { if (r < 0) return table_log_add_error(r); - r = table_print(t); - if (r < 0) - return table_log_print_error(r); + return table_print_or_warn(t); } } else { - sd_json_variant *v; - - v = streq_ptr(argv[0], "list-interfaces") ? + sd_json_variant *v = streq_ptr(argv[0], "list-interfaces") ? sd_json_variant_by_key(reply, "interfaces") : reply; - sd_json_variant_dump(v, arg_json_format_flags, stdout, NULL); + return sd_json_variant_dump(v, arg_json_format_flags, stdout, NULL); } - - return 0; } static size_t break_columns(void) { diff --git a/src/vpick/vpick-tool.c b/src/vpick/vpick-tool.c index 1f3277da45e6d..57df857c6a42d 100644 --- a/src/vpick/vpick-tool.c +++ b/src/vpick/vpick-tool.c @@ -337,9 +337,9 @@ static int run(int argc, char *argv[]) { return table_log_add_error(r); } - r = table_print(t); + r = table_print_or_warn(t); if (r < 0) - return table_log_print_error(r); + return r; break; } From 4419840ad1d86cf388d5fd8e9b2710faff02c284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 3 Apr 2026 11:56:04 +0200 Subject: [PATCH 0730/1296] analyze: consistently print error if table formatting fails We don't want to return an error without printing something. So for things which don't matter, explicitly suppress the error with (void). In other cases, add the standard message. --- src/analyze/analyze-blame.c | 16 +++++----------- src/analyze/analyze-calendar.c | 8 ++------ src/analyze/analyze-timespan.c | 8 ++------ src/analyze/analyze-timestamp.c | 10 +++------- 4 files changed, 12 insertions(+), 30 deletions(-) diff --git a/src/analyze/analyze-blame.c b/src/analyze/analyze-blame.c index 24b29a26a1ff1..d400380ade903 100644 --- a/src/analyze/analyze-blame.c +++ b/src/analyze/analyze-blame.c @@ -29,26 +29,20 @@ int verb_blame(int argc, char *argv[], uintptr_t _data, void *userdata) { return log_oom(); assert_se(cell = table_get_cell(table, 0, 0)); - r = table_set_ellipsize_percent(table, cell, 100); - if (r < 0) - return r; + (void) table_set_ellipsize_percent(table, cell, 100); - r = table_set_align_percent(table, cell, 100); - if (r < 0) - return r; + (void) table_set_align_percent(table, cell, 100); assert_se(cell = table_get_cell(table, 0, 1)); - r = table_set_ellipsize_percent(table, cell, 100); - if (r < 0) - return r; + (void) table_set_ellipsize_percent(table, cell, 100); r = table_set_sort(table, (size_t) 0); if (r < 0) - return r; + return table_log_sort_error(r); r = table_set_reverse(table, 0, true); if (r < 0) - return r; + return table_log_sort_error(r); for (UnitTimes *u = times; u->has_data; u++) { if (u->time <= 0) diff --git a/src/analyze/analyze-calendar.c b/src/analyze/analyze-calendar.c index c1427f25aabf2..a9fb1e897e67e 100644 --- a/src/analyze/analyze-calendar.c +++ b/src/analyze/analyze-calendar.c @@ -35,14 +35,10 @@ static int test_calendar_one(usec_t n, const char *p) { return log_oom(); assert_se(cell = table_get_cell(table, 0, 0)); - r = table_set_ellipsize_percent(table, cell, 100); - if (r < 0) - return r; + (void) table_set_ellipsize_percent(table, cell, 100); assert_se(cell = table_get_cell(table, 0, 1)); - r = table_set_ellipsize_percent(table, cell, 100); - if (r < 0) - return r; + (void) table_set_ellipsize_percent(table, cell, 100); if (!streq(t, p)) { r = table_add_many(table, diff --git a/src/analyze/analyze-timespan.c b/src/analyze/analyze-timespan.c index 31a201c5f6c5d..de78736e2ca09 100644 --- a/src/analyze/analyze-timespan.c +++ b/src/analyze/analyze-timespan.c @@ -28,14 +28,10 @@ int verb_timespan(int argc, char *argv[], uintptr_t _data, void *userdata) { return log_oom(); assert_se(cell = table_get_cell(table, 0, 0)); - r = table_set_ellipsize_percent(table, cell, 100); - if (r < 0) - return r; + (void) table_set_ellipsize_percent(table, cell, 100); assert_se(cell = table_get_cell(table, 0, 1)); - r = table_set_ellipsize_percent(table, cell, 100); - if (r < 0) - return r; + (void) table_set_ellipsize_percent(table, cell, 100); r = table_add_many(table, TABLE_FIELD, "Original", diff --git a/src/analyze/analyze-timestamp.c b/src/analyze/analyze-timestamp.c index 5d4fa2f6250f5..50cda12d71865 100644 --- a/src/analyze/analyze-timestamp.c +++ b/src/analyze/analyze-timestamp.c @@ -27,14 +27,10 @@ static int test_timestamp_one(const char *p) { return log_oom(); assert_se(cell = table_get_cell(table, 0, 0)); - r = table_set_ellipsize_percent(table, cell, 100); - if (r < 0) - return r; + (void) table_set_ellipsize_percent(table, cell, 100); assert_se(cell = table_get_cell(table, 0, 1)); - r = table_set_ellipsize_percent(table, cell, 100); - if (r < 0) - return r; + (void) table_set_ellipsize_percent(table, cell, 100); r = table_add_many(table, TABLE_FIELD, "Original form", @@ -65,7 +61,7 @@ static int test_timestamp_one(const char *p) { usec / USEC_PER_SEC, usec % USEC_PER_SEC); if (r < 0) - return r; + return table_log_add_error(r); r = table_add_many(table, TABLE_FIELD, "From now", From fec559d034ed32929771c80922fd2c2249edba15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 2 Apr 2026 22:20:48 +0200 Subject: [PATCH 0731/1296] meson: detect and use __attribute__((no_reorder)) In some builds (package builds, so with optimization and lto, but I haven't been able to pin down the exact combination on options that matters), we end up with items in the verbs array reordered. The order matters (because of groups, but also because we have some specific order for display), so this reordering is something that we don't want. From what I was able to read, the compiler + linker generally keep the order within a single translation unit, but this is more of a convention and implementation choice than a guarantee. Add this attribute [1]. It seems to have the desired effect in CI. [1] https://gcc.gnu.org/onlinedocs/gcc-15.2.0/gcc/Common-Function-Attributes.html#index-no_005freorder-function-attribute --- meson.build | 9 +++++++++ src/fundamental/macro-fundamental.h | 6 ++++++ src/shared/options.h | 1 + src/shared/verbs.h | 1 + 4 files changed, 17 insertions(+) diff --git a/meson.build b/meson.build index c9e96b2591495..0f8f0b5375ac6 100644 --- a/meson.build +++ b/meson.build @@ -549,6 +549,15 @@ foreach attr : possible_c_attributes conf.set10('HAVE_ATTRIBUTE_' + attr.to_upper(), have) endforeach +# TODO: drop this manual check when meson learns about this attribute +possible_c_attributes += ['no_reorder'] + +have = cc.compiles( + '__attribute__((__no_reorder__)) int x;', + args : '-Werror=attributes', + name : '__attribute__((__no_reorder__))') +conf.set10('HAVE_ATTRIBUTE_NO_REORDER', have) + ##################################################################### # compilation result tests diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro-fundamental.h index 5690859cf4b42..f595be2cc5dd2 100644 --- a/src/fundamental/macro-fundamental.h +++ b/src/fundamental/macro-fundamental.h @@ -116,6 +116,12 @@ # define _retain_ #endif +#if HAVE_ATTRIBUTE_NO_REORDER +# define _no_reorder_ __attribute__((__no_reorder__)) +#else +# define _no_reorder_ +#endif + #if __GNUC__ >= 15 # define _nonnull_if_nonzero_(p, n) __attribute__((nonnull_if_nonzero(p, n))) #else diff --git a/src/shared/options.h b/src/shared/options.h index afa17d9e3006f..c1ffa595ce828 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -25,6 +25,7 @@ typedef struct Option { _alignptr_ \ _used_ \ _retain_ \ + _no_reorder_ \ _variable_no_sanitize_address_ \ static const Option CONCATENATE(option, counter) = { \ .id = 0x100 + counter, \ diff --git a/src/shared/verbs.h b/src/shared/verbs.h index cd18359cd75ac..51a9e3a5253f2 100644 --- a/src/shared/verbs.h +++ b/src/shared/verbs.h @@ -26,6 +26,7 @@ typedef struct { _alignptr_ \ _used_ \ _retain_ \ + _no_reorder_ \ _variable_no_sanitize_address_ \ static const Verb CONCATENATE(verb_data_, __COUNTER__) = { \ .verb = v, \ From 190385b55dea5505bfd7fcd3dff3f48b38ee34ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 24 Mar 2026 10:35:30 +0100 Subject: [PATCH 0732/1296] vpick: use the new option parser -h/--help and --version are moved from a standalone section into the Output group, because they look misplaced without a header and having a new "Options" group with the verb-like entries also wasn't appealing. Output is changed a bit to avoid repeating "rather than path": - -p --print=filename Print selected filename rather than path - -p --print=version Print selected version rather than path - -p --print=type Print selected inode type rather than path - -p --print=arch Print selected architecture rather than path - -p --print=tries Print selected tries left/tries done rather than path - -p --print=all Print all of the above - --resolve=yes Canonicalize the result path + -h --help Show this help + --version Show package version + -p --print=WHAT Print selected WHAT rather than path + --print=filename ... print selected filename + --print=version ... print selected version + --print=type ... print selected inode + --print=arch ... print selected architecture + --print=tries ... print selected tries left/tries done + --print=all ... print all of the above + --resolve=BOOL Canonicalize the result path Co-developed-by: Claude --- src/vpick/vpick-tool.c | 182 ++++++++++++++++++++--------------------- 1 file changed, 91 insertions(+), 91 deletions(-) diff --git a/src/vpick/vpick-tool.c b/src/vpick/vpick-tool.c index 57df857c6a42d..595ecae68869d 100644 --- a/src/vpick/vpick-tool.c +++ b/src/vpick/vpick-tool.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include "alloc-util.h" #include "architecture.h" @@ -9,12 +8,14 @@ #include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-util.h" #include "path-util.h" #include "pretty-print.h" #include "stat-util.h" #include "string-table.h" #include "string-util.h" +#include "strv.h" #include "vpick.h" typedef enum { @@ -55,167 +56,165 @@ DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(print, Print); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *lookup_keys = NULL, *output = NULL; int r; r = terminal_urlify_man("systemd-vpick", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] PATH...\n" - "\n%5$sPick entry from versioned directory.%6$s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\n%3$sLookup Keys:%4$s\n" - " -B --basename=BASENAME\n" - " Look for specified basename\n" - " -V VERSION Look for specified version\n" - " -A ARCH Look for specified architecture\n" - " -S --suffix=SUFFIX Look for specified suffix\n" - " -t --type=TYPE Look for specified inode type\n" - "\n%3$sOutput:%4$s\n" - " -p --print=filename Print selected filename rather than path\n" - " -p --print=version Print selected version rather than path\n" - " -p --print=type Print selected inode type rather than path\n" - " -p --print=arch Print selected architecture rather than path\n" - " -p --print=tries Print selected tries left/tries done rather than path\n" - " -p --print=all Print all of the above\n" - " --resolve=yes Canonicalize the result path\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), ansi_normal(), - ansi_highlight(), ansi_normal()); + r = option_parser_get_help_table(&lookup_keys); + if (r < 0) + return r; - return 0; -} + r = option_parser_get_help_table_group("Output", &output); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, lookup_keys, output); -static int parse_argv(int argc, char *argv[]) { + printf("%s [OPTIONS...] PATH...\n" + "\n%sPick entry from versioned directory.%s\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal()); - enum { - ARG_VERSION = 0x100, - ARG_RESOLVE, - }; + printf("\n%sLookup Keys:%s\n", ansi_underline(), ansi_normal()); + r = table_print_or_warn(lookup_keys); + if (r < 0) + return r; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "basename", required_argument, NULL, 'B' }, - { "suffix", required_argument, NULL, 'S' }, - { "type", required_argument, NULL, 't' }, - { "print", required_argument, NULL, 'p' }, - { "resolve", required_argument, NULL, ARG_RESOLVE }, - {} - }; + printf("\n%sOutput:%s\n", ansi_underline(), ansi_normal()); + r = table_print_or_warn(output); + if (r < 0) + return r; - int c, r; + printf("\nSee the %s for details.\n", link); + return 0; +} + +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hB:V:A:S:t:p:", options, NULL)) >= 0) { + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - return help(); - - case ARG_VERSION: - return version(); - - case 'B': - if (!filename_part_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid basename string: %s", optarg); + OPTION('B', "basename", "BASENAME", "Look for specified basename"): + if (!filename_part_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid basename string: %s", arg); - r = free_and_strdup_warn(&arg_filter_basename, optarg); + r = free_and_strdup_warn(&arg_filter_basename, arg); if (r < 0) return r; break; - case 'V': - if (!version_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid version string: %s", optarg); + OPTION_SHORT('V', "VERSION", "Look for specified version"): + if (!version_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid version string: %s", arg); - r = free_and_strdup_warn(&arg_filter_version, optarg); + r = free_and_strdup_warn(&arg_filter_version, arg); if (r < 0) return r; break; - case 'A': - if (streq(optarg, "native")) + OPTION_SHORT('A', "ARCH", "Look for specified architecture"): + if (streq(arg, "native")) arg_filter_architecture = native_architecture(); - else if (streq(optarg, "secondary")) { + else if (streq(arg, "secondary")) { #ifdef ARCHITECTURE_SECONDARY arg_filter_architecture = ARCHITECTURE_SECONDARY; #else return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Local architecture has no secondary architecture."); #endif - } else if (streq(optarg, "uname")) + } else if (streq(arg, "uname")) arg_filter_architecture = uname_architecture(); - else if (streq(optarg, "auto")) + else if (streq(arg, "auto")) arg_filter_architecture = _ARCHITECTURE_INVALID; else { - arg_filter_architecture = architecture_from_string(optarg); + arg_filter_architecture = architecture_from_string(arg); if (arg_filter_architecture < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown architecture: %s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown architecture: %s", arg); } break; - case 'S': - if (!filename_part_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid suffix string: %s", optarg); + OPTION('S', "suffix", "SUFFIX", "Look for specified suffix"): + if (!filename_part_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid suffix string: %s", arg); - r = free_and_strdup_warn(&arg_filter_suffix, optarg); + r = free_and_strdup_warn(&arg_filter_suffix, arg); if (r < 0) return r; break; - case 't': - if (isempty(optarg)) + OPTION('t', "type", "TYPE", "Look for specified inode type"): + if (isempty(arg)) arg_filter_type_mask = 0; else { mode_t m; - m = inode_type_from_string(optarg); + m = inode_type_from_string(arg); if (m == MODE_INVALID) - return log_error_errno(m, "Unknown inode type: %s", optarg); + return log_error_errno(m, "Unknown inode type: %s", arg); arg_filter_type_mask |= UINT32_C(1) << IFTODT(m); } break; - case 'p': - if (streq(optarg, "arch")) /* accept abbreviation too */ + OPTION_GROUP("Output"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION('p', "print", "WHAT", + "Print selected WHAT rather than path"): {} + OPTION_LONG_FLAGS(OPTION_HELP_ENTRY, "print", "filename", + "... print selected filename"): {} + OPTION_LONG_FLAGS(OPTION_HELP_ENTRY, "print", "version", + "... print selected version"): {} + OPTION_LONG_FLAGS(OPTION_HELP_ENTRY, "print", "type", + "... print selected inode type"): {} + OPTION_LONG_FLAGS(OPTION_HELP_ENTRY, "print", "arch", + "... print selected architecture"): {} + OPTION_LONG_FLAGS(OPTION_HELP_ENTRY, "print", "tries", + "... print selected tries left/tries done"): {} + OPTION_LONG_FLAGS(OPTION_HELP_ENTRY, "print", "all", + "... print all of the above"): + + if (streq(arg, "arch")) /* accept abbreviation too */ arg_print = PRINT_ARCHITECTURE; else - arg_print = print_from_string(optarg); + arg_print = print_from_string(arg); if (arg_print < 0) - return log_error_errno(arg_print, "Unknown --print= argument: %s", optarg); + return log_error_errno(arg_print, "Unknown --print= argument: %s", arg); break; - case ARG_RESOLVE: - r = parse_boolean(optarg); + OPTION_LONG("resolve", "BOOL", "Canonicalize the result path"): + r = parse_boolean(arg); if (r < 0) return log_error_errno(r, "Failed to parse --resolve= value: %m"); SET_FLAG(arg_flags, PICK_RESOLVE, r); break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } if (arg_print < 0) arg_print = PRINT_PATH; + *ret_args = option_parser_get_args(&state); return 1; } @@ -224,18 +223,19 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - if (optind >= argc) + if (strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path to resolve must be specified."); - for (int i = optind; i < argc; i++) { + STRV_FOREACH(i, args) { _cleanup_free_ char *p = NULL; - r = path_make_absolute_cwd(argv[i], &p); + r = path_make_absolute_cwd(*i, &p); if (r < 0) - return log_error_errno(r, "Failed to make path '%s' absolute: %m", argv[i]); + return log_error_errno(r, "Failed to make path '%s' absolute: %m", *i); _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; r = path_pick(/* toplevel_path= */ NULL, From 94db1a961262fa577fd7b1f4c96eeaced748b9d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 24 Mar 2026 10:06:14 +0100 Subject: [PATCH 0733/1296] timedatectl: use the new option and verb wrappers --help is identical, except for alignment of second column and changed strings in the standarized options. Co-developed-by: Claude --- src/timedate/timedatectl.c | 242 ++++++++++++++++--------------------- 1 file changed, 105 insertions(+), 137 deletions(-) diff --git a/src/timedate/timedatectl.c b/src/timedate/timedatectl.c index d02cb80bb1dab..75c142e84ec58 100644 --- a/src/timedate/timedatectl.c +++ b/src/timedate/timedatectl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -20,6 +19,7 @@ #include "in-addr-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -180,7 +180,8 @@ static int print_status_info(const StatusInfo *i) { return 0; } -static int verb_show_status(int argc, char *argv[], uintptr_t _data, void *userdata) { +VERB(verb_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT, "Show current time settings"); +static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { StatusInfo info = {}; static const struct bus_properties_map map[] = { { "Timezone", "s", NULL, offsetof(StatusInfo, timezone) }, @@ -212,7 +213,8 @@ static int verb_show_status(int argc, char *argv[], uintptr_t _data, void *userd return print_status_info(&info); } -static int verb_show_properties(int argc, char *argv[], uintptr_t _data, void *userdata) { +VERB_NOARG(verb_show, "show", "Show properties of systemd-timedated"); +static int verb_show(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -229,6 +231,7 @@ static int verb_show_properties(int argc, char *argv[], uintptr_t _data, void *u return 0; } +VERB(verb_set_time, "set-time", "TIME", 2, 2, 0, "Set system time"); static int verb_set_time(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = userdata; @@ -254,6 +257,7 @@ static int verb_set_time(int argc, char *argv[], uintptr_t _data, void *userdata return 0; } +VERB(verb_set_timezone, "set-timezone", "ZONE", 2, 2, 0, "Set system time zone"); static int verb_set_timezone(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = userdata; @@ -268,6 +272,30 @@ static int verb_set_timezone(int argc, char *argv[], uintptr_t _data, void *user return 0; } +VERB_NOARG(verb_list_timezones, "list-timezones", "Show known time zones"); +static int verb_list_timezones(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + _cleanup_strv_free_ char **zones = NULL; + + r = bus_call_method(bus, bus_timedate, "ListTimezones", &error, &reply, NULL); + if (r < 0) + return log_error_errno(r, "Failed to request list of time zones: %s", + bus_error_message(&error, r)); + + r = sd_bus_message_read_strv(reply, &zones); + if (r < 0) + return bus_log_parse_error(r); + + pager_open(arg_pager_flags); + strv_print(zones); + + return 0; +} + +VERB(verb_set_local_rtc, "set-local-rtc", "BOOL", 2, 2, 0, "Control whether RTC is in local time"); static int verb_set_local_rtc(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = userdata; @@ -299,6 +327,7 @@ static int verb_set_local_rtc(int argc, char *argv[], uintptr_t _data, void *use return 0; } +VERB(verb_set_ntp, "set-ntp", "BOOL", 2, 2, 0, "Enable or disable network time synchronization"); static int verb_set_ntp(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -327,28 +356,6 @@ static int verb_set_ntp(int argc, char *argv[], uintptr_t _data, void *userdata) return 0; } -static int verb_list_timezones(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - int r; - _cleanup_strv_free_ char **zones = NULL; - - r = bus_call_method(bus, bus_timedate, "ListTimezones", &error, &reply, NULL); - if (r < 0) - return log_error_errno(r, "Failed to request list of time zones: %s", - bus_error_message(&error, r)); - - r = sd_bus_message_read_strv(reply, &zones); - if (r < 0) - return bus_log_parse_error(r); - - pager_open(arg_pager_flags); - strv_print(zones); - - return 0; -} - typedef struct NTPStatusInfo { const char *server_name; char *server_address; @@ -676,7 +683,10 @@ static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error return show_timesync_status_once(sd_bus_message_get_bus(m)); } -static int verb_show_timesync_status(int argc, char *argv[], uintptr_t _data, void *userdata) { +VERB_GROUP("systemd-timesyncd Commands"); + +VERB_NOARG(verb_timesync_status, "timesync-status", "Show status of systemd-timesyncd"); +static int verb_timesync_status(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_event_unrefp) sd_event *event = NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; @@ -780,6 +790,7 @@ static int print_timesync_property(const char *name, const char *expected_value, return 0; } +VERB_NOARG(verb_show_timesync, "show-timesync", "Show properties of systemd-timesyncd"); static int verb_show_timesync(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); @@ -829,6 +840,8 @@ static int parse_ifindex_bus(sd_bus *bus, const char *str) { return i; } +VERB(verb_ntp_servers, "ntp-servers", "INTERFACE SERVER…", 3, VERB_ANY, 0, + "Set the interface specific NTP servers"); static int verb_ntp_servers(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; @@ -860,6 +873,7 @@ static int verb_ntp_servers(int argc, char *argv[], uintptr_t _data, void *userd return 0; } +VERB(verb_revert, "revert", "INTERFACE", 2, 2, 0, "Revert the interface specific NTP servers"); static int verb_revert(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); @@ -880,179 +894,133 @@ static int verb_revert(int argc, char *argv[], uintptr_t _data, void *userdata) static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL, *verbs2 = NULL, *options = NULL; int r; r = terminal_urlify_man("timedatectl", "1", &link); if (r < 0) return log_oom(); + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = verbs_get_help_table_group("systemd-timesyncd Commands", &verbs2); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, verbs2, options); + printf("%s [OPTIONS...] COMMAND ...\n" "\n%sQuery or change system time and date settings.%s\n" - "\nCommands:\n" - " status Show current time settings\n" - " show Show properties of systemd-timedated\n" - " set-time TIME Set system time\n" - " set-timezone ZONE Set system time zone\n" - " list-timezones Show known time zones\n" - " set-local-rtc BOOL Control whether RTC is in local time\n" - " set-ntp BOOL Enable or disable network time synchronization\n" - "\nsystemd-timesyncd Commands:\n" - " timesync-status Show status of systemd-timesyncd\n" - " show-timesync Show properties of systemd-timesyncd\n" - " ntp-servers INTERFACE SERVER…\n" - " Set the interface specific NTP servers\n" - " revert INTERFACE Revert the interface specific NTP servers\n" - "\nOptions:\n" - " -h --help Show this help message\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --no-ask-password Do not prompt for password\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --adjust-system-clock Adjust system clock when changing local RTC mode\n" - " --monitor Monitor status of systemd-timesyncd\n" - " -p --property=NAME Show only properties by this name\n" - " -a --all Show all properties, including empty ones\n" - " --value When showing properties, only print the value\n" - " -P NAME Equivalent to --value --property=NAME\n" - "\nSee the %s for details.\n", + "\nCommands:\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + r = table_print_or_warn(verbs); + if (r < 0) + return r; - return 0; -} + printf("\nsystemd-timesyncd Commands:\n"); + r = table_print_or_warn(verbs2); + if (r < 0) + return r; -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} + printf("\nOptions:\n"); + r = table_print_or_warn(options); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_ADJUST_SYSTEM_CLOCK, - ARG_NO_ASK_PASSWORD, - ARG_MONITOR, - ARG_VALUE, - }; + printf("\nSee the %s for details.\n", link); + return 0; +} - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK }, - { "monitor", no_argument, NULL, ARG_MONITOR }, - { "property", required_argument, NULL, 'p' }, - { "value", no_argument, NULL, ARG_VALUE }, - { "all", no_argument, NULL, 'a' }, - {} - }; +VERB_COMMON_HELP_HIDDEN(help); - int c, r; +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hH:M:p:P:a", options, NULL)) >= 0) - switch (c) { + OptionParser state = { argc, argv }; + const Option *current; + const char *arg; - case 'h': + FOREACH_OPTION_FULL(&state, c, ¤t, &arg, /* on_error= */ return c) + switch (c) { + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); - if (r < 0) - return r; + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; break; - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; + OPTION_COMMON_HOST: + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = arg; break; - case ARG_ADJUST_SYSTEM_CLOCK: - arg_adjust_system_clock = true; + OPTION_COMMON_MACHINE: + r = parse_machine_argument(arg, &arg_host, &arg_transport); + if (r < 0) + return r; break; - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; + OPTION_LONG("adjust-system-clock", NULL, "Adjust system clock when changing local RTC mode"): + arg_adjust_system_clock = true; break; - case ARG_MONITOR: + OPTION_LONG("monitor", NULL, "Monitor status of systemd-timesyncd"): arg_monitor = true; break; - case 'p': - case 'P': - r = strv_extend(&arg_property, optarg); + OPTION('p', "property", "NAME", "Show only properties by this name"): {} + OPTION_SHORT('P', "NAME", "Equivalent to --value --property=NAME"): + r = strv_extend(&arg_property, arg); if (r < 0) return log_oom(); /* If the user asked for a particular property, show it to them, even if empty. */ SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true); - if (c == 'p') + if (current->short_code == 'p') break; _fallthrough_; - case ARG_VALUE: + OPTION_LONG("value", NULL, "When showing properties, only print the value"): SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); break; - case 'a': + OPTION('a', "all", NULL, "Show all properties, including empty ones"): SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true); break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state); return 1; } -static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) { - static const Verb verbs[] = { - { "status", VERB_ANY, 1, VERB_DEFAULT, verb_show_status }, - { "show", VERB_ANY, 1, 0, verb_show_properties }, - { "set-time", 2, 2, 0, verb_set_time }, - { "set-timezone", 2, 2, 0, verb_set_timezone }, - { "list-timezones", VERB_ANY, 1, 0, verb_list_timezones }, - { "set-local-rtc", 2, 2, 0, verb_set_local_rtc }, - { "set-ntp", 2, 2, 0, verb_set_ntp }, - { "timesync-status", VERB_ANY, 1, 0, verb_show_timesync_status }, - { "show-timesync", VERB_ANY, 1, 0, verb_show_timesync }, - { "ntp-servers", 3, VERB_ANY, 0, verb_ntp_servers }, - { "revert", 2, 2, 0, verb_revert }, - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, /* Not documented, but supported since it has been created. */ - {} - }; - - return dispatch_verb(argc, argv, verbs, bus); -} - static int run(int argc, char *argv[]) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + char **args = NULL; int r; setlocale(LC_ALL, ""); log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -1062,7 +1030,7 @@ static int run(int argc, char *argv[]) { (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); - return timedatectl_main(bus, argc, argv); + return dispatch_verb_with_args(args, bus); } DEFINE_MAIN_FUNCTION(run); From ff2fa93ac0f158d03650a34189218981fbec42fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 26 Mar 2026 00:04:34 +0100 Subject: [PATCH 0734/1296] timedatectl: stop using _fallthrough_ gcc and newer clang seem to be fine with it, but clang 14, 16, 18 is unhappy: ../src/timedate/timedatectl.c:1006:25: error: fallthrough annotation does not directly precede switch label _fallthrough_; ^ _fallthrough_ doesn't seem to be used very often in option parsing, so let's remove the use for now. --- src/timedate/timedatectl.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/timedate/timedatectl.c b/src/timedate/timedatectl.c index 75c142e84ec58..7d6ca7450a6be 100644 --- a/src/timedate/timedatectl.c +++ b/src/timedate/timedatectl.c @@ -995,9 +995,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { /* If the user asked for a particular property, show it to them, even if empty. */ SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true); - if (current->short_code == 'p') - break; - _fallthrough_; + if (current->short_code == 'P') + SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); + break; OPTION_LONG("value", NULL, "When showing properties, only print the value"): SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); From d3d142e5fdeeb2a0f425358090be45c68bfd847d Mon Sep 17 00:00:00 2001 From: Clayton Craft Date: Thu, 2 Apr 2026 10:06:51 -0700 Subject: [PATCH 0735/1296] updatectl: fix unexpected polkit prompt on update When running `updatectl update` since f0b2ea63, Install() was called with the version resolved by Acquire() rather than the originally requested (empty) version, causing the stricter update-to-version polkit action to be used instead of the update polkit action. --- src/sysupdate/updatectl.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/sysupdate/updatectl.c b/src/sysupdate/updatectl.c index 86561cf1c734d..636a3f064b94f 100644 --- a/src/sysupdate/updatectl.c +++ b/src/sysupdate/updatectl.c @@ -75,6 +75,8 @@ typedef struct Operation { /* Only used for Acquire()/Install() operations: */ char *acquired_version; + /* The version the user requested, possibly empty */ + char *requested_version; } Operation; static Operation* operation_free(Operation *p) { @@ -90,6 +92,7 @@ static Operation* operation_free(Operation *p) { free(p->job_path); free(p->acquired_version); + free(p->requested_version); sd_event_source_disable_unref(p->job_interrupt_source); sd_bus_slot_unref(p->job_properties_slot); @@ -1068,7 +1071,7 @@ static int update_acquire_finished(sd_bus_message *m, void *userdata, sd_bus_err update_install_started, op, "st", - op->acquired_version, + op->requested_version, 0LU); if (r < 0) return log_bus_error(r, NULL, op->target_id, "call Install"); @@ -1246,6 +1249,11 @@ static int do_update(sd_bus *bus, char **targets) { 0LU); if (r < 0) return log_bus_error(r, NULL, targets[i], "call Acquire"); + + op->requested_version = strdup(versions[i]); + if (!op->requested_version) + return log_oom(); + TAKE_PTR(op); remaining++; From 6a9888c4c6ca1318cad3a30dc3b7628d305eadf6 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 27 Mar 2026 13:53:02 +0000 Subject: [PATCH 0736/1296] vmspawn: add --cxl= option and memory hotplug support Add --cxl=BOOL option to enable CXL (Compute Express Link) support in the virtual machine. CXL is a high-speed interconnect standard that allows CPUs to access memory attached to devices such as accelerators and memory expanders, enabling flexible memory pooling and expansion beyond what is physically installed on the motherboard. When enabled, adds cxl=on to the QEMU machine configuration. Only supported on x86_64 and aarch64 architectures. This is added for testing purposes and for feature parity with mkosi's CXL= setting. Extend --ram= to accept an optional maximum size for memory hotplug, using the syntax --ram=SIZE[:MAXSIZE] (e.g. --ram=2G:8G). When a maximum is specified, the maxmem key is added to the QEMU memory configuration section to enable memory hotplug up to the given limit. Co-developed-by: Claude Opus 4.6 --- man/systemd-vmspawn.xml | 20 ++++++- shell-completion/bash/systemd-vmspawn | 2 +- src/vmspawn/vmspawn-util.h | 6 ++ src/vmspawn/vmspawn.c | 81 ++++++++++++++++++++++++++- 4 files changed, 102 insertions(+), 7 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 9feb7407ca6ee..d65bd965a39de 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -167,10 +167,12 @@ - + - The amount of memory to start the virtual machine with. - Defaults to 2G. + The amount of memory to start the virtual machine with. Defaults to 2G. + If a maximum size is specified after a colon, memory hotplug is enabled with the given + upper limit. The number of hotplug slots can optionally be specified after a second colon + and defaults to 1. @@ -185,6 +187,18 @@ + + + + Controls whether to enable CXL (Compute Express Link) support in the virtual + machine. CXL is a high-speed interconnect standard that allows CPUs to access memory attached to + devices such as accelerators and memory expanders, enabling flexible memory pooling and expansion + beyond what is physically installed on the motherboard. Only supported on x86_64 and aarch64 + architectures. + + + + diff --git a/shell-completion/bash/systemd-vmspawn b/shell-completion/bash/systemd-vmspawn index b035a42a6550e..995aeb1271298 100644 --- a/shell-completion/bash/systemd-vmspawn +++ b/shell-completion/bash/systemd-vmspawn @@ -31,7 +31,7 @@ _systemd_vmspawn() { local -A OPTS=( [STANDALONE]='-h --help --version -q --quiet --no-pager -n --network-tap --network-user-mode --user --system -x --ephemeral' [PATH]='-D --directory -i --image --linux --initrd --extra-drive --forward-journal --efi-nvram-template' - [BOOL]='--kvm --vsock --tpm --discard-disk --register --pass-ssh-key' + [BOOL]='--kvm --cxl --vsock --tpm --discard-disk --register --pass-ssh-key' [SECURE_BOOT]='--secure-boot' [FIRMWARE]='--firmware' [FIRMWARE_FEATURES]='--firmware-features' diff --git a/src/vmspawn/vmspawn-util.h b/src/vmspawn/vmspawn-util.h index 9fec6641aa3d0..d9272b49000b2 100644 --- a/src/vmspawn/vmspawn-util.h +++ b/src/vmspawn/vmspawn-util.h @@ -33,6 +33,12 @@ # define ARCHITECTURE_SUPPORTS_HPET 0 #endif +#if defined(__x86_64__) || defined(__aarch64__) +# define ARCHITECTURE_SUPPORTS_CXL 1 +#else +# define ARCHITECTURE_SUPPORTS_CXL 0 +#endif + #if defined(__x86_64__) || defined(__i386__) # define QEMU_MACHINE_TYPE "q35" #elif defined(__arm__) || defined(__aarch64__) || defined(__riscv) || defined(__loongarch64) || defined(__m68k__) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 1b1e31bb5b103..92a5555f64bbc 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -130,7 +130,10 @@ static char *arg_slice = NULL; static char **arg_property = NULL; static char *arg_cpus = NULL; static uint64_t arg_ram = UINT64_C(2) * U64_GB; +static uint64_t arg_ram_max = 0; +static unsigned arg_ram_slots = 0; static int arg_kvm = -1; +static bool arg_cxl = false; static int arg_vsock = -1; static unsigned arg_vsock_cid = VMADDR_CID_ANY; static int arg_tpm = -1; @@ -229,8 +232,11 @@ static int help(void) { " scsi-cd; default: virtio-blk)\n" "\n%3$sHost Configuration:%4$s\n" " --cpus=CPUS Configure number of CPUs in guest\n" - " --ram=BYTES Configure guest's RAM size\n" + " --ram=BYTES[:MAXBYTES[:SLOTS]]\n" + " Configure guest's RAM size (and max/slots for\n" + " hotplug)\n" " --kvm=BOOL Enable use of KVM\n" + " --cxl=BOOL Enable use of CXL\n" " --vsock=BOOL Override autodetection of VSOCK support\n" " --vsock-cid=CID Specify the CID to use for the guest's VSOCK support\n" " --tpm=BOOL Enable use of a virtual TPM\n" @@ -326,6 +332,42 @@ static int parse_environment(void) { return 0; } +static int parse_ram(const char *s) { + _cleanup_free_ char *ram = NULL, *ram_max = NULL, *ram_slots = NULL; + int r; + + assert(s); + + const char *p = s; + r = extract_many_words(&p, ":", EXTRACT_DONT_COALESCE_SEPARATORS, &ram, &ram_max, &ram_slots); + if (r < 0) + return log_error_errno(r, "Failed to parse --ram=%s: %m", s); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse --ram=%s", s); + if (!isempty(p)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unexpected trailing data in --ram=%s", s); + + r = parse_size(ram, 1024, &arg_ram); + if (r < 0) + return log_error_errno(r, "Failed to parse --ram=%s: %m", s); + + if (!isempty(ram_max)) { + r = parse_size(ram_max, 1024, &arg_ram_max); + if (r < 0) + return log_error_errno(r, "Failed to parse --ram=%s: %m", s); + } else + arg_ram_max = 0; + + if (!isempty(ram_slots)) { + r = safe_atou(ram_slots, &arg_ram_slots); + if (r < 0) + return log_error_errno(r, "Failed to parse --ram=%s: %m", s); + } else + arg_ram_slots = 0; + + return 0; +} + static int parse_argv(int argc, char *argv[]) { int r; @@ -340,6 +382,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_CPUS, ARG_RAM, ARG_KVM, + ARG_CXL, ARG_VSOCK, ARG_VSOCK_CID, ARG_TPM, @@ -398,6 +441,7 @@ static int parse_argv(int argc, char *argv[]) { { "ram", required_argument, NULL, ARG_RAM }, { "qemu-mem", required_argument, NULL, ARG_RAM }, /* Compat alias */ { "kvm", required_argument, NULL, ARG_KVM }, + { "cxl", required_argument, NULL, ARG_CXL }, { "qemu-kvm", required_argument, NULL, ARG_KVM }, /* Compat alias */ { "vsock", required_argument, NULL, ARG_VSOCK }, { "qemu-vsock", required_argument, NULL, ARG_VSOCK }, /* Compat alias */ @@ -518,9 +562,9 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_RAM: - r = parse_size(optarg, 1024, &arg_ram); + r = parse_ram(optarg); if (r < 0) - return log_error_errno(r, "Failed to parse --ram=%s: %m", optarg); + return r; break; case ARG_KVM: @@ -529,6 +573,15 @@ static int parse_argv(int argc, char *argv[]) { return r; break; + case ARG_CXL: + r = parse_boolean_argument("--cxl=", optarg, &arg_cxl); + if (r < 0) + return r; + if (arg_cxl && !ARCHITECTURE_SUPPORTS_CXL) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "CXL not supported on %s.", architecture_to_string(native_architecture())); + break; + case ARG_VSOCK: r = parse_tristate_argument_with_auto("--vsock=", optarg, &arg_vsock); if (r < 0) @@ -993,6 +1046,12 @@ static int parse_argv(int argc, char *argv[]) { if (!strv_isempty(arg_bind_user_groups) && strv_isempty(arg_bind_user)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --bind-user-group= without --bind-user="); + if (arg_ram_max > 0 && arg_ram_max < arg_ram) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Maximum RAM size must be greater than or equal to initial RAM size"); + + if (arg_ram_slots > 0 && arg_ram_max == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Memory hotplug slots require a maximum RAM size"); + if (arg_ephemeral && arg_extra_drives.n_drives > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --ephemeral with --extra-drive="); @@ -2311,6 +2370,12 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return r; } + if (arg_cxl) { + r = qemu_config_key(config_file, "cxl", "on"); + if (r < 0) + return r; + } + if (arg_directory || arg_runtime_mounts.n_mounts != 0) { r = qemu_config_key(config_file, "memory-backend", "mem"); if (r < 0) @@ -2333,6 +2398,16 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (r < 0) return r; + if (arg_ram_max > 0) { + r = qemu_config_keyf(config_file, "maxmem", "%" PRIu64 "M", DIV_ROUND_UP(arg_ram_max, U64_MB)); + if (r < 0) + return r; + + r = qemu_config_keyf(config_file, "slots", "%u", arg_ram_slots > 0 ? arg_ram_slots : 1u); + if (r < 0) + return r; + } + r = qemu_config_section(config_file, "object", "rng0", "qom-type", "rng-random", "filename", "/dev/urandom"); From 47257677303ed6c1679e6680d76463beb409ea18 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 3 Apr 2026 15:21:53 +0100 Subject: [PATCH 0737/1296] Fix build with -fno-semantic-interposition Fixes https://github.com/systemd/systemd/issues/41494 Follow-up for 44cc82bfbf160bba2562bec08ffacbd587771210 --- src/shared/mount-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index 02f63f802a4a8..f7bba5d080c85 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -2067,7 +2067,7 @@ int make_fsmount( r = extract_first_word(&p, &word, ",", EXTRACT_KEEP_QUOTE); if (r < 0) - return log_full_errno(error_log_level, r, "Failed to parse mount option string \"%s\": %m", o); + return log_full_errno(error_log_level, r, "Failed to parse mount option string \"%s\": %m", strempty(o)); if (r == 0) break; From 4ded480ea535d98a3e0e329b4f7aa8153d362302 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 3 Apr 2026 09:13:25 +0200 Subject: [PATCH 0738/1296] repart: Use --shrink with mkfs.btrfs to get a minimal filesystem Avoids populating the filesystem twice similar to read-only filesystems. --- man/repart.d.xml | 6 +++--- src/repart/repart.c | 32 +++++++++++++++++++++----------- test/units/TEST-58-REPART.sh | 12 ++++++++++++ 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/man/repart.d.xml b/man/repart.d.xml index d0992830b7825..f3ed246b6ec47 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -842,9 +842,9 @@ off when false, and best when true). Defaults to off. If set to best, the partition will have the minimal size required to store the sources configured with CopyFiles=. best - is currently only supported for read-only filesystems. If set to guess, the - partition is created at least as big as required to store the sources configured with - CopyFiles=. Note that unless the filesystem is a read-only filesystem, + is currently only supported for read-only filesystems and btrfs. If set to guess, + the partition is created at least as big as required to store the sources configured with + CopyFiles=. Note that unless the filesystem is a read-only filesystem or btrfs, systemd-repart will have to populate the filesystem twice to guess the minimal required size, so enabling this option might slow down repart when populating large partitions. diff --git a/src/repart/repart.c b/src/repart/repart.c index 118c6dfc07efa..a21ec10da86cc 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -2983,9 +2983,13 @@ static int partition_read_definition( return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), "Minimize= can only be enabled if Format= or Verity=hash are set."); - if (p->minimize == MINIMIZE_BEST && (p->format && !fstype_is_ro(p->format)) && p->verity != VERITY_HASH) + if (p->minimize == MINIMIZE_BEST && + p->format && + !fstype_is_ro(p->format) && + !streq(p->format, "btrfs") && + p->verity != VERITY_HASH) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "Minimize=best can only be used with read-only filesystems or Verity=hash."); + "Minimize=best can only be used with read-only filesystems, btrfs, or Verity=hash."); if (partition_needs_populate(p) && !mkfs_supports_root_option(p->format) && geteuid() != 0) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EPERM), @@ -7014,6 +7018,9 @@ static int finalize_extra_mkfs_options(const Partition *p, const char *root, cha if (r < 0) return r; } + + if (p->minimize != MINIMIZE_OFF && strv_extend(&sv, "--shrink") < 0) + return log_oom(); } *ret = TAKE_PTR(sv); @@ -9109,6 +9116,8 @@ static int context_minimize(Context *context) { if (!p->format) continue; + bool is_btrfs = streq(p->format, "btrfs"); + if (p->copy_blocks_fd >= 0) continue; @@ -9124,7 +9133,7 @@ static int context_minimize(Context *context) { (void) partition_hint(p, context->node, &hint); - log_info("Pre-populating %s filesystem of partition %s twice to calculate minimal partition size", + log_info("Pre-populating %s filesystem of partition %s to calculate minimal partition size", p->format, strna(hint)); if (!vt) { @@ -9146,7 +9155,9 @@ static int context_minimize(Context *context) { if (fd < 0) return log_error_errno(errno, "Failed to open temporary file %s: %m", temp); - if (fstype_is_ro(p->format)) + if (fstype_is_ro(p->format) || is_btrfs) + /* Read-only filesystems and btrfs (with mkfs.btrfs --shrink) produce a minimal + * filesystem in one pass, so we can use the real UUID directly. */ fs_uuid = p->fs_uuid; else { /* This may seem huge but it will be created sparse so it doesn't take up any space @@ -9168,7 +9179,7 @@ static int context_minimize(Context *context) { return r; } - if (!d || fstype_is_ro(p->format) || (streq_ptr(p->format, "btrfs") && p->compression)) { + if (!d || fstype_is_ro(p->format)) { if (!mkfs_supports_root_option(p->format)) return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "Loop device access is required to populate %s filesystems.", @@ -9198,8 +9209,9 @@ static int context_minimize(Context *context) { return r; /* Read-only filesystems are minimal from the first try because they create and size the - * loopback file for us. */ - if (fstype_is_ro(p->format)) { + * loopback file for us. Similarly, mkfs.btrfs --shrink populates the filesystem from the + * root directory and then shrinks the backing file to the minimal size. */ + if (fstype_is_ro(p->format) || is_btrfs) { fd = safe_close(fd); fd = open(temp, O_RDONLY|O_CLOEXEC|O_NONBLOCK); @@ -9234,10 +9246,8 @@ static int context_minimize(Context *context) { /* Other filesystems need to be provided with a pre-sized loopback file and will adapt to * fully occupy it. Because we gave the filesystem a 1T sparse file, we need to shrink the - * filesystem down to a reasonable size again to fit it in the disk image. While there are - * some filesystems that support shrinking, it doesn't always work properly (e.g. shrinking - * btrfs gives us a 2.0G filesystem regardless of what we put in it). Instead, let's populate - * the filesystem again, but this time, instead of providing the filesystem with a 1T sparse + * filesystem down to a reasonable size again to fit it in the disk image. Let's populate the + * filesystem again, but this time, instead of providing the filesystem with a 1T sparse * loopback file, let's size the loopback file based on the actual data used by the * filesystem in the sparse file after the first attempt. This should be a good guess of the * minimal amount of space needed in the filesystem to fit all the required data. diff --git a/test/units/TEST-58-REPART.sh b/test/units/TEST-58-REPART.sh index 546df29f44aa8..fb3dbcfad5d65 100755 --- a/test/units/TEST-58-REPART.sh +++ b/test/units/TEST-58-REPART.sh @@ -1254,6 +1254,18 @@ Minimize=guess EOF done + if command -v mkfs.btrfs >/dev/null; then + for minimize in guess best; do + tee "$defs/root-btrfs-${minimize}.conf" </dev/null; then tee "$defs/root-squashfs.conf" < Date: Fri, 3 Apr 2026 13:43:57 +0000 Subject: [PATCH 0739/1296] dissect-image: add crypto_LUKS and swap to blkid probe filter allowed_fstypes() returns the list of filesystem types that we are willing to mount. However, the blkid probe filter needs to detect additional non-mountable types: crypto_LUKS (so that LUKS-encrypted partitions can be identified and decrypted) and swap (so that swap partitions can be identified). Without these types in the BLKID_FLTR_ONLYIN filter, blkid reports "No type detected" for encrypted and swap partitions, causing image policy checks to fail (e.g. "encrypted was required") and mount operations to fail with "File system type not supported". Note that verity types (DM_verity_hash, verity_hash_signature) do not need to be added here because their fstype is assigned directly during partition table parsing, not via blkid probing. Follow-up for e33eb053fb. --- src/shared/dissect-image.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 2bef82dd34b53..ef73b11014b68 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -195,6 +195,13 @@ static int probe_blkid_filter(blkid_probe p) { if (r < 0) return r; + /* allowed_fstypes() returns the list of filesystem types that we are willing to mount. For the + * blkid probe filter we additionally need to be able to detect crypto_LUKS (so that we can set up + * LUKS decryption for encrypted partitions) and swap (so that we can identify swap partitions). */ + r = strv_extend_many(&fstypes, "crypto_LUKS", "swap"); + if (r < 0) + return r; + errno = 0; r = sym_blkid_probe_filter_superblocks_type(p, BLKID_FLTR_ONLYIN, fstypes); if (r != 0) From ef0afed65172553c04384e0340de33abf0b2ee4b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 3 Apr 2026 10:09:14 +0000 Subject: [PATCH 0740/1296] dissect-image: Drop blkid_probe_filter_superblocks_usage() call from probe_blkid_filter() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit probe_blkid_filter() sets up a blkid superblock filter to restrict filesystem detection to a known-safe set of types (btrfs, erofs, ext4, f2fs, squashfs, vfat, xfs). It does so via two consecutive calls: 1. blkid_probe_filter_superblocks_type(BLKID_FLTR_ONLYIN, ...) 2. blkid_probe_filter_superblocks_usage(BLKID_FLTR_NOTIN, BLKID_USAGE_RAID) However, both filter functions share the same internal bitmap in libblkid. Each call goes through blkid_probe_get_filter(), which zeroes the entire bitmap before applying the new filter. This means the second call (usage filter) silently destroys the type filter set by the first call. The result is that only RAID superblocks end up being filtered, while all other filesystem types — including iso9660 — pass through unfiltered. This causes ISO images (e.g. those with El Torito boot catalogs and GPT) to be incorrectly dissected: blkid detects the iso9660 superblock on the whole device (since iso9660 is marked BLKID_IDINFO_TOLERANT and can coexist with partition tables), the code enters the unpartitioned single-filesystem path, and then mounting fails because iso9660 is not in the allowed filesystem list: "File system type 'iso9660' is not allowed to be mounted as result of automatic dissection." Fix this by dropping the blkid_probe_filter_superblocks_usage() call. The BLKID_FLTR_ONLYIN type filter already restricts probing to only the listed types, which implicitly excludes RAID superblocks as well, making the usage filter redundant. Follow-up for 72bf86663c ("dissect: use blkid_probe filters to restrict probing to supported FSes and no raid") --- src/shared/dissect-image.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index ef73b11014b68..9817ed87ec129 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -207,10 +207,11 @@ static int probe_blkid_filter(blkid_probe p) { if (r != 0) return errno_or_else(EINVAL); - errno = 0; - r = sym_blkid_probe_filter_superblocks_usage(p, BLKID_FLTR_NOTIN, BLKID_USAGE_RAID); - if (r != 0) - return errno_or_else(EINVAL); + /* Note: don't call blkid_probe_filter_superblocks_usage() here. Both filter functions share the + * same bitmap internally, and each call resets it before applying its own filter — so a subsequent + * usage filter would wipe the type filter we just set. The ONLYIN type filter above already + * excludes everything not in the allowed list, including RAID superblocks, so a separate usage + * filter is redundant anyway. */ return 0; } From 2d75149348af83c210d226c974a12db6147011f4 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 3 Apr 2026 10:09:30 +0000 Subject: [PATCH 0741/1296] test: Add El Torito boot catalog dissection test Verify that GPT images with an ISO9660 El Torito boot catalog are dissected via the GPT partition table rather than being treated as a single iso9660 filesystem. Follow-up for e33eb053fb ("dissect-image: Drop blkid_probe_filter_superblocks_usage() call from probe_blkid_filter()") --- test/units/TEST-50-DISSECT.dissect.sh | 41 +++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/test/units/TEST-50-DISSECT.dissect.sh b/test/units/TEST-50-DISSECT.dissect.sh index 8cc07df3b967e..65e684fa07ad9 100755 --- a/test/units/TEST-50-DISSECT.dissect.sh +++ b/test/units/TEST-50-DISSECT.dissect.sh @@ -1203,6 +1203,47 @@ systemd-sysext unmerge systemctl status foo.service 2>&1 | grep -v -F "Warning" >/dev/null rm /var/lib/extensions/app-reload.raw +# Test that GPT images with an ISO9660 El Torito boot catalog are dissected correctly. The blkid +# superblock filter must prevent the iso9660 superblock from being detected, so dissection proceeds via +# the GPT partition table rather than treating the whole image as a single iso9660 filesystem. +defs="$(mktemp --directory "$IMAGE_DIR/test-50-dissect-eltorito.defs.XXXXXXXXXX")" +imgs="$(mktemp --directory "$IMAGE_DIR/test-50-dissect-eltorito.imgs.XXXXXXXXXX")" + +tee "$defs/00-esp.conf" < Date: Fri, 3 Apr 2026 19:58:47 +0000 Subject: [PATCH 0742/1296] po: Translated using Weblate (Hungarian) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (266 of 266 strings) Co-authored-by: Balázs Meskó Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/hu/ Translation: systemd/main --- po/hu.po | 158 ++++++++++++++++++++++--------------------------------- 1 file changed, 64 insertions(+), 94 deletions(-) diff --git a/po/hu.po b/po/hu.po index 7329d259df9f7..23468d2e1ba4b 100644 --- a/po/hu.po +++ b/po/hu.po @@ -5,22 +5,22 @@ # # Gabor Kelemen , 2015, 2016. # Balázs Úr , 2016. -# Balázs Meskó , 2022. +# Balázs Meskó , 2022, 2026. # Balázs Úr , 2023. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2023-09-27 01:36+0000\n" -"Last-Translator: Balázs Úr \n" +"PO-Revision-Date: 2026-04-03 19:58+0000\n" +"Last-Translator: Balázs Meskó \n" "Language-Team: Hungarian \n" +"systemd/main/hu/>\n" "Language: hu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.0.2\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -30,7 +30,7 @@ msgstr "Jelmondat visszaküldése a rendszernek" msgid "" "Authentication is required to send the entered passphrase back to the system." msgstr "" -"Hitelesítés szükséges a megadott jelmondatnak a rendszernek való " +"Hitelesítés szükséges a megadott jelmondat a rendszernek való " "visszaküldéséhez." #: src/core/org.freedesktop.systemd1.policy.in:33 @@ -120,12 +120,10 @@ msgid "Authentication is required to update a user's home area." msgstr "Hitelesítés szükséges a felhasználó saját területének frissítéséhez." #: src/home/org.freedesktop.home1.policy:53 -#, fuzzy msgid "Update your home area" msgstr "Saját terület frissítése" #: src/home/org.freedesktop.home1.policy:54 -#, fuzzy msgid "Authentication is required to update your home area." msgstr "Hitelesítés szükséges a felhasználó saját területének frissítéséhez." @@ -149,24 +147,20 @@ msgstr "" "megváltoztatásához." #: src/home/org.freedesktop.home1.policy:83 -#, fuzzy msgid "Activate a home area" -msgstr "Saját terület létrehozása" +msgstr "Saját terület aktiválása" #: src/home/org.freedesktop.home1.policy:84 -#, fuzzy msgid "Authentication is required to activate a user's home area." -msgstr "Hitelesítés szükséges a felhasználó saját területének létrehozásához." +msgstr "Hitelesítés szükséges a felhasználó saját területének aktiválásához." #: src/home/org.freedesktop.home1.policy:93 msgid "Manage Home Directory Signing Keys" -msgstr "" +msgstr "Saját könyvtár aláírókulcsainak kezelése" #: src/home/org.freedesktop.home1.policy:94 -#, fuzzy msgid "Authentication is required to manage signing keys for home directories." -msgstr "" -"Hitelesítés szükséges a rendszerszolgáltatások vagy más egységek kezeléséhez." +msgstr "Hitelesítés szükséges a saját könyvtárak aláírókulcsainak kezeléséhez." #: src/home/pam_systemd_home.c:330 #, c-format @@ -280,7 +274,7 @@ msgstr "" #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" -"%s felhasználó biztonsági tokenjének PIN-kódja helytelen (már egy " +"%s felhasználó biztonsági tokenjének PIN-kódja helytelen (még egy " "próbálkozás maradt!)" #: src/home/pam_systemd_home.c:679 @@ -394,46 +388,37 @@ msgid "Authentication is required to get system description." msgstr "Hitelesítés szükséges a rendszer leírásának lekéréséhez." #: src/import/org.freedesktop.import1.policy:22 -#, fuzzy msgid "Import a disk image" -msgstr "VM vagy konténer lemezképének importálása" +msgstr "Lemezkép importálása" #: src/import/org.freedesktop.import1.policy:23 -#, fuzzy msgid "Authentication is required to import an image." -msgstr "Hitelesítés szükséges a VM vagy konténer lemezképének importálásához." +msgstr "Hitelesítés szükséges a lemezkép importálásához." #: src/import/org.freedesktop.import1.policy:32 -#, fuzzy msgid "Export a disk image" -msgstr "VM vagy konténer lemezképének exportálása" +msgstr "Lemezkép exportálása" #: src/import/org.freedesktop.import1.policy:33 -#, fuzzy msgid "Authentication is required to export disk image." -msgstr "Hitelesítés szükséges a VM vagy konténer lemezképének exportálásához." +msgstr "Hitelesítés szükséges a lemezkép exportálásához." #: src/import/org.freedesktop.import1.policy:42 -#, fuzzy msgid "Download a disk image" -msgstr "VM vagy konténer lemezképének letöltése" +msgstr "Lemezkép letöltése" #: src/import/org.freedesktop.import1.policy:43 -#, fuzzy msgid "Authentication is required to download a disk image." -msgstr "Hitelesítés szükséges a VM vagy konténer lemezképének letöltéséhez." +msgstr "Hitelesítés szükséges a lemezkép letöltéséhez." #: src/import/org.freedesktop.import1.policy:52 msgid "Cancel transfer of a disk image" -msgstr "" +msgstr "Lemezkép átvitelének megszakítása" #: src/import/org.freedesktop.import1.policy:53 -#, fuzzy msgid "" "Authentication is required to cancel the ongoing transfer of a disk image." -msgstr "" -"Hitelesítés szükséges a felhasználó saját területe jelszavának " -"megváltoztatásához." +msgstr "Hitelesítés szükséges a lemezkép futó átvitelének megszakításához." #: src/locale/org.freedesktop.locale1.policy:22 msgid "Set system locale" @@ -489,7 +474,7 @@ msgstr "Az alkalmazások késleltethetik a rendszer altatását" #: src/login/org.freedesktop.login1.policy:56 msgid "Authentication is required for an application to delay system sleep." msgstr "" -"Hitelesítés szükséges egy alkalmazás számára a rendszeraltatás " +"Hitelesítés szükséges egy alkalmazás számára a rendszer altatásának " "késleltetéséhez." #: src/login/org.freedesktop.login1.policy:65 @@ -501,8 +486,8 @@ msgid "" "Authentication is required for an application to inhibit automatic system " "suspend." msgstr "" -"Hitelesítés szükséges egy alkalmazás számára az automatikus " -"rendszerfelfüggesztés meggátlásához." +"Hitelesítés szükséges egy alkalmazás számára a rendszer automatikus " +"felfüggesztésének meggátlásához." #: src/login/org.freedesktop.login1.policy:75 msgid "Allow applications to inhibit system handling of the power key" @@ -514,8 +499,8 @@ msgid "" "Authentication is required for an application to inhibit system handling of " "the power key." msgstr "" -"Hitelesítés szükséges egy alkalmazás számára a bekapcsoló gomb rendszer " -"általi kezelésének meggátlásához." +"Hitelesítés szükséges, hogy egy alkalmazás számára a bekapcsoló gomb " +"rendszer általi kezelésének meggátlásához." #: src/login/org.freedesktop.login1.policy:86 msgid "Allow applications to inhibit system handling of the suspend key" @@ -849,7 +834,6 @@ msgid "Set a wall message" msgstr "Falüzenet beállítása" #: src/login/org.freedesktop.login1.policy:397 -#, fuzzy msgid "Authentication is required to set a wall message." msgstr "Hitelesítés szükséges a falüzenet beállításához." @@ -922,26 +906,24 @@ msgid "" msgstr "Hitelesítés szükséges helyi virtuális gépek és konténerek kezeléséhez." #: src/machine/org.freedesktop.machine1.policy:95 -#, fuzzy msgid "Create a local virtual machine or container" -msgstr "Helyi virtuális gépek és konténerek kezelése" +msgstr "Helyi virtuális gép vagy konténer létrehozása" #: src/machine/org.freedesktop.machine1.policy:96 -#, fuzzy msgid "" "Authentication is required to create a local virtual machine or container." -msgstr "Hitelesítés szükséges helyi virtuális gépek és konténerek kezeléséhez." +msgstr "" +"Hitelesítés szükséges a helyi virtuális gép vagy konténer létrehozásához." #: src/machine/org.freedesktop.machine1.policy:106 -#, fuzzy msgid "Register a local virtual machine or container" -msgstr "Helyi virtuális gépek és konténerek kezelése" +msgstr "Helyi virtuális gép vagy konténer regisztrálása" #: src/machine/org.freedesktop.machine1.policy:107 -#, fuzzy msgid "" "Authentication is required to register a local virtual machine or container." -msgstr "Hitelesítés szükséges helyi virtuális gépek és konténerek kezeléséhez." +msgstr "" +"Hitelesítés szükséges a helyi virtuális gép vagy konténer regisztrálásához." #: src/machine/org.freedesktop.machine1.policy:116 msgid "Manage local virtual machine and container images" @@ -1066,12 +1048,12 @@ msgid "DHCP server sends force renew message" msgstr "A DHCP-kiszolgáló kényszerített megújítási üzenetet küld" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy -#| msgid "Authentication is required to send force renew message." msgid "" "Authentication is required to send a force renew message from the DHCP " "server." -msgstr "Hitelesítés szükséges a kényszerített megújítási üzenetet küldéséhez." +msgstr "" +"Hitelesítés szükséges a kényszerített megújítási üzenetet küldéséhez a DHCP " +"kiszolgálótól." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" @@ -1099,21 +1081,23 @@ msgstr "Hitelesítés szükséges a hálózati csatoló újrakonfigurálásához #: src/network/org.freedesktop.network1.policy:187 msgid "Specify whether persistent storage for systemd-networkd is available" -msgstr "" +msgstr "Adja meg, hogy érhető-e el tartós tároló a systemd-networkd számára" #: src/network/org.freedesktop.network1.policy:188 msgid "" "Authentication is required to specify whether persistent storage for systemd-" "networkd is available." msgstr "" +"Hitelesítés szükséges annak a beállításához, hogy érhető-e el tartós tároló " +"a systemd-networkd számára." #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Hálózati csatolók kezelése" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" +msgstr "Hitelesítés szükséges a hálózati csatolók kezeléséhez." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" @@ -1151,7 +1135,6 @@ msgid "Register a DNS-SD service" msgstr "DNS-SD szolgáltatás regisztrálása" #: src/resolve/org.freedesktop.resolve1.policy:23 -#, fuzzy msgid "Authentication is required to register a DNS-SD service." msgstr "Hitelesítés szükséges a DNS-SD szolgáltatás regisztrálásához." @@ -1160,7 +1143,6 @@ msgid "Unregister a DNS-SD service" msgstr "DNS-SD szolgáltatás regisztrációjának törlése" #: src/resolve/org.freedesktop.resolve1.policy:34 -#, fuzzy msgid "Authentication is required to unregister a DNS-SD service." msgstr "" "Hitelesítés szükséges a DNS-SD szolgáltatás regisztrációjának törléséhez." @@ -1175,106 +1157,95 @@ msgstr "Hitelesítés szükséges a névfeloldási beállítások visszaállít #: src/resolve/org.freedesktop.resolve1.policy:143 msgid "Subscribe query results" -msgstr "" +msgstr "Feliratkozás lekérdezési találatokra" #: src/resolve/org.freedesktop.resolve1.policy:144 -#, fuzzy msgid "Authentication is required to subscribe query results." -msgstr "Hitelesítés szükséges a rendszer felfüggesztéséhez." +msgstr "Hitelesítés szükséges a lekérdezési találatokra való feliratkozáshoz." #: src/resolve/org.freedesktop.resolve1.policy:154 msgid "Subscribe to DNS configuration" -msgstr "" +msgstr "Feliratkozás a DNS beállításokra" #: src/resolve/org.freedesktop.resolve1.policy:155 -#, fuzzy msgid "Authentication is required to subscribe to DNS configuration." -msgstr "Hitelesítés szükséges a rendszer felfüggesztéséhez." +msgstr "Hitelesítés szükséges a DNS beállításokra való feliratkozáshoz." #: src/resolve/org.freedesktop.resolve1.policy:165 msgid "Dump cache" -msgstr "" +msgstr "Gyorsítótár eldboása" #: src/resolve/org.freedesktop.resolve1.policy:166 -#, fuzzy msgid "Authentication is required to dump cache." -msgstr "Hitelesítés szükséges a tartományok beállításához." +msgstr "Hitelesítés szükséges a gyorsítótár eldobásához." #: src/resolve/org.freedesktop.resolve1.policy:176 msgid "Dump server state" -msgstr "" +msgstr "Kiszolgálóállapot eldobása" #: src/resolve/org.freedesktop.resolve1.policy:177 -#, fuzzy msgid "Authentication is required to dump server state." -msgstr "Hitelesítés szükséges az NTP-kiszolgálók beállításához." +msgstr "Hitelesítés szükséges a kiszolgálóállapot eldobásához." #: src/resolve/org.freedesktop.resolve1.policy:187 msgid "Dump statistics" -msgstr "" +msgstr "Statisztikák eldobása" #: src/resolve/org.freedesktop.resolve1.policy:188 -#, fuzzy msgid "Authentication is required to dump statistics." -msgstr "Hitelesítés szükséges a tartományok beállításához." +msgstr "Hitelesítés szükséges a statisztikák eldobásához." #: src/resolve/org.freedesktop.resolve1.policy:198 msgid "Reset statistics" -msgstr "" +msgstr "Statisztikák visszaállítása" #: src/resolve/org.freedesktop.resolve1.policy:199 -#, fuzzy msgid "Authentication is required to reset statistics." -msgstr "Hitelesítés szükséges az NTP-beállítások visszaállításához." +msgstr "Hitelesítés szükséges a statisztikák visszaállításához." #: src/sysupdate/org.freedesktop.sysupdate1.policy:35 msgid "Check for system updates" -msgstr "" +msgstr "Rendszerfrissítések keresése" #: src/sysupdate/org.freedesktop.sysupdate1.policy:36 -#, fuzzy msgid "Authentication is required to check for system updates." -msgstr "Hitelesítés szükséges a rendszeridő beállításához." +msgstr "Hitelesítés szükséges a rendszerfrissítések kereséséhez." #: src/sysupdate/org.freedesktop.sysupdate1.policy:45 msgid "Install system updates" -msgstr "" +msgstr "Rendszerfrissítések telepítése" #: src/sysupdate/org.freedesktop.sysupdate1.policy:46 -#, fuzzy msgid "Authentication is required to install system updates." -msgstr "Hitelesítés szükséges a rendszeridő beállításához." +msgstr "Hitelesítés szükséges a rendszerfrissítések telepítéséhez." #: src/sysupdate/org.freedesktop.sysupdate1.policy:55 msgid "Install specific system version" -msgstr "" +msgstr "Konkrét rendszerverzió telepítése" #: src/sysupdate/org.freedesktop.sysupdate1.policy:56 -#, fuzzy msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." -msgstr "Hitelesítés szükséges a rendszer időzónájának beállításához." +msgstr "" +"Hitelesítés szükséges a rendszer egy konkrét (esetleg régi) verzióra történő " +"frissítéséhez." #: src/sysupdate/org.freedesktop.sysupdate1.policy:65 msgid "Cleanup old system updates" -msgstr "" +msgstr "Régi rendszerfrissítések eltakarítása" #: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -#, fuzzy msgid "Authentication is required to cleanup old system updates." -msgstr "Hitelesítés szükséges a rendszeridő beállításához." +msgstr "Hitelesítés szükséges a régi rendszerfrissítések eltakarításához." #: src/sysupdate/org.freedesktop.sysupdate1.policy:75 msgid "Manage optional features" -msgstr "" +msgstr "Választható funkciók kezelése" #: src/sysupdate/org.freedesktop.sysupdate1.policy:76 -#, fuzzy msgid "Authentication is required to manage optional features." -msgstr "" -"Hitelesítés szükséges az aktív munkamenetek, felhasználók és munkaállomások " -"kezeléséhez." +msgstr "Hitelesítés szükséges a választható funkciók kezeléséhez." #: src/timedate/org.freedesktop.timedate1.policy:22 msgid "Set system time" @@ -1341,13 +1312,12 @@ msgstr "" "küldéséhez." #: src/core/dbus-unit.c:621 -#, fuzzy msgid "" "Authentication is required to send a UNIX signal to the processes of " "subgroup of '$(unit)'." msgstr "" -"Hitelesítés szükséges a(z) „$(unit)” folyamatainak történő UNIX szignál " -"küldéséhez." +"Hitelesítés szükséges a(z) „$(unit)” alcsoport folyamatainak történő UNIX " +"szignál küldéséhez." #: src/core/dbus-unit.c:649 msgid "Authentication is required to reset the \"failed\" state of '$(unit)'." From 02fcb8f544cee33d070ff7c9590e6d605bbc155c Mon Sep 17 00:00:00 2001 From: Marek Adamski Date: Fri, 3 Apr 2026 19:58:48 +0000 Subject: [PATCH 0743/1296] po: Translated using Weblate (Polish) Currently translated at 100.0% (266 of 266 strings) Co-authored-by: Marek Adamski Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/pl/ Translation: systemd/main --- po/pl.po | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/po/pl.po b/po/pl.po index 72f05f9f9fee5..913de0d7a73fb 100644 --- a/po/pl.po +++ b/po/pl.po @@ -3,12 +3,14 @@ # Polish translation for systemd. # # Piotr Drąg , 2023, 2024, 2025. +# Marek Adamski , 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2025-07-24 14:54+0000\n" -"Last-Translator: Piotr Drąg \n" +"PO-Revision-Date: 2026-04-03 19:58+0000\n" +"Last-Translator: Marek Adamski " +"\n" "Language-Team: Polish \n" "Language: pl\n" @@ -17,7 +19,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2;\n" -"X-Generator: Weblate 5.12.2\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1052,13 +1054,12 @@ msgid "DHCP server sends force renew message" msgstr "Serwer DHCP wysyła komunikat wymuszonego odnowienia" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy -#| msgid "Authentication is required to send force renew message." msgid "" "Authentication is required to send a force renew message from the DHCP " "server." msgstr "" -"Wymagane jest uwierzytelnienie, aby wysłać komunikat wymuszonego odnowienia." +"Wymagane jest uwierzytelnienie, aby wysłać komunikat wymuszonego odnowienia z" +" serwera DHCP." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" @@ -1102,11 +1103,11 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Zarządzanie łączami sieciowymi" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" +msgstr "Wymagane jest uwierzytelnienie, aby zarządzać łączami sieciowymi." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" From 1cc7353dcfe9d4077a5838d6e7c02ceb58e4118d Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 3 Apr 2026 22:03:52 +0200 Subject: [PATCH 0744/1296] fd-util: Add missing assert() --- src/basic/fd-util.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index b623dc7ff1678..c2d46aae155a2 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -167,6 +167,8 @@ int fd_nonblock(int fd, bool nonblock) { } void nonblock_resetp(int *fd) { + assert(fd); + PROTECT_ERRNO; if (*fd >= 0) From cd18656d47710c251a44a8f5f9d616151a909152 Mon Sep 17 00:00:00 2001 From: Chris Down Date: Sat, 4 Apr 2026 00:03:28 +0900 Subject: [PATCH 0745/1296] TEST-70-TPM2: Suppress PCR public key auto-loading in basic tests When systemd-cryptenroll --tpm2-device=auto is called on a system where a tpm2-pcr-public-key.pem exists it automatically creates tokens with a signed PCR policy. Unlocking such a token via --unlock-tpm2-device=auto requires a tpm2-pcr-signature.json file, which is not present. This creates a race with systemd-tpm2-setup.service at boot: if the service completes before the test, the key exists and the subsequent --unlock-tpm2-device=auto calls fail, which I believe is the cause of the test flakiness. This also seems to mesh with the fact that this only flakes on Debian CI, since that's built with ukify which installs a public key. Let's hopefully fix this by passing --tpm2-public-key= to all --tpm2-device= enrollment calls that aren't explicitly intended to test signed PCR policy behaviour. --- test/units/TEST-70-TPM2.cryptenroll.sh | 13 +++++------ test/units/TEST-70-TPM2.cryptsetup.sh | 30 +++++++++++++------------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/test/units/TEST-70-TPM2.cryptenroll.sh b/test/units/TEST-70-TPM2.cryptenroll.sh index f18ef020a75e9..d09f702093681 100755 --- a/test/units/TEST-70-TPM2.cryptenroll.sh +++ b/test/units/TEST-70-TPM2.cryptenroll.sh @@ -27,13 +27,14 @@ chmod 0600 /tmp/password cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom "$IMAGE" /tmp/password # Enroll additional tokens, keys, and passwords to exercise the list and wipe stuff -systemd-cryptenroll --unlock-key-file=/tmp/password --tpm2-device=auto "$IMAGE" +# Use --tpm2-public-key= to suppress auto-loading any PCR public key from the host +systemd-cryptenroll --unlock-key-file=/tmp/password --tpm2-device=auto --tpm2-public-key= "$IMAGE" NEWPASSWORD="" systemd-cryptenroll --unlock-key-file=/tmp/password --password "$IMAGE" NEWPASSWORD=foo systemd-cryptenroll --unlock-key-file=/tmp/password --password "$IMAGE" for _ in {0..9}; do systemd-cryptenroll --unlock-key-file=/tmp/password --recovery-key "$IMAGE" done -PASSWORD="" NEWPIN=123456 systemd-cryptenroll --tpm2-device=auto --tpm2-with-pin=true "$IMAGE" +PASSWORD="" NEWPIN=123456 systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-with-pin=true "$IMAGE" # Do some basic checks before we start wiping stuff systemd-cryptenroll "$IMAGE" systemd-cryptenroll "$IMAGE" | grep password @@ -60,15 +61,15 @@ systemd-cryptenroll --tpm2-pcrs=8 "$IMAGE" systemd-cryptenroll --tpm2-pcrs=boot-loader-code+boot-loader-config "$IMAGE" # Unlocking using TPM2 -PASSWORD=foo systemd-cryptenroll --tpm2-device=auto "$IMAGE" +PASSWORD=foo systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= "$IMAGE" systemd-cryptenroll --unlock-tpm2-device=auto --recovery-key "$IMAGE" -systemd-cryptenroll --unlock-tpm2-device=auto --tpm2-device=auto --wipe-slot=tpm2 "$IMAGE" +systemd-cryptenroll --unlock-tpm2-device=auto --tpm2-device=auto --tpm2-public-key= --wipe-slot=tpm2 "$IMAGE" # Add PIN to TPM2 enrollment -NEWPIN=1234 systemd-cryptenroll --unlock-tpm2-device=auto --tpm2-device=auto --tpm2-with-pin=yes "$IMAGE" +NEWPIN=1234 systemd-cryptenroll --unlock-tpm2-device=auto --tpm2-device=auto --tpm2-public-key= --tpm2-with-pin=yes "$IMAGE" # Change PIN on TPM2 enrollment -PIN=1234 NEWPIN=4321 systemd-cryptenroll --unlock-tpm2-device=auto --tpm2-device=auto --tpm2-with-pin=yes "$IMAGE" +PIN=1234 NEWPIN=4321 systemd-cryptenroll --unlock-tpm2-device=auto --tpm2-device=auto --tpm2-public-key= --tpm2-with-pin=yes "$IMAGE" PIN=4321 systemd-cryptenroll --unlock-tpm2-device=auto --recovery-key "$IMAGE" (! systemd-cryptenroll --fido2-with-client-pin=false) diff --git a/test/units/TEST-70-TPM2.cryptsetup.sh b/test/units/TEST-70-TPM2.cryptsetup.sh index c94d515ff9b82..24c87d0f2495c 100755 --- a/test/units/TEST-70-TPM2.cryptsetup.sh +++ b/test/units/TEST-70-TPM2.cryptsetup.sh @@ -49,10 +49,10 @@ chmod 0600 /tmp/passphrase cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom "$IMAGE" /tmp/passphrase # Unlocking via keyfile -systemd-cryptenroll --unlock-key-file=/tmp/passphrase --tpm2-device=auto --tpm2-pcrs=7 "$IMAGE" +systemd-cryptenroll --unlock-key-file=/tmp/passphrase --tpm2-device=auto --tpm2-public-key= --tpm2-pcrs=7 "$IMAGE" # Enroll unlock with SecureBoot (PCR 7) PCR policy -PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=7 "$IMAGE" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-pcrs=7 "$IMAGE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume @@ -62,7 +62,7 @@ tpm2_pcrextend 7:sha256=00000000000000000000000000000000000000000000000000000000 # Enroll unlock with PCR+PIN policy systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" -PASSWORD=passphrase NEWPIN=123456 systemd-cryptenroll --tpm2-device=auto --tpm2-with-pin=true --tpm2-pcrs=7 "$IMAGE" +PASSWORD=passphrase NEWPIN=123456 systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-with-pin=true --tpm2-pcrs=7 "$IMAGE" PIN=123456 systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume @@ -90,7 +90,7 @@ tpm2_pcrextend 7:sha256=00000000000000000000000000000000000000000000000000000000 # Enroll unlock with PCR 0+7 systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" -PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 "$IMAGE" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-pcrs=0+7 "$IMAGE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume @@ -102,21 +102,21 @@ if tpm_has_pcr sha256 12; then # Enroll using an explicit PCR value (that does match current PCR value) systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" EXPECTED_PCR_VALUE=$(cat /sys/class/tpm/tpm0/pcr-sha256/12) - PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs="12:sha256=$EXPECTED_PCR_VALUE" "$IMAGE" + PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-pcrs="12:sha256=$EXPECTED_PCR_VALUE" "$IMAGE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume # Same as above plus more PCRs without the value or alg specified systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" EXPECTED_PCR_VALUE=$(cat /sys/class/tpm/tpm0/pcr-sha256/12) - PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs="1,12:sha256=$EXPECTED_PCR_VALUE,3" "$IMAGE" + PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-pcrs="1,12:sha256=$EXPECTED_PCR_VALUE,3" "$IMAGE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume # Same as above plus more PCRs with hash alg specified but hash value not specified systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" EXPECTED_PCR_VALUE=$(cat /sys/class/tpm/tpm0/pcr-sha256/12) - PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs="1:sha256,12:sha256=$EXPECTED_PCR_VALUE,3" "$IMAGE" + PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-pcrs="1:sha256,12:sha256=$EXPECTED_PCR_VALUE,3" "$IMAGE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume @@ -125,7 +125,7 @@ if tpm_has_pcr sha256 12; then tpm2_pcrread -Q -o /tmp/pcr.dat sha256:12 CURRENT_PCR_VALUE=$(cat /sys/class/tpm/tpm0/pcr-sha256/12) EXPECTED_PCR_VALUE=$(cat /tmp/pcr.dat /tmp/pcr.dat | openssl dgst -sha256 -r | cut -d ' ' -f 1) - PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs="12:sha256=$EXPECTED_PCR_VALUE" "$IMAGE" + PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-pcrs="12:sha256=$EXPECTED_PCR_VALUE" "$IMAGE" (! systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1) tpm2_pcrextend "12:sha256=$CURRENT_PCR_VALUE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 @@ -143,7 +143,7 @@ if tpm_has_pcr sha256 12; then # --tpm2-device-key= requires OpenSSL >= 3 with KDF-SS if openssl_supports_kdf SSKDF; then - PASSWORD=passphrase systemd-cryptenroll --tpm2-device-key=/tmp/srk.pub --tpm2-pcrs="12:sha256=$CURRENT_PCR_VALUE" "$IMAGE" + PASSWORD=passphrase systemd-cryptenroll --tpm2-device-key=/tmp/srk.pub --tpm2-public-key= --tpm2-pcrs="12:sha256=$CURRENT_PCR_VALUE" "$IMAGE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume fi @@ -153,23 +153,23 @@ fi # Use default (0) seal key handle systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" -PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0 "$IMAGE" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-seal-key-handle=0 "$IMAGE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" -PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0x0 "$IMAGE" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-seal-key-handle=0x0 "$IMAGE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume # Use SRK seal key handle systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" -PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=81000001 "$IMAGE" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-seal-key-handle=81000001 "$IMAGE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" -PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0x81000001 "$IMAGE" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-seal-key-handle=0x81000001 "$IMAGE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume @@ -189,12 +189,12 @@ PERSISTENT_HANDLE="0x${PERSISTENT_LINE##*0x}" tpm2_flushcontext -t systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" -PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle="${PERSISTENT_HANDLE#0x}" "$IMAGE" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-seal-key-handle="${PERSISTENT_HANDLE#0x}" "$IMAGE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" -PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle="$PERSISTENT_HANDLE" "$IMAGE" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-seal-key-handle="$PERSISTENT_HANDLE" "$IMAGE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume From 7632fa1da2dd9d60b4a73d67d731876e9dd706f2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 2 Apr 2026 14:08:17 +0200 Subject: [PATCH 0746/1296] iso9660: prefer uint8_t for 'arbitrary bytes' --- src/repart/iso9660.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/repart/iso9660.h b/src/repart/iso9660.h index 7a51928fd604b..23db9e919a417 100644 --- a/src/repart/iso9660.h +++ b/src/repart/iso9660.h @@ -127,14 +127,14 @@ struct _packed_ iso9660_primary_volume_descriptor { uint8_t file_structure_version; /* 1 */ uint8_t unused_5; char application_used[512]; - char reserved[653]; + uint8_t reserved[653]; }; assert_cc(sizeof(struct iso9660_primary_volume_descriptor) == 2048); struct _packed_ el_torito_validation_entry { uint8_t header_indicator; uint8_t platform; - char reserved[2]; + uint8_t reserved[2]; char id_string[24]; le16_t checksum; uint8_t key_bytes[2]; From b0d52a8101db84dca26e85bc6f81d4c24f3776dd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 2 Apr 2026 14:10:16 +0200 Subject: [PATCH 0747/1296] iso9660: rename all functions so that they are prefixed by iso9660 --- src/repart/iso9660.c | 18 +++++++++--------- src/repart/iso9660.h | 12 ++++++------ src/repart/repart.c | 38 +++++++++++++++++++------------------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/repart/iso9660.c b/src/repart/iso9660.c index 5bc9588e68928..3f200942b2d51 100644 --- a/src/repart/iso9660.c +++ b/src/repart/iso9660.c @@ -8,7 +8,7 @@ #include "string-util.h" #include "time-util.h" -void no_iso9660_datetime(struct iso9660_datetime *ret) { +void iso9660_datetime_zero(struct iso9660_datetime *ret) { assert(ret); memcpy(ret->year, "0000", 4); @@ -21,7 +21,7 @@ void no_iso9660_datetime(struct iso9660_datetime *ret) { ret->zone = 0; } -int time_to_iso9660_datetime(usec_t usec, bool utc, struct iso9660_datetime *ret) { +int iso9660_datetime_from_usec(usec_t usec, bool utc, struct iso9660_datetime *ret) { struct tm t; int r; @@ -49,7 +49,7 @@ int time_to_iso9660_datetime(usec_t usec, bool utc, struct iso9660_datetime *ret return 0; } -int time_to_iso9660_dir_datetime(usec_t usec, bool utc, struct iso9660_dir_time *ret) { +int iso9660_dir_datetime_from_usec(usec_t usec, bool utc, struct iso9660_dir_time *ret) { struct tm t; int r; @@ -76,15 +76,15 @@ int time_to_iso9660_dir_datetime(usec_t usec, bool utc, struct iso9660_dir_time return 0; } -static bool valid_iso9660_string(const char *str, bool allow_a_chars) { +static bool iso9660_valid_string(const char *str, bool allow_a_chars) { /* note that a-chars are not supposed to accept lower case letters, but it looks like common practice * to use them */ return in_charset(str, allow_a_chars ? UPPERCASE_LETTERS LOWERCASE_LETTERS DIGITS " _!\"%&'()*+,-./:;<=>?" : UPPERCASE_LETTERS DIGITS "_"); } -int set_iso9660_string(char target[], size_t len, const char *source, bool allow_a_chars) { - if (source && !valid_iso9660_string(source, allow_a_chars)) +int iso9660_set_string(char target[], size_t len, const char *source, bool allow_a_chars) { + if (source && !iso9660_valid_string(source, allow_a_chars)) return -EINVAL; if (source) { @@ -101,16 +101,16 @@ int set_iso9660_string(char target[], size_t len, const char *source, bool allow bool iso9660_volume_name_valid(const char *name) { /* In theory the volume identifier should be d-chars, but in practice, a-chars are allowed */ - return valid_iso9660_string(name, /* allow_a_chars= */ true) && + return iso9660_valid_string(name, /* allow_a_chars= */ true) && strlen(name) <= 32; } bool iso9660_system_name_valid(const char *name) { - return valid_iso9660_string(name, /* allow_a_chars= */ true) && + return iso9660_valid_string(name, /* allow_a_chars= */ true) && strlen(name) <= 32; } bool iso9660_publisher_name_valid(const char *name) { - return valid_iso9660_string(name, /* allow_a_chars= */ true) && + return iso9660_valid_string(name, /* allow_a_chars= */ true) && strlen(name) <= 128; } diff --git a/src/repart/iso9660.h b/src/repart/iso9660.h index 23db9e919a417..cdb6eef22678f 100644 --- a/src/repart/iso9660.h +++ b/src/repart/iso9660.h @@ -158,13 +158,13 @@ struct _packed_ el_torito_section_header { char id_string[28]; }; -void no_iso9660_datetime(struct iso9660_datetime *ret); -int time_to_iso9660_datetime(usec_t usec, bool utc, struct iso9660_datetime *ret); -int time_to_iso9660_dir_datetime(usec_t usec, bool utc, struct iso9660_dir_time *ret); -int set_iso9660_string(char target[], size_t len, const char *source, bool allow_a_chars); +void iso9660_datetime_zero(struct iso9660_datetime *ret); +int iso9660_datetime_from_usec(usec_t usec, bool utc, struct iso9660_datetime *ret); +int iso9660_dir_datetime_from_usec(usec_t usec, bool utc, struct iso9660_dir_time *ret); +int iso9660_set_string(char target[], size_t len, const char *source, bool allow_a_chars); -static inline void set_iso9660_const_string(char target[], size_t len, const char *source, bool allow_a_chars) { - assert_se(set_iso9660_string(target, len, source, allow_a_chars) == 0); +static inline void iso9660_set_const_string(char target[], size_t len, const char *source, bool allow_a_chars) { + assert_se(iso9660_set_string(target, len, source, allow_a_chars) == 0); } bool iso9660_volume_name_valid(const char *name); diff --git a/src/repart/repart.c b/src/repart/repart.c index a21ec10da86cc..121cb22193613 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -7769,43 +7769,43 @@ static int write_primary_descriptor( } }; - set_iso9660_const_string(desc.header.identifier, sizeof(desc.header.identifier), "CD001", /* allow_a_chars= */ true); + iso9660_set_const_string(desc.header.identifier, sizeof(desc.header.identifier), "CD001", /* allow_a_chars= */ true); - r = time_to_iso9660_dir_datetime(usec, utc, &desc.root_directory_entry.time); + r = iso9660_dir_datetime_from_usec(usec, utc, &desc.root_directory_entry.time); if (r < 0) return r; - r = set_iso9660_string(desc.system_identifier, sizeof(desc.system_identifier), system_id, /* allow_a_chars= */ true); + r = iso9660_set_string(desc.system_identifier, sizeof(desc.system_identifier), system_id, /* allow_a_chars= */ true); if (r < 0) return r; /* In theory the volume identifier should be d-chars, but in practice, a-chars are allowed */ - r = set_iso9660_string(desc.volume_identifier, sizeof(desc.volume_identifier), volume_id, /* allow_a_chars= */ true); + r = iso9660_set_string(desc.volume_identifier, sizeof(desc.volume_identifier), volume_id, /* allow_a_chars= */ true); if (r < 0) return r; - set_iso9660_const_string(desc.volume_set_identifier, sizeof(desc.volume_set_identifier), NULL, /* allow_a_chars= */ false); + iso9660_set_const_string(desc.volume_set_identifier, sizeof(desc.volume_set_identifier), NULL, /* allow_a_chars= */ false); - r = set_iso9660_string(desc.publisher_identifier, sizeof(desc.publisher_identifier), publisher_id, /* allow_a_chars= */ true); + r = iso9660_set_string(desc.publisher_identifier, sizeof(desc.publisher_identifier), publisher_id, /* allow_a_chars= */ true); if (r < 0) return r; - set_iso9660_const_string(desc.data_preparer_identifier, sizeof(desc.data_preparer_identifier), NULL, /* allow_a_chars= */ true); - set_iso9660_const_string(desc.application_identifier, sizeof(desc.application_identifier), "SYSTEMD-REPART", /* allow_a_chars= */ true); - set_iso9660_const_string(desc.copyright_file_identifier, sizeof(desc.copyright_file_identifier), NULL, /* allow_a_chars= */ false); - set_iso9660_const_string(desc.abstract_file_identifier, sizeof(desc.abstract_file_identifier), NULL, /* allow_a_chars= */ false); - set_iso9660_const_string(desc.bibliographic_file_identifier, sizeof(desc.bibliographic_file_identifier), NULL, /* allow_a_chars= */ false); + iso9660_set_const_string(desc.data_preparer_identifier, sizeof(desc.data_preparer_identifier), NULL, /* allow_a_chars= */ true); + iso9660_set_const_string(desc.application_identifier, sizeof(desc.application_identifier), "SYSTEMD-REPART", /* allow_a_chars= */ true); + iso9660_set_const_string(desc.copyright_file_identifier, sizeof(desc.copyright_file_identifier), NULL, /* allow_a_chars= */ false); + iso9660_set_const_string(desc.abstract_file_identifier, sizeof(desc.abstract_file_identifier), NULL, /* allow_a_chars= */ false); + iso9660_set_const_string(desc.bibliographic_file_identifier, sizeof(desc.bibliographic_file_identifier), NULL, /* allow_a_chars= */ false); - r = time_to_iso9660_datetime(usec, utc, &desc.volume_creation_date); + r = iso9660_datetime_from_usec(usec, utc, &desc.volume_creation_date); if (r < 0) return r; - r = time_to_iso9660_datetime(usec, utc, &desc.volume_modification_date); + r = iso9660_datetime_from_usec(usec, utc, &desc.volume_modification_date); if (r < 0) return r; - no_iso9660_datetime(&desc.volume_expiration_date); - no_iso9660_datetime(&desc.volume_effective_date); + iso9660_datetime_zero(&desc.volume_expiration_date); + iso9660_datetime_zero(&desc.volume_effective_date); ssize_t s = pwrite(fd, &desc, sizeof(desc), ISO9660_PRIMARY_DESCRIPTOR*ISO9660_BLOCK_SIZE); if (s < 0) @@ -7825,7 +7825,7 @@ static int write_eltorito_descriptor(int fd, uint32_t catalog_sector) { .boot_catalog_sector = htole32(catalog_sector), }; - set_iso9660_const_string(desc.header.identifier, sizeof(desc.header.identifier), "CD001", /* allow_a_chars= */ true); + iso9660_set_const_string(desc.header.identifier, sizeof(desc.header.identifier), "CD001", /* allow_a_chars= */ true); strncpy(desc.boot_system_identifier, "EL TORITO SPECIFICATION", sizeof(desc.boot_system_identifier)); @@ -7846,7 +7846,7 @@ static int write_terminal_descriptor(int fd) { }, }; - set_iso9660_const_string(desc.header.identifier, sizeof(desc.header.identifier), "CD001", /* allow_a_chars= */ true); + iso9660_set_const_string(desc.header.identifier, sizeof(desc.header.identifier), "CD001", /* allow_a_chars= */ true); ssize_t s = pwrite(fd, &desc, sizeof(desc), ISO9660_TERMINAL_DESCRIPTOR*ISO9660_BLOCK_SIZE); if (s < 0) @@ -7929,7 +7929,7 @@ static int write_directories(int fd, usec_t usec, bool utc, uint32_t root_sector .ident[0] = 0, /* special value for self */ }; - r = time_to_iso9660_dir_datetime(usec, utc, &self.time); + r = iso9660_dir_datetime_from_usec(usec, utc, &self.time); if (r < 0) return r; @@ -7948,7 +7948,7 @@ static int write_directories(int fd, usec_t usec, bool utc, uint32_t root_sector // TODO: we should probably add some text file explaining there is no content through ISO9660 - r = time_to_iso9660_dir_datetime(usec, utc, &parent.time); + r = iso9660_dir_datetime_from_usec(usec, utc, &parent.time); if (r < 0) return r; From 8953262390fb148803fd2a7aa1e7f994ca6cfd97 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 2 Apr 2026 14:11:00 +0200 Subject: [PATCH 0748/1296] iso9660: add extra paranoia when converting dates the ISO9660 date range and the "struct tm" range are quite different, let's add extra paranoia checks that we can always convert the dates without issues or fail cleanly. --- src/repart/iso9660.c | 43 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/src/repart/iso9660.c b/src/repart/iso9660.c index 3f200942b2d51..ef10d05bf2680 100644 --- a/src/repart/iso9660.c +++ b/src/repart/iso9660.c @@ -21,6 +21,26 @@ void iso9660_datetime_zero(struct iso9660_datetime *ret) { ret->zone = 0; } +static int validate_tm(const struct tm *t) { + assert(t); + + /* Safety checks on bounded fields of struct tm, ranges as per tm(3type). Mostly in place because + * ISO9660 date/time ranges and struct tm ranges differ. */ + + if (t->tm_mon < 0 || t->tm_mon > 11) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Month out of range."); + if (t->tm_mday < 1 || t->tm_mday > 31) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Day of month out of range."); + if (t->tm_hour < 0 || t->tm_hour > 23) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Hour out of range."); + if (t->tm_min < 0 || t->tm_min > 59) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Minute out of range."); + if (t->tm_sec < 0 || t->tm_sec > 60) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Seconds out of range."); + + return 0; +} + int iso9660_datetime_from_usec(usec_t usec, bool utc, struct iso9660_datetime *ret) { struct tm t; int r; @@ -31,11 +51,19 @@ int iso9660_datetime_from_usec(usec_t usec, bool utc, struct iso9660_datetime *r if (r < 0) return r; + r = validate_tm(&t); + if (r < 0) + return r; + if (t.tm_year >= 10000 - 1900) return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Year has more than 4 digits and is incompatible with ISO9660."); if (t.tm_year + 1900 < 0) return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Year is negative and is incompatible with ISO9660."); + long offset = t.tm_gmtoff / (15*60); /* The time zone is encoded by 15 minutes increments */ + if (offset < INT8_MIN || offset > INT8_MAX) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "GMT offset out of range."); + char buf[17]; /* Ignore leap seconds, no real hope for hardware. Deci-seconds always zero. */ xsprintf(buf, "%04d%02d%02d%02d%02d%02d00", @@ -43,8 +71,7 @@ int iso9660_datetime_from_usec(usec_t usec, bool utc, struct iso9660_datetime *r t.tm_hour, t.tm_min, MIN(t.tm_sec, 59)); memcpy(ret, buf, sizeof(buf)-1); - /* The time zone is encoded by 15 minutes increments */ - ret->zone = t.tm_gmtoff / (15*60); + ret->zone = offset; return 0; } @@ -59,8 +86,16 @@ int iso9660_dir_datetime_from_usec(usec_t usec, bool utc, struct iso9660_dir_tim if (r < 0) return r; + r = validate_tm(&t); + if (r < 0) + return r; + if (t.tm_year < 0 || t.tm_year > UINT8_MAX) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Year is incompatible with ISO9660."); + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Year is incompatible with ISO9660."); + + long offset = t.tm_gmtoff / (15*60); /* The time zone is encoded by 15 minutes increments */ + if (offset < INT8_MIN || offset > INT8_MAX) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "GMT offset out of range."); *ret = (struct iso9660_dir_time) { .year = t.tm_year, @@ -70,7 +105,7 @@ int iso9660_dir_datetime_from_usec(usec_t usec, bool utc, struct iso9660_dir_tim .minute = t.tm_min, .second = MIN(t.tm_sec, 59), /* The time zone is encoded by 15 minutes increments */ - .offset = t.tm_gmtoff / (15*60), + .offset = offset, }; return 0; From d723bb753ecd59d3ce0a82882e13258f7265aa3f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 2 Apr 2026 14:12:44 +0200 Subject: [PATCH 0749/1296] iso9660: add extra size regarding buffer size --- src/repart/iso9660.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/repart/iso9660.c b/src/repart/iso9660.c index ef10d05bf2680..72d4c285cb626 100644 --- a/src/repart/iso9660.c +++ b/src/repart/iso9660.c @@ -65,6 +65,7 @@ int iso9660_datetime_from_usec(usec_t usec, bool utc, struct iso9660_datetime *r return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "GMT offset out of range."); char buf[17]; + assert_cc(sizeof(buf)-1 == offsetof(struct iso9660_datetime, zone)); /* Ignore leap seconds, no real hope for hardware. Deci-seconds always zero. */ xsprintf(buf, "%04d%02d%02d%02d%02d%02d00", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, From a678bc69f943431ade55238c919dcc6dfe60f811 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 2 Apr 2026 14:13:13 +0200 Subject: [PATCH 0750/1296] iso9660: small iso9660_set_string() clean-ups --- src/repart/iso9660.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/repart/iso9660.c b/src/repart/iso9660.c index 72d4c285cb626..a555fb9f9e3b6 100644 --- a/src/repart/iso9660.c +++ b/src/repart/iso9660.c @@ -120,15 +120,17 @@ static bool iso9660_valid_string(const char *str, bool allow_a_chars) { } int iso9660_set_string(char target[], size_t len, const char *source, bool allow_a_chars) { - if (source && !iso9660_valid_string(source, allow_a_chars)) - return -EINVAL; + assert(target || len == 0); if (source) { + if (!iso9660_valid_string(source, allow_a_chars)) + return -EINVAL; + size_t slen = strlen(source); if (slen > len) return -EINVAL; - void *p = mempcpy(target, source, slen); - memset(p, ' ', len - slen); + + memset(mempcpy(target, source, slen), ' ', len - slen); } else memset(target, ' ', len); From 4c1e269f00af97f387740982b2f14baa3c77aaf9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 2 Apr 2026 14:13:35 +0200 Subject: [PATCH 0751/1296] iso9660: validate a bunch func params via assert() --- src/repart/repart.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/repart/repart.c b/src/repart/repart.c index 121cb22193613..ecd0518530ff0 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -7741,6 +7741,8 @@ static int write_primary_descriptor( const char *publisher_id) { int r; + assert(fd >= 0); + struct iso9660_primary_volume_descriptor desc = { .header = { .type = 1, @@ -7817,6 +7819,8 @@ static int write_primary_descriptor( } static int write_eltorito_descriptor(int fd, uint32_t catalog_sector) { + assert(fd >= 0); + struct iso9660_eltorito_descriptor desc = { .header = { .type = 0, @@ -7839,6 +7843,8 @@ static int write_eltorito_descriptor(int fd, uint32_t catalog_sector) { } static int write_terminal_descriptor(int fd) { + assert(fd >= 0); + struct iso9660_terminal_descriptor desc = { .header = { .type = 255, @@ -7858,6 +7864,7 @@ static int write_terminal_descriptor(int fd) { } static uint16_t calculate_validation_entry_checksum(const void *p, size_t size) { + assert(p || size == 0); assert(size % 2 == 0); uint16_t checksum = 0; @@ -7869,6 +7876,8 @@ static uint16_t calculate_validation_entry_checksum(const void *p, size_t size) } static int write_boot_catalog(int fd, uint32_t load_block) { + assert(fd >= 0); + struct el_torito_validation_entry ve = { .header_indicator = 1, .platform = 0xef, /* EFI */ @@ -7914,6 +7923,8 @@ static int write_boot_catalog(int fd, uint32_t load_block) { static int write_directories(int fd, usec_t usec, bool utc, uint32_t root_sector) { int r; + assert(fd >= 0); + uint32_t dir_size = 2*sizeof(struct iso9660_directory_entry); /* 2 entries with ident size 1: . and .. */ struct iso9660_directory_entry self = { @@ -7970,6 +7981,8 @@ static int write_directories(int fd, usec_t usec, bool utc, uint32_t root_sector static int write_eltorito(int fd, usec_t usec, bool utc, uint32_t load_block, const char *system_id, const char *volume_id, const char *publisher_id) { int r; + assert(fd >= 0); + r = write_primary_descriptor(fd, ISO9660_ROOT_DIRECTORY, usec, utc, system_id, volume_id, publisher_id); if (r < 0) return r; From 56245fe67fd1779dc83a20dd3850a8020fefeffa Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sat, 4 Apr 2026 08:17:29 +0200 Subject: [PATCH 0752/1296] iso9660: line break overly long parameter list --- src/repart/repart.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index ecd0518530ff0..9ed4815aee98a 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -7920,7 +7920,12 @@ static int write_boot_catalog(int fd, uint32_t load_block) { return 0; } -static int write_directories(int fd, usec_t usec, bool utc, uint32_t root_sector) { +static int write_directories( + int fd, + usec_t usec, + bool utc, + uint32_t root_sector) { + int r; assert(fd >= 0); @@ -7978,7 +7983,15 @@ static int write_directories(int fd, usec_t usec, bool utc, uint32_t root_sector return 0; } -static int write_eltorito(int fd, usec_t usec, bool utc, uint32_t load_block, const char *system_id, const char *volume_id, const char *publisher_id) { +static int write_eltorito( + int fd, + usec_t usec, + bool utc, + uint32_t load_block, /* in iso9660 blocks */ + const char *system_id, + const char *volume_id, + const char *publisher_id) { + int r; assert(fd >= 0); From d2d7bd13db5104b51a830002fee439eb3c94cdfd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sat, 4 Apr 2026 08:20:18 +0200 Subject: [PATCH 0753/1296] update TODO --- TODO | 2 -- 1 file changed, 2 deletions(-) diff --git a/TODO b/TODO index 09eeda07ef958..37345f4a754c0 100644 --- a/TODO +++ b/TODO @@ -1354,8 +1354,6 @@ Features: * automatic boot assessment: add one more default success check that just waits for a bit after boot, and blesses the boot if the system stayed up that long. -* systemd-repart: add support for generating ISO9660 images - * systemd-repart: in addition to the existing "factory reset" mode (which simply empties existing partitions marked for that). add a mode where partitions marked for it are entirely removed. Use case: remove secondary OS From 71c15cdbeaf6e58fa57e6c3f769e906ec0727c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 4 Apr 2026 11:24:45 +0200 Subject: [PATCH 0754/1296] TEST-74-AUX-UTILS: check for failed units after capsule test TEST-74-AUX-UTILS has a number of subtests. test/units/TEST-74-AUX-UTILS.capsule.sh runs first and starts and stops capsule@foobar.service. Looking at the test, the unit is cleanly stopped. But later test/units/TEST-74-AUX-UTILS.machine-id-setup.sh tests for failed units. capsule@foobar.service is listed as failed, causing the second subtest to fail. Add the same test in test/units/TEST-74-AUX-UTILS.capsule.sh to see if the failed test really originates from there. --- test/units/TEST-74-AUX-UTILS.capsule.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/units/TEST-74-AUX-UTILS.capsule.sh b/test/units/TEST-74-AUX-UTILS.capsule.sh index c2c3073ea80a0..c32f7b9b5716a 100755 --- a/test/units/TEST-74-AUX-UTILS.capsule.sh +++ b/test/units/TEST-74-AUX-UTILS.capsule.sh @@ -58,3 +58,8 @@ systemctl clean capsule@foobar.service --what=all (! test -f /run/capsules/foobar ) (! test -f /var/lib/capsules/foobar ) (! id -u c-foobar ) + +systemctl status capsule@foobar.service || : + +systemctl --state=failed --no-legend --no-pager | tee /failed +test ! -s /failed From 4ae4bd34d703560eabf7cd4b9e6f5bc3b7db8138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Santamar=C3=ADa=20Rogado?= Date: Fri, 3 Apr 2026 20:59:42 +0200 Subject: [PATCH 0755/1296] hwdb: sensor: aquarius improve comments Differentiate Cmp NS483 v2 accel variants. --- hwdb.d/60-sensor.hwdb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hwdb.d/60-sensor.hwdb b/hwdb.d/60-sensor.hwdb index 480dab4cd2b2c..a8e990de97913 100644 --- a/hwdb.d/60-sensor.hwdb +++ b/hwdb.d/60-sensor.hwdb @@ -114,10 +114,10 @@ sensor:modalias:acpi:BMA250E:*:dmi:*:svnAcer:*:rnAigner:* # Iconia Tab 8W ######################################### sensor:modalias:acpi:MXC6655:*:dmi:*:svnAquarius:pnNS483:* # Cmp NS483 -sensor:modalias:acpi:MXC4005:*:dmi:*:svnAquarius:pnCmpNS483:* # Cmp NS483 v2 +sensor:modalias:acpi:MXC4005:*:dmi:*:svnAquarius:pnCmpNS483:* # Cmp NS483 v2 (MXC4005 accel) ACCEL_MOUNT_MATRIX=-1, 0, 0; 0, 1, 0; 0, 0, 1 -sensor:modalias:acpi:MXC6655:*:dmi:*:svnAquarius:pnCmpNS483:* # Cmp NS483 v2 +sensor:modalias:acpi:MXC6655:*:dmi:*:svnAquarius:pnCmpNS483:* # Cmp NS483 v2 (MXC6655 accel) ACCEL_MOUNT_MATRIX=-1, 0, 0; 0, -1, 0; 0, 0, 1 ######################################### From 0245bf7d7e32db6539faa3fcb7d7c6106860cd08 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 3 Apr 2026 19:53:42 +0000 Subject: [PATCH 0756/1296] repart: Split out El Torito boot catalog writing into context_eltorito() Writing the El Torito boot catalog should be independent of writing the partition table. Previously, the El Torito logic was embedded in context_write_partition_table(), which meant it was skipped when the partition table hadn't changed. Extract it into a separate context_eltorito() function and invoke it after context_write_partition_table() so the boot catalog is always written when enabled. Also move the overlap verification to be done as soon as we have all the necessary information to do the check and before doing any expensive work; --- src/repart/repart.c | 66 +++++++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index 9ed4815aee98a..0417d4133a9cd 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -8020,7 +8020,7 @@ static int write_eltorito( } static int context_verify_eltorito_overlap(Context *context) { - /* before writing the partition table, we check if we have collision with ISO9660 */ + /* Check if the partition table collides with the ISO9660 El Torito area. */ assert(context); if (!arg_eltorito) @@ -8138,10 +8138,6 @@ static int context_write_partition_table(Context *context) { (void) context_notify(context, PROGRESS_WRITING_TABLE, /* object= */ NULL, UINT_MAX); - r = context_verify_eltorito_overlap(context); - if (r < 0) - return r; - r = fdisk_write_disklabel(context->fdisk_context); if (r < 0) return log_error_errno(r, "Failed to write partition table: %m"); @@ -8161,25 +8157,39 @@ static int context_write_partition_table(Context *context) { } else log_notice("Not telling kernel to reread partition table, because selected image does not support kernel partition block devices."); - if (arg_eltorito) { - bool utc = true; - usec_t usec = parse_source_date_epoch(); - if (usec == USEC_INFINITY) { - usec = now(CLOCK_REALTIME); - utc = false; - } + log_info("Partition table written."); - uint64_t esp_offset; - r = context_find_esp_offset(context, &esp_offset); - if (r < 0) - return r; + return 0; +} - r = write_eltorito(fdisk_get_devfd(context->fdisk_context), usec, utc, esp_offset / ISO9660_BLOCK_SIZE, arg_eltorito_system, arg_eltorito_volume, arg_eltorito_publisher); - if (r < 0) - return log_error_errno(r, "Failed to write El Torito boot catalog: %m"); +static int context_write_eltorito(Context *context) { + int r; + + assert(context); + + if (!arg_eltorito) + return 0; + + if (context->dry_run) + return 0; + + bool utc = true; + usec_t usec = parse_source_date_epoch(); + if (usec == USEC_INFINITY) { + usec = now(CLOCK_REALTIME); + utc = false; } - log_info("All done."); + uint64_t esp_offset; + r = context_find_esp_offset(context, &esp_offset); + if (r < 0) + return r; + + log_info("Writing El Torito boot catalog."); + + r = write_eltorito(fdisk_get_devfd(context->fdisk_context), usec, utc, esp_offset / ISO9660_BLOCK_SIZE, arg_eltorito_system, arg_eltorito_volume, arg_eltorito_publisher); + if (r < 0) + return log_error_errno(r, "Failed to write El Torito boot catalog: %m"); return 0; } @@ -11065,6 +11075,10 @@ static int vl_method_run( SD_JSON_BUILD_PAIR_UNSIGNED("minimalSizeBytes", minimal_size)); } + r = context_verify_eltorito_overlap(context); + if (r < 0) + return r; + r = context_ponder(context); if (r == -ENOSPC) { uint64_t current_size, foreign_size, minimal_size; @@ -11113,6 +11127,10 @@ static int vl_method_run( if (r < 0) return r; + r = context_write_eltorito(context); + if (r < 0) + return r; + context_disarm_auto_removal(context); return sd_varlink_reply(link, NULL); @@ -11379,6 +11397,10 @@ static int run(int argc, char *argv[]) { return r; } + r = context_verify_eltorito_overlap(context); + if (r < 0) + return r; + r = context_ponder(context); if (r == -ENOSPC) { /* When we hit space issues, tell the user the minimal size. */ @@ -11394,6 +11416,10 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; + r = context_write_eltorito(context); + if (r < 0) + return r; + r = context_split(context); if (r < 0) return r; From 4e5c605d4b3e29c42c4838ef7181e4db7bc28542 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 30 Mar 2026 18:59:09 +0000 Subject: [PATCH 0757/1296] boot: use EFI_DISK_IO_PROTOCOL instead of EFI_BLOCK_IO_PROTOCOL for disk reads EFI_DISK_IO_PROTOCOL (UEFI spec section 13.7, https://uefi.org/specs/UEFI/2.10/13_Protocols_Media_Access.html#disk-i-o-protocol) supports reads at arbitrary byte offsets with no alignment requirements on the buffer. The UEFI spec mandates that firmware produces this protocol on every handle that also has EFI_BLOCK_IO_PROTOCOL, so it is always available. This is a better fit than EFI_BLOCK_IO_PROTOCOL for our GPT parsing and BitLocker detection because Block I/O requires that both the read offset (LBA) and the buffer are aligned to the media's IoAlign value. Meeting that constraint forces us to use xmalloc_aligned_pages() with PHYSICAL_ADDRESS_TO_POINTER(), page-granularity allocations, and manual size rounding (ALIGN_TO). Disk I/O handles all of that internally, so callers can use plain xmalloc() or even stack buffers and read exactly the number of bytes they need. Co-developed-by: Claude Opus 4.6 --- src/boot/boot.c | 24 +++++------ src/boot/part-discovery.c | 87 +++++++++++++++++---------------------- src/boot/proto/disk-io.h | 24 +++++++++++ 3 files changed, 73 insertions(+), 62 deletions(-) create mode 100644 src/boot/proto/disk-io.h diff --git a/src/boot/boot.c b/src/boot/boot.c index 904f9bf589457..2492f474b405b 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -20,6 +20,7 @@ #include "part-discovery.h" #include "pe.h" #include "proto/block-io.h" +#include "proto/disk-io.h" #include "proto/load-file.h" #include "proto/simple-text-io.h" #include "random-seed.h" @@ -2165,22 +2166,19 @@ static EFI_STATUS call_boot_windows_bitlocker(const BootEntry *entry, EFI_FILE * if (err != EFI_SUCCESS || block_io->Media->BlockSize < 512 || block_io->Media->BlockSize > 4096) continue; - #define BLOCK_IO_BUFFER_SIZE 4096 - _cleanup_pages_ Pages buf_pages = xmalloc_aligned_pages( - AllocateMaxAddress, - EfiLoaderData, - EFI_SIZE_TO_PAGES(BLOCK_IO_BUFFER_SIZE), - block_io->Media->IoAlign, - /* On 32-bit allocate below 4G boundary as we can't easily access anything above that. - * 64-bit platforms don't suffer this limitation, so we can allocate from anywhere. - * addr= */ UINTPTR_MAX); - char *buf = PHYSICAL_ADDRESS_TO_POINTER(buf_pages.addr); - - err = block_io->ReadBlocks(block_io, block_io->Media->MediaId, /* LBA= */ 0, BLOCK_IO_BUFFER_SIZE, buf); + EFI_DISK_IO_PROTOCOL *disk_io; + err = BS->HandleProtocol(handles[i], MAKE_GUID_PTR(EFI_DISK_IO_PROTOCOL), (void **) &disk_io); + if (err != EFI_SUCCESS) { + log_debug_status(err, "Failed to get disk I/O protocol: %m"); + continue; + } + + char buf[STRLEN("-FVE-FS-")]; + err = disk_io->ReadDisk(disk_io, block_io->Media->MediaId, /* Offset= */ 3, sizeof(buf), buf); if (err != EFI_SUCCESS) continue; - if (memcmp(buf + 3, "-FVE-FS-", STRLEN("-FVE-FS-")) == 0) { + if (memcmp(buf, "-FVE-FS-", STRLEN("-FVE-FS-")) == 0) { found = true; break; } diff --git a/src/boot/part-discovery.c b/src/boot/part-discovery.c index 25f60521acf30..60959d4f78453 100644 --- a/src/boot/part-discovery.c +++ b/src/boot/part-discovery.c @@ -4,6 +4,7 @@ #include "part-discovery.h" #include "proto/block-io.h" #include "proto/device-path.h" +#include "proto/disk-io.h" #include "util.h" typedef struct { @@ -72,80 +73,60 @@ static bool verify_gpt(/* const */ GptHeader *h, EFI_LBA lba_expected) { static EFI_STATUS try_gpt( const EFI_GUID *type, - EFI_BLOCK_IO_PROTOCOL *block_io, + EFI_DISK_IO_PROTOCOL *disk_io, + uint32_t media_id, + uint32_t block_size, EFI_LBA lba, EFI_LBA *ret_backup_lba, /* May be changed even on error! */ HARDDRIVE_DEVICE_PATH *ret_hd) { - EFI_PARTITION_ENTRY *entries; - _cleanup_pages_ Pages gpt_pages = {}; - _cleanup_pages_ Pages entries_pages = {}; - GptHeader *gpt; + GptHeader gpt; EFI_STATUS err; uint32_t crc32; size_t size; - assert(block_io); - assert(block_io->Media); + assert(disk_io); assert(ret_hd); - gpt_pages = xmalloc_aligned_pages( - AllocateMaxAddress, - EfiLoaderData, - EFI_SIZE_TO_PAGES(sizeof(GptHeader)), - block_io->Media->IoAlign, - /* On 32-bit allocate below 4G boundary as we can't easily access anything above that. - * 64-bit platforms don't suffer this limitation, so we can allocate from anywhere. - * addr= */ UINTPTR_MAX); - gpt = PHYSICAL_ADDRESS_TO_POINTER(gpt_pages.addr); - /* Read the GPT header */ - err = block_io->ReadBlocks( - block_io, - block_io->Media->MediaId, - lba, - sizeof(*gpt), gpt); + uint64_t offset; + if (!MUL_SAFE(&offset, lba, block_size)) + return EFI_INVALID_PARAMETER; + + err = disk_io->ReadDisk(disk_io, media_id, offset, sizeof(gpt), &gpt); if (err != EFI_SUCCESS) return err; /* Indicate the location of backup LBA even if the rest of the header is corrupt. */ if (ret_backup_lba) - *ret_backup_lba = gpt->AlternateLBA; + *ret_backup_lba = gpt.AlternateLBA; - if (!verify_gpt(gpt, lba)) + if (!verify_gpt(&gpt, lba)) return EFI_NOT_FOUND; /* Now load the GPT entry table */ - size = ALIGN_TO((size_t) gpt->SizeOfPartitionEntry * (size_t) gpt->NumberOfPartitionEntries, 512); + size = (size_t) gpt.SizeOfPartitionEntry * (size_t) gpt.NumberOfPartitionEntries; if (size == SIZE_MAX) /* overflow check */ return EFI_OUT_OF_RESOURCES; - entries_pages = xmalloc_aligned_pages( - AllocateMaxAddress, - EfiLoaderData, - EFI_SIZE_TO_PAGES(size), - block_io->Media->IoAlign, - /* On 32-bit allocate below 4G boundary as we can't easily access anything above that. - * 64-bit platforms don't suffer this limitation, so we can allocate from anywhere. - * addr= */ UINTPTR_MAX); - entries = PHYSICAL_ADDRESS_TO_POINTER(entries_pages.addr); - - err = block_io->ReadBlocks( - block_io, - block_io->Media->MediaId, - gpt->PartitionEntryLBA, - size, entries); + + _cleanup_free_ void *entries = xmalloc(size); + + if (!MUL_SAFE(&offset, gpt.PartitionEntryLBA, block_size)) + return EFI_INVALID_PARAMETER; + + err = disk_io->ReadDisk(disk_io, media_id, offset, size, entries); if (err != EFI_SUCCESS) return err; /* Calculate CRC of entries array, too */ err = BS->CalculateCrc32(entries, size, &crc32); - if (err != EFI_SUCCESS || crc32 != gpt->PartitionEntryArrayCRC32) + if (err != EFI_SUCCESS || crc32 != gpt.PartitionEntryArrayCRC32) return EFI_CRC_ERROR; /* Now we can finally look for xbootloader partitions. */ - for (size_t i = 0; i < gpt->NumberOfPartitionEntries; i++) { + for (size_t i = 0; i < gpt.NumberOfPartitionEntries; i++) { EFI_PARTITION_ENTRY *entry = - (EFI_PARTITION_ENTRY *) ((uint8_t *) entries + gpt->SizeOfPartitionEntry * i); + (EFI_PARTITION_ENTRY *) ((uint8_t *) entries + gpt.SizeOfPartitionEntry * i); if (!efi_guid_equal(&entry->PartitionTypeGUID, type)) continue; @@ -184,7 +165,7 @@ static EFI_STATUS find_device(const EFI_GUID *type, EFI_HANDLE *device, EFI_DEVI EFI_DEVICE_PATH *partition_path; err = BS->HandleProtocol(device, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &partition_path); if (err != EFI_SUCCESS) - return err; + return log_debug_status(err, "Failed to get device path: %m"); /* Find the (last) partition node itself. */ EFI_DEVICE_PATH *part_node = NULL; @@ -196,8 +177,10 @@ static EFI_STATUS find_device(const EFI_GUID *type, EFI_HANDLE *device, EFI_DEVI part_node = node; } - if (!part_node) + if (!part_node) { + log_debug("No hard drive device path node found."); return EFI_NOT_FOUND; + } /* Chop off the partition part, leaving us with the full path to the disk itself. */ _cleanup_free_ EFI_DEVICE_PATH *disk_path = NULL; @@ -207,7 +190,7 @@ static EFI_STATUS find_device(const EFI_GUID *type, EFI_HANDLE *device, EFI_DEVI EFI_BLOCK_IO_PROTOCOL *block_io; err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), &p, &disk_handle); if (err != EFI_SUCCESS) - return err; + return log_debug_status(err, "Failed to locate disk device: %m"); /* The drivers for other partitions on this drive may not be initialized on fastboot firmware, so we * have to ask the firmware to do just that. */ @@ -215,16 +198,22 @@ static EFI_STATUS find_device(const EFI_GUID *type, EFI_HANDLE *device, EFI_DEVI err = BS->HandleProtocol(disk_handle, MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), (void **) &block_io); if (err != EFI_SUCCESS) - return err; + return log_debug_status(err, "Failed to get block I/O protocol: %m"); /* Filter out some block devices early. (We only care about block devices that aren't * partitions themselves — we look for GPT partition tables to parse after all —, and only * those which contain a medium and have at least 2 blocks.) */ if (block_io->Media->LogicalPartition || !block_io->Media->MediaPresent || - block_io->Media->LastBlock <= 1) + block_io->Media->LastBlock <= 1 || + block_io->Media->BlockSize < 512 || block_io->Media->BlockSize > 4096) return EFI_NOT_FOUND; + EFI_DISK_IO_PROTOCOL *disk_io; + err = BS->HandleProtocol(disk_handle, MAKE_GUID_PTR(EFI_DISK_IO_PROTOCOL), (void **) &disk_io); + if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to get disk I/O protocol: %m"); + /* Try several copies of the GPT header, in case one is corrupted */ EFI_LBA backup_lba = 0; for (size_t nr = 0; nr < 3; nr++) { @@ -243,7 +232,7 @@ static EFI_STATUS find_device(const EFI_GUID *type, EFI_HANDLE *device, EFI_DEVI continue; HARDDRIVE_DEVICE_PATH hd; - err = try_gpt(type, block_io, lba, + err = try_gpt(type, disk_io, block_io->Media->MediaId, block_io->Media->BlockSize, lba, nr == 0 ? &backup_lba : NULL, /* Only get backup LBA location from first GPT header. */ &hd); if (err != EFI_SUCCESS) { diff --git a/src/boot/proto/disk-io.h b/src/boot/proto/disk-io.h new file mode 100644 index 0000000000000..d758a8eb2897e --- /dev/null +++ b/src/boot/proto/disk-io.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "efi.h" + +#define EFI_DISK_IO_PROTOCOL_GUID \ + GUID_DEF(0xCE345171, 0xBA0B, 0x11d2, 0x8e, 0x4F, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b) + +typedef struct EFI_DISK_IO_PROTOCOL EFI_DISK_IO_PROTOCOL; +struct EFI_DISK_IO_PROTOCOL { + uint64_t Revision; + EFI_STATUS (EFIAPI *ReadDisk)( + EFI_DISK_IO_PROTOCOL *This, + uint32_t MediaId, + uint64_t Offset, + size_t BufferSize, + void *Buffer); + EFI_STATUS (EFIAPI *WriteDisk)( + EFI_DISK_IO_PROTOCOL *This, + uint32_t MediaId, + uint64_t Offset, + size_t BufferSize, + const void *Buffer); +}; From 37b852c948759a977e2577dbf635f12d90caba7d Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 30 Mar 2026 21:20:46 +0200 Subject: [PATCH 0758/1296] boot: Split out read_gpt_entries() --- src/boot/part-discovery.c | 77 +++++++++++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 20 deletions(-) diff --git a/src/boot/part-discovery.c b/src/boot/part-discovery.c index 60959d4f78453..cafc3aa3e7152 100644 --- a/src/boot/part-discovery.c +++ b/src/boot/part-discovery.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "device-path-util.h" +#include "efi-log.h" #include "part-discovery.h" #include "proto/block-io.h" #include "proto/device-path.h" @@ -71,14 +72,14 @@ static bool verify_gpt(/* const */ GptHeader *h, EFI_LBA lba_expected) { return true; } -static EFI_STATUS try_gpt( - const EFI_GUID *type, +static EFI_STATUS read_gpt_entries( EFI_DISK_IO_PROTOCOL *disk_io, uint32_t media_id, uint32_t block_size, EFI_LBA lba, - EFI_LBA *ret_backup_lba, /* May be changed even on error! */ - HARDDRIVE_DEVICE_PATH *ret_hd) { + EFI_LBA *reterr_backup_lba, /* May be changed even on error! */ + GptHeader *ret_gpt, + void **ret_entries) { GptHeader gpt; EFI_STATUS err; @@ -86,44 +87,80 @@ static EFI_STATUS try_gpt( size_t size; assert(disk_io); - assert(ret_hd); + assert(ret_gpt); + assert(ret_entries); - /* Read the GPT header */ uint64_t offset; if (!MUL_SAFE(&offset, lba, block_size)) - return EFI_INVALID_PARAMETER; + return log_debug_status( + EFI_INVALID_PARAMETER, + "LBA %" PRIu64 " * block size %" PRIu32 " overflow: %m", + lba, + block_size); err = disk_io->ReadDisk(disk_io, media_id, offset, sizeof(gpt), &gpt); if (err != EFI_SUCCESS) - return err; + return log_debug_status(err, "Failed to read GPT header at LBA %" PRIu64 ": %m", lba); - /* Indicate the location of backup LBA even if the rest of the header is corrupt. */ - if (ret_backup_lba) - *ret_backup_lba = gpt.AlternateLBA; + /* Expose backup LBA even if the rest of the header is corrupt, so the caller can + * try the backup GPT. */ + if (reterr_backup_lba) + *reterr_backup_lba = gpt.AlternateLBA; if (!verify_gpt(&gpt, lba)) - return EFI_NOT_FOUND; + return log_debug_status(EFI_NOT_FOUND, "GPT header at LBA %" PRIu64 " is not valid: %m", lba); - /* Now load the GPT entry table */ size = (size_t) gpt.SizeOfPartitionEntry * (size_t) gpt.NumberOfPartitionEntries; if (size == SIZE_MAX) /* overflow check */ - return EFI_OUT_OF_RESOURCES; + return log_debug_status(EFI_OUT_OF_RESOURCES, "GPT partition entries size overflow: %m"); _cleanup_free_ void *entries = xmalloc(size); if (!MUL_SAFE(&offset, gpt.PartitionEntryLBA, block_size)) - return EFI_INVALID_PARAMETER; + return log_debug_status( + EFI_INVALID_PARAMETER, + "Partition entry LBA %" PRIu64 " * block size %" PRIu32 " overflow: %m", + gpt.PartitionEntryLBA, + block_size); err = disk_io->ReadDisk(disk_io, media_id, offset, size, entries); if (err != EFI_SUCCESS) - return err; + return log_debug_status(err, "Failed to read GPT partition entries at LBA %" PRIu64 ": %m", gpt.PartitionEntryLBA); - /* Calculate CRC of entries array, too */ err = BS->CalculateCrc32(entries, size, &crc32); - if (err != EFI_SUCCESS || crc32 != gpt.PartitionEntryArrayCRC32) - return EFI_CRC_ERROR; + if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to calculate CRC32 of GPT partition entries: %m"); + if (crc32 != gpt.PartitionEntryArrayCRC32) + return log_debug_status( + EFI_CRC_ERROR, + "GPT partition entries CRC32 mismatch (got 0x%08" PRIx32 ", expected 0x%08" PRIx32 "): %m", + crc32, + gpt.PartitionEntryArrayCRC32); + + *ret_gpt = gpt; + *ret_entries = TAKE_PTR(entries); + return EFI_SUCCESS; +} + +static EFI_STATUS try_gpt( + const EFI_GUID *type, + EFI_DISK_IO_PROTOCOL *disk_io, + uint32_t media_id, + uint32_t block_size, + EFI_LBA lba, + EFI_LBA *reterr_backup_lba, /* May be changed even on error! */ + HARDDRIVE_DEVICE_PATH *ret_hd) { + + GptHeader gpt; + _cleanup_free_ void *entries = NULL; + EFI_STATUS err; + + assert(ret_hd); + + err = read_gpt_entries(disk_io, media_id, block_size, lba, reterr_backup_lba, &gpt, &entries); + if (err != EFI_SUCCESS) + return err; - /* Now we can finally look for xbootloader partitions. */ for (size_t i = 0; i < gpt.NumberOfPartitionEntries; i++) { EFI_PARTITION_ENTRY *entry = (EFI_PARTITION_ENTRY *) ((uint8_t *) entries + gpt.SizeOfPartitionEntry * i); From 05e1ca31447fc75e591edb3bca20c0acf2b3b682 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 30 Mar 2026 19:23:10 +0000 Subject: [PATCH 0759/1296] boot: add El Torito CDROM partition UUID discovery When booting from a CD-ROM via El Torito, the UEFI device path contains a CDROM_DEVICE_PATH node instead of a HARDDRIVE_DEVICE_PATH node. Unlike the hard drive variant, the CDROM node does not carry a partition UUID, so systemd-boot previously could not determine the boot partition UUID in this scenario. Add disk_get_part_uuid_cdrom() which recovers the partition UUID by reading the GPT from the underlying disk. Since ISO images are commonly mastered with 512-byte GPT sectors on media with 2048-byte blocks, the function probes for the GPT header at multiple sector sizes (512, 1024, 2048, 4096) and matches the partition by comparing byte offsets between the CDROM node's PartitionStart and each GPT entry's StartingLBA. The function reuses read_gpt_entries() for GPT parsing and adds debug logging for each failure path to aid diagnosis on real hardware. Also adds the CDROM_DEVICE_PATH struct and MEDIA_CDROM_DP subtype constant to device-path.h, and fixes disk_get_part_uuid() to preserve the original device path pointer so it can be passed to the CDROM fallback. Co-developed-by: Claude Opus 4.6 --- src/boot/part-discovery.c | 136 +++++++++++++++++++++++++++++++++-- src/boot/proto/device-path.h | 9 +++ 2 files changed, 141 insertions(+), 4 deletions(-) diff --git a/src/boot/part-discovery.c b/src/boot/part-discovery.c index cafc3aa3e7152..9249dc24a18d8 100644 --- a/src/boot/part-discovery.c +++ b/src/boot/part-discovery.c @@ -6,6 +6,7 @@ #include "proto/block-io.h" #include "proto/device-path.h" #include "proto/disk-io.h" +#include "string-util-fundamental.h" #include "util.h" typedef struct { @@ -319,6 +320,132 @@ EFI_STATUS partition_open(const EFI_GUID *type, EFI_HANDLE *device, EFI_HANDLE * return EFI_SUCCESS; } +static char16_t* disk_get_part_uuid_cdrom(const EFI_DEVICE_PATH *dp) { + EFI_STATUS err; + + assert(dp); + + /* When booting from a CD-ROM via El Torito, the device path contains a CDROM node instead of + * a HARDDRIVE node. The CDROM node doesn't carry a partition UUID, so we need to read the GPT + * from the underlying disk to find it. */ + + const CDROM_DEVICE_PATH *cdrom = NULL; + for (const EFI_DEVICE_PATH *node = dp; !device_path_is_end(node); node = device_path_next_node(node)) + if (node->Type == MEDIA_DEVICE_PATH && node->SubType == MEDIA_CDROM_DP) + cdrom = (const CDROM_DEVICE_PATH *) node; + if (!cdrom) { + log_debug("No CDROM device path node found."); + return NULL; + } + + /* Chop off the CDROM node to get the whole-disk device path */ + _cleanup_free_ EFI_DEVICE_PATH *disk_path = device_path_replace_node(dp, &cdrom->Header, /* new_node= */ NULL); + + EFI_DEVICE_PATH *remaining = disk_path; + EFI_HANDLE disk_handle; + err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), &remaining, &disk_handle); + if (err != EFI_SUCCESS) { + log_debug_status(err, "Failed to locate disk device for CDROM: %m"); + return NULL; + } + + (void) BS->ConnectController(disk_handle, /* DriverImageHandle= */ NULL, /* RemainingDevicePath= */ NULL, /* Recursive= */ true); + + EFI_BLOCK_IO_PROTOCOL *block_io; + err = BS->HandleProtocol(disk_handle, MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), (void **) &block_io); + if (err != EFI_SUCCESS) { + log_debug_status(err, "Failed to get block I/O protocol for CDROM disk: %m"); + return NULL; + } + + if (block_io->Media->LogicalPartition || !block_io->Media->MediaPresent || + block_io->Media->LastBlock <= 1) { + log_debug("CDROM disk has unsuitable media (partition=%ls, present=%ls, lastblock=%" PRIu64 ").", + yes_no(block_io->Media->LogicalPartition), + yes_no(block_io->Media->MediaPresent), + (uint64_t) block_io->Media->LastBlock); + return NULL; + } + + uint32_t iso9660_block_size = block_io->Media->BlockSize; + if (iso9660_block_size < 512 || iso9660_block_size > 4096 || !ISPOWEROF2(iso9660_block_size)) { + log_debug("Unexpected CDROM block size %" PRIu32 ", skipping.", iso9660_block_size); + return NULL; + } + + EFI_DISK_IO_PROTOCOL *disk_io; + err = BS->HandleProtocol(disk_handle, MAKE_GUID_PTR(EFI_DISK_IO_PROTOCOL), (void **) &disk_io); + if (err != EFI_SUCCESS) { + log_debug_status(err, "Failed to get disk I/O protocol for CDROM disk: %m"); + return NULL; + } + + uint32_t media_id = block_io->Media->MediaId; + + /* Probe for the GPT header at multiple possible sector sizes (512, 1024, 2048, 4096). + * The GPT header is at LBA 1, i.e. byte offset == sector_size. On CD-ROMs, the GPT + * may use a different sector size than the media's block size (e.g. 512-byte GPT sectors + * on 2048-byte CD-ROM blocks), so we try all possibilities. If the primary GPT header is + * corrupt but contains a valid backup LBA, fall back to the backup header. */ + uint32_t gpt_sector_size = 0; + GptHeader gpt; + _cleanup_free_ void *entries = NULL; + for (uint32_t ss = 512; ss <= 4096; ss <<= 1) { + EFI_LBA backup_lba = 0; + + err = read_gpt_entries(disk_io, media_id, ss, /* lba= */ 1, &backup_lba, &gpt, &entries); + if (err == EFI_SUCCESS) { + gpt_sector_size = ss; + break; + } + if (err != EFI_NOT_FOUND) + log_debug_status(err, "Failed to read primary GPT header at sector size %"PRIu32", ignoring: %m", ss); + + if (backup_lba != 0) { + err = read_gpt_entries(disk_io, media_id, ss, backup_lba, /* reterr_backup_lba= */ NULL, &gpt, &entries); + if (err == EFI_SUCCESS) { + gpt_sector_size = ss; + break; + } + if (err != EFI_NOT_FOUND) + log_debug_status(err, "Failed to read backup GPT header at sector size %"PRIu32", ignoring: %m", ss); + } + } + + if (gpt_sector_size == 0) { + log_debug("No valid GPT found on CDROM at any sector size."); + return NULL; + } + + log_debug("Found GPT on CDROM with sector size %" PRIu32 ", %" PRIu32 " partition entries.", + gpt_sector_size, gpt.NumberOfPartitionEntries); + + /* Find the partition whose byte offset matches the CDROM's PartitionStart. + * CDROM PartitionStart is in media iso9660_block_size units, GPT StartingLBA is in gpt_sector_size units. */ + uint64_t cdrom_start; + if (!MUL_SAFE(&cdrom_start, cdrom->PartitionStart, iso9660_block_size)) { + log_debug("CDROM start offset overflow."); + return NULL; + } + + for (size_t i = 0; i < gpt.NumberOfPartitionEntries; i++) { + const EFI_PARTITION_ENTRY *entry = + (const EFI_PARTITION_ENTRY *) ((const uint8_t *) entries + gpt.SizeOfPartitionEntry * i); + + if (!efi_guid_equal(&entry->PartitionTypeGUID, &(const EFI_GUID) ESP_GUID)) + continue; + + uint64_t entry_start; + if (MUL_SAFE(&entry_start, entry->StartingLBA, gpt_sector_size) && + entry_start == cdrom_start) + return xasprintf(GUID_FORMAT_STR, GUID_FORMAT_VAL(entry->UniquePartitionGUID)); + } + + log_debug("No ESP partition matches CDROM start offset %" PRIu64 " (block size %" PRIu32 ").", + cdrom->PartitionStart, iso9660_block_size); + return NULL; +} + char16_t *disk_get_part_uuid(EFI_HANDLE *handle) { EFI_STATUS err; EFI_DEVICE_PATH *dp; @@ -332,16 +459,17 @@ char16_t *disk_get_part_uuid(EFI_HANDLE *handle) { if (err != EFI_SUCCESS) return NULL; - for (; !device_path_is_end(dp); dp = device_path_next_node(dp)) { - if (dp->Type != MEDIA_DEVICE_PATH || dp->SubType != MEDIA_HARDDRIVE_DP) + for (EFI_DEVICE_PATH *node = dp; !device_path_is_end(node); node = device_path_next_node(node)) { + if (node->Type != MEDIA_DEVICE_PATH || node->SubType != MEDIA_HARDDRIVE_DP) continue; - HARDDRIVE_DEVICE_PATH *hd = (HARDDRIVE_DEVICE_PATH *) dp; + HARDDRIVE_DEVICE_PATH *hd = (HARDDRIVE_DEVICE_PATH *) node; if (hd->SignatureType != SIGNATURE_TYPE_GUID) continue; return xasprintf(GUID_FORMAT_STR, GUID_FORMAT_VAL(hd->SignatureGuid)); } - return NULL; + /* No GPT partition node found — try CDROM device path as fallback */ + return disk_get_part_uuid_cdrom(dp); } diff --git a/src/boot/proto/device-path.h b/src/boot/proto/device-path.h index d81c0e1f8dd17..658c482df504a 100644 --- a/src/boot/proto/device-path.h +++ b/src/boot/proto/device-path.h @@ -30,6 +30,7 @@ enum { ACPI_DP = 0x01, MEDIA_HARDDRIVE_DP = 0x01, + MEDIA_CDROM_DP = 0x02, MEDIA_VENDOR_DP = 0x03, MEDIA_FILEPATH_DP = 0x04, MEDIA_PIWG_FW_FILE_DP = 0x06, @@ -84,6 +85,14 @@ typedef struct { uint8_t SignatureType; } _packed_ HARDDRIVE_DEVICE_PATH; +typedef struct { + EFI_DEVICE_PATH Header; + uint32_t BootEntry; + uint64_t PartitionStart; /* In media block size units */ + uint64_t PartitionSize; /* In media block size units */ +} _packed_ CDROM_DEVICE_PATH; +assert_cc(sizeof(CDROM_DEVICE_PATH) == 24); + typedef struct { EFI_DEVICE_PATH Header; char16_t PathName[]; From 235faaad89e61d3607f16a31b2a6e2fb6e063160 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 2 Apr 2026 19:49:56 +0200 Subject: [PATCH 0760/1296] dissect: Don't try to set loop name if foreign --- src/dissect/dissect.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index ceaedb262fd71..a90d394479793 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -2033,7 +2033,7 @@ static int run(int argc, char *argv[]) { log_debug_errno(r, "Lacking permissions or missing /dev/loop-control to set up loopback block device for %s, using service: %m", arg_image); arg_via_service = true; } else { - if (arg_loop_ref) { + if (arg_loop_ref && !LOOP_DEVICE_IS_FOREIGN(d)) { r = loop_device_set_filename(d, arg_loop_ref); if (r < 0) log_warning_errno(r, "Failed to set loop reference string to '%s', ignoring: %m", arg_loop_ref); From ba259f5a789f26e1e37a191ee84da87c77de61a6 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 2 Apr 2026 19:50:13 +0200 Subject: [PATCH 0761/1296] loop-util: Skip loop_device_set_autoclear() if foreign --- src/shared/loop-util.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c index 15f987604a3e7..9349a98493ed9 100644 --- a/src/shared/loop-util.c +++ b/src/shared/loop-util.c @@ -1233,6 +1233,9 @@ int loop_device_set_autoclear(LoopDevice *d, bool autoclear) { assert(d); + if (LOOP_DEVICE_IS_FOREIGN(d)) + return 0; + if (ioctl(ASSERT_FD(d->fd), LOOP_GET_STATUS64, &info) < 0) return -errno; From 8f5735ad50e27a036925b02bd876eedce2630f8f Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 2 Apr 2026 15:01:40 +0000 Subject: [PATCH 0762/1296] dissect: resolve sysfs paths to devnodes in --attach When a udev rule uses ENV{SYSTEMD_WANTS}+="systemd-loop@.service" on a block device, the %f specifier in the service file resolves to the sysfs path rather than the device node path. Detect sysfs paths in parse_image_path_argument() and resolve them to the corresponding devnode using sd_device_new_from_syspath() + sd_device_get_devname(). --- src/dissect/dissect.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index a90d394479793..b3fe28e2cf97e 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -190,6 +190,25 @@ static int parse_image_path_argument(const char *path, char **ret_root, char **r if (r < 0) return r; + /* If we got a sysfs path (e.g. from a udev-instantiated template unit's %f specifier), + * resolve it to the corresponding devnode. */ + if (path_startswith(p, "/sys/")) { + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + const char *devname; + + r = sd_device_new_from_syspath(&dev, p); + if (r < 0) + return log_error_errno(r, "Failed to get device from syspath '%s': %m", p); + + r = sd_device_get_devname(dev, &devname); + if (r < 0) + return log_error_errno(r, "Failed to get devname for '%s': %m", p); + + r = free_and_strdup(&p, devname); + if (r < 0) + return log_oom(); + } + if (stat(p, &st) < 0) return log_error_errno(errno, "Failed to stat %s: %m", p); From 047ae265cc3ce339e14022ac63281535da6b6f8f Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 30 Mar 2026 20:11:15 +0000 Subject: [PATCH 0763/1296] udev: probe GPT sector size and trigger loop device on mismatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the GPT partition table uses a different sector size than the device's native block size (e.g. 512-byte GPT on a 2048-byte CD-ROM booted via El Torito), the kernel cannot parse the partition table. Probe the GPT sector size upfront and configure blkid with the correct value so it always finds the partition table. If a sector size mismatch is detected, trigger a loop device to re-expose the device with the correct sector size and skip root partition discovery on the original device — it will happen on the loop device instead. Co-developed-by: Claude Opus 4.6 --- rules.d/99-systemd.rules.in | 6 +++ src/udev/udev-builtin-blkid.c | 87 ++++++++++++++++++++++++++++++----- 2 files changed, 82 insertions(+), 11 deletions(-) diff --git a/rules.d/99-systemd.rules.in b/rules.d/99-systemd.rules.in index bebc4d7d09cc3..da2d311ce4934 100644 --- a/rules.d/99-systemd.rules.in +++ b/rules.d/99-systemd.rules.in @@ -89,4 +89,10 @@ SUBSYSTEM=="module", KERNEL=="configfs", TAG+="systemd", ENV{SYSTEMD_WANTS}+="sy SUBSYSTEM=="tpmrm", KERNEL=="tpmrm[0-9]*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="tpm2.target" SUBSYSTEM=="tpm", KERNEL=="tpm[0-9]*", TAG+="systemd" +# If the GPT sector size doesn't match the device's native sector size (e.g. 512-byte GPT on a +# 2048-byte CD-ROM booted via El Torito), trigger a loop device to re-expose it with the correct +# sector size so the kernel can parse the partition table. +SUBSYSTEM=="block", ENV{ID_PART_GPT_AUTO_ROOT_DISK_SECTOR_SIZE_MISMATCH}=="1", \ + ENV{SYSTEMD_WANTS}+="systemd-loop@.service" + LABEL="systemd_end" diff --git a/src/udev/udev-builtin-blkid.c b/src/udev/udev-builtin-blkid.c index 6ec02674b9fd6..b5fb87437c0ac 100644 --- a/src/udev/udev-builtin-blkid.c +++ b/src/udev/udev-builtin-blkid.c @@ -20,6 +20,7 @@ #include "blockdev-util.h" #include "device-util.h" #include "devnum-util.h" +#include "efi-api.h" #include "efi-loader.h" #include "errno-util.h" #include "fd-util.h" @@ -421,6 +422,64 @@ static int read_loopback_backing_inode( return 0; } +static int probe_gpt_sector_size_mismatch(UdevEvent *event, int fd) { + sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + int r; + + /* Probe the GPT sector size. For CD-ROMs booted via El Torito, the GPT may use a different + * sector size than the device (e.g. 512-byte GPT on a 2048-byte CD-ROM). If there's a mismatch, + * check if this is the boot disk by comparing GPT partition UUIDs with the ESP/XBOOTLDR UUID + * exported by the boot loader. If it matches, set a property so that udev rules can set up a + * loop device with the correct sector size — the kernel can't parse the partition table itself + * in this case. */ + + _cleanup_free_ void *entries = NULL; + uint32_t n_entries, entry_size; + ssize_t gpt_ssz = gpt_probe(fd, /* ret_header= */ NULL, &entries, &n_entries, &entry_size); + if (gpt_ssz < 0) + return log_device_debug_errno(dev, gpt_ssz, "Failed to probe GPT: %m"); + if (gpt_ssz == 0) + return 0; + + uint32_t device_ssz; + r = blockdev_get_sector_size(fd, &device_ssz); + if (r < 0) + return log_device_debug_errno(dev, r, "Failed to get device sector size: %m"); + + if ((uint32_t) gpt_ssz == device_ssz) + return 0; + + log_device_debug(dev, "GPT sector size %zi does not match device sector size %" PRIu32 ".", + gpt_ssz, device_ssz); + + sd_id128_t loader_part_uuid; + r = efi_loader_get_device_part_uuid(&loader_part_uuid); + if (r < 0) { + if (r != -ENOENT && !ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return log_device_debug_errno(dev, r, "Failed to get loader partition UUID: %m"); + + return 0; + } + + for (uint32_t i = 0; i < n_entries; i++) { + const GptPartitionEntry *entry = (const GptPartitionEntry *) ((const uint8_t *) entries + (size_t) entry_size * i); + + sd_id128_t type = efi_guid_to_id128(entry->partition_type_guid); + if (!sd_id128_in_set(type, SD_GPT_ESP, SD_GPT_XBOOTLDR)) + continue; + + if (!sd_id128_equal(efi_guid_to_id128(entry->unique_partition_guid), loader_part_uuid)) + continue; + + log_device_debug(dev, "Found boot partition (ESP/XBOOTLDR) on disk with sector size mismatch."); + udev_builtin_add_property(event, "ID_PART_GPT_AUTO_ROOT_DISK_SECTOR_SIZE_MISMATCH", "1"); + udev_builtin_add_propertyf(event, "ID_PART_GPT_SECTOR_SIZE", "%zi", gpt_ssz); + return 1; /* mismatch detected and handled */ + } + + return 0; +} + static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) { sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); const char *devnode, *root_partition = NULL, *data, *name; @@ -476,17 +535,6 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) { } } - sym_blkid_probe_set_superblocks_flags(pr, - BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID | - BLKID_SUBLKS_TYPE | BLKID_SUBLKS_SECTYPE | -#ifdef BLKID_SUBLKS_FSINFO /* since util-linux 2.39 */ - BLKID_SUBLKS_FSINFO | -#endif - BLKID_SUBLKS_USAGE | BLKID_SUBLKS_VERSION); - - if (noraid) - sym_blkid_probe_filter_superblocks_usage(pr, BLKID_FLTR_NOTIN, BLKID_USAGE_RAID); - r = sd_device_get_devname(dev, &devnode); if (r < 0) return log_device_debug_errno(dev, r, "Failed to get device name: %m"); @@ -499,6 +547,23 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) { return ignore ? 0 : fd; } + if (offset == 0) { + r = probe_gpt_sector_size_mismatch(event, fd); + if (r > 0) + return 0; + } + + sym_blkid_probe_set_superblocks_flags(pr, + BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID | + BLKID_SUBLKS_TYPE | BLKID_SUBLKS_SECTYPE | +#ifdef BLKID_SUBLKS_FSINFO /* since util-linux 2.39 */ + BLKID_SUBLKS_FSINFO | +#endif + BLKID_SUBLKS_USAGE | BLKID_SUBLKS_VERSION); + + if (noraid) + sym_blkid_probe_filter_superblocks_usage(pr, BLKID_FLTR_NOTIN, BLKID_USAGE_RAID); + errno = 0; r = sym_blkid_probe_set_device(pr, fd, offset, 0); if (r < 0) From 865f16d7a08f57b5c2895b99d9d7957415ce14e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 1 Apr 2026 17:56:13 +0200 Subject: [PATCH 0764/1296] sd-varlink: simplify allocation Suggested in post-merge review. --- src/libsystemd/sd-varlink/sd-varlink.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index 4beb785199799..90be0177054cc 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -3100,11 +3100,8 @@ _public_ int sd_varlink_set_sentinel(sd_varlink *v, const char *error_id) { -EUCLEAN); char *s = NULL; - if (error_id) { - s = strdup(error_id); - if (!s) - return log_oom_debug(); - } + if (strdup_to(&s, error_id) < 0) + return log_oom_debug(); if (v->sentinel != POINTER_MAX) free(v->sentinel); From 58b0dac9f88fb2b2451b461cb28ff8942c924350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 4 Apr 2026 22:10:27 +0200 Subject: [PATCH 0765/1296] meson: fix coccinelle test names fs.name() returns empty if the string ends with "/". --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 5db299a2a7476..659aecd421877 100644 --- a/meson.build +++ b/meson.build @@ -2998,7 +2998,7 @@ if spatch.found() foreach dir : coccinelle_src_dirs if dir not in coccinelle_exclude test( - 'coccinelle-@0@'.format(fs.name(dir)), + 'coccinelle-@0@'.format(fs.name(dir.strip('/'))), check_coccinelle_sh, args : [meson.project_source_root() / dir, meson.project_source_root() / 'coccinelle'], From e7182635c6d012b436827746d49cea8963189d50 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 20 Mar 2026 14:00:23 +0100 Subject: [PATCH 0766/1296] shutdown: paranoia, switch to secure_getenv() We have this rule in systemd that unless we are sure that getenv() is safe and there's a reason to use it we should always prefer secure_getenv(). Follow our own rules here, as per CODING_STYLE document. This really doesn't matter here, all of this is highly privileged, but hopefully Claude & Colleagues shut up about this then, and maybe detect the pattern better. --- src/shutdown/shutdown.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/shutdown/shutdown.c b/src/shutdown/shutdown.c index 73c6dd6d8708b..83fa8c0b668f0 100644 --- a/src/shutdown/shutdown.c +++ b/src/shutdown/shutdown.c @@ -286,14 +286,14 @@ static void init_watchdog(void) { const char *s; int r; - s = getenv("WATCHDOG_DEVICE"); + s = secure_getenv("WATCHDOG_DEVICE"); if (s) { r = watchdog_set_device(s); if (r < 0) - log_warning_errno(r, "Failed to set watchdog device to %s, ignoring: %m", s); + log_warning_errno(r, "Failed to set watchdog device to '%s', ignoring: %m", s); } - s = getenv("WATCHDOG_USEC"); + s = secure_getenv("WATCHDOG_USEC"); if (s) { usec_t usec; From 8cfae74dcd13457fdb2f6bd563313c3abf47d68e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 26 Mar 2026 08:50:08 +0100 Subject: [PATCH 0767/1296] shutdown: check WATCHDOG_PID= if it is set Alternative to: #35167 --- src/shutdown/shutdown.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/shutdown/shutdown.c b/src/shutdown/shutdown.c index 83fa8c0b668f0..9b0328bfca05e 100644 --- a/src/shutdown/shutdown.c +++ b/src/shutdown/shutdown.c @@ -286,6 +286,22 @@ static void init_watchdog(void) { const char *s; int r; + /* NB: we do not insist on $WATCHDOG_PID being set because old systemd versions didn't set it at all, + * and we want to retain some basic compatibility between an old service manager and a new shutdown + * binary. If it *is* set we'll insist on it being set to 1 however. */ + s = secure_getenv("WATCHDOG_PID"); + if (s) { + pid_t pid; + + r = parse_pid(s, &pid); + if (r < 0) + log_warning_errno(r, "Failed to parse $WATCHDOG_PID, ignoring: %s", s); + else if (pid != getpid_cached()) { + log_warning("$WATCHDOG_PID set, but not to " PID_FMT ", skipping watchdog logic.", getpid_cached()); + return; + } + } + s = secure_getenv("WATCHDOG_DEVICE"); if (s) { r = watchdog_set_device(s); From 0874eea302d0ba2d436dcce0b992cdc957190ff4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 10 Mar 2026 08:06:02 +0100 Subject: [PATCH 0768/1296] shutdown: enforce a minimum uptime to make boot loops less annoying Fixes: #9453 --- man/kernel-command-line.xml | 1 + man/systemd-system.conf.xml | 16 ++++++++++++++++ man/systemd.xml | 10 ++++++++++ src/core/main.c | 18 ++++++++++++++++++ src/core/system.conf.in | 1 + src/shutdown/shutdown.c | 36 ++++++++++++++++++++++++++++++++++++ 6 files changed, 82 insertions(+) diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 088ce24154042..9e24e749b3f97 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -69,6 +69,7 @@ systemd.import_credentials= systemd.reload_limit_interval_sec= systemd.reload_limit_burst= + systemd.minimum_uptime_sec= Parameters understood by the system and service manager to control system behavior. For details, see diff --git a/man/systemd-system.conf.xml b/man/systemd-system.conf.xml index 172657de65cbf..e9e7d3d78db57 100644 --- a/man/systemd-system.conf.xml +++ b/man/systemd-system.conf.xml @@ -633,6 +633,22 @@ + + + MinimumUptimeSec= + + Specifies the minimum uptime for the system which has to be reached before a shutdown + is executed. Defaults to 15s. This mechanism is introduced to avoid high frequency reboot loops, when + technical failures trigger an automatic shutdown during the boot process. Each reboot cycle is + delayed to the specified minimum time, giving the user a chance to review screen contents or + otherwise interact with the device before the shutdown proceeds. The delay takes place during the + very last phase of system shutdown, immediately before the reboot() system call + is executed. If the system is already running for longer than the specified time, this setting has no + effect. This logic is also skipped in container environments. Set to zero in order to disable this + logic. + + + diff --git a/man/systemd.xml b/man/systemd.xml index 06d9102e475f1..30ae385029b10 100644 --- a/man/systemd.xml +++ b/man/systemd.xml @@ -1061,6 +1061,16 @@ + + + systemd.minimum_uptime_sec= + + Takes a time in seconds. Specifies the minimum uptime of the system before the system + shuts down. For more information see the MinimumUptimeSec= setting described in + systemd-system.conf5. + + + For other kernel command line parameters understood by diff --git a/src/core/main.c b/src/core/main.c index bd065c351d23a..d0c0023f899f4 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -163,6 +163,7 @@ static void *arg_random_seed; static size_t arg_random_seed_size; static usec_t arg_reload_limit_interval_sec; static unsigned arg_reload_limit_burst; +static usec_t arg_minimum_uptime_usec; /* A copy of the original environment block */ static char **saved_env = NULL; @@ -554,6 +555,17 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat return 0; } + } else if (proc_cmdline_key_streq(key, "systemd.minimum_uptime_sec")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = parse_sec(value, &arg_minimum_uptime_usec); + if (r < 0) { + log_warning_errno(r, "Failed to parse systemd.minimum_uptime_sec= argument '%s', ignoring: %m", value); + return 0; + } + } else if (streq(key, "quiet") && !value) { if (arg_show_status == _SHOW_STATUS_INVALID) @@ -813,6 +825,7 @@ static int parse_config_file(void) { { "Manager", "ReloadLimitIntervalSec", config_parse_sec, 0, &arg_reload_limit_interval_sec }, { "Manager", "ReloadLimitBurst", config_parse_unsigned, 0, &arg_reload_limit_burst }, { "Manager", "DefaultMemoryZSwapWriteback", config_parse_bool, 0, &arg_defaults.memory_zswap_writeback }, + { "Manager", "MinimumUptimeSec", config_parse_sec, 0, &arg_minimum_uptime_usec }, #if ENABLE_SMACK { "Manager", "DefaultSmackProcessLabel", config_parse_string, 0, &arg_defaults.smack_process_label }, #else @@ -1740,6 +1753,9 @@ static int become_shutdown(int objective, int retval) { if (arg_watchdog_device) (void) strv_extendf(&env_block, "WATCHDOG_DEVICE=%s", arg_watchdog_device); + if (arg_minimum_uptime_usec != USEC_INFINITY) + (void) strv_extendf(&env_block, "MINIMUM_UPTIME_USEC=" USEC_FMT, arg_minimum_uptime_usec); + (void) write_boot_or_shutdown_osc("shutdown"); execve(SYSTEMD_SHUTDOWN_BINARY_PATH, (char **) command_line, env_block); @@ -2830,6 +2846,8 @@ static void reset_arguments(void) { arg_reload_limit_interval_sec = 0; arg_reload_limit_burst = 0; + + arg_minimum_uptime_usec = USEC_INFINITY; } static void determine_default_oom_score_adjust(void) { diff --git a/src/core/system.conf.in b/src/core/system.conf.in index 6000d1702e097..ef2c59bd79a8e 100644 --- a/src/core/system.conf.in +++ b/src/core/system.conf.in @@ -37,6 +37,7 @@ #RebootWatchdogSec=10min #KExecWatchdogSec=off #WatchdogDevice= +#MinimumUptimeSec=15s #CapabilityBoundingSet= #NoNewPrivileges=no #ProtectSystem=auto diff --git a/src/shutdown/shutdown.c b/src/shutdown/shutdown.c index 9b0328bfca05e..1fb0f422e53b7 100644 --- a/src/shutdown/shutdown.c +++ b/src/shutdown/shutdown.c @@ -51,6 +51,7 @@ #define SYNC_PROGRESS_ATTEMPTS 3 #define SYNC_TIMEOUT_USEC (10*USEC_PER_SEC) +#define DEFAULT_MINIMUM_UPTIME_USEC (15U * USEC_PER_SEC) static const char *arg_verb = NULL; static uint8_t arg_exit_code = 0; @@ -343,6 +344,35 @@ static void notify_supervisor(void) { arg_exit_code, arg_verb); } +static void sleep_until_minimum_uptime(void) { + uint64_t minimum_uptime_usec = DEFAULT_MINIMUM_UPTIME_USEC; + int r; + + const char *e = secure_getenv("MINIMUM_UPTIME_USEC"); + if (e) { + r = safe_atou64(e, &minimum_uptime_usec); + if (r < 0) + log_warning_errno(r, "Failed to parse $MINIMUM_UPTIME_USEC, ignoring: %s", e); + } + + if (minimum_uptime_usec <= 0) /* turned off? */ + return; + + for (;;) { + usec_t n = now(CLOCK_BOOTTIME); + if (n >= minimum_uptime_usec) + break; + + usec_t m = minimum_uptime_usec - n; + log_notice("Delaying shutdown for %s, in order to reach minimum uptime of %s.", + FORMAT_TIMESPAN(m, USEC_PER_SEC), + FORMAT_TIMESPAN(minimum_uptime_usec, USEC_PER_SEC)); + + /* Sleep for up to 3s, then show message again, as a progress indicator. */ + usleep_safe(MIN(m, 3 * USEC_PER_SEC)); + } +} + int main(int argc, char *argv[]) { static const char* const dirs[] = { SYSTEM_SHUTDOWN_PATH, @@ -611,6 +641,12 @@ int main(int argc, char *argv[]) { notify_supervisor(); + /* Enforce the minimum uptime, but don't bother with it in containers, since – unlike on bare metal + * and VMs – the screen output isn't flushed out immediately when we reboot (as OVMF or real PC + * firmwares do) */ + if (!in_container) + sleep_until_minimum_uptime(); + if (streq(arg_verb, "exit")) { if (in_container) { log_info("Exiting container."); From 94430369b789af3976017c08065d4d00bcdcbb14 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Mar 2026 23:49:49 +0100 Subject: [PATCH 0769/1296] update TODO --- TODO | 3 --- 1 file changed, 3 deletions(-) diff --git a/TODO b/TODO index 37345f4a754c0..378d3181228ac 100644 --- a/TODO +++ b/TODO @@ -298,9 +298,6 @@ Features: * Add knob to cryptsetup, to trigger automatic reboot on failure to unlock disk. Enable this by default for rootfs, also in gpt-auto-generator -* Add RebootUptimeMinSec= knob to PID 1, that makes systemd-shutdown sleep - until the specified uptime has passed, to lengthen tight boot loops. - * replace bootctl's PE version check to actually use APIs from pe-binary.[ch] to find binary version. From 4e0eabd40118c2607e52009c39a936c2054e6153 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sat, 4 Apr 2026 22:24:47 +0000 Subject: [PATCH 0770/1296] udev: also trigger loop device for boot disk when partition scanning is unsupported MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, probe_gpt_sector_size_mismatch() would bail out early when the GPT sector size matched the device sector size. However, some devices (e.g. certain CD-ROM drives) do not support kernel partition scanning even when sector sizes match. In that case, the kernel still cannot parse the partition table, and we need to set up a loop device to expose the partitions — just as we do for the sector size mismatch case. Check blockdev_partscan_enabled() when sector sizes match, and only skip the boot partition check if partition scanning is actually supported. Also rename the function, udev property, and log messages to reflect the broader scope: - probe_gpt_sector_size_mismatch() -> probe_gpt_boot_disk_needs_loop() - ID_PART_GPT_AUTO_ROOT_DISK_SECTOR_SIZE_MISMATCH -> ID_PART_GPT_AUTO_ROOT_DISK_NEEDS_LOOP --- rules.d/99-systemd.rules.in | 8 ++++---- src/udev/udev-builtin-blkid.c | 34 ++++++++++++++++++++++++---------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/rules.d/99-systemd.rules.in b/rules.d/99-systemd.rules.in index da2d311ce4934..98f483503e211 100644 --- a/rules.d/99-systemd.rules.in +++ b/rules.d/99-systemd.rules.in @@ -89,10 +89,10 @@ SUBSYSTEM=="module", KERNEL=="configfs", TAG+="systemd", ENV{SYSTEMD_WANTS}+="sy SUBSYSTEM=="tpmrm", KERNEL=="tpmrm[0-9]*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="tpm2.target" SUBSYSTEM=="tpm", KERNEL=="tpm[0-9]*", TAG+="systemd" -# If the GPT sector size doesn't match the device's native sector size (e.g. 512-byte GPT on a -# 2048-byte CD-ROM booted via El Torito), trigger a loop device to re-expose it with the correct -# sector size so the kernel can parse the partition table. -SUBSYSTEM=="block", ENV{ID_PART_GPT_AUTO_ROOT_DISK_SECTOR_SIZE_MISMATCH}=="1", \ +# If the kernel cannot parse the GPT partition table on the boot disk (e.g. due to a sector size +# mismatch on a CD-ROM booted via El Torito, or because the device does not support partition +# scanning), trigger a loop device to expose the partitions. +SUBSYSTEM=="block", ENV{ID_PART_GPT_AUTO_ROOT_DISK_NEEDS_LOOP}=="1", \ ENV{SYSTEMD_WANTS}+="systemd-loop@.service" LABEL="systemd_end" diff --git a/src/udev/udev-builtin-blkid.c b/src/udev/udev-builtin-blkid.c index b5fb87437c0ac..49bb0fbff37ea 100644 --- a/src/udev/udev-builtin-blkid.c +++ b/src/udev/udev-builtin-blkid.c @@ -422,7 +422,7 @@ static int read_loopback_backing_inode( return 0; } -static int probe_gpt_sector_size_mismatch(UdevEvent *event, int fd) { +static int probe_gpt_boot_disk_needs_loop(UdevEvent *event, int fd) { sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); int r; @@ -431,7 +431,11 @@ static int probe_gpt_sector_size_mismatch(UdevEvent *event, int fd) { * check if this is the boot disk by comparing GPT partition UUIDs with the ESP/XBOOTLDR UUID * exported by the boot loader. If it matches, set a property so that udev rules can set up a * loop device with the correct sector size — the kernel can't parse the partition table itself - * in this case. */ + * in this case. + * + * Even if the sector sizes match, if the device does not support partition scanning (e.g. some + * CD-ROM drives), the kernel still can't parse the partition table. In that case, if the disk + * contains the ESP we booted from, we still need a loop device to expose the partitions. */ _cleanup_free_ void *entries = NULL; uint32_t n_entries, entry_size; @@ -446,11 +450,21 @@ static int probe_gpt_sector_size_mismatch(UdevEvent *event, int fd) { if (r < 0) return log_device_debug_errno(dev, r, "Failed to get device sector size: %m"); - if ((uint32_t) gpt_ssz == device_ssz) - return 0; + bool sector_size_mismatch = (uint32_t) gpt_ssz != device_ssz; + + if (!sector_size_mismatch) { + r = blockdev_partscan_enabled(dev); + if (r < 0) + return log_device_debug_errno(dev, r, "Failed to check if partition scanning is enabled: %m"); + if (r > 0) + return 0; + } - log_device_debug(dev, "GPT sector size %zi does not match device sector size %" PRIu32 ".", - gpt_ssz, device_ssz); + if (sector_size_mismatch) + log_device_debug(dev, "GPT sector size %zi does not match device sector size %" PRIu32 ".", + gpt_ssz, device_ssz); + else + log_device_debug(dev, "Device does not support partition scanning."); sd_id128_t loader_part_uuid; r = efi_loader_get_device_part_uuid(&loader_part_uuid); @@ -471,10 +485,10 @@ static int probe_gpt_sector_size_mismatch(UdevEvent *event, int fd) { if (!sd_id128_equal(efi_guid_to_id128(entry->unique_partition_guid), loader_part_uuid)) continue; - log_device_debug(dev, "Found boot partition (ESP/XBOOTLDR) on disk with sector size mismatch."); - udev_builtin_add_property(event, "ID_PART_GPT_AUTO_ROOT_DISK_SECTOR_SIZE_MISMATCH", "1"); + log_device_debug(dev, "Found boot partition (ESP/XBOOTLDR) on disk where kernel cannot scan partitions."); + udev_builtin_add_property(event, "ID_PART_GPT_AUTO_ROOT_DISK_NEEDS_LOOP", "1"); udev_builtin_add_propertyf(event, "ID_PART_GPT_SECTOR_SIZE", "%zi", gpt_ssz); - return 1; /* mismatch detected and handled */ + return 1; /* boot disk needs loop device */ } return 0; @@ -548,7 +562,7 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) { } if (offset == 0) { - r = probe_gpt_sector_size_mismatch(event, fd); + r = probe_gpt_boot_disk_needs_loop(event, fd); if (r > 0) return 0; } From 41da1ae6a042363e856b1748c11cd176ef1991c8 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sat, 4 Apr 2026 22:02:46 +0000 Subject: [PATCH 0771/1296] vmspawn: also search for qemu binary at /usr/lib/qemu-kvm This is the qemu binary path on CentOS Stream. --- src/vmspawn/vmspawn-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vmspawn/vmspawn-util.c b/src/vmspawn/vmspawn-util.c index 7e085d7faf0fc..ca4ddc70c7f2e 100644 --- a/src/vmspawn/vmspawn-util.c +++ b/src/vmspawn/vmspawn-util.c @@ -523,7 +523,7 @@ int find_qemu_binary(char **ret_qemu_binary) { * If the native architecture is not supported by qemu -EOPNOTSUPP will be returned; */ - FOREACH_STRING(s, "qemu", "qemu-kvm") { + FOREACH_STRING(s, "qemu", "qemu-kvm", "/usr/lib/qemu-kvm") { r = find_executable(s, ret_qemu_binary); if (r == 0) return 0; From c82cf5662e25591ca50831b8b386fa2da94dd3c1 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 5 Apr 2026 08:17:59 +0000 Subject: [PATCH 0772/1296] boot: generalize CDROM terminology to El Torito MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per UEFI specification §13.3.2, El Torito partition discovery applies to any block device, not just optical media. Rename disk_get_part_uuid_cdrom() to disk_get_part_uuid_eltorito() and update all log messages and comments to say "El Torito" instead of "CDROM" to reflect this. --- src/boot/part-discovery.c | 44 ++++++++++++++++++----------------- src/udev/udev-builtin-blkid.c | 12 +++++----- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/boot/part-discovery.c b/src/boot/part-discovery.c index 9249dc24a18d8..8f5fefad47c1d 100644 --- a/src/boot/part-discovery.c +++ b/src/boot/part-discovery.c @@ -320,21 +320,23 @@ EFI_STATUS partition_open(const EFI_GUID *type, EFI_HANDLE *device, EFI_HANDLE * return EFI_SUCCESS; } -static char16_t* disk_get_part_uuid_cdrom(const EFI_DEVICE_PATH *dp) { +static char16_t* disk_get_part_uuid_eltorito(const EFI_DEVICE_PATH *dp) { EFI_STATUS err; assert(dp); - /* When booting from a CD-ROM via El Torito, the device path contains a CDROM node instead of - * a HARDDRIVE node. The CDROM node doesn't carry a partition UUID, so we need to read the GPT - * from the underlying disk to find it. */ + /* When booting via El Torito, the device path contains a CDROM node instead of a HARDDRIVE + * node (UEFI specification §10.3.5.2). The CDROM node doesn't carry a partition UUID, so we + * need to read the GPT from the underlying disk to find it. Per §13.3.2, El Torito partition + * discovery applies to any block device, not just optical media (e.g. an ISO image dd'd to a + * USB stick). */ const CDROM_DEVICE_PATH *cdrom = NULL; for (const EFI_DEVICE_PATH *node = dp; !device_path_is_end(node); node = device_path_next_node(node)) if (node->Type == MEDIA_DEVICE_PATH && node->SubType == MEDIA_CDROM_DP) cdrom = (const CDROM_DEVICE_PATH *) node; if (!cdrom) { - log_debug("No CDROM device path node found."); + log_debug("No El Torito device path node found."); return NULL; } @@ -345,7 +347,7 @@ static char16_t* disk_get_part_uuid_cdrom(const EFI_DEVICE_PATH *dp) { EFI_HANDLE disk_handle; err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), &remaining, &disk_handle); if (err != EFI_SUCCESS) { - log_debug_status(err, "Failed to locate disk device for CDROM: %m"); + log_debug_status(err, "Failed to locate disk device for El Torito boot: %m"); return NULL; } @@ -354,13 +356,13 @@ static char16_t* disk_get_part_uuid_cdrom(const EFI_DEVICE_PATH *dp) { EFI_BLOCK_IO_PROTOCOL *block_io; err = BS->HandleProtocol(disk_handle, MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), (void **) &block_io); if (err != EFI_SUCCESS) { - log_debug_status(err, "Failed to get block I/O protocol for CDROM disk: %m"); + log_debug_status(err, "Failed to get block I/O protocol for El Torito disk: %m"); return NULL; } if (block_io->Media->LogicalPartition || !block_io->Media->MediaPresent || block_io->Media->LastBlock <= 1) { - log_debug("CDROM disk has unsuitable media (partition=%ls, present=%ls, lastblock=%" PRIu64 ").", + log_debug("El Torito disk has unsuitable media (partition=%ls, present=%ls, lastblock=%" PRIu64 ").", yes_no(block_io->Media->LogicalPartition), yes_no(block_io->Media->MediaPresent), (uint64_t) block_io->Media->LastBlock); @@ -369,24 +371,24 @@ static char16_t* disk_get_part_uuid_cdrom(const EFI_DEVICE_PATH *dp) { uint32_t iso9660_block_size = block_io->Media->BlockSize; if (iso9660_block_size < 512 || iso9660_block_size > 4096 || !ISPOWEROF2(iso9660_block_size)) { - log_debug("Unexpected CDROM block size %" PRIu32 ", skipping.", iso9660_block_size); + log_debug("Unexpected El Torito block size %" PRIu32 ", skipping.", iso9660_block_size); return NULL; } EFI_DISK_IO_PROTOCOL *disk_io; err = BS->HandleProtocol(disk_handle, MAKE_GUID_PTR(EFI_DISK_IO_PROTOCOL), (void **) &disk_io); if (err != EFI_SUCCESS) { - log_debug_status(err, "Failed to get disk I/O protocol for CDROM disk: %m"); + log_debug_status(err, "Failed to get disk I/O protocol for El Torito disk: %m"); return NULL; } uint32_t media_id = block_io->Media->MediaId; /* Probe for the GPT header at multiple possible sector sizes (512, 1024, 2048, 4096). - * The GPT header is at LBA 1, i.e. byte offset == sector_size. On CD-ROMs, the GPT + * The GPT header is at LBA 1, i.e. byte offset == sector_size. On El Torito media, the GPT * may use a different sector size than the media's block size (e.g. 512-byte GPT sectors - * on 2048-byte CD-ROM blocks), so we try all possibilities. If the primary GPT header is - * corrupt but contains a valid backup LBA, fall back to the backup header. */ + * on 2048-byte blocks), so we try all possibilities. If the primary GPT header is corrupt + * but contains a valid backup LBA, fall back to the backup header. */ uint32_t gpt_sector_size = 0; GptHeader gpt; _cleanup_free_ void *entries = NULL; @@ -413,18 +415,18 @@ static char16_t* disk_get_part_uuid_cdrom(const EFI_DEVICE_PATH *dp) { } if (gpt_sector_size == 0) { - log_debug("No valid GPT found on CDROM at any sector size."); + log_debug("No valid GPT found on El Torito disk at any sector size."); return NULL; } - log_debug("Found GPT on CDROM with sector size %" PRIu32 ", %" PRIu32 " partition entries.", + log_debug("Found GPT on El Torito disk with sector size %" PRIu32 ", %" PRIu32 " partition entries.", gpt_sector_size, gpt.NumberOfPartitionEntries); - /* Find the partition whose byte offset matches the CDROM's PartitionStart. - * CDROM PartitionStart is in media iso9660_block_size units, GPT StartingLBA is in gpt_sector_size units. */ + /* Find the partition whose byte offset matches the El Torito PartitionStart. + * El Torito PartitionStart is in media iso9660_block_size units, GPT StartingLBA is in gpt_sector_size units. */ uint64_t cdrom_start; if (!MUL_SAFE(&cdrom_start, cdrom->PartitionStart, iso9660_block_size)) { - log_debug("CDROM start offset overflow."); + log_debug("El Torito start offset overflow."); return NULL; } @@ -441,7 +443,7 @@ static char16_t* disk_get_part_uuid_cdrom(const EFI_DEVICE_PATH *dp) { return xasprintf(GUID_FORMAT_STR, GUID_FORMAT_VAL(entry->UniquePartitionGUID)); } - log_debug("No ESP partition matches CDROM start offset %" PRIu64 " (block size %" PRIu32 ").", + log_debug("No ESP partition matches El Torito start offset %" PRIu64 " (block size %" PRIu32 ").", cdrom->PartitionStart, iso9660_block_size); return NULL; } @@ -470,6 +472,6 @@ char16_t *disk_get_part_uuid(EFI_HANDLE *handle) { return xasprintf(GUID_FORMAT_STR, GUID_FORMAT_VAL(hd->SignatureGuid)); } - /* No GPT partition node found — try CDROM device path as fallback */ - return disk_get_part_uuid_cdrom(dp); + /* No GPT partition node found — try El Torito device path as fallback */ + return disk_get_part_uuid_eltorito(dp); } diff --git a/src/udev/udev-builtin-blkid.c b/src/udev/udev-builtin-blkid.c index 49bb0fbff37ea..edadb71c7ab4f 100644 --- a/src/udev/udev-builtin-blkid.c +++ b/src/udev/udev-builtin-blkid.c @@ -426,12 +426,12 @@ static int probe_gpt_boot_disk_needs_loop(UdevEvent *event, int fd) { sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); int r; - /* Probe the GPT sector size. For CD-ROMs booted via El Torito, the GPT may use a different - * sector size than the device (e.g. 512-byte GPT on a 2048-byte CD-ROM). If there's a mismatch, - * check if this is the boot disk by comparing GPT partition UUIDs with the ESP/XBOOTLDR UUID - * exported by the boot loader. If it matches, set a property so that udev rules can set up a - * loop device with the correct sector size — the kernel can't parse the partition table itself - * in this case. + /* Probe the GPT sector size. For devices booted via El Torito, the GPT may use a different + * sector size than the device (e.g. a 512-byte GPT on a device with 2048-byte blocks). If + * there's a mismatch, check if this is the boot disk by comparing GPT partition UUIDs with the + * ESP/XBOOTLDR UUID exported by the boot loader. If it matches, set a property so that udev + * rules can set up a loop device with the correct sector size — the kernel can't parse the + * partition table itself in this case. * * Even if the sector sizes match, if the device does not support partition scanning (e.g. some * CD-ROM drives), the kernel still can't parse the partition table. In that case, if the disk From 39eb7fd2591c1532a96f6fe23d0f0b5217d4b369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luan=20Vitor=20Simi=C3=A3o=20oliveira?= Date: Sun, 5 Apr 2026 02:37:05 -0300 Subject: [PATCH 0773/1296] hwdb: cooler master rgb controller is not a mouse --- hwdb.d/60-input-id.hwdb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hwdb.d/60-input-id.hwdb b/hwdb.d/60-input-id.hwdb index 03535884e5dbc..700e7ee264ba6 100644 --- a/hwdb.d/60-input-id.hwdb +++ b/hwdb.d/60-input-id.hwdb @@ -67,6 +67,11 @@ id-input:modalias:input:b0003v07C0p1125* ID_INPUT_MOUSE= ID_INPUT_JOYSTICK=1 +# Cooler Master ARGB GEN-2 controller +id-input:modalias:input:b0003v2516p01C9* + ID_INPUT=0 + ID_INPUT_MOUSE=0 + # GOLD WARRIOR SIM PhoenixRC 10411R id-input:modalias:input:b0003v1781p0898* ID_INPUT_ACCELEROMETER= From ba937b45bed0f6368983c627a55375dae95479ab Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 5 Apr 2026 18:37:31 +0200 Subject: [PATCH 0774/1296] vmspawn: Fix qemu-kvm path on centos stream Follow up for 41da1ae6a042363e856b1748c11cd176ef1991c8 --- src/vmspawn/vmspawn-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vmspawn/vmspawn-util.c b/src/vmspawn/vmspawn-util.c index ca4ddc70c7f2e..6e8a25baf5391 100644 --- a/src/vmspawn/vmspawn-util.c +++ b/src/vmspawn/vmspawn-util.c @@ -523,7 +523,7 @@ int find_qemu_binary(char **ret_qemu_binary) { * If the native architecture is not supported by qemu -EOPNOTSUPP will be returned; */ - FOREACH_STRING(s, "qemu", "qemu-kvm", "/usr/lib/qemu-kvm") { + FOREACH_STRING(s, "qemu", "qemu-kvm", "/usr/libexec/qemu-kvm") { r = find_executable(s, ret_qemu_binary); if (r == 0) return 0; From 4753c60053aea00d650e103a5eda893fd47c022b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 5 Apr 2026 17:12:35 +0000 Subject: [PATCH 0775/1296] swtpm: gracefully fall back when --print-profiles output is not JSON Older swtpm versions print --help output instead of JSON when swtpm_setup --print-profiles is invoked. Previously, the JSON parse failure was treated as fatal, preventing swtpm manufacture entirely on these older versions. Extract profile detection into a separate swtpm_find_best_profile() helper and treat JSON parse failure as a graceful fallback: log a notice and continue without a profile, same as when no builtin profiles are found. --- src/shared/swtpm-util.c | 114 ++++++++++++++++++++++++---------------- 1 file changed, 69 insertions(+), 45 deletions(-) diff --git a/src/shared/swtpm-util.c b/src/shared/swtpm-util.c index 55e3f2f34c52a..61413e7226aad 100644 --- a/src/shared/swtpm-util.c +++ b/src/shared/swtpm-util.c @@ -18,20 +18,16 @@ #include "strv.h" #include "swtpm-util.h" -int manufacture_swtpm(const char *state_dir, const char *secret) { +static int swtpm_find_best_profile(const char *swtpm_setup, char **ret) { int r; - assert(state_dir); - - _cleanup_free_ char *swtpm_setup = NULL; - r = find_executable("swtpm_setup", &swtpm_setup); - if (r < 0) - return log_error_errno(r, "Failed to find 'swtpm_setup' binary: %m"); + assert(swtpm_setup); + assert(ret); _cleanup_strv_free_ char **args = strv_new( swtpm_setup, - "--tpm2", - "--print-profiles"); + "--tpm2", + "--print-profiles"); if (!args) return log_oom(); @@ -83,44 +79,72 @@ int manufacture_swtpm(const char *state_dir, const char *secret) { return log_error_errno(r, "Failed to read memory fd: %m"); _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + r = sd_json_parse(text, SD_JSON_PARSE_MUST_BE_OBJECT, &j, /* reterr_line= */ NULL, /* reterr_column= */ NULL); + if (r < 0) { + log_notice("Failed to parse swtpm's --print-profiles output as JSON, assuming the implementation is too old to know the concept of profiles."); + *ret = NULL; + return 0; + } + + sd_json_variant *v = sd_json_variant_by_key(j, "builtin"); + if (!v) { + *ret = NULL; + return 0; + } + + if (!sd_json_variant_is_array(v)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'builtin' field is not an array."); + const char *best_profile = NULL; - if (isempty(text)) - log_notice("No list of supported profiles could be acquired from swtpm, assuming the implementation is too old to know the concept of profiles."); - else { - r = sd_json_parse(text, SD_JSON_PARSE_MUST_BE_OBJECT, &j, /* reterr_line= */ NULL, /* reterr_column= */ NULL); - if (r < 0) - return log_error_errno(r, "Failed to parse swtpm's --print-profiles output: %m"); - - sd_json_variant *v = sd_json_variant_by_key(j, "builtin"); - if (v) { - if (!sd_json_variant_is_array(v)) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'builtin' field is not an array: %m"); - - sd_json_variant *i; - JSON_VARIANT_ARRAY_FOREACH(i, v) { - if (!sd_json_variant_is_object(i)) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Profile object is not a JSON object."); - - sd_json_variant *n = sd_json_variant_by_key(i, "Name"); - if (!n) { - log_debug("Object in profiles array does not have a 'Name', skipping."); - continue; - } - - if (!sd_json_variant_is_string(n)) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Profile's 'Name' field is not a string."); - - const char *s = sd_json_variant_string(n); - - /* Pick the best of the default-v1, default-v2, … profiles */ - if (!startswith(s, "default-v")) - continue; - if (!best_profile || strverscmp_improved(s, best_profile) > 0) - best_profile = s; - } + sd_json_variant *i; + JSON_VARIANT_ARRAY_FOREACH(i, v) { + if (!sd_json_variant_is_object(i)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Profile object is not a JSON object."); + + sd_json_variant *n = sd_json_variant_by_key(i, "Name"); + if (!n) { + log_debug("Object in profiles array does not have a 'Name', skipping."); + continue; } + + if (!sd_json_variant_is_string(n)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Profile's 'Name' field is not a string."); + + const char *s = sd_json_variant_string(n); + + /* Pick the best of the default-v1, default-v2, … profiles */ + if (!startswith(s, "default-v")) + continue; + if (!best_profile || strverscmp_improved(s, best_profile) > 0) + best_profile = s; + } + + _cleanup_free_ char *copy = NULL; + if (best_profile) { + copy = strdup(best_profile); + if (!copy) + return log_oom(); } + *ret = TAKE_PTR(copy); + return 0; +} + +int manufacture_swtpm(const char *state_dir, const char *secret) { + int r; + + assert(state_dir); + + _cleanup_free_ char *swtpm_setup = NULL; + r = find_executable("swtpm_setup", &swtpm_setup); + if (r < 0) + return log_error_errno(r, "Failed to find 'swtpm_setup' binary: %m"); + + _cleanup_free_ char *best_profile = NULL; + r = swtpm_find_best_profile(swtpm_setup, &best_profile); + if (r < 0) + return r; + /* Create custom swtpm config files so that swtpm_localca uses our state directory instead of * the system-wide /var/lib/swtpm-localca/ which may not be writable. */ _cleanup_free_ char *localca_conf = path_join(state_dir, "swtpm-localca.conf"); @@ -172,8 +196,8 @@ int manufacture_swtpm(const char *state_dir, const char *secret) { if (r < 0) return log_error_errno(r, "Failed to write swtpm_setup.conf: %m"); - strv_free(args); - args = strv_new(swtpm_setup, + _cleanup_strv_free_ char **args = strv_new( + swtpm_setup, "--tpm-state", state_dir, "--tpm2", "--pcr-banks", "sha256", From f8e88be1d156c5e8000a85cc47ea5ab9007bdaf8 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 5 Apr 2026 19:36:14 +0200 Subject: [PATCH 0776/1296] swtpm-util: Silence noise from swtpm_setup There's no way to configure the log level for swtpm_setup, so pipe it's logfile (which defaults to stderr) to /dev/null unless debug logging is enabled. --- src/shared/swtpm-util.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/shared/swtpm-util.c b/src/shared/swtpm-util.c index 61413e7226aad..156a5422df9c5 100644 --- a/src/shared/swtpm-util.c +++ b/src/shared/swtpm-util.c @@ -216,6 +216,9 @@ int manufacture_swtpm(const char *state_dir, const char *secret) { if (best_profile && strv_extendf(&args, "--profile-name=%s", best_profile) < 0) return log_oom(); + if (!DEBUG_LOGGING && strv_extend_many(&args, "--logfile", "/dev/null") < 0) + return log_oom(); + if (DEBUG_LOGGING) { _cleanup_free_ char *cmdline = quote_command_line(args, SHELL_ESCAPE_EMPTY); log_debug("About to spawn: %s", strnull(cmdline)); From a85845f0437cd97b325ab09746ce81b4ecda0e90 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 5 Apr 2026 17:43:33 +0000 Subject: [PATCH 0777/1296] vmspawn: Add comment explaining substring match in firmware_data_matches_machine() The machine types in QEMU firmware descriptions are glob patterns like "pc-q35-*", so we use strstr() substring matching to check if our machine type is covered by a given firmware entry. --- src/vmspawn/vmspawn-util.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vmspawn/vmspawn-util.c b/src/vmspawn/vmspawn-util.c index 6e8a25baf5391..72187b6731a0a 100644 --- a/src/vmspawn/vmspawn-util.c +++ b/src/vmspawn/vmspawn-util.c @@ -151,6 +151,11 @@ static bool firmware_data_matches_machine(const FirmwareData *fwd, const char *a if (!streq((*t)->architecture, arch)) continue; + /* The machine types in firmware descriptions are glob patterns such as "pc-q35-*", but + * we pass the short alias (e.g. "q35") as the machine type to QEMU as it always points to + * the latest version. We can't use fnmatch() here because "q35" doesn't match the + * "pc-q35-*" glob, so instead we use substring matching to check if our machine type + * appears in the pattern. */ STRV_FOREACH(m, (*t)->machines) if (strstr(*m, machine)) return true; From fb4bfe651b7dda85b0545d340eac21c7988fe383 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 5 Apr 2026 17:46:12 +0000 Subject: [PATCH 0778/1296] vmspawn: Use ~ instead of ! as negation prefix for --firmware-features= Switch the negation character for firmware feature exclusion from "!" to "~" to be consistent with other systemd options that support negation such as SystemCallFilter=. --- man/systemd-vmspawn.xml | 4 ++-- src/vmspawn/vmspawn.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index d65bd965a39de..cca7496ed0468 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -358,7 +358,7 @@ Takes a comma-delimited list of firmware feature strings. This option may be specified multiple times, in which case the feature lists are combined. When specified, only firmware definitions that have all the required features will be considered during automatic - firmware discovery. Features prefixed with ! are excluded: firmware that has + firmware discovery. Features prefixed with ~ are excluded: firmware that has such a feature will be skipped. If a feature appears in both the included and excluded lists, inclusion takes priority. By default, firmware with the enrolled-keys feature is excluded. If an empty string is passed, both the included and excluded feature lists @@ -384,7 +384,7 @@ Configure whether to search for firmware which supports Secure Boot. Takes a boolean or auto. Setting this to yes is equivalent to and setting this to no is equivalent to - . Setting this to auto + . Setting this to auto removes secure-boot from both the included and excluded feature lists. diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 92a5555f64bbc..96ca4cee91e9b 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -857,7 +857,7 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); STRV_FOREACH(feature, features) { - const char *e = startswith(*feature, "!"); + const char *e = startswith(*feature, "~"); r = set_put_strdup(e ? &arg_firmware_features_exclude : &arg_firmware_features_include, e ?: *feature); if (r < 0) return log_oom(); From bde83a9e1ed2a86d518c0a1461ac7cb0a773564e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 5 Apr 2026 20:51:37 +0000 Subject: [PATCH 0779/1296] vmspawn: Redirect QEMU's stdin/stdout/stderr to the PTY When a PTY is allocated for the console, QEMU's own stdio file descriptors were still inherited directly from vmspawn, meaning any output QEMU writes to stdout/stderr (e.g. warnings) would bypass the PTY forwarder and go straight to the terminal. Similarly, QEMU could read directly from the terminal's stdin. Fix this by opening the PTY slave side and passing it as stdio_fds to the fork call with FORK_REARRANGE_STDIO, so that all of QEMU's I/O goes through the PTY and is properly forwarded. --- src/vmspawn/vmspawn.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 96ca4cee91e9b..ee2c49b778fb7 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -3517,12 +3517,20 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { log_debug("Executing: %s", joined); } + _cleanup_close_ int child_pty = -EBADF; + if (master >= 0) { + child_pty = pty_open_peer(master, O_RDWR|O_CLOEXEC|O_NOCTTY); + if (child_pty < 0) + return log_error_errno(child_pty, "Failed to open PTY slave: %m"); + } + _cleanup_(pidref_done) PidRef child_pidref = PIDREF_NULL; r = pidref_safe_fork_full( qemu_binary, - /* stdio_fds= */ NULL, + child_pty >= 0 ? (const int[]) { child_pty, child_pty, child_pty } : NULL, pass_fds, n_pass_fds, - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_CLOEXEC_OFF|FORK_RLIMIT_NOFILE_SAFE, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_CLOEXEC_OFF|FORK_RLIMIT_NOFILE_SAFE| + (child_pty >= 0 ? FORK_REARRANGE_STDIO : 0), &child_pidref); if (r < 0) return r; @@ -3539,6 +3547,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } /* Close relevant fds we passed to qemu in the parent. We don't need them anymore. */ + child_pty = safe_close(child_pty); child_vsock_fd = safe_close(child_vsock_fd); tap_fd = safe_close(tap_fd); From 3c96d1429f182d6b79cb6d25942caa18883fa552 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sat, 4 Apr 2026 19:56:13 +0200 Subject: [PATCH 0780/1296] namespace-util: Add logging to process_is_owned_by_uid() --- src/basic/namespace-util.c | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/basic/namespace-util.c b/src/basic/namespace-util.c index f156bfdf24994..3f355e082f759 100644 --- a/src/basic/namespace-util.c +++ b/src/basic/namespace-util.c @@ -804,16 +804,19 @@ int process_is_owned_by_uid(const PidRef *pidref, uid_t uid) { uid_t process_uid; r = pidref_get_uid(pidref, &process_uid); if (r < 0) - return r; + return log_debug_errno(r, "Failed to get UID of process " PID_FMT ": %m", pidref->pid); if (process_uid == uid) return true; + log_debug("Process " PID_FMT " has UID " UID_FMT ", which doesn't match expected UID " UID_FMT ", checking user namespace ownership.", + pidref->pid, process_uid, uid); + _cleanup_close_ int userns_fd = -EBADF; userns_fd = pidref_namespace_open_by_type(pidref, NAMESPACE_USER); if (userns_fd == -ENOPKG) /* If userns is not supported, then they don't matter for ownership */ return false; if (userns_fd < 0) - return userns_fd; + return log_debug_errno(userns_fd, "Failed to open user namespace of process " PID_FMT ": %m", pidref->pid); for (unsigned iteration = 0;; iteration++) { uid_t ns_uid; @@ -822,14 +825,21 @@ int process_is_owned_by_uid(const PidRef *pidref, uid_t uid) { * themselves matter. */ r = is_our_namespace(userns_fd, NAMESPACE_USER); if (r < 0) - return r; - if (r > 0) + return log_debug_errno(r, "Failed to check if user namespace of process " PID_FMT " is our own (iteration %u): %m", pidref->pid, iteration); + if (r > 0) { + log_debug("User namespace of process " PID_FMT " is our own namespace (iteration %u), not owned by expected UID.", pidref->pid, iteration); return false; + } if (ioctl(userns_fd, NS_GET_OWNER_UID, &ns_uid) < 0) - return -errno; - if (ns_uid == uid) + return log_debug_errno(errno, "Failed to get owner UID of user namespace of process " PID_FMT " (iteration %u): %m", pidref->pid, iteration); + if (ns_uid == uid) { + log_debug("User namespace of process " PID_FMT " is owned by UID " UID_FMT " (iteration %u), ownership check passed.", pidref->pid, uid, iteration); return true; + } + + log_debug("User namespace of process " PID_FMT " is owned by UID " UID_FMT ", expected UID " UID_FMT " (iteration %u), going up the tree.", + pidref->pid, ns_uid, uid, iteration); /* Paranoia check */ if (iteration > 16) @@ -838,10 +848,12 @@ int process_is_owned_by_uid(const PidRef *pidref, uid_t uid) { /* Go up the tree */ _cleanup_close_ int parent_fd = ioctl(userns_fd, NS_GET_USERNS); if (parent_fd < 0) { - if (errno == EPERM) /* EPERM means we left our own userns */ + if (errno == EPERM) { /* EPERM means we left our own userns */ + log_debug("NS_GET_USERNS ioctl returned EPERM for process " PID_FMT " (iteration %u), left our own userns.", pidref->pid, iteration); return false; + } - return -errno; + return log_debug_errno(errno, "NS_GET_USERNS ioctl failed for process " PID_FMT " (iteration %u): %m", pidref->pid, iteration); } close_and_replace(userns_fd, parent_fd); From 0e231729fdfbea0b137c9622c03f98c92fa56942 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sat, 4 Apr 2026 18:00:13 +0000 Subject: [PATCH 0781/1296] machined: skip leader ownership check for user scope When registering a machine, machined verifies that the leader process is owned by the calling user via process_is_owned_by_uid(). This check fails for user scope machined when the leader is inside a user namespace: after the leader calls setns(CLONE_NEWUSER), it becomes non-dumpable, and the subsequent ptrace_may_access() check in the kernel denies access to the process's user namespace, since the calling user lacks CAP_SYS_PTRACE in the mm's user namespace (the host namespace), even though the user owns the child user namespace. Skip this check when running in user scope. For system scope, the check is important because multiple users share the same machined instance, so one user must not be able to claim another user's process as a machine leader. For user scope this is unnecessary: the varlink socket lives under $XDG_RUNTIME_DIR (mode 0700), so only the owning user can connect, and the user machined instance can only perform operations bounded by that user's own privileges. Registering a foreign PID does not escalate capabilities. --- src/machine/machine-varlink.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c index dfc7020fc9583..07a860f6c16ca 100644 --- a/src/machine/machine-varlink.c +++ b/src/machine/machine-varlink.c @@ -192,8 +192,10 @@ int vl_method_register(sd_varlink *link, sd_json_variant *parameters, sd_varlink if (r < 0) return r; - /* Ensure an unprivileged user cannot claim any process they don't control as their own machine */ - if (machine->uid != 0) { + /* In system scope, ensure an unprivileged user cannot claim any process they don't + * control as their own machine. In user scope the varlink socket is already + * protected by $XDG_RUNTIME_DIR permissions. */ + if (manager->runtime_scope != RUNTIME_SCOPE_USER && machine->uid != 0) { r = process_is_owned_by_uid(&machine->leader, machine->uid); if (r < 0) return r; From 1e084aad7d5c2132ed32ff2a75bbe21205c0f5f3 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 29 Mar 2026 18:22:40 +0000 Subject: [PATCH 0782/1296] shared: move machine registration to shared machine-register.{c,h} Move register_machine() and unregister_machine() from vmspawn-register.{c,h} into shared machine-register.{c,h} so both nspawn and vmspawn can use the same implementation. The unified register_machine() uses varlink first (for richer features like SSH support and unit allocation) with a D-Bus RegisterMachineWithNetwork fallback for older machined. The interface adds a class parameter ("vm" or "container") and local_ifindex for nspawn's network interface support. The unified unregister_machine() similarly tries varlink first (io.systemd.Machine.Unregister) before falling back to D-Bus. Both register_machine() and unregister_machine() only log at debug level internally, leaving error/notice logging to callers. Add register_machine_with_fallback() which tries system and/or user scope registration based on a RuntimeScope parameter (_RUNTIME_SCOPE_INVALID for both), and unregister_machine_with_fallback() as its counterpart. Both use RET_GATHER() to collect errors from each scope. Make --register= a tristate (yes/no/auto) defaulting to auto. When set to auto, registration failures are logged at notice level and ignored. When set to yes, failures are fatal. Co-developed-by: Claude Opus 4.6 --- man/systemd-nspawn.xml | 10 +- man/systemd-vmspawn.xml | 11 +- shell-completion/bash/systemd-nspawn | 2 +- shell-completion/bash/systemd-vmspawn | 6 +- shell-completion/zsh/_systemd-nspawn | 2 +- src/nspawn/nspawn-register.c | 144 ---------- src/nspawn/nspawn-register.h | 10 - src/nspawn/nspawn.c | 59 ++-- src/shared/machine-register.c | 393 ++++++++++++++++++++++++++ src/shared/machine-register.h | 45 +++ src/shared/meson.build | 1 + src/vmspawn/meson.build | 1 - src/vmspawn/vmspawn-register.c | 104 ------- src/vmspawn/vmspawn-register.h | 19 -- src/vmspawn/vmspawn.c | 61 ++-- 15 files changed, 493 insertions(+), 375 deletions(-) create mode 100644 src/shared/machine-register.c create mode 100644 src/shared/machine-register.h delete mode 100644 src/vmspawn/vmspawn-register.c delete mode 100644 src/vmspawn/vmspawn-register.h diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index d241ca5c52c5e..e973b914dd049 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -781,14 +781,14 @@ Controls whether the container is registered with systemd-machined8. Takes a - boolean argument, which defaults to yes. This option should be enabled when the container - runs a full Operating System (more specifically: a system and service manager as PID 1), and is useful to - ensure that the container is accessible via + boolean argument or auto, and defaults to auto. This option should be + enabled when the container runs a full Operating System (more specifically: a system and service manager as + PID 1), and is useful to ensure that the container is accessible via machinectl1 and shown by tools such as ps1. If the container - does not run a service manager, it is recommended to set this option to - no. + does not run a service manager, it is recommended to set this option to no. When set to + auto, registration is attempted but failures are ignored. diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index cca7496ed0468..0a61d781d39d0 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -501,13 +501,10 @@ Controls whether the virtual machine is registered with systemd-machined8. Takes a - boolean argument, which defaults to yes when running as root, and no when - running as a regular user. This ensures that the virtual machine is accessible via - machinectl1. - - Note: root privileges are required to use this option as registering with - systemd-machined8 - requires privileged D-Bus method calls. + boolean argument or auto, and defaults to auto. This ensures that the + virtual machine is accessible via + machinectl1. When set to + auto, registration is attempted but failures are ignored. diff --git a/shell-completion/bash/systemd-nspawn b/shell-completion/bash/systemd-nspawn index 574018b1fff20..692020aa62cfb 100644 --- a/shell-completion/bash/systemd-nspawn +++ b/shell-completion/bash/systemd-nspawn @@ -131,7 +131,7 @@ _systemd_nspawn() { comps='' ;; --register) - comps='yes no' + comps='yes no auto' ;; --network-interface) comps=$(__get_interfaces) diff --git a/shell-completion/bash/systemd-vmspawn b/shell-completion/bash/systemd-vmspawn index 995aeb1271298..1ca45091a7ef4 100644 --- a/shell-completion/bash/systemd-vmspawn +++ b/shell-completion/bash/systemd-vmspawn @@ -31,8 +31,8 @@ _systemd_vmspawn() { local -A OPTS=( [STANDALONE]='-h --help --version -q --quiet --no-pager -n --network-tap --network-user-mode --user --system -x --ephemeral' [PATH]='-D --directory -i --image --linux --initrd --extra-drive --forward-journal --efi-nvram-template' - [BOOL]='--kvm --cxl --vsock --tpm --discard-disk --register --pass-ssh-key' - [SECURE_BOOT]='--secure-boot' + [BOOL]='--kvm --cxl --vsock --tpm --discard-disk --pass-ssh-key' + [TRISTATE]='--register --secure-boot' [FIRMWARE]='--firmware' [FIRMWARE_FEATURES]='--firmware-features' [BIND]='--bind --bind-ro' @@ -47,7 +47,7 @@ _systemd_vmspawn() { if __contains_word "$prev" ${OPTS[BOOL]}; then comps='yes no' - elif __contains_word "$prev" ${OPTS[SECURE_BOOT]}; then + elif __contains_word "$prev" ${OPTS[TRISTATE]}; then comps='yes no auto' elif __contains_word "$prev" ${OPTS[PATH]}; then compopt -o nospace -o filenames diff --git a/shell-completion/zsh/_systemd-nspawn b/shell-completion/zsh/_systemd-nspawn index 5f081700644c6..f613db908e30e 100644 --- a/shell-completion/zsh/_systemd-nspawn +++ b/shell-completion/zsh/_systemd-nspawn @@ -45,7 +45,7 @@ _arguments \ '--tmpfs=[Mount an empty tmpfs to the specified directory.]: : _files' \ '--setenv=[Specifies an environment variable assignment to pass to the init process in the container, in the format "NAME=VALUE".]: : _message "environment variables"' \ '--share-system[Allows the container to share certain system facilities with the host.]' \ - '--register=[Controls whether the container is registered with systemd-machined(8).]:systemd-machined registration:( yes no )' \ + '--register=[Controls whether the container is registered with systemd-machined(8).]:systemd-machined registration:( yes no auto )' \ '--keep-unit[Instead of creating a transient scope unit to run the container in, simply register the service or scope unit systemd-nspawn has been invoked in with systemd-machined(8).]' \ '--personality=[Control the architecture ("personality") reported by uname(2) in the container.]:architecture:(x86 x86-64)' \ '--volatile=[Run the system in volatile mode.]:volatile:(no yes state)' \ diff --git a/src/nspawn/nspawn-register.c b/src/nspawn/nspawn-register.c index 04031adcc5ab5..ace0f6637a545 100644 --- a/src/nspawn/nspawn-register.c +++ b/src/nspawn/nspawn-register.c @@ -131,150 +131,6 @@ static int can_set_coredump_receive(sd_bus *bus) { return r >= 0; } -static int register_machine_ex( - sd_bus *bus, - const char *machine_name, - const PidRef *pid, - const char *directory, - sd_id128_t uuid, - int local_ifindex, - const char *service, - sd_bus_error *error) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - int r; - - assert(bus); - assert(machine_name); - assert(service); - assert(error); - - r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "RegisterMachineEx"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "s", machine_name); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(m, 'a', "(sv)"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "(sv)(sv)(sv)", - "Id", "ay", SD_BUS_MESSAGE_APPEND_ID128(uuid), - "Service", "s", service, - "Class", "s", "container"); - if (r < 0) - return bus_log_create_error(r); - - if (pidref_is_set(pid)) { - if (pid->fd >= 0) { - r = sd_bus_message_append(m, "(sv)", "LeaderPIDFD", "h", pid->fd); - if (r < 0) - return bus_log_create_error(r); - } - - if (pid->fd_id > 0) { - r = sd_bus_message_append(m, "(sv)", "LeaderPIDFDID", "t", pid->fd_id); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "(sv)", "LeaderPID", "u", pid->pid); - if (r < 0) - return bus_log_create_error(r); - } - } - - if (!isempty(directory)) { - r = sd_bus_message_append(m, "(sv)", "RootDirectory", "s", directory); - if (r < 0) - return bus_log_create_error(r); - } - - if (local_ifindex > 0) { - r = sd_bus_message_append(m, "(sv)", "NetworkInterfaces", "ai", 1, local_ifindex); - if (r < 0) - return bus_log_create_error(r); - } - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - return sd_bus_call(bus, m, 0, error, NULL); -} - -int register_machine( - sd_bus *bus, - const char *machine_name, - const PidRef *pid, - const char *directory, - sd_id128_t uuid, - int local_ifindex, - const char *service) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(machine_name); - assert(service); - - r = register_machine_ex( - bus, - machine_name, - pid, - directory, - uuid, - local_ifindex, - service, - &error); - if (r >= 0) - return 0; - if (!sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) - return log_error_errno(r, "Failed to register machine: %s", bus_error_message(&error, r)); - - sd_bus_error_free(&error); - - r = bus_call_method( - bus, - bus_machine_mgr, - "RegisterMachineWithNetwork", - &error, - NULL, - "sayssusai", - machine_name, - SD_BUS_MESSAGE_APPEND_ID128(uuid), - service, - "container", - pidref_is_set(pid) ? (uint32_t) pid->pid : 0, - strempty(directory), - local_ifindex > 0 ? 1 : 0, local_ifindex); - if (r < 0) - return log_error_errno(r, "Failed to register machine: %s", bus_error_message(&error, r)); - - return 0; -} - -int unregister_machine( - sd_bus *bus, - const char *machine_name) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - - r = bus_call_method(bus, bus_machine_mgr, "UnregisterMachine", &error, NULL, "s", machine_name); - if (r < 0) - log_debug("Failed to unregister machine: %s", bus_error_message(&error, r)); - - return 0; -} - int allocate_scope( sd_bus *bus, const char *machine_name, diff --git a/src/nspawn/nspawn-register.h b/src/nspawn/nspawn-register.h index c4b8048606251..d82c780181c6d 100644 --- a/src/nspawn/nspawn-register.h +++ b/src/nspawn/nspawn-register.h @@ -4,16 +4,6 @@ #include "shared-forward.h" #include "nspawn-settings.h" -int register_machine( - sd_bus *bus, - const char *machine_name, - const PidRef *pid, - const char *directory, - sd_id128_t uuid, - int local_ifindex, - const char *service); -int unregister_machine(sd_bus *bus, const char *machine_name); - typedef enum AllocateScopeFlags { ALLOCATE_SCOPE_ALLOW_PIDFD = 1 << 0, } AllocateScopeFlags; diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 98e2de2711056..62676e935f002 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -66,6 +66,7 @@ #include "loopback-setup.h" #include "machine-bind-user.h" #include "machine-credential.h" +#include "machine-register.h" #include "main-func.h" #include "mkdir.h" #include "mount-util.h" @@ -192,7 +193,7 @@ static CustomMount *arg_custom_mounts = NULL; static size_t arg_n_custom_mounts = 0; static char **arg_setenv = NULL; static bool arg_quiet = false; -static bool arg_register = true; +static int arg_register = -1; static bool arg_keep_unit = false; static char **arg_network_interfaces = NULL; static char **arg_network_macvlan = NULL; @@ -1163,7 +1164,7 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_REGISTER: - r = parse_boolean_argument("--register=", optarg, &arg_register); + r = parse_tristate_argument_with_auto("--register=", optarg, &arg_register); if (r < 0) return r; @@ -5613,7 +5614,7 @@ static int run_container( /* Registration always happens on the system bus */ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *system_bus = NULL; - if (arg_register || (arg_privileged && !arg_keep_unit)) { + if (arg_register != 0 || (arg_privileged && !arg_keep_unit)) { r = sd_bus_default_system(&system_bus); if (r < 0) return log_error_errno(r, "Failed to open system bus: %m"); @@ -5628,7 +5629,7 @@ static int run_container( _cleanup_(sd_bus_flush_close_unrefp) sd_bus *user_bus = NULL; _cleanup_(sd_bus_unrefp) sd_bus *runtime_bus = NULL; - if (arg_register || !arg_keep_unit) { + if (arg_register != 0 || !arg_keep_unit) { if (arg_privileged) runtime_bus = sd_bus_ref(system_bus); else { @@ -5697,40 +5698,27 @@ static int run_container( } bool registered_system = false, registered_runtime = false; - if (arg_register) { - r = register_machine( + if (arg_register != 0) { + r = register_machine_with_fallback_and_log( + arg_privileged ? RUNTIME_SCOPE_SYSTEM : _RUNTIME_SCOPE_INVALID, system_bus, + runtime_bus, arg_machine, + arg_uuid, + arg_container_service_name, + "container", pid, arg_directory, - arg_uuid, + /* cid= */ 0, ifi, - arg_container_service_name); - if (r < 0) { - if (arg_privileged) /* if privileged the request to register definitely failed */ - return r; - - log_notice_errno(r, "Failed to register machine in system context, will try in user context."); - } else - registered_system = true; - - if (!arg_privileged) { - r = register_machine( - runtime_bus, - arg_machine, - pid, - arg_directory, - arg_uuid, - ifi, - arg_container_service_name); - if (r < 0) { - if (!registered_system) /* neither registration worked: fail */ - return r; - - log_notice_errno(r, "Failed to register machine in user context, but succeeded in system context, will proceed."); - } else - registered_runtime = true; - } + /* address= */ NULL, + /* key_path= */ NULL, + /* allocate_unit= */ false, + /* graceful= */ arg_register < 0, + ®istered_system, + ®istered_runtime); + if (r < 0) + return r; } if (arg_keep_unit && (arg_slice || arg_property)) @@ -5942,10 +5930,7 @@ static int run_container( r = wait_for_container(pid, &container_status); /* Tell machined that we are gone. */ - if (registered_system) - (void) unregister_machine(system_bus, arg_machine); - if (registered_runtime) - (void) unregister_machine(runtime_bus, arg_machine); + (void) unregister_machine_with_fallback_and_log(system_bus, runtime_bus, arg_machine, registered_system, registered_runtime); if (r < 0) /* We failed to wait for the container, or the container exited abnormally. */ diff --git a/src/shared/machine-register.c b/src/shared/machine-register.c new file mode 100644 index 0000000000000..d6e76db85c9fc --- /dev/null +++ b/src/shared/machine-register.c @@ -0,0 +1,393 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-bus.h" +#include "sd-id128.h" +#include "sd-json.h" +#include "sd-varlink.h" + +#include "bus-error.h" +#include "bus-locator.h" +#include "bus-util.h" +#include "errno-util.h" +#include "json-util.h" +#include "log.h" +#include "machine-register.h" +#include "path-lookup.h" +#include "pidref.h" +#include "runtime-scope.h" +#include "socket-util.h" +#include "string-util.h" +#include "terminal-util.h" + +static int register_machine_dbus_ex( + sd_bus *bus, + const char *machine_name, + sd_id128_t uuid, + const char *service, + const char *class, + const PidRef *pidref, + const char *directory, + int local_ifindex, + sd_bus_error *error) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + assert(bus); + assert(machine_name); + assert(service); + assert(class); + + r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "RegisterMachineEx"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", machine_name); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "(sv)(sv)(sv)", + "Id", "ay", SD_BUS_MESSAGE_APPEND_ID128(uuid), + "Service", "s", service, + "Class", "s", class); + if (r < 0) + return bus_log_create_error(r); + + if (pidref_is_set(pidref)) { + if (pidref->fd >= 0) { + r = sd_bus_message_append(m, "(sv)", "LeaderPIDFD", "h", pidref->fd); + if (r < 0) + return bus_log_create_error(r); + } + + if (pidref->fd_id > 0) { + r = sd_bus_message_append(m, "(sv)", "LeaderPIDFDID", "t", pidref->fd_id); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "(sv)", "LeaderPID", "u", pidref->pid); + if (r < 0) + return bus_log_create_error(r); + } + } + + if (!isempty(directory)) { + r = sd_bus_message_append(m, "(sv)", "RootDirectory", "s", directory); + if (r < 0) + return bus_log_create_error(r); + } + + if (local_ifindex > 0) { + r = sd_bus_message_append(m, "(sv)", "NetworkInterfaces", "ai", 1, local_ifindex); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + return sd_bus_call(bus, m, 0, error, NULL); +} + +static int register_machine_dbus( + sd_bus *bus, + const char *machine_name, + sd_id128_t uuid, + const char *service, + const char *class, + const PidRef *pidref, + const char *directory, + int local_ifindex) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(machine_name); + assert(service); + assert(class); + + /* First try RegisterMachineEx which supports PIDFD-based leader tracking. */ + r = register_machine_dbus_ex(bus, machine_name, uuid, service, class, pidref, directory, local_ifindex, &error); + if (r >= 0) + return 0; + if (!sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) + return log_debug_errno(r, "Failed to register machine via D-Bus: %s", bus_error_message(&error, r)); + + sd_bus_error_free(&error); + + r = bus_call_method( + bus, + bus_machine_mgr, + "RegisterMachineWithNetwork", + &error, + NULL, + "sayssusai", + machine_name, + SD_BUS_MESSAGE_APPEND_ID128(uuid), + service, + class, + pidref_is_set(pidref) ? (uint32_t) pidref->pid : 0, + strempty(directory), + local_ifindex > 0 ? 1 : 0, local_ifindex); + if (r < 0) + return log_debug_errno(r, "Failed to register machine via D-Bus: %s", bus_error_message(&error, r)); + + return 0; +} + +int register_machine( + sd_bus *bus, + const char *machine_name, + sd_id128_t uuid, + const char *service, + const char *class, + const PidRef *pidref, + const char *directory, + unsigned cid, + int local_ifindex, + const char *address, + const char *key_path, + bool allocate_unit, + RuntimeScope scope) { + + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + int r; + + assert(machine_name); + assert(service); + assert(class); + + /* First try to use varlink, as it provides more features (such as SSH support). */ + _cleanup_free_ char *p = NULL; + r = runtime_directory_generic(scope, "systemd/machine/io.systemd.Machine", &p); + if (r >= 0) + r = sd_varlink_connect_address(&vl, p); + if (r == -ENOENT || ERRNO_IS_DISCONNECT(r)) { + log_debug_errno(r, "Failed to connect to machined via varlink%s%s, falling back to D-Bus: %m", + p ? " on " : "", strempty(p)); + + /* In case we are running with an older machined, fall back to D-Bus. */ + if (!bus) + return log_debug_errno(SYNTHETIC_ERRNO(ESRCH), "Varlink connection to machined not available and no bus provided."); + + return register_machine_dbus(bus, machine_name, uuid, service, class, pidref, directory, local_ifindex); + } + if (r < 0) + return log_debug_errno(r, "Failed to connect to machined on %s: %m", strna(p)); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = sd_varlink_callbo( + vl, + "io.systemd.Machine.Register", + &reply, + &error_id, + SD_JSON_BUILD_PAIR_STRING("name", machine_name), + SD_JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(uuid), "id", SD_JSON_BUILD_ID128(uuid)), + SD_JSON_BUILD_PAIR_STRING("service", service), + SD_JSON_BUILD_PAIR_STRING("class", class), + SD_JSON_BUILD_PAIR_CONDITION(VSOCK_CID_IS_REGULAR(cid), "vSockCid", SD_JSON_BUILD_UNSIGNED(cid)), + SD_JSON_BUILD_PAIR_CONDITION(local_ifindex > 0, "ifIndices", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_INTEGER(local_ifindex))), + SD_JSON_BUILD_PAIR_CONDITION(!!directory, "rootDirectory", SD_JSON_BUILD_STRING(directory)), + SD_JSON_BUILD_PAIR_CONDITION(!!address, "sshAddress", SD_JSON_BUILD_STRING(address)), + SD_JSON_BUILD_PAIR_CONDITION(!!key_path, "sshPrivateKeyPath", SD_JSON_BUILD_STRING(key_path)), + SD_JSON_BUILD_PAIR_CONDITION(isatty_safe(STDIN_FILENO), "allowInteractiveAuthentication", SD_JSON_BUILD_BOOLEAN(true)), + SD_JSON_BUILD_PAIR_CONDITION(allocate_unit, "allocateUnit", SD_JSON_BUILD_BOOLEAN(true)), + SD_JSON_BUILD_PAIR_CONDITION(pidref_is_set(pidref), "leaderProcessId", JSON_BUILD_PIDREF(pidref))); + if (r < 0) + return log_debug_errno(r, "Failed to register machine via varlink: %m"); + if (error_id) + return log_debug_errno(sd_varlink_error_to_errno(error_id, reply), + "Failed to register machine via varlink: %s", error_id); + + return 0; +} + +static const char* machine_registration_scope_string(RuntimeScope scope, bool registered_system, bool registered_user) { + if (scope == _RUNTIME_SCOPE_INVALID) { + if (!registered_system && !registered_user) + return "system and user"; + if (!registered_system) + return "system"; + return "user"; + } + + return runtime_scope_to_string(scope); +} + +int register_machine_with_fallback_and_log( + RuntimeScope scope, + sd_bus *system_bus, + sd_bus *user_bus, + const char *machine_name, + sd_id128_t uuid, + const char *service, + const char *class, + const PidRef *pidref, + const char *directory, + unsigned cid, + int local_ifindex, + const char *address, + const char *key_path, + bool allocate_unit, + bool graceful, + bool *reterr_registered_system, + bool *reterr_registered_user) { + + bool registered_system = false, registered_user = false; + int r = 0; + + assert(IN_SET(scope, RUNTIME_SCOPE_SYSTEM, RUNTIME_SCOPE_USER, _RUNTIME_SCOPE_INVALID)); + assert(system_bus || !IN_SET(scope, RUNTIME_SCOPE_SYSTEM, _RUNTIME_SCOPE_INVALID)); + assert(user_bus || !IN_SET(scope, RUNTIME_SCOPE_USER, _RUNTIME_SCOPE_INVALID)); + assert(machine_name); + assert(service); + assert(class); + assert(reterr_registered_system); + assert(reterr_registered_user); + + if (IN_SET(scope, RUNTIME_SCOPE_SYSTEM, _RUNTIME_SCOPE_INVALID)) { + int q = register_machine( + system_bus, + machine_name, + uuid, + service, + class, + pidref, + directory, + cid, + local_ifindex, + address, + key_path, + scope == RUNTIME_SCOPE_SYSTEM ? allocate_unit : false, + RUNTIME_SCOPE_SYSTEM); + if (q < 0) + RET_GATHER(r, q); + else + registered_system = true; + } + + if (IN_SET(scope, RUNTIME_SCOPE_USER, _RUNTIME_SCOPE_INVALID)) { + int q = register_machine( + user_bus, + machine_name, + uuid, + service, + class, + pidref, + directory, + cid, + local_ifindex, + address, + key_path, + allocate_unit, + RUNTIME_SCOPE_USER); + if (q < 0) + RET_GATHER(r, q); + else + registered_user = true; + } + + if (r < 0) { + if (graceful) { + log_notice_errno(r, "Failed to register machine in %s context, ignoring: %m", + machine_registration_scope_string(scope, registered_system, registered_user)); + r = 0; + } else + r = log_error_errno(r, "Failed to register machine in %s context: %m", + machine_registration_scope_string(scope, registered_system, registered_user)); + } + + if (reterr_registered_system) + *reterr_registered_system = registered_system; + if (reterr_registered_user) + *reterr_registered_user = registered_user; + + return r; +} + +int unregister_machine_with_fallback_and_log( + sd_bus *system_bus, + sd_bus *user_bus, + const char *machine_name, + bool registered_system, + bool registered_user) { + + int r = 0; + bool failed_system = false, failed_user = false; + + if (registered_system) { + int q = unregister_machine(system_bus, machine_name, RUNTIME_SCOPE_SYSTEM); + if (q < 0) { + RET_GATHER(r, q); + failed_system = true; + } + } + + if (registered_user) { + int q = unregister_machine(user_bus, machine_name, RUNTIME_SCOPE_USER); + if (q < 0) { + RET_GATHER(r, q); + failed_user = true; + } + } + + if (r < 0) + log_notice_errno(r, "Failed to unregister machine in %s context, ignoring: %m", + machine_registration_scope_string( + registered_system && registered_user ? _RUNTIME_SCOPE_INVALID : + registered_system ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER, + !failed_system, !failed_user)); + + return 0; +} + +int unregister_machine(sd_bus *bus, const char *machine_name, RuntimeScope scope) { + int r; + + assert(machine_name); + + /* First try varlink */ + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + _cleanup_free_ char *p = NULL; + r = runtime_directory_generic(scope, "systemd/machine/io.systemd.Machine", &p); + if (r >= 0) + r = sd_varlink_connect_address(&vl, p); + if (r >= 0) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = sd_varlink_callbo( + vl, + "io.systemd.Machine.Unregister", + &reply, + &error_id, + SD_JSON_BUILD_PAIR_STRING("name", machine_name)); + if (r >= 0 && !error_id) + return 0; + if (r >= 0) + r = sd_varlink_error_to_errno(error_id, reply); + } + + log_debug_errno(r, "Failed to unregister machine via varlink, falling back to D-Bus: %m"); + + /* Fall back to D-Bus */ + if (!bus) + return log_debug_errno(SYNTHETIC_ERRNO(ESRCH), "Varlink connection to machined not available and no bus provided."); + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = bus_call_method(bus, bus_machine_mgr, "UnregisterMachine", &error, NULL, "s", machine_name); + if (r < 0) + return log_debug_errno(r, "Failed to unregister machine via D-Bus: %s", bus_error_message(&error, r)); + + return 0; +} diff --git a/src/shared/machine-register.h b/src/shared/machine-register.h new file mode 100644 index 0000000000000..e405873b5ba2b --- /dev/null +++ b/src/shared/machine-register.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +int register_machine( + sd_bus *bus, + const char *machine_name, + sd_id128_t uuid, + const char *service, + const char *class, + const PidRef *pidref, + const char *directory, + unsigned cid, + int local_ifindex, + const char *address, + const char *key_path, + bool allocate_unit, + RuntimeScope scope); +int register_machine_with_fallback_and_log( + RuntimeScope scope, + sd_bus *system_bus, + sd_bus *user_bus, + const char *machine_name, + sd_id128_t uuid, + const char *service, + const char *class, + const PidRef *pidref, + const char *directory, + unsigned cid, + int local_ifindex, + const char *address, + const char *key_path, + bool allocate_unit, + bool graceful, + bool *reterr_registered_system, + bool *reterr_registered_user); + +int unregister_machine(sd_bus *bus, const char *machine_name, RuntimeScope scope); +int unregister_machine_with_fallback_and_log( + sd_bus *system_bus, + sd_bus *user_bus, + const char *machine_name, + bool registered_system, + bool registered_user); diff --git a/src/shared/meson.build b/src/shared/meson.build index e2c1501adb86b..2c6207d494454 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -125,6 +125,7 @@ shared_sources = files( 'machine-bind-user.c', 'machine-credential.c', 'machine-id-setup.c', + 'machine-register.c', 'macvlan-util.c', 'main-func.c', 'metrics.c', diff --git a/src/vmspawn/meson.build b/src/vmspawn/meson.build index 722e6a52cc7f2..99bad2d618973 100644 --- a/src/vmspawn/meson.build +++ b/src/vmspawn/meson.build @@ -10,7 +10,6 @@ vmspawn_sources = files( 'vmspawn-settings.c', 'vmspawn-scope.c', 'vmspawn-mount.c', - 'vmspawn-register.c', ) vmspawn_extract_sources = files( 'vmspawn-util.c', diff --git a/src/vmspawn/vmspawn-register.c b/src/vmspawn/vmspawn-register.c deleted file mode 100644 index 46f292ce49525..0000000000000 --- a/src/vmspawn/vmspawn-register.c +++ /dev/null @@ -1,104 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include - -#include "sd-bus.h" -#include "sd-id128.h" -#include "sd-json.h" -#include "sd-varlink.h" - -#include "bus-error.h" -#include "bus-locator.h" -#include "errno-util.h" -#include "json-util.h" -#include "log.h" -#include "path-lookup.h" -#include "pidref.h" -#include "socket-util.h" -#include "string-util.h" -#include "terminal-util.h" -#include "varlink-util.h" -#include "vmspawn-register.h" - -int register_machine( - sd_bus *bus, - const char *machine_name, - sd_id128_t uuid, - const char *service, - const PidRef *pidref, - const char *directory, - unsigned cid, - const char *address, - const char *key_path, - bool allocate_unit, - RuntimeScope scope) { - - _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; - int r; - - assert(machine_name); - assert(service); - - /* First try to use varlink, as it provides more features (such as SSH support). */ - _cleanup_free_ char *p = NULL; - r = runtime_directory_generic(scope, "systemd/machine/io.systemd.Machine", &p); - if (r < 0) - return r; - - r = sd_varlink_connect_address(&vl, p); - if (r == -ENOENT || ERRNO_IS_DISCONNECT(r)) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - - assert(bus); - - /* In case we are running with an older machined, fallback to the existing D-Bus method. */ - r = bus_call_method( - bus, - bus_machine_mgr, - "RegisterMachine", - &error, - NULL, - "sayssus", - machine_name, - SD_BUS_MESSAGE_APPEND_ID128(uuid), - service, - "vm", - (uint32_t) (pidref_is_set(pidref) ? pidref->pid : 0), - strempty(directory)); - if (r < 0) - return log_error_errno(r, "Failed to register machine: %s", bus_error_message(&error, r)); - - return 0; - } - if (r < 0) - return log_error_errno(r, "Failed to connect to machined on %p: %m", p); - - return varlink_callbo_and_log( - vl, - "io.systemd.Machine.Register", - /* ret_reply= */ NULL, - SD_JSON_BUILD_PAIR_STRING("name", machine_name), - SD_JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(uuid), "id", SD_JSON_BUILD_ID128(uuid)), - SD_JSON_BUILD_PAIR_STRING("service", service), - SD_JSON_BUILD_PAIR_STRING("class", "vm"), - SD_JSON_BUILD_PAIR_CONDITION(VSOCK_CID_IS_REGULAR(cid), "vSockCid", SD_JSON_BUILD_UNSIGNED(cid)), - SD_JSON_BUILD_PAIR_CONDITION(!!directory, "rootDirectory", SD_JSON_BUILD_STRING(directory)), - SD_JSON_BUILD_PAIR_CONDITION(!!address, "sshAddress", SD_JSON_BUILD_STRING(address)), - SD_JSON_BUILD_PAIR_CONDITION(!!key_path, "sshPrivateKeyPath", SD_JSON_BUILD_STRING(key_path)), - SD_JSON_BUILD_PAIR_CONDITION(isatty_safe(STDIN_FILENO), "allowInteractiveAuthentication", SD_JSON_BUILD_BOOLEAN(true)), - SD_JSON_BUILD_PAIR_CONDITION(allocate_unit, "allocateUnit", SD_JSON_BUILD_BOOLEAN(true)), - SD_JSON_BUILD_PAIR_CONDITION(pidref_is_set(pidref), "leaderProcessId", JSON_BUILD_PIDREF(pidref))); -} - -int unregister_machine(sd_bus *bus, const char *machine_name) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - - r = bus_call_method(bus, bus_machine_mgr, "UnregisterMachine", &error, NULL, "s", machine_name); - if (r < 0) - log_debug("Failed to unregister machine: %s", bus_error_message(&error, r)); - - return 0; -} diff --git a/src/vmspawn/vmspawn-register.h b/src/vmspawn/vmspawn-register.h deleted file mode 100644 index de118b7492fa2..0000000000000 --- a/src/vmspawn/vmspawn-register.h +++ /dev/null @@ -1,19 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include "shared-forward.h" - -int register_machine( - sd_bus *bus, - const char *machine_name, - sd_id128_t uuid, - const char *service, - const PidRef *pidref, - const char *directory, - unsigned cid, - const char *address, - const char *key_path, - bool allocate_unit, - RuntimeScope scope); - -int unregister_machine(sd_bus *bus, const char *machine_name); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index ee2c49b778fb7..f91c36193dd01 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -51,6 +51,7 @@ #include "log.h" #include "machine-bind-user.h" #include "machine-credential.h" +#include "machine-register.h" #include "main-func.h" #include "mkdir.h" #include "namespace-util.h" @@ -89,7 +90,6 @@ #include "utf8.h" #include "vmspawn-mount.h" #include "vmspawn-qemu-config.h" -#include "vmspawn-register.h" #include "vmspawn-scope.h" #include "vmspawn-settings.h" #include "vmspawn-util.h" @@ -150,7 +150,7 @@ static bool arg_firmware_describe = false; static Set *arg_firmware_features_include = NULL; static Set *arg_firmware_features_exclude = NULL; static char *arg_forward_journal = NULL; -static bool arg_register = true; +static int arg_register = -1; static bool arg_keep_unit = false; static sd_id128_t arg_uuid = {}; static char **arg_kernel_cmdline_extra = NULL; @@ -665,7 +665,7 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_REGISTER: - r = parse_boolean_argument("--register=", optarg, &arg_register); + r = parse_tristate_argument_with_auto("--register=", optarg, &arg_register); if (r < 0) return r; @@ -2234,7 +2234,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { /* Registration always happens on the system bus */ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *system_bus = NULL; - if (arg_register || arg_runtime_scope == RUNTIME_SCOPE_SYSTEM) { + if (arg_register != 0 || arg_runtime_scope == RUNTIME_SCOPE_SYSTEM) { r = sd_bus_default_system(&system_bus); if (r < 0) return log_error_errno(r, "Failed to open system bus: %m"); @@ -3571,7 +3571,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } bool scope_allocated = false; - if (!arg_keep_unit && (!arg_register || arg_runtime_scope != RUNTIME_SCOPE_SYSTEM)) { + if (!arg_keep_unit && (arg_register == 0 || arg_runtime_scope != RUNTIME_SCOPE_SYSTEM)) { r = allocate_scope( runtime_bus, arg_machine, @@ -3596,51 +3596,29 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } bool registered_system = false, registered_runtime = false; - if (arg_register) { + if (arg_register != 0) { char vm_address[STRLEN("vsock/") + DECIMAL_STR_MAX(unsigned)]; xsprintf(vm_address, "vsock/%u", child_cid); - r = register_machine( + r = register_machine_with_fallback_and_log( + arg_runtime_scope == RUNTIME_SCOPE_USER ? _RUNTIME_SCOPE_INVALID : RUNTIME_SCOPE_SYSTEM, system_bus, + runtime_bus, arg_machine, arg_uuid, "systemd-vmspawn", + "vm", &child_pidref, arg_directory, child_cid, + /* local_ifindex= */ 0, child_cid != VMADDR_CID_ANY ? vm_address : NULL, ssh_private_key_path, - !arg_keep_unit && arg_runtime_scope == RUNTIME_SCOPE_SYSTEM, - RUNTIME_SCOPE_SYSTEM); - if (r < 0) { - /* if privileged the request to register definitely failed */ - if (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM) - return r; - - log_notice_errno(r, "Failed to register machine in system context, will try in user context."); - } else - registered_system = true; - - if (arg_runtime_scope == RUNTIME_SCOPE_USER) { - r = register_machine( - runtime_bus, - arg_machine, - arg_uuid, - "systemd-vmspawn", - &child_pidref, - arg_directory, - child_cid, - child_cid != VMADDR_CID_ANY ? vm_address : NULL, - ssh_private_key_path, - !arg_keep_unit, - RUNTIME_SCOPE_USER); - if (r < 0) { - if (!registered_system) /* neither registration worked: fail */ - return r; - - log_notice_errno(r, "Failed to register machine in user context, but succeeded in system context, will proceed."); - } else - registered_runtime = true; - } + !arg_keep_unit, + /* graceful= */ arg_register < 0, + ®istered_system, + ®istered_runtime); + if (r < 0) + return r; } /* Report that the VM is now set up */ @@ -3743,10 +3721,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (scope_allocated) terminate_scope(runtime_bus, arg_machine); - if (registered_system) - (void) unregister_machine(system_bus, arg_machine); - if (registered_runtime) - (void) unregister_machine(runtime_bus, arg_machine); + (void) unregister_machine_with_fallback_and_log(system_bus, runtime_bus, arg_machine, registered_system, registered_runtime); if (use_vsock) { if (exit_status == INT_MAX) { From e7fb7296f56dacc24054cddb2e1f0aa55ee7dc94 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 29 Mar 2026 11:10:42 +0000 Subject: [PATCH 0783/1296] nspawn: rename --user= to --uid= and repurpose --user/--system for runtime scope Rename nspawn's --user=NAME option to --uid=NAME for selecting the container user. The -u short option is preserved. --user=NAME and --user NAME are still accepted but emit a deprecation warning. A pre-parsing step stitches the space-separated --user NAME form into --user=NAME before getopt sees it, preserving backwards compatibility despite --user now being an optional_argument. Repurpose --user (without argument) and --system as standalone switches for selecting the runtime scope (user vs system service manager). Replace all uses of the arg_privileged boolean with arg_runtime_scope comparisons throughout nspawn. The default scope is auto-detected from the effective UID. Co-developed-by: Claude Opus 4.6 --- NEWS | 7 ++ man/systemd-nspawn.xml | 26 ++++- shell-completion/bash/systemd-nspawn | 6 +- shell-completion/zsh/_systemd-nspawn | 4 +- src/nspawn/nspawn.c | 143 ++++++++++++++++++++------- src/vmspawn/vmspawn.c | 4 +- test/units/TEST-13-NSPAWN.nspawn.sh | 13 ++- 7 files changed, 154 insertions(+), 49 deletions(-) diff --git a/NEWS b/NEWS index c717a355ecdfa..71ad3455230a3 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,13 @@ CHANGES WITH 261 in spe: Feature Removals and Incompatible Changes: + * systemd-nspawn's --user= option has been renamed to --uid=. The -u + short option continues to work. The old --user NAME and --user=NAME + form (with and without "=") are still accepted but deprecated; a warning + is emitted suggesting --uid=NAME. The --user option (without an argument) + has been repurposed as a standalone switch (without argument) to select + the user service manager scope, matching --system. + * It was discovered that systemd-stub does not measure all the events it measures to the TPM to the hardware CC registers (e.g. Intel TDX RTMRs) using EFI_CC_MEASUREMENT_PROTOCOL. In particular, devicetree, diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index e973b914dd049..5c7acf51594bc 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -629,13 +629,16 @@ - + After transitioning into the container, change to the specified user defined in the container's user database. Like all other systemd-nspawn features, this is not a security feature and - provides protection against accidental destructive operations only. + provides protection against accidental destructive operations only. This option was previously named + . The old name is still accepted but deprecated. The short option + is compatible with both old and new versions of + systemd-nspawn. - Note that if credentials are used in combination with a non-root + Note that if credentials are used in combination with a non-root (e.g.: or ), then must be used, and or must not be used, as the credentials would otherwise be unreadable @@ -1922,6 +1925,23 @@ After=sys-subsystem-net-devices-ens1.device Other + + + + + Specify whether to run in system or user scope. This controls which service manager and + machined instance to interact with, as well as operational defaults such as user namespace mode and + private networking. If neither is specified, is implied when running as + root, otherwise. + + Note: for backwards compatibility, followed by a positional argument + is interpreted as the deprecated form (now + ). To use for scope selection when positional + arguments follow, separate them with --. + + + + diff --git a/shell-completion/bash/systemd-nspawn b/shell-completion/bash/systemd-nspawn index 692020aa62cfb..08ff25d906c1f 100644 --- a/shell-completion/bash/systemd-nspawn +++ b/shell-completion/bash/systemd-nspawn @@ -69,8 +69,8 @@ _systemd_nspawn() { local -A OPTS=( [STANDALONE]='-h --help --version --private-network -b --boot --read-only -q --quiet --share-system --keep-unit -n --network-veth -j -x --ephemeral -a --as-pid2 -U --suppress-sync=yes - --cleanup' - [ARG]='-D --directory -u --user --uuid --capability --drop-capability --link-journal --bind --bind-ro + --cleanup --user --system' + [ARG]='-D --directory -u --uid --uuid --capability --drop-capability --link-journal --bind --bind-ro -M --machine -S --slice -E --setenv -Z --selinux-context -L --selinux-apifs-context --register --network-interface --network-bridge --personality -i --image --image-policy --tmpfs --volatile --network-macvlan --kill-signal --template --notify-ready --root-hash --chdir @@ -88,7 +88,7 @@ _systemd_nspawn() { compopt -o nospace comps=$(compgen -S/ -A directory -- "$cur" ) ;; - --user|-u) + --uid|-u) comps=$( __get_users ) ;; --uuid|--root-hash) diff --git a/shell-completion/zsh/_systemd-nspawn b/shell-completion/zsh/_systemd-nspawn index f613db908e30e..fa79b7f8d8679 100644 --- a/shell-completion/zsh/_systemd-nspawn +++ b/shell-completion/zsh/_systemd-nspawn @@ -22,7 +22,9 @@ _arguments \ '(--ephemeral -x)'{--ephemeral,-x}'[Run container with snapshot of root directory, and remove it after exit.]' \ '(--image -i)'{--image=,-i+}'[Disk image to mount the root directory for the container from.]:disk image: _files' \ '(--boot -b)'{--boot,-b}'[Automatically search for an init binary and invoke it instead of a shell or a user supplied program.]' \ - '(--user -u)'{--user=,-u+}'[Run the command under specified user, create home directory and cd into it.]:user:_users' \ + '(--uid -u)'{--uid=,-u+}'[Run the command under specified user, create home directory and cd into it.]:user:_users' \ + '--user[Run in user service manager scope]' \ + '--system[Run in system service manager scope]' \ '(--machine -M)'{--machine=,-M+}'[Sets the machine name for this container.]: : _message "container name"' \ '--uuid=[Set the specified uuid for the container.]: : _message "container UUID"' \ '(--slice -S)'{--slice=,-S+}'[Make the container part of the specified slice, instead of the default machine.slice.]: : _message slice' \ diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 62676e935f002..ff5f79a7cb6b7 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -154,7 +154,7 @@ static char *arg_hostname = NULL; /* The name the payload sees by default */ static const char *arg_selinux_context = NULL; static const char *arg_selinux_apifs_context = NULL; static char *arg_slice = NULL; -static bool arg_private_network; /* initialized depending on arg_privileged in run() */ +static bool arg_private_network = false; static bool arg_read_only = false; static StartMode arg_start_mode = START_PID1; static bool arg_ephemeral = false; @@ -213,7 +213,7 @@ static VolatileMode arg_volatile_mode = VOLATILE_NO; static ExposePort *arg_expose_ports = NULL; static char **arg_property = NULL; static sd_bus_message *arg_property_message = NULL; -static UserNamespaceMode arg_userns_mode; /* initialized depending on arg_privileged in run() */ +static UserNamespaceMode arg_userns_mode = _USER_NAMESPACE_MODE_INVALID; static uid_t arg_uid_shift = UID_INVALID, arg_uid_range = 0x10000U; static unsigned arg_delegate_container_ranges = 0; static UserNamespaceOwnership arg_userns_ownership = _USER_NAMESPACE_OWNERSHIP_INVALID; @@ -254,7 +254,7 @@ static char *arg_settings_filename = NULL; static Architecture arg_architecture = _ARCHITECTURE_INVALID; static ImagePolicy *arg_image_policy = NULL; static char *arg_background = NULL; -static bool arg_privileged = false; +static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID; static bool arg_cleanup = false; static bool arg_ask_password = true; @@ -408,7 +408,7 @@ static int help(void) { " -b --boot Boot up full system (i.e. invoke init)\n" " --chdir=PATH Set working directory in the container\n" " -E --setenv=NAME[=VALUE] Pass an environment variable to PID 1\n" - " -u --user=USER Run the command under specified user or UID\n" + " -u --uid=USER Run the command under specified user or UID\n" " --kill-signal=SIGNAL Select signal to use for shutting down PID 1\n" " --notify-ready=BOOLEAN Receive notifications from the child init process\n" " --suppress-sync=BOOLEAN\n" @@ -522,6 +522,9 @@ static int help(void) { " --load-credential=ID:PATH\n" " Load credential to pass to container from file or\n" " AF_UNIX stream socket.\n" + "\n%3$sOther:%4$s\n" + " --system Run in the system service manager scope\n" + " --user Run in the user service manager scope\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -750,6 +753,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_CLEANUP, ARG_NO_ASK_PASSWORD, ARG_MSTACK, + ARG_USER, + ARG_SYSTEM, }; static const struct option options[] = { @@ -758,7 +763,8 @@ static int parse_argv(int argc, char *argv[]) { { "directory", required_argument, NULL, 'D' }, { "template", required_argument, NULL, ARG_TEMPLATE }, { "ephemeral", no_argument, NULL, 'x' }, - { "user", required_argument, NULL, 'u' }, + { "uid", required_argument, NULL, 'u' }, + { "user", optional_argument, NULL, ARG_USER }, { "private-network", no_argument, NULL, ARG_PRIVATE_NETWORK }, { "as-pid2", no_argument, NULL, 'a' }, { "boot", no_argument, NULL, 'b' }, @@ -831,6 +837,7 @@ static int parse_argv(int argc, char *argv[]) { { "cleanup", no_argument, NULL, ARG_CLEANUP }, { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, { "mstack", required_argument, NULL, ARG_MSTACK }, + { "system", no_argument, NULL, ARG_SYSTEM }, {} }; @@ -841,6 +848,43 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); + /* --user= used to require an argument (the container user to run as). It has been repurposed to + * optionally set the runtime scope, with --uid= replacing the old container user functionality. + * To maintain backwards compatibility with the space-separated form (--user NAME), stitch them + * together into --user=NAME before getopt sees them. Without this, getopt's optional_argument + * handling would interpret --user NAME as --user (no arg) followed by a positional argument. + * Operate on a copy to avoid modifying the caller's argv. */ + _cleanup_strv_free_ char **argv_copy = NULL; + for (int i = 1; i < argc - 1; i++) { + if (streq(argv[i], "--")) + break; /* Respect end-of-options sentinel */ + if (!streq(argv[i], "--user")) + continue; + if (argv[i + 1][0] == '-') + continue; /* Next arg is an option, not a username */ + + /* Deep copy so we can freely replace and free entries */ + if (!argv_copy) { + argv_copy = strv_copy(argv); + if (!argv_copy) + return log_oom(); + argv = argv_copy; + } + + log_warning("--user NAME is deprecated, use --uid=NAME instead."); + + /* Stitch "--user" and the following argument into "--user=NAME" */ + free(argv[i]); + argv[i] = strjoin("--user=", argv[i + 1]); + if (!argv[i]) + return log_oom(); + + /* Remove the now-consumed argument and shrink argc accordingly */ + free(argv[i + 1]); + memmove(argv + i + 1, argv + i + 2, (argc - i - 1) * sizeof(char*)); + argc--; + } + /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long() * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */ optind = 0; @@ -1230,8 +1274,11 @@ static int parse_argv(int argc, char *argv[]) { case 'U': if (userns_supported()) { - /* Note that arg_userns_ownership is implied by USER_NAMESPACE_PICK further down. */ - arg_userns_mode = arg_privileged ? USER_NAMESPACE_PICK : USER_NAMESPACE_MANAGED; + /* Note that arg_userns_ownership is implied by USER_NAMESPACE_PICK further down. + * We use _USER_NAMESPACE_MODE_INVALID as a marker so that the final resolution + * (PICK vs MANAGED) is deferred to after the getopt loop where arg_runtime_scope + * has its final value regardless of option order. */ + arg_userns_mode = _USER_NAMESPACE_MODE_INVALID; arg_uid_shift = UID_INVALID; arg_uid_range = UINT32_C(0x10000); @@ -1600,6 +1647,24 @@ static int parse_argv(int argc, char *argv[]) { arg_ask_password = false; break; + case ARG_USER: + if (optarg) { + /* --user=NAME is a deprecated alias for --uid=NAME */ + log_warning("--user=NAME is deprecated, use --uid=NAME instead."); + + r = free_and_strdup(&arg_user, optarg); + if (r < 0) + return log_oom(); + + arg_settings_mask |= SETTING_USER; + } else + arg_runtime_scope = RUNTIME_SCOPE_USER; + break; + + case ARG_SYSTEM: + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; + case '?': return -EINVAL; @@ -1623,6 +1688,26 @@ static int parse_argv(int argc, char *argv[]) { * --directory=". */ arg_directory = TAKE_PTR(arg_template); + /* Derive runtime scope from UID if not explicitly set via --user/--system */ + if (arg_runtime_scope < 0) + arg_runtime_scope = getuid() == 0 ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER; + + if (arg_userns_mode == _USER_NAMESPACE_MODE_INVALID) { + /* -U sets arg_userns_mode to _USER_NAMESPACE_MODE_INVALID to defer the PICK vs MANAGED + * resolution to here where arg_runtime_scope has its final value. */ + if (arg_runtime_scope == RUNTIME_SCOPE_USER) + arg_userns_mode = USER_NAMESPACE_MANAGED; + else if (FLAGS_SET(arg_settings_mask, SETTING_USERNS)) + arg_userns_mode = USER_NAMESPACE_PICK; + else + arg_userns_mode = USER_NAMESPACE_NO; + } + + if (!FLAGS_SET(arg_settings_mask, SETTING_NETWORK)) + /* Imply private networking for unprivileged operation, since kernel otherwise + * refuses mounting sysfs. */ + arg_private_network = arg_runtime_scope == RUNTIME_SCOPE_USER; + arg_caps_retain |= plus; arg_caps_retain |= arg_private_network ? UINT64_C(1) << CAP_NET_ADMIN : 0; arg_caps_retain &= ~minus; @@ -1650,7 +1735,7 @@ static int verify_arguments(void) { /* We can mount selinuxfs only if we are privileged and can do so before userns. In managed mode we * have to enter the userns earlier, hence cannot do that. */ - /* SET_FLAG(arg_mount_settings, MOUNT_PRIVILEGED, arg_privileged); */ + /* SET_FLAG(arg_mount_settings, MOUNT_PRIVILEGED, arg_runtime_scope == RUNTIME_SCOPE_SYSTEM); */ SET_FLAG(arg_mount_settings, MOUNT_PRIVILEGED, arg_userns_mode != USER_NAMESPACE_MANAGED); SET_FLAG(arg_mount_settings, MOUNT_USE_USERNS, arg_userns_mode != USER_NAMESPACE_NO); @@ -1658,8 +1743,8 @@ static int verify_arguments(void) { if (arg_private_network) SET_FLAG(arg_mount_settings, MOUNT_APPLY_APIVFS_NETNS, arg_private_network); - if (!arg_privileged && arg_userns_mode != USER_NAMESPACE_MANAGED) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unprivileged operation requires managed user namespaces, as otherwise no UID range can be acquired."); + if (arg_runtime_scope != RUNTIME_SCOPE_SYSTEM && arg_userns_mode != USER_NAMESPACE_MANAGED) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User-scoped operation requires managed user namespaces, as otherwise no UID range can be acquired."); if (arg_userns_mode == USER_NAMESPACE_MANAGED && !arg_private_network) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Managed user namespace operation requires private networking, as otherwise /sys/ may not be mounted."); @@ -3211,7 +3296,7 @@ static int determine_names(void) { if (arg_machine) { _cleanup_(image_unrefp) Image *i = NULL; - r = image_find(arg_privileged ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER, + r = image_find(arg_runtime_scope, IMAGE_MACHINE, arg_machine, NULL, &i); if (r == -ENOENT) return log_error_errno(r, "No image for machine '%s'.", arg_machine); @@ -5183,7 +5268,7 @@ static int load_settings(void) { _SD_PATH_INVALID, }; - const uint64_t *q = arg_privileged ? lookup_dir_system : lookup_dir_user; + const uint64_t *q = arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? lookup_dir_system : lookup_dir_user; for (; *q != _SD_PATH_INVALID; q++) { _cleanup_free_ char *cd = NULL; r = sd_path_lookup(*q, "systemd/nspawn", &cd); @@ -5614,7 +5699,7 @@ static int run_container( /* Registration always happens on the system bus */ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *system_bus = NULL; - if (arg_register != 0 || (arg_privileged && !arg_keep_unit)) { + if (arg_register != 0 || (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM && !arg_keep_unit)) { r = sd_bus_default_system(&system_bus); if (r < 0) return log_error_errno(r, "Failed to open system bus: %m"); @@ -5630,7 +5715,7 @@ static int run_container( _cleanup_(sd_bus_unrefp) sd_bus *runtime_bus = NULL; if (arg_register != 0 || !arg_keep_unit) { - if (arg_privileged) + if (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM) runtime_bus = sd_bus_ref(system_bus); else { r = sd_bus_default_user(&user_bus); @@ -5700,7 +5785,7 @@ static int run_container( bool registered_system = false, registered_runtime = false; if (arg_register != 0) { r = register_machine_with_fallback_and_log( - arg_privileged ? RUNTIME_SCOPE_SYSTEM : _RUNTIME_SCOPE_INVALID, + arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? RUNTIME_SCOPE_SYSTEM : _RUNTIME_SCOPE_INVALID, system_bus, runtime_bus, arg_machine, @@ -6081,20 +6166,10 @@ static int cant_be_in_netns(void) { return 0; } -static void initialize_defaults(void) { - arg_privileged = getuid() == 0; - - /* If running unprivileged default to systemd-nsresourced operation */ - arg_userns_mode = arg_privileged ? USER_NAMESPACE_NO : USER_NAMESPACE_MANAGED; - - /* Imply private networking for unprivileged operation, since kernel otherwise refuses mounting sysfs */ - arg_private_network = !arg_privileged; -} - static void cleanup_propagation_and_export_directories(void) { const char *p; - if (!arg_machine || !arg_privileged) + if (!arg_machine || arg_runtime_scope != RUNTIME_SCOPE_SYSTEM) return; p = strjoina("/run/systemd/nspawn/propagate/", arg_machine); @@ -6138,8 +6213,6 @@ static int run(int argc, char *argv[]) { log_setup(); - initialize_defaults(); - r = parse_argv(argc, argv); if (r <= 0) goto finish; @@ -6294,7 +6367,7 @@ static int run(int argc, char *argv[]) { r = create_ephemeral_snapshot( arg_directory, - arg_privileged ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER, + arg_runtime_scope, arg_read_only, &tree_global_lock, &tree_local_lock, @@ -6315,10 +6388,10 @@ static int run(int argc, char *argv[]) { goto finish; r = image_path_lock( - arg_privileged ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER, + arg_runtime_scope, arg_directory, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, - arg_privileged ? &tree_global_lock : NULL, + arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? &tree_global_lock : NULL, &tree_local_lock); if (r == -EBUSY) { log_error_errno(r, "Directory tree %s is currently busy.", arg_directory); @@ -6446,10 +6519,10 @@ static int run(int argc, char *argv[]) { /* Always take an exclusive lock on our own ephemeral copy. */ r = image_path_lock( - arg_privileged ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER, + arg_runtime_scope, np, LOCK_EX|LOCK_NB, - arg_privileged ? &tree_global_lock : NULL, + arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? &tree_global_lock : NULL, &tree_local_lock); if (r < 0) { log_error_errno(r, "Failed to create image lock: %m"); @@ -6474,10 +6547,10 @@ static int run(int argc, char *argv[]) { remove_image = true; } else { r = image_path_lock( - arg_privileged ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER, + arg_runtime_scope, arg_image, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, - arg_privileged ? &tree_global_lock : NULL, + arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? &tree_global_lock : NULL, &tree_local_lock); if (r == -EBUSY) { log_error_errno(r, "Disk image %s is currently busy.", arg_image); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index f91c36193dd01..ae324bb218b6f 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -220,8 +220,8 @@ static int help(void) { " -q --quiet Do not show status information\n" " --no-pager Do not pipe output into a pager\n" " --no-ask-password Do not prompt for password\n" - " --user Interact with user manager\n" - " --system Interact with system manager\n" + " --system Run in the system service manager scope\n" + " --user Run in the user service manager scope\n" "\n%3$sImage:%4$s\n" " -D --directory=PATH Root directory for the VM\n" " -x --ephemeral Run VM with snapshot of the disk or directory\n" diff --git a/test/units/TEST-13-NSPAWN.nspawn.sh b/test/units/TEST-13-NSPAWN.nspawn.sh index d5cc05b89f582..2868cc54ef354 100755 --- a/test/units/TEST-13-NSPAWN.nspawn.sh +++ b/test/units/TEST-13-NSPAWN.nspawn.sh @@ -202,7 +202,7 @@ testcase_sanity() { systemd-nspawn --register=no --directory="$root" bash -xec '[[ $$ -eq 1 ]]' systemd-nspawn --register=no --directory="$root" --as-pid2 bash -xec '[[ $$ -eq 2 ]]' - # --user= + # --uid= # "Fake" getent passwd's bare minimum, so we don't have to pull it in # with all the DSO shenanigans cat >"$root/bin/getent" <<\EOF @@ -222,6 +222,9 @@ EOF # as bash isn't invoked with the necessary environment variables for that. useradd --root="$root" --uid 1000 --user-group --create-home testuser systemd-nspawn --register=no --directory="$root" bash -xec '[[ $USER == root ]]' + systemd-nspawn --register=no --directory="$root" --uid=testuser bash -xec '[[ $USER == testuser ]]' + # Backward compat: --user NAME (space-separated) and --user=testuser should still work + systemd-nspawn --register=no --directory="$root" --user testuser bash -xec '[[ $USER == testuser ]]' systemd-nspawn --register=no --directory="$root" --user=testuser bash -xec '[[ $USER == testuser ]]' # --settings= + .nspawn files @@ -335,10 +338,10 @@ EOF --load-credential=cred.path:/tmp/cred.path \ --set-credential="cred.set:hello world" \ bash -xec '[[ "$("$root/bin/getent" <<\EOF @@ -933,7 +936,7 @@ EOF systemd-nspawn --register=no \ --directory="$root" \ -U \ - --user=testuser \ + --uid=testuser \ --bind=/tmp/owneridmap/bind:/home/testuser:owneridmap \ ${COVERAGE_BUILD_DIR:+--bind="$COVERAGE_BUILD_DIR"} \ bash -c "$cmd" |& tee nspawn.out; then From d872be82a0f80980c6e92440e32441441c682b12 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 6 Apr 2026 20:20:49 +0200 Subject: [PATCH 0784/1296] vmspawn: fix error message when opening user bus The error message incorrectly says "system bus" when the code is actually opening the user bus via sd_bus_default_user(). Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index ae324bb218b6f..c6289830fb707 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -2254,7 +2254,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { else { r = sd_bus_default_user(&user_bus); if (r < 0) - return log_error_errno(r, "Failed to open system bus: %m"); + return log_error_errno(r, "Failed to open user bus: %m"); r = sd_bus_set_close_on_exit(user_bus, false); if (r < 0) From 2b77bbeb2053a0c38b0d4e10e1bc2f382243cd8c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 6 Apr 2026 20:21:52 +0200 Subject: [PATCH 0785/1296] vmspawn: only open runtime bus when needed for registration or scope allocation The runtime bus (user bus in user scope, system bus in system scope) is only needed for scope allocation (!arg_keep_unit) or machine registration (arg_register != 0). When both are disabled the bus was still opened unconditionally which causes unnecessary failures if the user bus is unavailable. Gate the runtime bus opening on the same condition nspawn already uses. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index c6289830fb707..9610199c02930 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -2246,21 +2246,23 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { (void) sd_bus_set_allow_interactive_authorization(system_bus, arg_ask_password); } - /* Scope allocation happens on the user bus if we are unpriv, otherwise system bus. */ + /* Scope allocation and machine registration happen on the user bus if we are unpriv, otherwise system bus. */ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *user_bus = NULL; _cleanup_(sd_bus_unrefp) sd_bus *runtime_bus = NULL; - if (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM) - runtime_bus = sd_bus_ref(system_bus); - else { - r = sd_bus_default_user(&user_bus); - if (r < 0) - return log_error_errno(r, "Failed to open user bus: %m"); + if (arg_register != 0 || !arg_keep_unit) { + if (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM) + runtime_bus = sd_bus_ref(system_bus); + else { + r = sd_bus_default_user(&user_bus); + if (r < 0) + return log_error_errno(r, "Failed to open user bus: %m"); - r = sd_bus_set_close_on_exit(user_bus, false); - if (r < 0) - return log_error_errno(r, "Failed to disable close-on-exit behaviour: %m"); + r = sd_bus_set_close_on_exit(user_bus, false); + if (r < 0) + return log_error_errno(r, "Failed to disable close-on-exit behaviour: %m"); - runtime_bus = sd_bus_ref(user_bus); + runtime_bus = sd_bus_ref(user_bus); + } } bool use_kvm = arg_kvm > 0; From 6b3b9b1abda8d44a7d4cbef442a37cbd4be7415e Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 6 Apr 2026 20:27:09 +0200 Subject: [PATCH 0786/1296] shared: document allocateUnit limitation on D-Bus fallback path The D-Bus registration methods (RegisterMachineEx, RegisterMachineWithNetwork) do not support the allocateUnit feature that the varlink path provides. When varlink is unavailable and registration falls back to D-Bus, machined discovers the caller's existing cgroup unit instead of creating a dedicated scope. Callers that skip client-side scope allocation (relying on the server to do it via allocateUnit) will end up without a dedicated scope on the D-Bus fallback path. Document this limitation at the fallback site so callers are aware. Signed-off-by: Christian Brauner (Amutable) --- src/shared/machine-register.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/shared/machine-register.c b/src/shared/machine-register.c index d6e76db85c9fc..2f18fa8cf9546 100644 --- a/src/shared/machine-register.c +++ b/src/shared/machine-register.c @@ -176,7 +176,11 @@ int register_machine( log_debug_errno(r, "Failed to connect to machined via varlink%s%s, falling back to D-Bus: %m", p ? " on " : "", strempty(p)); - /* In case we are running with an older machined, fall back to D-Bus. */ + /* In case we are running with an older machined, fall back to D-Bus. Note that the D-Bus + * methods do not support the allocateUnit feature — machined will look up the caller's + * existing cgroup unit instead of creating a dedicated scope. Callers that skip client-side + * scope allocation when allocate_unit is set should be aware that on the D-Bus path no scope + * will be created at all. */ if (!bus) return log_debug_errno(SYNTHETIC_ERRNO(ESRCH), "Varlink connection to machined not available and no bus provided."); From 2af171653f0c9e3fa36247ff4514f93c7ce0207e Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 6 Apr 2026 19:57:52 +0200 Subject: [PATCH 0787/1296] shared: introduce MachineRegistration struct for machine registration Replace the long positional parameter lists in register_machine() and register_machine_with_fallback_and_log() with a MachineRegistration struct that bundles all machine-describing fields. This reduces register_machine() from 13 parameters to 3 and register_machine_with_fallback_and_log() from 17 parameters to 7. Callers now use designated initializers, which makes omitted fields (zero/NULL/false) implicit and the code much more readable. Field names are aligned with the existing Machine struct in machine.h (id, root_directory, vsock_cid, ssh_address, ssh_private_key_path). Signed-off-by: Christian Brauner (Amutable) --- src/nspawn/nspawn.c | 22 ++--- src/shared/machine-register.c | 170 ++++++++++++---------------------- src/shared/machine-register.h | 40 ++++---- src/vmspawn/vmspawn.c | 26 +++--- 4 files changed, 104 insertions(+), 154 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index ff5f79a7cb6b7..b5583974189bb 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -5784,21 +5784,21 @@ static int run_container( bool registered_system = false, registered_runtime = false; if (arg_register != 0) { + const MachineRegistration reg = { + .name = arg_machine, + .id = arg_uuid, + .service = arg_container_service_name, + .class = "container", + .pidref = pid, + .root_directory = arg_directory, + .local_ifindex = ifi, + }; + r = register_machine_with_fallback_and_log( arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? RUNTIME_SCOPE_SYSTEM : _RUNTIME_SCOPE_INVALID, system_bus, runtime_bus, - arg_machine, - arg_uuid, - arg_container_service_name, - "container", - pid, - arg_directory, - /* cid= */ 0, - ifi, - /* address= */ NULL, - /* key_path= */ NULL, - /* allocate_unit= */ false, + ®, /* graceful= */ arg_register < 0, ®istered_system, ®istered_runtime); diff --git a/src/shared/machine-register.c b/src/shared/machine-register.c index 2f18fa8cf9546..8ca2bf2f1d018 100644 --- a/src/shared/machine-register.c +++ b/src/shared/machine-register.c @@ -23,28 +23,23 @@ static int register_machine_dbus_ex( sd_bus *bus, - const char *machine_name, - sd_id128_t uuid, - const char *service, - const char *class, - const PidRef *pidref, - const char *directory, - int local_ifindex, + const MachineRegistration *reg, sd_bus_error *error) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; int r; assert(bus); - assert(machine_name); - assert(service); - assert(class); + assert(reg); + assert(reg->name); + assert(reg->service); + assert(reg->class); r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "RegisterMachineEx"); if (r < 0) return bus_log_create_error(r); - r = sd_bus_message_append(m, "s", machine_name); + r = sd_bus_message_append(m, "s", reg->name); if (r < 0) return bus_log_create_error(r); @@ -55,38 +50,38 @@ static int register_machine_dbus_ex( r = sd_bus_message_append( m, "(sv)(sv)(sv)", - "Id", "ay", SD_BUS_MESSAGE_APPEND_ID128(uuid), - "Service", "s", service, - "Class", "s", class); + "Id", "ay", SD_BUS_MESSAGE_APPEND_ID128(reg->id), + "Service", "s", reg->service, + "Class", "s", reg->class); if (r < 0) return bus_log_create_error(r); - if (pidref_is_set(pidref)) { - if (pidref->fd >= 0) { - r = sd_bus_message_append(m, "(sv)", "LeaderPIDFD", "h", pidref->fd); + if (pidref_is_set(reg->pidref)) { + if (reg->pidref->fd >= 0) { + r = sd_bus_message_append(m, "(sv)", "LeaderPIDFD", "h", reg->pidref->fd); if (r < 0) return bus_log_create_error(r); } - if (pidref->fd_id > 0) { - r = sd_bus_message_append(m, "(sv)", "LeaderPIDFDID", "t", pidref->fd_id); + if (reg->pidref->fd_id > 0) { + r = sd_bus_message_append(m, "(sv)", "LeaderPIDFDID", "t", reg->pidref->fd_id); if (r < 0) return bus_log_create_error(r); - r = sd_bus_message_append(m, "(sv)", "LeaderPID", "u", pidref->pid); + r = sd_bus_message_append(m, "(sv)", "LeaderPID", "u", reg->pidref->pid); if (r < 0) return bus_log_create_error(r); } } - if (!isempty(directory)) { - r = sd_bus_message_append(m, "(sv)", "RootDirectory", "s", directory); + if (!isempty(reg->root_directory)) { + r = sd_bus_message_append(m, "(sv)", "RootDirectory", "s", reg->root_directory); if (r < 0) return bus_log_create_error(r); } - if (local_ifindex > 0) { - r = sd_bus_message_append(m, "(sv)", "NetworkInterfaces", "ai", 1, local_ifindex); + if (reg->local_ifindex > 0) { + r = sd_bus_message_append(m, "(sv)", "NetworkInterfaces", "ai", 1, reg->local_ifindex); if (r < 0) return bus_log_create_error(r); } @@ -100,24 +95,19 @@ static int register_machine_dbus_ex( static int register_machine_dbus( sd_bus *bus, - const char *machine_name, - sd_id128_t uuid, - const char *service, - const char *class, - const PidRef *pidref, - const char *directory, - int local_ifindex) { + const MachineRegistration *reg) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; assert(bus); - assert(machine_name); - assert(service); - assert(class); + assert(reg); + assert(reg->name); + assert(reg->service); + assert(reg->class); /* First try RegisterMachineEx which supports PIDFD-based leader tracking. */ - r = register_machine_dbus_ex(bus, machine_name, uuid, service, class, pidref, directory, local_ifindex, &error); + r = register_machine_dbus_ex(bus, reg, &error); if (r >= 0) return 0; if (!sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) @@ -132,13 +122,13 @@ static int register_machine_dbus( &error, NULL, "sayssusai", - machine_name, - SD_BUS_MESSAGE_APPEND_ID128(uuid), - service, - class, - pidref_is_set(pidref) ? (uint32_t) pidref->pid : 0, - strempty(directory), - local_ifindex > 0 ? 1 : 0, local_ifindex); + reg->name, + SD_BUS_MESSAGE_APPEND_ID128(reg->id), + reg->service, + reg->class, + pidref_is_set(reg->pidref) ? (uint32_t) reg->pidref->pid : 0, + strempty(reg->root_directory), + reg->local_ifindex > 0 ? 1 : 0, reg->local_ifindex); if (r < 0) return log_debug_errno(r, "Failed to register machine via D-Bus: %s", bus_error_message(&error, r)); @@ -147,25 +137,16 @@ static int register_machine_dbus( int register_machine( sd_bus *bus, - const char *machine_name, - sd_id128_t uuid, - const char *service, - const char *class, - const PidRef *pidref, - const char *directory, - unsigned cid, - int local_ifindex, - const char *address, - const char *key_path, - bool allocate_unit, + const MachineRegistration *reg, RuntimeScope scope) { _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; int r; - assert(machine_name); - assert(service); - assert(class); + assert(reg); + assert(reg->name); + assert(reg->service); + assert(reg->class); /* First try to use varlink, as it provides more features (such as SSH support). */ _cleanup_free_ char *p = NULL; @@ -184,7 +165,7 @@ int register_machine( if (!bus) return log_debug_errno(SYNTHETIC_ERRNO(ESRCH), "Varlink connection to machined not available and no bus provided."); - return register_machine_dbus(bus, machine_name, uuid, service, class, pidref, directory, local_ifindex); + return register_machine_dbus(bus, reg); } if (r < 0) return log_debug_errno(r, "Failed to connect to machined on %s: %m", strna(p)); @@ -196,18 +177,18 @@ int register_machine( "io.systemd.Machine.Register", &reply, &error_id, - SD_JSON_BUILD_PAIR_STRING("name", machine_name), - SD_JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(uuid), "id", SD_JSON_BUILD_ID128(uuid)), - SD_JSON_BUILD_PAIR_STRING("service", service), - SD_JSON_BUILD_PAIR_STRING("class", class), - SD_JSON_BUILD_PAIR_CONDITION(VSOCK_CID_IS_REGULAR(cid), "vSockCid", SD_JSON_BUILD_UNSIGNED(cid)), - SD_JSON_BUILD_PAIR_CONDITION(local_ifindex > 0, "ifIndices", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_INTEGER(local_ifindex))), - SD_JSON_BUILD_PAIR_CONDITION(!!directory, "rootDirectory", SD_JSON_BUILD_STRING(directory)), - SD_JSON_BUILD_PAIR_CONDITION(!!address, "sshAddress", SD_JSON_BUILD_STRING(address)), - SD_JSON_BUILD_PAIR_CONDITION(!!key_path, "sshPrivateKeyPath", SD_JSON_BUILD_STRING(key_path)), + SD_JSON_BUILD_PAIR_STRING("name", reg->name), + SD_JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(reg->id), "id", SD_JSON_BUILD_ID128(reg->id)), + SD_JSON_BUILD_PAIR_STRING("service", reg->service), + SD_JSON_BUILD_PAIR_STRING("class", reg->class), + SD_JSON_BUILD_PAIR_CONDITION(VSOCK_CID_IS_REGULAR(reg->vsock_cid), "vSockCid", SD_JSON_BUILD_UNSIGNED(reg->vsock_cid)), + SD_JSON_BUILD_PAIR_CONDITION(reg->local_ifindex > 0, "ifIndices", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_INTEGER(reg->local_ifindex))), + SD_JSON_BUILD_PAIR_CONDITION(!!reg->root_directory, "rootDirectory", SD_JSON_BUILD_STRING(reg->root_directory)), + SD_JSON_BUILD_PAIR_CONDITION(!!reg->ssh_address, "sshAddress", SD_JSON_BUILD_STRING(reg->ssh_address)), + SD_JSON_BUILD_PAIR_CONDITION(!!reg->ssh_private_key_path, "sshPrivateKeyPath", SD_JSON_BUILD_STRING(reg->ssh_private_key_path)), SD_JSON_BUILD_PAIR_CONDITION(isatty_safe(STDIN_FILENO), "allowInteractiveAuthentication", SD_JSON_BUILD_BOOLEAN(true)), - SD_JSON_BUILD_PAIR_CONDITION(allocate_unit, "allocateUnit", SD_JSON_BUILD_BOOLEAN(true)), - SD_JSON_BUILD_PAIR_CONDITION(pidref_is_set(pidref), "leaderProcessId", JSON_BUILD_PIDREF(pidref))); + SD_JSON_BUILD_PAIR_CONDITION(reg->allocate_unit, "allocateUnit", SD_JSON_BUILD_BOOLEAN(true)), + SD_JSON_BUILD_PAIR_CONDITION(pidref_is_set(reg->pidref), "leaderProcessId", JSON_BUILD_PIDREF(reg->pidref))); if (r < 0) return log_debug_errno(r, "Failed to register machine via varlink: %m"); if (error_id) @@ -233,17 +214,7 @@ int register_machine_with_fallback_and_log( RuntimeScope scope, sd_bus *system_bus, sd_bus *user_bus, - const char *machine_name, - sd_id128_t uuid, - const char *service, - const char *class, - const PidRef *pidref, - const char *directory, - unsigned cid, - int local_ifindex, - const char *address, - const char *key_path, - bool allocate_unit, + const MachineRegistration *reg, bool graceful, bool *reterr_registered_system, bool *reterr_registered_user) { @@ -254,27 +225,19 @@ int register_machine_with_fallback_and_log( assert(IN_SET(scope, RUNTIME_SCOPE_SYSTEM, RUNTIME_SCOPE_USER, _RUNTIME_SCOPE_INVALID)); assert(system_bus || !IN_SET(scope, RUNTIME_SCOPE_SYSTEM, _RUNTIME_SCOPE_INVALID)); assert(user_bus || !IN_SET(scope, RUNTIME_SCOPE_USER, _RUNTIME_SCOPE_INVALID)); - assert(machine_name); - assert(service); - assert(class); + assert(reg); + assert(reg->name); + assert(reg->service); + assert(reg->class); assert(reterr_registered_system); assert(reterr_registered_user); if (IN_SET(scope, RUNTIME_SCOPE_SYSTEM, _RUNTIME_SCOPE_INVALID)) { - int q = register_machine( - system_bus, - machine_name, - uuid, - service, - class, - pidref, - directory, - cid, - local_ifindex, - address, - key_path, - scope == RUNTIME_SCOPE_SYSTEM ? allocate_unit : false, - RUNTIME_SCOPE_SYSTEM); + MachineRegistration system_reg = *reg; + if (scope != RUNTIME_SCOPE_SYSTEM) + system_reg.allocate_unit = false; + + int q = register_machine(system_bus, &system_reg, RUNTIME_SCOPE_SYSTEM); if (q < 0) RET_GATHER(r, q); else @@ -282,20 +245,7 @@ int register_machine_with_fallback_and_log( } if (IN_SET(scope, RUNTIME_SCOPE_USER, _RUNTIME_SCOPE_INVALID)) { - int q = register_machine( - user_bus, - machine_name, - uuid, - service, - class, - pidref, - directory, - cid, - local_ifindex, - address, - key_path, - allocate_unit, - RUNTIME_SCOPE_USER); + int q = register_machine(user_bus, reg, RUNTIME_SCOPE_USER); if (q < 0) RET_GATHER(r, q); else diff --git a/src/shared/machine-register.h b/src/shared/machine-register.h index e405873b5ba2b..8fc63fd75ed4b 100644 --- a/src/shared/machine-register.h +++ b/src/shared/machine-register.h @@ -1,37 +1,33 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "sd-id128.h" + #include "shared-forward.h" +typedef struct MachineRegistration { + const char *name; + sd_id128_t id; + const char *service; + const char *class; + const PidRef *pidref; + const char *root_directory; + unsigned vsock_cid; + int local_ifindex; + const char *ssh_address; + const char *ssh_private_key_path; + bool allocate_unit; +} MachineRegistration; + int register_machine( sd_bus *bus, - const char *machine_name, - sd_id128_t uuid, - const char *service, - const char *class, - const PidRef *pidref, - const char *directory, - unsigned cid, - int local_ifindex, - const char *address, - const char *key_path, - bool allocate_unit, + const MachineRegistration *reg, RuntimeScope scope); int register_machine_with_fallback_and_log( RuntimeScope scope, sd_bus *system_bus, sd_bus *user_bus, - const char *machine_name, - sd_id128_t uuid, - const char *service, - const char *class, - const PidRef *pidref, - const char *directory, - unsigned cid, - int local_ifindex, - const char *address, - const char *key_path, - bool allocate_unit, + const MachineRegistration *reg, bool graceful, bool *reterr_registered_system, bool *reterr_registered_user); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 9610199c02930..5295d4d21b097 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -3601,21 +3601,25 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (arg_register != 0) { char vm_address[STRLEN("vsock/") + DECIMAL_STR_MAX(unsigned)]; xsprintf(vm_address, "vsock/%u", child_cid); + + const MachineRegistration reg = { + .name = arg_machine, + .id = arg_uuid, + .service = "systemd-vmspawn", + .class = "vm", + .pidref = &child_pidref, + .root_directory = arg_directory, + .vsock_cid = child_cid, + .ssh_address = child_cid != VMADDR_CID_ANY ? vm_address : NULL, + .ssh_private_key_path = ssh_private_key_path, + .allocate_unit = !arg_keep_unit, + }; + r = register_machine_with_fallback_and_log( arg_runtime_scope == RUNTIME_SCOPE_USER ? _RUNTIME_SCOPE_INVALID : RUNTIME_SCOPE_SYSTEM, system_bus, runtime_bus, - arg_machine, - arg_uuid, - "systemd-vmspawn", - "vm", - &child_pidref, - arg_directory, - child_cid, - /* local_ifindex= */ 0, - child_cid != VMADDR_CID_ANY ? vm_address : NULL, - ssh_private_key_path, - !arg_keep_unit, + ®, /* graceful= */ arg_register < 0, ®istered_system, ®istered_runtime); From 454d9c1be1056dcf439e73ba550f545d543fffcf Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 6 Apr 2026 20:03:21 +0200 Subject: [PATCH 0788/1296] shared: introduce MachineRegistrationContext to track bus and registration state Bundle scope, buses, and registration success booleans into a MachineRegistrationContext struct. This eliminates the reterr_registered_system and reterr_registered_user output parameters from register_machine_with_fallback_and_log() and the corresponding input parameters from unregister_machine_with_fallback_and_log(). The struct carries state from registration to unregistration so the caller no longer needs to manually thread individual booleans between the two calls. register_machine_with_fallback_and_log() goes from 7 to 3 parameters, unregister_machine_with_fallback_and_log() goes from 5 to 2. Signed-off-by: Christian Brauner (Amutable) --- src/nspawn/nspawn.c | 18 +++++----- src/shared/machine-register.c | 67 ++++++++++++++--------------------- src/shared/machine-register.h | 26 +++++++------- src/vmspawn/vmspawn.c | 16 ++++----- 4 files changed, 58 insertions(+), 69 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index b5583974189bb..006e91caa914c 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -1754,7 +1754,7 @@ static int verify_arguments(void) { if (!(arg_clone_ns_flags & CLONE_NEWPID) || !(arg_clone_ns_flags & CLONE_NEWUTS)) { - arg_register = false; + arg_register = 0; if (arg_start_mode != START_PID1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--boot cannot be used without namespacing."); } @@ -5782,7 +5782,11 @@ static int run_container( scope_allocated = true; } - bool registered_system = false, registered_runtime = false; + MachineRegistrationContext machine_ctx = { + .scope = arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? RUNTIME_SCOPE_SYSTEM : _RUNTIME_SCOPE_INVALID, + .system_bus = system_bus, + .user_bus = runtime_bus, + }; if (arg_register != 0) { const MachineRegistration reg = { .name = arg_machine, @@ -5795,13 +5799,9 @@ static int run_container( }; r = register_machine_with_fallback_and_log( - arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? RUNTIME_SCOPE_SYSTEM : _RUNTIME_SCOPE_INVALID, - system_bus, - runtime_bus, + &machine_ctx, ®, - /* graceful= */ arg_register < 0, - ®istered_system, - ®istered_runtime); + /* graceful= */ arg_register < 0); if (r < 0) return r; } @@ -6015,7 +6015,7 @@ static int run_container( r = wait_for_container(pid, &container_status); /* Tell machined that we are gone. */ - (void) unregister_machine_with_fallback_and_log(system_bus, runtime_bus, arg_machine, registered_system, registered_runtime); + unregister_machine_with_fallback_and_log(&machine_ctx, arg_machine); if (r < 0) /* We failed to wait for the container, or the container exited abnormally. */ diff --git a/src/shared/machine-register.c b/src/shared/machine-register.c index 8ca2bf2f1d018..924d768877108 100644 --- a/src/shared/machine-register.c +++ b/src/shared/machine-register.c @@ -34,6 +34,7 @@ static int register_machine_dbus_ex( assert(reg->name); assert(reg->service); assert(reg->class); + assert(error); r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "RegisterMachineEx"); if (r < 0) @@ -211,85 +212,73 @@ static const char* machine_registration_scope_string(RuntimeScope scope, bool re } int register_machine_with_fallback_and_log( - RuntimeScope scope, - sd_bus *system_bus, - sd_bus *user_bus, + MachineRegistrationContext *ctx, const MachineRegistration *reg, - bool graceful, - bool *reterr_registered_system, - bool *reterr_registered_user) { + bool graceful) { - bool registered_system = false, registered_user = false; int r = 0; - assert(IN_SET(scope, RUNTIME_SCOPE_SYSTEM, RUNTIME_SCOPE_USER, _RUNTIME_SCOPE_INVALID)); - assert(system_bus || !IN_SET(scope, RUNTIME_SCOPE_SYSTEM, _RUNTIME_SCOPE_INVALID)); - assert(user_bus || !IN_SET(scope, RUNTIME_SCOPE_USER, _RUNTIME_SCOPE_INVALID)); + assert(ctx); + assert(IN_SET(ctx->scope, RUNTIME_SCOPE_SYSTEM, RUNTIME_SCOPE_USER, _RUNTIME_SCOPE_INVALID)); + assert(ctx->system_bus || !IN_SET(ctx->scope, RUNTIME_SCOPE_SYSTEM, _RUNTIME_SCOPE_INVALID)); + assert(ctx->user_bus || !IN_SET(ctx->scope, RUNTIME_SCOPE_USER, _RUNTIME_SCOPE_INVALID)); assert(reg); assert(reg->name); assert(reg->service); assert(reg->class); - assert(reterr_registered_system); - assert(reterr_registered_user); - if (IN_SET(scope, RUNTIME_SCOPE_SYSTEM, _RUNTIME_SCOPE_INVALID)) { + if (IN_SET(ctx->scope, RUNTIME_SCOPE_SYSTEM, _RUNTIME_SCOPE_INVALID)) { MachineRegistration system_reg = *reg; - if (scope != RUNTIME_SCOPE_SYSTEM) + if (ctx->scope != RUNTIME_SCOPE_SYSTEM) system_reg.allocate_unit = false; - int q = register_machine(system_bus, &system_reg, RUNTIME_SCOPE_SYSTEM); + int q = register_machine(ctx->system_bus, &system_reg, RUNTIME_SCOPE_SYSTEM); if (q < 0) RET_GATHER(r, q); else - registered_system = true; + ctx->registered_system = true; } - if (IN_SET(scope, RUNTIME_SCOPE_USER, _RUNTIME_SCOPE_INVALID)) { - int q = register_machine(user_bus, reg, RUNTIME_SCOPE_USER); + if (IN_SET(ctx->scope, RUNTIME_SCOPE_USER, _RUNTIME_SCOPE_INVALID)) { + int q = register_machine(ctx->user_bus, reg, RUNTIME_SCOPE_USER); if (q < 0) RET_GATHER(r, q); else - registered_user = true; + ctx->registered_user = true; } if (r < 0) { if (graceful) { log_notice_errno(r, "Failed to register machine in %s context, ignoring: %m", - machine_registration_scope_string(scope, registered_system, registered_user)); + machine_registration_scope_string(ctx->scope, ctx->registered_system, ctx->registered_user)); r = 0; } else r = log_error_errno(r, "Failed to register machine in %s context: %m", - machine_registration_scope_string(scope, registered_system, registered_user)); + machine_registration_scope_string(ctx->scope, ctx->registered_system, ctx->registered_user)); } - if (reterr_registered_system) - *reterr_registered_system = registered_system; - if (reterr_registered_user) - *reterr_registered_user = registered_user; - return r; } -int unregister_machine_with_fallback_and_log( - sd_bus *system_bus, - sd_bus *user_bus, - const char *machine_name, - bool registered_system, - bool registered_user) { +void unregister_machine_with_fallback_and_log( + const MachineRegistrationContext *ctx, + const char *machine_name) { int r = 0; bool failed_system = false, failed_user = false; - if (registered_system) { - int q = unregister_machine(system_bus, machine_name, RUNTIME_SCOPE_SYSTEM); + assert(ctx); + + if (ctx->registered_system) { + int q = unregister_machine(ctx->system_bus, machine_name, RUNTIME_SCOPE_SYSTEM); if (q < 0) { RET_GATHER(r, q); failed_system = true; } } - if (registered_user) { - int q = unregister_machine(user_bus, machine_name, RUNTIME_SCOPE_USER); + if (ctx->registered_user) { + int q = unregister_machine(ctx->user_bus, machine_name, RUNTIME_SCOPE_USER); if (q < 0) { RET_GATHER(r, q); failed_user = true; @@ -299,11 +288,9 @@ int unregister_machine_with_fallback_and_log( if (r < 0) log_notice_errno(r, "Failed to unregister machine in %s context, ignoring: %m", machine_registration_scope_string( - registered_system && registered_user ? _RUNTIME_SCOPE_INVALID : - registered_system ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER, + ctx->registered_system && ctx->registered_user ? _RUNTIME_SCOPE_INVALID : + ctx->registered_system ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER, !failed_system, !failed_user)); - - return 0; } int unregister_machine(sd_bus *bus, const char *machine_name, RuntimeScope scope) { diff --git a/src/shared/machine-register.h b/src/shared/machine-register.h index 8fc63fd75ed4b..996bcfe7a2d52 100644 --- a/src/shared/machine-register.h +++ b/src/shared/machine-register.h @@ -3,6 +3,7 @@ #include "sd-id128.h" +#include "runtime-scope.h" #include "shared-forward.h" typedef struct MachineRegistration { @@ -19,23 +20,24 @@ typedef struct MachineRegistration { bool allocate_unit; } MachineRegistration; +typedef struct MachineRegistrationContext { + RuntimeScope scope; + sd_bus *system_bus; + sd_bus *user_bus; + bool registered_system; + bool registered_user; +} MachineRegistrationContext; + int register_machine( sd_bus *bus, const MachineRegistration *reg, RuntimeScope scope); int register_machine_with_fallback_and_log( - RuntimeScope scope, - sd_bus *system_bus, - sd_bus *user_bus, + MachineRegistrationContext *ctx, const MachineRegistration *reg, - bool graceful, - bool *reterr_registered_system, - bool *reterr_registered_user); + bool graceful); int unregister_machine(sd_bus *bus, const char *machine_name, RuntimeScope scope); -int unregister_machine_with_fallback_and_log( - sd_bus *system_bus, - sd_bus *user_bus, - const char *machine_name, - bool registered_system, - bool registered_user); +void unregister_machine_with_fallback_and_log( + const MachineRegistrationContext *ctx, + const char *machine_name); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 5295d4d21b097..a27a9bc80a823 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -3597,7 +3597,11 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return log_error_errno(r, "Failed to get our own unit: %m"); } - bool registered_system = false, registered_runtime = false; + MachineRegistrationContext machine_ctx = { + .scope = arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? RUNTIME_SCOPE_SYSTEM : _RUNTIME_SCOPE_INVALID, + .system_bus = system_bus, + .user_bus = runtime_bus, + }; if (arg_register != 0) { char vm_address[STRLEN("vsock/") + DECIMAL_STR_MAX(unsigned)]; xsprintf(vm_address, "vsock/%u", child_cid); @@ -3616,13 +3620,9 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { }; r = register_machine_with_fallback_and_log( - arg_runtime_scope == RUNTIME_SCOPE_USER ? _RUNTIME_SCOPE_INVALID : RUNTIME_SCOPE_SYSTEM, - system_bus, - runtime_bus, + &machine_ctx, ®, - /* graceful= */ arg_register < 0, - ®istered_system, - ®istered_runtime); + /* graceful= */ arg_register < 0); if (r < 0) return r; } @@ -3727,7 +3727,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (scope_allocated) terminate_scope(runtime_bus, arg_machine); - (void) unregister_machine_with_fallback_and_log(system_bus, runtime_bus, arg_machine, registered_system, registered_runtime); + unregister_machine_with_fallback_and_log(&machine_ctx, arg_machine); if (use_vsock) { if (exit_status == INT_MAX) { From 26a9ed955975c8a278d0cfbda410b7cd00b9d4bc Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 2 Apr 2026 09:01:16 +0200 Subject: [PATCH 0789/1296] ssh-proxy: fix use-after-free of borrowed varlink reply reference sd_varlink_call_full() returns borrowed references into the varlink connection's receive buffer (v->current). fetch_machine() stored this borrowed reference with _cleanup_(sd_json_variant_unrefp), which would unref it on error paths -- potentially freeing the parent object while the varlink connection still owns it. On success, TAKE_PTR passed the raw borrowed pointer to the caller, but the varlink connection (and its receive buffer) is freed when fetch_machine returns, leaving the caller with a dangling pointer. Fix by removing the cleanup attribute (the reference is borrowed, not owned) and taking a real ref via sd_json_variant_ref() before returning to the caller, so the data survives the varlink connection's cleanup. Signed-off-by: Christian Brauner (Amutable) --- src/ssh-generator/ssh-proxy.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ssh-generator/ssh-proxy.c b/src/ssh-generator/ssh-proxy.c index bfb91b5867c4e..fa42c5bee8021 100644 --- a/src/ssh-generator/ssh-proxy.c +++ b/src/ssh-generator/ssh-proxy.c @@ -282,7 +282,7 @@ static int fetch_machine(const char *machine, RuntimeScope scope, sd_json_varian if (r < 0) return log_error_errno(r, "Failed to connect to machined on %s: %m", addr); - _cleanup_(sd_json_variant_unrefp) sd_json_variant *result = NULL; + sd_json_variant *result = NULL; const char *error_id; r = sd_varlink_callbo( vl, @@ -303,7 +303,9 @@ static int fetch_machine(const char *machine, RuntimeScope scope, sd_json_varian return log_error_errno(r, "Failed to issue io.systemd.Machine.List() varlink call: %s", error_id); } - *ret = TAKE_PTR(result); + /* result is a borrowed reference into the varlink connection's receive buffer. Take a real ref so + * that it survives the cleanup of vl below. */ + *ret = sd_json_variant_ref(result); return 0; } From 5b5423efa37d0c865c99ac02a22821f39ffdd2e1 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 1 Apr 2026 23:23:18 +0200 Subject: [PATCH 0790/1296] sd-json: fix sd_json_variant_unsigned() dispatching to wrong accessor for references sd_json_variant_unsigned() incorrectly calls sd_json_variant_integer() for reference-type variants instead of recursing to itself. This silently returns 0 for unsigned values in the range INT64_MAX+1 through UINT64_MAX, since sd_json_variant_integer() cannot represent them. The sibling functions sd_json_variant_integer() and sd_json_variant_real() correctly recurse to themselves. Signed-off-by: Christian Brauner (Amutable) --- src/libsystemd/sd-json/sd-json.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index 03557d3ac6822..6245d471b7a6f 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -1003,7 +1003,7 @@ _public_ uint64_t sd_json_variant_unsigned(sd_json_variant *v) { if (!json_variant_is_regular(v)) goto mismatch; if (v->is_reference) - return sd_json_variant_integer(v->reference); + return sd_json_variant_unsigned(v->reference); switch (v->type) { From ccecae0efde6651dd8e50e0b7729eda028780657 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 1 Apr 2026 15:59:13 +0200 Subject: [PATCH 0791/1296] vmspawn: use machine name in runtime directory path Replace the random hex suffix in the runtime directory with the machine name, changing the layout from /run/systemd/vmspawn. to /run/systemd/vmspawn//. This makes runtime directories machine-discoverable from the filesystem and groups all vmspawn instances under a shared parent directory, similar to how nspawn uses /run/systemd/nspawn/. Use runtime_directory_generic() instead of runtime_directory() since vmspawn is not a service with RuntimeDirectory= set and the $RUNTIME_DIRECTORY check in the latter never succeeds. The directory is always created by vmspawn itself and cleaned up via rm_rf_physical_and_freep on exit. The parent vmspawn/ directory is intentionally left behind as a shared namespace. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn.c | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index a27a9bc80a823..c1ae51261d9a2 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -2324,33 +2324,32 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (asprintf(&mem, "%" PRIu64 "M", DIV_ROUND_UP(arg_ram, U64_MB)) < 0) return log_oom(); - /* Create runtime directory for the QEMU config file and other state */ - _cleanup_free_ char *runtime_dir = NULL; + /* Create our runtime directory. We need this for the QEMU config file, TPM state, virtiofsd + * sockets, runtime mounts, and SSH key material. */ + _cleanup_free_ char *runtime_dir = NULL, *runtime_dir_suffix = NULL; _cleanup_(rm_rf_physical_and_freep) char *runtime_dir_destroy = NULL; - { - _cleanup_free_ char *subdir = NULL; - if (asprintf(&subdir, "systemd/vmspawn.%" PRIx64, random_u64()) < 0) - return log_oom(); + runtime_dir_suffix = path_join("systemd/vmspawn", arg_machine); + if (!runtime_dir_suffix) + return log_oom(); - r = runtime_directory(arg_runtime_scope, subdir, &runtime_dir); - if (r < 0) - return log_error_errno(r, "Failed to lookup runtime directory: %m"); - if (r > 0) { /* We need to create our own runtime dir */ - r = mkdir_p(runtime_dir, 0755); - if (r < 0) - return log_error_errno(r, "Failed to create runtime directory '%s': %m", runtime_dir); + r = runtime_directory_generic(arg_runtime_scope, runtime_dir_suffix, &runtime_dir); + if (r < 0) + return log_error_errno(r, "Failed to determine runtime directory: %m"); - /* We created this, hence also destroy it */ - runtime_dir_destroy = TAKE_PTR(runtime_dir); + /* If a previous vmspawn instance was killed without cleanup (e.g. SIGKILL), the directory may + * already exist with stale contents. This is harmless: varlink's sockaddr_un_unlink() removes stale + * sockets before bind(), and other files (QEMU config, SSH keys) are created fresh. This matches + * nspawn's approach of not proactively cleaning stale runtime directories. */ + r = mkdir_p(runtime_dir, 0755); + if (r < 0) + return log_error_errno(r, "Failed to create runtime directory '%s': %m", runtime_dir); - runtime_dir = strdup(runtime_dir_destroy); - if (!runtime_dir) - return log_oom(); - } + runtime_dir_destroy = strdup(runtime_dir); + if (!runtime_dir_destroy) + return log_oom(); - log_debug("Using runtime directory: %s", runtime_dir); - } + log_debug("Using runtime directory: %s", runtime_dir); /* Build a QEMU config file for -readconfig. Items that can be expressed as QemuOpts sections go * here; things that require cmdline-only switches (e.g. -kernel, -smbios, -nographic, --add-fd) From 9c735dec679e1ceaead5d745fca49051a390300e Mon Sep 17 00:00:00 2001 From: Ivan Shapovalov Date: Mon, 6 Apr 2026 22:03:36 +0200 Subject: [PATCH 0792/1296] time-util: add TIMESPEC_STORE_NSEC() --- src/basic/time-util.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/basic/time-util.h b/src/basic/time-util.h index 5b6a524c4863e..9a66a90859d67 100644 --- a/src/basic/time-util.h +++ b/src/basic/time-util.h @@ -114,6 +114,7 @@ struct timespec* timespec_store(struct timespec *ts, usec_t u); struct timespec* timespec_store_nsec(struct timespec *ts, nsec_t n); #define TIMESPEC_STORE(u) timespec_store(&(struct timespec) {}, (u)) +#define TIMESPEC_STORE_NSEC(n) timespec_store_nsec(&(struct timespec) {}, (n)) usec_t timeval_load(const struct timeval *tv) _pure_; struct timeval* timeval_store(struct timeval *tv, usec_t u); From e18b75c3f14960cc2a9e8bce7cce3bc3ed8c6114 Mon Sep 17 00:00:00 2001 From: Ivan Shapovalov Date: Fri, 20 Mar 2026 16:36:44 +0100 Subject: [PATCH 0793/1296] tmpfiles: use `NSEC_INFINITY` consistently in dir_cleanup() Correctness analysis ==================== The *time_nsec variables are used for a total of 2 or 3 times: - twice in needs_cleanup() (lines 788, 839) - once in a recursive dir_cleanup() (line 764) as self_*time_nsec In needs_cleanup(), all passed timestamps are guarded against NSEC_INFINITY (this does not fix any real bugs as a 0 value is also older than any cutoff point and thus would not cause any deletions). Recursively in dir_cleanup(), the self_* variables are used to reset the toplevel directory utimes, where they are superficially compared against NSEC_INFINITY as a guard, but subsequently mishandled in the case when only one of the times is NSEC_INFINITY: in this case, it will be a) logged as a bogus value and b) passed through directly to timespec_store_nsec(), which does special-case it, but in a way that is invalid for futimens(). This is further fixed up by explicitly mapping NSEC_INFINITY to TIMESPEC_OMIT. This constitutes a bugfix in theory, as a ~STATX_ATIME return from statx() would have previously caused the corresponding utime to be reset to 0 epoch) rather than being omitted from being set. However, in a directory with ~STATX_ATIME, attempts to set atime would likely be ignored as well. Mostly this is a self-consistency fix that establishes that dir_cleanup() should be called with NSEC_INFINITY in place of absent timestamps. --- src/tmpfiles/tmpfiles.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 17f263790eda0..2b1c612f5ceff 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -710,10 +710,10 @@ static int dir_cleanup( continue; } - atime_nsec = FLAGS_SET(sx.stx_mask, STATX_ATIME) ? statx_timestamp_load_nsec(&sx.stx_atime) : 0; - mtime_nsec = FLAGS_SET(sx.stx_mask, STATX_MTIME) ? statx_timestamp_load_nsec(&sx.stx_mtime) : 0; - ctime_nsec = FLAGS_SET(sx.stx_mask, STATX_CTIME) ? statx_timestamp_load_nsec(&sx.stx_ctime) : 0; - btime_nsec = FLAGS_SET(sx.stx_mask, STATX_BTIME) ? statx_timestamp_load_nsec(&sx.stx_btime) : 0; + atime_nsec = FLAGS_SET(sx.stx_mask, STATX_ATIME) ? statx_timestamp_load_nsec(&sx.stx_atime) : NSEC_INFINITY; + mtime_nsec = FLAGS_SET(sx.stx_mask, STATX_MTIME) ? statx_timestamp_load_nsec(&sx.stx_mtime) : NSEC_INFINITY; + ctime_nsec = FLAGS_SET(sx.stx_mask, STATX_CTIME) ? statx_timestamp_load_nsec(&sx.stx_ctime) : NSEC_INFINITY; + btime_nsec = FLAGS_SET(sx.stx_mask, STATX_BTIME) ? statx_timestamp_load_nsec(&sx.stx_btime) : NSEC_INFINITY; sub_path = path_join(p, de->d_name); if (!sub_path) { @@ -867,11 +867,19 @@ static int dir_cleanup( log_action("Would restore", "Restoring", "%s access and modification time on \"%s\": %s, %s", p, - FORMAT_TIMESTAMP_STYLE(self_atime_nsec / NSEC_PER_USEC, TIMESTAMP_US), - FORMAT_TIMESTAMP_STYLE(self_mtime_nsec / NSEC_PER_USEC, TIMESTAMP_US)); - - timespec_store_nsec(ts + 0, self_atime_nsec); - timespec_store_nsec(ts + 1, self_mtime_nsec); + self_atime_nsec != NSEC_INFINITY + ? FORMAT_TIMESTAMP_STYLE(self_atime_nsec / NSEC_PER_USEC, TIMESTAMP_US) + : "(omitted)", + self_mtime_nsec != NSEC_INFINITY + ? FORMAT_TIMESTAMP_STYLE(self_mtime_nsec / NSEC_PER_USEC, TIMESTAMP_US) + : "(omitted)"); + + ts[0] = self_atime_nsec != NSEC_INFINITY + ? *TIMESPEC_STORE_NSEC(self_atime_nsec) + : TIMESPEC_OMIT; + ts[1] = self_mtime_nsec != NSEC_INFINITY + ? *TIMESPEC_STORE_NSEC(self_mtime_nsec) + : TIMESPEC_OMIT; /* Restore original directory timestamps */ if (!arg_dry_run && From 122de8d93dbf496b0013c3aa3bc49a5dc6721ff8 Mon Sep 17 00:00:00 2001 From: Ivan Shapovalov Date: Fri, 20 Mar 2026 16:45:07 +0100 Subject: [PATCH 0794/1296] tmpfiles: do not mandate `STATX_ATIME` and `STATX_MTIME` Timestamps are not guaranteed to be set by `statx()`, and their presence should not be asserted as a proxy to judge the kernel version. In particular, `STATX_ATIME` is omitted from the return when querying a file on a `noatime` superblock, causing spurious errors from tmpfiles. Correctness analysis ==================== The timestamps produced by the `statx()` call in `opendir_and_stat()` are only ever used once, in `clean_item_instance()` (lines 3148-3149) as inputs to `dir_cleanup()`. Convert absent timestamps into `NSEC_INFINITY` as per the previous commit. Fixes #41227. --- src/tmpfiles/tmpfiles.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 2b1c612f5ceff..baef92f7af5d3 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -591,8 +591,8 @@ static int opendir_and_stat( /* path= */ NULL, AT_EMPTY_PATH, /* xstatx_flags= */ 0, - STATX_MODE|STATX_INO|STATX_ATIME|STATX_MTIME, - /* optional_mask= */ 0, + STATX_MODE|STATX_INO, + STATX_ATIME|STATX_MTIME, STATX_ATTR_MOUNT_ROOT, &sx); if (r < 0) @@ -3124,6 +3124,7 @@ static int clean_item_instance( return 0; usec_t cutoff = n - i->age; + nsec_t atime_nsec, mtime_nsec; _cleanup_closedir_ DIR *d = NULL; struct statx sx; @@ -3134,6 +3135,9 @@ static int clean_item_instance( if (r <= 0) return r; + atime_nsec = FLAGS_SET(sx.stx_mask, STATX_ATIME) ? statx_timestamp_load_nsec(&sx.stx_atime) : NSEC_INFINITY; + mtime_nsec = FLAGS_SET(sx.stx_mask, STATX_MTIME) ? statx_timestamp_load_nsec(&sx.stx_mtime) : NSEC_INFINITY; + if (DEBUG_LOGGING) { _cleanup_free_ char *ab_f = NULL, *ab_d = NULL; @@ -3153,8 +3157,8 @@ static int clean_item_instance( } return dir_cleanup(c, i, instance, d, - statx_timestamp_load_nsec(&sx.stx_atime), - statx_timestamp_load_nsec(&sx.stx_mtime), + atime_nsec, + mtime_nsec, cutoff * NSEC_PER_USEC, sx.stx_dev_major, sx.stx_dev_minor, mountpoint, From f778f08fae814ccd68444393a11e1c5bc82dcc48 Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Wed, 18 Mar 2026 15:46:01 +0100 Subject: [PATCH 0795/1296] NEWS: fix sysext/confext configuration file names in v259 /etc/systemd/systemd-{confext,sysext}.conf are likely just leftovers from an older in-development version of the feature. --- NEWS | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 71ad3455230a3..573afc9bb77ba 100644 --- a/NEWS +++ b/NEWS @@ -689,9 +689,9 @@ CHANGES WITH 259: systemd-sysext/systemd-confext: * systemd-sysext and systemd-confext now support configuration files - /etc/systemd/systemd-sysext.conf and /etc/systemd/systemd-confext.conf, - which can be used to configure mutability or the image policy to - apply to DDI images. + /etc/systemd/sysext.conf and /etc/systemd/confext.conf, which can be + used to configure mutability or the image policy to apply to DDI + images. * systemd-sysext's and systemd-confext's --mutable= switch now accepts a new value "help" for listing available mutability modes. From c70b17daad3a2186e2cbdcb80896cb638c1d9f40 Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Wed, 18 Mar 2026 17:09:24 +0100 Subject: [PATCH 0796/1296] sysext: provide systemd-{sysext,confext}-sysroot.service services The new services are used to activate system and configuration extensions for the main system from the initrd, this allows to overcome the limitation that sysext/confext cannot be used to update the resources which are required in the earliest boot of the system (before systemd-sysext/systemd-confext start). --- NEWS | 9 ++++++ TODO | 5 ++- man/rules/meson.build | 2 ++ man/systemd-sysext.xml | 44 ++++++++++++++++++--------- presets/90-systemd-initrd.preset | 2 ++ presets/90-systemd.preset | 2 ++ units/meson.build | 8 +++++ units/systemd-confext-initrd.service | 2 +- units/systemd-confext-sysroot.service | 32 +++++++++++++++++++ units/systemd-sysext-initrd.service | 2 +- units/systemd-sysext-sysroot.service | 31 +++++++++++++++++++ 11 files changed, 120 insertions(+), 19 deletions(-) create mode 100644 units/systemd-confext-sysroot.service create mode 100644 units/systemd-sysext-sysroot.service diff --git a/NEWS b/NEWS index 573afc9bb77ba..dc205e6a477ca 100644 --- a/NEWS +++ b/NEWS @@ -28,6 +28,15 @@ CHANGES WITH 261 in spe: require direct IMDS access. The new meson option "-Dimds-network=" can be used to change the default mode to "locked" at build-time. + Changes in systemd-sysext/systemd-confext: + + * New initrd services systemd-sysext-sysroot.service and + systemd-confext-sysroot.service are provided. These services are + used to merge system and configuration extensions for the main system + from the initrd. This overcomes the limitation that system and + configuration extensions merged from the main system itself cannot be + used to modify the resources which are used in the early boot. + CHANGES WITH 260: Feature Removals and Incompatible Changes: diff --git a/TODO b/TODO index 378d3181228ac..aca06755c0614 100644 --- a/TODO +++ b/TODO @@ -119,6 +119,8 @@ Deprecations and removals: similar "devices" Features: +* sysext: make systemd-{sys,conf}ext-sysroot.service work in the split '/var' + configuration. * sd-varlink: add fully async modes of the protocol upgrade stuff @@ -1769,9 +1771,6 @@ Features: * in sd-id128: also parse UUIDs in RFC4122 URN syntax (i.e. chop off urn:uuid: prefix) -* systemd-sysext: optionally, run it in initrd already, before transitioning - into host, to open up possibility for services shipped like that. - * whenever we receive fds via SCM_RIGHTS make sure none got dropped due to the reception limit the kernel silently enforces. diff --git a/man/rules/meson.build b/man/rules/meson.build index 911a68543e960..aa2653ce0d82e 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -1197,8 +1197,10 @@ manpages = [ '8', ['systemd-confext', 'systemd-confext-initrd.service', + 'systemd-confext-sysroot.service', 'systemd-confext.service', 'systemd-sysext-initrd.service', + 'systemd-sysext-sysroot.service', 'systemd-sysext.service'], 'ENABLE_SYSEXT'], ['systemd-system-update-generator', '8', [], ''], diff --git a/man/systemd-sysext.xml b/man/systemd-sysext.xml index c9a8f1ed017e1..49cfee8668741 100644 --- a/man/systemd-sysext.xml +++ b/man/systemd-sysext.xml @@ -20,9 +20,11 @@ systemd-sysext systemd-sysext.service systemd-sysext-initrd.service + systemd-sysext-sysroot.service systemd-confext systemd-confext.service systemd-confext-initrd.service + systemd-confext-sysroot.service Activates System Extension Images @@ -114,22 +116,36 @@ systemd-stub7 with extension images found in the system's EFI System Partition. - During boot OS extension images are activated automatically, if the - systemd-sysext.service is enabled. Note that this service runs only after the - underlying file systems where system extensions may be located have been mounted. This means they are not - suitable for shipping resources that are processed by subsystems running in earliest boot. Specifically, - OS extension images are not suitable for shipping system services or + During boot, system and configuration extension images are activated automatically if the + systemd-sysext.service and systemd-confext.service services are + enabled. Note that these services run only after the underlying file systems where system and configuration + extensions may be located have been mounted. To make it possible to ship resources that are processed by + subsystems running in the earliest boot stages (for example, system services or systemd-sysusers8 - definitions. See the Portable Services page - for a simple mechanism for shipping system services in disk images, in a similar fashion to OS - extensions. Note the different isolation on these two mechanisms: while system extension directly extend - the underlying OS image with additional files that appear in a way very similar to as if they were - shipped in the OS image itself and thus imply no security isolation, portable services imply service - level sandboxing in one way or another. The systemd-sysext.service service is - guaranteed to finish start-up before basic.target is reached; i.e. at the time + definitions), the systemd-sysext-sysroot.service and + systemd-confext-sysroot.service initrd services are provided. Currently, these + services cannot be used to merge system extensions from /sysroot/var/lib/extensions/ + and configuration extensions from /sysroot/var/lib/confexts/ when the + /var/ partition is split off. These extensions are later merged by the + systemd-sysext.service and systemd-confext.service services + during the main OS boot process. + + Also, see the Portable Services + page for a simple mechanism for shipping system services in disk images, in a similar fashion to OS + extensions. Note the differences in isolation between these two mechanisms: while system extensions directly extend + the underlying OS image with additional files that appear as if they were shipped in the OS image itself + and thus imply no security isolation, portable services imply service-level sandboxing in one way or another. + + The systemd-sysext.service and systemd-confext.service + services are guaranteed to finish start-up before basic.target is reached; i.e., by the time regular services initialize (those which do not use DefaultDependencies=no), the files - and directories system extensions provide are available in /usr/ and - /opt/ and may be accessed. + and directories provided by system and configuration extensions are available in /usr/, + /opt/, and /etc/ and may be accessed. + + System and configuration extensions can also be used to extend the initrd, and the + systemd-sysext-initrd.service and systemd-confext-initrd.service + initrd services are provided. Note that some limitations apply: resources that are used in the earliest boot + stages of the initrd (e.g. system services) cannot be updated. Note that there is no concept of enabling/disabling installed system extension images: all installed extension images are automatically activated at boot. However, you can place an empty directory diff --git a/presets/90-systemd-initrd.preset b/presets/90-systemd-initrd.preset index e966a182f15ad..b7b966daca249 100644 --- a/presets/90-systemd-initrd.preset +++ b/presets/90-systemd-initrd.preset @@ -9,12 +9,14 @@ # Settings for systemd units distributed with systemd itself, specific to initrds. +enable systemd-confext-sysroot.service enable systemd-journald-audit.socket enable systemd-network-generator.service enable systemd-networkd.service enable systemd-networkd-wait-online.service enable systemd-pstore.service enable systemd-resolved.service +enable systemd-sysext-sysroot.service enable systemd-tpm2-clear.service disable console-getty.service diff --git a/presets/90-systemd.preset b/presets/90-systemd.preset index 56f9e9370613e..cd7afb5df2523 100644 --- a/presets/90-systemd.preset +++ b/presets/90-systemd.preset @@ -52,7 +52,9 @@ disable proc-sys-fs-binfmt_misc.mount disable syslog.socket disable systemd-boot-check-no-failures.service +disable systemd-confext-sysroot.service disable systemd-journal-gatewayd.* disable systemd-journal-remote.* disable systemd-journal-upload.* +disable systemd-sysext-sysroot.service disable systemd-time-wait-sync.service diff --git a/units/meson.build b/units/meson.build index 02c2db074c259..16c082a623ab5 100644 --- a/units/meson.build +++ b/units/meson.build @@ -311,6 +311,10 @@ units = [ 'conditions' : ['ENABLE_INITRD', 'ENABLE_SYSEXT'], 'symlinks' : ['initrd.target.wants/'], }, + { + 'file' : 'systemd-confext-sysroot.service', + 'conditions' : ['ENABLE_INITRD', 'ENABLE_SYSEXT'], + }, { 'file' : 'systemd-coredump.socket', 'conditions' : ['ENABLE_COREDUMP'], @@ -759,6 +763,10 @@ units = [ 'conditions' : ['ENABLE_INITRD', 'ENABLE_SYSEXT'], 'symlinks' : ['initrd.target.wants/'], }, + { + 'file' : 'systemd-sysext-sysroot.service', + 'conditions' : ['ENABLE_INITRD', 'ENABLE_SYSEXT'], + }, { 'file' : 'systemd-sysext.socket', 'conditions' : ['ENABLE_SYSEXT'], diff --git a/units/systemd-confext-initrd.service b/units/systemd-confext-initrd.service index 073307edcce7f..67e1b1b8d3f33 100644 --- a/units/systemd-confext-initrd.service +++ b/units/systemd-confext-initrd.service @@ -8,7 +8,7 @@ # (at your option) any later version. [Unit] -Description=Merge System Configuration Images into /etc/ +Description=Merge System Configuration Images into /etc/ of the initrd Documentation=man:systemd-confext-initrd.service(8) ConditionCapability=CAP_SYS_ADMIN diff --git a/units/systemd-confext-sysroot.service b/units/systemd-confext-sysroot.service new file mode 100644 index 0000000000000..2ca6da70aac21 --- /dev/null +++ b/units/systemd-confext-sysroot.service @@ -0,0 +1,32 @@ +# 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=Merge System Configuration Images into /sysroot/etc/ +Documentation=man:systemd-confext-sysroot.service(8) + +ConditionCapability=CAP_SYS_ADMIN +ConditionDirectoryNotEmpty=|/sysroot/var/lib/confexts +ConditionDirectoryNotEmpty=|/sysroot/usr/local/lib/confexts +ConditionDirectoryNotEmpty=|/sysroot/usr/lib/confexts +ConditionPathExists=/etc/initrd-release + +DefaultDependencies=no +Conflicts=shutdown.target +Before=initrd-root-fs.target shutdown.target +Wants=modprobe@loop.service modprobe@dm_mod.service +After=modprobe@loop.service modprobe@dm_mod.service sysroot.mount sysroot-usr.mount systemd-volatile-root.service + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=systemd-confext --root=/sysroot refresh + +[Install] +WantedBy=initrd.target diff --git a/units/systemd-sysext-initrd.service b/units/systemd-sysext-initrd.service index 4a411bb65e0ef..c6e93a37195d1 100644 --- a/units/systemd-sysext-initrd.service +++ b/units/systemd-sysext-initrd.service @@ -8,7 +8,7 @@ # (at your option) any later version. [Unit] -Description=Merge System Extension Images into /usr/ and /opt/ +Description=Merge System Extension Images into /usr/ and /opt/ of the initrd Documentation=man:systemd-sysext-initrd.service(8) ConditionCapability=CAP_SYS_ADMIN diff --git a/units/systemd-sysext-sysroot.service b/units/systemd-sysext-sysroot.service new file mode 100644 index 0000000000000..11841ebdcd47e --- /dev/null +++ b/units/systemd-sysext-sysroot.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=Merge System Extension Images into /sysroot/usr/ and /sysroot/opt/ +Documentation=man:systemd-sysext-sysroot.service(8) + +ConditionCapability=CAP_SYS_ADMIN +ConditionDirectoryNotEmpty=|/sysroot/etc/extensions +ConditionDirectoryNotEmpty=|/sysroot/var/lib/extensions +ConditionPathExists=/etc/initrd-release + +DefaultDependencies=no +Conflicts=shutdown.target +Before=initrd-root-fs.target shutdown.target +Wants=modprobe@loop.service modprobe@dm_mod.service +After=modprobe@loop.service modprobe@dm_mod.service sysroot.mount sysroot-usr.mount systemd-volatile-root.service + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=systemd-sysext --root=/sysroot refresh + +[Install] +WantedBy=initrd.target From 2299a37e28249edd06fc66bfda2ad36106cf69da Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Thu, 19 Mar 2026 16:04:34 +0100 Subject: [PATCH 0797/1296] sysext: provide a cmdline kill switch for the sysext/confext merging logic While it is possible to disable sysext/confext merging in the main system with 'systemctl disable', sysext/confext are always merged in the initrd, both by systemd-{sys,conf}ext-initrd.service and by systemd-{sys,conf}ext-sysroot.service and especially the latter can be unexpected. Provide kernel cmdline options systemd.{sys,conf}ext=0 and rd.systemd.{sys,conf}ext=0 covering all options. --- man/kernel-command-line.xml | 16 ++++++++++++++++ man/systemd-sysext.xml | 8 +++++++- src/sysext/sysext.c | 17 +++++++++++++++++ units/systemd-confext-initrd.service | 1 + units/systemd-confext-sysroot.service | 1 + units/systemd-confext.service | 1 + units/systemd-sysext-initrd.service | 1 + units/systemd-sysext-sysroot.service | 1 + units/systemd-sysext.service | 1 + 9 files changed, 46 insertions(+), 1 deletion(-) diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 9e24e749b3f97..0ad3c9c772f3e 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -829,6 +829,22 @@ + + systemd.sysext= + systemd.confext= + rd.systemd.sysext= + rd.systemd.confext= + + Take boolean arguments, default to on. Control whether system and configuration + extensions for the initrd (rd.systemd.sysext=, rd.systemd.confext=) + and for the main system (systemd.sysext=, systemd.confext=) are + merged automatically on boot. See + systemd-sysext8 + for details. + + + + diff --git a/man/systemd-sysext.xml b/man/systemd-sysext.xml index 49cfee8668741..a0e77ad72945b 100644 --- a/man/systemd-sysext.xml +++ b/man/systemd-sysext.xml @@ -150,7 +150,13 @@ Note that there is no concept of enabling/disabling installed system extension images: all installed extension images are automatically activated at boot. However, you can place an empty directory named like the extension (no .raw) in /etc/extensions/ to "mask" - an extension with the same name in a system folder with lower precedence. + an extension with the same name in a system folder with lower precedence. It is also possible to disable + automatic merging altogether using the rd.systemd.sysext=, rd.systemd.confext=, + systemd.sysext=, and systemd.confext= kernel command line options. + Note that systemd-sysext-sysroot.service and + systemd-confext-sysroot.service are controlled by the systemd.sysext= + and systemd.confext= options, as these services merge system and configuration + extensions for the main system, not for the initrd. A simple mechanism for version compatibility is enforced: a system extension image must carry a /usr/lib/extension-release.d/extension-release.NAME diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 52e6c3be666b0..0ac35d1b56b8d 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -51,6 +51,7 @@ #include "path-util.h" #include "pidref.h" #include "pretty-print.h" +#include "proc-cmdline.h" #include "process-util.h" #include "rm-rf.h" #include "runtime-scope.h" @@ -3038,6 +3039,22 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + /* PROC_CMDLINE_STRIP_RD_PREFIX cannot be used here as we need to be able to distinguish between + * rd.systemd.{sysext,confext} and systemd.{sysext,confext} in the initrd where they are both used + * and have different meaning. */ + const char *string_class = image_class_to_string(arg_image_class); + const char *cmdline_opt = strjoina(in_initrd() && !arg_root ? "rd." : "", "systemd.", string_class); + + bool enabled; + r = proc_cmdline_get_bool(cmdline_opt, PROC_CMDLINE_TRUE_WHEN_MISSING, &enabled); + if (r < 0) + log_debug_errno(r, "Failed to check '%s=' kernel command line option, proceeding: %m", cmdline_opt); + else if (!enabled && invoked_by_systemd()) { + /* Kernel command line option should not affect manual invocation. */ + log_notice("Disabled by the kernel command line option '%s=', skipping execution.", cmdline_opt); + return 0; + } + /* Parse configuration file after argv because it needs --root=. * The config entries will not overwrite values set already by * env/argv because we track initialization. */ diff --git a/units/systemd-confext-initrd.service b/units/systemd-confext-initrd.service index 67e1b1b8d3f33..9984bd7065217 100644 --- a/units/systemd-confext-initrd.service +++ b/units/systemd-confext-initrd.service @@ -19,6 +19,7 @@ ConditionDirectoryNotEmpty=|/usr/lib/confexts ConditionDirectoryNotEmpty=|/.extra/confext ConditionDirectoryNotEmpty=|/.extra/global_confext ConditionPathExists=/etc/initrd-release +ConditionKernelCommandLine=!rd.systemd.confext=0 DefaultDependencies=no Before=local-fs-pre.target cryptsetup-pre.target systemd-tmpfiles-setup.service diff --git a/units/systemd-confext-sysroot.service b/units/systemd-confext-sysroot.service index 2ca6da70aac21..e30193e17e4dc 100644 --- a/units/systemd-confext-sysroot.service +++ b/units/systemd-confext-sysroot.service @@ -16,6 +16,7 @@ ConditionDirectoryNotEmpty=|/sysroot/var/lib/confexts ConditionDirectoryNotEmpty=|/sysroot/usr/local/lib/confexts ConditionDirectoryNotEmpty=|/sysroot/usr/lib/confexts ConditionPathExists=/etc/initrd-release +ConditionKernelCommandLine=!systemd.confext=0 DefaultDependencies=no Conflicts=shutdown.target diff --git a/units/systemd-confext.service b/units/systemd-confext.service index e509036d03599..ffbf8345b8d6a 100644 --- a/units/systemd-confext.service +++ b/units/systemd-confext.service @@ -17,6 +17,7 @@ ConditionDirectoryNotEmpty=|/var/lib/confexts ConditionDirectoryNotEmpty=|/usr/local/lib/confexts ConditionDirectoryNotEmpty=|/usr/lib/confexts ConditionPathExists=!/etc/initrd-release +ConditionKernelCommandLine=!systemd.confext=0 DefaultDependencies=no After=local-fs.target diff --git a/units/systemd-sysext-initrd.service b/units/systemd-sysext-initrd.service index c6e93a37195d1..2d9fb59cf2900 100644 --- a/units/systemd-sysext-initrd.service +++ b/units/systemd-sysext-initrd.service @@ -18,6 +18,7 @@ ConditionDirectoryNotEmpty=|/var/lib/extensions ConditionDirectoryNotEmpty=|/.extra/sysext ConditionDirectoryNotEmpty=|/.extra/global_sysext ConditionPathExists=/etc/initrd-release +ConditionKernelCommandLine=!rd.systemd.sysext=0 DefaultDependencies=no Before=local-fs-pre.target cryptsetup-pre.target systemd-tmpfiles-setup.service diff --git a/units/systemd-sysext-sysroot.service b/units/systemd-sysext-sysroot.service index 11841ebdcd47e..d68c70da69127 100644 --- a/units/systemd-sysext-sysroot.service +++ b/units/systemd-sysext-sysroot.service @@ -15,6 +15,7 @@ ConditionCapability=CAP_SYS_ADMIN ConditionDirectoryNotEmpty=|/sysroot/etc/extensions ConditionDirectoryNotEmpty=|/sysroot/var/lib/extensions ConditionPathExists=/etc/initrd-release +ConditionKernelCommandLine=!systemd.sysext=0 DefaultDependencies=no Conflicts=shutdown.target diff --git a/units/systemd-sysext.service b/units/systemd-sysext.service index f20a076128022..3246ea7fb7b76 100644 --- a/units/systemd-sysext.service +++ b/units/systemd-sysext.service @@ -17,6 +17,7 @@ ConditionDirectoryNotEmpty=|/run/extensions ConditionDirectoryNotEmpty=|/var/lib/extensions ConditionDirectoryNotEmpty=|/var/lib/extensions.mutable ConditionPathExists=!/etc/initrd-release +ConditionKernelCommandLine=!systemd.sysext=0 DefaultDependencies=no After=local-fs.target From 6adc9abc4a6958741e9b0242cbee22912e128745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 4 Apr 2026 19:43:04 +0200 Subject: [PATCH 0798/1296] imds: convert to the new option parser Cosmetic changes in --help output only. Co-developed-by: Claude Opus 4.6 --- src/imds/imds-tool.c | 121 ++++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 70 deletions(-) diff --git a/src/imds/imds-tool.c b/src/imds/imds-tool.c index ff5a9b317af8a..0d71801aec659 100644 --- a/src/imds/imds-tool.c +++ b/src/imds/imds-tool.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include @@ -26,6 +25,7 @@ #include "json-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "pcrextend-util.h" #include "pretty-print.h" @@ -51,148 +51,129 @@ STATIC_DESTRUCTOR_REGISTER(arg_key, freep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-imds", "1", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...] [KEY]\n" - "\n%sIMDS data acquisition.%s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -K --well-known=[hostname|region|zone|ipv4-public|ipv6-public|ssh-key|\n" - " userdata|userdata-base|userdata-base64]\n" - " Select well-known key/base\n" - " --refresh=SEC Set minimum freshness time for returned data\n" - " --cache=no Disable cache use\n" - " -u --userdata Dump user data\n" - " --import Import system credentials from IMDS userdata\n" - " and place them in /run/credstore/\n" - "\nSee the %s for details.\n", + "\n%sIMDS data acquisition.%s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_REFRESH, - ARG_CACHE, - ARG_IMPORT, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "well-known", required_argument, NULL, 'K' }, - { "refresh", required_argument, NULL, ARG_REFRESH }, - { "cache", required_argument, NULL, ARG_CACHE }, - { "userdata", no_argument, NULL, 'u' }, - { "import", no_argument, NULL, ARG_IMPORT }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hK:u", options, NULL)) >= 0) { + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'K': { - if (isempty(optarg)) { + OPTION('K', "well-known", "KEY", + "Select well-known key/base, one of:" + " hostname, region, zone, ipv4-public, ipv6-public, ssh-key," + " userdata, userdata-base, userdata-base64"): { + if (isempty(arg)) { arg_well_known = _IMDS_WELL_KNOWN_INVALID; break; } - if (streq(optarg, "help")) + if (streq(arg, "help")) return DUMP_STRING_TABLE(imds_well_known, ImdsWellKnown, _IMDS_WELL_KNOWN_MAX); - ImdsWellKnown wk = imds_well_known_from_string(optarg); + ImdsWellKnown wk = imds_well_known_from_string(arg); if (wk < 0) - return log_error_errno(wk, "Failed to parse --well-known= argument: %s", optarg); + return log_error_errno(wk, "Failed to parse --well-known= argument: %s", arg); arg_well_known = wk; break; } - case ARG_CACHE: - r = parse_tristate_argument_with_auto("--cache=", optarg, &arg_cache); - if (r < 0) - return r; - - break; - - case ARG_REFRESH: { - if (isempty(optarg)) { + OPTION_LONG("refresh", "SEC", "Set minimum freshness time for returned data"): { + if (isempty(arg)) { arg_refresh_usec_set = false; break; } usec_t t; - r = parse_sec(optarg, &t); + r = parse_sec(arg, &t); if (r < 0) - return log_error_errno(r, "Failed to parse refresh timeout: %s", optarg); + return log_error_errno(r, "Failed to parse refresh timeout: %s", arg); arg_refresh_usec = t; arg_refresh_usec_set = true; break; } - case 'u': + OPTION_LONG("cache", "BOOL", "Control cache use"): + r = parse_tristate_argument_with_auto("--cache=", arg, &arg_cache); + if (r < 0) + return r; + break; + + OPTION('u', "userdata", NULL, "Dump user data"): arg_action = ACTION_USERDATA; break; - case ARG_IMPORT: + OPTION_LONG("import", NULL, + "Import system credentials from IMDS userdata" + " and place them in /run/credstore/"): arg_action = ACTION_IMPORT; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } + + char **args = option_parser_get_args(&state); + size_t n_args = option_parser_get_n_args(&state); if (IN_SET(arg_action, ACTION_USERDATA, ACTION_IMPORT)) { - if (argc != optind) + if (n_args != 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No parameters expected."); } else { assert(arg_action < 0); - if (argc > optind + 1) + if (n_args > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "None or one argument expected."); - if (argc == optind && arg_well_known < 0) + if (n_args == 0 && arg_well_known < 0) arg_action = ACTION_SUMMARY; else { if (arg_well_known < 0) arg_well_known = IMDS_BASE; - if (argc > optind) { - if (!imds_key_is_valid(argv[optind])) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified IMDS key is not valid, refusing: %s", argv[optind]); + if (n_args > 0) { + if (!imds_key_is_valid(args[0])) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified IMDS key is not valid, refusing: %s", args[0]); if (!imds_well_known_can_suffix(arg_well_known)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Well known key '%s' does not take a key suffix, refusing.", imds_well_known_to_string(arg_well_known)); - r = free_and_strdup_warn(&arg_key, argv[optind]); + r = free_and_strdup_warn(&arg_key, args[0]); if (r < 0) return r; } From e59fea008841c0050297692109922b4a34fe8dba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 4 Apr 2026 20:38:52 +0200 Subject: [PATCH 0799/1296] shared/options: quote the metavar in --help output imdsd uses --extra-header='NAME: VALUE'. We could include the quotes in the metavar string, but I think it's nicer to only do that in the printed output, so that later, when we add introspection, the value there will not include the quotes. --- src/shared/options.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/shared/options.c b/src/shared/options.c index e6b82fe34d07c..f94ba28432104 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -297,6 +297,7 @@ int _option_parser_get_help_table( * "=" is shown only when a long option is defined: -l --long=ARG, --long=ARG, -s ARG. */ bool need_eq = option_takes_arg(opt) && opt->long_code; + bool need_quote = opt->metavar && strchr(opt->metavar, ' '); _cleanup_free_ char *s = strjoin( " ", sc, @@ -305,7 +306,9 @@ int _option_parser_get_help_table( strempty(opt->long_code), option_arg_optional(opt) ? "[" : "", need_eq ? "=" : "", + need_quote ? "'" : "", strempty(opt->metavar), + need_quote ? "'" : "", option_arg_optional(opt) ? "]" : ""); if (!s) return log_oom(); From e3d25c82abc56e95d00fd844dca46877ac922b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 4 Apr 2026 19:58:52 +0200 Subject: [PATCH 0800/1296] imdsd: convert to the new option parser Previously -w was ambiguously described in --help as taking an argument, but it is in fact an argumentless alias for --wait=yes. Co-developed-by: Claude Opus 4.6 --- src/imds/imdsd.c | 297 +++++++++++++++++++---------------------------- 1 file changed, 120 insertions(+), 177 deletions(-) diff --git a/src/imds/imdsd.c b/src/imds/imdsd.c index 68a03b7232c7b..c0ab089830350 100644 --- a/src/imds/imdsd.c +++ b/src/imds/imdsd.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include @@ -26,6 +25,7 @@ #include "event-util.h" #include "fd-util.h" #include "format-ifname.h" +#include "format-table.h" #include "hash-funcs.h" #include "hashmap.h" #include "imds-util.h" @@ -36,6 +36,7 @@ #include "log.h" #include "main-func.h" #include "netlink-util.h" +#include "options.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" @@ -2203,50 +2204,45 @@ static int vl_server(void) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *endpoint_options = NULL; int r; r = terminal_urlify_man("systemd-imdsd@.service", "8", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + r = option_parser_get_help_table_group("Manual Endpoint Configuration", &endpoint_options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, options, endpoint_options); + printf("%1$s [OPTIONS...] KEY\n" - "\n%5$sLow-level IMDS data acquisition.%6$s\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -i --interface=INTERFACE\n" - " Use the specified interface\n" - " --refresh=SEC Set token refresh time\n" - " --fwmark=INTEGER Choose firewall mark for HTTP traffic\n" - " --cache=no Disable cache use\n" - " -w --wait=yes Wait for connectivity\n" - " -K --well-known= Select well-known key\n" - " --setup-network Generate .network and .rr files\n" - "\n%3$sManual Endpoint Configuration:%4$s\n" - " --vendor=VENDOR Specify IMDS vendor literally\n" - " --token-url=URL URL for acquiring token\n" - " --refresh-header-name=NAME\n" - " Header name for passing refresh time\n" - " --data-url=URL Base URL for acquiring data\n" - " --data-url-suffix=STRING\n" - " Suffix to append to data URL\n" - " --token-header-name=NAME\n" - " Header name for passing token string\n" - " --extra-header='NAME: VALUE'\n" - " Additional header to pass to data transfer\n" - " --address-ipv4=ADDRESS\n" - " --address-ipv6=ADDRESS\n" - " Configure the IPv4 and IPv6 address of the IMDS server\n" - " --well-known-key=NAME:KEY\n" - " Configure the location of well-known keys\n" - "\nSee the %2$s for details.\n", + "\n%2$sLow-level IMDS data acquisition.%3$s\n" + "\n%4$sOptions:%5$s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\n%sManual Endpoint Configuration:%s\n", + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(endpoint_options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } @@ -2259,91 +2255,48 @@ static bool http_header_valid(const char *a) { } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_REFRESH, - ARG_FWMARK, - ARG_CACHE, - ARG_WAIT, - ARG_VENDOR, - ARG_TOKEN_URL, - ARG_REFRESH_HEADER_NAME, - ARG_DATA_URL, - ARG_DATA_URL_SUFFIX, - ARG_TOKEN_HEADER_NAME, - ARG_EXTRA_HEADER, - ARG_ADDRESS_IPV4, - ARG_ADDRESS_IPV6, - ARG_WELL_KNOWN_KEY, - ARG_SETUP_NETWORK, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "interface", required_argument, NULL, 'i' }, - { "refresh", required_argument, NULL, ARG_REFRESH }, - { "fwmark", required_argument, NULL, ARG_FWMARK }, - { "cache", required_argument, NULL, ARG_CACHE }, - { "wait", required_argument, NULL, ARG_WAIT }, - { "well-known", required_argument, NULL, 'K' }, - { "setup-network", no_argument, NULL, ARG_SETUP_NETWORK }, - - /* The following all configure endpoint information explicitly */ - { "vendor", required_argument, NULL, ARG_VENDOR }, - { "token-url", required_argument, NULL, ARG_TOKEN_URL }, - { "refresh-header-name", required_argument, NULL, ARG_REFRESH_HEADER_NAME }, - { "data-url", required_argument, NULL, ARG_DATA_URL }, - { "data-url-suffix", required_argument, NULL, ARG_DATA_URL_SUFFIX }, - { "token-header-name", required_argument, NULL, ARG_TOKEN_HEADER_NAME }, - { "extra-header", required_argument, NULL, ARG_EXTRA_HEADER }, - { "address-ipv4", required_argument, NULL, ARG_ADDRESS_IPV4 }, - { "address-ipv6", required_argument, NULL, ARG_ADDRESS_IPV6 }, - { "well-known-key", required_argument, NULL, ARG_WELL_KNOWN_KEY }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hi:wK:", options, NULL)) >= 0) { + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'i': - if (isempty(optarg)) { + OPTION('i', "interface", "INTERFACE", "Use the specified interface"): + if (isempty(arg)) { arg_ifname = mfree(arg_ifname); break; } - if (!ifname_valid_full(optarg, IFNAME_VALID_ALTERNATIVE|IFNAME_VALID_NUMERIC)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Interface name '%s' is not valid.", optarg); + if (!ifname_valid_full(arg, IFNAME_VALID_ALTERNATIVE|IFNAME_VALID_NUMERIC)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Interface name '%s' is not valid.", arg); - r = free_and_strdup_warn(&arg_ifname, optarg); + r = free_and_strdup_warn(&arg_ifname, arg); if (r < 0) return r; break; - case ARG_REFRESH: { - if (isempty(optarg)) { + OPTION_LONG("refresh", "SEC", "Set token refresh time"): { + if (isempty(arg)) { arg_refresh_usec = REFRESH_USEC_DEFAULT; break; } usec_t t; - r = parse_sec(optarg, &t); + r = parse_sec(arg, &t); if (r < 0) - return log_error_errno(r, "Failed to parse refresh timeout: %s", optarg); + return log_error_errno(r, "Failed to parse refresh timeout: %s", arg); if (t < REFRESH_USEC_MIN) { log_warning("Increasing specified refresh time to %s, lower values are not supported.", FORMAT_TIMESPAN(REFRESH_USEC_MIN, 0)); arg_refresh_usec = REFRESH_USEC_MIN; @@ -2352,50 +2305,48 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_FWMARK: - if (isempty(optarg)) { + OPTION_LONG("fwmark", "INTEGER", "Choose firewall mark for HTTP traffic"): + if (isempty(arg)) { arg_fwmark_set = false; break; } - if (streq(optarg, "default")) { + if (streq(arg, "default")) { arg_fwmark = FWMARK_DEFAULT; arg_fwmark_set = true; break; } - r = safe_atou32(optarg, &arg_fwmark); + r = safe_atou32(arg, &arg_fwmark); if (r < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse --fwmark= parameter: %s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse --fwmark= parameter: %s", arg); arg_fwmark_set = true; break; - case ARG_CACHE: - r = parse_boolean_argument("--cache", optarg, &arg_cache); + OPTION_LONG("cache", "BOOL", "Enable/disable cache use"): + r = parse_boolean_argument("--cache", arg, &arg_cache); if (r < 0) return r; - break; - case ARG_WAIT: - r = parse_boolean_argument("--wait", optarg, &arg_wait); + OPTION_LONG("wait", "BOOL", "Whether to wait for connectivity"): + r = parse_boolean_argument("--wait", arg, &arg_wait); if (r < 0) return r; - break; - case 'w': + OPTION_SHORT('w', NULL, "Same as --wait=yes"): arg_wait = true; break; - case 'K': { - if (isempty(optarg)) { + OPTION('K', "well-known", "KEY", "Select well-known key"): { + if (isempty(arg)) { arg_well_known = _IMDS_WELL_KNOWN_INVALID; break; } - ImdsWellKnown wk = imds_well_known_from_string(optarg); + ImdsWellKnown wk = imds_well_known_from_string(arg); if (wk < 0) return log_error_errno(wk, "Failed to parse --well-known= parameter: %m"); @@ -2403,146 +2354,148 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_VENDOR: - if (isempty(optarg)) { + OPTION_LONG("setup-network", NULL, "Generate .network and .rr files"): + arg_setup_network = true; + break; + + /* The following all configure endpoint information explicitly */ + OPTION_GROUP("Manual Endpoint Configuration"): + break; + + OPTION_LONG("vendor", "VENDOR", "Specify IMDS vendor literally"): + if (isempty(arg)) { arg_vendor = mfree(arg_vendor); break; } - r = free_and_strdup_warn(&arg_vendor, optarg); + r = free_and_strdup_warn(&arg_vendor, arg); if (r < 0) return r; break; - case ARG_TOKEN_URL: - if (isempty(optarg)) { + OPTION_LONG("token-url", "URL", "URL for acquiring token"): + if (isempty(arg)) { arg_token_url = mfree(arg_token_url); break; } - if (!http_url_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid URL: %s", optarg); + if (!http_url_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid URL: %s", arg); - r = free_and_strdup_warn(&arg_token_url, optarg); + r = free_and_strdup_warn(&arg_token_url, arg); if (r < 0) return r; - break; - case ARG_REFRESH_HEADER_NAME: - if (isempty(optarg)) { + OPTION_LONG("refresh-header-name", "NAME", "Header name for passing refresh time"): + if (isempty(arg)) { arg_refresh_header_name = mfree(arg_refresh_header_name); break; } - if (!http_header_name_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header name: %s", optarg); + if (!http_header_name_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header name: %s", arg); - r = free_and_strdup_warn(&arg_refresh_header_name, optarg); + r = free_and_strdup_warn(&arg_refresh_header_name, arg); if (r < 0) return r; - break; - case ARG_DATA_URL: - if (isempty(optarg)) { + OPTION_LONG("data-url", "URL", "Base URL for acquiring data"): + if (isempty(arg)) { arg_data_url = mfree(arg_data_url); break; } - if (!http_url_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid URL: %s", optarg); + if (!http_url_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid URL: %s", arg); - r = free_and_strdup_warn(&arg_data_url, optarg); + r = free_and_strdup_warn(&arg_data_url, arg); if (r < 0) return r; - break; - case ARG_DATA_URL_SUFFIX: - if (isempty(optarg)) { + OPTION_LONG("data-url-suffix", "STRING", "Suffix to append to data URL"): + if (isempty(arg)) { arg_data_url_suffix = mfree(arg_data_url_suffix); break; } - if (!ascii_is_valid(optarg) || string_has_cc(optarg, /* ok= */ NULL)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid URL suffix: %s", optarg); + if (!ascii_is_valid(arg) || string_has_cc(arg, /* ok= */ NULL)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid URL suffix: %s", arg); - r = free_and_strdup_warn(&arg_data_url_suffix, optarg); + r = free_and_strdup_warn(&arg_data_url_suffix, arg); if (r < 0) return r; - break; - case ARG_TOKEN_HEADER_NAME: - if (isempty(optarg)) { + OPTION_LONG("token-header-name", "NAME", "Header name for passing token string"): + if (isempty(arg)) { arg_token_header_name = mfree(arg_token_header_name); break; } - if (!http_header_name_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header name: %s", optarg); + if (!http_header_name_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header name: %s", arg); - r = free_and_strdup_warn(&arg_token_header_name, optarg); + r = free_and_strdup_warn(&arg_token_header_name, arg); if (r < 0) return r; - break; - case ARG_EXTRA_HEADER: - if (isempty(optarg)) { + OPTION_LONG("extra-header", "NAME: VALUE", "Additional header to pass to data transfer"): + if (isempty(arg)) { arg_extra_header = strv_free(arg_extra_header); break; } - if (!http_header_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header: %s", optarg); + if (!http_header_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header: %s", arg); - if (strv_extend(&arg_extra_header, optarg) < 0) + if (strv_extend(&arg_extra_header, arg) < 0) return log_oom(); - break; - case ARG_ADDRESS_IPV4: { - if (isempty(optarg)) { + OPTION_LONG("address-ipv4", "ADDRESS", "Configure IPv4 address of the IMDS server"): { + if (isempty(arg)) { arg_address_ipv4 = (struct in_addr) {}; break; } union in_addr_union u; - r = in_addr_from_string(AF_INET, optarg, &u); + r = in_addr_from_string(AF_INET, arg, &u); if (r < 0) - return log_error_errno(r, "Failed to parse IPv4 address: %s", optarg); + return log_error_errno(r, "Failed to parse IPv4 address: %s", arg); arg_address_ipv4 = u.in; break; } - case ARG_ADDRESS_IPV6: { - if (isempty(optarg)) { + OPTION_LONG("address-ipv6", "ADDRESS", "Configure IPv6 address of the IMDS server"): { + if (isempty(arg)) { arg_address_ipv6 = (struct in6_addr) {}; break; } union in_addr_union u; - r = in_addr_from_string(AF_INET6, optarg, &u); + r = in_addr_from_string(AF_INET6, arg, &u); if (r < 0) - return log_error_errno(r, "Failed to parse IPv6 address: %s", optarg); + return log_error_errno(r, "Failed to parse IPv6 address: %s", arg); arg_address_ipv6 = u.in6; break; } - case ARG_WELL_KNOWN_KEY: { - if (isempty(optarg)) { + OPTION_LONG("well-known-key", "NAME:KEY", "Configure the location of well-known keys"): { + if (isempty(arg)) { for (ImdsWellKnown wk = 0; wk < _IMDS_WELL_KNOWN_MAX; wk++) arg_well_known_key[wk] = mfree(arg_well_known_key[wk]); break; } - const char *e = strchr(optarg, ':'); + const char *e = strchr(arg, ':'); if (!e) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--well-known-key= expects colon separated name and key pairs."); - _cleanup_free_ char *name = strndup(optarg, e - optarg); + _cleanup_free_ char *name = strndup(arg, e - arg); if (!name) return log_oom(); @@ -2557,21 +2510,9 @@ static int parse_argv(int argc, char *argv[]) { r = free_and_strdup_warn(arg_well_known_key + wk, e); if (r < 0) return r; - break; } - - case ARG_SETUP_NETWORK: - arg_setup_network = true; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } if (arg_vendor || arg_token_url || arg_refresh_header_name || arg_data_url || arg_data_url_suffix || arg_token_header_name || arg_extra_header) arg_endpoint_source = ENDPOINT_USER; @@ -2583,23 +2524,25 @@ static int parse_argv(int argc, char *argv[]) { arg_varlink = r; if (!arg_varlink) { + char **args = option_parser_get_args(&state); + size_t n_args = option_parser_get_n_args(&state); if (arg_setup_network) { - if (optind != argc) + if (n_args != 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No argument expected."); } else { if (arg_well_known < 0) { /* if no --well-known= parameter was specified we require an argument */ - if (argc != optind+1) + if (n_args != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "A single argument expected."); - } else if (argc > optind+1) /* if not, then the additional parameter is optional */ + } else if (n_args > 1) /* if not, then the additional parameter is optional */ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "At most a single argument expected."); - if (argc > optind) { - if (!imds_key_is_valid(argv[optind])) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified IMDS key is not valid, refusing: %s", argv[optind]); + if (n_args > 0) { + if (!imds_key_is_valid(args[0])) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified IMDS key is not valid, refusing: %s", args[0]); - r = free_and_strdup_warn(&arg_key, argv[optind]); + r = free_and_strdup_warn(&arg_key, args[0]); if (r < 0) return r; } From e1eae0cf7816db8c7d3eca8d0d64b2705b37b153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 4 Apr 2026 20:16:28 +0200 Subject: [PATCH 0801/1296] varlinkctl: convert to the new option parser The -E short option previously used fallthrough into the --more case; since macro-generated case labels don't support fallthrough (with some older compilers), the --more logic is now duplicated inline in the -E handler. Co-developed-by: Claude Opus 4.6 --- src/varlinkctl/varlinkctl.c | 185 +++++++++++++----------------------- 1 file changed, 66 insertions(+), 119 deletions(-) diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index 4124d0d570726..2dc191bff32bf 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -20,6 +19,7 @@ #include "log.h" #include "main-func.h" #include "memfd-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -72,17 +72,22 @@ STATIC_DESTRUCTOR_REGISTER(arg_push_fds, push_fds_done); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("varlinkctl", "1", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + pager_open(arg_pager_flags); printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%5$sIntrospect Varlink Services.%6$s\n" - "\n%3$sCommands:%4$s\n" + "%3$sIntrospect Varlink Services.%4$s\n" + "\n%2$sCommands:%4$s\n" " info ADDRESS Show service information\n" " list-interfaces ADDRESS\n" " List interfaces implemented by service\n" @@ -98,33 +103,17 @@ static int help(void) { " list-registry Show list of services in the service registry\n" " validate-idl [FILE] Validate interface description\n" " help Show this help\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-ask-password Do not prompt for password\n" - " --no-pager Do not pipe output into a pager\n" - " --system Enumerate system registry\n" - " --user Enumerate user registry\n" - " --more Request multiple responses\n" - " --collect Collect multiple responses in a JSON array\n" - " --oneway Do not request response\n" - " --json=MODE Output as JSON\n" - " -j Same as --json=pretty on tty, --json=short otherwise\n" - " -q --quiet Do not output method reply\n" - " --graceful=ERROR Treat specified Varlink error as success\n" - " --timeout=SECS Maximum time to wait for method call completion\n" - " -E Short for --more --timeout=infinity\n" - " --upgrade Request protocol upgrade (connection becomes raw\n" - " bidirectional pipe on stdin/stdout after reply)\n" - " --push-fd=FD Pass the specified fd along with method call\n" - "\nSee the %2$s for details.\n", + "\n%2$sOptions:%4$s\n", program_invocation_short_name, - link, ansi_underline(), - ansi_normal(), ansi_highlight(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } @@ -133,145 +122,121 @@ static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_MORE, - ARG_ONEWAY, - ARG_JSON, - ARG_COLLECT, - ARG_GRACEFUL, - ARG_TIMEOUT, - ARG_EXEC, - ARG_UPGRADE, - ARG_PUSH_FD, - ARG_NO_ASK_PASSWORD, - ARG_USER, - ARG_SYSTEM, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "more", no_argument, NULL, ARG_MORE }, - { "oneway", no_argument, NULL, ARG_ONEWAY }, - { "json", required_argument, NULL, ARG_JSON }, - { "collect", no_argument, NULL, ARG_COLLECT }, - { "quiet", no_argument, NULL, 'q' }, - { "graceful", required_argument, NULL, ARG_GRACEFUL }, - { "timeout", required_argument, NULL, ARG_TIMEOUT }, - { "exec", no_argument, NULL, ARG_EXEC }, - { "upgrade", no_argument, NULL, ARG_UPGRADE }, - { "push-fd", required_argument, NULL, ARG_PUSH_FD }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - {}, - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hjqE", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case 'E': - arg_timeout = USEC_INFINITY; - _fallthrough_; + OPTION_LONG("system", NULL, "Enumerate system registry"): + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; - case ARG_MORE: - arg_method_flags = (arg_method_flags & ~SD_VARLINK_METHOD_ONEWAY) | SD_VARLINK_METHOD_MORE; + OPTION_LONG("user", NULL, "Enumerate user registry"): + arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_ONEWAY: - arg_method_flags = (arg_method_flags & ~SD_VARLINK_METHOD_MORE) | SD_VARLINK_METHOD_ONEWAY; + OPTION_LONG("more", NULL, "Request multiple responses"): + arg_method_flags = (arg_method_flags & ~SD_VARLINK_METHOD_ONEWAY) | SD_VARLINK_METHOD_MORE; break; - case ARG_COLLECT: + OPTION_LONG("collect", NULL, "Collect multiple responses in a JSON array"): arg_collect = true; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_LONG("oneway", NULL, "Do not request response"): + arg_method_flags = (arg_method_flags & ~SD_VARLINK_METHOD_MORE) | SD_VARLINK_METHOD_ONEWAY; + break; + + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); if (r <= 0) return r; - break; - case 'j': + OPTION_COMMON_LOWERCASE_J: arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; break; - case 'q': + OPTION('q', "quiet", NULL, "Do not output method reply"): arg_quiet = true; break; - case ARG_GRACEFUL: - r = varlink_idl_qualified_symbol_name_is_valid(optarg); + OPTION_LONG("graceful", "ERROR", "Treat specified Varlink error as success"): + r = varlink_idl_qualified_symbol_name_is_valid(arg); if (r < 0) - return log_error_errno(r, "Failed to validate Varlink error name '%s': %m", optarg); + return log_error_errno(r, "Failed to validate Varlink error name '%s': %m", arg); if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid Varlink error name: %s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid Varlink error name: %s", arg); - if (strv_extend(&arg_graceful, optarg) < 0) + if (strv_extend(&arg_graceful, arg) < 0) return log_oom(); - break; - case ARG_TIMEOUT: - if (isempty(optarg)) { + OPTION_LONG("timeout", "SECS", "Maximum time to wait for method call completion"): + if (isempty(arg)) { arg_timeout = USEC_INFINITY; break; } - r = parse_sec(optarg, &arg_timeout); + r = parse_sec(arg, &arg_timeout); if (r < 0) - return log_error_errno(r, "Failed to parse --timeout= parameter '%s': %m", optarg); + return log_error_errno(r, "Failed to parse --timeout= parameter '%s': %m", arg); if (arg_timeout == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Timeout cannot be zero."); - break; - case ARG_EXEC: - arg_exec = true; + OPTION_SHORT('E', NULL, "Short for --more --timeout=infinity"): + arg_timeout = USEC_INFINITY; + arg_method_flags = (arg_method_flags & ~SD_VARLINK_METHOD_ONEWAY) | SD_VARLINK_METHOD_MORE; break; - case ARG_UPGRADE: + OPTION_LONG("upgrade", NULL, + "Request protocol upgrade (connection becomes raw" + " bidirectional pipe on stdin/stdout after reply)"): arg_upgrade = true; break; - case ARG_PUSH_FD: { + OPTION_LONG("exec", NULL, "Invoke method and pass response and fds to command"): + arg_exec = true; + break; + + OPTION_LONG("push-fd", "FD", "Pass the specified fd along with method call"): { if (!GREEDY_REALLOC(arg_push_fds.fds, arg_push_fds.n_fds + 1)) return log_oom(); _cleanup_close_ int add_fd = -EBADF; - if (STARTSWITH_SET(optarg, "/", "./")) { + if (STARTSWITH_SET(arg, "/", "./")) { /* We usually expect a numeric fd spec, but as an extension let's treat this * as a path to open in read-only mode in case this is clearly an absolute or * relative path */ - add_fd = open(optarg, O_CLOEXEC|O_RDONLY|O_NOCTTY); + add_fd = open(arg, O_CLOEXEC|O_RDONLY|O_NOCTTY); if (add_fd < 0) - return log_error_errno(errno, "Failed to open '%s': %m", optarg); + return log_error_errno(errno, "Failed to open '%s': %m", arg); } else { - int parsed_fd = parse_fd(optarg); + int parsed_fd = parse_fd(arg); if (parsed_fd < 0) - return log_error_errno(parsed_fd, "Failed to parse --push-fd= parameter: %s", optarg); + return log_error_errno(parsed_fd, "Failed to parse --push-fd= parameter: %s", arg); /* Make a copy, so that the same fd could be used multiple times in a reasonable * way. This also validates the fd early */ @@ -283,24 +248,6 @@ static int parse_argv(int argc, char *argv[]) { arg_push_fds.fds[arg_push_fds.n_fds++] = TAKE_FD(add_fd); break; } - - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; - break; - - case ARG_USER: - arg_runtime_scope = RUNTIME_SCOPE_USER; - break; - - case ARG_SYSTEM: - arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } /* If more than one reply is expected, imply JSON-SEQ output, and set SD_JSON_FORMAT_FLUSH */ From fd5c19e8d0a38199c64b1fcb947332d61337f4a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 4 Apr 2026 20:24:16 +0200 Subject: [PATCH 0802/1296] varlinkctl: convert to the new verb macros The description of --exec is moved to a separate footer. It requires special formatting and doesn't fit in the autogenerated table of verbs. Co-developed-by: Claude Opus 4.6 --- src/varlinkctl/varlinkctl.c | 77 ++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 43 deletions(-) diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index 2dc191bff32bf..00bd71f34dedc 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -72,7 +72,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_push_fds, push_fds_done); static int help(void) { _cleanup_free_ char *link = NULL; - _cleanup_(table_unrefp) Table *options = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("varlinkctl", "1", &link); @@ -83,45 +83,42 @@ static int help(void) { if (r < 0) return r; + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, options, verbs); + pager_open(arg_pager_flags); - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%3$sIntrospect Varlink Services.%4$s\n" - "\n%2$sCommands:%4$s\n" - " info ADDRESS Show service information\n" - " list-interfaces ADDRESS\n" - " List interfaces implemented by service\n" - " list-methods ADDRESS [INTERFACE…]\n" - " List methods implemented by services or specific\n" - " interfaces\n" - " introspect ADDRESS [INTERFACE…]\n" - " Show interface definition\n" - " call ADDRESS METHOD [PARAMS]\n" - " Invoke method\n" - " --exec call ADDRESS METHOD PARAMS -- CMDLINE…\n" - " Invoke method and pass response and fds to command\n" - " list-registry Show list of services in the service registry\n" - " validate-idl [FILE] Validate interface description\n" - " help Show this help\n" - "\n%2$sOptions:%4$s\n", + printf("%s [OPTIONS...] COMMAND ...\n\n" + "%sIntrospect Varlink Services.%s\n" + "\nCommands:\n", program_invocation_short_name, - ansi_underline(), ansi_highlight(), ansi_normal()); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + printf("\nOptions:\n"); + r = table_print_or_warn(options); if (r < 0) return r; + printf("\nWith --exec, specify the command to invoke:\n" + " %s --exec call ADDRESS METHOD PARAMS -- CMDLINE…\n", + program_invocation_short_name); + printf("\nSee the %s for details.\n", link); return 0; } -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} +VERB_COMMON_HELP(help); -static int parse_argv(int argc, char *argv[]) { +static int parse_argv(int argc, char *argv[], char ***ret_args) { int r; assert(argc >= 0); @@ -256,6 +253,7 @@ static int parse_argv(int argc, char *argv[]) { strv_sort_uniq(arg_graceful); + *ret_args = option_parser_get_args(&state); return 1; } @@ -324,6 +322,8 @@ static void get_info_data_done(GetInfoData *d) { d->interfaces = strv_free(d->interfaces); } +VERB(verb_info, "info", "ADDRESS", 2, 2, 0, "Show service information"); +VERB(verb_info, "list-interfaces", "ADDRESS", 2, 2, 0, "List interfaces implemented by service"); static int verb_info(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; const char *url; @@ -417,6 +417,9 @@ typedef struct GetInterfaceDescriptionData { const char *description; } GetInterfaceDescriptionData; +VERB(verb_introspect, "introspect", "ADDRESS [INTERFACE…]", 2, VERB_ANY, 0, "Show interface definition"); +VERB(verb_introspect, "list-methods", "ADDRESS [INTERFACE…]", 2, VERB_ANY, 0, + "List methods implemented by services or specific interfaces"); static int verb_introspect(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; _cleanup_strv_free_ char **auto_interfaces = NULL; @@ -742,6 +745,7 @@ static int varlink_call_and_upgrade(const char *url, const char *method, sd_json return 0; } +VERB(verb_call, "call", "ADDRESS METHOD [PARAMS]", 3, VERB_ANY, 0, "Invoke method"); static int verb_call(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *jp = NULL; _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; @@ -1015,6 +1019,7 @@ static int verb_call(int argc, char *argv[], uintptr_t _data, void *userdata) { return 0; } +VERB(verb_validate_idl, "validate-idl", "[FILE]", 1, 2, 0, "Validate interface description"); static int verb_validate_idl(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_varlink_interface_freep) sd_varlink_interface *vi = NULL; _cleanup_free_ char *text = NULL; @@ -1064,6 +1069,7 @@ static int verb_validate_idl(int argc, char *argv[], uintptr_t _data, void *user return 0; } +VERB_NOARG(verb_list_registry, "list-registry", "Show list of services in the service registry"); static int verb_list_registry(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -1163,32 +1169,17 @@ static int verb_list_registry(int argc, char *argv[], uintptr_t _data, void *use return 0; } -static int varlinkctl_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "info", 2, 2, 0, verb_info }, - { "list-interfaces", 2, 2, 0, verb_info }, - { "introspect", 2, VERB_ANY, 0, verb_introspect }, - { "list-methods", 2, VERB_ANY, 0, verb_introspect }, - { "call", 3, VERB_ANY, 0, verb_call }, - { "list-registry", VERB_ANY, 1, 0, verb_list_registry }, - { "validate-idl", 1, 2, 0, verb_validate_idl }, - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { int r; log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; /* unnecessary initialization to appease gcc <= 13 */ + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return varlinkctl_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); From 386dc2175d0b32f7de9730e756e1bedcde5ad610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 4 Apr 2026 20:54:02 +0200 Subject: [PATCH 0803/1296] tmpfiles: convert to the new option parser The --image fallthrough into -E is replaced by duplicating the exclude_default_prefixes() call inline. Cosmetic differences in --help only. Co-developed-by: Claude Opus 4.6 --- src/tmpfiles/tmpfiles.c | 233 +++++++++++++++++----------------------- 1 file changed, 96 insertions(+), 137 deletions(-) diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 17f263790eda0..e869c3da69341 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -31,6 +30,7 @@ #include "extract-word.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "format-util.h" #include "fs-util.h" #include "glob-util.h" @@ -45,6 +45,7 @@ #include "mount-util.h" #include "mountpoint-util.h" #include "offline-passwd.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -209,7 +210,7 @@ static char **arg_include_prefixes = NULL; static char **arg_exclude_prefixes = NULL; static char *arg_root = NULL; static char *arg_image = NULL; -static char *arg_replace = NULL; +static const char *arg_replace = NULL; static ImagePolicy *arg_image_policy = NULL; #define MAX_DEPTH 256 @@ -4129,218 +4130,174 @@ static int exclude_default_prefixes(void) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *cmds = NULL, *opts = NULL; int r; r = terminal_urlify_man("systemd-tmpfiles", "8", &link); if (r < 0) return log_oom(); - printf("%1$s COMMAND [OPTIONS...] [CONFIGURATION FILE...]\n" - "\n%2$sCreate, delete, and clean up files and directories.%4$s\n" - "\n%3$sCommands:%4$s\n" - " --create Create and adjust files and directories\n" - " --clean Clean up files and directories\n" - " --remove Remove files and directories marked for removal\n" - " --purge Delete files and directories marked for creation in\n" - " specified configuration files (careful!)\n" - " --cat-config Show configuration files\n" - " --tldr Show non-comment parts of configuration files\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\n%3$sOptions:%4$s\n" - " --user Execute user configuration\n" - " --boot Execute actions only safe at boot\n" - " --graceful Quietly ignore unknown users or groups\n" - " --prefix=PATH Only apply rules with the specified prefix\n" - " --exclude-prefix=PATH Ignore rules with the specified prefix\n" - " -E Ignore rules prefixed with /dev, /proc, /run, /sys\n" - " --root=PATH Operate on an alternate filesystem root\n" - " --image=PATH Operate on disk image as filesystem root\n" - " --image-policy=POLICY Specify disk image dissection policy\n" - " --replace=PATH Treat arguments as replacement for PATH\n" - " --dry-run Just print what would be done\n" - " --inline Treat arguments as configuration lines\n" - " --no-pager Do not pipe output into a pager\n" - "\nSee the %5$s for details.\n", + r = option_parser_get_help_table(&cmds); + if (r < 0) + return r; + + r = option_parser_get_help_table_group("Options", &opts); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, cmds, opts); + + printf("%s COMMAND [OPTIONS...] [CONFIGURATION FILE...]\n" + "\n%sCreate, delete, and clean up files and directories.%s\n" + "\nCommands:\n", program_invocation_short_name, ansi_highlight(), - ansi_underline(), - ansi_normal(), - link); + ansi_normal()); - return 0; -} + r = table_print_or_warn(cmds); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_CAT_CONFIG, - ARG_TLDR, - ARG_USER, - ARG_CREATE, - ARG_CLEAN, - ARG_REMOVE, - ARG_PURGE, - ARG_BOOT, - ARG_GRACEFUL, - ARG_PREFIX, - ARG_EXCLUDE_PREFIX, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_REPLACE, - ARG_DRY_RUN, - ARG_INLINE, - ARG_NO_PAGER, - }; + printf("\nOptions:\n"); - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "user", no_argument, NULL, ARG_USER }, - { "version", no_argument, NULL, ARG_VERSION }, - { "cat-config", no_argument, NULL, ARG_CAT_CONFIG }, - { "tldr", no_argument, NULL, ARG_TLDR }, - { "create", no_argument, NULL, ARG_CREATE }, - { "clean", no_argument, NULL, ARG_CLEAN }, - { "remove", no_argument, NULL, ARG_REMOVE }, - { "purge", no_argument, NULL, ARG_PURGE }, - { "boot", no_argument, NULL, ARG_BOOT }, - { "graceful", no_argument, NULL, ARG_GRACEFUL }, - { "prefix", required_argument, NULL, ARG_PREFIX }, - { "exclude-prefix", required_argument, NULL, ARG_EXCLUDE_PREFIX }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "replace", required_argument, NULL, ARG_REPLACE }, - { "dry-run", no_argument, NULL, ARG_DRY_RUN }, - { "inline", no_argument, NULL, ARG_INLINE }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - {} - }; + r = table_print_or_warn(opts); + if (r < 0) + return r; - int c, r; + printf("\nSee the %s for details.\n", link); + return 0; +} + +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hE", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - return help(); - - case ARG_VERSION: - return version(); + OPTION_LONG("create", NULL, "Create and adjust files and directories"): + arg_operation |= OPERATION_CREATE; + break; - case ARG_CAT_CONFIG: - arg_cat_flags = CAT_CONFIG_ON; + OPTION_LONG("clean", NULL, "Clean up files and directories"): + arg_operation |= OPERATION_CLEAN; break; - case ARG_TLDR: - arg_cat_flags = CAT_TLDR; + OPTION_LONG("remove", NULL, "Remove files and directories marked for removal"): + arg_operation |= OPERATION_REMOVE; break; - case ARG_USER: - arg_runtime_scope = RUNTIME_SCOPE_USER; + OPTION_LONG("purge", NULL, + "Delete files and directories marked for creation in" + " specified configuration files (careful!)"): + arg_operation |= OPERATION_PURGE; break; - case ARG_CREATE: - arg_operation |= OPERATION_CREATE; + OPTION_COMMON_CAT_CONFIG: + arg_cat_flags = CAT_CONFIG_ON; break; - case ARG_CLEAN: - arg_operation |= OPERATION_CLEAN; + OPTION_COMMON_TLDR: + arg_cat_flags = CAT_TLDR; break; - case ARG_REMOVE: - arg_operation |= OPERATION_REMOVE; + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION_GROUP("Options"): break; - case ARG_BOOT: - arg_boot = true; + OPTION_LONG("user", NULL, "Execute user configuration"): + arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_PURGE: - arg_operation |= OPERATION_PURGE; + OPTION_LONG("boot", NULL, "Execute actions only safe at boot"): + arg_boot = true; break; - case ARG_GRACEFUL: + OPTION_LONG("graceful", NULL, "Quietly ignore unknown users or groups"): arg_graceful = true; break; - case ARG_PREFIX: - if (strv_extend(&arg_include_prefixes, optarg) < 0) + OPTION_LONG("prefix", "PATH", "Only apply rules with the specified prefix"): + if (strv_extend(&arg_include_prefixes, arg) < 0) return log_oom(); break; - case ARG_EXCLUDE_PREFIX: - if (strv_extend(&arg_exclude_prefixes, optarg) < 0) + OPTION_LONG("exclude-prefix", "PATH", "Ignore rules with the specified prefix"): + if (strv_extend(&arg_exclude_prefixes, arg) < 0) return log_oom(); break; - case ARG_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root); + OPTION_SHORT('E', NULL, "Ignore rules prefixed with /dev, /proc, /run, /sys"): + r = exclude_default_prefixes(); if (r < 0) return r; break; - case ARG_IMAGE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image); + OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_root); if (r < 0) return r; + break; - /* Imply -E here since it makes little sense to create files persistently in the /run mountpoint of a disk image */ - _fallthrough_; + OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image); + if (r < 0) + return r; - case 'E': + /* Imply -E here since it makes little sense to create files persistently in the /run mountpoint of a disk image */ r = exclude_default_prefixes(); if (r < 0) return r; - break; - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); + OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): + r = parse_image_policy_argument(arg, &arg_image_policy); if (r < 0) return r; break; - case ARG_REPLACE: - if (!path_is_absolute(optarg)) + OPTION_LONG("replace", "PATH", "Treat arguments as replacement for PATH"): + if (!path_is_absolute(arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The argument to --replace= must be an absolute path."); - if (!endswith(optarg, ".conf")) + if (!endswith(arg, ".conf")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The argument to --replace= must have the extension '.conf'."); - arg_replace = optarg; + arg_replace = arg; break; - case ARG_DRY_RUN: + OPTION_LONG("dry-run", NULL, "Just print what would be done"): arg_dry_run = true; break; - case ARG_INLINE: + OPTION_LONG("inline", NULL, "Treat arguments as configuration lines"): arg_inline = true; break; - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + char **args = option_parser_get_args(&state); + size_t n_args = option_parser_get_n_args(&state); + if (arg_operation == 0 && arg_cat_flags == CAT_CONFIG_OFF) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "You need to specify at least one of --clean, --create, --remove, or --purge."); - if (FLAGS_SET(arg_operation, OPERATION_PURGE) && optind >= argc) + if (FLAGS_SET(arg_operation, OPERATION_PURGE) && n_args == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Refusing --purge without specification of a configuration file."); @@ -4352,7 +4309,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --inline is not supported with --cat-config/--tldr."); - if (arg_replace && optind >= argc) + if (arg_replace && n_args == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "When --replace= is given, some configuration items must be specified."); @@ -4364,6 +4321,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported."); + *ret_args = args; return 1; } @@ -4564,7 +4522,8 @@ static int run(int argc, char *argv[]) { } phase; int r; - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -4619,7 +4578,7 @@ static int run(int argc, char *argv[]) { } if (arg_cat_flags != CAT_CONFIG_OFF) - return cat_config(config_dirs, argv + optind); + return cat_config(config_dirs, args); if (should_bypass("SYSTEMD_TMPFILES")) return 0; @@ -4663,10 +4622,10 @@ static int run(int argc, char *argv[]) { * insert the positional arguments at the specified place. Otherwise, if command line arguments are * specified, execute just them, and finally, without --replace= or any positional arguments, just * read configuration and execute it. */ - if (arg_replace || optind >= argc) - r = read_config_files(&c, config_dirs, argv + optind, &invalid_config); + if (arg_replace || strv_isempty(args)) + r = read_config_files(&c, config_dirs, args, &invalid_config); else - r = parse_arguments(&c, config_dirs, argv + optind, &invalid_config); + r = parse_arguments(&c, config_dirs, args, &invalid_config); if (r < 0) return r; From e106ebbcab4626cf2c9b260e7c8a4e647c4914b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 4 Apr 2026 21:08:51 +0200 Subject: [PATCH 0804/1296] sysusers: convert to the new option parser Cosmetic differences in --help only. Co-developed-by: Claude Opus 4.6 --- src/sysusers/sysusers.c | 151 ++++++++++++++++++---------------------- 1 file changed, 67 insertions(+), 84 deletions(-) diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index d1570eda56fec..015043a0a4dcd 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -16,6 +15,7 @@ #include "extract-word.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "format-util.h" #include "fs-util.h" #include "hashmap.h" @@ -28,6 +28,7 @@ #include "loop-util.h" #include "main-func.h" #include "mount-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "path-util.h" @@ -2048,138 +2049,118 @@ static int cat_config(void) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *cmds = NULL, *opts = NULL; int r; r = terminal_urlify_man("systemd-sysusers.service", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] [CONFIGURATION FILE...]\n" - "\n%2$sCreates system user and group accounts.%4$s\n" - "\n%3$sCommands:%4$s\n" - " --cat-config Show configuration files\n" - " --tldr Show non-comment parts of configuration\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\n%3$sOptions:%4$s\n" - " --root=PATH Operate on an alternate filesystem root\n" - " --image=PATH Operate on disk image as filesystem root\n" - " --image-policy=POLICY Specify disk image dissection policy\n" - " --replace=PATH Treat arguments as replacement for PATH\n" - " --dry-run Just print what would be done\n" - " --inline Treat arguments as configuration lines\n" - " --no-pager Do not pipe output into a pager\n" - "\nSee the %5$s for details.\n", + r = option_parser_get_help_table(&cmds); + if (r < 0) + return r; + + r = option_parser_get_help_table_group("Options", &opts); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, cmds, opts); + + printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n" + "\n%sCreates system user and group accounts.%s\n" + "\nCommands:\n", program_invocation_short_name, ansi_highlight(), - ansi_underline(), - ansi_normal(), - link); + ansi_normal()); - return 0; -} + r = table_print_or_warn(cmds); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_CAT_CONFIG, - ARG_TLDR, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_REPLACE, - ARG_DRY_RUN, - ARG_INLINE, - ARG_NO_PAGER, - }; + printf("\nOptions:\n"); - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "cat-config", no_argument, NULL, ARG_CAT_CONFIG }, - { "tldr", no_argument, NULL, ARG_TLDR }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "replace", required_argument, NULL, ARG_REPLACE }, - { "dry-run", no_argument, NULL, ARG_DRY_RUN }, - { "inline", no_argument, NULL, ARG_INLINE }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - {} - }; + r = table_print_or_warn(opts); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); + return 0; +} - int c, r; +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - return help(); - - case ARG_VERSION: - return version(); - - case ARG_CAT_CONFIG: + OPTION_COMMON_CAT_CONFIG: arg_cat_flags = CAT_CONFIG_ON; break; - case ARG_TLDR: + OPTION_COMMON_TLDR: arg_cat_flags = CAT_TLDR; break; - case ARG_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root); + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION_GROUP("Options"): + break; + + OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_root); if (r < 0) return r; break; - case ARG_IMAGE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image); + OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image); if (r < 0) return r; break; - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); + OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): + r = parse_image_policy_argument(arg, &arg_image_policy); if (r < 0) return r; break; - case ARG_REPLACE: - if (!path_is_absolute(optarg)) + OPTION_LONG("replace", "PATH", "Treat arguments as replacement for PATH"): + if (!path_is_absolute(arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The argument to --replace= must be an absolute path."); - if (!endswith(optarg, ".conf")) + if (!endswith(arg, ".conf")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The argument to --replace= must have the extension '.conf'."); - arg_replace = optarg; + arg_replace = arg; break; - case ARG_DRY_RUN: + OPTION_LONG("dry-run", NULL, "Just print what would be done"): arg_dry_run = true; break; - case ARG_INLINE: + OPTION_LONG("inline", NULL, "Treat arguments as configuration lines"): arg_inline = true; break; - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + char **args = option_parser_get_args(&state); + size_t n_args = option_parser_get_n_args(&state); + if (arg_replace && arg_cat_flags != CAT_CONFIG_OFF) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --replace= is not supported with --cat-config/--tldr."); @@ -2188,7 +2169,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --inline is not supported with --cat-config/--tldr."); - if (arg_replace && optind >= argc) + if (arg_replace && n_args == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "When --replace= is given, some configuration items must be specified."); @@ -2196,6 +2177,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Use either --root= or --image=, the combination of both is not supported."); + *ret_args = args; return 1; } @@ -2281,7 +2263,8 @@ static int run(int argc, char *argv[]) { Item *i; int r; - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -2331,10 +2314,10 @@ static int run(int argc, char *argv[]) { * insert the positional arguments at the specified place. Otherwise, if command line arguments are * specified, execute just them, and finally, without --replace= or any positional arguments, just * read configuration and execute it. */ - if (arg_replace || optind >= argc) - r = read_config_files(&c, argv + optind); + if (arg_replace || strv_isempty(args)) + r = read_config_files(&c, args); else - r = parse_arguments(&c, argv + optind); + r = parse_arguments(&c, args); if (r < 0) return r; From 35d7a0f04796b3948dfb63cccc5fdeb632be4009 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 4 Apr 2026 21:15:54 +0200 Subject: [PATCH 0805/1296] test-offline-passwd: convert to the new option parser Co-developed-by: Claude Opus 4.6 --- src/tmpfiles/test-offline-passwd.c | 41 +++++++++++++----------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/src/tmpfiles/test-offline-passwd.c b/src/tmpfiles/test-offline-passwd.c index 7be29ff798556..9695ba9b63c2c 100644 --- a/src/tmpfiles/test-offline-passwd.c +++ b/src/tmpfiles/test-offline-passwd.c @@ -1,13 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "format-util.h" #include "hashmap.h" #include "offline-passwd.h" +#include "options.h" +#include "strv.h" #include "tests.h" -static char *arg_root = NULL; +static const char *arg_root = NULL; static void test_resolve_one(const char *name) { bool relaxed = name || arg_root; @@ -39,30 +39,22 @@ static void test_resolve_one(const char *name) { assert_se(relaxed || r == 0); } -static int parse_argv(int argc, char *argv[]) { - static const struct option options[] = { - { "root", required_argument, NULL, 'r' }, - {} - }; - - int c; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert_se(argc >= 0); assert_se(argv); - while ((c = getopt_long(argc, argv, "r:", options, NULL)) >= 0) - switch (c) { - case 'r': - arg_root = optarg; - break; + OptionParser state = { argc, argv }; + const char *arg; - case '?': - return -EINVAL; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + switch (c) { - default: - assert_not_reached(); + OPTION('r', "root", "PATH", "Operate on an alternate filesystem root"): + arg_root = arg; + break; } + *ret_args = option_parser_get_args(&state); return 0; } @@ -71,15 +63,16 @@ int main(int argc, char **argv) { test_setup_logging(LOG_DEBUG); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r < 0) return r; - if (optind >= argc) + if (strv_isempty(args)) test_resolve_one(NULL); else - while (optind < argc) - test_resolve_one(argv[optind++]); + STRV_FOREACH(a, args) + test_resolve_one(*a); return 0; } From 3ed2abf2877a7519063cd0cd12f2501d927f08f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 4 Apr 2026 21:26:01 +0200 Subject: [PATCH 0806/1296] tpm2-clear: convert to the new option parser --help is identical except for whitespace changes. Co-developed-by: Claude Opus 4.6 --- src/tpm2-setup/tpm2-clear.c | 61 +++++++++++++++---------------------- 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/src/tpm2-setup/tpm2-clear.c b/src/tpm2-setup/tpm2-clear.c index 0800a90747961..e6a063f2a8d43 100644 --- a/src/tpm2-setup/tpm2-clear.c +++ b/src/tpm2-setup/tpm2-clear.c @@ -1,15 +1,15 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-messages.h" #include "alloc-util.h" #include "build.h" #include "env-util.h" #include "fileio.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pretty-print.h" #include "proc-cmdline.h" #include "tpm2-util.h" @@ -18,68 +18,55 @@ static bool arg_graceful = false; static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-tpm2-clear", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...]\n" - "\n%5$sRequest clearing of the TPM2 from PC firmware.%6$s\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --graceful Exit gracefully if no TPM2 device is found\n" - "\nSee the %2$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n" + "\n%sRequest clearing of the TPM2 from PC firmware.%s\n" + "\n%sOptions:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_GRACEFUL, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "graceful", no_argument, NULL, ARG_GRACEFUL }, - {} - }; - - int c; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + + FOREACH_OPTION(&state, c, /* ret_a= */ NULL, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_GRACEFUL: + OPTION_LONG("graceful", NULL, "Exit gracefully if no TPM2 device is found"): arg_graceful = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind != argc) + if (option_parser_get_n_args(&state) != 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program expects no arguments."); return 1; From 358827d09e0e1de5c871ff7ba4bfce70dde99126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 4 Apr 2026 21:29:38 +0200 Subject: [PATCH 0807/1296] tpm2-setup: convert to the new option parser --help is identical except for whitespace changes. Co-developed-by: Claude Opus 4.6 --- src/tpm2-setup/tpm2-setup.c | 85 +++++++++++++++---------------------- 1 file changed, 34 insertions(+), 51 deletions(-) diff --git a/src/tpm2-setup/tpm2-setup.c b/src/tpm2-setup/tpm2-setup.c index 92a4bfa12a615..74e13219f7a27 100644 --- a/src/tpm2-setup/tpm2-setup.c +++ b/src/tpm2-setup/tpm2-setup.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -13,11 +12,13 @@ #include "errno-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "fs-util.h" #include "hexdecoct.h" #include "log.h" #include "main-func.h" #include "mkdir.h" +#include "options.h" #include "parse-util.h" #include "pretty-print.h" #include "set.h" @@ -38,94 +39,76 @@ STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); #define TPM2_SRK_TPM2B_PUBLIC_PERSISTENT_PATH "/var/lib/systemd/tpm2-srk-public-key.tpm2b_public" #define TPM2_SRK_TPM2B_PUBLIC_RUNTIME_PATH "/run/systemd/tpm2-srk-public-key.tpm2b_public" -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-tpm2-setup", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...]\n" - "\n%5$sSet up the TPM2 Storage Root Key (SRK), and initialize NvPCRs.%6$s\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --tpm2-device=PATH\n" - " Pick TPM2 device\n" - " --early=BOOL Store SRK public key in /run/ rather than /var/lib/\n" - " --graceful Exit gracefully if no TPM2 device is found\n" - "\nSee the %2$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n" + "\n%sSet up the TPM2 Storage Root Key (SRK), and initialize NvPCRs.%s\n" + "\n%sOptions:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_TPM2_DEVICE, - ARG_EARLY, - ARG_GRACEFUL, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, - { "early", required_argument, NULL, ARG_EARLY }, - { "graceful", no_argument, NULL, ARG_GRACEFUL }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - return help(0, NULL, NULL); + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_TPM2_DEVICE: - if (streq(optarg, "list")) + OPTION_LONG("tpm2-device", "PATH", "Pick TPM2 device"): + if (streq(arg, "list")) return tpm2_list_devices(/* legend= */ true, /* quiet= */ false); - if (free_and_strdup(&arg_tpm2_device, streq(optarg, "auto") ? NULL : optarg) < 0) + if (free_and_strdup(&arg_tpm2_device, streq(arg, "auto") ? NULL : arg) < 0) return log_oom(); - break; - case ARG_EARLY: - r = parse_boolean(optarg); + OPTION_LONG("early", "BOOL", "Store SRK public key in /run/ rather than /var/lib/"): + r = parse_boolean(arg); if (r < 0) - return log_error_errno(r, "Failed to parse --early= argument: %s", optarg); + return log_error_errno(r, "Failed to parse --early= argument: %s", arg); arg_early = r; break; - case ARG_GRACEFUL: + OPTION_LONG("graceful", NULL, "Exit gracefully if no TPM2 device is found"): arg_graceful = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind != argc) + if (option_parser_get_n_args(&state) != 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program expects no argument."); return 1; From 3ec64fa2bade54263872b9dde4e4ba5ed66d7a5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 4 Apr 2026 21:52:46 +0200 Subject: [PATCH 0808/1296] updatectl: convert to the new option and verb parsers Cosmetic differences in --help only. Co-developed-by: Claude Opus 4.6 --- src/sysupdate/updatectl.c | 167 ++++++++++++++++++-------------------- 1 file changed, 81 insertions(+), 86 deletions(-) diff --git a/src/sysupdate/updatectl.c b/src/sysupdate/updatectl.c index 636a3f064b94f..c6e5c33fdfe48 100644 --- a/src/sysupdate/updatectl.c +++ b/src/sysupdate/updatectl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-bus.h" @@ -19,6 +18,7 @@ #include "hashmap.h" #include "json-util.h" #include "main-func.h" +#include "options.h" #include "pager.h" #include "polkit-agent.h" #include "pretty-print.h" @@ -644,6 +644,8 @@ static int describe(sd_bus *bus, const char *target_path, const char *version) { return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, arg_legend); } +VERB(verb_list, "list", "[TARGET[@VERSION]]", VERB_ANY, 2, VERB_DEFAULT|VERB_ONLINE_ONLY, + "List available targets and versions"); static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); _cleanup_free_ char *target_path = NULL, *version = NULL; @@ -755,6 +757,8 @@ static int check_finished(sd_bus_message *reply, void *userdata, sd_bus_error *r return 0; } +VERB(verb_check, "check", "[TARGET...]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Check for updates"); static int verb_check(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); _cleanup_(table_unrefp) Table *table = NULL; @@ -1291,6 +1295,8 @@ static int do_update(sd_bus *bus, char **targets) { return did_anything ? 1 : 0; } +VERB(verb_update, "update", "[TARGET[@VERSION]...]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Install updates"); static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); _cleanup_strv_free_ char **targets = NULL; @@ -1344,6 +1350,8 @@ static int do_vacuum(sd_bus *bus, const char *target, const char *path) { return count + disabled > 0 ? 1 : 0; } +VERB(verb_vacuum, "vacuum", "[TARGET...]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Clean up old updates"); static int verb_vacuum(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); _cleanup_strv_free_ char **targets = NULL, **target_paths = NULL; @@ -1488,6 +1496,8 @@ static int list_features(sd_bus *bus) { return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, arg_legend); } +VERB(verb_features, "features", "[FEATURE]", VERB_ANY, 2, VERB_ONLINE_ONLY, + "List and inspect optional features on host OS"); static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); _cleanup_(table_unrefp) Table *table = NULL; @@ -1537,6 +1547,10 @@ static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, false); } +VERB(verb_enable, "enable", "FEATURE...", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Enable optional feature on host OS"); +VERB(verb_enable, "disable", "FEATURE...", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Disable optional feature on host OS"); static int verb_enable(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); bool did_anything = false, enable; @@ -1618,111 +1632,102 @@ static int verb_enable(int argc, char *argv[], uintptr_t _data, void *userdata) static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL, *verbs2 = NULL, *options = NULL; int r; r = terminal_urlify_man("updatectl", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] [VERSION]\n" - "\n%5$sManage system updates.%6$s\n" - "\n%3$sCommands:%4$s\n" - " list [TARGET[@VERSION]] List available targets and versions\n" - " check [TARGET...] Check for updates\n" - " update [TARGET[@VERSION]...] Install updates\n" - " vacuum [TARGET...] Clean up old updates\n" - " features [FEATURE] List and inspect optional features on host OS\n" - " enable FEATURE... Enable optional feature on host OS\n" - " disable FEATURE... Disable optional feature on host OS\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\n%3$sOptions:%4$s\n" - " --reboot Reboot after updating to newer version\n" - " --offline Do not fetch metadata from the network\n" - " --now Download/delete resources immediately\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - "\nSee the %2$s for details.\n" - , program_invocation_short_name - , link - , ansi_underline(), ansi_normal() - , ansi_highlight(), ansi_normal() - ); + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; - return 0; -} + r = option_parser_get_help_table_group("Verbs", &verbs2); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { + r = option_parser_get_help_table(&options); + if (r < 0) + return r; - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_REBOOT, - ARG_OFFLINE, - ARG_NOW, - }; + (void) table_sync_column_widths(0, verbs, verbs2, options); - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "host", required_argument, NULL, 'H' }, - { "reboot", no_argument, NULL, ARG_REBOOT }, - { "offline", no_argument, NULL, ARG_OFFLINE }, - { "now", no_argument, NULL, ARG_NOW }, - {} - }; + printf("%s [OPTIONS...] [VERSION]\n" + "\n%sManage system updates.%s\n" + "\n%sCommands:%s\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal(), + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(verbs); + if (r < 0) + return r; + r = table_print_or_warn(verbs2); + if (r < 0) + return r; + + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); + return 0; +} - int c; +VERB_COMMON_HELP_HIDDEN(help); +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hH:", options, NULL)) >= 0) { - switch (c) { + OptionParser state = { argc, argv }; + const char *arg; - case 'h': - return help(); + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + switch (c) { - case ARG_VERSION: - return version(); + OPTION_LONG("reboot", NULL, "Reboot after updating to newer version"): + arg_reboot = true; + break; - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; + OPTION_LONG("offline", NULL, "Do not fetch metadata from the network"): + arg_offline = true; break; - case ARG_NO_LEGEND: - arg_legend = false; + OPTION_LONG("now", NULL, "Download/delete resources immediately"): + arg_now = true; break; - case 'H': + OPTION_COMMON_HOST: arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + arg_host = arg; break; - case ARG_REBOOT: - arg_reboot = true; + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; break; - case ARG_OFFLINE: - arg_offline = true; + OPTION_COMMON_NO_LEGEND: + arg_legend = false; break; - case ARG_NOW: - arg_now = true; - break; + OPTION_GROUP("Verbs"): {} - case '?': - return -EINVAL; + OPTION_COMMON_HELP: + return help(); - default: - assert_not_reached(); + OPTION_COMMON_VERSION: + return version(); } - } + *ret_args = option_parser_get_args(&state); return 1; } @@ -1730,23 +1735,13 @@ static int run(int argc, char *argv[]) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; - static const Verb verbs[] = { - { "list", VERB_ANY, 2, VERB_DEFAULT|VERB_ONLINE_ONLY, verb_list }, - { "check", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_check }, - { "update", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_update }, - { "vacuum", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_vacuum }, - { "features", VERB_ANY, 2, VERB_ONLINE_ONLY, verb_features }, - { "enable", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_enable }, - { "disable", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_enable }, - {} - }; - setlocale(LC_ALL, ""); log_setup(); (void) signal(SIGWINCH, columns_lines_cache_reset); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -1759,7 +1754,7 @@ static int run(int argc, char *argv[]) { (void) sd_bus_set_allow_interactive_authorization(bus, true); - return dispatch_verb(argc, argv, verbs, bus); + return dispatch_verb_with_args(args, bus); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); From c074adda05e71d622bcef55ec0224b13f1507913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sun, 5 Apr 2026 17:38:46 +0200 Subject: [PATCH 0809/1296] battery-check: convert to the new option parser --help is identical except for whitespace changes. Co-developed-by: Claude Opus 4.6 --- src/battery-check/battery-check.c | 51 ++++++++++++------------------- 1 file changed, 20 insertions(+), 31 deletions(-) diff --git a/src/battery-check/battery-check.c b/src/battery-check/battery-check.c index 43ff0e53e0386..706a7d869c53a 100644 --- a/src/battery-check/battery-check.c +++ b/src/battery-check/battery-check.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include /* IWYU pragma: keep */ #include "sd-messages.h" @@ -9,9 +8,11 @@ #include "battery-util.h" #include "build.h" #include "fd-util.h" +#include "format-table.h" #include "glyph-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "plymouth-util.h" #include "pretty-print.h" #include "proc-cmdline.h" @@ -27,22 +28,28 @@ static bool arg_doit = true; static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-battery-check", "8", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s\n\n" - "%sCheck battery level to see whether there's enough charge.%s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\nSee the %s for details.\n", + "%sCheck battery level to see whether there's enough charge.%s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } @@ -70,41 +77,23 @@ static int plymouth_send_message(const char *mode, const char *message) { return 0; } -static int parse_argv(int argc, char * argv[]) { - - enum { - ARG_VERSION = 0x100, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {} - }; - - int c; - +static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + FOREACH_OPTION(&state, c, /* ret_a= */ NULL, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind < argc) + if (option_parser_get_n_args(&state) != 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s takes no argument.", program_invocation_short_name); From 88aa9672b916576fdabce39f722030a5282d821e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sun, 5 Apr 2026 17:41:12 +0200 Subject: [PATCH 0810/1296] boot-check-no-failures: convert to the new option parser --help is identical except for whitespace changes. Co-developed-by: Claude Opus 4.6 --- src/bless-boot/boot-check-no-failures.c | 51 ++++++++++--------------- 1 file changed, 20 insertions(+), 31 deletions(-) diff --git a/src/bless-boot/boot-check-no-failures.c b/src/bless-boot/boot-check-no-failures.c index b3018924748f1..bea5e5791665e 100644 --- a/src/bless-boot/boot-check-no-failures.c +++ b/src/bless-boot/boot-check-no-failures.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-bus.h" @@ -8,63 +7,53 @@ #include "alloc-util.h" #include "build.h" #include "bus-error.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pretty-print.h" static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-boot-check-no-failures.service", "8", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...]\n" - "\n%sVerify system operational state.%s\n\n" - " -h --help Show this help\n" - " --version Print version\n" - "\nSee the %s for details.\n", + "\n%sVerify system operational state.%s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_PATH = 0x100, - ARG_VERSION, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {} - }; - - int c; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + + FOREACH_OPTION(&state, c, /* ret_a= */ NULL, /* on_error= */ return c) switch (c) { - case 'h': - help(); - return 0; + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } return 1; From 3d67b5a0842ba834835ee219f63de8ffe0965f5c Mon Sep 17 00:00:00 2001 From: Jonas Rebmann Date: Tue, 7 Apr 2026 11:03:48 +0200 Subject: [PATCH 0811/1296] test-specifier: update comment to moved file src/partition/repart.c was renamed to src/repart/repart.c in commit 211d2f972dd1 ("Rename src/partition to src/repart"), update the comment accordingly. Signed-off-by: Jonas Rebmann --- src/test/test-specifier.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/test-specifier.c b/src/test/test-specifier.c index 850f961bfedcf..6c6c00f53f08a 100644 --- a/src/test/test-specifier.c +++ b/src/test/test-specifier.c @@ -156,7 +156,7 @@ TEST(specifiers_assorted) { const sd_id128_t id = SD_ID128_ALLF; const uint64_t llu = UINT64_MAX; const Specifier table[] = { - /* Used in src/partition/repart.c */ + /* Used in src/repart/repart.c */ { 'a', specifier_uuid, &id }, { 'b', specifier_uint64, &llu }, {} From 07745c222c8a323c4c9c09407bab70aa2a3ec2f8 Mon Sep 17 00:00:00 2001 From: Jonas Rebmann Date: Tue, 7 Apr 2026 11:06:25 +0200 Subject: [PATCH 0812/1296] repart: Do not refer to SizeMinBytes= as SizeMin= No SizeMin= option exists for repart.d; it seems that SizeMinBytes= was intended. Update all references accordingly. Signed-off-by: Jonas Rebmann --- man/repart.d.xml | 2 +- src/repart/repart.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/man/repart.d.xml b/man/repart.d.xml index f3ed246b6ec47..217692d813551 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -402,7 +402,7 @@ be empty. If this option is used, the size allocation algorithm is slightly altered: the partition is created at least as big as required to fit the data in, i.e. the data size is an additional minimum size value taken into consideration for the allocation algorithm, similar to and in addition to the - SizeMin= value configured above. + SizeMinBytes= value configured above. This option has no effect if the partition it is declared for already exists, i.e. existing data is never overwritten. Note that the data is copied in before the partition table is updated, diff --git a/src/repart/repart.c b/src/repart/repart.c index 0417d4133a9cd..e9cd0d85699a7 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -1116,7 +1116,7 @@ static uint64_t partition_min_size(const Context *context, const Partition *p) { * exists the current size is what we really need. If it doesn't exist yet refuse to allocate less * than 4K. * - * DEFAULT_MIN_SIZE is the default SizeMin= we configure if nothing else is specified. */ + * DEFAULT_MIN_SIZE is the default SizeMinBytes= we configure if nothing else is specified. */ if (PARTITION_IS_FOREIGN(p)) { /* Don't allow changing size of partitions not managed by us */ From 6e76281b2f4ca93fcb00fd08ba1a7584c087fe5d Mon Sep 17 00:00:00 2001 From: noxiouz Date: Mon, 6 Apr 2026 11:22:58 +0100 Subject: [PATCH 0813/1296] journald-native: fix field-count limit off-by-one Reject entries once the configured maximum field count is reached. The previous check used n > ENTRY_FIELD_COUNT_MAX before appending a new field, which let one extra field through in boundary cases. Switch the check to n >= ENTRY_FIELD_COUNT_MAX so an entry at the limit is rejected before adding another property. Co-developed-by: Codex (GPT-5) --- src/journal/journald-native.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/journal/journald-native.c b/src/journal/journald-native.c index 1e2a872c6ed59..fade424ebcdef 100644 --- a/src/journal/journald-native.c +++ b/src/journal/journald-native.c @@ -146,7 +146,7 @@ static int manager_process_entry( } /* A property follows */ - if (n > ENTRY_FIELD_COUNT_MAX) { + if (n >= ENTRY_FIELD_COUNT_MAX) { log_debug("Received an entry that has more than " STRINGIFY(ENTRY_FIELD_COUNT_MAX) " fields, ignoring entry."); goto finish; } From e4f535fdcae0cf715880f6f4307f5d0e58d93020 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 7 Apr 2026 08:59:03 +0000 Subject: [PATCH 0814/1296] vmspawn: Always enable CXL on supported architectures Drop the --cxl= option and unconditionally enable cxl=on the QEMU machine type whenever the host architecture supports it (x86_64 and aarch64). The flag was only added for testing parity with mkosi's CXL= setting and there is no reason to leave it as an opt-in toggle: with no pxb-cxl device or cxl-fmw window attached, enabling it on the machine only reserves a small MMIO region and emits an empty CEDT, so the cost is negligible while removing one knob users would otherwise have to flip explicitly to exercise the CXL code paths in QEMU. --- man/systemd-vmspawn.xml | 12 ------------ shell-completion/bash/systemd-vmspawn | 2 +- src/vmspawn/vmspawn.c | 15 +-------------- 3 files changed, 2 insertions(+), 27 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 0a61d781d39d0..92872233aee54 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -187,18 +187,6 @@ - - - - Controls whether to enable CXL (Compute Express Link) support in the virtual - machine. CXL is a high-speed interconnect standard that allows CPUs to access memory attached to - devices such as accelerators and memory expanders, enabling flexible memory pooling and expansion - beyond what is physically installed on the motherboard. Only supported on x86_64 and aarch64 - architectures. - - - - diff --git a/shell-completion/bash/systemd-vmspawn b/shell-completion/bash/systemd-vmspawn index 1ca45091a7ef4..b2b3f4f5a8ba3 100644 --- a/shell-completion/bash/systemd-vmspawn +++ b/shell-completion/bash/systemd-vmspawn @@ -31,7 +31,7 @@ _systemd_vmspawn() { local -A OPTS=( [STANDALONE]='-h --help --version -q --quiet --no-pager -n --network-tap --network-user-mode --user --system -x --ephemeral' [PATH]='-D --directory -i --image --linux --initrd --extra-drive --forward-journal --efi-nvram-template' - [BOOL]='--kvm --cxl --vsock --tpm --discard-disk --pass-ssh-key' + [BOOL]='--kvm --vsock --tpm --discard-disk --pass-ssh-key' [TRISTATE]='--register --secure-boot' [FIRMWARE]='--firmware' [FIRMWARE_FEATURES]='--firmware-features' diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index c1ae51261d9a2..1b3558b47e75e 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -133,7 +133,6 @@ static uint64_t arg_ram = UINT64_C(2) * U64_GB; static uint64_t arg_ram_max = 0; static unsigned arg_ram_slots = 0; static int arg_kvm = -1; -static bool arg_cxl = false; static int arg_vsock = -1; static unsigned arg_vsock_cid = VMADDR_CID_ANY; static int arg_tpm = -1; @@ -236,7 +235,6 @@ static int help(void) { " Configure guest's RAM size (and max/slots for\n" " hotplug)\n" " --kvm=BOOL Enable use of KVM\n" - " --cxl=BOOL Enable use of CXL\n" " --vsock=BOOL Override autodetection of VSOCK support\n" " --vsock-cid=CID Specify the CID to use for the guest's VSOCK support\n" " --tpm=BOOL Enable use of a virtual TPM\n" @@ -382,7 +380,6 @@ static int parse_argv(int argc, char *argv[]) { ARG_CPUS, ARG_RAM, ARG_KVM, - ARG_CXL, ARG_VSOCK, ARG_VSOCK_CID, ARG_TPM, @@ -441,7 +438,6 @@ static int parse_argv(int argc, char *argv[]) { { "ram", required_argument, NULL, ARG_RAM }, { "qemu-mem", required_argument, NULL, ARG_RAM }, /* Compat alias */ { "kvm", required_argument, NULL, ARG_KVM }, - { "cxl", required_argument, NULL, ARG_CXL }, { "qemu-kvm", required_argument, NULL, ARG_KVM }, /* Compat alias */ { "vsock", required_argument, NULL, ARG_VSOCK }, { "qemu-vsock", required_argument, NULL, ARG_VSOCK }, /* Compat alias */ @@ -573,15 +569,6 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - case ARG_CXL: - r = parse_boolean_argument("--cxl=", optarg, &arg_cxl); - if (r < 0) - return r; - if (arg_cxl && !ARCHITECTURE_SUPPORTS_CXL) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "CXL not supported on %s.", architecture_to_string(native_architecture())); - break; - case ARG_VSOCK: r = parse_tristate_argument_with_auto("--vsock=", optarg, &arg_vsock); if (r < 0) @@ -2371,7 +2358,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return r; } - if (arg_cxl) { + if (ARCHITECTURE_SUPPORTS_CXL) { r = qemu_config_key(config_file, "cxl", "on"); if (r < 0) return r; From 0c510671f8aec65936fe7d03c9b74ddeb74bedc6 Mon Sep 17 00:00:00 2001 From: "LevitatingBusinessMan (Rein Fernhout)" Date: Mon, 6 Apr 2026 03:14:24 +0200 Subject: [PATCH 0815/1296] oci-registry: use overrideRegistry in fedora default In registry.fedora.oci-registry use overrideRegistry instead of defaultRegistry. fixes #41518 --- src/import/oci-registry/registry.fedora.oci-registry | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/import/oci-registry/registry.fedora.oci-registry b/src/import/oci-registry/registry.fedora.oci-registry index e416bf2853523..e7d2ccdb2a062 100644 --- a/src/import/oci-registry/registry.fedora.oci-registry +++ b/src/import/oci-registry/registry.fedora.oci-registry @@ -1,3 +1,3 @@ { - "defaultRegistry" : "registry.fedoraproject.org" + "overrideRegistry" : "registry.fedoraproject.org" } From ee8483775e0309c64d1fa390a11a8a94d1285368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 7 Apr 2026 15:54:54 +0200 Subject: [PATCH 0816/1296] =?UTF-8?q?shared/options:=20add=20equivalent=20?= =?UTF-8?q?of=20"+=E2=80=A6"=20for=20nested=20commandline=20parsing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/options.c | 5 ++ src/shared/options.h | 23 ++++--- src/test/test-options.c | 146 +++++++++++++++++++++++++++++----------- 3 files changed, 123 insertions(+), 51 deletions(-) diff --git a/src/shared/options.c b/src/shared/options.c index e6b82fe34d07c..f00d9674d7aff 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -108,6 +108,11 @@ int option_parse( /* Looks like we found an option parameter */ break; + if (state->stop_at_first_nonoption) { + state->parsing_stopped = true; + return 0; + } + state->optind++; } diff --git a/src/shared/options.h b/src/shared/options.h index c1ffa595ce828..1b0488579f94b 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -79,17 +79,20 @@ extern const Option __start_SYSTEMD_OPTIONS[]; extern const Option __stop_SYSTEMD_OPTIONS[]; typedef struct OptionParser { - /* Those two should stay first so that it's possible to initialize the struct as { argc, argv }. */ - int argc; /* The original argc. */ - char **argv; /* The argv array, possibly reordered. */ + /* Those three should stay first so that it's possible to initialize the struct as { argc, argv } + * or { argc, argv, true/false }. */ + int argc; /* The original argc. */ + char **argv; /* The argv array, possibly reordered. */ + bool stop_at_first_nonoption; /* Same as "+…" for getopt_long — only parse options before the first + * positional argument. */ - int optind; /* Position of the parameter being handled. - * 0 → option parsing hasn't been started yet. */ - int short_option_offset; /* Set when we're parsing an argument with one or more short options. - * 0 → we're not parsing short options. */ - int positional_offset; /* Offset to where positional parameters are. After processing has been - * finished, all options and their args are to the left of this offset. */ - bool parsing_stopped; /* We processed "--" or an option that terminates option parsing. */ + bool parsing_stopped; /* We processed "--" or an option that terminates option parsing. */ + int optind; /* Position of the parameter being handled. + * 0 → option parsing hasn't been started yet. */ + int short_option_offset; /* Set when we're parsing an argument with one or more short options. + * 0 → we're not parsing short options. */ + int positional_offset; /* Offset to where positional parameters are. After processing has been + * finished, all options and their args are to the left of this offset. */ } OptionParser; int option_parse( diff --git a/src/test/test-options.c b/src/test/test-options.c index fb1f61f358d02..a9cafbdd73327 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -14,7 +14,8 @@ static void test_option_parse_one( char **argv, const Option options[], const Entry *entries, - char **remaining) { + char **remaining, + bool stop_at_first_nonoption) { _cleanup_free_ char *joined = strv_join(argv, ", "); log_debug("/* %s(%s) */", __func__, joined); @@ -31,7 +32,7 @@ static void test_option_parse_one( for (const Entry *e = entries; e && (e->long_code || e->short_code != 0); e++) n_entries++; - OptionParser state = { argc, argv }; + OptionParser state = { argc, argv, stop_at_first_nonoption }; const Option *opt; const char *arg; for (int c; (c = option_parse(options, options + n_options, &state, &opt, &arg)) != 0; ) { @@ -99,7 +100,8 @@ TEST(option_parse) { test_option_parse_one(STRV_MAKE("arg0"), options, NULL, - NULL); + NULL, + false); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -111,7 +113,8 @@ TEST(option_parse) { STRV_MAKE("string1", "string2", "string3", - "string4")); + "string4"), + false); test_option_parse_one(STRV_MAKE("arg0", "--", @@ -124,7 +127,8 @@ TEST(option_parse) { STRV_MAKE("string1", "--help", "-h", - "string4")); + "string4"), + false); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -137,7 +141,8 @@ TEST(option_parse) { STRV_MAKE("string1", "string2", "--", - "string4")); + "string4"), + false); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -150,7 +155,8 @@ TEST(option_parse) { STRV_MAKE("string1", "string2", "string3", - "string4")); + "string4"), + false); test_option_parse_one(STRV_MAKE("arg0", "--help"), @@ -159,7 +165,21 @@ TEST(option_parse) { { "help" }, {} }, - NULL); + NULL, + false); + + test_option_parse_one(STRV_MAKE("arg0", + "--help", + "string1", + "--help"), + options, + (Entry[]) { + { "help" }, + {} + }, + STRV_MAKE("string1", + "--help"), + true); test_option_parse_one(STRV_MAKE("arg0", "-h"), @@ -168,7 +188,8 @@ TEST(option_parse) { { "help" }, {} }, - NULL); + NULL, + false); test_option_parse_one(STRV_MAKE("arg0", "--help", @@ -184,7 +205,8 @@ TEST(option_parse) { STRV_MAKE("string1", "string2", "string3", - "string4")); + "string4"), + false); test_option_parse_one(STRV_MAKE("arg0", "-h", @@ -200,7 +222,8 @@ TEST(option_parse) { STRV_MAKE("string1", "string2", "string3", - "string4")); + "string4"), + false); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -216,7 +239,8 @@ TEST(option_parse) { STRV_MAKE("string1", "string2", "string3", - "string4")); + "string4"), + false); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -232,7 +256,8 @@ TEST(option_parse) { STRV_MAKE("string1", "string2", "string3", - "string4")); + "string4"), + false); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -248,7 +273,8 @@ TEST(option_parse) { STRV_MAKE("string1", "string2", "string3", - "string4")); + "string4"), + false); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -264,7 +290,8 @@ TEST(option_parse) { STRV_MAKE("string1", "string2", "string3", - "string4")); + "string4"), + false); test_option_parse_one(STRV_MAKE("arg0", "--required1", "reqarg1"), @@ -273,7 +300,8 @@ TEST(option_parse) { { "required1", "reqarg1" }, {} }, - NULL); + NULL, + false); test_option_parse_one(STRV_MAKE("arg0", "-r", "reqarg1"), @@ -282,7 +310,8 @@ TEST(option_parse) { { "required1", "reqarg1" }, {} }, - NULL); + NULL, + false); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -294,7 +323,19 @@ TEST(option_parse) { {} }, STRV_MAKE("string1", - "string2")); + "string2"), + false); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "-r", "reqarg1"), + options, + NULL, + STRV_MAKE("string1", + "string2", + "-r", "reqarg1"), + true); test_option_parse_one(STRV_MAKE("arg0", "--optional1=optarg1"), @@ -303,7 +344,8 @@ TEST(option_parse) { { "optional1", "optarg1" }, {} }, - NULL); + NULL, + true); test_option_parse_one(STRV_MAKE("arg0", "--optional1", "string1"), @@ -312,7 +354,8 @@ TEST(option_parse) { { "optional1", NULL }, {} }, - STRV_MAKE("string1")); + STRV_MAKE("string1"), + false); test_option_parse_one(STRV_MAKE("arg0", "-ooptarg1"), @@ -321,7 +364,8 @@ TEST(option_parse) { { "optional1", "optarg1" }, {} }, - NULL); + NULL, + false); test_option_parse_one(STRV_MAKE("arg0", "-o", "string1"), @@ -330,7 +374,8 @@ TEST(option_parse) { { "optional1", NULL }, {} }, - STRV_MAKE("string1")); + STRV_MAKE("string1"), + false); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -399,7 +444,8 @@ TEST(option_parse) { "string7", "--help", "--required1", - "--optional1")); + "--optional1"), + false); } TEST(option_stops_parsing) { @@ -422,7 +468,8 @@ TEST(option_stops_parsing) { {} }, STRV_MAKE("--help", - "foo")); + "foo"), + false); /* Options before --exec are still parsed */ test_option_parse_one(STRV_MAKE("arg0", @@ -437,7 +484,8 @@ TEST(option_stops_parsing) { {} }, STRV_MAKE("--version", - "bar")); + "bar"), + false); /* --exec with no trailing args */ test_option_parse_one(STRV_MAKE("arg0", @@ -447,7 +495,8 @@ TEST(option_stops_parsing) { { "exec" }, {} }, - NULL); + NULL, + false); /* --exec after positional args */ test_option_parse_one(STRV_MAKE("arg0", @@ -463,7 +512,8 @@ TEST(option_stops_parsing) { STRV_MAKE("pos1", "--help", "--required", - "val")); + "val"), + false); /* "--" after --exec: "--" is still consumed as end-of-options marker. This is needed for * backwards compatibility, systemd-dissect implemented this behaviour. But also, it makes @@ -478,7 +528,8 @@ TEST(option_stops_parsing) { { "exec" }, {} }, - STRV_MAKE("--help")); + STRV_MAKE("--help"), + false); /* "--" before --exec: "--" terminates first, --exec is positional */ test_option_parse_one(STRV_MAKE("arg0", @@ -488,7 +539,8 @@ TEST(option_stops_parsing) { options, NULL, STRV_MAKE("--exec", - "--help")); + "--help"), + false); /* Multiple options then --exec then more option-like args */ test_option_parse_one(STRV_MAKE("arg0", @@ -506,7 +558,8 @@ TEST(option_stops_parsing) { }, STRV_MAKE("-h", "--required", - "val2")); + "val2"), + false); } TEST(option_group_marker) { @@ -530,7 +583,8 @@ TEST(option_group_marker) { { "debug" }, {} }, - NULL); + NULL, + false); /* Check that group marker name is ignored */ test_option_parse_one(STRV_MAKE("arg0", @@ -542,7 +596,8 @@ TEST(option_group_marker) { { "version" }, {} }, - NULL); + NULL, + false); /* Verify that the group marker is not mistaken for an option */ test_option_invalid_one(STRV_MAKE("arg0", @@ -569,7 +624,8 @@ TEST(option_group_marker) { { "Advance" }, {} }, - NULL); + NULL, + false); /* Partial match with multiple candidates */ test_option_invalid_one(STRV_MAKE("arg0", @@ -592,7 +648,8 @@ TEST(option_optional_arg) { { "output", "foo.txt" }, {} }, - NULL); + NULL, + false); /* Long option without = does NOT consume the next arg */ test_option_parse_one(STRV_MAKE("arg0", @@ -602,7 +659,8 @@ TEST(option_optional_arg) { { "output", NULL }, {} }, - STRV_MAKE("foo.txt")); + STRV_MAKE("foo.txt"), + false); /* Short option with inline arg */ test_option_parse_one(STRV_MAKE("arg0", @@ -612,7 +670,8 @@ TEST(option_optional_arg) { { "output", "foo.txt" }, {} }, - NULL); + NULL, + false); /* Short option without inline arg does NOT consume the next arg */ test_option_parse_one(STRV_MAKE("arg0", @@ -622,7 +681,8 @@ TEST(option_optional_arg) { { "output", NULL }, {} }, - STRV_MAKE("foo.txt")); + STRV_MAKE("foo.txt"), + false); /* Optional arg option at end of argv */ test_option_parse_one(STRV_MAKE("arg0", @@ -632,7 +692,8 @@ TEST(option_optional_arg) { { "output", NULL }, {} }, - NULL); + NULL, + false); /* Mixed: optional arg with other options */ test_option_parse_one(STRV_MAKE("arg0", @@ -646,7 +707,8 @@ TEST(option_optional_arg) { { "help" }, {} }, - NULL); + NULL, + false); /* Short combo: -ho (h then o with no arg) */ test_option_parse_one(STRV_MAKE("arg0", @@ -657,7 +719,8 @@ TEST(option_optional_arg) { { "output", NULL }, {} }, - STRV_MAKE("pos1")); + STRV_MAKE("pos1"), + false); /* Short combo: -hobar (h then o with inline arg "bar") */ test_option_parse_one(STRV_MAKE("arg0", @@ -668,7 +731,8 @@ TEST(option_optional_arg) { { "output", "bar" }, {} }, - NULL); + NULL, + false); } /* Test the OPTION, OPTION_LONG, OPTION_SHORT, OPTION_FULL, OPTION_GROUP macros From 874fbb870cdcf40e502165bc0f688cf2ec3d70b1 Mon Sep 17 00:00:00 2001 From: noxiouz Date: Tue, 7 Apr 2026 14:52:38 +0100 Subject: [PATCH 0817/1296] coredumpctl: use loop_write() for dumping inline journal coredumps Replace the bare write() call with loop_write(), which handles short writes and EINTR retries. This also drops the now-unnecessary ssize_t variable and the redundant r = log_error_errno(r, ...) self-assignment, since loop_write() already stores its result in r. Co-developed-by: Codex (GPT-5) --- src/coredump/coredumpctl.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index 96724de12b635..b7c687d996c34 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -30,6 +30,7 @@ #include "fs-util.h" #include "glob-util.h" #include "image-policy.h" +#include "io-util.h" #include "journal-internal.h" #include "journal-util.h" #include "json-util.h" @@ -1109,8 +1110,6 @@ static int save_core(sd_journal *j, FILE *file, char **path, bool *unlink_temp) goto error; #endif } else { - ssize_t sz; - /* We want full data, nothing truncated. */ sd_journal_set_data_threshold(j, 0); @@ -1122,14 +1121,9 @@ static int save_core(sd_journal *j, FILE *file, char **path, bool *unlink_temp) data += 9; len -= 9; - sz = write(fd, data, len); - if (sz < 0) { - r = log_error_errno(errno, "Failed to write output: %m"); - goto error; - } - if (sz != (ssize_t) len) { - log_error("Short write to output."); - r = -EIO; + r = loop_write(fd, data, len); + if (r < 0) { + log_error_errno(r, "Failed to write output: %m"); goto error; } } From c2283986f96ee6fde0765ca7da611d2047de3b40 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 20 Mar 2026 13:37:42 +0100 Subject: [PATCH 0818/1296] logind: move reset_scheduled_shutdown() to new logind-shutdown.c This function operates on generic Manager state and will be needed by the varlink shutdown interface too. Move it out of logind-dbus.c into a new logind-shutdown.c, alongside the SHUTDOWN_SCHEDULE_FILE define and use `manager_reset_scheduled_shutdown() as the new name. --- src/login/logind-dbus.c | 42 +++++++------------------------------ src/login/logind-shutdown.c | 32 ++++++++++++++++++++++++++++ src/login/logind-shutdown.h | 8 +++++++ src/login/meson.build | 1 + 4 files changed, 49 insertions(+), 34 deletions(-) create mode 100644 src/login/logind-shutdown.c create mode 100644 src/login/logind-shutdown.h diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 14ec1f90ab400..d02d836c75e79 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -45,6 +45,7 @@ #include "logind-seat.h" #include "logind-seat-dbus.h" #include "logind-session-dbus.h" +#include "logind-shutdown.h" #include "logind-user.h" #include "logind-user-dbus.h" #include "logind-utmp.h" @@ -76,10 +77,6 @@ */ #define WALL_MESSAGE_MAX 4096U -#define SHUTDOWN_SCHEDULE_FILE "/run/systemd/shutdown/scheduled" - -static void reset_scheduled_shutdown(Manager *m); - static int get_sender_session( Manager *m, sd_bus_message *message, @@ -2454,7 +2451,7 @@ static int method_do_shutdown_or_sleep( /* reset case we're shorting a scheduled shutdown */ m->unlink_nologin = false; - reset_scheduled_shutdown(m); + manager_reset_scheduled_shutdown(m); m->scheduled_shutdown_timeout = 0; m->scheduled_shutdown_action = action; @@ -2568,29 +2565,6 @@ static usec_t nologin_timeout_usec(usec_t elapse) { return LESS_BY(elapse, 5 * USEC_PER_MINUTE); } -static void reset_scheduled_shutdown(Manager *m) { - assert(m); - - m->scheduled_shutdown_timeout_source = sd_event_source_disable_unref(m->scheduled_shutdown_timeout_source); - m->wall_message_timeout_source = sd_event_source_disable_unref(m->wall_message_timeout_source); - m->nologin_timeout_source = sd_event_source_disable_unref(m->nologin_timeout_source); - - m->scheduled_shutdown_action = _HANDLE_ACTION_INVALID; - m->scheduled_shutdown_timeout = USEC_INFINITY; - m->scheduled_shutdown_uid = UID_INVALID; - m->scheduled_shutdown_tty = mfree(m->scheduled_shutdown_tty); - m->shutdown_dry_run = false; - - if (m->unlink_nologin) { - (void) unlink_or_warn("/run/nologin"); - m->unlink_nologin = false; - } - - (void) unlink(SHUTDOWN_SCHEDULE_FILE); - - manager_send_changed(m, "ScheduledShutdown"); -} - static int update_schedule_file(Manager *m) { _cleanup_(unlink_and_freep) char *temp_path = NULL; _cleanup_fclose_ FILE *f = NULL; @@ -2669,7 +2643,7 @@ static int manager_scheduled_shutdown_handler( bus_manager_log_shutdown(m, a); log_info("Running in dry run, suppressing action."); - reset_scheduled_shutdown(m); + manager_reset_scheduled_shutdown(m); return 0; } @@ -2683,7 +2657,7 @@ static int manager_scheduled_shutdown_handler( return 0; error: - reset_scheduled_shutdown(m); + manager_reset_scheduled_shutdown(m); return r; } @@ -2738,7 +2712,7 @@ void manager_load_scheduled_shutdown(Manager *m) { "TTY", &tty); /* reset will delete the file */ - reset_scheduled_shutdown(m); + manager_reset_scheduled_shutdown(m); if (r == -ENOENT) return; @@ -2784,7 +2758,7 @@ void manager_load_scheduled_shutdown(Manager *m) { r = manager_setup_shutdown_timers(m); if (r < 0) - return reset_scheduled_shutdown(m); + return manager_reset_scheduled_shutdown(m); (void) manager_setup_wall_message_timer(m); (void) update_schedule_file(m); @@ -2853,7 +2827,7 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_ r = update_schedule_file(m); if (r < 0) { - reset_scheduled_shutdown(m); + manager_reset_scheduled_shutdown(m); return r; } @@ -2913,7 +2887,7 @@ static int method_cancel_scheduled_shutdown(sd_bus_message *message, void *userd } cancel_delayed_action(m); - reset_scheduled_shutdown(m); + manager_reset_scheduled_shutdown(m); return sd_bus_reply_method_return(message, "b", true); } diff --git a/src/login/logind-shutdown.c b/src/login/logind-shutdown.c new file mode 100644 index 0000000000000..eec4832ea85d1 --- /dev/null +++ b/src/login/logind-shutdown.c @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-event.h" + +#include "alloc-util.h" +#include "fs-util.h" +#include "logind.h" +#include "logind-dbus.h" +#include "logind-shutdown.h" + +void manager_reset_scheduled_shutdown(Manager *m) { + assert(m); + + m->scheduled_shutdown_timeout_source = sd_event_source_disable_unref(m->scheduled_shutdown_timeout_source); + m->wall_message_timeout_source = sd_event_source_disable_unref(m->wall_message_timeout_source); + m->nologin_timeout_source = sd_event_source_disable_unref(m->nologin_timeout_source); + + m->scheduled_shutdown_action = _HANDLE_ACTION_INVALID; + m->scheduled_shutdown_timeout = USEC_INFINITY; + m->scheduled_shutdown_uid = UID_INVALID; + m->scheduled_shutdown_tty = mfree(m->scheduled_shutdown_tty); + m->shutdown_dry_run = false; + + if (m->unlink_nologin) { + (void) unlink_or_warn("/run/nologin"); + m->unlink_nologin = false; + } + + (void) unlink(SHUTDOWN_SCHEDULE_FILE); + + manager_send_changed(m, "ScheduledShutdown"); +} diff --git a/src/login/logind-shutdown.h b/src/login/logind-shutdown.h new file mode 100644 index 0000000000000..46f37325a2983 --- /dev/null +++ b/src/login/logind-shutdown.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "logind-forward.h" + +#define SHUTDOWN_SCHEDULE_FILE "/run/systemd/shutdown/scheduled" + +void manager_reset_scheduled_shutdown(Manager *m); diff --git a/src/login/meson.build b/src/login/meson.build index d6654ff5ced50..390960d5f6c10 100644 --- a/src/login/meson.build +++ b/src/login/meson.build @@ -21,6 +21,7 @@ systemd_logind_extract_sources = files( 'logind-session-dbus.c', 'logind-session-device.c', 'logind-session.c', + 'logind-shutdown.c', 'logind-user-dbus.c', 'logind-user.c', 'logind-utmp.c', From d1d72563e0d90f924c8e789a978ff95196e1b969 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 20 Mar 2026 13:37:59 +0100 Subject: [PATCH 0819/1296] logind: add io.systemd.Shutdown varlink Shutdown interface The shutdown interface is currently only exposed via dbus. This commit adds a comparable varlink implementation. It is inspired by the existing dbus methods and implements PowerOff, Reboot, Halt, Kexec, SoftReboot. It is (intentional) simpler than the dbus for now, i.e. strictly root only. To match dbus we will need to the functionality of verify_shutdown_creds() which is dbus-ish right now and would need some refactor. For the same reason it does not do the Can* methods - we will need the verify_shutdown_creds() equivalent first. --- man/systemd-logind.service.xml | 9 ++ src/login/logind-varlink.c | 116 ++++++++++++++++++++++- src/shared/meson.build | 1 + src/shared/varlink-io.systemd.Shutdown.c | 52 ++++++++++ src/shared/varlink-io.systemd.Shutdown.h | 6 ++ units/systemd-logind-varlink.socket | 2 +- 6 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 src/shared/varlink-io.systemd.Shutdown.c create mode 100644 src/shared/varlink-io.systemd.Shutdown.h diff --git a/man/systemd-logind.service.xml b/man/systemd-logind.service.xml index 34f6330bf7c07..30d9dab14e830 100644 --- a/man/systemd-logind.service.xml +++ b/man/systemd-logind.service.xml @@ -84,6 +84,15 @@ org.freedesktop.LogControl15 for information about the D-Bus APIs systemd-logind provides. + In addition to the D-Bus interface, systemd-logind also provides a Varlink + interface io.systemd.Shutdown for shutting down or rebooting the system. It + supports PowerOff, Reboot, Halt, + KExec, and SoftReboot methods. Each method accepts an + optional skipInhibitors boolean parameter to bypass active block inhibitors + (matching the SD_LOGIND_SKIP_INHIBITORS flag of the D-Bus interface). The + interface can be queried with + varlinkctl introspect /run/systemd/io.systemd.Login io.systemd.Shutdown. + For more information see Inhibitor Locks. diff --git a/src/login/logind-varlink.c b/src/login/logind-varlink.c index ae2e32d3fafa1..a14569cacd7c2 100644 --- a/src/login/logind-varlink.c +++ b/src/login/logind-varlink.c @@ -4,15 +4,19 @@ #include "sd-event.h" #include "alloc-util.h" +#include "bus-error.h" +#include "bus-polkit.h" #include "cgroup-util.h" #include "fd-util.h" #include "format-util.h" #include "hashmap.h" #include "json-util.h" -#include "logind-session.h" +#include "login-util.h" #include "logind.h" #include "logind-dbus.h" #include "logind-seat.h" +#include "logind-session.h" +#include "logind-shutdown.h" #include "logind-user.h" #include "logind-varlink.h" #include "strv.h" @@ -20,6 +24,7 @@ #include "user-record.h" #include "user-util.h" #include "varlink-io.systemd.Login.h" +#include "varlink-io.systemd.Shutdown.h" #include "varlink-io.systemd.service.h" #include "varlink-util.h" @@ -336,6 +341,107 @@ static int vl_method_release_session(sd_varlink *link, sd_json_variant *paramete return sd_varlink_reply(link, NULL); } +static int setup_wall_message_timer(Manager *m, sd_varlink *link) { + uid_t uid = UID_INVALID; + int r; + + (void) sd_varlink_get_peer_uid(link, &uid); + m->scheduled_shutdown_uid = uid; + + _cleanup_free_ char *tty = NULL; + pid_t pid = 0; + r = sd_varlink_get_peer_pid(link, &pid); + if (r >= 0) + (void) get_ctty(pid, /* ret_devnr= */ NULL, &tty); + + r = free_and_strdup_warn(&m->scheduled_shutdown_tty, tty); + if (r < 0) + return log_oom(); + + return manager_setup_wall_message_timer(m); +} + +static int manager_do_shutdown_action(sd_varlink *link, sd_json_variant *parameters, HandleAction action) { + Manager *m = ASSERT_PTR(sd_varlink_get_userdata(link)); + int skip_inhibitors = -1; + uid_t uid; + int r; + + static const sd_json_dispatch_field dispatch_table[] = { + { "skipInhibitors", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, 0, 0 }, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &skip_inhibitors); + if (r != 0) + return r; + + uint64_t flags = skip_inhibitors > 0 ? SD_LOGIND_SKIP_INHIBITORS : 0; + + const HandleActionData *a = handle_action_lookup(action); + assert(a); + + /* TODO: provide full polkit support (matching the D-Bus verify_shutdown_creds() with + * multiple-sessions and inhibitor-override checks). This requires some refactor. */ + r = varlink_check_privileged_peer(link); + if (r < 0) + return r; + + r = sd_varlink_get_peer_uid(link, &uid); + if (r < 0) + return r; + + /* Check for active inhibitors, mirroring the D-Bus verify_shutdown_creds() logic, + * we need this to ensure we handle the strong INHIBIT_BLOCK + * TODO: drop once we do the verify_shutdown_creds() */ + if (manager_is_inhibited(m, a->inhibit_what, NULL, /* flags= */ 0, uid, /* ret_offending= */ NULL) && + !FLAGS_SET(flags, SD_LOGIND_SKIP_INHIBITORS)) + return sd_varlink_error(link, "io.systemd.Shutdown.BlockedByInhibitor", /* parameters= */ NULL); + + if (m->delayed_action) + return sd_varlink_error(link, "io.systemd.Shutdown.AlreadyInProgress", /* parameters= */ NULL); + + /* Reset in case we're short-circuiting a scheduled shutdown */ + m->unlink_nologin = false; + manager_reset_scheduled_shutdown(m); + + m->scheduled_shutdown_timeout = 0; + m->scheduled_shutdown_action = action; + + (void) setup_wall_message_timer(m, link); + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = bus_manager_shutdown_or_sleep_now_or_later(m, a, &error); + if (r < 0) { + log_warning_errno(r, "Failed to execute %s: %s", + handle_action_to_string(action), + bus_error_message(&error, r)); + return sd_varlink_error_errno(link, r); + } + + return sd_varlink_reply(link, NULL); +} + +static int vl_method_power_off(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_shutdown_action(link, parameters, HANDLE_POWEROFF); +} + +static int vl_method_reboot(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_shutdown_action(link, parameters, HANDLE_REBOOT); +} + +static int vl_method_halt(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_shutdown_action(link, parameters, HANDLE_HALT); +} + +static int vl_method_kexec(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_shutdown_action(link, parameters, HANDLE_KEXEC); +} + +static int vl_method_soft_reboot(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return manager_do_shutdown_action(link, parameters, HANDLE_SOFT_REBOOT); +} + int manager_varlink_init(Manager *m, int fd) { _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; _unused_ _cleanup_close_ int fd_close = fd; @@ -358,14 +464,20 @@ int manager_varlink_init(Manager *m, int fd) { r = sd_varlink_server_add_interface_many( s, &vl_interface_io_systemd_Login, + &vl_interface_io_systemd_Shutdown, &vl_interface_io_systemd_service); if (r < 0) - return log_error_errno(r, "Failed to add Login interface to varlink server: %m"); + return log_error_errno(r, "Failed to add varlink interfaces: %m"); r = sd_varlink_server_bind_method_many( s, "io.systemd.Login.CreateSession", vl_method_create_session, "io.systemd.Login.ReleaseSession", vl_method_release_session, + "io.systemd.Shutdown.PowerOff", vl_method_power_off, + "io.systemd.Shutdown.Reboot", vl_method_reboot, + "io.systemd.Shutdown.Halt", vl_method_halt, + "io.systemd.Shutdown.KExec", vl_method_kexec, + "io.systemd.Shutdown.SoftReboot", vl_method_soft_reboot, "io.systemd.service.Ping", varlink_method_ping, "io.systemd.service.SetLogLevel", varlink_method_set_log_level, "io.systemd.service.GetEnvironment", varlink_method_get_environment); diff --git a/src/shared/meson.build b/src/shared/meson.build index 2c6207d494454..22dccf0e2a7da 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -230,6 +230,7 @@ shared_sources = files( 'varlink-io.systemd.Resolve.c', 'varlink-io.systemd.Resolve.Hook.c', 'varlink-io.systemd.Resolve.Monitor.c', + 'varlink-io.systemd.Shutdown.c', 'varlink-io.systemd.Udev.c', 'varlink-io.systemd.Unit.c', 'varlink-io.systemd.UserDatabase.c', diff --git a/src/shared/varlink-io.systemd.Shutdown.c b/src/shared/varlink-io.systemd.Shutdown.c new file mode 100644 index 0000000000000..96f4fef04d60c --- /dev/null +++ b/src/shared/varlink-io.systemd.Shutdown.c @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "bus-polkit.h" +#include "varlink-io.systemd.Shutdown.h" + +static SD_VARLINK_DEFINE_METHOD( + PowerOff, + VARLINK_DEFINE_POLKIT_INPUT, + SD_VARLINK_FIELD_COMMENT("Skip active inhibitors and force the operation"), + SD_VARLINK_DEFINE_INPUT(skipInhibitors, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_METHOD( + Reboot, + VARLINK_DEFINE_POLKIT_INPUT, + SD_VARLINK_FIELD_COMMENT("Skip active inhibitors and force the operation"), + SD_VARLINK_DEFINE_INPUT(skipInhibitors, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_METHOD( + Halt, + VARLINK_DEFINE_POLKIT_INPUT, + SD_VARLINK_FIELD_COMMENT("Skip active inhibitors and force the operation"), + SD_VARLINK_DEFINE_INPUT(skipInhibitors, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_METHOD( + KExec, + VARLINK_DEFINE_POLKIT_INPUT, + SD_VARLINK_FIELD_COMMENT("Skip active inhibitors and force the operation"), + SD_VARLINK_DEFINE_INPUT(skipInhibitors, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_METHOD( + SoftReboot, + VARLINK_DEFINE_POLKIT_INPUT, + SD_VARLINK_FIELD_COMMENT("Skip active inhibitors and force the operation"), + SD_VARLINK_DEFINE_INPUT(skipInhibitors, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_ERROR(AlreadyInProgress); +static SD_VARLINK_DEFINE_ERROR(BlockedByInhibitor); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_Shutdown, + "io.systemd.Shutdown", + SD_VARLINK_INTERFACE_COMMENT("APIs for shutting down or rebooting the system."), + SD_VARLINK_SYMBOL_COMMENT("Power off the system"), + &vl_method_PowerOff, + SD_VARLINK_SYMBOL_COMMENT("Reboot the system"), + &vl_method_Reboot, + SD_VARLINK_SYMBOL_COMMENT("Halt the system"), + &vl_method_Halt, + SD_VARLINK_SYMBOL_COMMENT("Reboot the system via kexec"), + &vl_method_KExec, + SD_VARLINK_SYMBOL_COMMENT("Reboot userspace only"), + &vl_method_SoftReboot, + SD_VARLINK_SYMBOL_COMMENT("Another shutdown or sleep operation is already in progress"), + &vl_error_AlreadyInProgress, + SD_VARLINK_SYMBOL_COMMENT("Operation denied due to active block inhibitor"), + &vl_error_BlockedByInhibitor); diff --git a/src/shared/varlink-io.systemd.Shutdown.h b/src/shared/varlink-io.systemd.Shutdown.h new file mode 100644 index 0000000000000..e97853b0bc799 --- /dev/null +++ b/src/shared/varlink-io.systemd.Shutdown.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_Shutdown; diff --git a/units/systemd-logind-varlink.socket b/units/systemd-logind-varlink.socket index 1d3652e049742..377eac7006fdf 100644 --- a/units/systemd-logind-varlink.socket +++ b/units/systemd-logind-varlink.socket @@ -13,7 +13,7 @@ Documentation=man:systemd-logind.service(8) [Socket] ListenStream=/run/systemd/io.systemd.Login -Symlinks=/run/varlink/registry/io.systemd.Login +Symlinks=/run/varlink/registry/io.systemd.Login /run/varlink/registry/io.systemd.Shutdown /run/systemd/io.systemd.Shutdown FileDescriptorName=varlink SocketMode=0666 Service=systemd-logind.service From c1b928c810c8d231657393ad3499d6d2940059b3 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 20 Mar 2026 13:42:30 +0100 Subject: [PATCH 0820/1296] logind: move verify_shutdown_creds() to logind-shutdown.c Move verify_shutdown_creds() and its helper have_multiple_sessions() from logind-dbus.c to logind-shutdown.c so that they can be reused by the varlink transport. No functional changes. Also prefix both with `manager_` now that they are public. --- src/login/logind-dbus.c | 139 +-------------------------------- src/login/logind-shutdown.c | 148 ++++++++++++++++++++++++++++++++++++ src/login/logind-shutdown.h | 2 + 3 files changed, 153 insertions(+), 136 deletions(-) diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index d02d836c75e79..52bdde1f08ac8 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -1863,24 +1863,6 @@ static int method_flush_devices(sd_bus_message *message, void *userdata, sd_bus_ return sd_bus_reply_method_return(message, NULL); } -static int have_multiple_sessions( - Manager *m, - uid_t uid) { - - Session *session; - - assert(m); - - /* Check for other users' sessions. Greeter sessions do not - * count, and non-login sessions do not count either. */ - HASHMAP_FOREACH(session, m->sessions) - if (SESSION_CLASS_IS_INHIBITOR_LIKE(session->class) && - session->user->user_record->uid != uid) - return true; - - return false; -} - static int bus_manager_log_shutdown( Manager *m, const HandleActionData *a) { @@ -2186,121 +2168,6 @@ int bus_manager_shutdown_or_sleep_now_or_later( return r; } -static int verify_shutdown_creds( - Manager *m, - sd_bus_message *message, - const HandleActionData *a, - uint64_t flags, - sd_bus_error *error) { - - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - bool multiple_sessions, blocked, interactive; - _unused_ bool error_or_denial = false; - Inhibitor *offending = NULL; - uid_t uid; - int r; - - assert(m); - assert(a); - assert(message); - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds); - if (r < 0) - return r; - - r = sd_bus_creds_get_euid(creds, &uid); - if (r < 0) - return r; - - r = have_multiple_sessions(m, uid); - if (r < 0) - return r; - - multiple_sessions = r > 0; - blocked = manager_is_inhibited(m, a->inhibit_what, NULL, /* flags= */ 0, uid, &offending); - interactive = flags & SD_LOGIND_INTERACTIVE; - - if (multiple_sessions) { - r = bus_verify_polkit_async_full( - message, - a->polkit_action_multiple_sessions, - /* details= */ NULL, - /* good_user= */ UID_INVALID, - interactive ? POLKIT_ALLOW_INTERACTIVE : 0, - &m->polkit_registry, - error); - if (r < 0) { - /* If we get -EBUSY, it means a polkit decision was made, but not for - * this action in particular. Assuming we are blocked on inhibitors, - * ignore that error and allow the decision to be revealed below. */ - if (blocked && r == -EBUSY) - error_or_denial = true; - else - return r; - } - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - } - - if (blocked) { - PolkitFlags polkit_flags = 0; - - /* With a strong inhibitor, if the skip flag is not set, reject outright. - * With a weak inhibitor, if root is asking and the root flag is set, reject outright. - * All else, check polkit first. */ - if (!FLAGS_SET(flags, SD_LOGIND_SKIP_INHIBITORS) && - (offending->mode != INHIBIT_BLOCK_WEAK || - (uid == 0 && FLAGS_SET(flags, SD_LOGIND_ROOT_CHECK_INHIBITORS)))) - return sd_bus_error_set(error, BUS_ERROR_BLOCKED_BY_INHIBITOR_LOCK, - "Operation denied due to active block inhibitor"); - - /* We want to always ask here, even for root, to only allow bypassing if explicitly allowed - * by polkit, unless a weak blocker is used, in which case it will be authorized. */ - if (offending->mode != INHIBIT_BLOCK_WEAK) - polkit_flags |= POLKIT_ALWAYS_QUERY; - - if (interactive) - polkit_flags |= POLKIT_ALLOW_INTERACTIVE; - - r = bus_verify_polkit_async_full( - message, - a->polkit_action_ignore_inhibit, - /* details= */ NULL, - /* good_user= */ UID_INVALID, - polkit_flags, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - } - - if (!multiple_sessions && !blocked) { - r = bus_verify_polkit_async_full( - message, - a->polkit_action, - /* details= */ NULL, - /* good_user= */ UID_INVALID, - interactive ? POLKIT_ALLOW_INTERACTIVE : 0, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - } - - /* If error_or_denial was set above, it means that a polkit denial or - * error was deferred for a future call to bus_verify_polkit_async_full() - * to catch. In any case, it also means that the payload guarded by - * these polkit calls should never be executed, and hence we should - * never reach this point. */ - assert(!error_or_denial); - - return 0; -} - static int setup_wall_message_timer(Manager *m, sd_bus_message* message) { _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; int r; @@ -2439,7 +2306,7 @@ static int method_do_shutdown_or_sleep( } else if (!a) assert_se(a = handle_action_lookup(action)); - r = verify_shutdown_creds(m, message, a, flags, error); + r = manager_verify_shutdown_creds(m, message, a, flags, error); if (r != 0) return r; @@ -2793,7 +2660,7 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_ assert_se(a = handle_action_lookup(handle)); assert(a->polkit_action); - r = verify_shutdown_creds(m, message, a, 0, error); + r = manager_verify_shutdown_creds(m, message, a, 0, error); if (r != 0) return r; @@ -2943,7 +2810,7 @@ static int method_can_shutdown_or_sleep( if (r < 0) return r; - r = have_multiple_sessions(m, uid); + r = manager_have_multiple_sessions(m, uid); if (r < 0) return r; diff --git a/src/login/logind-shutdown.c b/src/login/logind-shutdown.c index eec4832ea85d1..96698cb55bc2f 100644 --- a/src/login/logind-shutdown.c +++ b/src/login/logind-shutdown.c @@ -1,12 +1,160 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + +#include "sd-bus.h" #include "sd-event.h" #include "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-polkit.h" #include "fs-util.h" +#include "hashmap.h" +#include "login-util.h" #include "logind.h" #include "logind-dbus.h" +#include "logind-inhibit.h" +#include "logind-session.h" #include "logind-shutdown.h" +#include "logind-user.h" +#include "user-record.h" + +int manager_have_multiple_sessions( + Manager *m, + uid_t uid) { + + Session *session; + + assert(m); + + /* Check for other users' sessions. Greeter sessions do not + * count, and non-login sessions do not count either. */ + HASHMAP_FOREACH(session, m->sessions) + if (SESSION_CLASS_IS_INHIBITOR_LIKE(session->class) && + session->user->user_record->uid != uid) + return true; + + return false; +} + +int manager_verify_shutdown_creds( + Manager *m, + sd_bus_message *message, + const HandleActionData *a, + uint64_t flags, + sd_bus_error *error) { + + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + bool multiple_sessions, blocked, interactive; + _unused_ bool error_or_denial = false; + Inhibitor *offending = NULL; + uid_t uid; + int r; + + assert(m); + assert(a); + assert(message); + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds); + if (r < 0) + return r; + + r = sd_bus_creds_get_euid(creds, &uid); + if (r < 0) + return r; + + r = manager_have_multiple_sessions(m, uid); + if (r < 0) + return r; + + multiple_sessions = r > 0; + blocked = manager_is_inhibited(m, a->inhibit_what, NULL, /* flags= */ 0, uid, &offending); + interactive = flags & SD_LOGIND_INTERACTIVE; + + if (multiple_sessions) { + r = bus_verify_polkit_async_full( + message, + a->polkit_action_multiple_sessions, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, + &m->polkit_registry, + error); + if (r < 0) { + /* If we get -EBUSY, it means a polkit decision was made, but not for + * this action in particular. Assuming we are blocked on inhibitors, + * ignore that error and allow the decision to be revealed below. */ + if (blocked && r == -EBUSY) + error_or_denial = true; + else + return r; + } + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + } + + if (blocked) { + PolkitFlags polkit_flags = 0; + + /* With a strong inhibitor, if the skip flag is not set, reject outright. + * With a weak inhibitor, if root is asking and the root flag is set, reject outright. + * All else, check polkit first. */ + if (!FLAGS_SET(flags, SD_LOGIND_SKIP_INHIBITORS) && + (offending->mode != INHIBIT_BLOCK_WEAK || + (uid == 0 && FLAGS_SET(flags, SD_LOGIND_ROOT_CHECK_INHIBITORS)))) { + if (error) + return sd_bus_error_set(error, BUS_ERROR_BLOCKED_BY_INHIBITOR_LOCK, + "Operation denied due to active block inhibitor"); + else + return -EACCES; + } + + /* We want to always ask here, even for root, to only allow bypassing if explicitly allowed + * by polkit, unless a weak blocker is used, in which case it will be authorized. */ + if (offending->mode != INHIBIT_BLOCK_WEAK) + polkit_flags |= POLKIT_ALWAYS_QUERY; + + if (interactive) + polkit_flags |= POLKIT_ALLOW_INTERACTIVE; + + r = bus_verify_polkit_async_full( + message, + a->polkit_action_ignore_inhibit, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + polkit_flags, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + } + + if (!multiple_sessions && !blocked) { + r = bus_verify_polkit_async_full( + message, + a->polkit_action, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + } + + /* If error_or_denial was set above, it means that a polkit denial or + * error was deferred for a future call to bus_verify_polkit_async_full() + * to catch. In any case, it also means that the payload guarded by + * these polkit calls should never be executed, and hence we should + * never reach this point. */ + assert(!error_or_denial); + + return 0; +} void manager_reset_scheduled_shutdown(Manager *m) { assert(m); diff --git a/src/login/logind-shutdown.h b/src/login/logind-shutdown.h index 46f37325a2983..b8c70a9630be4 100644 --- a/src/login/logind-shutdown.h +++ b/src/login/logind-shutdown.h @@ -5,4 +5,6 @@ #define SHUTDOWN_SCHEDULE_FILE "/run/systemd/shutdown/scheduled" +int manager_have_multiple_sessions(Manager *m, uid_t uid); +int manager_verify_shutdown_creds(Manager *m, sd_bus_message *message, const HandleActionData *a, uint64_t flags, sd_bus_error *error); void manager_reset_scheduled_shutdown(Manager *m); From db426d147d0ea8082bd8c69628aba63d3ddd635e Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 20 Mar 2026 13:49:50 +0100 Subject: [PATCH 0821/1296] logind: extend verify_shutdown_creds() to take `sd_varlink *link` To properly support a shutdown interface with varlink we need the functionality of verify_shutdown_creds(). This is currently dbus only. There are some options: 1. refactor and abstract so that verify_shutdown_creds() is agnostic 2. provide a equivalent function with varlink 3. allow to call it with either a dbus or a varlink message. The most elegant of course is (1) but it makes reviewing harder and has a higher regression risk. It will also be more code. Doing (2) has the risk of drift, i.e. we will need to keep the two functions in sync and not forget about it ever. So this commit opts for (3): allowing either dbus or varlink. This is quite ugly, however the big advantage is that its very simple to review as the dbus/varlink branches mirror each other. And there is no risk of drift the dbus/varlink options are close to each other. It unlikely we get a third bus, so it will most likely stay this way. It is still ugly though so I can understand if this is undesired and I can look into (1) if its too ugly. With this function avaialble logind-varlink.c is now updated to use it. --- src/login/logind-dbus.c | 4 +- src/login/logind-shutdown.c | 117 ++++++++++++++++------- src/login/logind-shutdown.h | 7 +- src/login/logind-varlink.c | 19 +--- src/shared/varlink-io.systemd.Shutdown.c | 7 +- 5 files changed, 99 insertions(+), 55 deletions(-) diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 52bdde1f08ac8..ac5da453d64ab 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -2306,7 +2306,7 @@ static int method_do_shutdown_or_sleep( } else if (!a) assert_se(a = handle_action_lookup(action)); - r = manager_verify_shutdown_creds(m, message, a, flags, error); + r = manager_verify_shutdown_creds(m, message, /* link= */ NULL, a, flags, error); if (r != 0) return r; @@ -2660,7 +2660,7 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_ assert_se(a = handle_action_lookup(handle)); assert(a->polkit_action); - r = manager_verify_shutdown_creds(m, message, a, 0, error); + r = manager_verify_shutdown_creds(m, message, /* link= */ NULL, a, 0, error); if (r != 0) return r; diff --git a/src/login/logind-shutdown.c b/src/login/logind-shutdown.c index 96698cb55bc2f..d79843a287fcb 100644 --- a/src/login/logind-shutdown.c +++ b/src/login/logind-shutdown.c @@ -4,8 +4,8 @@ #include "sd-bus.h" #include "sd-event.h" +#include "sd-varlink.h" -#include "alloc-util.h" #include "bus-common-errors.h" #include "bus-polkit.h" #include "fs-util.h" @@ -40,11 +40,11 @@ int manager_have_multiple_sessions( int manager_verify_shutdown_creds( Manager *m, sd_bus_message *message, + sd_varlink *link, const HandleActionData *a, uint64_t flags, sd_bus_error *error) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; bool multiple_sessions, blocked, interactive; _unused_ bool error_or_denial = false; Inhibitor *offending = NULL; @@ -53,15 +53,24 @@ int manager_verify_shutdown_creds( assert(m); assert(a); - assert(message); + assert(!!message != !!link); /* exactly one transport */ + assert(!link || !error); /* varlink doesn't use sd_bus_error */ - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds); - if (r < 0) - return r; + if (message) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - r = sd_bus_creds_get_euid(creds, &uid); - if (r < 0) - return r; + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds); + if (r < 0) + return r; + + r = sd_bus_creds_get_euid(creds, &uid); + if (r < 0) + return r; + } else { + r = sd_varlink_get_peer_uid(link, &uid); + if (r < 0) + return r; + } r = manager_have_multiple_sessions(m, uid); if (r < 0) @@ -72,14 +81,25 @@ int manager_verify_shutdown_creds( interactive = flags & SD_LOGIND_INTERACTIVE; if (multiple_sessions) { - r = bus_verify_polkit_async_full( - message, - a->polkit_action_multiple_sessions, - /* details= */ NULL, - /* good_user= */ UID_INVALID, - interactive ? POLKIT_ALLOW_INTERACTIVE : 0, - &m->polkit_registry, - error); + if (message) + r = bus_verify_polkit_async_full( + message, + a->polkit_action_multiple_sessions, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, + &m->polkit_registry, + error); + else + r = varlink_verify_polkit_async_full( + link, + m->bus, + a->polkit_action_multiple_sessions, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, + &m->polkit_registry); + if (r < 0) { /* If we get -EBUSY, it means a polkit decision was made, but not for * this action in particular. Assuming we are blocked on inhibitors, @@ -102,11 +122,16 @@ int manager_verify_shutdown_creds( if (!FLAGS_SET(flags, SD_LOGIND_SKIP_INHIBITORS) && (offending->mode != INHIBIT_BLOCK_WEAK || (uid == 0 && FLAGS_SET(flags, SD_LOGIND_ROOT_CHECK_INHIBITORS)))) { + if (link) + return sd_varlink_errorbo( + link, + "io.systemd.Shutdown.BlockedByInhibitor", + SD_JSON_BUILD_PAIR_STRING("who", offending->who), + SD_JSON_BUILD_PAIR_STRING("why", offending->why)); if (error) return sd_bus_error_set(error, BUS_ERROR_BLOCKED_BY_INHIBITOR_LOCK, "Operation denied due to active block inhibitor"); - else - return -EACCES; + return -EACCES; } /* We want to always ask here, even for root, to only allow bypassing if explicitly allowed @@ -117,14 +142,25 @@ int manager_verify_shutdown_creds( if (interactive) polkit_flags |= POLKIT_ALLOW_INTERACTIVE; - r = bus_verify_polkit_async_full( - message, - a->polkit_action_ignore_inhibit, - /* details= */ NULL, - /* good_user= */ UID_INVALID, - polkit_flags, - &m->polkit_registry, - error); + if (message) + r = bus_verify_polkit_async_full( + message, + a->polkit_action_ignore_inhibit, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + polkit_flags, + &m->polkit_registry, + error); + else + r = varlink_verify_polkit_async_full( + link, + m->bus, + a->polkit_action_ignore_inhibit, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + polkit_flags, + &m->polkit_registry); + if (r < 0) return r; if (r == 0) @@ -132,14 +168,25 @@ int manager_verify_shutdown_creds( } if (!multiple_sessions && !blocked) { - r = bus_verify_polkit_async_full( - message, - a->polkit_action, - /* details= */ NULL, - /* good_user= */ UID_INVALID, - interactive ? POLKIT_ALLOW_INTERACTIVE : 0, - &m->polkit_registry, - error); + if (message) + r = bus_verify_polkit_async_full( + message, + a->polkit_action, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, + &m->polkit_registry, + error); + else + r = varlink_verify_polkit_async_full( + link, + m->bus, + a->polkit_action, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, + &m->polkit_registry); + if (r < 0) return r; if (r == 0) diff --git a/src/login/logind-shutdown.h b/src/login/logind-shutdown.h index b8c70a9630be4..38b8b7a9c0841 100644 --- a/src/login/logind-shutdown.h +++ b/src/login/logind-shutdown.h @@ -6,5 +6,10 @@ #define SHUTDOWN_SCHEDULE_FILE "/run/systemd/shutdown/scheduled" int manager_have_multiple_sessions(Manager *m, uid_t uid); -int manager_verify_shutdown_creds(Manager *m, sd_bus_message *message, const HandleActionData *a, uint64_t flags, sd_bus_error *error); + +/* manager_verify_shutdown_creds() takes *either* a "message" or "link" depending on if it is used + * to validate a D-Bus or Varlink shutdown request. When varlink is used the sd_bus_error *error + * must be NULL */ +int manager_verify_shutdown_creds(Manager *m, sd_bus_message *message, sd_varlink *link, const HandleActionData *a, uint64_t flags, sd_bus_error *error); + void manager_reset_scheduled_shutdown(Manager *m); diff --git a/src/login/logind-varlink.c b/src/login/logind-varlink.c index a14569cacd7c2..f9ba74c632083 100644 --- a/src/login/logind-varlink.c +++ b/src/login/logind-varlink.c @@ -364,11 +364,11 @@ static int setup_wall_message_timer(Manager *m, sd_varlink *link) { static int manager_do_shutdown_action(sd_varlink *link, sd_json_variant *parameters, HandleAction action) { Manager *m = ASSERT_PTR(sd_varlink_get_userdata(link)); int skip_inhibitors = -1; - uid_t uid; int r; static const sd_json_dispatch_field dispatch_table[] = { { "skipInhibitors", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, 0, 0 }, + VARLINK_DISPATCH_POLKIT_FIELD, {} }; @@ -381,23 +381,10 @@ static int manager_do_shutdown_action(sd_varlink *link, sd_json_variant *paramet const HandleActionData *a = handle_action_lookup(action); assert(a); - /* TODO: provide full polkit support (matching the D-Bus verify_shutdown_creds() with - * multiple-sessions and inhibitor-override checks). This requires some refactor. */ - r = varlink_check_privileged_peer(link); - if (r < 0) - return r; - - r = sd_varlink_get_peer_uid(link, &uid); - if (r < 0) + r = manager_verify_shutdown_creds(m, /* message= */ NULL, link, a, flags, /* error= */ NULL); + if (r != 0) return r; - /* Check for active inhibitors, mirroring the D-Bus verify_shutdown_creds() logic, - * we need this to ensure we handle the strong INHIBIT_BLOCK - * TODO: drop once we do the verify_shutdown_creds() */ - if (manager_is_inhibited(m, a->inhibit_what, NULL, /* flags= */ 0, uid, /* ret_offending= */ NULL) && - !FLAGS_SET(flags, SD_LOGIND_SKIP_INHIBITORS)) - return sd_varlink_error(link, "io.systemd.Shutdown.BlockedByInhibitor", /* parameters= */ NULL); - if (m->delayed_action) return sd_varlink_error(link, "io.systemd.Shutdown.AlreadyInProgress", /* parameters= */ NULL); diff --git a/src/shared/varlink-io.systemd.Shutdown.c b/src/shared/varlink-io.systemd.Shutdown.c index 96f4fef04d60c..55728b7463e2c 100644 --- a/src/shared/varlink-io.systemd.Shutdown.c +++ b/src/shared/varlink-io.systemd.Shutdown.c @@ -30,7 +30,12 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_DEFINE_INPUT(skipInhibitors, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_ERROR(AlreadyInProgress); -static SD_VARLINK_DEFINE_ERROR(BlockedByInhibitor); +static SD_VARLINK_DEFINE_ERROR( + BlockedByInhibitor, + SD_VARLINK_FIELD_COMMENT("Who is holding the inhibitor"), + SD_VARLINK_DEFINE_FIELD(who, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Why the inhibitor is held"), + SD_VARLINK_DEFINE_FIELD(why, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); SD_VARLINK_DEFINE_INTERFACE( io_systemd_Shutdown, From 768b507adc08b47e0dfebb30e6dd0cb30c9a517d Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 24 Mar 2026 15:40:40 +0100 Subject: [PATCH 0822/1296] logind: log peer ID when shutdown is called The io.systemd.Manager.{PowerOff,SoftReboot,Halt,Kexec} manager varlink and bus methods log the peer ID when calling shutdown. The logind code is missing this, so this commit adds a similar logging now. The code is quite similar to the one in existing in src/core/manager.c but its hard to share code so this adds a bit of duplication. --- src/login/logind-dbus.c | 7 +++++++ src/login/logind-shutdown.c | 23 +++++++++++++++++++++++ src/login/logind-shutdown.h | 2 ++ src/login/logind-varlink.c | 7 +++++++ 4 files changed, 39 insertions(+) diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index ac5da453d64ab..98c651896d281 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -2310,6 +2310,13 @@ static int method_do_shutdown_or_sleep( if (r != 0) return r; + { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + + (void) bus_query_sender_pidref(message, &pidref); + log_shutdown_caller(&pidref, handle_action_to_string(a->handle)); + } + if (m->delayed_action) return sd_bus_error_setf(error, BUS_ERROR_OPERATION_IN_PROGRESS, "Action %s already in progress, refusing requested %s operation.", diff --git a/src/login/logind-shutdown.c b/src/login/logind-shutdown.c index d79843a287fcb..064ebf8e2ff8f 100644 --- a/src/login/logind-shutdown.c +++ b/src/login/logind-shutdown.c @@ -8,8 +8,11 @@ #include "bus-common-errors.h" #include "bus-polkit.h" +#include "cgroup-util.h" +#include "format-util.h" #include "fs-util.h" #include "hashmap.h" +#include "log.h" #include "login-util.h" #include "logind.h" #include "logind-dbus.h" @@ -17,6 +20,8 @@ #include "logind-session.h" #include "logind-shutdown.h" #include "logind-user.h" +#include "pidref.h" +#include "process-util.h" #include "user-record.h" int manager_have_multiple_sessions( @@ -37,6 +42,24 @@ int manager_have_multiple_sessions( return false; } +void log_shutdown_caller(const PidRef *caller, const char *method) { + _cleanup_free_ char *comm = NULL, *unit = NULL; + + assert(method); + + if (!pidref_is_set(caller)) { + return log_notice("%s requested from unknown client PID...", method); + } + + (void) pidref_get_comm(caller, &comm); + (void) cg_pidref_get_unit(caller, &unit); + + log_notice("%s requested from client PID " PID_FMT "%s%s%s%s%s%s...", + method, caller->pid, + comm ? " ('" : "", strempty(comm), comm ? "')" : "", + unit ? " (unit " : "", strempty(unit), unit ? ")" : ""); +} + int manager_verify_shutdown_creds( Manager *m, sd_bus_message *message, diff --git a/src/login/logind-shutdown.h b/src/login/logind-shutdown.h index 38b8b7a9c0841..e6bcc8c4f5d4e 100644 --- a/src/login/logind-shutdown.h +++ b/src/login/logind-shutdown.h @@ -7,6 +7,8 @@ int manager_have_multiple_sessions(Manager *m, uid_t uid); +void log_shutdown_caller(const PidRef *caller, const char *method); + /* manager_verify_shutdown_creds() takes *either* a "message" or "link" depending on if it is used * to validate a D-Bus or Varlink shutdown request. When varlink is used the sd_bus_error *error * must be NULL */ diff --git a/src/login/logind-varlink.c b/src/login/logind-varlink.c index f9ba74c632083..40dee113c4292 100644 --- a/src/login/logind-varlink.c +++ b/src/login/logind-varlink.c @@ -385,6 +385,13 @@ static int manager_do_shutdown_action(sd_varlink *link, sd_json_variant *paramet if (r != 0) return r; + { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + + (void) varlink_get_peer_pidref(link, &pidref); + log_shutdown_caller(&pidref, handle_action_to_string(action)); + } + if (m->delayed_action) return sd_varlink_error(link, "io.systemd.Shutdown.AlreadyInProgress", /* parameters= */ NULL); From 4c778c51c07db3eb0e07dd875f10eb85f086f096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 7 Apr 2026 16:22:21 +0200 Subject: [PATCH 0823/1296] vmspawn: convert to the new option parser Uses stop_at_first_nonoption for POSIX-style option parsing. --help output is the same, apart from whitespace differences and common strings. The error message for --ssh-key-type= is fixed. Co-developed-by: Claude Opus 4.6 --- src/vmspawn/vmspawn.c | 865 ++++++++++++++++++------------------------ 1 file changed, 360 insertions(+), 505 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 1b3558b47e75e..93c8fbbbbd2ed 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -39,6 +38,7 @@ #include "fd-util.h" #include "fileio.h" #include "fork-notify.h" +#include "format-table.h" #include "format-util.h" #include "fs-util.h" #include "gpt.h" @@ -58,6 +58,7 @@ #include "netif-util.h" #include "nsresource.h" #include "osc-context.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -202,6 +203,11 @@ STATIC_DESTRUCTOR_REGISTER(arg_bind_user, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user_shell, freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user_groups, strv_freep); +static void unref_many_tables(Table* (*tablesp)[]) { + for (Table **t = *ASSERT_PTR(tablesp); *t; t++) + *t = table_unref(*t); +} + static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -212,107 +218,46 @@ static int help(void) { if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] [ARGUMENTS...]\n\n" - "%5$sSpawn a command or OS in a virtual machine.%6$s\n\n" - " -h --help Show this help\n" - " --version Print version string\n" - " -q --quiet Do not show status information\n" - " --no-pager Do not pipe output into a pager\n" - " --no-ask-password Do not prompt for password\n" - " --system Run in the system service manager scope\n" - " --user Run in the user service manager scope\n" - "\n%3$sImage:%4$s\n" - " -D --directory=PATH Root directory for the VM\n" - " -x --ephemeral Run VM with snapshot of the disk or directory\n" - " -i --image=FILE|DEVICE Root file system disk image or device for the VM\n" - " --image-format=FORMAT Specify disk image format (raw, qcow2; default: raw)\n" - " --image-disk-type=TYPE\n" - " Specify disk type (virtio-blk, virtio-scsi, nvme,\n" - " scsi-cd; default: virtio-blk)\n" - "\n%3$sHost Configuration:%4$s\n" - " --cpus=CPUS Configure number of CPUs in guest\n" - " --ram=BYTES[:MAXBYTES[:SLOTS]]\n" - " Configure guest's RAM size (and max/slots for\n" - " hotplug)\n" - " --kvm=BOOL Enable use of KVM\n" - " --vsock=BOOL Override autodetection of VSOCK support\n" - " --vsock-cid=CID Specify the CID to use for the guest's VSOCK support\n" - " --tpm=BOOL Enable use of a virtual TPM\n" - " --tpm-state=off|auto|PATH\n" - " Where to store TPM state\n" - " --efi-nvram-template=PATH\n" - " Set the path to the EFI NVRAM template file to use\n" - " --efi-nvram-state=off|auto|PATH\n" - " Where to store EFI Variable NVRAM state\n" - " --linux=PATH Specify the linux kernel for direct kernel boot\n" - " --initrd=PATH Specify the initrd for direct kernel boot\n" - " -n --network-tap Create a TAP device for networking\n" - " --network-user-mode Use user mode networking\n" - " --secure-boot=BOOL|auto\n" - " Enable searching for firmware supporting SecureBoot\n" - " --firmware=PATH|list|describe\n" - " Select firmware definition file (or list/describe\n" - " available)\n" - " --firmware-features=FEATURE[,FEATURE...]|list\n" - " Require/exclude specific firmware features\n" - " --discard-disk=BOOL Control processing of discard requests\n" - " -G --grow-image=BYTES Grow image file to specified size in bytes\n" - "\n%3$sExecution:%4$s\n" - " -s --smbios11=STRING Pass an arbitrary SMBIOS Type #11 string to the VM\n" - " --notify-ready=BOOL Wait for ready notification from the VM\n" - "\n%3$sSystem Identity:%4$s\n" - " -M --machine=NAME Set the machine name for the VM\n" - " --uuid=UUID Set a specific machine UUID for the VM\n" - "\n%3$sProperties:%4$s\n" - " -S --slice=SLICE Place the VM in the specified slice\n" - " --property=NAME=VALUE Set scope unit property\n" - " --register=BOOLEAN Register VM as machine\n" - " --keep-unit Do not register a scope for the machine, reuse\n" - " the service unit vmspawn is running in\n" - "\n%3$sUser Namespacing:%4$s\n" - " --private-users=UIDBASE[:NUIDS]\n" - " Configure the UID/GID range to map into the\n" - " virtiofsd namespace\n" - "\n%3$sMounts:%4$s\n" - " --bind=SOURCE[:TARGET]\n" - " Mount a file or directory from the host into the VM\n" - " --bind-ro=SOURCE[:TARGET]\n" - " Mount a file or directory, but read-only\n" - " --extra-drive=[FORMAT:][DISKTYPE:]PATH\n" - " Adds an additional disk to the VM\n" - " FORMAT: raw, qcow2\n" - " DISKTYPE: virtio-blk, virtio-scsi, nvme,\n" - " scsi-cd\n" - " --bind-user=NAME Bind user from host to virtual machine\n" - " --bind-user-shell=BOOL|PATH\n" - " Configure the shell to use for --bind-user= users\n" - " --bind-user-group=GROUP\n" - " Add an auxiliary group to --bind-user= users\n" - "\n%3$sIntegration:%4$s\n" - " --forward-journal=FILE|DIR\n" - " Forward the VM's journal to the host\n" - " --pass-ssh-key=BOOL Create an SSH key to access the VM\n" - " --ssh-key-type=TYPE Choose what type of SSH key to pass\n" - "\n%3$sInput/Output:%4$s\n" - " --console=MODE Console mode (interactive, native, gui, read-only\n" - " or headless)\n" - " --console-transport=TRANSPORT\n" - " Console transport (virtio or serial)\n" - " --background=COLOR Set ANSI color for background\n" - "\n%3$sCredentials:%4$s\n" - " --set-credential=ID:VALUE\n" - " Pass a credential with literal value to the VM\n" - " --load-credential=ID:PATH\n" - " Load credential for the VM from file or AF_UNIX\n" - " stream socket.\n" - "\nSee the %2$s for details.\n", + static const char *groups[] = { + NULL, + "Image", + "Host Configuration", + "Execution", + "System Identity", + "Properties", + "User Namespacing", + "Mounts", + "Integration", + "Input/Output", + "Credentials", + }; + + _cleanup_(unref_many_tables) Table* tables[ELEMENTSOF(groups) + 1] = {}; + + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + r = option_parser_get_help_table_group(groups[i], &tables[i]); + if (r < 0) + return r; + } + + (void) table_sync_column_widths(0, tables[0], tables[1], tables[2], tables[3], tables[4], + tables[5], tables[6], tables[7], tables[8], tables[9], tables[10]); + + printf("%s [OPTIONS...] [ARGUMENTS...]\n\n" + "%sSpawn a command or OS in a virtual machine.%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), ansi_normal()); + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + printf("\n%s%s:%s\n", ansi_underline(), groups[i] ?: "Options", ansi_normal()); + + r = table_print_or_warn(tables[i]); + if (r < 0) + return r; + } + + printf("\nSee the %s for details.\n", link); return 0; } @@ -374,216 +319,117 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return log_oom(); - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_CPUS, - ARG_RAM, - ARG_KVM, - ARG_VSOCK, - ARG_VSOCK_CID, - ARG_TPM, - ARG_LINUX, - ARG_INITRD, - ARG_QEMU_GUI, - ARG_NETWORK_USER_MODE, - ARG_UUID, - ARG_REGISTER, - ARG_KEEP_UNIT, - ARG_BIND, - ARG_BIND_RO, - ARG_EXTRA_DRIVE, - ARG_SECURE_BOOT, - ARG_PRIVATE_USERS, - ARG_FORWARD_JOURNAL, - ARG_PASS_SSH_KEY, - ARG_SSH_KEY_TYPE, - ARG_SET_CREDENTIAL, - ARG_LOAD_CREDENTIAL, - ARG_FIRMWARE, - ARG_FIRMWARE_FEATURES, - ARG_DISCARD_DISK, - ARG_CONSOLE, - ARG_BACKGROUND, - ARG_TPM_STATE, - ARG_EFI_NVRAM_TEMPLATE, - ARG_EFI_NVRAM_STATE, - ARG_NO_ASK_PASSWORD, - ARG_PROPERTY, - ARG_NOTIFY_READY, - ARG_BIND_USER, - ARG_BIND_USER_SHELL, - ARG_BIND_USER_GROUP, - ARG_SYSTEM, - ARG_USER, - ARG_IMAGE_FORMAT, - ARG_IMAGE_DISK_TYPE, - ARG_CONSOLE_TRANSPORT, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "quiet", no_argument, NULL, 'q' }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "image", required_argument, NULL, 'i' }, - { "image-format", required_argument, NULL, ARG_IMAGE_FORMAT }, - { "image-disk-type", required_argument, NULL, ARG_IMAGE_DISK_TYPE }, - { "ephemeral", no_argument, NULL, 'x' }, - { "directory", required_argument, NULL, 'D' }, - { "machine", required_argument, NULL, 'M' }, - { "slice", required_argument, NULL, 'S' }, - { "cpus", required_argument, NULL, ARG_CPUS }, - { "qemu-smp", required_argument, NULL, ARG_CPUS }, /* Compat alias */ - { "ram", required_argument, NULL, ARG_RAM }, - { "qemu-mem", required_argument, NULL, ARG_RAM }, /* Compat alias */ - { "kvm", required_argument, NULL, ARG_KVM }, - { "qemu-kvm", required_argument, NULL, ARG_KVM }, /* Compat alias */ - { "vsock", required_argument, NULL, ARG_VSOCK }, - { "qemu-vsock", required_argument, NULL, ARG_VSOCK }, /* Compat alias */ - { "vsock-cid", required_argument, NULL, ARG_VSOCK_CID }, - { "tpm", required_argument, NULL, ARG_TPM }, - { "linux", required_argument, NULL, ARG_LINUX }, - { "initrd", required_argument, NULL, ARG_INITRD }, - { "console", required_argument, NULL, ARG_CONSOLE }, - { "console-transport", required_argument, NULL, ARG_CONSOLE_TRANSPORT }, - { "qemu-gui", no_argument, NULL, ARG_QEMU_GUI }, /* compat option */ - { "network-tap", no_argument, NULL, 'n' }, - { "network-user-mode", no_argument, NULL, ARG_NETWORK_USER_MODE }, - { "uuid", required_argument, NULL, ARG_UUID }, - { "register", required_argument, NULL, ARG_REGISTER }, - { "keep-unit", no_argument, NULL, ARG_KEEP_UNIT }, - { "bind", required_argument, NULL, ARG_BIND }, - { "bind-ro", required_argument, NULL, ARG_BIND_RO }, - { "extra-drive", required_argument, NULL, ARG_EXTRA_DRIVE }, - { "secure-boot", required_argument, NULL, ARG_SECURE_BOOT }, - { "private-users", required_argument, NULL, ARG_PRIVATE_USERS }, - { "forward-journal", required_argument, NULL, ARG_FORWARD_JOURNAL }, - { "pass-ssh-key", required_argument, NULL, ARG_PASS_SSH_KEY }, - { "ssh-key-type", required_argument, NULL, ARG_SSH_KEY_TYPE }, - { "set-credential", required_argument, NULL, ARG_SET_CREDENTIAL }, - { "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL }, - { "firmware", required_argument, NULL, ARG_FIRMWARE }, - { "firmware-features", required_argument, NULL, ARG_FIRMWARE_FEATURES }, - { "discard-disk", required_argument, NULL, ARG_DISCARD_DISK }, - { "background", required_argument, NULL, ARG_BACKGROUND }, - { "smbios11", required_argument, NULL, 's' }, - { "grow-image", required_argument, NULL, 'G' }, - { "tpm-state", required_argument, NULL, ARG_TPM_STATE }, - { "efi-nvram-template", required_argument, NULL, ARG_EFI_NVRAM_TEMPLATE }, - { "efi-nvram-state", required_argument, NULL, ARG_EFI_NVRAM_STATE }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "property", required_argument, NULL, ARG_PROPERTY }, - { "notify-ready", required_argument, NULL, ARG_NOTIFY_READY }, - { "bind-user", required_argument, NULL, ARG_BIND_USER }, - { "bind-user-shell", required_argument, NULL, ARG_BIND_USER_SHELL }, - { "bind-user-group", required_argument, NULL, ARG_BIND_USER_GROUP }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - {} - }; - - int c; - assert(argc >= 0); assert(argv); - optind = 0; - while ((c = getopt_long(argc, argv, "+hD:i:xM:nqs:G:S:", options, NULL)) >= 0) + OptionParser state = { argc, argv, /* stop_at_first_nonoption= */ true }; + const Option *current; + const char *arg; + + FOREACH_OPTION_FULL(&state, c, ¤t, &arg, /* on_error= */ return c) switch (c) { - case 'h': + + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'q': + OPTION('q', "quiet", NULL, "Do not show status information"): arg_quiet = true; break; - case 'D': - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_directory); + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; + + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + OPTION_LONG("system", NULL, "Run in the system service manager scope"): + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; + + OPTION_LONG("user", NULL, "Run in the user service manager scope"): + arg_runtime_scope = RUNTIME_SCOPE_USER; + break; + + OPTION_GROUP("Image"): + break; + + OPTION('D', "directory", "PATH", "Root directory for the VM"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_directory); if (r < 0) return r; + break; + OPTION('x', "ephemeral", NULL, "Run VM with snapshot of the disk or directory"): + arg_ephemeral = true; break; - case 'i': - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image); + OPTION('i', "image", "FILE|DEVICE", "Root file system disk image or device for the VM"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image); if (r < 0) return r; - break; - case ARG_IMAGE_FORMAT: - arg_image_format = image_format_from_string(optarg); + OPTION_LONG("image-format", "FORMAT", "Specify disk image format (raw, qcow2; default: raw)"): + arg_image_format = image_format_from_string(arg); if (arg_image_format < 0) return log_error_errno(arg_image_format, - "Invalid image format: %s", optarg); + "Invalid image format: %s", arg); break; - case ARG_IMAGE_DISK_TYPE: - arg_image_disk_type = disk_type_from_string(optarg); + OPTION_LONG("image-disk-type", "TYPE", + "Specify disk type (virtio-blk, virtio-scsi, nvme, scsi-cd; default: virtio-blk)"): + arg_image_disk_type = disk_type_from_string(arg); if (arg_image_disk_type < 0) return log_error_errno(arg_image_disk_type, - "Invalid image disk type: %s", optarg); - break; - - case 'M': - if (isempty(optarg)) - arg_machine = mfree(arg_machine); - else { - if (!hostname_is_valid(optarg, 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid machine name: %s", optarg); - - r = free_and_strdup(&arg_machine, optarg); - if (r < 0) - return log_oom(); - } - break; - - case 'x': - arg_ephemeral = true; + "Invalid image disk type: %s", arg); break; - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; + OPTION_GROUP("Host Configuration"): break; - case ARG_CPUS: - r = free_and_strdup_warn(&arg_cpus, optarg); + OPTION_LONG("cpus", "CPUS", "Configure number of CPUs in guest"): {} + OPTION_LONG("qemu-smp", "CPUS", /* help= */ NULL): /* Compat alias */ + r = free_and_strdup_warn(&arg_cpus, arg); if (r < 0) return r; break; - case ARG_RAM: - r = parse_ram(optarg); + OPTION_LONG("ram", "BYTES[:MAXBYTES[:SLOTS]]", + "Configure guest's RAM size (and max/slots for hotplug)"): {} + OPTION_LONG("qemu-mem", "BYTES", /* help= */ NULL): /* Compat alias */ + r = parse_ram(arg); if (r < 0) return r; break; - case ARG_KVM: - r = parse_tristate_argument_with_auto("--kvm=", optarg, &arg_kvm); + OPTION_LONG("kvm", "BOOL", "Enable use of KVM"): {} + OPTION_LONG("qemu-kvm", "BOOL", /* help= */ NULL): /* Compat alias */ + r = parse_tristate_argument_with_auto("--kvm=", arg, &arg_kvm); if (r < 0) return r; break; - case ARG_VSOCK: - r = parse_tristate_argument_with_auto("--vsock=", optarg, &arg_vsock); + OPTION_LONG("vsock", "BOOL", "Override autodetection of VSOCK support"): {} + OPTION_LONG("qemu-vsock", "BOOL", /* help= */ NULL): /* Compat alias */ + r = parse_tristate_argument_with_auto("--vsock=", arg, &arg_vsock); if (r < 0) return r; break; - case ARG_VSOCK_CID: - if (isempty(optarg)) + OPTION_LONG("vsock-cid", "CID", "Specify the CID to use for the guest's VSOCK support"): + if (isempty(arg)) arg_vsock_cid = VMADDR_CID_ANY; else { unsigned cid; - r = vsock_parse_cid(optarg, &cid); + r = vsock_parse_cid(arg, &cid); if (r < 0) - return log_error_errno(r, "Failed to parse --vsock-cid: %s", optarg); + return log_error_errno(r, "Failed to parse --vsock-cid: %s", arg); if (!VSOCK_CID_IS_REGULAR(cid)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified CID is not regular, refusing: %u", cid); @@ -591,140 +437,97 @@ static int parse_argv(int argc, char *argv[]) { } break; - case ARG_TPM: - r = parse_tristate_argument_with_auto("--tpm=", optarg, &arg_tpm); - if (r < 0) - return r; - break; - - case ARG_LINUX: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_linux); + OPTION_LONG("tpm", "BOOL", "Enable use of a virtual TPM"): + r = parse_tristate_argument_with_auto("--tpm=", arg, &arg_tpm); if (r < 0) return r; break; - case ARG_INITRD: { - _cleanup_free_ char *initrd_path = NULL; - r = parse_path_argument(optarg, /* suppress_root= */ false, &initrd_path); - if (r < 0) - return r; - - r = strv_consume(&arg_initrds, TAKE_PTR(initrd_path)); - if (r < 0) - return log_oom(); - - break; - } + OPTION_LONG("tpm-state", "off|auto|PATH", "Where to store TPM state"): + r = isempty(arg) ? false : + streq(arg, "auto") ? true : + parse_boolean(arg); + if (r >= 0) { + arg_tpm_state_mode = r ? STATE_AUTO : STATE_OFF; + arg_tpm_state_path = mfree(arg_tpm_state_path); + break; + } - case ARG_CONSOLE: - arg_console_mode = console_mode_from_string(optarg); - if (arg_console_mode < 0) - return log_error_errno(arg_console_mode, "Failed to parse specified console mode: %s", optarg); + if (!path_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path in --tpm-state= parameter: %s", arg); - break; + if (!path_is_absolute(arg) && !startswith(arg, "./")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path in --tpm-state= parameter must be absolute or start with './': %s", arg); - case ARG_CONSOLE_TRANSPORT: - arg_console_transport = console_transport_from_string(optarg); - if (arg_console_transport < 0) - return log_error_errno(arg_console_transport, "Failed to parse specified console transport: %s", optarg); + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm_state_path); + if (r < 0) + return r; + arg_tpm_state_mode = STATE_PATH; break; - case ARG_QEMU_GUI: - arg_console_mode = CONSOLE_GUI; - break; + OPTION_LONG("efi-nvram-template", "PATH", "Set the path to the EFI NVRAM template file to use"): + if (!isempty(arg) && !path_is_absolute(arg) && !startswith(arg, "./")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Absolute path or path starting with './' required."); - case 'n': - arg_network_stack = NETWORK_STACK_TAP; + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_efi_nvram_template); + if (r < 0) + return r; break; - case ARG_NETWORK_USER_MODE: - arg_network_stack = NETWORK_STACK_USER; - break; + OPTION_LONG("efi-nvram-state", "off|auto|PATH", "Where to store EFI Variable NVRAM state"): + r = isempty(arg) ? false : + streq(arg, "auto") ? true : + parse_boolean(arg); + if (r >= 0) { + arg_efi_nvram_state_mode = r ? STATE_AUTO : STATE_OFF; + arg_efi_nvram_state_path = mfree(arg_efi_nvram_state_path); + break; + } - case ARG_UUID: - r = id128_from_string_nonzero(optarg, &arg_uuid); - if (r == -ENXIO) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Machine UUID may not be all zeroes."); - if (r < 0) - return log_error_errno(r, "Invalid UUID: %s", optarg); + if (!path_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path in --efi-nvram-state= parameter: %s", arg); - break; + if (!path_is_absolute(arg) && !startswith(arg, "./")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path in --efi-nvram-state= parameter must be absolute or start with './': %s", arg); - case ARG_REGISTER: - r = parse_tristate_argument_with_auto("--register=", optarg, &arg_register); + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_efi_nvram_state_path); if (r < 0) return r; + arg_efi_nvram_state_mode = STATE_PATH; break; - case ARG_KEEP_UNIT: - arg_keep_unit = true; - break; - - case ARG_BIND: - case ARG_BIND_RO: - r = runtime_mount_parse(&arg_runtime_mounts, optarg, c == ARG_BIND_RO); + OPTION_LONG("linux", "PATH", "Specify the linux kernel for direct kernel boot"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_linux); if (r < 0) - return log_error_errno(r, "Failed to parse --bind(-ro)= argument %s: %m", optarg); - + return r; break; - case ARG_EXTRA_DRIVE: { - ImageFormat format = IMAGE_FORMAT_RAW; - DiskType extra_disk_type = _DISK_TYPE_INVALID; - const char *dp = optarg; - - /* Parse optional colon-separated prefixes. The format and disk type - * value sets don't overlap, so they can appear in any order. */ - for (;;) { - const char *colon = strchr(dp, ':'); - if (!colon) - break; - - _cleanup_free_ char *prefix = strndup(dp, colon - dp); - if (!prefix) - return log_oom(); - - ImageFormat f = image_format_from_string(prefix); - if (f >= 0) { - format = f; - dp = colon + 1; - continue; - } - - DiskType dt = disk_type_from_string(prefix); - if (dt >= 0) { - extra_disk_type = dt; - dp = colon + 1; - continue; - } - - /* Not a recognized prefix, treat the rest as the path */ - break; - } - - _cleanup_free_ char *drive_path = NULL; - r = parse_path_argument(dp, /* suppress_root= */ false, &drive_path); + OPTION_LONG("initrd", "PATH", "Specify the initrd for direct kernel boot"): { + _cleanup_free_ char *initrd_path = NULL; + r = parse_path_argument(arg, /* suppress_root= */ false, &initrd_path); if (r < 0) return r; - if (!GREEDY_REALLOC(arg_extra_drives.drives, arg_extra_drives.n_drives + 1)) + r = strv_consume(&arg_initrds, TAKE_PTR(initrd_path)); + if (r < 0) return log_oom(); + break; + } - arg_extra_drives.drives[arg_extra_drives.n_drives++] = (ExtraDrive) { - .path = TAKE_PTR(drive_path), - .format = format, - .disk_type = extra_disk_type, - }; + OPTION('n', "network-tap", NULL, "Create a TAP device for networking"): + arg_network_stack = NETWORK_STACK_TAP; + break; + OPTION_LONG("network-user-mode", NULL, "Use user mode networking"): + arg_network_stack = NETWORK_STACK_USER; break; - } - case ARG_SECURE_BOOT: { + OPTION_LONG("secure-boot", "BOOL|auto", "Enable searching for firmware supporting SecureBoot"): { int b; - r = parse_tristate_argument_with_auto("--secure-boot=", optarg, &b); + r = parse_tristate_argument_with_auto("--secure-boot=", arg, &b); if (r < 0) return r; @@ -736,54 +539,12 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return log_oom(); } - - break; - } - - case ARG_PRIVATE_USERS: - r = parse_userns_uid_range(optarg, &arg_uid_shift, &arg_uid_range); - if (r < 0) - return r; - break; - - case ARG_FORWARD_JOURNAL: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_forward_journal); - if (r < 0) - return r; - break; - - case ARG_PASS_SSH_KEY: - r = parse_boolean_argument("--pass-ssh-key=", optarg, &arg_pass_ssh_key); - if (r < 0) - return r; - break; - - case ARG_SSH_KEY_TYPE: - if (!string_is_safe(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value for --arg-ssh-key-type=: %s", optarg); - - r = free_and_strdup_warn(&arg_ssh_key_type, optarg); - if (r < 0) - return r; - break; - - case ARG_SET_CREDENTIAL: { - r = machine_credential_set(&arg_credentials, optarg); - if (r < 0) - return r; - break; - } - - case ARG_LOAD_CREDENTIAL: { - r = machine_credential_load(&arg_credentials, optarg); - if (r < 0) - return r; - break; } - case ARG_FIRMWARE: - if (streq(optarg, "list")) { + OPTION_LONG("firmware", "PATH|list|describe", + "Select firmware definition file (or list/describe available)"): + if (streq(arg, "list")) { _cleanup_strv_free_ char **l = NULL; r = list_ovmf_config(&l); @@ -798,7 +559,7 @@ static int parse_argv(int argc, char *argv[]) { return 0; } - if (streq(optarg, "describe")) { + if (streq(arg, "describe")) { /* Handled after argument parsing so that --firmware-features= is * taken into account. */ arg_firmware = mfree(arg_firmware); @@ -808,23 +569,23 @@ static int parse_argv(int argc, char *argv[]) { arg_firmware_describe = false; - if (!isempty(optarg) && !path_is_absolute(optarg) && !startswith(optarg, "./")) + if (!isempty(arg) && !path_is_absolute(arg) && !startswith(arg, "./")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Absolute path or path starting with './' required."); - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_firmware); + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_firmware); if (r < 0) return r; - break; - case ARG_FIRMWARE_FEATURES: { - if (isempty(optarg)) { + OPTION_LONG("firmware-features", "FEATURE,...|list", + "Require/exclude specific firmware features"): { + if (isempty(arg)) { arg_firmware_features_include = set_free(arg_firmware_features_include); arg_firmware_features_exclude = set_free(arg_firmware_features_exclude); break; } - if (streq(optarg, "list")) { + if (streq(arg, "list")) { _cleanup_strv_free_ char **l = NULL; r = list_ovmf_firmware_features(&l); @@ -839,7 +600,7 @@ static int parse_argv(int argc, char *argv[]) { return 0; } - _cleanup_strv_free_ char **features = strv_split(optarg, ","); + _cleanup_strv_free_ char **features = strv_split(arg, ","); if (!features) return log_oom(); @@ -849,178 +610,271 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return log_oom(); } - break; } - case ARG_DISCARD_DISK: - r = parse_boolean_argument("--discard-disk=", optarg, &arg_discard_disk); + OPTION_LONG("discard-disk", "BOOL", "Control processing of discard requests"): + r = parse_boolean_argument("--discard-disk=", arg, &arg_discard_disk); if (r < 0) return r; break; - case ARG_BACKGROUND: - r = parse_background_argument(optarg, &arg_background); + OPTION('G', "grow-image", "BYTES", "Grow image file to specified size in bytes"): + if (isempty(arg)) { + arg_grow_image = 0; + break; + } + + r = parse_size(arg, 1024, &arg_grow_image); if (r < 0) - return r; + return log_error_errno(r, "Failed to parse --grow-image= parameter: %s", arg); + break; + + OPTION_GROUP("Execution"): break; - case 's': - if (isempty(optarg)) { + OPTION('s', "smbios11", "STRING", "Pass an arbitrary SMBIOS Type #11 string to the VM"): + if (isempty(arg)) { arg_smbios11 = strv_free(arg_smbios11); break; } - if (!utf8_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "SMBIOS Type 11 string is not UTF-8 clean, refusing: %s", optarg); + if (!utf8_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "SMBIOS Type 11 string is not UTF-8 clean, refusing: %s", arg); - if (strv_extend(&arg_smbios11, optarg) < 0) + if (strv_extend(&arg_smbios11, arg) < 0) return log_oom(); - break; - case 'G': - if (isempty(optarg)) { - arg_grow_image = 0; - break; - } - - r = parse_size(optarg, 1024, &arg_grow_image); + OPTION_LONG("notify-ready", "BOOL", "Wait for ready notification from the VM"): + r = parse_boolean_argument("--notify-ready=", arg, &arg_notify_ready); if (r < 0) - return log_error_errno(r, "Failed to parse --grow-image= parameter: %s", optarg); + return r; + break; + OPTION_GROUP("System Identity"): break; - case ARG_TPM_STATE: - r = isempty(optarg) ? false : - streq(optarg, "auto") ? true : - parse_boolean(optarg); - if (r >= 0) { - arg_tpm_state_mode = r ? STATE_AUTO : STATE_OFF; - arg_tpm_state_path = mfree(arg_tpm_state_path); - break; + OPTION('M', "machine", "NAME", "Set the machine name for the VM"): + if (isempty(arg)) + arg_machine = mfree(arg_machine); + else { + if (!hostname_is_valid(arg, 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid machine name: %s", arg); + + r = free_and_strdup(&arg_machine, arg); + if (r < 0) + return log_oom(); } + break; + + OPTION_LONG("uuid", "UUID", "Set a specific machine UUID for the VM"): + r = id128_from_string_nonzero(arg, &arg_uuid); + if (r == -ENXIO) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Machine UUID may not be all zeroes."); + if (r < 0) + return log_error_errno(r, "Invalid UUID: %s", arg); + break; - if (!path_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path in --tpm-state= parameter: %s", optarg); + OPTION_GROUP("Properties"): + break; - if (!path_is_absolute(optarg) && !startswith(optarg, "./")) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path in --tpm-state= parameter must be absolute or start with './': %s", optarg); + OPTION('S', "slice", "SLICE", "Place the VM in the specified slice"): { + _cleanup_free_ char *mangled = NULL; - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm_state_path); + r = unit_name_mangle_with_suffix(arg, /* operation= */ NULL, UNIT_NAME_MANGLE_WARN, ".slice", &mangled); if (r < 0) - return r; + return log_error_errno(r, "Failed to turn '%s' into unit name: %m", arg); - arg_tpm_state_mode = STATE_PATH; + free_and_replace(arg_slice, mangled); break; + } - case ARG_EFI_NVRAM_TEMPLATE: - if (!isempty(optarg) && !path_is_absolute(optarg) && !startswith(optarg, "./")) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Absolute path or path starting with './' required."); + OPTION_LONG("property", "NAME=VALUE", "Set scope unit property"): + if (strv_extend(&arg_property, arg) < 0) + return log_oom(); + break; - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_efi_nvram_template); + OPTION_LONG("register", "BOOLEAN", "Register VM as machine"): + r = parse_tristate_argument_with_auto("--register=", arg, &arg_register); if (r < 0) return r; - break; - case ARG_EFI_NVRAM_STATE: - r = isempty(optarg) ? false : - streq(optarg, "auto") ? true : - parse_boolean(optarg); - if (r >= 0) { - arg_efi_nvram_state_mode = r ? STATE_AUTO : STATE_OFF; - arg_efi_nvram_state_path = mfree(arg_efi_nvram_state_path); - break; - } - - if (!path_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path in --efi-nvram-state= parameter: %s", optarg); + OPTION_LONG("keep-unit", NULL, + "Do not register a scope for the machine, reuse the service unit vmspawn is running in"): + arg_keep_unit = true; + break; - if (!path_is_absolute(optarg) && !startswith(optarg, "./")) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path in --efi-nvram-state= parameter must be absolute or start with './': %s", optarg); + OPTION_GROUP("User Namespacing"): + break; - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_efi_nvram_state_path); + OPTION_LONG("private-users", "UIDBASE[:NUIDS]", + "Configure the UID/GID range to map into the virtiofsd namespace"): + r = parse_userns_uid_range(arg, &arg_uid_shift, &arg_uid_range); if (r < 0) return r; - - arg_efi_nvram_state_mode = STATE_PATH; break; - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; + OPTION_GROUP("Mounts"): break; - case 'S': { - _cleanup_free_ char *mangled = NULL; - - r = unit_name_mangle_with_suffix(optarg, /* operation= */ NULL, UNIT_NAME_MANGLE_WARN, ".slice", &mangled); + OPTION_LONG("bind", "SOURCE[:TARGET]", "Mount a file or directory from the host into the VM"): {} + OPTION_LONG("bind-ro", "SOURCE[:TARGET]", "Mount a file or directory, but read-only"): { + bool read_only = streq(current->long_code, "bind-ro"); + r = runtime_mount_parse(&arg_runtime_mounts, arg, read_only); if (r < 0) - return log_error_errno(r, "Failed to turn '%s' into unit name: %m", optarg); - - free_and_replace(arg_slice, mangled); + return log_error_errno(r, "Failed to parse --%s= argument %s: %m", current->long_code, arg); break; } - case ARG_PROPERTY: - if (strv_extend(&arg_property, optarg) < 0) - return log_oom(); + OPTION_LONG("extra-drive", "[FORMAT:][DISKTYPE:]PATH", "Adds an additional disk to the VM"): { + ImageFormat format = IMAGE_FORMAT_RAW; + DiskType extra_disk_type = _DISK_TYPE_INVALID; + const char *dp = arg; - break; + /* Parse optional colon-separated prefixes. The format and disk type + * value sets don't overlap, so they can appear in any order. */ + for (;;) { + const char *colon = strchr(dp, ':'); + if (!colon) + break; + + _cleanup_free_ char *prefix = strndup(dp, colon - dp); + if (!prefix) + return log_oom(); + + ImageFormat f = image_format_from_string(prefix); + if (f >= 0) { + format = f; + dp = colon + 1; + continue; + } + + DiskType dt = disk_type_from_string(prefix); + if (dt >= 0) { + extra_disk_type = dt; + dp = colon + 1; + continue; + } - case ARG_NOTIFY_READY: - r = parse_boolean_argument("--notify-ready=", optarg, &arg_notify_ready); + /* Not a recognized prefix, treat the rest as the path */ + break; + } + + _cleanup_free_ char *drive_path = NULL; + r = parse_path_argument(dp, /* suppress_root= */ false, &drive_path); if (r < 0) return r; + if (!GREEDY_REALLOC(arg_extra_drives.drives, arg_extra_drives.n_drives + 1)) + return log_oom(); + + arg_extra_drives.drives[arg_extra_drives.n_drives++] = (ExtraDrive) { + .path = TAKE_PTR(drive_path), + .format = format, + .disk_type = extra_disk_type, + }; break; + } - case ARG_BIND_USER: - if (!valid_user_group_name(optarg, /* flags= */ 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid user name to bind: %s", optarg); + OPTION_LONG("bind-user", "NAME", "Bind user from host to virtual machine"): + if (!valid_user_group_name(arg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid user name to bind: %s", arg); - if (strv_extend(&arg_bind_user, optarg) < 0) + if (strv_extend(&arg_bind_user, arg) < 0) return log_oom(); - break; - case ARG_BIND_USER_SHELL: { + OPTION_LONG("bind-user-shell", "BOOL|PATH", + "Configure the shell to use for --bind-user= users"): { bool copy = false; char *sh = NULL; - r = parse_user_shell(optarg, &sh, ©); + r = parse_user_shell(arg, &sh, ©); if (r == -ENOMEM) return log_oom(); if (r < 0) - return log_error_errno(r, "Invalid user shell to bind: %s", optarg); + return log_error_errno(r, "Invalid user shell to bind: %s", arg); free_and_replace(arg_bind_user_shell, sh); arg_bind_user_shell_copy = copy; - break; } - case ARG_BIND_USER_GROUP: - if (!valid_user_group_name(optarg, /* flags= */ 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bind user auxiliary group name: %s", optarg); + OPTION_LONG("bind-user-group", "GROUP", "Add an auxiliary group to --bind-user= users"): + if (!valid_user_group_name(arg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bind user auxiliary group name: %s", arg); - if (strv_extend(&arg_bind_user_groups, optarg) < 0) + if (strv_extend(&arg_bind_user_groups, arg) < 0) return log_oom(); + break; + OPTION_GROUP("Integration"): break; - case ARG_SYSTEM: - arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + OPTION_LONG("forward-journal", "FILE|DIR", "Forward the VM's journal to the host"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_forward_journal); + if (r < 0) + return r; break; - case ARG_USER: - arg_runtime_scope = RUNTIME_SCOPE_USER; + OPTION_LONG("pass-ssh-key", "BOOL", "Create an SSH key to access the VM"): + r = parse_boolean_argument("--pass-ssh-key=", arg, &arg_pass_ssh_key); + if (r < 0) + return r; break; - case '?': - return -EINVAL; + OPTION_LONG("ssh-key-type", "TYPE", "Choose what type of SSH key to pass"): + if (!string_is_safe(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value for --ssh-key-type=: %s", arg); - default: - assert_not_reached(); + r = free_and_strdup_warn(&arg_ssh_key_type, arg); + if (r < 0) + return r; + break; + + OPTION_GROUP("Input/Output"): + break; + + OPTION_LONG("console", "MODE", + "Console mode (interactive, native, gui, read-only or headless)"): + arg_console_mode = console_mode_from_string(arg); + if (arg_console_mode < 0) + return log_error_errno(arg_console_mode, "Failed to parse specified console mode: %s", arg); + break; + + OPTION_LONG("console-transport", "TRANSPORT", "Console transport (virtio or serial)"): + arg_console_transport = console_transport_from_string(arg); + if (arg_console_transport < 0) + return log_error_errno(arg_console_transport, "Failed to parse specified console transport: %s", arg); + break; + + OPTION_LONG("qemu-gui", NULL, /* help= */ NULL): /* Compat alias */ + arg_console_mode = CONSOLE_GUI; + break; + + OPTION_LONG("background", "COLOR", "Set ANSI color for background"): + r = parse_background_argument(arg, &arg_background); + if (r < 0) + return r; + break; + + OPTION_GROUP("Credentials"): + break; + + OPTION_LONG("set-credential", "ID:VALUE", "Pass a credential with literal value to the VM"): + r = machine_credential_set(&arg_credentials, arg); + if (r < 0) + return r; + break; + + OPTION_LONG("load-credential", "ID:PATH", + "Load credential for the VM from file or AF_UNIX stream socket"): + r = machine_credential_load(&arg_credentials, arg); + if (r < 0) + return r; + break; } /* Drop duplicate --bind-user= and --bind-user-group= entries */ @@ -1058,8 +912,9 @@ static int parse_argv(int argc, char *argv[]) { arg_uid_range = 0x10000; } - if (argc > optind) { - arg_kernel_cmdline_extra = strv_copy(argv + optind); + char **args = option_parser_get_args(&state); + if (!strv_isempty(args)) { + arg_kernel_cmdline_extra = strv_copy(args); if (!arg_kernel_cmdline_extra) return log_oom(); } From bf5bc9a7b26191ff4254836bd909cbb92eafe480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 7 Apr 2026 17:13:46 +0200 Subject: [PATCH 0824/1296] nspawn: convert to the new option parser Uses stop_at_first_nonoption for POSIX-style option parsing. Includes a fixup for b4df0a9ee62d553e21f3b70c28841cfd1b8736f1, where global optarg was used instead of the function param. This made no difference previously because they were always equal. Co-developed-by: Claude Opus 4.6 --- src/nspawn/nspawn.c | 1308 +++++++++++++++++-------------------------- 1 file changed, 528 insertions(+), 780 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 006e91caa914c..153babe67c01e 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -50,6 +49,7 @@ #include "fd-util.h" #include "fdset.h" #include "fileio.h" +#include "format-table.h" #include "format-util.h" #include "fs-util.h" #include "gpt.h" @@ -90,6 +90,7 @@ #include "nsresource.h" #include "os-util.h" #include "osc-context.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -346,7 +347,7 @@ static int parse_private_users( *ret_uid_shift = 0; *ret_uid_range = UINT32_C(0x10000); - } else if (streq(optarg, "managed")) { + } else if (streq(s, "managed")) { /* managed: User namespace on, and acquire it from systemd-nsresourced */ *ret_userns_mode = USER_NAMESPACE_MANAGED; *ret_uid_shift = UID_INVALID; @@ -354,7 +355,7 @@ static int parse_private_users( } else { /* anything else: User namespacing on, UID range is explicitly configured */ - r = parse_userns_uid_range(optarg, ret_uid_shift, ret_uid_range); + r = parse_userns_uid_range(s, ret_uid_shift, ret_uid_range); if (r < 0) return r; *ret_userns_mode = USER_NAMESPACE_FIXED; @@ -363,6 +364,11 @@ static int parse_private_users( return 0; } +static void unref_many_tables(Table* (*tablesp)[]) { + for (Table **t = *ASSERT_PTR(tablesp); *t; t++) + *t = table_unref(*t); +} + static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -373,166 +379,55 @@ static int help(void) { if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n" - "%5$sSpawn a command or OS in a lightweight container.%6$s\n\n" - " -h --help Show this help\n" - " --version Print version string\n" - " -q --quiet Do not show status information\n" - " --no-pager Do not pipe output into a pager\n" - " --settings=BOOLEAN Load additional settings from .nspawn file\n" - " --cleanup Clean up left-over mounts and underlying mount\n" - " points used by the container\n" - " --no-ask-password Do not prompt for password\n" - "\n%3$sImage:%4$s\n" - " -D --directory=PATH Root directory for the container\n" - " --template=PATH Initialize root directory from template directory,\n" - " if missing\n" - " -x --ephemeral Run container with snapshot of root directory, and\n" - " remove it after exit\n" - " -i --image=PATH Root file system disk image (or device node) for\n" - " the container\n" - " --image-policy=POLICY Specify disk image dissection policy\n" - " --oci-bundle=PATH OCI bundle directory\n" - " --read-only Mount the root directory read-only\n" - " --volatile[=MODE] Run the system in volatile mode\n" - " --root-hash=HASH Specify verity root hash for root disk image\n" - " --root-hash-sig=SIG Specify pkcs7 signature of root hash for verity\n" - " as a DER encoded PKCS7, either as a path to a file\n" - " or as an ASCII base64 encoded string prefixed by\n" - " 'base64:'\n" - " --verity-data=PATH Specify hash device for verity\n" - " --pivot-root=PATH[:PATH]\n" - " Pivot root to given directory in the container\n" - "\n%3$sExecution:%4$s\n" - " -a --as-pid2 Maintain a stub init as PID1, invoke binary as PID2\n" - " -b --boot Boot up full system (i.e. invoke init)\n" - " --chdir=PATH Set working directory in the container\n" - " -E --setenv=NAME[=VALUE] Pass an environment variable to PID 1\n" - " -u --uid=USER Run the command under specified user or UID\n" - " --kill-signal=SIGNAL Select signal to use for shutting down PID 1\n" - " --notify-ready=BOOLEAN Receive notifications from the child init process\n" - " --suppress-sync=BOOLEAN\n" - " Suppress any form of disk data synchronization\n" - "\n%3$sSystem Identity:%4$s\n" - " -M --machine=NAME Set the machine name for the container\n" - " --hostname=NAME Override the hostname for the container\n" - " --uuid=UUID Set a specific machine UUID for the container\n" - "\n%3$sProperties:%4$s\n" - " -S --slice=SLICE Place the container in the specified slice\n" - " --property=NAME=VALUE Set scope unit property\n" - " --register=BOOLEAN Register container as machine\n" - " --keep-unit Do not register a scope for the machine, reuse\n" - " the service unit nspawn is running in\n" - "\n%3$sUser Namespacing:%4$s\n" - " --private-users=no Run without user namespacing\n" - " --private-users=yes|pick|identity|managed\n" - " Run within user namespace, autoselect UID/GID range\n" - " --private-users=UIDBASE[:NUIDS]\n" - " Similar, but with user configured UID/GID range\n" - " --private-users-ownership=MODE\n" - " Adjust ('chown') or map ('map') OS tree ownership\n" - " to private UID/GID range\n" - " --private-users-delegate=N\n" - " Delegate N additional 64K UID/GID ranges for use\n" - " by nested containers (requires managed user\n" - " namespaces)\n" - " -U Equivalent to --private-users=pick and\n" - " --private-users-ownership=auto\n" - "\n%3$sNetworking:%4$s\n" - " --private-network Disable network in container\n" - " --network-interface=HOSTIF[:CONTAINERIF]\n" - " Assign an existing network interface to the\n" - " container\n" - " --network-macvlan=HOSTIF[:CONTAINERIF]\n" - " Create a macvlan network interface based on an\n" - " existing network interface to the container\n" - " --network-ipvlan=HOSTIF[:CONTAINERIF]\n" - " Create an ipvlan network interface based on an\n" - " existing network interface to the container\n" - " -n --network-veth Add a virtual Ethernet connection between host\n" - " and container\n" - " --network-veth-extra=HOSTIF[:CONTAINERIF]\n" - " Add an additional virtual Ethernet link between\n" - " host and container\n" - " --network-bridge=INTERFACE\n" - " Add a virtual Ethernet connection to the container\n" - " and attach it to an existing bridge on the host\n" - " --network-zone=NAME Similar, but attach the new interface to an\n" - " automatically managed bridge interface\n" - " --network-namespace-path=PATH\n" - " Set network namespace to the one represented by\n" - " the specified kernel namespace file node\n" - " -p --port=[PROTOCOL:]HOSTPORT[:CONTAINERPORT]\n" - " Expose a container IP port on the host\n" - "\n%3$sSecurity:%4$s\n" - " --capability=CAP In addition to the default, retain specified\n" - " capability\n" - " --drop-capability=CAP Drop the specified capability from the default set\n" - " --ambient-capability=CAP\n" - " Sets the specified capability for the started\n" - " process. Not useful if booting a machine.\n" - " --no-new-privileges Set PR_SET_NO_NEW_PRIVS flag for container payload\n" - " --system-call-filter=LIST|~LIST\n" - " Permit/prohibit specific system calls\n" - " -Z --selinux-context=SECLABEL\n" - " Set the SELinux security context to be used by\n" - " processes in the container\n" - " -L --selinux-apifs-context=SECLABEL\n" - " Set the SELinux security context to be used by\n" - " API/tmpfs file systems in the container\n" - "\n%3$sResources:%4$s\n" - " --rlimit=NAME=LIMIT Set a resource limit for the payload\n" - " --oom-score-adjust=VALUE\n" - " Adjust the OOM score value for the payload\n" - " --cpu-affinity=CPUS Adjust the CPU affinity of the container\n" - " --personality=ARCH Pick personality for this container\n" - "\n%3$sIntegration:%4$s\n" - " --resolv-conf=MODE Select mode of /etc/resolv.conf initialization\n" - " --timezone=MODE Select mode of /etc/localtime initialization\n" - " --link-journal=MODE Link up guest journal, one of no, auto, guest, \n" - " host, try-guest, try-host\n" - " -j Equivalent to --link-journal=try-guest\n" - "\n%3$sMounts:%4$s\n" - " --bind=PATH[:PATH[:OPTIONS]]\n" - " Bind mount a file or directory from the host into\n" - " the container\n" - " --bind-ro=PATH[:PATH[:OPTIONS]\n" - " Similar, but creates a read-only bind mount\n" - " --inaccessible=PATH Over-mount file node with inaccessible node to mask\n" - " it\n" - " --tmpfs=PATH:[OPTIONS] Mount an empty tmpfs to the specified directory\n" - " --overlay=PATH[:PATH...]:PATH\n" - " Create an overlay mount from the host to \n" - " the container\n" - " --overlay-ro=PATH[:PATH...]:PATH\n" - " Similar, but creates a read-only overlay mount\n" - " --bind-user=NAME Bind user from host to container\n" - " --bind-user-shell=BOOL|PATH\n" - " Configure the shell to use for --bind-user= users\n" - " --bind-user-group=GROUP\n" - " Add an auxiliary group to --bind-user= users\n" - "\n%3$sInput/Output:%4$s\n" - " --console=MODE Select how stdin/stdout/stderr and /dev/console are\n" - " set up for the container.\n" - " -P --pipe Equivalent to --console=pipe\n" - " --background=COLOR Set ANSI color for background\n" - "\n%3$sCredentials:%4$s\n" - " --set-credential=ID:VALUE\n" - " Pass a credential with literal value to container.\n" - " --load-credential=ID:PATH\n" - " Load credential to pass to container from file or\n" - " AF_UNIX stream socket.\n" - "\n%3$sOther:%4$s\n" - " --system Run in the system service manager scope\n" - " --user Run in the user service manager scope\n" - "\nSee the %2$s for details.\n", + static const char *groups[] = { + NULL, + "Image", + "Execution", + "System Identity", + "Properties", + "User Namespacing", + "Networking", + "Security", + "Resources", + "Integration", + "Mounts", + "Input/Output", + "Credentials", + "Other", + }; + + _cleanup_(unref_many_tables) Table* tables[ELEMENTSOF(groups) + 1] = {}; + + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + r = option_parser_get_help_table_group(groups[i], &tables[i]); + if (r < 0) + return r; + } + + (void) table_sync_column_widths(0, tables[0], tables[1], tables[2], tables[3], + tables[4], tables[5], tables[6], tables[7], + tables[8], tables[9], tables[10], tables[11], + tables[12], tables[13]); + + printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n" + "%sSpawn a command or OS in a lightweight container.%s\n\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), ansi_normal()); + r = table_print_or_warn(tables[0]); + if (r < 0) + return r; + + for (size_t i = 1; i < ELEMENTSOF(groups); i++) { + printf("\n%s%s:%s\n", ansi_underline(), groups[i], ansi_normal()); + + r = table_print_or_warn(tables[i]); + if (r < 0) + return r; + } + + printf("\nSee the %s for details.\n", link); return 0; } @@ -689,159 +584,7 @@ static int parse_environment(void) { } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_PRIVATE_NETWORK, - ARG_UUID, - ARG_READ_ONLY, - ARG_CAPABILITY, - ARG_AMBIENT_CAPABILITY, - ARG_DROP_CAPABILITY, - ARG_LINK_JOURNAL, - ARG_BIND, - ARG_BIND_RO, - ARG_TMPFS, - ARG_OVERLAY, - ARG_OVERLAY_RO, - ARG_INACCESSIBLE, - ARG_SHARE_SYSTEM, - ARG_REGISTER, - ARG_KEEP_UNIT, - ARG_NETWORK_INTERFACE, - ARG_NETWORK_MACVLAN, - ARG_NETWORK_IPVLAN, - ARG_NETWORK_BRIDGE, - ARG_NETWORK_ZONE, - ARG_NETWORK_VETH_EXTRA, - ARG_NETWORK_NAMESPACE_PATH, - ARG_PERSONALITY, - ARG_VOLATILE, - ARG_TEMPLATE, - ARG_PROPERTY, - ARG_PRIVATE_USERS, - ARG_PRIVATE_USERS_DELEGATE, - ARG_KILL_SIGNAL, - ARG_SETTINGS, - ARG_CHDIR, - ARG_PIVOT_ROOT, - ARG_PRIVATE_USERS_CHOWN, - ARG_PRIVATE_USERS_OWNERSHIP, - ARG_NOTIFY_READY, - ARG_ROOT_HASH, - ARG_ROOT_HASH_SIG, - ARG_VERITY_DATA, - ARG_SYSTEM_CALL_FILTER, - ARG_RLIMIT, - ARG_HOSTNAME, - ARG_NO_NEW_PRIVILEGES, - ARG_OOM_SCORE_ADJUST, - ARG_CPU_AFFINITY, - ARG_RESOLV_CONF, - ARG_TIMEZONE, - ARG_CONSOLE, - ARG_PIPE, - ARG_OCI_BUNDLE, - ARG_NO_PAGER, - ARG_SET_CREDENTIAL, - ARG_LOAD_CREDENTIAL, - ARG_BIND_USER, - ARG_BIND_USER_SHELL, - ARG_BIND_USER_GROUP, - ARG_SUPPRESS_SYNC, - ARG_IMAGE_POLICY, - ARG_BACKGROUND, - ARG_CLEANUP, - ARG_NO_ASK_PASSWORD, - ARG_MSTACK, - ARG_USER, - ARG_SYSTEM, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "directory", required_argument, NULL, 'D' }, - { "template", required_argument, NULL, ARG_TEMPLATE }, - { "ephemeral", no_argument, NULL, 'x' }, - { "uid", required_argument, NULL, 'u' }, - { "user", optional_argument, NULL, ARG_USER }, - { "private-network", no_argument, NULL, ARG_PRIVATE_NETWORK }, - { "as-pid2", no_argument, NULL, 'a' }, - { "boot", no_argument, NULL, 'b' }, - { "uuid", required_argument, NULL, ARG_UUID }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - { "capability", required_argument, NULL, ARG_CAPABILITY }, - { "ambient-capability", required_argument, NULL, ARG_AMBIENT_CAPABILITY }, - { "drop-capability", required_argument, NULL, ARG_DROP_CAPABILITY }, - { "no-new-privileges", required_argument, NULL, ARG_NO_NEW_PRIVILEGES }, - { "link-journal", required_argument, NULL, ARG_LINK_JOURNAL }, - { "bind", required_argument, NULL, ARG_BIND }, - { "bind-ro", required_argument, NULL, ARG_BIND_RO }, - { "tmpfs", required_argument, NULL, ARG_TMPFS }, - { "overlay", required_argument, NULL, ARG_OVERLAY }, - { "overlay-ro", required_argument, NULL, ARG_OVERLAY_RO }, - { "inaccessible", required_argument, NULL, ARG_INACCESSIBLE }, - { "machine", required_argument, NULL, 'M' }, - { "hostname", required_argument, NULL, ARG_HOSTNAME }, - { "slice", required_argument, NULL, 'S' }, - { "setenv", required_argument, NULL, 'E' }, - { "selinux-context", required_argument, NULL, 'Z' }, - { "selinux-apifs-context", required_argument, NULL, 'L' }, - { "quiet", no_argument, NULL, 'q' }, - { "share-system", no_argument, NULL, ARG_SHARE_SYSTEM }, /* not documented */ - { "register", required_argument, NULL, ARG_REGISTER }, - { "keep-unit", no_argument, NULL, ARG_KEEP_UNIT }, - { "network-interface", required_argument, NULL, ARG_NETWORK_INTERFACE }, - { "network-macvlan", required_argument, NULL, ARG_NETWORK_MACVLAN }, - { "network-ipvlan", required_argument, NULL, ARG_NETWORK_IPVLAN }, - { "network-veth", no_argument, NULL, 'n' }, - { "network-veth-extra", required_argument, NULL, ARG_NETWORK_VETH_EXTRA }, - { "network-bridge", required_argument, NULL, ARG_NETWORK_BRIDGE }, - { "network-zone", required_argument, NULL, ARG_NETWORK_ZONE }, - { "network-namespace-path", required_argument, NULL, ARG_NETWORK_NAMESPACE_PATH }, - { "personality", required_argument, NULL, ARG_PERSONALITY }, - { "image", required_argument, NULL, 'i' }, - { "volatile", optional_argument, NULL, ARG_VOLATILE }, - { "port", required_argument, NULL, 'p' }, - { "property", required_argument, NULL, ARG_PROPERTY }, - { "private-users", optional_argument, NULL, ARG_PRIVATE_USERS }, - { "private-users-chown", optional_argument, NULL, ARG_PRIVATE_USERS_CHOWN }, /* obsolete */ - { "private-users-ownership",required_argument, NULL, ARG_PRIVATE_USERS_OWNERSHIP}, - { "private-users-delegate", required_argument, NULL, ARG_PRIVATE_USERS_DELEGATE }, - { "kill-signal", required_argument, NULL, ARG_KILL_SIGNAL }, - { "settings", required_argument, NULL, ARG_SETTINGS }, - { "chdir", required_argument, NULL, ARG_CHDIR }, - { "pivot-root", required_argument, NULL, ARG_PIVOT_ROOT }, - { "notify-ready", required_argument, NULL, ARG_NOTIFY_READY }, - { "root-hash", required_argument, NULL, ARG_ROOT_HASH }, - { "root-hash-sig", required_argument, NULL, ARG_ROOT_HASH_SIG }, - { "verity-data", required_argument, NULL, ARG_VERITY_DATA }, - { "system-call-filter", required_argument, NULL, ARG_SYSTEM_CALL_FILTER }, - { "rlimit", required_argument, NULL, ARG_RLIMIT }, - { "oom-score-adjust", required_argument, NULL, ARG_OOM_SCORE_ADJUST }, - { "cpu-affinity", required_argument, NULL, ARG_CPU_AFFINITY }, - { "resolv-conf", required_argument, NULL, ARG_RESOLV_CONF }, - { "timezone", required_argument, NULL, ARG_TIMEZONE }, - { "console", required_argument, NULL, ARG_CONSOLE }, - { "pipe", no_argument, NULL, ARG_PIPE }, - { "oci-bundle", required_argument, NULL, ARG_OCI_BUNDLE }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "set-credential", required_argument, NULL, ARG_SET_CREDENTIAL }, - { "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL }, - { "bind-user", required_argument, NULL, ARG_BIND_USER }, - { "bind-user-shell", required_argument, NULL, ARG_BIND_USER_SHELL }, - { "bind-user-group", required_argument, NULL, ARG_BIND_USER_GROUP }, - { "suppress-sync", required_argument, NULL, ARG_SUPPRESS_SYNC }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "background", required_argument, NULL, ARG_BACKGROUND }, - { "cleanup", no_argument, NULL, ARG_CLEANUP }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "mstack", required_argument, NULL, ARG_MSTACK }, - { "system", no_argument, NULL, ARG_SYSTEM }, - {} - }; - - int c, r; + int r; uint64_t plus = 0, minus = 0; bool mask_all_settings = false, mask_no_settings = false; @@ -885,563 +628,519 @@ static int parse_argv(int argc, char *argv[]) { argc--; } - /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long() - * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */ - optind = 0; - while ((c = getopt_long(argc, argv, "+hD:u:abL:M:jS:Z:qi:xp:nUE:P", options, NULL)) >= 0) + OptionParser state = { argc, argv, /* stop_at_first_nonoption= */ true }; + const Option *opt; + const char *arg; + + FOREACH_OPTION_FULL(&state, c, &opt, &arg, /* on_error= */ return c) { switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'D': - r = parse_path_argument(optarg, false, &arg_directory); - if (r < 0) - return r; + OPTION('q', "quiet", NULL, "Do not show status information"): + arg_quiet = true; + break; - arg_settings_mask |= SETTING_DIRECTORY; + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; break; - case ARG_TEMPLATE: - r = parse_path_argument(optarg, false, &arg_template); - if (r < 0) - return r; + OPTION_LONG("settings", "BOOLEAN", "Load additional settings from .nspawn file"): + /* no → do not read files + * yes → read files, do not override cmdline, trust only subset + * override → read files, override cmdline, trust only subset + * trusted → read files, do not override cmdline, trust all + */ - arg_settings_mask |= SETTING_DIRECTORY; + r = parse_boolean(arg); + if (r < 0) { + if (streq(arg, "trusted")) { + mask_all_settings = false; + mask_no_settings = false; + arg_settings_trusted = true; + + } else if (streq(arg, "override")) { + mask_all_settings = false; + mask_no_settings = true; + arg_settings_trusted = -1; + } else + return log_error_errno(r, "Failed to parse --settings= argument: %s", arg); + } else if (r > 0) { + /* yes */ + mask_all_settings = false; + mask_no_settings = false; + arg_settings_trusted = -1; + } else { + /* no */ + mask_all_settings = true; + mask_no_settings = false; + arg_settings_trusted = false; + } break; - case 'i': - r = parse_path_argument(optarg, false, &arg_image); - if (r < 0) - return r; + OPTION_LONG("cleanup", NULL, + "Clean up left-over mounts and underlying mount points used by the container"): + arg_cleanup = true; + break; - arg_settings_mask |= SETTING_DIRECTORY; + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; break; - case ARG_MSTACK: - r = parse_path_argument(optarg, false, &arg_mstack); + OPTION_GROUP("Image"): {} + + OPTION('D', "directory", "PATH", "Root directory for the container"): + r = parse_path_argument(arg, false, &arg_directory); if (r < 0) return r; - arg_settings_mask |= SETTING_DIRECTORY; break; - case ARG_OCI_BUNDLE: - r = parse_path_argument(optarg, false, &arg_oci_bundle); + OPTION_LONG("template", "PATH", + "Initialize root directory from template directory, if missing"): + r = parse_path_argument(arg, false, &arg_template); if (r < 0) return r; - + arg_settings_mask |= SETTING_DIRECTORY; break; - case 'x': + OPTION('x', "ephemeral", NULL, + "Run container with snapshot of root directory, and remove it after exit"): arg_ephemeral = true; arg_settings_mask |= SETTING_EPHEMERAL; break; - case 'u': - r = free_and_strdup(&arg_user, optarg); + OPTION('i', "image", "PATH", + "Root file system disk image (or device node) for the container"): + r = parse_path_argument(arg, false, &arg_image); if (r < 0) - return log_oom(); + return r; + arg_settings_mask |= SETTING_DIRECTORY; + break; - arg_settings_mask |= SETTING_USER; + OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): + r = parse_image_policy_argument(arg, &arg_image_policy); + if (r < 0) + return r; break; - case ARG_NETWORK_ZONE: { - _cleanup_free_ char *j = NULL; + OPTION_LONG("mstack", "PATH", /* help= */ NULL): + r = parse_path_argument(arg, false, &arg_mstack); + if (r < 0) + return r; + arg_settings_mask |= SETTING_DIRECTORY; + break; - j = strjoin("vz-", optarg); - if (!j) - return log_oom(); + OPTION_LONG("oci-bundle", "PATH", "OCI bundle directory"): + r = parse_path_argument(arg, false, &arg_oci_bundle); + if (r < 0) + return r; + break; - if (!ifname_valid(j)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Network zone name not valid: %s", j); + OPTION_LONG("read-only", NULL, "Mount the root directory read-only"): + arg_read_only = true; + arg_settings_mask |= SETTING_READ_ONLY; + break; - free_and_replace(arg_network_zone, j); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "volatile", "MODE", "Run the system in volatile mode"): + if (!arg) + arg_volatile_mode = VOLATILE_YES; + else if (streq(arg, "help")) + return DUMP_STRING_TABLE(volatile_mode, VolatileMode, _VOLATILE_MODE_MAX); + else { + VolatileMode m; - arg_network_veth = true; - arg_private_network = true; - arg_settings_mask |= SETTING_NETWORK; + m = volatile_mode_from_string(arg); + if (m < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse --volatile= argument: %s", arg); + else + arg_volatile_mode = m; + } + arg_settings_mask |= SETTING_VOLATILE_MODE; break; - } - - case ARG_NETWORK_BRIDGE: - if (!ifname_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Bridge interface name not valid: %s", optarg); + OPTION_LONG("root-hash", "HASH", "Specify verity root hash for root disk image"): { + _cleanup_(iovec_done) struct iovec k = {}; - r = free_and_strdup(&arg_network_bridge, optarg); + r = unhexmem(arg, &k.iov_base, &k.iov_len); if (r < 0) - return log_oom(); + return log_error_errno(r, "Failed to parse root hash: %s", arg); + if (k.iov_len < sizeof(sd_id128_t)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Root hash must be at least 128-bit long: %s", arg); - _fallthrough_; - case 'n': - arg_network_veth = true; - arg_private_network = true; - arg_settings_mask |= SETTING_NETWORK; + iovec_done(&arg_verity_settings.root_hash); + arg_verity_settings.root_hash = TAKE_STRUCT(k); break; + } - case ARG_NETWORK_VETH_EXTRA: - r = veth_extra_parse(&arg_network_veth_extra, optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --network-veth-extra= parameter: %s", optarg); + OPTION_LONG("root-hash-sig", "SIG", + "Specify pkcs7 signature of root hash for verity"): { + _cleanup_(iovec_done) struct iovec p = {}; + const char *value; - arg_private_network = true; - arg_settings_mask |= SETTING_NETWORK; - break; + if ((value = startswith(arg, "base64:"))) { + r = unbase64mem(value, &p.iov_base, &p.iov_len); + if (r < 0) + return log_error_errno(r, "Failed to parse root hash signature '%s': %m", arg); - case ARG_NETWORK_INTERFACE: - r = interface_pair_parse(&arg_network_interfaces, optarg); - if (r < 0) - return r; + } else { + r = read_full_file(arg, (char**) &p.iov_base, &p.iov_len); + if (r < 0) + return log_error_errno(r, "Failed to parse root hash signature file '%s': %m", arg); + } - arg_private_network = true; - arg_settings_mask |= SETTING_NETWORK; + iovec_done(&arg_verity_settings.root_hash_sig); + arg_verity_settings.root_hash_sig = TAKE_STRUCT(p); break; + } - case ARG_NETWORK_MACVLAN: - r = macvlan_pair_parse(&arg_network_macvlan, optarg); + OPTION_LONG("verity-data", "PATH", "Specify hash device for verity"): + r = parse_path_argument(arg, false, &arg_verity_settings.data_path); if (r < 0) return r; - - arg_private_network = true; - arg_settings_mask |= SETTING_NETWORK; break; - case ARG_NETWORK_IPVLAN: - r = ipvlan_pair_parse(&arg_network_ipvlan, optarg); + OPTION_LONG("pivot-root", "PATH[:PATH]", + "Pivot root to given directory in the container"): + r = pivot_root_parse(&arg_pivot_root_new, &arg_pivot_root_old, arg); if (r < 0) - return r; - - _fallthrough_; - case ARG_PRIVATE_NETWORK: - arg_private_network = true; - arg_settings_mask |= SETTING_NETWORK; + return log_error_errno(r, "Failed to parse --pivot-root= argument %s: %m", arg); + arg_settings_mask |= SETTING_PIVOT_ROOT; break; - case ARG_NETWORK_NAMESPACE_PATH: - r = parse_path_argument(optarg, false, &arg_network_namespace_path); - if (r < 0) - return r; + OPTION_GROUP("Execution"): {} - arg_settings_mask |= SETTING_NETWORK; - break; - - case 'b': - if (arg_start_mode == START_PID2) + OPTION('a', "as-pid2", NULL, "Maintain a stub init as PID1, invoke binary as PID2"): + if (arg_start_mode == START_BOOT) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--boot and --as-pid2 may not be combined."); - - arg_start_mode = START_BOOT; + arg_start_mode = START_PID2; arg_settings_mask |= SETTING_START_MODE; break; - case 'a': - if (arg_start_mode == START_BOOT) + OPTION('b', "boot", NULL, "Boot up full system (i.e. invoke init)"): + if (arg_start_mode == START_PID2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--boot and --as-pid2 may not be combined."); - - arg_start_mode = START_PID2; + arg_start_mode = START_BOOT; arg_settings_mask |= SETTING_START_MODE; break; - case ARG_UUID: - r = id128_from_string_nonzero(optarg, &arg_uuid); - if (r == -ENXIO) + OPTION_LONG("chdir", "PATH", "Set working directory in the container"): { + _cleanup_free_ char *wd = NULL; + + if (!path_is_absolute(arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Machine UUID may not be all zeroes."); - if (r < 0) - return log_error_errno(r, "Invalid UUID: %s", optarg); + "Working directory %s is not an absolute path.", arg); - arg_settings_mask |= SETTING_MACHINE_ID; - break; + r = path_simplify_alloc(arg, &wd); + if (r < 0) + return log_error_errno(r, "Failed to simplify path %s: %m", arg); - case 'S': { - _cleanup_free_ char *mangled = NULL; + if (!path_is_normalized(wd)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Working directory path is not normalized: %s", wd); - r = unit_name_mangle_with_suffix(optarg, NULL, UNIT_NAME_MANGLE_WARN, ".slice", &mangled); - if (r < 0) - return log_oom(); + if (path_below_api_vfs(wd)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Working directory is below API VFS, refusing: %s", wd); - free_and_replace(arg_slice, mangled); - arg_settings_mask |= SETTING_SLICE; + free_and_replace(arg_chdir, wd); + arg_settings_mask |= SETTING_WORKING_DIRECTORY; break; } - case 'M': - if (!isempty(optarg) && !hostname_is_valid(optarg, /* flags= */ 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid machine name: %s", optarg); - - r = free_and_strdup_warn(&arg_machine, optarg); + OPTION('E', "setenv", "NAME[=VALUE]", "Pass an environment variable to PID 1"): + r = strv_env_replace_strdup_passthrough(&arg_setenv, arg); if (r < 0) - return r; + return log_error_errno(r, "Cannot assign environment variable %s: %m", arg); + arg_settings_mask |= SETTING_ENVIRONMENT; break; - case ARG_HOSTNAME: - if (!isempty(optarg) && !hostname_is_valid(optarg, /* flags= */ 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid hostname: %s", optarg); - - r = free_and_strdup_warn(&arg_hostname, optarg); + OPTION('u', "uid", "USER", "Run the command under specified user or UID"): + r = free_and_strdup(&arg_user, arg); if (r < 0) - return r; - - arg_settings_mask |= SETTING_HOSTNAME; - break; - - case 'Z': - arg_selinux_context = optarg; + return log_oom(); + arg_settings_mask |= SETTING_USER; break; - case 'L': - arg_selinux_apifs_context = optarg; - break; + OPTION_LONG("kill-signal", "SIGNAL", "Select signal to use for shutting down PID 1"): + if (streq(arg, "help")) + return DUMP_STRING_TABLE(signal, int, _NSIG); - case ARG_READ_ONLY: - arg_read_only = true; - arg_settings_mask |= SETTING_READ_ONLY; + arg_kill_signal = signal_from_string(arg); + if (arg_kill_signal < 0) + return log_error_errno(arg_kill_signal, "Cannot parse signal: %s", arg); + arg_settings_mask |= SETTING_KILL_SIGNAL; break; - case ARG_AMBIENT_CAPABILITY: { - uint64_t m; - r = parse_capability_spec(optarg, &m); - if (r <= 0) + OPTION_LONG("notify-ready", "BOOLEAN", "Receive notifications from the child init process"): + r = parse_boolean_argument("--notify-ready=", arg, &arg_notify_ready); + if (r < 0) return r; - arg_caps_ambient |= m; - arg_settings_mask |= SETTING_CAPABILITY; + arg_settings_mask |= SETTING_NOTIFY_READY; break; - } - case ARG_CAPABILITY: - case ARG_DROP_CAPABILITY: { - uint64_t m; - r = parse_capability_spec(optarg, &m); - if (r <= 0) - return r; - if (c == ARG_CAPABILITY) - plus |= m; - else - minus |= m; - arg_settings_mask |= SETTING_CAPABILITY; - break; - } - case ARG_NO_NEW_PRIVILEGES: - r = parse_boolean_argument("--no-new-privileges=", optarg, &arg_no_new_privileges); + OPTION_LONG("suppress-sync", "BOOLEAN", "Suppress any form of disk data synchronization"): + r = parse_boolean_argument("--suppress-sync=", arg, &arg_suppress_sync); if (r < 0) return r; - - arg_settings_mask |= SETTING_NO_NEW_PRIVILEGES; - break; - - case 'j': - arg_link_journal = LINK_GUEST; - arg_link_journal_try = true; - arg_settings_mask |= SETTING_LINK_JOURNAL; + arg_settings_mask |= SETTING_SUPPRESS_SYNC; break; - case ARG_LINK_JOURNAL: - r = parse_link_journal(optarg, &arg_link_journal, &arg_link_journal_try); - if (r < 0) - return log_error_errno(r, "Failed to parse link journal mode %s", optarg); + OPTION_GROUP("System Identity"): {} - arg_settings_mask |= SETTING_LINK_JOURNAL; - break; - - case ARG_BIND: - case ARG_BIND_RO: - r = bind_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, optarg, c == ARG_BIND_RO); + OPTION('M', "machine", "NAME", "Set the machine name for the container"): + if (!isempty(arg) && !hostname_is_valid(arg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid machine name: %s", arg); + r = free_and_strdup_warn(&arg_machine, arg); if (r < 0) - return log_error_errno(r, "Failed to parse --bind(-ro)= argument %s: %m", optarg); - - arg_settings_mask |= SETTING_CUSTOM_MOUNTS; + return r; break; - case ARG_TMPFS: - r = tmpfs_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, optarg); + OPTION_LONG("hostname", "NAME", "Override the hostname for the container"): + if (!isempty(arg) && !hostname_is_valid(arg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid hostname: %s", arg); + r = free_and_strdup_warn(&arg_hostname, arg); if (r < 0) - return log_error_errno(r, "Failed to parse --tmpfs= argument %s: %m", optarg); - - arg_settings_mask |= SETTING_CUSTOM_MOUNTS; + return r; + arg_settings_mask |= SETTING_HOSTNAME; break; - case ARG_OVERLAY: - case ARG_OVERLAY_RO: - r = overlay_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, optarg, c == ARG_OVERLAY_RO); - if (r == -EADDRNOTAVAIL) - return log_error_errno(r, "--overlay(-ro)= needs at least two colon-separated directories specified."); + OPTION_LONG("uuid", "UUID", "Set a specific machine UUID for the container"): + r = id128_from_string_nonzero(arg, &arg_uuid); + if (r == -ENXIO) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Machine UUID may not be all zeroes."); if (r < 0) - return log_error_errno(r, "Failed to parse --overlay(-ro)= argument %s: %m", optarg); - - arg_settings_mask |= SETTING_CUSTOM_MOUNTS; + return log_error_errno(r, "Invalid UUID: %s", arg); + arg_settings_mask |= SETTING_MACHINE_ID; break; - case ARG_INACCESSIBLE: - r = inaccessible_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --inaccessible= argument %s: %m", optarg); + OPTION_GROUP("Properties"): {} - arg_settings_mask |= SETTING_CUSTOM_MOUNTS; - break; + OPTION('S', "slice", "SLICE", "Place the container in the specified slice"): { + _cleanup_free_ char *mangled = NULL; - case 'E': - r = strv_env_replace_strdup_passthrough(&arg_setenv, optarg); + r = unit_name_mangle_with_suffix(arg, NULL, UNIT_NAME_MANGLE_WARN, ".slice", &mangled); if (r < 0) - return log_error_errno(r, "Cannot assign environment variable %s: %m", optarg); - - arg_settings_mask |= SETTING_ENVIRONMENT; - break; + return log_oom(); - case 'q': - arg_quiet = true; + free_and_replace(arg_slice, mangled); + arg_settings_mask |= SETTING_SLICE; break; + } - case ARG_SHARE_SYSTEM: - /* We don't officially support this anymore, except for compat reasons. People should use the - * $SYSTEMD_NSPAWN_SHARE_* environment variables instead. */ - log_warning("Please do not use --share-system anymore, use $SYSTEMD_NSPAWN_SHARE_* instead."); - arg_clone_ns_flags = 0; + OPTION_LONG("property", "NAME=VALUE", "Set scope unit property"): + if (strv_extend(&arg_property, arg) < 0) + return log_oom(); break; - case ARG_REGISTER: - r = parse_tristate_argument_with_auto("--register=", optarg, &arg_register); + OPTION_LONG("register", "BOOLEAN", "Register container as machine"): + r = parse_tristate_argument_with_auto("--register=", arg, &arg_register); if (r < 0) return r; - break; - case ARG_KEEP_UNIT: + OPTION_LONG("keep-unit", NULL, + "Do not register a scope for the machine, reuse the service unit nspawn is running in"): arg_keep_unit = true; break; - case ARG_PERSONALITY: + OPTION_GROUP("User Namespacing"): {} - arg_personality = personality_from_string(optarg); - if (arg_personality == PERSONALITY_INVALID) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown or unsupported personality '%s'.", optarg); - - arg_settings_mask |= SETTING_PERSONALITY; - break; - - case ARG_VOLATILE: - - if (!optarg) - arg_volatile_mode = VOLATILE_YES; - else if (streq(optarg, "help")) - return DUMP_STRING_TABLE(volatile_mode, VolatileMode, _VOLATILE_MODE_MAX); - else { - VolatileMode m; - - m = volatile_mode_from_string(optarg); - if (m < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse --volatile= argument: %s", optarg); - else - arg_volatile_mode = m; - } - - arg_settings_mask |= SETTING_VOLATILE_MODE; + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "private-users", "MODE", + "Run within user namespace, configure UID/GID range"): + r = parse_private_users(arg, &arg_userns_mode, &arg_uid_shift, &arg_uid_range); + if (r < 0) + return r; + arg_settings_mask |= SETTING_USERNS; break; - case 'p': - r = expose_port_parse(&arg_expose_ports, optarg); - if (r == -EEXIST) - return log_error_errno(r, "Duplicate port specification: %s", optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse host port %s: %m", optarg); + OPTION_LONG("private-users-ownership", "MODE", + "Adjust ('chown') or map ('map') OS tree ownership to private UID/GID range"): + if (streq(arg, "help")) + return DUMP_STRING_TABLE(user_namespace_ownership, UserNamespaceOwnership, _USER_NAMESPACE_OWNERSHIP_MAX); - arg_settings_mask |= SETTING_EXPOSE_PORTS; + arg_userns_ownership = user_namespace_ownership_from_string(arg); + if (arg_userns_ownership < 0) + return log_error_errno(arg_userns_ownership, "Cannot parse --private-users-ownership= value: %s", arg); + arg_settings_mask |= SETTING_USERNS; break; - case ARG_PROPERTY: - if (strv_extend(&arg_property, optarg) < 0) - return log_oom(); - + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "private-users-chown", "MODE", /* help= */ NULL): /* obsolete */ + arg_userns_ownership = USER_NAMESPACE_OWNERSHIP_CHOWN; + arg_settings_mask |= SETTING_USERNS; break; - case ARG_PRIVATE_USERS: - r = parse_private_users(optarg, &arg_userns_mode, &arg_uid_shift, &arg_uid_range); + OPTION_LONG("private-users-delegate", "N", + "Delegate N additional 64K UID/GID ranges for use by nested containers"): + r = safe_atou(arg, &arg_delegate_container_ranges); if (r < 0) - return r; - + return log_error_errno(r, "Failed to parse --private-users-delegate= parameter: %s", arg); arg_settings_mask |= SETTING_USERNS; break; - case 'U': + OPTION_SHORT('U', NULL, + "Equivalent to --private-users=pick and --private-users-ownership=auto"): if (userns_supported()) { - /* Note that arg_userns_ownership is implied by USER_NAMESPACE_PICK further down. - * We use _USER_NAMESPACE_MODE_INVALID as a marker so that the final resolution - * (PICK vs MANAGED) is deferred to after the getopt loop where arg_runtime_scope - * has its final value regardless of option order. */ arg_userns_mode = _USER_NAMESPACE_MODE_INVALID; arg_uid_shift = UID_INVALID; arg_uid_range = UINT32_C(0x10000); - arg_settings_mask |= SETTING_USERNS; } - break; - case ARG_PRIVATE_USERS_CHOWN: - arg_userns_ownership = USER_NAMESPACE_OWNERSHIP_CHOWN; + OPTION_GROUP("Networking"): {} - arg_settings_mask |= SETTING_USERNS; + OPTION_LONG("private-network", NULL, "Disable network in container"): + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; break; - case ARG_PRIVATE_USERS_OWNERSHIP: - if (streq(optarg, "help")) - return DUMP_STRING_TABLE(user_namespace_ownership, UserNamespaceOwnership, _USER_NAMESPACE_OWNERSHIP_MAX); - - arg_userns_ownership = user_namespace_ownership_from_string(optarg); - if (arg_userns_ownership < 0) - return log_error_errno(arg_userns_ownership, "Cannot parse --private-users-ownership= value: %s", optarg); - - arg_settings_mask |= SETTING_USERNS; + OPTION_LONG("network-interface", "HOSTIF[:CONTAINERIF]", + "Assign an existing network interface to the container"): + r = interface_pair_parse(&arg_network_interfaces, arg); + if (r < 0) + return r; + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; break; - case ARG_PRIVATE_USERS_DELEGATE: - r = safe_atou(optarg, &arg_delegate_container_ranges); + OPTION_LONG("network-macvlan", "HOSTIF[:CONTAINERIF]", + "Create a macvlan network interface based on an existing network interface to the container"): + r = macvlan_pair_parse(&arg_network_macvlan, arg); if (r < 0) - return log_error_errno(r, "Failed to parse --private-users-delegate= parameter: %s", optarg); - - arg_settings_mask |= SETTING_USERNS; + return r; + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; break; - case ARG_KILL_SIGNAL: - if (streq(optarg, "help")) - return DUMP_STRING_TABLE(signal, int, _NSIG); - - arg_kill_signal = signal_from_string(optarg); - if (arg_kill_signal < 0) - return log_error_errno(arg_kill_signal, "Cannot parse signal: %s", optarg); - - arg_settings_mask |= SETTING_KILL_SIGNAL; + OPTION_LONG("network-ipvlan", "HOSTIF[:CONTAINERIF]", + "Create an ipvlan network interface based on an existing network interface to the container"): + r = ipvlan_pair_parse(&arg_network_ipvlan, arg); + if (r < 0) + return r; + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; break; - case ARG_SETTINGS: - - /* no → do not read files - * yes → read files, do not override cmdline, trust only subset - * override → read files, override cmdline, trust only subset - * trusted → read files, do not override cmdline, trust all - */ - - r = parse_boolean(optarg); - if (r < 0) { - if (streq(optarg, "trusted")) { - mask_all_settings = false; - mask_no_settings = false; - arg_settings_trusted = true; - - } else if (streq(optarg, "override")) { - mask_all_settings = false; - mask_no_settings = true; - arg_settings_trusted = -1; - } else - return log_error_errno(r, "Failed to parse --settings= argument: %s", optarg); - } else if (r > 0) { - /* yes */ - mask_all_settings = false; - mask_no_settings = false; - arg_settings_trusted = -1; - } else { - /* no */ - mask_all_settings = true; - mask_no_settings = false; - arg_settings_trusted = false; - } - + OPTION('n', "network-veth", NULL, + "Add a virtual Ethernet connection between host and container"): + arg_network_veth = true; + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; break; - case ARG_CHDIR: { - _cleanup_free_ char *wd = NULL; + OPTION_LONG("network-veth-extra", "HOSTIF[:CONTAINERIF]", + "Add an additional virtual Ethernet link between host and container"): + r = veth_extra_parse(&arg_network_veth_extra, arg); + if (r < 0) + return log_error_errno(r, "Failed to parse --network-veth-extra= parameter: %s", arg); + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; + break; - if (!path_is_absolute(optarg)) + OPTION_LONG("network-bridge", "INTERFACE", + "Add a virtual Ethernet connection to the container and attach it to an existing bridge on the host"): + if (!ifname_valid(arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Working directory %s is not an absolute path.", optarg); - - r = path_simplify_alloc(optarg, &wd); + "Bridge interface name not valid: %s", arg); + r = free_and_strdup(&arg_network_bridge, arg); if (r < 0) - return log_error_errno(r, "Failed to simplify path %s: %m", optarg); + return log_oom(); + arg_network_veth = true; + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; + break; - if (!path_is_normalized(wd)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Working directory path is not normalized: %s", wd); + OPTION_LONG("network-zone", "NAME", + "Similar, but attach the new interface to an automatically managed bridge interface"): { + _cleanup_free_ char *j = NULL; - if (path_below_api_vfs(wd)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Working directory is below API VFS, refusing: %s", wd); + j = strjoin("vz-", arg); + if (!j) + return log_oom(); - free_and_replace(arg_chdir, wd); - arg_settings_mask |= SETTING_WORKING_DIRECTORY; + if (!ifname_valid(j)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Network zone name not valid: %s", j); + + free_and_replace(arg_network_zone, j); + arg_network_veth = true; + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; break; } - case ARG_PIVOT_ROOT: - r = pivot_root_parse(&arg_pivot_root_new, &arg_pivot_root_old, optarg); + OPTION_LONG("network-namespace-path", "PATH", + "Set network namespace to the one represented by the specified kernel namespace file node"): + r = parse_path_argument(arg, false, &arg_network_namespace_path); if (r < 0) - return log_error_errno(r, "Failed to parse --pivot-root= argument %s: %m", optarg); - - arg_settings_mask |= SETTING_PIVOT_ROOT; + return r; + arg_settings_mask |= SETTING_NETWORK; break; - case ARG_NOTIFY_READY: - r = parse_boolean_argument("--notify-ready=", optarg, &arg_notify_ready); + OPTION('p', "port", "[PROTOCOL:]HOSTPORT[:CONTAINERPORT]", + "Expose a container IP port on the host"): + r = expose_port_parse(&arg_expose_ports, arg); + if (r == -EEXIST) + return log_error_errno(r, "Duplicate port specification: %s", arg); if (r < 0) - return r; - - arg_settings_mask |= SETTING_NOTIFY_READY; + return log_error_errno(r, "Failed to parse host port %s: %m", arg); + arg_settings_mask |= SETTING_EXPOSE_PORTS; break; - case ARG_ROOT_HASH: { - _cleanup_(iovec_done) struct iovec k = {}; + OPTION_GROUP("Security"): {} - r = unhexmem(optarg, &k.iov_base, &k.iov_len); - if (r < 0) - return log_error_errno(r, "Failed to parse root hash: %s", optarg); - if (k.iov_len < sizeof(sd_id128_t)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Root hash must be at least 128-bit long: %s", optarg); + OPTION_LONG("capability", "CAP", + "In addition to the default, retain specified capability"): {} + OPTION_LONG("drop-capability", "CAP", + "Drop the specified capability from the default set"): { + uint64_t m; + r = parse_capability_spec(arg, &m); + if (r <= 0) + return r; - iovec_done(&arg_verity_settings.root_hash); - arg_verity_settings.root_hash = TAKE_STRUCT(k); + if (streq(opt->long_code, "capability")) + plus |= m; + else + minus |= m; + arg_settings_mask |= SETTING_CAPABILITY; break; } - case ARG_ROOT_HASH_SIG: { - _cleanup_(iovec_done) struct iovec p = {}; - char *value; - - if ((value = startswith(optarg, "base64:"))) { - r = unbase64mem(value, &p.iov_base, &p.iov_len); - if (r < 0) - return log_error_errno(r, "Failed to parse root hash signature '%s': %m", optarg); - - } else { - r = read_full_file(optarg, (char**) &p.iov_base, &p.iov_len); - if (r < 0) - return log_error_errno(r, "Failed to parse root hash signature file '%s': %m", optarg); - } - - iovec_done(&arg_verity_settings.root_hash_sig); - arg_verity_settings.root_hash_sig = TAKE_STRUCT(p); + OPTION_LONG("ambient-capability", "CAP", + "Sets the specified capability for the started process"): { + uint64_t m; + r = parse_capability_spec(arg, &m); + if (r <= 0) + return r; + arg_caps_ambient |= m; + arg_settings_mask |= SETTING_CAPABILITY; break; } - case ARG_VERITY_DATA: - r = parse_path_argument(optarg, false, &arg_verity_settings.data_path); + OPTION_LONG("no-new-privileges", "BOOL", + "Set PR_SET_NO_NEW_PRIVS flag for container payload"): + r = parse_boolean_argument("--no-new-privileges=", arg, &arg_no_new_privileges); if (r < 0) return r; + arg_settings_mask |= SETTING_NO_NEW_PRIVILEGES; break; - case ARG_SYSTEM_CALL_FILTER: { + OPTION_LONG("system-call-filter", "LIST|~LIST", + "Permit/prohibit specific system calls"): { bool negative; const char *items; - negative = optarg[0] == '~'; - items = negative ? optarg + 1 : optarg; + negative = arg[0] == '~'; + items = negative ? arg + 1 : arg; for (;;) { _cleanup_free_ char *word = NULL; @@ -1461,25 +1160,36 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return log_oom(); } - arg_settings_mask |= SETTING_SYSCALL_FILTER; break; } - case ARG_RLIMIT: { + OPTION('Z', "selinux-context", "SECLABEL", + "Set the SELinux security context to be used by processes in the container"): + arg_selinux_context = arg; + break; + + OPTION('L', "selinux-apifs-context", "SECLABEL", + "Set the SELinux security context to be used by API/tmpfs file systems in the container"): + arg_selinux_apifs_context = arg; + break; + + OPTION_GROUP("Resources"): {} + + OPTION_LONG("rlimit", "NAME=LIMIT", "Set a resource limit for the payload"): { const char *eq; _cleanup_free_ char *name = NULL; int rl; - if (streq(optarg, "help")) + if (streq(arg, "help")) return DUMP_STRING_TABLE(rlimit, int, _RLIMIT_MAX); - eq = strchr(optarg, '='); + eq = strchr(arg, '='); if (!eq) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--rlimit= expects an '=' assignment."); - name = strndup(optarg, eq - optarg); + name = strndup(arg, eq - arg); if (!name) return log_oom(); @@ -1501,180 +1211,218 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_OOM_SCORE_ADJUST: - r = parse_oom_score_adjust(optarg, &arg_oom_score_adjust); + OPTION_LONG("oom-score-adjust", "VALUE", "Adjust the OOM score value for the payload"): + r = parse_oom_score_adjust(arg, &arg_oom_score_adjust); if (r < 0) - return log_error_errno(r, "Failed to parse --oom-score-adjust= parameter: %s", optarg); - + return log_error_errno(r, "Failed to parse --oom-score-adjust= parameter: %s", arg); arg_oom_score_adjust_set = true; arg_settings_mask |= SETTING_OOM_SCORE_ADJUST; break; - case ARG_CPU_AFFINITY: { + OPTION_LONG("cpu-affinity", "CPUS", "Adjust the CPU affinity of the container"): { CPUSet cpuset; - r = parse_cpu_set(optarg, &cpuset); + r = parse_cpu_set(arg, &cpuset); if (r < 0) - return log_error_errno(r, "Failed to parse CPU affinity mask %s: %m", optarg); + return log_error_errno(r, "Failed to parse CPU affinity mask %s: %m", arg); cpu_set_done_and_replace(arg_cpu_set, cpuset); arg_settings_mask |= SETTING_CPU_AFFINITY; break; } - case ARG_RESOLV_CONF: - if (streq(optarg, "help")) + OPTION_LONG("personality", "ARCH", "Pick personality for this container"): + arg_personality = personality_from_string(arg); + if (arg_personality == PERSONALITY_INVALID) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown or unsupported personality '%s'.", arg); + arg_settings_mask |= SETTING_PERSONALITY; + break; + + OPTION_GROUP("Integration"): {} + + OPTION_LONG("resolv-conf", "MODE", "Select mode of /etc/resolv.conf initialization"): + if (streq(arg, "help")) return DUMP_STRING_TABLE(resolv_conf_mode, ResolvConfMode, _RESOLV_CONF_MODE_MAX); - arg_resolv_conf = resolv_conf_mode_from_string(optarg); + arg_resolv_conf = resolv_conf_mode_from_string(arg); if (arg_resolv_conf < 0) return log_error_errno(arg_resolv_conf, - "Failed to parse /etc/resolv.conf mode: %s", optarg); - + "Failed to parse /etc/resolv.conf mode: %s", arg); arg_settings_mask |= SETTING_RESOLV_CONF; break; - case ARG_TIMEZONE: - if (streq(optarg, "help")) + OPTION_LONG("timezone", "MODE", "Select mode of /etc/localtime initialization"): + if (streq(arg, "help")) return DUMP_STRING_TABLE(timezone_mode, TimezoneMode, _TIMEZONE_MODE_MAX); - arg_timezone = timezone_mode_from_string(optarg); + arg_timezone = timezone_mode_from_string(arg); if (arg_timezone < 0) return log_error_errno(arg_timezone, - "Failed to parse /etc/localtime mode: %s", optarg); - + "Failed to parse /etc/localtime mode: %s", arg); arg_settings_mask |= SETTING_TIMEZONE; break; - case ARG_CONSOLE: - if (streq(optarg, "help")) - return DUMP_STRING_TABLE(console_mode, ConsoleMode, _CONSOLE_MODE_MAX); - - arg_console_mode = console_mode_from_string(optarg); - if (arg_console_mode < 0) - return log_error_errno(arg_console_mode, "Unknown console mode: %s", optarg); - - arg_settings_mask |= SETTING_CONSOLE_MODE; - + OPTION_LONG("link-journal", "MODE", + "Link up guest journal, one of no, auto, guest, host, try-guest, try-host"): + r = parse_link_journal(arg, &arg_link_journal, &arg_link_journal_try); + if (r < 0) + return log_error_errno(r, "Failed to parse link journal mode %s", arg); + arg_settings_mask |= SETTING_LINK_JOURNAL; break; - case 'P': - case ARG_PIPE: - arg_console_mode = CONSOLE_PIPE; - arg_settings_mask |= SETTING_CONSOLE_MODE; + OPTION_SHORT('j', NULL, "Equivalent to --link-journal=try-guest"): + arg_link_journal = LINK_GUEST; + arg_link_journal_try = true; + arg_settings_mask |= SETTING_LINK_JOURNAL; break; - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; - break; + OPTION_GROUP("Mounts"): {} - case ARG_SET_CREDENTIAL: - r = machine_credential_set(&arg_credentials, optarg); + OPTION_LONG("bind", "PATH[:PATH[:OPTIONS]]", + "Bind mount a file or directory from the host into the container"): {} + OPTION_LONG("bind-ro", "PATH[:PATH[:OPTIONS]]", + "Similar, but creates a read-only bind mount"): + r = bind_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, arg, + streq(opt->long_code, "bind-ro")); if (r < 0) - return r; - - arg_settings_mask |= SETTING_CREDENTIALS; + return log_error_errno(r, "Failed to parse --bind(-ro)= argument %s: %m", arg); + arg_settings_mask |= SETTING_CUSTOM_MOUNTS; break; - case ARG_LOAD_CREDENTIAL: - r = machine_credential_load(&arg_credentials, optarg); + OPTION_LONG("inaccessible", "PATH", + "Over-mount file node with inaccessible node to mask it"): + r = inaccessible_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, arg); if (r < 0) - return r; + return log_error_errno(r, "Failed to parse --inaccessible= argument %s: %m", arg); + arg_settings_mask |= SETTING_CUSTOM_MOUNTS; + break; - arg_settings_mask |= SETTING_CREDENTIALS; + OPTION_LONG("tmpfs", "PATH:[OPTIONS]", + "Mount an empty tmpfs to the specified directory"): + r = tmpfs_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, arg); + if (r < 0) + return log_error_errno(r, "Failed to parse --tmpfs= argument %s: %m", arg); + arg_settings_mask |= SETTING_CUSTOM_MOUNTS; break; - case ARG_BIND_USER: - if (!valid_user_group_name(optarg, 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid user name to bind: %s", optarg); + OPTION_LONG("overlay", "PATH[:PATH...]:PATH", + "Create an overlay mount from the host to the container"): {} + OPTION_LONG("overlay-ro", "PATH[:PATH...]:PATH", + "Similar, but creates a read-only overlay mount"): + r = overlay_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, arg, + streq(opt->long_code, "overlay-ro")); + if (r == -EADDRNOTAVAIL) + return log_error_errno(r, "--overlay(-ro)= needs at least two colon-separated directories specified."); + if (r < 0) + return log_error_errno(r, "Failed to parse --overlay(-ro)= argument %s: %m", arg); + arg_settings_mask |= SETTING_CUSTOM_MOUNTS; + break; - if (strv_extend(&arg_bind_user, optarg) < 0) + OPTION_LONG("bind-user", "NAME", "Bind user from host to container"): + if (!valid_user_group_name(arg, 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid user name to bind: %s", arg); + if (strv_extend(&arg_bind_user, arg) < 0) return log_oom(); - arg_settings_mask |= SETTING_BIND_USER; break; - case ARG_BIND_USER_SHELL: { + OPTION_LONG("bind-user-shell", "BOOL|PATH", + "Configure the shell to use for --bind-user= users"): { bool copy = false; char *sh = NULL; - r = parse_user_shell(optarg, &sh, ©); + r = parse_user_shell(arg, &sh, ©); if (r == -ENOMEM) return log_oom(); if (r < 0) - return log_error_errno(r, "Invalid user shell to bind: %s", optarg); + return log_error_errno(r, "Invalid user shell to bind: %s", arg); free_and_replace(arg_bind_user_shell, sh); arg_bind_user_shell_copy = copy; - arg_settings_mask |= SETTING_BIND_USER_SHELL; break; } - case ARG_BIND_USER_GROUP: - if (!valid_user_group_name(optarg, /* flags= */ 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bind user auxiliary group name: %s", optarg); - - if (strv_extend(&arg_bind_user_groups, optarg) < 0) + OPTION_LONG("bind-user-group", "GROUP", + "Add an auxiliary group to --bind-user= users"): + if (!valid_user_group_name(arg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bind user auxiliary group name: %s", arg); + if (strv_extend(&arg_bind_user_groups, arg) < 0) return log_oom(); + break; + + OPTION_GROUP("Input/Output"): {} + + OPTION_LONG("console", "MODE", + "Select how stdin/stdout/stderr and /dev/console are set up for the container"): + if (streq(arg, "help")) + return DUMP_STRING_TABLE(console_mode, ConsoleMode, _CONSOLE_MODE_MAX); + arg_console_mode = console_mode_from_string(arg); + if (arg_console_mode < 0) + return log_error_errno(arg_console_mode, "Unknown console mode: %s", arg); + arg_settings_mask |= SETTING_CONSOLE_MODE; break; - case ARG_SUPPRESS_SYNC: - r = parse_boolean_argument("--suppress-sync=", optarg, &arg_suppress_sync); + OPTION('P', "pipe", NULL, "Equivalent to --console=pipe"): + arg_console_mode = CONSOLE_PIPE; + arg_settings_mask |= SETTING_CONSOLE_MODE; + break; + + OPTION_LONG("background", "COLOR", "Set ANSI color for background"): + r = parse_background_argument(arg, &arg_background); if (r < 0) return r; - - arg_settings_mask |= SETTING_SUPPRESS_SYNC; break; - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); + OPTION_GROUP("Credentials"): {} + + OPTION_LONG("set-credential", "ID:VALUE", + "Pass a credential with literal value to container"): + r = machine_credential_set(&arg_credentials, arg); if (r < 0) return r; + arg_settings_mask |= SETTING_CREDENTIALS; break; - case ARG_BACKGROUND: - r = parse_background_argument(optarg, &arg_background); + OPTION_LONG("load-credential", "ID:PATH", + "Load credential to pass to container from file or AF_UNIX stream socket"): + r = machine_credential_load(&arg_credentials, arg); if (r < 0) return r; + arg_settings_mask |= SETTING_CREDENTIALS; break; - case ARG_CLEANUP: - arg_cleanup = true; - break; + OPTION_GROUP("Other"): {} - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; + OPTION_LONG("share-system", NULL, /* help= */ NULL): /* not documented */ + log_warning("Please do not use --share-system anymore, use $SYSTEMD_NSPAWN_SHARE_* instead."); + arg_clone_ns_flags = 0; break; - case ARG_USER: - if (optarg) { + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "user", "NAME", "Run in the user service manager scope"): + if (arg) { /* --user=NAME is a deprecated alias for --uid=NAME */ log_warning("--user=NAME is deprecated, use --uid=NAME instead."); - r = free_and_strdup(&arg_user, optarg); + r = free_and_strdup(&arg_user, arg); if (r < 0) return log_oom(); - arg_settings_mask |= SETTING_USER; } else arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Run in the system service manager scope"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + } - if (argc > optind) { + char **args = option_parser_get_args(&state); + if (!strv_isempty(args)) { strv_free(arg_parameters); - arg_parameters = strv_copy(argv + optind); + arg_parameters = strv_copy(args); if (!arg_parameters) return log_oom(); From f293418a5035ec6ead2e28e80329e768d4b9b500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 7 Apr 2026 17:48:25 +0200 Subject: [PATCH 0825/1296] shared/options: add helper function to peek at or consume the next arg The test was partially written with Claude Opus 4.6. It's a bit on the verbose side, but does the job. --- src/shared/options.c | 22 +++++++ src/shared/options.h | 3 + src/test/test-options.c | 141 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+) diff --git a/src/shared/options.c b/src/shared/options.c index f00d9674d7aff..20ca7948e5b31 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -239,6 +239,28 @@ int option_parse( return option->id; } +char* option_parser_next_arg(const OptionParser *state) { + /* Peek at the next argument, whatever it is (option or position arg). + * May return NULL. */ + + assert(state->optind > 0); + assert(state->positional_offset <= state->argc); + + return state->optind < state->argc ? state->argv[state->optind] : NULL; +} + +char* option_parser_consume_next_arg(OptionParser *state) { + /* "Take" the next argument, whatever it is (option or position arg). + * The argument remains in the array, but the optind pointer is moved + * so we won't try to interpret it as an option. + * May return NULL. */ + + char *t = option_parser_next_arg(state); + if (t) + shift_arg(state->argv, state->positional_offset++, state->optind++); + return t; +} + char** option_parser_get_args(const OptionParser *state) { /* Returns positional args as a strv. * If "--" was found, it has been moved before state->positional_offset. diff --git a/src/shared/options.h b/src/shared/options.h index 1b0488579f94b..e834cbededa4f 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -113,6 +113,9 @@ int option_parse( #define FOREACH_OPTION(parser, opt, ret_a, on_error) \ FOREACH_OPTION_FULL(parser, opt, /* ret_o= */ NULL, ret_a, on_error) +char* option_parser_next_arg(const OptionParser *state); +char* option_parser_consume_next_arg(OptionParser *state); + char** option_parser_get_args(const OptionParser *state); size_t option_parser_get_n_args(const OptionParser *state); diff --git a/src/test/test-options.c b/src/test/test-options.c index a9cafbdd73327..b91ac54884c6b 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -1091,4 +1091,145 @@ TEST(option_macros) { "-h")); } +/* Test the pattern used by nspawn's --user: an optional-arg option that also + * peeks at the next arg to handle legacy "space-separated" form. */ +TEST(option_optional_arg_consume) { + static const Option options[] = { + { 1, .short_code = 'h', .long_code = "help" }, + { 2, .long_code = "user", .metavar = "NAME", .flags = OPTION_OPTIONAL_ARG }, + { 3, .short_code = 'u', .long_code = "uid", .metavar = "USER" }, + {} + }; + + /* --user=NAME: optional arg provided via = */ + test_option_parse_one(STRV_MAKE("arg0", + "--user=root"), + options, + (Entry[]) { + { "user", "root" }, + {} + }, + NULL, + /* stop_at_first_nonoption= */ false); + + /* --user without arg: next arg is an option, so no consumption */ + test_option_parse_one(STRV_MAKE("arg0", + "--user", + "--help"), + options, + (Entry[]) { + { "user", NULL }, + { "help" }, + {} + }, + NULL, + /* stop_at_first_nonoption= */ false); + + /* --user without arg: next arg is positional (doesn't start with -). + * The option parser returns NULL for the arg. The caller would then + * use option_parser_next_arg/consume_next_arg to grab it. */ + { + char **argv = STRV_MAKE("arg0", "--user", "someuser", "pos1"); + int argc = strv_length(argv); + + OptionParser state = { argc, argv }; + const Option *opt; + const char *arg; + + ASSERT_OK_POSITIVE(option_parse(options, options + 3, &state, &opt, &arg)); + ASSERT_STREQ(opt->long_code, "user"); + ASSERT_NULL(arg); + ASSERT_STREQ(option_parser_next_arg(&state), "someuser"); + ASSERT_STREQ(option_parser_consume_next_arg(&state), "someuser"); + + ASSERT_EQ(option_parse(options, options + 3, &state, &opt, &arg), 0); + + ASSERT_TRUE(strv_equal(option_parser_get_args(&state), STRV_MAKE("pos1"))); + } + + /* --user at end of args: no next arg, so scope mode */ + { + char **argv = STRV_MAKE("arg0", "--user"); + int argc = strv_length(argv); + + OptionParser state = { argc, argv }; + const Option *opt; + const char *arg; + + ASSERT_OK_POSITIVE(option_parse(options, options + 3, &state, &opt, &arg)); + ASSERT_STREQ(opt->long_code, "user"); + ASSERT_NULL(arg); + ASSERT_NULL(option_parser_next_arg(&state)); + ASSERT_NULL(option_parser_consume_next_arg(&state)); + + ASSERT_EQ(option_parse(options, options + 3, &state, &opt, &arg), 0); + + ASSERT_TRUE(strv_isempty(option_parser_get_args(&state))); + } + + /* --user followed by -u (option): scope mode, -u gets its own processing */ + { + char **argv = STRV_MAKE("arg0", "--user", "-u", "nobody"); + int argc = strv_length(argv); + + OptionParser state = { argc, argv }; + const Option *opt; + const char *arg; + + ASSERT_OK_POSITIVE(option_parse(options, options + 3, &state, &opt, &arg)); + ASSERT_STREQ(opt->long_code, "user"); + ASSERT_NULL(arg); + ASSERT_STREQ(option_parser_next_arg(&state), "-u"); + + ASSERT_OK_POSITIVE(option_parse(options, options + 3, &state, &opt, &arg)); + ASSERT_STREQ(opt->long_code, "uid"); + ASSERT_STREQ(arg, "nobody"); + ASSERT_NULL(option_parser_next_arg(&state)); + ASSERT_NULL(option_parser_consume_next_arg(&state)); + + ASSERT_EQ(option_parse(options, options + 3, &state, &opt, &arg), 0); + + ASSERT_TRUE(strv_isempty(option_parser_get_args(&state))); + } + + /* "Functional test": --user followed by -u (option): scope mode, -u gets its own processing, + * handled like in a real option parser. */ + { + char **argv = STRV_MAKE("arg0", "--user", "-u", "nobody", "nogroup", "--user=nobody", "--user"); + int argc = strv_length(argv); + + OptionParser state = { argc, argv }; + const Option *opt; + const char *arg; + int scope_seen = 0; + int nobody_seen = 0; + + for (int c; (c = option_parse(options, options + 3, &state, &opt, &arg)) != 0; ) { + ASSERT_OK(c); + + if (streq_ptr(opt->long_code, "user")) { + if (!arg) { + const char *t = option_parser_next_arg(&state); + if (t && t[0] != '-') + arg = option_parser_consume_next_arg(&state); + } + + if (arg) { + ASSERT_STREQ(arg, "nobody"); + nobody_seen ++; + } else + scope_seen ++; + + } else if (streq_ptr(opt->long_code, "uid")) { + ASSERT_STREQ(arg, "nobody"); + nobody_seen ++; + } + } + + ASSERT_EQ(nobody_seen, 2); + ASSERT_EQ(scope_seen, 2); + ASSERT_TRUE(strv_equal(option_parser_get_args(&state), STRV_MAKE("nogroup"))); + } +} + DEFINE_TEST_MAIN(LOG_DEBUG); From c9da9188057efff853fe5780ca517cd3145977b8 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 7 Apr 2026 23:08:29 +0100 Subject: [PATCH 0826/1296] scsi_id: null-terminate serial after append_vendor_model append_vendor_model() uses memcpy() to write VENDOR_LENGTH + MODEL_LENGTH bytes without null-terminating. While the caller zeroes the buffer beforehand, Coverity cannot trace this. Add explicit null termination so the subsequent strlen() is provably safe. CID#1469706 Follow-up for 86fd0337c652b04755008cdca23e2d9c727fa9a9 --- src/udev/scsi_id/scsi_serial.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/udev/scsi_id/scsi_serial.c b/src/udev/scsi_id/scsi_serial.c index 82557e3b057a9..7de9999257850 100644 --- a/src/udev/scsi_id/scsi_serial.c +++ b/src/udev/scsi_id/scsi_serial.c @@ -491,9 +491,14 @@ static int check_fill_0x83_id(struct scsi_id_device *dev_scsi, * this differs from SCSI_ID_T10_VENDOR, where the vendor is * included in the identifier. */ - if (id_search->id_type == SCSI_ID_VENDOR_SPECIFIC) + if (id_search->id_type == SCSI_ID_VENDOR_SPECIFIC) { if (append_vendor_model(dev_scsi, serial + 1) < 0) return 1; + /* append_vendor_model() uses memcpy() without null-terminating. + * The buffer was zeroed by the caller, but ensure the string is + * explicitly terminated for strlen() below. */ + serial[1 + VENDOR_LENGTH + MODEL_LENGTH] = '\0'; + } i = 4; /* offset to the start of the identifier */ s = j = strlen(serial); From 1934c65108505d0cd1888a889494467660a330c6 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 7 Apr 2026 23:45:30 +0100 Subject: [PATCH 0827/1296] uid-range: add assert to silence coverity Coverity flags range->n_entries - j - 1 and j-- as potential underflows. Add an assert that j > 0 before decrementing, since j starts at i + 1 >= 1 and is never decremented below its initial value. CID#1548015 Follow-up for 8dcc66cefc8ab489568c737adcba960756d76a3c --- src/basic/uid-range.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/basic/uid-range.c b/src/basic/uid-range.c index 628710a8709bc..3d8f8445c4559 100644 --- a/src/basic/uid-range.c +++ b/src/basic/uid-range.c @@ -76,6 +76,9 @@ static void uid_range_coalesce(UIDRange *range) { memmove(y, y + 1, sizeof(UIDRangeEntry) * (range->n_entries - j - 1)); range->n_entries--; + + /* Silence static analyzers, j cannot be 0 here since it starts at i + 1, i.e. >= 1 */ + assert(j > 0); j--; } } From 8fa088b8860c9ba6f67d962953cb8f11316f0505 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 7 Apr 2026 23:59:16 +0100 Subject: [PATCH 0828/1296] recurse-dir: add assert for MALLOC_SIZEOF_SAFE lower bound Coverity flags MALLOC_SIZEOF_SAFE(de) - offsetof(DirectoryEntries, buffer) as a potential underflow when MALLOC_SIZEOF_SAFE returns 0. After a successful malloc the return value is at least as large as the requested size, but Coverity cannot trace this. Add an assert to establish the lower bound. CID#1548020 Follow-up for 6393b847f459dba14d2b615ee93babb143168b57 --- src/basic/recurse-dir.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/basic/recurse-dir.c b/src/basic/recurse-dir.c index 1bd8231966314..8f691d922945f 100644 --- a/src/basic/recurse-dir.c +++ b/src/basic/recurse-dir.c @@ -55,6 +55,8 @@ int readdir_all(int dir_fd, RecurseDirFlags flags, DirectoryEntries **ret) { size_t bs; ssize_t n; + /* Silence static analyzers, MALLOC_SIZEOF_SAFE is at least as large as the allocation */ + assert(MALLOC_SIZEOF_SAFE(de) >= offsetof(DirectoryEntries, buffer)); bs = MIN(MALLOC_SIZEOF_SAFE(de) - offsetof(DirectoryEntries, buffer), (size_t) SSIZE_MAX); assert(bs > de->buffer_size); From e60937b5853de50bbb0941fef49fe6faef213909 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 8 Apr 2026 00:11:01 +0100 Subject: [PATCH 0829/1296] sd-bus: assert ALIGN8 result is not SIZE_MAX Coverity flags sizeof(BusMessageHeader) + ALIGN8(m->fields_size) as overflowing because ALIGN_TO can return SIZE_MAX as an overflow sentinel. Assert that the aligned value is not SIZE_MAX to prove the addition is safe. CID#1548023 CID#1548046 Follow-up for 2ac7c17f9d8eeb403b91ee5a389562edaf47fb87 --- src/libsystemd/sd-bus/bus-message.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libsystemd/sd-bus/bus-message.h b/src/libsystemd/sd-bus/bus-message.h index fe9679393ecea..94eff878e5648 100644 --- a/src/libsystemd/sd-bus/bus-message.h +++ b/src/libsystemd/sd-bus/bus-message.h @@ -153,7 +153,8 @@ static inline uint64_t BUS_MESSAGE_COOKIE(sd_bus_message *m) { } static inline size_t BUS_MESSAGE_SIZE(sd_bus_message *m) { - /* Silence static analyzers */ + /* Silence static analyzers, fields_size is validated at message creation */ + assert(ALIGN8(m->fields_size) != SIZE_MAX); assert(ALIGN8(m->fields_size) <= SIZE_MAX - sizeof(BusMessageHeader)); assert(m->body_size <= SIZE_MAX - sizeof(BusMessageHeader) - ALIGN8(m->fields_size)); return @@ -163,7 +164,8 @@ static inline size_t BUS_MESSAGE_SIZE(sd_bus_message *m) { } static inline size_t BUS_MESSAGE_BODY_BEGIN(sd_bus_message *m) { - /* Silence static analyzers */ + /* Silence static analyzers, fields_size is validated at message creation */ + assert(ALIGN8(m->fields_size) != SIZE_MAX); assert(ALIGN8(m->fields_size) <= SIZE_MAX - sizeof(BusMessageHeader)); return sizeof(BusMessageHeader) + From 0aaf7e697f068b2699b7c964a34711010cbb7fd8 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 8 Apr 2026 00:15:18 +0100 Subject: [PATCH 0830/1296] test-path: use usec_add() for timeout calculation Coverity flags now() + 30 * USEC_PER_SEC as overflowing because now() can return USEC_INFINITY. Use usec_add() which saturates on overflow instead of wrapping. CID#1548025 Follow-up for 331461a5a2ffe323190c4ca6b7bcd35944e36f92 --- src/test/test-path.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/test/test-path.c b/src/test/test-path.c index 82b1f27ed7550..8b02f5d0fffa4 100644 --- a/src/test/test-path.c +++ b/src/test/test-path.c @@ -78,9 +78,7 @@ static int _check_states(unsigned line, assert_se(m); assert_se(service); - /* Silence static analyzers */ - assert_cc(30 * USEC_PER_SEC <= USEC_INFINITY); - usec_t end = now(CLOCK_MONOTONIC) + 30 * USEC_PER_SEC; + usec_t end = usec_add(now(CLOCK_MONOTONIC), 30 * USEC_PER_SEC); while (path->state != path_state || service->state != service_state || path->result != PATH_SUCCESS || service->result != SERVICE_SUCCESS) { From aaab31d7a05563c5d44d4e6c7fae7a0bd802c45f Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 8 Apr 2026 00:26:33 +0100 Subject: [PATCH 0831/1296] nss-myhostname: use INC_SAFE for buffer index accumulation Use overflow-safe INC_SAFE() instead of raw addition for idx accumulation, so that Coverity can see the addition is checked. CID#1548028 Follow-up for a05483a921a518fd283e7cb32dc8c8e816b2ab2c --- src/nss-myhostname/nss-myhostname.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/nss-myhostname/nss-myhostname.c b/src/nss-myhostname/nss-myhostname.c index 6a016a1f5cc12..601a4198dd8e8 100644 --- a/src/nss-myhostname/nss-myhostname.c +++ b/src/nss-myhostname/nss-myhostname.c @@ -238,14 +238,10 @@ static enum nss_status fill_in_hostent( if (additional) { ((char**) r_aliases)[0] = r_alias; ((char**) r_aliases)[1] = NULL; - /* Silence static analyzers */ - assert(idx <= buflen - 2 * sizeof(char*)); - idx += 2*sizeof(char*); + assert_se(INC_SAFE(&idx, 2 * sizeof(char*))); } else { ((char**) r_aliases)[0] = NULL; - /* Silence static analyzers */ - assert(idx <= buflen - sizeof(char*)); - idx += sizeof(char*); + assert_se(INC_SAFE(&idx, sizeof(char*))); } /* Third, add addresses */ From f0bb176ee1c3d9f1084af271359e0ae51eb03d91 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 8 Apr 2026 00:34:56 +0100 Subject: [PATCH 0832/1296] sd-bus: use INC_SAFE and assert for message_from_header allocation Coverity flags ALIGN() as potentially returning SIZE_MAX and the subsequent a += label_sz + 1 as overflowing. Assert ALIGN result is not SIZE_MAX and use INC_SAFE for the addition. CID#1548030 Follow-up for 55354d5930fd0b7952d649d9ad5a850279fc73e1 --- src/libsystemd/sd-bus/bus-message.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libsystemd/sd-bus/bus-message.c b/src/libsystemd/sd-bus/bus-message.c index 507c5d7ff4060..f66b2fa3e2264 100644 --- a/src/libsystemd/sd-bus/bus-message.c +++ b/src/libsystemd/sd-bus/bus-message.c @@ -359,12 +359,12 @@ static int message_from_header( /* Note that we are happy with unknown flags in the flags header! */ a = ALIGN(sizeof(sd_bus_message)); + /* Silence static analyzers, ALIGN cannot overflow for sizeof() */ + assert(a != SIZE_MAX); if (label) { label_sz = strlen(label); - /* Silence static analyzers */ - assert(label_sz <= SIZE_MAX - ALIGN(sizeof(sd_bus_message)) - 1); - a += label_sz + 1; + assert_se(INC_SAFE(&a, label_sz + 1)); } m = malloc0(a); From 79a9a9972870b99a045483f2b2126dceaf39f9df Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 8 Apr 2026 00:40:01 +0100 Subject: [PATCH 0833/1296] sd-bus: assert ALIGN result in sd_bus_message_new Coverity flags ALIGN(sizeof(sd_bus_message)) as potentially returning SIZE_MAX, making the subsequent + sizeof(BusMessageHeader) overflow. Store the ALIGN result in a local and assert it is not SIZE_MAX. CID#1548031 Follow-up for 4f5b28b72c7ff78c7eabcce7ad4f0eaebfd5545d --- src/libsystemd/sd-bus/bus-message.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libsystemd/sd-bus/bus-message.c b/src/libsystemd/sd-bus/bus-message.c index f66b2fa3e2264..358fb3ca756dd 100644 --- a/src/libsystemd/sd-bus/bus-message.c +++ b/src/libsystemd/sd-bus/bus-message.c @@ -464,8 +464,8 @@ _public_ int sd_bus_message_new( /* Creation of messages with _SD_BUS_MESSAGE_TYPE_INVALID is allowed. */ assert_return(type < _SD_BUS_MESSAGE_TYPE_MAX, -EINVAL); - /* Silence static analyzers */ - assert_cc(sizeof(sd_bus_message) + sizeof(void*) + sizeof(BusMessageHeader) <= SIZE_MAX); + /* Silence static analyzers, ALIGN cannot overflow for sizeof() */ + assert(ALIGN(sizeof(sd_bus_message)) != SIZE_MAX); sd_bus_message *t = malloc0(ALIGN(sizeof(sd_bus_message)) + sizeof(BusMessageHeader)); if (!t) return -ENOMEM; From fda487ef30b83002bda2470af7eefc855191db78 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 8 Apr 2026 00:53:07 +0100 Subject: [PATCH 0834/1296] sd-event: validate ssi_signo fits in signed int Coverity flags si.ssi_signo as tainted data from read(), and warns that casting it to signed could produce a negative value. Add an explicit range check against INT_MAX before the SIGNAL_VALID check to prove the cast is safe. CID#1548033 Follow-up for c8b53fcfd3463679e6475e9b57b61a97dac1a287 --- src/libsystemd/sd-event/sd-event.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index ad82f308baac5..19feff5668852 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -3804,11 +3804,11 @@ static int process_signal(sd_event *e, struct signal_data *d, uint32_t events, i if (_unlikely_(n != sizeof(si))) return -EIO; - if (_unlikely_(!SIGNAL_VALID(si.ssi_signo))) + if (_unlikely_(si.ssi_signo > INT_MAX)) /* Ensure value fits in int before casting */ return -EIO; - /* Silence static analyzers */ - assert(si.ssi_signo < _NSIG); + if (_unlikely_(!SIGNAL_VALID(si.ssi_signo))) + return -EIO; if (e->signal_sources) s = e->signal_sources[si.ssi_signo]; From 40eef914f35208ee3f34faf063e2e5bdb94cc034 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 8 Apr 2026 00:59:48 +0100 Subject: [PATCH 0835/1296] limits-util: use MUL_SAFE for physical memory calculation Coverity flags (uint64_t)sc * (uint64_t)ps as a potential overflow. Use MUL_SAFE which Coverity understands via __builtin_mul_overflow. Physical page count times page size cannot realistically overflow uint64_t, but this makes it provable to static analyzers. CID#1548042 Follow-up for 09bb6448ae221c09a00d1f4a9b45ce8535003319 --- src/basic/limits-util.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/basic/limits-util.c b/src/basic/limits-util.c index 02fbe92cc7712..732d0c6a6f44b 100644 --- a/src/basic/limits-util.c +++ b/src/basic/limits-util.c @@ -28,9 +28,9 @@ uint64_t physical_memory(void) { assert(sc > 0); ps = page_size(); - /* Silence static analyzers */ - assert((uint64_t) sc <= UINT64_MAX / (uint64_t) ps); - mem = (uint64_t) sc * (uint64_t) ps; + /* Physical page count times page size cannot realistically overflow uint64_t, + * but use MUL_SAFE to make this obvious to static analyzers. */ + assert_se(MUL_SAFE(&mem, (uint64_t) sc, (uint64_t) ps)); r = cg_get_root_path(&root); if (r < 0) { From 3d5bd67a2259e7a4edc27476d4cae049653c4414 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Tue, 7 Apr 2026 11:16:42 +0200 Subject: [PATCH 0836/1296] fstab-generator: support swap on network block devices Teach swap units to support the _netdev option as well, which should make swaps on iSCSI possible. This mirrors the logic we already have for regular mounts in both the fstab-generator and the core (mount.c/swap.c). Co-developed-by: Claude Opus 4.6 --- man/systemd.swap.xml | 30 ++++++++++-- src/core/swap.c | 46 ++++++++++++++++--- src/fstab-generator/fstab-generator.c | 17 +++++-- src/shared/generator.c | 3 +- .../systemd-remount-fs.service | 0 .../sysroot.mount | 0 .../50-netdev-dependencies.conf | 5 ++ .../dev-sdx1.swap | 10 ++++ .../systemd-remount-fs.service | 0 .../remote-fs.target.requires/dev-sdx1.swap | 1 + .../50-netdev-dependencies.conf | 5 ++ .../dev-sdx1.swap | 10 ++++ .../sysroot.mount | 0 .../remote-fs.target.requires/dev-sdx1.swap | 1 + .../test-21-swap-netdev.fstab.input | 1 + 15 files changed, 114 insertions(+), 15 deletions(-) create mode 100644 test/test-fstab-generator/test-21-swap-netdev.fstab.expected.container.sysroot/local-fs.target.wants/systemd-remount-fs.service create mode 100644 test/test-fstab-generator/test-21-swap-netdev.fstab.expected.container/initrd-usr-fs.target.requires/sysroot.mount create mode 100644 test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/dev-sdx1.device.d/50-netdev-dependencies.conf create mode 100644 test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/dev-sdx1.swap create mode 100644 test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/local-fs.target.wants/systemd-remount-fs.service create mode 120000 test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/remote-fs.target.requires/dev-sdx1.swap create mode 100644 test/test-fstab-generator/test-21-swap-netdev.fstab.expected/dev-sdx1.device.d/50-netdev-dependencies.conf create mode 100644 test/test-fstab-generator/test-21-swap-netdev.fstab.expected/dev-sdx1.swap create mode 100644 test/test-fstab-generator/test-21-swap-netdev.fstab.expected/initrd-usr-fs.target.requires/sysroot.mount create mode 120000 test/test-fstab-generator/test-21-swap-netdev.fstab.expected/remote-fs.target.requires/dev-sdx1.swap create mode 100644 test/test-fstab-generator/test-21-swap-netdev.fstab.input diff --git a/man/systemd.swap.xml b/man/systemd.swap.xml index 2b65ba68f3f6d..2dc98d3f5d9bb 100644 --- a/man/systemd.swap.xml +++ b/man/systemd.swap.xml @@ -90,9 +90,15 @@ The following dependencies are added unless DefaultDependencies=no is set: - Swap units automatically acquire a Conflicts= and a + Local swap units automatically acquire a Conflicts= and a Before= dependency on umount.target so that they are deactivated at shutdown as well as a Before=swap.target dependency. + + Network swap units (those with in their options) automatically acquire + After= dependencies on remote-fs-pre.target and + network.target, plus After= and Wants= dependencies + on network-online.target, and a Before= dependency on + remote-fs.target instead of swap.target. @@ -124,7 +130,8 @@ With , the swap unit will not be added as a dependency for - swap.target. This means that it will not + swap.target (or remote-fs.target for network swap devices, + see below). This means that it will not be activated automatically during boot, unless it is pulled in by some other unit. The option has the opposite meaning and is the default. @@ -138,8 +145,8 @@ With , the swap unit will be only wanted, not required by - swap.target. This means that the boot - will continue even if this swap device is not activated + swap.target (or remote-fs.target for network swap + devices). This means that the boot will continue even if this swap device is not activated successfully. @@ -167,6 +174,21 @@ + + + + + Marks this swap device as requiring network access. This is useful for swap on + network block devices (e.g. iSCSI). + + Network swap units are ordered between remote-fs-pre.target and + remote-fs.target, instead of being ordered before + swap.target. They also pull in network-online.target and + are ordered after it and network.target. + + + + diff --git a/src/core/swap.c b/src/core/swap.c index 960d831c5cc3d..cedabc430dc0c 100644 --- a/src/core/swap.c +++ b/src/core/swap.c @@ -223,6 +223,7 @@ static int swap_add_device_dependencies(Swap *s) { } static int swap_add_default_dependencies(Swap *s) { + SwapParameters *p; int r; assert(s); @@ -236,13 +237,46 @@ static int swap_add_default_dependencies(Swap *s) { if (detect_container() > 0) return 0; - /* swap units generated for the swap dev links are missing the - * ordering dep against the swap target. */ - r = unit_add_dependency_by_name(UNIT(s), UNIT_BEFORE, SPECIAL_SWAP_TARGET, true, UNIT_DEPENDENCY_DEFAULT); - if (r < 0) - return r; + p = swap_get_parameters(s); + + if (p && fstab_test_option(p->options, "_netdev\0")) { + /* Network swap devices (those with _netdev in options) are routed through + * remote-fs.target instead of swap.target, mirroring how network mounts use + * remote-fs.target instead of local-fs.target. This avoids an ordering cycle: + * swap.target is pulled in at sysinit.target time, but network-online.target + * only comes after basic.target which is after sysinit.target. */ + r = unit_add_dependency_by_name(UNIT(s), UNIT_AFTER, SPECIAL_REMOTE_FS_PRE_TARGET, + /* add_reference= */ true, UNIT_DEPENDENCY_DEFAULT); + if (r < 0) + return r; + + r = unit_add_dependency_by_name(UNIT(s), UNIT_BEFORE, SPECIAL_REMOTE_FS_TARGET, + /* add_reference= */ true, UNIT_DEPENDENCY_DEFAULT); + if (r < 0) + return r; + + /* Pull in and order after network-online.target, analogous to + * mount_add_default_network_dependencies() for network mounts. */ + r = unit_add_dependency_by_name(UNIT(s), UNIT_AFTER, SPECIAL_NETWORK_TARGET, + /* add_reference= */ true, UNIT_DEPENDENCY_DEFAULT); + if (r < 0) + return r; + + r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_WANTS, UNIT_AFTER, SPECIAL_NETWORK_ONLINE_TARGET, + /* add_reference= */ true, UNIT_DEPENDENCY_DEFAULT); + if (r < 0) + return r; + } else { + /* swap units generated for the swap dev links are missing the + * ordering dep against the swap target. */ + r = unit_add_dependency_by_name(UNIT(s), UNIT_BEFORE, SPECIAL_SWAP_TARGET, + /* add_reference= */ true, UNIT_DEPENDENCY_DEFAULT); + if (r < 0) + return r; + } - return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, true, UNIT_DEPENDENCY_DEFAULT); + return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, + /* add_reference= */ true, UNIT_DEPENDENCY_DEFAULT); } static int swap_verify(Swap *s) { diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c index 1bfebf3c4089f..572f30a1cf7f4 100644 --- a/src/fstab-generator/fstab-generator.c +++ b/src/fstab-generator/fstab-generator.c @@ -221,6 +221,7 @@ static int add_swap( _cleanup_free_ char *name = NULL; _cleanup_fclose_ FILE *f = NULL; + bool is_network; int r; assert(what); @@ -240,11 +241,14 @@ static int add_swap( return true; } - log_debug("Found swap entry what=%s makefs=%s growfs=%s pcrfs=%s validatefs=%s noauto=%s nofail=%s", + is_network = fstab_test_option(options, "_netdev\0"); + + log_debug("Found swap entry what=%s makefs=%s growfs=%s pcrfs=%s validatefs=%s noauto=%s nofail=%s netdev=%s", what, yes_no(flags & MOUNT_MAKEFS), yes_no(flags & MOUNT_GROWFS), yes_no(flags & MOUNT_PCRFS), yes_no(flags & MOUNT_VALIDATEFS), - yes_no(flags & MOUNT_NOAUTO), yes_no(flags & MOUNT_NOFAIL)); + yes_no(flags & MOUNT_NOAUTO), yes_no(flags & MOUNT_NOFAIL), + yes_no(is_network)); r = unit_name_from_path(what, ".swap", &name); if (r < 0) @@ -285,6 +289,12 @@ static int add_swap( if (r < 0) return r; + if (is_network) { + r = generator_write_network_device_deps(arg_dest, what, /* where= */ NULL, options); + if (r < 0) + return r; + } + if (flags & MOUNT_MAKEFS) { r = generator_hook_up_mkswap(arg_dest, what); if (r < 0) @@ -300,7 +310,8 @@ static int add_swap( log_warning("%s: validating swap devices is currently unsupported.", what); if (!(flags & MOUNT_NOAUTO)) { - r = generator_add_symlink(arg_dest, SPECIAL_SWAP_TARGET, + const char *target = is_network ? SPECIAL_REMOTE_FS_TARGET : SPECIAL_SWAP_TARGET; + r = generator_add_symlink(arg_dest, target, (flags & MOUNT_NOFAIL) ? "wants" : "requires", name); if (r < 0) return r; diff --git a/src/shared/generator.c b/src/shared/generator.c index 603e969436e9b..fd527b3e6d65a 100644 --- a/src/shared/generator.c +++ b/src/shared/generator.c @@ -490,9 +490,8 @@ int generator_write_network_device_deps( assert(dir); assert(what); - assert(where); - if (fstab_is_extrinsic(where, opts)) + if (where && fstab_is_extrinsic(where, opts)) return 0; if (!fstab_test_option(opts, "_netdev\0")) diff --git a/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.container.sysroot/local-fs.target.wants/systemd-remount-fs.service b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.container.sysroot/local-fs.target.wants/systemd-remount-fs.service new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.container/initrd-usr-fs.target.requires/sysroot.mount b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.container/initrd-usr-fs.target.requires/sysroot.mount new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/dev-sdx1.device.d/50-netdev-dependencies.conf b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/dev-sdx1.device.d/50-netdev-dependencies.conf new file mode 100644 index 0000000000000..33d814c275505 --- /dev/null +++ b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/dev-sdx1.device.d/50-netdev-dependencies.conf @@ -0,0 +1,5 @@ +# Automatically generated by systemd-fstab-generator + +[Unit] +After=network-online.target network.target +Wants=network-online.target diff --git a/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/dev-sdx1.swap b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/dev-sdx1.swap new file mode 100644 index 0000000000000..32f276c9e1c90 --- /dev/null +++ b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/dev-sdx1.swap @@ -0,0 +1,10 @@ +# Automatically generated by systemd-fstab-generator + +[Unit] +Documentation=man:fstab(5) man:systemd-fstab-generator(8) +SourcePath=/etc/fstab +After=blockdev@dev-sdx1.target + +[Swap] +What=/dev/sdx1 +Options=_netdev diff --git a/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/local-fs.target.wants/systemd-remount-fs.service b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/local-fs.target.wants/systemd-remount-fs.service new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/remote-fs.target.requires/dev-sdx1.swap b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/remote-fs.target.requires/dev-sdx1.swap new file mode 120000 index 0000000000000..00f0c5ce6621d --- /dev/null +++ b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected.sysroot/remote-fs.target.requires/dev-sdx1.swap @@ -0,0 +1 @@ +../dev-sdx1.swap \ No newline at end of file diff --git a/test/test-fstab-generator/test-21-swap-netdev.fstab.expected/dev-sdx1.device.d/50-netdev-dependencies.conf b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected/dev-sdx1.device.d/50-netdev-dependencies.conf new file mode 100644 index 0000000000000..33d814c275505 --- /dev/null +++ b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected/dev-sdx1.device.d/50-netdev-dependencies.conf @@ -0,0 +1,5 @@ +# Automatically generated by systemd-fstab-generator + +[Unit] +After=network-online.target network.target +Wants=network-online.target diff --git a/test/test-fstab-generator/test-21-swap-netdev.fstab.expected/dev-sdx1.swap b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected/dev-sdx1.swap new file mode 100644 index 0000000000000..32f276c9e1c90 --- /dev/null +++ b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected/dev-sdx1.swap @@ -0,0 +1,10 @@ +# Automatically generated by systemd-fstab-generator + +[Unit] +Documentation=man:fstab(5) man:systemd-fstab-generator(8) +SourcePath=/etc/fstab +After=blockdev@dev-sdx1.target + +[Swap] +What=/dev/sdx1 +Options=_netdev diff --git a/test/test-fstab-generator/test-21-swap-netdev.fstab.expected/initrd-usr-fs.target.requires/sysroot.mount b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected/initrd-usr-fs.target.requires/sysroot.mount new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/test/test-fstab-generator/test-21-swap-netdev.fstab.expected/remote-fs.target.requires/dev-sdx1.swap b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected/remote-fs.target.requires/dev-sdx1.swap new file mode 120000 index 0000000000000..00f0c5ce6621d --- /dev/null +++ b/test/test-fstab-generator/test-21-swap-netdev.fstab.expected/remote-fs.target.requires/dev-sdx1.swap @@ -0,0 +1 @@ +../dev-sdx1.swap \ No newline at end of file diff --git a/test/test-fstab-generator/test-21-swap-netdev.fstab.input b/test/test-fstab-generator/test-21-swap-netdev.fstab.input new file mode 100644 index 0000000000000..5f719a4202813 --- /dev/null +++ b/test/test-fstab-generator/test-21-swap-netdev.fstab.input @@ -0,0 +1 @@ +/dev/sdx1 none swap _netdev 0 0 From e02f66d36fe6a15c036fc86d1e9c1f9b828eb4c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 7 Apr 2026 17:49:54 +0200 Subject: [PATCH 0837/1296] =?UTF-8?q?nspawn:=20make=20handling=20of=20comp?= =?UTF-8?q?at=20--user=3D=E2=80=A6=20less=20elaborate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up for e7fb7296f56dacc24054cddb2e1f0aa55ee7dc94. --- src/nspawn/nspawn.c | 54 +++++++++++++-------------------------------- 1 file changed, 15 insertions(+), 39 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 153babe67c01e..211f1248932a2 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -584,50 +584,13 @@ static int parse_environment(void) { } static int parse_argv(int argc, char *argv[]) { - int r; uint64_t plus = 0, minus = 0; bool mask_all_settings = false, mask_no_settings = false; + int r; assert(argc >= 0); assert(argv); - /* --user= used to require an argument (the container user to run as). It has been repurposed to - * optionally set the runtime scope, with --uid= replacing the old container user functionality. - * To maintain backwards compatibility with the space-separated form (--user NAME), stitch them - * together into --user=NAME before getopt sees them. Without this, getopt's optional_argument - * handling would interpret --user NAME as --user (no arg) followed by a positional argument. - * Operate on a copy to avoid modifying the caller's argv. */ - _cleanup_strv_free_ char **argv_copy = NULL; - for (int i = 1; i < argc - 1; i++) { - if (streq(argv[i], "--")) - break; /* Respect end-of-options sentinel */ - if (!streq(argv[i], "--user")) - continue; - if (argv[i + 1][0] == '-') - continue; /* Next arg is an option, not a username */ - - /* Deep copy so we can freely replace and free entries */ - if (!argv_copy) { - argv_copy = strv_copy(argv); - if (!argv_copy) - return log_oom(); - argv = argv_copy; - } - - log_warning("--user NAME is deprecated, use --uid=NAME instead."); - - /* Stitch "--user" and the following argument into "--user=NAME" */ - free(argv[i]); - argv[i] = strjoin("--user=", argv[i + 1]); - if (!argv[i]) - return log_oom(); - - /* Remove the now-consumed argument and shrink argc accordingly */ - free(argv[i + 1]); - memmove(argv + i + 1, argv + i + 2, (argc - i - 1) * sizeof(char*)); - argc--; - } - OptionParser state = { argc, argv, /* stop_at_first_nonoption= */ true }; const Option *opt; const char *arg; @@ -1401,10 +1364,23 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "user", "NAME", "Run in the user service manager scope"): - if (arg) { + if (arg) /* --user=NAME is a deprecated alias for --uid=NAME */ log_warning("--user=NAME is deprecated, use --uid=NAME instead."); + else { + /* --user= used to require an argument (the container user to run as). It has + * been repurposed to optionally set the runtime scope, with --uid= replacing + * the old container user functionality. To maintain backwards compatibility + * with the space-separated form (--user NAME), if the next arg does not look + * like an option, interpret it a user name. */ + const char *t = option_parser_next_arg(&state); + if (t && t[0] != '-') { + arg = option_parser_consume_next_arg(&state); + log_warning("--user NAME is deprecated, use --uid=NAME instead."); + } + } + if (arg) { r = free_and_strdup(&arg_user, arg); if (r < 0) return log_oom(); From 34649c6f558f78470451cedb294ae8f859791ead Mon Sep 17 00:00:00 2001 From: noxiouz Date: Tue, 7 Apr 2026 14:47:44 +0100 Subject: [PATCH 0838/1296] networkd: keep static lease section valid on MACAddress= reset config_parse_dhcp_static_lease_hwaddr() uses a cleanup helper that marks a lease section invalid unless ownership is taken. Add TAKE_PTR(lease) on the empty-rvalue reset path so subsequent valid MACAddress= assignments in the same section are not dropped. Co-developed-by: Codex (GPT-5) --- src/network/networkd-dhcp-server-static-lease.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/network/networkd-dhcp-server-static-lease.c b/src/network/networkd-dhcp-server-static-lease.c index 2f04232c49f1c..2814be8987cca 100644 --- a/src/network/networkd-dhcp-server-static-lease.c +++ b/src/network/networkd-dhcp-server-static-lease.c @@ -188,6 +188,7 @@ int config_parse_dhcp_static_lease_hwaddr( if (isempty(rvalue)) { lease->client_id = mfree(lease->client_id); lease->client_id_size = 0; + TAKE_PTR(lease); return 0; } From b5a1ecb8fc837643abda78429f09f744aac7fe12 Mon Sep 17 00:00:00 2001 From: noxiouz Date: Tue, 7 Apr 2026 14:47:11 +0100 Subject: [PATCH 0839/1296] networkd-wwan: handle link_get_by_name() errors in modem_simple_connect() modem_simple_connect() ignored the return value of link_get_by_name() and then checked link for NULL. Since the helper only sets the output pointer on success, that could read an indeterminate value. Check and log the return code directly with log_debug_errno(). Co-developed-by: Codex (GPT-5) --- src/network/networkd-wwan-bus.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/network/networkd-wwan-bus.c b/src/network/networkd-wwan-bus.c index 5d9818987522e..434063ee6c1d5 100644 --- a/src/network/networkd-wwan-bus.c +++ b/src/network/networkd-wwan-bus.c @@ -604,9 +604,9 @@ static void modem_simple_connect(Modem *modem) { if (!modem->port_name) return; - (void) link_get_by_name(modem->manager, modem->port_name, &link); - if (!link) - return (void) log_debug("ModemManager: cannot find link for %s", modem->port_name); + r = link_get_by_name(modem->manager, modem->port_name, &link); + if (r < 0) + return (void) log_debug_errno(r, "ModemManager: cannot find link for %s: %m", modem->port_name); /* Check if .network file found at all */ if (!link->network) From 8a8f22f1d8eb52ede2f3597b9e3674182beec2b4 Mon Sep 17 00:00:00 2001 From: Nandakumar Raghavan Date: Tue, 7 Apr 2026 06:33:41 +0000 Subject: [PATCH 0840/1296] tmpfiles: skip redundant label writes to avoid unnecessary timestamp changes When systemd-tmpfiles processes a 'z' (relabel) entry, fd_set_perms() unconditionally calls label_fix_full() even when mode, owner, and group already match. This causes setfilecon_raw() (SELinux) or xsetxattr() (SMACK) to write the security label even if it is already correct, which on some kernels updates the file's timestamps unnecessarily. Fix this by comparing the current label with the desired label before writing, and skipping the write when they already match. This is consistent with how fd_set_perms() already skips chmod/chown when the values are unchanged. --- src/basic/xattr-util.c | 12 +++++++++++ src/shared/selinux-util.c | 44 +++++++++++++++++++++++---------------- src/shared/smack-util.c | 10 +-------- 3 files changed, 39 insertions(+), 27 deletions(-) diff --git a/src/basic/xattr-util.c b/src/basic/xattr-util.c index e77d0bc8e84ef..fb886f6ae82c5 100644 --- a/src/basic/xattr-util.c +++ b/src/basic/xattr-util.c @@ -301,6 +301,18 @@ int xsetxattr_full( if (size == SIZE_MAX) size = strlen(value); + /* Skip the write if the xattr already has the correct value, to avoid + * unnecessary timestamp changes on the file. Only do this for plain + * replace mode (xattr_flags == 0) — XATTR_CREATE callers expect + * -EEXIST when the xattr already exists. */ + _cleanup_free_ char *old_value = NULL; + size_t old_size; + + if (xattr_flags == 0 && + getxattr_at_malloc(fd, path, name, at_flags, &old_value, &old_size) >= 0 && + memcmp_nn(old_value, old_size, value, size) == 0) + return 0; + if (have_xattrat && !isempty(path)) { struct xattr_args args = { .value = PTR_TO_UINT64(value), diff --git a/src/shared/selinux-util.c b/src/shared/selinux-util.c index 1049044f87bbd..6b8e318a4c8d0 100644 --- a/src/shared/selinux-util.c +++ b/src/shared/selinux-util.c @@ -360,6 +360,19 @@ void mac_selinux_disable_logging(void) { } #if HAVE_SELINUX +static int setfilecon_idempotent(int fd, const char *context) { + _cleanup_freecon_ char *oldcon = NULL; + + assert(fd >= 0); + assert(context); + + /* Read current context via /proc/self/fd/ so this works for O_PATH fds too */ + if (sym_getfilecon_raw(FORMAT_PROC_FD_PATH(fd), &oldcon) >= 0 && streq_ptr(context, oldcon)) + return 0; /* Already correct */ + + return RET_NERRNO(sym_setfilecon_raw(FORMAT_PROC_FD_PATH(fd), context)); +} + static int selinux_fix_fd( int fd, const char *label_path, @@ -389,25 +402,19 @@ static int selinux_fix_fd( return log_selinux_enforcing_errno(errno, "Unable to lookup intended SELinux security context of %s: %m", label_path); } - r = RET_NERRNO(sym_setfilecon_raw(FORMAT_PROC_FD_PATH(fd), fcon)); - if (r < 0) { - /* If the FS doesn't support labels, then exit without warning */ - if (ERRNO_IS_NOT_SUPPORTED(r)) - return 0; - - /* It the FS is read-only and we were told to ignore failures caused by that, suppress error */ - if (r == -EROFS && (flags & LABEL_IGNORE_EROFS)) - return 0; + r = setfilecon_idempotent(fd, fcon); + if (r >= 0) + return 0; - /* If the old label is identical to the new one, suppress any kind of error */ - _cleanup_freecon_ char *oldcon = NULL; - if (sym_getfilecon_raw(FORMAT_PROC_FD_PATH(fd), &oldcon) >= 0 && streq_ptr(fcon, oldcon)) - return 0; + /* If the FS doesn't support labels, then exit without warning */ + if (ERRNO_IS_NOT_SUPPORTED(r)) + return 0; - return log_selinux_enforcing_errno(r, "Unable to fix SELinux security context of %s: %m", label_path); - } + /* If the FS is read-only and we were told to ignore failures caused by that, suppress error */ + if (r == -EROFS && (flags & LABEL_IGNORE_EROFS)) + return 0; - return 0; + return log_selinux_enforcing_errno(r, "Unable to fix SELinux security context of %s: %m", label_path); } #endif @@ -495,8 +502,9 @@ int mac_selinux_apply_fd(int fd, const char *path, const char *label) { assert(label); - if (sym_setfilecon_raw(FORMAT_PROC_FD_PATH(fd), label) < 0) - return log_selinux_enforcing_errno(errno, "Failed to set SELinux security context %s on path %s: %m", label, strna(path)); + r = setfilecon_idempotent(fd, label); + if (r < 0) + return log_selinux_enforcing_errno(r, "Failed to set SELinux security context %s on path %s: %m", label, strna(path)); #endif return 0; } diff --git a/src/shared/smack-util.c b/src/shared/smack-util.c index 6faec02a9f8ee..e1dbf8686edda 100644 --- a/src/shared/smack-util.c +++ b/src/shared/smack-util.c @@ -149,16 +149,8 @@ static int smack_fix_fd( to ignore failures caused by that, suppress error */ return 0; - if (r < 0) { - /* If the old label is identical to the new one, suppress any kind of error */ - _cleanup_free_ char *old_label = NULL; - - if (fgetxattr_malloc(fd, "security.SMACK64", &old_label, /* ret_size= */ NULL) >= 0 && - streq(old_label, label)) - return 0; - + if (r < 0) return log_debug_errno(r, "Unable to fix SMACK label of '%s': %m", label_path); - } return 0; } From 329233dcbf769a98eeaa6f1416b6f446d9263f13 Mon Sep 17 00:00:00 2001 From: noxiouz Date: Tue, 7 Apr 2026 16:29:51 +0100 Subject: [PATCH 0841/1296] sd-journal: make sd_journal_get_data() output params optional Allow callers to pass NULL for ret_data and/or ret_size when they only need to check whether a field exists. Initialize provided output pointers to safe defaults and update the manual page accordingly. Propagate the NULL-ness through to journal_file_data_payload() so that downstream helpers can optimize for the existence-check case. Co-developed-by: Claude Opus 4.6 --- man/sd_journal_get_data.xml | 10 ++++++++-- src/libsystemd/sd-journal/sd-journal.c | 11 ++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/man/sd_journal_get_data.xml b/man/sd_journal_get_data.xml index e3c8e0b5cd99e..a902d76c73e3b 100644 --- a/man/sd_journal_get_data.xml +++ b/man/sd_journal_get_data.xml @@ -83,8 +83,10 @@ sd_journal_get_data() gets the data object associated with a specific field from the current journal entry. It takes four arguments: the journal context object, a string with the - field name to request, plus a pair of pointers to pointer/size variables where the data object and its - size shall be stored in. The field name should be an entry field name. Well-known field names are listed in + field name to request, plus a pair of optional pointers to pointer/size variables where the data object and + its size shall be stored in. Either pointer may be NULL, in which case the corresponding + value is not returned (this is supported since version 261). The field name should be an entry field name. + Well-known field names are listed in systemd.journal-fields7, but any field can be specified. The returned data is in a read-only memory map and is only valid until the next invocation of sd_journal_get_data(), @@ -162,6 +164,10 @@ -EINVAL One of the required parameters is NULL or invalid. + For sd_journal_get_data(), only the journal context object and field name + are required non-NULL. For sd_journal_enumerate_data() + and sd_journal_enumerate_available_data(), + ret_data and ret_size are required as well. diff --git a/src/libsystemd/sd-journal/sd-journal.c b/src/libsystemd/sd-journal/sd-journal.c index befa1945176f3..3e5185b23f7e0 100644 --- a/src/libsystemd/sd-journal/sd-journal.c +++ b/src/libsystemd/sd-journal/sd-journal.c @@ -2822,8 +2822,6 @@ _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void ** assert_return(j, -EINVAL); assert_return(!journal_origin_changed(j), -ECHILD); assert_return(field, -EINVAL); - assert_return(ret_data, -EINVAL); - assert_return(ret_size, -EINVAL); assert_return(field_is_valid(field), -EINVAL); f = j->current_file; @@ -2846,7 +2844,8 @@ _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void ** size_t l; p = journal_file_entry_item_object_offset(f, o, i); - r = journal_file_data_payload(f, NULL, p, field, field_length, j->data_threshold, &d, &l); + r = journal_file_data_payload(f, NULL, p, field, field_length, j->data_threshold, + ret_data ? &d : NULL, ret_size ? &l : NULL); if (r == 0) continue; if (IN_SET(r, -EADDRNOTAVAIL, -EBADMSG)) { @@ -2856,8 +2855,10 @@ _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void ** if (r < 0) return r; - *ret_data = d; - *ret_size = l; + if (ret_data) + *ret_data = d; + if (ret_size) + *ret_size = l; return 0; } From a5c811591ff25fef3f3fd2b6ae09eaa6eeb7e5bd Mon Sep 17 00:00:00 2001 From: noxiouz Date: Tue, 7 Apr 2026 16:29:56 +0100 Subject: [PATCH 0842/1296] sd-journal: skip full decompression when caller only checks field existence When both ret_data and ret_size are NULL after decompress_startswith() has confirmed the field matches, skip the decompress_blob() call. This avoids decompressing potentially large payloads (e.g. inline coredumps) just to discard the result. Co-developed-by: Claude Opus 4.6 --- src/libsystemd/sd-journal/journal-file.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index 363df258a1770..235f471224504 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -1965,6 +1965,10 @@ static int maybe_decompress_payload( *ret_size = 0; return 0; } + + /* Caller only wants to check field existence, skip full decompression */ + if (!ret_data && !ret_size) + return 1; } r = decompress_blob(compression, payload, size, &f->compress_buffer, &rsize, 0); From 836de2900ba4d3b2afd015308774d26a1de2ca81 Mon Sep 17 00:00:00 2001 From: noxiouz Date: Tue, 7 Apr 2026 16:30:04 +0100 Subject: [PATCH 0843/1296] coredumpctl: use NULL outputs for COREDUMP existence checks print_list() and print_info() used RETRIEVE() to strndup() the entire COREDUMP field into a heap-allocated string, only to check whether it exists. With sd_journal_set_data_threshold(j, 0) in print_info(), this copies the full coredump binary (potentially hundreds of MB) to heap just to print "Storage: journal". Now that sd_journal_get_data() accepts NULL output pointers, use a direct NULL/NULL existence check instead. Co-developed-by: Claude Opus 4.6 --- src/coredump/coredumpctl.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index 96724de12b635..86d75bbdc9123 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -552,14 +552,14 @@ static int print_list(FILE* file, sd_journal *j, Table *t) { _cleanup_free_ char *mid = NULL, *pid = NULL, *uid = NULL, *gid = NULL, *sgnl = NULL, *exe = NULL, *comm = NULL, - *filename = NULL, *truncated = NULL, *coredump = NULL; + *filename = NULL, *truncated = NULL; const void *d; size_t l; usec_t ts; int r, signal_as_int = 0; const char *present = NULL, *color = NULL; uint64_t size = UINT64_MAX; - bool normal_coredump; + bool normal_coredump, has_inline_coredump; uid_t uid_as_int = UID_INVALID; gid_t gid_as_int = GID_INVALID; pid_t pid_as_int = 0; @@ -578,9 +578,11 @@ static int print_list(FILE* file, sd_journal *j, Table *t) { RETRIEVE(d, l, "COREDUMP_COMM", comm); RETRIEVE(d, l, "COREDUMP_FILENAME", filename); RETRIEVE(d, l, "COREDUMP_TRUNCATED", truncated); - RETRIEVE(d, l, "COREDUMP", coredump); } + /* Check for an inline coredump without copying the (potentially large) payload to heap. */ + has_inline_coredump = sd_journal_get_data(j, "COREDUMP", NULL, NULL) >= 0; + if (!pid || !uid || !gid || !sgnl || !comm) { log_warning("Found a coredump entry without mandatory fields (PID=%s, UID=%s, GID=%s, SIGNAL=%s, COMM=%s), ignoring.", strna(pid), strna(uid), strna(gid), strna(sgnl), strna(comm)); @@ -604,7 +606,7 @@ static int print_list(FILE* file, sd_journal *j, Table *t) { return r; analyze_coredump_file(filename, &present, &color, &size); - } else if (coredump) + } else if (has_inline_coredump) present = "journal"; else if (normal_coredump) { present = "none"; @@ -640,12 +642,12 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) { *boot_id = NULL, *machine_id = NULL, *hostname = NULL, *slice = NULL, *cgroup = NULL, *owner_uid = NULL, *message = NULL, *timestamp = NULL, *filename = NULL, - *truncated = NULL, *coredump = NULL, + *truncated = NULL, *pkgmeta_name = NULL, *pkgmeta_version = NULL, *pkgmeta_json = NULL, *tid = NULL, *thread_name = NULL; const void *d; size_t l; - bool normal_coredump; + bool normal_coredump, has_inline_coredump; int r; assert(file); @@ -672,7 +674,6 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) { RETRIEVE(d, l, "COREDUMP_TIMESTAMP", timestamp); RETRIEVE(d, l, "COREDUMP_FILENAME", filename); RETRIEVE(d, l, "COREDUMP_TRUNCATED", truncated); - RETRIEVE(d, l, "COREDUMP", coredump); RETRIEVE(d, l, "COREDUMP_PACKAGE_NAME", pkgmeta_name); RETRIEVE(d, l, "COREDUMP_PACKAGE_VERSION", pkgmeta_version); RETRIEVE(d, l, "COREDUMP_PACKAGE_JSON", pkgmeta_json); @@ -683,6 +684,9 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) { RETRIEVE(d, l, "MESSAGE", message); } + /* Check for an inline coredump without copying the (potentially large) payload to heap. */ + has_inline_coredump = sd_journal_get_data(j, "COREDUMP", NULL, NULL) >= 0; + if (need_space) fputs("\n", file); @@ -820,7 +824,7 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) { if (size != UINT64_MAX) fprintf(file, " Size on Disk: %s\n", FORMAT_BYTES(size)); - } else if (coredump) + } else if (has_inline_coredump) fprintf(file, " Storage: journal\n"); else fprintf(file, " Storage: none\n"); From 69b6e950e2497283d7c7639da4073285c500fa5b Mon Sep 17 00:00:00 2001 From: noxiouz Date: Tue, 7 Apr 2026 14:47:33 +0100 Subject: [PATCH 0844/1296] networkd-wwan: drop unreachable unknown-bearer fallback path bearer_get_by_path() only succeeds when both modem and bearer are found. On failure, trying bearer_new_and_initialize(modem, path) was unreachable and relied on a modem value that is not returned on that path. Treat unknown bearers as no-op and rely on modem_map_bearers() for association during initialization. Co-developed-by: Codex (GPT-5) --- src/network/networkd-wwan-bus.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/network/networkd-wwan-bus.c b/src/network/networkd-wwan-bus.c index 434063ee6c1d5..19fcf081ae188 100644 --- a/src/network/networkd-wwan-bus.c +++ b/src/network/networkd-wwan-bus.c @@ -807,15 +807,10 @@ static int bearer_properties_changed_handler( if (!path) return 0; - if (bearer_get_by_path(manager, path, &modem, &b) < 0) { - /* - * Have new bearer: check if we have the corresponding modem - * for it which we might not during initialization. - */ - if (modem) - (void) bearer_new_and_initialize(modem, path); + if (bearer_get_by_path(manager, path, &modem, &b) < 0) + /* Unknown bearer, nothing to do. Modem-bearer association is handled + * by modem_map_bearers() during modem property initialization. */ return 0; - } if (b->slot_getall) { /* Not initialized yet. Re-initialize it. */ From 7b2a10b594d7ed0f463b78f29f1b30f8c1c6e64e Mon Sep 17 00:00:00 2001 From: ipv6 Date: Wed, 8 Apr 2026 10:28:59 -0500 Subject: [PATCH 0845/1296] Added tmpfiles.d/root.conf to set access permissions to root / dir --- tmpfiles.d/root.conf | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 tmpfiles.d/root.conf diff --git a/tmpfiles.d/root.conf b/tmpfiles.d/root.conf new file mode 100644 index 0000000000000..9db0aaa92eda4 --- /dev/null +++ b/tmpfiles.d/root.conf @@ -0,0 +1,10 @@ +# 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. + +# See tmpfiles.d(5) for details. + +z / 555 - - - From 15384f6e1f614bc7461f2dccac6a0ccdc04704d4 Mon Sep 17 00:00:00 2001 From: ipv6 Date: Wed, 8 Apr 2026 10:43:49 -0500 Subject: [PATCH 0846/1296] Added NEWS --- NEWS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS b/NEWS index c717a355ecdfa..b8fbd7eb089f6 100644 --- a/NEWS +++ b/NEWS @@ -15,6 +15,9 @@ CHANGES WITH 261 in spe: New features: + * A new tmpfiles.d/root.conf has been added that sets permissions + on the root directory (/) to 0555 + * Networking to cloud IMDS services may be locked down for recognized clouds. This is recommended for secure installations, but typically conflicts with traditional IMDS clients such as cloud-init, which From a4b5985734ca9c29b1839c28f904cbdffa94e000 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 8 Apr 2026 17:35:10 +0000 Subject: [PATCH 0847/1296] clangd: Strip GCC-only flags and silence unknown-attributes Several GCC-only options in our compile_commands.json (-fwide-exec-charset=UCS2, used by EFI boot code for UTF-16 string literals, and -maccumulate-outgoing-args) cause clangd to emit driver-level "unknown argument" errors. These can't be silenced through Diagnostics.Suppress, so remove them via CompileFlags.Remove before clang ever sees them. Also suppress the -Wunknown-attributes warning that fires on every use of _no_reorder_, since meson unconditionally expands it to the GCC-only __no_reorder__ attribute when configured with GCC. --- .clangd | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.clangd b/.clangd index 24886efe9e86a..7ce59c6002f41 100644 --- a/.clangd +++ b/.clangd @@ -1,4 +1,16 @@ # SPDX-License-Identifier: LGPL-2.1-or-later +# Strip GCC-only flags from compile_commands.json before clang sees them. +# clangd reports these as driver-level "unknown argument" errors which can't +# be silenced via Diagnostics.Suppress, so they must be removed instead. +# -fwide-exec-charset: used by EFI boot code to make L"..." literals UTF-16 +# -maccumulate-outgoing-args: GCC x86 codegen flag, no clang equivalent +CompileFlags: + Remove: [-fwide-exec-charset=*, -maccumulate-outgoing-args] + Diagnostics: UnusedIncludes: Strict + # __no_reorder__ is a GCC-only attribute (see _no_reorder_ in + # src/fundamental/macro-fundamental.h). Meson detects it during configure + # with GCC and enables it unconditionally, so clangd flags every use. + Suppress: [unknown-attributes] From 1e551f0d96c4af44f61195bfe23646aa88c1528f Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Wed, 8 Apr 2026 16:30:54 +0200 Subject: [PATCH 0848/1296] sd-varlink: check flags against the correct field Otherwise even a method without SD_VARLINK_SUPPORTS_MORE/REQUIRES_MORE can emit "continues" replies without our IDL validation catching it. --- src/libsystemd/sd-varlink/sd-varlink-idl.c | 2 +- src/test/test-varlink-idl.c | 42 ++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/libsystemd/sd-varlink/sd-varlink-idl.c b/src/libsystemd/sd-varlink/sd-varlink-idl.c index 22b6026a01f14..0b0ea244d6c9f 100644 --- a/src/libsystemd/sd-varlink/sd-varlink-idl.c +++ b/src/libsystemd/sd-varlink/sd-varlink-idl.c @@ -1955,7 +1955,7 @@ int varlink_idl_validate_method_reply(const sd_varlink_symbol *method, sd_json_v return -EBADMSG; /* If method replies have the "continues" flag set, but the method is not allowed to generate that, return a recognizable error */ - if (FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES) && (method->symbol_type & (SD_VARLINK_SUPPORTS_MORE|SD_VARLINK_REQUIRES_MORE)) == 0) + if (FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES) && (method->symbol_flags & (SD_VARLINK_SUPPORTS_MORE|SD_VARLINK_REQUIRES_MORE)) == 0) return -EBADE; return varlink_idl_validate_symbol(method, v, SD_VARLINK_OUTPUT, reterr_bad_field); diff --git a/src/test/test-varlink-idl.c b/src/test/test-varlink-idl.c index 0759c8292dcf3..4b1e79c03cffd 100644 --- a/src/test/test-varlink-idl.c +++ b/src/test/test-varlink-idl.c @@ -621,4 +621,46 @@ TEST(null_map_element) { ASSERT_STREQ(bad_field, "m"); } +static SD_VARLINK_DEFINE_METHOD_FULL( + SupportsMoreMethod, + SD_VARLINK_SUPPORTS_MORE, + SD_VARLINK_DEFINE_OUTPUT(result, SD_VARLINK_STRING, 0)); + +TEST(reply_continues_with_more_flag) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + ASSERT_OK(sd_json_buildo(&v, SD_JSON_BUILD_PAIR_STRING("result", "hello"))); + + const char *bad_field = NULL; + ASSERT_OK(varlink_idl_validate_method_reply( + &vl_method_SupportsMoreMethod, v, SD_VARLINK_REPLY_CONTINUES, &bad_field)); + ASSERT_NULL(bad_field); + + ASSERT_OK(varlink_idl_validate_method_reply( + &vl_method_SupportsMoreMethod, v, /* flags= */ 0, &bad_field)); + ASSERT_NULL(bad_field); +} + +static SD_VARLINK_DEFINE_METHOD( + NoMoreMethod, + SD_VARLINK_DEFINE_OUTPUT(result, SD_VARLINK_STRING, 0)); + +TEST(reply_continues_without_more_flag) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + ASSERT_OK(sd_json_buildo(&v, SD_JSON_BUILD_PAIR_STRING("result", "hello"))); + + const char *bad_field = NULL; + /* Request a "continues" reply from a method without SD_VARLINK_SUPPORTS_MORE/REQUIRES_MORE - this + * should fail the validation with EBADE */ + ASSERT_ERROR(varlink_idl_validate_method_reply( + &vl_method_NoMoreMethod, v, SD_VARLINK_REPLY_CONTINUES, &bad_field), EBADE); + ASSERT_NULL(bad_field); + + /* Without the "continues" flag, validation should succeed */ + ASSERT_OK(varlink_idl_validate_method_reply( + &vl_method_NoMoreMethod, v, /* flags= */ 0, &bad_field)); + ASSERT_NULL(bad_field); +} + DEFINE_TEST_MAIN(LOG_DEBUG); From e2227c8a207e7afbaa07cd796aac5728d423bf4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 8 Apr 2026 16:41:06 +0200 Subject: [PATCH 0849/1296] nspawn,vmspawn: fixups for recent changes Nits found in post-merge review for bf5bc9a7b26191ff4254836bd909cbb92eafe480 and 4c778c51c07db3eb0e07dd875f10eb85f086f096. --- src/nspawn/nspawn.c | 4 ++-- src/vmspawn/vmspawn.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 211f1248932a2..10fda88c48054 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -379,7 +379,7 @@ static int help(void) { if (r < 0) return log_oom(); - static const char *groups[] = { + static const char* const groups[] = { NULL, "Image", "Execution", @@ -1372,7 +1372,7 @@ static int parse_argv(int argc, char *argv[]) { * been repurposed to optionally set the runtime scope, with --uid= replacing * the old container user functionality. To maintain backwards compatibility * with the space-separated form (--user NAME), if the next arg does not look - * like an option, interpret it a user name. */ + * like an option, interpret it as a user name. */ const char *t = option_parser_next_arg(&state); if (t && t[0] != '-') { arg = option_parser_consume_next_arg(&state); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 93c8fbbbbd2ed..f5dc4f17a8ce3 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -218,7 +218,7 @@ static int help(void) { if (r < 0) return log_oom(); - static const char *groups[] = { + static const char* const groups[] = { NULL, "Image", "Host Configuration", From bb93fdaaf3b3e47360d8f136e7137cdb352dd9ed Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 27 Mar 2026 14:26:16 +0100 Subject: [PATCH 0850/1296] vmspawn: Support direct kernel boot without UEFI firmware When --linux= specifies a non-PE kernel image, automatically disable UEFI firmware loading (as if --firmware= was passed). If --firmware= is explicitly set to a path in this case, fail with an error. Booting a UKI with --firmware= is also rejected since UKIs require UEFI. --firmware= (empty string) can also be used explicitly to disable firmware loading for PE kernels. Other changes: - Extract OVMF pflash drive setup into cmdline_add_ovmf() - Extract kernel image type detection into determine_kernel() - Add smbios_supported() helper to centralize the SMBIOS availability check (always available on x86, elsewhere requires firmware) - Gate SMM, OVMF drives, SMBIOS11 and credential SMBIOS paths on firmware/SMBIOS being available - Beef up the credential logic to fall back to fw_cfg and kernel command line in case SMBIOS is not available --- man/systemd-vmspawn.xml | 24 +- src/vmspawn/vmspawn-settings.c | 8 + src/vmspawn/vmspawn-settings.h | 9 + src/vmspawn/vmspawn-util.h | 20 ++ src/vmspawn/vmspawn.c | 551 +++++++++++++++++++++------------ 5 files changed, 411 insertions(+), 201 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 92872233aee54..6ae4bd304a002 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -330,12 +330,19 @@ - Takes an absolute path, or a relative path beginning with - ./. Specifies a JSON firmware definition file, which allows selecting the - firmware to boot in the VM. If not specified, a suitable firmware is automatically discovered. If the - special string list is specified lists all discovered firmwares. If the special - string describe is specified, the firmware that would be selected (taking - into account) is printed and the program exits. + Selects which firmware to use in the VM. Takes one of auto, + uefi, bios, none, an absolute path, or a + relative path beginning with ./. Defaults to auto, which + selects UEFI firmware unless specifies a non-PE kernel image, in which + case none is selected. uefi loads OVMF firmware (use a path + to a JSON firmware definition file to select a specific one). bios skips OVMF + loading and lets QEMU use its built-in BIOS (e.g. SeaBIOS on x86). none disables + firmware loading entirely and requires to be specified for direct kernel + boot. Booting a UKI requires uefi. If the special string list + is specified, all discovered firmware definition files are listed. If the special string + describe is specified, the UEFI firmware that would be selected (taking + into account) is printed and the program exits. If an empty + string is specified, the option is reset to its default. @@ -761,6 +768,11 @@ embed a NUL byte). Note that the invoking shell might already apply unescaping once, hence this might require double escaping! + Credentials are preferably passed to the VM via SMBIOS Type 11 strings or QEMU fw_cfg files. + If neither mechanism is available, credentials are passed on the kernel command line using + systemd.set_credential_binary= which is not a confidential channel. Do not use + this for passing secrets to the VM in that case. + diff --git a/src/vmspawn/vmspawn-settings.c b/src/vmspawn/vmspawn-settings.c index 56a07b3f6f01d..9382172e2ca80 100644 --- a/src/vmspawn/vmspawn-settings.c +++ b/src/vmspawn/vmspawn-settings.c @@ -44,3 +44,11 @@ static const char *const console_transport_table[_CONSOLE_TRANSPORT_MAX] = { }; DEFINE_STRING_TABLE_LOOKUP(console_transport, ConsoleTransport); + +static const char *const firmware_table[_FIRMWARE_MAX] = { + [FIRMWARE_UEFI] = "uefi", + [FIRMWARE_BIOS] = "bios", + [FIRMWARE_NONE] = "none", +}; + +DEFINE_STRING_TABLE_LOOKUP(firmware, Firmware); diff --git a/src/vmspawn/vmspawn-settings.h b/src/vmspawn/vmspawn-settings.h index 83d28725359df..f02b499201ed8 100644 --- a/src/vmspawn/vmspawn-settings.h +++ b/src/vmspawn/vmspawn-settings.h @@ -49,6 +49,14 @@ typedef enum ConsoleTransport { _CONSOLE_TRANSPORT_INVALID = -EINVAL, } ConsoleTransport; +typedef enum Firmware { + FIRMWARE_UEFI, /* load OVMF firmware */ + FIRMWARE_BIOS, /* don't load OVMF, let qemu use its built-in BIOS (e.g. SeaBIOS on x86) */ + FIRMWARE_NONE, /* no firmware at all, requires --linux= for direct kernel boot */ + _FIRMWARE_MAX, + _FIRMWARE_INVALID = -EINVAL, +} Firmware; + typedef enum SettingsMask { SETTING_START_MODE = UINT64_C(1) << 0, SETTING_MACHINE_ID = UINT64_C(1) << 6, @@ -62,4 +70,5 @@ typedef enum SettingsMask { DECLARE_STRING_TABLE_LOOKUP(console_mode, ConsoleMode); DECLARE_STRING_TABLE_LOOKUP(console_transport, ConsoleTransport); DECLARE_STRING_TABLE_LOOKUP(disk_type, DiskType); +DECLARE_STRING_TABLE_LOOKUP(firmware, Firmware); DECLARE_STRING_TABLE_LOOKUP(image_format, ImageFormat); diff --git a/src/vmspawn/vmspawn-util.h b/src/vmspawn/vmspawn-util.h index d9272b49000b2..4e3e4c131326d 100644 --- a/src/vmspawn/vmspawn-util.h +++ b/src/vmspawn/vmspawn-util.h @@ -39,6 +39,26 @@ # define ARCHITECTURE_SUPPORTS_CXL 0 #endif +#if defined(__x86_64__) || defined(__i386__) || defined(__arm__) || defined(__aarch64__) || defined(__riscv) || defined(__loongarch64) +# define ARCHITECTURE_SUPPORTS_FW_CFG 1 +#else +# define ARCHITECTURE_SUPPORTS_FW_CFG 0 +#endif + +/* QEMU's fw_cfg file path buffer is FW_CFG_MAX_FILE_PATH (56) bytes including NUL */ +#define QEMU_FW_CFG_MAX_KEY_LEN 55 + +/* These match the kernel's COMMAND_LINE_SIZE for each architecture */ +#if defined(__loongarch64) +# define KERNEL_CMDLINE_SIZE 4096 +#elif defined(__x86_64__) || defined(__i386__) || defined(__aarch64__) +# define KERNEL_CMDLINE_SIZE 2048 +#elif defined(__arm__) || defined(__riscv) +# define KERNEL_CMDLINE_SIZE 1024 +#else +# define KERNEL_CMDLINE_SIZE 512 +#endif + #if defined(__x86_64__) || defined(__i386__) # define QEMU_MACHINE_TYPE "q35" #elif defined(__arm__) || defined(__aarch64__) || defined(__riscv) || defined(__loongarch64) || defined(__m68k__) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 93c8fbbbbd2ed..5065f38744059 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -138,6 +138,7 @@ static int arg_vsock = -1; static unsigned arg_vsock_cid = VMADDR_CID_ANY; static int arg_tpm = -1; static char *arg_linux = NULL; +static KernelImageType arg_linux_image_type = _KERNEL_IMAGE_TYPE_INVALID; static char **arg_initrds = NULL; static ConsoleMode arg_console_mode = CONSOLE_INTERACTIVE; static ConsoleTransport arg_console_transport = CONSOLE_TRANSPORT_VIRTIO; @@ -146,6 +147,7 @@ static MachineCredentialContext arg_credentials = {}; static uid_t arg_uid_shift = UID_INVALID, arg_uid_range = 0x10000U; static RuntimeMountContext arg_runtime_mounts = {}; static char *arg_firmware = NULL; +static Firmware arg_firmware_type = _FIRMWARE_INVALID; static bool arg_firmware_describe = false; static Set *arg_firmware_features_include = NULL; static Set *arg_firmware_features_exclude = NULL; @@ -542,8 +544,15 @@ static int parse_argv(int argc, char *argv[]) { break; } - OPTION_LONG("firmware", "PATH|list|describe", - "Select firmware definition file (or list/describe available)"): + OPTION_LONG("firmware", "auto|uefi|bios|none|PATH|list|describe", + "Select firmware to use, or a firmware definition file (or list/describe available)"): { + if (isempty(arg) || streq(arg, "auto")) { + arg_firmware = mfree(arg_firmware); + arg_firmware_type = _FIRMWARE_INVALID; + arg_firmware_describe = false; + break; + } + if (streq(arg, "list")) { _cleanup_strv_free_ char **l = NULL; @@ -563,19 +572,33 @@ static int parse_argv(int argc, char *argv[]) { /* Handled after argument parsing so that --firmware-features= is * taken into account. */ arg_firmware = mfree(arg_firmware); + /* We only look for UEFI firmware when "describe" is specified. */ + arg_firmware_type = FIRMWARE_UEFI; arg_firmware_describe = true; break; } - arg_firmware_describe = false; + Firmware f = firmware_from_string(arg); + if (f >= 0) { + arg_firmware = mfree(arg_firmware); + arg_firmware_type = f; + arg_firmware_describe = false; + break; + } - if (!isempty(arg) && !path_is_absolute(arg) && !startswith(arg, "./")) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Absolute path or path starting with './' required."); + if (!path_is_absolute(arg) && !startswith(arg, "./")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Expected one of 'auto', 'uefi', 'bios', 'none', 'list', 'describe', or an absolute path or path starting with './', got: %s", + arg); r = parse_path_argument(arg, /* suppress_root= */ false, &arg_firmware); if (r < 0) return r; + + arg_firmware_type = FIRMWARE_UEFI; + arg_firmware_describe = false; break; + } OPTION_LONG("firmware-features", "FEATURE,...|list", "Require/exclude specific firmware features"): { @@ -1265,16 +1288,16 @@ static int on_child_exit(sd_event_source *s, const siginfo_t *si, void *userdata return 0; } -static int cmdline_add_vsock(char ***cmdline, int vsock_fd) { - int r; +static bool smbios_supported(void) { + /* SMBIOS is always available on x86 (via SeaBIOS fallback), but on + * other architectures it requires UEFI firmware to be loaded. */ + return ARCHITECTURE_SUPPORTS_SMBIOS && + (IN_SET(native_architecture(), ARCHITECTURE_X86, ARCHITECTURE_X86_64) || arg_firmware_type == FIRMWARE_UEFI); +} - assert(cmdline); +static int add_vsock_credential(int vsock_fd) { assert(vsock_fd >= 0); - r = strv_extend(cmdline, "-smbios"); - if (r < 0) - return r; - union sockaddr_union addr; socklen_t addr_len = sizeof addr.vm; if (getsockname(vsock_fd, &addr.sa, &addr_len) < 0) @@ -1283,54 +1306,57 @@ static int cmdline_add_vsock(char ***cmdline, int vsock_fd) { assert(addr_len >= sizeof addr.vm); assert(addr.vm.svm_family == AF_VSOCK); - r = strv_extendf(cmdline, "type=11,value=io.systemd.credential:vmm.notify_socket=vsock-stream:%u:%u", (unsigned) VMADDR_CID_HOST, addr.vm.svm_port); - if (r < 0) - return r; + _cleanup_free_ char *value = NULL; + if (asprintf(&value, "vsock-stream:%u:%u", (unsigned) VMADDR_CID_HOST, addr.vm.svm_port) < 0) + return -ENOMEM; - return 0; + return machine_credential_add(&arg_credentials, "vmm.notify_socket", value, SIZE_MAX); } -static int cmdline_add_kernel_cmdline(char ***cmdline, const char *kernel, const char *smbios_dir) { +static int cmdline_add_kernel_cmdline(char ***cmdline, int smbios_dir_fd, const char *smbios_dir) { int r; assert(cmdline); + assert(smbios_dir_fd >= 0); assert(smbios_dir); if (strv_isempty(arg_kernel_cmdline_extra)) return 0; - KernelImageType type = _KERNEL_IMAGE_TYPE_INVALID; - if (kernel) { - r = inspect_kernel(AT_FDCWD, kernel, &type); - if (r < 0) - return log_error_errno(r, "Failed to determine '%s' kernel image type: %m", kernel); - } - _cleanup_free_ char *kcl = strv_join(arg_kernel_cmdline_extra, " "); if (!kcl) return log_oom(); - if (kernel && type != KERNEL_IMAGE_TYPE_UKI) { + size_t kcl_len = strlen(kcl); + if (kcl_len >= KERNEL_CMDLINE_SIZE) + return log_error_errno(SYNTHETIC_ERRNO(E2BIG), + "Kernel command line length (%zu) exceeds the kernel's COMMAND_LINE_SIZE (%d).", + kcl_len, KERNEL_CMDLINE_SIZE); + + if (arg_linux_image_type >= 0 && arg_linux_image_type != KERNEL_IMAGE_TYPE_UKI) { if (strv_extend_many(cmdline, "-append", kcl) < 0) return log_oom(); } else { - if (!ARCHITECTURE_SUPPORTS_SMBIOS) { + if (!smbios_supported()) { log_warning("Cannot append extra args to kernel cmdline, native architecture doesn't support SMBIOS, ignoring."); return 0; } FOREACH_STRING(id, "io.systemd.stub.kernel-cmdline-extra", "io.systemd.boot.kernel-cmdline-extra") { - _cleanup_free_ char *p = path_join(smbios_dir, id); - if (!p) + _cleanup_free_ char *content = strjoin(id, "=", kcl); + if (!content) return log_oom(); - r = write_string_filef( - p, - WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE|WRITE_STRING_FILE_MODE_0600, - "%s=%s", id, kcl); + r = write_string_file_at( + smbios_dir_fd, id, content, + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE|WRITE_STRING_FILE_MODE_0600); if (r < 0) return log_error_errno(r, "Failed to write smbios kernel command line to file: %m"); + _cleanup_free_ char *p = path_join(smbios_dir, id); + if (!p) + return log_oom(); + if (strv_extend(cmdline, "-smbios") < 0) return log_oom(); @@ -1342,15 +1368,112 @@ static int cmdline_add_kernel_cmdline(char ***cmdline, const char *kernel, const return 0; } -static int cmdline_add_smbios11(char ***cmdline, const char* smbios_dir) { +static int cmdline_add_credentials(char ***cmdline, int smbios_dir_fd, const char *smbios_dir) { + int r; + + assert(cmdline); + assert(smbios_dir_fd >= 0); + assert(smbios_dir); + + FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) { + _cleanup_free_ char *cred_data_b64 = NULL; + ssize_t n; + + n = base64mem(cred->data, cred->size, &cred_data_b64); + if (n < 0) + return log_oom(); + + if (smbios_supported()) { + _cleanup_free_ char *content = NULL; + if (asprintf(&content, "io.systemd.credential.binary:%s=%s", cred->id, cred_data_b64) < 0) + return log_oom(); + + r = write_string_file_at( + smbios_dir_fd, cred->id, content, + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE|WRITE_STRING_FILE_MODE_0600); + if (r < 0) + return log_error_errno(r, "Failed to write smbios credential file: %m"); + + _cleanup_free_ char *p = path_join(smbios_dir, cred->id); + if (!p) + return log_oom(); + + if (strv_extend(cmdline, "-smbios") < 0) + return log_oom(); + + if (strv_extend_joined(cmdline, "type=11,path=", p) < 0) + return log_oom(); + + } else if (ARCHITECTURE_SUPPORTS_FW_CFG) { + /* fw_cfg keys are limited to 55 characters */ + _cleanup_free_ char *key = strjoin("opt/io.systemd.credentials/", cred->id); + if (!key) + return log_oom(); + + if (strlen(key) <= QEMU_FW_CFG_MAX_KEY_LEN) { + r = write_data_file_atomic_at( + smbios_dir_fd, cred->id, + &IOVEC_MAKE(cred->data, cred->size), + WRITE_DATA_FILE_MODE_0400); + if (r < 0) + return log_error_errno(r, "Failed to write fw_cfg credential file: %m"); + + _cleanup_free_ char *p = path_join(smbios_dir, cred->id); + if (!p) + return log_oom(); + + if (strv_extend(cmdline, "-fw_cfg") < 0) + return log_oom(); + + if (strv_extendf(cmdline, "name=%s,file=%s", key, p) < 0) + return log_oom(); + + continue; + } + + /* Fall through to kernel command line if key is too long */ + log_notice("fw_cfg key '%s' exceeds %d character limit, passing credential via kernel command line. " + "Note that this will make literal credentials readable to unprivileged userspace.", + key, QEMU_FW_CFG_MAX_KEY_LEN); + + if (arg_linux_image_type < 0) + return log_error_errno( + SYNTHETIC_ERRNO(E2BIG), + "Cannot pass credential '%s' to VM, fw_cfg key exceeds %d character limit and no kernel for direct boot specified.", + cred->id, + QEMU_FW_CFG_MAX_KEY_LEN); + + if (strv_extendf(&arg_kernel_cmdline_extra, + "systemd.set_credential_binary=%s:%s", cred->id, cred_data_b64) < 0) + return log_oom(); + + } else if (arg_linux_image_type >= 0) { + log_notice("Both SMBIOS and fw_cfg are not supported, passing credential via kernel command line. " + "Note that this will make literal credentials readable to unprivileged userspace."); + if (strv_extendf(&arg_kernel_cmdline_extra, + "systemd.set_credential_binary=%s:%s", cred->id, cred_data_b64) < 0) + return log_oom(); + } else + return log_error_errno( + SYNTHETIC_ERRNO(EOPNOTSUPP), + "Cannot pass credential '%s' to VM, native architecture doesn't support SMBIOS or fw_cfg and no kernel for direct boot specified.", + cred->id); + } + + return 0; +} + +static int cmdline_add_smbios11(char ***cmdline, int smbios_dir_fd, const char *smbios_dir) { int r; assert(cmdline); + assert(smbios_dir_fd >= 0); + assert(smbios_dir); if (strv_isempty(arg_smbios11)) return 0; - if (!ARCHITECTURE_SUPPORTS_SMBIOS) { + if (!smbios_supported()) { log_warning("Cannot issue SMBIOS Type #11 strings, native architecture doesn't support SMBIOS, ignoring."); return 0; } @@ -1362,8 +1485,13 @@ static int cmdline_add_smbios11(char ***cmdline, const char* smbios_dir) { if (r < 0) return r; - r = write_string_file( - p, *i, + _cleanup_free_ char *fn = NULL; + r = path_extract_filename(p, &fn); + if (r < 0) + return r; + + r = write_string_file_at( + smbios_dir_fd, fn, *i, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE|WRITE_STRING_FILE_MODE_0600); if (r < 0) return log_error_errno(r, "Failed to write smbios data to smbios file %s: %m", p); @@ -2057,9 +2185,117 @@ static int disk_serial(const char *filename, size_t max_len, char **ret) { return 0; } +static int cmdline_add_ovmf(FILE *config_file, const OvmfConfig *ovmf_config, char **ret_ovmf_vars) { + int r; + + assert(config_file); + assert(ret_ovmf_vars); + + if (!ovmf_config) { + *ret_ovmf_vars = NULL; + return 0; + } + + r = qemu_config_section(config_file, "drive", "ovmf-code", + "if", "pflash", + "format", ovmf_config_format(ovmf_config), + "readonly", "on", + "file", ovmf_config->path); + if (r < 0) + return r; + + if (!ovmf_config->vars && !arg_efi_nvram_template) { + *ret_ovmf_vars = NULL; + return 0; + } + + if (arg_efi_nvram_state_mode == STATE_AUTO && !arg_ephemeral) { + assert(!arg_efi_nvram_state_path); + + r = make_sidecar_path(".efinvramstate", &arg_efi_nvram_state_path); + if (r < 0) + return r; + + log_debug("Storing EFI NVRAM state persistently under '%s'.", arg_efi_nvram_state_path); + } + + const char *vars_source = arg_efi_nvram_template ?: ovmf_config->vars; + _cleanup_close_ int target_fd = -EBADF; + _cleanup_(unlink_and_freep) char *destroy_path = NULL; + bool newly_created; + const char *state; + if (arg_efi_nvram_state_path) { + _cleanup_free_ char *d = strdup(arg_efi_nvram_state_path); + if (!d) + return log_oom(); + + target_fd = openat_report_new(AT_FDCWD, arg_efi_nvram_state_path, O_WRONLY|O_CREAT|O_CLOEXEC, 0600, &newly_created); + if (target_fd < 0) + return log_error_errno(target_fd, "Failed to open file for OVMF vars at %s: %m", arg_efi_nvram_state_path); + + if (newly_created) + destroy_path = TAKE_PTR(d); + + r = fd_verify_regular(target_fd); + if (r < 0) + return log_error_errno(r, "Not a regular file for OVMF variables at %s: %m", arg_efi_nvram_state_path); + + state = arg_efi_nvram_state_path; + } else { + _cleanup_free_ char *t = NULL; + r = tempfn_random_child(/* p= */ NULL, "vmspawn-", &t); + if (r < 0) + return log_error_errno(r, "Failed to create temporary filename: %m"); + + target_fd = open(t, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0600); + if (target_fd < 0) + return log_error_errno(errno, "Failed to create regular file for OVMF vars at %s: %m", t); + + newly_created = true; + state = *ret_ovmf_vars = TAKE_PTR(t); + } + + if (newly_created) { + _cleanup_close_ int source_fd = open(vars_source, O_RDONLY|O_CLOEXEC); + if (source_fd < 0) + return log_error_errno(errno, "Failed to open OVMF vars file %s: %m", vars_source); + + r = copy_bytes(source_fd, target_fd, UINT64_MAX, COPY_REFLINK); + if (r < 0) + return log_error_errno(r, "Failed to copy bytes from %s to %s: %m", vars_source, state); + + /* This isn't always available so don't raise an error if it fails */ + (void) copy_times(source_fd, target_fd, 0); + } + + destroy_path = mfree(destroy_path); /* disarm auto-destroy */ + + /* Mark the UEFI variable store pflash as requiring SMM access. This + * prevents the guest OS from writing to pflash directly, ensuring all + * variable updates go through the firmware's validation checks. Without + * this, secure boot keys could be overwritten by the OS. */ + if (ARCHITECTURE_SUPPORTS_SMM) { + r = qemu_config_section(config_file, "global", /* id= */ NULL, + "driver", "cfi.pflash01", + "property", "secure", + "value", "on"); + if (r < 0) + return r; + } + + r = qemu_config_section(config_file, "drive", "ovmf-vars", + "file", state, + "if", "pflash", + "format", ovmf_config_format(ovmf_config)); + if (r < 0) + return r; + + return 0; +} + static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { _cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL; - _cleanup_free_ char *qemu_binary = NULL, *mem = NULL, *kernel = NULL; + _cleanup_free_ char *qemu_binary = NULL, *mem = NULL; _cleanup_(rm_rf_physical_and_freep) char *ssh_private_key_path = NULL, *ssh_public_key_path = NULL; _cleanup_(rm_rf_subvolume_and_freep) char *snapshot_directory = NULL; _cleanup_(release_lock_file) LockFile tree_global_lock = LOCK_FILE_INIT, tree_local_lock = LOCK_FILE_INIT; @@ -2115,18 +2351,20 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { use_kvm = r; } - if (arg_firmware) - r = load_ovmf_config(arg_firmware, &ovmf_config); - else - r = find_ovmf_config(arg_firmware_features_include, arg_firmware_features_exclude, &ovmf_config, /* ret_firmware_json= */ NULL); - if (r < 0) - return log_error_errno(r, "Failed to find OVMF config: %m"); + if (arg_firmware_type == FIRMWARE_UEFI) { + if (arg_firmware) + r = load_ovmf_config(arg_firmware, &ovmf_config); + else + r = find_ovmf_config(arg_firmware_features_include, arg_firmware_features_exclude, &ovmf_config, /* ret_firmware_json= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to find OVMF config: %m"); - if (set_contains(arg_firmware_features_include, "secure-boot") && !ovmf_config->supports_sb) - return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), - "Secure Boot requested, but selected OVMF firmware doesn't support it."); + if (set_contains(arg_firmware_features_include, "secure-boot") && !ovmf_config->supports_sb) + return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), + "Secure Boot requested, but selected OVMF firmware doesn't support it."); - log_debug("Using OVMF firmware %s Secure Boot support.", ovmf_config->supports_sb ? "with" : "without"); + log_debug("Using OVMF firmware %s Secure Boot support.", ovmf_config->supports_sb ? "with" : "without"); + } _cleanup_(machine_bind_user_context_freep) MachineBindUserContext *bind_user_context = NULL; r = machine_bind_user_prepare( @@ -2144,19 +2382,6 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (r < 0) return r; - if (arg_linux) { - kernel = strdup(arg_linux); - if (!kernel) - return log_oom(); - } else if (arg_directory) { - /* a kernel is required for directory type images so attempt to locate a UKI under /boot and /efi */ - r = discover_boot_entry(arg_directory, &kernel, &arg_initrds); - if (r < 0) - return log_error_errno(r, "Failed to locate UKI in directory type image, please specify one with --linux=."); - - log_debug("Discovered UKI image at %s", kernel); - } - r = find_qemu_binary(&qemu_binary); if (r == -EOPNOTSUPP) return log_error_errno(r, "Native architecture is not supported by qemu."); @@ -2207,7 +2432,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (r < 0) return r; - if (ARCHITECTURE_SUPPORTS_SMM) { + if (ovmf_config && ARCHITECTURE_SUPPORTS_SMM) { r = qemu_config_key(config_file, "smm", on_off(ovmf_config->supports_sb)); if (r < 0) return r; @@ -2399,7 +2624,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return r; } - bool use_vsock = arg_vsock > 0 && ARCHITECTURE_SUPPORTS_SMBIOS; + bool use_vsock = arg_vsock > 0; if (arg_vsock < 0) { r = qemu_check_vsock_support(); if (r < 0) @@ -2595,106 +2820,19 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } } - r = qemu_config_section(config_file, "drive", "ovmf-code", - "if", "pflash", - "format", ovmf_config_format(ovmf_config), - "readonly", "on", - "file", ovmf_config->path); + _cleanup_(unlink_and_freep) char *ovmf_vars = NULL; + r = cmdline_add_ovmf(config_file, ovmf_config, &ovmf_vars); if (r < 0) return r; - if (arg_efi_nvram_state_mode == STATE_AUTO && !arg_ephemeral) { - assert(!arg_efi_nvram_state_path); - - r = make_sidecar_path(".efinvramstate", &arg_efi_nvram_state_path); - if (r < 0) - return r; - - log_debug("Storing EFI NVRAM state persistently under '%s'.", arg_efi_nvram_state_path); - } - - _cleanup_(unlink_and_freep) char *ovmf_vars = NULL; - if (ovmf_config->vars || arg_efi_nvram_template) { - const char *vars_source = arg_efi_nvram_template ?: ovmf_config->vars; - _cleanup_close_ int target_fd = -EBADF; - _cleanup_(unlink_and_freep) char *destroy_path = NULL; - bool newly_created; - const char *state; - if (arg_efi_nvram_state_path) { - _cleanup_free_ char *d = strdup(arg_efi_nvram_state_path); - if (!d) - return log_oom(); - - target_fd = openat_report_new(AT_FDCWD, arg_efi_nvram_state_path, O_WRONLY|O_CREAT|O_CLOEXEC, 0600, &newly_created); - if (target_fd < 0) - return log_error_errno(target_fd, "Failed to open file for OVMF vars at %s: %m", arg_efi_nvram_state_path); - - if (newly_created) - destroy_path = TAKE_PTR(d); - - r = fd_verify_regular(target_fd); - if (r < 0) - return log_error_errno(r, "Not a regular file for OVMF variables at %s: %m", arg_efi_nvram_state_path); - - state = arg_efi_nvram_state_path; - } else { - _cleanup_free_ char *t = NULL; - r = tempfn_random_child(/* p= */ NULL, "vmspawn-", &t); - if (r < 0) - return log_error_errno(r, "Failed to create temporary filename: %m"); - - target_fd = open(t, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0600); - if (target_fd < 0) - return log_error_errno(errno, "Failed to create regular file for OVMF vars at %s: %m", t); - - newly_created = true; - state = ovmf_vars = TAKE_PTR(t); - } - - if (newly_created) { - _cleanup_close_ int source_fd = open(vars_source, O_RDONLY|O_CLOEXEC); - if (source_fd < 0) - return log_error_errno(errno, "Failed to open OVMF vars file %s: %m", vars_source); - - r = copy_bytes(source_fd, target_fd, UINT64_MAX, COPY_REFLINK); - if (r < 0) - return log_error_errno(r, "Failed to copy bytes from %s to %s: %m", vars_source, state); - - /* This isn't always available so don't raise an error if it fails */ - (void) copy_times(source_fd, target_fd, 0); - } - - destroy_path = mfree(destroy_path); /* disarm auto-destroy */ - - /* Mark the UEFI variable store pflash as requiring SMM access. This - * prevents the guest OS from writing to pflash directly, ensuring all - * variable updates go through the firmware's validation checks. Without - * this, secure boot keys could be overwritten by the OS. */ - if (ARCHITECTURE_SUPPORTS_SMM) { - r = qemu_config_section(config_file, "global", /* id= */ NULL, - "driver", "cfi.pflash01", - "property", "secure", - "value", "on"); - if (r < 0) - return r; - } - - r = qemu_config_section(config_file, "drive", "ovmf-vars", - "file", state, - "if", "pflash", - "format", ovmf_config_format(ovmf_config)); - if (r < 0) - return r; - } - - if (kernel) { - r = strv_extend_many(&cmdline, "-kernel", kernel); + if (arg_linux) { + r = strv_extend_many(&cmdline, "-kernel", arg_linux); if (r < 0) return log_oom(); /* We can't rely on gpt-auto-generator when direct kernel booting so synthesize a root= * kernel argument instead. */ - if (arg_image) { + if (arg_linux_image_type != KERNEL_IMAGE_TYPE_UKI && arg_image) { r = kernel_cmdline_maybe_append_root(); if (r < 0) return r; @@ -3074,15 +3212,11 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } _cleanup_(rm_rf_physical_and_freep) char *smbios_dir = NULL; - r = mkdtemp_malloc("/var/tmp/vmspawn-smbios-XXXXXX", &smbios_dir); - if (r < 0) - return log_error_errno(r, "Failed to create temporary directory: %m"); - - r = cmdline_add_kernel_cmdline(&cmdline, kernel, smbios_dir); - if (r < 0) - return r; + _cleanup_close_ int smbios_dir_fd = mkdtemp_open("/var/tmp/vmspawn-smbios-XXXXXX", /* flags= */ 0, &smbios_dir); + if (smbios_dir_fd < 0) + return log_error_errno(smbios_dir_fd, "Failed to create temporary directory: %m"); - r = cmdline_add_smbios11(&cmdline, smbios_dir); + r = cmdline_add_smbios11(&cmdline, smbios_dir_fd, smbios_dir); if (r < 0) return r; @@ -3284,47 +3418,24 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return log_error_errno(r, "Failed to set credential systemd.unit-dropin.sshd-vsock@.service: %m"); } - if (ARCHITECTURE_SUPPORTS_SMBIOS) - FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) { - _cleanup_free_ char *p = NULL, *cred_data_b64 = NULL; - ssize_t n; - - n = base64mem(cred->data, cred->size, &cred_data_b64); - if (n < 0) - return log_oom(); - - p = path_join(smbios_dir, cred->id); - if (!p) - return log_oom(); - - r = write_string_filef( - p, - WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE|WRITE_STRING_FILE_MODE_0600, - "io.systemd.credential.binary:%s=%s", cred->id, cred_data_b64); - if (r < 0) - return log_error_errno(r, "Failed to write smbios credential file %s: %m", p); - - r = strv_extend(&cmdline, "-smbios"); - if (r < 0) - return log_oom(); - - r = strv_extend_joined(&cmdline, "type=11,path=", p); - if (r < 0) - return log_oom(); - } - if (use_vsock) { notify_sock_fd = open_vsock(); if (notify_sock_fd < 0) return log_error_errno(notify_sock_fd, "Failed to open VSOCK: %m"); - r = cmdline_add_vsock(&cmdline, notify_sock_fd); - if (r == -ENOMEM) - return log_oom(); + r = add_vsock_credential(notify_sock_fd); if (r < 0) - return log_error_errno(r, "Failed to call getsockname on VSOCK: %m"); + return log_error_errno(r, "Failed to add VSOCK credential: %m"); } + r = cmdline_add_credentials(&cmdline, smbios_dir_fd, smbios_dir); + if (r < 0) + return r; + + r = cmdline_add_kernel_cmdline(&cmdline, smbios_dir_fd, smbios_dir); + if (r < 0) + return r; + /* Finalize the config file and add -readconfig to the cmdline */ r = fflush_and_check(config_file); if (r < 0) @@ -3651,10 +3762,56 @@ static int determine_names(void) { return 0; } +static int determine_kernel(void) { + int r; + + if (!arg_linux && arg_directory) { + /* A kernel is required for directory type images so attempt to find one under /boot and /efi */ + r = discover_boot_entry(arg_directory, &arg_linux, &arg_initrds); + if (r < 0) + return log_error_errno(r, "Failed to locate UKI in directory type image, please specify one with --linux=."); + + log_debug("Discovered UKI image at %s", arg_linux); + } + + if (!arg_linux) { + if (arg_firmware_type == _FIRMWARE_INVALID) + arg_firmware_type = FIRMWARE_UEFI; + return 0; + } + + r = inspect_kernel(AT_FDCWD, arg_linux, &arg_linux_image_type); + if (r < 0) + return log_error_errno(r, "Failed to determine '%s' kernel image type: %m", arg_linux); + + if (arg_linux_image_type == KERNEL_IMAGE_TYPE_UNKNOWN) { + if (arg_firmware_type == FIRMWARE_UEFI) + return log_error_errno( + SYNTHETIC_ERRNO(EINVAL), + "Kernel image '%s' is not a PE binary, --firmware=uefi (or a firmware path) is not supported.", + arg_linux); + if (arg_firmware_type == _FIRMWARE_INVALID) + arg_firmware_type = FIRMWARE_NONE; + } + + if (arg_firmware_type == _FIRMWARE_INVALID) + arg_firmware_type = FIRMWARE_UEFI; + + return 0; +} + static int verify_arguments(void) { if (!strv_isempty(arg_initrds) && !arg_linux) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --initrd= cannot be used without --linux=."); + if (arg_firmware_type != FIRMWARE_UEFI && arg_linux_image_type == KERNEL_IMAGE_TYPE_UKI) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Booting a UKI requires --firmware=uefi."); + + if (arg_firmware_type == FIRMWARE_NONE && !arg_linux) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--firmware=none requires --linux= to be specified."); + if (arg_image_disk_type == DISK_TYPE_VIRTIO_SCSI_CDROM) { if (arg_ephemeral) log_warning("--ephemeral has no effect with --image-disk-type=scsi-cd (CD-ROMs are read-only)."); @@ -3702,6 +3859,10 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; + r = determine_kernel(); + if (r < 0) + return r; + r = verify_arguments(); if (r < 0) return r; From 9bd72b612b76fab62ff6275c48ec19ced918e662 Mon Sep 17 00:00:00 2001 From: noxiouz Date: Tue, 7 Apr 2026 21:32:52 +0100 Subject: [PATCH 0851/1296] compress: write sparse files when decompressing to regular files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Core dumps are often very sparse, containing large zero-filled regions whose actual disk usage can be significantly reduced by preserving holes. Previously, decompress_stream() always wrote dense output, expanding all zero regions into allocated disk blocks. Each decompression backend (xz, lz4, zstd) now auto-detects whether the output fd is suitable for sparse writes via a shared should_sparse() helper. The check requires both S_ISREG (regular file) and !O_APPEND, since O_APPEND causes write() to ignore the file position set by lseek(), which would collapse the holes and corrupt the output. For pipes, sockets, and append-mode files, dense writes are preserved via loop_write_full() with USEC_INFINITY timeout, matching the original behavior. After sparse decompression, finalize_sparse() sets the final file size to account for any trailing holes. This is transparent to callers — all public signatures are unchanged. coredumpctl benefits automatically: - coredumpctl debug: temp file in /var/tmp is now sparse - coredumpctl dump -o file: output file is now sparse - coredumpctl dump > file: redirected stdout is now sparse - coredumpctl dump | ...: pipe output unchanged (dense) - coredumpctl dump >> file: append mode, falls back to dense Co-developed-by: Claude Opus 4.6 Co-developed-by: Codex (GPT-5) --- src/basic/compress.c | 73 +++++++++++++++++-- src/test/test-compress.c | 153 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+), 6 deletions(-) diff --git a/src/basic/compress.c b/src/basic/compress.c index d9759ad417fba..5f00f968a2842 100644 --- a/src/basic/compress.c +++ b/src/basic/compress.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include #include #include #include @@ -951,11 +952,69 @@ int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_unco #endif } +#if HAVE_COMPRESSION +/* Determine whether sparse writes should be used for this fd. Sparse writes are only safe on + * regular files without O_APPEND (O_APPEND ignores lseek position, which would collapse holes). */ +static int should_sparse(int fd) { + struct stat st; + + assert(fd >= 0); + + if (fstat(fd, &st) < 0) + return -errno; + + int flags = fcntl(fd, F_GETFL); + if (flags < 0) + return -errno; + + return S_ISREG(st.st_mode) && !FLAGS_SET(flags, O_APPEND); +} + +/* After sparse decompression, set the file size to the current position to account for + * trailing holes that sparse_write() created via lseek but never extended the file size for. */ +static int finalize_sparse(int fd) { + off_t pos; + + assert(fd >= 0); + + pos = lseek(fd, 0, SEEK_CUR); + if (pos < 0) + return -errno; + + if (ftruncate(fd, pos) < 0) + return -errno; + + return 0; +} + +static int maybe_sparse_write(int fd, const void *buf, size_t nbytes, bool sparse) { + int r; + + if (sparse) { + ssize_t k; + + /* Note: sparse_write() does not retry on EINTR and converts short writes to -EIO. + * This is fine here since sparse mode is only used on regular files, where short + * writes and EINTR are not expected in practice. */ + k = sparse_write(fd, buf, nbytes, 64); + if (k < 0) + return (int) k; + } else { + r = loop_write_full(fd, buf, nbytes, USEC_INFINITY); + if (r < 0) + return r; + } + + return 0; +} +#endif + int decompress_stream_xz(int fdf, int fdt, uint64_t max_bytes) { assert(fdf >= 0); assert(fdt >= 0); #if HAVE_XZ + bool sparse = should_sparse(fdt) > 0; int r; r = dlopen_lzma(); @@ -1009,7 +1068,7 @@ int decompress_stream_xz(int fdf, int fdt, uint64_t max_bytes) { max_bytes -= n; } - k = loop_write(fdt, out, n); + k = maybe_sparse_write(fdt, out, n, sparse); if (k < 0) return k; @@ -1021,7 +1080,7 @@ int decompress_stream_xz(int fdf, int fdt, uint64_t max_bytes) { s.total_in, s.total_out, (double) s.total_out / s.total_in * 100); - return 0; + return sparse ? finalize_sparse(fdt) : 0; } } } @@ -1038,6 +1097,7 @@ int decompress_stream_lz4(int fdf, int fdt, uint64_t max_bytes) { _cleanup_free_ char *buf = NULL; char *src; struct stat st; + bool sparse = should_sparse(fdt) > 0; int r; size_t total_in = 0, total_out = 0; @@ -1082,7 +1142,7 @@ int decompress_stream_lz4(int fdf, int fdt, uint64_t max_bytes) { goto cleanup; } - r = loop_write(fdt, buf, produced); + r = maybe_sparse_write(fdt, buf, produced, sparse); if (r < 0) goto cleanup; } @@ -1093,7 +1153,7 @@ int decompress_stream_lz4(int fdf, int fdt, uint64_t max_bytes) { log_debug("LZ4 decompression finished (%zu -> %zu bytes, %.1f%%)", total_in, total_out, (double) total_out / total_in * 100); - r = 0; + r = sparse ? finalize_sparse(fdt) : 0; cleanup: munmap(src, st.st_size); return r; @@ -1219,6 +1279,7 @@ int decompress_stream_zstd(int fdf, int fdt, uint64_t max_bytes) { #if HAVE_ZSTD _cleanup_(ZSTD_freeDCtxp) ZSTD_DCtx *dctx = NULL; _cleanup_free_ void *in_buff = NULL, *out_buff = NULL; + bool sparse = should_sparse(fdt) > 0; size_t in_allocsize, out_allocsize; size_t last_result = 0; uint64_t left = max_bytes, in_bytes = 0; @@ -1290,7 +1351,7 @@ int decompress_stream_zstd(int fdf, int fdt, uint64_t max_bytes) { if (left < output.pos) return -EFBIG; - wrote = loop_write_full(fdt, output.dst, output.pos, USEC_INFINITY); + wrote = maybe_sparse_write(fdt, output.dst, output.pos, sparse); if (wrote < 0) return wrote; @@ -1319,7 +1380,7 @@ int decompress_stream_zstd(int fdf, int fdt, uint64_t max_bytes) { in_bytes, max_bytes - left, (double) (max_bytes - left) / in_bytes * 100); - return 0; + return sparse ? finalize_sparse(fdt) : 0; #else return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "Cannot decompress file. Compiled without ZSTD support."); diff --git a/src/test/test-compress.c b/src/test/test-compress.c index 7b12e88adf661..80f2923dd62dc 100644 --- a/src/test/test-compress.c +++ b/src/test/test-compress.c @@ -12,6 +12,7 @@ #include "compress.h" #include "dlfcn-util.h" #include "fd-util.h" +#include "io-util.h" #include "path-util.h" #include "random-util.h" #include "tests.h" @@ -225,6 +226,152 @@ _unused_ static void test_compress_stream(const char *compression, r = decompress(dst, dst2, st.st_size - 1); assert_se(r == -EFBIG); } + +_unused_ static void test_decompress_stream_sparse(const char *compression, + compress_stream_t compress, + decompress_stream_t decompress) { + + _cleanup_close_ int src = -EBADF, compressed = -EBADF, decompressed = -EBADF; + _cleanup_(unlink_tempfilep) char + pattern_src[] = "/tmp/systemd-test.sparse-src.XXXXXX", + pattern_compressed[] = "/tmp/systemd-test.sparse-compressed.XXXXXX", + pattern_decompressed[] = "/tmp/systemd-test.sparse-decompressed.XXXXXX"; + /* Create a sparse-like input: 4K of data, 64K of zeros, 4K of data, 64K trailing zeros. + * Total apparent size: 136K, but most of it is zeros. */ + uint8_t data_block[4096]; + struct stat st_src, st_decompressed; + uint64_t uncompressed_size; + int r; + + assert(compression); + + log_debug("/* testing %s sparse decompression */", compression); + + random_bytes(data_block, sizeof(data_block)); + + assert_se((src = mkostemp_safe(pattern_src)) >= 0); + + /* Write: 4K data, 64K zeros, 4K data, 64K zeros */ + assert_se(loop_write(src, data_block, sizeof(data_block)) >= 0); + assert_se(ftruncate(src, sizeof(data_block) + 65536) >= 0); + assert_se(lseek(src, sizeof(data_block) + 65536, SEEK_SET) >= 0); + assert_se(loop_write(src, data_block, sizeof(data_block)) >= 0); + assert_se(ftruncate(src, 2 * sizeof(data_block) + 2 * 65536) >= 0); + assert_se(lseek(src, 0, SEEK_SET) == 0); + + assert_se(fstat(src, &st_src) >= 0); + assert_se(st_src.st_size == 2 * (off_t) sizeof(data_block) + 2 * 65536); + + /* Compress */ + assert_se((compressed = mkostemp_safe(pattern_compressed)) >= 0); + ASSERT_OK(compress(src, compressed, -1, &uncompressed_size)); + assert_se((uint64_t) st_src.st_size == uncompressed_size); + + /* Decompress to a regular file (sparse writes auto-detected) */ + assert_se((decompressed = mkostemp_safe(pattern_decompressed)) >= 0); + assert_se(lseek(compressed, 0, SEEK_SET) == 0); + r = decompress(compressed, decompressed, st_src.st_size); + assert_se(r == 0); + + /* Verify apparent size matches */ + assert_se(fstat(decompressed, &st_decompressed) >= 0); + assert_se(st_decompressed.st_size == st_src.st_size); + + /* Verify content matches by comparing bytes */ + assert_se(lseek(src, 0, SEEK_SET) == 0); + assert_se(lseek(decompressed, 0, SEEK_SET) == 0); + + for (off_t offset = 0; offset < st_src.st_size;) { + uint8_t buf_src[4096], buf_dst[4096]; + size_t to_read = MIN((size_t) (st_src.st_size - offset), sizeof(buf_src)); + ssize_t n; + + n = loop_read(src, buf_src, to_read, true); + assert_se(n == (ssize_t) to_read); + n = loop_read(decompressed, buf_dst, to_read, true); + assert_se(n == (ssize_t) to_read); + assert_se(memcmp(buf_src, buf_dst, to_read) == 0); + offset += to_read; + } + + /* Verify the decompressed file is actually sparse (uses less disk than apparent size). + * st_blocks is in 512-byte units. The file has 128K of zeros, so disk usage should be + * noticeably less than the apparent size if sparse writes worked. + * Only assert if the filesystem supports holes (SEEK_HOLE). */ + log_debug("%s sparse decompression: apparent=%jd disk=%jd", + compression, + (intmax_t) st_decompressed.st_size, + (intmax_t) st_decompressed.st_blocks * 512); + if (lseek(decompressed, 0, SEEK_HOLE) < st_decompressed.st_size) + assert_se(st_decompressed.st_blocks * 512 < st_decompressed.st_size); + else + log_debug("Filesystem does not support holes, skipping sparsity check"); + + /* Test all-zeros input: entire output should be a hole */ + log_debug("/* testing %s sparse decompression of all-zeros */", compression); + { + _cleanup_close_ int zsrc = -EBADF, zcompressed = -EBADF, zdecompressed = -EBADF; + _cleanup_(unlink_tempfilep) char + zp_src[] = "/tmp/systemd-test.sparse-zero-src.XXXXXX", + zp_compressed[] = "/tmp/systemd-test.sparse-zero-compressed.XXXXXX", + zp_decompressed[] = "/tmp/systemd-test.sparse-zero-decompressed.XXXXXX"; + struct stat zst; + uint64_t zsize; + uint8_t zeros[65536] = {}; + + assert_se((zsrc = mkostemp_safe(zp_src)) >= 0); + assert_se(loop_write(zsrc, zeros, sizeof(zeros)) >= 0); + assert_se(lseek(zsrc, 0, SEEK_SET) == 0); + + assert_se((zcompressed = mkostemp_safe(zp_compressed)) >= 0); + ASSERT_OK(compress(zsrc, zcompressed, -1, &zsize)); + assert_se(zsize == sizeof(zeros)); + + assert_se((zdecompressed = mkostemp_safe(zp_decompressed)) >= 0); + assert_se(lseek(zcompressed, 0, SEEK_SET) == 0); + assert_se(decompress(zcompressed, zdecompressed, sizeof(zeros)) == 0); + + assert_se(fstat(zdecompressed, &zst) >= 0); + assert_se(zst.st_size == (off_t) sizeof(zeros)); + /* All zeros — disk usage should be minimal */ + log_debug("%s all-zeros sparse: apparent=%jd disk=%jd", + compression, (intmax_t) zst.st_size, (intmax_t) zst.st_blocks * 512); + if (lseek(zdecompressed, 0, SEEK_HOLE) < zst.st_size) + assert_se(zst.st_blocks * 512 < zst.st_size); + else + log_debug("Filesystem does not support holes, skipping sparsity check"); + } + + /* Test data ending with non-zero bytes: ftruncate should be a no-op */ + log_debug("/* testing %s sparse decompression ending with data */", compression); + { + _cleanup_close_ int dsrc = -EBADF, dcompressed = -EBADF, ddecompressed = -EBADF; + _cleanup_(unlink_tempfilep) char + dp_src[] = "/tmp/systemd-test.sparse-end-src.XXXXXX", + dp_compressed[] = "/tmp/systemd-test.sparse-end-compressed.XXXXXX", + dp_decompressed[] = "/tmp/systemd-test.sparse-end-decompressed.XXXXXX"; + struct stat dst; + uint64_t dsize; + uint8_t zeros[65536] = {}; + + /* 64K zeros followed by 4K random data */ + assert_se((dsrc = mkostemp_safe(dp_src)) >= 0); + assert_se(loop_write(dsrc, zeros, sizeof(zeros)) >= 0); + assert_se(loop_write(dsrc, data_block, sizeof(data_block)) >= 0); + assert_se(lseek(dsrc, 0, SEEK_SET) == 0); + + assert_se((dcompressed = mkostemp_safe(dp_compressed)) >= 0); + ASSERT_OK(compress(dsrc, dcompressed, -1, &dsize)); + assert_se(dsize == sizeof(zeros) + sizeof(data_block)); + + assert_se((ddecompressed = mkostemp_safe(dp_decompressed)) >= 0); + assert_se(lseek(dcompressed, 0, SEEK_SET) == 0); + assert_se(decompress(dcompressed, ddecompressed, dsize) == 0); + + assert_se(fstat(ddecompressed, &dst) >= 0); + assert_se(dst.st_size == (off_t)(sizeof(zeros) + sizeof(data_block))); + } +} #endif #if HAVE_LZ4 @@ -314,6 +461,8 @@ int main(int argc, char *argv[]) { test_compress_stream("XZ", "xzcat", compress_stream_xz, decompress_stream_xz, srcfile); + test_decompress_stream_sparse("XZ", compress_stream_xz, decompress_stream_xz); + test_decompress_startswith_short("XZ", compress_blob_xz, decompress_startswith_xz); #else @@ -340,6 +489,8 @@ int main(int argc, char *argv[]) { test_compress_stream("LZ4", "lz4cat", compress_stream_lz4, decompress_stream_lz4, srcfile); + test_decompress_stream_sparse("LZ4", compress_stream_lz4, decompress_stream_lz4); + test_lz4_decompress_partial(); test_decompress_startswith_short("LZ4", compress_blob_lz4, decompress_startswith_lz4); @@ -368,6 +519,8 @@ int main(int argc, char *argv[]) { test_compress_stream("ZSTD", "zstdcat", compress_stream_zstd, decompress_stream_zstd, srcfile); + test_decompress_stream_sparse("ZSTD", compress_stream_zstd, decompress_stream_zstd); + test_decompress_startswith_short("ZSTD", compress_blob_zstd, decompress_startswith_zstd); #else log_info("/* ZSTD test skipped */"); From 82b04e7f8b7a5b4ee0cc50d92a830f8716ef2f77 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Thu, 2 Apr 2026 12:16:35 +0200 Subject: [PATCH 0852/1296] sd-varlink: extract varlink_handle_upgrade_fds() helper Extract the fd-handling logic from sd_varlink_call_and_upgrade() into a shared static helper so that it can be reused by the upcoming server-side sd_varlink_reply_and_upgrade(). --- src/libsystemd/sd-varlink/sd-varlink.c | 99 +++++++++++++++----------- 1 file changed, 57 insertions(+), 42 deletions(-) diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index 90be0177054cc..25e67b4b74849 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -2385,6 +2385,58 @@ _public_ int sd_varlink_call( return sd_varlink_call_full(v, method, parameters, ret_parameters, ret_error_id, NULL); } +static int varlink_handle_upgrade_fds(sd_varlink *v, int *ret_input_fd, int *ret_output_fd) { + int r; + + assert(v); + assert(ret_input_fd || ret_output_fd); + + /* Ensure no post-upgrade data was consumed into our input buffer (we ensure this via MSG_PEEK or + * byte-to-byte) and refuse the upgrade rather than silently losing the data. */ + if (v->input_buffer_size != 0) + return varlink_log_errno(v, SYNTHETIC_ERRNO(EPROTO), + "Unexpected buffered data during protocol upgrade, refusing."); + + /* Pass the connection fds to the caller, it owns them now. Reset to blocking mode + * since callers of the upgraded protocol will generally expect normal blocking + * semantics. */ + r = fd_nonblock(v->input_fd, false); + if (r < 0) + return varlink_log_errno(v, r, "Failed to set input fd to blocking mode: %m"); + if (v->input_fd != v->output_fd) { + r = fd_nonblock(v->output_fd, false); + if (r < 0) + return varlink_log_errno(v, r, "Failed to set output fd to blocking mode: %m"); + } + + /* Hand out the fds to the caller. When the caller doesn't want one direction, shut it + * down: but avoid closing the underlying fd if the other direction still needs it + * (i.e. when input_fd == output_fd). */ + bool same_fd = v->input_fd == v->output_fd; + + if (ret_input_fd) + *ret_input_fd = TAKE_FD(v->input_fd); + else { + (void) shutdown(v->input_fd, SHUT_RD); + if (same_fd && ret_output_fd) + TAKE_FD(v->input_fd); /* don't close yet, output branch needs it */ + else + v->input_fd = safe_close(v->input_fd); + } + + if (ret_output_fd) + *ret_output_fd = TAKE_FD(v->output_fd); + else { + (void) shutdown(v->output_fd, SHUT_WR); + if (same_fd && ret_input_fd) + TAKE_FD(v->output_fd); + else + v->output_fd = safe_close(v->output_fd); + } + + return 0; +} + _public_ int sd_varlink_call_and_upgrade( sd_varlink *v, const char *method, @@ -2436,45 +2488,12 @@ _public_ int sd_varlink_call_and_upgrade( goto finish; } - /* Pass the connection fds to the caller, it owns them now. Reset to blocking mode - * since callers of the upgraded protocol will generally expect normal blocking - * semantics. */ - r = fd_nonblock(v->input_fd, false); + /* Even if setting up the fds fails we must disconnect: the server already accepted the + * upgrade, so the other side is speaking raw protocol while we expect JSON. */ + r = varlink_handle_upgrade_fds(v, ret_input_fd, ret_output_fd); if (r < 0) { - varlink_log_errno(v, r, "Failed to set input fd to blocking mode: %m"); - goto disconnect; - } - if (v->input_fd != v->output_fd) { - r = fd_nonblock(v->output_fd, false); - if (r < 0) { - varlink_log_errno(v, r, "Failed to set output fd to blocking mode: %m"); - goto disconnect; - } - } - - /* Hand out the fds to the caller. When the caller doesn't want one direction, shut it - * down: but avoid closing the underlying fd if the other direction still needs it - * (i.e. when input_fd == output_fd). */ - bool same_fd = v->input_fd == v->output_fd; - - if (ret_input_fd) - *ret_input_fd = TAKE_FD(v->input_fd); - else { - (void) shutdown(v->input_fd, SHUT_RD); - if (same_fd && ret_output_fd) - TAKE_FD(v->input_fd); /* don't close yet, output branch needs it */ - else - v->input_fd = safe_close(v->input_fd); - } - - if (ret_output_fd) - *ret_output_fd = TAKE_FD(v->output_fd); - else { - (void) shutdown(v->output_fd, SHUT_WR); - if (same_fd && ret_input_fd) - TAKE_FD(v->output_fd); - else - v->output_fd = safe_close(v->output_fd); + varlink_set_state(v, VARLINK_DISCONNECTED); + goto finish; } varlink_set_state(v, VARLINK_DISCONNECTED); @@ -2488,10 +2507,6 @@ _public_ int sd_varlink_call_and_upgrade( return 1; -disconnect: - /* If we fail after the server already accepted the upgrade, nothing can be done but disconnect. - * The other side is speaking raw protocol while we expect JSON. */ - varlink_set_state(v, VARLINK_DISCONNECTED); finish: v->protocol_upgrade = false; assert(v->n_pending == 1); From 3fa1f48695759baa0d8cef9312d8e12acc1aa667 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 9 Apr 2026 10:11:26 +0200 Subject: [PATCH 0853/1296] man: fix borked reference to v262 --- man/systemd-vmspawn.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 6ae4bd304a002..5749136a5d310 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -729,7 +729,7 @@ , , and modes. - + From 2c6f9af8e5425c2086fbc8ca496843f162e4af9b Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 1 Apr 2026 16:55:55 +0200 Subject: [PATCH 0854/1296] libsystemd: add sd_varlink_reply_and_upgrade protocol upgrade This commit adds protocol upgrade support in the libsystemd server side API code. --- src/libsystemd/libsystemd.sym | 1 + src/libsystemd/sd-varlink/sd-varlink-idl.c | 14 +++ src/libsystemd/sd-varlink/sd-varlink.c | 104 ++++++++++++++++++- src/systemd/sd-varlink-idl.h | 4 +- src/systemd/sd-varlink.h | 18 +++- src/test/test-varlink.c | 111 ++++++++++++++++++++- 6 files changed, 245 insertions(+), 7 deletions(-) diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym index 6af86aa2b4a2b..619bcf820c875 100644 --- a/src/libsystemd/libsystemd.sym +++ b/src/libsystemd/libsystemd.sym @@ -1094,5 +1094,6 @@ global: LIBSYSTEMD_261 { global: sd_varlink_call_and_upgrade; + sd_varlink_reply_and_upgrade; sd_varlink_set_sentinel; } LIBSYSTEMD_260; diff --git a/src/libsystemd/sd-varlink/sd-varlink-idl.c b/src/libsystemd/sd-varlink/sd-varlink-idl.c index 0b0ea244d6c9f..dc09080cdabf3 100644 --- a/src/libsystemd/sd-varlink/sd-varlink-idl.c +++ b/src/libsystemd/sd-varlink/sd-varlink-idl.c @@ -401,6 +401,16 @@ static int varlink_idl_format_symbol( fputs("\n", f); } + if ((symbol->symbol_flags & (SD_VARLINK_REQUIRES_UPGRADE|SD_VARLINK_SUPPORTS_UPGRADE)) != 0) { + fputs(colors[COLOR_COMMENT], f); + if (FLAGS_SET(symbol->symbol_flags, SD_VARLINK_REQUIRES_UPGRADE)) + fputs("# [Requires 'upgrade' flag]", f); + else + fputs("# [Supports 'upgrade' flag]", f); + fputs(colors[COLOR_RESET], f); + fputs("\n", f); + } + fputs(colors[COLOR_SYMBOL_TYPE], f); fputs("method ", f); fputs(colors[COLOR_IDENTIFIER], f); @@ -1945,6 +1955,10 @@ int varlink_idl_validate_method_call(const sd_varlink_symbol *method, sd_json_va if (FLAGS_SET(method->symbol_flags, SD_VARLINK_REQUIRES_MORE) && !FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)) return -EBADE; + /* Same for upgrade */ + if (FLAGS_SET(method->symbol_flags, SD_VARLINK_REQUIRES_UPGRADE) && !FLAGS_SET(flags, SD_VARLINK_METHOD_UPGRADE)) + return -EBADE; + return varlink_idl_validate_symbol(method, v, SD_VARLINK_INPUT, reterr_bad_field); } diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index 25e67b4b74849..1c03cfc17367e 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -1544,6 +1544,8 @@ static int varlink_dispatch_method(sd_varlink *v) { (flags & SD_VARLINK_METHOD_ONEWAY) ? VARLINK_PROCESSING_METHOD_ONEWAY : VARLINK_PROCESSING_METHOD); + v->protocol_upgrade = FLAGS_SET(flags, SD_VARLINK_METHOD_UPGRADE); + assert(v->server); /* First consult user supplied method implementations */ @@ -1566,11 +1568,15 @@ static int varlink_dispatch_method(sd_varlink *v) { r = varlink_idl_validate_method_call(v->current_method, parameters, flags, &bad_field); if (r == -EBADE) { - varlink_log_errno(v, r, "Method %s() called without 'more' flag, but flag needs to be set.", - method); + bool missing_upgrade = FLAGS_SET(v->current_method->symbol_flags, SD_VARLINK_REQUIRES_UPGRADE) && + !FLAGS_SET(flags, SD_VARLINK_METHOD_UPGRADE); + + varlink_log_errno(v, r, "Method %s() called without '%s' flag, but flag needs to be set.", + method, missing_upgrade ? "upgrade" : "more"); if (v->state == VARLINK_PROCESSING_METHOD) { - r = sd_varlink_error(v, SD_VARLINK_ERROR_EXPECTED_MORE, NULL); + r = sd_varlink_error(v, missing_upgrade ? SD_VARLINK_ERROR_EXPECTED_UPGRADE + : SD_VARLINK_ERROR_EXPECTED_MORE, NULL); /* If we didn't manage to enqueue an error response, then fail the * connection completely. Otherwise ignore the error from * sd_varlink_error() here, as it is synthesized from the function's @@ -2821,6 +2827,97 @@ _public_ int sd_varlink_replyb(sd_varlink *v, ...) { return sd_varlink_reply(v, parameters); } +_public_ int sd_varlink_reply_and_upgrade(sd_varlink *v, sd_json_variant *parameters, int *ret_input_fd, int *ret_output_fd) { + int r; + + assert_return(v, -EINVAL); + assert_return(ret_input_fd || ret_output_fd, -EINVAL); + + if (v->state == VARLINK_DISCONNECTED) + return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); + + if (!IN_SET(v->state, + VARLINK_PROCESSING_METHOD, + VARLINK_PENDING_METHOD)) + return varlink_log_errno(v, SYNTHETIC_ERRNO(EBUSY), "Connection busy."); + + /* Verify the client actually requested a protocol upgrade */ + if (!v->protocol_upgrade) + return varlink_log_errno(v, SYNTHETIC_ERRNO(EPROTO), + "Method call did not request a protocol upgrade."); + + /* Ensure we did not buffer any data beyond the upgrade request. Check this before sending the + * reply so that we can return a normal error (the framework will send an error reply to the + * client). In normal operation this cannot happen because the client waits for our reply before + * sending raw data, and we set protocol_upgrade=true in dispatch to limit subsequent reads to + * single bytes. But a misbehaving client could pipeline data early. */ + if (v->input_buffer_size > 0) + return varlink_log_errno(v, SYNTHETIC_ERRNO(EBADMSG), + "Unexpected buffered data from client during protocol upgrade."); + + /* Validate parameters BEFORE sanitization (same validation as sd_varlink_reply(), but upgrade + * replies never carry the 'continues' flag so we always pass flags=0) */ + if (v->current_method) { + const char *bad_field = NULL; + + r = varlink_idl_validate_method_reply(v->current_method, parameters, /* flags= */ 0, &bad_field); + if (r < 0) + /* Please adjust test/units/end.sh when updating the log message. */ + varlink_log_errno(v, r, "Return parameters for method reply %s() didn't pass validation on field '%s', ignoring: %m", + v->current_method->name, strna(bad_field)); + } + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *m = NULL; + r = sd_json_buildo(&m, JSON_BUILD_PAIR_VARIANT_NON_EMPTY("parameters", parameters)); + if (r < 0) + return varlink_log_errno(v, r, "Failed to build json message: %m"); + + r = varlink_enqueue_json(v, m); + if (r < 0) + return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); + + /* Flush the reply to the socket before stealing the fds. The reply must be fully written + * before the caller starts speaking the upgraded protocol. */ + for (;;) { + r = varlink_write(v); + if (r < 0) { + varlink_log_errno(v, r, "Failed to flush reply: %m"); + goto disconnect; + } + if (v->output_buffer_size == 0 && !v->output_queue) + break; + if (v->write_disconnected) { + r = varlink_log_errno(v, SYNTHETIC_ERRNO(ECONNRESET), + "Write disconnected during upgrade reply flush."); + goto disconnect; + } + + r = fd_wait_for_event(v->output_fd, POLLOUT, USEC_INFINITY); + if (ERRNO_IS_NEG_TRANSIENT(r)) + continue; + if (r < 0) { + varlink_log_errno(v, r, "Failed to wait for writable fd: %m"); + goto disconnect; + } + assert(r > 0); + + handle_revents(v, r); + } + + /* Detach from the event loop before stealing the fds */ + varlink_detach_event_sources(v); + + /* Now hand the original FDs over to the caller, from this point on we have nothing to do with the + * connection anymore, it's up to the caller and we close the connection below */ + r = varlink_handle_upgrade_fds(v, ret_input_fd, ret_output_fd); + +disconnect: + /* This also sets the connection state to VARLINK_DISCONNECTED */ + sd_varlink_close(v); + + return r < 0 ? r : 1; +} + _public_ int sd_varlink_reset_fds(sd_varlink *v) { assert_return(v, -EINVAL); @@ -4572,6 +4669,7 @@ _public_ int sd_varlink_error_to_errno(const char *error, sd_json_variant *param { SD_VARLINK_ERROR_INVALID_PARAMETER, -EINVAL }, { SD_VARLINK_ERROR_PERMISSION_DENIED, -EACCES }, { SD_VARLINK_ERROR_EXPECTED_MORE, -EBADE }, + { SD_VARLINK_ERROR_EXPECTED_UPGRADE, -EPROTOTYPE }, }; int r; diff --git a/src/systemd/sd-varlink-idl.h b/src/systemd/sd-varlink-idl.h index ab85a95cc7512..1122e31324206 100644 --- a/src/systemd/sd-varlink-idl.h +++ b/src/systemd/sd-varlink-idl.h @@ -52,7 +52,9 @@ __extension__ typedef enum _SD_ENUM_TYPE_S64(sd_varlink_symbol_type_t) { __extension__ typedef enum _SD_ENUM_TYPE_S64(sd_varlink_symbol_flags_t) { SD_VARLINK_SUPPORTS_MORE = 1 << 0, /* Call supports "more" flag */ SD_VARLINK_REQUIRES_MORE = 1 << 1, /* Call requires "more" flag */ - _SD_VARLINK_SYMBOL_FLAGS_MAX = (1 << 2) - 1, + SD_VARLINK_SUPPORTS_UPGRADE = 1 << 2, /* Call supports "upgrade" flag */ + SD_VARLINK_REQUIRES_UPGRADE = 1 << 3, /* Call requires "upgrade" flag */ + _SD_VARLINK_SYMBOL_FLAGS_MAX = (1 << 4) - 1, _SD_VARLINK_SYMBOL_FLAGS_INVALID = -EINVAL, _SD_ENUM_FORCE_S64(SD_VARLINK_SYMBOL_FLAGS) } sd_varlink_symbol_flags_t; diff --git a/src/systemd/sd-varlink.h b/src/systemd/sd-varlink.h index 9d6e939d64f0d..0b999b9154d2f 100644 --- a/src/systemd/sd-varlink.h +++ b/src/systemd/sd-varlink.h @@ -137,8 +137,9 @@ int sd_varlink_callb(sd_varlink *v, const char *method, sd_json_variant **ret_pa sd_varlink_callb((v), (method), (ret_parameters), (ret_error_id), SD_JSON_BUILD_OBJECT(__VA_ARGS__)) /* Send method call with upgrade, wait for reply, then steal the connection fds for raw I/O. - * For bidirectional sockets ret_input_fd and ret_output_fd will be the same fd. - * ret_parameters and ret_error_id are borrowed references valid only until v is closed or unreffed. + * For bidirectional sockets ret_input_fd and ret_output_fd will be the same fd. Callers + * that need independent fds should dup() one of them. ret_parameters and ret_error_id are + * borrowed references valid only until v is closed or unreffed. * Returns > 0 if the connection was upgraded, 0 if a Varlink error occurred (and ret_error_id was set), * or < 0 on local failure. */ int sd_varlink_call_and_upgrade(sd_varlink *v, const char *method, sd_json_variant *parameters, sd_json_variant **ret_parameters, const char **ret_error_id, int *ret_input_fd, int *ret_output_fd); @@ -168,6 +169,18 @@ int sd_varlink_replyb(sd_varlink *v, ...); #define sd_varlink_replybo(v, ...) \ sd_varlink_replyb((v), SD_JSON_BUILD_OBJECT(__VA_ARGS__)) +/* Send a final reply to an upgrade request, then steal the connection fds for raw I/O. + * The fds are returned in blocking mode. The varlink connection is disconnected afterwards. + * For bidirectional sockets ret_input_fd and ret_output_fd will be the same fd. Callers + * that need independent fds should dup() one of them. For pipe pairs (e.g. ssh-exec + * transport) they will differ. Either ret pointer may be NULL. + * + * Note: this call synchronously blocks until the reply is flushed to the socket. This is + * usually fine as flush is fast but a misbehaving/adversary client that stops reading + * could stall the caller. So do not use in servers that multiplex many varlink + * connections. */ +int sd_varlink_reply_and_upgrade(sd_varlink *v, sd_json_variant *parameters, int *ret_input_fd, int *ret_output_fd); + /* Enqueue a (final) error */ int sd_varlink_error(sd_varlink *v, const char *error_id, sd_json_variant *parameters); int sd_varlink_errorb(sd_varlink *v, const char *error_id, ...); @@ -322,6 +335,7 @@ _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_varlink_server, sd_varlink_server_unref); #define SD_VARLINK_ERROR_INVALID_PARAMETER "org.varlink.service.InvalidParameter" #define SD_VARLINK_ERROR_PERMISSION_DENIED "org.varlink.service.PermissionDenied" #define SD_VARLINK_ERROR_EXPECTED_MORE "org.varlink.service.ExpectedMore" +#define SD_VARLINK_ERROR_EXPECTED_UPGRADE "org.varlink.service.ExpectedUpgrade" _SD_END_DECLARATIONS; diff --git a/src/test/test-varlink.c b/src/test/test-varlink.c index 3324421f68787..36a46393760c6 100644 --- a/src/test/test-varlink.c +++ b/src/test/test-varlink.c @@ -11,6 +11,7 @@ #include "sd-varlink.h" #include "fd-util.h" +#include "io-util.h" #include "json-util.h" #include "memfd-util.h" #include "rm-rf.h" @@ -725,7 +726,7 @@ static int reply_notify_then_error(sd_varlink *link, sd_json_variant *parameters TEST(notify_then_error) { _cleanup_(sd_event_unrefp) sd_event *e = NULL; - ASSERT_OK(sd_event_default(&e)); + ASSERT_OK(sd_event_new(&e)); _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; ASSERT_OK(sd_varlink_server_new(&s, 0)); @@ -752,4 +753,112 @@ TEST(notify_then_error) { ASSERT_OK(sd_event_loop(e)); } +static int method_upgrade(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + _cleanup_close_ int input_fd = -EBADF, output_fd = -EBADF; + int r; + + ASSERT_TRUE(FLAGS_SET(flags, SD_VARLINK_METHOD_UPGRADE)); + + r = sd_varlink_reply_and_upgrade(link, /* parameters= */ NULL, &input_fd, &output_fd); + if (r < 0) + return r; + + /* For a socketpair connection, both fds point to the same socket — avoid double-close */ + if (input_fd == output_fd) + output_fd = -EBADF; + + /* After upgrade, do raw I/O: read until EOF, reverse, write back. + * The client shuts down its write side after sending, so we get a clean EOF. */ + char buf[64] = {}; + ssize_t n = ASSERT_OK(loop_read(input_fd, buf, sizeof(buf) - 1, /* do_poll= */ true)); + ASSERT_GT(n, 0); + + /* Reverse the received bytes */ + for (ssize_t i = 0; i < n / 2; i++) + SWAP_TWO(buf[i], buf[n - 1 - i]); + + int write_fd = output_fd >= 0 ? output_fd : input_fd; + ASSERT_OK(loop_write(write_fd, buf, n)); + + return 0; +} + +static int method_upgrade_without_flag(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + int input_fd = -EBADF, output_fd = -EBADF; + + /* Calling reply_and_upgrade without the client requesting it should fail with -EPROTO */ + ASSERT_ERROR(sd_varlink_reply_and_upgrade(link, /* parameters= */ NULL, &input_fd, &output_fd), EPROTO); + + sd_event_exit(sd_varlink_get_event(link), EXIT_SUCCESS); + + return sd_varlink_reply(link, /* parameters= */ NULL); +} + +static void *upgrade_thread(void *arg) { + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *c = NULL; + _cleanup_close_ int input_fd = -EBADF, output_fd = -EBADF; + sd_json_variant *o = NULL; + const char *error_id = NULL; + + ASSERT_OK(sd_varlink_connect_address(&c, arg)); + ASSERT_OK(sd_varlink_set_description(c, "upgrade-client")); + + ASSERT_OK(sd_varlink_call_and_upgrade(c, "io.test.Upgrade", /* parameters= */ NULL, &o, &error_id, &input_fd, &output_fd)); + ASSERT_NULL(error_id); + ASSERT_GE(input_fd, 0); + ASSERT_GE(output_fd, 0); + + /* For a socketpair connection, both fds point to the same socket — avoid double-close */ + if (input_fd == output_fd) + output_fd = -EBADF; + + /* Send a test string, expect reversed reply */ + static const char msg[] = "Hello!"; + int write_fd = output_fd >= 0 ? output_fd : input_fd; + ASSERT_OK(loop_write(write_fd, msg, strlen(msg))); + ASSERT_OK_ERRNO(shutdown(write_fd, SHUT_WR)); + + char buf[64] = {}; + ssize_t n = ASSERT_OK(loop_read(input_fd, buf, strlen(msg), /* do_poll= */ true)); + ASSERT_EQ((size_t) n, strlen(msg)); + ASSERT_STREQ(buf, "!olleH"); + + /* Also test that a regular call (without upgrade flag) correctly rejects reply_and_upgrade on + * the server side, and still works as a normal call */ + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *c2 = NULL; + ASSERT_OK(sd_varlink_connect_address(&c2, arg)); + ASSERT_OK(sd_varlink_set_description(c2, "no-upgrade-client")); + ASSERT_OK(sd_varlink_call(c2, "io.test.UpgradeWithoutFlag", /* parameters= */ NULL, &o, &error_id)); + ASSERT_NULL(error_id); + + return NULL; +} + +TEST(upgrade) { + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; + _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + pthread_t t; + const char *sp; + + ASSERT_OK(mkdtemp_malloc("/tmp/varlink-test-XXXXXX", &tmpdir)); + sp = strjoina(tmpdir, "/socket"); + + ASSERT_OK(sd_event_new(&e)); + + ASSERT_OK(sd_varlink_server_new(&s, 0)); + ASSERT_OK(sd_varlink_server_set_description(s, "upgrade-server")); + ASSERT_OK(sd_varlink_server_bind_method(s, "io.test.Upgrade", method_upgrade)); + ASSERT_OK(sd_varlink_server_bind_method(s, "io.test.UpgradeWithoutFlag", method_upgrade_without_flag)); + ASSERT_OK(sd_varlink_server_listen_address(s, sp, 0600)); + ASSERT_OK(sd_varlink_server_attach_event(s, e, 0)); + + ASSERT_OK(-pthread_create(&t, NULL, upgrade_thread, (void*) sp)); + + /* Run the event loop until no more connections (the thread will disconnect when done) */ + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK(-pthread_join(t, NULL)); +} + DEFINE_TEST_MAIN(LOG_DEBUG); From 34f29079fdba7eb3820e6e79e370671fd293bd87 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Thu, 2 Apr 2026 09:38:41 +0200 Subject: [PATCH 0855/1296] varlinkctl: add new `serve` verb to allow wrapping command in varlink With the new protocol upgrade support in varlinkctl client we can now do the equivalent for the server side. This commit adds a new `serve` verb that will serve any command that speaks stdin/stdout via varlink and its protocol upgrade feature. This is the "inetd for varlink". This is useful for various reasons: 1. Allows to e.g. provide a heavily sandboxed io.myorg.xz.Decompress varlink endpoint, c.f. xz CVE-2024-3094) 2. Allow sftp over varlink which is quite useful with the varlink-http-bridge (that has more flexible auth mechanism than plain sftp). 3. Makes testing the varlinkctl client protocol upgrade simpler. 4. Because we can. --- man/varlinkctl.xml | 70 ++++++++++ src/varlinkctl/varlinkctl.c | 152 +++++++++++++++++++++ test/units/TEST-74-AUX-UTILS.varlinkctl.sh | 48 +++++-- 3 files changed, 261 insertions(+), 9 deletions(-) diff --git a/man/varlinkctl.xml b/man/varlinkctl.xml index 6aff5a05e1349..adf26b8fe6150 100644 --- a/man/varlinkctl.xml +++ b/man/varlinkctl.xml @@ -73,6 +73,14 @@ CMDLINE + + varlinkctl + OPTIONS + serve + METHOD + CMDLINE + + varlinkctl OPTIONS @@ -181,6 +189,28 @@ + + serve METHOD CMDLINE… + + Run a Varlink server that accepts protocol upgrade requests for the specified method + and connects the upgraded connection to the standard input and output of the specified command. This + can act as a server-side counterpart to call . + + The listening socket must be passed via socket activation (i.e. the + $LISTEN_FDS protocol), making this command suitable for use in socket-activated + service units. When a client calls the specified method with the upgrade flag, the server sends a + reply confirming the upgrade, then forks and executes the given command line with the upgraded + connection on its standard input and output. + + This effectively turns any command that speaks a protocol over standard input/output into a + Varlink service, discoverable via the service registry and authenticated via socket credentials. + Because each connection is handled by a forked child process, the service unit can apply systemd's + sandboxing options (such as ProtectSystem=, etc.) and does not operate in the + caller's environment. + + + + list-registry @@ -533,6 +563,46 @@ method Extend( # varlinkctl call ssh-exec:somehost:systemd-creds org.varlink.service.GetInfo '{}' + + Serving a Sandboxed Decompressor via Protocol Upgrade + + The following socket and service units expose xz decompression as a Varlink + service. Clients connect and send compressed data over the upgraded connection, receiving decompressed + output in return. + + # /etc/systemd/system/varlink-decompress-xz.socket +[Socket] +ListenStream=/run/varlink/registry/com.example.Decompress.XZ + +[Install] +WantedBy=sockets.target + +# /etc/systemd/system/varlink-decompress-xz.service +[Service] +ExecStart=varlinkctl serve com.example.Decompress.XZ xz -d +DynamicUser=yes +PrivateNetwork=yes +ProtectSystem=strict +ProtectHome=yes +NoNewPrivileges=yes +SystemCallFilter=~@privileged @resources +MemoryMax=256M + + A client can then decompress data through this service: + + $ echo "hello" | xz | varlinkctl call --upgrade \ + unix:/run/varlink/registry/com.example.Decompress.XZ \ + com.example.Decompress.XZ '{}' +hello + + For quick testing without unit files, systemd-socket-activate can be used + to provide the listening socket: + + $ systemd-socket-activate -l /tmp/decompress.sock -- varlinkctl serve com.example.Decompress.XZ xz -d & +$ echo "hello" | xz | varlinkctl call --upgrade unix:/tmp/decompress.sock com.example.Decompress.XZ '{}' +hello + + diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index 00bd71f34dedc..1876b90bde00f 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -1169,6 +1169,158 @@ static int verb_list_registry(int argc, char *argv[], uintptr_t _data, void *use return 0; } +/* Build a minimal IDL from a qualified method name so that introspection works. The parsed interface is + * returned to the caller who must keep it alive for the lifetime of the server + * (sd_varlink_server_add_interface() borrows the pointer). */ +static int varlink_server_add_interface_from_method(sd_varlink_server *s, const char *method, sd_varlink_interface **ret_interface) { + assert(s); + assert(method); + assert(ret_interface); + + const char *dot = strrchr(method, '.'); + assert(dot); + + _cleanup_free_ char *interface_name = strndup(method, dot - method); + if (!interface_name) + return log_oom(); + + /* Note that we do not need to put the upgrade flag comment here, it is added automatically + * by varlink_idl_format_symbol() because of the SD_VARLINK_REQUIRES_UPGRADE flag. */ + _cleanup_free_ char *idl_text = strjoin( + "interface ", interface_name, "\n" + "\n" + "method ", dot + 1, " () -> ()\n"); + if (!idl_text) + return log_oom(); + + _cleanup_(sd_varlink_interface_freep) sd_varlink_interface *iface = NULL; + int r = sd_varlink_idl_parse(idl_text, /* reterr_line= */ NULL, /* reterr_column= */ NULL, &iface); + if (r < 0) + return log_error_errno(r, "Failed to parse IDL for method '%s': %m", method); + + /* Mark the method as requiring the upgrade flag so introspection shows the annotation */ + assert(iface->symbols[0] && iface->symbols[0]->symbol_type == SD_VARLINK_METHOD); + ((sd_varlink_symbol*) iface->symbols[0])->symbol_flags |= SD_VARLINK_REQUIRES_UPGRADE; + + r = sd_varlink_server_add_interface(s, iface); + if (r < 0) + return r; + + *ret_interface = TAKE_PTR(iface); + + return 0; +} + +static int method_serve_upgrade(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + char **exec_cmdline = ASSERT_PTR(userdata); + _cleanup_close_ int input_fd = -EBADF, _output_fd = -EBADF; + int output_fd, r; + + if (!FLAGS_SET(flags, SD_VARLINK_METHOD_UPGRADE)) + return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_UPGRADE, NULL); + + r = sd_varlink_reply_and_upgrade(link, /* parameters= */ NULL, &input_fd, &output_fd); + if (r < 0) + return log_error_errno(r, "Failed to upgrade connection: %m"); + + if (output_fd != input_fd) + _output_fd = output_fd; + + /* Copy exec_cmdline before forking: pidref_safe_fork() calls rename_process() which + * overwrites the argv area that exec_cmdline points into. */ + _cleanup_strv_free_ char **cmdline_copy = strv_copy(exec_cmdline); + if (!cmdline_copy) + return log_oom(); + + r = pidref_safe_fork_full( + "(serve)", + (int[]) { input_fd, output_fd, STDERR_FILENO }, + /* except_fds= */ NULL, /* n_except_fds= */ 0, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_REARRANGE_STDIO|FORK_DETACH|FORK_LOG, + /* ret= */ NULL); + if (r < 0) + return r; + if (r == 0) { + execvp(cmdline_copy[0], cmdline_copy); + log_error_errno(errno, "Failed to execute '%s': %m", cmdline_copy[0]); + _exit(EXIT_FAILURE); + } + + return 0; +} + +VERB(verb_serve, "serve", "METHOD CMDLINE…", 3, VERB_ANY, 0, "Serve a command via varlink protocol upgrade"); +static int verb_serve(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + const char *method; + char **exec_cmdline; + int r, n; + + assert(argc >= 3); /* Guaranteed by verb dispatch table */ + + method = argv[1]; + exec_cmdline = argv + 2; + + r = varlink_idl_qualified_symbol_name_is_valid(method); + if (r < 0) + return log_error_errno(r, "Failed to validate method name '%s': %m", method); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid qualified method name: '%s'", method); + + /* Require socket activation */ + n = sd_listen_fds(/* unset_environment= */ true); + if (n < 0) + return log_error_errno(n, "Failed to determine passed file descriptors: %m"); + if (n == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No file descriptors passed via socket activation."); + if (n > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected exactly one socket activation fd, got %d.", n); + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); + + r = sd_varlink_server_new(&s, SD_VARLINK_SERVER_INHERIT_USERDATA); + if (r < 0) + return log_error_errno(r, "Failed to allocate varlink server: %m"); + + _cleanup_free_ char *description = strjoin("serve:", method); + if (!description) + return log_oom(); + + r = sd_varlink_server_set_description(s, description); + if (r < 0) + return log_error_errno(r, "Failed to set server description: %m"); + + r = sd_varlink_server_bind_method(s, method, method_serve_upgrade); + if (r < 0) + return log_error_errno(r, "Failed to bind method '%s': %m", method); + + _cleanup_(sd_varlink_interface_freep) sd_varlink_interface *iface = NULL; + r = varlink_server_add_interface_from_method(s, method, &iface); + if (r < 0) + return log_error_errno(r, "Failed to add interface for method '%s': %m", method); + + sd_varlink_server_set_userdata(s, exec_cmdline); + + r = sd_varlink_server_listen_fd(s, SD_LISTEN_FDS_START); + if (r < 0) + return log_error_errno(r, "Failed to listen on socket activation fd: %m"); + + r = sd_varlink_server_attach_event(s, event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach varlink server to event loop: %m"); + + (void) sd_notify(/* unset_environment= */ false, "READY=1"); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + return 0; +} + static int run(int argc, char *argv[]) { int r; diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh index b6d270cfd4703..782a6dc6e973c 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -257,6 +257,9 @@ systemd-run --wait --pipe --user --machine testuser@ \ varlinkctl --more call "/run/user/$testuser_uid/systemd/io.systemd.Manager" io.systemd.Unit.List '{}' # test --upgrade (protocol upgrade) +# The basic --upgrade proxy test is covered by the "varlinkctl serve" tests below (which use +# serve+rev/gunzip as the server). The tests here exercise features that need the Python +# server: file-input (defer fallback), ssh-exec transport (pipe pairs) and --exec mode. UPGRADE_SOCKET="$(mktemp -d)/upgrade.sock" UPGRADE_SERVER="$(mktemp)" cat >"$UPGRADE_SERVER" <<'PYEOF' @@ -320,15 +323,6 @@ if sock: PYEOF chmod +x "$UPGRADE_SERVER" -# Start the server in the background, wait for readiness via sd_notify -systemd-notify --fork -q -- python3 "$UPGRADE_SERVER" "$UPGRADE_SOCKET" - -# Test proxy mode: pipe data through --upgrade, passing parameters and validate -result="$(echo "hello world" | varlinkctl call --upgrade "unix:$UPGRADE_SOCKET" io.systemd.test.Reverse '{"foo":"bar"}')" -echo "$result" | grep "<<< UPGRADED >>>" >/dev/null -echo "$result" | grep '"foo": "bar"' >/dev/null -echo "$result" | grep "dlrow olleh" >/dev/null - # Test --upgrade with stdin redirected from a regular file (epoll can't poll regular files, # so this exercises the sd_event_add_defer fallback path) UPGRADE_SOCKET2="$(mktemp -d)/upgrade.sock" @@ -370,3 +364,39 @@ rm -f "$EXEC_RESULT" rm -f "$UPGRADE_SOCKET" "$UPGRADE_SOCKET2" "$UPGRADE_SERVER" /tmp/test-upgrade-input rm -rf "$(dirname "$UPGRADE_SOCKET")" "$(dirname "$UPGRADE_SOCKET2")" + +# Test varlinkctl serve: expose a stdio command via varlink protocol upgrade with socket activation. +# This is the "inetd for varlink" pattern: any stdio tool becomes a varlink service. +SERVE_SOCKET="$(mktemp -d)/serve.sock" + +# Test 1: serve rev: proves bidirectional data flow through the upgrade +SERVE_PID=$(systemd-notify --fork -- \ + systemd-socket-activate -l "$SERVE_SOCKET" -- \ + varlinkctl serve io.systemd.test.Reverse rev) + +# Verify introspection works on the serve endpoint and shows the upgrade annotation +varlinkctl introspect "unix:$SERVE_SOCKET" io.systemd.test | grep "method Reverse" >/dev/null +varlinkctl introspect "unix:$SERVE_SOCKET" io.systemd.test | grep "Requires 'upgrade' flag" >/dev/null + +result="$(echo "hello world" | varlinkctl call --upgrade "unix:$SERVE_SOCKET" io.systemd.test.Reverse '{}')" +echo "$result" | grep "dlrow olleh" >/dev/null +kill "$SERVE_PID" 2>/dev/null || true +wait "$SERVE_PID" 2>/dev/null || true +rm -f "$SERVE_SOCKET" + +# Test 2: decompress via serve: the "sandboxed decompressor" use-case (the real thing would be a proper +# unit with real sandboxing). +# Pipe gzip-compressed data through a varlinkctl serve + gunzip endpoint and verify round-trip. +SERVE_PID=$(systemd-notify --fork -- \ + systemd-socket-activate -l "$SERVE_SOCKET" -- \ + varlinkctl serve io.systemd.Compress.Decompress gunzip) + +SERVE_TMPDIR="$(mktemp -d)" +echo "untrusted data decompressed safely via varlink serve" | gzip > "$SERVE_TMPDIR/compressed.gz" +result="$(varlinkctl call --upgrade "unix:$SERVE_SOCKET" io.systemd.Compress.Decompress '{}' < "$SERVE_TMPDIR/compressed.gz")" +echo "$result" | grep "untrusted data decompressed safely" >/dev/null +kill "$SERVE_PID" 2>/dev/null || true +wait "$SERVE_PID" 2>/dev/null || true + +rm -f "$SERVE_SOCKET" +rm -rf "$(dirname "$SERVE_SOCKET")" "$SERVE_TMPDIR" From 0d21b105d23d9015f87121a2ec6383aa9c9df421 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Sun, 5 Apr 2026 10:05:30 +0200 Subject: [PATCH 0856/1296] libsystemd,varlink: always return two fds in varlink upgrade API This commit tweaks the API of sd_varlink_call_and_upgrade and sd_varlink_reply_and_upgrade to return two independent fds even if the internal {input,output}_fd are the same (e.g. a socket). This makes the external API easier as there is no longer the risk of double close. The sd_varlink_call_and_upgrade() is not in a released version of systemd yet so I presume it is okay to update it still. This also allowed some simplifications in varlinkctl.c now that the handling is easier. --- src/libsystemd/sd-varlink/sd-varlink.c | 22 ++++++++++------------ src/systemd/sd-varlink.h | 12 ++++++------ src/test/test-varlink.c | 19 +++++-------------- src/varlinkctl/varlinkctl.c | 16 ++-------------- 4 files changed, 23 insertions(+), 46 deletions(-) diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index 1c03cfc17367e..fcf1704792d08 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -2415,29 +2415,27 @@ static int varlink_handle_upgrade_fds(sd_varlink *v, int *ret_input_fd, int *ret return varlink_log_errno(v, r, "Failed to set output fd to blocking mode: %m"); } - /* Hand out the fds to the caller. When the caller doesn't want one direction, shut it - * down: but avoid closing the underlying fd if the other direction still needs it - * (i.e. when input_fd == output_fd). */ - bool same_fd = v->input_fd == v->output_fd; + /* For bidirectional sockets (input_fd == output_fd), dup the fd so that callers + * always get two independent fds they can close separately. */ + if (v->input_fd == v->output_fd) { + v->output_fd = fcntl(v->input_fd, F_DUPFD_CLOEXEC, 3); + if (v->output_fd < 0) + return varlink_log_errno(v, errno, "Failed to dup upgraded connection fd: %m"); + } + /* Hand out requested fds, shut down unwanted directions. */ if (ret_input_fd) *ret_input_fd = TAKE_FD(v->input_fd); else { (void) shutdown(v->input_fd, SHUT_RD); - if (same_fd && ret_output_fd) - TAKE_FD(v->input_fd); /* don't close yet, output branch needs it */ - else - v->input_fd = safe_close(v->input_fd); + v->input_fd = safe_close(v->input_fd); } if (ret_output_fd) *ret_output_fd = TAKE_FD(v->output_fd); else { (void) shutdown(v->output_fd, SHUT_WR); - if (same_fd && ret_input_fd) - TAKE_FD(v->output_fd); - else - v->output_fd = safe_close(v->output_fd); + v->output_fd = safe_close(v->output_fd); } return 0; diff --git a/src/systemd/sd-varlink.h b/src/systemd/sd-varlink.h index 0b999b9154d2f..527415ab80165 100644 --- a/src/systemd/sd-varlink.h +++ b/src/systemd/sd-varlink.h @@ -137,9 +137,9 @@ int sd_varlink_callb(sd_varlink *v, const char *method, sd_json_variant **ret_pa sd_varlink_callb((v), (method), (ret_parameters), (ret_error_id), SD_JSON_BUILD_OBJECT(__VA_ARGS__)) /* Send method call with upgrade, wait for reply, then steal the connection fds for raw I/O. - * For bidirectional sockets ret_input_fd and ret_output_fd will be the same fd. Callers - * that need independent fds should dup() one of them. ret_parameters and ret_error_id are - * borrowed references valid only until v is closed or unreffed. + * For bidirectional sockets ret_input_fd and ret_output_fd will be separate (dupped) fds + * referring to the same underlying socket. ret_parameters and ret_error_id are borrowed + * references valid only until v is closed or unreffed. * Returns > 0 if the connection was upgraded, 0 if a Varlink error occurred (and ret_error_id was set), * or < 0 on local failure. */ int sd_varlink_call_and_upgrade(sd_varlink *v, const char *method, sd_json_variant *parameters, sd_json_variant **ret_parameters, const char **ret_error_id, int *ret_input_fd, int *ret_output_fd); @@ -171,9 +171,9 @@ int sd_varlink_replyb(sd_varlink *v, ...); /* Send a final reply to an upgrade request, then steal the connection fds for raw I/O. * The fds are returned in blocking mode. The varlink connection is disconnected afterwards. - * For bidirectional sockets ret_input_fd and ret_output_fd will be the same fd. Callers - * that need independent fds should dup() one of them. For pipe pairs (e.g. ssh-exec - * transport) they will differ. Either ret pointer may be NULL. + * For bidirectional sockets ret_input_fd and ret_output_fd will be separate (dupped) fds + * referring to the same underlying socket. For pipe pairs (e.g. ssh-exec transport) they + * will differ. Either ret pointer may be NULL. * * Note: this call synchronously blocks until the reply is flushed to the socket. This is * usually fine as flush is fast but a misbehaving/adversary client that stops reading diff --git a/src/test/test-varlink.c b/src/test/test-varlink.c index 36a46393760c6..219966c5d74df 100644 --- a/src/test/test-varlink.c +++ b/src/test/test-varlink.c @@ -763,10 +763,6 @@ static int method_upgrade(sd_varlink *link, sd_json_variant *parameters, sd_varl if (r < 0) return r; - /* For a socketpair connection, both fds point to the same socket — avoid double-close */ - if (input_fd == output_fd) - output_fd = -EBADF; - /* After upgrade, do raw I/O: read until EOF, reverse, write back. * The client shuts down its write side after sending, so we get a clean EOF. */ char buf[64] = {}; @@ -777,8 +773,7 @@ static int method_upgrade(sd_varlink *link, sd_json_variant *parameters, sd_varl for (ssize_t i = 0; i < n / 2; i++) SWAP_TWO(buf[i], buf[n - 1 - i]); - int write_fd = output_fd >= 0 ? output_fd : input_fd; - ASSERT_OK(loop_write(write_fd, buf, n)); + ASSERT_OK(loop_write(output_fd, buf, n)); return 0; } @@ -807,16 +802,12 @@ static void *upgrade_thread(void *arg) { ASSERT_NULL(error_id); ASSERT_GE(input_fd, 0); ASSERT_GE(output_fd, 0); + ASSERT_NE(input_fd, output_fd); /* library dups for bidirectional sockets */ - /* For a socketpair connection, both fds point to the same socket — avoid double-close */ - if (input_fd == output_fd) - output_fd = -EBADF; - - /* Send a test string, expect reversed reply */ + /* Send a test string, shut down write side so server sees EOF, then read the reversed reply */ static const char msg[] = "Hello!"; - int write_fd = output_fd >= 0 ? output_fd : input_fd; - ASSERT_OK(loop_write(write_fd, msg, strlen(msg))); - ASSERT_OK_ERRNO(shutdown(write_fd, SHUT_WR)); + ASSERT_OK(loop_write(output_fd, msg, strlen(msg))); + ASSERT_OK_ERRNO(shutdown(output_fd, SHUT_WR)); char buf[64] = {}; ssize_t n = ASSERT_OK(loop_read(input_fd, buf, strlen(msg), /* do_poll= */ true)); diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index 1876b90bde00f..9a8e89b26c0bf 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -672,15 +672,6 @@ static int varlink_call_and_upgrade(const char *url, const char *method, sd_json if (!isempty(error_id)) return log_error_errno(SYNTHETIC_ERRNO(EBADE), "Upgrade via %s() failed with error: %s", method, error_id); - /* For bidirectional sockets input_fd == output_fd. Dup immediately so that _cleanup_close_ - * on both variables can never double-close the same fd. Note that on fcntl() failure - * output_fd is overwritten with -1, so only input_fd holds the real fd at cleanup time. */ - if (input_fd == output_fd) { - output_fd = fcntl(input_fd, F_DUPFD_CLOEXEC, 3); - if (output_fd < 0) - return log_error_errno(errno, "Failed to dup upgraded connection fd: %m"); - } - if (!strv_isempty(exec_cmdline)) { /* --exec mode: place the upgraded connection on stdin/stdout so that the child * process can just read/write naturally. */ @@ -1213,8 +1204,8 @@ static int varlink_server_add_interface_from_method(sd_varlink_server *s, const static int method_serve_upgrade(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { char **exec_cmdline = ASSERT_PTR(userdata); - _cleanup_close_ int input_fd = -EBADF, _output_fd = -EBADF; - int output_fd, r; + _cleanup_close_ int input_fd = -EBADF, output_fd = -EBADF; + int r; if (!FLAGS_SET(flags, SD_VARLINK_METHOD_UPGRADE)) return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_UPGRADE, NULL); @@ -1223,9 +1214,6 @@ static int method_serve_upgrade(sd_varlink *link, sd_json_variant *parameters, s if (r < 0) return log_error_errno(r, "Failed to upgrade connection: %m"); - if (output_fd != input_fd) - _output_fd = output_fd; - /* Copy exec_cmdline before forking: pidref_safe_fork() calls rename_process() which * overwrites the argv area that exec_cmdline points into. */ _cleanup_strv_free_ char **cmdline_copy = strv_copy(exec_cmdline); From 34b9607e4e7621e93a8d3418799eac3e16be5d30 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 7 Apr 2026 17:47:50 +0200 Subject: [PATCH 0857/1296] varlink: use single byte reads on SD_VARLINK_SERVER_UPGRADABLE When the server side of a varlink connection supports connection upgrades we need to go into single byte-read mode to avoid the risk of a client that sends the json to protocol upgrade and then immediately the custom protocol payload. This commit implements this. The next step is using MSG_PEEK to avoid the single-byte overhead. --- src/libsystemd/sd-varlink/sd-varlink.c | 3 +- src/systemd/sd-varlink.h | 1 + src/test/test-varlink.c | 86 +++++++++++++++++++++++++- src/varlinkctl/varlinkctl.c | 2 +- 4 files changed, 89 insertions(+), 3 deletions(-) diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index fcf1704792d08..bc7e93f40797b 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -3710,7 +3710,8 @@ _public_ int sd_varlink_server_new(sd_varlink_server **ret, sd_varlink_server_fl SD_VARLINK_SERVER_ALLOW_FD_PASSING_OUTPUT| SD_VARLINK_SERVER_FD_PASSING_INPUT_STRICT| SD_VARLINK_SERVER_HANDLE_SIGINT| - SD_VARLINK_SERVER_HANDLE_SIGTERM)) == 0, -EINVAL); + SD_VARLINK_SERVER_HANDLE_SIGTERM| + SD_VARLINK_SERVER_UPGRADABLE)) == 0, -EINVAL); s = new(sd_varlink_server, 1); if (!s) diff --git a/src/systemd/sd-varlink.h b/src/systemd/sd-varlink.h index 527415ab80165..3be82a7ddbc34 100644 --- a/src/systemd/sd-varlink.h +++ b/src/systemd/sd-varlink.h @@ -72,6 +72,7 @@ __extension__ typedef enum _SD_ENUM_TYPE_S64(sd_varlink_server_flags_t) { SD_VARLINK_SERVER_FD_PASSING_INPUT_STRICT = 1 << 7, /* Reject input messages with fds if fd passing is disabled (needs kernel v6.16+) */ SD_VARLINK_SERVER_HANDLE_SIGINT = 1 << 8, /* Exit cleanly on SIGINT */ SD_VARLINK_SERVER_HANDLE_SIGTERM = 1 << 9, /* Exit cleanly on SIGTERM */ + SD_VARLINK_SERVER_UPGRADABLE = 1 << 10, /* Server has upgrade methods; avoid consuming post-upgrade data during reads */ _SD_ENUM_FORCE_S64(SD_VARLINK_SERVER) } sd_varlink_server_flags_t; diff --git a/src/test/test-varlink.c b/src/test/test-varlink.c index 219966c5d74df..1bbc87c32c0f9 100644 --- a/src/test/test-varlink.c +++ b/src/test/test-varlink.c @@ -15,6 +15,7 @@ #include "json-util.h" #include "memfd-util.h" #include "rm-rf.h" +#include "socket-util.h" #include "tests.h" #include "tmpfile-util.h" #include "varlink-util.h" @@ -837,7 +838,7 @@ TEST(upgrade) { ASSERT_OK(sd_event_new(&e)); - ASSERT_OK(sd_varlink_server_new(&s, 0)); + ASSERT_OK(sd_varlink_server_new(&s, SD_VARLINK_SERVER_UPGRADABLE)); ASSERT_OK(sd_varlink_server_set_description(s, "upgrade-server")); ASSERT_OK(sd_varlink_server_bind_method(s, "io.test.Upgrade", method_upgrade)); ASSERT_OK(sd_varlink_server_bind_method(s, "io.test.UpgradeWithoutFlag", method_upgrade_without_flag)); @@ -852,4 +853,87 @@ TEST(upgrade) { ASSERT_OK(-pthread_join(t, NULL)); } +static int method_upgrade_and_exit(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + sd_event *event = ASSERT_PTR(userdata); + + int r = method_upgrade(link, parameters, flags, /* userdata= */ NULL); + + /* Exit the event loop after the upgrade is handled. We can't use sd_varlink_get_event() + * here because the connection is already disconnected after reply_and_upgrade. */ + (void) sd_event_exit(event, r < 0 ? r : EXIT_SUCCESS); + return r; +} + +static void *upgrade_pipelining_thread(void *arg) { + union sockaddr_union sa = {}; + _cleanup_close_ int fd = -EBADF; + + /* Connect a raw socket and pipeline: upgrade JSON + \0 + raw data in a single write. + * This tests that the server's byte-by-byte reading (SD_VARLINK_SERVER_UPGRADABLE) + * doesn't consume the raw data into the varlink input buffer. */ + fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); + ASSERT_FD(fd); + int addrlen = sockaddr_un_set_path(&sa.un, arg); + ASSERT_OK(addrlen); + ASSERT_OK_ERRNO(connect(fd, &sa.sa, addrlen)); + + /* Build pipelined message: upgrade JSON + \0 + raw payload, all in one write */ + static const char upgrade_msg[] = "{\"method\":\"io.test.Upgrade\",\"upgrade\":true}"; + static const char raw_payload[] = "Pipelined!"; + char send_buf[sizeof(upgrade_msg) + sizeof(raw_payload)]; /* includes \0 from upgrade_msg as delimiter */ + + memcpy(send_buf, upgrade_msg, sizeof(upgrade_msg)); /* copies trailing \0 = varlink delimiter */ + memcpy(send_buf + sizeof(upgrade_msg), raw_payload, sizeof(raw_payload) - 1); + + size_t total = sizeof(upgrade_msg) + strlen(raw_payload); + ASSERT_OK(loop_write(fd, send_buf, total)); + + /* Shut down write side so server's method_upgrade sees EOF after raw payload */ + ASSERT_OK_ERRNO(shutdown(fd, SHUT_WR)); + + /* Read everything: upgrade reply (JSON + \0) + reversed raw payload. The server closes + * the connection after writing, so loop_read() reads until EOF and gets it all. */ + char buf[256] = {}; + ssize_t n = ASSERT_OK(loop_read(fd, buf, sizeof(buf) - 1, /* do_poll= */ true)); + ASSERT_GT(n, 0); + + /* Split at the \0 delimiter between JSON reply and raw payload */ + char *delim = memchr(buf, 0, n); + ASSERT_NOT_NULL(delim); + + char *raw = delim + 1; + size_t raw_size = (size_t) n - (size_t)(raw - buf); + + ASSERT_EQ(raw_size, strlen(raw_payload)); + ASSERT_STREQ(strndupa_safe(raw, raw_size), "!denilepiP"); + + return NULL; +} + +TEST(upgrade_pipelining) { + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; + _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + pthread_t t; + const char *sp; + + ASSERT_OK(mkdtemp_malloc("/tmp/varlink-test-XXXXXX", &tmpdir)); + sp = strjoina(tmpdir, "/socket"); + + ASSERT_OK(sd_event_new(&e)); + + ASSERT_OK(sd_varlink_server_new(&s, SD_VARLINK_SERVER_UPGRADABLE|SD_VARLINK_SERVER_INHERIT_USERDATA)); + ASSERT_OK(sd_varlink_server_set_description(s, "upgrade-pipelining-server")); + ASSERT_OK(sd_varlink_server_bind_method(s, "io.test.Upgrade", method_upgrade_and_exit)); + ASSERT_OK(sd_varlink_server_listen_address(s, sp, 0600)); + ASSERT_OK(sd_varlink_server_attach_event(s, e, 0)); + sd_varlink_server_set_userdata(s, e); + + ASSERT_OK(-pthread_create(&t, NULL, upgrade_pipelining_thread, (void*) sp)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK(-pthread_join(t, NULL)); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index 9a8e89b26c0bf..18a639962c11f 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -1269,7 +1269,7 @@ static int verb_serve(int argc, char *argv[], uintptr_t _data, void *userdata) { if (r < 0) return log_error_errno(r, "Failed to get event loop: %m"); - r = sd_varlink_server_new(&s, SD_VARLINK_SERVER_INHERIT_USERDATA); + r = sd_varlink_server_new(&s, SD_VARLINK_SERVER_INHERIT_USERDATA|SD_VARLINK_SERVER_UPGRADABLE); if (r < 0) return log_error_errno(r, "Failed to allocate varlink server: %m"); From cd6b57ff7060c344c7f3a2a779f2618e488bddaa Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 7 Apr 2026 17:54:28 +0200 Subject: [PATCH 0858/1296] sd-varlink: use MSG_PEEK for protocol_upgrade connections When there is a potential protocol upgrade we need to be careful that we do not read beyond our json message as the custom protocol may be anything. This was archived via a byte-by-byte read. This is of course very inefficient. So this commit moves to use MSG_PEEK to find the boundary of the json message instead. This makes the performance hit a lot smaller. Thanks to Lennart for suggesting this. --- src/libsystemd/sd-varlink/sd-varlink.c | 54 +++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index bc7e93f40797b..fe2bf0e6381a7 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -844,10 +844,49 @@ static int varlink_write(sd_varlink *v) { #define VARLINK_FDS_MAX (16U*1024U) +static bool varlink_may_protocol_upgrade(sd_varlink *v) { + return v->protocol_upgrade || (v->server && FLAGS_SET(v->server->flags, SD_VARLINK_SERVER_UPGRADABLE)); +} + +/* When a protocol upgrade might happen, peek at the socket data to find the \0 message + * boundary and return a read size that won't consume past it. This prevents over-reading + * raw post-upgrade data into the varlink input buffer. Falls back to byte-by-byte for + * non-socket fds where MSG_PEEK is not available. */ +static ssize_t varlink_peek_upgrade_boundary(sd_varlink *v, void *p, size_t rs) { + assert(v); + + if (!varlink_may_protocol_upgrade(v)) + return rs; + + if (v->prefer_read) + return 1; + + ssize_t peeked = recv(v->input_fd, p, rs, MSG_PEEK|MSG_DONTWAIT); + if (peeked < 0) { + if (errno == ENOTSOCK) { + v->prefer_read = true; + return 1; /* Not a socket, fall back to byte-to-byte */ + } else if (!ERRNO_IS_TRANSIENT(errno)) + return -errno; + + /* Transient error, this should not happen but fall back to byte-to-byte */ + return 1; + } + /* EOF, the real recv() will also get it so what we return does not matter */ + if (peeked == 0) + return rs; + + void *nul_chr = memchr(p, 0, peeked); + if (nul_chr) + return (ssize_t) ((char*) nul_chr - (char*) p) + 1; + + return peeked; +} + static int varlink_read(sd_varlink *v) { struct iovec iov; struct msghdr mh; - size_t rs; + ssize_t rs; ssize_t n; void *p; @@ -895,11 +934,14 @@ static int varlink_read(sd_varlink *v) { p = v->input_buffer + v->input_buffer_index + v->input_buffer_size; - /* When a protocol upgrade is requested we can't consume any post-upgrade data from the socket buffer */ - if (v->protocol_upgrade) - rs = 1; - else - rs = MALLOC_SIZEOF_SAFE(v->input_buffer) - (v->input_buffer_index + v->input_buffer_size); + rs = MALLOC_SIZEOF_SAFE(v->input_buffer) - (v->input_buffer_index + v->input_buffer_size); + + /* When a protocol upgrade is requested we can't consume any post-upgrade data from the socket + * buffer. Use MSG_PEEK to find the \0 message boundary and only consume up to it. For non-socket + * fds (pipes) MSG_PEEK is not available, so fall back to byte-by-byte reading. */ + rs = varlink_peek_upgrade_boundary(v, p, rs); + if (rs < 0) + return varlink_log_errno(v, rs, "Failed to peek upgrade boundary: %m"); if (v->allow_fd_passing_input > 0) { iov = IOVEC_MAKE(p, rs); From 7da67c3f8be734000f22203ffe9fdda9394e6ef5 Mon Sep 17 00:00:00 2001 From: Franck Bui Date: Wed, 8 Apr 2026 18:39:58 +0200 Subject: [PATCH 0859/1296] vconsole-setup: skip setfont(8) when the console driver lacks font support Don't run setfont(8) on consoles that don't support fonts. systemd-vconsole-setup neither fails nor reports errors on such consoles unlike setfont(8) which emits the following error [1]: systemd-vconsole-setup[169]: setfont: ERROR kdfontop.c:183 put_font_kdfontop: Unable to load such font with such kernel version The check already existed in setup_remaining_vcs() but it was performed too late. [1] this was simply ignored by setfont(8) until https://github.com/legionus/kbd/commit/1e15af4d8b272ca50e9ee1d0c584c5859102c848 --- src/vconsole/vconsole-setup.c | 48 ++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/src/vconsole/vconsole-setup.c b/src/vconsole/vconsole-setup.c index e6da288e427eb..73bf240cf5130 100644 --- a/src/vconsole/vconsole-setup.c +++ b/src/vconsole/vconsole-setup.c @@ -215,6 +215,22 @@ static int verify_vc_display_mode(int fd) { return mode != KD_TEXT ? -EBUSY : 0; } +static int verify_vc_support_font(int fd) { + struct console_font_op cfo = { + .op = KD_FONT_OP_GET, + .width = UINT_MAX, + .height = UINT_MAX, + .charcount = UINT_MAX, + }; + + assert(fd >= 0); + + if (ioctl(fd, KDFONTOP, &cfo) < 0) + return ERRNO_IS_NOT_SUPPORTED(errno) ? 0 : -errno; + + return 1; +} + static int toggle_utf8_vc(const char *name, int fd, bool utf8) { int r; struct termios tc = {}; @@ -315,7 +331,7 @@ static int keyboard_load_and_wait(const char *vc, Context *c, bool utf8) { return 1; /* Report that we did something */ } -static int font_load_and_wait(const char *vc, Context *c) { +static int font_load_and_wait(int fd, const char *vc, Context *c) { const char* args[9]; unsigned i = 0; int r; @@ -340,6 +356,16 @@ static int font_load_and_wait(const char *vc, Context *c) { return 0; /* Report that we skipped this */ } + /* May be called on the dummy console (e.g. during keymap setup with fbcon deferred takeover). Font + * changes are not supported here and will fail. */ + r = verify_vc_support_font(fd); + if (r < 0) + return log_error_errno(r, "Failed to check '%s' has font support: %m", vc); + if (r == 0) { + log_notice("'%s' has no font support, skipping.", vc); + return 0; /* Report that we skipped this */ + } + args[i++] = KBD_SETFONT; args[i++] = "-C"; args[i++] = vc; @@ -371,9 +397,8 @@ static int font_load_and_wait(const char *vc, Context *c) { _exit(EXIT_FAILURE); } - /* setfont returns EX_OSERR when ioctl(KDFONTOP/PIO_FONTX/PIO_FONTX) fails. This might mean various - * things, but in particular lack of a graphical console. Let's be generous and not treat this as an - * error. */ + /* setfont returns EX_OSERR when ioctl(KDFONTOP/PIO_FONTX/PIO_FONTX) fails. Let's be generous and not + * treat this as an error. */ r = pidref_wait_for_terminate_and_check(KBD_SETFONT, &pidref, WAIT_LOG_ABNORMAL); if (r < 0) return r; /* WAIT_LOG_ABNORMAL means we already have logged about these kinds of errors */ @@ -404,7 +429,7 @@ static void setup_remaining_vcs(int src_fd, unsigned src_idx, bool utf8) { struct unimapdesc unimapd; _cleanup_free_ struct unipair* unipairs = NULL; _cleanup_free_ void *fontbuf = NULL; - int log_level = LOG_WARNING, r; + int r; assert(src_fd >= 0); @@ -415,14 +440,7 @@ static void setup_remaining_vcs(int src_fd, unsigned src_idx, bool utf8) { /* get metadata of the current font (width, height, count) */ r = ioctl(src_fd, KDFONTOP, &cfo); if (r < 0) { - /* We might be called to operate on the dummy console (to setup keymap - * mainly) when fbcon deferred takeover is used for example. In such case, - * setting font is not supported and is expected to fail. */ - if (errno == ENOSYS) - log_level = LOG_DEBUG; - - log_full_errno(log_level, errno, - "KD_FONT_OP_GET failed while trying to get the font metadata: %m"); + log_warning_errno(errno, "KD_FONT_OP_GET failed while trying to get the font metadata: %m"); } else { /* verify parameter sanity first */ if (cfo.width > 32 || cfo.height > 32 || cfo.charcount > 512) @@ -458,7 +476,7 @@ static void setup_remaining_vcs(int src_fd, unsigned src_idx, bool utf8) { } if (cfo.op != KD_FONT_OP_SET) - log_full(log_level, "Fonts will not be copied to remaining consoles"); + log_warning("Fonts will not be copied to remaining consoles"); for (unsigned i = 1; i <= 63; i++) { char ttyname[sizeof("/dev/tty63")]; @@ -656,7 +674,7 @@ static int run(int argc, char **argv) { (void) toggle_utf8_vc(vc, fd, utf8); - int setfont_status = font_load_and_wait(vc, &c); + int setfont_status = font_load_and_wait(fd, vc, &c); int loadkeys_status = keyboard_load_and_wait(vc, &c, utf8); if (idx > 0) { From b0083b2a5ed613bc8b2aba9cb922b061331b7beb Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 9 Apr 2026 11:55:46 +0200 Subject: [PATCH 0860/1296] Revert "mkosi: Mark minimal images as Incremental=relaxed" The setting has fundamental flaws that can't be easily fixed (see https://github.com/systemd/mkosi/pull/4273) so revert it's use as we're dropping it in systemd. Image builds will take a bit longer again until I figure out a proper fix for this. This reverts commit 7a70c323681b091328fcf6c9ca3104c7958a1331. --- mkosi/mkosi.images/minimal-0/mkosi.conf | 2 -- mkosi/mkosi.images/minimal-1/mkosi.conf | 2 -- mkosi/mkosi.images/minimal-base/mkosi.conf | 1 - mkosi/mkosi.images/minimal-base/mkosi.conf.d/arch.conf | 3 +++ .../mkosi.images/minimal-base/mkosi.conf.d/centos-fedora.conf | 3 +++ .../mkosi.images/minimal-base/mkosi.conf.d/debian-ubuntu.conf | 4 ++++ mkosi/mkosi.images/minimal-base/mkosi.conf.d/opensuse.conf | 4 ++++ 7 files changed, 14 insertions(+), 5 deletions(-) diff --git a/mkosi/mkosi.images/minimal-0/mkosi.conf b/mkosi/mkosi.images/minimal-0/mkosi.conf index 0e897a53c2381..5d6717f897bd3 100644 --- a/mkosi/mkosi.images/minimal-0/mkosi.conf +++ b/mkosi/mkosi.images/minimal-0/mkosi.conf @@ -9,8 +9,6 @@ SplitArtifacts=yes [Build] Environment=SYSTEMD_REPART_OVERRIDE_FSTYPE=squashfs -Incremental=relaxed -CacheOnly=metadata [Content] BaseTrees=%O/minimal-base diff --git a/mkosi/mkosi.images/minimal-1/mkosi.conf b/mkosi/mkosi.images/minimal-1/mkosi.conf index 0e897a53c2381..5d6717f897bd3 100644 --- a/mkosi/mkosi.images/minimal-1/mkosi.conf +++ b/mkosi/mkosi.images/minimal-1/mkosi.conf @@ -9,8 +9,6 @@ SplitArtifacts=yes [Build] Environment=SYSTEMD_REPART_OVERRIDE_FSTYPE=squashfs -Incremental=relaxed -CacheOnly=metadata [Content] BaseTrees=%O/minimal-base diff --git a/mkosi/mkosi.images/minimal-base/mkosi.conf b/mkosi/mkosi.images/minimal-base/mkosi.conf index 60c6b4cc71153..48b45b7a3197c 100644 --- a/mkosi/mkosi.images/minimal-base/mkosi.conf +++ b/mkosi/mkosi.images/minimal-base/mkosi.conf @@ -5,7 +5,6 @@ Format=directory [Build] Environment=SYSTEMD_REQUIRED_DEPS_ONLY=1 -Incremental=relaxed [Content] Bootable=no diff --git a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/arch.conf b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/arch.conf index ce62ded2943af..7add5d32f6cde 100644 --- a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/arch.conf +++ b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/arch.conf @@ -11,6 +11,9 @@ Packages= iproute nmap +VolatilePackages= + systemd-libs + RemoveFiles= # Arch Linux doesn't split their gcc-libs package so we manually remove # unneeded stuff here to make sure it doesn't end up in the image. diff --git a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/centos-fedora.conf b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/centos-fedora.conf index ac951fbf60f98..6f08609d1b20a 100644 --- a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/centos-fedora.conf +++ b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/centos-fedora.conf @@ -12,3 +12,6 @@ Packages= iproute iproute-tc nmap-ncat + +VolatilePackages= + systemd-libs diff --git a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/debian-ubuntu.conf b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/debian-ubuntu.conf index 8b148d8422151..acbcea7cd272a 100644 --- a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/debian-ubuntu.conf +++ b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/debian-ubuntu.conf @@ -12,3 +12,7 @@ Packages= iproute2 mount ncat + +VolatilePackages= + libsystemd0 + libudev1 diff --git a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/opensuse.conf b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/opensuse.conf index ebf55a3188a9f..87fa34715348d 100644 --- a/mkosi/mkosi.images/minimal-base/mkosi.conf.d/opensuse.conf +++ b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/opensuse.conf @@ -16,3 +16,7 @@ Packages= patterns-base-minimal_base sed xz + +VolatilePackages= + libsystemd0 + libudev1 From 5168e9eada5166343c8bfe11fba9eda17af99fdc Mon Sep 17 00:00:00 2001 From: ipv6 Date: Thu, 9 Apr 2026 14:04:16 -0500 Subject: [PATCH 0861/1296] added root.conf to meson.build --- tmpfiles.d/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/tmpfiles.d/meson.build b/tmpfiles.d/meson.build index c8f9015b2ecc8..83839dd627f90 100644 --- a/tmpfiles.d/meson.build +++ b/tmpfiles.d/meson.build @@ -6,6 +6,7 @@ endif files = [['README' ], ['home.conf' ], + ['root.conf' ], ['journal-nocow.conf' ], ['portables.conf', 'ENABLE_PORTABLED'], ['systemd-network.conf', 'ENABLE_NETWORKD' ], From 44d0f273fa9d237c73b80b110a67da5045822796 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Thu, 9 Apr 2026 18:11:33 +0200 Subject: [PATCH 0862/1296] portablectl: fix swapped arguments for setns() Follow-up for 824fcb95c9e66abe6b350ebab6e0593498ff7aa1. --- src/portable/portable.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/portable/portable.c b/src/portable/portable.c index bae23b1a5115e..2a03c6f7ae6a8 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -524,7 +524,7 @@ static int portable_extract_by_path( seq[0] = safe_close(seq[0]); errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); - if (setns(CLONE_NEWUSER, userns_fd) < 0) { + if (setns(userns_fd, CLONE_NEWUSER) < 0) { r = log_debug_errno(errno, "Failed to join userns: %m"); report_errno_and_exit(errno_pipe_fd[1], r); } From f08796065d863a536791bf5cd96c8b8e1a65c339 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sat, 28 Mar 2026 23:21:18 +0000 Subject: [PATCH 0863/1296] compress: consolidate all compression into compress.c with dlopen Move the push-based streaming compression API from import-compress.c into compress.c and delete import-compress.c/h. This consolidates all compression code in one place and makes all compression libraries (liblzma, liblz4, libzstd, libz, libbz2) runtime-loaded via dlopen instead of directly linked. Introduce opaque Compressor/Decompressor types backed by a heap- allocated struct defined only in compress.c, keeping all third-party library headers out of compress.h. Rewrite the per-codec fd-to-fd stream functions as thin wrappers around the push API via generic compress_stream()/decompress_stream() taking a Compression type parameter. Integrate LZ4 into this framework using the LZ4 Frame API, eliminating all LZ4 special-casing. Extend the Compression enum with COMPRESSION_GZIP and COMPRESSION_BZIP2 and add the corresponding blob, startswith, and stream functions for both. Rename the ImportCompress types and functions: ImportCompressType becomes the existing Compression enum, ImportCompress becomes Compressor (with Decompressor typedef), and all import_compress_*/import_uncompress_* become compressor_*/decompressor_*. Rename dlopen_lzma() to dlopen_xz() for consistency. Make compression_to_string() return lowercase by default. Add INT_MAX/UINT_MAX overflow checks for LZ4, zlib, and bzip2 blob functions where the codec API uses narrower integer types than our uint64_t parameters. Migrate test-compress.c and test-compress-benchmark.c to the TEST() macro framework, new assertion macros, and codec-generic loops instead of per-codec duplication. Co-developed-by: Claude Opus 4.6 --- meson.build | 2 + src/basic/compress.c | 2398 ++++++++++++----- src/basic/compress.h | 132 +- src/basic/meson.build | 4 +- src/boot/test-bcd.c | 2 +- src/coredump/coredump-submit.c | 4 +- src/coredump/coredumpctl.c | 2 +- src/fundamental/macro-fundamental.h | 6 + src/import/export-raw.c | 25 +- src/import/export-raw.h | 4 +- src/import/export-tar.c | 23 +- src/import/export-tar.h | 4 +- src/import/export.c | 34 +- src/import/import-common.c | 5 +- src/import/import-common.h | 2 - src/import/import-compress.c | 611 ----- src/import/import-compress.h | 54 - src/import/import-raw.c | 19 +- src/import/import-tar.c | 17 +- src/import/meson.build | 5 - src/import/pull-common.c | 2 + src/import/pull-job.c | 11 +- src/import/pull-job.h | 6 +- src/import/pull-oci.c | 2 + src/import/pull-raw.c | 2 + src/import/pull-tar.c | 2 + src/import/qcow2-util.c | 22 +- src/journal-remote/journal-compression-util.c | 2 +- src/journal-remote/journal-remote-main.c | 4 +- src/journal-remote/journal-upload-journal.c | 2 +- src/journal-remote/journal-upload.c | 10 +- src/libsystemd/sd-journal/journal-file.c | 2 +- src/test/test-compress-benchmark.c | 181 +- src/test/test-compress.c | 841 +++--- src/test/test-dlopen-so.c | 4 +- ...EST-04-JOURNAL.SYSTEMD_JOURNAL_COMPRESS.sh | 17 +- 36 files changed, 2431 insertions(+), 2032 deletions(-) delete mode 100644 src/import/import-compress.c delete mode 100644 src/import/import-compress.h diff --git a/meson.build b/meson.build index 659aecd421877..5e01f22d5b411 100644 --- a/meson.build +++ b/meson.build @@ -1373,6 +1373,7 @@ conf.set10('HAVE_DWFL_SET_SYSROOT', libz = dependency('zlib', required : get_option('zlib')) conf.set10('HAVE_ZLIB', libz.found()) +libz_cflags = libz.partial_dependency(includes: true, compile_args: true) feature = get_option('bzip2') libbzip2 = dependency('bzip2', @@ -1382,6 +1383,7 @@ if not libbzip2.found() libbzip2 = cc.find_library('bz2', required : feature) endif conf.set10('HAVE_BZIP2', libbzip2.found()) +libbzip2_cflags = libbzip2.partial_dependency(includes: true, compile_args: true) libxz = dependency('liblzma', required : get_option('xz')) diff --git a/src/basic/compress.c b/src/basic/compress.c index 5f00f968a2842..251eb02fbb75a 100644 --- a/src/basic/compress.c +++ b/src/basic/compress.c @@ -2,18 +2,17 @@ #include #include -#include #include #include +#if HAVE_XZ +#include +#endif + #if HAVE_LZ4 #include -#include #include -#endif - -#if HAVE_XZ -#include +#include #endif #if HAVE_ZSTD @@ -21,19 +20,47 @@ #include #endif +#if HAVE_ZLIB +#include +#endif + +#if HAVE_BZIP2 +#include +#endif + #include "sd-dlopen.h" #include "alloc-util.h" #include "bitfield.h" #include "compress.h" #include "dlfcn-util.h" -#include "fileio.h" #include "io-util.h" #include "log.h" #include "string-table.h" -#include "string-util.h" #include "unaligned.h" +#if HAVE_XZ +static void *lzma_dl = NULL; + +static DLSYM_PROTOTYPE(lzma_code) = NULL; +static DLSYM_PROTOTYPE(lzma_easy_encoder) = NULL; +static DLSYM_PROTOTYPE(lzma_end) = NULL; +static DLSYM_PROTOTYPE(lzma_stream_buffer_encode) = NULL; +static DLSYM_PROTOTYPE(lzma_stream_decoder) = NULL; +static DLSYM_PROTOTYPE(lzma_lzma_preset) = NULL; + +/* We can’t just do _cleanup_(sym_lzma_end) because a compiler bug makes + * this fail with: + * ../src/basic/compress.c: In function ‘decompress_blob_xz’: + * ../src/basic/compress.c:304:9: error: cleanup argument not a function + * 304 | _cleanup_(sym_lzma_end) lzma_stream s = LZMA_STREAM_INIT; + * | ^~~~~~~~~ + */ +static inline void lzma_end_wrapper(lzma_stream *ls) { + sym_lzma_end(ls); +} +#endif + #if HAVE_LZ4 static void *lz4_dl = NULL; @@ -48,16 +75,14 @@ static DLSYM_PROTOTYPE(LZ4F_freeCompressionContext) = NULL; static DLSYM_PROTOTYPE(LZ4F_freeDecompressionContext) = NULL; static DLSYM_PROTOTYPE(LZ4F_isError) = NULL; static DLSYM_PROTOTYPE(LZ4_compress_HC) = NULL; -/* These are used in test-compress.c so we don't make them static. */ -// NOLINTBEGIN(misc-use-internal-linkage) -DLSYM_PROTOTYPE(LZ4_compress_default) = NULL; -DLSYM_PROTOTYPE(LZ4_decompress_safe) = NULL; -DLSYM_PROTOTYPE(LZ4_decompress_safe_partial) = NULL; -DLSYM_PROTOTYPE(LZ4_versionNumber) = NULL; -// NOLINTEND(misc-use-internal-linkage) +static DLSYM_PROTOTYPE(LZ4_compress_default) = NULL; +static DLSYM_PROTOTYPE(LZ4_decompress_safe) = NULL; +static DLSYM_PROTOTYPE(LZ4_decompress_safe_partial) = NULL; +static DLSYM_PROTOTYPE(LZ4_versionNumber) = NULL; -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(LZ4F_compressionContext_t, sym_LZ4F_freeCompressionContext, LZ4F_freeCompressionContextp, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(LZ4F_decompressionContext_t, sym_LZ4F_freeDecompressionContext, LZ4F_freeDecompressionContextp, NULL); +static const LZ4F_preferences_t lz4_preferences = { + .frameInfo.blockSizeID = 5, +}; #endif #if HAVE_ZSTD @@ -80,7 +105,6 @@ static DLSYM_PROTOTYPE(ZSTD_getErrorName) = NULL; static DLSYM_PROTOTYPE(ZSTD_getFrameContentSize) = NULL; static DLSYM_PROTOTYPE(ZSTD_isError) = NULL; -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(ZSTD_CCtx*, sym_ZSTD_freeCCtx, ZSTD_freeCCtxp, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(ZSTD_DCtx*, sym_ZSTD_freeDCtx, ZSTD_freeDCtxp, NULL); static int zstd_ret_to_errno(size_t ret) { @@ -95,53 +119,146 @@ static int zstd_ret_to_errno(size_t ret) { } #endif -#if HAVE_XZ -static void *lzma_dl = NULL; +#if HAVE_ZLIB +static void *zlib_dl = NULL; -static DLSYM_PROTOTYPE(lzma_code) = NULL; -static DLSYM_PROTOTYPE(lzma_easy_encoder) = NULL; -static DLSYM_PROTOTYPE(lzma_end) = NULL; -static DLSYM_PROTOTYPE(lzma_stream_buffer_encode) = NULL; -static DLSYM_PROTOTYPE(lzma_stream_decoder) = NULL; -static DLSYM_PROTOTYPE(lzma_lzma_preset) = NULL; +static DLSYM_PROTOTYPE(deflateInit2_) = NULL; +static DLSYM_PROTOTYPE(deflate) = NULL; +static DLSYM_PROTOTYPE(deflateEnd) = NULL; +static DLSYM_PROTOTYPE(inflateInit2_) = NULL; +static DLSYM_PROTOTYPE(inflate) = NULL; +static DLSYM_PROTOTYPE(inflateEnd) = NULL; -/* We can't just do _cleanup_(sym_lzma_end) because a compiler bug makes - * this fail with: - * ../src/basic/compress.c: In function ‘decompress_blob_xz’: - * ../src/basic/compress.c:304:9: error: cleanup argument not a function - * 304 | _cleanup_(sym_lzma_end) lzma_stream s = LZMA_STREAM_INIT; - * | ^~~~~~~~~ - */ -static inline void lzma_end_wrapper(lzma_stream *ls) { - sym_lzma_end(ls); +static inline void deflateEnd_wrapper(z_stream *s) { + sym_deflateEnd(s); +} + +static inline void inflateEnd_wrapper(z_stream *s) { + sym_inflateEnd(s); +} +#endif + +#if HAVE_BZIP2 +static void *bzip2_dl = NULL; + +static DLSYM_PROTOTYPE(BZ2_bzCompressInit) = NULL; +static DLSYM_PROTOTYPE(BZ2_bzCompress) = NULL; +static DLSYM_PROTOTYPE(BZ2_bzCompressEnd) = NULL; +static DLSYM_PROTOTYPE(BZ2_bzDecompressInit) = NULL; +static DLSYM_PROTOTYPE(BZ2_bzDecompress) = NULL; +static DLSYM_PROTOTYPE(BZ2_bzDecompressEnd) = NULL; + +static inline void BZ2_bzCompressEnd_wrapper(bz_stream *s) { + sym_BZ2_bzCompressEnd(s); +} + +static inline void BZ2_bzDecompressEnd_wrapper(bz_stream *s) { + sym_BZ2_bzDecompressEnd(s); } #endif +/* Opaque Compressor/Decompressor struct definition */ +struct Compressor { + Compression type; + bool encoding; + union { +#if HAVE_XZ + lzma_stream xz; +#endif +#if HAVE_LZ4 + struct { + LZ4F_compressionContext_t c_lz4; + void *lz4_header; /* stashed frame header from LZ4F_compressBegin */ + size_t lz4_header_size; + }; + LZ4F_decompressionContext_t d_lz4; +#endif +#if HAVE_ZSTD + ZSTD_CCtx *c_zstd; + ZSTD_DCtx *d_zstd; +#endif +#if HAVE_ZLIB + z_stream gzip; +#endif +#if HAVE_BZIP2 + bz_stream bzip2; +#endif + }; +}; + #define ALIGN_8(l) ALIGN_TO(l, sizeof(size_t)) +/* zlib windowBits value for gzip format: MAX_WBITS (15) + 16 to enable gzip header detection/generation */ +#define ZLIB_WBITS_GZIP (15 + 16) + static const char* const compression_table[_COMPRESSION_MAX] = { - [COMPRESSION_NONE] = "NONE", - [COMPRESSION_XZ] = "XZ", - [COMPRESSION_LZ4] = "LZ4", - [COMPRESSION_ZSTD] = "ZSTD", + [COMPRESSION_NONE] = "uncompressed", /* backwards compatibility with importd */ + [COMPRESSION_XZ] = "xz", + [COMPRESSION_LZ4] = "lz4", + [COMPRESSION_ZSTD] = "zstd", + [COMPRESSION_GZIP] = "gzip", + [COMPRESSION_BZIP2] = "bzip2", +}; + +static const char* const compression_uppercase_table[_COMPRESSION_MAX] = { + [COMPRESSION_NONE] = "NONE", /* backwards compatibility with SYSTEMD_JOURNAL_COMPRESS=NONE */ + [COMPRESSION_XZ] = "XZ", + [COMPRESSION_LZ4] = "LZ4", + [COMPRESSION_ZSTD] = "ZSTD", + [COMPRESSION_GZIP] = "GZIP", + [COMPRESSION_BZIP2] = "BZIP2", }; -static const char* const compression_lowercase_table[_COMPRESSION_MAX] = { - [COMPRESSION_NONE] = "none", - [COMPRESSION_XZ] = "xz", - [COMPRESSION_LZ4] = "lz4", - [COMPRESSION_ZSTD] = "zstd", +static const char* const compression_extension_table[_COMPRESSION_MAX] = { + [COMPRESSION_NONE] = "", + [COMPRESSION_XZ] = ".xz", + [COMPRESSION_LZ4] = ".lz4", + [COMPRESSION_ZSTD] = ".zst", + [COMPRESSION_GZIP] = ".gz", + [COMPRESSION_BZIP2] = ".bz2", }; DEFINE_STRING_TABLE_LOOKUP(compression, Compression); -DEFINE_STRING_TABLE_LOOKUP(compression_lowercase, Compression); +DEFINE_STRING_TABLE_LOOKUP(compression_uppercase, Compression); +DEFINE_STRING_TABLE_LOOKUP(compression_extension, Compression); + +Compression compression_from_string_harder(const char *s) { + Compression c; + + assert(s); + + c = compression_from_string(s); + if (c >= 0) + return c; + + return compression_uppercase_from_string(s); +} + +Compression compression_from_filename(const char *filename) { + Compression c; + const char *e; + + assert(filename); + + e = strrchr(filename, '.'); + if (!e) + return COMPRESSION_NONE; + + c = compression_extension_from_string(e); + if (c < 0) + return COMPRESSION_NONE; + + return c; +} bool compression_supported(Compression c) { static const unsigned supported = (1U << COMPRESSION_NONE) | (1U << COMPRESSION_XZ) * HAVE_XZ | (1U << COMPRESSION_LZ4) * HAVE_LZ4 | - (1U << COMPRESSION_ZSTD) * HAVE_ZSTD; + (1U << COMPRESSION_ZSTD) * HAVE_ZSTD | + (1U << COMPRESSION_GZIP) * HAVE_ZLIB | + (1U << COMPRESSION_BZIP2) * HAVE_BZIP2; assert(c >= 0); assert(c < _COMPRESSION_MAX); @@ -149,7 +266,7 @@ bool compression_supported(Compression c) { return BIT_SET(supported, c); } -int dlopen_lzma(void) { +int dlopen_xz(void) { #if HAVE_XZ SD_ELF_NOTE_DLOPEN( "lzma", @@ -171,8 +288,120 @@ int dlopen_lzma(void) { #endif } -int compress_blob_xz(const void *src, uint64_t src_size, - void *dst, size_t dst_alloc_size, size_t *dst_size, int level) { +int dlopen_lz4(void) { +#if HAVE_LZ4 + SD_ELF_NOTE_DLOPEN( + "lz4", + "Support lz4 compression in journal and coredump files", + COMPRESSION_PRIORITY_LZ4, + "liblz4.so.1"); + + return dlopen_many_sym_or_warn( + &lz4_dl, + "liblz4.so.1", LOG_DEBUG, + DLSYM_ARG(LZ4F_compressBegin), + DLSYM_ARG(LZ4F_compressBound), + DLSYM_ARG(LZ4F_compressEnd), + DLSYM_ARG(LZ4F_compressUpdate), + DLSYM_ARG(LZ4F_createCompressionContext), + DLSYM_ARG(LZ4F_createDecompressionContext), + DLSYM_ARG(LZ4F_decompress), + DLSYM_ARG(LZ4F_freeCompressionContext), + DLSYM_ARG(LZ4F_freeDecompressionContext), + DLSYM_ARG(LZ4F_isError), + DLSYM_ARG(LZ4_compress_default), + DLSYM_ARG(LZ4_compress_HC), + DLSYM_ARG(LZ4_decompress_safe), + DLSYM_ARG(LZ4_decompress_safe_partial), + DLSYM_ARG(LZ4_versionNumber)); +#else + return -EOPNOTSUPP; +#endif +} + +int dlopen_zstd(void) { +#if HAVE_ZSTD + SD_ELF_NOTE_DLOPEN( + "zstd", + "Support zstd compression in journal and coredump files", + COMPRESSION_PRIORITY_ZSTD, + "libzstd.so.1"); + + return dlopen_many_sym_or_warn( + &zstd_dl, + "libzstd.so.1", LOG_DEBUG, + DLSYM_ARG(ZSTD_getErrorCode), + DLSYM_ARG(ZSTD_compress), + DLSYM_ARG(ZSTD_getFrameContentSize), + DLSYM_ARG(ZSTD_decompressStream), + DLSYM_ARG(ZSTD_getErrorName), + DLSYM_ARG(ZSTD_DStreamOutSize), + DLSYM_ARG(ZSTD_CStreamInSize), + DLSYM_ARG(ZSTD_CStreamOutSize), + DLSYM_ARG(ZSTD_CCtx_setParameter), + DLSYM_ARG(ZSTD_compressStream2), + DLSYM_ARG(ZSTD_DStreamInSize), + DLSYM_ARG(ZSTD_freeCCtx), + DLSYM_ARG(ZSTD_freeDCtx), + DLSYM_ARG(ZSTD_isError), + DLSYM_ARG(ZSTD_createDCtx), + DLSYM_ARG(ZSTD_createCCtx)); +#else + return -EOPNOTSUPP; +#endif +} + +int dlopen_zlib(void) { +#if HAVE_ZLIB + SD_ELF_NOTE_DLOPEN( + "zlib", + "Support gzip compression and decompression", + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libz.so.1"); + + return dlopen_many_sym_or_warn( + &zlib_dl, + "libz.so.1", LOG_DEBUG, + DLSYM_ARG(deflateInit2_), + DLSYM_ARG(deflate), + DLSYM_ARG(deflateEnd), + DLSYM_ARG(inflateInit2_), + DLSYM_ARG(inflate), + DLSYM_ARG(inflateEnd)); +#else + return -EOPNOTSUPP; +#endif +} + +int dlopen_bzip2(void) { +#if HAVE_BZIP2 + SD_ELF_NOTE_DLOPEN( + "bzip2", + "Support bzip2 compression and decompression", + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libbz2.so.1"); + + return dlopen_many_sym_or_warn( + &bzip2_dl, + "libbz2.so.1", LOG_DEBUG, + DLSYM_ARG(BZ2_bzCompressInit), + DLSYM_ARG(BZ2_bzCompress), + DLSYM_ARG(BZ2_bzCompressEnd), + DLSYM_ARG(BZ2_bzDecompressInit), + DLSYM_ARG(BZ2_bzDecompress), + DLSYM_ARG(BZ2_bzDecompressEnd)); +#else + return -EOPNOTSUPP; +#endif +} + +static int compress_blob_xz( + const void *src, + uint64_t src_size, + void *dst, + size_t dst_alloc_size, + size_t *dst_size, + int level) { assert(src); assert(src_size > 0); @@ -193,7 +422,7 @@ int compress_blob_xz(const void *src, uint64_t src_size, size_t out_pos = 0; int r; - r = dlopen_lzma(); + r = dlopen_xz(); if (r < 0) return r; @@ -221,39 +450,13 @@ int compress_blob_xz(const void *src, uint64_t src_size, #endif } -int dlopen_lz4(void) { -#if HAVE_LZ4 - SD_ELF_NOTE_DLOPEN( - "lz4", - "Support lz4 compression in journal and coredump files", - COMPRESSION_PRIORITY_LZ4, - "liblz4.so.1"); - - return dlopen_many_sym_or_warn( - &lz4_dl, - "liblz4.so.1", LOG_DEBUG, - DLSYM_ARG(LZ4F_compressBegin), - DLSYM_ARG(LZ4F_compressBound), - DLSYM_ARG(LZ4F_compressEnd), - DLSYM_ARG(LZ4F_compressUpdate), - DLSYM_ARG(LZ4F_createCompressionContext), - DLSYM_ARG(LZ4F_createDecompressionContext), - DLSYM_ARG(LZ4F_decompress), - DLSYM_ARG(LZ4F_freeCompressionContext), - DLSYM_ARG(LZ4F_freeDecompressionContext), - DLSYM_ARG(LZ4F_isError), - DLSYM_ARG(LZ4_compress_default), - DLSYM_ARG(LZ4_compress_HC), - DLSYM_ARG(LZ4_decompress_safe), - DLSYM_ARG(LZ4_decompress_safe_partial), - DLSYM_ARG(LZ4_versionNumber)); -#else - return -EOPNOTSUPP; -#endif -} - -int compress_blob_lz4(const void *src, uint64_t src_size, - void *dst, size_t dst_alloc_size, size_t *dst_size, int level) { +static int compress_blob_lz4( + const void *src, + uint64_t src_size, + void *dst, + size_t dst_alloc_size, + size_t *dst_size, + int level) { assert(src); assert(src_size > 0); @@ -273,6 +476,11 @@ int compress_blob_lz4(const void *src, uint64_t src_size, if (src_size < 9) return -ENOBUFS; + if (src_size > INT_MAX) + return -EFBIG; + if (dst_alloc_size > INT_MAX) + dst_alloc_size = INT_MAX; + if (level <= 0) r = sym_LZ4_compress_default(src, (char*)dst + 8, src_size, (int) dst_alloc_size - 8); else @@ -289,41 +497,13 @@ int compress_blob_lz4(const void *src, uint64_t src_size, #endif } -int dlopen_zstd(void) { -#if HAVE_ZSTD - SD_ELF_NOTE_DLOPEN( - "zstd", - "Support zstd compression in journal and coredump files", - COMPRESSION_PRIORITY_ZSTD, - "libzstd.so.1"); - - return dlopen_many_sym_or_warn( - &zstd_dl, - "libzstd.so.1", LOG_DEBUG, - DLSYM_ARG(ZSTD_getErrorCode), - DLSYM_ARG(ZSTD_compress), - DLSYM_ARG(ZSTD_getFrameContentSize), - DLSYM_ARG(ZSTD_decompressStream), - DLSYM_ARG(ZSTD_getErrorName), - DLSYM_ARG(ZSTD_DStreamOutSize), - DLSYM_ARG(ZSTD_CStreamInSize), - DLSYM_ARG(ZSTD_CStreamOutSize), - DLSYM_ARG(ZSTD_CCtx_setParameter), - DLSYM_ARG(ZSTD_compressStream2), - DLSYM_ARG(ZSTD_DStreamInSize), - DLSYM_ARG(ZSTD_freeCCtx), - DLSYM_ARG(ZSTD_freeDCtx), - DLSYM_ARG(ZSTD_isError), - DLSYM_ARG(ZSTD_createDCtx), - DLSYM_ARG(ZSTD_createCCtx)); -#else - return -EOPNOTSUPP; -#endif -} - -int compress_blob_zstd( - const void *src, uint64_t src_size, - void *dst, size_t dst_alloc_size, size_t *dst_size, int level) { +static int compress_blob_zstd( + const void *src, + uint64_t src_size, + void *dst, + size_t dst_alloc_size, + size_t *dst_size, + int level) { assert(src); assert(src_size > 0); @@ -350,35 +530,149 @@ int compress_blob_zstd( #endif } -int decompress_blob_xz( - const void *src, - uint64_t src_size, - void **dst, - size_t* dst_size, - size_t dst_max) { +static int compress_blob_gzip(const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size, int level) { assert(src); assert(src_size > 0); assert(dst); + assert(dst_alloc_size > 0); assert(dst_size); -#if HAVE_XZ +#if HAVE_ZLIB int r; - r = dlopen_lzma(); + r = dlopen_zlib(); if (r < 0) return r; - _cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT; - lzma_ret ret = sym_lzma_stream_decoder(&s, UINT64_MAX, 0); - if (ret != LZMA_OK) - return -ENOMEM; - - size_t space = MIN(src_size * 2, dst_max ?: SIZE_MAX); - if (!greedy_realloc(dst, space, 1)) + if (src_size > UINT_MAX) + return -EFBIG; + if (dst_alloc_size > UINT_MAX) + dst_alloc_size = UINT_MAX; + + _cleanup_(deflateEnd_wrapper) z_stream s = {}; + + r = sym_deflateInit2_(&s, level < 0 ? Z_DEFAULT_COMPRESSION : level, + /* method= */ Z_DEFLATED, + /* windowBits= */ ZLIB_WBITS_GZIP, + /* memLevel= */ 8, + /* strategy= */ Z_DEFAULT_STRATEGY, + ZLIB_VERSION, (int) sizeof(s)); + if (r != Z_OK) return -ENOMEM; - s.next_in = src; + s.next_in = (void*) src; + s.avail_in = src_size; + s.next_out = dst; + s.avail_out = dst_alloc_size; + + r = sym_deflate(&s, Z_FINISH); + if (r != Z_STREAM_END) + return -ENOBUFS; + + *dst_size = dst_alloc_size - s.avail_out; + return 0; +#else + return -EPROTONOSUPPORT; +#endif +} + +static int compress_blob_bzip2( + const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size, int level) { + + assert(src); + assert(src_size > 0); + assert(dst); + assert(dst_alloc_size > 0); + assert(dst_size); + +#if HAVE_BZIP2 + int r; + + r = dlopen_bzip2(); + if (r < 0) + return r; + + if (src_size > UINT_MAX) + return -EFBIG; + if (dst_alloc_size > UINT_MAX) + dst_alloc_size = UINT_MAX; + + _cleanup_(BZ2_bzCompressEnd_wrapper) bz_stream s = {}; + + r = sym_BZ2_bzCompressInit(&s, level < 0 ? 9 : level, /* verbosity= */ 0, /* workFactor= */ 0); + if (r != BZ_OK) + return -ENOMEM; + + s.next_in = (char*) src; + s.avail_in = src_size; + s.next_out = (char*) dst; + s.avail_out = dst_alloc_size; + + r = sym_BZ2_bzCompress(&s, BZ_FINISH); + + if (r != BZ_STREAM_END) + return -ENOBUFS; + + *dst_size = dst_alloc_size - s.avail_out; + return 0; +#else + return -EPROTONOSUPPORT; +#endif +} + +int compress_blob( + Compression compression, + const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size, int level) { + + switch (compression) { + case COMPRESSION_XZ: + return compress_blob_xz(src, src_size, dst, dst_alloc_size, dst_size, level); + case COMPRESSION_LZ4: + return compress_blob_lz4(src, src_size, dst, dst_alloc_size, dst_size, level); + case COMPRESSION_ZSTD: + return compress_blob_zstd(src, src_size, dst, dst_alloc_size, dst_size, level); + case COMPRESSION_GZIP: + return compress_blob_gzip(src, src_size, dst, dst_alloc_size, dst_size, level); + case COMPRESSION_BZIP2: + return compress_blob_bzip2(src, src_size, dst, dst_alloc_size, dst_size, level); + default: + return -EOPNOTSUPP; + } +} + +static int decompress_blob_xz( + const void *src, + uint64_t src_size, + void **dst, + size_t *dst_size, + size_t dst_max) { + + assert(src); + assert(src_size > 0); + assert(dst); + assert(dst_size); + +#if HAVE_XZ + int r; + + r = dlopen_xz(); + if (r < 0) + return r; + + _cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT; + lzma_ret ret = sym_lzma_stream_decoder(&s, UINT64_MAX, /* flags= */ 0); + if (ret != LZMA_OK) + return -ENOMEM; + + size_t space = MIN(src_size * 2, dst_max ?: SIZE_MAX); + if (!greedy_realloc(dst, space, 1)) + return -ENOMEM; + + s.next_in = src; s.avail_in = src_size; s.next_out = *dst; @@ -416,11 +710,11 @@ int decompress_blob_xz( #endif } -int decompress_blob_lz4( +static int decompress_blob_lz4( const void *src, uint64_t src_size, void **dst, - size_t* dst_size, + size_t *dst_size, size_t dst_max) { assert(src); @@ -439,6 +733,9 @@ int decompress_blob_lz4( if (src_size <= 8) return -EBADMSG; + if (src_size - 8 > INT_MAX) + return -EFBIG; + size = unaligned_read_le64(src); if (size < 0 || (unsigned) size != unaligned_read_le64(src)) return -EFBIG; @@ -457,7 +754,7 @@ int decompress_blob_lz4( #endif } -int decompress_blob_zstd( +static int decompress_blob_zstd( const void *src, uint64_t src_size, void **dst, @@ -515,12 +812,146 @@ int decompress_blob_zstd( #endif } +static int decompress_blob_gzip( + const void *src, + uint64_t src_size, + void **dst, + size_t *dst_size, + size_t dst_max) { + + assert(src); + assert(src_size > 0); + assert(dst); + assert(dst_size); + +#if HAVE_ZLIB + int r; + + r = dlopen_zlib(); + if (r < 0) + return r; + + if (src_size > UINT_MAX) + return -EFBIG; + + _cleanup_(inflateEnd_wrapper) z_stream s = {}; + + r = sym_inflateInit2_(&s, /* windowBits= */ ZLIB_WBITS_GZIP, ZLIB_VERSION, (int) sizeof(s)); + if (r != Z_OK) + return -ENOMEM; + + size_t space = MIN3(src_size * 2, dst_max ?: SIZE_MAX, (size_t) UINT_MAX); + if (!greedy_realloc(dst, space, 1)) + return -ENOMEM; + + s.next_in = (void*) src; + s.avail_in = src_size; + s.next_out = *dst; + s.avail_out = space; + + for (;;) { + size_t used; + + r = sym_inflate(&s, Z_NO_FLUSH); + if (r == Z_STREAM_END) + break; + if (!IN_SET(r, Z_OK, Z_BUF_ERROR)) + return -EBADMSG; + + if (dst_max > 0 && (space - s.avail_out) >= dst_max) + break; + if (dst_max > 0 && space == dst_max) + return -ENOBUFS; + + used = space - s.avail_out; + space = MIN3(2 * space, dst_max ?: SIZE_MAX, UINT_MAX); + if (!greedy_realloc(dst, space, 1)) + return -ENOMEM; + + s.avail_out = space - used; + s.next_out = *(uint8_t**)dst + used; + } + + *dst_size = space - s.avail_out; + return 0; +#else + return -EPROTONOSUPPORT; +#endif +} + +static int decompress_blob_bzip2( + const void *src, + uint64_t src_size, + void **dst, + size_t *dst_size, + size_t dst_max) { + + assert(src); + assert(src_size > 0); + assert(dst); + assert(dst_size); + +#if HAVE_BZIP2 + int r; + + r = dlopen_bzip2(); + if (r < 0) + return r; + + if (src_size > UINT_MAX) + return -EFBIG; + + _cleanup_(BZ2_bzDecompressEnd_wrapper) bz_stream s = {}; + + r = sym_BZ2_bzDecompressInit(&s, /* verbosity= */ 0, /* small= */ 0); + if (r != BZ_OK) + return -ENOMEM; + + size_t space = MIN3(src_size * 2, dst_max ?: SIZE_MAX, (size_t) UINT_MAX); + if (!greedy_realloc(dst, space, 1)) + return -ENOMEM; + + s.next_in = (char*) src; + s.avail_in = src_size; + s.next_out = (char*) *dst; + s.avail_out = space; + + for (;;) { + size_t used; + + r = sym_BZ2_bzDecompress(&s); + if (r == BZ_STREAM_END) + break; + if (r != BZ_OK) + return -EBADMSG; + + if (dst_max > 0 && (space - s.avail_out) >= dst_max) + break; + if (dst_max > 0 && space == dst_max) + return -ENOBUFS; + + used = space - s.avail_out; + space = MIN3(2 * space, dst_max ?: SIZE_MAX, (size_t) UINT_MAX); + if (!greedy_realloc(dst, space, 1)) + return -ENOMEM; + + s.avail_out = space - used; + s.next_out = (char*) *dst + used; + } + + *dst_size = space - s.avail_out; + return 0; +#else + return -EPROTONOSUPPORT; +#endif +} + int decompress_blob( Compression compression, const void *src, uint64_t src_size, void **dst, - size_t* dst_size, + size_t *dst_size, size_t dst_max) { switch (compression) { @@ -536,12 +967,62 @@ int decompress_blob( return decompress_blob_zstd( src, src_size, dst, dst_size, dst_max); + case COMPRESSION_GZIP: + return decompress_blob_gzip( + src, src_size, + dst, dst_size, dst_max); + case COMPRESSION_BZIP2: + return decompress_blob_bzip2( + src, src_size, + dst, dst_size, dst_max); default: return -EPROTONOSUPPORT; } } -int decompress_startswith_xz( +int decompress_zlib_raw( + const void *src, + uint64_t src_size, + void *dst, + size_t dst_size, + int wbits) { + +#if HAVE_ZLIB + int r; + + r = dlopen_zlib(); + if (r < 0) + return r; + + if (src_size > UINT_MAX) + return -EFBIG; + if (dst_size > UINT_MAX) + return -EFBIG; + + _cleanup_(inflateEnd_wrapper) z_stream s = { + .next_in = (void*) src, + .avail_in = src_size, + .next_out = dst, + .avail_out = dst_size, + }; + + r = sym_inflateInit2_(&s, /* windowBits= */ wbits, ZLIB_VERSION, (int) sizeof(s)); + if (r != Z_OK) + return -EIO; + + r = sym_inflate(&s, Z_FINISH); + size_t produced = (uint8_t*) s.next_out - (uint8_t*) dst; + + if (r != Z_STREAM_END || produced != dst_size) + return -EBADMSG; + + return 0; +#else + return -EPROTONOSUPPORT; +#endif +} + +static int decompress_startswith_xz( const void *src, uint64_t src_size, void **buffer, @@ -560,12 +1041,12 @@ int decompress_startswith_xz( #if HAVE_XZ int r; - r = dlopen_lzma(); + r = dlopen_xz(); if (r < 0) return r; _cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT; - lzma_ret ret = sym_lzma_stream_decoder(&s, UINT64_MAX, 0); + lzma_ret ret = sym_lzma_stream_decoder(&s, UINT64_MAX, /* flags= */ 0); if (ret != LZMA_OK) return -EBADMSG; @@ -607,7 +1088,7 @@ int decompress_startswith_xz( #endif } -int decompress_startswith_lz4( +static int decompress_startswith_lz4( const void *src, uint64_t src_size, void **buffer, @@ -634,6 +1115,9 @@ int decompress_startswith_lz4( if (src_size <= 8) return -EBADMSG; + if (src_size - 8 > INT_MAX) + return -EFBIG; + if (!(greedy_realloc(buffer, ALIGN_8(prefix_len + 1), 1))) return -ENOMEM; allocated = MALLOC_SIZEOF_SAFE(*buffer); @@ -680,7 +1164,7 @@ int decompress_startswith_lz4( #endif } -int decompress_startswith_zstd( +static int decompress_startswith_zstd( const void *src, uint64_t src_size, void **buffer, @@ -738,8 +1222,7 @@ int decompress_startswith_zstd( #endif } -int decompress_startswith( - Compression compression, +static int decompress_startswith_gzip( const void *src, uint64_t src_size, void **buffer, @@ -747,212 +1230,234 @@ int decompress_startswith( size_t prefix_len, uint8_t extra) { - switch (compression) { - - case COMPRESSION_XZ: - return decompress_startswith_xz( - src, src_size, - buffer, - prefix, prefix_len, - extra); - - case COMPRESSION_LZ4: - return decompress_startswith_lz4( - src, src_size, - buffer, - prefix, prefix_len, - extra); - case COMPRESSION_ZSTD: - return decompress_startswith_zstd( - src, src_size, - buffer, - prefix, prefix_len, - extra); - default: - return -EBADMSG; - } -} - -int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) { - assert(fdf >= 0); - assert(fdt >= 0); + assert(src); + assert(src_size > 0); + assert(buffer); + assert(prefix); -#if HAVE_XZ +#if HAVE_ZLIB int r; - r = dlopen_lzma(); + r = dlopen_zlib(); if (r < 0) return r; - _cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT; - lzma_ret ret = sym_lzma_easy_encoder(&s, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC64); - if (ret != LZMA_OK) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to initialize XZ encoder: code %u", - ret); + if (src_size > UINT_MAX) + return -EFBIG; - uint8_t buf[BUFSIZ], out[BUFSIZ]; - lzma_action action = LZMA_RUN; - for (;;) { - if (s.avail_in == 0 && action == LZMA_RUN) { - size_t m = sizeof(buf); - ssize_t n; - - if (max_bytes != UINT64_MAX && (uint64_t) m > max_bytes) - m = (size_t) max_bytes; - - n = read(fdf, buf, m); - if (n < 0) - return -errno; - if (n == 0) - action = LZMA_FINISH; - else { - s.next_in = buf; - s.avail_in = n; - - if (max_bytes != UINT64_MAX) { - assert(max_bytes >= (uint64_t) n); - max_bytes -= n; - } - } - } + _cleanup_(inflateEnd_wrapper) z_stream s = {}; - if (s.avail_out == 0) { - s.next_out = out; - s.avail_out = sizeof(out); - } + r = sym_inflateInit2_(&s, /* windowBits= */ ZLIB_WBITS_GZIP, ZLIB_VERSION, (int) sizeof(s)); + if (r != Z_OK) + return -EBADMSG; - ret = sym_lzma_code(&s, action); - if (!IN_SET(ret, LZMA_OK, LZMA_STREAM_END)) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), - "Compression failed: code %u", - ret); + if (!(greedy_realloc(buffer, ALIGN_8(prefix_len + 1), 1))) + return -ENOMEM; - if (s.avail_out == 0 || ret == LZMA_STREAM_END) { - ssize_t n, k; + size_t allocated = MALLOC_SIZEOF_SAFE(*buffer); - n = sizeof(out) - s.avail_out; + s.next_in = (void*) src; + s.avail_in = src_size; - k = loop_write(fdt, out, n); - if (k < 0) - return k; + s.next_out = *buffer; + s.avail_out = MIN(allocated, (size_t) UINT_MAX); - if (ret == LZMA_STREAM_END) { - if (ret_uncompressed_size) - *ret_uncompressed_size = s.total_in; + for (;;) { + r = sym_inflate(&s, Z_FINISH); - if (s.total_in == 0) - log_debug("XZ compression finished (no input data)"); - else - log_debug("XZ compression finished (%"PRIu64" -> %"PRIu64" bytes, %.1f%%)", - s.total_in, s.total_out, - (double) s.total_out / s.total_in * 100); + if (!IN_SET(r, Z_OK, Z_STREAM_END, Z_BUF_ERROR)) + return -EBADMSG; - return 0; - } - } + if (allocated - s.avail_out >= prefix_len + 1) + return memcmp(*buffer, prefix, prefix_len) == 0 && + ((const uint8_t*) *buffer)[prefix_len] == extra; + + if (r == Z_STREAM_END) + return 0; + + size_t used = allocated - s.avail_out; + + if (!(greedy_realloc(buffer, allocated * 2, 1))) + return -ENOMEM; + + allocated = MALLOC_SIZEOF_SAFE(*buffer); + s.avail_out = MIN(allocated - used, (size_t) UINT_MAX); + s.next_out = *(uint8_t**)buffer + used; } #else return -EPROTONOSUPPORT; #endif } -#define LZ4_BUFSIZE (512*1024u) +static int decompress_startswith_bzip2( + const void *src, + uint64_t src_size, + void **buffer, + const void *prefix, + size_t prefix_len, + uint8_t extra) { -int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) { + assert(src); + assert(src_size > 0); + assert(buffer); + assert(prefix); -#if HAVE_LZ4 - LZ4F_errorCode_t c; - _cleanup_(LZ4F_freeCompressionContextp) LZ4F_compressionContext_t ctx = NULL; - _cleanup_free_ void *in_buff = NULL; - _cleanup_free_ char *out_buff = NULL; - size_t out_allocsize, n, offset = 0, frame_size; - uint64_t total_in = 0, total_out; +#if HAVE_BZIP2 int r; - static const LZ4F_preferences_t preferences = { - .frameInfo.blockSizeID = 5, - }; - r = dlopen_lz4(); + r = dlopen_bzip2(); if (r < 0) return r; - c = sym_LZ4F_createCompressionContext(&ctx, LZ4F_VERSION); - if (sym_LZ4F_isError(c)) - return -ENOMEM; + if (src_size > UINT_MAX) + return -EFBIG; - frame_size = sym_LZ4F_compressBound(LZ4_BUFSIZE, &preferences); - out_allocsize = frame_size + 64*1024; /* add some space for header and trailer */ - out_buff = malloc(out_allocsize); - if (!out_buff) - return -ENOMEM; + _cleanup_(BZ2_bzDecompressEnd_wrapper) bz_stream s = {}; + + r = sym_BZ2_bzDecompressInit(&s, /* verbosity= */ 0, /* small= */ 0); + if (r != BZ_OK) + return -EBADMSG; - in_buff = malloc(LZ4_BUFSIZE); - if (!in_buff) + if (!(greedy_realloc(buffer, ALIGN_8(prefix_len + 1), 1))) return -ENOMEM; - n = offset = total_out = sym_LZ4F_compressBegin(ctx, out_buff, out_allocsize, &preferences); - if (sym_LZ4F_isError(n)) - return -EINVAL; + size_t allocated = MALLOC_SIZEOF_SAFE(*buffer); + + s.next_in = (char*) src; + s.avail_in = src_size; - log_debug("Buffer size is %zu bytes, header size %zu bytes.", out_allocsize, n); + s.next_out = *buffer; + s.avail_out = MIN(allocated, (size_t) UINT_MAX); for (;;) { - ssize_t k; + r = sym_BZ2_bzDecompress(&s); - k = loop_read(fdf, in_buff, LZ4_BUFSIZE, true); - if (k < 0) - return k; - if (k == 0) - break; - n = sym_LZ4F_compressUpdate(ctx, out_buff + offset, out_allocsize - offset, - in_buff, k, NULL); - if (sym_LZ4F_isError(n)) - return -ENOTRECOVERABLE; + if (!IN_SET(r, BZ_OK, BZ_STREAM_END)) + return -EBADMSG; + + if (allocated - s.avail_out >= prefix_len + 1) + return memcmp(*buffer, prefix, prefix_len) == 0 && + ((const uint8_t*) *buffer)[prefix_len] == extra; - total_in += k; - offset += n; - total_out += n; + if (r == BZ_STREAM_END) + return 0; - if (max_bytes != UINT64_MAX && total_out > (size_t) max_bytes) - return log_debug_errno(SYNTHETIC_ERRNO(EFBIG), - "Compressed stream longer than %" PRIu64 " bytes", max_bytes); + size_t used = allocated - s.avail_out; - if (out_allocsize - offset < frame_size + 4) { - k = loop_write(fdt, out_buff, offset); - if (k < 0) - return k; - offset = 0; - } + if (!(greedy_realloc(buffer, allocated * 2, 1))) + return -ENOMEM; + + allocated = MALLOC_SIZEOF_SAFE(*buffer); + s.avail_out = MIN(allocated - used, (size_t) UINT_MAX); + s.next_out = (char*) *buffer + used; + } +#else + return -EPROTONOSUPPORT; +#endif +} + +int decompress_startswith( + Compression compression, + const void *src, uint64_t src_size, + void **buffer, + const void *prefix, size_t prefix_len, + uint8_t extra) { + + switch (compression) { + case COMPRESSION_XZ: + return decompress_startswith_xz(src, src_size, buffer, prefix, prefix_len, extra); + case COMPRESSION_LZ4: + return decompress_startswith_lz4(src, src_size, buffer, prefix, prefix_len, extra); + case COMPRESSION_ZSTD: + return decompress_startswith_zstd(src, src_size, buffer, prefix, prefix_len, extra); + case COMPRESSION_GZIP: + return decompress_startswith_gzip(src, src_size, buffer, prefix, prefix_len, extra); + case COMPRESSION_BZIP2: + return decompress_startswith_bzip2(src, src_size, buffer, prefix, prefix_len, extra); + default: + return -EOPNOTSUPP; } +} + +int compress_stream( + Compression type, + int fdf, int fdt, + uint64_t max_bytes, + uint64_t *ret_uncompressed_size) { + + _cleanup_(compressor_freep) Compressor *c = NULL; + _cleanup_free_ void *buf = NULL; + _cleanup_free_ uint8_t *input = NULL; + size_t buf_size = 0, buf_alloc = 0; + uint64_t total_in = 0, total_out = 0; + int r; - n = sym_LZ4F_compressEnd(ctx, out_buff + offset, out_allocsize - offset, NULL); - if (sym_LZ4F_isError(n)) - return -ENOTRECOVERABLE; + assert(fdf >= 0); + assert(fdt >= 0); - offset += n; - total_out += n; - r = loop_write(fdt, out_buff, offset); + r = compressor_new(&c, type); if (r < 0) return r; + input = new(uint8_t, COMPRESS_PIPE_BUFFER_SIZE); + if (!input) + return -ENOMEM; + + for (;;) { + size_t m = COMPRESS_PIPE_BUFFER_SIZE; + ssize_t n; + + if (max_bytes != UINT64_MAX && (uint64_t) m > max_bytes) + m = (size_t) max_bytes; + + n = read(fdf, input, m); + if (n < 0) + return -errno; + + if (n == 0) { + r = compressor_finish(c, &buf, &buf_size, &buf_alloc); + if (r < 0) + return r; + + if (buf_size > 0) { + r = loop_write(fdt, buf, buf_size); + if (r < 0) + return r; + total_out += buf_size; + } + break; + } + + total_in += n; + if (max_bytes != UINT64_MAX) { + assert(max_bytes >= (uint64_t) n); + max_bytes -= n; + } + + r = compressor_start(c, input, n, &buf, &buf_size, &buf_alloc); + if (r < 0) + return r; + + if (buf_size > 0) { + r = loop_write(fdt, buf, buf_size); + if (r < 0) + return r; + total_out += buf_size; + } + } + if (ret_uncompressed_size) *ret_uncompressed_size = total_in; if (total_in == 0) - log_debug("LZ4 compression finished (no input data)"); + log_debug("%s compression finished (no input data)", compression_to_string(type)); else - log_debug("LZ4 compression finished (%" PRIu64 " -> %" PRIu64 " bytes, %.1f%%)", - total_in, total_out, - (double) total_out / total_in * 100); + log_debug("%s compression finished (%" PRIu64 " -> %" PRIu64 " bytes, %.1f%%)", + compression_to_string(type), total_in, total_out, (double) total_out / total_in * 100); return 0; -#else - return -EPROTONOSUPPORT; -#endif } -#if HAVE_COMPRESSION /* Determine whether sparse writes should be used for this fd. Sparse writes are only safe on * regular files without O_APPEND (O_APPEND ignores lseek position, which would collapse holes). */ static int should_sparse(int fd) { @@ -987,414 +1492,1049 @@ static int finalize_sparse(int fd) { return 0; } -static int maybe_sparse_write(int fd, const void *buf, size_t nbytes, bool sparse) { - int r; +/* Common helper for decompress_stream_*() wrappers */ + +struct decompress_stream_userdata { + int fd; + uint64_t max_bytes; + uint64_t total_out; + bool sparse; +}; + +static int decompress_stream_write_callback(const void *data, size_t size, void *userdata) { + struct decompress_stream_userdata *u = ASSERT_PTR(userdata); - if (sparse) { - ssize_t k; + if (u->max_bytes != UINT64_MAX) { + if (u->max_bytes < size) + return -EFBIG; + u->max_bytes -= size; + } + + u->total_out += size; + if (u->sparse) { /* Note: sparse_write() does not retry on EINTR and converts short writes to -EIO. * This is fine here since sparse mode is only used on regular files, where short * writes and EINTR are not expected in practice. */ - k = sparse_write(fd, buf, nbytes, 64); + ssize_t k = sparse_write(u->fd, data, size, 64); if (k < 0) return (int) k; - } else { - r = loop_write_full(fd, buf, nbytes, USEC_INFINITY); - if (r < 0) - return r; + return 0; } - return 0; + return loop_write(u->fd, data, size); } + +static int decompressor_new(Decompressor **ret, Compression type) { +#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2 + int r; #endif -int decompress_stream_xz(int fdf, int fdt, uint64_t max_bytes) { - assert(fdf >= 0); - assert(fdt >= 0); + assert(ret); -#if HAVE_XZ - bool sparse = should_sparse(fdt) > 0; - int r; + _cleanup_(compressor_freep) Decompressor *c = new0(Decompressor, 1); + if (!c) + return -ENOMEM; - r = dlopen_lzma(); - if (r < 0) - return r; + c->type = _COMPRESSION_INVALID; - _cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT; - lzma_ret ret = sym_lzma_stream_decoder(&s, UINT64_MAX, 0); - if (ret != LZMA_OK) - return log_debug_errno(SYNTHETIC_ERRNO(ENOMEM), - "Failed to initialize XZ decoder: code %u", - ret); + switch (type) { - uint8_t buf[BUFSIZ], out[BUFSIZ]; - lzma_action action = LZMA_RUN; - for (;;) { - if (s.avail_in == 0 && action == LZMA_RUN) { - ssize_t n; - - n = read(fdf, buf, sizeof(buf)); - if (n < 0) - return -errno; - if (n == 0) - action = LZMA_FINISH; - else { - s.next_in = buf; - s.avail_in = n; - } - } +#if HAVE_XZ + case COMPRESSION_XZ: + r = dlopen_xz(); + if (r < 0) + return r; - if (s.avail_out == 0) { - s.next_out = out; - s.avail_out = sizeof(out); - } + if (sym_lzma_stream_decoder(&c->xz, UINT64_MAX, LZMA_TELL_UNSUPPORTED_CHECK | LZMA_CONCATENATED) != LZMA_OK) + return -EIO; + break; +#endif - ret = sym_lzma_code(&s, action); - if (!IN_SET(ret, LZMA_OK, LZMA_STREAM_END)) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), - "Decompression failed: code %u", - ret); +#if HAVE_LZ4 + case COMPRESSION_LZ4: { + r = dlopen_lz4(); + if (r < 0) + return r; + + size_t rc = sym_LZ4F_createDecompressionContext(&c->d_lz4, LZ4F_VERSION); + if (sym_LZ4F_isError(rc)) + return -ENOMEM; + + break; + } +#endif - if (s.avail_out == 0 || ret == LZMA_STREAM_END) { - ssize_t n, k; +#if HAVE_ZSTD + case COMPRESSION_ZSTD: + r = dlopen_zstd(); + if (r < 0) + return r; - n = sizeof(out) - s.avail_out; + c->d_zstd = sym_ZSTD_createDCtx(); + if (!c->d_zstd) + return -ENOMEM; + break; +#endif - if (max_bytes != UINT64_MAX) { - if (max_bytes < (uint64_t) n) - return -EFBIG; +#if HAVE_ZLIB + case COMPRESSION_GZIP: + r = dlopen_zlib(); + if (r < 0) + return r; - max_bytes -= n; - } + r = sym_inflateInit2_(&c->gzip, /* windowBits= */ ZLIB_WBITS_GZIP, ZLIB_VERSION, (int) sizeof(c->gzip)); + if (r != Z_OK) + return -EIO; + break; +#endif - k = maybe_sparse_write(fdt, out, n, sparse); - if (k < 0) - return k; +#if HAVE_BZIP2 + case COMPRESSION_BZIP2: + r = dlopen_bzip2(); + if (r < 0) + return r; - if (ret == LZMA_STREAM_END) { - if (s.total_in == 0) - log_debug("XZ decompression finished (no input data)"); - else - log_debug("XZ decompression finished (%"PRIu64" -> %"PRIu64" bytes, %.1f%%)", - s.total_in, s.total_out, - (double) s.total_out / s.total_in * 100); + r = sym_BZ2_bzDecompressInit(&c->bzip2, /* verbosity= */ 0, /* small= */ 0); + if (r != BZ_OK) + return -EIO; + break; +#endif - return sparse ? finalize_sparse(fdt) : 0; - } - } + default: + return -EOPNOTSUPP; } -#else - return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), - "Cannot decompress file. Compiled without XZ support."); -#endif + + c->type = type; + c->encoding = false; + *ret = TAKE_PTR(c); + return 0; } -int decompress_stream_lz4(int fdf, int fdt, uint64_t max_bytes) { -#if HAVE_LZ4 - size_t c; - _cleanup_(LZ4F_freeDecompressionContextp) LZ4F_decompressionContext_t ctx = NULL; - _cleanup_free_ char *buf = NULL; - char *src; - struct stat st; - bool sparse = should_sparse(fdt) > 0; +int decompress_stream( + Compression type, + int fdf, int fdt, + uint64_t max_bytes) { + + _cleanup_(compressor_freep) Decompressor *c = NULL; + _cleanup_free_ uint8_t *buf = NULL; + uint64_t total_in = 0; int r; - size_t total_in = 0, total_out = 0; - r = dlopen_lz4(); + assert(fdf >= 0); + assert(fdt >= 0); + + r = decompressor_new(&c, type); if (r < 0) return r; - c = sym_LZ4F_createDecompressionContext(&ctx, LZ4F_VERSION); - if (sym_LZ4F_isError(c)) - return -ENOMEM; - - if (fstat(fdf, &st) < 0) - return log_debug_errno(errno, "fstat() failed: %m"); - - if (file_offset_beyond_memory_size(st.st_size)) - return -EFBIG; + struct decompress_stream_userdata userdata = { + .fd = fdt, + .max_bytes = max_bytes, + .sparse = should_sparse(fdt) > 0, + }; - buf = malloc(LZ4_BUFSIZE); + buf = new(uint8_t, COMPRESS_PIPE_BUFFER_SIZE); if (!buf) return -ENOMEM; - src = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fdf, 0); - if (src == MAP_FAILED) - return -errno; - - while (total_in < (size_t) st.st_size) { - size_t produced = LZ4_BUFSIZE; - size_t used = st.st_size - total_in; - - c = sym_LZ4F_decompress(ctx, buf, &produced, src + total_in, &used, NULL); - if (sym_LZ4F_isError(c)) { - r = -EBADMSG; - goto cleanup; - } + for (;;) { + ssize_t n; - total_in += used; - total_out += produced; + n = read(fdf, buf, COMPRESS_PIPE_BUFFER_SIZE); + if (n < 0) + return -errno; + if (n == 0) + break; - if (max_bytes != UINT64_MAX && total_out > (size_t) max_bytes) { - log_debug("Decompressed stream longer than %"PRIu64" bytes", max_bytes); - r = -EFBIG; - goto cleanup; - } + total_in += n; - r = maybe_sparse_write(fdt, buf, produced, sparse); + r = decompressor_push(c, buf, n, decompress_stream_write_callback, &userdata); if (r < 0) - goto cleanup; + return r; } if (total_in == 0) - log_debug("LZ4 decompression finished (no input data)"); - else - log_debug("LZ4 decompression finished (%zu -> %zu bytes, %.1f%%)", - total_in, total_out, - (double) total_out / total_in * 100); - r = sparse ? finalize_sparse(fdt) : 0; - cleanup: - munmap(src, st.st_size); - return r; -#else - return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), - "Cannot decompress file. Compiled without LZ4 support."); -#endif -} + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%s decompression failed: no data read", + compression_to_string(type)); -int compress_stream_zstd(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) { - assert(fdf >= 0); - assert(fdt >= 0); + if (userdata.sparse) { + r = finalize_sparse(fdt); + if (r < 0) + return r; + } -#if HAVE_ZSTD - _cleanup_(ZSTD_freeCCtxp) ZSTD_CCtx *cctx = NULL; - _cleanup_free_ void *in_buff = NULL, *out_buff = NULL; - size_t in_allocsize, out_allocsize; - size_t z; - uint64_t left = max_bytes, in_bytes = 0; - int r; + log_debug("%s decompression finished (%" PRIu64 " -> %" PRIu64 " bytes, %.1f%%)", + compression_to_string(type), total_in, userdata.total_out, + (double) userdata.total_out / total_in * 100); - r = dlopen_zstd(); - if (r < 0) - return r; + return 0; +} - /* Create the context and buffers */ - in_allocsize = sym_ZSTD_CStreamInSize(); - out_allocsize = sym_ZSTD_CStreamOutSize(); - in_buff = malloc(in_allocsize); - out_buff = malloc(out_allocsize); - cctx = sym_ZSTD_createCCtx(); - if (!cctx || !out_buff || !in_buff) - return -ENOMEM; +int decompress_stream_by_filename(const char *filename, int fdf, int fdt, uint64_t max_bytes) { + Compression c = compression_from_filename(filename); + if (c == COMPRESSION_NONE) + return -EPROTONOSUPPORT; - z = sym_ZSTD_CCtx_setParameter(cctx, ZSTD_c_checksumFlag, 1); - if (sym_ZSTD_isError(z)) - log_debug("Failed to enable ZSTD checksum, ignoring: %s", sym_ZSTD_getErrorName(z)); + return decompress_stream(c, fdf, fdt, max_bytes); +} - /* This loop read from the input file, compresses that entire chunk, - * and writes all output produced to the output file. - */ - for (;;) { - bool is_last_chunk; - ZSTD_inBuffer input = { - .src = in_buff, - .size = 0, - .pos = 0 - }; - ssize_t red; +/* Push-based streaming compression/decompression context API */ - red = loop_read(fdf, in_buff, in_allocsize, true); - if (red < 0) - return red; - is_last_chunk = red == 0; +Compressor* compressor_free(Compressor *c) { + if (!c) + return NULL; - in_bytes += (size_t) red; - input.size = (size_t) red; + switch (c->type) { - for (bool finished = false; !finished;) { - ZSTD_outBuffer output = { - .dst = out_buff, - .size = out_allocsize, - .pos = 0 - }; - size_t remaining; - ssize_t wrote; - - /* Compress into the output buffer and write all of the - * output to the file so we can reuse the buffer next - * iteration. - */ - remaining = sym_ZSTD_compressStream2( - cctx, &output, &input, - is_last_chunk ? ZSTD_e_end : ZSTD_e_continue); - - if (sym_ZSTD_isError(remaining)) { - log_debug("ZSTD encoder failed: %s", sym_ZSTD_getErrorName(remaining)); - return zstd_ret_to_errno(remaining); - } +#if HAVE_XZ + case COMPRESSION_XZ: + sym_lzma_end(&c->xz); + break; +#endif - if (left < output.pos) - return -EFBIG; +#if HAVE_LZ4 + case COMPRESSION_LZ4: + if (c->encoding) { + sym_LZ4F_freeCompressionContext(c->c_lz4); + c->c_lz4 = NULL; + c->lz4_header = mfree(c->lz4_header); + } else { + sym_LZ4F_freeDecompressionContext(c->d_lz4); + c->d_lz4 = NULL; + } + break; +#endif - wrote = loop_write_full(fdt, output.dst, output.pos, USEC_INFINITY); - if (wrote < 0) - return wrote; +#if HAVE_ZSTD + case COMPRESSION_ZSTD: + if (c->encoding) { + sym_ZSTD_freeCCtx(c->c_zstd); + c->c_zstd = NULL; + } else { + sym_ZSTD_freeDCtx(c->d_zstd); + c->d_zstd = NULL; + } + break; +#endif - left -= output.pos; +#if HAVE_ZLIB + case COMPRESSION_GZIP: + if (c->encoding) + sym_deflateEnd(&c->gzip); + else + sym_inflateEnd(&c->gzip); + break; +#endif - /* If we're on the last chunk we're finished when zstd - * returns 0, which means its consumed all the input AND - * finished the frame. Otherwise, we're finished when - * we've consumed all the input. - */ - finished = is_last_chunk ? (remaining == 0) : (input.pos == input.size); - } +#if HAVE_BZIP2 + case COMPRESSION_BZIP2: + if (c->encoding) + sym_BZ2_bzCompressEnd(&c->bzip2); + else + sym_BZ2_bzDecompressEnd(&c->bzip2); + break; +#endif - /* zstd only returns 0 when the input is completely consumed */ - assert(input.pos == input.size); - if (is_last_chunk) - break; + default: + break; } - if (ret_uncompressed_size) - *ret_uncompressed_size = in_bytes; - - if (in_bytes == 0) - log_debug("ZSTD compression finished (no input data)"); - else - log_debug("ZSTD compression finished (%" PRIu64 " -> %" PRIu64 " bytes, %.1f%%)", - in_bytes, max_bytes - left, (double) (max_bytes - left) / in_bytes * 100); + return mfree(c); +} - return 0; -#else - return -EPROTONOSUPPORT; -#endif +Compression compressor_type(const Compressor *c) { + return c ? c->type : _COMPRESSION_INVALID; } -int decompress_stream_zstd(int fdf, int fdt, uint64_t max_bytes) { - assert(fdf >= 0); - assert(fdt >= 0); +int decompressor_detect(Decompressor **ret, const void *data, size_t size) { + static const uint8_t xz_signature[] = { + 0xfd, '7', 'z', 'X', 'Z', 0x00 + }; + static const uint8_t lz4_signature[] = { + 0x04, 0x22, 0x4d, 0x18 + }; + static const uint8_t zstd_signature[] = { + 0x28, 0xb5, 0x2f, 0xfd + }; + static const uint8_t gzip_signature[] = { + 0x1f, 0x8b + }; + static const uint8_t bzip2_signature[] = { + 'B', 'Z', 'h' + }; -#if HAVE_ZSTD - _cleanup_(ZSTD_freeDCtxp) ZSTD_DCtx *dctx = NULL; - _cleanup_free_ void *in_buff = NULL, *out_buff = NULL; - bool sparse = should_sparse(fdt) > 0; - size_t in_allocsize, out_allocsize; - size_t last_result = 0; - uint64_t left = max_bytes, in_bytes = 0; +#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2 int r; +#endif - r = dlopen_zstd(); - if (r < 0) - return r; - /* Create the context and buffers */ - in_allocsize = sym_ZSTD_DStreamInSize(); - out_allocsize = sym_ZSTD_DStreamOutSize(); - in_buff = malloc(in_allocsize); - out_buff = malloc(out_allocsize); - dctx = sym_ZSTD_createDCtx(); - if (!dctx || !out_buff || !in_buff) - return -ENOMEM; - - /* This loop assumes that the input file is one or more concatenated - * zstd streams. This example won't work if there is trailing non-zstd - * data at the end, but streaming decompression in general handles this - * case. ZSTD_decompressStream() returns 0 exactly when the frame is - * completed, and doesn't consume input after the frame. - */ - for (;;) { - bool has_error = false; - ZSTD_inBuffer input = { - .src = in_buff, - .size = 0, - .pos = 0 - }; - ssize_t red; + assert(ret); - red = loop_read(fdf, in_buff, in_allocsize, true); - if (red < 0) - return red; - if (red == 0) - break; + if (*ret) + return 1; - in_bytes += (size_t) red; - input.size = (size_t) red; - input.pos = 0; + if (size < MAX5(sizeof(xz_signature), + sizeof(gzip_signature), + sizeof(zstd_signature), + sizeof(bzip2_signature), + sizeof(lz4_signature))) + return 0; - /* Given a valid frame, zstd won't consume the last byte of the - * frame until it has flushed all of the decompressed data of - * the frame. So input.pos < input.size means frame is not done - * or there is still output available. - */ - while (input.pos < input.size) { - ZSTD_outBuffer output = { - .dst = out_buff, - .size = out_allocsize, - .pos = 0 - }; - ssize_t wrote; - /* The return code is zero if the frame is complete, but - * there may be multiple frames concatenated together. - * Zstd will automatically reset the context when a - * frame is complete. Still, calling ZSTD_DCtx_reset() - * can be useful to reset the context to a clean state, - * for instance if the last decompression call returned - * an error. - */ - last_result = sym_ZSTD_decompressStream(dctx, &output, &input); - if (sym_ZSTD_isError(last_result)) { - has_error = true; - break; - } + assert(data); - if (left < output.pos) - return -EFBIG; + _cleanup_(compressor_freep) Decompressor *c = new0(Decompressor, 1); + if (!c) + return -ENOMEM; - wrote = maybe_sparse_write(fdt, output.dst, output.pos, sparse); - if (wrote < 0) - return wrote; + c->type = COMPRESSION_NONE; - left -= output.pos; - } - if (has_error) - break; - } +#if HAVE_XZ + if (c->type == COMPRESSION_NONE && memcmp(data, xz_signature, sizeof(xz_signature)) == 0) { + r = dlopen_xz(); + if (r < 0) + return r; - if (in_bytes == 0) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "ZSTD decoder failed: no data read"); + lzma_ret xzr = sym_lzma_stream_decoder(&c->xz, UINT64_MAX, LZMA_TELL_UNSUPPORTED_CHECK | LZMA_CONCATENATED); + if (xzr != LZMA_OK) + return -EIO; - if (last_result != 0) { - /* The last return value from ZSTD_decompressStream did not end - * on a frame, but we reached the end of the file! We assume - * this is an error, and the input was truncated. - */ - log_debug("ZSTD decoder failed: %s", sym_ZSTD_getErrorName(last_result)); - return zstd_ret_to_errno(last_result); + c->type = COMPRESSION_XZ; } - - if (in_bytes == 0) - log_debug("ZSTD decompression finished (no input data)"); - else - log_debug("ZSTD decompression finished (%" PRIu64 " -> %" PRIu64 " bytes, %.1f%%)", - in_bytes, - max_bytes - left, - (double) (max_bytes - left) / in_bytes * 100); - return sparse ? finalize_sparse(fdt) : 0; -#else - return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), - "Cannot decompress file. Compiled without ZSTD support."); #endif -} - -int decompress_stream(const char *filename, int fdf, int fdt, uint64_t max_bytes) { - if (endswith(filename, ".lz4")) - return decompress_stream_lz4(fdf, fdt, max_bytes); - if (endswith(filename, ".xz")) - return decompress_stream_xz(fdf, fdt, max_bytes); - if (endswith(filename, ".zst")) - return decompress_stream_zstd(fdf, fdt, max_bytes); +#if HAVE_LZ4 + if (c->type == COMPRESSION_NONE && memcmp(data, lz4_signature, sizeof(lz4_signature)) == 0) { + r = dlopen_lz4(); + if (r < 0) + return r; - return -EPROTONOSUPPORT; + size_t rc = sym_LZ4F_createDecompressionContext(&c->d_lz4, LZ4F_VERSION); + if (sym_LZ4F_isError(rc)) + return -ENOMEM; + + c->type = COMPRESSION_LZ4; + } +#endif + +#if HAVE_ZSTD + if (c->type == COMPRESSION_NONE && memcmp(data, zstd_signature, sizeof(zstd_signature)) == 0) { + r = dlopen_zstd(); + if (r < 0) + return r; + + c->d_zstd = sym_ZSTD_createDCtx(); + if (!c->d_zstd) + return -ENOMEM; + + c->type = COMPRESSION_ZSTD; + } +#endif + +#if HAVE_ZLIB + if (c->type == COMPRESSION_NONE && memcmp(data, gzip_signature, sizeof(gzip_signature)) == 0) { + r = dlopen_zlib(); + if (r < 0) + return r; + + r = sym_inflateInit2_(&c->gzip, /* windowBits= */ ZLIB_WBITS_GZIP, ZLIB_VERSION, (int) sizeof(c->gzip)); + if (r != Z_OK) + return -EIO; + + c->type = COMPRESSION_GZIP; + } +#endif + +#if HAVE_BZIP2 + if (c->type == COMPRESSION_NONE && memcmp(data, bzip2_signature, sizeof(bzip2_signature)) == 0) { + r = dlopen_bzip2(); + if (r < 0) + return r; + + r = sym_BZ2_bzDecompressInit(&c->bzip2, /* verbosity= */ 0, /* small= */ 0); + if (r != BZ_OK) + return -EIO; + + c->type = COMPRESSION_BZIP2; + } +#endif + + c->encoding = false; + + log_debug("Detected compression type: %s", compression_to_string(c->type)); + *ret = TAKE_PTR(c); + return 1; +} + +int decompressor_force_off(Decompressor **ret) { + assert(ret); + + *ret = compressor_free(*ret); + + Decompressor *c = new0(Decompressor, 1); + if (!c) + return -ENOMEM; + + c->type = COMPRESSION_NONE; + c->encoding = false; + *ret = c; + return 0; +} + +int decompressor_push(Decompressor *c, const void *data, size_t size, DecompressorCallback callback, void *userdata) { +#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2 + _cleanup_free_ uint8_t *buffer = NULL; +#endif + int r; + + assert(c); + assert(callback); + + if (c->encoding) + return -EINVAL; + + if (size == 0) + return 1; + + assert(data); + +#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2 + if (c->type != COMPRESSION_NONE) { + buffer = new(uint8_t, COMPRESS_PIPE_BUFFER_SIZE); + if (!buffer) + return -ENOMEM; + } +#endif + + switch (c->type) { + + case COMPRESSION_NONE: + r = callback(data, size, userdata); + if (r < 0) + return r; + + break; + +#if HAVE_XZ + case COMPRESSION_XZ: + c->xz.next_in = data; + c->xz.avail_in = size; + + while (c->xz.avail_in > 0) { + c->xz.next_out = buffer; + c->xz.avail_out = COMPRESS_PIPE_BUFFER_SIZE; + + lzma_ret lzr = sym_lzma_code(&c->xz, LZMA_RUN); + if (!IN_SET(lzr, LZMA_OK, LZMA_STREAM_END)) + return -EBADMSG; + + if (c->xz.avail_out < COMPRESS_PIPE_BUFFER_SIZE) { + r = callback(buffer, COMPRESS_PIPE_BUFFER_SIZE - c->xz.avail_out, userdata); + if (r < 0) + return r; + } + } + + break; +#endif + +#if HAVE_LZ4 + case COMPRESSION_LZ4: { + const uint8_t *src = data; + size_t src_remaining = size; + + while (src_remaining > 0) { + size_t produced = COMPRESS_PIPE_BUFFER_SIZE; + size_t consumed = src_remaining; + + size_t rc = sym_LZ4F_decompress(c->d_lz4, buffer, &produced, src, &consumed, NULL); + if (sym_LZ4F_isError(rc)) + return -EBADMSG; + + if (consumed == 0 && produced == 0) + break; /* No progress possible with current input */ + + src += consumed; + src_remaining -= consumed; + + if (produced > 0) { + r = callback(buffer, produced, userdata); + if (r < 0) + return r; + } + } + + break; + } +#endif + +#if HAVE_ZSTD + case COMPRESSION_ZSTD: { + ZSTD_inBuffer input = { + .src = (void*) data, + .size = size, + }; + + while (input.pos < input.size) { + ZSTD_outBuffer output = { + .dst = buffer, + .size = COMPRESS_PIPE_BUFFER_SIZE, + }; + + size_t res = sym_ZSTD_decompressStream(c->d_zstd, &output, &input); + if (sym_ZSTD_isError(res)) + return -EBADMSG; + + if (output.pos > 0) { + r = callback(output.dst, output.pos, userdata); + if (r < 0) + return r; + } + } + + break; + } +#endif + +#if HAVE_ZLIB + case COMPRESSION_GZIP: + if (size > UINT_MAX) + return -EFBIG; + + c->gzip.next_in = (void*) data; + c->gzip.avail_in = size; + + while (c->gzip.avail_in > 0) { + c->gzip.next_out = buffer; + c->gzip.avail_out = COMPRESS_PIPE_BUFFER_SIZE; + + int zr = sym_inflate(&c->gzip, Z_NO_FLUSH); + if (!IN_SET(zr, Z_OK, Z_STREAM_END)) + return -EBADMSG; + + if (c->gzip.avail_out < COMPRESS_PIPE_BUFFER_SIZE) { + r = callback(buffer, COMPRESS_PIPE_BUFFER_SIZE - c->gzip.avail_out, userdata); + if (r < 0) + return r; + } + + if (zr == Z_STREAM_END) + break; + } + + break; +#endif + +#if HAVE_BZIP2 + case COMPRESSION_BZIP2: + if (size > UINT_MAX) + return -EFBIG; + + c->bzip2.next_in = (char*) data; + c->bzip2.avail_in = size; + + while (c->bzip2.avail_in > 0) { + c->bzip2.next_out = (char*) buffer; + c->bzip2.avail_out = COMPRESS_PIPE_BUFFER_SIZE; + + int bzr = sym_BZ2_bzDecompress(&c->bzip2); + if (!IN_SET(bzr, BZ_OK, BZ_STREAM_END)) + return -EBADMSG; + + if (c->bzip2.avail_out < COMPRESS_PIPE_BUFFER_SIZE) { + r = callback(buffer, COMPRESS_PIPE_BUFFER_SIZE - c->bzip2.avail_out, userdata); + if (r < 0) + return r; + } + + if (bzr == BZ_STREAM_END) + break; + } + + break; +#endif + + default: + assert_not_reached(); + } + + return 1; +} + +int compressor_new(Compressor **ret, Compression type) { +#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2 + int r; +#endif + + assert(ret); + + _cleanup_(compressor_freep) Compressor *c = new0(Compressor, 1); + if (!c) + return -ENOMEM; + + c->type = _COMPRESSION_INVALID; + /* Set encoding early so that compressor_freep calls the correct cleanup (compression vs + * decompression) if any operation in the switch fails after setting c->type. This is safe + * because _COMPRESSION_INVALID hits the default: break case regardless of the encoding flag. */ + c->encoding = true; + + switch (type) { + +#if HAVE_XZ + case COMPRESSION_XZ: { + r = dlopen_xz(); + if (r < 0) + return r; + + lzma_ret xzr = sym_lzma_easy_encoder(&c->xz, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC64); + if (xzr != LZMA_OK) + return -EIO; + + c->type = COMPRESSION_XZ; + break; + } +#endif + +#if HAVE_LZ4 + case COMPRESSION_LZ4: { + r = dlopen_lz4(); + if (r < 0) + return r; + + size_t rc = sym_LZ4F_createCompressionContext(&c->c_lz4, LZ4F_VERSION); + if (sym_LZ4F_isError(rc)) + return -ENOMEM; + + c->type = COMPRESSION_LZ4; + + /* Generate the frame header and stash it for the first compressor_start call */ + size_t header_bound = sym_LZ4F_compressBound(0, &lz4_preferences); + c->lz4_header = malloc(header_bound); + if (!c->lz4_header) + return -ENOMEM; + + c->lz4_header_size = sym_LZ4F_compressBegin(c->c_lz4, c->lz4_header, header_bound, &lz4_preferences); + if (sym_LZ4F_isError(c->lz4_header_size)) + return -EINVAL; + + break; + } +#endif + +#if HAVE_ZSTD + case COMPRESSION_ZSTD: + r = dlopen_zstd(); + if (r < 0) + return r; + + c->c_zstd = sym_ZSTD_createCCtx(); + if (!c->c_zstd) + return -ENOMEM; + + c->type = COMPRESSION_ZSTD; + + size_t z = sym_ZSTD_CCtx_setParameter(c->c_zstd, ZSTD_c_compressionLevel, ZSTD_CLEVEL_DEFAULT); + if (sym_ZSTD_isError(z)) + return -EIO; + + z = sym_ZSTD_CCtx_setParameter(c->c_zstd, ZSTD_c_checksumFlag, /* enable= */ 1); + if (sym_ZSTD_isError(z)) + log_debug("Failed to enable ZSTD checksum, ignoring: %s", sym_ZSTD_getErrorName(z)); + + break; +#endif + +#if HAVE_ZLIB + case COMPRESSION_GZIP: + r = dlopen_zlib(); + if (r < 0) + return r; + + r = sym_deflateInit2_(&c->gzip, + Z_DEFAULT_COMPRESSION, + /* method= */ Z_DEFLATED, + /* windowBits= */ ZLIB_WBITS_GZIP, + /* memLevel= */ 8, + /* strategy= */ Z_DEFAULT_STRATEGY, + ZLIB_VERSION, (int) sizeof(c->gzip)); + if (r != Z_OK) + return -EIO; + + c->type = COMPRESSION_GZIP; + break; +#endif + +#if HAVE_BZIP2 + case COMPRESSION_BZIP2: + r = dlopen_bzip2(); + if (r < 0) + return r; + + r = sym_BZ2_bzCompressInit(&c->bzip2, /* blockSize100k= */ 9, /* verbosity= */ 0, /* workFactor= */ 0); + if (r != BZ_OK) + return -EIO; + + c->type = COMPRESSION_BZIP2; + break; +#endif + + case COMPRESSION_NONE: + c->type = COMPRESSION_NONE; + break; + + default: + return -EOPNOTSUPP; + } + + *ret = TAKE_PTR(c); + return 0; +} + +#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2 +static int enlarge_buffer(void **buffer, size_t *buffer_size, size_t *buffer_allocated, size_t need) { + assert(buffer); + assert(buffer_size); + assert(buffer_allocated); + + need = MAX3(need, *buffer_size + 1, (size_t) COMPRESS_PIPE_BUFFER_SIZE); + if (*buffer_allocated >= need) + return 0; + + if (!greedy_realloc(buffer, need, 1)) + return -ENOMEM; + + *buffer_allocated = MALLOC_SIZEOF_SAFE(*buffer); + return 1; +} +#endif + +int compressor_start( + Compressor *c, + const void *data, + size_t size, + void **buffer, + size_t *buffer_size, + size_t *buffer_allocated) { + +#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2 + int r; +#endif + + assert(c); + assert(buffer); + assert(buffer_size); + assert(buffer_allocated); + + if (!c->encoding) + return -EINVAL; + + if (size == 0) + return 0; + + assert(data); + + *buffer_size = 0; + + switch (c->type) { + +#if HAVE_XZ + case COMPRESSION_XZ: + + c->xz.next_in = data; + c->xz.avail_in = size; + + while (c->xz.avail_in > 0) { + lzma_ret lzr; + + r = enlarge_buffer(buffer, buffer_size, buffer_allocated, /* need= */ 0); + if (r < 0) + return r; + + c->xz.next_out = (uint8_t*) *buffer + *buffer_size; + c->xz.avail_out = *buffer_allocated - *buffer_size; + + lzr = sym_lzma_code(&c->xz, LZMA_RUN); + if (lzr != LZMA_OK) + return -EIO; + + *buffer_size += (*buffer_allocated - *buffer_size) - c->xz.avail_out; + } + + break; +#endif + +#if HAVE_LZ4 + case COMPRESSION_LZ4: { + /* Prepend any stashed frame header from compressor_new */ + if (c->lz4_header_size > 0) { + r = enlarge_buffer(buffer, buffer_size, buffer_allocated, c->lz4_header_size); + if (r < 0) + return r; + + memcpy(*buffer, c->lz4_header, c->lz4_header_size); + *buffer_size = c->lz4_header_size; + c->lz4_header = mfree(c->lz4_header); + c->lz4_header_size = 0; + } + + size_t bound = sym_LZ4F_compressBound(size, &lz4_preferences); + r = enlarge_buffer(buffer, buffer_size, buffer_allocated, *buffer_size + bound); + if (r < 0) + return r; + + size_t n = sym_LZ4F_compressUpdate(c->c_lz4, + (uint8_t*) *buffer + *buffer_size, + *buffer_allocated - *buffer_size, + data, size, NULL); + if (sym_LZ4F_isError(n)) + return -EIO; + + *buffer_size += n; + break; + } +#endif + +#if HAVE_ZSTD + case COMPRESSION_ZSTD: { + ZSTD_inBuffer input = { + .src = data, + .size = size, + }; + + while (input.pos < input.size) { + r = enlarge_buffer(buffer, buffer_size, buffer_allocated, /* need= */ 0); + if (r < 0) + return r; + + ZSTD_outBuffer output = { + .dst = ((uint8_t *) *buffer + *buffer_size), + .size = *buffer_allocated - *buffer_size, + }; + + size_t res = sym_ZSTD_compressStream2(c->c_zstd, &output, &input, ZSTD_e_continue); + if (sym_ZSTD_isError(res)) + return -EIO; + + *buffer_size += output.pos; + } + + break; + } +#endif + +#if HAVE_ZLIB + case COMPRESSION_GZIP: + if (size > UINT_MAX) + return -EFBIG; + + c->gzip.next_in = (void*) data; + c->gzip.avail_in = size; + + while (c->gzip.avail_in > 0) { + r = enlarge_buffer(buffer, buffer_size, buffer_allocated, /* need= */ 0); + if (r < 0) + return r; + + size_t avail = MIN(*buffer_allocated - *buffer_size, (size_t) UINT_MAX); + c->gzip.next_out = (uint8_t*) *buffer + *buffer_size; + c->gzip.avail_out = avail; + + r = sym_deflate(&c->gzip, Z_NO_FLUSH); + if (r != Z_OK) + return -EIO; + + *buffer_size += avail - c->gzip.avail_out; + } + + break; +#endif + +#if HAVE_BZIP2 + case COMPRESSION_BZIP2: + if (size > UINT_MAX) + return -EFBIG; + + c->bzip2.next_in = (void*) data; + c->bzip2.avail_in = size; + + while (c->bzip2.avail_in > 0) { + r = enlarge_buffer(buffer, buffer_size, buffer_allocated, /* need= */ 0); + if (r < 0) + return r; + + size_t avail = MIN(*buffer_allocated - *buffer_size, (size_t) UINT_MAX); + c->bzip2.next_out = (void*) ((uint8_t*) *buffer + *buffer_size); + c->bzip2.avail_out = avail; + + r = sym_BZ2_bzCompress(&c->bzip2, BZ_RUN); + if (r != BZ_RUN_OK) + return -EIO; + + *buffer_size += avail - c->bzip2.avail_out; + } + + break; +#endif + + case COMPRESSION_NONE: + + if (*buffer_allocated < size) { + void *p; + + p = realloc(*buffer, size); + if (!p) + return -ENOMEM; + + *buffer = p; + *buffer_allocated = size; + } + + memcpy(*buffer, data, size); + *buffer_size = size; + break; + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +int compressor_finish(Compressor *c, void **buffer, size_t *buffer_size, size_t *buffer_allocated) { +#if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2 + int r; +#endif + + assert(c); + assert(buffer); + assert(buffer_size); + assert(buffer_allocated); + + if (!c->encoding) + return -EINVAL; + + *buffer_size = 0; + + switch (c->type) { + +#if HAVE_XZ + case COMPRESSION_XZ: { + lzma_ret lzr; + + c->xz.avail_in = 0; + + do { + r = enlarge_buffer(buffer, buffer_size, buffer_allocated, /* need= */ 0); + if (r < 0) + return r; + + c->xz.next_out = (uint8_t*) *buffer + *buffer_size; + c->xz.avail_out = *buffer_allocated - *buffer_size; + + lzr = sym_lzma_code(&c->xz, LZMA_FINISH); + if (!IN_SET(lzr, LZMA_OK, LZMA_STREAM_END)) + return -EIO; + + *buffer_size += (*buffer_allocated - *buffer_size) - c->xz.avail_out; + } while (lzr != LZMA_STREAM_END); + + break; + } +#endif + +#if HAVE_LZ4 + case COMPRESSION_LZ4: { + size_t bound = sym_LZ4F_compressBound(0, &lz4_preferences); + r = enlarge_buffer(buffer, buffer_size, buffer_allocated, bound); + if (r < 0) + return r; + + size_t n = sym_LZ4F_compressEnd(c->c_lz4, *buffer, *buffer_allocated, NULL); + if (sym_LZ4F_isError(n)) + return -EIO; + + *buffer_size = n; + break; + } +#endif + +#if HAVE_ZSTD + case COMPRESSION_ZSTD: { + ZSTD_inBuffer input = {}; + size_t res; + + do { + r = enlarge_buffer(buffer, buffer_size, buffer_allocated, /* need= */ 0); + if (r < 0) + return r; + + ZSTD_outBuffer output = { + .dst = ((uint8_t *) *buffer + *buffer_size), + .size = *buffer_allocated - *buffer_size, + }; + + res = sym_ZSTD_compressStream2(c->c_zstd, &output, &input, ZSTD_e_end); + if (sym_ZSTD_isError(res)) + return -EIO; + + *buffer_size += output.pos; + } while (res != 0); + + break; + } +#endif + +#if HAVE_ZLIB + case COMPRESSION_GZIP: + c->gzip.avail_in = 0; + + do { + r = enlarge_buffer(buffer, buffer_size, buffer_allocated, /* need= */ 0); + if (r < 0) + return r; + + size_t avail = MIN(*buffer_allocated - *buffer_size, (size_t) UINT_MAX); + c->gzip.next_out = (uint8_t*) *buffer + *buffer_size; + c->gzip.avail_out = avail; + + r = sym_deflate(&c->gzip, Z_FINISH); + if (!IN_SET(r, Z_OK, Z_STREAM_END)) + return -EIO; + + *buffer_size += avail - c->gzip.avail_out; + } while (r != Z_STREAM_END); + + break; +#endif + +#if HAVE_BZIP2 + case COMPRESSION_BZIP2: + c->bzip2.avail_in = 0; + + do { + r = enlarge_buffer(buffer, buffer_size, buffer_allocated, /* need= */ 0); + if (r < 0) + return r; + + size_t avail = MIN(*buffer_allocated - *buffer_size, (size_t) UINT_MAX); + c->bzip2.next_out = (void*) ((uint8_t*) *buffer + *buffer_size); + c->bzip2.avail_out = avail; + + r = sym_BZ2_bzCompress(&c->bzip2, BZ_FINISH); + if (!IN_SET(r, BZ_FINISH_OK, BZ_STREAM_END)) + return -EIO; + + *buffer_size += avail - c->bzip2.avail_out; + } while (r != BZ_STREAM_END); + + break; +#endif + + case COMPRESSION_NONE: + break; + + default: + return -EOPNOTSUPP; + } + + return 0; } diff --git a/src/basic/compress.h b/src/basic/compress.h index 43885a7eedb5a..a5d31b3fc2421 100644 --- a/src/basic/compress.h +++ b/src/basic/compress.h @@ -8,103 +8,81 @@ typedef enum Compression { COMPRESSION_XZ, COMPRESSION_LZ4, COMPRESSION_ZSTD, + COMPRESSION_GZIP, + COMPRESSION_BZIP2, _COMPRESSION_MAX, _COMPRESSION_INVALID = -EINVAL, } Compression; DECLARE_STRING_TABLE_LOOKUP(compression, Compression); -DECLARE_STRING_TABLE_LOOKUP(compression_lowercase, Compression); +DECLARE_STRING_TABLE_LOOKUP(compression_uppercase, Compression); +DECLARE_STRING_TABLE_LOOKUP(compression_extension, Compression); + +/* Try the lowercase string table first, fall back to the uppercase one. Useful for parsing user input + * where both forms (e.g. "xz" and "XZ") have historically been accepted. */ +Compression compression_from_string_harder(const char *s); + +/* Derives the compression type from a filename's extension, defaulting to COMPRESSION_NONE if the + * filename does not carry a recognized compression suffix. */ +Compression compression_from_filename(const char *filename); bool compression_supported(Compression c); -int compress_blob_xz(const void *src, uint64_t src_size, - void *dst, size_t dst_alloc_size, size_t *dst_size, int level); -int compress_blob_lz4(const void *src, uint64_t src_size, - void *dst, size_t dst_alloc_size, size_t *dst_size, int level); -int compress_blob_zstd(const void *src, uint64_t src_size, - void *dst, size_t dst_alloc_size, size_t *dst_size, int level); - -int decompress_blob_xz(const void *src, uint64_t src_size, - void **dst, size_t* dst_size, size_t dst_max); -int decompress_blob_lz4(const void *src, uint64_t src_size, - void **dst, size_t* dst_size, size_t dst_max); -int decompress_blob_zstd(const void *src, uint64_t src_size, - void **dst, size_t* dst_size, size_t dst_max); +/* Buffer size used by streaming compression APIs and pipeline stages that feed into them. Sized to + * match the typical Linux pipe buffer so that pipeline stages don't lose throughput due to small + * intermediate buffers. */ +#define COMPRESS_PIPE_BUFFER_SIZE (128U*1024U) + +/* Compressor / Decompressor — opaque push-based streaming compression context */ + +typedef struct Compressor Compressor; +typedef Compressor Decompressor; + +typedef int (*DecompressorCallback)(const void *data, size_t size, void *userdata); + +Compressor* compressor_free(Compressor *c); +DEFINE_TRIVIAL_CLEANUP_FUNC(Compressor*, compressor_free); + +int compressor_new(Compressor **ret, Compression type); +int compressor_start(Compressor *c, const void *data, size_t size, void **buffer, size_t *buffer_size, size_t *buffer_allocated); +int compressor_finish(Compressor *c, void **buffer, size_t *buffer_size, size_t *buffer_allocated); + +int decompressor_detect(Decompressor **ret, const void *data, size_t size); +int decompressor_force_off(Decompressor **ret); +int decompressor_push(Decompressor *c, const void *data, size_t size, DecompressorCallback callback, void *userdata); + +Compression compressor_type(const Compressor *c); + +/* Blob compression/decompression */ + +int compress_blob(Compression compression, + const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size, int level); int decompress_blob(Compression compression, const void *src, uint64_t src_size, - void **dst, size_t* dst_size, size_t dst_max); - -int decompress_startswith_xz(const void *src, uint64_t src_size, - void **buffer, - const void *prefix, size_t prefix_len, - uint8_t extra); -int decompress_startswith_lz4(const void *src, uint64_t src_size, - void **buffer, - const void *prefix, size_t prefix_len, - uint8_t extra); -int decompress_startswith_zstd(const void *src, uint64_t src_size, - void **buffer, - const void *prefix, size_t prefix_len, - uint8_t extra); + void **dst, size_t *dst_size, size_t dst_max); + +int decompress_zlib_raw(const void *src, uint64_t src_size, + void *dst, size_t dst_size, int wbits); + int decompress_startswith(Compression compression, const void *src, uint64_t src_size, void **buffer, const void *prefix, size_t prefix_len, uint8_t extra); -int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size); -int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size); -int compress_stream_zstd(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size); +/* Stream compression/decompression (fd-to-fd) */ -int decompress_stream_xz(int fdf, int fdt, uint64_t max_bytes); -int decompress_stream_lz4(int fdf, int fdt, uint64_t max_bytes); -int decompress_stream_zstd(int fdf, int fdt, uint64_t max_bytes); +int compress_stream(Compression type, int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size); +int decompress_stream(Compression type, int fdf, int fdt, uint64_t max_bytes); +int decompress_stream_by_filename(const char *filename, int fdf, int fdt, uint64_t max_bytes); +int dlopen_xz(void); int dlopen_lz4(void); int dlopen_zstd(void); -int dlopen_lzma(void); - -static inline int compress_blob( - Compression compression, - const void *src, uint64_t src_size, - void *dst, size_t dst_alloc_size, size_t *dst_size, int level) { - - switch (compression) { - case COMPRESSION_ZSTD: - return compress_blob_zstd(src, src_size, dst, dst_alloc_size, dst_size, level); - case COMPRESSION_LZ4: - return compress_blob_lz4(src, src_size, dst, dst_alloc_size, dst_size, level); - case COMPRESSION_XZ: - return compress_blob_xz(src, src_size, dst, dst_alloc_size, dst_size, level); - default: - return -EOPNOTSUPP; - } -} - -static inline int compress_stream(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) { - switch (DEFAULT_COMPRESSION) { - case COMPRESSION_ZSTD: - return compress_stream_zstd(fdf, fdt, max_bytes, ret_uncompressed_size); - case COMPRESSION_LZ4: - return compress_stream_lz4(fdf, fdt, max_bytes, ret_uncompressed_size); - case COMPRESSION_XZ: - return compress_stream_xz(fdf, fdt, max_bytes, ret_uncompressed_size); - default: - return -EOPNOTSUPP; - } -} +int dlopen_zlib(void); +int dlopen_bzip2(void); static inline const char* default_compression_extension(void) { - switch (DEFAULT_COMPRESSION) { - case COMPRESSION_ZSTD: - return ".zst"; - case COMPRESSION_LZ4: - return ".lz4"; - case COMPRESSION_XZ: - return ".xz"; - default: - return ""; - } + return compression_extension_to_string(DEFAULT_COMPRESSION) ?: ""; } - -int decompress_stream(const char *filename, int fdf, int fdt, uint64_t max_bytes); diff --git a/src/basic/meson.build b/src/basic/meson.build index 775dc1fa3d595..f847b175b61f0 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -211,12 +211,14 @@ libbasic_static = static_library( fundamental_sources, include_directories : basic_includes, implicit_include_directories : false, - dependencies : [libdl, + dependencies : [libbzip2_cflags, + libdl, libgcrypt_cflags, liblz4_cflags, libm, librt, libxz_cflags, + libz_cflags, libzstd_cflags, threads, userspace], diff --git a/src/boot/test-bcd.c b/src/boot/test-bcd.c index 0924c94fa07f9..27102c236b8ab 100644 --- a/src/boot/test-bcd.c +++ b/src/boot/test-bcd.c @@ -17,7 +17,7 @@ static void load_bcd(const char *path, void **ret_bcd, size_t *ret_bcd_len) { assert_se(get_testdata_dir(path, &fn) >= 0); assert_se(read_full_file_full(AT_FDCWD, fn, UINT64_MAX, SIZE_MAX, 0, NULL, &compressed, &len) >= 0); - assert_se(decompress_blob_zstd(compressed, len, ret_bcd, ret_bcd_len, SIZE_MAX) >= 0); + assert_se(decompress_blob(COMPRESSION_ZSTD, compressed, len, ret_bcd, ret_bcd_len, SIZE_MAX) >= 0); } static void test_get_bcd_title_one( diff --git a/src/coredump/coredump-submit.c b/src/coredump/coredump-submit.c index 6cfbda0f46b59..6ce03cdec0770 100644 --- a/src/coredump/coredump-submit.c +++ b/src/coredump/coredump-submit.c @@ -373,7 +373,7 @@ static int save_external_coredump( if (fd_compressed < 0) return log_error_errno(fd_compressed, "Failed to create temporary file for coredump %s: %m", fn_compressed); - r = compress_stream(fd, fd_compressed, max_size, &uncompressed_size); + r = compress_stream(DEFAULT_COMPRESSION, fd, fd_compressed, max_size, &uncompressed_size); if (r < 0) return log_error_errno(r, "Failed to compress %s: %m", coredump_tmpfile_name(tmp_compressed)); @@ -386,7 +386,7 @@ static int save_external_coredump( tmp = unlink_and_free(tmp); fd = safe_close(fd); - r = compress_stream(context->input_fd, fd_compressed, max_size, &partial_uncompressed_size); + r = compress_stream(DEFAULT_COMPRESSION, context->input_fd, fd_compressed, max_size, &partial_uncompressed_size); if (r < 0) return log_error_errno(r, "Failed to compress %s: %m", coredump_tmpfile_name(tmp_compressed)); uncompressed_size += partial_uncompressed_size; diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index fe0af59992efa..b6ca0f8b7dd23 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -1103,7 +1103,7 @@ static int save_core(sd_journal *j, FILE *file, char **path, bool *unlink_temp) goto error; } - r = decompress_stream(filename, fdf, fd, -1); + r = decompress_stream_by_filename(filename, fdf, fd, -1); if (r < 0) { log_error_errno(r, "Failed to decompress %s: %m", filename); goto error; diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro-fundamental.h index f595be2cc5dd2..a5300d591ae20 100644 --- a/src/fundamental/macro-fundamental.h +++ b/src/fundamental/macro-fundamental.h @@ -227,6 +227,12 @@ assert_cc(sizeof(long long) == sizeof(intmax_t)); MAX(_d, a); \ }) +#define MAX5(x, y, z, a, b) \ + ({ \ + const typeof(x) _e = MAX4(x, y, z, a); \ + MAX(_e, b); \ + }) + #undef MIN #define MIN(a, b) __MIN(UNIQ, (a), UNIQ, (b)) #define __MIN(aq, a, bq, b) \ diff --git a/src/import/export-raw.c b/src/import/export-raw.c index 767e10f3ce318..31524b15747c3 100644 --- a/src/import/export-raw.c +++ b/src/import/export-raw.c @@ -11,7 +11,6 @@ #include "fd-util.h" #include "format-util.h" #include "fs-util.h" -#include "import-common.h" #include "log.h" #include "pretty-print.h" #include "ratelimit.h" @@ -32,7 +31,7 @@ typedef struct RawExport { int input_fd; int output_fd; - ImportCompress compress; + Compressor *compress; sd_event_source *output_event_source; @@ -59,7 +58,7 @@ RawExport *raw_export_unref(RawExport *e) { sd_event_source_unref(e->output_event_source); - import_compress_free(&e->compress); + e->compress = compressor_free(e->compress); sd_event_unref(e->event); @@ -143,7 +142,7 @@ static int raw_export_process(RawExport *e) { assert(e); - if (!e->tried_reflink && e->compress.type == IMPORT_COMPRESS_UNCOMPRESSED) { + if (!e->tried_reflink && compressor_type(e->compress) == COMPRESSION_NONE) { /* If we shall take an uncompressed snapshot we can * reflink source to destination directly. Let's see @@ -158,9 +157,9 @@ static int raw_export_process(RawExport *e) { e->tried_reflink = true; } - if (!e->tried_sendfile && e->compress.type == IMPORT_COMPRESS_UNCOMPRESSED) { + if (!e->tried_sendfile && compressor_type(e->compress) == COMPRESSION_NONE) { - l = sendfile(e->output_fd, e->input_fd, NULL, IMPORT_BUFFER_SIZE); + l = sendfile(e->output_fd, e->input_fd, NULL, COMPRESS_PIPE_BUFFER_SIZE); if (l < 0) { if (errno == EAGAIN) return 0; @@ -180,7 +179,7 @@ static int raw_export_process(RawExport *e) { } while (e->buffer_size <= 0) { - uint8_t input[IMPORT_BUFFER_SIZE]; + uint8_t input[COMPRESS_PIPE_BUFFER_SIZE]; if (e->eof) { r = 0; @@ -195,10 +194,10 @@ static int raw_export_process(RawExport *e) { if (l == 0) { e->eof = true; - r = import_compress_finish(&e->compress, &e->buffer, &e->buffer_size, &e->buffer_allocated); + r = compressor_finish(e->compress, &e->buffer, &e->buffer_size, &e->buffer_allocated); } else { e->written_uncompressed += l; - r = import_compress(&e->compress, input, l, &e->buffer, &e->buffer_size, &e->buffer_allocated); + r = compressor_start(e->compress, input, l, &e->buffer, &e->buffer_size, &e->buffer_allocated); } if (r < 0) { r = log_error_errno(r, "Failed to encode: %m"); @@ -280,15 +279,15 @@ static int reflink_snapshot(int fd, const char *path) { return new_fd; } -int raw_export_start(RawExport *e, const char *path, int fd, ImportCompressType compress) { +int raw_export_start(RawExport *e, const char *path, int fd, Compression compress) { _cleanup_close_ int sfd = -EBADF, tfd = -EBADF; int r; assert(e); assert(path); assert(fd >= 0); - assert(compress < _IMPORT_COMPRESS_TYPE_MAX); - assert(compress != IMPORT_COMPRESS_UNKNOWN); + assert(compress >= 0); + assert(compress < _COMPRESSION_MAX); if (e->output_fd >= 0) return -EBUSY; @@ -318,7 +317,7 @@ int raw_export_start(RawExport *e, const char *path, int fd, ImportCompressType else e->input_fd = TAKE_FD(sfd); - r = import_compress_init(&e->compress, compress); + r = compressor_new(&e->compress, compress); if (r < 0) return r; diff --git a/src/import/export-raw.h b/src/import/export-raw.h index 664bdfc8e7e50..f1f17c2c6d896 100644 --- a/src/import/export-raw.h +++ b/src/import/export-raw.h @@ -1,8 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "compress.h" #include "shared-forward.h" -#include "import-compress.h" typedef struct RawExport RawExport; @@ -13,4 +13,4 @@ RawExport* raw_export_unref(RawExport *e); DEFINE_TRIVIAL_CLEANUP_FUNC(RawExport*, raw_export_unref); -int raw_export_start(RawExport *e, const char *path, int fd, ImportCompressType compress); +int raw_export_start(RawExport *e, const char *path, int fd, Compression compress); diff --git a/src/import/export-tar.c b/src/import/export-tar.c index 22f731de5742a..93d163adda63d 100644 --- a/src/import/export-tar.c +++ b/src/import/export-tar.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include "sd-daemon.h" #include "sd-event.h" @@ -38,7 +39,7 @@ typedef struct TarExport { int tree_fd; /* directory fd of the tree to set up */ int userns_fd; - ImportCompress compress; + Compressor *compress; sd_event_source *output_event_source; @@ -74,7 +75,7 @@ TarExport *tar_export_unref(TarExport *e) { free(e->temp_path); } - import_compress_free(&e->compress); + e->compress = compressor_free(e->compress); sd_event_unref(e->event); @@ -188,9 +189,9 @@ static int tar_export_process(TarExport *e) { assert(e); - if (!e->tried_splice && e->compress.type == IMPORT_COMPRESS_UNCOMPRESSED) { + if (!e->tried_splice && compressor_type(e->compress) == COMPRESSION_NONE) { - l = splice(e->tar_fd, NULL, e->output_fd, NULL, IMPORT_BUFFER_SIZE, 0); + l = splice(e->tar_fd, NULL, e->output_fd, NULL, COMPRESS_PIPE_BUFFER_SIZE, 0); if (l < 0) { if (errno == EAGAIN) return 0; @@ -210,7 +211,7 @@ static int tar_export_process(TarExport *e) { } while (e->buffer_size <= 0) { - uint8_t input[IMPORT_BUFFER_SIZE]; + uint8_t input[COMPRESS_PIPE_BUFFER_SIZE]; if (e->eof) { r = tar_export_finish(e); @@ -225,10 +226,10 @@ static int tar_export_process(TarExport *e) { if (l == 0) { e->eof = true; - r = import_compress_finish(&e->compress, &e->buffer, &e->buffer_size, &e->buffer_allocated); + r = compressor_finish(e->compress, &e->buffer, &e->buffer_size, &e->buffer_allocated); } else { e->written_uncompressed += l; - r = import_compress(&e->compress, input, l, &e->buffer, &e->buffer_size, &e->buffer_allocated); + r = compressor_start(e->compress, input, l, &e->buffer, &e->buffer_size, &e->buffer_allocated); } if (r < 0) { r = log_error_errno(r, "Failed to encode: %m"); @@ -282,7 +283,7 @@ int tar_export_start( TarExport *e, const char *path, int fd, - ImportCompressType compress, + Compression compress, ImportFlags flags) { _cleanup_close_ int sfd = -EBADF; @@ -291,8 +292,8 @@ int tar_export_start( assert(e); assert(path); assert(fd >= 0); - assert(compress < _IMPORT_COMPRESS_TYPE_MAX); - assert(compress != IMPORT_COMPRESS_UNKNOWN); + assert(compress >= 0); + assert(compress < _COMPRESSION_MAX); if (e->output_fd >= 0) return -EBUSY; @@ -336,7 +337,7 @@ int tar_export_start( } } - r = import_compress_init(&e->compress, compress); + r = compressor_new(&e->compress, compress); if (r < 0) return r; diff --git a/src/import/export-tar.h b/src/import/export-tar.h index c5006d42319b1..be039b6b41a56 100644 --- a/src/import/export-tar.h +++ b/src/import/export-tar.h @@ -1,8 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "compress.h" #include "import-common.h" -#include "import-compress.h" #include "shared-forward.h" typedef struct TarExport TarExport; @@ -14,4 +14,4 @@ TarExport* tar_export_unref(TarExport *e); DEFINE_TRIVIAL_CLEANUP_FUNC(TarExport*, tar_export_unref); -int tar_export_start(TarExport *e, const char *path, int fd, ImportCompressType compress, ImportFlags flags); +int tar_export_start(TarExport *e, const char *path, int fd, Compression compress, ImportFlags flags); diff --git a/src/import/export.c b/src/import/export.c index 6612a1e70afed..389d5428bf2fc 100644 --- a/src/import/export.c +++ b/src/import/export.c @@ -2,6 +2,7 @@ #include #include +#include #include "sd-event.h" @@ -22,30 +23,15 @@ #include "verbs.h" static ImportFlags arg_import_flags = 0; -static ImportCompressType arg_compress = IMPORT_COMPRESS_UNKNOWN; +static Compression arg_compress = _COMPRESSION_INVALID; static ImageClass arg_class = IMAGE_MACHINE; static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID; static void determine_compression_from_filename(const char *p) { - - if (arg_compress != IMPORT_COMPRESS_UNKNOWN) - return; - - if (!p) { - arg_compress = IMPORT_COMPRESS_UNCOMPRESSED; + if (arg_compress >= 0) return; - } - if (endswith(p, ".xz")) - arg_compress = IMPORT_COMPRESS_XZ; - else if (endswith(p, ".gz")) - arg_compress = IMPORT_COMPRESS_GZIP; - else if (endswith(p, ".bz2")) - arg_compress = IMPORT_COMPRESS_BZIP2; - else if (endswith(p, ".zst")) - arg_compress = IMPORT_COMPRESS_ZSTD; - else - arg_compress = IMPORT_COMPRESS_UNCOMPRESSED; + arg_compress = p ? compression_from_filename(p) : COMPRESSION_NONE; } static void on_tar_finished(TarExport *export, int error, void *userdata) { @@ -91,7 +77,7 @@ static int verb_export_tar(int argc, char *argv[], uintptr_t _data, void *userda fd = open_fd; - log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, path, import_compress_type_to_string(arg_compress)); + log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, path, compression_to_string(arg_compress)); } else { _cleanup_free_ char *pretty = NULL; @@ -101,7 +87,7 @@ static int verb_export_tar(int argc, char *argv[], uintptr_t _data, void *userda fd = STDOUT_FILENO; (void) fd_get_path(fd, &pretty); - log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, strna(pretty), import_compress_type_to_string(arg_compress)); + log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, strna(pretty), compression_to_string(arg_compress)); } r = import_allocate_event_with_signals(&event); @@ -172,14 +158,14 @@ static int verb_export_raw(int argc, char *argv[], uintptr_t _data, void *userda fd = open_fd; - log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, path, import_compress_type_to_string(arg_compress)); + log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, path, compression_to_string(arg_compress)); } else { _cleanup_free_ char *pretty = NULL; fd = STDOUT_FILENO; (void) fd_get_path(fd, &pretty); - log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, strna(pretty), import_compress_type_to_string(arg_compress)); + log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, strna(pretty), compression_to_string(arg_compress)); } r = import_allocate_event_with_signals(&event); @@ -265,8 +251,8 @@ static int parse_argv(int argc, char *argv[]) { return version(); case ARG_FORMAT: - arg_compress = import_compress_type_from_string(optarg); - if (arg_compress < 0 || arg_compress == IMPORT_COMPRESS_UNKNOWN) + arg_compress = compression_from_string_harder(optarg); + if (arg_compress < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown format: %s", optarg); break; diff --git a/src/import/import-common.c b/src/import/import-common.c index 0a5144f94ecd6..948cd82988ab2 100644 --- a/src/import/import-common.c +++ b/src/import/import-common.c @@ -7,6 +7,7 @@ #include "sd-event.h" #include "capability-util.h" +#include "compress.h" #include "dirent-util.h" #include "dissect-image.h" #include "fd-util.h" @@ -43,7 +44,7 @@ int import_fork_tar_x(int tree_fd, int userns_fd, PidRef *ret_pid) { if (pipe2(pipefd, O_CLOEXEC) < 0) return log_error_errno(errno, "Failed to create pipe for tar: %m"); - (void) fcntl(pipefd[0], F_SETPIPE_SZ, IMPORT_BUFFER_SIZE); + (void) fcntl(pipefd[0], F_SETPIPE_SZ, COMPRESS_PIPE_BUFFER_SIZE); r = pidref_safe_fork_full( "tar-x", @@ -110,7 +111,7 @@ int import_fork_tar_c(int tree_fd, int userns_fd, PidRef *ret_pid) { if (pipe2(pipefd, O_CLOEXEC) < 0) return log_error_errno(errno, "Failed to create pipe for tar: %m"); - (void) fcntl(pipefd[0], F_SETPIPE_SZ, IMPORT_BUFFER_SIZE); + (void) fcntl(pipefd[0], F_SETPIPE_SZ, COMPRESS_PIPE_BUFFER_SIZE); r = pidref_safe_fork_full( "tar-c", diff --git a/src/import/import-common.h b/src/import/import-common.h index 6b10f8c29db87..69bdb335285df 100644 --- a/src/import/import-common.h +++ b/src/import/import-common.h @@ -49,5 +49,3 @@ int import_allocate_event_with_signals(sd_event **ret); int import_make_foreign_userns(int *userns_fd); int import_remove_tree(const char *path, int *userns_fd, ImportFlags flags); - -#define IMPORT_BUFFER_SIZE (128U*1024U) diff --git a/src/import/import-compress.c b/src/import/import-compress.c deleted file mode 100644 index aca4041f2a416..0000000000000 --- a/src/import/import-compress.c +++ /dev/null @@ -1,611 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include - -#include "import-common.h" -#include "import-compress.h" -#include "log.h" -#include "string-table.h" - -void import_compress_free(ImportCompress *c) { - assert(c); - - if (c->type == IMPORT_COMPRESS_XZ) - lzma_end(&c->xz); - else if (c->type == IMPORT_COMPRESS_GZIP) { - if (c->encoding) - deflateEnd(&c->gzip); - else - inflateEnd(&c->gzip); -#if HAVE_BZIP2 - } else if (c->type == IMPORT_COMPRESS_BZIP2) { - if (c->encoding) - BZ2_bzCompressEnd(&c->bzip2); - else - BZ2_bzDecompressEnd(&c->bzip2); -#endif -#if HAVE_ZSTD - } else if (c->type == IMPORT_COMPRESS_ZSTD) { - if (c->encoding) { - ZSTD_freeCCtx(c->c_zstd); - c->c_zstd = NULL; - } else { - ZSTD_freeDCtx(c->d_zstd); - c->d_zstd = NULL; - } -#endif - } - - c->type = IMPORT_COMPRESS_UNKNOWN; -} - -int import_uncompress_detect(ImportCompress *c, const void *data, size_t size) { - static const uint8_t xz_signature[] = { - 0xfd, '7', 'z', 'X', 'Z', 0x00 - }; - static const uint8_t gzip_signature[] = { - 0x1f, 0x8b - }; - static const uint8_t bzip2_signature[] = { - 'B', 'Z', 'h' - }; - static const uint8_t zstd_signature[] = { - 0x28, 0xb5, 0x2f, 0xfd - }; - - int r; - - assert(c); - - if (c->type != IMPORT_COMPRESS_UNKNOWN) - return 1; - - if (size < MAX4(sizeof(xz_signature), - sizeof(gzip_signature), - sizeof(zstd_signature), - sizeof(bzip2_signature))) - return 0; - - assert(data); - - if (memcmp(data, xz_signature, sizeof(xz_signature)) == 0) { - lzma_ret xzr; - - xzr = lzma_stream_decoder(&c->xz, UINT64_MAX, LZMA_TELL_UNSUPPORTED_CHECK | LZMA_CONCATENATED); - if (xzr != LZMA_OK) - return -EIO; - - c->type = IMPORT_COMPRESS_XZ; - - } else if (memcmp(data, gzip_signature, sizeof(gzip_signature)) == 0) { - r = inflateInit2(&c->gzip, 15+16); - if (r != Z_OK) - return -EIO; - - c->type = IMPORT_COMPRESS_GZIP; - -#if HAVE_BZIP2 - } else if (memcmp(data, bzip2_signature, sizeof(bzip2_signature)) == 0) { - r = BZ2_bzDecompressInit(&c->bzip2, 0, 0); - if (r != BZ_OK) - return -EIO; - - c->type = IMPORT_COMPRESS_BZIP2; -#endif -#if HAVE_ZSTD - } else if (memcmp(data, zstd_signature, sizeof(zstd_signature)) == 0) { - c->d_zstd = ZSTD_createDCtx(); - if (!c->d_zstd) - return -ENOMEM; - - c->type = IMPORT_COMPRESS_ZSTD; -#endif - } else - c->type = IMPORT_COMPRESS_UNCOMPRESSED; - - c->encoding = false; - - log_debug("Detected compression type: %s", import_compress_type_to_string(c->type)); - return 1; -} - -void import_uncompress_force_off(ImportCompress *c) { - assert(c); - - c->type = IMPORT_COMPRESS_UNCOMPRESSED; - c->encoding = false; -} - -int import_uncompress(ImportCompress *c, const void *data, size_t size, ImportCompressCallback callback, void *userdata) { - int r; - - assert(c); - assert(callback); - - r = import_uncompress_detect(c, data, size); - if (r <= 0) - return r; - - if (c->encoding) - return -EINVAL; - - if (size <= 0) - return 1; - - assert(data); - - switch (c->type) { - - case IMPORT_COMPRESS_UNCOMPRESSED: - r = callback(data, size, userdata); - if (r < 0) - return r; - - break; - - case IMPORT_COMPRESS_XZ: - c->xz.next_in = data; - c->xz.avail_in = size; - - while (c->xz.avail_in > 0) { - uint8_t buffer[IMPORT_BUFFER_SIZE]; - lzma_ret lzr; - - c->xz.next_out = buffer; - c->xz.avail_out = sizeof(buffer); - - lzr = lzma_code(&c->xz, LZMA_RUN); - if (!IN_SET(lzr, LZMA_OK, LZMA_STREAM_END)) - return -EIO; - - if (c->xz.avail_out < sizeof(buffer)) { - r = callback(buffer, sizeof(buffer) - c->xz.avail_out, userdata); - if (r < 0) - return r; - } - } - - break; - - case IMPORT_COMPRESS_GZIP: - c->gzip.next_in = (void*) data; - c->gzip.avail_in = size; - - while (c->gzip.avail_in > 0) { - uint8_t buffer[IMPORT_BUFFER_SIZE]; - - c->gzip.next_out = buffer; - c->gzip.avail_out = sizeof(buffer); - - r = inflate(&c->gzip, Z_NO_FLUSH); - if (!IN_SET(r, Z_OK, Z_STREAM_END)) - return -EIO; - - if (c->gzip.avail_out < sizeof(buffer)) { - r = callback(buffer, sizeof(buffer) - c->gzip.avail_out, userdata); - if (r < 0) - return r; - } - } - - break; - -#if HAVE_BZIP2 - case IMPORT_COMPRESS_BZIP2: - c->bzip2.next_in = (void*) data; - c->bzip2.avail_in = size; - - while (c->bzip2.avail_in > 0) { - uint8_t buffer[IMPORT_BUFFER_SIZE]; - - c->bzip2.next_out = (char*) buffer; - c->bzip2.avail_out = sizeof(buffer); - - r = BZ2_bzDecompress(&c->bzip2); - if (!IN_SET(r, BZ_OK, BZ_STREAM_END)) - return -EIO; - - if (c->bzip2.avail_out < sizeof(buffer)) { - r = callback(buffer, sizeof(buffer) - c->bzip2.avail_out, userdata); - if (r < 0) - return r; - } - } - - break; -#endif -#if HAVE_ZSTD - case IMPORT_COMPRESS_ZSTD: { - ZSTD_inBuffer input = { - .src = (void*) data, - .size = size, - }; - - while (input.pos < input.size) { - uint8_t buffer[IMPORT_BUFFER_SIZE]; - ZSTD_outBuffer output = { - .dst = buffer, - .size = sizeof(buffer), - }; - size_t res; - - res = ZSTD_decompressStream(c->d_zstd, &output, &input); - if (ZSTD_isError(res)) - return -EIO; - - if (output.pos > 0) { - r = callback(output.dst, output.pos, userdata); - if (r < 0) - return r; - } - } - - break; - } -#endif - - default: - assert_not_reached(); - } - - return 1; -} - -int import_compress_init(ImportCompress *c, ImportCompressType t) { - int r; - - assert(c); - - switch (t) { - - case IMPORT_COMPRESS_XZ: { - lzma_ret xzr; - - xzr = lzma_easy_encoder(&c->xz, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC64); - if (xzr != LZMA_OK) - return -EIO; - - c->type = IMPORT_COMPRESS_XZ; - break; - } - - case IMPORT_COMPRESS_GZIP: - r = deflateInit2(&c->gzip, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY); - if (r != Z_OK) - return -EIO; - - c->type = IMPORT_COMPRESS_GZIP; - break; - -#if HAVE_BZIP2 - case IMPORT_COMPRESS_BZIP2: - r = BZ2_bzCompressInit(&c->bzip2, 9, 0, 0); - if (r != BZ_OK) - return -EIO; - - c->type = IMPORT_COMPRESS_BZIP2; - break; -#endif - -#if HAVE_ZSTD - case IMPORT_COMPRESS_ZSTD: - c->c_zstd = ZSTD_createCCtx(); - if (!c->c_zstd) - return -ENOMEM; - - r = ZSTD_CCtx_setParameter(c->c_zstd, ZSTD_c_compressionLevel, ZSTD_CLEVEL_DEFAULT); - if (ZSTD_isError(r)) - return -EIO; - - c->type = IMPORT_COMPRESS_ZSTD; - break; -#endif - - case IMPORT_COMPRESS_UNCOMPRESSED: - c->type = IMPORT_COMPRESS_UNCOMPRESSED; - break; - - default: - return -EOPNOTSUPP; - } - - c->encoding = true; - return 0; -} - -static int enlarge_buffer(void **buffer, size_t *buffer_size, size_t *buffer_allocated) { - size_t l; - void *p; - - assert(buffer); - assert(buffer_size); - assert(buffer_allocated); - - if (*buffer_allocated > *buffer_size) - return 0; - - l = MAX(IMPORT_BUFFER_SIZE, (*buffer_size * 2)); - p = realloc(*buffer, l); - if (!p) - return -ENOMEM; - - *buffer = p; - *buffer_allocated = l; - - return 1; -} - -int import_compress(ImportCompress *c, const void *data, size_t size, void **buffer, size_t *buffer_size, size_t *buffer_allocated) { - int r; - - assert(c); - assert(buffer); - assert(buffer_size); - assert(buffer_allocated); - - if (!c->encoding) - return -EINVAL; - - if (size <= 0) - return 0; - - assert(data); - - *buffer_size = 0; - - switch (c->type) { - - case IMPORT_COMPRESS_XZ: - - c->xz.next_in = data; - c->xz.avail_in = size; - - while (c->xz.avail_in > 0) { - lzma_ret lzr; - - r = enlarge_buffer(buffer, buffer_size, buffer_allocated); - if (r < 0) - return r; - - c->xz.next_out = (uint8_t*) *buffer + *buffer_size; - c->xz.avail_out = *buffer_allocated - *buffer_size; - - lzr = lzma_code(&c->xz, LZMA_RUN); - if (lzr != LZMA_OK) - return -EIO; - - *buffer_size += (*buffer_allocated - *buffer_size) - c->xz.avail_out; - } - - break; - - case IMPORT_COMPRESS_GZIP: - - c->gzip.next_in = (void*) data; - c->gzip.avail_in = size; - - while (c->gzip.avail_in > 0) { - r = enlarge_buffer(buffer, buffer_size, buffer_allocated); - if (r < 0) - return r; - - c->gzip.next_out = (uint8_t*) *buffer + *buffer_size; - c->gzip.avail_out = *buffer_allocated - *buffer_size; - - r = deflate(&c->gzip, Z_NO_FLUSH); - if (r != Z_OK) - return -EIO; - - *buffer_size += (*buffer_allocated - *buffer_size) - c->gzip.avail_out; - } - - break; - -#if HAVE_BZIP2 - case IMPORT_COMPRESS_BZIP2: - - c->bzip2.next_in = (void*) data; - c->bzip2.avail_in = size; - - while (c->bzip2.avail_in > 0) { - r = enlarge_buffer(buffer, buffer_size, buffer_allocated); - if (r < 0) - return r; - - c->bzip2.next_out = (void*) ((uint8_t*) *buffer + *buffer_size); - c->bzip2.avail_out = *buffer_allocated - *buffer_size; - - r = BZ2_bzCompress(&c->bzip2, BZ_RUN); - if (r != BZ_RUN_OK) - return -EIO; - - *buffer_size += (*buffer_allocated - *buffer_size) - c->bzip2.avail_out; - } - - break; -#endif - -#if HAVE_ZSTD - case IMPORT_COMPRESS_ZSTD: { - ZSTD_inBuffer input = { - .src = data, - .size = size, - }; - - while (input.pos < input.size) { - r = enlarge_buffer(buffer, buffer_size, buffer_allocated); - if (r < 0) - return r; - - ZSTD_outBuffer output = { - .dst = ((uint8_t *) *buffer + *buffer_size), - .size = *buffer_allocated - *buffer_size, - }; - size_t res; - - res = ZSTD_compressStream2(c->c_zstd, &output, &input, ZSTD_e_continue); - if (ZSTD_isError(res)) - return -EIO; - - *buffer_size += output.pos; - } - - break; - } -#endif - - case IMPORT_COMPRESS_UNCOMPRESSED: - - if (*buffer_allocated < size) { - void *p; - - p = realloc(*buffer, size); - if (!p) - return -ENOMEM; - - *buffer = p; - *buffer_allocated = size; - } - - memcpy(*buffer, data, size); - *buffer_size = size; - break; - - default: - return -EOPNOTSUPP; - } - - return 0; -} - -int import_compress_finish(ImportCompress *c, void **buffer, size_t *buffer_size, size_t *buffer_allocated) { - int r; - - assert(c); - assert(buffer); - assert(buffer_size); - assert(buffer_allocated); - - if (!c->encoding) - return -EINVAL; - - *buffer_size = 0; - - switch (c->type) { - - case IMPORT_COMPRESS_XZ: { - lzma_ret lzr; - - c->xz.avail_in = 0; - - do { - r = enlarge_buffer(buffer, buffer_size, buffer_allocated); - if (r < 0) - return r; - - c->xz.next_out = (uint8_t*) *buffer + *buffer_size; - c->xz.avail_out = *buffer_allocated - *buffer_size; - - lzr = lzma_code(&c->xz, LZMA_FINISH); - if (!IN_SET(lzr, LZMA_OK, LZMA_STREAM_END)) - return -EIO; - - *buffer_size += (*buffer_allocated - *buffer_size) - c->xz.avail_out; - } while (lzr != LZMA_STREAM_END); - - break; - } - - case IMPORT_COMPRESS_GZIP: - c->gzip.avail_in = 0; - - do { - r = enlarge_buffer(buffer, buffer_size, buffer_allocated); - if (r < 0) - return r; - - c->gzip.next_out = (uint8_t*) *buffer + *buffer_size; - c->gzip.avail_out = *buffer_allocated - *buffer_size; - - r = deflate(&c->gzip, Z_FINISH); - if (!IN_SET(r, Z_OK, Z_STREAM_END)) - return -EIO; - - *buffer_size += (*buffer_allocated - *buffer_size) - c->gzip.avail_out; - } while (r != Z_STREAM_END); - - break; - -#if HAVE_BZIP2 - case IMPORT_COMPRESS_BZIP2: - c->bzip2.avail_in = 0; - - do { - r = enlarge_buffer(buffer, buffer_size, buffer_allocated); - if (r < 0) - return r; - - c->bzip2.next_out = (void*) ((uint8_t*) *buffer + *buffer_size); - c->bzip2.avail_out = *buffer_allocated - *buffer_size; - - r = BZ2_bzCompress(&c->bzip2, BZ_FINISH); - if (!IN_SET(r, BZ_FINISH_OK, BZ_STREAM_END)) - return -EIO; - - *buffer_size += (*buffer_allocated - *buffer_size) - c->bzip2.avail_out; - } while (r != BZ_STREAM_END); - - break; -#endif - -#if HAVE_ZSTD - case IMPORT_COMPRESS_ZSTD: { - ZSTD_inBuffer input = {}; - size_t res; - - do { - r = enlarge_buffer(buffer, buffer_size, buffer_allocated); - if (r < 0) - return r; - - ZSTD_outBuffer output = { - .dst = ((uint8_t *) *buffer + *buffer_size), - .size = *buffer_allocated - *buffer_size, - }; - - res = ZSTD_compressStream2(c->c_zstd, &output, &input, ZSTD_e_end); - if (ZSTD_isError(res)) - return -EIO; - - *buffer_size += output.pos; - } while (res != 0); - - break; - } -#endif - - case IMPORT_COMPRESS_UNCOMPRESSED: - break; - - default: - return -EOPNOTSUPP; - } - - return 0; -} - -static const char* const import_compress_type_table[_IMPORT_COMPRESS_TYPE_MAX] = { - [IMPORT_COMPRESS_UNKNOWN] = "unknown", - [IMPORT_COMPRESS_UNCOMPRESSED] = "uncompressed", - [IMPORT_COMPRESS_XZ] = "xz", - [IMPORT_COMPRESS_GZIP] = "gzip", -#if HAVE_BZIP2 - [IMPORT_COMPRESS_BZIP2] = "bzip2", -#endif -#if HAVE_ZSTD - [IMPORT_COMPRESS_ZSTD] = "zstd", -#endif -}; - -DEFINE_STRING_TABLE_LOOKUP(import_compress_type, ImportCompressType); diff --git a/src/import/import-compress.h b/src/import/import-compress.h deleted file mode 100644 index 647e623266787..0000000000000 --- a/src/import/import-compress.h +++ /dev/null @@ -1,54 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#if HAVE_BZIP2 -#include -#endif -#include -#include -#if HAVE_ZSTD -#include -#endif - -#include "shared-forward.h" - -typedef enum ImportCompressType { - IMPORT_COMPRESS_UNKNOWN, - IMPORT_COMPRESS_UNCOMPRESSED, - IMPORT_COMPRESS_XZ, - IMPORT_COMPRESS_GZIP, - IMPORT_COMPRESS_BZIP2, - IMPORT_COMPRESS_ZSTD, - _IMPORT_COMPRESS_TYPE_MAX, - _IMPORT_COMPRESS_TYPE_INVALID = -EINVAL, -} ImportCompressType; - -typedef struct ImportCompress { - ImportCompressType type; - bool encoding; - union { - lzma_stream xz; - z_stream gzip; -#if HAVE_BZIP2 - bz_stream bzip2; -#endif -#if HAVE_ZSTD - ZSTD_CCtx *c_zstd; - ZSTD_DCtx *d_zstd; -#endif - }; -} ImportCompress; - -typedef int (*ImportCompressCallback)(const void *data, size_t size, void *userdata); - -void import_compress_free(ImportCompress *c); - -int import_uncompress_detect(ImportCompress *c, const void *data, size_t size); -void import_uncompress_force_off(ImportCompress *c); -int import_uncompress(ImportCompress *c, const void *data, size_t size, ImportCompressCallback callback, void *userdata); - -int import_compress_init(ImportCompress *c, ImportCompressType t); -int import_compress(ImportCompress *c, const void *data, size_t size, void **buffer, size_t *buffer_size, size_t *buffer_allocated); -int import_compress_finish(ImportCompress *c, void **buffer, size_t *buffer_size, size_t *buffer_allocated); - -DECLARE_STRING_TABLE_LOOKUP(import_compress_type, ImportCompressType); diff --git a/src/import/import-raw.c b/src/import/import-raw.c index 1d7302cd88243..05d4bc9c9f62b 100644 --- a/src/import/import-raw.c +++ b/src/import/import-raw.c @@ -1,17 +1,18 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include "sd-daemon.h" #include "sd-event.h" #include "alloc-util.h" +#include "compress.h" #include "copy.h" #include "fd-util.h" #include "format-util.h" #include "fs-util.h" #include "import-common.h" -#include "import-compress.h" #include "import-raw.h" #include "import-util.h" #include "install-file.h" @@ -43,11 +44,11 @@ typedef struct RawImport { int input_fd; int output_fd; - ImportCompress compress; + Compressor *compress; sd_event_source *input_event_source; - uint8_t buffer[IMPORT_BUFFER_SIZE]; + uint8_t buffer[COMPRESS_PIPE_BUFFER_SIZE]; size_t buffer_size; uint64_t written_compressed; @@ -71,7 +72,7 @@ RawImport* raw_import_unref(RawImport *i) { unlink_and_free(i->temp_path); - import_compress_free(&i->compress); + i->compress = compressor_free(i->compress); sd_event_unref(i->event); @@ -328,7 +329,7 @@ static int raw_import_try_reflink(RawImport *i) { assert(i->input_fd >= 0); assert(i->output_fd >= 0); - if (i->compress.type != IMPORT_COMPRESS_UNCOMPRESSED) + if (compressor_type(i->compress) != COMPRESSION_NONE) return 0; if (i->offset != UINT64_MAX || i->size_max != UINT64_MAX) @@ -425,13 +426,13 @@ static int raw_import_process(RawImport *i) { i->buffer_size += l; - if (i->compress.type == IMPORT_COMPRESS_UNKNOWN) { + if (!i->compress) { if (l == 0) { /* EOF */ log_debug("File too short to be compressed, as no compression signature fits in, thus assuming uncompressed."); - import_uncompress_force_off(&i->compress); + decompressor_force_off(&i->compress); } else { - r = import_uncompress_detect(&i->compress, i->buffer, i->buffer_size); + r = decompressor_detect(&i->compress, i->buffer, i->buffer_size); if (r < 0) { log_error_errno(r, "Failed to detect file compression: %m"); goto finish; @@ -451,7 +452,7 @@ static int raw_import_process(RawImport *i) { goto complete; } - r = import_uncompress(&i->compress, i->buffer, i->buffer_size, raw_import_write, i); + r = decompressor_push(i->compress, i->buffer, i->buffer_size, raw_import_write, i); if (r < 0) { log_error_errno(r, "Failed to decode and write: %m"); goto finish; diff --git a/src/import/import-tar.c b/src/import/import-tar.c index 5e74de896e99c..4bd59788008e9 100644 --- a/src/import/import-tar.c +++ b/src/import/import-tar.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include "sd-daemon.h" #include "sd-event.h" @@ -8,12 +9,12 @@ #include "alloc-util.h" #include "btrfs-util.h" +#include "compress.h" #include "dissect-image.h" #include "errno-util.h" #include "fd-util.h" #include "format-util.h" #include "import-common.h" -#include "import-compress.h" #include "import-tar.h" #include "import-util.h" #include "install-file.h" @@ -50,11 +51,11 @@ typedef struct TarImport { int tree_fd; int userns_fd; - ImportCompress compress; + Compressor *compress; sd_event_source *input_event_source; - uint8_t buffer[IMPORT_BUFFER_SIZE]; + uint8_t buffer[COMPRESS_PIPE_BUFFER_SIZE]; size_t buffer_size; uint64_t written_compressed; @@ -81,7 +82,7 @@ TarImport* tar_import_unref(TarImport *i) { free(i->temp_path); } - import_compress_free(&i->compress); + i->compress = compressor_free(i->compress); sd_event_unref(i->event); @@ -344,13 +345,13 @@ static int tar_import_process(TarImport *i) { i->buffer_size += l; - if (i->compress.type == IMPORT_COMPRESS_UNKNOWN) { + if (!i->compress) { if (l == 0) { /* EOF */ log_debug("File too short to be compressed, as no compression signature fits in, thus assuming uncompressed."); - import_uncompress_force_off(&i->compress); + decompressor_force_off(&i->compress); } else { - r = import_uncompress_detect(&i->compress, i->buffer, i->buffer_size); + r = decompressor_detect(&i->compress, i->buffer, i->buffer_size); if (r < 0) { log_error_errno(r, "Failed to detect file compression: %m"); goto finish; @@ -364,7 +365,7 @@ static int tar_import_process(TarImport *i) { goto finish; } - r = import_uncompress(&i->compress, i->buffer, i->buffer_size, tar_import_write, i); + r = decompressor_push(i->compress, i->buffer, i->buffer_size, tar_import_write, i); if (r < 0) { log_error_errno(r, "Failed to decode and write: %m"); goto finish; diff --git a/src/import/meson.build b/src/import/meson.build index 30751058f1195..a98604d4915b8 100644 --- a/src/import/meson.build +++ b/src/import/meson.build @@ -7,11 +7,7 @@ if conf.get('ENABLE_IMPORTD') != 1 endif common_deps = [ - libbzip2, libcurl, - libxz, - libz, - libzstd, ] executables += [ @@ -25,7 +21,6 @@ executables += [ 'extract' : files( 'oci-util.c', 'import-common.c', - 'import-compress.c', 'qcow2-util.c', ), 'dependencies' : [common_deps, threads], diff --git a/src/import/pull-common.c b/src/import/pull-common.c index c0e0e9907e6b1..b234331945a9b 100644 --- a/src/import/pull-common.c +++ b/src/import/pull-common.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "sd-id128.h" #include "alloc-util.h" diff --git a/src/import/pull-job.c b/src/import/pull-job.c index dcdb1fe8fa7b3..385043dda8003 100644 --- a/src/import/pull-job.c +++ b/src/import/pull-job.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "alloc-util.h" #include "curl-util.h" @@ -53,7 +54,7 @@ PullJob* pull_job_unref(PullJob *j) { curl_glue_remove_and_free(j->glue, j->curl); curl_slist_free_all(j->request_header); - import_compress_free(&j->compress); + j->compress = compressor_free(j->compress); if (j->checksum_ctx) EVP_MD_CTX_free(j->checksum_ctx); @@ -134,7 +135,7 @@ int pull_job_restart(PullJob *j, const char *new_url) { curl_glue_remove_and_free(j->glue, j->curl); j->curl = NULL; - import_compress_free(&j->compress); + j->compress = compressor_free(j->compress); if (j->checksum_ctx) { EVP_MD_CTX_free(j->checksum_ctx); @@ -453,7 +454,7 @@ static int pull_job_write_compressed(PullJob *j, const struct iovec *data) { "Could not hash chunk."); } - r = import_uncompress(&j->compress, data->iov_base, data->iov_len, pull_job_write_uncompressed, j); + r = decompressor_push(j->compress, data->iov_base, data->iov_len, pull_job_write_uncompressed, j); if (r < 0) return r; @@ -502,13 +503,13 @@ static int pull_job_detect_compression(PullJob *j) { assert(j); - r = import_uncompress_detect(&j->compress, j->payload.iov_base, j->payload.iov_len); + r = decompressor_detect(&j->compress, j->payload.iov_base, j->payload.iov_len); if (r < 0) return log_error_errno(r, "Failed to initialize compressor: %m"); if (r == 0) return 0; - log_debug("Stream is compressed: %s", import_compress_type_to_string(j->compress.type)); + log_debug("Stream is compressed: %s", compression_to_string(compressor_type(j->compress))); r = pull_job_open_disk(j); if (r < 0) diff --git a/src/import/pull-job.h b/src/import/pull-job.h index ea58b62f3bfa9..1daa006c1c373 100644 --- a/src/import/pull-job.h +++ b/src/import/pull-job.h @@ -4,9 +4,9 @@ #include #include -#include "shared-forward.h" -#include "import-compress.h" +#include "compress.h" #include "openssl-util.h" +#include "shared-forward.h" typedef struct CurlGlue CurlGlue; typedef struct PullJob PullJob; @@ -73,7 +73,7 @@ typedef struct PullJob { usec_t mtime; char *content_type; - ImportCompress compress; + Compressor *compress; unsigned progress_percent; usec_t start_usec; diff --git a/src/import/pull-oci.c b/src/import/pull-oci.c index 0f16c65630a7e..4ae933928ff5f 100644 --- a/src/import/pull-oci.c +++ b/src/import/pull-oci.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "sd-event.h" #include "sd-json.h" #include "sd-varlink.h" diff --git a/src/import/pull-raw.c b/src/import/pull-raw.c index 31a08eb24ae6d..6fde8c5f8bccc 100644 --- a/src/import/pull-raw.c +++ b/src/import/pull-raw.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "sd-daemon.h" #include "sd-event.h" diff --git a/src/import/pull-tar.c b/src/import/pull-tar.c index f4a8bfca62276..a235c2fba3dd6 100644 --- a/src/import/pull-tar.c +++ b/src/import/pull-tar.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "sd-daemon.h" #include "sd-event.h" #include "sd-varlink.h" diff --git a/src/import/qcow2-util.c b/src/import/qcow2-util.c index fe0c8a26209e0..2b219b04a1f63 100644 --- a/src/import/qcow2-util.c +++ b/src/import/qcow2-util.c @@ -1,8 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include +#include #include "alloc-util.h" +#include "compress.h" #include "copy.h" #include "qcow2-util.h" #include "sparse-endian.h" @@ -97,8 +98,6 @@ static int decompress_cluster( void *buffer2) { _cleanup_free_ void *large_buffer = NULL; - z_stream s = {}; - uint64_t sz; ssize_t l; int r; @@ -119,20 +118,9 @@ static int decompress_cluster( if ((uint64_t) l != compressed_size) return -EIO; - s.next_in = buffer1; - s.avail_in = compressed_size; - s.next_out = buffer2; - s.avail_out = cluster_size; - - r = inflateInit2(&s, -12); - if (r != Z_OK) - return -EIO; - - r = inflate(&s, Z_FINISH); - sz = (uint8_t*) s.next_out - (uint8_t*) buffer2; - inflateEnd(&s); - if (r != Z_STREAM_END || sz != cluster_size) - return -EIO; + r = decompress_zlib_raw(buffer1, compressed_size, buffer2, cluster_size, /* wbits= */ -12); + if (r < 0) + return r; l = pwrite(dfd, buffer2, cluster_size, doffset); if (l < 0) diff --git a/src/journal-remote/journal-compression-util.c b/src/journal-remote/journal-compression-util.c index 00d39358956cf..8e415878b9580 100644 --- a/src/journal-remote/journal-compression-util.c +++ b/src/journal-remote/journal-compression-util.c @@ -130,7 +130,7 @@ int config_parse_compression( } } - Compression c = compression_lowercase_from_string(word); + Compression c = compression_from_string_harder(word); if (c <= 0 || !compression_supported(c)) { log_syntax(unit, LOG_WARNING, filename, line, c, "Compression algorithm '%s' is not supported on the system, ignoring.", word); diff --git a/src/journal-remote/journal-remote-main.c b/src/journal-remote/journal-remote-main.c index 0ff44ede6fc1c..fbb53cc42fdd1 100644 --- a/src/journal-remote/journal-remote-main.c +++ b/src/journal-remote/journal-remote-main.c @@ -209,7 +209,7 @@ static int build_accept_encoding(char **ret) { const CompressionConfig *cc; ORDERED_HASHMAP_FOREACH(cc, arg_compression) { - const char *c = compression_lowercase_to_string(cc->algorithm); + const char *c = compression_to_string(cc->algorithm); if (strextendf_with_separator(&buf, ",", "%s;q=%.1f", c, q) < 0) return -ENOMEM; q -= step; @@ -361,7 +361,7 @@ static mhd_result request_handler( RemoteSource *source = *connection_cls; header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Content-Encoding"); if (header) { - Compression c = compression_lowercase_from_string(header); + Compression c = compression_from_string_harder(header); if (c <= 0 || !compression_supported(c)) return mhd_respondf(connection, 0, MHD_HTTP_UNSUPPORTED_MEDIA_TYPE, "Unsupported Content-Encoding type: %s", header); diff --git a/src/journal-remote/journal-upload-journal.c b/src/journal-remote/journal-upload-journal.c index 054451aafc78c..66cc4114f40e4 100644 --- a/src/journal-remote/journal-upload-journal.c +++ b/src/journal-remote/journal-upload-journal.c @@ -354,7 +354,7 @@ static size_t journal_input_callback(void *buf, size_t size, size_t nmemb, void r = compress_blob(u->compression->algorithm, compression_buffer, filled, buf, size * nmemb, &compressed_size, u->compression->level); if (r < 0) { log_error_errno(r, "Failed to compress %zu bytes by %s with level %i: %m", - filled, compression_lowercase_to_string(u->compression->algorithm), u->compression->level); + filled, compression_to_string(u->compression->algorithm), u->compression->level); return CURL_READFUNC_ABORT; } diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c index c6123146a5507..c4eab80a1fc5a 100644 --- a/src/journal-remote/journal-upload.c +++ b/src/journal-remote/journal-upload.c @@ -218,7 +218,7 @@ int start_upload(Uploader *u, h = l; if (u->compression) { - _cleanup_free_ char *header = strjoin("Content-Encoding: ", compression_lowercase_to_string(u->compression->algorithm)); + _cleanup_free_ char *header = strjoin("Content-Encoding: ", compression_to_string(u->compression->algorithm)); if (!header) return log_oom(); @@ -369,7 +369,7 @@ static size_t fd_input_callback(void *buf, size_t size, size_t nmemb, void *user r = compress_blob(u->compression->algorithm, compression_buffer, n, buf, size * nmemb, &compressed_size, u->compression->level); if (r < 0) { log_error_errno(r, "Failed to compress %zd bytes by %s with level %i: %m", - n, compression_lowercase_to_string(u->compression->algorithm), u->compression->level); + n, compression_to_string(u->compression->algorithm), u->compression->level); return CURL_READFUNC_ABORT; } assert(compressed_size <= size * nmemb); @@ -528,7 +528,7 @@ static int update_content_encoding_header(Uploader *u, const CompressionConfig * return 0; /* Already picked the algorithm. Let's shortcut. */ if (cc) { - _cleanup_free_ char *header = strjoin("Content-Encoding: ", compression_lowercase_to_string(cc->algorithm)); + _cleanup_free_ char *header = strjoin("Content-Encoding: ", compression_to_string(cc->algorithm)); if (!header) return log_oom(); @@ -572,7 +572,7 @@ static int update_content_encoding_header(Uploader *u, const CompressionConfig * u->compression = cc; if (cc) - log_debug("Using compression algorithm %s with compression level %i.", compression_lowercase_to_string(cc->algorithm), cc->level); + log_debug("Using compression algorithm %s with compression level %i.", compression_to_string(cc->algorithm), cc->level); else log_debug("Disabled compression algorithm."); return 0; @@ -610,7 +610,7 @@ static int parse_accept_encoding_header(Uploader *u) { if (streq(word, "*")) return update_content_encoding_header(u, ordered_hashmap_first(arg_compression)); - Compression c = compression_lowercase_from_string(word); + Compression c = compression_from_string_harder(word); if (c <= 0 || !compression_supported(c)) continue; /* unsupported or invalid algorithm. */ diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index 235f471224504..54c647d75b8eb 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -370,7 +370,7 @@ static Compression getenv_compression(void) { if (r >= 0) return r ? DEFAULT_COMPRESSION : COMPRESSION_NONE; - c = compression_from_string(e); + c = compression_from_string_harder(e); if (c < 0) { log_debug_errno(c, "Failed to parse SYSTEMD_JOURNAL_COMPRESS value, ignoring: %s", e); return DEFAULT_COMPRESSION; diff --git a/src/test/test-compress-benchmark.c b/src/test/test-compress-benchmark.c index da68c6cb7b8cd..8b00a00a06fcc 100644 --- a/src/test/test-compress-benchmark.c +++ b/src/test/test-compress-benchmark.c @@ -1,27 +1,36 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "alloc-util.h" +#include "argv-util.h" #include "compress.h" -#include "nulstr-util.h" #include "parse-util.h" #include "process-util.h" #include "random-util.h" +#include "string-table.h" #include "tests.h" #include "time-util.h" -typedef int (compress_t)(const void *src, uint64_t src_size, void *dst, - size_t dst_alloc_size, size_t *dst_size, int level); -typedef int (decompress_t)(const void *src, uint64_t src_size, - void **dst, size_t* dst_size, size_t dst_max); - -#if HAVE_COMPRESSION - static usec_t arg_duration; static size_t arg_start; #define MAX_SIZE (1024*1024LU) #define PRIME 1048571 /* A prime close enough to one megabyte that mod 4 == 3 */ +typedef enum BenchmarkDataType { + BENCHMARK_DATA_ZEROS, + BENCHMARK_DATA_SIMPLE, + BENCHMARK_DATA_RANDOM, + _BENCHMARK_DATA_TYPE_MAX, +} BenchmarkDataType; + +static const char* const benchmark_data_type_table[_BENCHMARK_DATA_TYPE_MAX] = { + [BENCHMARK_DATA_ZEROS] = "zeros", + [BENCHMARK_DATA_SIMPLE] = "simple", + [BENCHMARK_DATA_RANDOM] = "random", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(benchmark_data_type, BenchmarkDataType); + static size_t _permute(size_t x) { size_t residue; @@ -39,19 +48,24 @@ static size_t permute(size_t x) { return _permute((_permute(x) + arg_start) % MAX_SIZE ^ 0xFF345); } -static char* make_buf(size_t count, const char *type) { +static char* make_buf(size_t count, BenchmarkDataType type) { char *buf; - size_t i; buf = malloc(count); - assert_se(buf); + ASSERT_NOT_NULL(buf); - if (streq(type, "zeros")) + switch (type) { + + case BENCHMARK_DATA_ZEROS: memzero(buf, count); - else if (streq(type, "simple")) - for (i = 0; i < count; i++) + break; + + case BENCHMARK_DATA_SIMPLE: + for (size_t i = 0; i < count; i++) buf[i] = 'a' + i % ('z' - 'a' + 1); - else if (streq(type, "random")) { + break; + + case BENCHMARK_DATA_RANDOM: { size_t step = count / 10; random_bytes(buf, step); @@ -64,110 +78,103 @@ static char* make_buf(size_t count, const char *type) { memzero(buf + 7*step, step); random_bytes(buf + 8*step, step); memzero(buf + 9*step, step); - } else + break; + } + + default: assert_not_reached(); + } return buf; } -static void test_compress_decompress(const char* label, const char* type, - compress_t compress, decompress_t decompress) { - usec_t n, n2 = 0; - float dt; +TEST(benchmark) { + for (BenchmarkDataType dt = 0; dt < _BENCHMARK_DATA_TYPE_MAX; dt++) + for (Compression c = 0; c < _COMPRESSION_MAX; c++) { + if (c == COMPRESSION_NONE || !compression_supported(c)) + continue; - _cleanup_free_ char *text = NULL, *buf = NULL; - _cleanup_free_ void *buf2 = NULL; - size_t skipped = 0, compressed = 0, total = 0; + const char *label = compression_to_string(c); + const char *type = benchmark_data_type_to_string(dt); + usec_t n, n2 = 0; - text = make_buf(MAX_SIZE, type); - buf = calloc(MAX_SIZE + 1, 1); - assert_se(text && buf); + _cleanup_free_ char *text = NULL, *buf = NULL; + _cleanup_free_ void *buf2 = NULL; + size_t skipped = 0, compressed = 0, total = 0; - n = now(CLOCK_MONOTONIC); + text = make_buf(MAX_SIZE, dt); + buf = calloc(MAX_SIZE + 1, 1); + ASSERT_NOT_NULL(text); + ASSERT_NOT_NULL(buf); - for (size_t i = 0; i <= MAX_SIZE; i++) { - size_t j = 0, k = 0, size; - int r; + n = now(CLOCK_MONOTONIC); - size = permute(i); - if (size == 0) - continue; + for (size_t i = 0; i <= MAX_SIZE; i++) { + size_t j = 0, k = 0, size; + int r; - log_debug("%s %zu %zu", type, i, size); + size = permute(i); + if (size == 0) + continue; - memzero(buf, MIN(size + 1000, MAX_SIZE)); + log_debug("%s %zu %zu", type, i, size); - r = compress(text, size, buf, size, &j, /* level= */ -1); - /* assume compression must be successful except for small or random inputs */ - assert_se(r >= 0 || (size < 2048 && r == -ENOBUFS) || streq(type, "random")); + memzero(buf, MIN(size + 1000, MAX_SIZE)); - /* check for overwrites */ - assert_se(buf[size] == 0); - if (r < 0) { - skipped += size; - continue; - } + r = compress_blob(c, text, size, buf, size, &j, /* level= */ -1); + /* assume compression must be successful except for small or random inputs */ + ASSERT_TRUE(r >= 0 || (size < 2048 && r == -ENOBUFS) || dt == BENCHMARK_DATA_RANDOM); - assert_se(j > 0); - if (j >= size) - log_error("%s \"compressed\" %zu -> %zu", label, size, j); + /* check for overwrites */ + ASSERT_EQ(buf[size], 0); + if (r < 0) { + skipped += size; + continue; + } - r = decompress(buf, j, &buf2, &k, 0); - assert_se(r == 0); - assert_se(k == size); + ASSERT_TRUE(j > 0); + if (j >= size) + log_error("%s \"compressed\" %zu -> %zu", label, size, j); - assert_se(memcmp(text, buf2, size) == 0); + ASSERT_OK_ZERO(decompress_blob(c, buf, j, &buf2, &k, 0)); + ASSERT_EQ(k, size); + ASSERT_EQ(memcmp(text, buf2, size), 0); - total += size; - compressed += j; + total += size; + compressed += j; - n2 = now(CLOCK_MONOTONIC); - if (n2 - n > arg_duration) - break; - } + n2 = now(CLOCK_MONOTONIC); + if (n2 - n > arg_duration) + break; + } - dt = (n2-n) / 1e6; + float elapsed = (n2-n) / 1e6; - log_info("%s/%s: compressed & decompressed %zu bytes in %.2fs (%.2fMiB/s), " - "mean compression %.2f%%, skipped %zu bytes", - label, type, total, dt, - total / 1024. / 1024 / dt, - 100 - compressed * 100. / total, - skipped); + log_info("%s/%s: compressed & decompressed %zu bytes in %.2fs (%.2fMiB/s), " + "mean compression %.2f%%, skipped %zu bytes", + label, type, total, elapsed, + total / 1024. / 1024 / elapsed, + 100 - compressed * 100. / total, + skipped); + } } -#endif - -int main(int argc, char *argv[]) { -#if HAVE_COMPRESSION - test_setup_logging(LOG_INFO); - if (argc >= 2) { +static int intro(void) { + if (saved_argc >= 2) { unsigned x; - assert_se(safe_atou(argv[1], &x) >= 0); + ASSERT_OK(safe_atou(saved_argv[1], &x)); arg_duration = x * USEC_PER_SEC; } else arg_duration = slow_tests_enabled() ? 2 * USEC_PER_SEC : USEC_PER_SEC / 50; - if (argc == 3) - (void) safe_atozu(argv[2], &arg_start); + if (saved_argc == 3) + (void) safe_atozu(saved_argv[2], &arg_start); else arg_start = getpid_cached(); - NULSTR_FOREACH(i, "zeros\0simple\0random\0") { -#if HAVE_XZ - test_compress_decompress("XZ", i, compress_blob_xz, decompress_blob_xz); -#endif -#if HAVE_LZ4 - test_compress_decompress("LZ4", i, compress_blob_lz4, decompress_blob_lz4); -#endif -#if HAVE_ZSTD - test_compress_decompress("ZSTD", i, compress_blob_zstd, decompress_blob_zstd); -#endif - } return 0; -#else - return log_tests_skipped("No compression feature is enabled"); -#endif } + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/test/test-compress.c b/src/test/test-compress.c index 80f2923dd62dc..0c90443a8738b 100644 --- a/src/test/test-compress.c +++ b/src/test/test-compress.c @@ -4,13 +4,9 @@ #include #include -#if HAVE_LZ4 -#include -#endif - #include "alloc-util.h" +#include "argv-util.h" #include "compress.h" -#include "dlfcn-util.h" #include "fd-util.h" #include "io-util.h" #include "path-util.h" @@ -18,516 +14,455 @@ #include "tests.h" #include "tmpfile-util.h" -#if HAVE_XZ -# define XZ_OK 0 -#else -# define XZ_OK -EPROTONOSUPPORT -#endif +#define HUGE_SIZE (4096*1024) -#if HAVE_LZ4 -# define LZ4_OK 0 -#else -# define LZ4_OK -EPROTONOSUPPORT -#endif +static const char text[] = + "text\0foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF" + "foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF"; +static char data[512] = "random\0"; +static char *huge = NULL; +static const char *srcfile; + +static const char* cat_for_compression(Compression c) { + switch (c) { + case COMPRESSION_XZ: return "xzcat"; + case COMPRESSION_LZ4: return "lz4cat"; + case COMPRESSION_ZSTD: return "zstdcat"; + case COMPRESSION_GZIP: return "zcat"; + case COMPRESSION_BZIP2: return "bzcat"; + default: return NULL; + } +} -#define HUGE_SIZE (4096*1024) +TEST(compress_decompress_blob) { + for (Compression c = 0; c < _COMPRESSION_MAX; c++) { + if (c == COMPRESSION_NONE || !compression_supported(c)) + continue; + + const char *label = compression_to_string(c); + + for (size_t t = 0; t < 2; t++) { + const char *input = t == 0 ? text : data; + size_t input_len = t == 0 ? sizeof(text) : sizeof(data); + bool may_fail = t == 1; + + char compressed[512]; + size_t csize; + _cleanup_free_ char *decompressed = NULL; + int r; + + log_info("/* testing %s %s blob compression/decompression */", label, input); + + r = compress_blob(c, input, input_len, compressed, sizeof(compressed), &csize, -1); + if (r == -ENOBUFS) { + log_info_errno(r, "compression failed: %m"); + ASSERT_TRUE(may_fail); + } else { + ASSERT_OK(r); + ASSERT_OK_ZERO(decompress_blob(c, compressed, csize, (void **) &decompressed, &csize, 0)); + ASSERT_NOT_NULL(decompressed); + ASSERT_EQ(memcmp(decompressed, input, input_len), 0); + } + + ASSERT_FAIL(decompress_blob(c, "garbage", 7, (void **) &decompressed, &csize, 0)); + } + } +} -typedef int (compress_blob_t)(const void *src, uint64_t src_size, - void *dst, size_t dst_alloc_size, size_t *dst_size, int level); -typedef int (decompress_blob_t)(const void *src, uint64_t src_size, - void **dst, - size_t* dst_size, size_t dst_max); -typedef int (decompress_sw_t)(const void *src, uint64_t src_size, - void **buffer, - const void *prefix, size_t prefix_len, - uint8_t extra); - -typedef int (compress_stream_t)(int fdf, int fdt, uint64_t max_bytes, uint64_t *uncompressed_size); -typedef int (decompress_stream_t)(int fdf, int fdt, uint64_t max_size); - -#if HAVE_COMPRESSION -_unused_ static void test_compress_decompress( - const char *compression, - compress_blob_t compress, - decompress_blob_t decompress, - const char *data, - size_t data_len, - bool may_fail) { - - char compressed[512]; - size_t csize; - _cleanup_free_ char *decompressed = NULL; - int r; - - log_info("/* testing %s %s blob compression/decompression */", - compression, data); - - r = compress(data, data_len, compressed, sizeof(compressed), &csize, /* level= */ -1); - if (r == -ENOBUFS) { - log_info_errno(r, "compression failed: %m"); - assert_se(may_fail); - } else { - assert_se(r >= 0); - r = decompress(compressed, csize, - (void **) &decompressed, &csize, 0); - assert_se(r == 0); - assert_se(decompressed); - assert_se(memcmp(decompressed, data, data_len) == 0); +TEST(decompress_startswith) { + for (Compression c = 0; c < _COMPRESSION_MAX; c++) { + if (c == COMPRESSION_NONE || !compression_supported(c)) + continue; + + const char *label = compression_to_string(c); + + struct { const char *buf; size_t len; bool may_fail; } inputs[] = { + { text, sizeof(text), false }, + { data, sizeof(data), true }, + { huge, HUGE_SIZE, true }, + }; + + for (size_t t = 0; t < ELEMENTSOF(inputs); t++) { + char *compressed; + _cleanup_free_ char *compressed1 = NULL, *compressed2 = NULL, *decompressed = NULL; + size_t csize, len; + int r; + + log_info("/* testing decompress_startswith with %s on %.20s */", label, inputs[t].buf); + + compressed = compressed1 = malloc(512); + ASSERT_NOT_NULL(compressed1); + r = compress_blob(c, inputs[t].buf, inputs[t].len, compressed, 512, &csize, -1); + if (r == -ENOBUFS) { + log_info_errno(r, "compression failed: %m"); + ASSERT_TRUE(inputs[t].may_fail); + + compressed = compressed2 = malloc(20000); + ASSERT_NOT_NULL(compressed2); + r = compress_blob(c, inputs[t].buf, inputs[t].len, compressed, 20000, &csize, -1); + } + if (r == -ENOBUFS) { + log_info_errno(r, "compression failed again: %m"); + ASSERT_TRUE(inputs[t].may_fail); + continue; + } + ASSERT_OK(r); + + len = strlen(inputs[t].buf); + + ASSERT_OK_POSITIVE(decompress_startswith(c, compressed, csize, (void **) &decompressed, inputs[t].buf, len, '\0')); + ASSERT_OK_ZERO(decompress_startswith(c, compressed, csize, (void **) &decompressed, inputs[t].buf, len, 'w')); + ASSERT_OK_POSITIVE(decompress_startswith(c, compressed, csize, (void **) &decompressed, inputs[t].buf, len - 1, inputs[t].buf[len-1])); + ASSERT_OK_ZERO(decompress_startswith(c, compressed, csize, (void **) &decompressed, inputs[t].buf, len - 1, 'w')); + } } +} - r = decompress("garbage", 7, - (void **) &decompressed, &csize, 0); - assert_se(r < 0); +TEST(decompress_startswith_large) { + /* Test decompress_startswith with large data to exercise the buffer growth path. */ - /* make sure to have the minimal lz4 compressed size */ - r = decompress("00000000\1g", 9, - (void **) &decompressed, &csize, 0); - assert_se(r < 0); + _cleanup_free_ char *large = NULL; + size_t large_size = 8 * 1024; - r = decompress("\100000000g", 9, - (void **) &decompressed, &csize, 0); - assert_se(r < 0); + ASSERT_NOT_NULL(large = malloc(large_size)); + for (size_t i = 0; i < large_size; i++) + large[i] = 'A' + (i % 26); - explicit_bzero_safe(decompressed, MALLOC_SIZEOF_SAFE(decompressed)); -} + for (Compression c = 0; c < _COMPRESSION_MAX; c++) { + if (c == COMPRESSION_NONE || !compression_supported(c)) + continue; + + _cleanup_free_ char *compressed = NULL; + size_t csize; -_unused_ static void test_decompress_startswith(const char *compression, - compress_blob_t compress, - decompress_sw_t decompress_sw, - const char *data, - size_t data_len, - bool may_fail) { - - char *compressed; - _cleanup_free_ char *compressed1 = NULL, *compressed2 = NULL, *decompressed = NULL; - size_t csize, len; - int r; - - log_info("/* testing decompress_startswith with %s on %.20s text */", - compression, data); - -#define BUFSIZE_1 512 -#define BUFSIZE_2 20000 - - compressed = compressed1 = malloc(BUFSIZE_1); - assert_se(compressed1); - r = compress(data, data_len, compressed, BUFSIZE_1, &csize, /* level= */ -1); - if (r == -ENOBUFS) { - log_info_errno(r, "compression failed: %m"); - assert_se(may_fail); - - compressed = compressed2 = malloc(BUFSIZE_2); - assert_se(compressed2); - r = compress(data, data_len, compressed, BUFSIZE_2, &csize, /* level= */ -1); + log_info("/* decompress_startswith_large with %s */", compression_to_string(c)); + + ASSERT_NOT_NULL(compressed = malloc(large_size)); + int r = compress_blob(c, large, large_size, compressed, large_size, &csize, -1); + if (r == -ENOBUFS) { + log_info_errno(r, "compression failed: %m"); + continue; + } + ASSERT_OK(r); + + _cleanup_free_ void *buf = NULL; + + ASSERT_OK_POSITIVE(decompress_startswith(c, compressed, csize, &buf, large, 1, large[1])); + ASSERT_OK_ZERO(decompress_startswith(c, compressed, csize, &buf, large, 1, 0xff)); + ASSERT_OK_POSITIVE(decompress_startswith(c, compressed, csize, &buf, large, 512, large[512])); + ASSERT_OK_ZERO(decompress_startswith(c, compressed, csize, &buf, large, 512, 0xff)); + ASSERT_OK_POSITIVE(decompress_startswith(c, compressed, csize, &buf, large, 4096, large[4096])); + ASSERT_OK_ZERO(decompress_startswith(c, compressed, csize, &buf, large, 4096, 0xff)); } - assert_se(r >= 0); - - len = strlen(data); - - r = decompress_sw(compressed, csize, (void **) &decompressed, data, len, '\0'); - assert_se(r > 0); - r = decompress_sw(compressed, csize, (void **) &decompressed, data, len, 'w'); - assert_se(r == 0); - r = decompress_sw(compressed, csize, (void **) &decompressed, "barbarbar", 9, ' '); - assert_se(r == 0); - r = decompress_sw(compressed, csize, (void **) &decompressed, data, len - 1, data[len-1]); - assert_se(r > 0); - r = decompress_sw(compressed, csize, (void **) &decompressed, data, len - 1, 'w'); - assert_se(r == 0); - r = decompress_sw(compressed, csize, (void **) &decompressed, data, len, '\0'); - assert_se(r > 0); } -_unused_ static void test_decompress_startswith_short(const char *compression, - compress_blob_t compress, - decompress_sw_t decompress_sw) { - +TEST(decompress_startswith_short) { #define TEXT "HUGE=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - char buf[1024]; - size_t csize; - int r; + for (Compression c = 0; c < _COMPRESSION_MAX; c++) { + if (c == COMPRESSION_NONE || !compression_supported(c)) + continue; + + char buf[1024]; + size_t csize; - log_info("/* %s with %s */", __func__, compression); + log_info("/* decompress_startswith_short with %s */", compression_to_string(c)); - r = compress(TEXT, sizeof TEXT, buf, sizeof buf, &csize, /* level= */ -1); - assert_se(r >= 0); + ASSERT_OK(compress_blob(c, TEXT, sizeof TEXT, buf, sizeof buf, &csize, -1)); - for (size_t i = 1; i < strlen(TEXT); i++) { - _cleanup_free_ void *buf2 = NULL; + for (size_t i = 1; i < strlen(TEXT); i++) { + _cleanup_free_ void *buf2 = NULL; - assert_se(buf2 = malloc(i)); + ASSERT_NOT_NULL(buf2 = malloc(i)); - assert_se(decompress_sw(buf, csize, &buf2, TEXT, i, TEXT[i]) == 1); - assert_se(decompress_sw(buf, csize, &buf2, TEXT, i, 'y') == 0); + ASSERT_OK_POSITIVE(decompress_startswith(c, buf, csize, &buf2, TEXT, i, TEXT[i])); + ASSERT_OK_ZERO(decompress_startswith(c, buf, csize, &buf2, TEXT, i, 'y')); + } } +#undef TEXT } -_unused_ static void test_compress_stream(const char *compression, - const char *cat, - compress_stream_t compress, - decompress_stream_t decompress, - const char *srcfile) { - - _cleanup_close_ int src = -EBADF, dst = -EBADF, dst2 = -EBADF; - _cleanup_(unlink_tempfilep) char - pattern[] = "/tmp/systemd-test.compressed.XXXXXX", - pattern2[] = "/tmp/systemd-test.compressed.XXXXXX"; - int r; - _cleanup_free_ char *cmd = NULL, *cmd2 = NULL; - struct stat st = {}; - uint64_t uncompressed_size; - - r = find_executable(cat, NULL); - if (r < 0) { - log_error_errno(r, "Skipping %s, could not find %s binary: %m", __func__, cat); - return; - } +TEST(compress_decompress_stream) { + for (Compression c = 0; c < _COMPRESSION_MAX; c++) { + if (c == COMPRESSION_NONE || !compression_supported(c)) + continue; - log_debug("/* testing %s compression */", compression); + const char *cat = cat_for_compression(c); + if (!cat) + continue; - log_debug("/* create source from %s */", srcfile); + int r = find_executable(cat, NULL); + if (r < 0) { + log_error_errno(r, "Skipping %s, could not find %s binary: %m", + compression_to_string(c), cat); + continue; + } - ASSERT_OK(src = open(srcfile, O_RDONLY|O_CLOEXEC)); + _cleanup_close_ int src = -EBADF, dst = -EBADF, dst2 = -EBADF; + _cleanup_(unlink_tempfilep) char + pattern[] = "/tmp/systemd-test.compressed.XXXXXX", + pattern2[] = "/tmp/systemd-test.compressed.XXXXXX"; + _cleanup_free_ char *cmd = NULL, *cmd2 = NULL; + struct stat st = {}; + uint64_t uncompressed_size; - log_debug("/* test compression */"); + log_debug("/* testing %s stream compression */", compression_to_string(c)); - assert_se((dst = mkostemp_safe(pattern)) >= 0); + ASSERT_OK(src = open(srcfile, O_RDONLY|O_CLOEXEC)); + ASSERT_OK(dst = mkostemp_safe(pattern)); - ASSERT_OK(compress(src, dst, -1, &uncompressed_size)); + ASSERT_OK(compress_stream(c, src, dst, -1, &uncompressed_size)); - if (cat) { - assert_se(asprintf(&cmd, "%s %s | diff '%s' -", cat, pattern, srcfile) > 0); - assert_se(system(cmd) == 0); - } + ASSERT_OK_POSITIVE(asprintf(&cmd, "%s %s | diff '%s' -", cat, pattern, srcfile)); + ASSERT_OK_ZERO(system(cmd)); + + ASSERT_OK(dst2 = mkostemp_safe(pattern2)); + + ASSERT_OK_ZERO_ERRNO(stat(srcfile, &st)); + ASSERT_EQ((uint64_t) st.st_size, uncompressed_size); - log_debug("/* test decompression */"); + ASSERT_OK_ERRNO(lseek(dst, 0, SEEK_SET)); + ASSERT_OK_ZERO(decompress_stream(c, dst, dst2, st.st_size)); - assert_se((dst2 = mkostemp_safe(pattern2)) >= 0); + ASSERT_OK_POSITIVE(asprintf(&cmd2, "diff '%s' %s", srcfile, pattern2)); + ASSERT_OK_ZERO(system(cmd2)); - assert_se(stat(srcfile, &st) == 0); - assert_se((uint64_t)st.st_size == uncompressed_size); + log_debug("/* test faulty decompression */"); - assert_se(lseek(dst, 0, SEEK_SET) == 0); - r = decompress(dst, dst2, st.st_size); - assert_se(r == 0); + ASSERT_OK_ERRNO(lseek(dst, 1, SEEK_SET)); + r = decompress_stream(c, dst, dst2, st.st_size); + ASSERT_TRUE(IN_SET(r, 0, -EBADMSG)); - assert_se(asprintf(&cmd2, "diff '%s' %s", srcfile, pattern2) > 0); - assert_se(system(cmd2) == 0); + ASSERT_OK_ERRNO(lseek(dst, 0, SEEK_SET)); + ASSERT_OK_ERRNO(lseek(dst2, 0, SEEK_SET)); + ASSERT_ERROR(decompress_stream(c, dst, dst2, st.st_size - 1), EFBIG); + } +} + +struct decompressor_test_data { + uint8_t *buf; + size_t size; +}; - log_debug("/* test faulty decompression */"); +static int test_decompressor_callback(const void *p, size_t size, void *userdata) { + struct decompressor_test_data *d = ASSERT_PTR(userdata); - assert_se(lseek(dst, 1, SEEK_SET) == 1); - r = decompress(dst, dst2, st.st_size); - assert_se(IN_SET(r, 0, -EBADMSG)); + if (!GREEDY_REALLOC(d->buf, d->size + size)) + return -ENOMEM; - assert_se(lseek(dst, 0, SEEK_SET) == 0); - assert_se(lseek(dst2, 0, SEEK_SET) == 0); - r = decompress(dst, dst2, st.st_size - 1); - assert_se(r == -EFBIG); + memcpy(d->buf + d->size, p, size); + d->size += size; + return 0; } -_unused_ static void test_decompress_stream_sparse(const char *compression, - compress_stream_t compress, - decompress_stream_t decompress) { - - _cleanup_close_ int src = -EBADF, compressed = -EBADF, decompressed = -EBADF; - _cleanup_(unlink_tempfilep) char - pattern_src[] = "/tmp/systemd-test.sparse-src.XXXXXX", - pattern_compressed[] = "/tmp/systemd-test.sparse-compressed.XXXXXX", - pattern_decompressed[] = "/tmp/systemd-test.sparse-decompressed.XXXXXX"; - /* Create a sparse-like input: 4K of data, 64K of zeros, 4K of data, 64K trailing zeros. - * Total apparent size: 136K, but most of it is zeros. */ - uint8_t data_block[4096]; - struct stat st_src, st_decompressed; - uint64_t uncompressed_size; - int r; - - assert(compression); - - log_debug("/* testing %s sparse decompression */", compression); - - random_bytes(data_block, sizeof(data_block)); - - assert_se((src = mkostemp_safe(pattern_src)) >= 0); - - /* Write: 4K data, 64K zeros, 4K data, 64K zeros */ - assert_se(loop_write(src, data_block, sizeof(data_block)) >= 0); - assert_se(ftruncate(src, sizeof(data_block) + 65536) >= 0); - assert_se(lseek(src, sizeof(data_block) + 65536, SEEK_SET) >= 0); - assert_se(loop_write(src, data_block, sizeof(data_block)) >= 0); - assert_se(ftruncate(src, 2 * sizeof(data_block) + 2 * 65536) >= 0); - assert_se(lseek(src, 0, SEEK_SET) == 0); - - assert_se(fstat(src, &st_src) >= 0); - assert_se(st_src.st_size == 2 * (off_t) sizeof(data_block) + 2 * 65536); - - /* Compress */ - assert_se((compressed = mkostemp_safe(pattern_compressed)) >= 0); - ASSERT_OK(compress(src, compressed, -1, &uncompressed_size)); - assert_se((uint64_t) st_src.st_size == uncompressed_size); - - /* Decompress to a regular file (sparse writes auto-detected) */ - assert_se((decompressed = mkostemp_safe(pattern_decompressed)) >= 0); - assert_se(lseek(compressed, 0, SEEK_SET) == 0); - r = decompress(compressed, decompressed, st_src.st_size); - assert_se(r == 0); - - /* Verify apparent size matches */ - assert_se(fstat(decompressed, &st_decompressed) >= 0); - assert_se(st_decompressed.st_size == st_src.st_size); - - /* Verify content matches by comparing bytes */ - assert_se(lseek(src, 0, SEEK_SET) == 0); - assert_se(lseek(decompressed, 0, SEEK_SET) == 0); - - for (off_t offset = 0; offset < st_src.st_size;) { - uint8_t buf_src[4096], buf_dst[4096]; - size_t to_read = MIN((size_t) (st_src.st_size - offset), sizeof(buf_src)); - ssize_t n; - - n = loop_read(src, buf_src, to_read, true); - assert_se(n == (ssize_t) to_read); - n = loop_read(decompressed, buf_dst, to_read, true); - assert_se(n == (ssize_t) to_read); - assert_se(memcmp(buf_src, buf_dst, to_read) == 0); - offset += to_read; - } +TEST(decompress_stream_sparse) { + for (Compression c = 0; c < _COMPRESSION_MAX; c++) { + if (c == COMPRESSION_NONE || !compression_supported(c)) + continue; - /* Verify the decompressed file is actually sparse (uses less disk than apparent size). - * st_blocks is in 512-byte units. The file has 128K of zeros, so disk usage should be - * noticeably less than the apparent size if sparse writes worked. - * Only assert if the filesystem supports holes (SEEK_HOLE). */ - log_debug("%s sparse decompression: apparent=%jd disk=%jd", - compression, - (intmax_t) st_decompressed.st_size, - (intmax_t) st_decompressed.st_blocks * 512); - if (lseek(decompressed, 0, SEEK_HOLE) < st_decompressed.st_size) - assert_se(st_decompressed.st_blocks * 512 < st_decompressed.st_size); - else - log_debug("Filesystem does not support holes, skipping sparsity check"); - - /* Test all-zeros input: entire output should be a hole */ - log_debug("/* testing %s sparse decompression of all-zeros */", compression); - { - _cleanup_close_ int zsrc = -EBADF, zcompressed = -EBADF, zdecompressed = -EBADF; + _cleanup_close_ int src = -EBADF, compressed = -EBADF, decompressed = -EBADF; _cleanup_(unlink_tempfilep) char - zp_src[] = "/tmp/systemd-test.sparse-zero-src.XXXXXX", - zp_compressed[] = "/tmp/systemd-test.sparse-zero-compressed.XXXXXX", - zp_decompressed[] = "/tmp/systemd-test.sparse-zero-decompressed.XXXXXX"; - struct stat zst; - uint64_t zsize; - uint8_t zeros[65536] = {}; - - assert_se((zsrc = mkostemp_safe(zp_src)) >= 0); - assert_se(loop_write(zsrc, zeros, sizeof(zeros)) >= 0); - assert_se(lseek(zsrc, 0, SEEK_SET) == 0); - - assert_se((zcompressed = mkostemp_safe(zp_compressed)) >= 0); - ASSERT_OK(compress(zsrc, zcompressed, -1, &zsize)); - assert_se(zsize == sizeof(zeros)); - - assert_se((zdecompressed = mkostemp_safe(zp_decompressed)) >= 0); - assert_se(lseek(zcompressed, 0, SEEK_SET) == 0); - assert_se(decompress(zcompressed, zdecompressed, sizeof(zeros)) == 0); - - assert_se(fstat(zdecompressed, &zst) >= 0); - assert_se(zst.st_size == (off_t) sizeof(zeros)); - /* All zeros — disk usage should be minimal */ - log_debug("%s all-zeros sparse: apparent=%jd disk=%jd", - compression, (intmax_t) zst.st_size, (intmax_t) zst.st_blocks * 512); - if (lseek(zdecompressed, 0, SEEK_HOLE) < zst.st_size) - assert_se(zst.st_blocks * 512 < zst.st_size); + pattern_src[] = "/tmp/systemd-test.sparse-src.XXXXXX", + pattern_compressed[] = "/tmp/systemd-test.sparse-compressed.XXXXXX", + pattern_decompressed[] = "/tmp/systemd-test.sparse-decompressed.XXXXXX"; + /* Create a sparse-like input: 4K of data, 64K of zeros, 4K of data, 64K trailing zeros. + * Total apparent size: 136K, but most of it is zeros. */ + uint8_t data_block[4096]; + struct stat st_src, st_decompressed; + uint64_t uncompressed_size; + + log_debug("/* testing %s sparse decompression */", compression_to_string(c)); + + random_bytes(data_block, sizeof(data_block)); + + ASSERT_OK(src = mkostemp_safe(pattern_src)); + + /* Write: 4K data, 64K zeros, 4K data, 64K zeros */ + ASSERT_OK(loop_write(src, data_block, sizeof(data_block))); + ASSERT_OK_ERRNO(ftruncate(src, sizeof(data_block) + 65536)); + ASSERT_OK_ERRNO(lseek(src, sizeof(data_block) + 65536, SEEK_SET)); + ASSERT_OK(loop_write(src, data_block, sizeof(data_block))); + ASSERT_OK_ERRNO(ftruncate(src, 2 * sizeof(data_block) + 2 * 65536)); + ASSERT_EQ(lseek(src, 0, SEEK_SET), (off_t) 0); + + ASSERT_OK_ERRNO(fstat(src, &st_src)); + ASSERT_EQ(st_src.st_size, 2 * (off_t) sizeof(data_block) + 2 * 65536); + + /* Compress */ + ASSERT_OK(compressed = mkostemp_safe(pattern_compressed)); + ASSERT_OK(compress_stream(c, src, compressed, -1, &uncompressed_size)); + ASSERT_EQ((uint64_t) st_src.st_size, uncompressed_size); + + /* Decompress to a regular file (sparse writes auto-detected) */ + ASSERT_OK(decompressed = mkostemp_safe(pattern_decompressed)); + ASSERT_EQ(lseek(compressed, 0, SEEK_SET), (off_t) 0); + ASSERT_OK_ZERO(decompress_stream(c, compressed, decompressed, st_src.st_size)); + + /* Verify apparent size matches */ + ASSERT_OK_ERRNO(fstat(decompressed, &st_decompressed)); + ASSERT_EQ(st_decompressed.st_size, st_src.st_size); + + /* Verify content matches by comparing bytes */ + ASSERT_EQ(lseek(src, 0, SEEK_SET), (off_t) 0); + ASSERT_EQ(lseek(decompressed, 0, SEEK_SET), (off_t) 0); + + for (off_t offset = 0; offset < st_src.st_size;) { + uint8_t buf_src[4096], buf_dst[4096]; + size_t to_read = MIN((size_t) (st_src.st_size - offset), sizeof(buf_src)); + + ASSERT_EQ(loop_read(src, buf_src, to_read, true), (ssize_t) to_read); + ASSERT_EQ(loop_read(decompressed, buf_dst, to_read, true), (ssize_t) to_read); + ASSERT_EQ(memcmp(buf_src, buf_dst, to_read), 0); + offset += to_read; + } + + /* Verify the decompressed file is actually sparse (uses less disk than apparent size). + * st_blocks is in 512-byte units. The file has 128K of zeros, so disk usage should be + * noticeably less than the apparent size if sparse writes worked. + * Only assert if the filesystem supports holes (SEEK_HOLE). */ + log_debug("%s sparse decompression: apparent=%jd disk=%jd", + compression_to_string(c), + (intmax_t) st_decompressed.st_size, + (intmax_t) st_decompressed.st_blocks * 512); + if (lseek(decompressed, 0, SEEK_HOLE) < st_decompressed.st_size) + ASSERT_LT(st_decompressed.st_blocks * 512, st_decompressed.st_size); else log_debug("Filesystem does not support holes, skipping sparsity check"); - } - /* Test data ending with non-zero bytes: ftruncate should be a no-op */ - log_debug("/* testing %s sparse decompression ending with data */", compression); - { - _cleanup_close_ int dsrc = -EBADF, dcompressed = -EBADF, ddecompressed = -EBADF; - _cleanup_(unlink_tempfilep) char - dp_src[] = "/tmp/systemd-test.sparse-end-src.XXXXXX", - dp_compressed[] = "/tmp/systemd-test.sparse-end-compressed.XXXXXX", - dp_decompressed[] = "/tmp/systemd-test.sparse-end-decompressed.XXXXXX"; - struct stat dst; - uint64_t dsize; - uint8_t zeros[65536] = {}; - - /* 64K zeros followed by 4K random data */ - assert_se((dsrc = mkostemp_safe(dp_src)) >= 0); - assert_se(loop_write(dsrc, zeros, sizeof(zeros)) >= 0); - assert_se(loop_write(dsrc, data_block, sizeof(data_block)) >= 0); - assert_se(lseek(dsrc, 0, SEEK_SET) == 0); - - assert_se((dcompressed = mkostemp_safe(dp_compressed)) >= 0); - ASSERT_OK(compress(dsrc, dcompressed, -1, &dsize)); - assert_se(dsize == sizeof(zeros) + sizeof(data_block)); - - assert_se((ddecompressed = mkostemp_safe(dp_decompressed)) >= 0); - assert_se(lseek(dcompressed, 0, SEEK_SET) == 0); - assert_se(decompress(dcompressed, ddecompressed, dsize) == 0); - - assert_se(fstat(ddecompressed, &dst) >= 0); - assert_se(dst.st_size == (off_t)(sizeof(zeros) + sizeof(data_block))); + /* Test all-zeros input: entire output should be a hole */ + log_debug("/* testing %s sparse decompression of all-zeros */", compression_to_string(c)); + { + _cleanup_close_ int zsrc = -EBADF, zcompressed = -EBADF, zdecompressed = -EBADF; + _cleanup_(unlink_tempfilep) char + zp_src[] = "/tmp/systemd-test.sparse-zero-src.XXXXXX", + zp_compressed[] = "/tmp/systemd-test.sparse-zero-compressed.XXXXXX", + zp_decompressed[] = "/tmp/systemd-test.sparse-zero-decompressed.XXXXXX"; + struct stat zst; + uint64_t zsize; + uint8_t zeros[65536] = {}; + + ASSERT_OK(zsrc = mkostemp_safe(zp_src)); + ASSERT_OK(loop_write(zsrc, zeros, sizeof(zeros))); + ASSERT_EQ(lseek(zsrc, 0, SEEK_SET), (off_t) 0); + + ASSERT_OK(zcompressed = mkostemp_safe(zp_compressed)); + ASSERT_OK(compress_stream(c, zsrc, zcompressed, -1, &zsize)); + ASSERT_EQ(zsize, (uint64_t) sizeof(zeros)); + + ASSERT_OK(zdecompressed = mkostemp_safe(zp_decompressed)); + ASSERT_EQ(lseek(zcompressed, 0, SEEK_SET), (off_t) 0); + ASSERT_OK_ZERO(decompress_stream(c, zcompressed, zdecompressed, sizeof(zeros))); + + ASSERT_OK_ERRNO(fstat(zdecompressed, &zst)); + ASSERT_EQ(zst.st_size, (off_t) sizeof(zeros)); + /* All zeros — disk usage should be minimal */ + log_debug("%s all-zeros sparse: apparent=%jd disk=%jd", + compression_to_string(c), (intmax_t) zst.st_size, (intmax_t) zst.st_blocks * 512); + if (lseek(zdecompressed, 0, SEEK_HOLE) < zst.st_size) + ASSERT_LT(zst.st_blocks * 512, zst.st_size); + else + log_debug("Filesystem does not support holes, skipping sparsity check"); + } + + /* Test data ending with non-zero bytes: ftruncate should be a no-op */ + log_debug("/* testing %s sparse decompression ending with data */", compression_to_string(c)); + { + _cleanup_close_ int dsrc = -EBADF, dcompressed = -EBADF, ddecompressed = -EBADF; + _cleanup_(unlink_tempfilep) char + dp_src[] = "/tmp/systemd-test.sparse-end-src.XXXXXX", + dp_compressed[] = "/tmp/systemd-test.sparse-end-compressed.XXXXXX", + dp_decompressed[] = "/tmp/systemd-test.sparse-end-decompressed.XXXXXX"; + struct stat dst; + uint64_t dsize; + uint8_t zeros[65536] = {}; + + /* 64K zeros followed by 4K random data */ + ASSERT_OK(dsrc = mkostemp_safe(dp_src)); + ASSERT_OK(loop_write(dsrc, zeros, sizeof(zeros))); + ASSERT_OK(loop_write(dsrc, data_block, sizeof(data_block))); + ASSERT_EQ(lseek(dsrc, 0, SEEK_SET), (off_t) 0); + + ASSERT_OK(dcompressed = mkostemp_safe(dp_compressed)); + ASSERT_OK(compress_stream(c, dsrc, dcompressed, -1, &dsize)); + ASSERT_EQ(dsize, (uint64_t)(sizeof(zeros) + sizeof(data_block))); + + ASSERT_OK(ddecompressed = mkostemp_safe(dp_decompressed)); + ASSERT_EQ(lseek(dcompressed, 0, SEEK_SET), (off_t) 0); + ASSERT_OK_ZERO(decompress_stream(c, dcompressed, ddecompressed, dsize)); + + ASSERT_OK_ERRNO(fstat(ddecompressed, &dst)); + ASSERT_EQ(dst.st_size, (off_t)(sizeof(zeros) + sizeof(data_block))); + } } } -#endif -#if HAVE_LZ4 -extern DLSYM_PROTOTYPE(LZ4_compress_default); -extern DLSYM_PROTOTYPE(LZ4_decompress_safe); -extern DLSYM_PROTOTYPE(LZ4_decompress_safe_partial); -extern DLSYM_PROTOTYPE(LZ4_versionNumber); +TEST(compressor_decompressor_push_api) { + for (Compression c = 0; c < _COMPRESSION_MAX; c++) { + if (c == COMPRESSION_NONE || !compression_supported(c)) + continue; -static void test_lz4_decompress_partial(void) { - char buf[20000], buf2[100]; - size_t buf_size = sizeof(buf), compressed; - int r; - _cleanup_free_ char *huge = NULL; + log_info("/* testing %s Compressor/Decompressor push API */", compression_to_string(c)); - log_debug("/* %s */", __func__); + _cleanup_(compressor_freep) Compressor *compressor = NULL; + _cleanup_(compressor_freep) Decompressor *decompressor = NULL; + _cleanup_free_ void *compressed = NULL, *finish_buf = NULL; + size_t compressed_size = 0, compressed_alloc = 0; + size_t finish_size = 0, finish_alloc = 0; - assert_se(huge = malloc(HUGE_SIZE)); - memcpy(huge, "HUGE=", STRLEN("HUGE=")); - memset(&huge[STRLEN("HUGE=")], 'x', HUGE_SIZE - STRLEN("HUGE=") - 1); - huge[HUGE_SIZE - 1] = '\0'; + /* Compress */ + ASSERT_OK(compressor_new(&compressor, c)); + ASSERT_EQ(compressor_type(compressor), c); - r = sym_LZ4_compress_default(huge, buf, HUGE_SIZE, buf_size); - assert_se(r >= 0); - compressed = r; - log_info("Compressed %i → %zu", HUGE_SIZE, compressed); - - r = sym_LZ4_decompress_safe(buf, huge, r, HUGE_SIZE); - assert_se(r >= 0); - log_info("Decompressed → %i", r); - - r = sym_LZ4_decompress_safe_partial(buf, huge, - compressed, - 12, HUGE_SIZE); - assert_se(r >= 0); - log_info("Decompressed partial %i/%i → %i", 12, HUGE_SIZE, r); - - for (size_t size = 1; size < sizeof(buf2); size++) { - /* This failed in older lz4s but works in newer ones. */ - r = sym_LZ4_decompress_safe_partial(buf, buf2, compressed, size, size); - log_info("Decompressed partial %zu/%zu → %i (%s)", size, size, r, - r < 0 ? "bad" : "good"); - if (r >= 0 && sym_LZ4_versionNumber() >= 10803) - /* lz4 <= 1.8.2 should fail that test, let's only check for newer ones */ - assert_se(memcmp(buf2, huge, r) == 0); + ASSERT_OK(compressor_start(compressor, text, sizeof(text), &compressed, &compressed_size, &compressed_alloc)); + ASSERT_OK(compressor_finish(compressor, &finish_buf, &finish_size, &finish_alloc)); + + size_t total_compressed = compressed_size + finish_size; + _cleanup_free_ void *full_compressed = malloc(total_compressed); + ASSERT_NOT_NULL(full_compressed); + memcpy(full_compressed, compressed, compressed_size); + if (finish_size > 0) + memcpy((uint8_t*) full_compressed + compressed_size, finish_buf, finish_size); + + compressor = compressor_free(compressor); + + /* Decompress via detect + push and verify content */ + ASSERT_OK_POSITIVE(decompressor_detect(&decompressor, full_compressed, total_compressed)); + ASSERT_EQ(compressor_type(decompressor), c); + + struct decompressor_test_data result = {}; + ASSERT_OK(decompressor_push(decompressor, full_compressed, total_compressed, test_decompressor_callback, &result)); + ASSERT_EQ(result.size, sizeof(text)); + ASSERT_EQ(memcmp(result.buf, text, sizeof(text)), 0); + free(result.buf); + + decompressor = compressor_free(decompressor); } -} -#endif -int main(int argc, char *argv[]) { -#if HAVE_COMPRESSION - _unused_ const char text[] = - "text\0foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF" - "foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF"; + /* Test compressor_type on NULL */ + ASSERT_EQ(compressor_type(NULL), _COMPRESSION_INVALID); - /* The file to test compression on can be specified as the first argument */ - const char *srcfile = argc > 1 ? argv[1] : argv[0]; + /* Test decompressor_force_off */ + _cleanup_(compressor_freep) Decompressor *d = NULL; + ASSERT_OK(decompressor_force_off(&d)); + ASSERT_EQ(compressor_type(d), COMPRESSION_NONE); + d = compressor_free(d); - char data[512] = "random\0"; + /* Test decompressor_detect returning 0 on too-small input */ + ASSERT_OK_ZERO(decompressor_detect(&d, "x", 1)); + ASSERT_NULL(d); +} - _cleanup_free_ char *huge = NULL; +static int intro(void) { + srcfile = saved_argc > 1 ? saved_argv[1] : saved_argv[0]; - assert_se(huge = malloc(HUGE_SIZE)); + ASSERT_NOT_NULL(huge = malloc(HUGE_SIZE)); memcpy(huge, "HUGE=", STRLEN("HUGE=")); memset(&huge[STRLEN("HUGE=")], 'x', HUGE_SIZE - STRLEN("HUGE=") - 1); huge[HUGE_SIZE - 1] = '\0'; - test_setup_logging(LOG_DEBUG); - random_bytes(data + 7, sizeof(data) - 7); -#if HAVE_XZ - test_compress_decompress("XZ", compress_blob_xz, decompress_blob_xz, - text, sizeof(text), false); - test_compress_decompress("XZ", compress_blob_xz, decompress_blob_xz, - data, sizeof(data), true); - - test_decompress_startswith("XZ", - compress_blob_xz, decompress_startswith_xz, - text, sizeof(text), false); - test_decompress_startswith("XZ", - compress_blob_xz, decompress_startswith_xz, - data, sizeof(data), true); - test_decompress_startswith("XZ", - compress_blob_xz, decompress_startswith_xz, - huge, HUGE_SIZE, true); - - test_compress_stream("XZ", "xzcat", - compress_stream_xz, decompress_stream_xz, srcfile); - - test_decompress_stream_sparse("XZ", compress_stream_xz, decompress_stream_xz); - - test_decompress_startswith_short("XZ", compress_blob_xz, decompress_startswith_xz); - -#else - log_info("/* XZ test skipped */"); -#endif - -#if HAVE_LZ4 - if (dlopen_lz4() >= 0) { - test_compress_decompress("LZ4", compress_blob_lz4, decompress_blob_lz4, - text, sizeof(text), false); - test_compress_decompress("LZ4", compress_blob_lz4, decompress_blob_lz4, - data, sizeof(data), true); - - test_decompress_startswith("LZ4", - compress_blob_lz4, decompress_startswith_lz4, - text, sizeof(text), false); - test_decompress_startswith("LZ4", - compress_blob_lz4, decompress_startswith_lz4, - data, sizeof(data), true); - test_decompress_startswith("LZ4", - compress_blob_lz4, decompress_startswith_lz4, - huge, HUGE_SIZE, true); - - test_compress_stream("LZ4", "lz4cat", - compress_stream_lz4, decompress_stream_lz4, srcfile); - - test_decompress_stream_sparse("LZ4", compress_stream_lz4, decompress_stream_lz4); - - test_lz4_decompress_partial(); - - test_decompress_startswith_short("LZ4", compress_blob_lz4, decompress_startswith_lz4); - } else - log_error("/* Can't load liblz4 */"); -#else - log_info("/* LZ4 test skipped */"); -#endif - -#if HAVE_ZSTD - test_compress_decompress("ZSTD", compress_blob_zstd, decompress_blob_zstd, - text, sizeof(text), false); - test_compress_decompress("ZSTD", compress_blob_zstd, decompress_blob_zstd, - data, sizeof(data), true); - - test_decompress_startswith("ZSTD", - compress_blob_zstd, decompress_startswith_zstd, - text, sizeof(text), false); - test_decompress_startswith("ZSTD", - compress_blob_zstd, decompress_startswith_zstd, - data, sizeof(data), true); - test_decompress_startswith("ZSTD", - compress_blob_zstd, decompress_startswith_zstd, - huge, HUGE_SIZE, true); - - test_compress_stream("ZSTD", "zstdcat", - compress_stream_zstd, decompress_stream_zstd, srcfile); - - test_decompress_stream_sparse("ZSTD", compress_stream_zstd, decompress_stream_zstd); - - test_decompress_startswith_short("ZSTD", compress_blob_zstd, decompress_startswith_zstd); -#else - log_info("/* ZSTD test skipped */"); -#endif - return 0; -#else - return log_tests_skipped("no compression algorithm supported"); -#endif } + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/src/test/test-dlopen-so.c b/src/test/test-dlopen-so.c index 4b805326982aa..89d211263058f 100644 --- a/src/test/test-dlopen-so.c +++ b/src/test/test-dlopen-so.c @@ -42,6 +42,7 @@ static int run(int argc, char **argv) { * where .so versions change and distributions update, but systemd doesn't have the new so names * around yet. */ + ASSERT_DLOPEN(dlopen_bzip2, HAVE_BZIP2); ASSERT_DLOPEN(dlopen_bpf, HAVE_LIBBPF); ASSERT_DLOPEN(dlopen_cryptsetup, HAVE_LIBCRYPTSETUP); ASSERT_DLOPEN(dlopen_dw, HAVE_ELFUTILS); @@ -60,14 +61,15 @@ static int run(int argc, char **argv) { ASSERT_DLOPEN(dlopen_libpam, HAVE_PAM); ASSERT_DLOPEN(dlopen_libseccomp, HAVE_SECCOMP); ASSERT_DLOPEN(dlopen_libselinux, HAVE_SELINUX); + ASSERT_DLOPEN(dlopen_xz, HAVE_XZ); ASSERT_DLOPEN(dlopen_lz4, HAVE_LZ4); - ASSERT_DLOPEN(dlopen_lzma, HAVE_XZ); ASSERT_DLOPEN(dlopen_p11kit, HAVE_P11KIT); ASSERT_DLOPEN(dlopen_passwdqc, HAVE_PASSWDQC); ASSERT_DLOPEN(dlopen_pcre2, HAVE_PCRE2); ASSERT_DLOPEN(dlopen_pwquality, HAVE_PWQUALITY); ASSERT_DLOPEN(dlopen_qrencode, HAVE_QRENCODE); ASSERT_DLOPEN(dlopen_tpm2, HAVE_TPM2); + ASSERT_DLOPEN(dlopen_zlib, HAVE_ZLIB); ASSERT_DLOPEN(dlopen_zstd, HAVE_ZSTD); return 0; diff --git a/test/units/TEST-04-JOURNAL.SYSTEMD_JOURNAL_COMPRESS.sh b/test/units/TEST-04-JOURNAL.SYSTEMD_JOURNAL_COMPRESS.sh index 13ca3751cb35d..97782f9634806 100755 --- a/test/units/TEST-04-JOURNAL.SYSTEMD_JOURNAL_COMPRESS.sh +++ b/test/units/TEST-04-JOURNAL.SYSTEMD_JOURNAL_COMPRESS.sh @@ -17,6 +17,13 @@ EOF systemctl reset-failed systemd-journald.service for c in NONE XZ LZ4 ZSTD; do + # compression_to_string() returns "uncompressed" for COMPRESSION_NONE + if [[ "${c}" == NONE ]]; then + log_name="uncompressed" + else + log_name="${c,,}" + fi + cat >/run/systemd/system/systemd-journald.service.d/compress.conf <&1 | grep -F 'compress=${c}' >/dev/null; do sleep .5; done" + timeout 10 bash -c "until SYSTEMD_LOG_LEVEL=debug journalctl --verify --quiet --file /var/log/journal/$MACHINE_ID/system.journal 2>&1 | grep -F 'compress=${log_name}' >/dev/null; do sleep .5; done" # $SYSTEMD_JOURNAL_COMPRESS= also works for journal-remote if [[ -x /usr/lib/systemd/systemd-journal-remote ]]; then for cc in NONE XZ LZ4 ZSTD; do + if [[ "${cc}" == NONE ]]; then + cc_log_name="uncompressed" + else + cc_log_name="${cc,,}" + fi + rm -f /tmp/foo.journal SYSTEMD_JOURNAL_COMPRESS="${cc}" /usr/lib/systemd/systemd-journal-remote --split-mode=none -o /tmp/foo.journal --getter="journalctl -b -o export -t $ID" - SYSTEMD_LOG_LEVEL=debug journalctl --verify --quiet --file /tmp/foo.journal 2>&1 | grep -F "compress=${cc}" >/dev/null + SYSTEMD_LOG_LEVEL=debug journalctl --verify --quiet --file /tmp/foo.journal 2>&1 | grep -F "compress=${cc_log_name}" >/dev/null journalctl -t "$ID" -o cat --file /tmp/foo.journal | grep -F "hoge with ${c}" >/dev/null done fi From 1121c775260dea53ab3f1600aca012cf1d022e36 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sat, 7 Mar 2026 21:13:06 +0100 Subject: [PATCH 0864/1296] test-mempress: Migrate to new assertion macros --- src/test/test-mempress.c | 165 +++++++++++++++++++-------------------- 1 file changed, 82 insertions(+), 83 deletions(-) diff --git a/src/test/test-mempress.c b/src/test/test-mempress.c index 817eaa421f1ad..f8e58bf1e48e2 100644 --- a/src/test/test-mempress.c +++ b/src/test/test-mempress.c @@ -39,16 +39,16 @@ static void *fake_pressure_thread(void *p) { usleep_safe(150); - assert_se(write(c->fifo_fd, &(const char) { 'x' }, 1) == 1); + ASSERT_EQ(write(c->fifo_fd, &(const char) { 'x' }, 1), 1); usleep_safe(150); cfd = accept4(c->socket_fd, NULL, NULL, SOCK_CLOEXEC); - assert_se(cfd >= 0); + ASSERT_OK_ERRNO(cfd); char buf[STRLEN("hello")+1] = {}; - assert_se(read(cfd, buf, sizeof(buf)-1) == sizeof(buf)-1); + ASSERT_EQ(read(cfd, buf, sizeof(buf)-1), (ssize_t) (sizeof(buf)-1)); ASSERT_STREQ(buf, "hello"); - assert_se(write(cfd, &(const char) { 'z' }, 1) == 1); + ASSERT_EQ(write(cfd, &(const char) { 'z' }, 1), 1); return NULL; } @@ -57,15 +57,15 @@ static int fake_pressure_callback(sd_event_source *s, void *userdata) { int *value = userdata; const char *d; - assert_se(s); - assert_se(sd_event_source_get_description(s, &d) >= 0); + ASSERT_NOT_NULL(s); + ASSERT_OK(sd_event_source_get_description(s, &d)); *value *= d[0]; log_notice("memory pressure event: %s", d); if (*value == 7 * 'f' * 's') - assert_se(sd_event_exit(sd_event_source_get_event(s), 0) >= 0); + ASSERT_OK(sd_event_exit(sd_event_source_get_event(s), 0)); return 0; } @@ -73,57 +73,56 @@ static int fake_pressure_callback(sd_event_source *s, void *userdata) { TEST(fake_pressure) { _cleanup_(sd_event_source_unrefp) sd_event_source *es = NULL, *ef = NULL; _cleanup_(sd_event_unrefp) sd_event *e = NULL; - _cleanup_free_ char *j = NULL, *k = NULL; _cleanup_(rm_rf_physical_and_freep) char *tmp = NULL; _cleanup_close_ int fifo_fd = -EBADF, socket_fd = -EBADF; union sockaddr_union sa; pthread_t th; int value = 7; - assert_se(sd_event_default(&e) >= 0); + ASSERT_OK(sd_event_default(&e)); - assert_se(mkdtemp_malloc(NULL, &tmp) >= 0); + ASSERT_OK(mkdtemp_malloc(NULL, &tmp)); - assert_se(j = path_join(tmp, "fifo")); - assert_se(mkfifo(j, 0600) >= 0); + _cleanup_free_ char *j = ASSERT_NOT_NULL(path_join(tmp, "fifo")); + ASSERT_OK_ERRNO(mkfifo(j, 0600)); fifo_fd = open(j, O_CLOEXEC|O_RDWR|O_NONBLOCK); - assert_se(fifo_fd >= 0); + ASSERT_OK_ERRNO(fifo_fd); - assert_se(k = path_join(tmp, "sock")); + _cleanup_free_ char *k = ASSERT_NOT_NULL(path_join(tmp, "sock")); socket_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); - assert_se(socket_fd >= 0); - assert_se(sockaddr_un_set_path(&sa.un, k) >= 0); - assert_se(bind(socket_fd, &sa.sa, sockaddr_un_len(&sa.un)) >= 0); - assert_se(listen(socket_fd, 1) >= 0); + ASSERT_OK_ERRNO(socket_fd); + ASSERT_OK(sockaddr_un_set_path(&sa.un, k)); + ASSERT_OK_ERRNO(bind(socket_fd, &sa.sa, sockaddr_un_len(&sa.un))); + ASSERT_OK_ERRNO(listen(socket_fd, 1)); /* Ideally we'd just allocate this on the stack, but AddressSanitizer doesn't like it if threads * access each other's stack */ struct fake_pressure_context *fp = new(struct fake_pressure_context, 1); - assert_se(fp); + ASSERT_NOT_NULL(fp); *fp = (struct fake_pressure_context) { .fifo_fd = fifo_fd, .socket_fd = socket_fd, }; - assert_se(pthread_create(&th, NULL, fake_pressure_thread, TAKE_PTR(fp)) == 0); + ASSERT_EQ(pthread_create(&th, NULL, fake_pressure_thread, TAKE_PTR(fp)), 0); - assert_se(setenv("MEMORY_PRESSURE_WATCH", j, /* override= */ true) >= 0); - assert_se(unsetenv("MEMORY_PRESSURE_WRITE") >= 0); + ASSERT_OK_ERRNO(setenv("MEMORY_PRESSURE_WATCH", j, /* override= */ true)); + ASSERT_OK_ERRNO(unsetenv("MEMORY_PRESSURE_WRITE")); - assert_se(sd_event_add_memory_pressure(e, &es, fake_pressure_callback, &value) >= 0); - assert_se(sd_event_source_set_description(es, "fifo event source") >= 0); + ASSERT_OK(sd_event_add_memory_pressure(e, &es, fake_pressure_callback, &value)); + ASSERT_OK(sd_event_source_set_description(es, "fifo event source")); - assert_se(setenv("MEMORY_PRESSURE_WATCH", k, /* override= */ true) >= 0); - assert_se(setenv("MEMORY_PRESSURE_WRITE", "aGVsbG8K", /* override= */ true) >= 0); + ASSERT_OK_ERRNO(setenv("MEMORY_PRESSURE_WATCH", k, /* override= */ true)); + ASSERT_OK_ERRNO(setenv("MEMORY_PRESSURE_WRITE", "aGVsbG8K", /* override= */ true)); - assert_se(sd_event_add_memory_pressure(e, &ef, fake_pressure_callback, &value) >= 0); - assert_se(sd_event_source_set_description(ef, "socket event source") >= 0); + ASSERT_OK(sd_event_add_memory_pressure(e, &ef, fake_pressure_callback, &value)); + ASSERT_OK(sd_event_source_set_description(ef, "socket event source")); - assert_se(sd_event_loop(e) >= 0); + ASSERT_OK(sd_event_loop(e)); - assert_se(value == 7 * 'f' * 's'); + ASSERT_EQ(value, 7 * 'f' * 's'); - assert_se(pthread_join(th, NULL) == 0); + ASSERT_EQ(pthread_join(th, NULL), 0); } struct real_pressure_context { @@ -134,15 +133,15 @@ static int real_pressure_callback(sd_event_source *s, void *userdata) { struct real_pressure_context *c = ASSERT_PTR(userdata); const char *d; - assert_se(s); - assert_se(sd_event_source_get_description(s, &d) >= 0); + ASSERT_NOT_NULL(s); + ASSERT_OK(sd_event_source_get_description(s, &d)); log_notice("real_memory pressure event: %s", d); sd_event_trim_memory(); - assert_se(c->pid); - assert_se(sd_event_source_send_child_signal(c->pid, SIGKILL, NULL, 0) >= 0); + ASSERT_NOT_NULL(c->pid); + ASSERT_OK(sd_event_source_send_child_signal(c->pid, SIGKILL, NULL, 0)); c->pid = NULL; return 0; @@ -156,13 +155,13 @@ _noreturn_ static void real_pressure_eat_memory(int pipe_fd) { /* Allocates and touches 10M at a time, until runs out of memory */ char x; - assert_se(read(pipe_fd, &x, 1) == 1); /* Wait for the GO! */ + ASSERT_EQ(read(pipe_fd, &x, 1), 1); /* Wait for the GO! */ for (;;) { void *p; p = mmap(NULL, MMAP_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); - assert_se(p != MAP_FAILED); + ASSERT_TRUE(p != MAP_FAILED); log_info("Eating another %s.", FORMAT_BYTES(MMAP_SIZE)); @@ -176,16 +175,16 @@ _noreturn_ static void real_pressure_eat_memory(int pipe_fd) { } static int real_pressure_child_callback(sd_event_source *s, const siginfo_t *si, void *userdata) { - assert_se(s); - assert_se(si); + ASSERT_NOT_NULL(s); + ASSERT_NOT_NULL(si); log_notice("child dead"); - assert_se(si->si_signo == SIGCHLD); - assert_se(si->si_status == SIGKILL); - assert_se(si->si_code == CLD_KILLED); + ASSERT_EQ(si->si_signo, SIGCHLD); + ASSERT_EQ(si->si_status, SIGKILL); + ASSERT_EQ(si->si_code, CLD_KILLED); - assert_se(sd_event_exit(sd_event_source_get_event(s), 31) >= 0); + ASSERT_OK(sd_event_exit(sd_event_source_get_event(s), 31)); return 0; } @@ -205,42 +204,42 @@ TEST(real_pressure) { if (r < 0) return (void) log_tests_skipped_errno(r, "can't connect to system bus"); - assert_se(bus_wait_for_jobs_new(bus, &w) >= 0); + ASSERT_OK(bus_wait_for_jobs_new(bus, &w)); - assert_se(bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit") >= 0); - assert_se(asprintf(&scope, "test-%" PRIu64 ".scope", random_u64()) >= 0); - assert_se(sd_bus_message_append(m, "ss", scope, "fail") >= 0); - assert_se(sd_bus_message_open_container(m, 'a', "(sv)") >= 0); - assert_se(sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, 0) >= 0); - assert_se(sd_bus_message_append(m, "(sv)", "MemoryAccounting", "b", true) >= 0); - assert_se(sd_bus_message_close_container(m) >= 0); - assert_se(sd_bus_message_append(m, "a(sa(sv))", 0) >= 0); + ASSERT_OK(bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit")); + ASSERT_OK(asprintf(&scope, "test-%" PRIu64 ".scope", random_u64())); + ASSERT_OK(sd_bus_message_append(m, "ss", scope, "fail")); + ASSERT_OK(sd_bus_message_open_container(m, 'a', "(sv)")); + ASSERT_OK(sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, 0)); + ASSERT_OK(sd_bus_message_append(m, "(sv)", "MemoryAccounting", "b", true)); + ASSERT_OK(sd_bus_message_close_container(m)); + ASSERT_OK(sd_bus_message_append(m, "a(sa(sv))", 0)); r = sd_bus_call(bus, m, 0, &error, &reply); if (r < 0) return (void) log_tests_skipped_errno(r, "can't issue transient unit call"); - assert_se(sd_bus_message_read(reply, "o", &object) >= 0); + ASSERT_OK(sd_bus_message_read(reply, "o", &object)); - assert_se(bus_wait_for_jobs_one(w, object, /* flags= */ BUS_WAIT_JOBS_LOG_ERROR, /* extra_args= */ NULL) >= 0); + ASSERT_OK(bus_wait_for_jobs_one(w, object, /* flags= */ BUS_WAIT_JOBS_LOG_ERROR, /* extra_args= */ NULL)); - assert_se(sd_event_default(&e) >= 0); + ASSERT_OK(sd_event_default(&e)); - assert_se(pipe2(pipe_fd, O_CLOEXEC) >= 0); + ASSERT_OK_ERRNO(pipe2(pipe_fd, O_CLOEXEC)); _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; r = pidref_safe_fork("(eat-memory)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM, &pidref); - assert_se(r >= 0); + ASSERT_OK(r); if (r == 0) { real_pressure_eat_memory(pipe_fd[0]); _exit(EXIT_SUCCESS); } - assert_se(event_add_child_pidref(e, &cs, &pidref, WEXITED, real_pressure_child_callback, NULL) >= 0); - assert_se(sd_event_source_set_child_process_own(cs, true) >= 0); + ASSERT_OK(event_add_child_pidref(e, &cs, &pidref, WEXITED, real_pressure_child_callback, NULL)); + ASSERT_OK(sd_event_source_set_child_process_own(cs, true)); - assert_se(unsetenv("MEMORY_PRESSURE_WATCH") >= 0); - assert_se(unsetenv("MEMORY_PRESSURE_WRITE") >= 0); + ASSERT_OK_ERRNO(unsetenv("MEMORY_PRESSURE_WATCH")); + ASSERT_OK_ERRNO(unsetenv("MEMORY_PRESSURE_WRITE")); struct real_pressure_context context = { .pid = cs, @@ -250,21 +249,21 @@ TEST(real_pressure) { if (r < 0) return (void) log_tests_skipped_errno(r, "can't allocate memory pressure fd"); - assert_se(sd_event_source_set_description(es, "real pressure event source") >= 0); - assert_se(sd_event_source_set_memory_pressure_type(es, "some") == 0); - assert_se(sd_event_source_set_memory_pressure_type(es, "full") > 0); - assert_se(sd_event_source_set_memory_pressure_type(es, "full") == 0); - assert_se(sd_event_source_set_memory_pressure_type(es, "some") > 0); - assert_se(sd_event_source_set_memory_pressure_type(es, "some") == 0); - assert_se(sd_event_source_set_memory_pressure_period(es, 70 * USEC_PER_MSEC, USEC_PER_SEC) > 0); - assert_se(sd_event_source_set_memory_pressure_period(es, 70 * USEC_PER_MSEC, USEC_PER_SEC) == 0); - assert_se(sd_event_source_set_enabled(es, SD_EVENT_ONESHOT) >= 0); + ASSERT_OK(sd_event_source_set_description(es, "real pressure event source")); + ASSERT_OK_ZERO(sd_event_source_set_memory_pressure_type(es, "some")); + ASSERT_OK_POSITIVE(sd_event_source_set_memory_pressure_type(es, "full")); + ASSERT_OK_ZERO(sd_event_source_set_memory_pressure_type(es, "full")); + ASSERT_OK_POSITIVE(sd_event_source_set_memory_pressure_type(es, "some")); + ASSERT_OK_ZERO(sd_event_source_set_memory_pressure_type(es, "some")); + ASSERT_OK_POSITIVE(sd_event_source_set_memory_pressure_period(es, 70 * USEC_PER_MSEC, USEC_PER_SEC)); + ASSERT_OK_ZERO(sd_event_source_set_memory_pressure_period(es, 70 * USEC_PER_MSEC, USEC_PER_SEC)); + ASSERT_OK(sd_event_source_set_enabled(es, SD_EVENT_ONESHOT)); _cleanup_free_ char *uo = NULL; - assert_se(uo = unit_dbus_path_from_name(scope)); + ASSERT_NOT_NULL(uo = unit_dbus_path_from_name(scope)); uint64_t mcurrent = UINT64_MAX; - assert_se(sd_bus_get_property_trivial(bus, "org.freedesktop.systemd1", uo, "org.freedesktop.systemd1.Scope", "MemoryCurrent", &error, 't', &mcurrent) >= 0); + ASSERT_OK(sd_bus_get_property_trivial(bus, "org.freedesktop.systemd1", uo, "org.freedesktop.systemd1.Scope", "MemoryCurrent", &error, 't', &mcurrent)); printf("current: %" PRIu64 "\n", mcurrent); if (mcurrent == UINT64_MAX) @@ -272,14 +271,14 @@ TEST(real_pressure) { m = sd_bus_message_unref(m); - assert_se(bus_message_new_method_call(bus, &m, bus_systemd_mgr, "SetUnitProperties") >= 0); - assert_se(sd_bus_message_append(m, "sb", scope, true) >= 0); - assert_se(sd_bus_message_open_container(m, 'a', "(sv)") >= 0); - assert_se(sd_bus_message_append(m, "(sv)", "MemoryHigh", "t", mcurrent + (15 * 1024 * 1024)) >= 0); - assert_se(sd_bus_message_append(m, "(sv)", "MemoryMax", "t", mcurrent + (50 * 1024 * 1024)) >= 0); - assert_se(sd_bus_message_close_container(m) >= 0); + ASSERT_OK(bus_message_new_method_call(bus, &m, bus_systemd_mgr, "SetUnitProperties")); + ASSERT_OK(sd_bus_message_append(m, "sb", scope, true)); + ASSERT_OK(sd_bus_message_open_container(m, 'a', "(sv)")); + ASSERT_OK(sd_bus_message_append(m, "(sv)", "MemoryHigh", "t", mcurrent + (15 * 1024 * 1024))); + ASSERT_OK(sd_bus_message_append(m, "(sv)", "MemoryMax", "t", mcurrent + (50 * 1024 * 1024))); + ASSERT_OK(sd_bus_message_close_container(m)); - assert_se(sd_bus_call(bus, m, 0, NULL, NULL) >= 0); + ASSERT_OK(sd_bus_call(bus, m, 0, NULL, NULL)); /* Generate some memory allocations via mempool */ #define NN (1024) @@ -291,12 +290,12 @@ TEST(real_pressure) { free(h); /* Now start eating memory */ - assert_se(write(pipe_fd[1], &(const char) { 'x' }, 1) == 1); + ASSERT_EQ(write(pipe_fd[1], &(const char) { 'x' }, 1), 1); - assert_se(sd_event_loop(e) >= 0); + ASSERT_OK(sd_event_loop(e)); int ex = 0; - assert_se(sd_event_get_exit_code(e, &ex) >= 0); - assert_se(ex == 31); + ASSERT_OK(sd_event_get_exit_code(e, &ex)); + ASSERT_EQ(ex, 31); } static int outro(void) { From b3bb4fefde2e116b663c05c29ada8496b97c3b0b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sat, 7 Mar 2026 21:25:09 +0100 Subject: [PATCH 0865/1296] test-mempress: Support unprivileged operation --- man/rules/meson.build | 5 +- man/sd_event_add_memory_pressure.xml | 86 ++++- src/basic/psi-util.c | 21 ++ src/basic/psi-util.h | 26 +- src/core/exec-invoke.c | 8 +- src/libsystemd/libsystemd.sym | 3 + src/libsystemd/sd-event/event-source.h | 3 +- src/libsystemd/sd-event/sd-event.c | 334 +++++++++++------- src/systemd/sd-event.h | 3 + src/test/meson.build | 2 +- src/test/{test-mempress.c => test-pressure.c} | 214 +++++++++-- 11 files changed, 523 insertions(+), 182 deletions(-) rename src/test/{test-mempress.c => test-pressure.c} (58%) diff --git a/man/rules/meson.build b/man/rules/meson.build index aa2653ce0d82e..81e7ef4f88262 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -608,7 +608,10 @@ manpages = [ ''], ['sd_event_add_memory_pressure', '3', - ['sd_event_source_set_memory_pressure_period', + ['sd_event_add_cpu_pressure', + 'sd_event_source_set_cpu_pressure_period', + 'sd_event_source_set_cpu_pressure_type', + 'sd_event_source_set_memory_pressure_period', 'sd_event_source_set_memory_pressure_type', 'sd_event_trim_memory'], ''], diff --git a/man/sd_event_add_memory_pressure.xml b/man/sd_event_add_memory_pressure.xml index b112855f061b0..1e6b734738f6c 100644 --- a/man/sd_event_add_memory_pressure.xml +++ b/man/sd_event_add_memory_pressure.xml @@ -21,7 +21,11 @@ sd_event_source_set_memory_pressure_period sd_event_trim_memory - Add and configure an event source run as result of memory pressure + sd_event_add_cpu_pressure + sd_event_source_set_cpu_pressure_type + sd_event_source_set_cpu_pressure_period + + Add and configure an event source run as result of memory or CPU pressure @@ -51,6 +55,27 @@ uint64_t window_usec + + int sd_event_add_cpu_pressure + sd_event *event + sd_event_source **ret_source + sd_event_handler_t handler + void *userdata + + + + int sd_event_source_set_cpu_pressure_type + sd_event_source *source + const char *type + + + + int sd_event_source_set_cpu_pressure_period + sd_event_source *source + uint64_t threshold_usec + uint64_t window_usec + + int sd_event_trim_memory void @@ -62,12 +87,14 @@ Description sd_event_add_memory_pressure() adds a new event source that is triggered - whenever memory pressure is seen. This functionality is built around the Linux kernel's sd_event_add_cpu_pressure() adds a new event source that is triggered whenever CPU + pressure is seen. This functionality is built around the Linux kernel's Pressure Stall Information (PSI) logic. - Expects an event loop object as first parameter, and returns the allocated event source object in - the second parameter, on success. The handler parameter is a function to call when - memory pressure is seen, or NULL. The handler function will be passed the + Both functions expect an event loop object as first parameter, and return the allocated event source + object in the second parameter, on success. The handler parameter is a function to + call when pressure is seen, or NULL. The handler function will be passed the userdata pointer, which may be chosen freely by the caller. The handler may return negative to signal an error (see below), other return values are ignored. If handler is NULL, a default handler that compacts allocation @@ -83,12 +110,13 @@ sd_event_source_set_enabled3 with SD_EVENT_OFF. - If the second parameter of sd_event_add_memory_pressure() is + If the second parameter of sd_event_add_memory_pressure() or + sd_event_add_cpu_pressure() is NULL no reference to the event source object is returned. In this case, the event source is considered "floating", and will be destroyed implicitly when the event loop itself is destroyed. - The event source will fire according to the following logic: + The memory pressure event source will fire according to the following logic: If the @@ -111,6 +139,13 @@ /proc/pressure/memory is watched instead. + The CPU pressure event source follows the same logic, but uses the + $CPU_PRESSURE_WATCH/$CPU_PRESSURE_WRITE environment variables, + the cpu.pressure cgroup file, and the system-wide PSI interface file + /proc/pressure/cpu instead. Note that /proc/pressure/cpu only + provides the some line, not the full line, so only + some is valid when watching at the system level. + Or in other words: preferably any explicit configuration passed in by an invoking service manager (or similar) is used as notification source, before falling back to local notifications of the service, and finally to global notifications of the system. @@ -143,7 +178,7 @@ The sd_event_source_set_memory_pressure_type() and sd_event_source_set_memory_pressure_period() functions can be used to fine-tune the - PSI parameters for pressure notifications. The former takes either some, + PSI parameters for memory pressure notifications. The former takes either some, full as second parameter, the latter takes threshold and period times in microseconds as parameters. For details about these three parameters see the PSI documentation. Note that these two calls must be invoked immediately after allocating the event source, as they must be configured before @@ -152,6 +187,16 @@ environment variables (or in other words: configuration supplied by a service manager wins over internal settings). + Similarly, sd_event_source_set_cpu_pressure_type() and + sd_event_source_set_cpu_pressure_period() can be used to fine-tune the PSI + parameters for CPU pressure notifications. They work identically to their memory pressure counterparts. + The type parameter takes either some or full, and the period + function takes threshold and period times in microseconds. The same constraints apply: these calls must + be invoked immediately after allocating the event source, and will fail if CPU pressure parameterization + has been passed in via the + $CPU_PRESSURE_WATCH/$CPU_PRESSURE_WRITE environment + variables. + The sd_event_trim_memory() function releases various internal allocation caches maintained by libsystemd and then invokes glibc's malloc_trim3. This @@ -197,8 +242,9 @@ -EHOSTDOWN - The $MEMORY_PRESSURE_WATCH variable has been set to the literal - string /dev/null, in order to explicitly disable memory pressure + The $MEMORY_PRESSURE_WATCH or + $CPU_PRESSURE_WATCH variable has been set to the literal + string /dev/null, in order to explicitly disable pressure handling. @@ -207,7 +253,8 @@ -EBADMSG - The $MEMORY_PRESSURE_WATCH variable has been set to an invalid + The $MEMORY_PRESSURE_WATCH or + $CPU_PRESSURE_WATCH variable has been set to an invalid string, for example a relative rather than an absolute path. @@ -216,7 +263,8 @@ -ENOTTY - The $MEMORY_PRESSURE_WATCH variable points to a regular file + The $MEMORY_PRESSURE_WATCH or + $CPU_PRESSURE_WATCH variable points to a regular file outside of the procfs or cgroupfs file systems. @@ -225,8 +273,9 @@ -EOPNOTSUPP - No configuration via $MEMORY_PRESSURE_WATCH has been specified - and the local kernel does not support the PSI interface. + No configuration via $MEMORY_PRESSURE_WATCH or + $CPU_PRESSURE_WATCH has been specified and the local kernel does not support the + PSI interface. @@ -234,8 +283,10 @@ -EBUSY - This is returned by sd_event_source_set_memory_pressure_type() - and sd_event_source_set_memory_pressure_period() if invoked on event sources + This is returned by sd_event_source_set_memory_pressure_type(), + sd_event_source_set_memory_pressure_period(), + sd_event_source_set_cpu_pressure_type(), + and sd_event_source_set_cpu_pressure_period() if invoked on event sources at a time later than immediately after allocating them. @@ -277,6 +328,9 @@ sd_event_source_set_memory_pressure_type(), sd_event_source_set_memory_pressure_period(), and sd_event_trim_memory() were added in version 254. + sd_event_add_cpu_pressure(), + sd_event_source_set_cpu_pressure_type(), and + sd_event_source_set_cpu_pressure_period() were added in version 261. diff --git a/src/basic/psi-util.c b/src/basic/psi-util.c index df1ccbc1b20fb..cf05485dc7b67 100644 --- a/src/basic/psi-util.c +++ b/src/basic/psi-util.c @@ -10,6 +10,7 @@ #include "fileio.h" #include "parse-util.h" #include "psi-util.h" +#include "string-table.h" #include "string-util.h" #include "strv.h" @@ -104,6 +105,26 @@ int read_resource_pressure(const char *path, PressureType type, ResourcePressure return 0; } +const PressureResourceInfo pressure_resource_info[_PRESSURE_RESOURCE_MAX] = { + [PRESSURE_MEMORY] = { + .name = "memory", + .env_watch = "MEMORY_PRESSURE_WATCH", + .env_write = "MEMORY_PRESSURE_WRITE", + }, + [PRESSURE_CPU] = { + .name = "cpu", + .env_watch = "CPU_PRESSURE_WATCH", + .env_write = "CPU_PRESSURE_WRITE", + }, +}; + +static const char* const pressure_resource_table[_PRESSURE_RESOURCE_MAX] = { + [PRESSURE_MEMORY] = "memory", + [PRESSURE_CPU] = "cpu", +}; + +DEFINE_STRING_TABLE_LOOKUP(pressure_resource, PressureResource); + int is_pressure_supported(void) { static thread_local int cached = -1; int r; diff --git a/src/basic/psi-util.h b/src/basic/psi-util.h index f5e79960a8159..aed74ef742d5a 100644 --- a/src/basic/psi-util.h +++ b/src/basic/psi-util.h @@ -9,6 +9,13 @@ typedef enum PressureType { PRESSURE_TYPE_FULL, } PressureType; +typedef enum PressureResource { + PRESSURE_MEMORY, + PRESSURE_CPU, + _PRESSURE_RESOURCE_MAX, + _PRESSURE_RESOURCE_INVALID = -EINVAL, +} PressureResource; + /* Averages are stored in fixed-point with 11 bit fractions */ typedef struct ResourcePressure { loadavg_t avg10; @@ -27,7 +34,18 @@ int read_resource_pressure(const char *path, PressureType type, ResourcePressure /* Was the kernel compiled with CONFIG_PSI=y? 1 if yes, 0 if not, negative on error. */ int is_pressure_supported(void); -/* Default parameters for memory pressure watch logic in sd-event and PID 1 */ -#define MEMORY_PRESSURE_DEFAULT_TYPE "some" -#define MEMORY_PRESSURE_DEFAULT_THRESHOLD_USEC (200 * USEC_PER_MSEC) -#define MEMORY_PRESSURE_DEFAULT_WINDOW_USEC (2 * USEC_PER_SEC) +/* Metadata for each pressure resource type, for use in sd-event and PID 1 */ +typedef struct PressureResourceInfo { + const char *name; /* "memory", "cpu", "io" */ + const char *env_watch; /* "MEMORY_PRESSURE_WATCH", etc. */ + const char *env_write; /* "MEMORY_PRESSURE_WRITE", etc. */ +} PressureResourceInfo; + +extern const PressureResourceInfo pressure_resource_info[_PRESSURE_RESOURCE_MAX]; + +DECLARE_STRING_TABLE_LOOKUP(pressure_resource, PressureResource); + +/* Default parameters for pressure watch logic in sd-event and PID 1 */ +#define PRESSURE_DEFAULT_TYPE "some" +#define PRESSURE_DEFAULT_THRESHOLD_USEC (200 * USEC_PER_MSEC) +#define PRESSURE_DEFAULT_WINDOW_USEC (2 * USEC_PER_SEC) diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index b91a964cdd6ab..7500888c414a3 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -2224,10 +2224,10 @@ static int build_environment( _cleanup_free_ char *b = NULL, *x = NULL; if (asprintf(&b, "%s " USEC_FMT " " USEC_FMT, - MEMORY_PRESSURE_DEFAULT_TYPE, - cgroup_context->memory_pressure_threshold_usec == USEC_INFINITY ? MEMORY_PRESSURE_DEFAULT_THRESHOLD_USEC : - CLAMP(cgroup_context->memory_pressure_threshold_usec, 1U, MEMORY_PRESSURE_DEFAULT_WINDOW_USEC), - MEMORY_PRESSURE_DEFAULT_WINDOW_USEC) < 0) + PRESSURE_DEFAULT_TYPE, + cgroup_context->memory_pressure_threshold_usec == USEC_INFINITY ? PRESSURE_DEFAULT_THRESHOLD_USEC : + CLAMP(cgroup_context->memory_pressure_threshold_usec, 1U, PRESSURE_DEFAULT_WINDOW_USEC), + PRESSURE_DEFAULT_WINDOW_USEC) < 0) return -ENOMEM; if (base64mem(b, strlen(b) + 1, &x) < 0) diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym index 619bcf820c875..5f5eca60833b2 100644 --- a/src/libsystemd/libsystemd.sym +++ b/src/libsystemd/libsystemd.sym @@ -1096,4 +1096,7 @@ global: sd_varlink_call_and_upgrade; sd_varlink_reply_and_upgrade; sd_varlink_set_sentinel; + sd_event_add_cpu_pressure; + sd_event_source_set_cpu_pressure_type; + sd_event_source_set_cpu_pressure_period; } LIBSYSTEMD_260; diff --git a/src/libsystemd/sd-event/event-source.h b/src/libsystemd/sd-event/event-source.h index e4dc456fae8ea..c7d5ba166da31 100644 --- a/src/libsystemd/sd-event/event-source.h +++ b/src/libsystemd/sd-event/event-source.h @@ -26,6 +26,7 @@ typedef enum EventSourceType { SOURCE_WATCHDOG, SOURCE_INOTIFY, SOURCE_MEMORY_PRESSURE, + SOURCE_CPU_PRESSURE, _SOURCE_EVENT_SOURCE_TYPE_MAX, _SOURCE_EVENT_SOURCE_TYPE_INVALID = -EINVAL, } EventSourceType; @@ -144,7 +145,7 @@ struct sd_event_source { size_t write_buffer_size; uint32_t events, revents; LIST_FIELDS(sd_event_source, write_list); - } memory_pressure; + } pressure; }; }; diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index 19feff5668852..4b539a35cf60b 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -76,6 +76,7 @@ static const char* const event_source_type_table[_SOURCE_EVENT_SOURCE_TYPE_MAX] [SOURCE_WATCHDOG] = "watchdog", [SOURCE_INOTIFY] = "inotify", [SOURCE_MEMORY_PRESSURE] = "memory-pressure", + [SOURCE_CPU_PRESSURE] = "cpu-pressure", }; DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int); @@ -99,7 +100,8 @@ DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int); SOURCE_SIGNAL, \ SOURCE_DEFER, \ SOURCE_INOTIFY, \ - SOURCE_MEMORY_PRESSURE) + SOURCE_MEMORY_PRESSURE, \ + SOURCE_CPU_PRESSURE) /* This is used to assert that we didn't pass an unexpected source type to event_source_time_prioq_put(). * Time sources and ratelimited sources can be passed, so effectively this is the same as the @@ -144,8 +146,8 @@ struct sd_event { /* A list of inotify objects that already have events buffered which aren't processed yet */ LIST_HEAD(InotifyData, buffered_inotify_data_list); - /* A list of memory pressure event sources that still need their subscription string written */ - LIST_HEAD(sd_event_source, memory_pressure_write_list); + /* A list of pressure event sources that still need their subscription string written */ + LIST_HEAD(sd_event_source, pressure_write_list); uint64_t origin_id; @@ -564,63 +566,65 @@ static int source_child_pidfd_register(sd_event_source *s, int enabled) { return 0; } -static void source_memory_pressure_unregister(sd_event_source *s) { +#define EVENT_SOURCE_IS_PRESSURE(s) IN_SET((s)->type, SOURCE_MEMORY_PRESSURE, SOURCE_CPU_PRESSURE) + +static void source_pressure_unregister(sd_event_source *s) { assert(s); - assert(s->type == SOURCE_MEMORY_PRESSURE); + assert(EVENT_SOURCE_IS_PRESSURE(s)); if (event_origin_changed(s->event)) return; - if (!s->memory_pressure.registered) + if (!s->pressure.registered) return; - if (epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->memory_pressure.fd, NULL) < 0) + if (epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->pressure.fd, NULL) < 0) log_debug_errno(errno, "Failed to remove source %s (type %s) from epoll, ignoring: %m", strna(s->description), event_source_type_to_string(s->type)); - s->memory_pressure.registered = false; + s->pressure.registered = false; } -static int source_memory_pressure_register(sd_event_source *s, int enabled) { +static int source_pressure_register(sd_event_source *s, int enabled) { assert(s); - assert(s->type == SOURCE_MEMORY_PRESSURE); + assert(EVENT_SOURCE_IS_PRESSURE(s)); assert(enabled != SD_EVENT_OFF); struct epoll_event ev = { - .events = s->memory_pressure.write_buffer_size > 0 ? EPOLLOUT : - (s->memory_pressure.events | (enabled == SD_EVENT_ONESHOT ? EPOLLONESHOT : 0)), + .events = s->pressure.write_buffer_size > 0 ? EPOLLOUT : + (s->pressure.events | (enabled == SD_EVENT_ONESHOT ? EPOLLONESHOT : 0)), .data.ptr = s, }; if (epoll_ctl(s->event->epoll_fd, - s->memory_pressure.registered ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, - s->memory_pressure.fd, &ev) < 0) + s->pressure.registered ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, + s->pressure.fd, &ev) < 0) return -errno; - s->memory_pressure.registered = true; + s->pressure.registered = true; return 0; } -static void source_memory_pressure_add_to_write_list(sd_event_source *s) { +static void source_pressure_add_to_write_list(sd_event_source *s) { assert(s); - assert(s->type == SOURCE_MEMORY_PRESSURE); + assert(EVENT_SOURCE_IS_PRESSURE(s)); - if (s->memory_pressure.in_write_list) + if (s->pressure.in_write_list) return; - LIST_PREPEND(memory_pressure.write_list, s->event->memory_pressure_write_list, s); - s->memory_pressure.in_write_list = true; + LIST_PREPEND(pressure.write_list, s->event->pressure_write_list, s); + s->pressure.in_write_list = true; } -static void source_memory_pressure_remove_from_write_list(sd_event_source *s) { +static void source_pressure_remove_from_write_list(sd_event_source *s) { assert(s); - assert(s->type == SOURCE_MEMORY_PRESSURE); + assert(EVENT_SOURCE_IS_PRESSURE(s)); - if (!s->memory_pressure.in_write_list) + if (!s->pressure.in_write_list) return; - LIST_REMOVE(memory_pressure.write_list, s->event->memory_pressure_write_list, s); - s->memory_pressure.in_write_list = false; + LIST_REMOVE(pressure.write_list, s->event->pressure_write_list, s); + s->pressure.in_write_list = false; } static clockid_t event_source_type_to_clock(EventSourceType t) { @@ -1047,8 +1051,9 @@ static void source_disconnect(sd_event_source *s) { } case SOURCE_MEMORY_PRESSURE: - source_memory_pressure_remove_from_write_list(s); - source_memory_pressure_unregister(s); + case SOURCE_CPU_PRESSURE: + source_pressure_remove_from_write_list(s); + source_pressure_unregister(s); break; default: @@ -1111,9 +1116,9 @@ static sd_event_source* source_free(sd_event_source *s) { s->child.pidfd = safe_close(s->child.pidfd); } - if (s->type == SOURCE_MEMORY_PRESSURE) { - s->memory_pressure.fd = safe_close(s->memory_pressure.fd); - s->memory_pressure.write_buffer = mfree(s->memory_pressure.write_buffer); + if (EVENT_SOURCE_IS_PRESSURE(s)) { + s->pressure.fd = safe_close(s->pressure.fd); + s->pressure.write_buffer = mfree(s->pressure.write_buffer); } if (s->destroy_callback) @@ -1191,7 +1196,8 @@ static sd_event_source* source_new(sd_event *e, bool floating, EventSourceType t [SOURCE_POST] = endoffsetof_field(sd_event_source, post), [SOURCE_EXIT] = endoffsetof_field(sd_event_source, exit), [SOURCE_INOTIFY] = endoffsetof_field(sd_event_source, inotify), - [SOURCE_MEMORY_PRESSURE] = endoffsetof_field(sd_event_source, memory_pressure), + [SOURCE_MEMORY_PRESSURE] = endoffsetof_field(sd_event_source, pressure), + [SOURCE_CPU_PRESSURE] = endoffsetof_field(sd_event_source, pressure), }; sd_event_source *s; @@ -1917,17 +1923,21 @@ static int memory_pressure_callback(sd_event_source *s, void *userdata) { return 0; } -_public_ int sd_event_add_memory_pressure( +static int event_add_pressure( sd_event *e, sd_event_source **ret, sd_event_handler_t callback, - void *userdata) { + void *userdata, + EventSourceType type, + sd_event_handler_t default_callback, + PressureResource resource) { _cleanup_free_ char *w = NULL; _cleanup_(source_freep) sd_event_source *s = NULL; _cleanup_close_ int path_fd = -EBADF, fd = -EBADF; _cleanup_free_ void *write_buffer = NULL; - const char *watch, *watch_fallback = NULL, *env; + _cleanup_free_ char *watch_fallback = NULL; + const char *watch, *env; size_t write_buffer_size = 0; struct stat st; uint32_t events; @@ -1939,32 +1949,35 @@ _public_ int sd_event_add_memory_pressure( assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); assert_return(!event_origin_changed(e), -ECHILD); + assert(resource >= 0 && resource < _PRESSURE_RESOURCE_MAX); + const PressureResourceInfo *info = &pressure_resource_info[resource]; + if (!callback) - callback = memory_pressure_callback; + callback = default_callback; - s = source_new(e, !ret, SOURCE_MEMORY_PRESSURE); + s = source_new(e, !ret, type); if (!s) return -ENOMEM; s->wakeup = WAKEUP_EVENT_SOURCE; - s->memory_pressure.callback = callback; + s->pressure.callback = callback; s->userdata = userdata; s->enabled = SD_EVENT_ON; - s->memory_pressure.fd = -EBADF; + s->pressure.fd = -EBADF; - env = secure_getenv("MEMORY_PRESSURE_WATCH"); + env = secure_getenv(info->env_watch); if (env) { if (isempty(env) || path_equal(env, "/dev/null")) return log_debug_errno(SYNTHETIC_ERRNO(EHOSTDOWN), - "Memory pressure logic is explicitly disabled via $MEMORY_PRESSURE_WATCH."); + "Pressure logic is explicitly disabled via $%s.", info->env_watch); if (!path_is_absolute(env) || !path_is_normalized(env)) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), - "$MEMORY_PRESSURE_WATCH set to invalid path: %s", env); + "$%s set to invalid path: %s", info->env_watch, env); watch = env; - env = secure_getenv("MEMORY_PRESSURE_WRITE"); + env = secure_getenv(info->env_write); if (env) { r = unbase64mem(env, &write_buffer, &write_buffer_size); if (r < 0) @@ -1980,8 +1993,8 @@ _public_ int sd_event_add_memory_pressure( if (r == 0) return -EOPNOTSUPP; - /* By default we want to watch memory pressure on the local cgroup, but we'll fall back on - * the system wide pressure if for some reason we cannot (which could be: memory controller + /* By default we want to watch pressure on the local cgroup, but we'll fall back on + * the system wide pressure if for some reason we cannot (which could be: controller * not delegated to us, or PSI simply not available in the kernel). */ _cleanup_free_ char *cg = NULL; @@ -1989,12 +2002,19 @@ _public_ int sd_event_add_memory_pressure( if (r < 0) return r; - w = path_join("/sys/fs/cgroup", cg, "memory.pressure"); + _cleanup_free_ char *cgroup_file = strjoin(info->name, ".pressure"); + if (!cgroup_file) + return -ENOMEM; + + w = path_join("/sys/fs/cgroup", cg, cgroup_file); if (!w) return -ENOMEM; watch = w; - watch_fallback = "/proc/pressure/memory"; + + watch_fallback = strjoin("/proc/pressure/", info->name); + if (!watch_fallback) + return -ENOMEM; /* Android uses three levels in its userspace low memory killer logic: * some 70000 1000000 @@ -2011,9 +2031,9 @@ _public_ int sd_event_add_memory_pressure( * kernel will allow us to do unprivileged, also in the future. */ if (asprintf((char**) &write_buffer, "%s " USEC_FMT " " USEC_FMT, - MEMORY_PRESSURE_DEFAULT_TYPE, - MEMORY_PRESSURE_DEFAULT_THRESHOLD_USEC, - MEMORY_PRESSURE_DEFAULT_WINDOW_USEC) < 0) + PRESSURE_DEFAULT_TYPE, + PRESSURE_DEFAULT_THRESHOLD_USEC, + PRESSURE_DEFAULT_WINDOW_USEC) < 0) return -ENOMEM; write_buffer_size = strlen(write_buffer) + 1; @@ -2080,24 +2100,24 @@ _public_ int sd_event_add_memory_pressure( else return -EBADF; - s->memory_pressure.fd = TAKE_FD(fd); - s->memory_pressure.write_buffer = TAKE_PTR(write_buffer); - s->memory_pressure.write_buffer_size = write_buffer_size; - s->memory_pressure.events = events; - s->memory_pressure.locked = locked; + s->pressure.fd = TAKE_FD(fd); + s->pressure.write_buffer = TAKE_PTR(write_buffer); + s->pressure.write_buffer_size = write_buffer_size; + s->pressure.events = events; + s->pressure.locked = locked; /* So here's the thing: if we are talking to PSI we need to write the watch string before adding the * fd to epoll (if we ignore this, then the watch won't work). Hence we'll not actually register the - * fd with the epoll right-away. Instead, we just add the event source to a list of memory pressure - * event sources on which writes must be executed before the first event loop iteration is - * executed. (We could also write the data here, right away, but we want to give the caller the - * freedom to call sd_event_source_set_memory_pressure_type() and - * sd_event_source_set_memory_pressure_rate() before we write it. */ - - if (s->memory_pressure.write_buffer_size > 0) - source_memory_pressure_add_to_write_list(s); + * fd with the epoll right-away. Instead, we just add the event source to a list of pressure event + * sources on which writes must be executed before the first event loop iteration is executed. (We + * could also write the data here, right away, but we want to give the caller the freedom to call + * sd_event_source_set_{memory,cpu}_pressure_type() and + * sd_event_source_set_{memory,cpu}_pressure_period() before we write it. */ + + if (s->pressure.write_buffer_size > 0) + source_pressure_add_to_write_list(s); else { - r = source_memory_pressure_register(s, s->enabled); + r = source_pressure_register(s, s->enabled); if (r < 0) return r; } @@ -2109,6 +2129,38 @@ _public_ int sd_event_add_memory_pressure( return 0; } +_public_ int sd_event_add_memory_pressure( + sd_event *e, + sd_event_source **ret, + sd_event_handler_t callback, + void *userdata) { + + return event_add_pressure( + e, ret, callback, userdata, + SOURCE_MEMORY_PRESSURE, + memory_pressure_callback, + PRESSURE_MEMORY); +} + +static int cpu_pressure_callback(sd_event_source *s, void *userdata) { + assert(s); + + return 0; +} + +_public_ int sd_event_add_cpu_pressure( + sd_event *e, + sd_event_source **ret, + sd_event_handler_t callback, + void *userdata) { + + return event_add_pressure( + e, ret, callback, userdata, + SOURCE_CPU_PRESSURE, + cpu_pressure_callback, + PRESSURE_CPU); +} + static void event_free_inotify_data(sd_event *e, InotifyData *d) { assert(e); @@ -2910,7 +2962,8 @@ static int event_source_offline( break; case SOURCE_MEMORY_PRESSURE: - source_memory_pressure_unregister(s); + case SOURCE_CPU_PRESSURE: + source_pressure_unregister(s); break; case SOURCE_TIME_REALTIME: @@ -3001,10 +3054,11 @@ static int event_source_online( break; case SOURCE_MEMORY_PRESSURE: - /* As documented in sd_event_add_memory_pressure(), we can only register the PSI fd with - * epoll after writing the watch string. */ - if (s->memory_pressure.write_buffer_size == 0) { - r = source_memory_pressure_register(s, enabled); + case SOURCE_CPU_PRESSURE: + /* As documented in sd_event_add_{memory,cpu,io}_pressure(), we can only register the PSI fd + * with epoll after writing the watch string. */ + if (s->pressure.write_buffer_size == 0) { + r = source_pressure_register(s, enabled); if (r < 0) return r; } @@ -3986,30 +4040,30 @@ static int process_inotify(sd_event *e) { return done; } -static int process_memory_pressure(sd_event_source *s, uint32_t revents) { +static int process_pressure(sd_event_source *s, uint32_t revents) { assert(s); - assert(s->type == SOURCE_MEMORY_PRESSURE); + assert(EVENT_SOURCE_IS_PRESSURE(s)); if (s->pending) - s->memory_pressure.revents |= revents; + s->pressure.revents |= revents; else - s->memory_pressure.revents = revents; + s->pressure.revents = revents; return source_set_pending(s, true); } -static int source_memory_pressure_write(sd_event_source *s) { +static int source_pressure_write(sd_event_source *s) { ssize_t n; int r; assert(s); - assert(s->type == SOURCE_MEMORY_PRESSURE); + assert(EVENT_SOURCE_IS_PRESSURE(s)); /* once we start writing, the buffer is locked, we allow no further changes. */ - s->memory_pressure.locked = true; + s->pressure.locked = true; - if (s->memory_pressure.write_buffer_size > 0) { - n = write(s->memory_pressure.fd, s->memory_pressure.write_buffer, s->memory_pressure.write_buffer_size); + if (s->pressure.write_buffer_size > 0) { + n = write(s->pressure.fd, s->pressure.write_buffer, s->pressure.write_buffer_size); if (n < 0) { if (!ERRNO_IS_TRANSIENT(errno)) { /* If kernel is built with CONFIG_PSI_DEFAULT_DISABLED it will expose PSI @@ -4018,7 +4072,7 @@ static int source_memory_pressure_write(sd_event_source *s) { * so late. Let's make the best of it, and turn off the event source like we * do for failed event source handlers. */ - log_debug_errno(errno, "Writing memory pressure settings to kernel failed, disabling memory pressure event source: %m"); + log_debug_errno(errno, "Writing pressure settings to kernel failed, disabling pressure event source: %m"); assert_se(sd_event_source_set_enabled(s, SD_EVENT_OFF) >= 0); return 0; } @@ -4030,41 +4084,41 @@ static int source_memory_pressure_write(sd_event_source *s) { assert(n >= 0); - if ((size_t) n == s->memory_pressure.write_buffer_size) { - s->memory_pressure.write_buffer = mfree(s->memory_pressure.write_buffer); + if ((size_t) n == s->pressure.write_buffer_size) { + s->pressure.write_buffer = mfree(s->pressure.write_buffer); if (n > 0) { - s->memory_pressure.write_buffer_size = 0; + s->pressure.write_buffer_size = 0; /* Update epoll events mask, since we have now written everything and don't care for EPOLLOUT anymore */ - r = source_memory_pressure_register(s, s->enabled); + r = source_pressure_register(s, s->enabled); if (r < 0) return r; } } else if (n > 0) { _cleanup_free_ void *c = NULL; - assert((size_t) n < s->memory_pressure.write_buffer_size); + assert((size_t) n < s->pressure.write_buffer_size); - c = memdup((uint8_t*) s->memory_pressure.write_buffer + n, s->memory_pressure.write_buffer_size - n); + c = memdup((uint8_t*) s->pressure.write_buffer + n, s->pressure.write_buffer_size - n); if (!c) return -ENOMEM; - free_and_replace(s->memory_pressure.write_buffer, c); - s->memory_pressure.write_buffer_size -= n; + free_and_replace(s->pressure.write_buffer, c); + s->pressure.write_buffer_size -= n; return 1; } return 0; } -static int source_memory_pressure_initiate_dispatch(sd_event_source *s) { +static int source_pressure_initiate_dispatch(sd_event_source *s) { int r; assert(s); - assert(s->type == SOURCE_MEMORY_PRESSURE); + assert(EVENT_SOURCE_IS_PRESSURE(s)); - r = source_memory_pressure_write(s); + r = source_pressure_write(s); if (r < 0) return r; if (r > 0) @@ -4072,22 +4126,22 @@ static int source_memory_pressure_initiate_dispatch(sd_event_source *s) { * function. Instead, shortcut it so that we wait for next EPOLLOUT immediately. */ /* No pending incoming IO? Then let's not continue further */ - if ((s->memory_pressure.revents & (EPOLLIN|EPOLLPRI)) == 0) { + if ((s->pressure.revents & (EPOLLIN|EPOLLPRI)) == 0) { /* Treat IO errors on the notifier the same ways errors returned from a callback */ - if ((s->memory_pressure.revents & (EPOLLHUP|EPOLLERR|EPOLLRDHUP)) != 0) + if ((s->pressure.revents & (EPOLLHUP|EPOLLERR|EPOLLRDHUP)) != 0) return -EIO; return 1; /* leave dispatch, we already processed everything */ } - if (s->memory_pressure.revents & EPOLLIN) { + if (s->pressure.revents & EPOLLIN) { uint8_t pipe_buf[PIPE_BUF]; ssize_t n; /* If the fd is readable, then flush out anything that might be queued */ - n = read(s->memory_pressure.fd, pipe_buf, sizeof(pipe_buf)); + n = read(s->pressure.fd, pipe_buf, sizeof(pipe_buf)); if (n < 0 && !ERRNO_IS_TRANSIENT(errno)) return -errno; } @@ -4158,8 +4212,8 @@ static int source_dispatch(sd_event_source *s) { if (r < 0) return r; - if (s->type == SOURCE_MEMORY_PRESSURE) { - r = source_memory_pressure_initiate_dispatch(s); + if (EVENT_SOURCE_IS_PRESSURE(s)) { + r = source_pressure_initiate_dispatch(s); if (r == -EIO) /* handle EIO errors similar to callback errors */ goto finish; if (r < 0) @@ -4254,7 +4308,8 @@ static int source_dispatch(sd_event_source *s) { } case SOURCE_MEMORY_PRESSURE: - r = s->memory_pressure.callback(s, s->userdata); + case SOURCE_CPU_PRESSURE: + r = s->pressure.callback(s, s->userdata); break; case SOURCE_WATCHDOG: @@ -4422,7 +4477,7 @@ static void event_close_inode_data_fds(sd_event *e) { } } -static int event_memory_pressure_write_list(sd_event *e) { +static int event_pressure_write_list(sd_event *e) { int r; assert(e); @@ -4430,15 +4485,15 @@ static int event_memory_pressure_write_list(sd_event *e) { for (;;) { sd_event_source *s; - s = LIST_POP(memory_pressure.write_list, e->memory_pressure_write_list); + s = LIST_POP(pressure.write_list, e->pressure_write_list); if (!s) break; - assert(s->type == SOURCE_MEMORY_PRESSURE); - assert(s->memory_pressure.write_buffer_size > 0); - s->memory_pressure.in_write_list = false; + assert(EVENT_SOURCE_IS_PRESSURE(s)); + assert(s->pressure.write_buffer_size > 0); + s->pressure.in_write_list = false; - r = source_memory_pressure_write(s); + r = source_pressure_write(s); if (r < 0) return r; } @@ -4499,7 +4554,7 @@ _public_ int sd_event_prepare(sd_event *e) { if (r < 0) return r; - r = event_memory_pressure_write_list(e); + r = event_pressure_write_list(e); if (r < 0) return r; @@ -4668,7 +4723,8 @@ static int process_epoll(sd_event *e, usec_t timeout, int64_t threshold, int64_t break; case SOURCE_MEMORY_PRESSURE: - r = process_memory_pressure(s, i->events); + case SOURCE_CPU_PRESSURE: + r = process_pressure(s, i->events); break; default: @@ -5306,27 +5362,27 @@ _public_ int sd_event_get_exit_on_idle(sd_event *e) { return e->exit_on_idle; } -_public_ int sd_event_source_set_memory_pressure_type(sd_event_source *s, const char *ty) { +static int event_source_set_pressure_type(sd_event_source *s, const char *ty) { _cleanup_free_ char *b = NULL; _cleanup_free_ void *w = NULL; assert_return(s, -EINVAL); - assert_return(s->type == SOURCE_MEMORY_PRESSURE, -EDOM); + assert_return(EVENT_SOURCE_IS_PRESSURE(s), -EDOM); assert_return(ty, -EINVAL); assert_return(!event_origin_changed(s->event), -ECHILD); if (!STR_IN_SET(ty, "some", "full")) return -EINVAL; - if (s->memory_pressure.locked) /* Refuse adjusting parameters, if caller told us how to watch for events */ + if (s->pressure.locked) /* Refuse adjusting parameters, if caller told us how to watch for events */ return -EBUSY; - char* space = memchr(s->memory_pressure.write_buffer, ' ', s->memory_pressure.write_buffer_size); + char* space = memchr(s->pressure.write_buffer, ' ', s->pressure.write_buffer_size); if (!space) return -EINVAL; - size_t l = space - (char*) s->memory_pressure.write_buffer; - b = memdup_suffix0(s->memory_pressure.write_buffer, l); + size_t l = space - (char*) s->pressure.write_buffer; + b = memdup_suffix0(s->pressure.write_buffer, l); if (!b) return -ENOMEM; if (!STR_IN_SET(b, "some", "full")) @@ -5335,26 +5391,40 @@ _public_ int sd_event_source_set_memory_pressure_type(sd_event_source *s, const if (streq(b, ty)) return 0; - size_t nl = strlen(ty) + (s->memory_pressure.write_buffer_size - l); + size_t nl = strlen(ty) + (s->pressure.write_buffer_size - l); w = new(char, nl); if (!w) return -ENOMEM; - memcpy(stpcpy(w, ty), space, (s->memory_pressure.write_buffer_size - l)); + memcpy(stpcpy(w, ty), space, (s->pressure.write_buffer_size - l)); - free_and_replace(s->memory_pressure.write_buffer, w); - s->memory_pressure.write_buffer_size = nl; - s->memory_pressure.locked = false; + free_and_replace(s->pressure.write_buffer, w); + s->pressure.write_buffer_size = nl; + s->pressure.locked = false; return 1; } -_public_ int sd_event_source_set_memory_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec) { +_public_ int sd_event_source_set_memory_pressure_type(sd_event_source *s, const char *ty) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_MEMORY_PRESSURE, -EDOM); + + return event_source_set_pressure_type(s, ty); +} + +_public_ int sd_event_source_set_cpu_pressure_type(sd_event_source *s, const char *ty) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_CPU_PRESSURE, -EDOM); + + return event_source_set_pressure_type(s, ty); +} + +static int event_source_set_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec) { _cleanup_free_ char *b = NULL; _cleanup_free_ void *w = NULL; assert_return(s, -EINVAL); - assert_return(s->type == SOURCE_MEMORY_PRESSURE, -EDOM); + assert_return(EVENT_SOURCE_IS_PRESSURE(s), -EDOM); assert_return(!event_origin_changed(s->event), -ECHILD); if (threshold_usec <= 0 || threshold_usec >= UINT64_MAX) @@ -5364,15 +5434,15 @@ _public_ int sd_event_source_set_memory_pressure_period(sd_event_source *s, uint if (threshold_usec > window_usec) return -EINVAL; - if (s->memory_pressure.locked) /* Refuse adjusting parameters, if caller told us how to watch for events */ + if (s->pressure.locked) /* Refuse adjusting parameters, if caller told us how to watch for events */ return -EBUSY; - char* space = memchr(s->memory_pressure.write_buffer, ' ', s->memory_pressure.write_buffer_size); + char* space = memchr(s->pressure.write_buffer, ' ', s->pressure.write_buffer_size); if (!space) return -EINVAL; - size_t l = space - (char*) s->memory_pressure.write_buffer; - b = memdup_suffix0(s->memory_pressure.write_buffer, l); + size_t l = space - (char*) s->pressure.write_buffer; + b = memdup_suffix0(s->pressure.write_buffer, l); if (!b) return -ENOMEM; if (!STR_IN_SET(b, "some", "full")) @@ -5386,12 +5456,26 @@ _public_ int sd_event_source_set_memory_pressure_period(sd_event_source *s, uint return -EINVAL; l = strlen(w) + 1; - if (memcmp_nn(s->memory_pressure.write_buffer, s->memory_pressure.write_buffer_size, w, l) == 0) + if (memcmp_nn(s->pressure.write_buffer, s->pressure.write_buffer_size, w, l) == 0) return 0; - free_and_replace(s->memory_pressure.write_buffer, w); - s->memory_pressure.write_buffer_size = l; - s->memory_pressure.locked = false; + free_and_replace(s->pressure.write_buffer, w); + s->pressure.write_buffer_size = l; + s->pressure.locked = false; return 1; } + +_public_ int sd_event_source_set_memory_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_MEMORY_PRESSURE, -EDOM); + + return event_source_set_pressure_period(s, threshold_usec, window_usec); +} + +_public_ int sd_event_source_set_cpu_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_CPU_PRESSURE, -EDOM); + + return event_source_set_pressure_period(s, threshold_usec, window_usec); +} diff --git a/src/systemd/sd-event.h b/src/systemd/sd-event.h index f5c79acdfdabc..71fc9504889e6 100644 --- a/src/systemd/sd-event.h +++ b/src/systemd/sd-event.h @@ -97,6 +97,7 @@ int sd_event_add_defer(sd_event *e, sd_event_source **ret, sd_event_handler_t ca int sd_event_add_post(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata); int sd_event_add_exit(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata); int sd_event_add_memory_pressure(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata); +int sd_event_add_cpu_pressure(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata); int sd_event_prepare(sd_event *e); int sd_event_wait(sd_event *e, uint64_t timeout); @@ -162,6 +163,8 @@ int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *ret); int sd_event_source_get_inotify_path(sd_event_source *s, const char **ret); int sd_event_source_set_memory_pressure_type(sd_event_source *s, const char *ty); int sd_event_source_set_memory_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec); +int sd_event_source_set_cpu_pressure_type(sd_event_source *s, const char *ty); +int sd_event_source_set_cpu_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec); int sd_event_source_set_destroy_callback(sd_event_source *s, sd_event_destroy_t callback); int sd_event_source_get_destroy_callback(sd_event_source *s, sd_event_destroy_t *ret); int sd_event_source_get_floating(sd_event_source *s); diff --git a/src/test/meson.build b/src/test/meson.build index aa6e5b97f44e8..fbb730ab5e148 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -368,7 +368,7 @@ executables += [ 'dependencies' : libm, }, test_template + { - 'sources' : files('test-mempress.c'), + 'sources' : files('test-pressure.c'), 'dependencies' : threads, }, test_template + { diff --git a/src/test/test-mempress.c b/src/test/test-pressure.c similarity index 58% rename from src/test/test-mempress.c rename to src/test/test-pressure.c index f8e58bf1e48e2..44ff810753e8a 100644 --- a/src/test/test-mempress.c +++ b/src/test/test-pressure.c @@ -28,6 +28,8 @@ #include "tmpfile-util.h" #include "unit-def.h" +/* Shared infrastructure for fake pressure tests */ + struct fake_pressure_context { int fifo_fd; int socket_fd; @@ -62,7 +64,7 @@ static int fake_pressure_callback(sd_event_source *s, void *userdata) { *value *= d[0]; - log_notice("memory pressure event: %s", d); + log_notice("pressure event: %s", d); if (*value == 7 * 'f' * 's') ASSERT_OK(sd_event_exit(sd_event_source_get_event(s), 0)); @@ -70,7 +72,12 @@ static int fake_pressure_callback(sd_event_source *s, void *userdata) { return 0; } -TEST(fake_pressure) { +typedef int (*event_add_pressure_t)(sd_event *, sd_event_source **, sd_event_handler_t, void *); + +static void test_fake_pressure( + const char *resource, + event_add_pressure_t add_pressure) { + _cleanup_(sd_event_source_unrefp) sd_event_source *es = NULL, *ef = NULL; _cleanup_(sd_event_unrefp) sd_event *e = NULL; _cleanup_(rm_rf_physical_and_freep) char *tmp = NULL; @@ -79,6 +86,12 @@ TEST(fake_pressure) { pthread_t th; int value = 7; + _cleanup_free_ char *resource_upper = ASSERT_NOT_NULL(strdup(resource)); + ascii_strupper(resource_upper); + + _cleanup_free_ char *env_watch = ASSERT_NOT_NULL(strjoin(resource_upper, "_PRESSURE_WATCH")), + *env_write = ASSERT_NOT_NULL(strjoin(resource_upper, "_PRESSURE_WRITE")); + ASSERT_OK(sd_event_default(&e)); ASSERT_OK(mkdtemp_malloc(NULL, &tmp)); @@ -106,16 +119,16 @@ TEST(fake_pressure) { ASSERT_EQ(pthread_create(&th, NULL, fake_pressure_thread, TAKE_PTR(fp)), 0); - ASSERT_OK_ERRNO(setenv("MEMORY_PRESSURE_WATCH", j, /* override= */ true)); - ASSERT_OK_ERRNO(unsetenv("MEMORY_PRESSURE_WRITE")); + ASSERT_OK_ERRNO(setenv(env_watch, j, /* override= */ true)); + ASSERT_OK_ERRNO(unsetenv(env_write)); - ASSERT_OK(sd_event_add_memory_pressure(e, &es, fake_pressure_callback, &value)); + ASSERT_OK(add_pressure(e, &es, fake_pressure_callback, &value)); ASSERT_OK(sd_event_source_set_description(es, "fifo event source")); - ASSERT_OK_ERRNO(setenv("MEMORY_PRESSURE_WATCH", k, /* override= */ true)); - ASSERT_OK_ERRNO(setenv("MEMORY_PRESSURE_WRITE", "aGVsbG8K", /* override= */ true)); + ASSERT_OK_ERRNO(setenv(env_watch, k, /* override= */ true)); + ASSERT_OK_ERRNO(setenv(env_write, "aGVsbG8K", /* override= */ true)); - ASSERT_OK(sd_event_add_memory_pressure(e, &ef, fake_pressure_callback, &value)); + ASSERT_OK(add_pressure(e, &ef, fake_pressure_callback, &value)); ASSERT_OK(sd_event_source_set_description(ef, "socket event source")); ASSERT_OK(sd_event_loop(e)); @@ -125,18 +138,52 @@ TEST(fake_pressure) { ASSERT_EQ(pthread_join(th, NULL), 0); } +static int fake_pressure_wrapper(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata) { + return sd_event_add_memory_pressure(e, ret, callback, userdata); +} + +TEST(fake_memory_pressure) { + test_fake_pressure("memory", fake_pressure_wrapper); +} + +static int fake_cpu_pressure_wrapper(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata) { + return sd_event_add_cpu_pressure(e, ret, callback, userdata); +} + +TEST(fake_cpu_pressure) { + test_fake_pressure("cpu", fake_cpu_pressure_wrapper); +} + +/* Shared infrastructure for real pressure tests */ + struct real_pressure_context { sd_event_source *pid; }; -static int real_pressure_callback(sd_event_source *s, void *userdata) { +static int real_pressure_child_callback(sd_event_source *s, const siginfo_t *si, void *userdata) { + ASSERT_NOT_NULL(s); + ASSERT_NOT_NULL(si); + + log_notice("child dead"); + + ASSERT_EQ(si->si_signo, SIGCHLD); + ASSERT_EQ(si->si_status, SIGKILL); + ASSERT_EQ(si->si_code, CLD_KILLED); + + ASSERT_OK(sd_event_exit(sd_event_source_get_event(s), 31)); + return 0; +} + +/* Memory pressure real test */ + +static int real_memory_pressure_callback(sd_event_source *s, void *userdata) { struct real_pressure_context *c = ASSERT_PTR(userdata); const char *d; ASSERT_NOT_NULL(s); ASSERT_OK(sd_event_source_get_description(s, &d)); - log_notice("real_memory pressure event: %s", d); + log_notice("real memory pressure event: %s", d); sd_event_trim_memory(); @@ -174,21 +221,7 @@ _noreturn_ static void real_pressure_eat_memory(int pipe_fd) { } } -static int real_pressure_child_callback(sd_event_source *s, const siginfo_t *si, void *userdata) { - ASSERT_NOT_NULL(s); - ASSERT_NOT_NULL(si); - - log_notice("child dead"); - - ASSERT_EQ(si->si_signo, SIGCHLD); - ASSERT_EQ(si->si_status, SIGKILL); - ASSERT_EQ(si->si_code, CLD_KILLED); - - ASSERT_OK(sd_event_exit(sd_event_source_get_event(s), 31)); - return 0; -} - -TEST(real_pressure) { +TEST(real_memory_pressure) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_event_source_unrefp) sd_event_source *es = NULL, *cs = NULL; @@ -200,9 +233,12 @@ TEST(real_pressure) { const char *object; int r; - r = sd_bus_open_system(&bus); + if (getuid() == 0) + r = sd_bus_open_system(&bus); + else + r = sd_bus_open_user(&bus); if (r < 0) - return (void) log_tests_skipped_errno(r, "can't connect to system bus"); + return (void) log_tests_skipped_errno(r, "can't connect to bus"); ASSERT_OK(bus_wait_for_jobs_new(bus, &w)); @@ -245,7 +281,7 @@ TEST(real_pressure) { .pid = cs, }; - r = sd_event_add_memory_pressure(e, &es, real_pressure_callback, &context); + r = sd_event_add_memory_pressure(e, &es, real_memory_pressure_callback, &context); if (r < 0) return (void) log_tests_skipped_errno(r, "can't allocate memory pressure fd"); @@ -255,8 +291,9 @@ TEST(real_pressure) { ASSERT_OK_ZERO(sd_event_source_set_memory_pressure_type(es, "full")); ASSERT_OK_POSITIVE(sd_event_source_set_memory_pressure_type(es, "some")); ASSERT_OK_ZERO(sd_event_source_set_memory_pressure_type(es, "some")); - ASSERT_OK_POSITIVE(sd_event_source_set_memory_pressure_period(es, 70 * USEC_PER_MSEC, USEC_PER_SEC)); - ASSERT_OK_ZERO(sd_event_source_set_memory_pressure_period(es, 70 * USEC_PER_MSEC, USEC_PER_SEC)); + /* Unprivileged writes require a minimum of 2s otherwise the kernel will refuse the write. */ + ASSERT_OK_POSITIVE(sd_event_source_set_memory_pressure_period(es, 70 * USEC_PER_MSEC, 2 * USEC_PER_SEC)); + ASSERT_OK_ZERO(sd_event_source_set_memory_pressure_period(es, 70 * USEC_PER_MSEC, 2 * USEC_PER_SEC)); ASSERT_OK(sd_event_source_set_enabled(es, SD_EVENT_ONESHOT)); _cleanup_free_ char *uo = NULL; @@ -298,6 +335,123 @@ TEST(real_pressure) { ASSERT_EQ(ex, 31); } +/* CPU pressure real test */ + +static int real_cpu_pressure_callback(sd_event_source *s, void *userdata) { + struct real_pressure_context *c = ASSERT_PTR(userdata); + const char *d; + + ASSERT_NOT_NULL(s); + ASSERT_OK(sd_event_source_get_description(s, &d)); + + log_notice("real cpu pressure event: %s", d); + + ASSERT_NOT_NULL(c->pid); + ASSERT_OK(sd_event_source_send_child_signal(c->pid, SIGKILL, NULL, 0)); + c->pid = NULL; + + return 0; +} + +_noreturn_ static void real_pressure_eat_cpu(int pipe_fd) { + char x; + ASSERT_EQ(read(pipe_fd, &x, 1), 1); /* Wait for the GO! */ + + /* Busy-loop to generate CPU pressure */ + for (;;) + __asm__ volatile("" ::: "memory"); /* Prevent optimization */ +} + +TEST(real_cpu_pressure) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *es = NULL, *cs = NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_close_pair_ int pipe_fd[2] = EBADF_PAIR; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_free_ char *scope = NULL; + const char *object; + int r; + + if (getuid() == 0) + r = sd_bus_open_system(&bus); + else + r = sd_bus_open_user(&bus); + if (r < 0) + return (void) log_tests_skipped_errno(r, "can't connect to bus"); + + ASSERT_OK(bus_wait_for_jobs_new(bus, &w)); + + ASSERT_OK(bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit")); + ASSERT_OK(asprintf(&scope, "test-%" PRIu64 ".scope", random_u64())); + ASSERT_OK(sd_bus_message_append(m, "ss", scope, "fail")); + ASSERT_OK(sd_bus_message_open_container(m, 'a', "(sv)")); + ASSERT_OK(sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, 0)); + ASSERT_OK(sd_bus_message_append(m, "(sv)", "CPUAccounting", "b", true)); + ASSERT_OK(sd_bus_message_close_container(m)); + ASSERT_OK(sd_bus_message_append(m, "a(sa(sv))", 0)); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return (void) log_tests_skipped_errno(r, "can't issue transient unit call"); + + ASSERT_OK(sd_bus_message_read(reply, "o", &object)); + + ASSERT_OK(bus_wait_for_jobs_one(w, object, /* flags= */ BUS_WAIT_JOBS_LOG_ERROR, /* extra_args= */ NULL)); + + ASSERT_OK(sd_event_default(&e)); + + ASSERT_OK_ERRNO(pipe2(pipe_fd, O_CLOEXEC)); + + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + r = pidref_safe_fork("(eat-cpu)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM, &pidref); + ASSERT_OK(r); + if (r == 0) { + real_pressure_eat_cpu(pipe_fd[0]); + _exit(EXIT_SUCCESS); + } + + ASSERT_OK(event_add_child_pidref(e, &cs, &pidref, WEXITED, real_pressure_child_callback, NULL)); + ASSERT_OK(sd_event_source_set_child_process_own(cs, true)); + + ASSERT_OK_ERRNO(unsetenv("CPU_PRESSURE_WATCH")); + ASSERT_OK_ERRNO(unsetenv("CPU_PRESSURE_WRITE")); + + struct real_pressure_context context = { + .pid = cs, + }; + + r = sd_event_add_cpu_pressure(e, &es, real_cpu_pressure_callback, &context); + if (r < 0) + return (void) log_tests_skipped_errno(r, "can't allocate cpu pressure fd"); + + ASSERT_OK(sd_event_source_set_description(es, "real pressure event source")); + ASSERT_OK_ZERO(sd_event_source_set_cpu_pressure_type(es, "some")); + /* Unprivileged writes require a minimum of 2s otherwise the kernel will refuse the write. */ + ASSERT_OK_POSITIVE(sd_event_source_set_cpu_pressure_period(es, 70 * USEC_PER_MSEC, 2 * USEC_PER_SEC)); + ASSERT_OK_ZERO(sd_event_source_set_cpu_pressure_period(es, 70 * USEC_PER_MSEC, 2 * USEC_PER_SEC)); + ASSERT_OK(sd_event_source_set_enabled(es, SD_EVENT_ONESHOT)); + + m = sd_bus_message_unref(m); + + ASSERT_OK(bus_message_new_method_call(bus, &m, bus_systemd_mgr, "SetUnitProperties")); + ASSERT_OK(sd_bus_message_append(m, "sb", scope, true)); + ASSERT_OK(sd_bus_message_open_container(m, 'a', "(sv)")); + ASSERT_OK(sd_bus_message_append(m, "(sv)", "CPUQuotaPerSecUSec", "t", (uint64_t) 1000)); /* 0.1% CPU */ + ASSERT_OK(sd_bus_message_close_container(m)); + + ASSERT_OK(sd_bus_call(bus, m, 0, NULL, NULL)); + + /* Now start eating CPU */ + ASSERT_EQ(write(pipe_fd[1], &(const char) { 'x' }, 1), 1); + + ASSERT_OK(sd_event_loop(e)); + int ex = 0; + ASSERT_OK(sd_event_get_exit_code(e, &ex)); + ASSERT_EQ(ex, 31); +} + static int outro(void) { hashmap_trim_pools(); return 0; From 316d17fcbd191404b5790b36b65c73151c21f6cf Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sat, 7 Mar 2026 23:20:19 +0100 Subject: [PATCH 0866/1296] core: Add support for CPU pressure notifications Works the same way as memory pressure notifications. Code is refactored to work on enum arrays to reduce duplication. --- man/org.freedesktop.systemd1.xml | 100 ++++++++++- man/systemd-system.conf.xml | 14 ++ man/systemd.exec.xml | 12 ++ man/systemd.resource-control.xml | 49 ++++++ src/basic/psi-util.h | 5 + src/core/cgroup.c | 29 ++-- src/core/cgroup.h | 43 +++-- src/core/dbus-cgroup.c | 24 ++- src/core/dbus-manager.c | 6 +- src/core/exec-invoke.c | 128 +++++++++----- src/core/execute-serialize.c | 30 +++- src/core/load-fragment-gperf.gperf.in | 6 +- src/core/load-fragment.c | 2 +- src/core/load-fragment.h | 2 +- src/core/main.c | 158 +++++++++--------- src/core/manager.c | 57 ++++--- src/core/manager.h | 7 +- src/core/system.conf.in | 2 + src/core/unit.c | 5 +- src/core/user.conf.in | 2 + src/core/varlink-cgroup.c | 6 +- src/core/varlink-manager.c | 6 +- src/libsystemd/sd-event/sd-event.c | 3 +- src/shared/bus-unit-util.c | 2 + src/shared/varlink-io.systemd.Manager.c | 4 + src/shared/varlink-io.systemd.Unit.c | 4 + .../meson.build | 0 test/integration-tests/meson.build | 2 +- ...EST-79-MEMPRESS.sh => TEST-79-PRESSURE.sh} | 55 +++++- 29 files changed, 562 insertions(+), 201 deletions(-) rename test/integration-tests/{TEST-79-MEMPRESS => TEST-79-PRESSURE}/meson.build (100%) rename test/units/{TEST-79-MEMPRESS.sh => TEST-79-PRESSURE.sh} (56%) diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index cbeb25efcd767..027a8deeb4653 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -552,6 +552,10 @@ node /org/freedesktop/systemd1 { readonly t DefaultMemoryPressureThresholdUSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly s DefaultMemoryPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t DefaultCPUPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s DefaultCPUPressureWatch = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly t TimerSlackNSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") @@ -793,6 +797,10 @@ node /org/freedesktop/systemd1 { + + + + @@ -1243,6 +1251,10 @@ node /org/freedesktop/systemd1 { + + + + @@ -3066,6 +3078,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t MemoryPressureThresholdUSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s CPUPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t CPUPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiss) NFTSet = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b CoredumpReceive = ...; @@ -3735,6 +3751,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + + + @@ -4427,6 +4447,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + + + @@ -5326,6 +5350,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t MemoryPressureThresholdUSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s CPUPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t CPUPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiss) NFTSet = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b CoredumpReceive = ...; @@ -6011,6 +6039,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + + + @@ -6677,6 +6709,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + + + @@ -7399,6 +7435,10 @@ node /org/freedesktop/systemd1/unit/home_2emount { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t MemoryPressureThresholdUSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s CPUPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t CPUPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiss) NFTSet = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b CoredumpReceive = ...; @@ -8008,6 +8048,10 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + + + @@ -8582,6 +8626,10 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + + + @@ -9437,6 +9485,10 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t MemoryPressureThresholdUSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s CPUPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t CPUPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiss) NFTSet = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b CoredumpReceive = ...; @@ -10028,6 +10080,10 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + + + @@ -10584,6 +10640,10 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + + + @@ -11292,6 +11352,10 @@ node /org/freedesktop/systemd1/unit/system_2eslice { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t MemoryPressureThresholdUSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s CPUPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t CPUPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiss) NFTSet = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b CoredumpReceive = ...; @@ -11465,6 +11529,10 @@ node /org/freedesktop/systemd1/unit/system_2eslice { + + + + @@ -11653,6 +11721,10 @@ node /org/freedesktop/systemd1/unit/system_2eslice { + + + + @@ -11864,6 +11936,10 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t MemoryPressureThresholdUSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s CPUPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t CPUPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiss) NFTSet = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b CoredumpReceive = ...; @@ -12051,6 +12127,10 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { + + + + @@ -12263,6 +12343,10 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { + + + + @@ -12475,7 +12559,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ RemoveSubgroupFromUnit(), and KillUnitSubgroup() were added in version 258. TransactionsWithOrderingCycle was added in version 259. - DefaultMemoryZSwapWriteback was added in version 261. + DefaultMemoryZSwapWriteback, + DefaultCPUPressureThresholdUSec and + DefaultCPUPressureWatch were added in version 261. Unit Objects @@ -12567,6 +12653,8 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ ExecReloadPostEx were added in version 259. BindNetworkInterface, MemoryTHP, RefreshOnReload, and RootMStack were added in version 260. + CPUPressureThresholdUSec and + CPUPressureWatch were added in version 261. Socket Unit Objects @@ -12637,6 +12725,8 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ ManagedOOMKills were added in 259. BindNetworkInterface MemoryTHP, and RootMStack were added in version 260. + CPUPressureThresholdUSec and + CPUPressureWatch were added in version 261. Mount Unit Objects @@ -12702,6 +12792,8 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ ManagedOOMKills were added in 259. BindNetworkInterface MemoryTHP, and RootMStack were added in version 260. + CPUPressureThresholdUSec and + CPUPressureWatch were added in version 261. Swap Unit Objects @@ -12765,6 +12857,8 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ ManagedOOMKills were added in 259. BindNetworkInterface, MemoryTHP, and RootMStack were added in version 260. + CPUPressureThresholdUSec and + CPUPressureWatch were added in version 261. Slice Unit Objects @@ -12798,6 +12892,8 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ OOMKills, and ManagedOOMKills were added in 259. BindNetworkInterface was added in version 260. + CPUPressureThresholdUSec and + CPUPressureWatch were added in version 261. Scope Unit Objects @@ -12829,6 +12925,8 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ OOMKills, and ManagedOOMKills were added in 259. BindNetworkInterface was added in version 260. + CPUPressureThresholdUSec and + CPUPressureWatch were added in version 261. Job Objects diff --git a/man/systemd-system.conf.xml b/man/systemd-system.conf.xml index e9e7d3d78db57..79133dc15ebca 100644 --- a/man/systemd-system.conf.xml +++ b/man/systemd-system.conf.xml @@ -326,6 +326,20 @@ + + + DefaultCPUPressureWatch= + DefaultCPUPressureThresholdSec= + + Configures the default settings for the per-unit + CPUPressureWatch= and CPUPressureThresholdSec= + settings. See + systemd.resource-control5 + for details. Defaults to auto and 200ms, respectively. This + also sets the CPU pressure monitoring threshold for the service manager itself. + + + diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 48bec7361bde1..1048fcadfc376 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -4705,6 +4705,18 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX + + $CPU_PRESSURE_WATCH + $CPU_PRESSURE_WRITE + + If CPU pressure monitoring is enabled for this service unit, the path to watch + and the data to write into it. See Resource Pressure + Handling for details about these variables and the service protocol data they + convey. + + + + $FDSTORE diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml index ac31971e54f6e..8d9e27f3d3a26 100644 --- a/man/systemd.resource-control.xml +++ b/man/systemd.resource-control.xml @@ -1657,6 +1657,55 @@ DeviceAllow=/dev/loop-control + + + CPUPressureWatch= + + Controls CPU pressure monitoring for invoked processes. Takes a boolean or one of + auto and skip. If no, tells the service not + to watch for CPU pressure events, by setting the $CPU_PRESSURE_WATCH + environment variable to the literal string /dev/null. If yes, + tells the service to watch for CPU pressure events. This ensures the + cpu.pressure cgroup attribute file is accessible for + reading and writing by the service's user. It then sets the $CPU_PRESSURE_WATCH + environment variable for processes invoked by the unit to the file system path to this file. The + threshold information configured with CPUPressureThresholdSec= is encoded in + the $CPU_PRESSURE_WRITE environment variable. If the auto + value is set the protocol is enabled if CPU resource controls are configured for the unit (e.g. because + CPUWeight= or CPUQuota= is set), and + disabled otherwise. If set to skip the logic is neither enabled, nor disabled and + the two environment variables are not set. + + Note that services are free to use the two environment variables, but it is unproblematic if + they ignore them. CPU pressure handling must be implemented individually in each service, and + usually means different things for different software. + + Services implemented using + sd-event3 may use + sd_event_add_cpu_pressure3 + to watch for and handle CPU pressure events. + + If not explicitly set, defaults to the DefaultCPUPressureWatch= setting in + systemd-system.conf5. + + + + + + CPUPressureThresholdSec= + + Sets the CPU pressure threshold time for CPU pressure monitor as configured via + CPUPressureWatch=. Specifies the maximum CPU stall time before a CPU + pressure event is signalled to the service, per 2s window. If not specified, defaults to the + DefaultCPUPressureThresholdSec= setting in + systemd-system.conf5 + (which in turn defaults to 200ms). The specified value expects a time unit such as + ms or μs, see + systemd.time7 for + details on the permitted syntax. + + + Coredump Control diff --git a/src/basic/psi-util.h b/src/basic/psi-util.h index aed74ef742d5a..b8737b8976bf4 100644 --- a/src/basic/psi-util.h +++ b/src/basic/psi-util.h @@ -43,6 +43,11 @@ typedef struct PressureResourceInfo { extern const PressureResourceInfo pressure_resource_info[_PRESSURE_RESOURCE_MAX]; +static inline const PressureResourceInfo* pressure_resource_get_info(PressureResource resource) { + assert(resource >= 0 && resource < _PRESSURE_RESOURCE_MAX); + return &pressure_resource_info[resource]; +} + DECLARE_STRING_TABLE_LOOKUP(pressure_resource, PressureResource); /* Default parameters for pressure watch logic in sd-event and PID 1 */ diff --git a/src/core/cgroup.c b/src/core/cgroup.c index 7bcd6777df244..a9982de659ffd 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -185,8 +185,10 @@ void cgroup_context_init(CGroupContext *c) { * moom_mem_pressure_duration_usec is set to infinity. */ .moom_mem_pressure_duration_usec = USEC_INFINITY, - .memory_pressure_watch = _CGROUP_PRESSURE_WATCH_INVALID, - .memory_pressure_threshold_usec = USEC_INFINITY, + .pressure = { + [PRESSURE_MEMORY] = { .watch = _CGROUP_PRESSURE_WATCH_INVALID, .threshold_usec = USEC_INFINITY }, + [PRESSURE_CPU] = { .watch = _CGROUP_PRESSURE_WATCH_INVALID, .threshold_usec = USEC_INFINITY }, + }, }; } @@ -528,6 +530,7 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) { "%sManagedOOMMemoryPressureLimit: " PERMYRIAD_AS_PERCENT_FORMAT_STR "\n" "%sManagedOOMPreference: %s\n" "%sMemoryPressureWatch: %s\n" + "%sCPUPressureWatch: %s\n" "%sCoredumpReceive: %s\n", prefix, yes_no(c->io_accounting), prefix, yes_no(c->memory_accounting), @@ -563,7 +566,8 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) { prefix, managed_oom_mode_to_string(c->moom_mem_pressure), prefix, PERMYRIAD_AS_PERCENT_FORMAT_VAL(UINT32_SCALE_TO_PERMYRIAD(c->moom_mem_pressure_limit)), prefix, managed_oom_preference_to_string(c->moom_preference), - prefix, cgroup_pressure_watch_to_string(c->memory_pressure_watch), + prefix, cgroup_pressure_watch_to_string(c->pressure[PRESSURE_MEMORY].watch), + prefix, cgroup_pressure_watch_to_string(c->pressure[PRESSURE_CPU].watch), prefix, yes_no(c->coredump_receive)); if (c->delegate_subgroup) @@ -574,9 +578,13 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) { fprintf(f, "%sBindNetworkInterface: %s\n", prefix, c->bind_network_interface); - if (c->memory_pressure_threshold_usec != USEC_INFINITY) + if (c->pressure[PRESSURE_MEMORY].threshold_usec != USEC_INFINITY) fprintf(f, "%sMemoryPressureThresholdSec: %s\n", - prefix, FORMAT_TIMESPAN(c->memory_pressure_threshold_usec, 1)); + prefix, FORMAT_TIMESPAN(c->pressure[PRESSURE_MEMORY].threshold_usec, 1)); + + if (c->pressure[PRESSURE_CPU].threshold_usec != USEC_INFINITY) + fprintf(f, "%sCPUPressureThresholdSec: %s\n", + prefix, FORMAT_TIMESPAN(c->pressure[PRESSURE_CPU].threshold_usec, 1)); if (c->moom_mem_pressure_duration_usec != USEC_INFINITY) fprintf(f, "%sManagedOOMMemoryPressureDurationSec: %s\n", @@ -2107,12 +2115,13 @@ static int unit_update_cgroup( cgroup_context_apply(u, target_mask, state); cgroup_xattr_apply(u); - /* For most units we expect that memory monitoring is set up before the unit is started and we won't - * touch it after. For PID 1 this is different though, because we couldn't possibly do that given - * that PID 1 runs before init.scope is even set up. Hence, whenever init.scope is realized, let's - * try to open the memory pressure interface anew. */ + /* For most units we expect that pressure monitoring is set up before the unit is started and we + * won't touch it after. For PID 1 this is different though, because we couldn't possibly do that + * given that PID 1 runs before init.scope is even set up. Hence, whenever init.scope is realized, + * let's try to open the pressure interfaces anew. */ if (unit_has_name(u, SPECIAL_INIT_SCOPE)) - (void) manager_setup_memory_pressure_event_source(u->manager); + for (PressureResource t = 0; t < _PRESSURE_RESOURCE_MAX; t++) + (void) manager_setup_pressure_event_source(u->manager, t); return 0; } diff --git a/src/core/cgroup.h b/src/core/cgroup.h index 0cd290e92f25d..c4a22765678eb 100644 --- a/src/core/cgroup.h +++ b/src/core/cgroup.h @@ -6,6 +6,7 @@ #include "cpu-set-util.h" #include "firewall-util.h" #include "list.h" +#include "psi-util.h" typedef struct CGroupTasksMax { /* If scale == 0, just use value; otherwise, value / scale. @@ -95,14 +96,19 @@ typedef struct CGroupSocketBindItem { } CGroupSocketBindItem; typedef enum CGroupPressureWatch { - CGROUP_PRESSURE_WATCH_NO, /* → tells the service payload explicitly not to watch for memory pressure */ + CGROUP_PRESSURE_WATCH_NO, /* → tells the service payload explicitly not to watch for pressure */ CGROUP_PRESSURE_WATCH_YES, - CGROUP_PRESSURE_WATCH_AUTO, /* → on if memory account is on anyway for the unit, otherwise off */ - CGROUP_PRESSURE_WATCH_SKIP, /* → doesn't set up memory pressure watch, but also doesn't explicitly tell payload to avoid it */ + CGROUP_PRESSURE_WATCH_AUTO, /* → on if relevant accounting is on anyway for the unit, otherwise off */ + CGROUP_PRESSURE_WATCH_SKIP, /* → doesn't set up pressure watch, but also doesn't explicitly tell payload to avoid it */ _CGROUP_PRESSURE_WATCH_MAX, _CGROUP_PRESSURE_WATCH_INVALID = -EINVAL, } CGroupPressureWatch; +typedef struct CGroupPressure { + CGroupPressureWatch watch; + usec_t threshold_usec; +} CGroupPressure; + /* The user-supplied cgroup-related configuration options. This remains mostly immutable while the service * manager is running (except for an occasional SetProperties() configuration change), outside of reload * cycles. */ @@ -189,11 +195,8 @@ typedef struct CGroupContext { usec_t moom_mem_pressure_duration_usec; ManagedOOMPreference moom_preference; - /* Memory pressure logic */ - CGroupPressureWatch memory_pressure_watch; - usec_t memory_pressure_threshold_usec; - /* NB: For now we don't make the period configurable, not the type, nor do we allow multiple - * triggers, nor triggers for non-memory pressure. We might add that later. */ + /* Pressure logic */ + CGroupPressure pressure[_PRESSURE_RESOURCE_MAX]; NFTSetContext nft_set_context; @@ -353,11 +356,29 @@ void cgroup_context_free_io_device_latency(CGroupContext *c, CGroupIODeviceLaten void cgroup_context_remove_bpf_foreign_program(CGroupContext *c, CGroupBPFForeignProgram *p); void cgroup_context_remove_socket_bind(CGroupSocketBindItem **head); -static inline bool cgroup_context_want_memory_pressure(const CGroupContext *c) { +static inline bool cgroup_context_want_pressure(const CGroupContext *c, PressureResource t) { assert(c); + assert(t >= 0 && t < _PRESSURE_RESOURCE_MAX); + + if (c->pressure[t].watch == CGROUP_PRESSURE_WATCH_YES) + return true; + + if (c->pressure[t].watch != CGROUP_PRESSURE_WATCH_AUTO) + return false; + + switch (t) { + + case PRESSURE_MEMORY: + return c->memory_accounting; + + case PRESSURE_CPU: + return c->cpu_weight != CGROUP_WEIGHT_INVALID || + c->startup_cpu_weight != CGROUP_WEIGHT_INVALID || + c->cpu_quota_per_sec_usec != USEC_INFINITY; - return c->memory_pressure_watch == CGROUP_PRESSURE_WATCH_YES || - (c->memory_pressure_watch == CGROUP_PRESSURE_WATCH_AUTO && c->memory_accounting); + default: + assert_not_reached(); + } } static inline bool cgroup_context_has_device_policy(const CGroupContext *c) { diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c index a7508c96daa11..c5a3302e08e84 100644 --- a/src/core/dbus-cgroup.c +++ b/src/core/dbus-cgroup.c @@ -427,8 +427,10 @@ const sd_bus_vtable bus_cgroup_vtable[] = { SD_BUS_PROPERTY("SocketBindDeny", "a(iiqq)", property_get_socket_bind, offsetof(CGroupContext, socket_bind_deny), 0), SD_BUS_PROPERTY("RestrictNetworkInterfaces", "(bas)", property_get_restrict_network_interfaces, 0, 0), SD_BUS_PROPERTY("BindNetworkInterface", "s", NULL, offsetof(CGroupContext, bind_network_interface), 0), - SD_BUS_PROPERTY("MemoryPressureWatch", "s", bus_property_get_cgroup_pressure_watch, offsetof(CGroupContext, memory_pressure_watch), 0), - SD_BUS_PROPERTY("MemoryPressureThresholdUSec", "t", bus_property_get_usec, offsetof(CGroupContext, memory_pressure_threshold_usec), 0), + SD_BUS_PROPERTY("MemoryPressureWatch", "s", bus_property_get_cgroup_pressure_watch, offsetof(CGroupContext, pressure[PRESSURE_MEMORY].watch), 0), + SD_BUS_PROPERTY("MemoryPressureThresholdUSec", "t", bus_property_get_usec, offsetof(CGroupContext, pressure[PRESSURE_MEMORY].threshold_usec), 0), + SD_BUS_PROPERTY("CPUPressureWatch", "s", bus_property_get_cgroup_pressure_watch, offsetof(CGroupContext, pressure[PRESSURE_CPU].watch), 0), + SD_BUS_PROPERTY("CPUPressureThresholdUSec", "t", bus_property_get_usec, offsetof(CGroupContext, pressure[PRESSURE_CPU].threshold_usec), 0), SD_BUS_PROPERTY("NFTSet", "a(iiss)", property_get_cgroup_nft_set, 0, 0), SD_BUS_PROPERTY("CoredumpReceive", "b", bus_property_get_bool, offsetof(CGroupContext, coredump_receive), 0), @@ -712,10 +714,12 @@ static int bus_cgroup_set_transient_property( return 1; - } else if (streq(name, "MemoryPressureWatch")) { + } else if (STR_IN_SET(name, "MemoryPressureWatch", "CPUPressureWatch")) { CGroupPressureWatch p; const char *t; + PressureResource pt = streq(name, "MemoryPressureWatch") ? PRESSURE_MEMORY : PRESSURE_CPU; + r = sd_bus_message_read(message, "s", &t); if (r < 0) return r; @@ -729,26 +733,28 @@ static int bus_cgroup_set_transient_property( } if (!UNIT_WRITE_FLAGS_NOOP(flags)) { - c->memory_pressure_watch = p; - unit_write_settingf(u, flags, name, "MemoryPressureWatch=%s", strempty(cgroup_pressure_watch_to_string(p))); + c->pressure[pt].watch = p; + unit_write_settingf(u, flags, name, "%s=%s", name, strempty(cgroup_pressure_watch_to_string(p))); } return 1; - } else if (streq(name, "MemoryPressureThresholdUSec")) { + } else if (STR_IN_SET(name, "MemoryPressureThresholdUSec", "CPUPressureThresholdUSec")) { uint64_t t; + PressureResource pt = streq(name, "MemoryPressureThresholdUSec") ? PRESSURE_MEMORY : PRESSURE_CPU; + r = sd_bus_message_read(message, "t", &t); if (r < 0) return r; if (!UNIT_WRITE_FLAGS_NOOP(flags)) { - c->memory_pressure_threshold_usec = t; + c->pressure[pt].threshold_usec = t; if (t == UINT64_MAX) - unit_write_setting(u, flags, name, "MemoryPressureThresholdUSec="); + unit_write_settingf(u, flags, name, "%s=", name); else - unit_write_settingf(u, flags, name, "MemoryPressureThresholdUSec=%" PRIu64, t); + unit_write_settingf(u, flags, name, "%s=%" PRIu64, name, t); } return 1; diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 749e2261af7a9..23f4c4c3de851 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -2980,8 +2980,10 @@ const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_PROPERTY("DefaultLimitRTTIME", "t", bus_property_get_rlimit, offsetof(Manager, defaults.rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DefaultLimitRTTIMESoft", "t", bus_property_get_rlimit, offsetof(Manager, defaults.rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DefaultTasksMax", "t", bus_property_get_tasks_max, offsetof(Manager, defaults.tasks_max), 0), - SD_BUS_PROPERTY("DefaultMemoryPressureThresholdUSec", "t", bus_property_get_usec, offsetof(Manager, defaults.memory_pressure_threshold_usec), 0), - SD_BUS_PROPERTY("DefaultMemoryPressureWatch", "s", bus_property_get_cgroup_pressure_watch, offsetof(Manager, defaults.memory_pressure_watch), 0), + SD_BUS_PROPERTY("DefaultMemoryPressureThresholdUSec", "t", bus_property_get_usec, offsetof(Manager, defaults.pressure[PRESSURE_MEMORY].threshold_usec), 0), + SD_BUS_PROPERTY("DefaultMemoryPressureWatch", "s", bus_property_get_cgroup_pressure_watch, offsetof(Manager, defaults.pressure[PRESSURE_MEMORY].watch), 0), + SD_BUS_PROPERTY("DefaultCPUPressureThresholdUSec", "t", bus_property_get_usec, offsetof(Manager, defaults.pressure[PRESSURE_CPU].threshold_usec), 0), + SD_BUS_PROPERTY("DefaultCPUPressureWatch", "s", bus_property_get_cgroup_pressure_watch, offsetof(Manager, defaults.pressure[PRESSURE_CPU].watch), 0), SD_BUS_PROPERTY("TimerSlackNSec", "t", property_get_timer_slack_nsec, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DefaultOOMPolicy", "s", bus_property_get_oom_policy, offsetof(Manager, defaults.oom_policy), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DefaultOOMScoreAdjust", "i", property_get_oom_score_adjust, 0, SD_BUS_VTABLE_PROPERTY_CONST), diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 7500888c414a3..1361038af8a4f 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -2034,7 +2034,7 @@ static int build_environment( const char *shell, dev_t journal_stream_dev, ino_t journal_stream_ino, - const char *memory_pressure_path, + char *const *pressure_path, bool needs_sandboxing, char ***ret) { @@ -2215,25 +2215,38 @@ static int build_environment( if (r < 0) return r; - if (memory_pressure_path) { - r = strv_extend_joined_with_size(&e, &n, "MEMORY_PRESSURE_WATCH=", memory_pressure_path); + for (PressureResource t = 0; t < _PRESSURE_RESOURCE_MAX; t++) { + if (!pressure_path[t]) + continue; + + const PressureResourceInfo *info = pressure_resource_get_info(t); + + _cleanup_free_ char *env_watch = strjoin(info->env_watch, "="); + if (!env_watch) + return -ENOMEM; + + r = strv_extend_joined_with_size(&e, &n, env_watch, pressure_path[t]); if (r < 0) return r; - if (!path_equal(memory_pressure_path, "/dev/null")) { + if (!path_equal(pressure_path[t], "/dev/null")) { _cleanup_free_ char *b = NULL, *x = NULL; if (asprintf(&b, "%s " USEC_FMT " " USEC_FMT, PRESSURE_DEFAULT_TYPE, - cgroup_context->memory_pressure_threshold_usec == USEC_INFINITY ? PRESSURE_DEFAULT_THRESHOLD_USEC : - CLAMP(cgroup_context->memory_pressure_threshold_usec, 1U, PRESSURE_DEFAULT_WINDOW_USEC), + cgroup_context->pressure[t].threshold_usec == USEC_INFINITY ? PRESSURE_DEFAULT_THRESHOLD_USEC : + CLAMP(cgroup_context->pressure[t].threshold_usec, 1U, PRESSURE_DEFAULT_WINDOW_USEC), PRESSURE_DEFAULT_WINDOW_USEC) < 0) return -ENOMEM; if (base64mem(b, strlen(b) + 1, &x) < 0) return -ENOMEM; - r = strv_extend_joined_with_size(&e, &n, "MEMORY_PRESSURE_WRITE=", x); + _cleanup_free_ char *env_write = strjoin(info->env_write, "="); + if (!env_write) + return -ENOMEM; + + r = strv_extend_joined_with_size(&e, &n, env_write, x); if (r < 0) return r; } @@ -3855,7 +3868,7 @@ static int apply_mount_namespace( const ExecParameters *params, const ExecRuntime *runtime, const PinnedResource *rootfs, - const char *memory_pressure_path, + char *const *pressure_path, bool needs_sandboxing, uid_t exec_directory_uid, gid_t exec_directory_gid, @@ -3887,16 +3900,28 @@ static int apply_mount_namespace( if (r < 0) return r; - /* We need to make the pressure path writable even if /sys/fs/cgroups is made read-only, as the - * service will need to write to it in order to start the notifications. */ - if (exec_is_cgroup_mount_read_only(context) && memory_pressure_path && !streq(memory_pressure_path, "/dev/null")) { + /* We need to make the pressure paths writable even if /sys/fs/cgroups is made read-only, as the + * service will need to write to them in order to start the notifications. */ + bool need_pressure_rw = false; + for (PressureResource t = 0; t < _PRESSURE_RESOURCE_MAX; t++) + if (pressure_path[t] && !streq(pressure_path[t], "/dev/null")) { + need_pressure_rw = true; + break; + } + + if (exec_is_cgroup_mount_read_only(context) && need_pressure_rw) { read_write_paths_cleanup = strv_copy(context->read_write_paths); if (!read_write_paths_cleanup) return -ENOMEM; - r = strv_extend(&read_write_paths_cleanup, memory_pressure_path); - if (r < 0) - return r; + for (PressureResource t = 0; t < _PRESSURE_RESOURCE_MAX; t++) { + if (!pressure_path[t] || streq(pressure_path[t], "/dev/null")) + continue; + + r = strv_extend(&read_write_paths_cleanup, pressure_path[t]); + if (r < 0) + return r; + } read_write_paths = read_write_paths_cleanup; } else @@ -4689,7 +4714,7 @@ static int setup_delegated_namespaces( const ExecRuntime *runtime, const PinnedResource *rootfs, bool delegate, - const char *memory_pressure_path, + char *const *pressure_path, uid_t uid, gid_t gid, const ExecCommand *command, @@ -4820,7 +4845,7 @@ static int setup_delegated_namespaces( params, runtime, rootfs, - memory_pressure_path, + pressure_path, needs_sandboxing, uid, gid, @@ -5146,6 +5171,10 @@ static int setup_term_environment(const ExecContext *context, char ***env) { return strv_env_replace_strdup(env, "TERM=" FALLBACK_TERM); } +static inline void free_pressure_paths(char *(*p)[_PRESSURE_RESOURCE_MAX]) { + free_many_charp(*p, _PRESSURE_RESOURCE_MAX); +} + int exec_invoke( const ExecCommand *command, const ExecContext *context, @@ -5157,7 +5186,8 @@ int exec_invoke( _cleanup_strv_free_ char **our_env = NULL, **pass_env = NULL, **joined_exec_search_path = NULL, **accum_env = NULL; int r; const char *username = NULL, *groupname = NULL; - _cleanup_free_ char *home_buffer = NULL, *memory_pressure_path = NULL, *own_user = NULL; + _cleanup_free_ char *home_buffer = NULL, *own_user = NULL; + _cleanup_(free_pressure_paths) char *pressure_path[_PRESSURE_RESOURCE_MAX] = {}; const char *pwent_home = NULL, *shell = NULL; dev_t journal_stream_dev = 0; ino_t journal_stream_ino = 0; @@ -5753,36 +5783,44 @@ int exec_invoke( } if (is_pressure_supported() > 0) { - if (cgroup_context_want_memory_pressure(cgroup_context)) { - r = cg_get_path(params->cgroup_path, "memory.pressure", &memory_pressure_path); - if (r < 0) { - *exit_status = EXIT_MEMORY; - return log_oom(); - } + for (PressureResource t = 0; t < _PRESSURE_RESOURCE_MAX; t++) { + if (cgroup_context_want_pressure(cgroup_context, t)) { + _cleanup_free_ char *pressure_file = strjoin(pressure_resource_to_string(t), ".pressure"); + if (!pressure_file) { + *exit_status = EXIT_MEMORY; + return log_oom(); + } - r = chmod_and_chown(memory_pressure_path, 0644, uid, gid); - if (r < 0) { - log_full_errno(r == -ENOENT || ERRNO_IS_PRIVILEGE(r) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to adjust ownership of '%s', ignoring: %m", memory_pressure_path); - memory_pressure_path = mfree(memory_pressure_path); - } - /* First we use the current cgroup path to chmod and chown the memory pressure path, then pass the path relative - * to the cgroup namespace to environment variables and mounts. If chown/chmod fails, we should not pass memory - * pressure path environment variable or read-write mount to the unit. This is why we check if - * memory_pressure_path != NULL in the conditional below. */ - if (memory_pressure_path && needs_sandboxing && exec_needs_cgroup_namespace(context)) { - memory_pressure_path = mfree(memory_pressure_path); - r = cg_get_path("/", "memory.pressure", &memory_pressure_path); + r = cg_get_path(params->cgroup_path, pressure_file, &pressure_path[t]); if (r < 0) { *exit_status = EXIT_MEMORY; return log_oom(); } - } - } else if (cgroup_context->memory_pressure_watch == CGROUP_PRESSURE_WATCH_NO) { - memory_pressure_path = strdup("/dev/null"); /* /dev/null is explicit indicator for turning of memory pressure watch */ - if (!memory_pressure_path) { - *exit_status = EXIT_MEMORY; - return log_oom(); + + r = chmod_and_chown(pressure_path[t], 0644, uid, gid); + if (r < 0) { + log_full_errno(r == -ENOENT || ERRNO_IS_PRIVILEGE(r) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to adjust ownership of '%s', ignoring: %m", pressure_path[t]); + pressure_path[t] = mfree(pressure_path[t]); + } + /* First we use the current cgroup path to chmod and chown the pressure path, then pass the + * path relative to the cgroup namespace to environment variables and mounts. If chown/chmod + * fails, we should not pass pressure path environment variable or read-write mount to the + * unit. This is why we check if pressure_path[t] != NULL in the conditional below. */ + if (pressure_path[t] && needs_sandboxing && exec_needs_cgroup_namespace(context)) { + pressure_path[t] = mfree(pressure_path[t]); + r = cg_get_path("/", pressure_file, &pressure_path[t]); + if (r < 0) { + *exit_status = EXIT_MEMORY; + return log_oom(); + } + } + } else if (cgroup_context->pressure[t].watch == CGROUP_PRESSURE_WATCH_NO) { + pressure_path[t] = strdup("/dev/null"); /* /dev/null is explicit indicator for turning off pressure watch */ + if (!pressure_path[t]) { + *exit_status = EXIT_MEMORY; + return log_oom(); + } } } } @@ -5829,7 +5867,7 @@ int exec_invoke( shell, journal_stream_dev, journal_stream_ino, - memory_pressure_path, + pressure_path, needs_sandboxing, &our_env); if (r < 0) { @@ -6047,7 +6085,7 @@ int exec_invoke( runtime, &rootfs, /* delegate= */ false, - memory_pressure_path, + pressure_path, uid, gid, command, @@ -6144,7 +6182,7 @@ int exec_invoke( runtime, &rootfs, /* delegate= */ true, - memory_pressure_path, + pressure_path, uid, gid, command, diff --git a/src/core/execute-serialize.c b/src/core/execute-serialize.c index c8f802687ee5b..d3d23500a91f7 100644 --- a/src/core/execute-serialize.c +++ b/src/core/execute-serialize.c @@ -279,7 +279,11 @@ static int exec_cgroup_context_serialize(const CGroupContext *c, FILE *f) { if (r < 0) return r; - r = serialize_item(f, "exec-cgroup-context-memory-pressure-watch", cgroup_pressure_watch_to_string(c->memory_pressure_watch)); + r = serialize_item(f, "exec-cgroup-context-memory-pressure-watch", cgroup_pressure_watch_to_string(c->pressure[PRESSURE_MEMORY].watch)); + if (r < 0) + return r; + + r = serialize_item(f, "exec-cgroup-context-cpu-pressure-watch", cgroup_pressure_watch_to_string(c->pressure[PRESSURE_CPU].watch)); if (r < 0) return r; @@ -287,8 +291,14 @@ static int exec_cgroup_context_serialize(const CGroupContext *c, FILE *f) { if (r < 0) return r; - if (c->memory_pressure_threshold_usec != USEC_INFINITY) { - r = serialize_usec(f, "exec-cgroup-context-memory-pressure-threshold-usec", c->memory_pressure_threshold_usec); + if (c->pressure[PRESSURE_MEMORY].threshold_usec != USEC_INFINITY) { + r = serialize_usec(f, "exec-cgroup-context-memory-pressure-threshold-usec", c->pressure[PRESSURE_MEMORY].threshold_usec); + if (r < 0) + return r; + } + + if (c->pressure[PRESSURE_CPU].threshold_usec != USEC_INFINITY) { + r = serialize_usec(f, "exec-cgroup-context-cpu-pressure-threshold-usec", c->pressure[PRESSURE_CPU].threshold_usec); if (r < 0) return r; } @@ -621,15 +631,23 @@ static int exec_cgroup_context_deserialize(CGroupContext *c, FILE *f) { if (r < 0) return r; } else if ((val = startswith(l, "exec-cgroup-context-memory-pressure-watch="))) { - c->memory_pressure_watch = cgroup_pressure_watch_from_string(val); - if (c->memory_pressure_watch < 0) + c->pressure[PRESSURE_MEMORY].watch = cgroup_pressure_watch_from_string(val); + if (c->pressure[PRESSURE_MEMORY].watch < 0) + return -EINVAL; + } else if ((val = startswith(l, "exec-cgroup-context-cpu-pressure-watch="))) { + c->pressure[PRESSURE_CPU].watch = cgroup_pressure_watch_from_string(val); + if (c->pressure[PRESSURE_CPU].watch < 0) return -EINVAL; } else if ((val = startswith(l, "exec-cgroup-context-delegate-subgroup="))) { r = free_and_strdup(&c->delegate_subgroup, val); if (r < 0) return r; } else if ((val = startswith(l, "exec-cgroup-context-memory-pressure-threshold-usec="))) { - r = deserialize_usec(val, &c->memory_pressure_threshold_usec); + r = deserialize_usec(val, &c->pressure[PRESSURE_MEMORY].threshold_usec); + if (r < 0) + return r; + } else if ((val = startswith(l, "exec-cgroup-context-cpu-pressure-threshold-usec="))) { + r = deserialize_usec(val, &c->pressure[PRESSURE_CPU].threshold_usec); if (r < 0) return r; } else if ((val = startswith(l, "exec-cgroup-context-device-allow="))) { diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index bf808d220bb73..297836def17e7 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -276,8 +276,10 @@ {{type}}.SocketBindAllow, config_parse_cgroup_socket_bind, 0, offsetof({{type}}, cgroup_context.socket_bind_allow) {{type}}.SocketBindDeny, config_parse_cgroup_socket_bind, 0, offsetof({{type}}, cgroup_context.socket_bind_deny) {{type}}.RestrictNetworkInterfaces, config_parse_restrict_network_interfaces, 0, offsetof({{type}}, cgroup_context) -{{type}}.MemoryPressureThresholdSec, config_parse_sec, 0, offsetof({{type}}, cgroup_context.memory_pressure_threshold_usec) -{{type}}.MemoryPressureWatch, config_parse_memory_pressure_watch, 0, offsetof({{type}}, cgroup_context.memory_pressure_watch) +{{type}}.MemoryPressureThresholdSec, config_parse_sec, 0, offsetof({{type}}, cgroup_context.pressure[PRESSURE_MEMORY].threshold_usec) +{{type}}.MemoryPressureWatch, config_parse_pressure_watch, 0, offsetof({{type}}, cgroup_context.pressure[PRESSURE_MEMORY].watch) +{{type}}.CPUPressureThresholdSec, config_parse_sec, 0, offsetof({{type}}, cgroup_context.pressure[PRESSURE_CPU].threshold_usec) +{{type}}.CPUPressureWatch, config_parse_pressure_watch, 0, offsetof({{type}}, cgroup_context.pressure[PRESSURE_CPU].watch) {{type}}.NFTSet, config_parse_cgroup_nft_set, NFT_SET_PARSE_CGROUP, offsetof({{type}}, cgroup_context) {{type}}.CoredumpReceive, config_parse_bool, 0, offsetof({{type}}, cgroup_context.coredump_receive) {{type}}.BindNetworkInterface, config_parse_bind_network_interface, 0, offsetof({{type}}, cgroup_context) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 840804fcf8dde..16f3afafe95eb 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -154,7 +154,7 @@ DEFINE_CONFIG_PARSE_ENUM(config_parse_service_timeout_failure_mode, service_time DEFINE_CONFIG_PARSE_ENUM(config_parse_socket_bind, socket_address_bind_ipv6_only_or_bool, SocketAddressBindIPv6Only); DEFINE_CONFIG_PARSE_ENUM(config_parse_oom_policy, oom_policy, OOMPolicy); DEFINE_CONFIG_PARSE_ENUM(config_parse_managed_oom_preference, managed_oom_preference, ManagedOOMPreference); -DEFINE_CONFIG_PARSE_ENUM(config_parse_memory_pressure_watch, cgroup_pressure_watch, CGroupPressureWatch); +DEFINE_CONFIG_PARSE_ENUM(config_parse_pressure_watch, cgroup_pressure_watch, CGroupPressureWatch); DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_ip_tos, ip_tos, int, -1); DEFINE_CONFIG_PARSE_PTR(config_parse_cg_weight, cg_weight_parse, uint64_t); DEFINE_CONFIG_PARSE_PTR(config_parse_cg_cpu_weight, cg_cpu_weight_parse, uint64_t); diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index 4677564904c52..a5b7595dbfdb9 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -164,7 +164,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_watchdog_sec); CONFIG_PARSER_PROTOTYPE(config_parse_tty_size); CONFIG_PARSER_PROTOTYPE(config_parse_log_filter_patterns); CONFIG_PARSER_PROTOTYPE(config_parse_open_file); -CONFIG_PARSER_PROTOTYPE(config_parse_memory_pressure_watch); +CONFIG_PARSER_PROTOTYPE(config_parse_pressure_watch); CONFIG_PARSER_PROTOTYPE(config_parse_cgroup_nft_set); CONFIG_PARSER_PROTOTYPE(config_parse_mount_node); CONFIG_PARSER_PROTOTYPE(config_parse_concurrency_max); diff --git a/src/core/main.c b/src/core/main.c index d0c0023f899f4..7fcd0fa672dba 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -747,89 +747,91 @@ static int config_parse_crash_reboot( static int parse_config_file(void) { const ConfigTableItem items[] = { - { "Manager", "LogLevel", config_parse_level2, 0, NULL }, - { "Manager", "LogTarget", config_parse_target, 0, NULL }, - { "Manager", "LogColor", config_parse_color, 0, NULL }, - { "Manager", "LogLocation", config_parse_location, 0, NULL }, - { "Manager", "LogTime", config_parse_time, 0, NULL }, - { "Manager", "DumpCore", config_parse_bool, 0, &arg_dump_core }, - { "Manager", "CrashChVT", /* legacy */ config_parse_crash_chvt, 0, &arg_crash_chvt }, - { "Manager", "CrashChangeVT", config_parse_crash_chvt, 0, &arg_crash_chvt }, - { "Manager", "CrashShell", config_parse_bool, 0, &arg_crash_shell }, - { "Manager", "CrashReboot", config_parse_crash_reboot, 0, &arg_crash_action }, - { "Manager", "CrashAction", config_parse_crash_action, 0, &arg_crash_action }, - { "Manager", "ShowStatus", config_parse_show_status, 0, &arg_show_status }, - { "Manager", "StatusUnitFormat", config_parse_status_unit_format, 0, &arg_status_unit_format }, - { "Manager", "CPUAffinity", config_parse_cpu_set, 0, &arg_cpu_affinity }, - { "Manager", "NUMAPolicy", config_parse_numa_policy, 0, &arg_numa_policy.type }, - { "Manager", "NUMAMask", config_parse_numa_mask, 0, &arg_numa_policy.nodes }, - { "Manager", "JoinControllers", config_parse_warn_compat, DISABLED_LEGACY, NULL }, - { "Manager", "RuntimeWatchdogSec", config_parse_watchdog_sec, 0, &arg_runtime_watchdog }, - { "Manager", "RuntimeWatchdogPreSec", config_parse_watchdog_sec, 0, &arg_pretimeout_watchdog }, - { "Manager", "RebootWatchdogSec", config_parse_watchdog_sec, 0, &arg_reboot_watchdog }, - { "Manager", "ShutdownWatchdogSec", config_parse_watchdog_sec, 0, &arg_reboot_watchdog }, /* obsolete alias */ - { "Manager", "KExecWatchdogSec", config_parse_watchdog_sec, 0, &arg_kexec_watchdog }, - { "Manager", "WatchdogDevice", config_parse_path, 0, &arg_watchdog_device }, - { "Manager", "RuntimeWatchdogPreGovernor", config_parse_string, CONFIG_PARSE_STRING_SAFE, &arg_watchdog_pretimeout_governor }, - { "Manager", "CapabilityBoundingSet", config_parse_capability_set, 0, &arg_capability_bounding_set }, - { "Manager", "NoNewPrivileges", config_parse_bool, 0, &arg_no_new_privs }, - { "Manager", "ProtectSystem", config_parse_protect_system_pid1, 0, &arg_protect_system }, + { "Manager", "LogLevel", config_parse_level2, 0, NULL }, + { "Manager", "LogTarget", config_parse_target, 0, NULL }, + { "Manager", "LogColor", config_parse_color, 0, NULL }, + { "Manager", "LogLocation", config_parse_location, 0, NULL }, + { "Manager", "LogTime", config_parse_time, 0, NULL }, + { "Manager", "DumpCore", config_parse_bool, 0, &arg_dump_core }, + { "Manager", "CrashChVT", /* legacy */ config_parse_crash_chvt, 0, &arg_crash_chvt }, + { "Manager", "CrashChangeVT", config_parse_crash_chvt, 0, &arg_crash_chvt }, + { "Manager", "CrashShell", config_parse_bool, 0, &arg_crash_shell }, + { "Manager", "CrashReboot", config_parse_crash_reboot, 0, &arg_crash_action }, + { "Manager", "CrashAction", config_parse_crash_action, 0, &arg_crash_action }, + { "Manager", "ShowStatus", config_parse_show_status, 0, &arg_show_status }, + { "Manager", "StatusUnitFormat", config_parse_status_unit_format, 0, &arg_status_unit_format }, + { "Manager", "CPUAffinity", config_parse_cpu_set, 0, &arg_cpu_affinity }, + { "Manager", "NUMAPolicy", config_parse_numa_policy, 0, &arg_numa_policy.type }, + { "Manager", "NUMAMask", config_parse_numa_mask, 0, &arg_numa_policy.nodes }, + { "Manager", "JoinControllers", config_parse_warn_compat, DISABLED_LEGACY, NULL }, + { "Manager", "RuntimeWatchdogSec", config_parse_watchdog_sec, 0, &arg_runtime_watchdog }, + { "Manager", "RuntimeWatchdogPreSec", config_parse_watchdog_sec, 0, &arg_pretimeout_watchdog }, + { "Manager", "RebootWatchdogSec", config_parse_watchdog_sec, 0, &arg_reboot_watchdog }, + { "Manager", "ShutdownWatchdogSec", config_parse_watchdog_sec, 0, &arg_reboot_watchdog }, /* obsolete alias */ + { "Manager", "KExecWatchdogSec", config_parse_watchdog_sec, 0, &arg_kexec_watchdog }, + { "Manager", "WatchdogDevice", config_parse_path, 0, &arg_watchdog_device }, + { "Manager", "RuntimeWatchdogPreGovernor", config_parse_string, CONFIG_PARSE_STRING_SAFE, &arg_watchdog_pretimeout_governor }, + { "Manager", "CapabilityBoundingSet", config_parse_capability_set, 0, &arg_capability_bounding_set }, + { "Manager", "NoNewPrivileges", config_parse_bool, 0, &arg_no_new_privs }, + { "Manager", "ProtectSystem", config_parse_protect_system_pid1, 0, &arg_protect_system }, #if HAVE_SECCOMP - { "Manager", "SystemCallArchitectures", config_parse_syscall_archs, 0, &arg_syscall_archs }, + { "Manager", "SystemCallArchitectures", config_parse_syscall_archs, 0, &arg_syscall_archs }, #else - { "Manager", "SystemCallArchitectures", config_parse_warn_compat, DISABLED_CONFIGURATION, NULL }, + { "Manager", "SystemCallArchitectures", config_parse_warn_compat, DISABLED_CONFIGURATION, NULL }, #endif - { "Manager", "TimerSlackNSec", config_parse_nsec, 0, &arg_timer_slack_nsec }, - { "Manager", "DefaultTimerAccuracySec", config_parse_sec, 0, &arg_defaults.timer_accuracy_usec }, - { "Manager", "DefaultStandardOutput", config_parse_output_restricted, 0, &arg_defaults.std_output }, - { "Manager", "DefaultStandardError", config_parse_output_restricted, 0, &arg_defaults.std_error }, - { "Manager", "DefaultTimeoutStartSec", config_parse_sec, 0, &arg_defaults.timeout_start_usec }, - { "Manager", "DefaultTimeoutStopSec", config_parse_sec, 0, &arg_defaults.timeout_stop_usec }, - { "Manager", "DefaultTimeoutAbortSec", config_parse_default_timeout_abort, 0, NULL }, - { "Manager", "DefaultDeviceTimeoutSec", config_parse_sec, 0, &arg_defaults.device_timeout_usec }, - { "Manager", "DefaultRestartSec", config_parse_sec, 0, &arg_defaults.restart_usec }, - { "Manager", "DefaultStartLimitInterval", config_parse_sec, 0, &arg_defaults.start_limit.interval }, /* obsolete alias */ - { "Manager", "DefaultStartLimitIntervalSec", config_parse_sec, 0, &arg_defaults.start_limit.interval }, - { "Manager", "DefaultStartLimitBurst", config_parse_unsigned, 0, &arg_defaults.start_limit.burst }, - { "Manager", "DefaultRestrictSUIDSGID", config_parse_bool, 0, &arg_defaults.restrict_suid_sgid }, - { "Manager", "DefaultEnvironment", config_parse_environ, arg_runtime_scope, &arg_default_environment }, - { "Manager", "ManagerEnvironment", config_parse_environ, arg_runtime_scope, &arg_manager_environment }, - { "Manager", "DefaultLimitCPU", config_parse_rlimit, RLIMIT_CPU, arg_defaults.rlimit }, - { "Manager", "DefaultLimitFSIZE", config_parse_rlimit, RLIMIT_FSIZE, arg_defaults.rlimit }, - { "Manager", "DefaultLimitDATA", config_parse_rlimit, RLIMIT_DATA, arg_defaults.rlimit }, - { "Manager", "DefaultLimitSTACK", config_parse_rlimit, RLIMIT_STACK, arg_defaults.rlimit }, - { "Manager", "DefaultLimitCORE", config_parse_rlimit, RLIMIT_CORE, arg_defaults.rlimit }, - { "Manager", "DefaultLimitRSS", config_parse_rlimit, RLIMIT_RSS, arg_defaults.rlimit }, - { "Manager", "DefaultLimitNOFILE", config_parse_rlimit, RLIMIT_NOFILE, arg_defaults.rlimit }, - { "Manager", "DefaultLimitAS", config_parse_rlimit, RLIMIT_AS, arg_defaults.rlimit }, - { "Manager", "DefaultLimitNPROC", config_parse_rlimit, RLIMIT_NPROC, arg_defaults.rlimit }, - { "Manager", "DefaultLimitMEMLOCK", config_parse_rlimit, RLIMIT_MEMLOCK, arg_defaults.rlimit }, - { "Manager", "DefaultLimitLOCKS", config_parse_rlimit, RLIMIT_LOCKS, arg_defaults.rlimit }, - { "Manager", "DefaultLimitSIGPENDING", config_parse_rlimit, RLIMIT_SIGPENDING, arg_defaults.rlimit }, - { "Manager", "DefaultLimitMSGQUEUE", config_parse_rlimit, RLIMIT_MSGQUEUE, arg_defaults.rlimit }, - { "Manager", "DefaultLimitNICE", config_parse_rlimit, RLIMIT_NICE, arg_defaults.rlimit }, - { "Manager", "DefaultLimitRTPRIO", config_parse_rlimit, RLIMIT_RTPRIO, arg_defaults.rlimit }, - { "Manager", "DefaultLimitRTTIME", config_parse_rlimit, RLIMIT_RTTIME, arg_defaults.rlimit }, - { "Manager", "DefaultCPUAccounting", config_parse_warn_compat, DISABLED_LEGACY, NULL }, - { "Manager", "DefaultIOAccounting", config_parse_bool, 0, &arg_defaults.io_accounting }, - { "Manager", "DefaultIPAccounting", config_parse_bool, 0, &arg_defaults.ip_accounting }, - { "Manager", "DefaultBlockIOAccounting", config_parse_warn_compat, DISABLED_LEGACY, NULL }, - { "Manager", "DefaultMemoryAccounting", config_parse_bool, 0, &arg_defaults.memory_accounting }, - { "Manager", "DefaultTasksAccounting", config_parse_bool, 0, &arg_defaults.tasks_accounting }, - { "Manager", "DefaultTasksMax", config_parse_tasks_max, 0, &arg_defaults.tasks_max }, - { "Manager", "DefaultMemoryPressureThresholdSec", config_parse_sec, 0, &arg_defaults.memory_pressure_threshold_usec }, - { "Manager", "DefaultMemoryPressureWatch", config_parse_memory_pressure_watch, 0, &arg_defaults.memory_pressure_watch }, - { "Manager", "CtrlAltDelBurstAction", config_parse_emergency_action, arg_runtime_scope, &arg_cad_burst_action }, - { "Manager", "DefaultOOMPolicy", config_parse_oom_policy, 0, &arg_defaults.oom_policy }, - { "Manager", "DefaultOOMScoreAdjust", config_parse_oom_score_adjust, 0, NULL }, - { "Manager", "ReloadLimitIntervalSec", config_parse_sec, 0, &arg_reload_limit_interval_sec }, - { "Manager", "ReloadLimitBurst", config_parse_unsigned, 0, &arg_reload_limit_burst }, - { "Manager", "DefaultMemoryZSwapWriteback", config_parse_bool, 0, &arg_defaults.memory_zswap_writeback }, - { "Manager", "MinimumUptimeSec", config_parse_sec, 0, &arg_minimum_uptime_usec }, + { "Manager", "TimerSlackNSec", config_parse_nsec, 0, &arg_timer_slack_nsec }, + { "Manager", "DefaultTimerAccuracySec", config_parse_sec, 0, &arg_defaults.timer_accuracy_usec }, + { "Manager", "DefaultStandardOutput", config_parse_output_restricted, 0, &arg_defaults.std_output }, + { "Manager", "DefaultStandardError", config_parse_output_restricted, 0, &arg_defaults.std_error }, + { "Manager", "DefaultTimeoutStartSec", config_parse_sec, 0, &arg_defaults.timeout_start_usec }, + { "Manager", "DefaultTimeoutStopSec", config_parse_sec, 0, &arg_defaults.timeout_stop_usec }, + { "Manager", "DefaultTimeoutAbortSec", config_parse_default_timeout_abort, 0, NULL }, + { "Manager", "DefaultDeviceTimeoutSec", config_parse_sec, 0, &arg_defaults.device_timeout_usec }, + { "Manager", "DefaultRestartSec", config_parse_sec, 0, &arg_defaults.restart_usec }, + { "Manager", "DefaultStartLimitInterval", config_parse_sec, 0, &arg_defaults.start_limit.interval }, /* obsolete alias */ + { "Manager", "DefaultStartLimitIntervalSec", config_parse_sec, 0, &arg_defaults.start_limit.interval }, + { "Manager", "DefaultStartLimitBurst", config_parse_unsigned, 0, &arg_defaults.start_limit.burst }, + { "Manager", "DefaultRestrictSUIDSGID", config_parse_bool, 0, &arg_defaults.restrict_suid_sgid }, + { "Manager", "DefaultEnvironment", config_parse_environ, arg_runtime_scope, &arg_default_environment }, + { "Manager", "ManagerEnvironment", config_parse_environ, arg_runtime_scope, &arg_manager_environment }, + { "Manager", "DefaultLimitCPU", config_parse_rlimit, RLIMIT_CPU, arg_defaults.rlimit }, + { "Manager", "DefaultLimitFSIZE", config_parse_rlimit, RLIMIT_FSIZE, arg_defaults.rlimit }, + { "Manager", "DefaultLimitDATA", config_parse_rlimit, RLIMIT_DATA, arg_defaults.rlimit }, + { "Manager", "DefaultLimitSTACK", config_parse_rlimit, RLIMIT_STACK, arg_defaults.rlimit }, + { "Manager", "DefaultLimitCORE", config_parse_rlimit, RLIMIT_CORE, arg_defaults.rlimit }, + { "Manager", "DefaultLimitRSS", config_parse_rlimit, RLIMIT_RSS, arg_defaults.rlimit }, + { "Manager", "DefaultLimitNOFILE", config_parse_rlimit, RLIMIT_NOFILE, arg_defaults.rlimit }, + { "Manager", "DefaultLimitAS", config_parse_rlimit, RLIMIT_AS, arg_defaults.rlimit }, + { "Manager", "DefaultLimitNPROC", config_parse_rlimit, RLIMIT_NPROC, arg_defaults.rlimit }, + { "Manager", "DefaultLimitMEMLOCK", config_parse_rlimit, RLIMIT_MEMLOCK, arg_defaults.rlimit }, + { "Manager", "DefaultLimitLOCKS", config_parse_rlimit, RLIMIT_LOCKS, arg_defaults.rlimit }, + { "Manager", "DefaultLimitSIGPENDING", config_parse_rlimit, RLIMIT_SIGPENDING, arg_defaults.rlimit }, + { "Manager", "DefaultLimitMSGQUEUE", config_parse_rlimit, RLIMIT_MSGQUEUE, arg_defaults.rlimit }, + { "Manager", "DefaultLimitNICE", config_parse_rlimit, RLIMIT_NICE, arg_defaults.rlimit }, + { "Manager", "DefaultLimitRTPRIO", config_parse_rlimit, RLIMIT_RTPRIO, arg_defaults.rlimit }, + { "Manager", "DefaultLimitRTTIME", config_parse_rlimit, RLIMIT_RTTIME, arg_defaults.rlimit }, + { "Manager", "DefaultCPUAccounting", config_parse_warn_compat, DISABLED_LEGACY, NULL }, + { "Manager", "DefaultIOAccounting", config_parse_bool, 0, &arg_defaults.io_accounting }, + { "Manager", "DefaultIPAccounting", config_parse_bool, 0, &arg_defaults.ip_accounting }, + { "Manager", "DefaultBlockIOAccounting", config_parse_warn_compat, DISABLED_LEGACY, NULL }, + { "Manager", "DefaultMemoryAccounting", config_parse_bool, 0, &arg_defaults.memory_accounting }, + { "Manager", "DefaultTasksAccounting", config_parse_bool, 0, &arg_defaults.tasks_accounting }, + { "Manager", "DefaultTasksMax", config_parse_tasks_max, 0, &arg_defaults.tasks_max }, + { "Manager", "DefaultMemoryPressureThresholdSec", config_parse_sec, 0, &arg_defaults.pressure[PRESSURE_MEMORY].threshold_usec }, + { "Manager", "DefaultMemoryPressureWatch", config_parse_pressure_watch, 0, &arg_defaults.pressure[PRESSURE_MEMORY].watch }, + { "Manager", "DefaultCPUPressureThresholdSec", config_parse_sec, 0, &arg_defaults.pressure[PRESSURE_CPU].threshold_usec }, + { "Manager", "DefaultCPUPressureWatch", config_parse_pressure_watch, 0, &arg_defaults.pressure[PRESSURE_CPU].watch }, + { "Manager", "CtrlAltDelBurstAction", config_parse_emergency_action, arg_runtime_scope, &arg_cad_burst_action }, + { "Manager", "DefaultOOMPolicy", config_parse_oom_policy, 0, &arg_defaults.oom_policy }, + { "Manager", "DefaultOOMScoreAdjust", config_parse_oom_score_adjust, 0, NULL }, + { "Manager", "ReloadLimitIntervalSec", config_parse_sec, 0, &arg_reload_limit_interval_sec }, + { "Manager", "ReloadLimitBurst", config_parse_unsigned, 0, &arg_reload_limit_burst }, + { "Manager", "DefaultMemoryZSwapWriteback", config_parse_bool, 0, &arg_defaults.memory_zswap_writeback }, + { "Manager", "MinimumUptimeSec", config_parse_sec, 0, &arg_minimum_uptime_usec }, #if ENABLE_SMACK - { "Manager", "DefaultSmackProcessLabel", config_parse_string, 0, &arg_defaults.smack_process_label }, + { "Manager", "DefaultSmackProcessLabel", config_parse_string, 0, &arg_defaults.smack_process_label }, #else - { "Manager", "DefaultSmackProcessLabel", config_parse_warn_compat, DISABLED_CONFIGURATION, NULL }, + { "Manager", "DefaultSmackProcessLabel", config_parse_warn_compat, DISABLED_CONFIGURATION, NULL }, #endif {} }; diff --git a/src/core/manager.c b/src/core/manager.c index 85b68b86d2bf6..c71d1a5d69a6e 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -616,6 +616,8 @@ static char** sanitize_environment(char **l) { l, "CACHE_DIRECTORY", "CONFIGURATION_DIRECTORY", + "CPU_PRESSURE_WATCH", + "CPU_PRESSURE_WRITE", "CREDENTIALS_DIRECTORY", "EXIT_CODE", "EXIT_STATUS", @@ -796,26 +798,37 @@ static int manager_setup_sigchld_event_source(Manager *m) { return 0; } -int manager_setup_memory_pressure_event_source(Manager *m) { +typedef int (*pressure_add_t)(sd_event *, sd_event_source **, sd_event_handler_t, void *); +typedef int (*pressure_set_period_t)(sd_event_source *, usec_t, usec_t); + +static const struct { + pressure_add_t add; + pressure_set_period_t set_period; +} pressure_dispatch_table[_PRESSURE_RESOURCE_MAX] = { + [PRESSURE_MEMORY] = { sd_event_add_memory_pressure, sd_event_source_set_memory_pressure_period }, + [PRESSURE_CPU] = { sd_event_add_cpu_pressure, sd_event_source_set_cpu_pressure_period }, +}; + +int manager_setup_pressure_event_source(Manager *m, PressureResource t) { int r; assert(m); + assert(t >= 0 && t < _PRESSURE_RESOURCE_MAX); - m->memory_pressure_event_source = sd_event_source_disable_unref(m->memory_pressure_event_source); + m->pressure_event_source[t] = sd_event_source_disable_unref(m->pressure_event_source[t]); - r = sd_event_add_memory_pressure(m->event, &m->memory_pressure_event_source, NULL, NULL); + r = pressure_dispatch_table[t].add(m->event, &m->pressure_event_source[t], NULL, NULL); if (r < 0) log_full_errno(ERRNO_IS_NOT_SUPPORTED(r) || ERRNO_IS_PRIVILEGE(r) || (r == -EHOSTDOWN) ? LOG_DEBUG : LOG_NOTICE, r, - "Failed to establish memory pressure event source, ignoring: %m"); - else if (m->defaults.memory_pressure_threshold_usec != USEC_INFINITY) { - - /* If there's a default memory pressure threshold set, also apply it to the service manager itself */ - r = sd_event_source_set_memory_pressure_period( - m->memory_pressure_event_source, - m->defaults.memory_pressure_threshold_usec, - MEMORY_PRESSURE_DEFAULT_WINDOW_USEC); + "Failed to establish %s pressure event source, ignoring: %m", pressure_resource_to_string(t)); + else if (m->defaults.pressure[t].threshold_usec != USEC_INFINITY) { + + r = pressure_dispatch_table[t].set_period( + m->pressure_event_source[t], + m->defaults.pressure[t].threshold_usec, + PRESSURE_DEFAULT_WINDOW_USEC); if (r < 0) - log_warning_errno(r, "Failed to adjust memory pressure threshold, ignoring: %m"); + log_warning_errno(r, "Failed to adjust %s pressure threshold, ignoring: %m", pressure_resource_to_string(t)); } return 0; @@ -1001,9 +1014,11 @@ int manager_new(RuntimeScope runtime_scope, ManagerTestRunFlags test_run_flags, if (r < 0) return r; - r = manager_setup_memory_pressure_event_source(m); - if (r < 0) - return r; + for (PressureResource t = 0; t < _PRESSURE_RESOURCE_MAX; t++) { + r = manager_setup_pressure_event_source(m, t); + if (r < 0) + return r; + } #if HAVE_LIBBPF if (MANAGER_IS_SYSTEM(m) && bpf_restrict_fs_supported(/* initialize= */ true)) { @@ -1711,7 +1726,8 @@ Manager* manager_free(Manager *m) { sd_event_source_unref(m->user_lookup_event_source); sd_event_source_unref(m->handoff_timestamp_event_source); sd_event_source_unref(m->pidref_event_source); - sd_event_source_unref(m->memory_pressure_event_source); + FOREACH_ARRAY(pressure_event_source, m->pressure_event_source, _PRESSURE_RESOURCE_MAX) + sd_event_source_unref(*pressure_event_source); safe_close(m->signal_fd); safe_close(m->notify_fd); @@ -4300,8 +4316,7 @@ int manager_set_unit_defaults(Manager *m, const UnitDefaults *defaults) { m->defaults.oom_score_adjust = defaults->oom_score_adjust; m->defaults.oom_score_adjust_set = defaults->oom_score_adjust_set; - m->defaults.memory_pressure_watch = defaults->memory_pressure_watch; - m->defaults.memory_pressure_threshold_usec = defaults->memory_pressure_threshold_usec; + memcpy(m->defaults.pressure, defaults->pressure, sizeof(m->defaults.pressure)); m->defaults.memory_zswap_writeback = defaults->memory_zswap_writeback; @@ -5195,8 +5210,10 @@ void unit_defaults_init(UnitDefaults *defaults, RuntimeScope scope) { .tasks_max = DEFAULT_TASKS_MAX, .timer_accuracy_usec = 1 * USEC_PER_MINUTE, - .memory_pressure_watch = CGROUP_PRESSURE_WATCH_AUTO, - .memory_pressure_threshold_usec = MEMORY_PRESSURE_DEFAULT_THRESHOLD_USEC, + .pressure = { + [PRESSURE_MEMORY] = { .watch = CGROUP_PRESSURE_WATCH_AUTO, .threshold_usec = PRESSURE_DEFAULT_THRESHOLD_USEC }, + [PRESSURE_CPU] = { .watch = CGROUP_PRESSURE_WATCH_AUTO, .threshold_usec = PRESSURE_DEFAULT_THRESHOLD_USEC }, + }, .oom_policy = OOM_STOP, .oom_score_adjust_set = false, diff --git a/src/core/manager.h b/src/core/manager.h index 1c04deabee9ef..7d58c330a1b82 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -149,8 +149,7 @@ typedef struct UnitDefaults { bool memory_zswap_writeback; - CGroupPressureWatch memory_pressure_watch; - usec_t memory_pressure_threshold_usec; + CGroupPressure pressure[_PRESSURE_RESOURCE_MAX]; char *smack_process_label; @@ -481,7 +480,7 @@ typedef struct Manager { /* Dump*() are slow, so always rate limit them to 10 per 10 minutes */ RateLimit dump_ratelimit; - sd_event_source *memory_pressure_event_source; + sd_event_source *pressure_event_source[_PRESSURE_RESOURCE_MAX]; /* For NFTSet= */ sd_netlink *nfnl; @@ -562,7 +561,7 @@ void manager_unwatch_pidref(Manager *m, const PidRef *pid); unsigned manager_dispatch_load_queue(Manager *m); -int manager_setup_memory_pressure_event_source(Manager *m); +int manager_setup_pressure_event_source(Manager *m, PressureResource t); int manager_default_environment(Manager *m); int manager_transient_environment_add(Manager *m, char **plus); diff --git a/src/core/system.conf.in b/src/core/system.conf.in index ef2c59bd79a8e..d3cb0160a01ea 100644 --- a/src/core/system.conf.in +++ b/src/core/system.conf.in @@ -78,6 +78,8 @@ #DefaultLimitRTTIME= #DefaultMemoryPressureThresholdSec=200ms #DefaultMemoryPressureWatch=auto +#DefaultCPUPressureThresholdSec=200ms +#DefaultCPUPressureWatch=auto #DefaultOOMPolicy=stop #DefaultSmackProcessLabel= #DefaultRestrictSUIDSGID= diff --git a/src/core/unit.c b/src/core/unit.c index 6dd5599f0a773..dc205097e050f 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -178,10 +178,9 @@ static void unit_init(Unit *u) { if (u->type != UNIT_SLICE) cc->tasks_max = u->manager->defaults.tasks_max; - cc->memory_pressure_watch = u->manager->defaults.memory_pressure_watch; - cc->memory_pressure_threshold_usec = u->manager->defaults.memory_pressure_threshold_usec; - cc->memory_zswap_writeback = u->manager->defaults.memory_zswap_writeback; + + memcpy(cc->pressure, u->manager->defaults.pressure, sizeof(cc->pressure)); } ec = unit_get_exec_context(u); diff --git a/src/core/user.conf.in b/src/core/user.conf.in index 9c37f4b54e9bd..fe45c00b74e4c 100644 --- a/src/core/user.conf.in +++ b/src/core/user.conf.in @@ -54,6 +54,8 @@ #DefaultLimitRTTIME= #DefaultMemoryPressureThresholdSec=200ms #DefaultMemoryPressureWatch=auto +#DefaultCPUPressureThresholdSec=200ms +#DefaultCPUPressureWatch=auto #DefaultSmackProcessLabel= #DefaultRestrictSUIDSGID= #ReloadLimitIntervalSec= diff --git a/src/core/varlink-cgroup.c b/src/core/varlink-cgroup.c index 65c07ecfad477..d4ec6049e66dc 100644 --- a/src/core/varlink-cgroup.c +++ b/src/core/varlink-cgroup.c @@ -323,8 +323,10 @@ int unit_cgroup_context_build_json(sd_json_variant **ret, const char *name, void JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("ManagedOOMMemoryPressureLimit", c->moom_mem_pressure_limit), JSON_BUILD_PAIR_FINITE_USEC("ManagedOOMMemoryPressureDurationUSec", c->moom_mem_pressure_duration_usec), SD_JSON_BUILD_PAIR_STRING("ManagedOOMPreference", managed_oom_preference_to_string(c->moom_preference)), - SD_JSON_BUILD_PAIR_STRING("MemoryPressureWatch", cgroup_pressure_watch_to_string(c->memory_pressure_watch)), - JSON_BUILD_PAIR_FINITE_USEC("MemoryPressureThresholdUSec", c->memory_pressure_threshold_usec), + SD_JSON_BUILD_PAIR_STRING("MemoryPressureWatch", cgroup_pressure_watch_to_string(c->pressure[PRESSURE_MEMORY].watch)), + JSON_BUILD_PAIR_FINITE_USEC("MemoryPressureThresholdUSec", c->pressure[PRESSURE_MEMORY].threshold_usec), + SD_JSON_BUILD_PAIR_STRING("CPUPressureWatch", cgroup_pressure_watch_to_string(c->pressure[PRESSURE_CPU].watch)), + JSON_BUILD_PAIR_FINITE_USEC("CPUPressureThresholdUSec", c->pressure[PRESSURE_CPU].threshold_usec), /* Others */ SD_JSON_BUILD_PAIR_BOOLEAN("CoredumpReceive", c->coredump_receive)); diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index c039ea8e53610..3953b8619f7af 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -106,8 +106,10 @@ static int manager_context_build_json(sd_json_variant **ret, const char *name, v SD_JSON_BUILD_PAIR_BOOLEAN("DefaultTasksAccounting", m->defaults.tasks_accounting), SD_JSON_BUILD_PAIR_CALLBACK("DefaultLimits", rlimit_table_build_json, m->defaults.rlimit), SD_JSON_BUILD_PAIR_UNSIGNED("DefaultTasksMax", cgroup_tasks_max_resolve(&m->defaults.tasks_max)), - JSON_BUILD_PAIR_FINITE_USEC("DefaultMemoryPressureThresholdUSec", m->defaults.memory_pressure_threshold_usec), - SD_JSON_BUILD_PAIR_STRING("DefaultMemoryPressureWatch", cgroup_pressure_watch_to_string(m->defaults.memory_pressure_watch)), + JSON_BUILD_PAIR_FINITE_USEC("DefaultMemoryPressureThresholdUSec", m->defaults.pressure[PRESSURE_MEMORY].threshold_usec), + SD_JSON_BUILD_PAIR_STRING("DefaultMemoryPressureWatch", cgroup_pressure_watch_to_string(m->defaults.pressure[PRESSURE_MEMORY].watch)), + JSON_BUILD_PAIR_FINITE_USEC("DefaultCPUPressureThresholdUSec", m->defaults.pressure[PRESSURE_CPU].threshold_usec), + SD_JSON_BUILD_PAIR_STRING("DefaultCPUPressureWatch", cgroup_pressure_watch_to_string(m->defaults.pressure[PRESSURE_CPU].watch)), JSON_BUILD_PAIR_FINITE_USEC("RuntimeWatchdogUSec", manager_get_watchdog(m, WATCHDOG_RUNTIME)), JSON_BUILD_PAIR_FINITE_USEC("RebootWatchdogUSec", manager_get_watchdog(m, WATCHDOG_REBOOT)), JSON_BUILD_PAIR_FINITE_USEC("KExecWatchdogUSec", manager_get_watchdog(m, WATCHDOG_KEXEC)), diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index 4b539a35cf60b..aba6bf9b4787b 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -1949,8 +1949,7 @@ static int event_add_pressure( assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); assert_return(!event_origin_changed(e), -ECHILD); - assert(resource >= 0 && resource < _PRESSURE_RESOURCE_MAX); - const PressureResourceInfo *info = &pressure_resource_info[resource]; + const PressureResourceInfo *info = pressure_resource_get_info(resource); if (!callback) callback = default_callback; diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 84de3478a7f9e..9c732543fac7d 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -2383,6 +2383,7 @@ static const BusProperty cgroup_properties[] = { { "ManagedOOMMemoryPressure", bus_append_string }, { "ManagedOOMPreference", bus_append_string }, { "MemoryPressureWatch", bus_append_string }, + { "CPUPressureWatch", bus_append_string }, { "DelegateSubgroup", bus_append_string }, { "ManagedOOMMemoryPressureLimit", bus_append_parse_permyriad }, { "MemoryAccounting", bus_append_parse_boolean }, @@ -2421,6 +2422,7 @@ static const BusProperty cgroup_properties[] = { { "SocketBindAllow", bus_append_socket_filter }, { "SocketBindDeny", bus_append_socket_filter }, { "MemoryPressureThresholdSec", bus_append_parse_sec_rename }, + { "CPUPressureThresholdSec", bus_append_parse_sec_rename }, { "NFTSet", bus_append_nft_set }, { "BindNetworkInterface", bus_append_string }, diff --git a/src/shared/varlink-io.systemd.Manager.c b/src/shared/varlink-io.systemd.Manager.c index ddf15b173ecc6..f947c0a05615c 100644 --- a/src/shared/varlink-io.systemd.Manager.c +++ b/src/shared/varlink-io.systemd.Manager.c @@ -64,6 +64,10 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(DefaultMemoryPressureThresholdUSec, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultMemoryPressureWatch="), SD_VARLINK_DEFINE_FIELD(DefaultMemoryPressureWatch, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultCPUPressureThresholdUSec="), + SD_VARLINK_DEFINE_FIELD(DefaultCPUPressureThresholdUSec, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultCPUPressureWatch="), + SD_VARLINK_DEFINE_FIELD(DefaultCPUPressureWatch, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#RuntimeWatchdogSec="), SD_VARLINK_DEFINE_FIELD(RuntimeWatchdogUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#RebootWatchdogSec="), diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c index 05676210ef2a4..a230f29daba8b 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -228,6 +228,10 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(MemoryPressureWatch, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#MemoryPressureThresholdSec="), SD_VARLINK_DEFINE_FIELD(MemoryPressureThresholdUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#CPUPressureWatch="), + SD_VARLINK_DEFINE_FIELD(CPUPressureWatch, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#CPUPressureThresholdSec="), + SD_VARLINK_DEFINE_FIELD(CPUPressureThresholdUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), /* Others */ SD_VARLINK_FIELD_COMMENT("Reflects whether to forward coredumps for processes that crash within this cgroup"), diff --git a/test/integration-tests/TEST-79-MEMPRESS/meson.build b/test/integration-tests/TEST-79-PRESSURE/meson.build similarity index 100% rename from test/integration-tests/TEST-79-MEMPRESS/meson.build rename to test/integration-tests/TEST-79-PRESSURE/meson.build diff --git a/test/integration-tests/meson.build b/test/integration-tests/meson.build index 5d71e87c79cbc..198371ccae49a 100644 --- a/test/integration-tests/meson.build +++ b/test/integration-tests/meson.build @@ -91,7 +91,7 @@ foreach dirname : [ 'TEST-74-AUX-UTILS', 'TEST-75-RESOLVED', 'TEST-78-SIGQUEUE', - 'TEST-79-MEMPRESS', + 'TEST-79-PRESSURE', 'TEST-80-NOTIFYACCESS', 'TEST-81-GENERATORS', 'TEST-82-SOFTREBOOT', diff --git a/test/units/TEST-79-MEMPRESS.sh b/test/units/TEST-79-PRESSURE.sh similarity index 56% rename from test/units/TEST-79-MEMPRESS.sh rename to test/units/TEST-79-PRESSURE.sh index 065916096682e..d4e4a9e06b5b4 100755 --- a/test/units/TEST-79-MEMPRESS.sh +++ b/test/units/TEST-79-PRESSURE.sh @@ -13,7 +13,7 @@ if ! cat /proc/pressure/memory >/dev/null ; then exit 0 fi -CGROUP=/sys/fs/cgroup/"$(systemctl show TEST-79-MEMPRESS.service -P ControlGroup)" +CGROUP=/sys/fs/cgroup/"$(systemctl show TEST-79-PRESSURE.service -P ControlGroup)" test -d "$CGROUP" if ! test -f "$CGROUP"/memory.pressure ; then @@ -61,4 +61,57 @@ systemd-run \ rm "$SCRIPT" +# Now test CPU pressure + +if ! cat /proc/pressure/cpu >/dev/null ; then + echo "kernel has no CPU PSI support." >&2 + echo OK >/testok + exit 0 +fi + +if ! test -f "$CGROUP"/cpu.pressure ; then + echo "No CPU accounting/PSI delegated via cgroup, can't test." >&2 + echo OK >/testok + exit 0 +fi + +UNIT="test-cpupress-$RANDOM.service" +SCRIPT="/tmp/cpupress-$RANDOM.sh" + +cat >"$SCRIPT" <<'EOF' +#!/usr/bin/env bash + +set -ex + +export +id + +test -n "$CPU_PRESSURE_WATCH" +test "$CPU_PRESSURE_WATCH" != /dev/null +test -w "$CPU_PRESSURE_WATCH" + +ls -al "$CPU_PRESSURE_WATCH" + +EXPECTED="$(echo -n -e "some 123000 2000000\x00" | base64)" + +test "$EXPECTED" = "$CPU_PRESSURE_WRITE" + +EOF + +chmod +x "$SCRIPT" + +systemd-run \ + -u "$UNIT" \ + -p Type=exec \ + -p ProtectControlGroups=1 \ + -p DynamicUser=1 \ + -p CPUPressureWatch=on \ + -p CPUPressureThresholdSec=123ms \ + -p BindPaths=$SCRIPT \ + `# Make sanitizers happy when DynamicUser=1 pulls in instrumented systemd NSS modules` \ + -p EnvironmentFile=-/usr/lib/systemd/systemd-asan-env \ + --wait "$SCRIPT" + +rm "$SCRIPT" + touch /testok From 594659da06be7398b2dc1efebae575353d20fd34 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sat, 7 Mar 2026 23:37:55 +0100 Subject: [PATCH 0867/1296] core: Add I/O pressure support --- man/org.freedesktop.systemd1.xml | 126 ++++++++++++++++++--- man/rules/meson.build | 3 + man/sd_event_add_memory_pressure.xml | 101 ++++++++++++----- man/systemd-system.conf.xml | 14 +++ man/systemd.exec.xml | 12 ++ man/systemd.resource-control.xml | 49 ++++++++ src/basic/psi-util.c | 6 + src/basic/psi-util.h | 1 + src/core/cgroup.c | 7 ++ src/core/cgroup.h | 8 ++ src/core/dbus-cgroup.c | 12 +- src/core/dbus-manager.c | 2 + src/core/execute-serialize.c | 18 +++ src/core/load-fragment-gperf.gperf.in | 2 + src/core/main.c | 2 + src/core/manager.c | 4 + src/core/system.conf.in | 2 + src/core/user.conf.in | 2 + src/core/varlink-cgroup.c | 2 + src/core/varlink-manager.c | 2 + src/libsystemd/libsystemd.sym | 3 + src/libsystemd/sd-event/event-source.h | 1 + src/libsystemd/sd-event/sd-event.c | 49 +++++++- src/shared/bus-unit-util.c | 2 + src/shared/varlink-io.systemd.Manager.c | 4 + src/shared/varlink-io.systemd.Unit.c | 4 + src/systemd/sd-event.h | 3 + src/test/test-pressure.c | 143 ++++++++++++++++++++++++ test/units/TEST-79-PRESSURE.sh | 53 +++++++++ 29 files changed, 586 insertions(+), 51 deletions(-) diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index 027a8deeb4653..76a8dd045f6c6 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -556,6 +556,10 @@ node /org/freedesktop/systemd1 { readonly t DefaultCPUPressureThresholdUSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly s DefaultCPUPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t DefaultIOPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s DefaultIOPressureWatch = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly t TimerSlackNSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") @@ -801,6 +805,10 @@ node /org/freedesktop/systemd1 { + + + + @@ -1255,6 +1263,10 @@ node /org/freedesktop/systemd1 { + + + + @@ -3082,6 +3094,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t CPUPressureThresholdUSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s IOPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t IOPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiss) NFTSet = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b CoredumpReceive = ...; @@ -3755,6 +3771,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + + + @@ -4451,6 +4471,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + + + @@ -5354,6 +5378,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t CPUPressureThresholdUSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s IOPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t IOPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiss) NFTSet = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b CoredumpReceive = ...; @@ -6043,6 +6071,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + + + @@ -6713,6 +6745,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + + + @@ -7439,6 +7475,10 @@ node /org/freedesktop/systemd1/unit/home_2emount { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t CPUPressureThresholdUSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s IOPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t IOPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiss) NFTSet = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b CoredumpReceive = ...; @@ -8052,6 +8092,10 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + + + @@ -8630,6 +8674,10 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + + + @@ -9489,6 +9537,10 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t CPUPressureThresholdUSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s IOPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t IOPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiss) NFTSet = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b CoredumpReceive = ...; @@ -10084,6 +10136,10 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + + + @@ -10644,6 +10700,10 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + + + @@ -11356,6 +11416,10 @@ node /org/freedesktop/systemd1/unit/system_2eslice { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t CPUPressureThresholdUSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s IOPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t IOPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiss) NFTSet = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b CoredumpReceive = ...; @@ -11533,6 +11597,10 @@ node /org/freedesktop/systemd1/unit/system_2eslice { + + + + @@ -11725,6 +11793,10 @@ node /org/freedesktop/systemd1/unit/system_2eslice { + + + + @@ -11940,6 +12012,10 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t CPUPressureThresholdUSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s IOPressureWatch = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t IOPressureThresholdUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiss) NFTSet = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b CoredumpReceive = ...; @@ -12131,6 +12207,10 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { + + + + @@ -12347,6 +12427,10 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { + + + + @@ -12560,8 +12644,10 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ KillUnitSubgroup() were added in version 258. TransactionsWithOrderingCycle was added in version 259. DefaultMemoryZSwapWriteback, - DefaultCPUPressureThresholdUSec and - DefaultCPUPressureWatch were added in version 261. + DefaultCPUPressureThresholdUSec, + DefaultCPUPressureWatch, + DefaultIOPressureThresholdUSec, and + DefaultIOPressureWatch were added in version 261. Unit Objects @@ -12653,8 +12739,10 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ ExecReloadPostEx were added in version 259. BindNetworkInterface, MemoryTHP, RefreshOnReload, and RootMStack were added in version 260. - CPUPressureThresholdUSec and - CPUPressureWatch were added in version 261. + CPUPressureThresholdUSec, + CPUPressureWatch, + IOPressureThresholdUSec, and + IOPressureWatch were added in version 261. Socket Unit Objects @@ -12725,8 +12813,10 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ ManagedOOMKills were added in 259. BindNetworkInterface MemoryTHP, and RootMStack were added in version 260. - CPUPressureThresholdUSec and - CPUPressureWatch were added in version 261. + CPUPressureThresholdUSec, + CPUPressureWatch, + IOPressureThresholdUSec, and + IOPressureWatch were added in version 261. Mount Unit Objects @@ -12792,8 +12882,10 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ ManagedOOMKills were added in 259. BindNetworkInterface MemoryTHP, and RootMStack were added in version 260. - CPUPressureThresholdUSec and - CPUPressureWatch were added in version 261. + CPUPressureThresholdUSec, + CPUPressureWatch, + IOPressureThresholdUSec, and + IOPressureWatch were added in version 261. Swap Unit Objects @@ -12857,8 +12949,10 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ ManagedOOMKills were added in 259. BindNetworkInterface, MemoryTHP, and RootMStack were added in version 260. - CPUPressureThresholdUSec and - CPUPressureWatch were added in version 261. + CPUPressureThresholdUSec, + CPUPressureWatch, + IOPressureThresholdUSec, and + IOPressureWatch were added in version 261. Slice Unit Objects @@ -12892,8 +12986,10 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ OOMKills, and ManagedOOMKills were added in 259. BindNetworkInterface was added in version 260. - CPUPressureThresholdUSec and - CPUPressureWatch were added in version 261. + CPUPressureThresholdUSec, + CPUPressureWatch, + IOPressureThresholdUSec, and + IOPressureWatch were added in version 261. Scope Unit Objects @@ -12925,8 +13021,10 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ OOMKills, and ManagedOOMKills were added in 259. BindNetworkInterface was added in version 260. - CPUPressureThresholdUSec and - CPUPressureWatch were added in version 261. + CPUPressureThresholdUSec, + CPUPressureWatch, + IOPressureThresholdUSec, and + IOPressureWatch were added in version 261. Job Objects diff --git a/man/rules/meson.build b/man/rules/meson.build index 81e7ef4f88262..525c56b1d3b34 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -609,8 +609,11 @@ manpages = [ ['sd_event_add_memory_pressure', '3', ['sd_event_add_cpu_pressure', + 'sd_event_add_io_pressure', 'sd_event_source_set_cpu_pressure_period', 'sd_event_source_set_cpu_pressure_type', + 'sd_event_source_set_io_pressure_period', + 'sd_event_source_set_io_pressure_type', 'sd_event_source_set_memory_pressure_period', 'sd_event_source_set_memory_pressure_type', 'sd_event_trim_memory'], diff --git a/man/sd_event_add_memory_pressure.xml b/man/sd_event_add_memory_pressure.xml index 1e6b734738f6c..05f2ff2b74528 100644 --- a/man/sd_event_add_memory_pressure.xml +++ b/man/sd_event_add_memory_pressure.xml @@ -25,7 +25,11 @@ sd_event_source_set_cpu_pressure_type sd_event_source_set_cpu_pressure_period - Add and configure an event source run as result of memory or CPU pressure + sd_event_add_io_pressure + sd_event_source_set_io_pressure_type + sd_event_source_set_io_pressure_period + + Add and configure an event source for memory, CPU, or IO pressure notifications @@ -76,6 +80,27 @@ uint64_t window_usec + + int sd_event_add_io_pressure + sd_event *event + sd_event_source **ret_source + sd_event_handler_t handler + void *userdata + + + + int sd_event_source_set_io_pressure_type + sd_event_source *source + const char *type + + + + int sd_event_source_set_io_pressure_period + sd_event_source *source + uint64_t threshold_usec + uint64_t window_usec + + int sd_event_trim_memory void @@ -88,19 +113,24 @@ sd_event_add_memory_pressure() adds a new event source that is triggered whenever memory pressure is seen. Similarly, - sd_event_add_cpu_pressure() adds a new event source that is triggered whenever CPU - pressure is seen. This functionality is built around the Linux kernel's sd_event_add_cpu_pressure() and sd_event_add_io_pressure() add + new event sources that are triggered whenever CPU or IO pressure is seen, respectively. This functionality + is built around the Linux kernel's Pressure Stall Information (PSI) logic. - Both functions expect an event loop object as first parameter, and return the allocated event source + These functions expect an event loop object as first parameter, and return the allocated event source object in the second parameter, on success. The handler parameter is a function to call when pressure is seen, or NULL. The handler function will be passed the userdata pointer, which may be chosen freely by the caller. The handler may return negative to signal an error (see below), other return values are ignored. If - handler is NULL, a default handler that compacts allocation - caches maintained by libsystemd as well as glibc (via malloc_trim3) - will be used. + handler is NULL, a default handler is used. For + sd_event_add_memory_pressure(), the default handler compacts allocation caches + maintained by libsystemd as well as glibc (via malloc_trim3). + For sd_event_add_cpu_pressure() and + sd_event_add_io_pressure(), the default handler is a no-op. It is recommended to + pass a custom handler for CPU and IO pressure to take meaningful action when pressure is + detected. To destroy an event source object use sd_event_source_unref3, @@ -110,8 +140,8 @@ sd_event_source_set_enabled3 with SD_EVENT_OFF. - If the second parameter of sd_event_add_memory_pressure() or - sd_event_add_cpu_pressure() is + If the second parameter of sd_event_add_memory_pressure(), + sd_event_add_cpu_pressure(), or sd_event_add_io_pressure() is NULL no reference to the event source object is returned. In this case, the event source is considered "floating", and will be destroyed implicitly when the event loop itself is destroyed. @@ -146,6 +176,11 @@ provides the some line, not the full line, so only some is valid when watching at the system level. + The IO pressure event source follows the same logic, but uses the + $IO_PRESSURE_WATCH/$IO_PRESSURE_WRITE environment variables, + the io.pressure cgroup file, and the system-wide PSI interface file + /proc/pressure/io instead. + Or in other words: preferably any explicit configuration passed in by an invoking service manager (or similar) is used as notification source, before falling back to local notifications of the service, and finally to global notifications of the system. @@ -189,12 +224,15 @@ Similarly, sd_event_source_set_cpu_pressure_type() and sd_event_source_set_cpu_pressure_period() can be used to fine-tune the PSI - parameters for CPU pressure notifications. They work identically to their memory pressure counterparts. + parameters for CPU pressure notifications, and + sd_event_source_set_io_pressure_type() and + sd_event_source_set_io_pressure_period() can be used to fine-tune the PSI + parameters for IO pressure notifications. They work identically to their memory pressure counterparts. The type parameter takes either some or full, and the period function takes threshold and period times in microseconds. The same constraints apply: these calls must - be invoked immediately after allocating the event source, and will fail if CPU pressure parameterization - has been passed in via the - $CPU_PRESSURE_WATCH/$CPU_PRESSURE_WRITE environment + be invoked immediately after allocating the event source, and will fail if pressure parameterization + has been passed in via the corresponding + $*_PRESSURE_WATCH/$*_PRESSURE_WRITE environment variables. The sd_event_trim_memory() function releases various internal allocation @@ -242,9 +280,9 @@ -EHOSTDOWN - The $MEMORY_PRESSURE_WATCH or - $CPU_PRESSURE_WATCH variable has been set to the literal - string /dev/null, in order to explicitly disable pressure + The $MEMORY_PRESSURE_WATCH, + $CPU_PRESSURE_WATCH, or $IO_PRESSURE_WATCH variable has been + set to the literal string /dev/null, in order to explicitly disable pressure handling. @@ -253,9 +291,9 @@ -EBADMSG - The $MEMORY_PRESSURE_WATCH or - $CPU_PRESSURE_WATCH variable has been set to an invalid - string, for example a relative rather than an absolute path. + The $MEMORY_PRESSURE_WATCH, + $CPU_PRESSURE_WATCH, or $IO_PRESSURE_WATCH variable has been + set to an invalid string, for example a relative rather than an absolute path. @@ -263,9 +301,9 @@ -ENOTTY - The $MEMORY_PRESSURE_WATCH or - $CPU_PRESSURE_WATCH variable points to a regular file - outside of the procfs or cgroupfs file systems. + The $MEMORY_PRESSURE_WATCH, + $CPU_PRESSURE_WATCH, or $IO_PRESSURE_WATCH variable points + to a regular file outside of the procfs or cgroupfs file systems. @@ -273,9 +311,9 @@ -EOPNOTSUPP - No configuration via $MEMORY_PRESSURE_WATCH or - $CPU_PRESSURE_WATCH has been specified and the local kernel does not support the - PSI interface. + No configuration via $MEMORY_PRESSURE_WATCH, + $CPU_PRESSURE_WATCH, or $IO_PRESSURE_WATCH has been specified + and the local kernel does not support the PSI interface. @@ -286,7 +324,9 @@ This is returned by sd_event_source_set_memory_pressure_type(), sd_event_source_set_memory_pressure_period(), sd_event_source_set_cpu_pressure_type(), - and sd_event_source_set_cpu_pressure_period() if invoked on event sources + sd_event_source_set_cpu_pressure_period(), + sd_event_source_set_io_pressure_type(), + and sd_event_source_set_io_pressure_period() if invoked on event sources at a time later than immediately after allocating them. @@ -329,8 +369,11 @@ sd_event_source_set_memory_pressure_period(), and sd_event_trim_memory() were added in version 254. sd_event_add_cpu_pressure(), - sd_event_source_set_cpu_pressure_type(), and - sd_event_source_set_cpu_pressure_period() were added in version 261. + sd_event_source_set_cpu_pressure_type(), + sd_event_source_set_cpu_pressure_period(), + sd_event_add_io_pressure(), + sd_event_source_set_io_pressure_type(), and + sd_event_source_set_io_pressure_period() were added in version 261. diff --git a/man/systemd-system.conf.xml b/man/systemd-system.conf.xml index 79133dc15ebca..eb14cb7f30746 100644 --- a/man/systemd-system.conf.xml +++ b/man/systemd-system.conf.xml @@ -340,6 +340,20 @@ + + + DefaultIOPressureWatch= + DefaultIOPressureThresholdSec= + + Configures the default settings for the per-unit + IOPressureWatch= and IOPressureThresholdSec= + settings. See + systemd.resource-control5 + for details. Defaults to auto and 200ms, respectively. This + also sets the IO pressure monitoring threshold for the service manager itself. + + + diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 1048fcadfc376..455f666374f99 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -4717,6 +4717,18 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX + + $IO_PRESSURE_WATCH + $IO_PRESSURE_WRITE + + If IO pressure monitoring is enabled for this service unit, the path to watch + and the data to write into it. See Resource Pressure + Handling for details about these variables and the service protocol data they + convey. + + + + $FDSTORE diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml index 8d9e27f3d3a26..f8a2e14e1b686 100644 --- a/man/systemd.resource-control.xml +++ b/man/systemd.resource-control.xml @@ -1706,6 +1706,55 @@ DeviceAllow=/dev/loop-control + + + IOPressureWatch= + + Controls IO pressure monitoring for invoked processes. Takes a boolean or one of + auto and skip. If no, tells the service not + to watch for IO pressure events, by setting the $IO_PRESSURE_WATCH + environment variable to the literal string /dev/null. If yes, + tells the service to watch for IO pressure events. This enables IO accounting for the + service, and ensures the io.pressure cgroup attribute file is accessible for + reading and writing by the service's user. It then sets the $IO_PRESSURE_WATCH + environment variable for processes invoked by the unit to the file system path to this file. The + threshold information configured with IOPressureThresholdSec= is encoded in + the $IO_PRESSURE_WRITE environment variable. If the auto + value is set the protocol is enabled if IO accounting is anyway enabled for the unit (e.g. because + IOWeight= or IODeviceWeight= is set), and + disabled otherwise. If set to skip the logic is neither enabled, nor disabled and + the two environment variables are not set. + + Note that services are free to use the two environment variables, but it is unproblematic if + they ignore them. IO pressure handling must be implemented individually in each service, and + usually means different things for different software. + + Services implemented using + sd-event3 may use + sd_event_add_io_pressure3 + to watch for and handle IO pressure events. + + If not explicitly set, defaults to the DefaultIOPressureWatch= setting in + systemd-system.conf5. + + + + + + IOPressureThresholdSec= + + Sets the IO pressure threshold time for IO pressure monitor as configured via + IOPressureWatch=. Specifies the maximum IO stall time before an IO + pressure event is signalled to the service, per 2s window. If not specified, defaults to the + DefaultIOPressureThresholdSec= setting in + systemd-system.conf5 + (which in turn defaults to 200ms). The specified value expects a time unit such as + ms or μs, see + systemd.time7 for + details on the permitted syntax. + + + Coredump Control diff --git a/src/basic/psi-util.c b/src/basic/psi-util.c index cf05485dc7b67..f2a93e674f0d9 100644 --- a/src/basic/psi-util.c +++ b/src/basic/psi-util.c @@ -116,11 +116,17 @@ const PressureResourceInfo pressure_resource_info[_PRESSURE_RESOURCE_MAX] = { .env_watch = "CPU_PRESSURE_WATCH", .env_write = "CPU_PRESSURE_WRITE", }, + [PRESSURE_IO] = { + .name = "io", + .env_watch = "IO_PRESSURE_WATCH", + .env_write = "IO_PRESSURE_WRITE", + }, }; static const char* const pressure_resource_table[_PRESSURE_RESOURCE_MAX] = { [PRESSURE_MEMORY] = "memory", [PRESSURE_CPU] = "cpu", + [PRESSURE_IO] = "io", }; DEFINE_STRING_TABLE_LOOKUP(pressure_resource, PressureResource); diff --git a/src/basic/psi-util.h b/src/basic/psi-util.h index b8737b8976bf4..8716767ca5931 100644 --- a/src/basic/psi-util.h +++ b/src/basic/psi-util.h @@ -12,6 +12,7 @@ typedef enum PressureType { typedef enum PressureResource { PRESSURE_MEMORY, PRESSURE_CPU, + PRESSURE_IO, _PRESSURE_RESOURCE_MAX, _PRESSURE_RESOURCE_INVALID = -EINVAL, } PressureResource; diff --git a/src/core/cgroup.c b/src/core/cgroup.c index a9982de659ffd..c64521a7e657e 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -188,6 +188,7 @@ void cgroup_context_init(CGroupContext *c) { .pressure = { [PRESSURE_MEMORY] = { .watch = _CGROUP_PRESSURE_WATCH_INVALID, .threshold_usec = USEC_INFINITY }, [PRESSURE_CPU] = { .watch = _CGROUP_PRESSURE_WATCH_INVALID, .threshold_usec = USEC_INFINITY }, + [PRESSURE_IO] = { .watch = _CGROUP_PRESSURE_WATCH_INVALID, .threshold_usec = USEC_INFINITY }, }, }; } @@ -531,6 +532,7 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) { "%sManagedOOMPreference: %s\n" "%sMemoryPressureWatch: %s\n" "%sCPUPressureWatch: %s\n" + "%sIOPressureWatch: %s\n" "%sCoredumpReceive: %s\n", prefix, yes_no(c->io_accounting), prefix, yes_no(c->memory_accounting), @@ -568,6 +570,7 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) { prefix, managed_oom_preference_to_string(c->moom_preference), prefix, cgroup_pressure_watch_to_string(c->pressure[PRESSURE_MEMORY].watch), prefix, cgroup_pressure_watch_to_string(c->pressure[PRESSURE_CPU].watch), + prefix, cgroup_pressure_watch_to_string(c->pressure[PRESSURE_IO].watch), prefix, yes_no(c->coredump_receive)); if (c->delegate_subgroup) @@ -586,6 +589,10 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) { fprintf(f, "%sCPUPressureThresholdSec: %s\n", prefix, FORMAT_TIMESPAN(c->pressure[PRESSURE_CPU].threshold_usec, 1)); + if (c->pressure[PRESSURE_IO].threshold_usec != USEC_INFINITY) + fprintf(f, "%sIOPressureThresholdSec: %s\n", + prefix, FORMAT_TIMESPAN(c->pressure[PRESSURE_IO].threshold_usec, 1)); + if (c->moom_mem_pressure_duration_usec != USEC_INFINITY) fprintf(f, "%sManagedOOMMemoryPressureDurationSec: %s\n", prefix, FORMAT_TIMESPAN(c->moom_mem_pressure_duration_usec, 1)); diff --git a/src/core/cgroup.h b/src/core/cgroup.h index c4a22765678eb..ce98f4ba7cd3b 100644 --- a/src/core/cgroup.h +++ b/src/core/cgroup.h @@ -376,6 +376,14 @@ static inline bool cgroup_context_want_pressure(const CGroupContext *c, Pressure c->startup_cpu_weight != CGROUP_WEIGHT_INVALID || c->cpu_quota_per_sec_usec != USEC_INFINITY; + case PRESSURE_IO: + return c->io_accounting || + c->io_weight != CGROUP_WEIGHT_INVALID || + c->startup_io_weight != CGROUP_WEIGHT_INVALID || + c->io_device_weights || + c->io_device_latencies || + c->io_device_limits; + default: assert_not_reached(); } diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c index c5a3302e08e84..927c133dd9e47 100644 --- a/src/core/dbus-cgroup.c +++ b/src/core/dbus-cgroup.c @@ -431,6 +431,8 @@ const sd_bus_vtable bus_cgroup_vtable[] = { SD_BUS_PROPERTY("MemoryPressureThresholdUSec", "t", bus_property_get_usec, offsetof(CGroupContext, pressure[PRESSURE_MEMORY].threshold_usec), 0), SD_BUS_PROPERTY("CPUPressureWatch", "s", bus_property_get_cgroup_pressure_watch, offsetof(CGroupContext, pressure[PRESSURE_CPU].watch), 0), SD_BUS_PROPERTY("CPUPressureThresholdUSec", "t", bus_property_get_usec, offsetof(CGroupContext, pressure[PRESSURE_CPU].threshold_usec), 0), + SD_BUS_PROPERTY("IOPressureWatch", "s", bus_property_get_cgroup_pressure_watch, offsetof(CGroupContext, pressure[PRESSURE_IO].watch), 0), + SD_BUS_PROPERTY("IOPressureThresholdUSec", "t", bus_property_get_usec, offsetof(CGroupContext, pressure[PRESSURE_IO].threshold_usec), 0), SD_BUS_PROPERTY("NFTSet", "a(iiss)", property_get_cgroup_nft_set, 0, 0), SD_BUS_PROPERTY("CoredumpReceive", "b", bus_property_get_bool, offsetof(CGroupContext, coredump_receive), 0), @@ -714,11 +716,12 @@ static int bus_cgroup_set_transient_property( return 1; - } else if (STR_IN_SET(name, "MemoryPressureWatch", "CPUPressureWatch")) { + } else if (STR_IN_SET(name, "MemoryPressureWatch", "CPUPressureWatch", "IOPressureWatch")) { CGroupPressureWatch p; const char *t; - PressureResource pt = streq(name, "MemoryPressureWatch") ? PRESSURE_MEMORY : PRESSURE_CPU; + PressureResource pt = streq(name, "MemoryPressureWatch") ? PRESSURE_MEMORY : + streq(name, "CPUPressureWatch") ? PRESSURE_CPU : PRESSURE_IO; r = sd_bus_message_read(message, "s", &t); if (r < 0) @@ -739,10 +742,11 @@ static int bus_cgroup_set_transient_property( return 1; - } else if (STR_IN_SET(name, "MemoryPressureThresholdUSec", "CPUPressureThresholdUSec")) { + } else if (STR_IN_SET(name, "MemoryPressureThresholdUSec", "CPUPressureThresholdUSec", "IOPressureThresholdUSec")) { uint64_t t; - PressureResource pt = streq(name, "MemoryPressureThresholdUSec") ? PRESSURE_MEMORY : PRESSURE_CPU; + PressureResource pt = streq(name, "MemoryPressureThresholdUSec") ? PRESSURE_MEMORY : + streq(name, "CPUPressureThresholdUSec") ? PRESSURE_CPU : PRESSURE_IO; r = sd_bus_message_read(message, "t", &t); if (r < 0) diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 23f4c4c3de851..78cab48f852fc 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -2984,6 +2984,8 @@ const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_PROPERTY("DefaultMemoryPressureWatch", "s", bus_property_get_cgroup_pressure_watch, offsetof(Manager, defaults.pressure[PRESSURE_MEMORY].watch), 0), SD_BUS_PROPERTY("DefaultCPUPressureThresholdUSec", "t", bus_property_get_usec, offsetof(Manager, defaults.pressure[PRESSURE_CPU].threshold_usec), 0), SD_BUS_PROPERTY("DefaultCPUPressureWatch", "s", bus_property_get_cgroup_pressure_watch, offsetof(Manager, defaults.pressure[PRESSURE_CPU].watch), 0), + SD_BUS_PROPERTY("DefaultIOPressureThresholdUSec", "t", bus_property_get_usec, offsetof(Manager, defaults.pressure[PRESSURE_IO].threshold_usec), 0), + SD_BUS_PROPERTY("DefaultIOPressureWatch", "s", bus_property_get_cgroup_pressure_watch, offsetof(Manager, defaults.pressure[PRESSURE_IO].watch), 0), SD_BUS_PROPERTY("TimerSlackNSec", "t", property_get_timer_slack_nsec, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DefaultOOMPolicy", "s", bus_property_get_oom_policy, offsetof(Manager, defaults.oom_policy), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DefaultOOMScoreAdjust", "i", property_get_oom_score_adjust, 0, SD_BUS_VTABLE_PROPERTY_CONST), diff --git a/src/core/execute-serialize.c b/src/core/execute-serialize.c index d3d23500a91f7..143cfe6286b91 100644 --- a/src/core/execute-serialize.c +++ b/src/core/execute-serialize.c @@ -287,6 +287,10 @@ static int exec_cgroup_context_serialize(const CGroupContext *c, FILE *f) { if (r < 0) return r; + r = serialize_item(f, "exec-cgroup-context-io-pressure-watch", cgroup_pressure_watch_to_string(c->pressure[PRESSURE_IO].watch)); + if (r < 0) + return r; + r = serialize_item(f, "exec-cgroup-context-delegate-subgroup", c->delegate_subgroup); if (r < 0) return r; @@ -303,6 +307,12 @@ static int exec_cgroup_context_serialize(const CGroupContext *c, FILE *f) { return r; } + if (c->pressure[PRESSURE_IO].threshold_usec != USEC_INFINITY) { + r = serialize_usec(f, "exec-cgroup-context-io-pressure-threshold-usec", c->pressure[PRESSURE_IO].threshold_usec); + if (r < 0) + return r; + } + LIST_FOREACH(device_allow, a, c->device_allow) { r = serialize_item_format(f, "exec-cgroup-context-device-allow", "%s %s", a->path, @@ -638,6 +648,10 @@ static int exec_cgroup_context_deserialize(CGroupContext *c, FILE *f) { c->pressure[PRESSURE_CPU].watch = cgroup_pressure_watch_from_string(val); if (c->pressure[PRESSURE_CPU].watch < 0) return -EINVAL; + } else if ((val = startswith(l, "exec-cgroup-context-io-pressure-watch="))) { + c->pressure[PRESSURE_IO].watch = cgroup_pressure_watch_from_string(val); + if (c->pressure[PRESSURE_IO].watch < 0) + return -EINVAL; } else if ((val = startswith(l, "exec-cgroup-context-delegate-subgroup="))) { r = free_and_strdup(&c->delegate_subgroup, val); if (r < 0) @@ -650,6 +664,10 @@ static int exec_cgroup_context_deserialize(CGroupContext *c, FILE *f) { r = deserialize_usec(val, &c->pressure[PRESSURE_CPU].threshold_usec); if (r < 0) return r; + } else if ((val = startswith(l, "exec-cgroup-context-io-pressure-threshold-usec="))) { + r = deserialize_usec(val, &c->pressure[PRESSURE_IO].threshold_usec); + if (r < 0) + return r; } else if ((val = startswith(l, "exec-cgroup-context-device-allow="))) { _cleanup_free_ char *path = NULL, *rwm = NULL; CGroupDevicePermissions p; diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index 297836def17e7..17ac9c5138b26 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -280,6 +280,8 @@ {{type}}.MemoryPressureWatch, config_parse_pressure_watch, 0, offsetof({{type}}, cgroup_context.pressure[PRESSURE_MEMORY].watch) {{type}}.CPUPressureThresholdSec, config_parse_sec, 0, offsetof({{type}}, cgroup_context.pressure[PRESSURE_CPU].threshold_usec) {{type}}.CPUPressureWatch, config_parse_pressure_watch, 0, offsetof({{type}}, cgroup_context.pressure[PRESSURE_CPU].watch) +{{type}}.IOPressureThresholdSec, config_parse_sec, 0, offsetof({{type}}, cgroup_context.pressure[PRESSURE_IO].threshold_usec) +{{type}}.IOPressureWatch, config_parse_pressure_watch, 0, offsetof({{type}}, cgroup_context.pressure[PRESSURE_IO].watch) {{type}}.NFTSet, config_parse_cgroup_nft_set, NFT_SET_PARSE_CGROUP, offsetof({{type}}, cgroup_context) {{type}}.CoredumpReceive, config_parse_bool, 0, offsetof({{type}}, cgroup_context.coredump_receive) {{type}}.BindNetworkInterface, config_parse_bind_network_interface, 0, offsetof({{type}}, cgroup_context) diff --git a/src/core/main.c b/src/core/main.c index 7fcd0fa672dba..655f0ac6659c6 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -821,6 +821,8 @@ static int parse_config_file(void) { { "Manager", "DefaultMemoryPressureWatch", config_parse_pressure_watch, 0, &arg_defaults.pressure[PRESSURE_MEMORY].watch }, { "Manager", "DefaultCPUPressureThresholdSec", config_parse_sec, 0, &arg_defaults.pressure[PRESSURE_CPU].threshold_usec }, { "Manager", "DefaultCPUPressureWatch", config_parse_pressure_watch, 0, &arg_defaults.pressure[PRESSURE_CPU].watch }, + { "Manager", "DefaultIOPressureThresholdSec", config_parse_sec, 0, &arg_defaults.pressure[PRESSURE_IO].threshold_usec }, + { "Manager", "DefaultIOPressureWatch", config_parse_pressure_watch, 0, &arg_defaults.pressure[PRESSURE_IO].watch }, { "Manager", "CtrlAltDelBurstAction", config_parse_emergency_action, arg_runtime_scope, &arg_cad_burst_action }, { "Manager", "DefaultOOMPolicy", config_parse_oom_policy, 0, &arg_defaults.oom_policy }, { "Manager", "DefaultOOMScoreAdjust", config_parse_oom_score_adjust, 0, NULL }, diff --git a/src/core/manager.c b/src/core/manager.c index c71d1a5d69a6e..73368ec18aec9 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -621,6 +621,8 @@ static char** sanitize_environment(char **l) { "CREDENTIALS_DIRECTORY", "EXIT_CODE", "EXIT_STATUS", + "IO_PRESSURE_WATCH", + "IO_PRESSURE_WRITE", "INVOCATION_ID", "JOURNAL_STREAM", "LISTEN_FDNAMES", @@ -807,6 +809,7 @@ static const struct { } pressure_dispatch_table[_PRESSURE_RESOURCE_MAX] = { [PRESSURE_MEMORY] = { sd_event_add_memory_pressure, sd_event_source_set_memory_pressure_period }, [PRESSURE_CPU] = { sd_event_add_cpu_pressure, sd_event_source_set_cpu_pressure_period }, + [PRESSURE_IO] = { sd_event_add_io_pressure, sd_event_source_set_io_pressure_period }, }; int manager_setup_pressure_event_source(Manager *m, PressureResource t) { @@ -5213,6 +5216,7 @@ void unit_defaults_init(UnitDefaults *defaults, RuntimeScope scope) { .pressure = { [PRESSURE_MEMORY] = { .watch = CGROUP_PRESSURE_WATCH_AUTO, .threshold_usec = PRESSURE_DEFAULT_THRESHOLD_USEC }, [PRESSURE_CPU] = { .watch = CGROUP_PRESSURE_WATCH_AUTO, .threshold_usec = PRESSURE_DEFAULT_THRESHOLD_USEC }, + [PRESSURE_IO] = { .watch = CGROUP_PRESSURE_WATCH_AUTO, .threshold_usec = PRESSURE_DEFAULT_THRESHOLD_USEC }, }, .oom_policy = OOM_STOP, diff --git a/src/core/system.conf.in b/src/core/system.conf.in index d3cb0160a01ea..63d28059305fe 100644 --- a/src/core/system.conf.in +++ b/src/core/system.conf.in @@ -80,6 +80,8 @@ #DefaultMemoryPressureWatch=auto #DefaultCPUPressureThresholdSec=200ms #DefaultCPUPressureWatch=auto +#DefaultIOPressureThresholdSec=200ms +#DefaultIOPressureWatch=auto #DefaultOOMPolicy=stop #DefaultSmackProcessLabel= #DefaultRestrictSUIDSGID= diff --git a/src/core/user.conf.in b/src/core/user.conf.in index fe45c00b74e4c..33c6733268c08 100644 --- a/src/core/user.conf.in +++ b/src/core/user.conf.in @@ -56,6 +56,8 @@ #DefaultMemoryPressureWatch=auto #DefaultCPUPressureThresholdSec=200ms #DefaultCPUPressureWatch=auto +#DefaultIOPressureThresholdSec=200ms +#DefaultIOPressureWatch=auto #DefaultSmackProcessLabel= #DefaultRestrictSUIDSGID= #ReloadLimitIntervalSec= diff --git a/src/core/varlink-cgroup.c b/src/core/varlink-cgroup.c index d4ec6049e66dc..ab32def28b7bb 100644 --- a/src/core/varlink-cgroup.c +++ b/src/core/varlink-cgroup.c @@ -327,6 +327,8 @@ int unit_cgroup_context_build_json(sd_json_variant **ret, const char *name, void JSON_BUILD_PAIR_FINITE_USEC("MemoryPressureThresholdUSec", c->pressure[PRESSURE_MEMORY].threshold_usec), SD_JSON_BUILD_PAIR_STRING("CPUPressureWatch", cgroup_pressure_watch_to_string(c->pressure[PRESSURE_CPU].watch)), JSON_BUILD_PAIR_FINITE_USEC("CPUPressureThresholdUSec", c->pressure[PRESSURE_CPU].threshold_usec), + SD_JSON_BUILD_PAIR_STRING("IOPressureWatch", cgroup_pressure_watch_to_string(c->pressure[PRESSURE_IO].watch)), + JSON_BUILD_PAIR_FINITE_USEC("IOPressureThresholdUSec", c->pressure[PRESSURE_IO].threshold_usec), /* Others */ SD_JSON_BUILD_PAIR_BOOLEAN("CoredumpReceive", c->coredump_receive)); diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index 3953b8619f7af..997bdc08d0122 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -110,6 +110,8 @@ static int manager_context_build_json(sd_json_variant **ret, const char *name, v SD_JSON_BUILD_PAIR_STRING("DefaultMemoryPressureWatch", cgroup_pressure_watch_to_string(m->defaults.pressure[PRESSURE_MEMORY].watch)), JSON_BUILD_PAIR_FINITE_USEC("DefaultCPUPressureThresholdUSec", m->defaults.pressure[PRESSURE_CPU].threshold_usec), SD_JSON_BUILD_PAIR_STRING("DefaultCPUPressureWatch", cgroup_pressure_watch_to_string(m->defaults.pressure[PRESSURE_CPU].watch)), + JSON_BUILD_PAIR_FINITE_USEC("DefaultIOPressureThresholdUSec", m->defaults.pressure[PRESSURE_IO].threshold_usec), + SD_JSON_BUILD_PAIR_STRING("DefaultIOPressureWatch", cgroup_pressure_watch_to_string(m->defaults.pressure[PRESSURE_IO].watch)), JSON_BUILD_PAIR_FINITE_USEC("RuntimeWatchdogUSec", manager_get_watchdog(m, WATCHDOG_RUNTIME)), JSON_BUILD_PAIR_FINITE_USEC("RebootWatchdogUSec", manager_get_watchdog(m, WATCHDOG_REBOOT)), JSON_BUILD_PAIR_FINITE_USEC("KExecWatchdogUSec", manager_get_watchdog(m, WATCHDOG_KEXEC)), diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym index 5f5eca60833b2..38ab92dea124b 100644 --- a/src/libsystemd/libsystemd.sym +++ b/src/libsystemd/libsystemd.sym @@ -1099,4 +1099,7 @@ global: sd_event_add_cpu_pressure; sd_event_source_set_cpu_pressure_type; sd_event_source_set_cpu_pressure_period; + sd_event_add_io_pressure; + sd_event_source_set_io_pressure_type; + sd_event_source_set_io_pressure_period; } LIBSYSTEMD_260; diff --git a/src/libsystemd/sd-event/event-source.h b/src/libsystemd/sd-event/event-source.h index c7d5ba166da31..8487c966ab409 100644 --- a/src/libsystemd/sd-event/event-source.h +++ b/src/libsystemd/sd-event/event-source.h @@ -27,6 +27,7 @@ typedef enum EventSourceType { SOURCE_INOTIFY, SOURCE_MEMORY_PRESSURE, SOURCE_CPU_PRESSURE, + SOURCE_IO_PRESSURE, _SOURCE_EVENT_SOURCE_TYPE_MAX, _SOURCE_EVENT_SOURCE_TYPE_INVALID = -EINVAL, } EventSourceType; diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index aba6bf9b4787b..9256ddd81bfea 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -77,6 +77,7 @@ static const char* const event_source_type_table[_SOURCE_EVENT_SOURCE_TYPE_MAX] [SOURCE_INOTIFY] = "inotify", [SOURCE_MEMORY_PRESSURE] = "memory-pressure", [SOURCE_CPU_PRESSURE] = "cpu-pressure", + [SOURCE_IO_PRESSURE] = "io-pressure", }; DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int); @@ -101,7 +102,8 @@ DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int); SOURCE_DEFER, \ SOURCE_INOTIFY, \ SOURCE_MEMORY_PRESSURE, \ - SOURCE_CPU_PRESSURE) + SOURCE_CPU_PRESSURE, \ + SOURCE_IO_PRESSURE) /* This is used to assert that we didn't pass an unexpected source type to event_source_time_prioq_put(). * Time sources and ratelimited sources can be passed, so effectively this is the same as the @@ -566,7 +568,7 @@ static int source_child_pidfd_register(sd_event_source *s, int enabled) { return 0; } -#define EVENT_SOURCE_IS_PRESSURE(s) IN_SET((s)->type, SOURCE_MEMORY_PRESSURE, SOURCE_CPU_PRESSURE) +#define EVENT_SOURCE_IS_PRESSURE(s) IN_SET((s)->type, SOURCE_MEMORY_PRESSURE, SOURCE_CPU_PRESSURE, SOURCE_IO_PRESSURE) static void source_pressure_unregister(sd_event_source *s) { assert(s); @@ -1052,6 +1054,7 @@ static void source_disconnect(sd_event_source *s) { case SOURCE_MEMORY_PRESSURE: case SOURCE_CPU_PRESSURE: + case SOURCE_IO_PRESSURE: source_pressure_remove_from_write_list(s); source_pressure_unregister(s); break; @@ -1198,6 +1201,7 @@ static sd_event_source* source_new(sd_event *e, bool floating, EventSourceType t [SOURCE_INOTIFY] = endoffsetof_field(sd_event_source, inotify), [SOURCE_MEMORY_PRESSURE] = endoffsetof_field(sd_event_source, pressure), [SOURCE_CPU_PRESSURE] = endoffsetof_field(sd_event_source, pressure), + [SOURCE_IO_PRESSURE] = endoffsetof_field(sd_event_source, pressure), }; sd_event_source *s; @@ -2110,8 +2114,8 @@ static int event_add_pressure( * fd with the epoll right-away. Instead, we just add the event source to a list of pressure event * sources on which writes must be executed before the first event loop iteration is executed. (We * could also write the data here, right away, but we want to give the caller the freedom to call - * sd_event_source_set_{memory,cpu}_pressure_type() and - * sd_event_source_set_{memory,cpu}_pressure_period() before we write it. */ + * sd_event_source_set_{memory,cpu,io}_pressure_type() and + * sd_event_source_set_{memory,cpu,io}_pressure_period() before we write it. */ if (s->pressure.write_buffer_size > 0) source_pressure_add_to_write_list(s); @@ -2160,6 +2164,25 @@ _public_ int sd_event_add_cpu_pressure( PRESSURE_CPU); } +static int io_pressure_callback(sd_event_source *s, void *userdata) { + assert(s); + + return 0; +} + +_public_ int sd_event_add_io_pressure( + sd_event *e, + sd_event_source **ret, + sd_event_handler_t callback, + void *userdata) { + + return event_add_pressure( + e, ret, callback, userdata, + SOURCE_IO_PRESSURE, + io_pressure_callback, + PRESSURE_IO); +} + static void event_free_inotify_data(sd_event *e, InotifyData *d) { assert(e); @@ -2962,6 +2985,7 @@ static int event_source_offline( case SOURCE_MEMORY_PRESSURE: case SOURCE_CPU_PRESSURE: + case SOURCE_IO_PRESSURE: source_pressure_unregister(s); break; @@ -3054,6 +3078,7 @@ static int event_source_online( case SOURCE_MEMORY_PRESSURE: case SOURCE_CPU_PRESSURE: + case SOURCE_IO_PRESSURE: /* As documented in sd_event_add_{memory,cpu,io}_pressure(), we can only register the PSI fd * with epoll after writing the watch string. */ if (s->pressure.write_buffer_size == 0) { @@ -4308,6 +4333,7 @@ static int source_dispatch(sd_event_source *s) { case SOURCE_MEMORY_PRESSURE: case SOURCE_CPU_PRESSURE: + case SOURCE_IO_PRESSURE: r = s->pressure.callback(s, s->userdata); break; @@ -4723,6 +4749,7 @@ static int process_epoll(sd_event *e, usec_t timeout, int64_t threshold, int64_t case SOURCE_MEMORY_PRESSURE: case SOURCE_CPU_PRESSURE: + case SOURCE_IO_PRESSURE: r = process_pressure(s, i->events); break; @@ -5418,6 +5445,13 @@ _public_ int sd_event_source_set_cpu_pressure_type(sd_event_source *s, const cha return event_source_set_pressure_type(s, ty); } +_public_ int sd_event_source_set_io_pressure_type(sd_event_source *s, const char *ty) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_IO_PRESSURE, -EDOM); + + return event_source_set_pressure_type(s, ty); +} + static int event_source_set_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec) { _cleanup_free_ char *b = NULL; _cleanup_free_ void *w = NULL; @@ -5478,3 +5512,10 @@ _public_ int sd_event_source_set_cpu_pressure_period(sd_event_source *s, uint64_ return event_source_set_pressure_period(s, threshold_usec, window_usec); } + +_public_ int sd_event_source_set_io_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_IO_PRESSURE, -EDOM); + + return event_source_set_pressure_period(s, threshold_usec, window_usec); +} diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 9c732543fac7d..1a6bc7370f81e 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -2384,6 +2384,7 @@ static const BusProperty cgroup_properties[] = { { "ManagedOOMPreference", bus_append_string }, { "MemoryPressureWatch", bus_append_string }, { "CPUPressureWatch", bus_append_string }, + { "IOPressureWatch", bus_append_string }, { "DelegateSubgroup", bus_append_string }, { "ManagedOOMMemoryPressureLimit", bus_append_parse_permyriad }, { "MemoryAccounting", bus_append_parse_boolean }, @@ -2423,6 +2424,7 @@ static const BusProperty cgroup_properties[] = { { "SocketBindDeny", bus_append_socket_filter }, { "MemoryPressureThresholdSec", bus_append_parse_sec_rename }, { "CPUPressureThresholdSec", bus_append_parse_sec_rename }, + { "IOPressureThresholdSec", bus_append_parse_sec_rename }, { "NFTSet", bus_append_nft_set }, { "BindNetworkInterface", bus_append_string }, diff --git a/src/shared/varlink-io.systemd.Manager.c b/src/shared/varlink-io.systemd.Manager.c index f947c0a05615c..9ce1b8350abee 100644 --- a/src/shared/varlink-io.systemd.Manager.c +++ b/src/shared/varlink-io.systemd.Manager.c @@ -68,6 +68,10 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(DefaultCPUPressureThresholdUSec, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultCPUPressureWatch="), SD_VARLINK_DEFINE_FIELD(DefaultCPUPressureWatch, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultIOPressureThresholdUSec="), + SD_VARLINK_DEFINE_FIELD(DefaultIOPressureThresholdUSec, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultIOPressureWatch="), + SD_VARLINK_DEFINE_FIELD(DefaultIOPressureWatch, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#RuntimeWatchdogSec="), SD_VARLINK_DEFINE_FIELD(RuntimeWatchdogUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#RebootWatchdogSec="), diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c index a230f29daba8b..c1ff4ebc5a76c 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -232,6 +232,10 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(CPUPressureWatch, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#CPUPressureThresholdSec="), SD_VARLINK_DEFINE_FIELD(CPUPressureThresholdUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#IOPressureWatch="), + SD_VARLINK_DEFINE_FIELD(IOPressureWatch, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#IOPressureThresholdSec="), + SD_VARLINK_DEFINE_FIELD(IOPressureThresholdUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), /* Others */ SD_VARLINK_FIELD_COMMENT("Reflects whether to forward coredumps for processes that crash within this cgroup"), diff --git a/src/systemd/sd-event.h b/src/systemd/sd-event.h index 71fc9504889e6..34bd396080dc3 100644 --- a/src/systemd/sd-event.h +++ b/src/systemd/sd-event.h @@ -98,6 +98,7 @@ int sd_event_add_post(sd_event *e, sd_event_source **ret, sd_event_handler_t cal int sd_event_add_exit(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata); int sd_event_add_memory_pressure(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata); int sd_event_add_cpu_pressure(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata); +int sd_event_add_io_pressure(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata); int sd_event_prepare(sd_event *e); int sd_event_wait(sd_event *e, uint64_t timeout); @@ -165,6 +166,8 @@ int sd_event_source_set_memory_pressure_type(sd_event_source *s, const char *ty) int sd_event_source_set_memory_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec); int sd_event_source_set_cpu_pressure_type(sd_event_source *s, const char *ty); int sd_event_source_set_cpu_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec); +int sd_event_source_set_io_pressure_type(sd_event_source *s, const char *ty); +int sd_event_source_set_io_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec); int sd_event_source_set_destroy_callback(sd_event_source *s, sd_event_destroy_t callback); int sd_event_source_get_destroy_callback(sd_event_source *s, sd_event_destroy_t *ret); int sd_event_source_get_floating(sd_event_source *s); diff --git a/src/test/test-pressure.c b/src/test/test-pressure.c index 44ff810753e8a..318b73e4fd6cc 100644 --- a/src/test/test-pressure.c +++ b/src/test/test-pressure.c @@ -154,6 +154,14 @@ TEST(fake_cpu_pressure) { test_fake_pressure("cpu", fake_cpu_pressure_wrapper); } +static int fake_io_pressure_wrapper(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata) { + return sd_event_add_io_pressure(e, ret, callback, userdata); +} + +TEST(fake_io_pressure) { + test_fake_pressure("io", fake_io_pressure_wrapper); +} + /* Shared infrastructure for real pressure tests */ struct real_pressure_context { @@ -452,7 +460,142 @@ TEST(real_cpu_pressure) { ASSERT_EQ(ex, 31); } +/* IO pressure real test */ + +static int real_io_pressure_callback(sd_event_source *s, void *userdata) { + struct real_pressure_context *c = ASSERT_PTR(userdata); + const char *d; + + ASSERT_NOT_NULL(s); + ASSERT_OK(sd_event_source_get_description(s, &d)); + + log_notice("real io pressure event: %s", d); + + ASSERT_NOT_NULL(c->pid); + ASSERT_OK(sd_event_source_send_child_signal(c->pid, SIGKILL, NULL, 0)); + c->pid = NULL; + + return 0; +} + +_noreturn_ static void real_pressure_eat_io(int pipe_fd) { + char x; + ASSERT_EQ(read(pipe_fd, &x, 1), 1); /* Wait for the GO! */ + + /* Write and fsync in a loop to generate IO pressure */ + for (;;) { + _cleanup_close_ int fd = -EBADF; + + fd = open("/var/tmp/.io-pressure-test", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0600); + if (fd < 0) + continue; + + char buf[4096]; + memset(buf, 'x', sizeof(buf)); + for (int i = 0; i < 256; i++) + if (write(fd, buf, sizeof(buf)) < 0) + break; + (void) fsync(fd); + } +} + +TEST(real_io_pressure) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *es = NULL, *cs = NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_close_pair_ int pipe_fd[2] = EBADF_PAIR; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_free_ char *scope = NULL; + const char *object; + int r; + + if (getuid() == 0) + r = sd_bus_open_system(&bus); + else + r = sd_bus_open_user(&bus); + if (r < 0) + return (void) log_tests_skipped_errno(r, "can't connect to bus"); + + ASSERT_OK(bus_wait_for_jobs_new(bus, &w)); + + ASSERT_OK(bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit")); + ASSERT_OK(asprintf(&scope, "test-%" PRIu64 ".scope", random_u64())); + ASSERT_OK(sd_bus_message_append(m, "ss", scope, "fail")); + ASSERT_OK(sd_bus_message_open_container(m, 'a', "(sv)")); + ASSERT_OK(sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, 0)); + ASSERT_OK(sd_bus_message_append(m, "(sv)", "IOAccounting", "b", true)); + ASSERT_OK(sd_bus_message_close_container(m)); + ASSERT_OK(sd_bus_message_append(m, "a(sa(sv))", 0)); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return (void) log_tests_skipped_errno(r, "can't issue transient unit call"); + + ASSERT_OK(sd_bus_message_read(reply, "o", &object)); + + ASSERT_OK(bus_wait_for_jobs_one(w, object, /* flags= */ BUS_WAIT_JOBS_LOG_ERROR, /* extra_args= */ NULL)); + + ASSERT_OK(sd_event_default(&e)); + + ASSERT_OK_ERRNO(pipe2(pipe_fd, O_CLOEXEC)); + + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + r = pidref_safe_fork("(eat-io)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM, &pidref); + ASSERT_OK(r); + if (r == 0) { + real_pressure_eat_io(pipe_fd[0]); + _exit(EXIT_SUCCESS); + } + + ASSERT_OK(event_add_child_pidref(e, &cs, &pidref, WEXITED, real_pressure_child_callback, NULL)); + ASSERT_OK(sd_event_source_set_child_process_own(cs, true)); + + ASSERT_OK_ERRNO(unsetenv("IO_PRESSURE_WATCH")); + ASSERT_OK_ERRNO(unsetenv("IO_PRESSURE_WRITE")); + + struct real_pressure_context context = { + .pid = cs, + }; + + r = sd_event_add_io_pressure(e, &es, real_io_pressure_callback, &context); + if (r < 0) + return (void) log_tests_skipped_errno(r, "can't allocate io pressure fd"); + + ASSERT_OK(sd_event_source_set_description(es, "real pressure event source")); + ASSERT_OK_ZERO(sd_event_source_set_io_pressure_type(es, "some")); + /* Unprivileged writes require a minimum of 2s otherwise the kernel will refuse the write. */ + ASSERT_OK_POSITIVE(sd_event_source_set_io_pressure_period(es, 70 * USEC_PER_MSEC, 2 * USEC_PER_SEC)); + ASSERT_OK_ZERO(sd_event_source_set_io_pressure_period(es, 70 * USEC_PER_MSEC, 2 * USEC_PER_SEC)); + ASSERT_OK(sd_event_source_set_enabled(es, SD_EVENT_ONESHOT)); + + m = sd_bus_message_unref(m); + + ASSERT_OK(bus_message_new_method_call(bus, &m, bus_systemd_mgr, "SetUnitProperties")); + ASSERT_OK(sd_bus_message_append(m, "sb", scope, true)); + ASSERT_OK(sd_bus_message_open_container(m, 'a', "(sv)")); + ASSERT_OK(sd_bus_message_open_container(m, 'r', "sv")); + ASSERT_OK(sd_bus_message_append(m, "s", "IOWriteBandwidthMax")); + ASSERT_OK(sd_bus_message_open_container(m, 'v', "a(st)")); + ASSERT_OK(sd_bus_message_append(m, "a(st)", 1, "/var/tmp", (uint64_t) 1024*1024)); /* 1M/s */ + ASSERT_OK(sd_bus_message_close_container(m)); + ASSERT_OK(sd_bus_message_close_container(m)); + ASSERT_OK(sd_bus_message_close_container(m)); + + ASSERT_OK(sd_bus_call(bus, m, 0, NULL, NULL)); + + /* Now start eating IO */ + ASSERT_EQ(write(pipe_fd[1], &(const char) { 'x' }, 1), 1); + + ASSERT_OK(sd_event_loop(e)); + int ex = 0; + ASSERT_OK(sd_event_get_exit_code(e, &ex)); + ASSERT_EQ(ex, 31); +} + static int outro(void) { + (void) unlink("/var/tmp/.io-pressure-test"); hashmap_trim_pools(); return 0; } diff --git a/test/units/TEST-79-PRESSURE.sh b/test/units/TEST-79-PRESSURE.sh index d4e4a9e06b5b4..72de8a1d9d189 100755 --- a/test/units/TEST-79-PRESSURE.sh +++ b/test/units/TEST-79-PRESSURE.sh @@ -114,4 +114,57 @@ systemd-run \ rm "$SCRIPT" +# Now test IO pressure + +if ! cat /proc/pressure/io >/dev/null ; then + echo "kernel has no IO PSI support." >&2 + echo OK >/testok + exit 0 +fi + +if ! test -f "$CGROUP"/io.pressure ; then + echo "No IO accounting/PSI delegated via cgroup, can't test." >&2 + echo OK >/testok + exit 0 +fi + +UNIT="test-iopress-$RANDOM.service" +SCRIPT="/tmp/iopress-$RANDOM.sh" + +cat >"$SCRIPT" <<'EOF' +#!/usr/bin/env bash + +set -ex + +export +id + +test -n "$IO_PRESSURE_WATCH" +test "$IO_PRESSURE_WATCH" != /dev/null +test -w "$IO_PRESSURE_WATCH" + +ls -al "$IO_PRESSURE_WATCH" + +EXPECTED="$(echo -n -e "some 123000 2000000\x00" | base64)" + +test "$EXPECTED" = "$IO_PRESSURE_WRITE" + +EOF + +chmod +x "$SCRIPT" + +systemd-run \ + -u "$UNIT" \ + -p Type=exec \ + -p ProtectControlGroups=1 \ + -p DynamicUser=1 \ + -p IOPressureWatch=on \ + -p IOPressureThresholdSec=123ms \ + -p BindPaths=$SCRIPT \ + `# Make sanitizers happy when DynamicUser=1 pulls in instrumented systemd NSS modules` \ + -p EnvironmentFile=-/usr/lib/systemd/systemd-asan-env \ + --wait "$SCRIPT" + +rm "$SCRIPT" + touch /testok From 158f2d50bfed3793502eaea410f61e017830b8a4 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 8 Mar 2026 00:33:09 +0100 Subject: [PATCH 0868/1296] docs: Update MEMORY_PRESSURE.md => PRESSURE.md Make the doc more generic and mention all pressure types, not just memory. --- docs/MEMORY_PRESSURE.md | 241 +------------------------ docs/PRESSURE.md | 255 +++++++++++++++++++++++++++ man/oomd.conf.xml | 2 +- man/sd_event_add_memory_pressure.xml | 2 +- man/systemd.exec.xml | 2 +- man/systemd.resource-control.xml | 2 +- 6 files changed, 261 insertions(+), 243 deletions(-) create mode 100644 docs/PRESSURE.md diff --git a/docs/MEMORY_PRESSURE.md b/docs/MEMORY_PRESSURE.md index 3d3832cac7ea2..95e8a9af9e721 100644 --- a/docs/MEMORY_PRESSURE.md +++ b/docs/MEMORY_PRESSURE.md @@ -1,241 +1,4 @@ --- -title: Memory Pressure Handling -category: Interfaces -layout: default -SPDX-License-Identifier: LGPL-2.1-or-later +layout: forward +target: /PRESSURE --- - -# Memory Pressure Handling in systemd - -When the system is under memory pressure (i.e. some component of the OS -requires memory allocation but there is only very little or none available), -it can attempt various things to make more memory available again ("reclaim"): - -* The kernel can flush out memory pages backed by files on disk, under the - knowledge that it can reread them from disk when needed again. Candidate - pages are the many memory mapped executable files and shared libraries on - disk, among others. - -* The kernel can flush out memory pages not backed by files on disk - ("anonymous" memory, i.e. memory allocated via `malloc()` and similar calls, - or `tmpfs` file system contents) if there's swap to write it to. - -* Userspace can proactively release memory it allocated but doesn't immediately - require back to the kernel. This includes allocation caches, and other forms - of caches that are not required for normal operation to continue. - -The latter is what we want to focus on in this document: how to ensure -userspace process can detect mounting memory pressure early and release memory -back to the kernel as it happens, relieving the memory pressure before it -becomes too critical. - -The effects of memory pressure during runtime generally are growing latencies -during operation: when a program requires memory but the system is busy writing -out memory to (relatively slow) disks in order make some available, this -generally surfaces in scheduling latencies, and applications and services will -slow down until memory pressure is relieved. Hence, to ensure stable service -latencies it is essential to release unneeded memory back to the kernel early -on. - -On Linux the [Pressure Stall Information -(PSI)](https://docs.kernel.org/accounting/psi.html) Linux kernel interface is -the primary way to determine the system or a part of it is under memory -pressure. PSI makes available to userspace a `poll()`-able file descriptor that -gets notifications whenever memory pressure latencies for the system or a -control group grow beyond some level. - -`systemd` itself makes use of PSI, and helps applications to do so too. -Specifically: - -* Most of systemd's long running components watch for PSI memory pressure - events, and release allocation caches and other resources once seen. - -* systemd's service manager provides a protocol for asking services to monitor - PSI events and configure the appropriate pressure thresholds. - -* systemd's `sd-event` event loop API provides a high-level call - `sd_event_add_memory_pressure()` enabling programs using it to efficiently - hook into the PSI memory pressure protocol provided by the service manager, - with very few lines of code. - -## Memory Pressure Service Protocol - -If memory pressure handling for a specific service is enabled via -`MemoryPressureWatch=` the memory pressure service protocol is used to tell the -service code about this. Specifically two environment variables are set by the -service manager, and typically consumed by the service: - -* The `$MEMORY_PRESSURE_WATCH` environment variable will contain an absolute - path in the file system to the file to watch for memory pressure events. This - will usually point to a PSI file such as the `memory.pressure` file of the - service's cgroup. In order to make debugging easier, and allow later - extension it is recommended for applications to also allow this path to refer - to an `AF_UNIX` stream socket in the file system or a FIFO inode in the file - system. Regardless of which of the three types of inodes this absolute path - refers to, all three are `poll()`-able for memory pressure events. The - variable can also be set to the literal string `/dev/null`. If so the service - code should take this as indication that memory pressure monitoring is not - desired and should be turned off. - -* The `$MEMORY_PRESSURE_WRITE` environment variable is optional. If set by the - service manager it contains Base64 encoded data (that may contain arbitrary - binary values, including NUL bytes) that should be written into the path - provided via `$MEMORY_PRESSURE_WATCH` right after opening it. Typically, if - talking directly to a PSI kernel file this will contain information about the - threshold settings configurable in the service manager. - -When a service initializes it hence should look for -`$MEMORY_PRESSURE_WATCH`. If set, it should try to open the specified path. If -it detects the path to refer to a regular file it should assume it refers to a -PSI kernel file. If so, it should write the data from `$MEMORY_PRESSURE_WRITE` -into the file descriptor (after Base64-decoding it, and only if the variable is -set) and then watch for `POLLPRI` events on it. If it detects the paths refers -to a FIFO inode, it should open it, write the `$MEMORY_PRESSURE_WRITE` data -into it (as above) and then watch for `POLLIN` events on it. Whenever `POLLIN` -is seen it should read and discard any data queued in the FIFO. If the path -refers to an `AF_UNIX` socket in the file system, the application should -`connect()` a stream socket to it, write `$MEMORY_PRESSURE_WRITE` into it (as -above) and watch for `POLLIN`, discarding any data it might receive. - -To summarize: - -* If `$MEMORY_PRESSURE_WATCH` points to a regular file: open and watch for - `POLLPRI`, never read from the file descriptor. - -* If `$MEMORY_PRESSURE_WATCH` points to a FIFO: open and watch for `POLLIN`, - read/discard any incoming data. - -* If `$MEMORY_PRESSURE_WATCH` points to an `AF_UNIX` socket: connect and watch - for `POLLIN`, read/discard any incoming data. - -* If `$MEMORY_PRESSURE_WATCH` contains the literal string `/dev/null`, turn off - memory pressure handling. - -(And in each case, immediately after opening/connecting to the path, write the -decoded `$MEMORY_PRESSURE_WRITE` data into it.) - -Whenever a `POLLPRI`/`POLLIN` event is seen the service is under memory -pressure. It should use this as hint to release suitable redundant resources, -for example: - -* glibc's memory allocation cache, via - [`malloc_trim()`](https://man7.org/linux/man-pages/man3/malloc_trim.3.html). Similar, - allocation caches implemented in the service itself. - -* Any other local caches, such DNS caches, or web caches (in particular if - service is a web browser). - -* Terminate any idle worker threads or processes. - -* Run a garbage collection (GC) cycle, if the runtime environment supports it. - -* Terminate the process if idle, and can be automatically started when - needed next. - -Which actions precisely to take depends on the service in question. Note that -the notifications are delivered when memory allocation latency already degraded -beyond some point. Hence when discussing which resources to keep and which to -discard, keep in mind it's typically acceptable that latencies incurred -recovering discarded resources at a later point are acceptable, given that -latencies *already* are affected negatively. - -In case the path supplied via `$MEMORY_PRESSURE_WATCH` points to a PSI kernel -API file, or to an `AF_UNIX` opening it multiple times is safe and reliable, -and should deliver notifications to each of the opened file descriptors. This -is specifically useful for services that consist of multiple processes, and -where each of them shall be able to release resources on memory pressure. - -The `POLLPRI`/`POLLIN` conditions will be triggered every time memory pressure -is detected, but not continuously. It is thus safe to keep `poll()`-ing on the -same file descriptor continuously, and executing resource release operations -whenever the file descriptor triggers without having to expect overloading the -process. - -(Currently, the protocol defined here only allows configuration of a single -"degree" of memory pressure, there's no distinction made on how strong the -pressure is. In future, if it becomes apparent that there's clear need to -extend this we might eventually add different degrees, most likely by adding -additional environment variables such as `$MEMORY_PRESSURE_WRITE_LOW` and -`$MEMORY_PRESSURE_WRITE_HIGH` or similar, which may contain different settings -for lower or higher memory pressure thresholds.) - -## Service Manager Settings - -The service manager provides two per-service settings that control the memory -pressure handling: - -* The - [`MemoryPressureWatch=`](https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#MemoryPressureWatch=) - setting controls whether to enable the memory pressure protocol for the - service in question. - -* The `MemoryPressureThresholdSec=` setting allows configuring the threshold - when to signal memory pressure to the services. It takes a time value - (usually in the millisecond range) that defines a threshold per 1s time - window: if memory allocation latencies grow beyond this threshold - notifications are generated towards the service, requesting it to release - resources. - -The `/etc/systemd/system.conf` file provides two settings that may be used to -select the default values for the above settings. If the threshold isn't -configured via the per-service nor system-wide option, it defaults to 100ms. - -When memory pressure monitoring is enabled for a service via -`MemoryPressureWatch=` this primarily does three things: - -* It enables cgroup memory accounting for the service (this is a requirement - for per-cgroup PSI) - -* It sets the aforementioned two environment variables for processes invoked - for the service, based on the control group of the service and provided - settings. - -* The `memory.pressure` PSI control group file associated with the service's - cgroup is delegated to the service (i.e. permissions are relaxed so that - unprivileged service payload code can open the file for writing). - -## Memory Pressure Events in `sd-event` - -The -[`sd-event`](https://www.freedesktop.org/software/systemd/man/latest/sd-event.html) -event loop library provides two API calls that encapsulate the -functionality described above: - -* The - [`sd_event_add_memory_pressure()`](https://www.freedesktop.org/software/systemd/man/latest/sd_event_add_memory_pressure.html) - call implements the service-side of the memory pressure protocol and - integrates it with an `sd-event` event loop. It reads the two environment - variables, connects/opens the specified file, writes the specified data to it, - then watches it for events. - -* The `sd_event_trim_memory()` call may be called to trim the calling - processes' memory. It's a wrapper around glibc's `malloc_trim()`, but first - releases allocation caches maintained by libsystemd internally. This function - serves as the default when a NULL callback is supplied to - `sd_event_add_memory_pressure()`. - -When implementing a service using `sd-event`, for automatic memory pressure -handling, it's typically sufficient to add a line such as: - -```c -(void) sd_event_add_memory_pressure(event, NULL, NULL, NULL); -``` - -– right after allocating the event loop object `event`. - -## Other APIs - -Other programming environments might have native APIs to watch memory -pressure/low memory events. Most notable is probably GLib's -[GMemoryMonitor](https://docs.gtk.org/gio/iface.MemoryMonitor.html). As of GLib -2.86.0, it uses the per-cgroup PSI kernel file to monitor for memory pressure, -but does not yet read the environment variables recommended above. - -In older versions, it used the per-system Linux PSI interface as the backend, but operated -differently than the above: memory pressure events were picked up by a system -service, which then propagated this through D-Bus to the applications. This was -typically less than ideal, since this means each notification event had to -traverse three processes before being handled. This traversal created -additional latencies at a time where the system is already experiencing adverse -latencies. Moreover, it focused on system-wide PSI events, even though -service-local ones are generally the better approach. diff --git a/docs/PRESSURE.md b/docs/PRESSURE.md new file mode 100644 index 0000000000000..6ea6b60711079 --- /dev/null +++ b/docs/PRESSURE.md @@ -0,0 +1,255 @@ +--- +title: Resource Pressure Handling +category: Interfaces +layout: default +SPDX-License-Identifier: LGPL-2.1-or-later +--- + +# Resource Pressure Handling in systemd + +On Linux the [Pressure Stall Information +(PSI)](https://docs.kernel.org/accounting/psi.html) Linux kernel interface +provides a way to monitor resource pressure — situations where tasks are +stalled waiting for a resource to become available. PSI covers three types of +resources: + +* **Memory pressure**: tasks are stalled because the system is low on memory + and the kernel is busy reclaiming it (e.g. writing out pages to swap or + flushing file-backed pages). + +* **CPU pressure**: tasks are stalled waiting for CPU time because the CPU is + oversubscribed. + +* **IO pressure**: tasks are stalled waiting for IO operations to complete + because the IO subsystem is saturated. + +PSI makes available to userspace a `poll()`-able file descriptor that gets +notifications whenever pressure latencies for the system or a control group +grow beyond some configured level. + +When the system is under memory pressure, userspace can proactively release +memory it allocated but doesn't immediately require back to the kernel. This +includes allocation caches, and other forms of caches that are not required for +normal operation to continue. Similarly, when CPU or IO pressure is detected, +services can take appropriate action such as reducing parallelism, deferring +background work, or shedding load. + +The effects of resource pressure during runtime generally are growing latencies +during operation: applications and services slow down until pressure is +relieved. Hence, to ensure stable service latencies it is essential to detect +pressure early and respond appropriately. + +`systemd` itself makes use of PSI, and helps applications to do so too. +Specifically: + +* Most of systemd's long running components watch for PSI memory pressure + events, and release allocation caches and other resources once seen. + +* systemd's service manager provides a protocol for asking services to monitor + PSI events and configure the appropriate pressure thresholds, for memory, CPU, + and IO pressure independently. + +* systemd's `sd-event` event loop API provides high-level calls + `sd_event_add_memory_pressure()`, `sd_event_add_cpu_pressure()`, and + `sd_event_add_io_pressure()` enabling programs using it to efficiently hook + into the PSI pressure protocol provided by the service manager, with very few + lines of code. + +## Pressure Service Protocol + +For each resource type, if pressure handling for a specific service is enabled +via the corresponding `*PressureWatch=` setting (i.e. `MemoryPressureWatch=`, +`CPUPressureWatch=`, or `IOPressureWatch=`), two environment variables are set +by the service manager: + +* `$MEMORY_PRESSURE_WATCH` / `$CPU_PRESSURE_WATCH` / `$IO_PRESSURE_WATCH` — + contains an absolute path in the file system to the file to watch for + pressure events. This will usually point to a PSI file such as the + `memory.pressure`, `cpu.pressure`, or `io.pressure` file of the service's + cgroup. In order to make debugging easier, and allow later extension it is + recommended for applications to also allow this path to refer to an `AF_UNIX` + stream socket in the file system or a FIFO inode in the file system. + Regardless of which of the three types of inodes this absolute path refers + to, all three are `poll()`-able for pressure events. The variable can also be + set to the literal string `/dev/null`. If so the service code should take this + as indication that pressure monitoring for this resource is not desired and + should be turned off. + +* `$MEMORY_PRESSURE_WRITE` / `$CPU_PRESSURE_WRITE` / `$IO_PRESSURE_WRITE` — + optional. If set by the service manager it contains Base64 encoded data (that + may contain arbitrary binary values, including NUL bytes) that should be + written into the path provided via the corresponding `*_PRESSURE_WATCH` + variable right after opening it. Typically, if talking directly to a PSI + kernel file this will contain information about the threshold settings + configurable in the service manager. + +The protocol works the same for all three resource types. The remainder of this +section uses memory pressure as the example, but the same logic applies to CPU +and IO pressure with the corresponding environment variable names. + +When a service initializes it hence should look for +`$MEMORY_PRESSURE_WATCH`. If set, it should try to open the specified path. If +it detects the path to refer to a regular file it should assume it refers to a +PSI kernel file. If so, it should write the data from `$MEMORY_PRESSURE_WRITE` +into the file descriptor (after Base64-decoding it, and only if the variable is +set) and then watch for `POLLPRI` events on it. If it detects the path refers +to a FIFO inode, it should open it, write the `$MEMORY_PRESSURE_WRITE` data +into it (as above) and then watch for `POLLIN` events on it. Whenever `POLLIN` +is seen it should read and discard any data queued in the FIFO. If the path +refers to an `AF_UNIX` socket in the file system, the application should +`connect()` a stream socket to it, write `$MEMORY_PRESSURE_WRITE` into it (as +above) and watch for `POLLIN`, discarding any data it might receive. + +To summarize: + +* If `$MEMORY_PRESSURE_WATCH` points to a regular file: open and watch for + `POLLPRI`, never read from the file descriptor. + +* If `$MEMORY_PRESSURE_WATCH` points to a FIFO: open and watch for `POLLIN`, + read/discard any incoming data. + +* If `$MEMORY_PRESSURE_WATCH` points to an `AF_UNIX` socket: connect and watch + for `POLLIN`, read/discard any incoming data. + +* If `$MEMORY_PRESSURE_WATCH` contains the literal string `/dev/null`, turn off + memory pressure handling. + +(And in each case, immediately after opening/connecting to the path, write the +decoded `$MEMORY_PRESSURE_WRITE` data into it.) + +Whenever a `POLLPRI`/`POLLIN` event is seen the service is under pressure. It +should use this as hint to release suitable redundant resources, for example: + +* glibc's memory allocation cache, via + [`malloc_trim()`](https://man7.org/linux/man-pages/man3/malloc_trim.3.html). Similarly, + allocation caches implemented in the service itself. + +* Any other local caches, such as DNS caches, or web caches (in particular if + service is a web browser). + +* Terminate any idle worker threads or processes. + +* Run a garbage collection (GC) cycle, if the runtime environment supports it. + +* Terminate the process if idle, and can be automatically started when + needed next. + +Which actions precisely to take depends on the service in question and the type +of pressure. Note that the notifications are delivered when resource latency +already degraded beyond some point. Hence when discussing which resources to +keep and which to discard, keep in mind it's typically acceptable that latencies +incurred recovering discarded resources at a later point are acceptable, given +that latencies *already* are affected negatively. + +In case the path supplied via `$MEMORY_PRESSURE_WATCH` points to a PSI kernel +API file, or to an `AF_UNIX` socket, opening it multiple times is safe and reliable, +and should deliver notifications to each of the opened file descriptors. This +is specifically useful for services that consist of multiple processes, and +where each of them shall be able to release resources on memory pressure. + +The `POLLPRI`/`POLLIN` conditions will be triggered every time pressure is +detected, but not continuously. It is thus safe to keep `poll()`-ing on the +same file descriptor continuously, and executing resource release operations +whenever the file descriptor triggers without having to expect overloading the +process. + +(Currently, the protocol defined here only allows configuration of a single +"degree" of pressure per resource type, there's no distinction made on how +strong the pressure is. In future, if it becomes apparent that there's clear +need to extend this we might eventually add different degrees, most likely by +adding additional environment variables such as `$MEMORY_PRESSURE_WRITE_LOW` +and `$MEMORY_PRESSURE_WRITE_HIGH` or similar, which may contain different +settings for lower or higher pressure thresholds.) + +## Service Manager Settings + +The service manager provides two per-service settings for each resource type +that control pressure handling: + +* `MemoryPressureWatch=` / `CPUPressureWatch=` / `IOPressureWatch=` controls + whether to enable the pressure protocol for the respective resource type for + the service in question. See + [`systemd.resource-control(5)`](https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#MemoryPressureWatch=) + for details. + +* `MemoryPressureThresholdSec=` / `CPUPressureThresholdSec=` / + `IOPressureThresholdSec=` allows configuring the threshold when to signal + pressure to the services. It takes a time value (usually in the millisecond + range) that defines a threshold per 1s time window: if resource latencies grow + beyond this threshold notifications are generated towards the service, + requesting it to release resources. + +The `/etc/systemd/system.conf` file provides two settings for each resource +type that may be used to select the default values for the above settings. If +the threshold isn't configured via the per-service nor system-wide option, it +defaults to 100ms. + +When pressure monitoring is enabled for a service this primarily does three +things: + +* It enables the corresponding cgroup accounting for the service (this is a + requirement for per-cgroup PSI). + +* It sets the aforementioned two environment variables for processes invoked + for the service, based on the control group of the service and provided + settings. + +* The corresponding PSI control group file (`memory.pressure`, `cpu.pressure`, + or `io.pressure`) associated with the service's cgroup is delegated to the + service (i.e. permissions are relaxed so that unprivileged service payload + code can open the file for writing). + +## Pressure Events in `sd-event` + +The +[`sd-event`](https://www.freedesktop.org/software/systemd/man/latest/sd-event.html) +event loop library provides API calls that encapsulate the functionality +described above: + +* [`sd_event_add_memory_pressure()`](https://www.freedesktop.org/software/systemd/man/latest/sd_event_add_memory_pressure.html), + `sd_event_add_cpu_pressure()`, and `sd_event_add_io_pressure()` implement the + service-side of the pressure protocol for each resource type and integrate it + with an `sd-event` event loop. Each reads the corresponding two environment + variables, connects/opens the specified file, writes the specified data to it, + then watches it for events. + +* The `sd_event_trim_memory()` call may be called to trim the calling + processes' memory. It's a wrapper around glibc's `malloc_trim()`, but first + releases allocation caches maintained by libsystemd internally. This function + serves as the default when a NULL callback is supplied to + `sd_event_add_memory_pressure()`. Note that the default handler for + `sd_event_add_cpu_pressure()` and `sd_event_add_io_pressure()` is a no-op; + a custom callback should be provided for CPU and IO pressure to take + meaningful action. + +When implementing a service using `sd-event`, for automatic memory pressure +handling, it's typically sufficient to add a line such as: + +```c +(void) sd_event_add_memory_pressure(event, NULL, NULL, NULL); +``` + +– right after allocating the event loop object `event`. For CPU and IO pressure, +a custom handler should be provided to take appropriate action: + +```c +(void) sd_event_add_cpu_pressure(event, NULL, my_cpu_pressure_handler, userdata); +(void) sd_event_add_io_pressure(event, NULL, my_io_pressure_handler, userdata); +``` + +## Other APIs + +Other programming environments might have native APIs to watch memory +pressure/low memory events. Most notable is probably GLib's +[GMemoryMonitor](https://docs.gtk.org/gio/iface.MemoryMonitor.html). As of GLib +2.86.0, it uses the per-cgroup PSI kernel file to monitor for memory pressure, +but does not yet read the environment variables recommended above. + +In older versions, it used the per-system Linux PSI interface as the backend, but operated +differently than the above: memory pressure events were picked up by a system +service, which then propagated this through D-Bus to the applications. This was +typically less than ideal, since this means each notification event had to +traverse three processes before being handled. This traversal created +additional latencies at a time where the system is already experiencing adverse +latencies. Moreover, it focused on system-wide PSI events, even though +service-local ones are generally the better approach. diff --git a/man/oomd.conf.xml b/man/oomd.conf.xml index a4be5e1274ff9..f8c3c0a173e15 100644 --- a/man/oomd.conf.xml +++ b/man/oomd.conf.xml @@ -62,7 +62,7 @@ Note that this is a privileged option as, even if it has a timeout, is synchronous and delays the kill, so use with care. The typically preferable mechanism to process memory pressure is to do what - MEMORY_PRESSURE describes which is unprivileged, + Resource Pressure Handling describes which is unprivileged, asynchronous and does not delay the kill. diff --git a/man/sd_event_add_memory_pressure.xml b/man/sd_event_add_memory_pressure.xml index 05f2ff2b74528..e472e620439c9 100644 --- a/man/sd_event_add_memory_pressure.xml +++ b/man/sd_event_add_memory_pressure.xml @@ -244,7 +244,7 @@ LOG_DEBUG level (with message ID f9b0be465ad540d0850ad32172d57c21) about the memory pressure operation. - For further details see Memory Pressure Handling in + For further details see Resource Pressure Handling in systemd. diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 455f666374f99..809cc285fdce1 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -4698,7 +4698,7 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX $MEMORY_PRESSURE_WRITE If memory pressure monitoring is enabled for this service unit, the path to watch - and the data to write into it. See Memory Pressure + and the data to write into it. See Resource Pressure Handling for details about these variables and the service protocol data they convey. diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml index f8a2e14e1b686..b5d559849dc3a 100644 --- a/man/systemd.resource-control.xml +++ b/man/systemd.resource-control.xml @@ -1628,7 +1628,7 @@ DeviceAllow=/dev/loop-control Note that services are free to use the two environment variables, but it is unproblematic if they ignore them. Memory pressure handling must be implemented individually in each service, and usually means different things for different software. For further details on memory pressure - handling see Memory Pressure Handling in + handling see Resource Pressure Handling in systemd. Services implemented using From 5ade3f6a01c6a67332f6838428d28d8307283c83 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 16 Mar 2026 14:20:12 +0100 Subject: [PATCH 0869/1296] docs: Fix window in PRESSURE.md --- docs/PRESSURE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/PRESSURE.md b/docs/PRESSURE.md index 6ea6b60711079..29efc07e5cf13 100644 --- a/docs/PRESSURE.md +++ b/docs/PRESSURE.md @@ -175,14 +175,14 @@ that control pressure handling: * `MemoryPressureThresholdSec=` / `CPUPressureThresholdSec=` / `IOPressureThresholdSec=` allows configuring the threshold when to signal pressure to the services. It takes a time value (usually in the millisecond - range) that defines a threshold per 1s time window: if resource latencies grow + range) that defines a threshold per 2s time window: if resource latencies grow beyond this threshold notifications are generated towards the service, requesting it to release resources. The `/etc/systemd/system.conf` file provides two settings for each resource type that may be used to select the default values for the above settings. If the threshold isn't configured via the per-service nor system-wide option, it -defaults to 100ms. +defaults to 200ms. When pressure monitoring is enabled for a service this primarily does three things: From 4b32ab5a36aea7752be26c18dabc3a554189b19d Mon Sep 17 00:00:00 2001 From: Milan Kyselica Date: Thu, 9 Apr 2026 19:45:19 +0200 Subject: [PATCH 0870/1296] udev: fix bounds check in dev_if_packed_info() The check compared bLength against (size - sizeof(descriptor)), which is an absolute limit unrelated to the current buffer position. Since bLength is uint8_t (max 255), this can never exceed size - 9 for any realistic input, making the check dead code. Use (size - pos) instead so the check actually catches descriptors that extend past the end of the read data. Fixes: https://github.com/systemd/systemd/issues/41570 --- src/udev/udev-builtin-usb_id.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/udev/udev-builtin-usb_id.c b/src/udev/udev-builtin-usb_id.c index 80597ea89ee25..61250b7072fe0 100644 --- a/src/udev/udev-builtin-usb_id.c +++ b/src/udev/udev-builtin-usb_id.c @@ -168,7 +168,7 @@ static int dev_if_packed_info(sd_device *dev, char *ifs_str, size_t len) { desc = (struct usb_interface_descriptor *) (buf + pos); if (desc->bLength < 3) break; - if (desc->bLength > size - sizeof(struct usb_interface_descriptor)) + if (desc->bLength > (size_t) size - pos) return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EIO), "Corrupt data read from \"%s\"", filename); pos += desc->bLength; From d3a1710bc22b9047620d1a05f76dee8590255206 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Thu, 9 Apr 2026 15:12:48 +0200 Subject: [PATCH 0871/1296] sd-varlink: fix a potential connection count leak With the old version there was a potential connection count leak if either of the two hashmap operations in count_connection() failed. In that case we'd return from sd_varlink_server_add_connection_pair() _before_ attached the sd_varlink_server object to an sd_varlink object, and since varlink_detach_server() is the only place where the connection counter is decremented (called through sd_varlink_close() in various error paths later _if_ the "server" object is not null, i.e. attached to the sd_varlink object) we'd "leak" a connection every time this happened. However, the potential of abusing this is very theoretical, as one would need to hit OOM every time either of the hashmap operations was executed for a while before exhausting the connection limit. Let's just increment the connection counter after any potential error path, so we don't have to deal with potential rollbacks. --- src/libsystemd/sd-varlink/sd-varlink.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index fe2bf0e6381a7..a9bbb8f79c130 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -3880,8 +3880,6 @@ static int count_connection(sd_varlink_server *server, const struct ucred *ucred assert(server); assert(ucred); - server->n_connections++; - if (FLAGS_SET(server->flags, SD_VARLINK_SERVER_ACCOUNT_UID)) { assert(uid_is_valid(ucred->uid)); @@ -3899,6 +3897,8 @@ static int count_connection(sd_varlink_server *server, const struct ucred *ucred return varlink_server_log_errno(server, r, "Failed to increment counter in UID hash table: %m"); } + server->n_connections++; + return 0; } From 6fe4a16f364ad268cb0717879d68a007cd7e652f Mon Sep 17 00:00:00 2001 From: Yaping Li <202858510+YapingLi04@users.noreply.github.com> Date: Wed, 8 Apr 2026 10:19:42 -0700 Subject: [PATCH 0872/1296] cgroup-util: add cg_get_keyed_attribute_uint64() helper Multiple callers of cg_get_keyed_attribute() follow the same pattern of reading a single keyed attribute and then parsing it as uint64_t with safe_atou64(). Add a helper that combines both steps. Convert all existing single-key + uint64 call sites in cgtop, cgroup.c, and oomd-util.c to use the new helper. --- src/basic/cgroup-util.c | 18 ++++++++++++++++++ src/basic/cgroup-util.h | 1 + src/cgtop/cgtop.c | 7 +------ src/core/cgroup.c | 32 +++++++++----------------------- src/oom/oomd-util.c | 8 ++------ 5 files changed, 31 insertions(+), 35 deletions(-) diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c index d613e65c4b820..1e42aa60aa4cc 100644 --- a/src/basic/cgroup-util.c +++ b/src/basic/cgroup-util.c @@ -1560,6 +1560,24 @@ int cg_get_keyed_attribute( return r; } +int cg_get_keyed_attribute_uint64(const char *path, const char *attribute, const char *key, uint64_t *ret) { + _cleanup_free_ char *val = NULL; + int r; + + assert(key); + assert(ret); + + r = cg_get_keyed_attribute(path, attribute, STRV_MAKE(key), &val); + if (r < 0) + return r; + + r = safe_atou64(val, ret); + if (r < 0) + return log_debug_errno(r, "Failed to parse value '%s' of key '%s' in cgroup attribute '%s': %m", val, key, attribute); + + return 0; +} + int cg_mask_to_string(CGroupMask mask, char **ret) { _cleanup_free_ char *s = NULL; bool space = false; diff --git a/src/basic/cgroup-util.h b/src/basic/cgroup-util.h index 68a771e5aed64..7cf0f779b8b95 100644 --- a/src/basic/cgroup-util.h +++ b/src/basic/cgroup-util.h @@ -165,6 +165,7 @@ int cg_get_attribute_as_uint64(const char *path, const char *attribute, uint64_t int cg_get_attribute_as_bool(const char *path, const char *attribute); int cg_get_keyed_attribute(const char *path, const char *attribute, char * const *keys, char **values); +int cg_get_keyed_attribute_uint64(const char *path, const char *attribute, const char *key, uint64_t *ret); int cg_get_owner(const char *path, uid_t *ret_uid); diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c index a9bf64a61651d..0c93339a029ec 100644 --- a/src/cgtop/cgtop.c +++ b/src/cgtop/cgtop.c @@ -285,19 +285,14 @@ static int process_cpu(Group *g, unsigned iteration) { if (r < 0) return r; } else { - _cleanup_free_ char *val = NULL; uint64_t u; - r = cg_get_keyed_attribute(g->path, "cpu.stat", STRV_MAKE("usage_usec"), &val); + r = cg_get_keyed_attribute_uint64(g->path, "cpu.stat", "usage_usec", &u); if (IN_SET(r, -ENOENT, -ENXIO)) return 0; if (r < 0) return r; - r = safe_atou64(val, &u); - if (r < 0) - return r; - new_usage = u * NSEC_PER_USEC; } diff --git a/src/core/cgroup.c b/src/core/cgroup.c index 7bcd6777df244..baf7dde12724a 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -2980,9 +2980,8 @@ int unit_check_oomd_kill(Unit *u) { } int unit_check_oom(Unit *u) { - _cleanup_free_ char *oom_kill = NULL; bool increased; - uint64_t c; + uint64_t c = 0; int r; CGroupRuntime *crt = unit_get_cgroup_runtime(u); @@ -2997,33 +2996,25 @@ int unit_check_oom(Unit *u) { * back to reading oom_kill if we can't find the file or field. */ if (ctx->memory_oom_group) { - r = cg_get_keyed_attribute( + r = cg_get_keyed_attribute_uint64( crt->cgroup_path, "memory.events.local", - STRV_MAKE("oom_group_kill"), - &oom_kill); + "oom_group_kill", + &c); if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO)) return log_unit_debug_errno(u, r, "Failed to read oom_group_kill field of memory.events.local cgroup attribute, ignoring: %m"); } - if (isempty(oom_kill)) { - r = cg_get_keyed_attribute( + if (!ctx->memory_oom_group || r < 0) { + r = cg_get_keyed_attribute_uint64( crt->cgroup_path, "memory.events", - STRV_MAKE("oom_kill"), - &oom_kill); + "oom_kill", + &c); if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO)) return log_unit_debug_errno(u, r, "Failed to read oom_kill field of memory.events cgroup attribute: %m"); } - if (!oom_kill) - c = 0; - else { - r = safe_atou64(oom_kill, &c); - if (r < 0) - return log_unit_debug_errno(u, r, "Failed to parse memory.events cgroup oom field: %m"); - } - increased = c > crt->oom_kill_last; crt->oom_kill_last = c; @@ -3569,14 +3560,9 @@ static int unit_get_cpu_usage_raw(const Unit *u, const CGroupRuntime *crt, nsec_ if (unit_has_host_root_cgroup(u)) return procfs_cpu_get_usage(ret); - _cleanup_free_ char *val = NULL; uint64_t us; - r = cg_get_keyed_attribute(crt->cgroup_path, "cpu.stat", STRV_MAKE("usage_usec"), &val); - if (r < 0) - return r; - - r = safe_atou64(val, &us); + r = cg_get_keyed_attribute_uint64(crt->cgroup_path, "cpu.stat", "usage_usec", &us); if (r < 0) return r; diff --git a/src/oom/oomd-util.c b/src/oom/oomd-util.c index 55e17df46f08c..c0e04041a7e6a 100644 --- a/src/oom/oomd-util.c +++ b/src/oom/oomd-util.c @@ -624,7 +624,7 @@ int oomd_select_by_swap_usage(Hashmap *h, uint64_t threshold_usage, OomdCGroupCo int oomd_cgroup_context_acquire(const char *path, OomdCGroupContext **ret) { _cleanup_(oomd_cgroup_context_unrefp) OomdCGroupContext *ctx = NULL; - _cleanup_free_ char *p = NULL, *val = NULL; + _cleanup_free_ char *p = NULL; bool is_root; int r; @@ -678,13 +678,9 @@ int oomd_cgroup_context_acquire(const char *path, OomdCGroupContext **ret) { else if (r < 0) return log_debug_errno(r, "Error getting memory.swap.current from %s: %m", path); - r = cg_get_keyed_attribute(path, "memory.stat", STRV_MAKE("pgscan"), &val); + r = cg_get_keyed_attribute_uint64(path, "memory.stat", "pgscan", &ctx->pgscan); if (r < 0) return log_debug_errno(r, "Error getting pgscan from memory.stat under %s: %m", path); - - r = safe_atou64(val, &ctx->pgscan); - if (r < 0) - return log_debug_errno(r, "Error converting pgscan value to uint64_t: %m"); } *ret = TAKE_PTR(ctx); From 8c65fe4fa11d9476558aa6c54d9e26db8cc80f32 Mon Sep 17 00:00:00 2001 From: Yaping Li <202858510+YapingLi04@users.noreply.github.com> Date: Thu, 2 Apr 2026 22:01:15 -0700 Subject: [PATCH 0873/1296] report: add cgroup metrics in a separate varlink service Add CpuUsage, MemoryUsage, IOReadBytes, IOReadOperations, and TasksCurrent in a standalone socket-activated varlink service. The new systemd-report-cgroup service listens at /run/systemd/report/io.systemd.CGroup and exposes: - io.systemd.CGroup.CpuUsage - io.systemd.CGroup.IOReadBytes - io.systemd.CGroup.IOReadOperations - io.systemd.CGroup.MemoryUsage (with type=current/available/peak) - io.systemd.CGroup.TasksCurrent --- src/report/meson.build | 7 + src/report/report-cgroup-server.c | 131 +++++++ src/report/report-cgroup.c | 495 ++++++++++++++++++++++++ src/report/report-cgroup.h | 20 + test/units/TEST-74-AUX-UTILS.report.sh | 7 + units/meson.build | 2 + units/systemd-report-cgroup.socket | 25 ++ units/systemd-report-cgroup@.service.in | 42 ++ 8 files changed, 729 insertions(+) create mode 100644 src/report/report-cgroup-server.c create mode 100644 src/report/report-cgroup.c create mode 100644 src/report/report-cgroup.h create mode 100644 units/systemd-report-cgroup.socket create mode 100644 units/systemd-report-cgroup@.service.in diff --git a/src/report/meson.build b/src/report/meson.build index 26d1bbfdc3e9c..36227bed9f56b 100644 --- a/src/report/meson.build +++ b/src/report/meson.build @@ -15,4 +15,11 @@ executables += [ 'report-basic.c', ), }, + libexec_template + { + 'name' : 'systemd-report-cgroup', + 'sources' : files( + 'report-cgroup.c', + 'report-cgroup-server.c', + ), + }, ] diff --git a/src/report/report-cgroup-server.c b/src/report/report-cgroup-server.c new file mode 100644 index 0000000000000..eef2ec05fcbfd --- /dev/null +++ b/src/report/report-cgroup-server.c @@ -0,0 +1,131 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-varlink.h" + +#include "alloc-util.h" +#include "ansi-color.h" +#include "build.h" +#include "log.h" +#include "main-func.h" +#include "pretty-print.h" +#include "report-cgroup.h" +#include "varlink-io.systemd.Metrics.h" +#include "varlink-util.h" + +static int vl_server(void) { + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *vs = NULL; + _cleanup_(cgroup_context_freep) CGroupContext *ctx = NULL; + int r; + + ctx = new0(CGroupContext, 1); + if (!ctx) + return log_oom(); + + r = varlink_server_new(&vs, SD_VARLINK_SERVER_INHERIT_USERDATA, ctx); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + r = sd_varlink_server_add_interface(vs, &vl_interface_io_systemd_Metrics); + if (r < 0) + return log_error_errno(r, "Failed to add Varlink interface: %m"); + + r = sd_varlink_server_bind_method_many( + vs, + "io.systemd.Metrics.List", vl_method_list_metrics, + "io.systemd.Metrics.Describe", vl_method_describe_metrics); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink methods: %m"); + + r = sd_varlink_server_loop_auto(vs); + if (r < 0) + return log_error_errno(r, "Failed to run Varlink event loop: %m"); + + return 0; +} + +static int help(void) { + _cleanup_free_ char *url = NULL; + int r; + + r = terminal_urlify_man("systemd-report-cgroup", "8", &url); + if (r < 0) + return log_oom(); + + printf("%s [OPTIONS...]\n" + "\n%sReport cgroup metrics.%s\n" + "\n%sOptions:%s\n" + " -h --help Show this help\n" + " --version Show package version\n" + "\nSee the %s for details.\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal(), + ansi_underline(), + ansi_normal(), + url); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + case 'h': + return help(); + + case ARG_VERSION: + return version(); + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + + if (optind < argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "This program takes no arguments."); + + 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) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "This program can only run as a Varlink service."); + + return 1; +} + +static int run(int argc, char *argv[]) { + int r; + + log_setup(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + return vl_server(); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/report/report-cgroup.c b/src/report/report-cgroup.c new file mode 100644 index 0000000000000..476b074ac05bf --- /dev/null +++ b/src/report/report-cgroup.c @@ -0,0 +1,495 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" +#include "sd-varlink.h" + +#include "alloc-util.h" +#include "cgroup-util.h" +#include "extract-word.h" +#include "fd-util.h" +#include "fileio.h" +#include "log.h" +#include "metrics.h" +#include "parse-util.h" +#include "path-util.h" +#include "report-cgroup.h" +#include "string-util.h" +#include "time-util.h" + +typedef struct CGroupInfo { + char *unit; + char *path; + uint64_t io_rbytes; + uint64_t io_rios; + int io_stat_cached; /* 0 = not attempted, > 0 = cached, < 0 = -errno */ +} CGroupInfo; + +static CGroupInfo *cgroup_info_free(CGroupInfo *info) { + if (!info) + return NULL; + free(info->unit); + free(info->path); + return mfree(info); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(CGroupInfo*, cgroup_info_free); + +static void cgroup_info_array_free(CGroupInfo **infos, size_t n) { + FOREACH_ARRAY(i, infos, n) + cgroup_info_free(*i); + free(infos); +} + +static void cgroup_context_flush(CGroupContext *ctx) { + assert(ctx); + cgroup_info_array_free(ctx->cgroups, ctx->n_cgroups); + ctx->cgroups = NULL; + ctx->n_cgroups = 0; + ctx->cache_populated = false; +} + +CGroupContext *cgroup_context_free(CGroupContext *ctx) { + if (!ctx) + return NULL; + cgroup_context_flush(ctx); + return mfree(ctx); +} + +static int walk_cgroups_recursive(const char *path, CGroupInfo ***infos, size_t *n_infos) { + _cleanup_closedir_ DIR *d = NULL; + int r; + + assert(path); + assert(infos); + assert(n_infos); + + /* Collect any unit cgroup we encounter */ + _cleanup_free_ char *name = NULL; + r = cg_path_get_unit(path, &name); + if (r >= 0) { + _cleanup_(cgroup_info_freep) CGroupInfo *info = new(CGroupInfo, 1); + if (!info) + return log_oom(); + + *info = (CGroupInfo) { + .unit = TAKE_PTR(name), + .path = strdup(path), + }; + if (!info->path) + return log_oom(); + + if (!GREEDY_REALLOC(*infos, *n_infos + 1)) + return log_oom(); + + (*infos)[(*n_infos)++] = TAKE_PTR(info); + return 0; /* Unit cgroups are leaf nodes for our purposes */ + } + + /* Stop at delegation boundaries — don't descend into delegated subtrees */ + r = cg_is_delegated(path); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_debug_errno(r, "Failed to check delegation for '%s': %m", path); + if (r > 0) + return 0; + + r = cg_enumerate_subgroups(path, &d); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_debug_errno(r, "Failed to enumerate cgroup '%s': %m", path); + + for (;;) { + _cleanup_free_ char *fn = NULL, *child = NULL; + + r = cg_read_subgroup(d, &fn); + if (r < 0) + return log_debug_errno(r, "Failed to read subgroup from '%s': %m", path); + if (r == 0) + break; + + child = path_join(empty_to_root(path), fn); + if (!child) + return log_oom(); + + path_simplify(child); + + r = walk_cgroups_recursive(child, infos, n_infos); + if (r < 0) + return r; + } + + return 0; +} + +static int walk_cgroups(CGroupContext *ctx, CGroupInfo ***ret, size_t *ret_n) { + int r; + + assert(ctx); + assert(ret); + assert(ret_n); + + /* Return cached result if available */ + if (ctx->cache_populated) { + *ret = ctx->cgroups; + *ret_n = ctx->n_cgroups; + return 0; + } + + CGroupInfo **infos = NULL; + size_t n_infos = 0; + CLEANUP_ARRAY(infos, n_infos, cgroup_info_array_free); + + r = walk_cgroups_recursive("", &infos, &n_infos); + if (r < 0) + return r; + + ctx->cgroups = TAKE_PTR(infos); + ctx->n_cgroups = TAKE_GENERIC(n_infos, size_t, 0); + ctx->cache_populated = true; + + *ret = ctx->cgroups; + *ret_n = ctx->n_cgroups; + return 0; +} + +static int cpu_usage_build_json(MetricFamilyContext *context, void *userdata) { + CGroupContext *ctx = ASSERT_PTR(userdata); + CGroupInfo **cgroups; + size_t n_cgroups; + int r; + + assert(context); + + r = walk_cgroups(ctx, &cgroups, &n_cgroups); + if (r < 0) + return 0; /* Skip metric on failure */ + + FOREACH_ARRAY(c, cgroups, n_cgroups) { + uint64_t us; + + r = cg_get_keyed_attribute_uint64((*c)->path, "cpu.stat", "usage_usec", &us); + if (r < 0) + continue; + + r = metric_build_send_unsigned( + context, + (*c)->unit, + us * NSEC_PER_USEC, + /* fields= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + +static int memory_usage_build_json(MetricFamilyContext *context, void *userdata) { + CGroupContext *ctx = ASSERT_PTR(userdata); + CGroupInfo **cgroups; + size_t n_cgroups; + int r; + + assert(context); + + r = walk_cgroups(ctx, &cgroups, &n_cgroups); + if (r < 0) + return 0; + + FOREACH_ARRAY(c, cgroups, n_cgroups) { + uint64_t current = 0, limit = UINT64_MAX; + + r = cg_get_attribute_as_uint64((*c)->path, "memory.current", ¤t); + if (r >= 0) { + /* Walk up the cgroup tree to find the tightest memory limit */ + _cleanup_free_ char *path_buf = strdup((*c)->path); + if (!path_buf) + return log_oom(); + + for (char *p = path_buf;;) { + uint64_t high, max; + + r = cg_get_attribute_as_uint64(p, "memory.max", &max); + if (r >= 0 && max < limit) + limit = max; + + r = cg_get_attribute_as_uint64(p, "memory.high", &high); + if (r >= 0 && high < limit) + limit = high; + + /* Move to parent */ + const char *e; + r = path_find_last_component(p, /* accept_dot_dot= */ false, &e, NULL); + if (r <= 0) + break; + p[e - p] = '\0'; + } + + if (limit != UINT64_MAX && limit > current) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *fields = NULL; + r = sd_json_buildo(&fields, SD_JSON_BUILD_PAIR_STRING("type", "available")); + if (r < 0) + return r; + + r = metric_build_send_unsigned( + context, + (*c)->unit, + limit - current, + fields); + if (r < 0) + return r; + } + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *fields = NULL; + r = sd_json_buildo(&fields, SD_JSON_BUILD_PAIR_STRING("type", "current")); + if (r < 0) + return r; + + r = metric_build_send_unsigned( + context, + (*c)->unit, + current, + fields); + if (r < 0) + return r; + } + + uint64_t val; + r = cg_get_attribute_as_uint64((*c)->path, "memory.peak", &val); + if (r >= 0) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *fields = NULL; + r = sd_json_buildo(&fields, SD_JSON_BUILD_PAIR_STRING("type", "peak")); + if (r < 0) + return r; + + r = metric_build_send_unsigned( + context, + (*c)->unit, + val, + fields); + if (r < 0) + return r; + } + } + + return 0; +} + +/* Parse io.stat for a cgroup once, summing both rbytes= and rios= fields in a + * single pass to avoid reading the file twice. */ +static int io_stat_parse(const char *cgroup_path, uint64_t *ret_rbytes, uint64_t *ret_rios) { + _cleanup_free_ char *path = NULL; + _cleanup_fclose_ FILE *f = NULL; + uint64_t rbytes = 0, rios = 0; + int r; + + r = cg_get_path(cgroup_path, "io.stat", &path); + if (r < 0) + return r; + + f = fopen(path, "re"); + if (!f) + return -errno; + + for (;;) { + _cleanup_free_ char *line = NULL; + const char *p; + + r = read_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return r; + if (r == 0) + break; + + p = line; + p += strcspn(p, WHITESPACE); + p += strspn(p, WHITESPACE); + + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, NULL, EXTRACT_RETAIN_ESCAPE); + if (r < 0) + return r; + if (r == 0) + break; + + const char *v; + uint64_t val; + + v = startswith(word, "rbytes="); + if (v && safe_atou64(v, &val) >= 0) { + rbytes += val; + continue; + } + + v = startswith(word, "rios="); + if (v && safe_atou64(v, &val) >= 0) + rios += val; + } + } + + *ret_rbytes = rbytes; + *ret_rios = rios; + return 0; +} + +static int ensure_io_stat_cached(CGroupInfo *info) { + int r; + + assert(info); + + if (info->io_stat_cached > 0) + return 0; + if (info->io_stat_cached < 0) + return info->io_stat_cached; + + r = io_stat_parse(info->path, &info->io_rbytes, &info->io_rios); + if (r < 0) { + if (r != -ENOENT) + log_debug_errno(r, "Failed to parse IO stats for '%s': %m", info->path); + info->io_stat_cached = r; + return r; + } + + info->io_stat_cached = 1; + return 0; +} + +static int io_read_bytes_build_json(MetricFamilyContext *context, void *userdata) { + CGroupContext *ctx = ASSERT_PTR(userdata); + CGroupInfo **cgroups; + size_t n_cgroups; + int r; + + assert(context); + + r = walk_cgroups(ctx, &cgroups, &n_cgroups); + if (r < 0) + return 0; + + FOREACH_ARRAY(c, cgroups, n_cgroups) { + if (ensure_io_stat_cached(*c) < 0) + continue; + + r = metric_build_send_unsigned( + context, + (*c)->unit, + (*c)->io_rbytes, + /* fields= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + +static int io_read_operations_build_json(MetricFamilyContext *context, void *userdata) { + CGroupContext *ctx = ASSERT_PTR(userdata); + CGroupInfo **cgroups; + size_t n_cgroups; + int r; + + assert(context); + + r = walk_cgroups(ctx, &cgroups, &n_cgroups); + if (r < 0) + return 0; + + FOREACH_ARRAY(c, cgroups, n_cgroups) { + if (ensure_io_stat_cached(*c) < 0) + continue; + + r = metric_build_send_unsigned( + context, + (*c)->unit, + (*c)->io_rios, + /* fields= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + +static int tasks_current_build_json(MetricFamilyContext *context, void *userdata) { + CGroupContext *ctx = ASSERT_PTR(userdata); + CGroupInfo **cgroups; + size_t n_cgroups; + int r; + + assert(context); + + r = walk_cgroups(ctx, &cgroups, &n_cgroups); + if (r < 0) + return 0; + + FOREACH_ARRAY(c, cgroups, n_cgroups) { + uint64_t val; + + r = cg_get_attribute_as_uint64((*c)->path, "pids.current", &val); + if (r < 0) + continue; + + r = metric_build_send_unsigned( + context, + (*c)->unit, + val, + /* fields= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + +static const MetricFamily cgroup_metric_family_table[] = { + /* Keep metrics ordered alphabetically */ + { + .name = METRIC_IO_SYSTEMD_CGROUP_PREFIX "CpuUsage", + .description = "Per unit metric: CPU usage in nanoseconds", + .type = METRIC_FAMILY_TYPE_COUNTER, + .generate = cpu_usage_build_json, + }, + { + .name = METRIC_IO_SYSTEMD_CGROUP_PREFIX "IOReadBytes", + .description = "Per unit metric: IO bytes read", + .type = METRIC_FAMILY_TYPE_COUNTER, + .generate = io_read_bytes_build_json, + }, + { + .name = METRIC_IO_SYSTEMD_CGROUP_PREFIX "IOReadOperations", + .description = "Per unit metric: IO read operations", + .type = METRIC_FAMILY_TYPE_COUNTER, + .generate = io_read_operations_build_json, + }, + { + .name = METRIC_IO_SYSTEMD_CGROUP_PREFIX "MemoryUsage", + .description = "Per unit metric: memory usage in bytes", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = memory_usage_build_json, + }, + { + .name = METRIC_IO_SYSTEMD_CGROUP_PREFIX "TasksCurrent", + .description = "Per unit metric: current number of tasks", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = tasks_current_build_json, + }, + {} +}; + +int vl_method_describe_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return metrics_method_describe(cgroup_metric_family_table, link, parameters, flags, userdata); +} + +int vl_method_list_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + CGroupContext *ctx = ASSERT_PTR(userdata); + int r; + + r = metrics_method_list(cgroup_metric_family_table, link, parameters, flags, userdata); + + cgroup_context_flush(ctx); + + return r; +} diff --git a/src/report/report-cgroup.h b/src/report/report-cgroup.h new file mode 100644 index 0000000000000..dae8411df58b0 --- /dev/null +++ b/src/report/report-cgroup.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +#define METRIC_IO_SYSTEMD_CGROUP_PREFIX "io.systemd.CGroup." + +typedef struct CGroupInfo CGroupInfo; + +typedef struct CGroupContext { + CGroupInfo **cgroups; + size_t n_cgroups; + bool cache_populated; +} CGroupContext; + +CGroupContext *cgroup_context_free(CGroupContext *ctx); +DEFINE_TRIVIAL_CLEANUP_FUNC(CGroupContext*, cgroup_context_free); + +int vl_method_list_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_describe_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/test/units/TEST-74-AUX-UTILS.report.sh b/test/units/TEST-74-AUX-UTILS.report.sh index 0b9006e0590e0..f92f1ed75078d 100755 --- a/test/units/TEST-74-AUX-UTILS.report.sh +++ b/test/units/TEST-74-AUX-UTILS.report.sh @@ -30,6 +30,13 @@ REPORT=/usr/lib/systemd/systemd-report "$REPORT" describe-metrics io.systemd piff "$REPORT" describe-metrics piff +# test io.systemd.CGroup Metrics +systemctl start systemd-report-cgroup.socket +varlinkctl info /run/systemd/report/io.systemd.CGroup +varlinkctl list-methods /run/systemd/report/io.systemd.CGroup +varlinkctl --more call /run/systemd/report/io.systemd.CGroup io.systemd.Metrics.List {} +varlinkctl --more call /run/systemd/report/io.systemd.CGroup io.systemd.Metrics.Describe {} + # test io.systemd.Network Metrics varlinkctl info /run/systemd/report/io.systemd.Network varlinkctl list-methods /run/systemd/report/io.systemd.Network diff --git a/units/meson.build b/units/meson.build index 02c2db074c259..a7a3e6c5d61c4 100644 --- a/units/meson.build +++ b/units/meson.build @@ -720,6 +720,8 @@ units = [ 'file' : 'systemd-repart@.service', 'conditions' : ['ENABLE_REPART'], }, + { 'file' : 'systemd-report-cgroup.socket' }, + { 'file' : 'systemd-report-cgroup@.service.in' }, { 'file' : 'systemd-resolved.service.in', 'conditions' : ['ENABLE_RESOLVE'], diff --git a/units/systemd-report-cgroup.socket b/units/systemd-report-cgroup.socket new file mode 100644 index 0000000000000..39a867cd40c85 --- /dev/null +++ b/units/systemd-report-cgroup.socket @@ -0,0 +1,25 @@ +# 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=CGroup Report Varlink Socket +DefaultDependencies=no +Before=sockets.target shutdown.target +Conflicts=shutdown.target + +[Socket] +ListenStream=/run/systemd/report/io.systemd.CGroup +FileDescriptorName=varlink +SocketMode=0666 +Accept=yes +MaxConnectionsPerSource=16 +RemoveOnStop=yes + +[Install] +WantedBy=sockets.target diff --git a/units/systemd-report-cgroup@.service.in b/units/systemd-report-cgroup@.service.in new file mode 100644 index 0000000000000..6f18c647dd248 --- /dev/null +++ b/units/systemd-report-cgroup@.service.in @@ -0,0 +1,42 @@ +# 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=CGroup Report Service +DefaultDependencies=no +Conflicts=shutdown.target +Before=shutdown.target + +[Service] +CapabilityBoundingSet= +DeviceAllow= +DynamicUser=yes +IPAddressDeny=any +LockPersonality=yes +MemoryDenyWriteExecute=yes +PrivateDevices=yes +PrivateIPC=yes +PrivateNetwork=yes +PrivateTmp=disconnected +ProtectControlGroups=yes +ProtectHome=yes +ProtectHostname=yes +ProtectKernelLogs=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +ProtectSystem=strict +RestrictAddressFamilies=AF_UNIX +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes +RuntimeMaxSec=1min +SystemCallArchitectures=native +SystemCallErrorNumber=EPERM +SystemCallFilter=@system-service +ExecStart={{LIBEXECDIR}}/systemd-report-cgroup From fb0ae7436dbd7182ec2d53cca366599f9031db4d Mon Sep 17 00:00:00 2001 From: azureuser Date: Tue, 3 Mar 2026 08:41:45 +0000 Subject: [PATCH 0874/1296] resolved: skip cache flush on server switch/re-probe when StaleRetentionSec is set manager_set_dns_server() and dns_server_flush_cache() call dns_cache_flush() unconditionally, wiping the entire cache even when StaleRetentionSec is configured. This defeats serve-stale by discarding cached records that should remain available during server switches and feature-level re-probes. The original serve-stale commit (5ed91481ab) added a stale_retention_usec guard to link_set_dns_server(), and a later commit (7928c0e0a1) added the same guard to dns_delegate_set_dns_server(), but these two call sites in resolved-dns-server.c were missed. This is particularly visible with DNSOverTLS, where TLS handshake failures trigger frequent feature-level downgrades and re-probes via dns_server_flush_cache(), flushing the cache each time. Add the same stale_retention_usec guard to both call sites so that cache entries are allowed to expire naturally via dns_cache_prune() when serve-stale is enabled. Fixes: #40781 This commit was prepared with assistance from an AI coding agent (GitHub Copilot). All changes have been reviewed for correctness and adherence to the systemd coding style. --- src/resolve/resolved-dns-server.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index 4f04476f2d4ac..6d1b349c4900a 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -1036,7 +1036,8 @@ DnsServer *manager_set_dns_server(Manager *m, DnsServer *s) { dns_server_unref(m->current_dns_server); m->current_dns_server = dns_server_ref(s); - if (m->unicast_scope) + /* Skip flushing the cache if server stale feature is enabled. */ + if (m->unicast_scope && m->stale_retention_usec == 0) dns_cache_flush(&m->unicast_scope->cache); (void) manager_send_changed(m, "CurrentDNSServer"); @@ -1155,6 +1156,10 @@ void dns_server_flush_cache(DnsServer *s) { if (!scope) return; + /* Skip flushing the cache if server stale feature is enabled. */ + if (s->manager->stale_retention_usec > 0) + return; + dns_cache_flush(&scope->cache); } From a65ebc3ff9a868bd447faa59789ee8e9ad8c534a Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 10 Apr 2026 08:04:08 +0000 Subject: [PATCH 0875/1296] claude-review: improve review quality for large PRs Several issues were identified from analyzing logs of a large (52-commit) PR review: - Claude was batching multiple commits into a single review agent instead of one per worktree. Strengthen the prompt to explicitly prohibit grouping. - Claude was reading pr-context.json and commit messages before spawning agents despite instructions not to, wasting time. Tighten the pre-spawn rules to only allow listing worktrees/ and reading review-schema.json. - Subagents were spawned with model "sonnet" instead of "opus". Add explicit instruction to use opus. - After agents returned, Claude spent 9 minutes re-verifying findings with bash/grep/sed commands, duplicating the agents' work. Add instruction to trust subagent findings and only read pr-context.json in phase 2. - Subagents returned markdown-wrapped JSON instead of raw JSON arrays. Add instruction requiring raw JSON output only. - Each subagent was independently reading review-schema.json. Instead have the main agent read it once and paste it into each subagent prompt. - The "drop low-confidence findings" instruction was being used to justify dropping findings that Claude itself acknowledged as valid ("solid cleanup suggestions", "reasonable consistency improvement"). Remove the instruction. - Simplify the deduplication instructions - Stop adding the severity to the body in the post processing job as claude is also adding it so they end up duplicated. --- .github/workflows/claude-review.yml | 59 +++++++++++++++-------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index a079cf1164265..bf20e7d51e9b9 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -256,27 +256,34 @@ jobs: ## Phase 1: Review commits - List the directories in `worktrees/` — there is one per commit. Each - worktree at `worktrees//` contains the full source tree checked out at - that commit, plus `commit.patch` (the diff) and `commit-message.txt` - (the commit message). Spawn one - review subagent per worktree, all in a single message so they run concurrently. - Do NOT pre-compute diffs or read any other files before spawning — the subagents - will do that themselves. + First, list the directories in `worktrees/` and read `review-schema.json`. + Then, spawn exactly one review subagent per worktree directory, all in a + single message so they run concurrently. Do NOT batch or group multiple + commits into a single agent. Do NOT read any other files before spawning — + the subagents will do that themselves. + + Each worktree at `worktrees//` contains the full source tree checked + out at that commit, plus `commit.patch` (the diff) and `commit-message.txt` + (the commit message). Each reviewer reviews design, code quality, style, potential bugs, and security implications. + Each subagent must be spawned with `model: "opus"`. + Each subagent prompt must include: - Instructions to read `pr-context.json` in the repository root for additional context. - - Instructions to read `review-schema.json` in the repository root and - return a JSON array matching the `comments` items schema from that file. + - The contents of `review-schema.json` (paste it into each prompt so the + agent doesn't have to read it separately). - The worktree path. - Instructions to read `commit-message.txt` and `commit.patch` in the worktree for the commit message and diff. - Instructions to verify every `line` and `start_line` value against the hunk ranges in `commit.patch` before returning. + - Instructions to return ONLY a raw JSON array of findings. No markdown, + no explanation, no code fences — just the JSON array. If there are no + findings, return `[]`. ## Phase 2: Collect, deduplicate, and summarize @@ -290,26 +297,20 @@ jobs: populate the `resolve` array. - If `tracking_comment` is non-null, use it as the basis for your summary. + Trust the subagent findings — do NOT re-verify them by running your own + bash, grep, sed, or awk commands against the source code. Phase 2 should + only read `pr-context.json` and then produce the structured output. + Then: - 1. Collect all issues. Merge duplicates (same file, lines within 3 of each other, same problem). - 2. Drop low-confidence findings. - 3. Check the existing inline review comments from `pr-context.json`. Do NOT - include a comment if one already exists on the same file about the same - problem, even if the exact line numbers differ (lines shift between - revisions). Also check for author replies that dismiss or reject a previous - comment — do NOT re-raise an issue the PR author has already responded to - disagreeing with. - Populate the `resolve` array with the REST API `id` (integer) of your own - review comment threads that should be resolved (user.login == "github-actions[bot]" - and body starts with "Claude: "). Do not resolve threads from human reviewers. - A thread should be resolved if: - - The issue it raised has been addressed in the current PR (i.e. your review - no longer flags it), or - - The PR author (or another reviewer) left a reply disagreeing with or - dismissing the comment. - Only include the `id` of the **first** comment in each thread (the one that - started the conversation). Do not resolve threads for issues that are still - present and unaddressed. + 1. Collect all issues. Merge duplicates across agents (same file, same + problem, lines within 3 of each other). + 2. Drop issues that already have a review comment on the same file about + the same problem, or where the PR author replied disagreeing. + 3. Populate the `resolve` array with the `id` of your own review comment + threads (user.login == "github-actions[bot]", body starts with + "Claude: ") that should be resolved — either because the issue was + fixed or because the author dismissed it. Use the first comment `id` + in each thread. Do not resolve threads from human reviewers. 4. Write a `summary` field in markdown for a top-level tracking comment. **If no existing tracking comment was found (first run):** @@ -486,7 +487,7 @@ jobs: ...(c.side != null && { side: c.side }), ...(c.start_line != null && { start_line: c.start_line }), ...(c.start_side != null && { start_side: c.start_side }), - body: `Claude: **${c.severity}**: ${c.body}`, + body: c.body, }); posted++; } catch (e) { From dd80e5a348bdb8185e040f66ede00fd4ffdee777 Mon Sep 17 00:00:00 2001 From: Milan Kyselica Date: Thu, 9 Apr 2026 19:43:14 +0200 Subject: [PATCH 0876/1296] resolved: replace assert() with error return in DNSSEC verify functions dnssec_rsa_verify_raw() asserts that RSA_size(key) matches the RRSIG signature size, and dnssec_ecdsa_verify_raw() asserts that EC_KEY_check_key() succeeds. Both conditions depend on parsed DNS record content. Replace with proper error returns. The actual crypto verify calls (EVP_PKEY_verify / ECDSA_do_verify) handle mismatches fine on their own, so the asserts were also redundant. While at it, fix the misleading "EC_POINT_bn2point failed" log message that actually refers to an EC_KEY_set_public_key() failure. Fixes: https://github.com/systemd/systemd/issues/41569 --- src/resolve/resolved-dns-dnssec.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index c82569ccf9f19..ff4df7b78ad40 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -100,7 +100,8 @@ static int dnssec_rsa_verify_raw( return -EIO; e = m = NULL; - assert((size_t) RSA_size(rpubkey) == signature_size); + if ((size_t) RSA_size(rpubkey) != signature_size) + return -EINVAL; epubkey = EVP_PKEY_new(); if (!epubkey) @@ -230,9 +231,11 @@ static int dnssec_ecdsa_verify_raw( if (EC_KEY_set_public_key(eckey, p) <= 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), - "EC_POINT_bn2point failed: 0x%lx", ERR_get_error()); + "EC_KEY_set_public_key failed: 0x%lx", ERR_get_error()); - assert(EC_KEY_check_key(eckey) == 1); + if (EC_KEY_check_key(eckey) != 1) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "EC_KEY_check_key failed: 0x%lx", ERR_get_error()); r = BN_bin2bn(signature_r, signature_r_size, NULL); if (!r) From 92d87ac3029a5fc6b7c94a4196e0d2b4db2cef3a Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Fri, 10 Apr 2026 10:52:21 +0200 Subject: [PATCH 0877/1296] sd-bus: don't overallocate the message buffer newa(t, n) already allocates sizeof(t) * n bytes, so previously we'd actually allocate sizeof(t) * sizeof(t) * n bytes, which is ~16x more (on x86_64) that we actually needed. This is probably an oversight from a tree-wide change in 6e9417f5b4f29938fab1eee2b5edf596cc580452 that replaced alloca() with newa(). Follow-up for 6e9417f5b4f29938fab1eee2b5edf596cc580452. --- src/libsystemd/sd-bus/bus-socket.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/libsystemd/sd-bus/bus-socket.c b/src/libsystemd/sd-bus/bus-socket.c index 3c5f596440ec9..638d650336b11 100644 --- a/src/libsystemd/sd-bus/bus-socket.c +++ b/src/libsystemd/sd-bus/bus-socket.c @@ -1238,7 +1238,6 @@ int bus_socket_take_fd(sd_bus *b) { int bus_socket_write_message(sd_bus *bus, sd_bus_message *m, size_t *idx) { struct iovec *iov; ssize_t k; - size_t n; unsigned j; int r; @@ -1254,9 +1253,8 @@ int bus_socket_write_message(sd_bus *bus, sd_bus_message *m, size_t *idx) { if (r < 0) return r; - n = m->n_iovec * sizeof(struct iovec); - iov = newa(struct iovec, n); - memcpy_safe(iov, m->iovec, n); + iov = newa(struct iovec, m->n_iovec); + memcpy_safe(iov, m->iovec, sizeof(struct iovec) * m->n_iovec); j = 0; iovec_advance(iov, &j, *idx); From 8355eb6e1109b91654363fae01b903735895c295 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 10 Apr 2026 08:14:53 +0200 Subject: [PATCH 0878/1296] meson: Check if files returned by git ls-files actually exist Otherwise you run into errors such as: """ ../meson.build:2899:28: ERROR: File src/test/test-loop-util.c does not exist. """ when deleting a file in git without staging the deletion. --- meson.build | 8 +++++++- test/fuzz/meson.build | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index 5e01f22d5b411..1c296073c2996 100644 --- a/meson.build +++ b/meson.build @@ -2896,7 +2896,13 @@ if git.found() 'ls-files', ':/*.[ch]', ':/*.cc', check : false) if all_files.returncode() == 0 - all_files = files(all_files.stdout().split()) + existing_files = [] + foreach f : all_files.stdout().split() + if fs.exists(f) + existing_files += f + endif + endforeach + all_files = files(existing_files) custom_target( output : 'tags', diff --git a/test/fuzz/meson.build b/test/fuzz/meson.build index 6f9f43a4105f9..d19fda3a02eaa 100644 --- a/test/fuzz/meson.build +++ b/test/fuzz/meson.build @@ -51,7 +51,7 @@ foreach p : out.stdout().split() # # Also, backslashes get mangled, so skip test. See # https://github.com/mesonbuild/meson/issues/1564. - if p.contains('\\') + if p.contains('\\') or not fs.exists(p) continue endif fuzzer = fs.name(fs.parent(p)) From ff6b70fe7e2a1c3f0c88697bfa9ad625cd4c5013 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 10 Apr 2026 14:46:17 +0200 Subject: [PATCH 0879/1296] Update TODO --- TODO | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/TODO b/TODO index aca06755c0614..37cd671d7eaa6 100644 --- a/TODO +++ b/TODO @@ -122,11 +122,12 @@ Features: * sysext: make systemd-{sys,conf}ext-sysroot.service work in the split '/var' configuration. -* sd-varlink: add fully async modes of the protocol upgrade stuff +* introduce a concept of /etc/machine-info "TAGS=" field that allows tagging + machines with zero, one or more roles, states or other forms of + categorization. Then, add a way of using this in sysupdate to automatically + enable certain transfers, one for each role. -* sd-varlink: optimize the read-byte-by-byte mode in case upgrade mode is - enabled, via recvmsg() with MSG_SEEK: first read non-destrictively, look for - NUL byte, and only then flush out +* sd-varlink: add fully async modes of the protocol upgrade stuff * repart: maybe remove iso9660/eltorito superblock from disk when booting via gpt, if there is one. From 3649c6b3d9c3e9f52e74bc220d188f455a168112 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Thu, 5 Mar 2026 02:30:39 -0800 Subject: [PATCH 0880/1296] test: extract varlink IDL test helpers into shared header Move the TEST_IDL_ENUM_TO_STRING, TEST_IDL_ENUM_FROM_STRING, and TEST_IDL_ENUM macros along with test_enum_to_string_name() from test-varlink-idl.c into test-varlink-idl-util.h so they can be reused by other test files. --- src/test/test-varlink-idl-util.h | 55 ++++++++++++++++++++++++++++++++ src/test/test-varlink-idl.c | 50 +---------------------------- 2 files changed, 56 insertions(+), 49 deletions(-) create mode 100644 src/test/test-varlink-idl-util.h diff --git a/src/test/test-varlink-idl-util.h b/src/test/test-varlink-idl-util.h new file mode 100644 index 0000000000000..7a27230d89963 --- /dev/null +++ b/src/test/test-varlink-idl-util.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-varlink-idl.h" + +#include "json-util.h" +#include "string-util.h" + +static inline void test_enum_to_string_name(const char *n, const sd_varlink_symbol *symbol) { + assert(n); + assert(symbol); + + assert(symbol->symbol_type == SD_VARLINK_ENUM_TYPE); + _cleanup_free_ char *m = ASSERT_PTR(json_underscorify(strdup(n))); + + bool found = false; + for (const sd_varlink_field *f = symbol->fields; f->name; f++) { + if (f->field_type == _SD_VARLINK_FIELD_COMMENT) + continue; + + assert(f->field_type == SD_VARLINK_ENUM_VALUE); + if (streq(m, f->name)) { + found = true; + break; + } + } + + log_debug("'%s' found in '%s': %s", m, strna(symbol->name), yes_no(found)); + assert(found); +} + +#define TEST_IDL_ENUM_TO_STRING(type, ename, symbol) \ + for (type t = 0;; t++) { \ + const char *n = ename##_to_string(t); \ + if (!n) \ + break; \ + test_enum_to_string_name(n, &(symbol)); \ + } + +#define TEST_IDL_ENUM_FROM_STRING(type, ename, symbol) \ + for (const sd_varlink_field *f = (symbol).fields; f->name; f++) { \ + if (f->field_type == _SD_VARLINK_FIELD_COMMENT) \ + continue; \ + assert(f->field_type == SD_VARLINK_ENUM_VALUE); \ + _cleanup_free_ char *m = ASSERT_PTR(json_dashify(strdup(f->name))); \ + type t = ename##_from_string(m); \ + log_debug("'%s' of '%s' translates: %s", f->name, strna((symbol).name), yes_no(t >= 0)); \ + assert(t >= 0); \ + } + +#define TEST_IDL_ENUM(type, name, symbol) \ + do { \ + TEST_IDL_ENUM_TO_STRING(type, name, symbol); \ + TEST_IDL_ENUM_FROM_STRING(type, name, symbol); \ + } while (false) diff --git a/src/test/test-varlink-idl.c b/src/test/test-varlink-idl.c index 4b1e79c03cffd..d0f3b914471d2 100644 --- a/src/test/test-varlink-idl.c +++ b/src/test/test-varlink-idl.c @@ -11,11 +11,11 @@ #include "discover-image.h" #include "fd-util.h" #include "gpt.h" -#include "json-util.h" #include "network-util.h" #include "pretty-print.h" #include "resolve-util.h" #include "tests.h" +#include "test-varlink-idl-util.h" #include "varlink-idl-util.h" #include "varlink-io.systemd.h" #include "varlink-io.systemd.AskPassword.h" @@ -481,54 +481,6 @@ TEST(validate_method_call) { assert_se(pthread_join(t, NULL) == 0); } -static void test_enum_to_string_name(const char *n, const sd_varlink_symbol *symbol) { - assert(n); - assert(symbol); - - assert(symbol->symbol_type == SD_VARLINK_ENUM_TYPE); - _cleanup_free_ char *m = ASSERT_PTR(json_underscorify(strdup(n))); - - bool found = false; - for (const sd_varlink_field *f = symbol->fields; f->name; f++) { - if (f->field_type == _SD_VARLINK_FIELD_COMMENT) - continue; - - assert(f->field_type == SD_VARLINK_ENUM_VALUE); - if (streq(m, f->name)) { - found = true; - break; - } - } - - log_debug("'%s' found in '%s': %s", m, strna(symbol->name), yes_no(found)); - assert(found); -} - -#define TEST_IDL_ENUM_TO_STRING(type, ename, symbol) \ - for (type t = 0;; t++) { \ - const char *n = ename##_to_string(t); \ - if (!n) \ - break; \ - test_enum_to_string_name(n, &(symbol)); \ - } - -#define TEST_IDL_ENUM_FROM_STRING(type, ename, symbol) \ - for (const sd_varlink_field *f = (symbol).fields; f->name; f++) { \ - if (f->field_type == _SD_VARLINK_FIELD_COMMENT) \ - continue; \ - assert(f->field_type == SD_VARLINK_ENUM_VALUE); \ - _cleanup_free_ char *m = ASSERT_PTR(json_dashify(strdup(f->name))); \ - type t = ename##_from_string(m); \ - log_debug("'%s' of '%s' translates: %s", f->name, strna((symbol).name), yes_no(t >= 0)); \ - assert(t >= 0); \ - } - -#define TEST_IDL_ENUM(type, name, symbol) \ - do { \ - TEST_IDL_ENUM_TO_STRING(type, name, symbol); \ - TEST_IDL_ENUM_FROM_STRING(type, name, symbol); \ - } while (false) - TEST(enums_idl) { TEST_IDL_ENUM(BootEntryType, boot_entry_type, vl_type_BootEntryType); TEST_IDL_ENUM_TO_STRING(BootEntrySource, boot_entry_source, vl_type_BootEntrySource); From 74573799ca6d11b6f945bd900b2cb5f818b4a976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 8 Apr 2026 17:17:32 +0200 Subject: [PATCH 0881/1296] Add DEFINE_ARRAY_DONE_FUNC This is a helper macro that defines a function to drop elements of an array but not the array itself. I used the "_many" suffix because it most closely matches what happens here: we are calling the cleanup function a bunch of times. --- src/fundamental/cleanup-fundamental.h | 8 ++++++++ src/nspawn/nspawn.c | 7 +------ src/shared/format-table.h | 1 + src/vmspawn/vmspawn.c | 7 +------ 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/fundamental/cleanup-fundamental.h b/src/fundamental/cleanup-fundamental.h index 86b9851fd54ab..91d7cdd0e8986 100644 --- a/src/fundamental/cleanup-fundamental.h +++ b/src/fundamental/cleanup-fundamental.h @@ -45,6 +45,14 @@ #define DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_MACRO(type, macro, empty) \ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_MACRO_RENAME(type, macro, macro##p, empty) +/* Clean up a NULL-terminated array by dropping all the items in it (up to the first NULL). + * The array itself is not deallocated. */ +#define DEFINE_ARRAY_DONE_FUNC(type, helper) \ + void helper ## _many(type (*p)[]) { \ + for (type *t = *ASSERT_PTR(p); *t; t++) \ + *t = helper(*t); \ + } + typedef void (*free_array_func_t)(void *p, size_t n); /* An automatic _cleanup_-like logic for destroy arrays (i.e. pointers + size) when leaving scope */ diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 10fda88c48054..accf448ea97f2 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -364,11 +364,6 @@ static int parse_private_users( return 0; } -static void unref_many_tables(Table* (*tablesp)[]) { - for (Table **t = *ASSERT_PTR(tablesp); *t; t++) - *t = table_unref(*t); -} - static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -396,7 +391,7 @@ static int help(void) { "Other", }; - _cleanup_(unref_many_tables) Table* tables[ELEMENTSOF(groups) + 1] = {}; + _cleanup_(table_unref_many) Table* tables[ELEMENTSOF(groups) + 1] = {}; for (size_t i = 0; i < ELEMENTSOF(groups); i++) { r = option_parser_get_help_table_group(groups[i], &tables[i]); diff --git a/src/shared/format-table.h b/src/shared/format-table.h index 0f52f80293d2e..7665c93e593e6 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -100,6 +100,7 @@ Table* table_new_vertical(void); Table* table_unref(Table *t); DEFINE_TRIVIAL_CLEANUP_FUNC(Table*, table_unref); +static inline DEFINE_ARRAY_DONE_FUNC(Table*, table_unref); int table_add_cell_full(Table *t, TableCell **ret_cell, TableDataType dt, const void *data, size_t minimum_width, size_t maximum_width, unsigned weight, unsigned align_percent, unsigned ellipsize_percent); static inline int table_add_cell(Table *t, TableCell **ret_cell, TableDataType dt, const void *data) { diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index f5dc4f17a8ce3..5064f09c7d1a3 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -203,11 +203,6 @@ STATIC_DESTRUCTOR_REGISTER(arg_bind_user, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user_shell, freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user_groups, strv_freep); -static void unref_many_tables(Table* (*tablesp)[]) { - for (Table **t = *ASSERT_PTR(tablesp); *t; t++) - *t = table_unref(*t); -} - static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -232,7 +227,7 @@ static int help(void) { "Credentials", }; - _cleanup_(unref_many_tables) Table* tables[ELEMENTSOF(groups) + 1] = {}; + _cleanup_(table_unref_many) Table* tables[ELEMENTSOF(groups) + 1] = {}; for (size_t i = 0; i < ELEMENTSOF(groups); i++) { r = option_parser_get_help_table_group(groups[i], &tables[i]); From 3ed9e7cd23185780b41a57bdc1e0e4950cb7dee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 8 Apr 2026 17:37:41 +0200 Subject: [PATCH 0882/1296] firewall-util: use DEFINE_ARRAY_DONE_FUNC for netlink message cleanup Replace the open-coded netlink_message_unref_many() function and its DEFINE_TRIVIAL_CLEANUP_FUNC wrapper with DEFINE_ARRAY_DONE_FUNC. Co-developed-by: Claude Opus 4.6 --- src/shared/firewall-util.c | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/src/shared/firewall-util.c b/src/shared/firewall-util.c index 93eef4eecf598..651870e369889 100644 --- a/src/shared/firewall-util.c +++ b/src/shared/firewall-util.c @@ -50,19 +50,7 @@ static const char* dnat_map_name(void) { return cached; } -static sd_netlink_message** netlink_message_unref_many(sd_netlink_message **m) { - if (!m) - return NULL; - - /* This does not free array. The end of the array must be NULL. */ - - for (sd_netlink_message **p = m; *p; p++) - *p = sd_netlink_message_unref(*p); - - return m; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(sd_netlink_message**, netlink_message_unref_many); +static DEFINE_ARRAY_DONE_FUNC(sd_netlink_message*, sd_netlink_message_unref); static int nfnl_open_expr_container(sd_netlink_message *m, const char *name) { int r; @@ -736,8 +724,7 @@ static uint32_t concat_types2(enum nft_key_types a, enum nft_key_types b) { } static int fw_nftables_init_family(sd_netlink *nfnl, int family) { - sd_netlink_message *messages[10] = {}; - _unused_ _cleanup_(netlink_message_unref_manyp) sd_netlink_message **unref = messages; + _cleanup_(sd_netlink_message_unref_many) sd_netlink_message *messages[10] = {}; size_t msgcnt = 0, ip_type_size; uint32_t set_id = 0; int ip_type, r; @@ -1058,8 +1045,7 @@ static int fw_nftables_add_local_dnat_internal( uint16_t remote_port, const union in_addr_union *previous_remote) { - sd_netlink_message *messages[3] = {}; - _unused_ _cleanup_(netlink_message_unref_manyp) sd_netlink_message **unref = messages; + _cleanup_(sd_netlink_message_unref_many) sd_netlink_message *messages[3] = {}; uint32_t data[5], key[2], dlen; size_t msgcnt = 0; int r; From bbc6af9ce3e2fe1c18c88fab3b6d1218ad389cac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 8 Apr 2026 17:56:09 +0200 Subject: [PATCH 0883/1296] Add DEFINE_POINTER_ARRAY_FREE_FUNC and conf_file_free_array As mentioned in the grandfather commit, I want to use the _many suffix for freeing of the contents of an array, so the functions to free the array to get the suffix _array. --- src/basic/conf-files.c | 11 +++-------- src/basic/conf-files.h | 2 +- src/fundamental/cleanup-fundamental.h | 11 +++++++++++ src/libsystemd/sd-journal/catalog.c | 2 +- src/modules-load/modules-load.c | 2 +- src/resolve/resolved-static-records.c | 2 +- src/shared/hwdb-util.c | 2 +- src/shared/pretty-print.c | 2 +- src/sysupdate/sysupdate.c | 6 +++--- src/udev/udev-rules.c | 2 +- src/udev/udevadm-cat.c | 2 +- src/udev/udevadm-util.c | 6 +++--- src/udev/udevadm-verify.c | 2 +- 13 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/basic/conf-files.c b/src/basic/conf-files.c index cd91c5bc1ab54..4db486d8ada7e 100644 --- a/src/basic/conf-files.c +++ b/src/basic/conf-files.c @@ -33,12 +33,7 @@ ConfFile* conf_file_free(ConfFile *c) { return mfree(c); } -void conf_file_free_many(ConfFile **array, size_t n) { - FOREACH_ARRAY(i, array, n) - conf_file_free(*i); - - free(array); -} +DEFINE_POINTER_ARRAY_FREE_FUNC(ConfFile*, conf_file_free); static int conf_files_log_level(ConfFilesFlags flags) { return FLAGS_SET(flags, CONF_FILES_WARN) ? LOG_WARNING : LOG_DEBUG; @@ -485,7 +480,7 @@ static int dump_files(Hashmap *fh, const char *root, ConfFilesFlags flags, ConfF size_t n_files = 0; int r; - CLEANUP_ARRAY(files, n_files, conf_file_free_many); + CLEANUP_ARRAY(files, n_files, conf_file_free_array); assert(ret_files); assert(ret_n_files); @@ -528,7 +523,7 @@ static int copy_and_sort_files_from_hashmap( int log_level = conf_files_log_level(flags); /* The entries in the array given by hashmap_dump_sorted() are still owned by the hashmap. - * Hence, do not use conf_file_free_many() for 'entries' */ + * Hence, do not use conf_file_free_array() for 'entries' */ r = hashmap_dump_sorted(fh, (void***) &files, &n_files); if (r < 0) return log_oom_full(log_level); diff --git a/src/basic/conf-files.h b/src/basic/conf-files.h index 031463172abbf..e6c248a59a1e8 100644 --- a/src/basic/conf-files.h +++ b/src/basic/conf-files.h @@ -29,7 +29,7 @@ typedef struct ConfFile { ConfFile* conf_file_free(ConfFile *c); DEFINE_TRIVIAL_CLEANUP_FUNC(ConfFile*, conf_file_free); -void conf_file_free_many(ConfFile **array, size_t n); +void conf_file_free_array(ConfFile **array, size_t n); int conf_file_new_at(const char *path, const char *root, int rfd, ConfFilesFlags flags, ConfFile **ret); int conf_file_new(const char *path, const char *root, ConfFilesFlags flags, ConfFile **ret); diff --git a/src/fundamental/cleanup-fundamental.h b/src/fundamental/cleanup-fundamental.h index 91d7cdd0e8986..b4f23a592044d 100644 --- a/src/fundamental/cleanup-fundamental.h +++ b/src/fundamental/cleanup-fundamental.h @@ -53,6 +53,17 @@ *t = helper(*t); \ } +/* Clean up an array of pointers to objects by dropping all the items in it. + * The size of the array is passed in as a parameter, so NULL items may appear in the middle of the array. + * Free the array itself afterwards. */ +#define DEFINE_POINTER_ARRAY_FREE_FUNC(type, helper) \ + void helper ## _array(type *array, size_t n) { \ + assert(array || n == 0); \ + FOREACH_ARRAY(item, array, n) \ + helper(*item); \ + free(array); \ + } + typedef void (*free_array_func_t)(void *p, size_t n); /* An automatic _cleanup_-like logic for destroy arrays (i.e. pointers + size) when leaving scope */ diff --git a/src/libsystemd/sd-journal/catalog.c b/src/libsystemd/sd-journal/catalog.c index a44ffe5585b7e..5d91b6a0e5344 100644 --- a/src/libsystemd/sd-journal/catalog.c +++ b/src/libsystemd/sd-journal/catalog.c @@ -451,7 +451,7 @@ int catalog_update(const char *database, const char *root, const char* const *di ConfFile **files = NULL; size_t n_files = 0; - CLEANUP_ARRAY(files, n_files, conf_file_free_many); + CLEANUP_ARRAY(files, n_files, conf_file_free_array); r = conf_files_list_strv_full(".catalog", root, CONF_FILES_REGULAR | CONF_FILES_FILTER_MASKED | CONF_FILES_WARN, diff --git a/src/modules-load/modules-load.c b/src/modules-load/modules-load.c index f644c9f0f0e27..e8aeb67f1fbcc 100644 --- a/src/modules-load/modules-load.c +++ b/src/modules-load/modules-load.c @@ -417,7 +417,7 @@ static int run(int argc, char *argv[]) { ConfFile **files = NULL; size_t n_files = 0; - CLEANUP_ARRAY(files, n_files, conf_file_free_many); + CLEANUP_ARRAY(files, n_files, conf_file_free_array); STRV_FOREACH(i, arg_proc_cmdline_modules) RET_GATHER(ret, modules_list_append_dup(&module_set, *i)); diff --git a/src/resolve/resolved-static-records.c b/src/resolve/resolved-static-records.c index d905d507d6ab3..d4d284d4f7a2a 100644 --- a/src/resolve/resolved-static-records.c +++ b/src/resolve/resolved-static-records.c @@ -145,7 +145,7 @@ static int manager_static_records_read(Manager *m) { ConfFile **files = NULL; size_t n_files = 0; - CLEANUP_ARRAY(files, n_files, conf_file_free_many); + CLEANUP_ARRAY(files, n_files, conf_file_free_array); r = conf_files_list_nulstr_full( ".rr", diff --git a/src/shared/hwdb-util.c b/src/shared/hwdb-util.c index 29942254f37d7..55579c2cf4553 100644 --- a/src/shared/hwdb-util.c +++ b/src/shared/hwdb-util.c @@ -608,7 +608,7 @@ int hwdb_update(const char *root, const char *hwdb_bin_dir, bool strict, bool co ConfFile **files = NULL; size_t n_files = 0; - CLEANUP_ARRAY(files, n_files, conf_file_free_many); + CLEANUP_ARRAY(files, n_files, conf_file_free_array); r = conf_files_list_strv_full(".hwdb", root, CONF_FILES_REGULAR | CONF_FILES_FILTER_MASKED | CONF_FILES_WARN, diff --git a/src/shared/pretty-print.c b/src/shared/pretty-print.c index 68996180513e6..76330cbb65d16 100644 --- a/src/shared/pretty-print.c +++ b/src/shared/pretty-print.c @@ -486,7 +486,7 @@ int conf_files_cat(const char *root, const char *name, CatFlags flags) { /* Then locate the drop-ins, if any */ ConfFile **dropins = NULL; size_t n_dropins = 0; - CLEANUP_ARRAY(dropins, n_dropins, conf_file_free_many); + CLEANUP_ARRAY(dropins, n_dropins, conf_file_free_array); r = conf_files_list_strv_full(extension, root, CONF_FILES_REGULAR | CONF_FILES_FILTER_MASKED | CONF_FILES_WARN, (const char* const*) dirs, &dropins, &n_dropins); if (r < 0) return log_error_errno(r, "Failed to query file list: %m"); diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 57b2125f92c9e..77cbb3a238957 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -128,7 +128,7 @@ static int read_definitions( size_t n_files = 0, n_transfers = 0, n_disabled = 0; int r; - CLEANUP_ARRAY(files, n_files, conf_file_free_many); + CLEANUP_ARRAY(files, n_files, conf_file_free_array); CLEANUP_ARRAY(transfers, n_transfers, free_transfers); CLEANUP_ARRAY(disabled, n_disabled, free_transfers); @@ -208,7 +208,7 @@ static int context_read_definitions(Context *c, const char* node, bool requires_ ConfFile **files = NULL; size_t n_files = 0; - CLEANUP_ARRAY(files, n_files, conf_file_free_many); + CLEANUP_ARRAY(files, n_files, conf_file_free_array); r = conf_files_list_strv_full(".feature", arg_root, CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED|CONF_FILES_WARN, @@ -1718,7 +1718,7 @@ static int verb_components(int argc, char *argv[], uintptr_t _data, void *userda ConfFile **directories = NULL; size_t n_directories = 0; - CLEANUP_ARRAY(directories, n_directories, conf_file_free_many); + CLEANUP_ARRAY(directories, n_directories, conf_file_free_array); r = conf_files_list_strv_full(".d", arg_root, CONF_FILES_DIRECTORY|CONF_FILES_WARN, (const char * const *) CONF_PATHS_STRV(""), &directories, &n_directories); diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c index 691230d7535ce..70238b9b54673 100644 --- a/src/udev/udev-rules.c +++ b/src/udev/udev-rules.c @@ -1841,7 +1841,7 @@ int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing ConfFile **files = NULL; size_t n_files = 0; - CLEANUP_ARRAY(files, n_files, conf_file_free_many); + CLEANUP_ARRAY(files, n_files, conf_file_free_array); r = conf_files_list_strv_full(".rules", /* root= */ NULL, CONF_FILES_REGULAR | CONF_FILES_FILTER_MASKED, (const char* const*) directories, &files, &n_files); diff --git a/src/udev/udevadm-cat.c b/src/udev/udevadm-cat.c index d6c488ff3fab4..9d94f5a86c652 100644 --- a/src/udev/udevadm-cat.c +++ b/src/udev/udevadm-cat.c @@ -105,7 +105,7 @@ int verb_cat_main(int argc, char *argv[], uintptr_t _data, void *userdata) { ConfFile **files = NULL; size_t n_files = 0; - CLEANUP_ARRAY(files, n_files, conf_file_free_many); + CLEANUP_ARRAY(files, n_files, conf_file_free_array); r = search_rules_files(strv_skip(argv, optind), arg_root, &files, &n_files); if (r < 0) diff --git a/src/udev/udevadm-util.c b/src/udev/udevadm-util.c index 14b508007f507..c30af47ff7c73 100644 --- a/src/udev/udevadm-util.c +++ b/src/udev/udevadm-util.c @@ -302,7 +302,7 @@ static int search_rules_file(const char *s, const char *root, ConfFile ***files, ConfFile **f = NULL; size_t n = 0; - CLEANUP_ARRAY(f, n, conf_file_free_many); + CLEANUP_ARRAY(f, n, conf_file_free_array); r = conf_files_list_strv_full(".rules", root, CONF_FILES_REGULAR | CONF_FILES_WARN, (const char* const*) STRV_MAKE_CONST(s), &f, &n); if (r < 0) @@ -311,7 +311,7 @@ static int search_rules_file(const char *s, const char *root, ConfFile ***files, if (!GREEDY_REALLOC_APPEND(*files, *n_files, f, n)) return log_oom(); - f = mfree(f); /* The array elements are owned by 'files'. So, conf_file_free_many() must not be called. */ + f = mfree(f); /* The array elements are owned by 'files'. So, conf_file_free_array() must not be called. */ n = 0; return 0; } @@ -321,7 +321,7 @@ int search_rules_files(char * const *a, const char *root, ConfFile ***ret_files, size_t n_files = 0; int r; - CLEANUP_ARRAY(files, n_files, conf_file_free_many); + CLEANUP_ARRAY(files, n_files, conf_file_free_array); assert(ret_files); assert(ret_n_files); diff --git a/src/udev/udevadm-verify.c b/src/udev/udevadm-verify.c index 03fed60d7e63a..6af7f06ab05fe 100644 --- a/src/udev/udevadm-verify.c +++ b/src/udev/udevadm-verify.c @@ -171,7 +171,7 @@ int verb_verify_main(int argc, char *argv[], uintptr_t _data, void *userdata) { ConfFile **files = NULL; size_t n_files = 0; - CLEANUP_ARRAY(files, n_files, conf_file_free_many); + CLEANUP_ARRAY(files, n_files, conf_file_free_array); r = search_rules_files(strv_skip(argv, optind), arg_root, &files, &n_files); if (r < 0) From 81d2d0270bb38d5a4087ef4eebb771b37b70b75e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 8 Apr 2026 18:27:34 +0200 Subject: [PATCH 0884/1296] Add DEFINE_ARRAY_FREE_FUNC and mount_image_free_array This is similar to DEFINE_POINTER_ARRAY_FREE_FUNC, but one pointer chase less. The name of the outer and inner functions are specified separately. The inner function does not free, so it'll be generally something like 'foo_done', but the outer function does free, so it can be called 'foo_array_free'. --- src/core/dbus-execute.c | 8 ++++---- src/core/execute.c | 4 ++-- src/core/load-fragment.c | 4 ++-- src/core/namespace.c | 17 +++++++---------- src/core/namespace.h | 2 +- src/fundamental/cleanup-fundamental.h | 10 ++++++++++ 6 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 9e6077c7e1fea..9843836eaf0df 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -4055,7 +4055,7 @@ int bus_exec_context_set_transient_property( MountImage *mount_images = NULL; size_t n_mount_images = 0; - CLEANUP_ARRAY(mount_images, n_mount_images, mount_image_free_many); + CLEANUP_ARRAY(mount_images, n_mount_images, mount_image_free_array); r = sd_bus_message_enter_container(message, 'a', "(ssba(ss))"); if (r < 0) @@ -4127,7 +4127,7 @@ int bus_exec_context_set_transient_property( if (!UNIT_WRITE_FLAGS_NOOP(flags)) { if (n_mount_images == 0) { - mount_image_free_many(c->mount_images, c->n_mount_images); + mount_image_free_array(c->mount_images, c->n_mount_images); c->mount_images = NULL; c->n_mount_images = 0; @@ -4158,7 +4158,7 @@ int bus_exec_context_set_transient_property( MountImage *extension_images = NULL; size_t n_extension_images = 0; - CLEANUP_ARRAY(extension_images, n_extension_images, mount_image_free_many); + CLEANUP_ARRAY(extension_images, n_extension_images, mount_image_free_array); r = sd_bus_message_enter_container(message, 'a', "(sba(ss))"); if (r < 0) @@ -4220,7 +4220,7 @@ int bus_exec_context_set_transient_property( if (!UNIT_WRITE_FLAGS_NOOP(flags)) { if (n_extension_images == 0) { - mount_image_free_many(c->extension_images, c->n_extension_images); + mount_image_free_array(c->extension_images, c->n_extension_images); c->extension_images = NULL; c->n_extension_images = 0; diff --git a/src/core/execute.c b/src/core/execute.c index 3661b8c98f631..e6cb9e5cc864a 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -696,10 +696,10 @@ void exec_context_done(ExecContext *c) { bind_mount_free_many(c->bind_mounts, c->n_bind_mounts); c->bind_mounts = NULL; c->n_bind_mounts = 0; - mount_image_free_many(c->mount_images, c->n_mount_images); + mount_image_free_array(c->mount_images, c->n_mount_images); c->mount_images = NULL; c->n_mount_images = 0; - mount_image_free_many(c->extension_images, c->n_extension_images); + mount_image_free_array(c->extension_images, c->n_extension_images); c->extension_images = NULL; c->n_extension_images = 0; c->extension_directories = strv_free(c->extension_directories); diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 840804fcf8dde..98e63b6cc8d37 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -5235,7 +5235,7 @@ int config_parse_mount_images( if (isempty(rvalue)) { /* Empty assignment resets the list */ - mount_image_free_many(c->mount_images, c->n_mount_images); + mount_image_free_array(c->mount_images, c->n_mount_images); c->mount_images = NULL; c->n_mount_images = 0; return 0; @@ -5385,7 +5385,7 @@ int config_parse_extension_images( if (isempty(rvalue)) { /* Empty assignment resets the list */ - mount_image_free_many(c->extension_images, c->n_extension_images); + mount_image_free_array(c->extension_images, c->n_extension_images); c->extension_images = NULL; c->n_extension_images = 0; return 0; diff --git a/src/core/namespace.c b/src/core/namespace.c index 6e4ec80dc96d4..ff4592b8b90cd 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -3250,18 +3250,15 @@ int bind_mount_add(BindMount **b, size_t *n, const BindMount *item) { return 0; } -void mount_image_free_many(MountImage *m, size_t n) { - assert(m || n == 0); - - FOREACH_ARRAY(i, m, n) { - free(i->source); - free(i->destination); - mount_options_free_all(i->mount_options); - } - - free(m); +static void mount_image_done(MountImage *m) { + assert(m); + m->source = mfree(m->source); + m->destination = mfree(m->destination); + m->mount_options = mount_options_free_all(m->mount_options); } +DEFINE_ARRAY_FREE_FUNC(mount_image_free_array, MountImage, mount_image_done); + int mount_image_add(MountImage **m, size_t *n, const MountImage *item) { _cleanup_free_ char *s = NULL, *d = NULL; _cleanup_(mount_options_free_allp) MountOptions *o = NULL; diff --git a/src/core/namespace.h b/src/core/namespace.h index f5d792dd77144..d25859f17aa46 100644 --- a/src/core/namespace.h +++ b/src/core/namespace.h @@ -303,7 +303,7 @@ DECLARE_STRING_TABLE_LOOKUP(private_pids, PrivatePIDs); void bind_mount_free_many(BindMount *b, size_t n); int bind_mount_add(BindMount **b, size_t *n, const BindMount *item); -void mount_image_free_many(MountImage *m, size_t n); +void mount_image_free_array(MountImage *array, size_t n); int mount_image_add(MountImage **m, size_t *n, const MountImage *item); void temporary_filesystem_free_many(TemporaryFileSystem *t, size_t n); diff --git a/src/fundamental/cleanup-fundamental.h b/src/fundamental/cleanup-fundamental.h index b4f23a592044d..9094cff2331e0 100644 --- a/src/fundamental/cleanup-fundamental.h +++ b/src/fundamental/cleanup-fundamental.h @@ -64,6 +64,16 @@ free(array); \ } +/* Clean up an array of objects of known size by dropping all the items in it. + * Then free the array itself. */ +#define DEFINE_ARRAY_FREE_FUNC(name, type, helper) \ + void name(type *array, size_t n) { \ + assert(array || n == 0); \ + FOREACH_ARRAY(item, array, n) \ + helper(item); \ + free(array); \ + } + typedef void (*free_array_func_t)(void *p, size_t n); /* An automatic _cleanup_-like logic for destroy arrays (i.e. pointers + size) when leaving scope */ From 3b478198e906fa4fa12f8b8f38a5fe2fbb9893e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 8 Apr 2026 18:30:37 +0200 Subject: [PATCH 0885/1296] stub: use DEFINE_ARRAY_FREE_FUNC --- src/boot/stub.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/boot/stub.c b/src/boot/stub.c index 3c8318a2adfe4..e2a8569cb33d7 100644 --- a/src/boot/stub.c +++ b/src/boot/stub.c @@ -412,14 +412,7 @@ static void named_addon_done(NamedAddon *a) { iovec_done(&a->blob); } -static void named_addon_free_many(NamedAddon *a, size_t n) { - assert(a || n == 0); - - FOREACH_ARRAY(i, a, n) - named_addon_done(i); - - free(a); -} +static DEFINE_ARRAY_FREE_FUNC(named_addon_free_array, NamedAddon, named_addon_done); static void install_addon_devicetrees( struct devicetree_state *dt_state, @@ -1294,9 +1287,9 @@ static EFI_STATUS run(EFI_HANDLE image) { /* Now that we have the UKI sections loaded, also load global first and then local (per-UKI) * addons. The data is loaded at once, and then used later. */ - CLEANUP_ARRAY(dt_addons, n_dt_addons, named_addon_free_many); - CLEANUP_ARRAY(initrd_addons, n_initrd_addons, named_addon_free_many); - CLEANUP_ARRAY(ucode_addons, n_ucode_addons, named_addon_free_many); + CLEANUP_ARRAY(dt_addons, n_dt_addons, named_addon_free_array); + CLEANUP_ARRAY(initrd_addons, n_initrd_addons, named_addon_free_array); + CLEANUP_ARRAY(ucode_addons, n_ucode_addons, named_addon_free_array); load_all_addons(image, loaded_image, uname, &cmdline_addons, &dt_addons, &n_dt_addons, &initrd_addons, &n_initrd_addons, &ucode_addons, &n_ucode_addons); /* If we have any extra command line to add via PE addons, load them now and append, and measure the From 954243f2bc6c17832252c91c926bd1e37116cd46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 8 Apr 2026 22:34:34 +0200 Subject: [PATCH 0886/1296] libsystemd-network: use DEFINE_ARRAY_FREE_FUNC, rename cleanup func --- src/libsystemd-network/dns-resolver-internal.h | 2 +- src/libsystemd-network/network-internal.c | 2 +- src/libsystemd-network/sd-dhcp-lease.c | 6 +++--- src/libsystemd-network/sd-dhcp6-lease.c | 2 +- src/libsystemd-network/sd-dns-resolver.c | 9 +-------- 5 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/libsystemd-network/dns-resolver-internal.h b/src/libsystemd-network/dns-resolver-internal.h index c8221421d1e6d..c9b3103cdb911 100644 --- a/src/libsystemd-network/dns-resolver-internal.h +++ b/src/libsystemd-network/dns-resolver-internal.h @@ -34,4 +34,4 @@ int dns_resolvers_to_dot_strv(const sd_dns_resolver *resolvers, size_t n_resolve void sd_dns_resolver_done(sd_dns_resolver *res); -void dns_resolver_done_many(sd_dns_resolver *resolvers, size_t n); +void dns_resolver_free_array(sd_dns_resolver *array, size_t n); diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c index c8aa6b63e0861..c1eaa361afa0d 100644 --- a/src/libsystemd-network/network-internal.c +++ b/src/libsystemd-network/network-internal.c @@ -169,7 +169,7 @@ int deserialize_dnr(sd_dns_resolver **ret, const char *string) { sd_dns_resolver *dnr = NULL; size_t n = 0; - CLEANUP_ARRAY(dnr, n, dns_resolver_done_many); + CLEANUP_ARRAY(dnr, n, dns_resolver_free_array); int priority = 0; for (;;) { diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index 0623bc0e4bf4d..fa41d293da2f3 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -434,7 +434,7 @@ static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) { for (sd_dhcp_lease_server_type_t i = 0; i < _SD_DHCP_LEASE_SERVER_TYPE_MAX; i++) free(lease->servers[i].addr); - dns_resolver_done_many(lease->dnr, lease->n_dnr); + dns_resolver_free_array(lease->dnr, lease->n_dnr); free(lease->static_routes); free(lease->classless_routes); free(lease->vendor_specific); @@ -642,7 +642,7 @@ static int lease_parse_dnr(const uint8_t *option, size_t len, sd_dns_resolver ** int r; sd_dns_resolver *res_list = NULL; size_t n_resolvers = 0; - CLEANUP_ARRAY(res_list, n_resolvers, dns_resolver_done_many); + CLEANUP_ARRAY(res_list, n_resolvers, dns_resolver_free_array); assert(option || len == 0); assert(dnr); @@ -747,7 +747,7 @@ static int lease_parse_dnr(const uint8_t *option, size_t len, sd_dns_resolver ** typesafe_qsort(res_list, n_resolvers, dns_resolver_prio_compare); - dns_resolver_done_many(*dnr, *n_dnr); + dns_resolver_free_array(*dnr, *n_dnr); *dnr = TAKE_PTR(res_list); *n_dnr = n_resolvers; diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c index 835f6c2d65303..4e745bbc3b569 100644 --- a/src/libsystemd-network/sd-dhcp6-lease.c +++ b/src/libsystemd-network/sd-dhcp6-lease.c @@ -1076,7 +1076,7 @@ static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) { dhcp6_ia_free(lease->ia_na); dhcp6_ia_free(lease->ia_pd); free(lease->dns); - dns_resolver_done_many(lease->dnr, lease->n_dnr); + dns_resolver_free_array(lease->dnr, lease->n_dnr); free(lease->fqdn); free(lease->captive_portal); strv_free(lease->domains); diff --git a/src/libsystemd-network/sd-dns-resolver.c b/src/libsystemd-network/sd-dns-resolver.c index c8c6618c725f1..94941170aee6e 100644 --- a/src/libsystemd-network/sd-dns-resolver.c +++ b/src/libsystemd-network/sd-dns-resolver.c @@ -26,14 +26,7 @@ sd_dns_resolver *sd_dns_resolver_unref(sd_dns_resolver *res) { return mfree(res); } -void dns_resolver_done_many(sd_dns_resolver resolvers[], size_t n) { - assert(resolvers || n == 0); - - FOREACH_ARRAY(res, resolvers, n) - sd_dns_resolver_done(res); - - free(resolvers); -} +DEFINE_ARRAY_FREE_FUNC(dns_resolver_free_array, sd_dns_resolver, sd_dns_resolver_done); int dns_resolver_prio_compare(const sd_dns_resolver *a, const sd_dns_resolver *b) { return CMP(ASSERT_PTR(a)->priority, ASSERT_PTR(b)->priority); From b8514cff52d20283fced0fc302484443fa4a0b56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 8 Apr 2026 22:40:21 +0200 Subject: [PATCH 0887/1296] libsystemd-network: use DEFINE_POINTER_ARRAY_FREE_FUNC, rename cleanup function --- src/libsystemd-network/sd-dns-resolver.c | 4 ++-- src/network/networkd-state-file.c | 6 +++--- src/network/networkd-wwan.c | 2 +- src/shared/socket-netlink.c | 11 ++--------- src/shared/socket-netlink.h | 4 ++-- 5 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/libsystemd-network/sd-dns-resolver.c b/src/libsystemd-network/sd-dns-resolver.c index 94941170aee6e..605397cf97ed5 100644 --- a/src/libsystemd-network/sd-dns-resolver.c +++ b/src/libsystemd-network/sd-dns-resolver.c @@ -299,7 +299,7 @@ int dns_resolvers_to_dot_addrs(const sd_dns_resolver *resolvers, size_t n_resolv struct in_addr_full **addrs = NULL; size_t n = 0; - CLEANUP_ARRAY(addrs, n, in_addr_full_array_free); + CLEANUP_ARRAY(addrs, n, in_addr_full_free_array); FOREACH_ARRAY(res, resolvers, n_resolvers) { if (!FLAGS_SET(res->transports, SD_DNS_ALPN_DOT)) @@ -340,7 +340,7 @@ int dns_resolvers_to_dot_strv(const sd_dns_resolver *resolvers, size_t n_resolve struct in_addr_full **addrs = NULL; size_t n = 0; - CLEANUP_ARRAY(addrs, n, in_addr_full_array_free); + CLEANUP_ARRAY(addrs, n, in_addr_full_free_array); r = dns_resolvers_to_dot_addrs(resolvers, n_resolvers, &addrs, &n); if (r < 0) diff --git a/src/network/networkd-state-file.c b/src/network/networkd-state-file.c index b3b85f18f5c2e..7943314b06e3d 100644 --- a/src/network/networkd-state-file.c +++ b/src/network/networkd-state-file.c @@ -136,7 +136,7 @@ static int link_put_dns(Link *link, OrderedSet **s) { if (r >= 0) { struct in_addr_full **dot_servers; size_t n = 0; - CLEANUP_ARRAY(dot_servers, n, in_addr_full_array_free); + CLEANUP_ARRAY(dot_servers, n, in_addr_full_free_array); r = dns_resolvers_to_dot_addrs(resolvers, r, &dot_servers, &n); if (r < 0) @@ -165,7 +165,7 @@ static int link_put_dns(Link *link, OrderedSet **s) { if (r >= 0) { struct in_addr_full **dot_servers; size_t n = 0; - CLEANUP_ARRAY(dot_servers, n, in_addr_full_array_free); + CLEANUP_ARRAY(dot_servers, n, in_addr_full_free_array); r = dns_resolvers_to_dot_addrs(resolvers, r, &dot_servers, &n); if (r < 0) @@ -193,7 +193,7 @@ static int link_put_dns(Link *link, OrderedSet **s) { SET_FOREACH(a, link->ndisc_dnr) { struct in_addr_full **dot_servers = NULL; size_t n = 0; - CLEANUP_ARRAY(dot_servers, n, in_addr_full_array_free); + CLEANUP_ARRAY(dot_servers, n, in_addr_full_free_array); r = dns_resolvers_to_dot_addrs(&a->resolver, 1, &dot_servers, &n); if (r < 0) diff --git a/src/network/networkd-wwan.c b/src/network/networkd-wwan.c index d5094ce55c198..ddc5ca38b45db 100644 --- a/src/network/networkd-wwan.c +++ b/src/network/networkd-wwan.c @@ -37,7 +37,7 @@ Bearer* bearer_free(Bearer *b) { free(b->name); free(b->apn); - in_addr_full_array_free(b->dns, b->n_dns); + in_addr_full_free_array(b->dns, b->n_dns); return mfree(b); } diff --git a/src/shared/socket-netlink.c b/src/shared/socket-netlink.c index ca9c259b26294..1d369353b976b 100644 --- a/src/shared/socket-netlink.c +++ b/src/shared/socket-netlink.c @@ -350,7 +350,7 @@ int in_addr_port_ifindex_name_from_string_auto( return r; } -struct in_addr_full *in_addr_full_free(struct in_addr_full *a) { +struct in_addr_full* in_addr_full_free(struct in_addr_full *a) { if (!a) return NULL; @@ -359,14 +359,7 @@ struct in_addr_full *in_addr_full_free(struct in_addr_full *a) { return mfree(a); } -void in_addr_full_array_free(struct in_addr_full *addrs[], size_t n) { - assert(addrs || n == 0); - - FOREACH_ARRAY(a, addrs, n) - in_addr_full_freep(a); - - free(addrs); -} +DEFINE_POINTER_ARRAY_FREE_FUNC(struct in_addr_full*, in_addr_full_free); int in_addr_full_new( int family, diff --git a/src/shared/socket-netlink.h b/src/shared/socket-netlink.h index f371967c6908b..be15398ba545a 100644 --- a/src/shared/socket-netlink.h +++ b/src/shared/socket-netlink.h @@ -36,9 +36,9 @@ struct in_addr_full { char *cached_server_string; /* Should not be handled directly, but through in_addr_full_to_string(). */ }; -struct in_addr_full *in_addr_full_free(struct in_addr_full *a); +struct in_addr_full* in_addr_full_free(struct in_addr_full *a); DEFINE_TRIVIAL_CLEANUP_FUNC(struct in_addr_full*, in_addr_full_free); -void in_addr_full_array_free(struct in_addr_full *addrs[], size_t n); +void in_addr_full_free_array(struct in_addr_full **array, size_t n); int in_addr_full_new(int family, const union in_addr_union *a, uint16_t port, int ifindex, const char *server_name, struct in_addr_full **ret); int in_addr_full_new_from_string(const char *s, struct in_addr_full **ret); const char* in_addr_full_to_string(struct in_addr_full *a); From 5ceaab3d4fb527388da68448a7bda1e1101120b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 8 Apr 2026 22:49:41 +0200 Subject: [PATCH 0888/1296] nsresourced: use DEFINE_ARRAY_FREE_FUNC, make func static and rename --- src/nsresourced/userns-registry.c | 19 +++++-------------- src/nsresourced/userns-registry.h | 1 - 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/nsresourced/userns-registry.c b/src/nsresourced/userns-registry.c index 371a35086f424..50efcd1153a5d 100644 --- a/src/nsresourced/userns-registry.c +++ b/src/nsresourced/userns-registry.c @@ -65,14 +65,7 @@ void delegated_userns_info_done(DelegatedUserNamespaceInfo *info) { info->n_ancestor_userns = 0; } -void delegated_userns_info_done_many(DelegatedUserNamespaceInfo infos[], size_t n) { - assert(infos || n == 0); - - FOREACH_ARRAY(info, infos, n) - delegated_userns_info_done(info); - - free(infos); -} +static DEFINE_ARRAY_FREE_FUNC(delegated_userns_info_free_array, DelegatedUserNamespaceInfo, delegated_userns_info_done); UserNamespaceInfo* userns_info_new(void) { UserNamespaceInfo *info = new(UserNamespaceInfo, 1); @@ -97,7 +90,7 @@ UserNamespaceInfo *userns_info_free(UserNamespaceInfo *userns) { free(userns->cgroups); free(userns->name); - delegated_userns_info_done_many(userns->delegates, userns->n_delegates); + delegated_userns_info_free_array(userns->delegates, userns->n_delegates); strv_free(userns->netifs); @@ -154,12 +147,10 @@ static int dispatch_delegates_array(const char *name, sd_json_variant *variant, size_t n = 0; int r; - CLEANUP_ARRAY(delegates, n, delegated_userns_info_done_many); + CLEANUP_ARRAY(delegates, n, delegated_userns_info_free_array); if (sd_json_variant_is_null(variant)) { - delegated_userns_info_done_many(info->delegates, info->n_delegates); - info->delegates = NULL; - info->n_delegates = 0; + CLEANUP_ARRAY(info->delegates, info->n_delegates, delegated_userns_info_free_array); return 0; } @@ -199,7 +190,7 @@ static int dispatch_delegates_array(const char *name, sd_json_variant *variant, n++; } - delegated_userns_info_done_many(info->delegates, info->n_delegates); + delegated_userns_info_free_array(info->delegates, info->n_delegates); info->delegates = TAKE_PTR(delegates); info->n_delegates = n; diff --git a/src/nsresourced/userns-registry.h b/src/nsresourced/userns-registry.h index f08b238861ae4..77ff2d6d20760 100644 --- a/src/nsresourced/userns-registry.h +++ b/src/nsresourced/userns-registry.h @@ -24,7 +24,6 @@ typedef struct DelegatedUserNamespaceInfo { } void delegated_userns_info_done(DelegatedUserNamespaceInfo *info); -void delegated_userns_info_done_many(DelegatedUserNamespaceInfo infos[], size_t n); typedef struct UserNamespaceInfo { uid_t owner; From 90c61571002926bb68a552da24f547d7e6b68c5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 8 Apr 2026 22:56:07 +0200 Subject: [PATCH 0889/1296] sd-journal: use NormalCasing for struct --- src/libsystemd/sd-journal/journal-vacuum.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libsystemd/sd-journal/journal-vacuum.c b/src/libsystemd/sd-journal/journal-vacuum.c index fade159820d87..e88ba0e539656 100644 --- a/src/libsystemd/sd-journal/journal-vacuum.c +++ b/src/libsystemd/sd-journal/journal-vacuum.c @@ -21,7 +21,7 @@ #include "time-util.h" #include "xattr-util.h" -typedef struct vacuum_info { +typedef struct VacuumInfo { uint64_t usage; char *filename; @@ -30,9 +30,9 @@ typedef struct vacuum_info { sd_id128_t seqnum_id; uint64_t seqnum; bool have_seqnum; -} vacuum_info; +} VacuumInfo; -static int vacuum_info_compare(const vacuum_info *a, const vacuum_info *b) { +static int vacuum_info_compare(const VacuumInfo *a, const VacuumInfo *b) { int r; if (a->have_seqnum && b->have_seqnum && @@ -49,7 +49,7 @@ static int vacuum_info_compare(const vacuum_info *a, const vacuum_info *b) { return strcmp(a->filename, b->filename); } -static void vacuum_info_array_free(vacuum_info *list, size_t n) { +static void vacuum_info_array_free(VacuumInfo *list, size_t n) { if (!list) return; @@ -137,7 +137,7 @@ int journal_directory_vacuum( uint64_t sum = 0, freed = 0, n_active_files = 0; size_t n_list = 0, i; _cleanup_closedir_ DIR *d = NULL; - vacuum_info *list = NULL; + VacuumInfo *list = NULL; usec_t retention_limit = 0; int r; @@ -280,7 +280,7 @@ int journal_directory_vacuum( if (!GREEDY_REALLOC(list, n_list + 1)) return -ENOMEM; - list[n_list++] = (vacuum_info) { + list[n_list++] = (VacuumInfo) { .filename = TAKE_PTR(p), .usage = size, .seqnum = seqnum, From 7024c0c38c8ac9418e264db94b69264016ab3dc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 8 Apr 2026 23:03:47 +0200 Subject: [PATCH 0890/1296] shared/tar-util: use DEFINE_ARRAY_FREE_FUNC, rename funcs --- src/shared/tar-util.c | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/src/shared/tar-util.c b/src/shared/tar-util.c index 33395e96bcf21..823fb9ac2b951 100644 --- a/src/shared/tar-util.c +++ b/src/shared/tar-util.c @@ -69,14 +69,7 @@ static void xattr_done(XAttr *xa) { iovec_done(&xa->data); } -static void xattr_done_many(XAttr *xa, size_t n) { - assert(xa || n == 0); - - FOREACH_ARRAY(i, xa, n) - xattr_done(i); - - free(xa); -} +static DEFINE_ARRAY_FREE_FUNC(xattr_free_array, XAttr, xattr_done); static void open_inode_done(OpenInode *of) { assert(of); @@ -87,7 +80,7 @@ static void open_inode_done(OpenInode *of) { of->fd = safe_close(of->fd); of->path = mfree(of->path); } - xattr_done_many(of->xattr, of->n_xattr); + xattr_free_array(of->xattr, of->n_xattr); #if HAVE_ACL if (of->acl_access) sym_acl_free(of->acl_access); @@ -96,14 +89,7 @@ static void open_inode_done(OpenInode *of) { #endif } -static void open_inode_done_many(OpenInode *array, size_t n) { - assert(array || n == 0); - - FOREACH_ARRAY(i, array, n) - open_inode_done(i); - - free(array); -} +static DEFINE_ARRAY_FREE_FUNC(open_inode_free_array, OpenInode, open_inode_done); static int open_inode_apply_acl(OpenInode *of) { int r = 0; @@ -792,7 +778,7 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { return log_oom(); size_t n_open_inodes = 0; - CLEANUP_ARRAY(open_inodes, n_open_inodes, open_inode_done_many); + CLEANUP_ARRAY(open_inodes, n_open_inodes, open_inode_free_array); /* Fill in the root inode. (Note: we leave the .path field as NULL to mark it as root inode.) */ open_inodes[0] = (OpenInode) { @@ -913,7 +899,7 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { acl_t acl_access = NULL, acl_default = NULL; XAttr *xa = NULL; size_t n_xa = 0; - CLEANUP_ARRAY(xa, n_xa, xattr_done_many); + CLEANUP_ARRAY(xa, n_xa, xattr_free_array); if (isempty(rest)) { /* This is the final node in the path, create it */ From 2a23e43f707b308c1d74951eddb02f4ad4fca6da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 8 Apr 2026 23:04:19 +0200 Subject: [PATCH 0891/1296] sysupdate: use DEFINE_POINTER_ARRAY_FREE_FUNC, rename func --- src/sysupdate/sysupdate.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 77cbb3a238957..9c469fbe856c4 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -111,11 +111,7 @@ static Context* context_new(void) { return new0(Context, 1); } -static void free_transfers(Transfer **array, size_t n) { - FOREACH_ARRAY(t, array, n) - transfer_free(*t); - free(array); -} +static DEFINE_POINTER_ARRAY_FREE_FUNC(Transfer*, transfer_free); static int read_definitions( Context *c, @@ -129,8 +125,8 @@ static int read_definitions( int r; CLEANUP_ARRAY(files, n_files, conf_file_free_array); - CLEANUP_ARRAY(transfers, n_transfers, free_transfers); - CLEANUP_ARRAY(disabled, n_disabled, free_transfers); + CLEANUP_ARRAY(transfers, n_transfers, transfer_free_array); + CLEANUP_ARRAY(disabled, n_disabled, transfer_free_array); assert(c); assert(dirs); From 79f90f4ae0d09a2748870646cb924cb5c74704e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 8 Apr 2026 23:04:47 +0200 Subject: [PATCH 0892/1296] various: use DEFINE_ARRAY_FREE_FUNC --- src/core/exec-invoke.c | 11 ++++------- src/libsystemd/sd-journal/journal-vacuum.c | 13 +++++-------- src/portable/portable.c | 17 ++++++----------- src/portable/portable.h | 2 +- src/shared/install.c | 14 ++++++-------- src/shared/install.h | 2 +- src/shared/mount-util.c | 9 +-------- src/shared/mount-util.h | 2 +- 8 files changed, 25 insertions(+), 45 deletions(-) diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index b91a964cdd6ab..fef2c3a0f2c5c 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -1086,15 +1086,12 @@ static int enforce_user( #if HAVE_PAM -static void pam_response_free_array(struct pam_response *responses, size_t n_responses) { - assert(responses || n_responses == 0); - - FOREACH_ARRAY(resp, responses, n_responses) - erase_and_free(resp->resp); - - free(responses); +static void pam_response_done(struct pam_response *response) { + erase_and_free(ASSERT_PTR(response)->resp); } +static DEFINE_ARRAY_FREE_FUNC(pam_response_free_array, struct pam_response, pam_response_done); + typedef struct AskPasswordConvData { const ExecContext *context; const ExecParameters *params; diff --git a/src/libsystemd/sd-journal/journal-vacuum.c b/src/libsystemd/sd-journal/journal-vacuum.c index e88ba0e539656..a49c073b3e572 100644 --- a/src/libsystemd/sd-journal/journal-vacuum.c +++ b/src/libsystemd/sd-journal/journal-vacuum.c @@ -49,16 +49,13 @@ static int vacuum_info_compare(const VacuumInfo *a, const VacuumInfo *b) { return strcmp(a->filename, b->filename); } -static void vacuum_info_array_free(VacuumInfo *list, size_t n) { - if (!list) - return; - - FOREACH_ARRAY(i, list, n) - free(i->filename); - - free(list); +static void vacuum_info_done(VacuumInfo *info) { + assert(info); + info->filename = mfree(info->filename); } +static DEFINE_ARRAY_FREE_FUNC(vacuum_info_array_free, VacuumInfo, vacuum_info_done); + static void patch_realtime( int fd, const char *fn, diff --git a/src/portable/portable.c b/src/portable/portable.c index bae23b1a5115e..12a14cbf4240a 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -1271,19 +1271,14 @@ static int portable_changes_add_with_prefix( return portable_changes_add(changes, n_changes, type_or_errno, path, source); } -void portable_changes_free(PortableChange *changes, size_t n_changes) { - size_t i; - - assert(changes || n_changes == 0); - - for (i = 0; i < n_changes; i++) { - free(changes[i].path); - free(changes[i].source); - } - - free(changes); +static void portable_change_done(PortableChange *change) { + assert(change); + change->path = mfree(change->path); + change->source = mfree(change->source); } +DEFINE_ARRAY_FREE_FUNC(portable_changes_free, PortableChange, portable_change_done); + static const char *root_setting_from_image(ImageType type) { switch (type) { case IMAGE_DIRECTORY: diff --git a/src/portable/portable.h b/src/portable/portable.h index 5065babaab24c..af5ef3ba652a2 100644 --- a/src/portable/portable.h +++ b/src/portable/portable.h @@ -112,7 +112,7 @@ int portable_get_state( int portable_get_profiles(RuntimeScope scope, char ***ret); -void portable_changes_free(PortableChange *changes, size_t n_changes); +void portable_changes_free(PortableChange *array, size_t n); DECLARE_STRING_TABLE_LOOKUP(portable_change_type, int); diff --git a/src/shared/install.c b/src/shared/install.c index 23982241766f9..149faed711a70 100644 --- a/src/shared/install.c +++ b/src/shared/install.c @@ -319,17 +319,15 @@ InstallChangeType install_changes_add( return type; } -void install_changes_free(InstallChange *changes, size_t n_changes) { - assert(changes || n_changes == 0); - - FOREACH_ARRAY(i, changes, n_changes) { - free(i->path); - free(i->source); - } +static void install_change_done(InstallChange *change) { + assert(change); - free(changes); + change->path = mfree(change->path); + change->source = mfree(change->source); } +DEFINE_ARRAY_FREE_FUNC(install_changes_free, InstallChange, install_change_done); + static void install_change_dump_success(const InstallChange *change) { assert(change); assert(change->path); diff --git a/src/shared/install.h b/src/shared/install.h index ad5803e32168e..f184b7191cad7 100644 --- a/src/shared/install.h +++ b/src/shared/install.h @@ -202,7 +202,7 @@ static inline int unit_file_exists(RuntimeScope scope, const LookupPaths *lp, co int unit_file_get_list(RuntimeScope scope, const char *root_dir, char * const *states, char * const *patterns, Hashmap **ret); InstallChangeType install_changes_add(InstallChange **changes, size_t *n_changes, InstallChangeType type, const char *path, const char *source); -void install_changes_free(InstallChange *changes, size_t n_changes); +void install_changes_free(InstallChange *array, size_t n); int install_change_dump_error(const InstallChange *change, char **ret_errmsg, const char **ret_bus_error); int install_changes_dump( diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index f7bba5d080c85..45ffd9113b7e6 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -1748,14 +1748,7 @@ static void sub_mount_clear(SubMount *s) { s->mount_fd = safe_close(s->mount_fd); } -void sub_mount_array_free(SubMount *s, size_t n) { - assert(s || n == 0); - - for (size_t i = 0; i < n; i++) - sub_mount_clear(s + i); - - free(s); -} +DEFINE_ARRAY_FREE_FUNC(sub_mount_array_free, SubMount, sub_mount_clear); #if HAVE_LIBMOUNT static int sub_mount_compare(const SubMount *a, const SubMount *b) { diff --git a/src/shared/mount-util.h b/src/shared/mount-util.h index 768aacf09c401..b178bf7bac28e 100644 --- a/src/shared/mount-util.h +++ b/src/shared/mount-util.h @@ -8,7 +8,7 @@ typedef struct SubMount { int mount_fd; } SubMount; -void sub_mount_array_free(SubMount *s, size_t n); +void sub_mount_array_free(SubMount *array, size_t n); int get_sub_mounts(const char *prefix, SubMount **ret_mounts, size_t *ret_n_mounts); int bind_mount_submounts( From 1912b3585dba772d3a92447bff7c641acfca179b Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Thu, 5 Mar 2026 02:31:24 -0800 Subject: [PATCH 0893/1296] varlink: add enum types for configuration settings in io.systemd.Unit Define proper varlink enum types for unit configuration settings that are part of the user-facing API (values users/clients can select). This replaces SD_VARLINK_STRING with SD_VARLINK_DEFINE_FIELD_BY_TYPE for these fields, giving them strong type semantics in the IDL. Enum types added for ExecContext (ExecInputType, ExecOutputType, ExecUtmpMode, ExecPreserveMode, ExecKeyringMode, MemoryTHP, ProtectProc, ProcSubset, ProtectSystem, ProtectHome, PrivateTmp, PrivateUsers, ProtectHostname, ProtectControlGroups, PrivatePIDs, PrivateBPF), CGroupContext (CGroupDevicePolicy, ManagedOOMMode, ManagedOOMPreference, CGroupPressureWatch, NFTSetSource, NFProto, BPFCGroupAttachType, CGroupController), and UnitContext (CollectMode, EmergencyAction, JobMode). Engine-reported runtime state fields (Type, LoadState, ActiveState, FreezerState, SubState, UnitFileState) remain as strings since only the engine selects those values. --- src/core/varlink-cgroup.c | 16 +- src/core/varlink-execute.c | 34 ++-- src/core/varlink-unit.c | 8 +- src/libsystemd/sd-json/json-util.h | 1 + src/shared/varlink-io.systemd.Unit.c | 284 +++++++++++++++++++++++---- src/shared/varlink-io.systemd.Unit.h | 25 +++ 6 files changed, 306 insertions(+), 62 deletions(-) diff --git a/src/core/varlink-cgroup.c b/src/core/varlink-cgroup.c index ab32def28b7bb..e0500420e4d7b 100644 --- a/src/core/varlink-cgroup.c +++ b/src/core/varlink-cgroup.c @@ -219,7 +219,7 @@ static int controllers_build_json(sd_json_variant **ret, const char *name, void if (!FLAGS_SET(*mask, CGROUP_CONTROLLER_TO_MASK(ctrl))) continue; - r = sd_json_variant_append_arrayb(&v, SD_JSON_BUILD_STRING(cgroup_controller_to_string(ctrl))); + r = sd_json_variant_append_arrayb(&v, JSON_BUILD_STRING_UNDERSCORIFY(cgroup_controller_to_string(ctrl))); if (r < 0) return r; } @@ -309,7 +309,7 @@ int unit_cgroup_context_build_json(sd_json_variant **ret, const char *name, void /* Device Access */ JSON_BUILD_PAIR_CALLBACK_NON_NULL("DeviceAllow", device_allow_build_json, c->device_allow), - SD_JSON_BUILD_PAIR_STRING("DevicePolicy", cgroup_device_policy_to_string(c->device_policy)), + JSON_BUILD_PAIR_ENUM("DevicePolicy", cgroup_device_policy_to_string(c->device_policy)), /* Control Group Management */ SD_JSON_BUILD_PAIR_BOOLEAN("Delegate", c->delegate), @@ -318,16 +318,16 @@ int unit_cgroup_context_build_json(sd_json_variant **ret, const char *name, void JSON_BUILD_PAIR_CALLBACK_NON_NULL("DisableControllers", controllers_build_json, &c->disable_controllers), /* Memory Pressure Control */ - SD_JSON_BUILD_PAIR_STRING("ManagedOOMSwap", managed_oom_mode_to_string(c->moom_swap)), - SD_JSON_BUILD_PAIR_STRING("ManagedOOMMemoryPressure", managed_oom_mode_to_string(c->moom_mem_pressure)), + JSON_BUILD_PAIR_ENUM("ManagedOOMSwap", managed_oom_mode_to_string(c->moom_swap)), + JSON_BUILD_PAIR_ENUM("ManagedOOMMemoryPressure", managed_oom_mode_to_string(c->moom_mem_pressure)), JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("ManagedOOMMemoryPressureLimit", c->moom_mem_pressure_limit), JSON_BUILD_PAIR_FINITE_USEC("ManagedOOMMemoryPressureDurationUSec", c->moom_mem_pressure_duration_usec), - SD_JSON_BUILD_PAIR_STRING("ManagedOOMPreference", managed_oom_preference_to_string(c->moom_preference)), - SD_JSON_BUILD_PAIR_STRING("MemoryPressureWatch", cgroup_pressure_watch_to_string(c->pressure[PRESSURE_MEMORY].watch)), + JSON_BUILD_PAIR_ENUM("ManagedOOMPreference", managed_oom_preference_to_string(c->moom_preference)), + JSON_BUILD_PAIR_ENUM("MemoryPressureWatch", cgroup_pressure_watch_to_string(c->pressure[PRESSURE_MEMORY].watch)), JSON_BUILD_PAIR_FINITE_USEC("MemoryPressureThresholdUSec", c->pressure[PRESSURE_MEMORY].threshold_usec), - SD_JSON_BUILD_PAIR_STRING("CPUPressureWatch", cgroup_pressure_watch_to_string(c->pressure[PRESSURE_CPU].watch)), + JSON_BUILD_PAIR_ENUM("CPUPressureWatch", cgroup_pressure_watch_to_string(c->pressure[PRESSURE_CPU].watch)), JSON_BUILD_PAIR_FINITE_USEC("CPUPressureThresholdUSec", c->pressure[PRESSURE_CPU].threshold_usec), - SD_JSON_BUILD_PAIR_STRING("IOPressureWatch", cgroup_pressure_watch_to_string(c->pressure[PRESSURE_IO].watch)), + JSON_BUILD_PAIR_ENUM("IOPressureWatch", cgroup_pressure_watch_to_string(c->pressure[PRESSURE_IO].watch)), JSON_BUILD_PAIR_FINITE_USEC("IOPressureThresholdUSec", c->pressure[PRESSURE_IO].threshold_usec), /* Others */ diff --git a/src/core/varlink-execute.c b/src/core/varlink-execute.c index ccb454c8c245b..054a79401d7f3 100644 --- a/src/core/varlink-execute.c +++ b/src/core/varlink-execute.c @@ -810,8 +810,8 @@ int unit_exec_context_build_json(sd_json_variant **ret, const char *name, void * SD_JSON_BUILD_PAIR_CALLBACK("ExtensionImagePolicy", image_policy_build_json, c->extension_image_policy), JSON_BUILD_PAIR_YES_NO("MountAPIVFS", exec_context_get_effective_mount_apivfs(c)), SD_JSON_BUILD_PAIR_BOOLEAN("BindLogSockets", exec_context_get_effective_bind_log_sockets(c)), - SD_JSON_BUILD_PAIR_STRING("ProtectProc", protect_proc_to_string(c->protect_proc)), - SD_JSON_BUILD_PAIR_STRING("ProcSubset", proc_subset_to_string(c->proc_subset)), + JSON_BUILD_PAIR_ENUM("ProtectProc", protect_proc_to_string(c->protect_proc)), + JSON_BUILD_PAIR_ENUM("ProcSubset", proc_subset_to_string(c->proc_subset)), JSON_BUILD_PAIR_CALLBACK_NON_NULL("BindPaths", bind_paths_build_json, c), JSON_BUILD_PAIR_CALLBACK_NON_NULL("BindReadOnlyPaths", bind_paths_build_json, c), JSON_BUILD_PAIR_CALLBACK_NON_NULL("MountImages", mount_images_build_json, c), @@ -852,7 +852,7 @@ int unit_exec_context_build_json(sd_json_variant **ret, const char *name, void * SD_JSON_BUILD_PAIR_CALLBACK("Limits", rlimit_table_with_defaults_build_json, u), JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("UMask", c->umask), JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("CoredumpFilter", exec_context_get_coredump_filter(c)), - SD_JSON_BUILD_PAIR_STRING("KeyringMode", exec_keyring_mode_to_string(c->keyring_mode)), + JSON_BUILD_PAIR_ENUM("KeyringMode", exec_keyring_mode_to_string(c->keyring_mode)), JSON_BUILD_PAIR_INTEGER_NON_ZERO("OOMScoreAdjust", exec_context_get_oom_score_adjust(c)), JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("TimerSlackNSec", exec_context_get_timer_slack_nsec(c), NSEC_INFINITY), JSON_BUILD_PAIR_STRING_NON_EMPTY("Personality", personality_to_string(c->personality)), @@ -870,17 +870,17 @@ int unit_exec_context_build_json(sd_json_variant **ret, const char *name, void * SD_JSON_BUILD_PAIR_INTEGER("IOSchedulingPriority", ioprio_prio_data(exec_context_get_effective_ioprio(c))), JSON_BUILD_PAIR_TRISTATE_NON_NULL("MemoryKSM", c->memory_ksm), - SD_JSON_BUILD_PAIR_STRING("MemoryTHP", memory_thp_to_string(c->memory_thp)), + JSON_BUILD_PAIR_ENUM("MemoryTHP", memory_thp_to_string(c->memory_thp)), /* Sandboxing */ - SD_JSON_BUILD_PAIR_STRING("ProtectSystem", protect_system_to_string(c->protect_system)), - SD_JSON_BUILD_PAIR_STRING("ProtectHome", protect_home_to_string(c->protect_home)), + JSON_BUILD_PAIR_ENUM("ProtectSystem", protect_system_to_string(c->protect_system)), + JSON_BUILD_PAIR_ENUM("ProtectHome", protect_home_to_string(c->protect_home)), JSON_BUILD_PAIR_CALLBACK_NON_NULL("RuntimeDirectory", exec_dir_build_json, &c->directories[EXEC_DIRECTORY_RUNTIME]), JSON_BUILD_PAIR_CALLBACK_NON_NULL("StateDirectory", exec_dir_build_json, &c->directories[EXEC_DIRECTORY_STATE]), JSON_BUILD_PAIR_CALLBACK_NON_NULL("CacheDirectory", exec_dir_build_json, &c->directories[EXEC_DIRECTORY_CACHE]), JSON_BUILD_PAIR_CALLBACK_NON_NULL("LogsDirectory", exec_dir_build_json, &c->directories[EXEC_DIRECTORY_LOGS]), JSON_BUILD_PAIR_CALLBACK_NON_NULL("ConfigurationDirectory", exec_dir_build_json, &c->directories[EXEC_DIRECTORY_CONFIGURATION]), - SD_JSON_BUILD_PAIR_STRING("RuntimeDirectoryPreserve", exec_preserve_mode_to_string(c->runtime_directory_preserve_mode)), + JSON_BUILD_PAIR_ENUM("RuntimeDirectoryPreserve", exec_preserve_mode_to_string(c->runtime_directory_preserve_mode)), JSON_BUILD_PAIR_FINITE_USEC("TimeoutCleanUSec", c->timeout_clean_usec), JSON_BUILD_PAIR_STRV_NON_EMPTY("ReadWritePaths", c->read_write_paths), JSON_BUILD_PAIR_STRV_NON_EMPTY("ReadOnlyPaths", c->read_only_paths), @@ -889,26 +889,26 @@ int unit_exec_context_build_json(sd_json_variant **ret, const char *name, void * JSON_BUILD_PAIR_STRV_NON_EMPTY("NoExecPaths", c->no_exec_paths), JSON_BUILD_PAIR_CALLBACK_NON_NULL("TemporaryFileSystem", temporary_filesystems_build_json, c), /* XXX should we make all these Private/Protect strings??? */ - SD_JSON_BUILD_PAIR_STRING("PrivateTmp", private_tmp_to_string(c->private_tmp)), + JSON_BUILD_PAIR_ENUM("PrivateTmp", private_tmp_to_string(c->private_tmp)), JSON_BUILD_PAIR_YES_NO("PrivateDevices", c->private_devices), JSON_BUILD_PAIR_YES_NO("PrivateNetwork", c->private_network), JSON_BUILD_PAIR_STRING_NON_EMPTY("NetworkNamespacePath", c->network_namespace_path), JSON_BUILD_PAIR_YES_NO("PrivateIPC", c->private_ipc), JSON_BUILD_PAIR_STRING_NON_EMPTY("IPCNamespacePath", c->ipc_namespace_path), - SD_JSON_BUILD_PAIR_STRING("PrivatePIDs", private_pids_to_string(c->private_pids)), - SD_JSON_BUILD_PAIR_STRING("PrivateUsers", private_users_to_string(c->private_users)), + JSON_BUILD_PAIR_ENUM("PrivatePIDs", private_pids_to_string(c->private_pids)), + JSON_BUILD_PAIR_ENUM("PrivateUsers", private_users_to_string(c->private_users)), JSON_BUILD_PAIR_STRING_NON_EMPTY("UserNamespacePath", c->user_namespace_path), - SD_JSON_BUILD_PAIR_STRING("ProtectHostname", protect_hostname_to_string(c->protect_hostname)), + JSON_BUILD_PAIR_ENUM("ProtectHostname", protect_hostname_to_string(c->protect_hostname)), JSON_BUILD_PAIR_YES_NO("ProtectClock", c->protect_clock), JSON_BUILD_PAIR_YES_NO("ProtectKernelTunables", c->protect_kernel_tunables), JSON_BUILD_PAIR_YES_NO("ProtectKernelModules", c->protect_kernel_modules), JSON_BUILD_PAIR_YES_NO("ProtectKernelLogs", c->protect_kernel_logs), - SD_JSON_BUILD_PAIR_STRING("ProtectControlGroups", protect_control_groups_to_string(c->protect_control_groups)), + JSON_BUILD_PAIR_ENUM("ProtectControlGroups", protect_control_groups_to_string(c->protect_control_groups)), JSON_BUILD_PAIR_CALLBACK_NON_NULL("RestrictAddressFamilies", address_families_build_json, c), JSON_BUILD_PAIR_CALLBACK_NON_NULL("RestrictFileSystems", restrict_filesystems_build_json, c), JSON_BUILD_PAIR_CALLBACK_NON_NULL("RestrictNamespaces", namespace_flags_build_json, ULONG_TO_PTR(c->restrict_namespaces)), JSON_BUILD_PAIR_CALLBACK_NON_NULL("DelegateNamespaces", namespace_flags_build_json, ULONG_TO_PTR(c->delegate_namespaces)), - SD_JSON_BUILD_PAIR_STRING("PrivatePBF", private_bpf_to_string(c->private_bpf)), + JSON_BUILD_PAIR_ENUM("PrivatePBF", private_bpf_to_string(c->private_bpf)), JSON_BUILD_PAIR_CALLBACK_NON_NULL("BPFDelegateCommands", private_bpf_delegate_commands_build_json, c), JSON_BUILD_PAIR_CALLBACK_NON_NULL("BPFDelegateMaps", private_bpf_delegate_maps_build_json, c), JSON_BUILD_PAIR_CALLBACK_NON_NULL("BPFDelegatePrograms", private_bpf_delegate_programs_build_json, c), @@ -934,9 +934,9 @@ int unit_exec_context_build_json(sd_json_variant **ret, const char *name, void * JSON_BUILD_PAIR_STRV_NON_EMPTY("UnsetEnvironment", c->unset_environment), /* Logging and Standard Input/Output */ - SD_JSON_BUILD_PAIR_STRING("StandardInput", exec_input_to_string(c->std_input)), - SD_JSON_BUILD_PAIR_STRING("StandardOutput", exec_output_to_string(c->std_output)), - SD_JSON_BUILD_PAIR_STRING("StandardError", exec_output_to_string(c->std_error)), + JSON_BUILD_PAIR_ENUM("StandardInput", exec_input_to_string(c->std_input)), + JSON_BUILD_PAIR_ENUM("StandardOutput", exec_output_to_string(c->std_output)), + JSON_BUILD_PAIR_ENUM("StandardError", exec_output_to_string(c->std_error)), JSON_BUILD_PAIR_STRING_NON_EMPTY("StandardInputFileDescriptorName", exec_context_fdname(c, STDIN_FILENO)), JSON_BUILD_PAIR_STRING_NON_EMPTY("StandardOutputFileDescriptorName", exec_context_fdname(c, STDOUT_FILENO)), JSON_BUILD_PAIR_STRING_NON_EMPTY("StandardErrorFileDescriptorName", exec_context_fdname(c, STDERR_FILENO)), @@ -966,5 +966,5 @@ int unit_exec_context_build_json(sd_json_variant **ret, const char *name, void * /* System V Compatibility */ JSON_BUILD_PAIR_STRING_NON_EMPTY("UtmpIdentifier", c->utmp_id), - SD_JSON_BUILD_PAIR_STRING("UtmpMode", exec_utmp_mode_to_string(c->utmp_mode))); + JSON_BUILD_PAIR_ENUM("UtmpMode", exec_utmp_mode_to_string(c->utmp_mode))); } diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index 2a2b75c8a9f03..15a425bbb19bc 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -24,7 +24,7 @@ #include "varlink-util.h" #define JSON_BUILD_EMERGENCY_ACTION_NON_EMPTY(name, value) \ - SD_JSON_BUILD_PAIR_CONDITION(value > EMERGENCY_ACTION_NONE, name, SD_JSON_BUILD_STRING(emergency_action_to_string(value))) + SD_JSON_BUILD_PAIR_CONDITION(value > EMERGENCY_ACTION_NONE, name, JSON_BUILD_STRING_UNDERSCORIFY(emergency_action_to_string(value))) static int unit_dependencies_build_json(sd_json_variant **ret, const char *name, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; @@ -149,8 +149,8 @@ static int unit_context_build_json(sd_json_variant **ret, const char *name, void JSON_BUILD_PAIR_CALLBACK_NON_NULL("JoinsNamespaceOf", unit_dependencies_build_json, u), JSON_BUILD_PAIR_CALLBACK_NON_NULL("RequiresMountsFor", unit_mounts_for_build_json, &u->mounts_for), JSON_BUILD_PAIR_CALLBACK_NON_NULL("WantsMountsFor", unit_mounts_for_build_json, &u->mounts_for), - SD_JSON_BUILD_PAIR_STRING("OnSuccessJobMode", job_mode_to_string(u->on_success_job_mode)), - SD_JSON_BUILD_PAIR_STRING("OnFailureJobMode", job_mode_to_string(u->on_failure_job_mode)), + JSON_BUILD_PAIR_ENUM("OnSuccessJobMode", job_mode_to_string(u->on_success_job_mode)), + JSON_BUILD_PAIR_ENUM("OnFailureJobMode", job_mode_to_string(u->on_failure_job_mode)), SD_JSON_BUILD_PAIR_BOOLEAN("IgnoreOnIsolate", u->ignore_on_isolate), SD_JSON_BUILD_PAIR_BOOLEAN("StopWhenUnneeded", u->stop_when_unneeded), SD_JSON_BUILD_PAIR_BOOLEAN("RefuseManualStart", u->refuse_manual_start), @@ -158,7 +158,7 @@ static int unit_context_build_json(sd_json_variant **ret, const char *name, void SD_JSON_BUILD_PAIR_BOOLEAN("AllowIsolate", u->allow_isolate), SD_JSON_BUILD_PAIR_BOOLEAN("DefaultDependencies", u->default_dependencies), SD_JSON_BUILD_PAIR_BOOLEAN("SurviveFinalKillSignal", u->survive_final_kill_signal), - SD_JSON_BUILD_PAIR_STRING("CollectMode", collect_mode_to_string(u->collect_mode)), + JSON_BUILD_PAIR_ENUM("CollectMode", collect_mode_to_string(u->collect_mode)), JSON_BUILD_EMERGENCY_ACTION_NON_EMPTY("FailureAction", u->failure_action), JSON_BUILD_EMERGENCY_ACTION_NON_EMPTY("SuccessAction", u->success_action), JSON_BUILD_PAIR_INTEGER_NON_NEGATIVE("FailureActionExitStatus", u->failure_action_exit_status), diff --git a/src/libsystemd/sd-json/json-util.h b/src/libsystemd/sd-json/json-util.h index 478d2a2a2122b..4ed0b708f8ac3 100644 --- a/src/libsystemd/sd-json/json-util.h +++ b/src/libsystemd/sd-json/json-util.h @@ -259,6 +259,7 @@ enum { #define JSON_BUILD_PAIR_TRISTATE(name, i) SD_JSON_BUILD_PAIR(name, JSON_BUILD_TRISTATE(i)) #define JSON_BUILD_PAIR_PIDREF(name, p) SD_JSON_BUILD_PAIR(name, JSON_BUILD_PIDREF(p)) #define JSON_BUILD_PAIR_DEVNUM(name, d) SD_JSON_BUILD_PAIR(name, JSON_BUILD_DEVNUM(d)) +#define JSON_BUILD_PAIR_ENUM(name, s) SD_JSON_BUILD_PAIR(name, JSON_BUILD_STRING_UNDERSCORIFY(s)) #define JSON_BUILD_PAIR_YES_NO(name, b) SD_JSON_BUILD_PAIR(name, SD_JSON_BUILD_STRING(yes_no(b))) #define JSON_BUILD_PAIR_CONDITION_UNSIGNED(condition, name, value) \ diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c index c1ff4ebc5a76c..aaa0374479d61 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -3,6 +3,198 @@ #include "varlink-idl-common.h" #include "varlink-io.systemd.Unit.h" +SD_VARLINK_DEFINE_ENUM_TYPE( + ExecInputType, + SD_VARLINK_DEFINE_ENUM_VALUE(null), + SD_VARLINK_DEFINE_ENUM_VALUE(tty), + SD_VARLINK_DEFINE_ENUM_VALUE(tty_force), + SD_VARLINK_DEFINE_ENUM_VALUE(tty_fail), + SD_VARLINK_DEFINE_ENUM_VALUE(socket), + SD_VARLINK_DEFINE_ENUM_VALUE(fd), + SD_VARLINK_DEFINE_ENUM_VALUE(data), + SD_VARLINK_DEFINE_ENUM_VALUE(file)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ExecOutputType, + SD_VARLINK_DEFINE_ENUM_VALUE(inherit), + SD_VARLINK_DEFINE_ENUM_VALUE(null), + SD_VARLINK_DEFINE_ENUM_VALUE(tty), + SD_VARLINK_DEFINE_ENUM_VALUE(kmsg), + SD_VARLINK_DEFINE_ENUM_VALUE(kmsg_console), + SD_VARLINK_DEFINE_ENUM_VALUE(journal), + SD_VARLINK_DEFINE_ENUM_VALUE(journal_console), + SD_VARLINK_DEFINE_ENUM_VALUE(socket), + SD_VARLINK_DEFINE_ENUM_VALUE(fd), + SD_VARLINK_DEFINE_ENUM_VALUE(file), + SD_VARLINK_DEFINE_ENUM_VALUE(append), + SD_VARLINK_DEFINE_ENUM_VALUE(truncate)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ExecUtmpMode, + SD_VARLINK_DEFINE_ENUM_VALUE(init), + SD_VARLINK_DEFINE_ENUM_VALUE(login), + SD_VARLINK_DEFINE_ENUM_VALUE(user)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ExecPreserveMode, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(yes), + SD_VARLINK_DEFINE_ENUM_VALUE(restart)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ExecKeyringMode, + SD_VARLINK_DEFINE_ENUM_VALUE(inherit), + SD_VARLINK_DEFINE_ENUM_VALUE(private), + SD_VARLINK_DEFINE_ENUM_VALUE(shared)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + MemoryTHP, + SD_VARLINK_DEFINE_ENUM_VALUE(inherit), + SD_VARLINK_DEFINE_ENUM_VALUE(disable), + SD_VARLINK_DEFINE_ENUM_VALUE(madvise), + SD_VARLINK_DEFINE_ENUM_VALUE(system)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ProtectProc, + SD_VARLINK_DEFINE_ENUM_VALUE(default), + SD_VARLINK_DEFINE_ENUM_VALUE(noaccess), + SD_VARLINK_DEFINE_ENUM_VALUE(invisible), + SD_VARLINK_DEFINE_ENUM_VALUE(ptraceable)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ProcSubset, + SD_VARLINK_DEFINE_ENUM_VALUE(all), + SD_VARLINK_DEFINE_ENUM_VALUE(pid)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ProtectSystem, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(yes), + SD_VARLINK_DEFINE_ENUM_VALUE(full), + SD_VARLINK_DEFINE_ENUM_VALUE(strict)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ProtectHome, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(yes), + SD_VARLINK_DEFINE_ENUM_VALUE(read_only), + SD_VARLINK_DEFINE_ENUM_VALUE(tmpfs)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + PrivateTmp, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(connected), + SD_VARLINK_DEFINE_ENUM_VALUE(disconnected)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + PrivateUsers, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(self), + SD_VARLINK_DEFINE_ENUM_VALUE(identity), + SD_VARLINK_DEFINE_ENUM_VALUE(full), + SD_VARLINK_DEFINE_ENUM_VALUE(managed)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ProtectHostname, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(yes), + SD_VARLINK_DEFINE_ENUM_VALUE(private)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ProtectControlGroups, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(yes), + SD_VARLINK_DEFINE_ENUM_VALUE(private), + SD_VARLINK_DEFINE_ENUM_VALUE(strict)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + PrivatePIDs, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(yes)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + CGroupDevicePolicy, + SD_VARLINK_DEFINE_ENUM_VALUE(auto), + SD_VARLINK_DEFINE_ENUM_VALUE(closed), + SD_VARLINK_DEFINE_ENUM_VALUE(strict)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ManagedOOMMode, + SD_VARLINK_DEFINE_ENUM_VALUE(auto), + SD_VARLINK_DEFINE_ENUM_VALUE(kill)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ManagedOOMPreference, + SD_VARLINK_DEFINE_ENUM_VALUE(none), + SD_VARLINK_DEFINE_ENUM_VALUE(avoid), + SD_VARLINK_DEFINE_ENUM_VALUE(omit)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + CGroupPressureWatch, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(yes), + SD_VARLINK_DEFINE_ENUM_VALUE(auto), + SD_VARLINK_DEFINE_ENUM_VALUE(skip)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + CollectMode, + SD_VARLINK_DEFINE_ENUM_VALUE(inactive), + SD_VARLINK_DEFINE_ENUM_VALUE(inactive_or_failed)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + EmergencyAction, + SD_VARLINK_DEFINE_ENUM_VALUE(none), + SD_VARLINK_DEFINE_ENUM_VALUE(exit), + SD_VARLINK_DEFINE_ENUM_VALUE(exit_force), + SD_VARLINK_DEFINE_ENUM_VALUE(reboot), + SD_VARLINK_DEFINE_ENUM_VALUE(reboot_force), + SD_VARLINK_DEFINE_ENUM_VALUE(reboot_immediate), + SD_VARLINK_DEFINE_ENUM_VALUE(poweroff), + SD_VARLINK_DEFINE_ENUM_VALUE(poweroff_force), + SD_VARLINK_DEFINE_ENUM_VALUE(poweroff_immediate), + SD_VARLINK_DEFINE_ENUM_VALUE(soft_reboot), + SD_VARLINK_DEFINE_ENUM_VALUE(soft_reboot_force), + SD_VARLINK_DEFINE_ENUM_VALUE(kexec), + SD_VARLINK_DEFINE_ENUM_VALUE(kexec_force), + SD_VARLINK_DEFINE_ENUM_VALUE(halt), + SD_VARLINK_DEFINE_ENUM_VALUE(halt_force), + SD_VARLINK_DEFINE_ENUM_VALUE(halt_immediate)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + JobMode, + SD_VARLINK_DEFINE_ENUM_VALUE(fail), + SD_VARLINK_DEFINE_ENUM_VALUE(lenient), + SD_VARLINK_DEFINE_ENUM_VALUE(replace), + SD_VARLINK_DEFINE_ENUM_VALUE(replace_irreversibly), + SD_VARLINK_DEFINE_ENUM_VALUE(isolate), + SD_VARLINK_DEFINE_ENUM_VALUE(flush), + SD_VARLINK_DEFINE_ENUM_VALUE(ignore_dependencies), + SD_VARLINK_DEFINE_ENUM_VALUE(ignore_requirements), + SD_VARLINK_DEFINE_ENUM_VALUE(triggering), + SD_VARLINK_DEFINE_ENUM_VALUE(restart_dependencies)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + CGroupController, + SD_VARLINK_DEFINE_ENUM_VALUE(cpu), + SD_VARLINK_DEFINE_ENUM_VALUE(cpuacct), + SD_VARLINK_DEFINE_ENUM_VALUE(cpuset), + SD_VARLINK_DEFINE_ENUM_VALUE(io), + SD_VARLINK_DEFINE_ENUM_VALUE(blkio), + SD_VARLINK_DEFINE_ENUM_VALUE(memory), + SD_VARLINK_DEFINE_ENUM_VALUE(devices), + SD_VARLINK_DEFINE_ENUM_VALUE(pids), + SD_VARLINK_DEFINE_ENUM_VALUE(bpf_firewall), + SD_VARLINK_DEFINE_ENUM_VALUE(bpf_devices), + SD_VARLINK_DEFINE_ENUM_VALUE(bpf_foreign), + SD_VARLINK_DEFINE_ENUM_VALUE(bpf_socket_bind), + SD_VARLINK_DEFINE_ENUM_VALUE(bpf_restrict_network_interfaces), + SD_VARLINK_DEFINE_ENUM_VALUE(bpf_bind_network_interface)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + PrivateBPF, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(yes)); + /* CGroupContext */ static SD_VARLINK_DEFINE_STRUCT_TYPE( CGroupTasksMax, @@ -199,7 +391,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#DeviceAllow="), SD_VARLINK_DEFINE_FIELD_BY_TYPE(DeviceAllow, CGroupDeviceAllow, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#DevicePolicy=auto%7Cclosed%7Cstrict"), - SD_VARLINK_DEFINE_FIELD(DevicePolicy, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(DevicePolicy, CGroupDevicePolicy, 0), /* Control Group Management * https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#Control%20Group%20Management */ @@ -208,32 +400,32 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#DelegateSubgroup="), SD_VARLINK_DEFINE_FIELD(DelegateSubgroup, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#DisableControllers="), - SD_VARLINK_DEFINE_FIELD(DelegateControllers, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(DelegateControllers, CGroupController, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#DisableControllers="), - SD_VARLINK_DEFINE_FIELD(DisableControllers, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(DisableControllers, CGroupController, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), /* Memory Pressure Control * https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#Memory%20Pressure%20Control */ SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#ManagedOOMSwap=auto%7Ckill"), - SD_VARLINK_DEFINE_FIELD(ManagedOOMSwap, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ManagedOOMSwap, ManagedOOMMode, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#ManagedOOMSwap=auto%7Ckill"), - SD_VARLINK_DEFINE_FIELD(ManagedOOMMemoryPressure, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ManagedOOMMemoryPressure, ManagedOOMMode, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#ManagedOOMMemoryPressureLimit="), SD_VARLINK_DEFINE_FIELD(ManagedOOMMemoryPressureLimit, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#ManagedOOMMemoryPressureDurationSec="), SD_VARLINK_DEFINE_FIELD(ManagedOOMMemoryPressureDurationUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#ManagedOOMPreference=none%7Cavoid%7Comit"), - SD_VARLINK_DEFINE_FIELD(ManagedOOMPreference, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ManagedOOMPreference, ManagedOOMPreference, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#MemoryPressureWatch="), - SD_VARLINK_DEFINE_FIELD(MemoryPressureWatch, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(MemoryPressureWatch, CGroupPressureWatch, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#MemoryPressureThresholdSec="), SD_VARLINK_DEFINE_FIELD(MemoryPressureThresholdUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#CPUPressureWatch="), - SD_VARLINK_DEFINE_FIELD(CPUPressureWatch, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(CPUPressureWatch, CGroupPressureWatch, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#CPUPressureThresholdSec="), SD_VARLINK_DEFINE_FIELD(CPUPressureThresholdUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#IOPressureWatch="), - SD_VARLINK_DEFINE_FIELD(IOPressureWatch, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(IOPressureWatch, CGroupPressureWatch, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#IOPressureThresholdSec="), SD_VARLINK_DEFINE_FIELD(IOPressureThresholdUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), @@ -445,9 +637,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#BindLogSockets="), SD_VARLINK_DEFINE_FIELD(BindLogSockets, SD_VARLINK_BOOL, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectProc="), - SD_VARLINK_DEFINE_FIELD(ProtectProc, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ProtectProc, ProtectProc, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProcSubset="), - SD_VARLINK_DEFINE_FIELD(ProcSubset, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ProcSubset, ProcSubset, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#BindPaths="), SD_VARLINK_DEFINE_FIELD_BY_TYPE(BindPaths, BindPath, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#BindPaths="), @@ -506,7 +698,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#CoredumpFilter="), SD_VARLINK_DEFINE_FIELD(CoredumpFilter, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#KeyringMode="), - SD_VARLINK_DEFINE_FIELD(KeyringMode, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(KeyringMode, ExecKeyringMode, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#OOMScoreAdjust="), SD_VARLINK_DEFINE_FIELD(OOMScoreAdjust, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#TimerSlackNSec="), @@ -540,14 +732,14 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#MemoryKSM="), SD_VARLINK_DEFINE_FIELD(MemoryKSM, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#MemoryTHP="), - SD_VARLINK_DEFINE_FIELD(MemoryTHP, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(MemoryTHP, MemoryTHP, 0), /* Sandboxing * https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#Sandboxing */ SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectSystem="), - SD_VARLINK_DEFINE_FIELD(ProtectSystem, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ProtectSystem, ProtectSystem, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectHome="), - SD_VARLINK_DEFINE_FIELD(ProtectHome, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ProtectHome, ProtectHome, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RuntimeDirectory="), SD_VARLINK_DEFINE_FIELD_BY_TYPE(RuntimeDirectory, ExecDirectory, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RuntimeDirectory="), @@ -559,7 +751,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RuntimeDirectory="), SD_VARLINK_DEFINE_FIELD_BY_TYPE(ConfigurationDirectory, ExecDirectory, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RuntimeDirectoryPreserve="), - SD_VARLINK_DEFINE_FIELD(RuntimeDirectoryPreserve, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(RuntimeDirectoryPreserve, ExecPreserveMode, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#TimeoutCleanSec="), SD_VARLINK_DEFINE_FIELD(TimeoutCleanUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ReadWritePaths="), @@ -575,7 +767,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#TemporaryFileSystem="), SD_VARLINK_DEFINE_FIELD_BY_TYPE(TemporaryFileSystem, TemporaryFilesystem, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#PrivateTmp="), - SD_VARLINK_DEFINE_FIELD(PrivateTmp, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(PrivateTmp, PrivateTmp, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#PrivateDevices="), SD_VARLINK_DEFINE_FIELD(PrivateDevices, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#PrivateNetwork="), @@ -587,13 +779,13 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#IPCNamespacePath="), SD_VARLINK_DEFINE_FIELD(IPCNamespacePath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#PrivatePIDs="), - SD_VARLINK_DEFINE_FIELD(PrivatePIDs, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(PrivatePIDs, PrivatePIDs, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#PrivateUsers="), - SD_VARLINK_DEFINE_FIELD(PrivateUsers, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(PrivateUsers, PrivateUsers, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#UserNamespacePath="), SD_VARLINK_DEFINE_FIELD(UserNamespacePath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectHostname="), - SD_VARLINK_DEFINE_FIELD(ProtectHostname, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ProtectHostname, ProtectHostname, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectClock="), SD_VARLINK_DEFINE_FIELD(ProtectClock, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectKernelTunables="), @@ -603,7 +795,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectKernelLogs="), SD_VARLINK_DEFINE_FIELD(ProtectKernelLogs, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectControlGroups="), - SD_VARLINK_DEFINE_FIELD(ProtectControlGroups, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ProtectControlGroups, ProtectControlGroups, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RestrictAddressFamilies="), SD_VARLINK_DEFINE_FIELD_BY_TYPE(RestrictAddressFamilies, AddressFamilyList, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RestrictFileSystems="), @@ -613,7 +805,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#DelegateNamespaces="), SD_VARLINK_DEFINE_FIELD(DelegateNamespaces, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#PrivatePBF="), - SD_VARLINK_DEFINE_FIELD(PrivatePBF, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(PrivatePBF, PrivateBPF, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#BPFDelegateCommands="), SD_VARLINK_DEFINE_FIELD(BPFDelegateCommands, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#BPFDelegateMaps="), @@ -662,11 +854,11 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( /* Logging and Standard Input/Output * https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#Logging%20and%20Standard%20Input/Output */ SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#StandardInput="), - SD_VARLINK_DEFINE_FIELD(StandardInput, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(StandardInput, ExecInputType, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#StandardOutput="), - SD_VARLINK_DEFINE_FIELD(StandardOutput, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(StandardOutput, ExecOutputType, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#StandardError="), - SD_VARLINK_DEFINE_FIELD(StandardError, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(StandardError, ExecOutputType, 0), SD_VARLINK_FIELD_COMMENT("The file descriptor name to connect standard input to"), SD_VARLINK_DEFINE_FIELD(StandardInputFileDescriptorName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The file descriptor name to connect standard output to"), @@ -724,7 +916,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#UtmpIdentifier="), SD_VARLINK_DEFINE_FIELD(UtmpIdentifier, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#UtmpMode="), - SD_VARLINK_DEFINE_FIELD(UtmpMode, SD_VARLINK_STRING, 0)); + SD_VARLINK_DEFINE_FIELD_BY_TYPE(UtmpMode, ExecUtmpMode, 0)); /* UnitContext */ static SD_VARLINK_DEFINE_STRUCT_TYPE( @@ -808,9 +1000,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#WantsMountsFor="), SD_VARLINK_DEFINE_FIELD(WantsMountsFor, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#OnSuccessJobMode="), - SD_VARLINK_DEFINE_FIELD(OnSuccessJobMode, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(OnSuccessJobMode, JobMode, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#OnSuccessJobMode="), - SD_VARLINK_DEFINE_FIELD(OnFailureJobMode, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(OnFailureJobMode, JobMode, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#IgnoreOnIsolate="), SD_VARLINK_DEFINE_FIELD(IgnoreOnIsolate, SD_VARLINK_BOOL, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#StopWhenUnneeded="), @@ -826,11 +1018,11 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#SurviveFinalKillSignal="), SD_VARLINK_DEFINE_FIELD(SurviveFinalKillSignal, SD_VARLINK_BOOL, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#CollectMode="), - SD_VARLINK_DEFINE_FIELD(CollectMode, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(CollectMode, CollectMode, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#FailureAction="), - SD_VARLINK_DEFINE_FIELD(FailureAction, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(FailureAction, EmergencyAction, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#FailureAction="), - SD_VARLINK_DEFINE_FIELD(SuccessAction, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(SuccessAction, EmergencyAction, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#FailureActionExitStatus="), SD_VARLINK_DEFINE_FIELD(FailureActionExitStatus, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#FailureActionExitStatus="), @@ -840,13 +1032,13 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#JobTimeoutSec="), SD_VARLINK_DEFINE_FIELD(JobRunningTimeoutUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#JobTimeoutAction="), - SD_VARLINK_DEFINE_FIELD(JobTimeoutAction, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(JobTimeoutAction, EmergencyAction, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#JobTimeoutAction="), SD_VARLINK_DEFINE_FIELD(JobTimeoutRebootArgument, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#StartLimitIntervalSec=interval"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(StartLimit, RateLimit, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#StartLimitIntervalSec=interval"), - SD_VARLINK_DEFINE_FIELD(StartLimitAction, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(StartLimitAction, EmergencyAction, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#RebootArgument="), SD_VARLINK_DEFINE_FIELD(RebootArgument, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#SourcePath="), @@ -1075,6 +1267,10 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_ProcessId, /* CGroupContext */ + &vl_type_CGroupDevicePolicy, + &vl_type_ManagedOOMMode, + &vl_type_ManagedOOMPreference, + &vl_type_CGroupPressureWatch, &vl_type_CGroupTasksMax, &vl_type_CGroupIODeviceWeight, &vl_type_CGroupIODeviceLimit, @@ -1084,6 +1280,7 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_CGroupRestrictNetworkInterfaces, &vl_type_CGroupNFTSet, &vl_type_CGroupBPFProgram, + &vl_type_CGroupController, &vl_type_CGroupDeviceAllow, SD_VARLINK_SYMBOL_COMMENT("CGroup context of a unit"), &vl_type_CGroupContext, @@ -1091,6 +1288,22 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_CGroupRuntime, /* ExecContext */ + &vl_type_ExecInputType, + &vl_type_ExecOutputType, + &vl_type_ExecUtmpMode, + &vl_type_ExecPreserveMode, + &vl_type_ExecKeyringMode, + &vl_type_MemoryTHP, + &vl_type_ProtectProc, + &vl_type_ProcSubset, + &vl_type_ProtectSystem, + &vl_type_ProtectHome, + &vl_type_PrivateTmp, + &vl_type_PrivateUsers, + &vl_type_ProtectHostname, + &vl_type_ProtectControlGroups, + &vl_type_PrivatePIDs, + &vl_type_PrivateBPF, &vl_type_WorkingDirectory, &vl_type_PartitionMountOptions, &vl_type_BindPath, @@ -1117,6 +1330,11 @@ SD_VARLINK_DEFINE_INTERFACE( SD_VARLINK_SYMBOL_COMMENT("Exec context of a unit"), &vl_type_ExecContext, + /* UnitContext enums */ + &vl_type_CollectMode, + &vl_type_EmergencyAction, + &vl_type_JobMode, + /* Errors */ SD_VARLINK_SYMBOL_COMMENT("No matching unit found"), &vl_error_NoSuchUnit, diff --git a/src/shared/varlink-io.systemd.Unit.h b/src/shared/varlink-io.systemd.Unit.h index 25433294534f9..ad621fdbfe078 100644 --- a/src/shared/varlink-io.systemd.Unit.h +++ b/src/shared/varlink-io.systemd.Unit.h @@ -4,3 +4,28 @@ #include "sd-varlink-idl.h" extern const sd_varlink_interface vl_interface_io_systemd_Unit; + +extern const sd_varlink_symbol vl_type_ExecInputType; +extern const sd_varlink_symbol vl_type_ExecOutputType; +extern const sd_varlink_symbol vl_type_ExecUtmpMode; +extern const sd_varlink_symbol vl_type_ExecPreserveMode; +extern const sd_varlink_symbol vl_type_ExecKeyringMode; +extern const sd_varlink_symbol vl_type_MemoryTHP; +extern const sd_varlink_symbol vl_type_ProtectProc; +extern const sd_varlink_symbol vl_type_ProcSubset; +extern const sd_varlink_symbol vl_type_ProtectSystem; +extern const sd_varlink_symbol vl_type_ProtectHome; +extern const sd_varlink_symbol vl_type_PrivateTmp; +extern const sd_varlink_symbol vl_type_PrivateUsers; +extern const sd_varlink_symbol vl_type_ProtectHostname; +extern const sd_varlink_symbol vl_type_ProtectControlGroups; +extern const sd_varlink_symbol vl_type_PrivatePIDs; +extern const sd_varlink_symbol vl_type_PrivateBPF; +extern const sd_varlink_symbol vl_type_CGroupDevicePolicy; +extern const sd_varlink_symbol vl_type_ManagedOOMMode; +extern const sd_varlink_symbol vl_type_ManagedOOMPreference; +extern const sd_varlink_symbol vl_type_CGroupPressureWatch; +extern const sd_varlink_symbol vl_type_CGroupController; +extern const sd_varlink_symbol vl_type_CollectMode; +extern const sd_varlink_symbol vl_type_EmergencyAction; +extern const sd_varlink_symbol vl_type_JobMode; From a1813a40ec77985d975b635653ae924c16afa1b6 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 10 Apr 2026 14:13:27 +0100 Subject: [PATCH 0894/1296] docs: beef up SECURITY.md rules for reporting With yeswehack.com suspended due to funding issues for triagers being worked out, reports on GH are starting to pile up. Explicitly define some ground rules to avoid noise and time wasting. --- docs/SECURITY.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/SECURITY.md b/docs/SECURITY.md index 0993f85da2bb6..6a3102a717416 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -18,3 +18,12 @@ Subscription to the Security Advisories and/or systemd-security mailing list is Those conditions should be backed by publicly accessible information (ideally, a track of posts and commits from the mail address in question). If you fall into one of those categories and wish to be subscribed, contact the maintainers or submit a **[subscription request](https://www.redhat.com/mailman/listinfo/systemd-security)**. + +# Requirements for a Valid Report + +- Please ensure the issue is reproducible on main. +- Please ensure a fully working, end-to-end reproducer is provided. +- Please ensure the reproducer is real-world and not simulated or abstracted. +- Please ensure the reproducer demonstrably violates a security boundary. +- Please understand that most of our maintainers are volunteers and already have a heavy review burden. While we will try to triage and fix issues in a timely manner, we cannot guarantee any fixed timeline for issue resolution. +- While modern industry practices around coordinated disclosures encourage public disclosure to avoid vendors stonewalling researchers, we are an open source project that would gain little from needlessly stonewalling researchers. We thus kindly request that reporters do not publicly disclose issues they have reported to us before an agreed-to disclosure date. From 99fe3283c5548099d718c39408e557932fb7f7c6 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Thu, 5 Mar 2026 03:05:00 -0800 Subject: [PATCH 0895/1296] test: add core-specific varlink enum sync test Add test-varlink-idl-unit that validates all varlink enum types in io.systemd.Unit match their corresponding C string tables. This catches drift between varlink IDL enum definitions and internal enum values. Uses core_test_template since it links against libcore for access to the string table lookup functions. ExecOutput uses TEST_IDL_ENUM_TO_STRING only because the '+' in 'kmsg+console' doesn't survive the underscorify/dashify round-trip. --- src/test/meson.build | 3 +++ src/test/test-varlink-idl-unit.c | 41 ++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 src/test/test-varlink-idl-unit.c diff --git a/src/test/meson.build b/src/test/meson.build index fbb730ab5e148..4cb77505f3dce 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -493,6 +493,9 @@ executables += [ 'sources' : files('test-varlink-idl.c'), 'dependencies' : threads, }, + core_test_template + { + 'sources' : files('test-varlink-idl-unit.c'), + }, test_template + { 'sources' : files('test-watchdog.c'), 'type' : 'unsafe', diff --git a/src/test/test-varlink-idl-unit.c b/src/test/test-varlink-idl-unit.c new file mode 100644 index 0000000000000..408396ae76410 --- /dev/null +++ b/src/test/test-varlink-idl-unit.c @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "cgroup.h" +#include "tests.h" +#include "test-varlink-idl-util.h" +#include "unit.h" +#include "varlink-io.systemd.Unit.h" + +TEST(unit_enums_idl) { + /* ExecContext enums */ + TEST_IDL_ENUM(ExecInput, exec_input, vl_type_ExecInputType); + TEST_IDL_ENUM_TO_STRING(ExecOutput, exec_output, vl_type_ExecOutputType); + TEST_IDL_ENUM(ExecUtmpMode, exec_utmp_mode, vl_type_ExecUtmpMode); + TEST_IDL_ENUM(ExecPreserveMode, exec_preserve_mode, vl_type_ExecPreserveMode); + TEST_IDL_ENUM(ExecKeyringMode, exec_keyring_mode, vl_type_ExecKeyringMode); + TEST_IDL_ENUM(MemoryTHP, memory_thp, vl_type_MemoryTHP); + TEST_IDL_ENUM(ProtectProc, protect_proc, vl_type_ProtectProc); + TEST_IDL_ENUM(ProcSubset, proc_subset, vl_type_ProcSubset); + TEST_IDL_ENUM(ProtectSystem, protect_system, vl_type_ProtectSystem); + TEST_IDL_ENUM(ProtectHome, protect_home, vl_type_ProtectHome); + TEST_IDL_ENUM(PrivateTmp, private_tmp, vl_type_PrivateTmp); + TEST_IDL_ENUM(PrivateUsers, private_users, vl_type_PrivateUsers); + TEST_IDL_ENUM(ProtectHostname, protect_hostname, vl_type_ProtectHostname); + TEST_IDL_ENUM(ProtectControlGroups, protect_control_groups, vl_type_ProtectControlGroups); + TEST_IDL_ENUM(PrivatePIDs, private_pids, vl_type_PrivatePIDs); + TEST_IDL_ENUM(PrivateBPF, private_bpf, vl_type_PrivateBPF); + + /* CGroupContext enums */ + TEST_IDL_ENUM(CGroupDevicePolicy, cgroup_device_policy, vl_type_CGroupDevicePolicy); + TEST_IDL_ENUM(ManagedOOMMode, managed_oom_mode, vl_type_ManagedOOMMode); + TEST_IDL_ENUM(ManagedOOMPreference, managed_oom_preference, vl_type_ManagedOOMPreference); + TEST_IDL_ENUM(CGroupPressureWatch, cgroup_pressure_watch, vl_type_CGroupPressureWatch); + TEST_IDL_ENUM(CGroupController, cgroup_controller, vl_type_CGroupController); + + /* UnitContext enums */ + TEST_IDL_ENUM(CollectMode, collect_mode, vl_type_CollectMode); + TEST_IDL_ENUM(EmergencyAction, emergency_action, vl_type_EmergencyAction); + TEST_IDL_ENUM(JobMode, job_mode, vl_type_JobMode); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); From 23f0c9070baaeb4fbc4cb2ff1da1650b053caeb1 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Fri, 10 Apr 2026 06:12:26 -0700 Subject: [PATCH 0896/1296] news: new record about strings vs enums in varlink --- NEWS | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/NEWS b/NEWS index 0fb8ed9a1a9d3..2d32bd08b4a01 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,16 @@ CHANGES WITH 261 in spe: has been repurposed as a standalone switch (without argument) to select the user service manager scope, matching --system. + * Several configuration fields in the io.systemd.Unit varlink interface + that were previously exposed as plain strings have been converted to + proper enum types. This adds type safety and IDL-level validation. + The output wire format now uses underscores instead of dashes and + plus signs in enum values (e.g. "tty-force" becomes "tty_force", + "kmsg+console" becomes "kmsg_console"). The previous use of plain + strings for these well-defined enumerations is considered a bug. + Affected enum types: ExecInputType, ExecOutputType, ProtectHome, + CGroupController, CollectMode, EmergencyAction, JobMode. + * It was discovered that systemd-stub does not measure all the events it measures to the TPM to the hardware CC registers (e.g. Intel TDX RTMRs) using EFI_CC_MEASUREMENT_PROTOCOL. In particular, devicetree, From 6b1a59d59426cdda56648b00394addde2d454418 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 9 Apr 2026 14:54:21 +0000 Subject: [PATCH 0897/1296] sd-json: add JsonStream transport-layer module and migrate sd-varlink MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces JsonStream, a generic transport layer for JSON-line message exchange over a pair of file descriptors. It owns the input/output buffers, SCM_RIGHTS fd passing, the deferred output queue, the read/write/parse step functions, sd-event integration (input/output/time event sources), the idle timeout machinery, and peer credential caching, but knows nothing about the specific JSON protocol on top — the consumer drives its state machine via phase/dispatch callbacks supplied at construction. sd-varlink is reworked to delegate the entire transport layer to a JsonStream owned by sd_varlink. The varlink struct drops every transport-related field (input/output buffers and fds, output queue, fd-passing state, ucred/pidfd cache, prefer_read/write fallback, idle timeout, description, event sources) — all of that lives in JsonStream now. What remains in sd_varlink is the varlink-protocol state machine (state, n_pending, current/previous/sentinel, server linkage, peer credentials accounting, exec_pidref, the varlink-specific quit and defer sources) and a thin wrapper layer over the JsonStream API. The should_disconnect / get_timeout / get_events / wait helpers all live in JsonStream now and are driven by a JsonStreamPhase the consumer reports via its phase callback. --- src/libsystemd/meson.build | 1 + src/libsystemd/sd-json/json-stream.c | 1438 ++++++++++++++++++ src/libsystemd/sd-json/json-stream.h | 267 ++++ src/libsystemd/sd-varlink/sd-varlink.c | 1399 +++-------------- src/libsystemd/sd-varlink/varlink-internal.h | 116 +- 5 files changed, 1959 insertions(+), 1262 deletions(-) create mode 100644 src/libsystemd/sd-json/json-stream.c create mode 100644 src/libsystemd/sd-json/json-stream.h diff --git a/src/libsystemd/meson.build b/src/libsystemd/meson.build index 976f0e998766c..08d8d7c5c39e7 100644 --- a/src/libsystemd/meson.build +++ b/src/libsystemd/meson.build @@ -80,6 +80,7 @@ sd_login_sources = files('sd-login/sd-login.c') ############################################################ sd_json_sources = files( + 'sd-json/json-stream.c', 'sd-json/json-util.c', 'sd-json/sd-json.c', ) diff --git a/src/libsystemd/sd-json/json-stream.c b/src/libsystemd/sd-json/json-stream.c new file mode 100644 index 0000000000000..d378d18a282b8 --- /dev/null +++ b/src/libsystemd/sd-json/json-stream.c @@ -0,0 +1,1438 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include + +#include "sd-event.h" +#include "sd-json.h" + +#include "alloc-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "iovec-util.h" +#include "json-stream.h" +#include "list.h" +#include "log.h" +#include "memory-util.h" +#include "process-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "time-util.h" +#include "user-util.h" + +#define JSON_STREAM_BUFFER_MAX_DEFAULT (16U * 1024U * 1024U) +#define JSON_STREAM_READ_SIZE_DEFAULT (64U * 1024U) +#define JSON_STREAM_QUEUE_MAX_DEFAULT (64U * 1024U) +#define JSON_STREAM_FDS_MAX (16U * 1024U) + +struct JsonStreamQueueItem { + LIST_FIELDS(JsonStreamQueueItem, queue); + sd_json_variant *data; + size_t n_fds; + int fds[]; +}; + +static const char* json_stream_description(const JsonStream *s) { + return (s ? s->description : NULL) ?: "json-stream"; +} + +/* Returns the size of the framing delimiter in bytes: strlen(delimiter) for multi-char + * delimiters (e.g. "\r\n"), or 1 for the default NUL-byte delimiter (delimiter == NULL). */ +static size_t json_stream_delimiter_size(const JsonStream *s) { + return strlen_ptr(s->delimiter) ?: 1; +} + +static usec_t json_stream_now(const JsonStream *s) { + usec_t t; + + if (s->event && sd_event_now(s->event, CLOCK_MONOTONIC, &t) >= 0) + return t; + + return now(CLOCK_MONOTONIC); +} + +#define json_stream_log(s, fmt, ...) \ + log_debug("%s: " fmt, json_stream_description(s), ##__VA_ARGS__) + +#define json_stream_log_errno(s, error, fmt, ...) \ + log_debug_errno((error), "%s: " fmt, json_stream_description(s), ##__VA_ARGS__) + +sd_json_variant** json_stream_queue_item_get_data(JsonStreamQueueItem *q) { + assert(q); + return &q->data; +} + +JsonStreamQueueItem* json_stream_queue_item_free(JsonStreamQueueItem *q) { + if (!q) + return NULL; + + sd_json_variant_unref(q->data); + close_many(q->fds, q->n_fds); + + return mfree(q); +} + +static JsonStreamQueueItem* json_stream_queue_item_new(sd_json_variant *m, const int fds[], size_t n_fds) { + JsonStreamQueueItem *q; + + assert(m); + assert(fds || n_fds == 0); + + size_t sz = sizeof(int); + if (!MUL_SAFE(&sz, sz, n_fds) || + !INC_SAFE(&sz, offsetof(JsonStreamQueueItem, fds))) + return NULL; + + q = malloc(sz); + if (!q) + return NULL; + + *q = (JsonStreamQueueItem) { + .data = sd_json_variant_ref(m), + .n_fds = n_fds, + }; + + memcpy_safe(q->fds, fds, n_fds * sizeof(int)); + + return TAKE_PTR(q); +} + +int json_stream_init(JsonStream *s, const JsonStreamParams *params) { + assert(s); + assert(params); + assert(params->phase); + assert(params->dispatch); + + char *delimiter = NULL; + if (params->delimiter) { + delimiter = strdup(params->delimiter); + if (!delimiter) + return -ENOMEM; + } + + *s = (JsonStream) { + .delimiter = delimiter, + .buffer_max = params->buffer_max > 0 ? params->buffer_max : JSON_STREAM_BUFFER_MAX_DEFAULT, + .read_chunk = params->read_chunk > 0 ? params->read_chunk : JSON_STREAM_READ_SIZE_DEFAULT, + .queue_max = params->queue_max > 0 ? params->queue_max : JSON_STREAM_QUEUE_MAX_DEFAULT, + .phase_cb = params->phase, + .dispatch_cb = params->dispatch, + .userdata = params->userdata, + .input_fd = -EBADF, + .output_fd = -EBADF, + .timeout = USEC_INFINITY, + .last_activity = USEC_INFINITY, + .ucred = UCRED_INVALID, + .peer_pidfd = -EBADF, + .af = -1, + }; + + return 0; +} + +static void json_stream_clear(JsonStream *s) { + if (!s) + return; + + json_stream_detach_event(s); + + s->delimiter = mfree(s->delimiter); + s->description = mfree(s->description); + + if (s->input_fd != s->output_fd) { + s->input_fd = safe_close(s->input_fd); + s->output_fd = safe_close(s->output_fd); + } else + s->output_fd = s->input_fd = safe_close(s->input_fd); + + s->peer_pidfd = safe_close(s->peer_pidfd); + s->ucred_acquired = false; + s->af = -1; + + close_many(s->input_fds, s->n_input_fds); + s->input_fds = mfree(s->input_fds); + s->n_input_fds = 0; + + s->input_buffer = FLAGS_SET(s->flags, JSON_STREAM_INPUT_SENSITIVE) ? erase_and_free(s->input_buffer) : mfree(s->input_buffer); + s->input_buffer_index = s->input_buffer_size = s->input_buffer_unscanned = 0; + + s->output_buffer = FLAGS_SET(s->flags, JSON_STREAM_OUTPUT_BUFFER_SENSITIVE) ? erase_and_free(s->output_buffer) : mfree(s->output_buffer); + s->output_buffer_index = s->output_buffer_size = 0; + s->flags &= ~JSON_STREAM_OUTPUT_BUFFER_SENSITIVE; + + s->input_control_buffer = mfree(s->input_control_buffer); + s->input_control_buffer_size = 0; + + close_many(s->output_fds, s->n_output_fds); + s->output_fds = mfree(s->output_fds); + s->n_output_fds = 0; + + close_many(s->pushed_fds, s->n_pushed_fds); + s->pushed_fds = mfree(s->pushed_fds); + s->n_pushed_fds = 0; + + LIST_CLEAR(queue, s->output_queue, json_stream_queue_item_free); + s->output_queue_tail = NULL; + s->n_output_queue = 0; +} + +void json_stream_done(JsonStream *s) { + if (!s) + return; + + json_stream_clear(s); +} + +int json_stream_set_description(JsonStream *s, const char *description) { + assert(s); + return free_and_strdup(&s->description, description); +} + +const char* json_stream_get_description(const JsonStream *s) { + assert(s); + return s->description; +} + +int json_stream_connect_address(JsonStream *s, const char *address) { + union sockaddr_union sockaddr; + int r; + + assert(s); + assert(address); + + _cleanup_close_ int sock_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (sock_fd < 0) + return json_stream_log_errno(s, errno, "Failed to create AF_UNIX socket: %m"); + + sock_fd = fd_move_above_stdio(sock_fd); + + r = sockaddr_un_set_path(&sockaddr.un, address); + if (r < 0) { + if (r != -ENAMETOOLONG) + return json_stream_log_errno(s, r, "Failed to set socket address '%s': %m", address); + + /* Path too long to fit into sockaddr_un, connect via O_PATH instead. */ + r = connect_unix_path(sock_fd, AT_FDCWD, address); + } else + r = RET_NERRNO(connect(sock_fd, &sockaddr.sa, r)); + + if (r < 0) { + if (!IN_SET(r, -EAGAIN, -EINPROGRESS)) + return json_stream_log_errno(s, r, "Failed to connect to %s: %m", address); + + /* The connect() is being processed in the background. As long as that's the + * case the socket is in a special state: we can poll it for POLLOUT, but + * write()s before POLLOUT will fail with ENOTCONN (rather than EAGAIN). Since + * ENOTCONN can mean two different things (not yet connected vs. already + * disconnected), we track this as a separate flag. */ + s->flags |= JSON_STREAM_CONNECTING; + } + + int fd = TAKE_FD(sock_fd); + return json_stream_attach_fds(s, fd, fd); +} + +int json_stream_attach_fds(JsonStream *s, int input_fd, int output_fd) { + struct stat st; + + assert(s); + + /* NB: input_fd and output_fd are donated to the JsonStream instance! */ + + if (s->input_fd != s->output_fd) { + safe_close(s->input_fd); + safe_close(s->output_fd); + } else + safe_close(s->input_fd); + + s->input_fd = input_fd; + s->output_fd = output_fd; + s->flags &= ~(JSON_STREAM_PREFER_READ|JSON_STREAM_PREFER_WRITE); + + /* Detect non-socket fds up front so the read/write paths use read()/write() for + * non-socket fds and send()/recv() for sockets (mostly for MSG_NOSIGNAL). */ + if (input_fd >= 0) { + if (fstat(input_fd, &st) < 0) + return -errno; + if (!S_ISSOCK(st.st_mode)) + s->flags |= JSON_STREAM_PREFER_READ; + } + + if (output_fd >= 0 && output_fd != input_fd) { + if (fstat(output_fd, &st) < 0) + return -errno; + if (!S_ISSOCK(st.st_mode)) + s->flags |= JSON_STREAM_PREFER_WRITE; + } else if (FLAGS_SET(s->flags, JSON_STREAM_PREFER_READ)) + s->flags |= JSON_STREAM_PREFER_WRITE; + + return 0; +} + +int json_stream_connect_fd_pair(JsonStream *s, int input_fd, int output_fd) { + int r; + + assert(s); + assert(input_fd >= 0); + assert(output_fd >= 0); + + r = fd_nonblock(input_fd, true); + if (r < 0) + return json_stream_log_errno(s, r, "Failed to make input fd %d nonblocking: %m", input_fd); + + if (input_fd != output_fd) { + r = fd_nonblock(output_fd, true); + if (r < 0) + return json_stream_log_errno(s, r, "Failed to make output fd %d nonblocking: %m", output_fd); + } + + return json_stream_attach_fds(s, input_fd, output_fd); +} + +bool json_stream_flags_set(const JsonStream *s, JsonStreamFlags flags) { + assert(s); + assert((flags & ~(JSON_STREAM_BOUNDED_READS|JSON_STREAM_INPUT_SENSITIVE|JSON_STREAM_ALLOW_FD_PASSING_INPUT|JSON_STREAM_ALLOW_FD_PASSING_OUTPUT)) == 0); + + return FLAGS_SET(s->flags, flags); +} + +/* Multiple flags may be passed — all are set or cleared together. */ +void json_stream_set_flags(JsonStream *s, JsonStreamFlags flags, bool b) { + assert(s); + assert((flags & ~(JSON_STREAM_BOUNDED_READS|JSON_STREAM_INPUT_SENSITIVE)) == 0); + + SET_FLAG(s->flags, flags, b); +} + +bool json_stream_has_buffered_input(const JsonStream *s) { + assert(s); + return s->input_buffer_size > 0; +} + +/* Query the consumer's current phase. The callback is mandatory (asserted at construction + * time), so we can call it unconditionally. */ +static JsonStreamPhase json_stream_current_phase(const JsonStream *s) { + assert(s); + return s->phase_cb(s->userdata); +} + +/* Both READING and AWAITING_REPLY mean "we want POLLIN and would lose if the read side + * died" — they only differ in whether the idle timeout is in force. */ +static bool phase_is_reading(JsonStreamPhase p) { + return IN_SET(p, JSON_STREAM_PHASE_READING, JSON_STREAM_PHASE_AWAITING_REPLY); +} + +bool json_stream_should_disconnect(const JsonStream *s) { + assert(s); + + /* Carefully decide when the consumer should initiate a teardown. We err on the side + * of staying around so half-open connections can flush remaining data and reads can + * surface buffered messages before we tear everything down. */ + + /* Wait until any in-flight async connect() completes — there's nothing reasonable + * to do until we know whether the socket is connected or not. */ + if (FLAGS_SET(s->flags, JSON_STREAM_CONNECTING)) + return false; + + /* Still bytes to write and we can write? Stay around so the flush can complete. */ + if (s->output_buffer_size > 0 && !FLAGS_SET(s->flags, JSON_STREAM_WRITE_DISCONNECTED)) + return false; + + /* Both sides gone already? Then there's no point in lingering. */ + if (FLAGS_SET(s->flags, JSON_STREAM_READ_DISCONNECTED|JSON_STREAM_WRITE_DISCONNECTED)) + return true; + + JsonStreamPhase phase = json_stream_current_phase(s); + + /* Caller is waiting for input but the read side is shut down — we'll never see + * another message. */ + if (phase_is_reading(phase) && FLAGS_SET(s->flags, JSON_STREAM_READ_DISCONNECTED)) + return true; + + /* Idle client whose write side has died, or we saw POLLHUP. We explicitly check for + * POLLHUP because we likely won't notice the write side being down via send() if we + * never wrote anything in the first place. */ + if (phase == JSON_STREAM_PHASE_IDLE_CLIENT && + (s->flags & (JSON_STREAM_WRITE_DISCONNECTED|JSON_STREAM_GOT_POLLHUP))) + return true; + + /* Caller has more output to send but the peer hung up, and we're either out of + * bytes or already saw a write error. Nothing left to do. */ + if (phase == JSON_STREAM_PHASE_PENDING_OUTPUT && + (FLAGS_SET(s->flags, JSON_STREAM_WRITE_DISCONNECTED) || s->output_buffer_size == 0) && + FLAGS_SET(s->flags, JSON_STREAM_GOT_POLLHUP)) + return true; + + return false; +} + +int json_stream_get_events(const JsonStream *s) { + int ret = 0; + + assert(s); + + /* While an asynchronous connect() is still in flight we only ask for POLLOUT, which + * tells us once the connection is fully established. We must not read or write before + * that. */ + if (FLAGS_SET(s->flags, JSON_STREAM_CONNECTING)) + return POLLOUT; + + if (phase_is_reading(json_stream_current_phase(s)) && + !FLAGS_SET(s->flags, JSON_STREAM_READ_DISCONNECTED) && + s->input_buffer_unscanned == 0) + ret |= POLLIN; + + if (!FLAGS_SET(s->flags, JSON_STREAM_WRITE_DISCONNECTED) && (s->output_queue || s->output_buffer_size > 0)) + ret |= POLLOUT; + + return ret; +} + +static void json_stream_handle_revents(JsonStream *s, int revents) { + assert(s); + + if (FLAGS_SET(s->flags, JSON_STREAM_CONNECTING)) { + /* If we have seen POLLOUT or POLLHUP on a socket we are asynchronously waiting a + * connect() to complete on, we know we are ready. We don't read the connection + * error here though — we'll get it on the next read() or write(). */ + if ((revents & (POLLOUT|POLLHUP)) == 0) + return; + + json_stream_log(s, "Asynchronous connection completed."); + s->flags &= ~JSON_STREAM_CONNECTING; + return; + } + + /* Note that we don't care much about POLLIN/POLLOUT here, we'll just try reading and + * writing what we can. However, we do care about POLLHUP to detect connection + * termination even if we momentarily don't want to read nor write anything. */ + if (FLAGS_SET(revents, POLLHUP)) { + json_stream_log(s, "Got POLLHUP from socket."); + s->flags |= JSON_STREAM_GOT_POLLHUP; + } +} + +int json_stream_wait(JsonStream *s, usec_t timeout) { + int events, r; + + assert(s); + + events = json_stream_get_events(s); + if (events < 0) + return events; + + /* MIN the caller's timeout with our own deadline (if any) so that we wake up to + * fire the idle timeout. */ + usec_t deadline = json_stream_get_timeout(s); + if (deadline != USEC_INFINITY) + timeout = MIN(timeout, usec_sub_unsigned(deadline, now(CLOCK_MONOTONIC))); + + struct pollfd pollfd[2]; + size_t n_poll_fd = 0; + + if (s->input_fd == s->output_fd) { + pollfd[n_poll_fd++] = (struct pollfd) { + .fd = s->input_fd, + .events = events, + }; + } else { + pollfd[n_poll_fd++] = (struct pollfd) { + .fd = s->input_fd, + .events = events & POLLIN, + }; + pollfd[n_poll_fd++] = (struct pollfd) { + .fd = s->output_fd, + .events = events & POLLOUT, + }; + } + + r = ppoll_usec(pollfd, n_poll_fd, timeout); + if (ERRNO_IS_NEG_TRANSIENT(r)) + /* Treat EINTR as not a timeout, but also nothing happened, and the caller gets + * a chance to call back into us. */ + return 1; + if (r <= 0) + return r; + + int revents = 0; + FOREACH_ARRAY(p, pollfd, n_poll_fd) + revents |= p->revents; + + json_stream_handle_revents(s, revents); + return 1; +} + +/* ===== Timeout management ===== */ + +static usec_t json_stream_get_deadline(const JsonStream *s) { + assert(s); + + return usec_add(s->last_activity, s->timeout); +} + +usec_t json_stream_get_timeout(const JsonStream *s) { + assert(s); + + /* The deadline is in force only when the consumer is in PHASE_AWAITING_REPLY. In + * other phases (idle server, between operations) we ignore the cached deadline even + * if it's still set from a previous operation. */ + if (json_stream_current_phase(s) != JSON_STREAM_PHASE_AWAITING_REPLY) + return USEC_INFINITY; + + return json_stream_get_deadline(s); +} + +static void json_stream_rearm_time_source(JsonStream *s) { + int r; + + assert(s); + + if (!s->time_event_source) + return; + + usec_t deadline = json_stream_get_timeout(s); + if (deadline == USEC_INFINITY) { + (void) sd_event_source_set_enabled(s->time_event_source, SD_EVENT_OFF); + return; + } + + r = sd_event_source_set_time(s->time_event_source, deadline); + if (r < 0) { + json_stream_log_errno(s, r, "Failed to set time source deadline: %m"); + return; + } + + (void) sd_event_source_set_enabled(s->time_event_source, SD_EVENT_ON); +} + +void json_stream_set_timeout(JsonStream *s, usec_t timeout) { + assert(s); + + s->timeout = timeout; + + /* If the configured timeout changes mid-flight, rearm the time source so the new + * deadline takes effect immediately rather than waiting for the next mark_activity + * or successful write. */ + json_stream_rearm_time_source(s); +} + +void json_stream_mark_activity(JsonStream *s) { + assert(s); + + s->last_activity = json_stream_now(s); + json_stream_rearm_time_source(s); +} + +static int json_stream_acquire_peer_ucred(JsonStream *s, struct ucred *ret) { + int r; + + assert(s); + assert(ret); + + if (!s->ucred_acquired) { + /* Peer credentials only make sense for a bidirectional socket. */ + if (s->input_fd != s->output_fd) + return -EBADF; + + r = getpeercred(s->input_fd, &s->ucred); + if (r < 0) + return r; + + s->ucred_acquired = true; + } + + *ret = s->ucred; + return 0; +} + +int json_stream_acquire_peer_uid(JsonStream *s, uid_t *ret) { + struct ucred ucred; + int r; + + assert(s); + assert(ret); + + r = json_stream_acquire_peer_ucred(s, &ucred); + if (r < 0) + return json_stream_log_errno(s, r, "Failed to acquire credentials: %m"); + + if (!uid_is_valid(ucred.uid)) + return json_stream_log_errno(s, SYNTHETIC_ERRNO(ENODATA), "Peer UID is invalid."); + + *ret = ucred.uid; + return 0; +} + +int json_stream_acquire_peer_gid(JsonStream *s, gid_t *ret) { + struct ucred ucred; + int r; + + assert(s); + assert(ret); + + r = json_stream_acquire_peer_ucred(s, &ucred); + if (r < 0) + return json_stream_log_errno(s, r, "Failed to acquire credentials: %m"); + + if (!gid_is_valid(ucred.gid)) + return json_stream_log_errno(s, SYNTHETIC_ERRNO(ENODATA), "Peer GID is invalid."); + + *ret = ucred.gid; + return 0; +} + +int json_stream_acquire_peer_pid(JsonStream *s, pid_t *ret) { + struct ucred ucred; + int r; + + assert(s); + assert(ret); + + r = json_stream_acquire_peer_ucred(s, &ucred); + if (r < 0) + return json_stream_log_errno(s, r, "Failed to acquire credentials: %m"); + + if (!pid_is_valid(ucred.pid)) + return json_stream_log_errno(s, SYNTHETIC_ERRNO(ENODATA), "Peer PID is invalid."); + + *ret = ucred.pid; + return 0; +} + +int json_stream_get_peer_ucred(const JsonStream *s, struct ucred *ret) { + assert(s); + assert(ret); + + if (!s->ucred_acquired) + return -ENODATA; + + *ret = s->ucred; + return 0; +} + +void json_stream_set_peer_ucred(JsonStream *s, const struct ucred *ucred) { + assert(s); + assert(ucred); + + s->ucred = *ucred; + s->ucred_acquired = true; +} + +int json_stream_acquire_peer_pidfd(JsonStream *s) { + assert(s); + + if (s->peer_pidfd >= 0) + return s->peer_pidfd; + + if (s->input_fd != s->output_fd) + return json_stream_log_errno(s, SYNTHETIC_ERRNO(EBADF), "Failed to acquire pidfd of peer: separate input/output fds"); + + s->peer_pidfd = getpeerpidfd(s->input_fd); + if (s->peer_pidfd < 0) + return json_stream_log_errno(s, s->peer_pidfd, "Failed to acquire pidfd of peer: %m"); + + return s->peer_pidfd; +} + +static int json_stream_verify_unix_socket(JsonStream *s) { + assert(s); + + /* Returns: + * • 0 if this is an AF_UNIX socket + * • -ENOTSOCK if this is not a socket at all + * • -ENOMEDIUM if this is a socket, but not an AF_UNIX socket + * + * The result is cached after the first call. af < 0 = unchecked, af == AF_UNSPEC = + * checked but not a socket, otherwise af is the resolved address family. */ + + if (s->af < 0) { + /* If we have distinct input + output fds, we don't consider ourselves to be + * connected via a regular AF_UNIX socket. */ + if (s->input_fd != s->output_fd) { + s->af = AF_UNSPEC; + return -ENOTSOCK; + } + + struct stat st; + + if (fstat(s->input_fd, &st) < 0) + return -errno; + if (!S_ISSOCK(st.st_mode)) { + s->af = AF_UNSPEC; + return -ENOTSOCK; + } + + s->af = socket_get_family(s->input_fd); + if (s->af < 0) + return s->af; + } + + if (s->af == AF_UNIX) + return 0; + if (s->af == AF_UNSPEC) + return -ENOTSOCK; + + return -ENOMEDIUM; +} + +int json_stream_set_allow_fd_passing_input(JsonStream *s, bool enabled, bool with_sockopt) { + int r; + + assert(s); + + if (FLAGS_SET(s->flags, JSON_STREAM_ALLOW_FD_PASSING_INPUT) == enabled) + return 0; + + r = json_stream_verify_unix_socket(s); + if (r < 0) { + /* If the caller is disabling, accept the verify failure silently — we just + * leave the flag as it was (or set it to false if currently true). */ + if (!enabled) { + s->flags &= ~JSON_STREAM_ALLOW_FD_PASSING_INPUT; + return 0; + } + return r; + } + + if (with_sockopt) { + r = setsockopt_int(s->input_fd, SOL_SOCKET, SO_PASSRIGHTS, enabled); + if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r)) + json_stream_log_errno(s, r, "Failed to set SO_PASSRIGHTS socket option: %m"); + } + + SET_FLAG(s->flags, JSON_STREAM_ALLOW_FD_PASSING_INPUT, enabled); + return 1; +} + +int json_stream_set_allow_fd_passing_output(JsonStream *s, bool enabled) { + int r; + + assert(s); + + if (FLAGS_SET(s->flags, JSON_STREAM_ALLOW_FD_PASSING_OUTPUT) == enabled) + return 0; + + r = json_stream_verify_unix_socket(s); + if (r < 0) + return r; + + SET_FLAG(s->flags, JSON_STREAM_ALLOW_FD_PASSING_OUTPUT, enabled); + return 1; +} + +/* ===== sd-event integration ===== */ + +static int json_stream_io_callback(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + JsonStream *s = ASSERT_PTR(userdata); + int r; + + json_stream_handle_revents(s, revents); + + r = s->dispatch_cb(s->userdata); + if (r < 0) + json_stream_log_errno(s, r, "Dispatch callback failed, ignoring: %m"); + + return 1; +} + +static int json_stream_time_callback(sd_event_source *source, uint64_t usec, void *userdata) { + JsonStream *s = ASSERT_PTR(userdata); + int r; + + /* Disable the source: it must not fire again until activity is marked. The consumer + * notices the timeout by comparing now() to json_stream_get_timeout() in its dispatch + * callback. */ + (void) sd_event_source_set_enabled(s->time_event_source, SD_EVENT_OFF); + + r = s->dispatch_cb(s->userdata); + if (r < 0) + json_stream_log_errno(s, r, "Dispatch callback failed, ignoring: %m"); + + return 1; +} + +static int json_stream_prepare_callback(sd_event_source *source, void *userdata) { + JsonStream *s = ASSERT_PTR(userdata); + int r, e; + + e = json_stream_get_events(s); + if (e < 0) + return e; + + if (s->input_event_source == s->output_event_source) + /* Same fd for input + output */ + r = sd_event_source_set_io_events(s->input_event_source, e); + else { + r = sd_event_source_set_io_events(s->input_event_source, e & POLLIN); + if (r >= 0) + r = sd_event_source_set_io_events(s->output_event_source, e & POLLOUT); + } + if (r < 0) + return json_stream_log_errno(s, r, "Failed to set io events: %m"); + + /* Rearm the timeout on every prepare cycle so that phase transitions (e.g. entering + * AWAITING_REPLY) are picked up without requiring the consumer to explicitly call + * mark_activity at every state change. */ + json_stream_rearm_time_source(s); + + return 1; +} + +void json_stream_detach_event(JsonStream *s) { + if (!s) + return; + + s->input_event_source = sd_event_source_disable_unref(s->input_event_source); + s->output_event_source = sd_event_source_disable_unref(s->output_event_source); + s->time_event_source = sd_event_source_disable_unref(s->time_event_source); + s->event = sd_event_unref(s->event); +} + +sd_event* json_stream_get_event(const JsonStream *s) { + assert(s); + return s->event; +} + +int json_stream_attach_event(JsonStream *s, sd_event *event, int64_t priority) { + int r; + + assert(s); + assert(!s->event); + assert(s->input_fd >= 0); + assert(s->output_fd >= 0); + + if (event) + s->event = sd_event_ref(event); + else { + r = sd_event_default(&s->event); + if (r < 0) + return json_stream_log_errno(s, r, "Failed to acquire default event loop: %m"); + } + + r = sd_event_add_io(s->event, &s->input_event_source, s->input_fd, 0, json_stream_io_callback, s); + if (r < 0) + goto fail; + + r = sd_event_source_set_prepare(s->input_event_source, json_stream_prepare_callback); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(s->input_event_source, priority); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(s->input_event_source, "json-stream-input"); + + if (s->input_fd == s->output_fd) + s->output_event_source = sd_event_source_ref(s->input_event_source); + else { + r = sd_event_add_io(s->event, &s->output_event_source, s->output_fd, 0, json_stream_io_callback, s); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(s->output_event_source, priority); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(s->output_event_source, "json-stream-output"); + } + + r = sd_event_add_time(s->event, &s->time_event_source, CLOCK_MONOTONIC, /* usec= */ 0, /* accuracy= */ 0, + json_stream_time_callback, s); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(s->time_event_source, priority); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(s->time_event_source, "json-stream-time"); + + /* Initially disabled — only enabled by mark_activity once a timeout is configured. */ + (void) sd_event_source_set_enabled(s->time_event_source, SD_EVENT_OFF); + json_stream_rearm_time_source(s); + + return 0; + +fail: + json_stream_log_errno(s, r, "Failed to attach event source: %m"); + json_stream_detach_event(s); + return r; +} + +int json_stream_flush(JsonStream *s) { + int ret = 0, r; + + assert(s); + + for (;;) { + if (s->output_buffer_size == 0 && !s->output_queue) + break; + if (FLAGS_SET(s->flags, JSON_STREAM_WRITE_DISCONNECTED)) + return -ECONNRESET; + + r = json_stream_write(s); + if (r < 0) + return r; + if (r > 0) { + ret = 1; + continue; + } + + r = json_stream_wait(s, USEC_INFINITY); + if (ERRNO_IS_NEG_TRANSIENT(r)) + continue; + if (r < 0) + return json_stream_log_errno(s, r, "Poll failed on fd: %m"); + assert(r > 0); + } + + return ret; +} + +int json_stream_push_fd(JsonStream *s, int fd) { + int i; + + assert(s); + assert(fd >= 0); + + if (s->n_pushed_fds >= SCM_MAX_FD) /* Kernel doesn't support more than 253 fds per message */ + return -ENOBUFS; + + if (!GREEDY_REALLOC(s->pushed_fds, s->n_pushed_fds + 1)) + return -ENOMEM; + + i = (int) s->n_pushed_fds; + s->pushed_fds[s->n_pushed_fds++] = fd; + return i; +} + +void json_stream_reset_pushed_fds(JsonStream *s) { + assert(s); + + close_many(s->pushed_fds, s->n_pushed_fds); + s->n_pushed_fds = 0; +} + +int json_stream_peek_input_fd(const JsonStream *s, size_t i) { + assert(s); + + if (i >= s->n_input_fds) + return -ENXIO; + + return s->input_fds[i]; +} + +int json_stream_take_input_fd(JsonStream *s, size_t i) { + assert(s); + + if (i >= s->n_input_fds) + return -ENXIO; + + return TAKE_FD(s->input_fds[i]); +} + +size_t json_stream_get_n_input_fds(const JsonStream *s) { + assert(s); + return s->n_input_fds; +} + +void json_stream_close_input_fds(JsonStream *s) { + assert(s); + + close_many(s->input_fds, s->n_input_fds); + s->input_fds = mfree(s->input_fds); + s->n_input_fds = 0; +} + +/* ===== Output formatting ===== */ + +static int json_stream_format_json(JsonStream *s, sd_json_variant *m) { + _cleanup_(erase_and_freep) char *text = NULL; + ssize_t sz, r; + + assert(s); + assert(m); + + sz = sd_json_variant_format(m, /* flags= */ 0, &text); + if (sz < 0) + return sz; + assert(text[sz] == '\0'); + + size_t dsz = json_stream_delimiter_size(s); + + /* Append the framing delimiter after the formatted JSON. For varlink (delimiter == + * NULL) this keeps the trailing NUL already placed by sd_json_variant_format(); for + * multi-char delimiters (e.g. "\r\n") we grow the buffer and copy them in. */ + if (s->delimiter) { + if (!GREEDY_REALLOC(text, sz + dsz)) + return -ENOMEM; + memcpy(text + sz, s->delimiter, dsz); + } + + if (s->output_buffer_size + sz + dsz > s->buffer_max) + return -ENOBUFS; + + if (DEBUG_LOGGING) { + _cleanup_(erase_and_freep) char *censored_text = NULL; + + /* Suppress sensitive fields in the debug output */ + r = sd_json_variant_format(m, SD_JSON_FORMAT_CENSOR_SENSITIVE, &censored_text); + if (r >= 0) + json_stream_log(s, "Sending message: %s", censored_text); + } + + if (s->output_buffer_size == 0) { + if (FLAGS_SET(s->flags, JSON_STREAM_OUTPUT_BUFFER_SENSITIVE)) { + s->output_buffer = erase_and_free(s->output_buffer); + s->flags &= ~JSON_STREAM_OUTPUT_BUFFER_SENSITIVE; + } + + free_and_replace(s->output_buffer, text); + + s->output_buffer_size = sz + dsz; + s->output_buffer_index = 0; + + } else if (!FLAGS_SET(s->flags, JSON_STREAM_OUTPUT_BUFFER_SENSITIVE) && s->output_buffer_index == 0) { + if (!GREEDY_REALLOC(s->output_buffer, s->output_buffer_size + sz + dsz)) + return -ENOMEM; + + memcpy(s->output_buffer + s->output_buffer_size, text, sz + dsz); + s->output_buffer_size += sz + dsz; + } else { + const size_t new_size = s->output_buffer_size + sz + dsz; + + char *n = new(char, new_size); + if (!n) + return -ENOMEM; + + memcpy(mempcpy(n, s->output_buffer + s->output_buffer_index, s->output_buffer_size), text, sz + dsz); + + if (FLAGS_SET(s->flags, JSON_STREAM_OUTPUT_BUFFER_SENSITIVE)) + s->output_buffer = erase_and_free(s->output_buffer); + else + free(s->output_buffer); + s->output_buffer = n; + s->output_buffer_size = new_size; + s->output_buffer_index = 0; + } + + if (sd_json_variant_is_sensitive_recursive(m)) + s->flags |= JSON_STREAM_OUTPUT_BUFFER_SENSITIVE; + else + text = mfree(text); /* Skip the erase_and_free() destructor declared above */ + + return 0; +} + +static int json_stream_format_queue(JsonStream *s) { + int r; + + assert(s); + + /* Drain entries out of the output queue and format them into the output buffer. Stop + * if there are unwritten output_fds, since adding more would corrupt the fd boundary. */ + + while (s->output_queue) { + assert(s->n_output_queue > 0); + + if (s->n_output_fds > 0) + return 0; + + JsonStreamQueueItem *q = s->output_queue; + _cleanup_free_ int *array = NULL; + + if (q->n_fds > 0) { + array = newdup(int, q->fds, q->n_fds); + if (!array) + return -ENOMEM; + } + + r = json_stream_format_json(s, q->data); + if (r < 0) + return r; + + free_and_replace(s->output_fds, array); + s->n_output_fds = q->n_fds; + q->n_fds = 0; + + LIST_REMOVE(queue, s->output_queue, q); + if (!s->output_queue) + s->output_queue_tail = NULL; + s->n_output_queue--; + + json_stream_queue_item_free(q); + } + + return 0; +} + +int json_stream_enqueue_item(JsonStream *s, JsonStreamQueueItem *q) { + assert(s); + assert(q); + + if (s->n_output_queue >= s->queue_max) + return -ENOBUFS; + + LIST_INSERT_AFTER(queue, s->output_queue, s->output_queue_tail, q); + s->output_queue_tail = q; + s->n_output_queue++; + return 0; +} + +int json_stream_enqueue(JsonStream *s, sd_json_variant *m) { + JsonStreamQueueItem *q; + + assert(s); + assert(m); + + /* Fast path: no fds pending and no items currently queued — append directly into the + * output buffer to avoid the queue allocation. */ + if (s->n_pushed_fds == 0 && !s->output_queue) + return json_stream_format_json(s, m); + + if (s->n_output_queue >= s->queue_max) + return -ENOBUFS; + + q = json_stream_queue_item_new(m, s->pushed_fds, s->n_pushed_fds); + if (!q) + return -ENOMEM; + + s->n_pushed_fds = 0; /* fds belong to the queue entry now */ + + assert_se(json_stream_enqueue_item(s, q) >= 0); + return 0; +} + +int json_stream_make_queue_item(JsonStream *s, sd_json_variant *m, JsonStreamQueueItem **ret) { + JsonStreamQueueItem *q; + + assert(s); + assert(m); + assert(ret); + + q = json_stream_queue_item_new(m, s->pushed_fds, s->n_pushed_fds); + if (!q) + return -ENOMEM; + + s->n_pushed_fds = 0; /* fds belong to the queue entry now */ + + *ret = q; + return 0; +} + +/* ===== Write side ===== */ + +int json_stream_write(JsonStream *s) { + ssize_t n; + int r; + + assert(s); + + if (FLAGS_SET(s->flags, JSON_STREAM_CONNECTING)) + return 0; + if (FLAGS_SET(s->flags, JSON_STREAM_WRITE_DISCONNECTED)) + return 0; + + /* Drain the deferred queue into the output buffer if possible */ + r = json_stream_format_queue(s); + if (r < 0) + return r; + + if (s->output_buffer_size == 0) + return 0; + + assert(s->output_fd >= 0); + + if (s->n_output_fds > 0) { + struct iovec iov = { + .iov_base = s->output_buffer + s->output_buffer_index, + .iov_len = s->output_buffer_size, + }; + struct msghdr mh = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_controllen = CMSG_SPACE(sizeof(int) * s->n_output_fds), + }; + + mh.msg_control = alloca0(mh.msg_controllen); + + struct cmsghdr *control = CMSG_FIRSTHDR(&mh); + control->cmsg_len = CMSG_LEN(sizeof(int) * s->n_output_fds); + control->cmsg_level = SOL_SOCKET; + control->cmsg_type = SCM_RIGHTS; + memcpy(CMSG_DATA(control), s->output_fds, sizeof(int) * s->n_output_fds); + + n = sendmsg(s->output_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL); + } else if (FLAGS_SET(s->flags, JSON_STREAM_PREFER_WRITE)) + n = write(s->output_fd, s->output_buffer + s->output_buffer_index, s->output_buffer_size); + else + n = send(s->output_fd, s->output_buffer + s->output_buffer_index, s->output_buffer_size, MSG_DONTWAIT|MSG_NOSIGNAL); + if (n < 0) { + if (ERRNO_IS_TRANSIENT(errno)) + return 0; + + if (ERRNO_IS_DISCONNECT(errno)) { + s->flags |= JSON_STREAM_WRITE_DISCONNECTED; + return 1; + } + + return -errno; + } + + if (FLAGS_SET(s->flags, JSON_STREAM_OUTPUT_BUFFER_SENSITIVE)) + explicit_bzero_safe(s->output_buffer + s->output_buffer_index, n); + + s->output_buffer_size -= n; + + if (s->output_buffer_size == 0) { + s->output_buffer_index = 0; + s->flags &= ~JSON_STREAM_OUTPUT_BUFFER_SENSITIVE; + } else + s->output_buffer_index += n; + + close_many(s->output_fds, s->n_output_fds); + s->n_output_fds = 0; + + /* Refresh activity timestamp on real progress (and rearm the time source if attached + * to an event loop). */ + s->last_activity = json_stream_now(s); + json_stream_rearm_time_source(s); + + return 1; +} + +/* ===== Read side ===== */ + +/* In bounded-reads mode, peek at the socket data to find the delimiter and return a read + * size that won't consume past it. This prevents over-reading data that belongs to whatever + * protocol the socket is being handed off to. Falls back to byte-by-byte for non-socket fds + * where MSG_PEEK is not available. */ +static ssize_t json_stream_peek_message_boundary(JsonStream *s, void *p, size_t rs) { + assert(s); + + if (!FLAGS_SET(s->flags, JSON_STREAM_BOUNDED_READS)) + return rs; + + if (FLAGS_SET(s->flags, JSON_STREAM_PREFER_READ)) + return 1; + + ssize_t peeked = recv(s->input_fd, p, rs, MSG_PEEK|MSG_DONTWAIT); + if (peeked < 0) { + if (!ERRNO_IS_TRANSIENT(errno)) + return -errno; + + /* Transient error: shouldn't happen but fall back to byte-by-byte */ + return 1; + } + /* EOF: the real recv() will also see it; what we return here doesn't matter */ + if (peeked == 0) + return rs; + + size_t dsz = json_stream_delimiter_size(s); + void *delim = memmem_safe(p, peeked, s->delimiter ?: "\0", dsz); + if (delim) + return (ssize_t) ((char*) delim - (char*) p) + dsz; + + return peeked; +} + +int json_stream_read(JsonStream *s) { + struct iovec iov; + struct msghdr mh; + ssize_t rs; + ssize_t n; + void *p; + + assert(s); + + if (FLAGS_SET(s->flags, JSON_STREAM_CONNECTING)) + return 0; + if (s->input_buffer_unscanned > 0) + return 0; + if (FLAGS_SET(s->flags, JSON_STREAM_READ_DISCONNECTED)) + return 0; + + if (s->input_buffer_size >= s->buffer_max) + return -ENOBUFS; + + assert(s->input_fd >= 0); + + if (MALLOC_SIZEOF_SAFE(s->input_buffer) <= s->input_buffer_index + s->input_buffer_size) { + size_t add; + + add = MIN(s->buffer_max - s->input_buffer_size, s->read_chunk); + + if (!FLAGS_SET(s->flags, JSON_STREAM_INPUT_SENSITIVE) && s->input_buffer_index == 0) { + if (!GREEDY_REALLOC(s->input_buffer, s->input_buffer_size + add)) + return -ENOMEM; + } else { + char *b; + + b = new(char, s->input_buffer_size + add); + if (!b) + return -ENOMEM; + + memcpy(b, s->input_buffer + s->input_buffer_index, s->input_buffer_size); + + if (FLAGS_SET(s->flags, JSON_STREAM_INPUT_SENSITIVE)) + s->input_buffer = erase_and_free(s->input_buffer); + else + free(s->input_buffer); + s->input_buffer = b; + s->input_buffer_index = 0; + } + } + + p = s->input_buffer + s->input_buffer_index + s->input_buffer_size; + + rs = MALLOC_SIZEOF_SAFE(s->input_buffer) - (s->input_buffer_index + s->input_buffer_size); + + /* If a protocol upgrade may follow, ensure we don't consume any post-upgrade bytes by + * limiting the read to the next delimiter. Uses MSG_PEEK on sockets, single-byte reads + * otherwise. */ + rs = json_stream_peek_message_boundary(s, p, rs); + if (rs < 0) + return json_stream_log_errno(s, (int) rs, "Failed to peek message boundary: %m"); + + if (FLAGS_SET(s->flags, JSON_STREAM_ALLOW_FD_PASSING_INPUT)) { + iov = IOVEC_MAKE(p, rs); + + if (!s->input_control_buffer) { + s->input_control_buffer_size = CMSG_SPACE(sizeof(int) * JSON_STREAM_FDS_MAX); + s->input_control_buffer = malloc(s->input_control_buffer_size); + if (!s->input_control_buffer) + return -ENOMEM; + } + + mh = (struct msghdr) { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = s->input_control_buffer, + .msg_controllen = s->input_control_buffer_size, + }; + + n = recvmsg_safe(s->input_fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); + } else if (FLAGS_SET(s->flags, JSON_STREAM_PREFER_READ)) + n = RET_NERRNO(read(s->input_fd, p, rs)); + else + n = RET_NERRNO(recv(s->input_fd, p, rs, MSG_DONTWAIT)); + if (ERRNO_IS_NEG_TRANSIENT(n)) + return 0; + if (ERRNO_IS_NEG_DISCONNECT(n)) { + s->flags |= JSON_STREAM_READ_DISCONNECTED; + return 1; + } + if (n < 0) + return n; + if (n == 0) { /* EOF */ + if (FLAGS_SET(s->flags, JSON_STREAM_ALLOW_FD_PASSING_INPUT)) + cmsg_close_all(&mh); + + s->flags |= JSON_STREAM_READ_DISCONNECTED; + return 1; + } + + if (FLAGS_SET(s->flags, JSON_STREAM_ALLOW_FD_PASSING_INPUT)) { + struct cmsghdr *cmsg; + + cmsg = cmsg_find(&mh, SOL_SOCKET, SCM_RIGHTS, (socklen_t) -1); + if (cmsg) { + size_t add; + + /* fds are only allowed with the first byte of a message; receiving them + * mid-stream is a protocol violation. */ + if (s->input_buffer_size != 0) { + cmsg_close_all(&mh); + return -EPROTO; + } + + add = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + if (add > INT_MAX - s->n_input_fds) { + cmsg_close_all(&mh); + return -EBADF; + } + + if (!GREEDY_REALLOC(s->input_fds, s->n_input_fds + add)) { + cmsg_close_all(&mh); + return -ENOMEM; + } + + memcpy_safe(s->input_fds + s->n_input_fds, CMSG_TYPED_DATA(cmsg, int), add * sizeof(int)); + s->n_input_fds += add; + } + } + + s->input_buffer_size += n; + s->input_buffer_unscanned += n; + + return 1; +} + +/* ===== Parse ===== */ + +int json_stream_parse(JsonStream *s, sd_json_variant **ret) { + char *begin, *e; + size_t sz; + int r; + + assert(s); + assert(ret); + + if (s->input_buffer_unscanned == 0) { + *ret = NULL; + return 0; + } + + assert(s->input_buffer_unscanned <= s->input_buffer_size); + assert(s->input_buffer_index + s->input_buffer_size <= MALLOC_SIZEOF_SAFE(s->input_buffer)); + + begin = s->input_buffer + s->input_buffer_index; + + size_t dsz = json_stream_delimiter_size(s); + e = memmem_safe(begin + s->input_buffer_size - s->input_buffer_unscanned, s->input_buffer_unscanned, s->delimiter ?: "\0", dsz); + if (!e) { + s->input_buffer_unscanned = 0; + *ret = NULL; + return 0; + } + + sz = e - begin + dsz; + + /* For non-NUL delimiters (e.g. "\r\n" for QMP) sd_json_parse() needs a NUL-terminated + * string; overwrite the first delimiter byte with NUL in place. For NUL delimiters + * this is a no-op since the byte is already '\0'. */ + if (s->delimiter) + *e = '\0'; + + r = sd_json_parse(begin, SD_JSON_PARSE_MUST_BE_OBJECT, ret, /* reterr_line= */ NULL, /* reterr_column= */ NULL); + if (FLAGS_SET(s->flags, JSON_STREAM_INPUT_SENSITIVE)) + explicit_bzero_safe(begin, sz); + if (r < 0) { + /* Unrecoverable parse failure: drop all buffered data. */ + s->input_buffer_index = s->input_buffer_size = s->input_buffer_unscanned = 0; + return json_stream_log_errno(s, r, "Failed to parse JSON object: %m"); + } + + if (DEBUG_LOGGING) { + _cleanup_(erase_and_freep) char *censored_text = NULL; + + /* Suppress sensitive fields in the debug output */ + r = sd_json_variant_format(*ret, /* flags= */ SD_JSON_FORMAT_CENSOR_SENSITIVE, &censored_text); + if (r >= 0) + json_stream_log(s, "Received message: %s", censored_text); + } + + s->input_buffer_size -= sz; + + if (s->input_buffer_size == 0) + s->input_buffer_index = 0; + else + s->input_buffer_index += sz; + + s->input_buffer_unscanned = s->input_buffer_size; + return 1; +} diff --git a/src/libsystemd/sd-json/json-stream.h b/src/libsystemd/sd-json/json-stream.h new file mode 100644 index 0000000000000..92a09d494191a --- /dev/null +++ b/src/libsystemd/sd-json/json-stream.h @@ -0,0 +1,267 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "sd-forward.h" + +#include "list.h" + +/* JsonStream provides the transport layer used by sd-varlink (and other consumers like + * the QMP client) for exchanging length-delimited JSON messages over a pair of file + * descriptors. It owns the input/output buffers, the file-descriptor passing machinery + * (SCM_RIGHTS), the deferred output queue, and the read/write/parse step functions. It + * does not implement any state machine, dispatch, callback or event-source plumbing — + * those concerns belong to the consumer. */ + +typedef struct JsonStreamQueueItem JsonStreamQueueItem; + +typedef enum JsonStreamFlags { + JSON_STREAM_BOUNDED_READS = 1u << 0, + JSON_STREAM_INPUT_SENSITIVE = 1u << 1, + JSON_STREAM_ALLOW_FD_PASSING_INPUT = 1u << 2, + JSON_STREAM_ALLOW_FD_PASSING_OUTPUT = 1u << 3, + JSON_STREAM_CONNECTING = 1u << 4, + JSON_STREAM_GOT_POLLHUP = 1u << 5, + JSON_STREAM_WRITE_DISCONNECTED = 1u << 6, + JSON_STREAM_READ_DISCONNECTED = 1u << 7, + JSON_STREAM_PREFER_READ = 1u << 8, + JSON_STREAM_PREFER_WRITE = 1u << 9, + JSON_STREAM_OUTPUT_BUFFER_SENSITIVE = 1u << 10, +} JsonStreamFlags; + +/* What the consumer's high-level state machine is currently doing — used by the various + * "what should I do right now?" APIs (get_events, wait, should_disconnect) to decide + * whether to ask for read events, whether transport death matters, and whether the idle + * timeout deadline is currently in force. */ +typedef enum JsonStreamPhase { + JSON_STREAM_PHASE_READING, /* waiting for the next inbound message, no deadline */ + JSON_STREAM_PHASE_AWAITING_REPLY, /* waiting for a reply with the idle timeout deadline */ + JSON_STREAM_PHASE_IDLE_CLIENT, /* idle client, no in-flight call */ + JSON_STREAM_PHASE_PENDING_OUTPUT, /* has more output queued, waiting to send */ + JSON_STREAM_PHASE_OTHER, /* none of the above */ +} JsonStreamPhase; + +/* Consumer hooks supplied at construction time: + * • phase — queried by get_events / wait / should_disconnect / attach_event's prepare + * callback whenever the consumer's current phase is needed. + * • dispatch — invoked by attach_event's io and time callbacks after the stream has + * consumed the revents, so the consumer can drive its state machine + * forward. Should return 0 on success or a negative errno; the stream logs + * the failure and continues running. */ +typedef JsonStreamPhase (*json_stream_phase_t)(void *userdata); +typedef int (*json_stream_dispatch_t)(void *userdata); + +typedef struct JsonStreamParams { + const char *delimiter; /* message delimiter; NULL → single NUL byte (varlink), e.g. "\r\n" for QMP */ + size_t buffer_max; /* maximum bytes buffered before -ENOBUFS; 0 = 16 MiB default */ + size_t read_chunk; /* per-read chunk size; 0 = 64 KiB default */ + size_t queue_max; /* maximum number of queued output items; 0 = 64 Ki default */ + + /* Consumer hooks (see typedefs above). */ + json_stream_phase_t phase; + json_stream_dispatch_t dispatch; + void *userdata; +} JsonStreamParams; + +typedef struct JsonStream { + char *delimiter; /* message delimiter; NULL → NUL byte (varlink), e.g. "\r\n" for QMP */ + size_t buffer_max; + size_t read_chunk; + size_t queue_max; + + char *description; + + int input_fd; + int output_fd; + + usec_t timeout; /* relative; USEC_INFINITY = no timeout */ + usec_t last_activity; /* CLOCK_MONOTONIC */ + + /* Cached peer credentials */ + struct ucred ucred; + bool ucred_acquired; + int peer_pidfd; + + /* Cached socket address family. -1 = unchecked, AF_UNSPEC = checked-not-socket, + * otherwise the resolved family. */ + int af; + + sd_event *event; + sd_event_source *input_event_source; + sd_event_source *output_event_source; + sd_event_source *time_event_source; + + json_stream_phase_t phase_cb; + json_stream_dispatch_t dispatch_cb; + void *userdata; + + char *input_buffer; + size_t input_buffer_index; + size_t input_buffer_size; + size_t input_buffer_unscanned; + + void *input_control_buffer; + size_t input_control_buffer_size; + + char *output_buffer; + size_t output_buffer_index; + size_t output_buffer_size; + + int *input_fds; + size_t n_input_fds; + + int *output_fds; + size_t n_output_fds; + + LIST_HEAD(JsonStreamQueueItem, output_queue); + JsonStreamQueueItem *output_queue_tail; + size_t n_output_queue; + + int *pushed_fds; + size_t n_pushed_fds; + + JsonStreamFlags flags; +} JsonStream; + +int json_stream_init(JsonStream *s, const JsonStreamParams *params); +void json_stream_done(JsonStream *s); + +/* Optional description used as the prefix for the stream's debug log lines (sent/received + * messages, POLLHUP detection, async connect completion, etc.). The string is duped. */ +int json_stream_set_description(JsonStream *s, const char *description); +const char* json_stream_get_description(const JsonStream *s); + +/* fd ownership */ +int json_stream_attach_fds(JsonStream *s, int input_fd, int output_fd); + +/* Open an AF_UNIX SOCK_STREAM socket and connect to the given filesystem path, attaching + * the resulting fd to the stream. Handles paths too long for sockaddr_un by routing through + * O_PATH (connect_unix_path()). If the connect() returns EAGAIN/EINPROGRESS the stream's + * connecting state is set so that the consumer waits for POLLOUT before treating the + * connection as established. Returns 0 on success or successfully started async connect, + * negative errno on failure. */ +int json_stream_connect_address(JsonStream *s, const char *address); + +/* Adopt a pre-connected pair of fds, ensuring both are non-blocking. Equivalent to + * json_stream_attach_fds() but does the fd_nonblock() dance up front, so the caller can + * pass in fds without having to know whether they were already configured. */ +int json_stream_connect_fd_pair(JsonStream *s, int input_fd, int output_fd); + +bool json_stream_flags_set(const JsonStream *s, JsonStreamFlags flags); +void json_stream_set_flags(JsonStream *s, JsonStreamFlags flags, bool b); + +/* Combines the transport-level disconnect signals (write/read disconnected, buffered + * output, POLLHUP, async connect) with the consumer's current phase (queried via the + * registered get_phase callback) to answer "should the consumer initiate teardown right + * now?". The decision logic mirrors what the original varlink transport did but stays + * generic enough for other JSON-line consumers. */ +bool json_stream_should_disconnect(const JsonStream *s); + +/* Enable/disable fd passing. These verify the underlying fd is an AF_UNIX socket and + * (for input) optionally set SO_PASSRIGHTS. */ +int json_stream_set_allow_fd_passing_input(JsonStream *s, bool enabled, bool with_sockopt); +int json_stream_set_allow_fd_passing_output(JsonStream *s, bool enabled); + +/* Output: enqueue a JSON variant. Fast path concatenates into the output buffer; if + * pushed_fds are present or the queue is non-empty the message is queued instead, so that + * fd-to-message boundaries are preserved. */ +int json_stream_enqueue(JsonStream *s, sd_json_variant *m); + +/* Allocate a queue item carrying `m` and the currently pushed fds. The pushed fds are + * transferred to the new item; on success n_pushed_fds is reset to 0. The caller may + * later submit the item via json_stream_enqueue_item() or free it. */ +int json_stream_make_queue_item(JsonStream *s, sd_json_variant *m, JsonStreamQueueItem **ret); +int json_stream_enqueue_item(JsonStream *s, JsonStreamQueueItem *q); +JsonStreamQueueItem* json_stream_queue_item_free(JsonStreamQueueItem *q); +DEFINE_TRIVIAL_CLEANUP_FUNC(JsonStreamQueueItem*, json_stream_queue_item_free); +sd_json_variant** json_stream_queue_item_get_data(JsonStreamQueueItem *q); + +/* fd push/peek/take */ +int json_stream_push_fd(JsonStream *s, int fd); +void json_stream_reset_pushed_fds(JsonStream *s); + +int json_stream_peek_input_fd(const JsonStream *s, size_t i); +int json_stream_take_input_fd(JsonStream *s, size_t i); +size_t json_stream_get_n_input_fds(const JsonStream *s); + +/* Close and free all currently received input fds (used after consuming a message). */ +void json_stream_close_input_fds(JsonStream *s); + +/* I/O steps. Same return-value contract as the original varlink_{write,read,parse_message}: + * 1 = made progress (call again), + * 0 = nothing to do (wait for I/O), + * <0 = error. */ +int json_stream_write(JsonStream *s); +int json_stream_read(JsonStream *s); + +/* Extract the next complete JSON message from the input buffer (delimited per + * params.delimiter). Returns 1 with *ret set on success, 0 if no full message is + * available yet (with *ret == NULL), <0 on parse error. The buffer slot occupied by the + * parsed message is erased if input_sensitive was set. */ +int json_stream_parse(JsonStream *s, sd_json_variant **ret); + +/* Status accessors used by the consumer's state machine. */ +bool json_stream_has_buffered_input(const JsonStream *s); + +/* Compute the poll events the consumer should wait for. The stream queries the consumer's + * phase via the registered get_phase callback. In JSON_STREAM_PHASE_READING the stream asks + * for POLLIN (provided the input buffer is empty and the read side is still alive); POLLOUT + * is added whenever there's pending output. When connecting we only ask for POLLOUT to + * learn when the non-blocking connect() completes. */ +int json_stream_get_events(const JsonStream *s); + +/* Block on poll() for the configured fds for at most `timeout` µs. Internally updates the + * connecting / got_pollhup state based on the seen revents. + * 1 = some event was observed (call us again), + * 0 = timeout, + * <0 = error (negative errno from ppoll_usec). */ +int json_stream_wait(JsonStream *s, usec_t timeout); + +/* Block until the output buffer is fully drained (or the write side disconnects). + * 1 = some bytes were written during the flush, + * 0 = nothing to flush, + * -ECONNRESET if the write side became disconnected before everything could be sent, + * <0 on other I/O errors. */ +int json_stream_flush(JsonStream *s); + +/* Peer credential helpers. All refuse if the stream uses different input/output fds, since + * peer credentials are only meaningful for a bidirectional socket. + * • acquire_peer_uid/gid/pid/pidfd() query the kernel on first use, cache the result, + * and log failures (using the stream's description). They each return 0 on success + * with the value in *ret, or a negative errno on failure (kernel error or invalid + * field). + * • get_peer_ucred() returns the *already-cached* ucred (set via a prior acquire or via + * set_peer_ucred()) without triggering a kernel query — returns -ENODATA if nothing is + * cached. Used by consumers that want to react to a previously-known ucred without + * forcing a fresh query (e.g. teardown bookkeeping). */ +int json_stream_acquire_peer_uid(JsonStream *s, uid_t *ret); +int json_stream_acquire_peer_gid(JsonStream *s, gid_t *ret); +int json_stream_acquire_peer_pid(JsonStream *s, pid_t *ret); +int json_stream_acquire_peer_pidfd(JsonStream *s); +int json_stream_get_peer_ucred(const JsonStream *s, struct ucred *ret); +void json_stream_set_peer_ucred(JsonStream *s, const struct ucred *ucred); + +/* Per-operation idle timeout. The deadline is computed as last_activity + timeout. + * Successful writes refresh last_activity automatically; the consumer should also call + * json_stream_mark_activity() at operation start (e.g. when initiating a method call) to + * reset the deadline. + * + * When the deadline elapses the time event source attached via json_stream_attach_event() + * fires and the consumer's dispatch callback is invoked. The consumer detects the timeout + * by comparing now(CLOCK_MONOTONIC) against json_stream_get_timeout(). */ +void json_stream_set_timeout(JsonStream *s, usec_t timeout); +void json_stream_mark_activity(JsonStream *s); + +/* Returns the absolute deadline (in CLOCK_MONOTONIC microseconds) currently in force for + * the consumer's phase, or USEC_INFINITY if no timeout applies (no timeout configured, no + * activity yet, or the current phase isn't AWAITING_REPLY). */ +usec_t json_stream_get_timeout(const JsonStream *s); + +/* sd-event integration. JsonStream owns the input/output io event sources and the time + * event source for its idle timeout, and installs its own internal prepare and io callbacks + * on them. The hooks (get_phase, io_dispatch) supplied via JsonStreamParams at construction + * are wired up automatically. */ +int json_stream_attach_event(JsonStream *s, sd_event *event, int64_t priority); +void json_stream_detach_event(JsonStream *s); +sd_event* json_stream_get_event(const JsonStream *s); diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index a9bbb8f79c130..fdcbcff0e1f06 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -18,8 +18,6 @@ #include "format-util.h" #include "glyph-util.h" #include "hashmap.h" -#include "io-util.h" -#include "iovec-util.h" #include "json-util.h" #include "list.h" #include "log.h" @@ -44,10 +42,7 @@ #define VARLINK_DEFAULT_CONNECTIONS_PER_UID_MAX 1024U #define VARLINK_DEFAULT_TIMEOUT_USEC (45U*USEC_PER_SEC) -#define VARLINK_BUFFER_MAX (16U*1024U*1024U) -#define VARLINK_READ_SIZE (64U*1024U) #define VARLINK_COLLECT_MAX 1024U -#define VARLINK_QUEUE_MAX (64U*1024U) static const char* const varlink_state_table[_VARLINK_STATE_MAX] = { [VARLINK_IDLE_CLIENT] = "idle-client", @@ -75,39 +70,8 @@ static const char* const varlink_state_table[_VARLINK_STATE_MAX] = { DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(varlink_state, VarlinkState); -static int varlink_format_queue(sd_varlink *v); static void varlink_server_test_exit_on_idle(sd_varlink_server *s); -static VarlinkJsonQueueItem* varlink_json_queue_item_free(VarlinkJsonQueueItem *q) { - if (!q) - return NULL; - - sd_json_variant_unref(q->data); - close_many(q->fds, q->n_fds); - - return mfree(q); -} - -static VarlinkJsonQueueItem* varlink_json_queue_item_new(sd_json_variant *m, const int fds[], size_t n_fds) { - VarlinkJsonQueueItem *q; - - assert(m); - assert(fds || n_fds == 0); - - q = malloc(offsetof(VarlinkJsonQueueItem, fds) + sizeof(int) * n_fds); - if (!q) - return NULL; - - *q = (VarlinkJsonQueueItem) { - .data = sd_json_variant_ref(m), - .n_fds = n_fds, - }; - - memcpy_safe(q->fds, fds, n_fds * sizeof(int)); - - return TAKE_PTR(q); -} - static void varlink_set_state(sd_varlink *v, VarlinkState state) { assert(v); assert(state >= 0 && state < _VARLINK_STATE_MAX); @@ -124,8 +88,40 @@ static void varlink_set_state(sd_varlink *v, VarlinkState state) { v->state = state; } +/* Map the varlink state machine onto the generic transport-level "phase". The transport + * uses this to decide whether to ask for POLLIN, whether the connection is salvageable + * after a read/write disconnect, and whether the idle timeout deadline is in force. */ +static JsonStreamPhase varlink_phase(void *userdata) { + sd_varlink *v = ASSERT_PTR(userdata); + + /* Client side reading a reply with the per-call deadline in force. */ + if (IN_SET(v->state, + VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, + VARLINK_CALLING, VARLINK_COLLECTING) && + !v->current) + return JSON_STREAM_PHASE_AWAITING_REPLY; + + /* Server side reading the next request — no deadline applies. */ + if (v->state == VARLINK_IDLE_SERVER && !v->current) + return JSON_STREAM_PHASE_READING; + + if (v->state == VARLINK_IDLE_CLIENT) + return JSON_STREAM_PHASE_IDLE_CLIENT; + + if (IN_SET(v->state, VARLINK_PENDING_METHOD, VARLINK_PENDING_METHOD_MORE)) + return JSON_STREAM_PHASE_PENDING_OUTPUT; + + return JSON_STREAM_PHASE_OTHER; +} + +static int varlink_dispatch(void *userdata) { + sd_varlink *v = ASSERT_PTR(userdata); + return sd_varlink_process(v); +} + static int varlink_new(sd_varlink **ret) { - sd_varlink *v; + _cleanup_(sd_varlink_unrefp) sd_varlink *v = NULL; + int r; assert(ret); @@ -135,32 +131,28 @@ static int varlink_new(sd_varlink **ret) { *v = (sd_varlink) { .n_ref = 1, - .input_fd = -EBADF, - .output_fd = -EBADF, - .state = _VARLINK_STATE_INVALID, - - .ucred = UCRED_INVALID, - - .peer_pidfd = -EBADF, - - .timestamp = USEC_INFINITY, - .timeout = VARLINK_DEFAULT_TIMEOUT_USEC, - - .allow_fd_passing_input = -1, - - .af = -1, - .exec_pidref = PIDREF_NULL, }; - *ret = v; + r = json_stream_init( + &v->stream, + &(JsonStreamParams) { + .phase = varlink_phase, + .dispatch = varlink_dispatch, + .userdata = v, + }); + if (r < 0) + return r; + + json_stream_set_timeout(&v->stream, VARLINK_DEFAULT_TIMEOUT_USEC); + + *ret = TAKE_PTR(v); return 0; } _public_ int sd_varlink_connect_address(sd_varlink **ret, const char *address) { _cleanup_(sd_varlink_unrefp) sd_varlink *v = NULL; - union sockaddr_union sockaddr; int r; assert_return(ret, -EINVAL); @@ -170,39 +162,9 @@ _public_ int sd_varlink_connect_address(sd_varlink **ret, const char *address) { if (r < 0) return log_debug_errno(r, "Failed to create varlink object: %m"); - v->input_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (v->input_fd < 0) - return log_debug_errno(errno, "Failed to create AF_UNIX socket: %m"); - - v->output_fd = v->input_fd = fd_move_above_stdio(v->input_fd); - v->af = AF_UNIX; - - r = sockaddr_un_set_path(&sockaddr.un, address); - if (r < 0) { - if (r != -ENAMETOOLONG) - return log_debug_errno(r, "Failed to set socket address '%s': %m", address); - - /* This is a file system path, and too long to fit into sockaddr_un. Let's connect via O_PATH - * to this socket. */ - - r = connect_unix_path(v->input_fd, AT_FDCWD, address); - } else - r = RET_NERRNO(connect(v->input_fd, &sockaddr.sa, r)); - - if (r < 0) { - if (!IN_SET(r, -EAGAIN, -EINPROGRESS)) - return log_debug_errno(r, "Failed to connect to %s: %m", address); - - v->connecting = true; /* We are asynchronously connecting, i.e. the connect() is being - * processed in the background. As long as that's the case the socket - * is in a special state: it's there, we can poll it for EPOLLOUT, but - * if we attempt to write() to it before we see EPOLLOUT we'll get - * ENOTCONN (and not EAGAIN, like we would for a normal connected - * socket that isn't writable at the moment). Since ENOTCONN on write() - * hence can mean two different things (i.e. connection not complete - * yet vs. already disconnected again), we store as a boolean whether - * we are still in connect(). */ - } + r = json_stream_connect_address(&v->stream, address); + if (r < 0) + return r; varlink_set_state(v, VARLINK_IDLE_CLIENT); @@ -291,8 +253,11 @@ _public_ int sd_varlink_connect_exec(sd_varlink **ret, const char *_command, cha if (r < 0) return log_debug_errno(r, "Failed to create varlink object: %m"); - v->output_fd = v->input_fd = TAKE_FD(pair[0]); - v->af = AF_UNIX; + int conn_fd = TAKE_FD(pair[0]); + r = json_stream_attach_fds(&v->stream, conn_fd, conn_fd); + if (r < 0) + return r; + v->exec_pidref = TAKE_PIDREF(pidref); varlink_set_state(v, VARLINK_IDLE_CLIENT); @@ -375,8 +340,11 @@ static int varlink_connect_ssh_unix(sd_varlink **ret, const char *where) { if (r < 0) return log_debug_errno(r, "Failed to create varlink object: %m"); - v->output_fd = v->input_fd = TAKE_FD(pair[0]); - v->af = AF_UNIX; + int conn_fd = TAKE_FD(pair[0]); + r = json_stream_attach_fds(&v->stream, conn_fd, conn_fd); + if (r < 0) + return r; + v->exec_pidref = TAKE_PIDREF(pidref); varlink_set_state(v, VARLINK_IDLE_CLIENT); @@ -467,9 +435,10 @@ static int varlink_connect_ssh_exec(sd_varlink **ret, const char *where) { if (r < 0) return log_debug_errno(r, "Failed to create varlink object: %m"); - v->input_fd = TAKE_FD(output_pipe[0]); - v->output_fd = TAKE_FD(input_pipe[1]); - v->af = AF_UNSPEC; + r = json_stream_attach_fds(&v->stream, TAKE_FD(output_pipe[0]), TAKE_FD(input_pipe[1])); + if (r < 0) + return r; + v->exec_pidref = TAKE_PIDREF(pidref); varlink_set_state(v, VARLINK_IDLE_CLIENT); @@ -575,35 +544,23 @@ _public_ int sd_varlink_connect_url(sd_varlink **ret, const char *url) { } _public_ int sd_varlink_connect_fd_pair(sd_varlink **ret, int input_fd, int output_fd, const struct ucred *override_ucred) { - sd_varlink *v; + _cleanup_(sd_varlink_unrefp) sd_varlink *v = NULL; int r; assert_return(ret, -EINVAL); assert_return(input_fd >= 0, -EBADF); assert_return(output_fd >= 0, -EBADF); - r = fd_nonblock(input_fd, true); - if (r < 0) - return log_debug_errno(r, "Failed to make input fd %d nonblocking: %m", input_fd); - - if (input_fd != output_fd) { - r = fd_nonblock(output_fd, true); - if (r < 0) - return log_debug_errno(r, "Failed to make output fd %d nonblocking: %m", output_fd); - } - r = varlink_new(&v); if (r < 0) return log_debug_errno(r, "Failed to create varlink object: %m"); - v->input_fd = input_fd; - v->output_fd = output_fd; - v->af = -1; + r = json_stream_connect_fd_pair(&v->stream, input_fd, output_fd); + if (r < 0) + return r; - if (override_ucred) { - v->ucred = *override_ucred; - v->ucred_acquired = true; - } + if (override_ucred) + json_stream_set_peer_ucred(&v->stream, override_ucred); varlink_set_state(v, VARLINK_IDLE_CLIENT); @@ -614,7 +571,7 @@ _public_ int sd_varlink_connect_fd_pair(sd_varlink **ret, int input_fd, int outp * varlink_connect_address() above, as there we do handle asynchronous connections ourselves and * avoid doing write() on it before we saw EPOLLOUT for the first time. */ - *ret = v; + *ret = TAKE_PTR(v); return 0; } @@ -622,16 +579,6 @@ _public_ int sd_varlink_connect_fd(sd_varlink **ret, int fd) { return sd_varlink_connect_fd_pair(ret, fd, fd, /* override_ucred= */ NULL); } -static void varlink_detach_event_sources(sd_varlink *v) { - assert(v); - - v->input_event_source = sd_event_source_disable_unref(v->input_event_source); - v->output_event_source = sd_event_source_disable_unref(v->output_event_source); - v->time_event_source = sd_event_source_disable_unref(v->time_event_source); - v->quit_event_source = sd_event_source_disable_unref(v->quit_event_source); - v->defer_event_source = sd_event_source_disable_unref(v->defer_event_source); -} - static void varlink_clear_current(sd_varlink *v) { assert(v); @@ -641,11 +588,9 @@ static void varlink_clear_current(sd_varlink *v) { v->current_method = NULL; v->current_reply_flags = 0; - close_many(v->input_fds, v->n_input_fds); - v->input_fds = mfree(v->input_fds); - v->n_input_fds = 0; + json_stream_close_input_fds(&v->stream); - v->previous = varlink_json_queue_item_free(v->previous); + v->previous = json_stream_queue_item_free(v->previous); if (v->sentinel != POINTER_MAX) v->sentinel = mfree(v->sentinel); else @@ -655,39 +600,17 @@ static void varlink_clear_current(sd_varlink *v) { static void varlink_clear(sd_varlink *v) { assert(v); - varlink_detach_event_sources(v); - - if (v->input_fd != v->output_fd) { - v->input_fd = safe_close(v->input_fd); - v->output_fd = safe_close(v->output_fd); - } else - v->output_fd = v->input_fd = safe_close(v->input_fd); + /* Detach event sources first so the kernel no longer has epoll watches on the + * stream's fds, then free the stream — json_stream_done() closes the input/output + * fds, the cached peer_pidfd, the received input fds, the queued output fds, and + * the pushed fds. */ + sd_varlink_detach_event(v); varlink_clear_current(v); - v->input_buffer = v->input_sensitive ? erase_and_free(v->input_buffer) : mfree(v->input_buffer); - v->output_buffer = v->output_buffer_sensitive ? erase_and_free(v->output_buffer) : mfree(v->output_buffer); - - v->input_control_buffer = mfree(v->input_control_buffer); - v->input_control_buffer_size = 0; - - close_many(v->output_fds, v->n_output_fds); - v->output_fds = mfree(v->output_fds); - v->n_output_fds = 0; - - close_many(v->pushed_fds, v->n_pushed_fds); - v->pushed_fds = mfree(v->pushed_fds); - v->n_pushed_fds = 0; - - LIST_CLEAR(queue, v->output_queue, varlink_json_queue_item_free); - v->output_queue_tail = NULL; - v->n_output_queue = 0; - - v->event = sd_event_unref(v->event); + json_stream_done(&v->stream); pidref_done_sigterm_wait(&v->exec_pidref); - - v->peer_pidfd = safe_close(v->peer_pidfd); } static sd_varlink* varlink_destroy(sd_varlink *v) { @@ -700,7 +623,6 @@ static sd_varlink* varlink_destroy(sd_varlink *v) { varlink_clear(v); - free(v->description); return mfree(v); } @@ -709,405 +631,67 @@ DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_varlink, sd_varlink, varlink_destroy); static int varlink_test_disconnect(sd_varlink *v) { assert(v); - /* Tests whether we the connection has been terminated. We are careful to not stop processing it - * prematurely, since we want to handle half-open connections as well as possible and want to flush - * out and read data before we close down if we can. */ - /* Already disconnected? */ if (!VARLINK_STATE_IS_ALIVE(v->state)) return 0; - /* Wait until connection setup is complete, i.e. until asynchronous connect() completes */ - if (v->connecting) - return 0; - - /* Still something to write and we can write? Stay around */ - if (v->output_buffer_size > 0 && !v->write_disconnected) + if (!json_stream_should_disconnect(&v->stream)) return 0; - /* Both sides gone already? Then there's no need to stick around */ - if (v->read_disconnected && v->write_disconnected) - goto disconnect; - - /* If we are waiting for incoming data but the read side is shut down, disconnect. */ - if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING, VARLINK_IDLE_SERVER) && v->read_disconnected) - goto disconnect; - - /* Similar, if are a client that hasn't written anything yet but the write side is dead, also - * disconnect. We also explicitly check for POLLHUP here since we likely won't notice the write side - * being down if we never wrote anything. */ - if (v->state == VARLINK_IDLE_CLIENT && (v->write_disconnected || v->got_pollhup)) - goto disconnect; - - /* We are on the server side and still want to send out more replies, but we saw POLLHUP already, and - * either got no buffered bytes to write anymore or already saw a write error. In that case we should - * shut down the varlink link. */ - if (IN_SET(v->state, VARLINK_PENDING_METHOD, VARLINK_PENDING_METHOD_MORE) && (v->write_disconnected || v->output_buffer_size == 0) && v->got_pollhup) - goto disconnect; - - return 0; - -disconnect: varlink_set_state(v, VARLINK_PENDING_DISCONNECT); return 1; } static int varlink_write(sd_varlink *v) { - ssize_t n; - int r; - assert(v); if (!VARLINK_STATE_IS_ALIVE(v->state)) return 0; - if (v->connecting) /* Writing while we are still wait for a non-blocking connect() to complete will - * result in ENOTCONN, hence exit early here */ - return 0; - if (v->write_disconnected) - return 0; - - /* If needed let's convert some output queue json variants into text form */ - r = varlink_format_queue(v); - if (r < 0) - return r; - - if (v->output_buffer_size == 0) - return 0; - - assert(v->output_fd >= 0); - - if (v->n_output_fds > 0) { /* If we shall send fds along, we must use sendmsg() */ - struct iovec iov = { - .iov_base = v->output_buffer + v->output_buffer_index, - .iov_len = v->output_buffer_size, - }; - struct msghdr mh = { - .msg_iov = &iov, - .msg_iovlen = 1, - .msg_controllen = CMSG_SPACE(sizeof(int) * v->n_output_fds), - }; - - mh.msg_control = alloca0(mh.msg_controllen); - - struct cmsghdr *control = CMSG_FIRSTHDR(&mh); - control->cmsg_len = CMSG_LEN(sizeof(int) * v->n_output_fds); - control->cmsg_level = SOL_SOCKET; - control->cmsg_type = SCM_RIGHTS; - memcpy(CMSG_DATA(control), v->output_fds, sizeof(int) * v->n_output_fds); - - n = sendmsg(v->output_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL); - } else { - /* We generally prefer recv()/send() (mostly because of MSG_NOSIGNAL) but also want to be compatible - * with non-socket IO, hence fall back automatically. - * - * Use a local variable to help gcc figure out that we set 'n' in all cases. */ - bool prefer_write = v->prefer_write; - if (!prefer_write) { - n = send(v->output_fd, v->output_buffer + v->output_buffer_index, v->output_buffer_size, MSG_DONTWAIT|MSG_NOSIGNAL); - if (n < 0 && errno == ENOTSOCK) - prefer_write = v->prefer_write = true; - } - if (prefer_write) - n = write(v->output_fd, v->output_buffer + v->output_buffer_index, v->output_buffer_size); - } - if (n < 0) { - if (errno == EAGAIN) - return 0; - - if (ERRNO_IS_DISCONNECT(errno)) { - /* If we get informed about a disconnect on write, then let's remember that, but not - * act on it just yet. Let's wait for read() to report the issue first. */ - v->write_disconnected = true; - return 1; - } - - return -errno; - } - - if (v->output_buffer_sensitive) - explicit_bzero_safe(v->output_buffer + v->output_buffer_index, n); - - v->output_buffer_size -= n; - - if (v->output_buffer_size == 0) { - v->output_buffer_index = 0; - v->output_buffer_sensitive = false; /* We can reset the sensitive flag once the buffer is empty */ - } else - v->output_buffer_index += n; - - close_many(v->output_fds, v->n_output_fds); - v->n_output_fds = 0; - - v->timestamp = now(CLOCK_MONOTONIC); - return 1; -} - -#define VARLINK_FDS_MAX (16U*1024U) - -static bool varlink_may_protocol_upgrade(sd_varlink *v) { - return v->protocol_upgrade || (v->server && FLAGS_SET(v->server->flags, SD_VARLINK_SERVER_UPGRADABLE)); -} - -/* When a protocol upgrade might happen, peek at the socket data to find the \0 message - * boundary and return a read size that won't consume past it. This prevents over-reading - * raw post-upgrade data into the varlink input buffer. Falls back to byte-by-byte for - * non-socket fds where MSG_PEEK is not available. */ -static ssize_t varlink_peek_upgrade_boundary(sd_varlink *v, void *p, size_t rs) { - assert(v); - - if (!varlink_may_protocol_upgrade(v)) - return rs; - - if (v->prefer_read) - return 1; - - ssize_t peeked = recv(v->input_fd, p, rs, MSG_PEEK|MSG_DONTWAIT); - if (peeked < 0) { - if (errno == ENOTSOCK) { - v->prefer_read = true; - return 1; /* Not a socket, fall back to byte-to-byte */ - } else if (!ERRNO_IS_TRANSIENT(errno)) - return -errno; - - /* Transient error, this should not happen but fall back to byte-to-byte */ - return 1; - } - /* EOF, the real recv() will also get it so what we return does not matter */ - if (peeked == 0) - return rs; - - void *nul_chr = memchr(p, 0, peeked); - if (nul_chr) - return (ssize_t) ((char*) nul_chr - (char*) p) + 1; - return peeked; + return json_stream_write(&v->stream); } static int varlink_read(sd_varlink *v) { - struct iovec iov; - struct msghdr mh; - ssize_t rs; - ssize_t n; - void *p; - assert(v); if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING, VARLINK_IDLE_SERVER)) return 0; - if (v->connecting) /* read() on a socket while we are in connect() will fail with EINVAL, hence exit early here */ - return 0; if (v->current) return 0; - if (v->input_buffer_unscanned > 0) - return 0; - if (v->read_disconnected) - return 0; - - if (v->input_buffer_size >= VARLINK_BUFFER_MAX) - return -ENOBUFS; - - assert(v->input_fd >= 0); - - if (MALLOC_SIZEOF_SAFE(v->input_buffer) <= v->input_buffer_index + v->input_buffer_size) { - size_t add; - - add = MIN(VARLINK_BUFFER_MAX - v->input_buffer_size, VARLINK_READ_SIZE); - - if (v->input_buffer_index == 0) { - - if (!GREEDY_REALLOC(v->input_buffer, v->input_buffer_size + add)) - return -ENOMEM; - - } else { - char *b; - - b = new(char, v->input_buffer_size + add); - if (!b) - return -ENOMEM; - - memcpy(b, v->input_buffer + v->input_buffer_index, v->input_buffer_size); - - free_and_replace(v->input_buffer, b); - v->input_buffer_index = 0; - } - } - - p = v->input_buffer + v->input_buffer_index + v->input_buffer_size; - - rs = MALLOC_SIZEOF_SAFE(v->input_buffer) - (v->input_buffer_index + v->input_buffer_size); - - /* When a protocol upgrade is requested we can't consume any post-upgrade data from the socket - * buffer. Use MSG_PEEK to find the \0 message boundary and only consume up to it. For non-socket - * fds (pipes) MSG_PEEK is not available, so fall back to byte-by-byte reading. */ - rs = varlink_peek_upgrade_boundary(v, p, rs); - if (rs < 0) - return varlink_log_errno(v, rs, "Failed to peek upgrade boundary: %m"); - - if (v->allow_fd_passing_input > 0) { - iov = IOVEC_MAKE(p, rs); - - /* Allocate the fd buffer on the heap, since we need a lot of space potentially */ - if (!v->input_control_buffer) { - v->input_control_buffer_size = CMSG_SPACE(sizeof(int) * VARLINK_FDS_MAX); - v->input_control_buffer = malloc(v->input_control_buffer_size); - if (!v->input_control_buffer) - return -ENOMEM; - } - - mh = (struct msghdr) { - .msg_iov = &iov, - .msg_iovlen = 1, - .msg_control = v->input_control_buffer, - .msg_controllen = v->input_control_buffer_size, - }; - - n = recvmsg_safe(v->input_fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); - } else { - bool prefer_read = v->prefer_read; - if (!prefer_read) { - n = recv(v->input_fd, p, rs, MSG_DONTWAIT); - if (n < 0) - n = -errno; - if (n == -ENOTSOCK) - prefer_read = v->prefer_read = true; - } - if (prefer_read) { - n = read(v->input_fd, p, rs); - if (n < 0) - n = -errno; - } - } - if (ERRNO_IS_NEG_TRANSIENT(n)) - return 0; - if (ERRNO_IS_NEG_DISCONNECT(n)) { - v->read_disconnected = true; - return 1; - } - if (n < 0) - return n; - if (n == 0) { /* EOF */ - - if (v->allow_fd_passing_input > 0) - cmsg_close_all(&mh); - v->read_disconnected = true; - return 1; - } - - if (v->allow_fd_passing_input > 0) { - struct cmsghdr *cmsg; - - cmsg = cmsg_find(&mh, SOL_SOCKET, SCM_RIGHTS, (socklen_t) -1); - if (cmsg) { - size_t add; - - /* We only allow file descriptors to be passed along with the first byte of a - * message. If they are passed with any other byte this is a protocol violation. */ - if (v->input_buffer_size != 0) { - cmsg_close_all(&mh); - return -EPROTO; - } - - add = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); - if (add > INT_MAX - v->n_input_fds) { - cmsg_close_all(&mh); - return -EBADF; - } - - if (!GREEDY_REALLOC(v->input_fds, v->n_input_fds + add)) { - cmsg_close_all(&mh); - return -ENOMEM; - } - - memcpy_safe(v->input_fds + v->n_input_fds, CMSG_TYPED_DATA(cmsg, int), add * sizeof(int)); - v->n_input_fds += add; - } - } - - v->input_buffer_size += n; - v->input_buffer_unscanned += n; - - return 1; + return json_stream_read(&v->stream); } static int varlink_parse_message(sd_varlink *v) { - const char *e; - char *begin; - size_t sz; int r; assert(v); if (v->current) return 0; - if (v->input_buffer_unscanned <= 0) - return 0; - - assert(v->input_buffer_unscanned <= v->input_buffer_size); - assert(v->input_buffer_index + v->input_buffer_size <= MALLOC_SIZEOF_SAFE(v->input_buffer)); - - begin = v->input_buffer + v->input_buffer_index; - e = memchr(begin + v->input_buffer_size - v->input_buffer_unscanned, 0, v->input_buffer_unscanned); - if (!e) { - v->input_buffer_unscanned = 0; - return 0; - } - - sz = e - begin + 1; - - r = sd_json_parse(begin, SD_JSON_PARSE_MUST_BE_OBJECT, &v->current, /* reterr_line= */ NULL, /* reterr_column= */ NULL); - if (v->input_sensitive) - explicit_bzero_safe(begin, sz); - if (r < 0) { - /* If we encounter a parse failure flush all data. We cannot possibly recover from this, - * hence drop all buffered data now. */ - v->input_buffer_index = v->input_buffer_size = v->input_buffer_unscanned = 0; - return varlink_log_errno(v, r, "Failed to parse JSON object: %m"); - } + r = json_stream_parse(&v->stream, &v->current); + if (r <= 0) + return r; - if (v->input_sensitive) { + if (json_stream_flags_set(&v->stream, JSON_STREAM_INPUT_SENSITIVE)) { /* Mark the parameters subfield as sensitive right-away, if that's requested */ sd_json_variant *parameters = sd_json_variant_by_key(v->current, "parameters"); if (parameters) sd_json_variant_sensitive(parameters); } - if (DEBUG_LOGGING) { - _cleanup_(erase_and_freep) char *censored_text = NULL; - - /* Suppress sensitive fields in the debug output */ - r = sd_json_variant_format(v->current, /* flags= */ SD_JSON_FORMAT_CENSOR_SENSITIVE, &censored_text); - if (r < 0) - return r; - - varlink_log(v, "Received message: %s", censored_text); - } - - v->input_buffer_size -= sz; - - if (v->input_buffer_size == 0) - v->input_buffer_index = 0; - else - v->input_buffer_index += sz; - - v->input_buffer_unscanned = v->input_buffer_size; return 1; } static int varlink_test_timeout(sd_varlink *v) { assert(v); - if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING)) - return 0; - if (v->timeout == USEC_INFINITY) - return 0; - - if (now(CLOCK_MONOTONIC) < usec_add(v->timestamp, v->timeout)) + usec_t deadline = json_stream_get_timeout(&v->stream); + if (deadline == USEC_INFINITY || now(CLOCK_MONOTONIC) < deadline) return 0; varlink_set_state(v, VARLINK_PENDING_TIMEOUT); - return 1; } @@ -1348,157 +932,11 @@ static int generic_method_get_interface_description( r = sd_varlink_idl_format(interface, &text); if (r < 0) - return r; - - return sd_varlink_replybo( - link, - SD_JSON_BUILD_PAIR_STRING("description", text)); -} - -static int varlink_format_json(sd_varlink *v, sd_json_variant *m) { - _cleanup_(erase_and_freep) char *text = NULL; - int sz, r; - - assert(v); - assert(m); - - sz = sd_json_variant_format(m, /* flags= */ 0, &text); - if (sz < 0) - return sz; - assert(text[sz] == '\0'); - - if (v->output_buffer_size + sz + 1 > VARLINK_BUFFER_MAX) - return -ENOBUFS; - - if (DEBUG_LOGGING) { - _cleanup_(erase_and_freep) char *censored_text = NULL; - - /* Suppress sensitive fields in the debug output */ - r = sd_json_variant_format(m, SD_JSON_FORMAT_CENSOR_SENSITIVE, &censored_text); - if (r < 0) - return r; - - varlink_log(v, "Sending message: %s", censored_text); - } - - if (v->output_buffer_size == 0) { - - free_and_replace(v->output_buffer, text); - - v->output_buffer_size = sz + 1; - v->output_buffer_index = 0; - - } else if (v->output_buffer_index == 0) { - - if (!GREEDY_REALLOC(v->output_buffer, v->output_buffer_size + sz + 1)) - return -ENOMEM; - - memcpy(v->output_buffer + v->output_buffer_size, text, sz + 1); - v->output_buffer_size += sz + 1; - } else { - char *n; - const size_t new_size = v->output_buffer_size + sz + 1; - - n = new(char, new_size); - if (!n) - return -ENOMEM; - - memcpy(mempcpy(n, v->output_buffer + v->output_buffer_index, v->output_buffer_size), text, sz + 1); - - free_and_replace(v->output_buffer, n); - v->output_buffer_size = new_size; - v->output_buffer_index = 0; - } - - if (sd_json_variant_is_sensitive_recursive(m)) - v->output_buffer_sensitive = true; /* Propagate sensitive flag */ - else - text = mfree(text); /* No point in the erase_and_free() destructor declared above */ - - return 0; -} - -static int varlink_format_queue(sd_varlink *v) { - int r; - - assert(v); - - /* Takes entries out of the output queue and formats them into the output buffer. But only if this - * would not corrupt our fd message boundaries */ - - while (v->output_queue) { - assert(v->n_output_queue > 0); - - if (v->n_output_fds > 0) /* unwritten fds? if we'd add more we'd corrupt the fd message boundaries, hence wait */ - return 0; - - VarlinkJsonQueueItem *q = v->output_queue; - _cleanup_free_ int *array = NULL; - - if (q->n_fds > 0) { - array = newdup(int, q->fds, q->n_fds); - if (!array) - return -ENOMEM; - } - - r = varlink_format_json(v, q->data); - if (r < 0) - return r; - - /* Take possession of the queue element's fds */ - free_and_replace(v->output_fds, array); - v->n_output_fds = q->n_fds; - q->n_fds = 0; - - LIST_REMOVE(queue, v->output_queue, q); - if (!v->output_queue) - v->output_queue_tail = NULL; - v->n_output_queue--; - - varlink_json_queue_item_free(q); - } - - return 0; -} - -static int varlink_enqueue_item(sd_varlink *v, VarlinkJsonQueueItem *q) { - assert(v); - assert(q); - - if (v->n_output_queue >= VARLINK_QUEUE_MAX) - return -ENOBUFS; - - LIST_INSERT_AFTER(queue, v->output_queue, v->output_queue_tail, q); - v->output_queue_tail = q; - v->n_output_queue++; - return 0; -} - -static int varlink_enqueue_json(sd_varlink *v, sd_json_variant *m) { - VarlinkJsonQueueItem *q; - - assert(v); - assert(m); - - /* If there are no file descriptors to be queued and no queue entries yet we can shortcut things and - * append this entry directly to the output buffer */ - if (v->n_pushed_fds == 0 && !v->output_queue) - return varlink_format_json(v, m); - - if (v->n_output_queue >= VARLINK_QUEUE_MAX) - return -ENOBUFS; - - /* Otherwise add a queue entry for this */ - q = varlink_json_queue_item_new(m, v->pushed_fds, v->n_pushed_fds); - if (!q) - return -ENOMEM; - - v->n_pushed_fds = 0; /* fds now belong to the queue entry */ - - /* We already checked the precondition ourselves so this call cannot fail. */ - assert_se(varlink_enqueue_item(v, q) >= 0); + return r; - return 0; + return sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR_STRING("description", text)); } static int varlink_dispatch_method(sd_varlink *v) { @@ -1586,10 +1024,17 @@ static int varlink_dispatch_method(sd_varlink *v) { (flags & SD_VARLINK_METHOD_ONEWAY) ? VARLINK_PROCESSING_METHOD_ONEWAY : VARLINK_PROCESSING_METHOD); - v->protocol_upgrade = FLAGS_SET(flags, SD_VARLINK_METHOD_UPGRADE); - assert(v->server); + /* Reset the per-call upgrade marker on every dispatch — a previous method's + * UPGRADE flag must not bleed into this one. The transport-level bounded reads + * stay active for SD_VARLINK_SERVER_UPGRADABLE servers regardless. */ + v->protocol_upgrade = FLAGS_SET(flags, SD_VARLINK_METHOD_UPGRADE); + json_stream_set_flags( + &v->stream, + JSON_STREAM_BOUNDED_READS, + v->protocol_upgrade || FLAGS_SET(v->server->flags, SD_VARLINK_SERVER_UPGRADABLE)); + /* First consult user supplied method implementations */ callback = hashmap_get(v->server->methods, method); if (!callback) { @@ -1653,7 +1098,7 @@ static int varlink_dispatch_method(sd_varlink *v) { r = sd_varlink_error_errno(v, r); } else if (v->sentinel) { if (v->previous) { - r = varlink_enqueue_item(v, v->previous); + r = json_stream_enqueue_item(&v->stream, v->previous); if (r >= 0) { TAKE_PTR(v->previous); varlink_set_state(v, VARLINK_PROCESSED_METHOD); @@ -1883,85 +1328,13 @@ _public_ int sd_varlink_get_current_parameters(sd_varlink *v, sd_json_variant ** return 0; } -static void handle_revents(sd_varlink *v, int revents) { - assert(v); - - if (v->connecting) { - /* If we have seen POLLOUT or POLLHUP on a socket we are asynchronously waiting a connect() - * to complete on, we know we are ready. We don't read the connection error here though, - * we'll get the error on the next read() or write(). */ - if ((revents & (POLLOUT|POLLHUP)) == 0) - return; - - varlink_log(v, "Asynchronous connection completed."); - v->connecting = false; - } else { - /* Note that we don't care much about POLLIN/POLLOUT here, we'll just try reading and writing - * what we can. However, we do care about POLLHUP to detect connection termination even if we - * momentarily don't want to read nor write anything. */ - - if (!FLAGS_SET(revents, POLLHUP)) - return; - - varlink_log(v, "Got POLLHUP from socket."); - v->got_pollhup = true; - } -} - _public_ int sd_varlink_wait(sd_varlink *v, uint64_t timeout) { - int r, events; - usec_t t; - assert_return(v, -EINVAL); if (v->state == VARLINK_DISCONNECTED) return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); - r = sd_varlink_get_timeout(v, &t); - if (r < 0) - return r; - if (t != USEC_INFINITY) - t = usec_sub_unsigned(t, now(CLOCK_MONOTONIC)); - - t = MIN(t, timeout); - - events = sd_varlink_get_events(v); - if (events < 0) - return events; - - struct pollfd pollfd[2]; - size_t n_poll_fd = 0; - - if (v->input_fd == v->output_fd) { - pollfd[n_poll_fd++] = (struct pollfd) { - .fd = v->input_fd, - .events = events, - }; - } else { - pollfd[n_poll_fd++] = (struct pollfd) { - .fd = v->input_fd, - .events = events & POLLIN, - }; - pollfd[n_poll_fd++] = (struct pollfd) { - .fd = v->output_fd, - .events = events & POLLOUT, - }; - }; - - r = ppoll_usec(pollfd, n_poll_fd, t); - if (ERRNO_IS_NEG_TRANSIENT(r)) /* Treat EINTR as not a timeout, but also nothing happened, and - * the caller gets a chance to call back into us */ - return 1; - if (r <= 0) - return r; - - /* Merge the seen events into one */ - int revents = 0; - FOREACH_ARRAY(p, pollfd, n_poll_fd) - revents |= p->revents; - - handle_revents(v, revents); - return 1; + return json_stream_wait(&v->stream, timeout); } _public_ int sd_varlink_is_idle(sd_varlink *v) { @@ -1982,68 +1355,55 @@ _public_ int sd_varlink_is_connected(sd_varlink *v) { } _public_ int sd_varlink_get_fd(sd_varlink *v) { - assert_return(v, -EINVAL); if (v->state == VARLINK_DISCONNECTED) return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); - if (v->input_fd != v->output_fd) + + int input_fd = v->stream.input_fd; + int output_fd = v->stream.output_fd; + + if (input_fd != output_fd) return varlink_log_errno(v, SYNTHETIC_ERRNO(EBADF), "Separate file descriptors for input/output set."); - if (v->input_fd < 0) + if (input_fd < 0) return varlink_log_errno(v, SYNTHETIC_ERRNO(EBADF), "No valid fd."); - return v->input_fd; + return input_fd; } _public_ int sd_varlink_get_input_fd(sd_varlink *v) { - assert_return(v, -EINVAL); if (v->state == VARLINK_DISCONNECTED) return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); - if (v->input_fd < 0) + + int input_fd = v->stream.input_fd; + if (input_fd < 0) return varlink_log_errno(v, SYNTHETIC_ERRNO(EBADF), "No valid input fd."); - return v->input_fd; + return input_fd; } _public_ int sd_varlink_get_output_fd(sd_varlink *v) { - assert_return(v, -EINVAL); if (v->state == VARLINK_DISCONNECTED) return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); - if (v->output_fd < 0) + + int output_fd = v->stream.output_fd; + if (output_fd < 0) return varlink_log_errno(v, SYNTHETIC_ERRNO(EBADF), "No valid output fd."); - return v->output_fd; + return output_fd; } _public_ int sd_varlink_get_events(sd_varlink *v) { - int ret = 0; - assert_return(v, -EINVAL); if (v->state == VARLINK_DISCONNECTED) return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); - if (v->connecting) /* When processing an asynchronous connect(), we only wait for EPOLLOUT, which - * tells us that the connection is now complete. Before that we should neither - * write() or read() from the fd. */ - return EPOLLOUT; - - if (!v->read_disconnected && - IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING, VARLINK_IDLE_SERVER) && - !v->current && - v->input_buffer_unscanned <= 0) - ret |= EPOLLIN; - - if (!v->write_disconnected && - (v->output_queue || - v->output_buffer_size > 0)) - ret |= EPOLLOUT; - - return ret; + return json_stream_get_events(&v->stream); } _public_ int sd_varlink_get_timeout(sd_varlink *v, uint64_t *ret) { @@ -2052,51 +1412,21 @@ _public_ int sd_varlink_get_timeout(sd_varlink *v, uint64_t *ret) { if (v->state == VARLINK_DISCONNECTED) return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); - if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING) && - v->timeout != USEC_INFINITY) { - if (ret) - *ret = usec_add(v->timestamp, v->timeout); - return 1; - } else { - if (ret) - *ret = USEC_INFINITY; - return 0; - } + usec_t deadline = json_stream_get_timeout(&v->stream); + + if (ret) + *ret = deadline; + + return deadline != USEC_INFINITY; } _public_ int sd_varlink_flush(sd_varlink *v) { - int ret = 0, r; - assert_return(v, -EINVAL); if (v->state == VARLINK_DISCONNECTED) return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); - for (;;) { - if (v->output_buffer_size == 0 && !v->output_queue) - break; - if (v->write_disconnected) - return -ECONNRESET; - - r = varlink_write(v); - if (r < 0) - return r; - if (r > 0) { - ret = 1; - continue; - } - - r = fd_wait_for_event(v->output_fd, POLLOUT, USEC_INFINITY); - if (ERRNO_IS_NEG_TRANSIENT(r)) - continue; - if (r < 0) - return varlink_log_errno(v, r, "Poll failed on fd: %m"); - assert(r > 0); - - handle_revents(v, r); - } - - return ret; + return json_stream_flush(&v->stream); } static void varlink_detach_server(sd_varlink *v) { @@ -2107,18 +1437,22 @@ static void varlink_detach_server(sd_varlink *v) { if (!v->server) return; + /* Only touch by_uid for connections we already counted in count_connection() — + * those are exactly the ones for which the ucred was acquired or injected during + * sd_varlink_server_add_connection_pair(). Don't trigger an acquire from here. */ + struct ucred ucred; if (v->server->by_uid && - v->ucred_acquired && - uid_is_valid(v->ucred.uid)) { + json_stream_get_peer_ucred(&v->stream, &ucred) >= 0 && + uid_is_valid(ucred.uid)) { unsigned c; - c = PTR_TO_UINT(hashmap_get(v->server->by_uid, UID_TO_PTR(v->ucred.uid))); + c = PTR_TO_UINT(hashmap_get(v->server->by_uid, UID_TO_PTR(ucred.uid))); assert(c > 0); if (c == 1) - (void) hashmap_remove(v->server->by_uid, UID_TO_PTR(v->ucred.uid)); + (void) hashmap_remove(v->server->by_uid, UID_TO_PTR(ucred.uid)); else - (void) hashmap_replace(v->server->by_uid, UID_TO_PTR(v->ucred.uid), UINT_TO_PTR(c - 1)); + (void) hashmap_replace(v->server->by_uid, UID_TO_PTR(ucred.uid), UINT_TO_PTR(c - 1)); } assert(v->server->n_connections > 0); @@ -2194,12 +1528,12 @@ _public_ int sd_varlink_send(sd_varlink *v, const char *method, sd_json_variant if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - r = varlink_enqueue_json(v, m); + r = json_stream_enqueue(&v->stream, m); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); /* No state change here, this is one-way only after all */ - v->timestamp = now(CLOCK_MONOTONIC); + json_stream_mark_activity(&v->stream); return 0; } @@ -2241,13 +1575,13 @@ _public_ int sd_varlink_invoke(sd_varlink *v, const char *method, sd_json_varian if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - r = varlink_enqueue_json(v, m); + r = json_stream_enqueue(&v->stream, m); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); varlink_set_state(v, VARLINK_AWAITING_REPLY); v->n_pending++; - v->timestamp = now(CLOCK_MONOTONIC); + json_stream_mark_activity(&v->stream); return 0; } @@ -2292,13 +1626,13 @@ _public_ int sd_varlink_observe(sd_varlink *v, const char *method, sd_json_varia if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - r = varlink_enqueue_json(v, m); + r = json_stream_enqueue(&v->stream, m); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); varlink_set_state(v, VARLINK_AWAITING_REPLY_MORE); v->n_pending++; - v->timestamp = now(CLOCK_MONOTONIC); + json_stream_mark_activity(&v->stream); return 0; } @@ -2339,13 +1673,13 @@ static int varlink_call_internal(sd_varlink *v, sd_json_variant *request) { * that we can assign a new reply shortly. */ varlink_clear_current(v); - r = varlink_enqueue_json(v, request); + r = json_stream_enqueue(&v->stream, request); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); varlink_set_state(v, VARLINK_CALLING); v->n_pending++; - v->timestamp = now(CLOCK_MONOTONIC); + json_stream_mark_activity(&v->stream); while (v->state == VARLINK_CALLING) { r = sd_varlink_process(v); @@ -2441,44 +1775,41 @@ static int varlink_handle_upgrade_fds(sd_varlink *v, int *ret_input_fd, int *ret /* Ensure no post-upgrade data was consumed into our input buffer (we ensure this via MSG_PEEK or * byte-to-byte) and refuse the upgrade rather than silently losing the data. */ - if (v->input_buffer_size != 0) + if (json_stream_has_buffered_input(&v->stream)) return varlink_log_errno(v, SYNTHETIC_ERRNO(EPROTO), "Unexpected buffered data during protocol upgrade, refusing."); + _cleanup_close_ int input_fd = TAKE_FD(v->stream.input_fd), + output_fd = TAKE_FD(v->stream.output_fd); + /* Pass the connection fds to the caller, it owns them now. Reset to blocking mode * since callers of the upgraded protocol will generally expect normal blocking - * semantics. */ - r = fd_nonblock(v->input_fd, false); - if (r < 0) - return varlink_log_errno(v, r, "Failed to set input fd to blocking mode: %m"); - if (v->input_fd != v->output_fd) { - r = fd_nonblock(v->output_fd, false); + * semantics. For bidirectional sockets (input_fd == output_fd), dup the fd so that + * callers always get two independent fds they can close separately. */ + if (input_fd == output_fd) { + output_fd = fcntl(input_fd, F_DUPFD_CLOEXEC, 3); + if (output_fd < 0) + return varlink_log_errno(v, errno, "Failed to dup upgraded connection fd: %m"); + } else { + r = fd_nonblock(output_fd, false); if (r < 0) return varlink_log_errno(v, r, "Failed to set output fd to blocking mode: %m"); } - /* For bidirectional sockets (input_fd == output_fd), dup the fd so that callers - * always get two independent fds they can close separately. */ - if (v->input_fd == v->output_fd) { - v->output_fd = fcntl(v->input_fd, F_DUPFD_CLOEXEC, 3); - if (v->output_fd < 0) - return varlink_log_errno(v, errno, "Failed to dup upgraded connection fd: %m"); - } + r = fd_nonblock(input_fd, false); + if (r < 0) + return varlink_log_errno(v, r, "Failed to set input fd to blocking mode: %m"); /* Hand out requested fds, shut down unwanted directions. */ if (ret_input_fd) - *ret_input_fd = TAKE_FD(v->input_fd); - else { - (void) shutdown(v->input_fd, SHUT_RD); - v->input_fd = safe_close(v->input_fd); - } + *ret_input_fd = TAKE_FD(input_fd); + else + (void) shutdown(input_fd, SHUT_RD); if (ret_output_fd) - *ret_output_fd = TAKE_FD(v->output_fd); - else { - (void) shutdown(v->output_fd, SHUT_WR); - v->output_fd = safe_close(v->output_fd); - } + *ret_output_fd = TAKE_FD(output_fd); + else + (void) shutdown(output_fd, SHUT_WR); return 0; } @@ -2508,14 +1839,16 @@ _public_ int sd_varlink_call_and_upgrade( return varlink_log_errno(v, r, "Failed to build json message: %m"); v->protocol_upgrade = true; + json_stream_set_flags(&v->stream, JSON_STREAM_BOUNDED_READS, true); r = varlink_call_internal(v, m); if (r < 0) { v->protocol_upgrade = false; + json_stream_set_flags(&v->stream, JSON_STREAM_BOUNDED_READS, false); return r; } /* ensure we did not consume any data from the upgraded protocol */ - assert(v->input_buffer_size == 0); + assert(!json_stream_has_buffered_input(&v->stream)); sd_json_variant *e = sd_json_variant_by_key(v->current, "error"), *p = sd_json_variant_by_key(v->current, "parameters"); @@ -2555,6 +1888,7 @@ _public_ int sd_varlink_call_and_upgrade( finish: v->protocol_upgrade = false; + json_stream_set_flags(&v->stream, JSON_STREAM_BOUNDED_READS, false); assert(v->n_pending == 1); v->n_pending--; return r; @@ -2647,13 +1981,13 @@ _public_ int sd_varlink_collect_full( if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - r = varlink_enqueue_json(v, m); + r = json_stream_enqueue(&v->stream, m); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); varlink_set_state(v, VARLINK_COLLECTING); v->n_pending++; - v->timestamp = now(CLOCK_MONOTONIC); + json_stream_mark_activity(&v->stream); for (;;) { while (v->state == VARLINK_COLLECTING) { @@ -2815,24 +2149,23 @@ _public_ int sd_varlink_reply(sd_varlink *v, sd_json_variant *parameters) { if (more && v->sentinel) { if (v->previous) { - r = sd_json_variant_set_field_boolean(&v->previous->data, "continues", true); + r = sd_json_variant_set_field_boolean(json_stream_queue_item_get_data(v->previous), "continues", true); if (r < 0) return r; - r = varlink_enqueue_item(v, v->previous); + r = json_stream_enqueue_item(&v->stream, v->previous); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); } - v->previous = varlink_json_queue_item_new(m, v->pushed_fds, v->n_pushed_fds); - if (!v->previous) - return -ENOMEM; + r = json_stream_make_queue_item(&v->stream, m, &v->previous); + if (r < 0) + return r; - v->n_pushed_fds = 0; /* fds now belong to the queue entry */ return 1; } - r = varlink_enqueue_json(v, m); + r = json_stream_enqueue(&v->stream, m); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); @@ -2891,7 +2224,7 @@ _public_ int sd_varlink_reply_and_upgrade(sd_varlink *v, sd_json_variant *parame * client). In normal operation this cannot happen because the client waits for our reply before * sending raw data, and we set protocol_upgrade=true in dispatch to limit subsequent reads to * single bytes. But a misbehaving client could pipeline data early. */ - if (v->input_buffer_size > 0) + if (json_stream_has_buffered_input(&v->stream)) return varlink_log_errno(v, SYNTHETIC_ERRNO(EBADMSG), "Unexpected buffered data from client during protocol upgrade."); @@ -2912,40 +2245,20 @@ _public_ int sd_varlink_reply_and_upgrade(sd_varlink *v, sd_json_variant *parame if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - r = varlink_enqueue_json(v, m); + r = json_stream_enqueue(&v->stream, m); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); /* Flush the reply to the socket before stealing the fds. The reply must be fully written * before the caller starts speaking the upgraded protocol. */ - for (;;) { - r = varlink_write(v); - if (r < 0) { - varlink_log_errno(v, r, "Failed to flush reply: %m"); - goto disconnect; - } - if (v->output_buffer_size == 0 && !v->output_queue) - break; - if (v->write_disconnected) { - r = varlink_log_errno(v, SYNTHETIC_ERRNO(ECONNRESET), - "Write disconnected during upgrade reply flush."); - goto disconnect; - } - - r = fd_wait_for_event(v->output_fd, POLLOUT, USEC_INFINITY); - if (ERRNO_IS_NEG_TRANSIENT(r)) - continue; - if (r < 0) { - varlink_log_errno(v, r, "Failed to wait for writable fd: %m"); - goto disconnect; - } - assert(r > 0); - - handle_revents(v, r); + r = json_stream_flush(&v->stream); + if (r < 0) { + varlink_log_errno(v, r, "Failed to flush reply before protocol upgrade: %m"); + goto disconnect; } /* Detach from the event loop before stealing the fds */ - varlink_detach_event_sources(v); + sd_varlink_detach_event(v); /* Now hand the original FDs over to the caller, from this point on we have nothing to do with the * connection anymore, it's up to the caller and we close the connection below */ @@ -2966,8 +2279,7 @@ _public_ int sd_varlink_reset_fds(sd_varlink *v) { * rollback the fds. Note that this is implicitly called whenever an error reply is sent, see * below. */ - close_many(v->pushed_fds, v->n_pushed_fds); - v->n_pushed_fds = 0; + json_stream_reset_pushed_fds(&v->stream); return 0; } @@ -2986,14 +2298,14 @@ _public_ int sd_varlink_error(sd_varlink *v, const char *error_id, sd_json_varia return varlink_log_errno(v, SYNTHETIC_ERRNO(EBUSY), "Connection busy."); if (v->previous) { - r = sd_json_variant_set_field_boolean(&v->previous->data, "continues", true); + r = sd_json_variant_set_field_boolean(json_stream_queue_item_get_data(v->previous), "continues", true); if (r < 0) return r; /* If we have a previous reply still ready make sure we queue it before the error. We only * ever set "previous" if we're in a streaming method so we pass more=true unconditionally * here as we know we're still going to queue an error afterwards. */ - r = varlink_enqueue_item(v, v->previous); + r = json_stream_enqueue_item(&v->stream, v->previous); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); @@ -3028,7 +2340,7 @@ _public_ int sd_varlink_error(sd_varlink *v, const char *error_id, sd_json_varia if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - r = varlink_enqueue_json(v, m); + r = json_stream_enqueue(&v->stream, m); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); @@ -3168,7 +2480,7 @@ _public_ int sd_varlink_notify(sd_varlink *v, sd_json_variant *parameters) { if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - r = varlink_enqueue_json(v, m); + r = json_stream_enqueue(&v->stream, m); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); @@ -3262,99 +2574,38 @@ _public_ int sd_varlink_set_sentinel(sd_varlink *v, const char *error_id) { return 0; } -static int varlink_acquire_ucred(sd_varlink *v) { - int r; - - assert(v); - - if (v->ucred_acquired) - return 0; - - /* If we are connected asymmetrically, let's refuse, since it's not clear if caller wants to know - * peer on read or write fd */ - if (v->input_fd != v->output_fd) - return -EBADF; - - r = getpeercred(v->input_fd, &v->ucred); - if (r < 0) - return r; - - v->ucred_acquired = true; - return 0; -} - _public_ int sd_varlink_get_peer_uid(sd_varlink *v, uid_t *ret) { - int r; - assert_return(v, -EINVAL); assert_return(ret, -EINVAL); - r = varlink_acquire_ucred(v); - if (r < 0) - return varlink_log_errno(v, r, "Failed to acquire credentials: %m"); - - if (!uid_is_valid(v->ucred.uid)) - return varlink_log_errno(v, SYNTHETIC_ERRNO(ENODATA), "Peer UID is invalid."); - - *ret = v->ucred.uid; - return 0; + return json_stream_acquire_peer_uid(&v->stream, ret); } _public_ int sd_varlink_get_peer_gid(sd_varlink *v, gid_t *ret) { - int r; - assert_return(v, -EINVAL); assert_return(ret, -EINVAL); - r = varlink_acquire_ucred(v); - if (r < 0) - return varlink_log_errno(v, r, "Failed to acquire credentials: %m"); - - if (!gid_is_valid(v->ucred.gid)) - return varlink_log_errno(v, SYNTHETIC_ERRNO(ENODATA), "Peer GID is invalid."); - - *ret = v->ucred.gid; - return 0; + return json_stream_acquire_peer_gid(&v->stream, ret); } _public_ int sd_varlink_get_peer_pid(sd_varlink *v, pid_t *ret) { - int r; - assert_return(v, -EINVAL); assert_return(ret, -EINVAL); - r = varlink_acquire_ucred(v); - if (r < 0) - return varlink_log_errno(v, r, "Failed to acquire credentials: %m"); - - if (!pid_is_valid(v->ucred.pid)) - return varlink_log_errno(v, SYNTHETIC_ERRNO(ENODATA), "Peer uid is invalid."); - - *ret = v->ucred.pid; - return 0; + return json_stream_acquire_peer_pid(&v->stream, ret); } _public_ int sd_varlink_get_peer_pidfd(sd_varlink *v) { assert_return(v, -EINVAL); - if (v->peer_pidfd >= 0) - return v->peer_pidfd; - - if (v->input_fd != v->output_fd) - return -EBADF; - - v->peer_pidfd = getpeerpidfd(v->input_fd); - if (v->peer_pidfd < 0) - return varlink_log_errno(v, v->peer_pidfd, "Failed to acquire pidfd of peer: %m"); - - return v->peer_pidfd; + return json_stream_acquire_peer_pidfd(&v->stream); } _public_ int sd_varlink_set_relative_timeout(sd_varlink *v, uint64_t timeout) { assert_return(v, -EINVAL); /* If set to 0, reset to default value */ - v->timeout = timeout == 0 ? VARLINK_DEFAULT_TIMEOUT_USEC : timeout; + json_stream_set_timeout(&v->stream, timeout == 0 ? VARLINK_DEFAULT_TIMEOUT_USEC : timeout); return 0; } @@ -3367,33 +2618,13 @@ _public_ sd_varlink_server *sd_varlink_get_server(sd_varlink *v) { _public_ int sd_varlink_set_description(sd_varlink *v, const char *description) { assert_return(v, -EINVAL); - return free_and_strdup(&v->description, description); + return json_stream_set_description(&v->stream, description); } _public_ const char* sd_varlink_get_description(sd_varlink *v) { assert_return(v, NULL); - return v->description; -} - -static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - sd_varlink *v = ASSERT_PTR(userdata); - - assert(s); - - handle_revents(v, revents); - (void) sd_varlink_process(v); - - return 1; -} - -static int time_callback(sd_event_source *s, uint64_t usec, void *userdata) { - sd_varlink *v = ASSERT_PTR(userdata); - - assert(s); - - (void) sd_varlink_process(v); - return 1; + return json_stream_get_description(&v->stream); } static int defer_callback(sd_event_source *s, void *userdata) { @@ -3405,47 +2636,6 @@ static int defer_callback(sd_event_source *s, void *userdata) { return 1; } -static int prepare_callback(sd_event_source *s, void *userdata) { - sd_varlink *v = ASSERT_PTR(userdata); - int r, e; - usec_t until; - bool have_timeout; - - assert(s); - - e = sd_varlink_get_events(v); - if (e < 0) - return e; - - if (v->input_event_source == v->output_event_source) - /* Same fd for input + output */ - r = sd_event_source_set_io_events(v->input_event_source, e); - else { - r = sd_event_source_set_io_events(v->input_event_source, e & EPOLLIN); - if (r >= 0) - r = sd_event_source_set_io_events(v->output_event_source, e & EPOLLOUT); - } - if (r < 0) - return varlink_log_errno(v, r, "Failed to set source events: %m"); - - r = sd_varlink_get_timeout(v, &until); - if (r < 0) - return r; - have_timeout = r > 0; - - if (have_timeout) { - r = sd_event_source_set_time(v->time_event_source, until); - if (r < 0) - return varlink_log_errno(v, r, "Failed to set source time: %m"); - } - - r = sd_event_source_set_enabled(v->time_event_source, have_timeout ? SD_EVENT_ON : SD_EVENT_OFF); - if (r < 0) - return varlink_log_errno(v, r, "Failed to enable event source: %m"); - - return 1; -} - static int quit_callback(sd_event_source *event, void *userdata) { sd_varlink *v = ASSERT_PTR(userdata); @@ -3461,27 +2651,15 @@ _public_ int sd_varlink_attach_event(sd_varlink *v, sd_event *e, int64_t priorit int r; assert_return(v, -EINVAL); - assert_return(!v->event, -EBUSY); - - if (e) - v->event = sd_event_ref(e); - else { - r = sd_event_default(&v->event); - if (r < 0) - return varlink_log_errno(v, r, "Failed to create event source: %m"); - } - - r = sd_event_add_time(v->event, &v->time_event_source, CLOCK_MONOTONIC, 0, 0, time_callback, v); - if (r < 0) - goto fail; + assert_return(!json_stream_get_event(&v->stream), -EBUSY); - r = sd_event_source_set_priority(v->time_event_source, priority); + r = json_stream_attach_event(&v->stream, e, priority); if (r < 0) - goto fail; + return r; - (void) sd_event_source_set_description(v->time_event_source, "varlink-time"); + sd_event *event = json_stream_get_event(&v->stream); - r = sd_event_add_exit(v->event, &v->quit_event_source, quit_callback, v); + r = sd_event_add_exit(event, &v->quit_event_source, quit_callback, v); if (r < 0) goto fail; @@ -3491,35 +2669,7 @@ _public_ int sd_varlink_attach_event(sd_varlink *v, sd_event *e, int64_t priorit (void) sd_event_source_set_description(v->quit_event_source, "varlink-quit"); - r = sd_event_add_io(v->event, &v->input_event_source, v->input_fd, 0, io_callback, v); - if (r < 0) - goto fail; - - r = sd_event_source_set_prepare(v->input_event_source, prepare_callback); - if (r < 0) - goto fail; - - r = sd_event_source_set_priority(v->input_event_source, priority); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(v->input_event_source, "varlink-input"); - - if (v->input_fd == v->output_fd) - v->output_event_source = sd_event_source_ref(v->input_event_source); - else { - r = sd_event_add_io(v->event, &v->output_event_source, v->output_fd, 0, io_callback, v); - if (r < 0) - goto fail; - - r = sd_event_source_set_priority(v->output_event_source, priority); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(v->output_event_source, "varlink-output"); - } - - r = sd_event_add_defer(v->event, &v->defer_event_source, defer_callback, v); + r = sd_event_add_defer(event, &v->defer_event_source, defer_callback, v); if (r < 0) goto fail; @@ -3541,38 +2691,28 @@ _public_ void sd_varlink_detach_event(sd_varlink *v) { if (!v) return; - varlink_detach_event_sources(v); - - v->event = sd_event_unref(v->event); + v->quit_event_source = sd_event_source_disable_unref(v->quit_event_source); + v->defer_event_source = sd_event_source_disable_unref(v->defer_event_source); + json_stream_detach_event(&v->stream); } _public_ sd_event* sd_varlink_get_event(sd_varlink *v) { assert_return(v, NULL); - return v->event; + return json_stream_get_event(&v->stream); } _public_ int sd_varlink_push_fd(sd_varlink *v, int fd) { - int i; - assert_return(v, -EINVAL); assert_return(fd >= 0, -EBADF); /* Takes an fd to send along with the *next* varlink message sent via this varlink connection. This * takes ownership of the specified fd. Use varlink_dup_fd() below to duplicate the fd first. */ - if (!v->allow_fd_passing_output) + if (!json_stream_flags_set(&v->stream, JSON_STREAM_ALLOW_FD_PASSING_OUTPUT)) return -EPERM; - if (v->n_pushed_fds >= SCM_MAX_FD) /* Kernel doesn't support more than 253 fds per message, refuse early hence */ - return -ENOBUFS; - - if (!GREEDY_REALLOC(v->pushed_fds, v->n_pushed_fds + 1)) - return -ENOMEM; - - i = (int) v->n_pushed_fds; - v->pushed_fds[v->n_pushed_fds++] = fd; - return i; + return json_stream_push_fd(&v->stream, fd); } _public_ int sd_varlink_push_dup_fd(sd_varlink *v, int fd) { @@ -3602,13 +2742,10 @@ _public_ int sd_varlink_peek_fd(sd_varlink *v, size_t i) { /* Returns one of the file descriptors that were received along with the current message. This does * not duplicate the fd nor invalidate it, it hence remains in our possession. */ - if (v->allow_fd_passing_input <= 0) + if (!json_stream_flags_set(&v->stream, JSON_STREAM_ALLOW_FD_PASSING_INPUT)) return -EPERM; - if (i >= v->n_input_fds) - return -ENXIO; - - return v->input_fds[i]; + return json_stream_peek_input_fd(&v->stream, i); } _public_ int sd_varlink_peek_dup_fd(sd_varlink *v, size_t i) { @@ -3628,113 +2765,42 @@ _public_ int sd_varlink_take_fd(sd_varlink *v, size_t i) { * we'll invalidate the reference to it under our possession. If called twice in a row will return * -EBADF */ - if (v->allow_fd_passing_input <= 0) + if (!json_stream_flags_set(&v->stream, JSON_STREAM_ALLOW_FD_PASSING_INPUT)) return -EPERM; - if (i >= v->n_input_fds) - return -ENXIO; - - return TAKE_FD(v->input_fds[i]); + return json_stream_take_input_fd(&v->stream, i); } _public_ int sd_varlink_get_n_fds(sd_varlink *v) { assert_return(v, -EINVAL); - if (v->allow_fd_passing_input <= 0) + if (!json_stream_flags_set(&v->stream, JSON_STREAM_ALLOW_FD_PASSING_INPUT)) return -EPERM; - return (int) v->n_input_fds; -} - -static int verify_unix_socket(sd_varlink *v) { - assert(v); - - /* Returns: - * • 0 if this is an AF_UNIX socket - * • -ENOTSOCK if this is not a socket at all - * • -ENOMEDIUM if this is a socket, but not an AF_UNIX socket - * - * Reminder: - * • v->af is < 0 if we haven't checked what kind of address family the thing is yet. - * • v->af == AF_UNSPEC if we checked but it's not a socket - * • otherwise: v->af contains the address family we determined */ - - if (v->af < 0) { - /* If we have distinct input + output fds, we don't consider ourselves to be connected via a regular - * AF_UNIX socket. */ - if (v->input_fd != v->output_fd) { - v->af = AF_UNSPEC; - return -ENOTSOCK; - } - - struct stat st; - - if (fstat(v->input_fd, &st) < 0) - return -errno; - if (!S_ISSOCK(st.st_mode)) { - v->af = AF_UNSPEC; - return -ENOTSOCK; - } - - v->af = socket_get_family(v->input_fd); - if (v->af < 0) - return v->af; - } - - return v->af == AF_UNIX ? 0 : - v->af == AF_UNSPEC ? -ENOTSOCK : -ENOMEDIUM; + return (int) json_stream_get_n_input_fds(&v->stream); } _public_ int sd_varlink_set_allow_fd_passing_input(sd_varlink *v, int b) { - int r; - assert_return(v, -EINVAL); - if (v->allow_fd_passing_input >= 0 && (v->allow_fd_passing_input > 0) == !!b) - return 0; - - r = verify_unix_socket(v); - if (r < 0) { - assert(v->allow_fd_passing_input <= 0); - - if (!b) { - v->allow_fd_passing_input = false; - return 0; - } - - return r; - } - - if (!v->server || FLAGS_SET(v->server->flags, SD_VARLINK_SERVER_FD_PASSING_INPUT_STRICT)) { - r = setsockopt_int(v->input_fd, SOL_SOCKET, SO_PASSRIGHTS, !!b); - if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r)) - log_debug_errno(r, "Failed to set SO_PASSRIGHTS socket option: %m"); - } + /* Server connections that haven't opted into FD_PASSING_INPUT_STRICT skip the + * per-connection SO_PASSRIGHTS setsockopt — the listening server already configured + * the socket option once at listen time. */ + bool with_sockopt = !v->server || FLAGS_SET(v->server->flags, SD_VARLINK_SERVER_FD_PASSING_INPUT_STRICT); - v->allow_fd_passing_input = !!b; - return 1; + return json_stream_set_allow_fd_passing_input(&v->stream, !!b, with_sockopt); } _public_ int sd_varlink_set_allow_fd_passing_output(sd_varlink *v, int b) { - int r; - assert_return(v, -EINVAL); - if (v->allow_fd_passing_output == !!b) - return 0; - - r = verify_unix_socket(v); - if (r < 0) - return r; - - v->allow_fd_passing_output = b; - return 1; + return json_stream_set_allow_fd_passing_output(&v->stream, !!b); } _public_ int sd_varlink_set_input_sensitive(sd_varlink *v) { assert_return(v, -EINVAL); - v->input_sensitive = true; + json_stream_set_flags(&v->stream, JSON_STREAM_INPUT_SENSITIVE, true); return 0; } @@ -3955,19 +3021,24 @@ _public_ int sd_varlink_server_add_connection_pair( v->server = sd_varlink_server_ref(server); sd_varlink_ref(v); - v->input_fd = input_fd; - v->output_fd = output_fd; + r = json_stream_attach_fds(&v->stream, input_fd, output_fd); + if (r < 0) + return r; + if (server->flags & SD_VARLINK_SERVER_INHERIT_USERDATA) v->userdata = server->userdata; - if (ucred_acquired) { - v->ucred = ucred; - v->ucred_acquired = true; - } + /* If the server might receive a protocol upgrade method, switch the input path to + * byte-bounded reads so we don't accidentally consume post-upgrade bytes. */ + if (FLAGS_SET(server->flags, SD_VARLINK_SERVER_UPGRADABLE)) + json_stream_set_flags(&v->stream, JSON_STREAM_BOUNDED_READS, true); + + if (ucred_acquired) + json_stream_set_peer_ucred(&v->stream, &ucred); _cleanup_free_ char *desc = NULL; if (asprintf(&desc, "%s-%i-%i", varlink_server_description(server), input_fd, output_fd) >= 0) - v->description = TAKE_PTR(desc); + json_stream_set_description(&v->stream, desc); (void) sd_varlink_set_allow_fd_passing_input(v, FLAGS_SET(server->flags, SD_VARLINK_SERVER_ALLOW_FD_PASSING_INPUT)); (void) sd_varlink_set_allow_fd_passing_output(v, FLAGS_SET(server->flags, SD_VARLINK_SERVER_ALLOW_FD_PASSING_OUTPUT)); @@ -3978,8 +3049,12 @@ _public_ int sd_varlink_server_add_connection_pair( r = sd_varlink_attach_event(v, server->event, server->event_priority); if (r < 0) { varlink_log_errno(v, r, "Failed to attach new connection: %m"); - TAKE_FD(v->input_fd); /* take the fd out of the connection again */ - TAKE_FD(v->output_fd); + /* Detach the fds from the connection so the caller (the connect callback) + * can decide what to do with them. The original fd value(s) the caller + * passed in are still owned by the caller; we just stop the connection + * from closing them on shutdown. */ + TAKE_FD(v->stream.input_fd); + TAKE_FD(v->stream.output_fd); sd_varlink_close(v); return r; } diff --git a/src/libsystemd/sd-varlink/varlink-internal.h b/src/libsystemd/sd-varlink/varlink-internal.h index 3dfe86d4487e6..966afd0b06cff 100644 --- a/src/libsystemd/sd-varlink/varlink-internal.h +++ b/src/libsystemd/sd-varlink/varlink-internal.h @@ -5,6 +5,7 @@ #include "sd-varlink.h" +#include "json-stream.h" #include "list.h" #include "pidref.h" #include "sd-forward.h" @@ -70,85 +71,21 @@ typedef enum VarlinkState { VARLINK_PROCESSING_METHOD, \ VARLINK_PROCESSING_METHOD_MORE) -typedef struct VarlinkJsonQueueItem VarlinkJsonQueueItem; - -/* A queued message we shall write into the socket, along with the file descriptors to send at the same - * time. This queue item binds them together so that message/fd boundaries are maintained throughout the - * whole pipeline. */ -struct VarlinkJsonQueueItem { - LIST_FIELDS(VarlinkJsonQueueItem, queue); - sd_json_variant *data; - size_t n_fds; - int fds[]; -}; - typedef struct sd_varlink { unsigned n_ref; sd_varlink_server *server; VarlinkState state; - bool connecting; /* This boolean indicates whether the socket fd we are operating on is currently - * processing an asynchronous connect(). In that state we watch the socket for - * EPOLLOUT, but we refrain from calling read() or write() on the socket as that - * will trigger ENOTCONN. Note that this boolean is kept separate from the - * VarlinkState above on purpose: while the connect() is still not complete we - * already want to allow queuing of messages and similar. Thus it's nice to keep - * these two state concepts separate: the VarlinkState encodes what our own view of - * the connection is, i.e. whether we think it's a server, a client, and has - * something queued already, while 'connecting' tells us a detail about the - * transport used below, that should have no effect on how we otherwise accept and - * process operations from the user. - * - * Or to say this differently: VARLINK_STATE_IS_ALIVE(state) tells you whether the - * connection is good to use, even if it might not be fully connected - * yet. connecting=true then informs you that actually we are still connecting, and - * the connection is actually not established yet and thus any requests you enqueue - * now will still work fine but will be queued only, not sent yet, but that - * shouldn't stop you from using the connection, since eventually whatever you queue - * *will* be sent. - * - * Or to say this even differently: 'state' is a high-level ("application layer" - * high, if you so will) state, while 'conecting' is a low-level ("transport layer" - * low, if you so will) state, and while they are not entirely unrelated and - * sometimes propagate effects to each other they are only asynchronously connected - * at most. */ - unsigned n_pending; - - int input_fd; - int output_fd; - - char *input_buffer; /* valid data starts at input_buffer_index, ends at input_buffer_index+input_buffer_size */ - size_t input_buffer_index; - size_t input_buffer_size; - size_t input_buffer_unscanned; - - void *input_control_buffer; - size_t input_control_buffer_size; - - char *output_buffer; /* valid data starts at output_buffer_index, ends at output_buffer_index+output_buffer_size */ - size_t output_buffer_index; - size_t output_buffer_size; - - int *input_fds; /* file descriptors associated with the data in input_buffer (for fd passing) */ - size_t n_input_fds; - - int *output_fds; /* file descriptors associated with the data in output_buffer (for fd passing) */ - size_t n_output_fds; - /* Further messages to output not yet formatted into text, and thus not included in output_buffer - * yet. We keep them separate from output_buffer, to not violate fd message boundaries: we want that - * each fd that is sent is associated with its fds, and that fds cannot be accidentally associated - * with preceding or following messages. */ - LIST_HEAD(VarlinkJsonQueueItem, output_queue); - VarlinkJsonQueueItem *output_queue_tail; - size_t n_output_queue; + /* Transport layer: input/output buffers, fd passing, output queue, read/write/parse + * step functions, sd-event integration (input/output/time event sources, idle + * timeout, description, peer credentials). The varlink-level state machine and + * dispatch logic live in sd-varlink.c; everything else about moving bytes is + * delegated. */ + JsonStream stream; - /* The fds to associate with the next message that is about to be enqueued. The user first pushes the - * fds it intends to send via varlink_push_fd() into this queue, and then once the message data is - * submitted we'll combine the fds and the message data into one. */ - int *pushed_fds; - size_t n_pushed_fds; + unsigned n_pending; sd_varlink_reply_t reply_callback; @@ -157,39 +94,18 @@ typedef struct sd_varlink { sd_varlink_reply_flags_t current_reply_flags; sd_varlink_symbol *current_method; - VarlinkJsonQueueItem *previous; + JsonStreamQueueItem *previous; char *sentinel; - int peer_pidfd; - struct ucred ucred; - bool ucred_acquired:1; - - bool write_disconnected:1; - bool read_disconnected:1; - bool prefer_read:1; - bool prefer_write:1; - bool got_pollhup:1; - - bool protocol_upgrade:1; /* Whether a protocol_upgrade was requested */ - - bool output_buffer_sensitive:1; /* whether to erase the output buffer after writing it to the socket */ - bool input_sensitive:1; /* Whether incoming messages might be sensitive */ - - bool allow_fd_passing_output; - int allow_fd_passing_input; - - int af; /* address family if socket; AF_UNSPEC if not socket; negative if not known */ - - usec_t timestamp; - usec_t timeout; + /* Per-call protocol-upgrade marker: set when the *current* method call carries the + * SD_VARLINK_METHOD_UPGRADE flag. Validated by sd_varlink_reply_and_upgrade() to + * ensure the caller's contract is honored. The transport-layer "stop reading at the + * next message boundary" behavior is governed independently by the JsonStream's + * bounded_reads flag. */ + bool protocol_upgrade:1; void *userdata; - char *description; - sd_event *event; - sd_event_source *input_event_source; - sd_event_source *output_event_source; - sd_event_source *time_event_source; sd_event_source *quit_event_source; sd_event_source *defer_event_source; @@ -254,7 +170,7 @@ typedef struct sd_varlink_server { log_debug("%s: " fmt, varlink_server_description(s), ##__VA_ARGS__) static inline const char* varlink_description(sd_varlink *v) { - return (v ? v->description : NULL) ?: "varlink"; + return (v ? json_stream_get_description(&v->stream) : NULL) ?: "varlink"; } static inline const char* varlink_server_description(sd_varlink_server *s) { From 1016dd315f94917cd0818a90bc09c99ef76ab556 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Fri, 10 Apr 2026 17:20:03 +0200 Subject: [PATCH 0898/1296] sd-json: limit the stack depth during parsing as well --- src/libsystemd/sd-json/sd-json.c | 12 +++++++++ src/test/test-json.c | 43 ++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index 6245d471b7a6f..1fd006c7d9572 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -3122,6 +3122,12 @@ static int json_parse_internal( goto finish; } + /* n_stack includes the top level entry, hence > instead of >= */ + if (n_stack > DEPTH_MAX) { + r = -ELNRNG; + goto finish; + } + if (!GREEDY_REALLOC(stack, n_stack+1)) { r = -ENOMEM; goto finish; @@ -3178,6 +3184,12 @@ static int json_parse_internal( goto finish; } + /* n_stack includes the top level entry, hence > instead of >= */ + if (n_stack > DEPTH_MAX) { + r = -ELNRNG; + goto finish; + } + if (!GREEDY_REALLOC(stack, n_stack+1)) { r = -ENOMEM; goto finish; diff --git a/src/test/test-json.c b/src/test/test-json.c index 016416dc4b728..8e2c8621c1f6d 100644 --- a/src/test/test-json.c +++ b/src/test/test-json.c @@ -556,6 +556,49 @@ TEST(depth) { fputs("\n", stdout); } +static char *prepare_nested_json(const char *open, unsigned depth) { + char *s, *p; + size_t olen; + + assert_se(open); + + olen = strlen(open); + s = p = new(char, olen * depth + 1); + if (!s) + return NULL; + + for (unsigned i = 0; i < depth; i++) + p = mempcpy(p, open, olen); + *p = '\0'; + + return s; +} + +TEST(parse_depth) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + _cleanup_free_ char *s = NULL; + + /* Refuse parsing > DEPTH_MAX (currently 2048) levels of nested arrays */ + s = prepare_nested_json("[", 2049); + ASSERT_ERROR(sd_json_parse(s, 0, &v, NULL, NULL), ELNRNG); + s = mfree(s); + + /* Same for nested objects */ + s = prepare_nested_json("{\"a\":", 2049); + ASSERT_ERROR(sd_json_parse(s, 0, &v, NULL, NULL), ELNRNG); + s = mfree(s); + + /* <= DEPTH_MAX levels of nested arrays should be refused by EINVAL + * later in the parsing process */ + s = prepare_nested_json("[", 2048); + ASSERT_ERROR(sd_json_parse(s, 0, &v, NULL, NULL), EINVAL); + s = mfree(s); + + /* And the same for nested objects */ + s = prepare_nested_json("{\"a\":", 2048); + ASSERT_ERROR(sd_json_parse(s, 0, &v, NULL, NULL), EINVAL); +} + TEST(normalize) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *w = NULL; _cleanup_free_ char *t = NULL; From 056bc106e1e344f98cdfa86fdf62e6fed72958c9 Mon Sep 17 00:00:00 2001 From: Nandakumar Raghavan Date: Tue, 24 Mar 2026 13:42:42 +0000 Subject: [PATCH 0899/1296] core: fix EBUSY on restart and clean of delegated services When a service is configured with Delegate=yes and DelegateSubgroup=sub, the delegated container may write domain controllers (e.g. "pids") into the service cgroup's cgroup.subtree_control via its cgroupns root. On container exit the stale controllers remain, and on service restart clone3() with CLONE_INTO_CGROUP fails with EBUSY because placing a process into a cgroup that has domain controllers in subtree_control violates the no-internal- processes rule. The same issue affects systemctl clean, where cg_attach() fails with EBUSY for the same reason. Add unit_cgroup_disable_all_controllers() helper in cgroup.c that clears stale controllers via cg_enable(mask=0) and updates cgroup_enabled_mask to keep internal tracking in sync. Call it from service_start() and service_clean() right before spawning, so that resource control is preserved for any lingering processes from the previous invocation as long as possible. --- src/core/cgroup.c | 23 +++++++++++++++++++++++ src/core/cgroup.h | 2 ++ src/core/service.c | 6 +++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/core/cgroup.c b/src/core/cgroup.c index ddca4afbc329b..ae5874cd99daa 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -3988,6 +3988,29 @@ bool unit_cgroup_delegate(Unit *u) { return c->delegate; } +void unit_cgroup_disable_all_controllers(Unit *u) { + int r; + + assert(u); + + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) + return; + + if (!unit_cgroup_delegate(u)) + return; + + /* For delegated units, the previous payload may have enabled controllers (e.g. "pids") in + * cgroup.subtree_control. These persist after the service stops and turn the cgroup into an + * "internal node", causing clone3(CLONE_INTO_CGROUP) to fail with EBUSY. Clear them now, right + * before the new start, so that resource control is preserved for lingering processes as long as + * possible. Ignore errors — if sub-cgroups still have live processes the write will fail, but so + * will the upcoming spawn. */ + r = cg_enable(u->manager->cgroup_supported, /* mask= */ 0, crt->cgroup_path, &crt->cgroup_enabled_mask); + if (r < 0) + log_unit_debug_errno(u, r, "Failed to disable controllers on cgroup %s, ignoring: %m", empty_to_root(crt->cgroup_path)); +} + void manager_invalidate_startup_units(Manager *m) { Unit *u; diff --git a/src/core/cgroup.h b/src/core/cgroup.h index ce98f4ba7cd3b..d9a6ded110214 100644 --- a/src/core/cgroup.h +++ b/src/core/cgroup.h @@ -471,6 +471,8 @@ void unit_cgroup_catchup(Unit *u); bool unit_cgroup_delegate(Unit *u); +void unit_cgroup_disable_all_controllers(Unit *u); + int unit_get_cpuset(Unit *u, CPUSet *cpus, const char *name); int unit_cgroup_freezer_action(Unit *u, FreezerAction action); diff --git a/src/core/service.c b/src/core/service.c index 569a6871d602f..63e659942188f 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -13,6 +13,7 @@ #include "bus-common-errors.h" #include "bus-error.h" #include "bus-util.h" +#include "cgroup.h" #include "chase.h" #include "dbus-service.h" #include "dbus-unit.h" @@ -3174,8 +3175,10 @@ static int service_start(Unit *u) { exec_status_reset(&s->main_exec_status); CGroupRuntime *crt = unit_get_cgroup_runtime(u); - if (crt) + if (crt) { + unit_cgroup_disable_all_controllers(u); crt->reset_accounting = true; + } service_enter_condition(s); return 1; @@ -5640,6 +5643,7 @@ static int service_clean(Unit *u, ExecCleanMask mask) { goto fail; } + unit_cgroup_disable_all_controllers(u); r = unit_fork_and_watch_rm_rf(u, l, &s->control_pid); if (r < 0) { log_unit_warning_errno(u, r, "Failed to spawn cleaning task: %m"); From 53c87c096c9aacf12379f3b12fa80dd2c2e7cd9a Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 11 Apr 2026 01:41:13 +0100 Subject: [PATCH 0900/1296] json-stream: fix NULL pointer passed to memcpy on first read with INPUT_SENSITIVE When JSON_STREAM_INPUT_SENSITIVE is set before the first read, input_buffer is NULL, input_buffer_size is 0, and input_buffer_index is 0. The old condition '!INPUT_SENSITIVE && index == 0' would route this case into the else branch which calls memcpy() with a NULL source pointer, which is undefined behavior even when the length is zero, and is caught by UBSan. Fix by checking input_buffer_index == 0 first, then allowing the GREEDY_REALLOC fast path also when input_buffer_size == 0, since there is no sensitive data to protect from realloc() copying in that case. The else branch is now only entered when there is actual data to copy (input_buffer_size > 0), guaranteeing input_buffer is non-NULL. Follow-up for 6b1a59d59426cdda56648b00394addde2d454418 --- src/libsystemd/sd-json/json-stream.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libsystemd/sd-json/json-stream.c b/src/libsystemd/sd-json/json-stream.c index d378d18a282b8..e8cd2c55ddd3c 100644 --- a/src/libsystemd/sd-json/json-stream.c +++ b/src/libsystemd/sd-json/json-stream.c @@ -1266,7 +1266,8 @@ int json_stream_read(JsonStream *s) { add = MIN(s->buffer_max - s->input_buffer_size, s->read_chunk); - if (!FLAGS_SET(s->flags, JSON_STREAM_INPUT_SENSITIVE) && s->input_buffer_index == 0) { + if (s->input_buffer_index == 0 && + (!FLAGS_SET(s->flags, JSON_STREAM_INPUT_SENSITIVE) || s->input_buffer_size == 0)) { if (!GREEDY_REALLOC(s->input_buffer, s->input_buffer_size + add)) return -ENOMEM; } else { From 10e78e0b0ee94c553fdf6cb88c115af702408c19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 10 Apr 2026 19:06:00 +0200 Subject: [PATCH 0901/1296] shared/options: add option to generate a help line for custom option format Sometimes we want to document what -RR or -vv does or some other special thing. Let's allow this by (ab-)using long_code pointer to store a preformatted string. --- src/shared/options.c | 61 +++++++++++++++++++++++++------------------- src/shared/options.h | 10 +++++--- 2 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/shared/options.c b/src/shared/options.c index e5206e641d80b..dc2fb34cf62f2 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -21,8 +21,9 @@ static bool option_arg_required(const Option *opt) { static bool option_is_metadata(const Option *opt) { /* A metadata entry that is not a real option, like the group marker */ - return FLAGS_SET(ASSERT_PTR(opt)->flags, OPTION_GROUP_MARKER) || - FLAGS_SET(ASSERT_PTR(opt)->flags, OPTION_HELP_ENTRY); + return ASSERT_PTR(opt)->flags & (OPTION_GROUP_MARKER | + OPTION_HELP_ENTRY | + OPTION_HELP_ENTRY_VERBATIM); } static void shift_arg(char* argv[], int target, int source) { @@ -313,30 +314,38 @@ int _option_parser_get_help_table( /* No help string — we do not show the option */ continue; - char sc[3] = " "; - if (opt->short_code != 0) - xsprintf(sc, "-%c", opt->short_code); - - /* We indent the option string by two spaces. We could set the minimum cell width and - * right-align for a similar result, but that'd be more work. This is only used for - * display. - * - * "=" is shown only when a long option is defined: -l --long=ARG, --long=ARG, -s ARG. - */ - bool need_eq = option_takes_arg(opt) && opt->long_code; - bool need_quote = opt->metavar && strchr(opt->metavar, ' '); - _cleanup_free_ char *s = strjoin( - " ", - sc, - " ", - opt->long_code ? "--" : "", - strempty(opt->long_code), - option_arg_optional(opt) ? "[" : "", - need_eq ? "=" : "", - need_quote ? "'" : "", - strempty(opt->metavar), - need_quote ? "'" : "", - option_arg_optional(opt) ? "]" : ""); + _cleanup_free_ char *s = NULL; + + if (FLAGS_SET(opt->flags, OPTION_HELP_ENTRY_VERBATIM)) { + assert(opt->long_code); + + s = strjoin(" ", + opt->long_code); + } else { + char sc[3] = " "; + if (opt->short_code != 0) + xsprintf(sc, "-%c", opt->short_code); + + /* We indent the option string by two spaces. We could set the minimum cell width and + * right-align for a similar result, but that'd be more work. This is only used for + * display. + * + * "=" is shown only when a long option is defined: -l --long=ARG, --long=ARG, -s ARG. + */ + bool need_eq = option_takes_arg(opt) && opt->long_code; + bool need_quote = opt->metavar && strchr(opt->metavar, ' '); + s = strjoin(" ", + sc, + " ", + opt->long_code ? "--" : "", + strempty(opt->long_code), + option_arg_optional(opt) ? "[" : "", + need_eq ? "=" : "", + need_quote ? "'" : "", + strempty(opt->metavar), + need_quote ? "'" : "", + option_arg_optional(opt) ? "]" : ""); + } if (!s) return log_oom(); diff --git a/src/shared/options.h b/src/shared/options.h index e834cbededa4f..e8256059d15cb 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -5,10 +5,11 @@ #include "shared-forward.h" typedef enum OptionFlags { - OPTION_OPTIONAL_ARG = 1U << 0, /* Same as optional_argument in getopt */ - OPTION_STOPS_PARSING = 1U << 1, /* This option acts like "--" */ - OPTION_GROUP_MARKER = 1U << 2, /* Fake option entry to separate groups */ - OPTION_HELP_ENTRY = 1U << 3, /* Fake option entry to insert an additional help line */ + OPTION_OPTIONAL_ARG = 1U << 0, /* Same as optional_argument in getopt */ + OPTION_STOPS_PARSING = 1U << 1, /* This option acts like "--" */ + OPTION_GROUP_MARKER = 1U << 2, /* Fake option entry to separate groups */ + OPTION_HELP_ENTRY = 1U << 3, /* Fake option entry to insert an additional help line */ + OPTION_HELP_ENTRY_VERBATIM = 1U << 4, /* Same, but use the long_code in the first column as written */ } OptionFlags; typedef struct Option { @@ -49,6 +50,7 @@ typedef struct Option { #define OPTION_LONG_FLAGS(fl, lc, mv, h) OPTION_FULL(fl, /* sc= */ 0, lc, mv, h) #define OPTION_SHORT(sc, mv, h) OPTION(sc, /* lc= */ NULL, mv, h) #define OPTION_SHORT_FLAGS(fl, sc, mv, h) OPTION_FULL(fl, sc, /* lc= */ NULL, mv, h) +#define OPTION_HELP_VERBATIM(lc, h) OPTION_FULL(OPTION_HELP_ENTRY_VERBATIM, /* sc= */ 0, lc, /* mv= */ NULL, h) #define OPTION_COMMON_HELP \ OPTION('h', "help", NULL, "Show this help") From 4f6fdbcf7a1c3b7c6ad18a9f7ec717c6ed2ce4af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 10 Apr 2026 16:41:54 +0200 Subject: [PATCH 0902/1296] shared/verbs: add _SCOPE variants of the verb macros In some of the large programs, verbs are defined as non-static functions. To support this cases, add variants of the VERB macros that take an explicit scope parameter. The existing macros then call those new macros with scope=static. The variant without static is the exception, so the macros are "optimized" toward the static helpers. I also considered allowing VERB macros to be used in different files, i.e. in different compilation units. This would actually work without too many changes, except for one caveat: the order in the array would be unspecified, so we'd need to somehow order the verbs appropriately. This most likely means that the verbs would need to be annotated with a number. But that doesn't seem attractive at all: we'd need to coordinate changes in different files. So just listing the verbs in one file seems like least bad option. --- src/shared/verbs.h | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/shared/verbs.h b/src/shared/verbs.h index 51a9e3a5253f2..4fb0486f7567d 100644 --- a/src/shared/verbs.h +++ b/src/shared/verbs.h @@ -39,19 +39,27 @@ typedef struct { .help = h, \ } -#define VERB_FULL(d, v, a, amin, amax, f, dat, h) \ +/* Forward-define function d. scope specifies the scope, e.g. static. */ +#define VERB_SCOPE_FULL(scope, d, v, a, amin, amax, f, dat, h) \ DISABLE_WARNING_REDUNDANT_DECLS \ - static int d(int, char**, uintptr_t, void*); \ + scope int d(int, char**, uintptr_t, void*); \ REENABLE_WARNING \ _VERB_DATA(d, v, a, amin, amax, f, dat, h) +/* The same as VERB_SCOPE_FULL with scope hardwired to 'static'. */ +#define VERB_FULL(d, v, a, amin, amax, f, dat, h) \ + VERB_SCOPE_FULL(static, d, v, a, amin, amax, f, dat, h) -/* The same as VERB_FULL, but without the data argument */ +/* The same as VERB_SCOPE_FULL/VERB_FULL, but without the data argument. */ +#define VERB_SCOPE(scope, d, v, a, amin, amax, f, h) \ + VERB_SCOPE_FULL(scope, d, v, a, amin, amax, f, /* dat= */ 0, h) #define VERB(d, v, a, amin, amax, f, h) \ - VERB_FULL(d, v, a, amin, amax, f, /* dat= */ 0, h) + VERB_SCOPE(static, d, v, a, amin, amax, f, h) -/* Simplified VERB for parameters that take no argument */ +/* Simplified VERB_SCOPE/VERB for verbs that take no argument. */ +#define VERB_SCOPE_NOARG(scope, d, v, h) \ + VERB_SCOPE(scope, d, v, /* a= */ NULL, /* amin= */ VERB_ANY, /* amax= */ 1, /* f= */ 0, h) #define VERB_NOARG(d, v, h) \ - VERB(d, v, /* a= */ NULL, /* amin= */ VERB_ANY, /* amax= */ 1, /* f= */ 0, h) + VERB_SCOPE_NOARG(static, d, v, h) /* Magic entry in the table (which will not be returned) that designates the start of the group . * The macro works as a separator between groups and must be between other VERB* stanzas. */ From 11505ac135c9462e52a58153ea28559d6d61f955 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sun, 5 Apr 2026 18:04:21 +0200 Subject: [PATCH 0903/1296] bootctl: convert options and verbs to the new macros -RR is formatted using the new OPTION_HELP_ENTRY_VERBATIM so that we get the same --help as before. Co-developed-by: Claude Opus 4.6 --- src/bootctl/bootctl.c | 520 +++++++++++++++++++----------------------- 1 file changed, 237 insertions(+), 283 deletions(-) diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index 68478612ed891..ef1116ced72bf 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-varlink.h" @@ -23,12 +22,14 @@ #include "efivars.h" #include "escape.h" #include "find-esp.h" +#include "format-table.h" #include "image-policy.h" #include "log.h" #include "loop-util.h" #include "main-func.h" #include "mount-util.h" #include "openssl-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "path-util.h" @@ -254,296 +255,270 @@ static int help(void) { if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n%5$sControl EFI firmware boot settings and manage boot loader.%6$s\n" - "\n%3$sGeneric EFI Firmware/Boot Loader Commands:%4$s\n" - " status Show status of installed boot loader and EFI variables\n" - " reboot-to-firmware [BOOL]\n" - " Query or set reboot-to-firmware EFI flag\n" - "\n%3$sBoot Loader Specification Commands:%4$s\n" - " list List boot loader entries\n" - " unlink ID Remove boot loader entry\n" - " cleanup Remove files in ESP not referenced in any boot entry\n" - "\n%3$sBoot Loader Interface Commands:%4$s\n" - " set-default ID Set default boot loader entry\n" - " set-oneshot ID Set default boot loader entry, for next boot only\n" - " set-sysfail ID Set boot loader entry used in case of a system failure\n" - " set-timeout SECONDS Set the menu timeout\n" - " set-timeout-oneshot SECONDS\n" - " Set the menu timeout for the next boot only\n" - "\n%3$ssystemd-boot Commands:%4$s\n" - " install Install systemd-boot to the ESP and EFI variables\n" - " update Update systemd-boot in the ESP and EFI variables\n" - " remove Remove systemd-boot from the ESP and EFI variables\n" - " is-installed Test whether systemd-boot is installed in the ESP\n" - " random-seed Initialize or refresh random seed in ESP and EFI\n" - " variables\n" - "\n%3$sKernel Image Commands:%4$s\n" - " kernel-identify KERNEL-IMAGE\n" - " Identify kernel image type\n" - " kernel-inspect KERNEL-IMAGE\n" - " Prints details about the kernel image\n" - "\n%3$sBlock Device Discovery Commands:%4$s\n" - " -p --print-esp-path Print path to the EFI System Partition mount point\n" - " -x --print-boot-path Print path to the $BOOT partition mount point\n" - " --print-loader-path\n" - " Print path to currently booted boot loader binary\n" - " --print-stub-path Print path to currently booted unified kernel binary\n" - " -R --print-root-device\n" - " Print path to the block device node backing the\n" - " root file system (returns e.g. /dev/nvme0n1p5)\n" - " -RR Print path to the whole disk block device node\n" - " backing the root FS (returns e.g. /dev/nvme0n1)\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version\n" - " --esp-path=PATH Path to the EFI System Partition (ESP)\n" - " --boot-path=PATH Path to the $BOOT partition\n" - " --root=PATH Operate on an alternate filesystem root\n" - " --image=PATH Operate on disk image as filesystem root\n" - " --image-policy=POLICY\n" - " Specify disk image dissection policy\n" - " --install-source=auto|image|host\n" - " Where to pick files when using --root=/--image=\n" - " --variables=yes|no\n" - " Whether to modify EFI variables\n" - " --random-seed=yes|no\n" - " Whether to create random-seed file during install\n" - " --no-pager Do not pipe output into a pager\n" - " --graceful Don't fail when the ESP cannot be found or EFI\n" - " variables cannot be written\n" - " -q --quiet Suppress output\n" - " --make-entry-directory=yes|no|auto\n" - " Create $BOOT/ENTRY-TOKEN/ directory\n" - " --entry-token=machine-id|os-id|os-image-id|auto|literal:…\n" - " Entry token to use for this installation\n" - " --json=pretty|short|off\n" - " Generate JSON output\n" - " --all-architectures\n" - " Install all supported EFI architectures\n" - " --efi-boot-option-description=DESCRIPTION\n" - " Description of the entry in the boot option list\n" - " --efi-boot-option-description-with-device=yes\n" - " Suffix description with disk vendor/model/serial\n" - " --dry-run Dry run (unlink and cleanup)\n" - " --secure-boot-auto-enroll=yes|no\n" - " Set up secure boot auto-enrollment\n" - " --private-key=PATH|URI\n" - " Private key to use when setting up secure boot\n" - " auto-enrollment or an engine or provider specific\n" - " designation if --private-key-source= is used\n" - " --private-key-source=file|provider:PROVIDER|engine:ENGINE\n" - " Specify how to use KEY for --private-key=. Allows\n" - " an OpenSSL engine/provider to be used when setting\n" - " up secure boot auto-enrollment\n" - " --certificate=PATH|URI\n" - " PEM certificate to use when setting up Secure Boot\n" - " auto-enrollment, or a provider specific designation\n" - " if --certificate-source= is used\n" - " --certificate-source=file|provider:PROVIDER\n" - " Specify how to interpret the certificate from\n" - " --certificate=. Allows the certificate to be loaded\n" - " from an OpenSSL provider\n" - "\nSee the %2$s for details.\n", + static const char *const verb_groups[] = { + "Generic EFI Firmware/Boot Loader Commands", + "Boot Loader Specification Commands", + "Boot Loader Interface Commands", + "systemd-boot Commands", + "Kernel Image Commands", + }; + + static const char *const option_groups[] = { + "Block Device Discovery Commands", + "Options", + }; + + _cleanup_(table_unref_many) Table *verb_tables[ELEMENTSOF(verb_groups) + 1] = {}; + _cleanup_(table_unref_many) Table *option_tables[ELEMENTSOF(option_groups) + 1] = {}; + + for (size_t i = 0; i < ELEMENTSOF(verb_groups); i++) { + r = verbs_get_help_table_group(verb_groups[i], &verb_tables[i]); + if (r < 0) + return r; + } + + for (size_t i = 0; i < ELEMENTSOF(option_groups); i++) { + r = option_parser_get_help_table_group(option_groups[i], &option_tables[i]); + if (r < 0) + return r; + } + + (void) table_sync_column_widths(0, + verb_tables[0], verb_tables[1], verb_tables[2], + verb_tables[3], verb_tables[4], + option_tables[0], option_tables[1]); + + printf("%s [OPTIONS...] COMMAND ...\n" + "\n%sControl EFI firmware boot settings and manage boot loader.%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), ansi_normal()); + for (size_t i = 0; i < ELEMENTSOF(verb_groups); i++) { + printf("\n%s%s:%s\n", ansi_underline(), verb_groups[i], ansi_normal()); + + r = table_print_or_warn(verb_tables[i]); + if (r < 0) + return r; + } + + for (size_t i = 0; i < ELEMENTSOF(option_groups); i++) { + printf("\n%s%s:%s\n", ansi_underline(), option_groups[i], ansi_normal()); + + r = table_print_or_warn(option_tables[i]); + if (r < 0) + return r; + } + + printf("\nSee the %s for details.\n", link); return 0; } -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} +VERB_COMMON_HELP(help); -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_ESP_PATH = 0x100, - ARG_BOOT_PATH, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_INSTALL_SOURCE, - ARG_VERSION, - ARG_VARIABLES, - ARG_NO_VARIABLES, - ARG_RANDOM_SEED, - ARG_NO_PAGER, - ARG_GRACEFUL, - ARG_MAKE_ENTRY_DIRECTORY, - ARG_ENTRY_TOKEN, - ARG_JSON, - ARG_ARCH_ALL, - ARG_EFI_BOOT_OPTION_DESCRIPTION, - ARG_EFI_BOOT_OPTION_DESCRIPTION_WITH_DEVICE, - ARG_DRY_RUN, - ARG_PRINT_LOADER_PATH, - ARG_PRINT_STUB_PATH, - ARG_SECURE_BOOT_AUTO_ENROLL, - ARG_CERTIFICATE, - ARG_CERTIFICATE_SOURCE, - ARG_PRIVATE_KEY, - ARG_PRIVATE_KEY_SOURCE, - }; +VERB_GROUP("Generic EFI Firmware/Boot Loader Commands"); - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "esp-path", required_argument, NULL, ARG_ESP_PATH }, - { "path", required_argument, NULL, ARG_ESP_PATH }, /* Compatibility alias */ - { "boot-path", required_argument, NULL, ARG_BOOT_PATH }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "install-source", required_argument, NULL, ARG_INSTALL_SOURCE }, - { "print-esp-path", no_argument, NULL, 'p' }, - { "print-path", no_argument, NULL, 'p' }, /* Compatibility alias */ - { "print-boot-path", no_argument, NULL, 'x' }, - { "print-loader-path", no_argument, NULL, ARG_PRINT_LOADER_PATH }, - { "print-stub-path", no_argument, NULL, ARG_PRINT_STUB_PATH }, - { "print-root-device", no_argument, NULL, 'R' }, - { "variables", required_argument, NULL, ARG_VARIABLES }, - { "no-variables", no_argument, NULL, ARG_NO_VARIABLES }, /* Compatibility alias */ - { "random-seed", required_argument, NULL, ARG_RANDOM_SEED }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "graceful", no_argument, NULL, ARG_GRACEFUL }, - { "quiet", no_argument, NULL, 'q' }, - { "make-entry-directory", required_argument, NULL, ARG_MAKE_ENTRY_DIRECTORY }, - { "make-machine-id-directory", required_argument, NULL, ARG_MAKE_ENTRY_DIRECTORY }, /* Compatibility alias */ - { "entry-token", required_argument, NULL, ARG_ENTRY_TOKEN }, - { "json", required_argument, NULL, ARG_JSON }, - { "all-architectures", no_argument, NULL, ARG_ARCH_ALL }, - { "efi-boot-option-description", required_argument, NULL, ARG_EFI_BOOT_OPTION_DESCRIPTION }, - { "efi-boot-option-description-with-device", required_argument, NULL, ARG_EFI_BOOT_OPTION_DESCRIPTION_WITH_DEVICE }, - { "dry-run", no_argument, NULL, ARG_DRY_RUN }, - { "secure-boot-auto-enroll", required_argument, NULL, ARG_SECURE_BOOT_AUTO_ENROLL }, - { "certificate", required_argument, NULL, ARG_CERTIFICATE }, - { "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE }, - { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, - { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, - {} - }; +VERB_SCOPE(, verb_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT, + "Show status of installed boot loader and EFI variables"); + +VERB_SCOPE(, verb_reboot_to_firmware, "reboot-to-firmware", "[BOOL]", VERB_ANY, 2, 0, + "Query or set reboot-to-firmware EFI flag"); + +VERB_GROUP("Boot Loader Specification Commands"); + +VERB_SCOPE_NOARG(, verb_list, "list", + "List boot loader entries"); + +VERB_SCOPE(, verb_unlink, "unlink", "ID", 2, 2, 0, + "Remove boot loader entry"); + +VERB_SCOPE_NOARG(, verb_cleanup, "cleanup", + "Remove files in ESP not referenced in any boot entry"); + +VERB_GROUP("Boot Loader Interface Commands"); + +VERB_SCOPE(, verb_set_efivar, "set-default", "ID", 2, 2, 0, + "Set default boot loader entry"); + +VERB_SCOPE(, verb_set_efivar, "set-oneshot", "ID", 2, 2, 0, + "Set default boot loader entry, for next boot only"); + +VERB_SCOPE(, verb_set_efivar, "set-sysfail", "ID", 2, 2, 0, + "Set boot loader entry used in case of a system failure"); + +VERB_SCOPE(, verb_set_efivar, "set-timeout", "SECONDS", 2, 2, 0, + "Set the menu timeout"); + +VERB_SCOPE(, verb_set_efivar, "set-timeout-oneshot", "SECONDS", 2, 2, 0, + "Set the menu timeout for the next boot only"); + +VERB_SCOPE(, verb_set_efivar, "set-preferred", "ID", 2, 2, 0, + /* help= */ NULL); + +VERB_GROUP("systemd-boot Commands"); + +VERB_SCOPE(, verb_install, "install", NULL, VERB_ANY, 1, 0, + "Install systemd-boot to the ESP and EFI variables"); + +VERB_SCOPE(, verb_install, "update", NULL, VERB_ANY, 1, 0, + "Update systemd-boot in the ESP and EFI variables"); + +VERB_SCOPE_NOARG(, verb_remove, "remove", + "Remove systemd-boot from the ESP and EFI variables"); + +VERB_SCOPE_NOARG(, verb_is_installed, "is-installed", + "Test whether systemd-boot is installed in the ESP"); + +VERB_SCOPE_NOARG(, verb_random_seed, "random-seed", + "Initialize or refresh random seed in ESP and EFI variables"); + +VERB_GROUP("Kernel Image Commands"); + +VERB_SCOPE(, verb_kernel_identify, "kernel-identify", "KERNEL-IMAGE", 2, 2, 0, + "Identify kernel image type"); - int c, r; +VERB_SCOPE(, verb_kernel_inspect, "kernel-inspect", "KERNEL-IMAGE", 2, 2, 0, + "Prints details about the kernel image"); + +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hpxRq", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_GROUP("Block Device Discovery Commands"): + break; + + OPTION('p', "print-esp-path", NULL, "Print path to the EFI System Partition mount point"): {} + OPTION_LONG("print-path", NULL, /* help= */ NULL): /* Compatibility alias */ + arg_print_esp_path = true; + break; + + OPTION('x', "print-boot-path", NULL, "Print path to the $BOOT partition mount point"): + arg_print_dollar_boot_path = true; + break; + + OPTION_LONG("print-loader-path", NULL, "Print path to currently booted boot loader binary"): + arg_print_loader_path = true; + break; + + OPTION_LONG("print-stub-path", NULL, "Print path to currently booted unified kernel binary"): + arg_print_stub_path = true; + break; + + OPTION('R', "print-root-device", NULL, + "Print path to the block device node backing the root file system" + " (returns e.g. /dev/nvme0n1p5)"): {} + OPTION_HELP_VERBATIM("-RR", + "Print path to the whole disk block device node backing the root FS" + " (returns e.g. /dev/nvme0n1)"): + arg_print_root_device++; + break; + + OPTION_GROUP("Options"): + break; + + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_ESP_PATH: - r = free_and_strdup(&arg_esp_path, optarg); + OPTION_LONG("esp-path", "PATH", "Path to the EFI System Partition (ESP)"): {} + OPTION_LONG("path", "PATH", /* help= */ NULL): /* Compatibility alias */ + r = free_and_strdup(&arg_esp_path, arg); if (r < 0) return log_oom(); break; - case ARG_BOOT_PATH: - r = free_and_strdup(&arg_xbootldr_path, optarg); + OPTION_LONG("boot-path", "PATH", "Path to the $BOOT partition"): + r = free_and_strdup(&arg_xbootldr_path, arg); if (r < 0) return log_oom(); break; - case ARG_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root); + OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): + r = parse_path_argument(arg, /* suppress_root= */ true, &arg_root); if (r < 0) return r; break; - case ARG_IMAGE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image); + OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image); if (r < 0) return r; break; - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); + OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): + r = parse_image_policy_argument(arg, &arg_image_policy); if (r < 0) return r; break; - case ARG_INSTALL_SOURCE: { - InstallSource is = install_source_from_string(optarg); + OPTION_LONG("install-source", "SOURCE", + "Where to pick files when using --root=/--image= (auto, image, host)"): { + InstallSource is = install_source_from_string(arg); if (is < 0) - return log_error_errno(is, "Unexpected parameter for --install-source=: %s", optarg); + return log_error_errno(is, "Unexpected parameter for --install-source=: %s", arg); arg_install_source = is; break; } - case 'p': - arg_print_esp_path = true; - break; - - case 'x': - arg_print_dollar_boot_path = true; - break; - - case ARG_PRINT_LOADER_PATH: - arg_print_loader_path = true; - break; - - case ARG_PRINT_STUB_PATH: - arg_print_stub_path = true; - break; - - case 'R': - arg_print_root_device++; - break; - - case ARG_VARIABLES: - r = parse_tristate_argument_with_auto("--variables=", optarg, &arg_touch_variables); + OPTION_LONG("variables", "BOOL", "Whether to modify EFI variables"): + r = parse_tristate_argument_with_auto("--variables=", arg, &arg_touch_variables); if (r < 0) return r; #if !ENABLE_EFI if (arg_touch_variables > 0) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "Compiled without support for EFI, --variables=%s cannot be specified.", optarg); + "Compiled without support for EFI, --variables=%s cannot be specified.", arg); #endif break; - case ARG_NO_VARIABLES: + OPTION_LONG("no-variables", NULL, /* help= */ NULL): /* Compatibility alias */ arg_touch_variables = false; break; - case ARG_RANDOM_SEED: - r = parse_boolean_argument("--random-seed=", optarg, &arg_install_random_seed); + OPTION_LONG("random-seed", "BOOL", "Whether to create random-seed file during install"): + r = parse_boolean_argument("--random-seed=", arg, &arg_install_random_seed); if (r < 0) return r; break; - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_GRACEFUL: + OPTION_LONG("graceful", NULL, + "Don't fail when the ESP cannot be found or EFI variables cannot be written"): _arg_graceful = ARG_GRACEFUL_YES; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress output"): arg_quiet = true; break; - case ARG_ENTRY_TOKEN: - r = parse_boot_entry_token_type(optarg, &arg_entry_token_type, &arg_entry_token); + OPTION_LONG("entry-token", "TOKEN", + "Entry token to use for this installation" + " (machine-id, os-id, os-image-id, auto, literal:…)"): + r = parse_boot_entry_token_type(arg, &arg_entry_token_type, &arg_entry_token); if (r < 0) return r; break; - case ARG_MAKE_ENTRY_DIRECTORY: - if (streq(optarg, "auto")) /* retained for backwards compatibility */ + OPTION_LONG("make-entry-directory", "yes|no|auto", "Create $BOOT/ENTRY-TOKEN/ directory"): {} + OPTION_LONG("make-machine-id-directory", "BOOL", /* help= */ NULL): /* Compatibility alias */ + if (streq(arg, "auto")) /* retained for backwards compatibility */ arg_make_entry_directory = -1; /* yes if machine-id is permanent */ else { - r = parse_boolean_argument("--make-entry-directory=", optarg, NULL); + r = parse_boolean_argument("--make-entry-directory=", arg, NULL); if (r < 0) return r; @@ -551,95 +526,99 @@ static int parse_argv(int argc, char *argv[]) { } break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); if (r <= 0) return r; break; - case ARG_ARCH_ALL: + OPTION_LONG("all-architectures", NULL, "Install all supported EFI architectures"): arg_arch_all = true; break; - case ARG_EFI_BOOT_OPTION_DESCRIPTION: - if (isempty(optarg) || !(string_is_safe(optarg) && utf8_is_valid(optarg))) { - _cleanup_free_ char *escaped = cescape(optarg); + OPTION_LONG("efi-boot-option-description", "DESCRIPTION", + "Description of the entry in the boot option list"): + if (isempty(arg) || !(string_is_safe(arg) && utf8_is_valid(arg))) { + _cleanup_free_ char *escaped = cescape(arg); return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid --efi-boot-option-description=: %s", strna(escaped)); } - if (strlen(optarg) > EFI_BOOT_OPTION_DESCRIPTION_MAX) + if (strlen(arg) > EFI_BOOT_OPTION_DESCRIPTION_MAX) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--efi-boot-option-description= too long: %zu > %zu", - strlen(optarg), EFI_BOOT_OPTION_DESCRIPTION_MAX); - r = free_and_strdup_warn(&arg_efi_boot_option_description, optarg); + strlen(arg), EFI_BOOT_OPTION_DESCRIPTION_MAX); + r = free_and_strdup_warn(&arg_efi_boot_option_description, arg); if (r < 0) return r; break; - case ARG_EFI_BOOT_OPTION_DESCRIPTION_WITH_DEVICE: - r = parse_boolean_argument("--efi-boot-option-description-with-device=", optarg, &arg_efi_boot_option_description_with_device); + OPTION_LONG("efi-boot-option-description-with-device", "BOOL", + "Suffix description with disk vendor/model/serial"): + r = parse_boolean_argument("--efi-boot-option-description-with-device=", arg, + &arg_efi_boot_option_description_with_device); if (r < 0) return r; - break; - case ARG_DRY_RUN: + OPTION_LONG("dry-run", NULL, "Dry run (unlink and cleanup)"): arg_dry_run = true; break; - case ARG_SECURE_BOOT_AUTO_ENROLL: - r = parse_boolean_argument("--secure-boot-auto-enroll=", optarg, &arg_secure_boot_auto_enroll); + OPTION_LONG("secure-boot-auto-enroll", "BOOL", "Set up secure boot auto-enrollment"): + r = parse_boolean_argument("--secure-boot-auto-enroll=", arg, + &arg_secure_boot_auto_enroll); if (r < 0) return r; break; - case ARG_CERTIFICATE: - r = free_and_strdup_warn(&arg_certificate, optarg); + OPTION_LONG("private-key", "PATH|URI", + "Private key for Secure Boot auto-enrollment"): + r = free_and_strdup_warn(&arg_private_key, arg); if (r < 0) return r; break; - case ARG_CERTIFICATE_SOURCE: - r = parse_openssl_certificate_source_argument( - optarg, - &arg_certificate_source, - &arg_certificate_source_type); + OPTION_LONG("private-key-source", "SOURCE", + "Specify how to use the private key " + "(file, provider:PROVIDER, engine:ENGINE)"): + r = parse_openssl_key_source_argument(arg, + &arg_private_key_source, + &arg_private_key_source_type); if (r < 0) return r; break; - case ARG_PRIVATE_KEY: { - r = free_and_strdup_warn(&arg_private_key, optarg); + OPTION_LONG("certificate", "PATH|URI", + "PEM certificate to use when setting up Secure Boot auto-enrollment, " + "or a provider specific designation if --certificate-source= is used"): + r = free_and_strdup_warn(&arg_certificate, arg); if (r < 0) return r; break; - } - case ARG_PRIVATE_KEY_SOURCE: - r = parse_openssl_key_source_argument( - optarg, - &arg_private_key_source, - &arg_private_key_source_type); + OPTION_LONG("certificate-source", "SOURCE", + "Specify how to interpret the certificate from --certificate=. " + "Allows the certificate to be loaded from an OpenSSL provider " + "(file, provider:PROVIDER)"): + r = parse_openssl_certificate_source_argument(arg, + &arg_certificate_source, + &arg_certificate_source_type); if (r < 0) return r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + char **args = option_parser_get_args(&state); + if (!!arg_print_esp_path + !!arg_print_dollar_boot_path + (arg_print_root_device > 0) + arg_print_loader_path + arg_print_stub_path > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--print-esp-path/-p, --print-boot-path/-x, --print-root-device=/-R, --print-loader-path, --print-stub-path cannot be combined."); - if ((arg_root || arg_image) && argv[optind] && !STR_IN_SET(argv[optind], "status", "list", + if ((arg_root || arg_image) && args[0] && !STR_IN_SET(args[0], "status", "list", "install", "update", "remove", "is-installed", "random-seed", "unlink", "cleanup")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Options --root= and --image= are not supported with verb %s.", - argv[optind]); + args[0]); if (arg_root && arg_image) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported."); @@ -647,7 +626,7 @@ static int parse_argv(int argc, char *argv[]) { if (arg_install_source != INSTALL_SOURCE_AUTO && !arg_root && !arg_image) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--install-from-host is only supported with --root= or --image=."); - if (arg_dry_run && argv[optind] && !STR_IN_SET(argv[optind], "unlink", "cleanup")) + if (arg_dry_run && args[0] && !STR_IN_SET(args[0], "unlink", "cleanup")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--dry-run is only supported with --unlink or --cleanup"); if (arg_secure_boot_auto_enroll) { @@ -670,36 +649,10 @@ static int parse_argv(int argc, char *argv[]) { arg_pager_flags |= PAGER_DISABLE; } + *ret_args = args; return 1; } -static int bootctl_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, - { "install", VERB_ANY, 1, 0, verb_install }, - { "update", VERB_ANY, 1, 0, verb_install }, - { "remove", VERB_ANY, 1, 0, verb_remove }, - { "is-installed", VERB_ANY, 1, 0, verb_is_installed }, - { "kernel-identify", 2, 2, 0, verb_kernel_identify }, - { "kernel-inspect", 2, 2, 0, verb_kernel_inspect }, - { "list", VERB_ANY, 1, 0, verb_list }, - { "unlink", 2, 2, 0, verb_unlink }, - { "cleanup", VERB_ANY, 1, 0, verb_cleanup }, - { "set-default", 2, 2, 0, verb_set_efivar }, - { "set-preferred", 2, 2, 0, verb_set_efivar }, - { "set-oneshot", 2, 2, 0, verb_set_efivar }, - { "set-timeout", 2, 2, 0, verb_set_efivar }, - { "set-timeout-oneshot", 2, 2, 0, verb_set_efivar }, - { "set-sysfail", 2, 2, 0, verb_set_efivar }, - { "random-seed", VERB_ANY, 1, 0, verb_random_seed }, - { "reboot-to-firmware", VERB_ANY, 2, 0, verb_reboot_to_firmware }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int vl_server(void) { _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; int r; @@ -740,7 +693,8 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -800,7 +754,7 @@ static int run(int argc, char *argv[]) { return log_oom(); } - return bootctl_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); From 4319ca43f9b423645be7b1be4993486f5dd1d302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 10 Apr 2026 18:19:41 +0200 Subject: [PATCH 0904/1296] import: convert to the new option and verb parsers --help output is the same. Co-developed-by: Claude Opus 4.6 --- src/import/import.c | 203 +++++++++++++++++--------------------------- 1 file changed, 78 insertions(+), 125 deletions(-) diff --git a/src/import/import.c b/src/import/import.c index 3fe6adb8d2d88..c678c68f11565 100644 --- a/src/import/import.c +++ b/src/import/import.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -12,12 +11,14 @@ #include "discover-image.h" #include "env-util.h" #include "fd-util.h" +#include "format-table.h" #include "import-raw.h" #include "import-tar.h" #include "import-util.h" #include "io-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" @@ -138,7 +139,8 @@ static void on_tar_finished(TarImport *import, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int verb_import_tar(int argc, char *argv[], uintptr_t _data, void *userdata) { +VERB(verb_tar, "tar", "FILE [NAME]", 2, 3, 0, "Import a TAR image"); +static int verb_tar(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(tar_import_unrefp) TarImport *import = NULL; _cleanup_free_ char *ll = NULL, *normalized = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; @@ -207,7 +209,8 @@ static void on_raw_finished(RawImport *import, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int verb_import_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { +VERB(verb_raw, "raw", "FILE [NAME]", 2, 3, 0, "Import a RAW image"); +static int verb_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(raw_import_unrefp) RawImport *import = NULL; _cleanup_free_ char *ll = NULL, *normalized = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; @@ -269,193 +272,152 @@ static int verb_import_raw(int argc, char *argv[], uintptr_t _data, void *userda } static int help(void) { - printf("%1$s [OPTIONS...] {COMMAND} ...\n" - "\n%4$sImport disk images.%5$s\n" - "\n%2$sCommands:%3$s\n" - " tar FILE [NAME] Import a TAR image\n" - " raw FILE [NAME] Import a RAW image\n" - "\n%2$sOptions:%3$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --force Force creation of image\n" - " --image-root=PATH Image root directory\n" - " --read-only Create a read-only image\n" - " --direct Import directly to specified file\n" - " --btrfs-subvol=BOOL Controls whether to create a btrfs subvolume\n" - " instead of a directory\n" - " --btrfs-quota=BOOL Controls whether to set up quota for btrfs\n" - " subvolume\n" - " --convert-qcow2=BOOL Controls whether to convert QCOW2 images to\n" - " regular disk images\n" - " --sync=BOOL Controls whether to sync() before completing\n" - " --offset=BYTES Offset to seek to in destination\n" - " --size-max=BYTES Maximum number of bytes to write to destination\n" - " --class=CLASS Select image class (machine, sysext, confext,\n" - " portable)\n" - " --system Operate in per-system mode\n" - " --user Operate in per-user mode\n", + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; + int r; + + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "%sImport disk images.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + return 0; } -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} +VERB_COMMON_HELP_HIDDEN(help); -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_FORCE, - ARG_IMAGE_ROOT, - ARG_READ_ONLY, - ARG_DIRECT, - ARG_BTRFS_SUBVOL, - ARG_BTRFS_QUOTA, - ARG_CONVERT_QCOW2, - ARG_SYNC, - ARG_OFFSET, - ARG_SIZE_MAX, - ARG_CLASS, - ARG_SYSTEM, - ARG_USER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "force", no_argument, NULL, ARG_FORCE }, - { "image-root", required_argument, NULL, ARG_IMAGE_ROOT }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - { "direct", no_argument, NULL, ARG_DIRECT }, - { "btrfs-subvol", required_argument, NULL, ARG_BTRFS_SUBVOL }, - { "btrfs-quota", required_argument, NULL, ARG_BTRFS_QUOTA }, - { "convert-qcow2", required_argument, NULL, ARG_CONVERT_QCOW2 }, - { "sync", required_argument, NULL, ARG_SYNC }, - { "offset", required_argument, NULL, ARG_OFFSET }, - { "size-max", required_argument, NULL, ARG_SIZE_MAX }, - { "class", required_argument, NULL, ARG_CLASS }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - {} - }; - - int r, c; +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_FORCE: + OPTION_LONG("force", NULL, "Force creation of image"): arg_import_flags |= IMPORT_FORCE; break; - case ARG_IMAGE_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image_root); + OPTION_LONG("image-root", "PATH", "Image root directory"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image_root); if (r < 0) return r; - break; - case ARG_READ_ONLY: + OPTION_LONG("read-only", NULL, "Create a read-only image"): arg_import_flags |= IMPORT_READ_ONLY; break; - case ARG_DIRECT: + OPTION_LONG("direct", NULL, "Import directly to specified file"): arg_import_flags |= IMPORT_DIRECT; break; - case ARG_BTRFS_SUBVOL: - r = parse_boolean_argument("--btrfs-subvol=", optarg, NULL); + OPTION_LONG("btrfs-subvol", "BOOL", + "Controls whether to create a btrfs subvolume instead of a directory"): + r = parse_boolean_argument("--btrfs-subvol=", arg, NULL); if (r < 0) return r; - SET_FLAG(arg_import_flags, IMPORT_BTRFS_SUBVOL, r); break; - case ARG_BTRFS_QUOTA: - r = parse_boolean_argument("--btrfs-quota=", optarg, NULL); + OPTION_LONG("btrfs-quota", "BOOL", + "Controls whether to set up quota for btrfs subvolume"): + r = parse_boolean_argument("--btrfs-quota=", arg, NULL); if (r < 0) return r; - SET_FLAG(arg_import_flags, IMPORT_BTRFS_QUOTA, r); break; - case ARG_CONVERT_QCOW2: - r = parse_boolean_argument("--convert-qcow2=", optarg, NULL); + OPTION_LONG("convert-qcow2", "BOOL", + "Controls whether to convert QCOW2 images to regular disk images"): + r = parse_boolean_argument("--convert-qcow2=", arg, NULL); if (r < 0) return r; - SET_FLAG(arg_import_flags, IMPORT_CONVERT_QCOW2, r); break; - case ARG_SYNC: - r = parse_boolean_argument("--sync=", optarg, NULL); + OPTION_LONG("sync", "BOOL", "Controls whether to sync() before completing"): + r = parse_boolean_argument("--sync=", arg, NULL); if (r < 0) return r; - SET_FLAG(arg_import_flags, IMPORT_SYNC, r); break; - case ARG_OFFSET: { + OPTION_LONG("offset", "BYTES", "Offset to seek to in destination"): { uint64_t u; - r = safe_atou64(optarg, &u); + r = safe_atou64(arg, &u); if (r < 0) - return log_error_errno(r, "Failed to parse --offset= argument: %s", optarg); + return log_error_errno(r, "Failed to parse --offset= argument: %s", arg); if (!FILE_SIZE_VALID(u)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --offset= switch too large: %s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --offset= switch too large: %s", arg); arg_offset = u; break; } - case ARG_SIZE_MAX: { + OPTION_LONG("size-max", "BYTES", "Maximum number of bytes to write to destination"): { uint64_t u; - r = parse_size(optarg, 1024, &u); + r = parse_size(arg, 1024, &u); if (r < 0) - return log_error_errno(r, "Failed to parse --size-max= argument: %s", optarg); + return log_error_errno(r, "Failed to parse --size-max= argument: %s", arg); if (!FILE_SIZE_VALID(u)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --size-max= switch too large: %s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --size-max= switch too large: %s", arg); arg_size_max = u; break; } - case ARG_CLASS: - arg_class = image_class_from_string(optarg); + OPTION_LONG("class", "CLASS", + "Select image class (machine, sysext, confext, portable)"): + arg_class = image_class_from_string(arg); if (arg_class < 0) - return log_error_errno(arg_class, "Failed to parse --class= argument: %s", optarg); - + return log_error_errno(arg_class, "Failed to parse --class= argument: %s", arg); break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Operate in per-system mode"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Operate in per-user mode"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } /* Make sure offset+size is still in the valid range if both set */ @@ -476,20 +438,10 @@ static int parse_argv(int argc, char *argv[]) { if (arg_runtime_scope == RUNTIME_SCOPE_USER) arg_import_flags |= IMPORT_FOREIGN_UID; + *ret_args = option_parser_get_args(&state); return 1; } -static int import_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "tar", 2, 3, 0, verb_import_tar }, - { "raw", 2, 3, 0, verb_import_raw }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static void parse_env(void) { int r; @@ -523,13 +475,14 @@ static int run(int argc, char *argv[]) { parse_env(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; (void) ignore_signals(SIGPIPE); - return import_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); From ee96f934c6efccd4a2a3fe1073f4da961fe4eb25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 10 Apr 2026 18:26:58 +0200 Subject: [PATCH 0905/1296] importctl: fix -N to actually clear keep-download flag -N was clearing and re-setting the same bit in arg_import_flags_mask, which is a no-op. It should clear the bit in arg_import_flags instead, matching what --keep-download=no does via SET_FLAG(). --- src/import/importctl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/import/importctl.c b/src/import/importctl.c index 2993073cc5c1d..cb6f61e89366c 100644 --- a/src/import/importctl.c +++ b/src/import/importctl.c @@ -1273,7 +1273,7 @@ static int parse_argv(int argc, char *argv[]) { break; case 'N': - arg_import_flags_mask &= ~IMPORT_PULL_KEEP_DOWNLOAD; + arg_import_flags &= ~IMPORT_PULL_KEEP_DOWNLOAD; arg_import_flags_mask |= IMPORT_PULL_KEEP_DOWNLOAD; break; From 959116d5defbd218af8d8444c8523e79b8a498f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 10 Apr 2026 18:28:24 +0200 Subject: [PATCH 0906/1296] importctl: convert to the new option and verb parsers Co-developed-by: Claude Opus 4.6 --- src/import/importctl.c | 689 ++++++++++++++++++----------------------- 1 file changed, 308 insertions(+), 381 deletions(-) diff --git a/src/import/importctl.c b/src/import/importctl.c index cb6f61e89366c..dceb03a6d6da5 100644 --- a/src/import/importctl.c +++ b/src/import/importctl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -20,6 +19,7 @@ #include "log.h" #include "main-func.h" #include "oci-util.h" +#include "options.h" #include "os-util.h" #include "pager.h" #include "parse-argument.h" @@ -261,6 +261,211 @@ static int transfer_image_common(sd_bus *bus, sd_bus_message *m) { return -r; } +VERB(verb_pull_tar, "pull-tar", "URL [NAME]", 2, 3, 0, "Download a TAR container image"); +static int verb_pull_tar(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *l = NULL, *ll = NULL; + const char *local, *remote; + sd_bus *bus = ASSERT_PTR(userdata); + int r; + + r = settle_image_class(); + if (r < 0) + return r; + + remote = argv[1]; + if (!http_url_is_valid(remote) && !file_url_is_valid(remote)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "URL '%s' is not valid.", remote); + + if (argc >= 3) + local = argv[2]; + else { + r = import_url_last_component(remote, &l); + if (r < 0) + return log_error_errno(r, "Failed to get final component of URL: %m"); + + local = l; + } + + local = empty_or_dash_to_null(local); + + if (local) { + r = tar_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!image_name_is_valid(local)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Local name %s is not a suitable image name.", + local); + } + + if (arg_image_class == IMAGE_MACHINE && (arg_import_flags & ~IMPORT_FORCE) == 0) { + r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullTar"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sssb", + remote, + local, + import_verify_to_string(arg_verify), + FLAGS_SET(arg_import_flags, IMPORT_FORCE)); + } else { + r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullTarEx"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sssst", + remote, + local, + image_class_to_string(arg_image_class), + import_verify_to_string(arg_verify), + (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_PULL_KEEP_DOWNLOAD)); + } + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +VERB(verb_pull_raw, "pull-raw", "URL [NAME]", 2, 3, 0, "Download a RAW container or VM image"); +static int verb_pull_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *l = NULL, *ll = NULL; + const char *local, *remote; + sd_bus *bus = ASSERT_PTR(userdata); + int r; + + r = settle_image_class(); + if (r < 0) + return r; + + remote = argv[1]; + if (!http_url_is_valid(remote) && !file_url_is_valid(remote)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "URL '%s' is not valid.", remote); + + if (argc >= 3) + local = argv[2]; + else { + r = import_url_last_component(remote, &l); + if (r < 0) + return log_error_errno(r, "Failed to get final component of URL: %m"); + + local = l; + } + + local = empty_or_dash_to_null(local); + + if (local) { + r = raw_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!image_name_is_valid(local)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Local name %s is not a suitable image name.", + local); + } + + if (arg_image_class == IMAGE_MACHINE && (arg_import_flags & ~IMPORT_FORCE) == 0) { + r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullRaw"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sssb", + remote, + local, + import_verify_to_string(arg_verify), + FLAGS_SET(arg_import_flags, IMPORT_FORCE)); + } else { + r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullRawEx"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sssst", + remote, + local, + image_class_to_string(arg_image_class), + import_verify_to_string(arg_verify), + (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_PULL_KEEP_DOWNLOAD)); + } + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +VERB(verb_pull_oci, "pull-oci", "REF [NAME]", 2, 3, 0, "Download an OCI container image"); +static int verb_pull_oci(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *l = NULL; + const char *local, *remote; + sd_bus *bus = ASSERT_PTR(userdata); + int r; + + r = settle_image_class(); + if (r < 0) + return r; + + remote = argv[1]; + _cleanup_free_ char *image = NULL; + r = oci_ref_parse(remote, /* ret_registry= */ NULL, &image, /* ret_tag= */ NULL); + if (r == -EINVAL) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Ref '%s' is not valid.", remote); + if (r < 0) + return log_error_errno(r, "Failed to determine if ref '%s' is valid.", remote); + + if (argc >= 3) + local = argv[2]; + else { + r = path_extract_filename(image, &l); + if (r < 0) + return log_error_errno(r, "Failed to get final component of reference: %m"); + + local = l; + } + + local = empty_or_dash_to_null(local); + + if (local) { + if (!image_name_is_valid(local)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Local name %s is not a suitable image name.", + local); + } + + r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullOci"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "ssst", + remote, + local, + image_class_to_string(arg_image_class), + (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY)); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +VERB(verb_import_tar, "import-tar", "FILE [NAME]", 2, 3, 0, "Import a local TAR container image"); static int verb_import_tar(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *ll = NULL, *fn = NULL; @@ -340,6 +545,7 @@ static int verb_import_tar(int argc, char *argv[], uintptr_t _data, void *userda return transfer_image_common(bus, m); } +VERB(verb_import_raw, "import-raw", "FILE [NAME]", 2, 3, 0, "Import a local RAW container or VM image"); static int verb_import_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *ll = NULL, *fn = NULL; @@ -419,6 +625,7 @@ static int verb_import_raw(int argc, char *argv[], uintptr_t _data, void *userda return transfer_image_common(bus, m); } +VERB(verb_import_fs, "import-fs", "DIRECTORY [NAME]", 2, 3, 0, "Import a local directory container image"); static int verb_import_fs(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; const char *local = NULL, *path = NULL; @@ -506,6 +713,7 @@ static void determine_compression_from_filename(const char *p) { arg_format = "zstd"; } +VERB(verb_export_tar, "export-tar", "NAME [FILE]", 2, 3, 0, "Export a TAR container image locally"); static int verb_export_tar(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_close_ int fd = -EBADF; @@ -565,6 +773,7 @@ static int verb_export_tar(int argc, char *argv[], uintptr_t _data, void *userda return transfer_image_common(bus, m); } +VERB(verb_export_raw, "export-raw", "NAME [FILE]", 2, 3, 0, "Export a RAW container or VM image locally"); static int verb_export_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_close_ int fd = -EBADF; @@ -624,207 +833,7 @@ static int verb_export_raw(int argc, char *argv[], uintptr_t _data, void *userda return transfer_image_common(bus, m); } -static int verb_pull_tar(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *l = NULL, *ll = NULL; - const char *local, *remote; - sd_bus *bus = ASSERT_PTR(userdata); - int r; - - r = settle_image_class(); - if (r < 0) - return r; - - remote = argv[1]; - if (!http_url_is_valid(remote) && !file_url_is_valid(remote)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "URL '%s' is not valid.", remote); - - if (argc >= 3) - local = argv[2]; - else { - r = import_url_last_component(remote, &l); - if (r < 0) - return log_error_errno(r, "Failed to get final component of URL: %m"); - - local = l; - } - - local = empty_or_dash_to_null(local); - - if (local) { - r = tar_strip_suffixes(local, &ll); - if (r < 0) - return log_oom(); - - local = ll; - - if (!image_name_is_valid(local)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Local name %s is not a suitable image name.", - local); - } - - if (arg_image_class == IMAGE_MACHINE && (arg_import_flags & ~IMPORT_FORCE) == 0) { - r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullTar"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "sssb", - remote, - local, - import_verify_to_string(arg_verify), - FLAGS_SET(arg_import_flags, IMPORT_FORCE)); - } else { - r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullTarEx"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "sssst", - remote, - local, - image_class_to_string(arg_image_class), - import_verify_to_string(arg_verify), - (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_PULL_KEEP_DOWNLOAD)); - } - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static int verb_pull_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *l = NULL, *ll = NULL; - const char *local, *remote; - sd_bus *bus = ASSERT_PTR(userdata); - int r; - - r = settle_image_class(); - if (r < 0) - return r; - - remote = argv[1]; - if (!http_url_is_valid(remote) && !file_url_is_valid(remote)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "URL '%s' is not valid.", remote); - - if (argc >= 3) - local = argv[2]; - else { - r = import_url_last_component(remote, &l); - if (r < 0) - return log_error_errno(r, "Failed to get final component of URL: %m"); - - local = l; - } - - local = empty_or_dash_to_null(local); - - if (local) { - r = raw_strip_suffixes(local, &ll); - if (r < 0) - return log_oom(); - - local = ll; - - if (!image_name_is_valid(local)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Local name %s is not a suitable image name.", - local); - } - - if (arg_image_class == IMAGE_MACHINE && (arg_import_flags & ~IMPORT_FORCE) == 0) { - r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullRaw"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "sssb", - remote, - local, - import_verify_to_string(arg_verify), - FLAGS_SET(arg_import_flags, IMPORT_FORCE)); - } else { - r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullRawEx"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "sssst", - remote, - local, - image_class_to_string(arg_image_class), - import_verify_to_string(arg_verify), - (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_PULL_KEEP_DOWNLOAD)); - } - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static int verb_pull_oci(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *l = NULL; - const char *local, *remote; - sd_bus *bus = ASSERT_PTR(userdata); - int r; - - r = settle_image_class(); - if (r < 0) - return r; - - remote = argv[1]; - _cleanup_free_ char *image = NULL; - r = oci_ref_parse(remote, /* ret_registry= */ NULL, &image, /* ret_tag= */ NULL); - if (r == -EINVAL) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Ref '%s' is not valid.", remote); - if (r < 0) - return log_error_errno(r, "Failed to determine if ref '%s' is valid.", remote); - - if (argc >= 3) - local = argv[2]; - else { - r = path_extract_filename(image, &l); - if (r < 0) - return log_error_errno(r, "Failed to get final component of reference: %m"); - - local = l; - } - - local = empty_or_dash_to_null(local); - - if (local) { - if (!image_name_is_valid(local)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Local name %s is not a suitable image name.", - local); - } - - r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullOci"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "ssst", - remote, - local, - image_class_to_string(arg_image_class), - (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY)); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - +VERB(verb_list_transfers, "list-transfers", NULL, VERB_ANY, 1, VERB_DEFAULT, "Show list of transfers in progress"); static int verb_list_transfers(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; @@ -929,6 +938,7 @@ static int verb_list_transfers(int argc, char *argv[], uintptr_t _data, void *us return 0; } +VERB(verb_cancel_transfer, "cancel-transfer", "[ID...]", 2, VERB_ANY, 0, "Cancel a transfer"); static int verb_cancel_transfer(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); @@ -951,6 +961,7 @@ static int verb_cancel_transfer(int argc, char *argv[], uintptr_t _data, void *u return 0; } +VERB_NOARG(verb_list_images, "list-images", "Show list of installed images"); static int verb_list_images(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; @@ -1050,6 +1061,7 @@ static int verb_list_images(int argc, char *argv[], uintptr_t _data, void *userd static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; pager_open(arg_pager_flags); @@ -1058,265 +1070,179 @@ static int help(void) { if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%5$sDownload, import or export disk images%6$s\n" - "\n%3$sCommands:%4$s\n" - " pull-tar URL [NAME] Download a TAR container image\n" - " pull-raw URL [NAME] Download a RAW container or VM image\n" - " pull-oci REF [NAME] Download an OCI container image\n" - " import-tar FILE [NAME] Import a local TAR container image\n" - " import-raw FILE [NAME] Import a local RAW container or VM image\n" - " import-fs DIRECTORY [NAME] Import a local directory container image\n" - " export-tar NAME [FILE] Export a TAR container image locally\n" - " export-raw NAME [FILE] Export a RAW container or VM image locally\n" - " list-transfers Show list of transfers in progress\n" - " cancel-transfer [ID...] Cancel a transfer\n" - " list-images Show list of installed images\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " --no-ask-password Do not ask for system passwords\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --system Connect to system machine manager\n" - " --user Connect to user machine manager\n" - " --read-only Create read-only image\n" - " -q --quiet Suppress output\n" - " --json=pretty|short|off Generate JSON output\n" - " -j Equvilant to --json=pretty on TTY, --json=short\n" - " otherwise\n" - " --verify=MODE Verification mode for downloaded images (no,\n" - " checksum, signature)\n" - " --format=xz|gzip|bzip2|zstd\n" - " Desired output format for export\n" - " --force Install image even if already exists\n" - " --class=TYPE Install as the specified TYPE\n" - " -m Install as --class=machine, machine image\n" - " -P Install as --class=portable,\n" - " portable service image\n" - " -S Install as --class=sysext, system extension image\n" - " -C Install as --class=confext,\n" - " configuration extension image\n" - " --keep-download=BOOL Control whether to keep pristine copy of download\n" - " -N Same as --keep-download=no\n" - "\nSee the %2$s for details.\n", + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] COMMAND ...\n\n" + "%sDownload, import or export disk images%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} +VERB_COMMON_HELP(help); -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_NO_ASK_PASSWORD, - ARG_READ_ONLY, - ARG_JSON, - ARG_VERIFY, - ARG_FORCE, - ARG_FORMAT, - ARG_CLASS, - ARG_KEEP_DOWNLOAD, - ARG_SYSTEM, - ARG_USER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - { "json", required_argument, NULL, ARG_JSON }, - { "quiet", no_argument, NULL, 'q' }, - { "verify", required_argument, NULL, ARG_VERIFY }, - { "force", no_argument, NULL, ARG_FORCE }, - { "format", required_argument, NULL, ARG_FORMAT }, - { "class", required_argument, NULL, ARG_CLASS }, - { "keep-download", required_argument, NULL, ARG_KEEP_DOWNLOAD }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - {} - }; - - int c, r; +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - for (;;) { - c = getopt_long(argc, argv, "hH:M:jqmPSCN", options, NULL); - if (c < 0) - break; + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_NO_LEGEND: + OPTION_COMMON_NO_LEGEND: arg_legend = false; break; - case ARG_NO_ASK_PASSWORD: + OPTION_COMMON_NO_ASK_PASSWORD: arg_ask_password = false; break; - case 'H': + OPTION_COMMON_HOST: arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + arg_host = arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(arg, &arg_host, &arg_transport); if (r < 0) return r; break; - case ARG_READ_ONLY: + OPTION_LONG("system", NULL, "Connect to system machine manager"): + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; + + OPTION_LONG("user", NULL, "Connect to user machine manager"): + arg_runtime_scope = RUNTIME_SCOPE_USER; + break; + + OPTION_LONG("read-only", NULL, "Create read-only image"): arg_import_flags |= IMPORT_READ_ONLY; arg_import_flags_mask |= IMPORT_READ_ONLY; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress output"): arg_quiet = true; break; - case ARG_VERIFY: - if (streq(optarg, "help")) - return DUMP_STRING_TABLE(import_verify, ImportVerify, _IMPORT_VERIFY_MAX); - - r = import_verify_from_string(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --verify= setting: %s", optarg); - arg_verify = r; + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); + if (r <= 0) + return r; + arg_legend = false; break; - case ARG_FORCE: - arg_import_flags |= IMPORT_FORCE; - arg_import_flags_mask |= IMPORT_FORCE; + OPTION_COMMON_LOWERCASE_J: + arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; + arg_legend = false; break; - case ARG_FORMAT: - if (!STR_IN_SET(optarg, "uncompressed", "xz", "gzip", "bzip2", "zstd")) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown format: %s", optarg); + OPTION_LONG("verify", "MODE", + "Verification mode for downloaded images (no, checksum, signature)"): + if (streq(arg, "help")) + return DUMP_STRING_TABLE(import_verify, ImportVerify, _IMPORT_VERIFY_MAX); - arg_format = optarg; + r = import_verify_from_string(arg); + if (r < 0) + return log_error_errno(r, "Failed to parse --verify= setting: %s", arg); + arg_verify = r; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); - if (r <= 0) - return r; - - arg_legend = false; + OPTION_LONG("format", "FORMAT", + "Desired output format for export (zstd, xz, gzip, bzip2)"): + if (!STR_IN_SET(arg, "uncompressed", "xz", "gzip", "bzip2", "zstd")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown format: %s", arg); + arg_format = arg; break; - case 'j': - arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; - arg_legend = false; + OPTION_LONG("force", NULL, "Install image even if already exists"): + arg_import_flags |= IMPORT_FORCE; + arg_import_flags_mask |= IMPORT_FORCE; break; - case ARG_CLASS: - arg_image_class = image_class_from_string(optarg); + OPTION_LONG("class", "TYPE", "Install as the specified TYPE"): + arg_image_class = image_class_from_string(arg); if (arg_image_class < 0) - return log_error_errno(arg_image_class, "Failed to parse --class= parameter: %s", optarg); + return log_error_errno(arg_image_class, "Failed to parse --class= parameter: %s", arg); break; - case 'm': + OPTION_SHORT('m', NULL, "Install as --class=machine, machine image"): arg_image_class = IMAGE_MACHINE; break; - case 'P': + OPTION_SHORT('P', NULL, "Install as --class=portable, portable service image"): arg_image_class = IMAGE_PORTABLE; break; - case 'S': + OPTION_SHORT('S', NULL, "Install as --class=sysext, system extension image"): arg_image_class = IMAGE_SYSEXT; break; - case 'C': + OPTION_SHORT('C', NULL, "Install as --class=confext, configuration extension image"): arg_image_class = IMAGE_CONFEXT; break; - case ARG_KEEP_DOWNLOAD: - r = parse_boolean(optarg); + OPTION_LONG("keep-download", "BOOL", + "Control whether to keep pristine copy of download"): + r = parse_boolean(arg); if (r < 0) - return log_error_errno(r, "Failed to parse --keep-download= value: %s", optarg); + return log_error_errno(r, "Failed to parse --keep-download= value: %s", arg); SET_FLAG(arg_import_flags, IMPORT_PULL_KEEP_DOWNLOAD, r); arg_import_flags_mask |= IMPORT_PULL_KEEP_DOWNLOAD; break; - case 'N': + OPTION_SHORT('N', NULL, "Same as --keep-download=no"): arg_import_flags &= ~IMPORT_PULL_KEEP_DOWNLOAD; arg_import_flags_mask |= IMPORT_PULL_KEEP_DOWNLOAD; break; - - case ARG_USER: - arg_runtime_scope = RUNTIME_SCOPE_USER; - break; - - case ARG_SYSTEM: - arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } + *ret_args = option_parser_get_args(&state); return 1; } -static int importctl_main(int argc, char *argv[], sd_bus *bus) { - - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "import-tar", 2, 3, 0, verb_import_tar }, - { "import-raw", 2, 3, 0, verb_import_raw }, - { "import-fs", 2, 3, 0, verb_import_fs }, - { "export-tar", 2, 3, 0, verb_export_tar }, - { "export-raw", 2, 3, 0, verb_export_raw }, - { "pull-tar", 2, 3, 0, verb_pull_tar }, - { "pull-oci", 2, 3, 0, verb_pull_oci }, - { "pull-raw", 2, 3, 0, verb_pull_raw }, - { "list-transfers", VERB_ANY, 1, VERB_DEFAULT, verb_list_transfers }, - { "cancel-transfer", 2, VERB_ANY, 0, verb_cancel_transfer }, - { "list-images", VERB_ANY, 1, 0, verb_list_images }, - {} - }; - - return dispatch_verb(argc, argv, verbs, bus); -} - static int run(int argc, char *argv[]) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; @@ -1324,7 +1250,8 @@ static int run(int argc, char *argv[]) { setlocale(LC_ALL, ""); log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -1334,7 +1261,7 @@ static int run(int argc, char *argv[]) { (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); - return importctl_main(argc, argv, bus); + return dispatch_verb_with_args(args, bus); } DEFINE_MAIN_FUNCTION(run); From e517eadbd23013f595342e27af71ffa55ff608f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 10 Apr 2026 21:21:02 +0200 Subject: [PATCH 0907/1296] pull: convert to the new option and verb parsers Duplicated word in description of --keep-download= is fixed. Co-developed-by: Claude Opus 4.6 --- src/import/pull.c | 262 +++++++++++++++++----------------------------- 1 file changed, 98 insertions(+), 164 deletions(-) diff --git a/src/import/pull.c b/src/import/pull.c index 270f70396cab6..10aa4c52bf810 100644 --- a/src/import/pull.c +++ b/src/import/pull.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -11,6 +10,7 @@ #include "build.h" #include "discover-image.h" #include "env-util.h" +#include "format-table.h" #include "hexdecoct.h" #include "import-common.h" #include "import-util.h" @@ -19,6 +19,7 @@ #include "log.h" #include "main-func.h" #include "oci-util.h" +#include "options.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" @@ -117,7 +118,8 @@ static void on_tar_finished(TarPull *pull, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int verb_pull_tar(int argc, char *argv[], uintptr_t _data, void *userdata) { +VERB(verb_tar, "tar", "URL [NAME]", 2, 3, 0, "Download a TAR image"); +static int verb_tar(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ char *ll = NULL, *normalized = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(tar_pull_unrefp) TarPull *pull = NULL; @@ -187,7 +189,8 @@ static void on_raw_finished(RawPull *pull, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int verb_pull_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { +VERB(verb_raw, "raw", "URL [NAME]", 2, 3, 0, "Download a RAW image"); +static int verb_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ char *ll = NULL, *normalized = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(raw_pull_unrefp) RawPull *pull = NULL; @@ -256,7 +259,8 @@ static void on_oci_finished(OciPull *pull, int error, void *userdata) { sd_event_exit(event, ABS(error)); } -static int verb_pull_oci(int argc, char *argv[], uintptr_t _data, void *userdata) { +VERB(verb_oci, "oci", "REF [NAME]", 2, 3, 0, "Download an OCI image"); +static int verb_oci(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; const char *ref = argv[1]; @@ -312,134 +316,79 @@ static int verb_pull_oci(int argc, char *argv[], uintptr_t _data, void *userdata } static int help(void) { - printf("%1$s [OPTIONS...] {COMMAND} ...\n" - "\n%4$sDownload disk images.%5$s\n" - "\n%2$sCommands:%3$s\n" - " tar URL [NAME] Download a TAR image\n" - " raw URL [NAME] Download a RAW image\n" - " oci REF [NAME] Download an OCI image\n" - "\n%2$sOptions:%3$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --force Force creation of image\n" - " --verify=MODE Verify downloaded image, one of: 'no',\n" - " 'checksum', 'signature' or literal SHA256 hash\n" - " --settings=BOOL Download settings file with image\n" - " --roothash=BOOL Download root hash file with image\n" - " --roothash-signature=BOOL\n" - " Download root hash signature file with image\n" - " --verity=BOOL Download verity file with image\n" - " --image-root=PATH Image root directory\n" - " --read-only Create a read-only image\n" - " --direct Download directly to specified file\n" - " --btrfs-subvol=BOOL Controls whether to create a btrfs subvolume\n" - " instead of a directory\n" - " --btrfs-quota=BOOL Controls whether to set up quota for btrfs\n" - " subvolume\n" - " --convert-qcow2=BOOL Controls whether to convert QCOW2 images to\n" - " regular disk images\n" - " --sync=BOOL Controls whether to sync() before completing\n" - " --offset=BYTES Offset to seek to in destination\n" - " --size-max=BYTES Maximum number of bytes to write to destination\n" - " --class=CLASS Select image class (machine, sysext, confext,\n" - " portable)\n" - " --keep-download=BOOL Keep a copy pristine copy of the downloaded file\n" - " around\n" - " --system Operate in per-system mode\n" - " --user Operate in per-user mode\n", + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; + int r; + + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "%sDownload disk images.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + return 0; } -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} +VERB_COMMON_HELP_HIDDEN(help); -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_FORCE, - ARG_IMAGE_ROOT, - ARG_VERIFY, - ARG_SETTINGS, - ARG_ROOTHASH, - ARG_ROOTHASH_SIGNATURE, - ARG_VERITY, - ARG_READ_ONLY, - ARG_DIRECT, - ARG_BTRFS_SUBVOL, - ARG_BTRFS_QUOTA, - ARG_CONVERT_QCOW2, - ARG_SYNC, - ARG_OFFSET, - ARG_SIZE_MAX, - ARG_CLASS, - ARG_KEEP_DOWNLOAD, - ARG_SYSTEM, - ARG_USER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "force", no_argument, NULL, ARG_FORCE }, - { "image-root", required_argument, NULL, ARG_IMAGE_ROOT }, - { "verify", required_argument, NULL, ARG_VERIFY }, - { "settings", required_argument, NULL, ARG_SETTINGS }, - { "roothash", required_argument, NULL, ARG_ROOTHASH }, - { "roothash-signature", required_argument, NULL, ARG_ROOTHASH_SIGNATURE }, - { "verity", required_argument, NULL, ARG_VERITY }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - { "direct", no_argument, NULL, ARG_DIRECT }, - { "btrfs-subvol", required_argument, NULL, ARG_BTRFS_SUBVOL }, - { "btrfs-quota", required_argument, NULL, ARG_BTRFS_QUOTA }, - { "convert-qcow2", required_argument, NULL, ARG_CONVERT_QCOW2 }, - { "sync", required_argument, NULL, ARG_SYNC }, - { "offset", required_argument, NULL, ARG_OFFSET }, - { "size-max", required_argument, NULL, ARG_SIZE_MAX }, - { "class", required_argument, NULL, ARG_CLASS }, - { "keep-download", required_argument, NULL, ARG_KEEP_DOWNLOAD }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - {} - }; - - int c, r; +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; bool auto_settings = true, auto_keep_download = true; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_FORCE: + OPTION_LONG("force", NULL, "Force creation of image"): arg_import_flags |= IMPORT_FORCE; break; - case ARG_IMAGE_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image_root); + OPTION_LONG("image-root", "PATH", "Image root directory"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image_root); if (r < 0) return r; - break; - case ARG_VERIFY: { + OPTION_LONG("verify", "MODE", + "Verify downloaded image, one of: 'no', 'checksum', 'signature' or literal SHA256 hash"): { ImportVerify v; - v = import_verify_from_string(optarg); + v = import_verify_from_string(arg); if (v < 0) { _cleanup_free_ void *h = NULL; size_t n; @@ -447,10 +396,10 @@ static int parse_argv(int argc, char *argv[]) { /* If this is not a valid verification mode, maybe it's a literally specified * SHA256 hash? We can handle that too... */ - r = unhexmem(optarg, &h, &n); + r = unhexmem(arg, &h, &n); if (r < 0 || n == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid verification setting: %s", optarg); + "Invalid verification setting: %s", arg); if (n != 32) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "64 hex character SHA256 hash required when specifying explicit checksum, %zu specified", n * 2); @@ -466,8 +415,8 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_SETTINGS: - r = parse_boolean_argument("--settings=", optarg, NULL); + OPTION_LONG("settings", "BOOL", "Download settings file with image"): + r = parse_boolean_argument("--settings=", arg, NULL); if (r < 0) return r; @@ -475,8 +424,8 @@ static int parse_argv(int argc, char *argv[]) { auto_settings = false; break; - case ARG_ROOTHASH: - r = parse_boolean_argument("--roothash=", optarg, NULL); + OPTION_LONG("roothash", "BOOL", "Download root hash file with image"): + r = parse_boolean_argument("--roothash=", arg, NULL); if (r < 0) return r; @@ -487,118 +436,113 @@ static int parse_argv(int argc, char *argv[]) { SET_FLAG(arg_import_flags, IMPORT_PULL_ROOTHASH_SIGNATURE, false); break; - case ARG_ROOTHASH_SIGNATURE: - r = parse_boolean_argument("--roothash-signature=", optarg, NULL); + OPTION_LONG("roothash-signature", "BOOL", + "Download root hash signature file with image"): + r = parse_boolean_argument("--roothash-signature=", arg, NULL); if (r < 0) return r; SET_FLAG(arg_import_flags, IMPORT_PULL_ROOTHASH_SIGNATURE, r); break; - case ARG_VERITY: - r = parse_boolean_argument("--verity=", optarg, NULL); + OPTION_LONG("verity", "BOOL", "Download verity file with image"): + r = parse_boolean_argument("--verity=", arg, NULL); if (r < 0) return r; SET_FLAG(arg_import_flags, IMPORT_PULL_VERITY, r); break; - case ARG_READ_ONLY: + OPTION_LONG("read-only", NULL, "Create a read-only image"): arg_import_flags |= IMPORT_READ_ONLY; break; - case ARG_DIRECT: + OPTION_LONG("direct", NULL, "Download directly to specified file"): arg_import_flags |= IMPORT_DIRECT; arg_import_flags &= ~(IMPORT_PULL_SETTINGS|IMPORT_PULL_ROOTHASH|IMPORT_PULL_ROOTHASH_SIGNATURE|IMPORT_PULL_VERITY); break; - case ARG_BTRFS_SUBVOL: - r = parse_boolean_argument("--btrfs-subvol=", optarg, NULL); + OPTION_LONG("btrfs-subvol", "BOOL", + "Controls whether to create a btrfs subvolume instead of a directory"): + r = parse_boolean_argument("--btrfs-subvol=", arg, NULL); if (r < 0) return r; - SET_FLAG(arg_import_flags, IMPORT_BTRFS_SUBVOL, r); break; - case ARG_BTRFS_QUOTA: - r = parse_boolean_argument("--btrfs-quota=", optarg, NULL); + OPTION_LONG("btrfs-quota", "BOOL", + "Controls whether to set up quota for btrfs subvolume"): + r = parse_boolean_argument("--btrfs-quota=", arg, NULL); if (r < 0) return r; - SET_FLAG(arg_import_flags, IMPORT_BTRFS_QUOTA, r); break; - case ARG_CONVERT_QCOW2: - r = parse_boolean_argument("--convert-qcow2=", optarg, NULL); + OPTION_LONG("convert-qcow2", "BOOL", + "Controls whether to convert QCOW2 images to regular disk images"): + r = parse_boolean_argument("--convert-qcow2=", arg, NULL); if (r < 0) return r; - SET_FLAG(arg_import_flags, IMPORT_CONVERT_QCOW2, r); break; - case ARG_SYNC: - r = parse_boolean_argument("--sync=", optarg, NULL); + OPTION_LONG("sync", "BOOL", "Controls whether to sync() before completing"): + r = parse_boolean_argument("--sync=", arg, NULL); if (r < 0) return r; - SET_FLAG(arg_import_flags, IMPORT_SYNC, r); break; - case ARG_OFFSET: { + OPTION_LONG("offset", "BYTES", "Offset to seek to in destination"): { uint64_t u; - r = safe_atou64(optarg, &u); + r = safe_atou64(arg, &u); if (r < 0) - return log_error_errno(r, "Failed to parse --offset= argument: %s", optarg); + return log_error_errno(r, "Failed to parse --offset= argument: %s", arg); if (!FILE_SIZE_VALID(u)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --offset= switch too large: %s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --offset= switch too large: %s", arg); arg_offset = u; break; } - case ARG_SIZE_MAX: { + OPTION_LONG("size-max", "BYTES", "Maximum number of bytes to write to destination"): { uint64_t u; - r = parse_size(optarg, 1024, &u); + r = parse_size(arg, 1024, &u); if (r < 0) - return log_error_errno(r, "Failed to parse --size-max= argument: %s", optarg); + return log_error_errno(r, "Failed to parse --size-max= argument: %s", arg); if (!FILE_SIZE_VALID(u)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --size-max= switch too large: %s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --size-max= switch too large: %s", arg); arg_size_max = u; break; } - case ARG_CLASS: - arg_class = image_class_from_string(optarg); + OPTION_LONG("class", "CLASS", + "Select image class (machine, sysext, confext, portable)"): + arg_class = image_class_from_string(arg); if (arg_class < 0) - return log_error_errno(arg_class, "Failed to parse --class= argument: %s", optarg); - + return log_error_errno(arg_class, "Failed to parse --class= argument: %s", arg); break; - case ARG_KEEP_DOWNLOAD: - r = parse_boolean(optarg); + OPTION_LONG("keep-download", "BOOL", + "Keep a pristine copy of the downloaded file around"): + r = parse_boolean(arg); if (r < 0) - return log_error_errno(r, "Failed to parse --keep-download= argument: %s", optarg); + return log_error_errno(r, "Failed to parse --keep-download= argument: %s", arg); SET_FLAG(arg_import_flags, IMPORT_PULL_KEEP_DOWNLOAD, r); auto_keep_download = false; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Operate in per-system mode"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Operate in per-user mode"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } /* Make sure offset+size is still in the valid range if both set */ @@ -631,6 +575,7 @@ static int parse_argv(int argc, char *argv[]) { if (arg_runtime_scope == RUNTIME_SCOPE_USER) arg_import_flags |= IMPORT_FOREIGN_UID; + *ret_args = option_parser_get_args(&state); return 1; } @@ -659,18 +604,6 @@ static void parse_env(void) { log_warning_errno(r, "Failed to parse $SYSTEMD_IMPORT_SYNC: %m"); } -static int pull_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "tar", 2, 3, 0, verb_pull_tar }, - { "raw", 2, 3, 0, verb_pull_raw }, - { "oci", 2, 3, 0, verb_pull_oci }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { int r; @@ -679,13 +612,14 @@ static int run(int argc, char *argv[]) { parse_env(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; (void) ignore_signals(SIGPIPE); - return pull_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); From c15550e1df48b728038483d7daa270a6a4357f68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 10 Apr 2026 21:28:38 +0200 Subject: [PATCH 0908/1296] escape: convert to the new option parser Co-developed-by: Claude Opus 4.6 --- src/escape/escape-tool.c | 105 ++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 63 deletions(-) diff --git a/src/escape/escape-tool.c b/src/escape/escape-tool.c index 621182897d8f8..aa4129cdeed98 100644 --- a/src/escape/escape-tool.c +++ b/src/escape/escape-tool.c @@ -1,12 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" #include "build.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "path-util.h" #include "pretty-print.h" #include "string-util.h" @@ -26,107 +27,83 @@ static bool arg_instance = false; static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-escape", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] [NAME...]\n\n" - "%3$sEscape strings for usage in systemd unit names.%4$s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --suffix=SUFFIX Unit suffix to append to escaped strings\n" - " --template=TEMPLATE Insert strings as instance into template\n" - " --instance With --unescape, show just the instance part\n" - " -u --unescape Unescape strings\n" - " -m --mangle Mangle strings\n" - " -p --path When escaping/unescaping assume the string is a path\n" - "\nSee the %2$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] [NAME...]\n\n" + "%sEscape strings for usage in systemd unit names.%s\n\n", program_invocation_short_name, - link, ansi_highlight(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_SUFFIX, - ARG_TEMPLATE - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "suffix", required_argument, NULL, ARG_SUFFIX }, - { "template", required_argument, NULL, ARG_TEMPLATE }, - { "unescape", no_argument, NULL, 'u' }, - { "mangle", no_argument, NULL, 'm' }, - { "path", no_argument, NULL, 'p' }, - { "instance", no_argument, NULL, 'i' }, - {} - }; - - int c; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hump", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_SUFFIX: { - UnitType t = unit_type_from_string(optarg); + OPTION_LONG("suffix", "SUFFIX", "Unit suffix to append to escaped strings"): { + UnitType t = unit_type_from_string(arg); if (t < 0) - return log_error_errno(t, "Invalid unit suffix type \"%s\".", optarg); + return log_error_errno(t, "Invalid unit suffix type \"%s\".", arg); - arg_suffix = optarg; + arg_suffix = arg; break; } - case ARG_TEMPLATE: - if (!unit_name_is_valid(optarg, UNIT_NAME_TEMPLATE)) + OPTION_LONG("template", "TEMPLATE", "Insert strings as instance into template"): + if (!unit_name_is_valid(arg, UNIT_NAME_TEMPLATE)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Template name %s is not valid.", optarg); + "Template name %s is not valid.", arg); - arg_template = optarg; + arg_template = arg; break; - case 'u': + OPTION_LONG("instance", NULL, "With --unescape, show just the instance part"): + arg_instance = true; + break; + + OPTION('u', "unescape", NULL, "Unescape strings"): arg_action = ACTION_UNESCAPE; break; - case 'm': + OPTION('m', "mangle", NULL, "Mangle strings"): arg_action = ACTION_MANGLE; break; - case 'p': + OPTION('p', "path", NULL, + "When escaping/unescaping assume the string is a path"): arg_path = true; break; - - case 'i': - arg_instance = true; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind >= argc) + if (option_parser_get_n_args(&state) == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not enough arguments."); @@ -154,6 +131,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--instance may not be combined with --template."); + *ret_args = option_parser_get_args(&state); return 1; } @@ -162,11 +140,12 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - STRV_FOREACH(i, argv + optind) { + STRV_FOREACH(i, args) { _cleanup_free_ char *e = NULL; switch (arg_action) { @@ -267,7 +246,7 @@ static int run(int argc, char *argv[]) { break; } - if (i != argv + optind) + if (i != args) fputc(' ', stdout); fputs(e, stdout); From 120edabb4f04a0fdef55f7ed3564f687e1d42fb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 10 Apr 2026 21:41:34 +0200 Subject: [PATCH 0909/1296] delta: convert to the new option parser --help for --diff= is changed from old-style "1|0" to "yes|no". Co-developed-by: Claude Opus 4.6 --- src/delta/delta.c | 91 +++++++++++++++++++---------------------------- 1 file changed, 37 insertions(+), 54 deletions(-) diff --git a/src/delta/delta.c b/src/delta/delta.c index df28a730a54e6..4a134ad2355f3 100644 --- a/src/delta/delta.c +++ b/src/delta/delta.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" @@ -10,12 +9,14 @@ #include "errno-util.h" #include "extract-word.h" #include "fd-util.h" +#include "format-table.h" #include "fs-util.h" #include "glyph-util.h" #include "hashmap.h" #include "log.h" #include "main-func.h" #include "nulstr-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "path-util.h" @@ -459,23 +460,26 @@ static int process_suffix_chop(const char *arg) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-delta", "1", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...] [SUFFIX...]\n\n" - "Find overridden configuration files.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --diff[=1|0] Show a diff when overridden files differ\n" - " -t --type=LIST... Only display a selected set of override types\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - link); + "Find overridden configuration files.\n\n", + program_invocation_short_name); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } @@ -509,66 +513,45 @@ static int parse_flags(const char *flag_str, int flags) { } } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_NO_PAGER = 0x100, - ARG_DIFF, - ARG_VERSION - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "diff", optional_argument, NULL, ARG_DIFF }, - { "type", required_argument, NULL, 't' }, - {} - }; - - int c, r; - - assert(argc >= 1); +static int parse_argv(int argc, char *argv[], char ***ret_args) { + assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "ht:", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case 't': { - int f; - f = parse_flags(optarg, arg_flags); - if (f < 0) + OPTION('t', "type", "TYPE...", "Only display a selected set of override types"): + r = parse_flags(arg, arg_flags); + if (r < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse flags field."); - arg_flags = f; + arg_flags = r; break; - } - case ARG_DIFF: - r = parse_boolean_argument("--diff", optarg, NULL); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "diff", "yes|no", + "Show a diff when overridden files differ"): + r = parse_boolean_argument("--diff", arg, NULL); if (r < 0) return r; arg_diff = r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state); return 1; } @@ -577,7 +560,8 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -591,17 +575,16 @@ static int run(int argc, char *argv[]) { pager_open(arg_pager_flags); - if (optind < argc) { - for (int i = optind; i < argc; i++) { - path_simplify(argv[i]); + if (!strv_isempty(args)) { + STRV_FOREACH(i, args) { + path_simplify(*i); - k = process_suffix_chop(argv[i]); + k = process_suffix_chop(*i); if (k < 0) r = k; else n_found += k; } - } else { k = process_suffixes(NULL); if (k < 0) From c5f444e85b1739ea01d550e4d0bb4e8b32c939a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 11 Apr 2026 10:03:41 +0200 Subject: [PATCH 0910/1296] bsod: convert to the new option parser Option indentation in --help is fixed. Description for --continuous is shortened. Co-developed-by: Claude Opus 4.6 --- src/journal/bsod.c | 74 ++++++++++++++++++---------------------------- 1 file changed, 29 insertions(+), 45 deletions(-) diff --git a/src/journal/bsod.c b/src/journal/bsod.c index 9a370af3908a7..b314c08660ac8 100644 --- a/src/journal/bsod.c +++ b/src/journal/bsod.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -11,10 +10,12 @@ #include "build.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "io-util.h" #include "log.h" #include "logs-show.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" #include "qrcode-util.h" @@ -29,29 +30,32 @@ STATIC_DESTRUCTOR_REGISTER(arg_tty, freep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-bsod", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...]\n\n" - "%5$sFilter the journal to fetch the first message from the current boot with an%6$s\n" - "%5$semergency log level and display it as a string and a QR code.%6$s\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -c --continuous Make systemd-bsod wait continuously\n" - " for changes in the journal\n" - " --tty=TTY Specify path to TTY to use\n" - "\nSee the %2$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n\n" + "%sFilter the journal to fetch the first message from the current boot with an\n" + "emergency log level and display it as a string and a QR code.%s\n" + "\n%sOptions:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } @@ -239,55 +243,35 @@ static int display_emergency_message_fullscreen(const char *message) { return r; } -static int parse_argv(int argc, char * argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_TTY, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "continuous", no_argument, NULL, 'c' }, - { "tty", required_argument, NULL, ARG_TTY }, - {} - }; - - int c, r; - +static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hc", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'c': + OPTION('c', "continuous", NULL, "Continuously wait for changes in the journal"): arg_continuous = true; break; - case ARG_TTY: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tty); + OPTION_LONG("tty", "TTY", "Specify path to TTY to use"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tty); if (r < 0) return r; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind < argc) + if (option_parser_get_n_args(&state) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s takes no argument.", program_invocation_short_name); From ddc97d68d677df5f6aef8529e6ce915e32cb4688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 11 Apr 2026 10:07:49 +0200 Subject: [PATCH 0911/1296] cat: convert to the new option parser --help is identical except for whitespace. Co-developed-by: Claude Opus 4.6 --- src/journal/cat.c | 104 +++++++++++++++++++--------------------------- 1 file changed, 42 insertions(+), 62 deletions(-) diff --git a/src/journal/cat.c b/src/journal/cat.c index 76d36fce7e477..e2419d36ab334 100644 --- a/src/journal/cat.c +++ b/src/journal/cat.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include @@ -12,12 +11,15 @@ #include "build.h" #include "env-util.h" #include "fd-util.h" +#include "format-table.h" #include "format-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" #include "string-util.h" +#include "strv.h" #include "syslog-util.h" static const char *arg_identifier = NULL; @@ -28,104 +30,81 @@ static bool arg_level_prefix = true; static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-cat", "1", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...] COMMAND ...\n" - "\n%sExecute process with stdout/stderr connected to the journal.%s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -t --identifier=STRING Set syslog identifier\n" - " -p --priority=PRIORITY Set priority value (0..7)\n" - " --stderr-priority=PRIORITY Set priority value (0..7) used for stderr\n" - " --level-prefix=BOOL Control whether level prefix shall be parsed\n" - " --namespace=NAMESPACE Connect to specified journal namespace\n" - "\nSee the %s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] COMMAND ...\n\n" + "%sExecute process with stdout/stderr connected to the journal.%s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_STDERR_PRIORITY, - ARG_LEVEL_PREFIX, - ARG_NAMESPACE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "identifier", required_argument, NULL, 't' }, - { "priority", required_argument, NULL, 'p' }, - { "stderr-priority", required_argument, NULL, ARG_STDERR_PRIORITY }, - { "level-prefix", required_argument, NULL, ARG_LEVEL_PREFIX }, - { "namespace", required_argument, NULL, ARG_NAMESPACE }, - {} - }; - - int c, r; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long() - * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */ - optind = 0; - while ((c = getopt_long(argc, argv, "+ht:p:", options, NULL)) >= 0) + OptionParser state = { argc, argv, /* stop_at_first_nonoption= */ true }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - help(); - return 0; + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 't': - arg_identifier = empty_to_null(optarg); + OPTION('t', "identifier", "STRING", "Set syslog identifier"): + arg_identifier = empty_to_null(arg); break; - case 'p': - arg_priority = log_level_from_string(optarg); + OPTION('p', "priority", "PRIORITY", "Set priority value (0..7)"): + arg_priority = log_level_from_string(arg); if (arg_priority < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse priority value."); break; - case ARG_STDERR_PRIORITY: - arg_stderr_priority = log_level_from_string(optarg); + OPTION_LONG("stderr-priority", "PRIORITY", + "Set priority value (0..7) used for stderr"): + arg_stderr_priority = log_level_from_string(arg); if (arg_stderr_priority < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse stderr priority value."); break; - case ARG_LEVEL_PREFIX: - r = parse_boolean_argument("--level-prefix=", optarg, &arg_level_prefix); + OPTION_LONG("level-prefix", "BOOL", + "Control whether level prefix shall be parsed"): + r = parse_boolean_argument("--level-prefix=", arg, &arg_level_prefix); if (r < 0) return r; break; - case ARG_NAMESPACE: - arg_namespace = empty_to_null(optarg); + OPTION_LONG("namespace", "NAMESPACE", + "Connect to specified journal namespace"): + arg_namespace = empty_to_null(arg); break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state); return 1; } @@ -135,7 +114,8 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -157,7 +137,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Failed to rearrange stdout/stderr: %m"); - if (argc <= optind) + if (strv_isempty(args)) (void) execlp("cat", "cat", NULL); else { struct stat st; @@ -171,7 +151,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Failed to set environment variable JOURNAL_STREAM: %m"); - (void) execvp(argv[optind], argv + optind); + (void) execvp(args[0], args); } r = -errno; From 227460f4ef7429f0aa1d7e6e4d24448ce5be008b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 11 Apr 2026 10:52:38 +0200 Subject: [PATCH 0912/1296] cryptenroll: reorder option cases to match --help output Co-developed-by: Claude Opus 4.6 --- src/cryptenroll/cryptenroll.c | 104 +++++++++++++++++----------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index de907c4ad6240..5640a3b9feebc 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -379,44 +379,15 @@ static int parse_argv(int argc, char *argv[]) { arg_pager_flags |= PAGER_DISABLE; break; - case ARG_FIDO2_WITH_PIN: - r = parse_boolean_argument("--fido2-with-client-pin=", optarg, NULL); - if (r < 0) - return r; - - SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_PIN, r); - break; - - case ARG_FIDO2_WITH_UP: - r = parse_boolean_argument("--fido2-with-user-presence=", optarg, NULL); - if (r < 0) - return r; - - SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UP, r); - break; + case ARG_LIST_DEVICES: + return blockdev_list(BLOCKDEV_LIST_SHOW_SYMLINKS|BLOCKDEV_LIST_REQUIRE_LUKS, + /* ret_devices= */ NULL, + /* ret_n_devices= */ NULL); - case ARG_FIDO2_WITH_UV: - r = parse_boolean_argument("--fido2-with-user-verification=", optarg, NULL); + case ARG_WIPE_SLOT: + r = parse_wipe_slot(optarg); if (r < 0) return r; - - SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UV, r); - break; - - case ARG_PASSWORD: - if (arg_enroll_type >= 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Multiple operations specified at once, refusing."); - - arg_enroll_type = ENROLL_PASSWORD; - break; - - case ARG_RECOVERY_KEY: - if (arg_enroll_type >= 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Multiple operations specified at once, refusing."); - - arg_enroll_type = ENROLL_RECOVERY; break; case ARG_UNLOCK_KEYFILE: @@ -471,6 +442,22 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_PASSWORD: + if (arg_enroll_type >= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Multiple operations specified at once, refusing."); + + arg_enroll_type = ENROLL_PASSWORD; + break; + + case ARG_RECOVERY_KEY: + if (arg_enroll_type >= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Multiple operations specified at once, refusing."); + + arg_enroll_type = ENROLL_RECOVERY; + break; + case ARG_PKCS11_TOKEN_URI: { _cleanup_free_ char *uri = NULL; @@ -499,12 +486,6 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_FIDO2_CRED_ALG: - r = parse_fido2_algorithm(optarg, &arg_fido2_cred_alg); - if (r < 0) - return log_error_errno(r, "Failed to parse COSE algorithm: %s", optarg); - break; - case ARG_FIDO2_DEVICE: { _cleanup_free_ char *device = NULL; @@ -540,6 +521,36 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_FIDO2_CRED_ALG: + r = parse_fido2_algorithm(optarg, &arg_fido2_cred_alg); + if (r < 0) + return log_error_errno(r, "Failed to parse COSE algorithm: %s", optarg); + break; + + case ARG_FIDO2_WITH_PIN: + r = parse_boolean_argument("--fido2-with-client-pin=", optarg, NULL); + if (r < 0) + return r; + + SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_PIN, r); + break; + + case ARG_FIDO2_WITH_UP: + r = parse_boolean_argument("--fido2-with-user-presence=", optarg, NULL); + if (r < 0) + return r; + + SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UP, r); + break; + + case ARG_FIDO2_WITH_UV: + r = parse_boolean_argument("--fido2-with-user-verification=", optarg, NULL); + if (r < 0) + return r; + + SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UV, r); + break; + case ARG_TPM2_DEVICE: { _cleanup_free_ char *device = NULL; @@ -632,17 +643,6 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_WIPE_SLOT: - r = parse_wipe_slot(optarg); - if (r < 0) - return r; - break; - - case ARG_LIST_DEVICES: - return blockdev_list(BLOCKDEV_LIST_SHOW_SYMLINKS|BLOCKDEV_LIST_REQUIRE_LUKS, - /* ret_devices= */ NULL, - /* ret_n_devices= */ NULL); - case '?': return -EINVAL; From 85c1c04a4e13867b085284fe0c5dad7724e2f905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 11 Apr 2026 11:26:05 +0200 Subject: [PATCH 0913/1296] cryptenroll: convert to the new option parser --help is the same, apart from linewrapping. Co-developed-by: Claude Opus 4.6 --- src/cryptenroll/cryptenroll.c | 359 ++++++++++++++-------------------- 1 file changed, 143 insertions(+), 216 deletions(-) diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index 5640a3b9feebc..e9bb27c254886 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-device.h" @@ -19,9 +18,11 @@ #include "cryptsetup-util.h" #include "extract-word.h" #include "fileio.h" +#include "format-table.h" #include "libfido2-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -30,6 +31,7 @@ #include "process-util.h" #include "string-table.h" #include "string-util.h" +#include "strv.h" #include "tpm2-pcr.h" #include "tpm2-util.h" @@ -231,178 +233,96 @@ static int help(void) { if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] [BLOCK-DEVICE]\n\n" - "%5$sEnroll a security token or authentication credential to a LUKS volume.%6$s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not spawn a pager\n" - " --list-devices List candidate block devices to operate on\n" - " --wipe-slot=SLOT1,SLOT2,…\n" - " Wipe specified slots\n" - "\n%3$sUnlocking:%4$s\n" - " --unlock-key-file=PATH\n" - " Use a file to unlock the volume\n" - " --unlock-fido2-device=PATH\n" - " Use a FIDO2 device to unlock the volume\n" - " --unlock-tpm2-device=PATH\n" - " Use a TPM2 device to unlock the volume\n" - "\n%3$sSimple Enrollment:%4$s\n" - " --password Enroll a user-supplied password\n" - " --recovery-key Enroll a recovery key\n" - "\n%3$sPKCS#11 Enrollment:%4$s\n" - " --pkcs11-token-uri=URI|auto|list\n" - " Enroll a PKCS#11 security token or list them\n" - "\n%3$sFIDO2 Enrollment:%4$s\n" - " --fido2-device=PATH|auto|list\n" - " Enroll a FIDO2-HMAC security token or list them\n" - " --fido2-salt-file=PATH\n" - " Use salt from a file instead of generating one\n" - " --fido2-parameters-in-header=BOOL\n" - " Whether to store FIDO2 parameters in the LUKS2 header\n" - " --fido2-credential-algorithm=STRING\n" - " Specify COSE algorithm for FIDO2 credential\n" - " --fido2-with-client-pin=BOOL\n" - " Whether to require entering a PIN to unlock the volume\n" - " --fido2-with-user-presence=BOOL\n" - " Whether to require user presence to unlock the volume\n" - " --fido2-with-user-verification=BOOL\n" - " Whether to require user verification to unlock the volume\n" - "\n%3$sTPM2 Enrollment:%4$s\n" - " --tpm2-device=PATH|auto|list\n" - " Enroll a TPM2 device or list them\n" - " --tpm2-device-key=PATH\n" - " Enroll a TPM2 device using its public key\n" - " --tpm2-seal-key-handle=HANDLE\n" - " Specify handle of key to use for sealing\n" - " --tpm2-pcrs=PCR1+PCR2+PCR3+…\n" - " Specify TPM2 PCRs to seal against\n" - " --tpm2-public-key=PATH\n" - " Enroll signed TPM2 PCR policy against PEM public key\n" - " --tpm2-public-key-pcrs=PCR1+PCR2+PCR3+…\n" - " Enroll signed TPM2 PCR policy for specified TPM2 PCRs\n" - " --tpm2-signature=PATH\n" - " Validate public key enrollment works with JSON signature\n" - " file\n" - " --tpm2-pcrlock=PATH\n" - " Specify pcrlock policy to lock against\n" - " --tpm2-with-pin=BOOL\n" - " Whether to require entering a PIN to unlock the volume\n" - "\nSee the %2$s for details.\n", + static const char* const groups[] = { + NULL, + "Unlocking", + "Simple Enrollment", + "PKCS#11 Enrollment", + "FIDO2 Enrollment", + "TPM2 Enrollment", + }; + + _cleanup_(table_unref_many) Table *tables[ELEMENTSOF(groups) + 1] = {}; + + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + r = option_parser_get_help_table_group(groups[i], &tables[i]); + if (r < 0) + return r; + } + + (void) table_sync_column_widths(0, tables[0], tables[1], tables[2], tables[3], tables[4], tables[5]); + + printf("%s [OPTIONS...] [BLOCK-DEVICE]\n\n" + "%sEnroll a security token or authentication credential to a LUKS volume.%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), ansi_normal()); + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + printf("\n%s%s:%s\n", ansi_underline(), groups[i] ?: "Options", ansi_normal()); + + r = table_print_or_warn(tables[i]); + if (r < 0) + return r; + } + + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_PASSWORD, - ARG_RECOVERY_KEY, - ARG_UNLOCK_KEYFILE, - ARG_UNLOCK_FIDO2_DEVICE, - ARG_UNLOCK_TPM2_DEVICE, - ARG_PKCS11_TOKEN_URI, - ARG_FIDO2_DEVICE, - ARG_FIDO2_SALT_FILE, - ARG_FIDO2_PARAMETERS_IN_HEADER, - ARG_TPM2_DEVICE, - ARG_TPM2_DEVICE_KEY, - ARG_TPM2_SEAL_KEY_HANDLE, - ARG_TPM2_PCRS, - ARG_TPM2_PUBLIC_KEY, - ARG_TPM2_PUBLIC_KEY_PCRS, - ARG_TPM2_SIGNATURE, - ARG_TPM2_PCRLOCK, - ARG_TPM2_WITH_PIN, - ARG_WIPE_SLOT, - ARG_FIDO2_WITH_PIN, - ARG_FIDO2_WITH_UP, - ARG_FIDO2_WITH_UV, - ARG_FIDO2_CRED_ALG, - ARG_LIST_DEVICES, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "password", no_argument, NULL, ARG_PASSWORD }, - { "recovery-key", no_argument, NULL, ARG_RECOVERY_KEY }, - { "unlock-key-file", required_argument, NULL, ARG_UNLOCK_KEYFILE }, - { "unlock-fido2-device", required_argument, NULL, ARG_UNLOCK_FIDO2_DEVICE }, - { "unlock-tpm2-device", required_argument, NULL, ARG_UNLOCK_TPM2_DEVICE }, - { "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI }, - { "fido2-credential-algorithm", required_argument, NULL, ARG_FIDO2_CRED_ALG }, - { "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE }, - { "fido2-salt-file", required_argument, NULL, ARG_FIDO2_SALT_FILE }, - { "fido2-parameters-in-header", required_argument, NULL, ARG_FIDO2_PARAMETERS_IN_HEADER }, - { "fido2-with-client-pin", required_argument, NULL, ARG_FIDO2_WITH_PIN }, - { "fido2-with-user-presence", required_argument, NULL, ARG_FIDO2_WITH_UP }, - { "fido2-with-user-verification", required_argument, NULL, ARG_FIDO2_WITH_UV }, - { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, - { "tpm2-device-key", required_argument, NULL, ARG_TPM2_DEVICE_KEY }, - { "tpm2-seal-key-handle", required_argument, NULL, ARG_TPM2_SEAL_KEY_HANDLE }, - { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS }, - { "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY }, - { "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS }, - { "tpm2-signature", required_argument, NULL, ARG_TPM2_SIGNATURE }, - { "tpm2-pcrlock", required_argument, NULL, ARG_TPM2_PCRLOCK }, - { "tpm2-with-pin", required_argument, NULL, ARG_TPM2_WITH_PIN }, - { "wipe-slot", required_argument, NULL, ARG_WIPE_SLOT }, - { "list-devices", no_argument, NULL, ARG_LIST_DEVICES }, - {} - }; - bool auto_public_key_pcr_mask = true, auto_pcrlock = true; - int c, r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_LIST_DEVICES: + OPTION_LONG("list-devices", NULL, + "List candidate block devices to operate on"): return blockdev_list(BLOCKDEV_LIST_SHOW_SYMLINKS|BLOCKDEV_LIST_REQUIRE_LUKS, /* ret_devices= */ NULL, /* ret_n_devices= */ NULL); - case ARG_WIPE_SLOT: - r = parse_wipe_slot(optarg); + OPTION_LONG("wipe-slot", "SLOT1,SLOT2,…", + "Wipe specified slots"): + r = parse_wipe_slot(arg); if (r < 0) return r; break; - case ARG_UNLOCK_KEYFILE: + OPTION_GROUP("Unlocking"): {} + + OPTION_LONG("unlock-key-file", "PATH", + "Use a file to unlock the volume"): if (arg_unlock_type != UNLOCK_PASSWORD) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple unlock methods specified at once, refusing."); - r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_unlock_keyfile); + r = parse_path_argument(arg, /* suppress_root= */ true, &arg_unlock_keyfile); if (r < 0) return r; arg_unlock_type = UNLOCK_KEYFILE; break; - case ARG_UNLOCK_FIDO2_DEVICE: { + OPTION_LONG("unlock-fido2-device", "PATH", + "Use a FIDO2 device to unlock the volume"): { _cleanup_free_ char *device = NULL; if (arg_unlock_type != UNLOCK_PASSWORD) @@ -411,8 +331,8 @@ static int parse_argv(int argc, char *argv[]) { assert(!arg_unlock_fido2_device); - if (!streq(optarg, "auto")) { - device = strdup(optarg); + if (!streq(arg, "auto")) { + device = strdup(arg); if (!device) return log_oom(); } @@ -422,7 +342,8 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_UNLOCK_TPM2_DEVICE: { + OPTION_LONG("unlock-tpm2-device", "PATH", + "Use a TPM2 device to unlock the volume"): { _cleanup_free_ char *device = NULL; if (arg_unlock_type != UNLOCK_PASSWORD) @@ -431,8 +352,8 @@ static int parse_argv(int argc, char *argv[]) { assert(!arg_unlock_tpm2_device); - if (!streq(optarg, "auto")) { - device = strdup(optarg); + if (!streq(arg, "auto")) { + device = strdup(arg); if (!device) return log_oom(); } @@ -442,7 +363,10 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_PASSWORD: + OPTION_GROUP("Simple Enrollment"): {} + + OPTION_LONG("password", NULL, + "Enroll a user-supplied password"): if (arg_enroll_type >= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple operations specified at once, refusing."); @@ -450,7 +374,8 @@ static int parse_argv(int argc, char *argv[]) { arg_enroll_type = ENROLL_PASSWORD; break; - case ARG_RECOVERY_KEY: + OPTION_LONG("recovery-key", NULL, + "Enroll a recovery key"): if (arg_enroll_type >= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple operations specified at once, refusing."); @@ -458,25 +383,28 @@ static int parse_argv(int argc, char *argv[]) { arg_enroll_type = ENROLL_RECOVERY; break; - case ARG_PKCS11_TOKEN_URI: { + OPTION_GROUP("PKCS#11 Enrollment"): {} + + OPTION_LONG("pkcs11-token-uri", "URI|auto|list", + "Enroll a PKCS#11 security token or list them"): { _cleanup_free_ char *uri = NULL; - if (streq(optarg, "list")) + if (streq(arg, "list")) return pkcs11_list_tokens(); if (arg_enroll_type >= 0 || arg_pkcs11_token_uri) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple operations specified at once, refusing."); - if (streq(optarg, "auto")) { + if (streq(arg, "auto")) { r = pkcs11_find_token_auto(&uri); if (r < 0) return r; } else { - if (!pkcs11_uri_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", optarg); + if (!pkcs11_uri_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", arg); - uri = strdup(optarg); + uri = strdup(arg); if (!uri) return log_oom(); } @@ -486,18 +414,21 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_FIDO2_DEVICE: { + OPTION_GROUP("FIDO2 Enrollment"): {} + + OPTION_LONG("fido2-device", "PATH|auto|list", + "Enroll a FIDO2-HMAC security token or list them"): { _cleanup_free_ char *device = NULL; - if (streq(optarg, "list")) + if (streq(arg, "list")) return fido2_list_devices(); if (arg_enroll_type >= 0 || arg_fido2_device) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple operations specified at once, refusing."); - if (!streq(optarg, "auto")) { - device = strdup(optarg); + if (!streq(arg, "auto")) { + device = strdup(arg); if (!device) return log_oom(); } @@ -507,62 +438,66 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_FIDO2_SALT_FILE: - r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_fido2_salt_file); + OPTION_LONG("fido2-salt-file", "PATH", + "Use salt from a file instead of generating one"): + r = parse_path_argument(arg, /* suppress_root= */ true, &arg_fido2_salt_file); if (r < 0) return r; - break; - case ARG_FIDO2_PARAMETERS_IN_HEADER: - r = parse_boolean_argument("--fido2-parameters-in-header=", optarg, &arg_fido2_parameters_in_header); + OPTION_LONG("fido2-parameters-in-header", "BOOL", + "Whether to store FIDO2 parameters in the LUKS2 header"): + r = parse_boolean_argument("--fido2-parameters-in-header=", arg, &arg_fido2_parameters_in_header); if (r < 0) return r; - break; - case ARG_FIDO2_CRED_ALG: - r = parse_fido2_algorithm(optarg, &arg_fido2_cred_alg); + OPTION_LONG("fido2-credential-algorithm", "STRING", + "Specify COSE algorithm for FIDO2 credential"): + r = parse_fido2_algorithm(arg, &arg_fido2_cred_alg); if (r < 0) - return log_error_errno(r, "Failed to parse COSE algorithm: %s", optarg); + return log_error_errno(r, "Failed to parse COSE algorithm: %s", arg); break; - case ARG_FIDO2_WITH_PIN: - r = parse_boolean_argument("--fido2-with-client-pin=", optarg, NULL); + OPTION_LONG("fido2-with-client-pin", "BOOL", + "Whether to require entering a PIN to unlock the volume"): + r = parse_boolean_argument("--fido2-with-client-pin=", arg, NULL); if (r < 0) return r; - SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_PIN, r); break; - case ARG_FIDO2_WITH_UP: - r = parse_boolean_argument("--fido2-with-user-presence=", optarg, NULL); + OPTION_LONG("fido2-with-user-presence", "BOOL", + "Whether to require user presence to unlock the volume"): + r = parse_boolean_argument("--fido2-with-user-presence=", arg, NULL); if (r < 0) return r; - SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UP, r); break; - case ARG_FIDO2_WITH_UV: - r = parse_boolean_argument("--fido2-with-user-verification=", optarg, NULL); + OPTION_LONG("fido2-with-user-verification", "BOOL", + "Whether to require user verification to unlock the volume"): + r = parse_boolean_argument("--fido2-with-user-verification=", arg, NULL); if (r < 0) return r; - SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UV, r); break; - case ARG_TPM2_DEVICE: { + OPTION_GROUP("TPM2 Enrollment"): {} + + OPTION_LONG("tpm2-device", "PATH|auto|list", + "Enroll a TPM2 device or list them"): { _cleanup_free_ char *device = NULL; - if (streq(optarg, "list")) + if (streq(arg, "list")) return tpm2_list_devices(/* legend= */ true, /* quiet= */ false); if (arg_enroll_type >= 0 || arg_tpm2_device) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple operations specified at once, refusing."); - if (!streq(optarg, "auto")) { - device = strdup(optarg); + if (!streq(arg, "auto")) { + device = strdup(arg); if (!device) return log_oom(); } @@ -572,104 +507,96 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_TPM2_DEVICE_KEY: + OPTION_LONG("tpm2-device-key", "PATH", + "Enroll a TPM2 device using its public key"): if (arg_enroll_type >= 0 || arg_tpm2_device_key) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple operations specified at once, refusing."); - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_device_key); + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_device_key); if (r < 0) return r; arg_enroll_type = ENROLL_TPM2; break; - case ARG_TPM2_SEAL_KEY_HANDLE: - r = safe_atou32_full(optarg, 16, &arg_tpm2_seal_key_handle); + OPTION_LONG("tpm2-seal-key-handle", "HANDLE", + "Specify handle of key to use for sealing"): + r = safe_atou32_full(arg, 16, &arg_tpm2_seal_key_handle); if (r < 0) - return log_error_errno(r, "Could not parse TPM2 seal key handle index '%s': %m", optarg); - + return log_error_errno(r, "Could not parse TPM2 seal key handle index '%s': %m", arg); break; - case ARG_TPM2_PCRS: - r = tpm2_parse_pcr_argument_append(optarg, &arg_tpm2_hash_pcr_values, &arg_tpm2_n_hash_pcr_values); + OPTION_LONG("tpm2-pcrs", "PCR1+PCR2+PCR3+…", + "Specify TPM2 PCRs to seal against"): + r = tpm2_parse_pcr_argument_append(arg, &arg_tpm2_hash_pcr_values, &arg_tpm2_n_hash_pcr_values); if (r < 0) return r; - break; - case ARG_TPM2_PUBLIC_KEY: + OPTION_LONG("tpm2-public-key", "PATH", + "Enroll signed TPM2 PCR policy against PEM public key"): /* an empty argument disables loading a public key */ - if (isempty(optarg)) { + if (isempty(arg)) { arg_tpm2_load_public_key = false; arg_tpm2_public_key = mfree(arg_tpm2_public_key); break; } - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_public_key); + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_public_key); if (r < 0) return r; arg_tpm2_load_public_key = true; - break; - case ARG_TPM2_PUBLIC_KEY_PCRS: + OPTION_LONG("tpm2-public-key-pcrs", "PCR1+PCR2+PCR3+…", + "Enroll signed TPM2 PCR policy for specified TPM2 PCRs"): auto_public_key_pcr_mask = false; - r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_public_key_pcr_mask); + r = tpm2_parse_pcr_argument_to_mask(arg, &arg_tpm2_public_key_pcr_mask); if (r < 0) return r; - break; - case ARG_TPM2_SIGNATURE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_signature); + OPTION_LONG("tpm2-signature", "PATH", + "Validate public key enrollment works with JSON signature file"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_signature); if (r < 0) return r; - break; - case ARG_TPM2_PCRLOCK: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_pcrlock); + OPTION_LONG("tpm2-pcrlock", "PATH", + "Specify pcrlock policy to lock against"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_pcrlock); if (r < 0) return r; - auto_pcrlock = false; break; - case ARG_TPM2_WITH_PIN: - r = parse_boolean_argument("--tpm2-with-pin=", optarg, &arg_tpm2_pin); + OPTION_LONG("tpm2-with-pin", "BOOL", + "Whether to require entering a PIN to unlock the volume"): + r = parse_boolean_argument("--tpm2-with-pin=", arg, &arg_tpm2_pin); if (r < 0) return r; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (argc > optind+1) + char **args = option_parser_get_args(&state); + + if (strv_length(args) > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments, refusing."); - if (optind < argc) { - r = parse_path_argument(argv[optind], false, &arg_node); - if (r < 0) - return r; - } else { - if (wipe_requested()) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Wiping requested and no block device node specified, refusing."); - + if (args[0]) + r = parse_path_argument(args[0], false, &arg_node); + else if (!wipe_requested()) r = determine_default_node(); - if (r < 0) - return r; - } + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Wiping requested and no block device node specified, refusing."); + if (r < 0) + return r; if (arg_enroll_type == ENROLL_FIDO2) { - if (arg_unlock_type == UNLOCK_FIDO2 && !(arg_fido2_device && arg_unlock_fido2_device)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "When both enrolling and unlocking with FIDO2 tokens, automatic discovery is unsupported. " From 98c57879b602287a9ced6b5bda5d81ba5cf1d788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 11 Apr 2026 11:54:05 +0200 Subject: [PATCH 0914/1296] cryptsetup: convert to the new option and verb parsers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The synopisis is moved from the header to the a new section: -systemd-cryptsetup attach VOLUME SOURCE-DEVICE [KEY-FILE] [CONFIG] -systemd-cryptsetup detach VOLUME +systemd-cryptsetup [OPTIONS...] {COMMAND} ... Attach or detach an encrypted block device. +Commands: + attach VOLUME SOURCE-DEVICE [KEY-FILE] [CONFIG] Attach an encrypted block + device + detach VOLUME Detach an encrypted block + device + +Options: I think that's OK… With the autogenerated table that's the natural thing to do. Co-developed-by: Claude Opus 4.6 --- src/cryptsetup/cryptsetup.c | 87 ++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 40 deletions(-) diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 04eeb6223e219..ff9449abd1669 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -11,7 +10,6 @@ #include "sd-messages.h" #include "alloc-util.h" -#include "argv-util.h" #include "ask-password-api.h" #include "build.h" #include "cryptsetup-fido2.h" @@ -27,6 +25,7 @@ #include "escape.h" #include "extract-word.h" #include "fileio.h" +#include "format-table.h" #include "fs-util.h" #include "hexdecoct.h" #include "json-util.h" @@ -36,6 +35,7 @@ #include "main-func.h" #include "memory-util.h" #include "nulstr-util.h" +#include "options.h" #include "parse-util.h" #include "path-util.h" #include "pkcs11-util.h" @@ -2451,61 +2451,69 @@ static int attach_luks_or_plain_or_bitlk( static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-cryptsetup", "8", &link); if (r < 0) return log_oom(); - printf("%1$s attach VOLUME SOURCE-DEVICE [KEY-FILE] [CONFIG]\n" - "%1$s detach VOLUME\n\n" - "%2$sAttach or detach an encrypted block device.%3$s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\nSee the %4$s for details.\n", + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "%sAttach or detach an encrypted block device.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, ansi_highlight(), ansi_normal(), - link); + ansi_underline(), + ansi_normal()); - return 0; -} + r = table_print_or_warn(verbs); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - }; + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {} - }; + r = table_print_or_warn(options); + if (r < 0) + return r; - int c; + printf("\nSee the %s for details.\n", link); + return 0; +} +VERB_COMMON_HELP_HIDDEN(help); + +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - if (argv_looks_like_help(argc, argv)) - return help(); + OptionParser state = { argc, argv }; + const char *arg; - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state); return 1; } @@ -2588,6 +2596,8 @@ static int discover_key(const char *key_file, const char *volume, TokenType toke return r; } +VERB(verb_attach, "attach", "VOLUME SOURCE-DEVICE [KEY-FILE] [CONFIG]", 3, 5, 0, + "Attach an encrypted block device"); static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; _unused_ _cleanup_(remove_and_erasep) const char *destroy_key_file = NULL; @@ -2828,6 +2838,8 @@ static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata) return 0; } +VERB(verb_detach, "detach", "VOLUME", 2, 2, 0, + "Detach an encrypted block device"); static int verb_detach(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; const char *volume = ASSERT_PTR(argv[1]); @@ -2862,19 +2874,14 @@ static int run(int argc, char *argv[]) { umask(0022); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; cryptsetup_enable_logging(NULL); - static const Verb verbs[] = { - { "attach", 3, 5, 0, verb_attach }, - { "detach", 2, 2, 0, verb_detach }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); From 1c9fbbab1deb81e520b94419ce03837436643c58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 11 Apr 2026 12:33:44 +0200 Subject: [PATCH 0915/1296] sd-event: replace dead code path with an assert Coverity complains that the -EOPNOTSUPP can never be returned, because we always have !watch_fallback==locked. CID#1654169 --- src/libsystemd/sd-event/sd-event.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index 9256ddd81bfea..9e7ba7813cde9 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -2048,11 +2048,13 @@ static int event_add_pressure( if (errno != ENOENT) return -errno; - /* We got ENOENT. Three options now: try the fallback if we have one, or return the error as - * is (if based on user/env config), or return -EOPNOTSUPP (because we picked the path, and - * the PSI service apparently is not supported) */ - if (!watch_fallback) - return locked ? -ENOENT : -EOPNOTSUPP; + /* We got ENOENT. Two options now: try the fallback if we have one, or return the error as is + * (when based on user/env config). */ + + if (!watch_fallback) { + assert(locked); + return -ENOENT; + } path_fd = open(watch_fallback, O_PATH|O_CLOEXEC); if (path_fd < 0) { From c9defc1bebe203ab0ccf7a4a58b56360a2e0cc21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 11 Apr 2026 12:44:03 +0200 Subject: [PATCH 0916/1296] varlinkctl: drop bogus variable assignment Coverity complains that r is overridden. In fact it isn't, but we shouldn't set it like this anyway. exec_with_listen_fds() already logs, so we only need to call _exit() if it fails. CID#1646716 --- src/varlinkctl/varlinkctl.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index 18a639962c11f..f2f8c271d4718 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -700,8 +700,8 @@ static int varlink_call_and_upgrade(const char *url, const char *method, sd_json _exit(EXIT_FAILURE); } - r = exec_with_listen_fds(exec_cmdline, /* fds= */ NULL, /* n_fds= */ 0); - /* This is only reached on failure, otherwise we continue with exec_cmldine). */ + (void) exec_with_listen_fds(exec_cmdline, /* fds= */ NULL, /* n_fds= */ 0); + /* This is only reached on failure, otherwise we continue with exec_cmdline). */ _exit(EXIT_FAILURE); } @@ -993,7 +993,7 @@ static int verb_call(int argc, char *argv[], uintptr_t _data, void *userdata) { _exit(EXIT_FAILURE); } - exec_with_listen_fds(exec_cmdline, fd_array, m); + (void) exec_with_listen_fds(exec_cmdline, fd_array, m); /* This is only reached on failure, otherwise we continue with exec_cmdline. */ _exit(EXIT_FAILURE); } From efbd8a26d65c68a16c64e3d8cb5ae9d298ac6abb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 11 Apr 2026 13:06:56 +0200 Subject: [PATCH 0917/1296] fundamental: add ABS_DIFF macro Sometimes we want need to diff two unsigned numbers, which is awkward because we need to cast them to something with a sign first, if we want to use abs(). Let's add a helper that avoids the function call altogether. Also drop unnecessary parens arounds args which are delimited by commas. --- src/fundamental/macro-fundamental.h | 20 ++++++++++++++------ src/test/test-macro.c | 7 +++++++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro-fundamental.h index a5300d591ae20..6ed6cf2f8a0a1 100644 --- a/src/fundamental/macro-fundamental.h +++ b/src/fundamental/macro-fundamental.h @@ -171,7 +171,7 @@ #define U64_GB (UINT64_C(1024) * U64_MB) #undef MAX -#define MAX(a, b) __MAX(UNIQ, (a), UNIQ, (b)) +#define MAX(a, b) __MAX(UNIQ, a, UNIQ, b) #define __MAX(aq, a, bq, b) \ ({ \ const typeof(a) UNIQ_T(A, aq) = (a); \ @@ -234,7 +234,7 @@ assert_cc(sizeof(long long) == sizeof(intmax_t)); }) #undef MIN -#define MIN(a, b) __MIN(UNIQ, (a), UNIQ, (b)) +#define MIN(a, b) __MIN(UNIQ, a, UNIQ, b) #define __MIN(aq, a, bq, b) \ ({ \ const typeof(a) UNIQ_T(A, aq) = (a); \ @@ -242,6 +242,14 @@ assert_cc(sizeof(long long) == sizeof(intmax_t)); UNIQ_T(A, aq) < UNIQ_T(B, bq) ? UNIQ_T(A, aq) : UNIQ_T(B, bq); \ }) +#define ABS_DIFF(a, b) __ABS_DIFF(UNIQ, a, UNIQ, b) +#define __ABS_DIFF(aq, a, bq, b) \ + ({ \ + const typeof(a) UNIQ_T(A, aq) = (a); \ + const typeof(b) UNIQ_T(B, bq) = (b); \ + UNIQ_T(A, aq) < UNIQ_T(B, bq) ? UNIQ_T(B, bq) - UNIQ_T(A, aq) : UNIQ_T(A, aq) - UNIQ_T(B, bq); \ + }) + /* evaluates to (void) if _A or _B are not constant or of different types */ #define CONST_MIN(_A, _B) \ (__builtin_choose_expr( \ @@ -312,7 +320,7 @@ assert_cc(sizeof(long long) == sizeof(intmax_t)); }) #undef CLAMP -#define CLAMP(x, low, high) __CLAMP(UNIQ, (x), UNIQ, (low), UNIQ, (high)) +#define CLAMP(x, low, high) __CLAMP(UNIQ, x, UNIQ, low, UNIQ, high) #define __CLAMP(xq, x, lowq, low, highq, high) \ ({ \ const typeof(x) UNIQ_T(X, xq) = (x); \ @@ -329,7 +337,7 @@ assert_cc(sizeof(long long) == sizeof(intmax_t)); * computation should be possible in the given type. Therefore, we use * [x / y + !!(x % y)]. Note that on "Real CPUs" a division returns both the * quotient and the remainder, so both should be equally fast. */ -#define DIV_ROUND_UP(x, y) __DIV_ROUND_UP(UNIQ, (x), UNIQ, (y)) +#define DIV_ROUND_UP(x, y) __DIV_ROUND_UP(UNIQ, x, UNIQ, y) #define __DIV_ROUND_UP(xq, x, yq, y) \ ({ \ const typeof(x) UNIQ_T(X, xq) = (x); \ @@ -341,11 +349,11 @@ assert_cc(sizeof(long long) == sizeof(intmax_t)); #define __ROUND_UP(q, x, y) \ ({ \ const typeof(y) UNIQ_T(A, q) = (y); \ - const typeof(x) UNIQ_T(B, q) = DIV_ROUND_UP((x), UNIQ_T(A, q)); \ + const typeof(x) UNIQ_T(B, q) = DIV_ROUND_UP(x, UNIQ_T(A, q)); \ typeof(x) UNIQ_T(C, q); \ MUL_SAFE(&UNIQ_T(C, q), UNIQ_T(B, q), UNIQ_T(A, q)) ? UNIQ_T(C, q) : (typeof(x)) -1; \ }) -#define ROUND_UP(x, y) __ROUND_UP(UNIQ, (x), (y)) +#define ROUND_UP(x, y) __ROUND_UP(UNIQ, x, y) #define CASE_F_1(X) case X: #define CASE_F_2(X, ...) case X: CASE_F_1( __VA_ARGS__) diff --git a/src/test/test-macro.c b/src/test/test-macro.c index 7f7bf1ce8dbda..9a9a1fa2dac7f 100644 --- a/src/test/test-macro.c +++ b/src/test/test-macro.c @@ -130,6 +130,13 @@ TEST(MAX) { assert_se(CLAMP(CLAMP(0, -10, 10), CLAMP(-5, 10, 20), CLAMP(100, -5, 20)) == 10); } +TEST(ABS_DIFF) { + ASSERT_EQ(ABS_DIFF(5, 3), 2); + ASSERT_EQ(ABS_DIFF(3, 5), 2); + ASSERT_EQ(ABS_DIFF(5llu, 2llu), 3llu); + ASSERT_EQ(ABS_DIFF(3llu, 5llu), 2llu); +} + #pragma GCC diagnostic push #ifdef __clang__ # pragma GCC diagnostic ignored "-Waddress-of-packed-member" From 3fac59557cf15bc142b9a2f43e4cfbe99d1a6190 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 11 Apr 2026 13:09:16 +0200 Subject: [PATCH 0918/1296] homed: drop unnecessary cast to double Coverity was complaining that we we're doing a integer division and then casting that to double. This was OK, but it was also a bit pointless. An operation on a double and unsigned promoted the unsigned to a double, so it's enough if we have a double somewhere as an argument early enough. Drop noop casts and parens to make the formulas easier to read. CID#1466459 --- src/home/homed-manager.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/home/homed-manager.c b/src/home/homed-manager.c index c9d43982d01f8..6c229abadcf6a 100644 --- a/src/home/homed-manager.c +++ b/src/home/homed-manager.c @@ -1889,10 +1889,10 @@ static int manager_rebalance_calculate(Manager *m) { assert(h->rebalance_usage <= usage_sum); assert(h->rebalance_weight <= weight_sum); - d = ((double) (free_sum / 4096.0) * (double) h->rebalance_weight) / (double) weight_sum; /* Calculate new space for this home in units of 4K */ + d = free_sum / 4096.0 * h->rebalance_weight / weight_sum; /* Calculate new space for this home in units of 4K */ /* Convert from units of 4K back to bytes */ - if (d >= (double) (UINT64_MAX/4096)) + if (d >= UINT64_MAX / 4096) new_free = UINT64_MAX; else new_free = (uint64_t) d * 4096; @@ -1928,7 +1928,7 @@ static int manager_rebalance_calculate(Manager *m) { h->rebalance_pending = true; } - if ((fabs((double) h->rebalance_size - (double) h->rebalance_goal) * 100 / (double) h->rebalance_size) >= 5.0) + if (ABS_DIFF(h->rebalance_size, h->rebalance_goal) * 100.0 / h->rebalance_size >= 5.0) relevant = true; } From 3c6a713836bcf462073469f5e3592b4d01599827 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 31 Mar 2026 19:01:28 +0200 Subject: [PATCH 0919/1296] tools: run check-coccinelle.sh with (updated) parsing_hacks.h This commit runs the check-coccinelle checker scripts with the parsing_hacks.h. Because this was missing before there were some issues that did not get flagged. While at it it also adds some missing cleanup attributes and iterators to get better results. Its a bit sad that there is no (easy/obvious) way to detect when new things are needed for parsing_hacks.h --- coccinelle/parsing_hacks.h | 21 +++++++++++++++++++-- tools/check-coccinelle.sh | 2 +- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/coccinelle/parsing_hacks.h b/coccinelle/parsing_hacks.h index 24a9f1be5ecab..774dbb1e6a102 100644 --- a/coccinelle/parsing_hacks.h +++ b/coccinelle/parsing_hacks.h @@ -61,21 +61,38 @@ /* Coccinelle doesn't know this keyword, so just drop it, since it's not important for any of our rules. */ #define thread_local +/* Coccinelle can't handle the __attribute__((__cleanup__(x))) GCC extension used by our _cleanup_* + * macros. Without this, any variable declared with _cleanup_free_ or _cleanup_(foo) makes the whole + * function unparsable. Drop the attribute since it's not relevant for semantic checks. */ +#define _cleanup_free_ +#define _cleanup_(x) + /* Coccinelle fails to parse these from the included headers, so let's just drop them. */ #define PAM_EXTERN #define STACK_OF(x) /* Mark a couple of iterator explicitly as iterators, otherwise Coccinelle gets a bit confused. Coccinelle * can usually infer this information automagically, but in these specific cases it needs a bit of help. */ +#define FOREACH_ARGUMENT(entry, ...) YACFE_ITERATOR #define FOREACH_ARRAY(i, array, num) YACFE_ITERATOR -#define FOREACH_ELEMENT(i, array) YACFE_ITERATOR +#define FOREACH_DIRENT(de, d, on_error) YACFE_ITERATOR #define FOREACH_DIRENT_ALL(de, d, on_error) YACFE_ITERATOR +#define FOREACH_DIRENT_IN_BUFFER(de, buf, sz) YACFE_ITERATOR +#define FOREACH_ELEMENT(i, array) YACFE_ITERATOR #define FOREACH_STRING(x, y, ...) YACFE_ITERATOR #define HASHMAP_FOREACH(e, h) YACFE_ITERATOR +#define HASHMAP_FOREACH_KEY(e, k, h) YACFE_ITERATOR #define LIST_FOREACH(name, i, head) YACFE_ITERATOR +#define LIST_FOREACH_BACKWARDS(name, i, start) YACFE_ITERATOR +#define NULSTR_FOREACH(s, l) YACFE_ITERATOR +#define NULSTR_FOREACH_PAIR(i, j, l) YACFE_ITERATOR #define ORDERED_HASHMAP_FOREACH(e, h) YACFE_ITERATOR +#define ORDERED_HASHMAP_FOREACH_KEY(e, k, h) YACFE_ITERATOR #define SET_FOREACH(e, s) YACFE_ITERATOR -#define STRV_FOREACH_BACKWARDS YACFE_ITERATOR +#define SET_FOREACH_MOVE(e, d, s) YACFE_ITERATOR +#define STRV_FOREACH(s, l) YACFE_ITERATOR +#define STRV_FOREACH_BACKWARDS(s, l) YACFE_ITERATOR +#define STRV_FOREACH_PAIR(x, y, l) YACFE_ITERATOR /* Coccinelle really doesn't like multiline macros that are not in the "usual" do { ... } while(0) format, so * let's help it a little here by providing simplified one-line versions. */ diff --git a/tools/check-coccinelle.sh b/tools/check-coccinelle.sh index c7d1f6f6da0d5..8a436624c97e7 100755 --- a/tools/check-coccinelle.sh +++ b/tools/check-coccinelle.sh @@ -10,7 +10,7 @@ FOUND=0 for cocci in "$COCCI_DIR"/check-*.cocci; do [[ -f "$cocci" ]] || continue - output=$(spatch --very-quiet --sp-file "$cocci" --dir "$SRC_DIR" 2>&1) + output=$(spatch --very-quiet --macro-file-builtins "$COCCI_DIR/parsing_hacks.h" --sp-file "$cocci" --dir "$SRC_DIR" 2>&1) if [[ -n "$output" ]]; then echo "FAIL: $(basename "$cocci") found issues in $SRC_DIR:" echo "$output" From 043689f8e098c3fd5f522b4d1d2b74d4fd9ee252 Mon Sep 17 00:00:00 2001 From: joo es Date: Sat, 11 Apr 2026 19:58:51 +0000 Subject: [PATCH 0920/1296] po: Translated using Weblate (Arabic) Currently translated at 100.0% (266 of 266 strings) Co-authored-by: joo es Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/ar/ Translation: systemd/main --- po/ar.po | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/po/ar.po b/po/ar.po index e4a8845ebfa35..1c9882eaa322d 100644 --- a/po/ar.po +++ b/po/ar.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: systemd\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2026-02-26 13:58+0000\n" +"PO-Revision-Date: 2026-04-11 19:58+0000\n" "Last-Translator: joo es \n" "Language-Team: Arabic \n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " "&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n" -"X-Generator: Weblate 5.16.1\n" +"X-Generator: Weblate 5.16.2\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -938,12 +938,10 @@ msgid "DHCP server sends force renew message" msgstr "خادم DHCP يرسل رسالة تجديد إجبارية" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy -#| msgid "Authentication is required to send force renew message." msgid "" "Authentication is required to send a force renew message from the DHCP " "server." -msgstr "الاستيثاق مطلوب للإرسال رسالة تجديد إجبارية." +msgstr "الاستيثاق مطلوب للإرسال رسالة تجديد إجبارية من خادم DHCP." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" From d9da339bf12f6433eaeb624589956f2f8737a6a0 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Fri, 10 Apr 2026 18:32:33 +0200 Subject: [PATCH 0921/1296] sd-varlink: scale down the limit of connections per UID to 128 1024 connections per UID is unnecessarily generous, so let's scale this down a bit. D-Bus defaults to 256 connections per UID, but let's be even more conservative and go with 128. --- src/libsystemd/sd-varlink/sd-varlink.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index fdcbcff0e1f06..372ede755b5bb 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -39,7 +39,7 @@ #include "varlink-org.varlink.service.h" #define VARLINK_DEFAULT_CONNECTIONS_MAX 4096U -#define VARLINK_DEFAULT_CONNECTIONS_PER_UID_MAX 1024U +#define VARLINK_DEFAULT_CONNECTIONS_PER_UID_MAX 128U #define VARLINK_DEFAULT_TIMEOUT_USEC (45U*USEC_PER_SEC) #define VARLINK_COLLECT_MAX 1024U From ff102359b7cf767c9c793a163ff34d95370d0107 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 11 Apr 2026 22:04:37 +0100 Subject: [PATCH 0922/1296] uid-range: add assert to prevent underflow in coalesce loop Coverity flags range->n_entries - j as a potential underflow in the memmove size calculation. Add assert(range->n_entries > 0) before decrementing n_entries, which holds since the loop condition guarantees j < n_entries. CID#1548015 Follow-up for 8dcc66cefc8ab489568c737adcba960756d76a3c --- src/basic/uid-range.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/basic/uid-range.c b/src/basic/uid-range.c index 3d8f8445c4559..62c7d7d928eb7 100644 --- a/src/basic/uid-range.c +++ b/src/basic/uid-range.c @@ -75,6 +75,8 @@ static void uid_range_coalesce(UIDRange *range) { if (range->n_entries > j + 1) memmove(y, y + 1, sizeof(UIDRangeEntry) * (range->n_entries - j - 1)); + /* Silence static analyzers, n_entries > 0 since j < n_entries holds in the loop condition */ + assert(range->n_entries > 0); range->n_entries--; /* Silence static analyzers, j cannot be 0 here since it starts at i + 1, i.e. >= 1 */ From 1afc0c6c608e75e6fccba13cc4f36039b0a7ae6e Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 11 Apr 2026 22:15:52 +0100 Subject: [PATCH 0923/1296] nss-myhostname: add more INC_SAFE for buffer index accumulation Use overflow-safe INC_SAFE() instead of raw addition for idx accumulation, so that Coverity can see the addition is checked. CID#1548028 Follow-up for a05483a921a518fd283e7cb32dc8c8e816b2ab2c --- src/nss-myhostname/nss-myhostname.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/nss-myhostname/nss-myhostname.c b/src/nss-myhostname/nss-myhostname.c index 601a4198dd8e8..b4a9775ef352b 100644 --- a/src/nss-myhostname/nss-myhostname.c +++ b/src/nss-myhostname/nss-myhostname.c @@ -230,7 +230,7 @@ static enum nss_status fill_in_hostent( if (additional) { r_alias = buffer + idx; memcpy(r_alias, additional, l_additional+1); - idx += ALIGN(l_additional+1); + assert_se(INC_SAFE(&idx, ALIGN(l_additional+1))); } /* Second, create aliases array */ @@ -258,14 +258,14 @@ static enum nss_status fill_in_hostent( } assert(i == c); - idx += c*ALIGN(alen); + assert_se(INC_SAFE(&idx, c*ALIGN(alen))); } else if (af == AF_INET) { *(uint32_t*) r_addr = local_address_ipv4; - idx += ALIGN(alen); + assert_se(INC_SAFE(&idx, ALIGN(alen))); } else if (socket_ipv6_is_enabled()) { memcpy(r_addr, LOCALADDRESS_IPV6, FAMILY_ADDRESS_SIZE(AF_INET6)); - idx += ALIGN(alen); + assert_se(INC_SAFE(&idx, ALIGN(alen))); } /* Fourth, add address pointer array */ @@ -277,15 +277,15 @@ static enum nss_status fill_in_hostent( ((char**) r_addr_list)[i] = r_addr + i*ALIGN(alen); ((char**) r_addr_list)[i] = NULL; - idx += (c+1) * sizeof(char*); + assert_se(INC_SAFE(&idx, (c+1) * sizeof(char*))); } else if (af == AF_INET || socket_ipv6_is_enabled()) { ((char**) r_addr_list)[0] = r_addr; ((char**) r_addr_list)[1] = NULL; - idx += 2 * sizeof(char*); + assert_se(INC_SAFE(&idx, 2 * sizeof(char*))); } else { ((char**) r_addr_list)[0] = NULL; - idx += sizeof(char*); + assert_se(INC_SAFE(&idx, sizeof(char*))); } /* Verify the size matches */ From efccc0dc2311ef21cb10334a2594616f340a415f Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 11 Apr 2026 22:50:39 +0100 Subject: [PATCH 0924/1296] debug-generator: assert breakpoint type is valid before bit shift The BreakpointType enum includes _BREAKPOINT_TYPE_INVALID (-EINVAL), so Coverity flags the bit shift as potentially using a negative shift amount. Add an assert to verify the type is in valid range, since the static table only contains valid entries. CID#1568482 Follow-up for 1929226e7e649b72f3f9acd464eaac771c00945c --- src/debug-generator/debug-generator.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/debug-generator/debug-generator.c b/src/debug-generator/debug-generator.c index e3b7768fbc8cf..9ef271343cc59 100644 --- a/src/debug-generator/debug-generator.c +++ b/src/debug-generator/debug-generator.c @@ -101,6 +101,7 @@ static int parse_breakpoint_from_string(const char *s, uint32_t *ret_breakpoints FOREACH_ELEMENT(i, breakpoint_info_table) if (FLAGS_SET(i->validity, BREAKPOINT_DEFAULT) && breakpoint_applies(i, INT_MAX)) { + assert(i->type >= 0 && i->type < _BREAKPOINT_TYPE_MAX); /* silence coverity */ breakpoints |= UINT32_C(1) << i->type; found_default = true; break; From 44296e41db20b40d0b9a4cbe320d262ffdd8905d Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 11 Apr 2026 22:52:47 +0100 Subject: [PATCH 0925/1296] test-json: add iszero_safe guards for float division at index 0 and 1 The existing iszero_safe guards at index 9 and 10 were added to silence Coverity, but the same division-by-float-zero warning also applies to the divisions at index 0 (DBL_MIN) and 1 (DBL_MAX). CID#1587762 Follow-up for 7f133c996c8b1ea9219540ec8f966b64b58d30a6 --- src/test/test-json.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/test-json.c b/src/test/test-json.c index 8e2c8621c1f6d..1bd5d94f987d9 100644 --- a/src/test/test-json.c +++ b/src/test/test-json.c @@ -690,7 +690,9 @@ static void test_float_match(sd_json_variant *v) { assert_se(sd_json_variant_is_array(v)); assert_se(sd_json_variant_elements(v) == 11); + assert_se(!iszero_safe(sd_json_variant_real(sd_json_variant_by_index(v, 0)))); assert_se(fabs(1.0 - (DBL_MIN / sd_json_variant_real(sd_json_variant_by_index(v, 0)))) <= delta); + assert_se(!iszero_safe(sd_json_variant_real(sd_json_variant_by_index(v, 1)))); assert_se(fabs(1.0 - (DBL_MAX / sd_json_variant_real(sd_json_variant_by_index(v, 1)))) <= delta); assert_se(sd_json_variant_is_null(sd_json_variant_by_index(v, 2))); /* nan is not supported by json → null */ assert_se(sd_json_variant_is_null(sd_json_variant_by_index(v, 3))); /* +inf is not supported by json → null */ From bd141bd818fcb2e35638f963b0680a1218776f5d Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 31 Mar 2026 19:53:24 +0200 Subject: [PATCH 0926/1296] many: fix remaining check-pointer-deref issues The updated parsing_hacks.h file uncovered a bunch of extra things that the check-pointer-deref coccinelle script flags. This commit fixes them to make the tree check-pointer-deref clean. --- src/analyze/analyze-plot.c | 2 ++ src/analyze/analyze-syscall-filter.c | 2 ++ src/analyze/analyze-time-data.c | 4 ++++ src/basic/build-path.c | 2 ++ src/basic/cgroup-util.c | 4 ++++ src/basic/fileio.c | 2 ++ src/basic/hashmap.c | 6 ++++++ src/basic/path-util.c | 2 ++ src/basic/string-util.c | 1 + src/basic/terminal-util.c | 2 ++ src/basic/uid-classification.c | 2 ++ src/basic/unit-name.c | 3 +++ src/basic/utf8.c | 3 +++ src/core/bpf-firewall.c | 2 ++ src/core/dbus.c | 1 + src/core/execute.c | 2 ++ src/core/main.c | 2 ++ src/core/manager.c | 2 ++ src/core/socket.c | 1 + src/core/unit-printf.c | 2 ++ src/core/unit.c | 5 +++++ src/core/varlink-cgroup.c | 2 ++ src/core/varlink-execute.c | 16 ++++++++++++---- src/coredump/coredump-context.c | 1 + src/cryptenroll/cryptenroll-pkcs11.c | 2 ++ src/hibernate-resume/hibernate-resume-config.c | 2 ++ src/home/homectl.c | 2 ++ src/home/homed-home-bus.c | 2 ++ src/home/homework-luks.c | 4 ++++ src/home/homework.c | 1 + src/home/user-record-util.c | 1 + src/hostname/hostnamed.c | 4 ++++ src/import/curl-util.c | 2 ++ src/import/oci-util.c | 4 +++- src/import/pull-common.c | 1 + src/import/pull.c | 2 ++ src/journal-remote/journal-gatewayd.c | 2 ++ src/journal/journald-manager.c | 2 ++ src/kernel-install/kernel-install.c | 1 + src/libsystemd/sd-bus/bus-message.c | 2 ++ src/libsystemd/sd-bus/test-bus-benchmark.c | 2 ++ .../sd-device/test-sd-device-monitor.c | 7 +++++++ src/libsystemd/sd-device/test-sd-device.c | 3 +++ .../sd-journal/test-journal-interleaving.c | 2 ++ src/libsystemd/sd-netlink/netlink-message-nfnl.c | 10 ++++++++++ src/libsystemd/sd-netlink/test-netlink.c | 2 ++ src/libsystemd/sd-varlink/sd-varlink-idl.c | 4 ++++ src/libsystemd/sd-varlink/varlink-util.c | 2 ++ src/login/logind-dbus.c | 3 +++ src/machine/machinectl.c | 1 + src/machine/machined-dbus.c | 2 ++ src/measure/measure-tool.c | 2 ++ src/mountfsd/mountfsd-manager.c | 2 ++ src/network/netdev/fou-tunnel.c | 1 + src/network/netdev/l2tp-tunnel.c | 2 ++ src/network/netdev/macsec.c | 1 + src/network/networkd-address.c | 2 ++ src/network/networkd-dhcp-server.c | 3 +++ src/network/networkd-dhcp6.c | 4 ++++ src/network/networkd-manager.c | 2 ++ src/network/networkd-nexthop.c | 2 ++ src/network/networkd-route.c | 2 ++ src/network/tc/qdisc.c | 2 ++ src/network/tc/tclass.c | 2 ++ src/nspawn/nspawn-mount.c | 5 +++-- src/nspawn/nspawn-network.c | 1 + src/nspawn/nspawn-oci.c | 1 + src/nspawn/nspawn.c | 2 ++ src/nsresourced/nsresourced-manager.c | 2 ++ src/nss-resolve/nss-resolve.c | 2 ++ src/pcrextend/pcrextend.c | 1 + src/pcrlock/pcrlock.c | 3 +++ src/portable/portablectl.c | 2 ++ src/repart/repart.c | 4 ++++ src/report/report-cgroup.c | 3 +++ src/report/report.c | 4 ++++ src/resolve/resolved-dns-dnssec.c | 2 ++ src/resolve/resolved-dns-synthesize.c | 2 ++ src/resolve/resolved-dns-transaction.c | 2 ++ src/resolve/resolved-static-records.c | 2 ++ src/resolve/resolved-varlink.c | 3 +++ src/resolve/test-dnssec-complex.c | 2 ++ src/shared/bootspec.c | 1 + src/shared/bpf-program.c | 2 ++ src/shared/bus-unit-util.c | 2 ++ src/shared/calendarspec.c | 1 + src/shared/cgroup-show.c | 2 ++ src/shared/conf-parser.c | 3 +++ src/shared/creds-util.c | 5 +++++ src/shared/cryptsetup-util.c | 4 ++++ src/shared/dissect-image.c | 1 + src/shared/dns-answer.c | 2 ++ src/shared/dns-packet.c | 5 +++++ src/shared/dns-rr.c | 1 + src/shared/extension-util.c | 1 + src/shared/fido2-util.c | 4 ++++ src/shared/hwdb-util.c | 2 ++ src/shared/install-printf.c | 2 ++ src/shared/install.c | 5 +++++ src/shared/libfido2-util.c | 6 ++++++ src/shared/libmount-util.c | 2 ++ src/shared/machine-bind-user.c | 2 ++ src/shared/mkfs-util.c | 5 +++-- src/shared/openssl-util.c | 14 ++++++++++++++ src/shared/pcrextend-util.c | 1 + src/shared/pkcs11-util.c | 12 ++++++++++++ src/shared/pretty-print.c | 8 ++++++++ src/shared/seccomp-util.c | 2 ++ src/shared/snapshot-util.c | 2 ++ src/shared/socket-netlink.c | 1 + src/shared/tar-util.c | 3 +++ src/shared/tpm2-util.c | 4 ++++ src/shared/unit-file.c | 2 ++ src/shared/volatile-util.c | 2 ++ src/shared/watchdog.c | 2 ++ src/systemctl/systemctl-log-setting.c | 2 ++ src/sysupdate/sysupdate-partition.c | 1 + src/sysupdate/sysupdate-resource.c | 1 + src/sysupdate/sysupdate-transfer.c | 1 + src/sysusers/sysusers.c | 8 ++++++++ src/tmpfiles/offline-passwd.c | 4 ++++ src/tmpfiles/tmpfiles.c | 1 + src/tpm2-setup/tpm2-swtpm.c | 2 ++ src/udev/udevadm-monitor.c | 2 ++ src/userdb/userdbd-manager.c | 2 ++ .../xdg-autostart-service.c | 3 +++ 126 files changed, 342 insertions(+), 9 deletions(-) diff --git a/src/analyze/analyze-plot.c b/src/analyze/analyze-plot.c index 7f92c1c6bb23e..3a3f07d2e36d8 100644 --- a/src/analyze/analyze-plot.c +++ b/src/analyze/analyze-plot.c @@ -87,6 +87,8 @@ static int acquire_host_info(sd_bus *bus, HostInfo **hi) { _cleanup_(free_host_infop) HostInfo *host = NULL; int r; + assert(hi); + host = new0(HostInfo, 1); if (!host) return log_oom(); diff --git a/src/analyze/analyze-syscall-filter.c b/src/analyze/analyze-syscall-filter.c index cb2a2eab16acd..73d03d47a183a 100644 --- a/src/analyze/analyze-syscall-filter.c +++ b/src/analyze/analyze-syscall-filter.c @@ -21,6 +21,8 @@ static int load_kernel_syscalls(Set **ret) { _cleanup_fclose_ FILE *f = NULL; int r; + assert(ret); + /* Let's read the available system calls from the list of available tracing events. Slightly dirty, * but good enough for analysis purposes. */ diff --git a/src/analyze/analyze-time-data.c b/src/analyze/analyze-time-data.c index 9b67a1b417c32..70332b0f691ad 100644 --- a/src/analyze/analyze-time-data.c +++ b/src/analyze/analyze-time-data.c @@ -172,6 +172,8 @@ int pretty_boot_time(sd_bus *bus, char **ret) { BootTimes *t; int r; + assert(ret); + r = acquire_boot_times(bus, /* require_finished= */ true, &t); if (r < 0) return r; @@ -297,6 +299,8 @@ int acquire_time_data(sd_bus *bus, bool require_finished, UnitTimes **out) { UnitInfo u; int r; + assert(out); + r = acquire_boot_times(bus, require_finished, &boot_times); if (r < 0) return r; diff --git a/src/basic/build-path.c b/src/basic/build-path.c index ddbaf4ee3c64c..44e04d98aca5b 100644 --- a/src/basic/build-path.c +++ b/src/basic/build-path.c @@ -195,6 +195,8 @@ static int find_build_dir_binary(const char *fn, char **ret) { static int find_environment_binary(const char *fn, const char **ret) { + assert(ret); + /* If a path such as /usr/lib/systemd/systemd-foobar is specified, then this will check for an * environment variable SYSTEMD_FOOBAR_PATH and return it if set. */ diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c index 1e42aa60aa4cc..6a40ede29ed66 100644 --- a/src/basic/cgroup-util.c +++ b/src/basic/cgroup-util.c @@ -1256,6 +1256,8 @@ bool cg_needs_escape(const char *p) { int cg_escape(const char *p, char **ret) { _cleanup_free_ char *n = NULL; + assert(ret); + /* This implements very minimal escaping for names to be used as file names in the cgroup tree: any * name which might conflict with a kernel name or is prefixed with '_' is prefixed with a '_'. That * way, when reading cgroup names it is sufficient to remove a single prefixing underscore if there @@ -1652,6 +1654,8 @@ int cg_mask_supported_subtree(const char *root, CGroupMask *ret) { CGroupMask mask; int r; + assert(ret); + /* Determines the mask of supported cgroup controllers. Only includes controllers we can make sense of and that * are actually accessible. Only covers real controllers, i.e. not the CGROUP_CONTROLLER_BPF_xyz * pseudo-controllers. */ diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 7edf54edf37fe..2dfa37f20bcc7 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -1370,6 +1370,8 @@ int read_timestamp_file(const char *fn, usec_t *ret) { uint64_t t; int r; + assert(ret); + r = read_one_line_file(fn, &ln); if (r < 0) return r; diff --git a/src/basic/hashmap.c b/src/basic/hashmap.c index 1d3ee1d9e79e0..451bb0d1ec25a 100644 --- a/src/basic/hashmap.c +++ b/src/basic/hashmap.c @@ -1853,6 +1853,8 @@ int set_consume(Set *s, void *value) { int hashmap_put_strdup_full(Hashmap **h, const struct hash_ops *hash_ops, const char *k, const char *v) { int r; + assert(h); + r = hashmap_ensure_allocated(h, hash_ops); if (r < 0) return r; @@ -2197,6 +2199,8 @@ int _hashmap_dump_keys_sorted(HashmapBase *h, void ***ret, size_t *ret_n) { size_t n; int r; + assert(ret); + r = _hashmap_dump_entries_sorted(h, &entries, &n); if (r < 0) return r; @@ -2216,6 +2220,8 @@ int _hashmap_dump_sorted(HashmapBase *h, void ***ret, size_t *ret_n) { size_t n; int r; + assert(ret); + r = _hashmap_dump_entries_sorted(h, &entries, &n); if (r < 0) return r; diff --git a/src/basic/path-util.c b/src/basic/path-util.c index 5b499fb6cd3c8..fedb347e4a461 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -1076,6 +1076,8 @@ int path_split_prefix_filename(const char *path, char **ret_dir, char **ret_file const char *c, *next = NULL; int r; + POINTER_MAY_BE_NULL(path); + /* Split the path into dir prefix/filename pair. Returns: * * -EINVAL → if the path is not valid diff --git a/src/basic/string-util.c b/src/basic/string-util.c index 303603baa334b..bd79dbfd24730 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -667,6 +667,7 @@ char* strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { assert(ibuf); assert(*ibuf); + POINTER_MAY_BE_NULL(_isz); /* This does three things: * diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index ecdc241247286..8c86d2e7be7c6 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -1463,6 +1463,8 @@ int getttyname_harder(int fd, char **ret) { _cleanup_free_ char *s = NULL; int r; + assert(ret); + r = getttyname_malloc(fd, &s); if (r < 0) return r; diff --git a/src/basic/uid-classification.c b/src/basic/uid-classification.c index 364bb7599736f..a3bc7ef3d4cb6 100644 --- a/src/basic/uid-classification.c +++ b/src/basic/uid-classification.c @@ -39,6 +39,8 @@ static int parse_alloc_uid(const char *path, const char *name, const char *t, ui #endif int read_login_defs(UGIDAllocationRange *ret_defs, const char *path, const char *root) { + assert(ret_defs); + #if ENABLE_COMPAT_MUTABLE_UID_BOUNDARIES _cleanup_fclose_ FILE *f = NULL; UGIDAllocationRange defs; diff --git a/src/basic/unit-name.c b/src/basic/unit-name.c index 8a04638b2f87b..70ea429e27969 100644 --- a/src/basic/unit-name.c +++ b/src/basic/unit-name.c @@ -345,6 +345,7 @@ int unit_name_unescape(const char *f, char **ret) { char *t; assert(f); + assert(ret); r = strdup(f); if (!r) @@ -547,6 +548,8 @@ int unit_name_hash_long(const char *name, char **ret) { le64_t h; size_t len; + assert(ret); + if (strlen(name) < UNIT_NAME_MAX) return -EMSGSIZE; diff --git a/src/basic/utf8.c b/src/basic/utf8.c index c527908b264bc..edb0ea1ca5513 100644 --- a/src/basic/utf8.c +++ b/src/basic/utf8.c @@ -284,6 +284,9 @@ int utf8_to_ascii(const char *str, char replacement_char, char **ret) { /* Convert to a string that has only ASCII chars, replacing anything that is not ASCII * by replacement_char. */ + assert(str); + assert(ret); + _cleanup_free_ char *ans = new(char, strlen(str) + 1); if (!ans) return -ENOMEM; diff --git a/src/core/bpf-firewall.c b/src/core/bpf-firewall.c index bc5d7f0351dcd..0a3107e8685b8 100644 --- a/src/core/bpf-firewall.c +++ b/src/core/bpf-firewall.c @@ -603,6 +603,8 @@ int bpf_firewall_compile(Unit *u) { } static int load_bpf_progs_from_fs_to_set(Unit *u, char **filter_paths, Set **set) { + assert(set); + set_clear(*set); STRV_FOREACH(bpf_fs_path, filter_paths) { diff --git a/src/core/dbus.c b/src/core/dbus.c index f5a117d2bde96..dba79b860266f 100644 --- a/src/core/dbus.c +++ b/src/core/dbus.c @@ -229,6 +229,7 @@ static int find_unit(Manager *m, sd_bus *bus, const char *path, Unit **unit, sd_ assert(m); assert(bus); assert(path); + assert(unit); if (streq(path, "/org/freedesktop/systemd1/unit/self")) { _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; diff --git a/src/core/execute.c b/src/core/execute.c index e6cb9e5cc864a..dea0699ba438a 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -2372,6 +2372,8 @@ static int exec_shared_runtime_add( assert(m); assert(id); + assert(tmp_dir); + assert(var_tmp_dir); /* tmp_dir, var_tmp_dir, {net,ipc}ns_storage_socket fds are donated on success */ diff --git a/src/core/main.c b/src/core/main.c index 655f0ac6659c6..b4022105e88b4 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -2652,6 +2652,8 @@ static int do_queue_default_job( Unit *target; int r; + assert(ret_error_message); + if (arg_default_unit) unit = arg_default_unit; else if (in_initrd()) diff --git a/src/core/manager.c b/src/core/manager.c index 73368ec18aec9..89e5ccd1a8ebb 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -2522,6 +2522,8 @@ int manager_load_startable_unit_or_warn( Unit *unit; int r; + assert(ret); + r = manager_load_unit(m, name, path, &error, &unit); if (r < 0) return log_error_errno(r, "Failed to load %s %s: %s", diff --git a/src/core/socket.c b/src/core/socket.c index f911f1758fb96..fbf0dfd9332ae 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -2026,6 +2026,7 @@ static int socket_chown(Socket *s, PidRef *ret_pid) { int r; assert(s); + assert(ret_pid); r = socket_arm_timer(s, /* relative= */ true, s->timeout_usec); if (r < 0) diff --git a/src/core/unit-printf.c b/src/core/unit-printf.c index 473f7c7d20d2c..8c168b0bb0e7d 100644 --- a/src/core/unit-printf.c +++ b/src/core/unit-printf.c @@ -50,6 +50,8 @@ static int specifier_last_component(char specifier, const void *data, const char char *dash; int r; + assert(ret); + r = unit_name_to_prefix(u->id, &prefix); if (r < 0) return r; diff --git a/src/core/unit.c b/src/core/unit.c index dc205097e050f..3404d7f8d3a76 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -132,6 +132,8 @@ int unit_new_for_name(Manager *m, size_t size, const char *name, Unit **ret) { _cleanup_(unit_freep) Unit *u = NULL; int r; + assert(ret); + u = unit_new(m, size); if (!u) return -ENOMEM; @@ -4301,6 +4303,9 @@ static int user_from_unit_name(Unit *u, char **ret) { _cleanup_free_ char *n = NULL; int r; + assert(u); + assert(ret); + r = unit_name_to_prefix(u->id, &n); if (r < 0) return r; diff --git a/src/core/varlink-cgroup.c b/src/core/varlink-cgroup.c index ab32def28b7bb..26abeefc0a8ed 100644 --- a/src/core/varlink-cgroup.c +++ b/src/core/varlink-cgroup.c @@ -194,6 +194,8 @@ static int device_allow_build_json(sd_json_variant **ret, const char *name, void CGroupDeviceAllow *allow = userdata; int r; + assert(ret); + LIST_FOREACH(device_allow, a, allow) { r = sd_json_variant_append_arraybo( &v, diff --git a/src/core/varlink-execute.c b/src/core/varlink-execute.c index ccb454c8c245b..5a65220f1e161 100644 --- a/src/core/varlink-execute.c +++ b/src/core/varlink-execute.c @@ -470,48 +470,56 @@ static int private_bpf_delegate_commands_build_json(sd_json_variant **ret, const ExecContext *c = ASSERT_PTR(userdata); _cleanup_free_ char *v = bpf_delegate_commands_to_string(c->bpf_delegate_commands); + assert(ret); + if (!v) { *ret = NULL; return 0; } - return sd_json_variant_new_string(ASSERT_PTR(ret), v); + return sd_json_variant_new_string(ret, v); } static int private_bpf_delegate_maps_build_json(sd_json_variant **ret, const char *name, void *userdata) { ExecContext *c = ASSERT_PTR(userdata); _cleanup_free_ char *v = bpf_delegate_maps_to_string(c->bpf_delegate_maps); + assert(ret); + if (!v) { *ret = NULL; return 0; } - return sd_json_variant_new_string(ASSERT_PTR(ret), v); + return sd_json_variant_new_string(ret, v); } static int private_bpf_delegate_programs_build_json(sd_json_variant **ret, const char *name, void *userdata) { ExecContext *c = ASSERT_PTR(userdata); _cleanup_free_ char *v = bpf_delegate_programs_to_string(c->bpf_delegate_programs); + assert(ret); + if (!v) { *ret = NULL; return 0; } - return sd_json_variant_new_string(ASSERT_PTR(ret), v); + return sd_json_variant_new_string(ret, v); } static int private_bpf_delegate_attachments_build_json(sd_json_variant **ret, const char *name, void *userdata) { ExecContext *c = ASSERT_PTR(userdata); _cleanup_free_ char *v = bpf_delegate_attachments_to_string(c->bpf_delegate_attachments); + assert(ret); + if (!v) { *ret = NULL; return 0; } - return sd_json_variant_new_string(ASSERT_PTR(ret), v); + return sd_json_variant_new_string(ret, v); } static int syscall_filter_build_json(sd_json_variant **ret, const char *name, void *userdata) { diff --git a/src/coredump/coredump-context.c b/src/coredump/coredump-context.c index 574d3ecd52928..6cacae4eff1ae 100644 --- a/src/coredump/coredump-context.c +++ b/src/coredump/coredump-context.c @@ -153,6 +153,7 @@ static int get_process_container_parent_cmdline(PidRef *pid, char** ret_cmdline) assert(pidref_is_set(pid)); assert(!pidref_is_remote(pid)); + assert(ret_cmdline); r = pidref_from_same_root_fs(pid, &PIDREF_MAKE_FROM_PID(1)); if (r < 0) diff --git a/src/cryptenroll/cryptenroll-pkcs11.c b/src/cryptenroll/cryptenroll-pkcs11.c index 18ffb2edc3e8b..7ddb9f871ba71 100644 --- a/src/cryptenroll/cryptenroll-pkcs11.c +++ b/src/cryptenroll/cryptenroll-pkcs11.c @@ -14,6 +14,8 @@ static int uri_set_private_class(const char *uri, char **ret_uri) { _cleanup_free_ char *private_uri = NULL; int r; + assert(ret_uri); + r = uri_from_string(uri, &p11kit_uri); if (r < 0) return log_error_errno(r, "Failed to parse PKCS#11 URI '%s': %m", uri); diff --git a/src/hibernate-resume/hibernate-resume-config.c b/src/hibernate-resume/hibernate-resume-config.c index 8e2ca625aeca0..8b4873924599c 100644 --- a/src/hibernate-resume/hibernate-resume-config.c +++ b/src/hibernate-resume/hibernate-resume-config.c @@ -242,6 +242,8 @@ int acquire_hibernate_info(HibernateInfo *ret) { _cleanup_(hibernate_info_done) HibernateInfo i = {}; int r; + assert(ret); + r = get_kernel_hibernate_location(&i.cmdline); if (r < 0) return r; diff --git a/src/home/homectl.c b/src/home/homectl.c index 9dc135fd70508..194e73b491749 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3658,6 +3658,8 @@ static int parse_environment_field(sd_json_variant **identity, const char *field static int parse_language_field(char ***languages, const char *arg) { int r; + assert(languages); + if (isempty(arg)) { r = drop_from_identity("preferredLanguage", "additionalLanguages"); if (r < 0) diff --git a/src/home/homed-home-bus.c b/src/home/homed-home-bus.c index 8b9a13f7ea5ef..10b9af8e026fc 100644 --- a/src/home/homed-home-bus.c +++ b/src/home/homed-home-bus.c @@ -801,6 +801,8 @@ static int bus_home_object_find( Home *h; int r; + assert(found); + r = sd_bus_path_decode(path, "/org/freedesktop/home1/home", &e); if (r <= 0) return 0; diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index caa05db26f491..1f8c12d3a3111 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -805,6 +805,7 @@ static int crypt_device_to_evp_cipher(struct crypt_device *cd, const EVP_CIPHER int r; assert(cd); + assert(ret); /* Let's find the right OpenSSL EVP_CIPHER object that matches the encryption settings of the LUKS * device */ @@ -857,6 +858,7 @@ static int luks_validate_home_record( assert(cd); assert(h); + assert(ret_luks_home_record); for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token++) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *rr = NULL; @@ -1918,6 +1920,7 @@ static int make_partition_table( assert(label); assert(ret_offset); assert(ret_size); + assert(ret_disk_uuid); t = fdisk_new_parttype(); if (!t) @@ -2786,6 +2789,7 @@ static int prepare_resize_partition( assert(fd >= 0); assert(ret_disk_uuid); assert(ret_table); + assert(ret_partition); assert((partition_offset & 511) == 0); assert((old_partition_size & 511) == 0); diff --git a/src/home/homework.c b/src/home/homework.c index 2efd3ddb608fa..a6e7d3751a1cc 100644 --- a/src/home/homework.c +++ b/src/home/homework.c @@ -915,6 +915,7 @@ static int home_activate(UserRecord *h, UserRecord **ret_home) { int r; assert(h); + assert(ret_home); if (!h->user_name) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks user name, refusing."); diff --git a/src/home/user-record-util.c b/src/home/user-record-util.c index 2563a53234de9..b27d993922a60 100644 --- a/src/home/user-record-util.c +++ b/src/home/user-record-util.c @@ -197,6 +197,7 @@ int user_record_reconcile( assert(host); assert(embedded); + assert(ret); /* Make sure both records are initialized */ if (!host->json || !embedded->json) diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index 49462c6a65d89..60b48112449cf 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -398,6 +398,8 @@ static int get_hardware_sku(Context *c, char **ret) { _cleanup_free_ char *model = NULL, *sku = NULL; int r; + assert(ret); + r = get_dmi_property(c, "ID_SKU", &sku); if (r < 0) return r; @@ -419,6 +421,8 @@ static int get_hardware_version(Context *c, char **ret) { _cleanup_free_ char *version = NULL; int r; + assert(ret); + r = get_dmi_property(c, "ID_HARDWARE_VERSION", &version); if (r < 0) return r; diff --git a/src/import/curl-util.c b/src/import/curl-util.c index bddc93d52b80d..48842a20b700b 100644 --- a/src/import/curl-util.c +++ b/src/import/curl-util.c @@ -207,6 +207,8 @@ int curl_glue_new(CurlGlue **glue, sd_event *event) { _cleanup_(sd_event_unrefp) sd_event *e = NULL; int r; + assert(glue); + if (event) e = sd_event_ref(event); else { diff --git a/src/import/oci-util.c b/src/import/oci-util.c index 96f7729fb6393..d8c16bc3d9785 100644 --- a/src/import/oci-util.c +++ b/src/import/oci-util.c @@ -153,7 +153,8 @@ int oci_ref_normalize(char **protocol, char **registry, char **image, char **tag assert(protocol); assert(registry); - assert(image && *image); + assert(image); + assert(*image); assert(tag); /* OCI container reference are supposed to have the form /:. Except that it's @@ -379,6 +380,7 @@ static const char *const go_arch_table[_ARCHITECTURE_MAX] = { DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(go_arch, Architecture); char* urlescape(const char *s) { + POINTER_MAY_BE_NULL(s); size_t l = strlen_ptr(s); _cleanup_free_ char *t = new(char, l * 3 + 1); diff --git a/src/import/pull-common.c b/src/import/pull-common.c index b234331945a9b..ac921addb28aa 100644 --- a/src/import/pull-common.c +++ b/src/import/pull-common.c @@ -678,6 +678,7 @@ int pull_job_restart_with_signature(PullJob *j, char **ret) { int r; assert(j); + assert(ret); /* Generic implementation of a PullJobNotFound handler, that restarts the job requesting a different * signature file. After the initial file, additional *.sha256.gpg, SHA256SUMS.gpg and SHA256SUMS.asc diff --git a/src/import/pull.c b/src/import/pull.c index 270f70396cab6..648fa07c37c5a 100644 --- a/src/import/pull.c +++ b/src/import/pull.c @@ -46,6 +46,8 @@ static int normalize_local(const char *local, const char *url, char **ret) { _cleanup_free_ char *ll = NULL; int r; + assert(ret); + if (arg_import_flags & IMPORT_DIRECT) { if (!local) diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index c140c406cb6b6..7cb884622490e 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -815,6 +815,8 @@ static int get_virtualization(char **v) { char *b = NULL; int r; + assert(v); + r = sd_bus_default_system(&bus); if (r < 0) return r; diff --git a/src/journal/journald-manager.c b/src/journal/journald-manager.c index 3abc0d3869a62..764e5091ab3fd 100644 --- a/src/journal/journald-manager.c +++ b/src/journal/journald-manager.c @@ -463,7 +463,9 @@ static int manager_find_user_journal(Manager *m, uid_t uid, JournalFile **ret) { _cleanup_free_ char *p = NULL; int r; + assert(m); assert(!uid_for_system_journal(uid)); + assert(ret); f = ordered_hashmap_get(m->user_journals, UID_TO_PTR(uid)); if (f) diff --git a/src/kernel-install/kernel-install.c b/src/kernel-install/kernel-install.c index 740791bba3562..8c0abba4207ad 100644 --- a/src/kernel-install/kernel-install.c +++ b/src/kernel-install/kernel-install.c @@ -1128,6 +1128,7 @@ static int kernel_from_version(const char *version, char **ret_kernel) { int r; assert(version); + assert(ret_kernel); vmlinuz = path_join("/usr/lib/modules/", version, "/vmlinuz"); if (!vmlinuz) diff --git a/src/libsystemd/sd-bus/bus-message.c b/src/libsystemd/sd-bus/bus-message.c index 358fb3ca756dd..041dc4821655d 100644 --- a/src/libsystemd/sd-bus/bus-message.c +++ b/src/libsystemd/sd-bus/bus-message.c @@ -418,6 +418,8 @@ int bus_message_from_malloc( size_t sz; int r; + assert(ret); + r = message_from_header( bus, buffer, length, diff --git a/src/libsystemd/sd-bus/test-bus-benchmark.c b/src/libsystemd/sd-bus/test-bus-benchmark.c index 4063d79159a55..f35056aa8e44e 100644 --- a/src/libsystemd/sd-bus/test-bus-benchmark.c +++ b/src/libsystemd/sd-bus/test-bus-benchmark.c @@ -27,6 +27,8 @@ typedef enum Type { static void server(sd_bus *b, size_t *result) { int r; + assert(result); + for (;;) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; diff --git a/src/libsystemd/sd-device/test-sd-device-monitor.c b/src/libsystemd/sd-device/test-sd-device-monitor.c index c1c720b364b08..8d88f3eb6727a 100644 --- a/src/libsystemd/sd-device/test-sd-device-monitor.c +++ b/src/libsystemd/sd-device/test-sd-device-monitor.c @@ -20,6 +20,8 @@ static void prepare_loopback(sd_device **ret) { _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + assert(ret); + ASSERT_OK(sd_device_new_from_syspath(&dev, "/sys/class/net/lo")); ASSERT_OK(device_add_property(dev, "ACTION", "add")); ASSERT_OK(device_add_property(dev, "SEQNUM", "10")); @@ -32,6 +34,8 @@ static int prepare_sda(sd_device **ret) { _cleanup_(sd_device_unrefp) sd_device *dev = NULL; int r; + assert(ret); + r = sd_device_new_from_subsystem_sysname(&dev, "block", "sda"); if (r < 0) return r; @@ -55,6 +59,9 @@ static int monitor_handler(sd_device_monitor *m, sd_device *d, void *userdata) { static void prepare_monitor(sd_device_monitor **ret_server, sd_device_monitor **ret_client, union sockaddr_union *ret_address) { _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor_server = NULL, *monitor_client = NULL; + assert(ret_server); + assert(ret_client); + ASSERT_OK(device_monitor_new_full(&monitor_server, MONITOR_GROUP_NONE, -EBADF)); ASSERT_OK(sd_device_monitor_set_description(monitor_server, "sender")); ASSERT_OK(sd_device_monitor_start(monitor_server, NULL, NULL)); diff --git a/src/libsystemd/sd-device/test-sd-device.c b/src/libsystemd/sd-device/test-sd-device.c index 43b76f46e9baf..df1a6bc600785 100644 --- a/src/libsystemd/sd-device/test-sd-device.c +++ b/src/libsystemd/sd-device/test-sd-device.c @@ -351,6 +351,9 @@ static void test_sd_device_enumerator_filter_subsystem_one( unsigned n_new_dev = 0, n_removed_dev = 0; sd_device *dev; + assert(ret_n_new_dev); + assert(ret_n_removed_dev); + ASSERT_OK(sd_device_enumerator_new(&e)); ASSERT_OK(sd_device_enumerator_add_match_subsystem(e, subsystem, true)); exclude_problematic_devices(e); diff --git a/src/libsystemd/sd-journal/test-journal-interleaving.c b/src/libsystemd/sd-journal/test-journal-interleaving.c index b0d7e80b116bb..5cf65d89ed95d 100644 --- a/src/libsystemd/sd-journal/test-journal-interleaving.c +++ b/src/libsystemd/sd-journal/test-journal-interleaving.c @@ -222,6 +222,8 @@ static void setup_unreferenced_data(void) { static void mkdtemp_chdir_chattr(const char *template, char **ret) { _cleanup_(rm_rf_physical_and_freep) char *path = NULL; + assert(ret); + ASSERT_OK(mkdtemp_malloc(template, &path)); ASSERT_OK_ERRNO(chdir(path)); diff --git a/src/libsystemd/sd-netlink/netlink-message-nfnl.c b/src/libsystemd/sd-netlink/netlink-message-nfnl.c index a485fd096fd61..51983e515e3a3 100644 --- a/src/libsystemd/sd-netlink/netlink-message-nfnl.c +++ b/src/libsystemd/sd-netlink/netlink-message-nfnl.c @@ -242,6 +242,8 @@ int sd_nfnl_nft_message_new_basechain( _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; int r; + assert(ret); + r = sd_nfnl_message_new(nfnl, &m, nfproto, NFNL_SUBSYS_NFTABLES, NFT_MSG_NEWCHAIN, NLM_F_CREATE); if (r < 0) return r; @@ -287,6 +289,8 @@ int sd_nfnl_nft_message_new_table( _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; int r; + assert(ret); + r = sd_nfnl_message_new(nfnl, &m, nfproto, NFNL_SUBSYS_NFTABLES, NFT_MSG_NEWTABLE, NLM_F_CREATE | NLM_F_EXCL); if (r < 0) return r; @@ -309,6 +313,8 @@ int sd_nfnl_nft_message_new_rule( _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; int r; + assert(ret); + r = sd_nfnl_message_new(nfnl, &m, nfproto, NFNL_SUBSYS_NFTABLES, NFT_MSG_NEWRULE, NLM_F_CREATE); if (r < 0) return r; @@ -337,6 +343,8 @@ int sd_nfnl_nft_message_new_set( _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; int r; + assert(ret); + r = sd_nfnl_message_new(nfnl, &m, nfproto, NFNL_SUBSYS_NFTABLES, NFT_MSG_NEWSET, NLM_F_CREATE); if (r < 0) return r; @@ -372,6 +380,8 @@ int sd_nfnl_nft_message_new_setelems( _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; int r; + assert(ret); + if (add) r = sd_nfnl_message_new(nfnl, &m, nfproto, NFNL_SUBSYS_NFTABLES, NFT_MSG_NEWSETELEM, NLM_F_CREATE); else diff --git a/src/libsystemd/sd-netlink/test-netlink.c b/src/libsystemd/sd-netlink/test-netlink.c index f127de7705c22..e0154b7ef7755 100644 --- a/src/libsystemd/sd-netlink/test-netlink.c +++ b/src/libsystemd/sd-netlink/test-netlink.c @@ -608,6 +608,8 @@ static void remove_dummy_interfacep(int *ifindex) { _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + POINTER_MAY_BE_NULL(ifindex); + if (!ifindex || *ifindex <= 0) return; diff --git a/src/libsystemd/sd-varlink/sd-varlink-idl.c b/src/libsystemd/sd-varlink/sd-varlink-idl.c index dc09080cdabf3..3811b5d03b7e2 100644 --- a/src/libsystemd/sd-varlink/sd-varlink-idl.c +++ b/src/libsystemd/sd-varlink/sd-varlink-idl.c @@ -840,6 +840,7 @@ static int varlink_idl_subparse_field_type( assert(p); assert(*p); assert(line); + assert(column); assert(field); r = varlink_idl_subparse_whitespace(p, line, column); @@ -1171,6 +1172,9 @@ _public_ int sd_varlink_idl_parse( _cleanup_(sd_varlink_interface_freep) sd_varlink_interface *interface = NULL; _cleanup_(varlink_symbol_freep) sd_varlink_symbol *symbol = NULL; + + assert_return(ret, -EINVAL); + enum { STATE_PRE_INTERFACE, STATE_INTERFACE, diff --git a/src/libsystemd/sd-varlink/varlink-util.c b/src/libsystemd/sd-varlink/varlink-util.c index 83921c57e0a15..8b61627c562c9 100644 --- a/src/libsystemd/sd-varlink/varlink-util.c +++ b/src/libsystemd/sd-varlink/varlink-util.c @@ -175,6 +175,8 @@ int varlink_server_new( _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; int r; + assert(ret); + r = sd_varlink_server_new(&s, flags|SD_VARLINK_SERVER_FD_PASSING_INPUT_STRICT); if (r < 0) return log_debug_errno(r, "Failed to allocate varlink server object: %m"); diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 98c651896d281..bef9f51aef834 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -90,6 +90,7 @@ static int get_sender_session( int r; assert(m); + assert(ret); /* Acquire the sender's session. This first checks if the sending process is inside a session itself, * and returns that. If not and 'consult_display' is true, this returns the display session of the @@ -165,6 +166,8 @@ static int get_sender_user(Manager *m, sd_bus_message *message, sd_bus_error *er User *user; int r; + assert(ret); + /* Note that we get the owner UID of the session, not the actual client UID here! */ r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_OWNER_UID|SD_BUS_CREDS_AUGMENT, &creds); if (r < 0) diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 6c684149cb957..d31cfcbbe5481 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -193,6 +193,7 @@ static int call_get_addresses( assert(name); assert(prefix); assert(prefix2); + assert(ret); r = bus_call_method(bus, bus_machine_mgr, "GetMachineAddresses", NULL, &reply, "s", name); if (r < 0) diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index 4e39594a44dfe..f5315b9763083 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -257,6 +257,8 @@ static int machine_add_from_params( assert(message); assert(name); assert(c == _MACHINE_CLASS_INVALID || MACHINE_CLASS_CAN_REGISTER(c)); + assert(leader_pidref); + assert(supervisor_pidref); assert(ret); if (leader_pidref->pid == 1) diff --git a/src/measure/measure-tool.c b/src/measure/measure-tool.c index d632bb62f5547..6a2e2994f9fea 100644 --- a/src/measure/measure-tool.c +++ b/src/measure/measure-tool.c @@ -639,6 +639,8 @@ static int pcr_states_allocate(PcrState **ret) { _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL; size_t n = 0; + assert(ret); + pcr_states = new0(PcrState, strv_length(arg_banks) + 1); if (!pcr_states) return log_oom(); diff --git a/src/mountfsd/mountfsd-manager.c b/src/mountfsd/mountfsd-manager.c index b4c5655c51d15..993dda950108e 100644 --- a/src/mountfsd/mountfsd-manager.c +++ b/src/mountfsd/mountfsd-manager.c @@ -69,6 +69,8 @@ int manager_new(Manager **ret) { _cleanup_(manager_freep) Manager *m = NULL; int r; + assert(ret); + m = new(Manager, 1); if (!m) return -ENOMEM; diff --git a/src/network/netdev/fou-tunnel.c b/src/network/netdev/fou-tunnel.c index 786ac9e0dd4c6..e2c5525266039 100644 --- a/src/network/netdev/fou-tunnel.c +++ b/src/network/netdev/fou-tunnel.c @@ -86,6 +86,7 @@ static int netdev_create_fou_tunnel_message(NetDev *netdev, sd_netlink_message * assert(netdev); assert(netdev->manager); + assert(ret); r = sd_genl_message_new(netdev->manager->genl, FOU_GENL_NAME, FOU_CMD_ADD, &m); if (r < 0) diff --git a/src/network/netdev/l2tp-tunnel.c b/src/network/netdev/l2tp-tunnel.c index 1afe109c1b108..fad6f20a4295d 100644 --- a/src/network/netdev/l2tp-tunnel.c +++ b/src/network/netdev/l2tp-tunnel.c @@ -104,6 +104,7 @@ static int netdev_l2tp_create_message_tunnel(NetDev *netdev, union in_addr_union assert(local_address); assert(netdev); assert(netdev->manager); + assert(ret); _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; uint16_t encap_type; @@ -200,6 +201,7 @@ static int netdev_l2tp_create_message_session(NetDev *netdev, L2tpSession *sessi assert(netdev->manager); assert(session); assert(session->tunnel); + assert(ret); r = sd_genl_message_new(netdev->manager->genl, L2TP_GENL_NAME, L2TP_CMD_SESSION_CREATE, &m); if (r < 0) diff --git a/src/network/netdev/macsec.c b/src/network/netdev/macsec.c index 9f3ddcc2b1937..1d6aee249b911 100644 --- a/src/network/netdev/macsec.c +++ b/src/network/netdev/macsec.c @@ -239,6 +239,7 @@ static int netdev_macsec_create_message(NetDev *netdev, int command, sd_netlink_ assert(netdev); assert(netdev->ifindex > 0); assert(netdev->manager); + assert(ret); r = sd_genl_message_new(netdev->manager->genl, MACSEC_GENL_NAME, command, &m); if (r < 0) diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 3d2bf95d9930e..01eef634f433b 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -173,6 +173,8 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( int address_new(Address **ret) { _cleanup_(address_unrefp) Address *address = NULL; + assert(ret); + address = new(Address, 1); if (!address) return -ENOMEM; diff --git a/src/network/networkd-dhcp-server.c b/src/network/networkd-dhcp-server.c index 23ee3024e902a..c88488258f002 100644 --- a/src/network/networkd-dhcp-server.c +++ b/src/network/networkd-dhcp-server.c @@ -456,6 +456,9 @@ static int dhcp4_server_parse_dns_server_string_and_warn( struct in_addr **addresses, size_t *n_addresses) { + assert(addresses); + assert(n_addresses); + for (;;) { _cleanup_free_ char *word = NULL, *server_name = NULL; union in_addr_union address; diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 26ab7ceb52944..c230a86587464 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -202,6 +202,10 @@ static int dhcp6_request_address( Address *existing; int r; + assert(link); + assert(server_address); + assert(ip6_addr); + r = address_new(&addr); if (r < 0) return log_oom(); diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 66366fe60d38c..f43709da356c9 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -677,6 +677,8 @@ static int persistent_storage_open(void) { int manager_new(Manager **ret, bool test_mode) { _cleanup_(manager_freep) Manager *m = NULL; + assert(ret); + m = new(Manager, 1); if (!m) return -ENOMEM; diff --git a/src/network/networkd-nexthop.c b/src/network/networkd-nexthop.c index 0e453e159748a..9a32a17050d05 100644 --- a/src/network/networkd-nexthop.c +++ b/src/network/networkd-nexthop.c @@ -131,6 +131,8 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( int nexthop_new(NextHop **ret) { _cleanup_(nexthop_unrefp) NextHop *nexthop = NULL; + assert(ret); + nexthop = new(NextHop, 1); if (!nexthop) return -ENOMEM; diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index 593832df51aa8..81cfe84add80a 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -245,6 +245,8 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( int route_new(Route **ret) { _cleanup_(route_unrefp) Route *route = NULL; + assert(ret); + route = new(Route, 1); if (!route) return -ENOMEM; diff --git a/src/network/tc/qdisc.c b/src/network/tc/qdisc.c index e16ace841e3ae..e0d383763f4a3 100644 --- a/src/network/tc/qdisc.c +++ b/src/network/tc/qdisc.c @@ -115,6 +115,8 @@ static int qdisc_new(QDiscKind kind, QDisc **ret) { _cleanup_(qdisc_unrefp) QDisc *qdisc = NULL; int r; + assert(ret); + if (kind == _QDISC_KIND_INVALID) { qdisc = new(QDisc, 1); if (!qdisc) diff --git a/src/network/tc/tclass.c b/src/network/tc/tclass.c index 3b53a59a1b686..5d7664866d4c5 100644 --- a/src/network/tc/tclass.c +++ b/src/network/tc/tclass.c @@ -77,6 +77,8 @@ static int tclass_new(TClassKind kind, TClass **ret) { _cleanup_(tclass_unrefp) TClass *tclass = NULL; int r; + assert(ret); + if (kind == _TCLASS_KIND_INVALID) { tclass = new(TClass, 1); if (!tclass) diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c index 1ee01238f31ef..a75582e2b4d6e 100644 --- a/src/nspawn/nspawn-mount.c +++ b/src/nspawn/nspawn-mount.c @@ -757,12 +757,13 @@ int mount_all(const char *dest, } static int parse_mount_bind_options(const char *options, unsigned long *open_tree_flags, char **mount_opts, RemountIdmapping *idmapping) { - unsigned long flags = *open_tree_flags; + unsigned long flags = *ASSERT_PTR(open_tree_flags); char *opts = NULL; - RemountIdmapping new_idmapping = *idmapping; + RemountIdmapping new_idmapping = *ASSERT_PTR(idmapping); int r; assert(options); + assert(mount_opts); for (;;) { _cleanup_free_ char *word = NULL; diff --git a/src/nspawn/nspawn-network.c b/src/nspawn/nspawn-network.c index c7e5c417cab4b..6949ecac724a9 100644 --- a/src/nspawn/nspawn-network.c +++ b/src/nspawn/nspawn-network.c @@ -177,6 +177,7 @@ int setup_veth(const char *machine_name, assert(machine_name); assert(pidref_is_set(pid)); assert(iface_name); + assert(provided_mac); /* Use two different interface name prefixes depending whether * we are in bridge mode or not. */ diff --git a/src/nspawn/nspawn-oci.c b/src/nspawn/nspawn-oci.c index 56e1f4db75fb7..5cbc58969f186 100644 --- a/src/nspawn/nspawn-oci.c +++ b/src/nspawn/nspawn-oci.c @@ -2082,6 +2082,7 @@ int oci_load(FILE *f, const char *bundle, Settings **ret) { int r; assert_se(bundle); + assert(ret); path = strjoina(bundle, "/config.json"); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index accf448ea97f2..68faa12c05f57 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -445,6 +445,8 @@ static int parse_capability_spec(const char *spec, uint64_t *ret_mask) { uint64_t mask = 0; int r; + assert(ret_mask); + for (;;) { _cleanup_free_ char *t = NULL; diff --git a/src/nsresourced/nsresourced-manager.c b/src/nsresourced/nsresourced-manager.c index 664cb2d1a2ad1..3102563617e1c 100644 --- a/src/nsresourced/nsresourced-manager.c +++ b/src/nsresourced/nsresourced-manager.c @@ -89,6 +89,8 @@ int manager_new(Manager **ret) { _cleanup_(manager_freep) Manager *m = NULL; int r; + assert(ret); + m = new(Manager, 1); if (!m) return -ENOMEM; diff --git a/src/nss-resolve/nss-resolve.c b/src/nss-resolve/nss-resolve.c index ea60727e906d9..2bcfca71e5bd8 100644 --- a/src/nss-resolve/nss-resolve.c +++ b/src/nss-resolve/nss-resolve.c @@ -50,6 +50,8 @@ static int connect_to_resolved(sd_varlink **ret) { _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; int r; + assert(ret); + r = sd_varlink_connect_address(&link, "/run/systemd/resolve/io.systemd.Resolve"); if (r < 0) return r; diff --git a/src/pcrextend/pcrextend.c b/src/pcrextend/pcrextend.c index 2a087ec8f89df..e35f579e63738 100644 --- a/src/pcrextend/pcrextend.c +++ b/src/pcrextend/pcrextend.c @@ -261,6 +261,7 @@ static int escape_and_truncate_data(const void *data, size_t size, char **ret) { _cleanup_free_ char *safe = NULL; assert(data || size == 0); + assert(ret); if (size > EXTENSION_STRING_SAFE_LIMIT) { safe = cescape_length(data, EXTENSION_STRING_SAFE_LIMIT); diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index 67303d032b019..a6ec5c23d03a2 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -2456,6 +2456,8 @@ static int event_log_load_and_process(EventLog **ret) { _cleanup_(event_log_freep) EventLog *el = NULL; int r; + assert(ret); + el = event_log_new(); if (!el) return log_oom(); @@ -3613,6 +3615,7 @@ static int pcrlock_file_system_path(const char *normalized_path, char **ret) { _cleanup_free_ char *s = NULL; assert(normalized_path); + assert(ret); if (path_equal(normalized_path, "/")) s = strdup(PCRLOCK_ROOT_FILE_SYSTEM_PATH); diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index 7c2cbf3e52b3a..2c555ee359877 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -132,6 +132,8 @@ static int extract_prefix(const char *path, char **ret) { size_t m; int r; + assert(ret); + r = path_extract_filename(path, &bn); if (r < 0) return r; diff --git a/src/repart/repart.c b/src/repart/repart.c index e9cd0d85699a7..10a9a949b5f4c 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -2691,6 +2691,8 @@ static int parse_key_file(const char *filename, struct iovec *key) { size_t n = 0; int r; + assert(key); + r = read_full_file_full( AT_FDCWD, filename, /* offset= */ UINT64_MAX, @@ -4383,6 +4385,8 @@ static int partition_hint(const Partition *p, const char *node, char **ret) { const char *label; sd_id128_t id; + assert(ret); + /* Tries really hard to find a suitable description for this partition */ if (p->definition_path) diff --git a/src/report/report-cgroup.c b/src/report/report-cgroup.c index 476b074ac05bf..c3dabe41b1016 100644 --- a/src/report/report-cgroup.c +++ b/src/report/report-cgroup.c @@ -284,6 +284,9 @@ static int io_stat_parse(const char *cgroup_path, uint64_t *ret_rbytes, uint64_t uint64_t rbytes = 0, rios = 0; int r; + assert(ret_rbytes); + assert(ret_rios); + r = cg_get_path(cgroup_path, "io.stat", &path); if (r < 0) return r; diff --git a/src/report/report.c b/src/report/report.c index ca169c94a8f07..74366cffdff77 100644 --- a/src/report/report.c +++ b/src/report/report.c @@ -337,6 +337,7 @@ static int output_collected_list(Context *context, Table **ret) { int r; assert(context); + assert(ret); _cleanup_(table_unrefp) Table *table = table_new("family", "object", "fields", "value"); if (!table) @@ -391,6 +392,7 @@ static int output_collected_describe(Context *context, Table **ret) { int r; assert(context); + assert(ret); _cleanup_(table_unrefp) Table *table = table_new("family", "type", "description"); if (!table) @@ -442,6 +444,7 @@ static int facts_output_list(Context *context, Table **ret) { int r; assert(context); + assert(ret); _cleanup_(table_unrefp) Table *table = table_new("family", "object", "value"); if (!table) @@ -493,6 +496,7 @@ static int facts_output_describe(Context *context, Table **ret) { int r; assert(context); + assert(ret); _cleanup_(table_unrefp) Table *table = table_new("family", "description"); if (!table) diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index ff4df7b78ad40..1a634810beafe 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -2057,6 +2057,8 @@ static int dnssec_test_positive_wildcard_nsec( bool authenticated = true; int r; + assert(_authenticated); + /* Run a positive NSEC wildcard proof. Specifically: * * A proof that there's neither a wildcard name nor a non-wildcard name that is a suffix of the name "name" and diff --git a/src/resolve/resolved-dns-synthesize.c b/src/resolve/resolved-dns-synthesize.c index 8ce9204e0c6e4..52da6068cd023 100644 --- a/src/resolve/resolved-dns-synthesize.c +++ b/src/resolve/resolved-dns-synthesize.c @@ -98,6 +98,8 @@ static int synthesize_localhost_rr(Manager *m, const DnsResourceKey *key, DnsAns static int answer_add_ptr(DnsAnswer **answer, const char *from, const char *to, int ifindex, DnsAnswerFlags flags) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + assert(answer); + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR, from); if (!rr) return -ENOMEM; diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index a320825d0d5a3..a14aa0de7dbf6 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -3300,7 +3300,9 @@ static int dnssec_validate_records( DnsResourceRecord *rr; int r; + assert(have_nsec); assert(nvalidations); + assert(validated); /* Returns negative on error, 0 if validation failed, 1 to restart validation, 2 when finished. */ diff --git a/src/resolve/resolved-static-records.c b/src/resolve/resolved-static-records.c index d4d284d4f7a2a..efcd91c0907d5 100644 --- a/src/resolve/resolved-static-records.c +++ b/src/resolve/resolved-static-records.c @@ -50,6 +50,8 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( static int load_static_record_file_item(sd_json_variant *rj, Hashmap **records) { int r; + assert(records); + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; r = dns_resource_record_from_json(rj, &rr); if (r < 0) diff --git a/src/resolve/resolved-varlink.c b/src/resolve/resolved-varlink.c index bb0100b04688d..2ac4ee9555d01 100644 --- a/src/resolve/resolved-varlink.c +++ b/src/resolve/resolved-varlink.c @@ -211,6 +211,9 @@ static int find_addr_records( DnsResourceRecord *rr; int ifindex, r; + assert(q); + POINTER_MAY_BE_NULL(canonical); + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *entry = NULL; int family; diff --git a/src/resolve/test-dnssec-complex.c b/src/resolve/test-dnssec-complex.c index f486cf09a32ad..8b4e376284c16 100644 --- a/src/resolve/test-dnssec-complex.c +++ b/src/resolve/test-dnssec-complex.c @@ -18,6 +18,8 @@ static void prefix_random(const char *name, char **ret) { uint64_t i, u; char *m = NULL; + assert(ret); + u = 1 + (random_u64() & 3); for (i = 0; i < u; i++) { diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index 36eb2e7086e81..9bd91f988d50b 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -1056,6 +1056,7 @@ static int pe_find_addon_sections( assert(fd >= 0); assert(path); + assert(ret_cmdline); r = pe_load_headers_and_sections(fd, path, §ions, &pe_header); if (r < 0) diff --git a/src/shared/bpf-program.c b/src/shared/bpf-program.c index 55b0fc284de8f..3cef280dc50d7 100644 --- a/src/shared/bpf-program.c +++ b/src/shared/bpf-program.c @@ -152,6 +152,8 @@ int bpf_program_new(uint32_t prog_type, const char *prog_name, BPFProgram **ret) _cleanup_(bpf_program_freep) BPFProgram *p = NULL; _cleanup_free_ char *name = NULL; + assert(ret); + if (prog_name) { if (strlen(prog_name) >= BPF_OBJ_NAME_LEN) return -ENAMETOOLONG; diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 1a6bc7370f81e..440c6ced290ea 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -1175,6 +1175,8 @@ static int bus_append_import_credential(sd_bus_message *m, const char *field, co static int bus_append_refresh_on_reload(sd_bus_message *m, const char *field, const char *eq) { int r; + assert(eq); + r = sd_bus_message_open_container(m, 'r', "sv"); if (r < 0) return bus_log_create_error(r); diff --git a/src/shared/calendarspec.c b/src/shared/calendarspec.c index 0f2d0c718c991..771363517b39e 100644 --- a/src/shared/calendarspec.c +++ b/src/shared/calendarspec.c @@ -1285,6 +1285,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) { assert(spec); assert(tm); + assert(usec); c = *tm; tm_usec = *usec; diff --git a/src/shared/cgroup-show.c b/src/shared/cgroup-show.c index cfc80d666f389..896dd58219178 100644 --- a/src/shared/cgroup-show.c +++ b/src/shared/cgroup-show.c @@ -413,6 +413,8 @@ int show_cgroup_get_path_and_warn( _cleanup_free_ char *root = NULL; int r; + assert(ret); + if (machine) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_free_ char *unit = NULL; diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c index 690bb70ed54c9..b448032939d53 100644 --- a/src/shared/conf-parser.c +++ b/src/shared/conf-parser.c @@ -189,6 +189,9 @@ static int parse_line( assert(line > 0); assert(lookup); assert(l); + assert(section); + assert(section_line); + assert(section_ignored); l = strstrip(l); if (isempty(l)) diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index fc58b4af30115..3ff214a09a361 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -275,6 +275,8 @@ int read_credential_strings_many_internal( bool all = true; int r, ret = 0; + assert(first_value); + /* Reads a bunch of credentials into the specified buffers. If the specified buffers are already * non-NULL frees them if a credential is found. Only supports string-based credentials * (i.e. refuses embedded NUL bytes). @@ -338,6 +340,9 @@ int get_credential_user_password(const char *username, char **ret_password, bool _cleanup_free_ char *cn = NULL; int r; + assert(ret_password); + assert(ret_is_hashed); + /* Try to pick up the password for this account via the credentials logic */ cn = strjoin("passwd.hashed-password.", username); if (!cn) diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c index 2ffd1b63bb2d5..ba7910d864cc0 100644 --- a/src/shared/cryptsetup-util.c +++ b/src/shared/cryptsetup-util.c @@ -214,6 +214,8 @@ int cryptsetup_get_volume_key_prefix( const char *uuid; char *s; + assert(ret); + uuid = sym_crypt_get_uuid(cd); if (!uuid) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get LUKS UUID."); @@ -248,6 +250,8 @@ int cryptsetup_get_volume_key_id( char *hex; int r; + assert(ret); + r = cryptsetup_get_volume_key_prefix(cd, volume_name, &prefix); if (r < 0) return log_debug_errno(r, "Failed to get LUKS volume key prefix."); diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 9817ed87ec129..c032a571d6cc3 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -942,6 +942,7 @@ static int dissect_image_from_unpartitioned( assert(devname); assert(m); assert(fstype); + POINTER_MAY_BE_NULL(mount_node_fd); if (!image_filter_test(filter, PARTITION_ROOT, /* label= */ NULL)) /* do a filter check with an empty partition label */ return -ECOMM; diff --git a/src/shared/dns-answer.c b/src/shared/dns-answer.c index bfc6aef6f55a5..24174d2bd2c07 100644 --- a/src/shared/dns-answer.c +++ b/src/shared/dns-answer.c @@ -574,6 +574,8 @@ int dns_answer_remove_by_answer_keys(DnsAnswer **a, DnsAnswer *b) { DnsAnswerItem *item; int r; + assert(a); + /* Removes all items from '*a' that have a matching key in 'b' */ DNS_ANSWER_FOREACH_ITEM(item, b) { diff --git a/src/shared/dns-packet.c b/src/shared/dns-packet.c index f8078c1211206..f250f40edff9f 100644 --- a/src/shared/dns-packet.c +++ b/src/shared/dns-packet.c @@ -1519,6 +1519,7 @@ int dns_packet_read_string(DnsPacket *p, char **ret, size_t *start) { int r; assert(p); + assert(ret); r = dns_packet_read_uint8(p, &c, NULL); if (r < 0) @@ -2448,6 +2449,8 @@ static int dns_packet_extract_question(DnsPacket *p, DnsQuestion **ret_question) unsigned n; int r; + assert(ret_question); + n = DNS_PACKET_QDCOUNT(p); if (n > 0) { question = dns_question_new(n); @@ -2501,6 +2504,8 @@ static int dns_packet_extract_answer(DnsPacket *p, DnsAnswer **ret_answer) { bool bad_opt = false; int r; + assert(ret_answer); + n = DNS_PACKET_RRCOUNT(p); if (n == 0) return 0; diff --git a/src/shared/dns-rr.c b/src/shared/dns-rr.c index e807cef3638df..0ad01de88a684 100644 --- a/src/shared/dns-rr.c +++ b/src/shared/dns-rr.c @@ -2012,6 +2012,7 @@ int dns_resource_record_get_cname_target(DnsResourceKey *key, DnsResourceRecord assert(key); assert(cname); + assert(ret); /* Checks if the RR `cname` is a CNAME/DNAME RR that matches the specified `key`. If so, returns the * target domain. If not, returns -EUNATCH */ diff --git a/src/shared/extension-util.c b/src/shared/extension-util.c index f2361b3fd1d39..1f99f46e8dfdd 100644 --- a/src/shared/extension-util.c +++ b/src/shared/extension-util.c @@ -140,6 +140,7 @@ int parse_env_extension_hierarchies(char ***ret_hierarchies, const char *hierarc _cleanup_free_ char **l = NULL; int r; + assert(ret_hierarchies); assert(hierarchy_env); r = getenv_path_list(hierarchy_env, &l); if (r == -ENXIO) { diff --git a/src/shared/fido2-util.c b/src/shared/fido2-util.c index 06c74a4851a2c..50e5147b1f986 100644 --- a/src/shared/fido2-util.c +++ b/src/shared/fido2-util.c @@ -14,6 +14,8 @@ int fido2_generate_salt(struct iovec *ret_salt) { _cleanup_(iovec_done) struct iovec salt = {}; int r; + assert(ret_salt); + r = crypto_random_bytes_allocate_iovec(FIDO2_SALT_SIZE, &salt); if (r < 0) return log_error_errno(r, "Failed to generate FIDO2 salt: %m"); @@ -27,6 +29,8 @@ int fido2_read_salt_file(const char *filename, uint64_t offset, const char *clie _cleanup_free_ char *bind_name = NULL; int r; + assert(ret_salt); + /* If we read the salt via AF_UNIX, make the client recognizable */ if (asprintf(&bind_name, "@%" PRIx64"/%s-fido2-salt/%s", random_u64(), client, node) < 0) return log_oom(); diff --git a/src/shared/hwdb-util.c b/src/shared/hwdb-util.c index 55579c2cf4553..46c3f26a9b807 100644 --- a/src/shared/hwdb-util.c +++ b/src/shared/hwdb-util.c @@ -190,6 +190,8 @@ static int trie_insert(struct trie *trie, struct trie_node *node, const char *se const char *filename, uint16_t file_priority, uint32_t line_number, bool compat) { int r = 0; + assert(node); + for (size_t i = 0;; i++) { size_t p; char c; diff --git a/src/shared/install-printf.c b/src/shared/install-printf.c index ccb73f061b53c..db93e90b27fa2 100644 --- a/src/shared/install-printf.c +++ b/src/shared/install-printf.c @@ -12,6 +12,8 @@ static int specifier_prefix_and_instance(char specifier, const void *data, const _cleanup_free_ char *prefix = NULL; int r; + assert(ret); + r = unit_name_to_prefix_and_instance(i->name, &prefix); if (r < 0) return r; diff --git a/src/shared/install.c b/src/shared/install.c index 149faed711a70..d01906c205d9d 100644 --- a/src/shared/install.c +++ b/src/shared/install.c @@ -1861,6 +1861,8 @@ int unit_file_verify_alias( _cleanup_free_ char *dst_updated = NULL; int r; + assert(ret_dst); + /* Verify that dst is a valid either a valid alias or a valid .wants/.requires symlink for the target * unit *i. Return negative on error or if not compatible, zero on success. * @@ -2903,6 +2905,9 @@ static int do_unit_file_disable( bool has_install_info = false; int r; + assert(changes); + assert(n_changes); + STRV_FOREACH(name, names) { InstallInfo *info; diff --git a/src/shared/libfido2-util.c b/src/shared/libfido2-util.c index 6224ad4b1d2c6..18d020d34c225 100644 --- a/src/shared/libfido2-util.c +++ b/src/shared/libfido2-util.c @@ -1153,6 +1153,12 @@ static int check_device_is_fido2_with_hmac_secret( _cleanup_(fido_dev_free_wrapper) fido_dev_t *d = NULL; int r; + assert(ret_has_rk); + assert(ret_has_client_pin); + assert(ret_has_up); + assert(ret_has_uv); + assert(ret_has_always_uv); + d = sym_fido_dev_new(); if (!d) return log_oom(); diff --git a/src/shared/libmount-util.c b/src/shared/libmount-util.c index be4dd0712ee4c..0d63675667aea 100644 --- a/src/shared/libmount-util.c +++ b/src/shared/libmount-util.c @@ -100,6 +100,8 @@ int libmount_parse_full( /* Older libmount seems to require this. */ assert(!source || path); assert(IN_SET(direction, MNT_ITER_FORWARD, MNT_ITER_BACKWARD)); + assert(ret_table); + assert(ret_iter); r = dlopen_libmount(); if (r < 0) diff --git a/src/shared/machine-bind-user.c b/src/shared/machine-bind-user.c index 278f7c99d0ccc..f65f32ca1948b 100644 --- a/src/shared/machine-bind-user.c +++ b/src/shared/machine-bind-user.c @@ -107,6 +107,8 @@ static int convert_user( assert(u); assert(g); assert(user_record_gid(u) == g->gid); + assert(ret_converted_user); + assert(ret_converted_group); if (shell_copy) shell = u->shell; diff --git a/src/shared/mkfs-util.c b/src/shared/mkfs-util.c index b3e575efd37fd..e45a106ed5e03 100644 --- a/src/shared/mkfs-util.c +++ b/src/shared/mkfs-util.c @@ -74,11 +74,12 @@ static int mangle_linux_fs_label(const char *s, size_t max_len, char **ret) { } static int mangle_fat_label(const char *s, char **ret) { - assert(s); - _cleanup_free_ char *q = NULL; int r; + assert(s); + assert(ret); + r = utf8_to_ascii(s, '_', &q); if (r < 0) return r; diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c index b512fcef949f5..18c6b06b17dc1 100644 --- a/src/shared/openssl-util.c +++ b/src/shared/openssl-util.c @@ -487,6 +487,9 @@ int rsa_encrypt_bytes( _cleanup_free_ void *b = NULL; size_t l; + assert(ret_encrypt_key); + assert(ret_encrypt_key_size); + ctx = EVP_PKEY_CTX_new(pkey, NULL); if (!ctx) return log_openssl_errors("Failed to allocate public key context"); @@ -1097,6 +1100,11 @@ static int ecc_pkey_generate_volume_keys( void **ret_saved_key, size_t *ret_saved_key_size) { + assert(ret_decrypted_key); + assert(ret_decrypted_key_size); + assert(ret_saved_key); + assert(ret_saved_key_size); + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_new = NULL; _cleanup_(erase_and_freep) void *decrypted_key = NULL; _cleanup_free_ unsigned char *saved_key = NULL; @@ -1150,6 +1158,11 @@ static int rsa_pkey_generate_volume_keys( size_t decrypted_key_size, saved_key_size; int r; + assert(ret_decrypted_key); + assert(ret_decrypted_key_size); + assert(ret_saved_key); + assert(ret_saved_key_size); + r = rsa_pkey_to_suitable_key_size(pkey, &decrypted_key_size); if (r < 0) return log_debug_errno(r, "Failed to determine RSA public key size."); @@ -1345,6 +1358,7 @@ static int openssl_load_private_key_from_file(const char *path, EVP_PKEY **ret) } static int openssl_ask_password_ui_new(const AskPasswordRequest *request, OpenSSLAskPasswordUI **ret) { + assert(request); assert(ret); #ifndef OPENSSL_NO_UI_CONSOLE diff --git a/src/shared/pcrextend-util.c b/src/shared/pcrextend-util.c index 7af436217d5eb..6598bf82142c0 100644 --- a/src/shared/pcrextend-util.c +++ b/src/shared/pcrextend-util.c @@ -197,6 +197,7 @@ int pcrextend_verity_word( assert(name); assert(iovec_is_set(root_hash)); + assert(ret); _cleanup_free_ char *name_escaped = xescape(name, ":"); /* Avoid ambiguity around ":" */ if (!name_escaped) diff --git a/src/shared/pkcs11-util.c b/src/shared/pkcs11-util.c index 7cc79fa09ae38..165fefbea1ff8 100644 --- a/src/shared/pkcs11-util.c +++ b/src/shared/pkcs11-util.c @@ -403,6 +403,8 @@ static int read_public_key_info( CK_OBJECT_HANDLE object, EVP_PKEY **ret_pkey) { + assert(ret_pkey); + CK_ATTRIBUTE attribute = { CKA_PUBLIC_KEY_INFO, NULL_PTR, 0 }; _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; CK_RV rv; @@ -445,6 +447,8 @@ int pkcs11_token_read_public_key( CK_RV rv; int r; + assert(ret_pkey); + r = read_public_key_info(m, session, object, &pkey); if (r >= 0) { *ret_pkey = TAKE_PTR(pkey); @@ -668,6 +672,8 @@ int pkcs11_token_read_x509_certificate( X509_NAME *name = NULL; int r; + assert(ret_cert); + r = dlopen_p11kit(); if (r < 0) return r; @@ -951,6 +957,9 @@ static int ecc_convert_to_compressed( CK_RV rv; int r; + assert(ret_compressed_point); + assert(ret_compressed_point_size); + rv = m->C_GetAttributeValue(session, object, &ec_params_attr, 1); if (!IN_SET(rv, CKR_OK, CKR_ATTRIBUTE_TYPE_INVALID)) return log_error_errno(SYNTHETIC_ERRNO(EIO), @@ -1157,6 +1166,9 @@ static int pkcs11_token_decrypt_data_rsa( CK_ULONG dbuffer_size = 0; CK_RV rv; + assert(ret_decrypted_data); + assert(ret_decrypted_data_size); + rv = m->C_DecryptInit(session, (CK_MECHANISM*) &mechanism, object); if (rv != CKR_OK) return log_error_errno(SYNTHETIC_ERRNO(EIO), diff --git a/src/shared/pretty-print.c b/src/shared/pretty-print.c index 76330cbb65d16..7a4bc10eebf2a 100644 --- a/src/shared/pretty-print.c +++ b/src/shared/pretty-print.c @@ -127,6 +127,8 @@ int file_url_from_path(const char *path, char **ret) { char *url = NULL; int r; + assert(ret); + if (uname(&u) < 0) return -errno; @@ -389,6 +391,12 @@ static int guess_type(const char **name, char ***ret_prefixes, bool *ret_is_coll _cleanup_free_ char *n = NULL; bool run = false, coll = false; const char *ext = ".conf"; + + assert(name); + assert(ret_prefixes); + assert(ret_is_collection); + assert(ret_extension); + /* This is static so that the array doesn't get deallocated when we exit the function */ static const char* const std_prefixes[] = { CONF_PATHS(""), NULL }; static const char* const run_prefixes[] = { "/run/", NULL }; diff --git a/src/shared/seccomp-util.c b/src/shared/seccomp-util.c index 9785fc45d78f3..49328ccde2e4c 100644 --- a/src/shared/seccomp-util.c +++ b/src/shared/seccomp-util.c @@ -278,6 +278,8 @@ int seccomp_init_for_arch(scmp_filter_ctx *ret, uint32_t arch, uint32_t default_ _cleanup_(seccomp_releasep) scmp_filter_ctx seccomp = NULL; int r; + assert(ret); + /* Much like seccomp_init(), but initializes the filter for one specific architecture only, without affecting * any others. Also, turns off the NNP fiddling. */ diff --git a/src/shared/snapshot-util.c b/src/shared/snapshot-util.c index 93fd886d7bab1..6e73bef78f1ba 100644 --- a/src/shared/snapshot-util.c +++ b/src/shared/snapshot-util.c @@ -23,6 +23,8 @@ int create_ephemeral_snapshot( _cleanup_free_ char *np = NULL; int r; + assert(ret_new_path); + /* If the specified path is a mount point we generate the new snapshot immediately * inside it under a random name. However if the specified is not a mount point we * create the new snapshot in the parent directory, just next to it. */ diff --git a/src/shared/socket-netlink.c b/src/shared/socket-netlink.c index 1d369353b976b..2ded2c0c0fa0a 100644 --- a/src/shared/socket-netlink.c +++ b/src/shared/socket-netlink.c @@ -372,6 +372,7 @@ int in_addr_full_new( _cleanup_free_ char *name = NULL; struct in_addr_full *x; + assert(a); assert(ret); if (!isempty(server_name)) { diff --git a/src/shared/tar-util.c b/src/shared/tar-util.c index 823fb9ac2b951..3f7dc88a6a9af 100644 --- a/src/shared/tar-util.c +++ b/src/shared/tar-util.c @@ -660,6 +660,8 @@ static int archive_entry_read_stat( int r; assert(entry); + assert(xa); + assert(n_xa); /* Fills in all fields that are present in the archive entry. Doesn't change the fields if the entry * doesn't contain the relevant data */ @@ -1160,6 +1162,7 @@ static int hardlink_lookup( assert(d); assert(inode_fd >= 0); assert(sx); + assert(ret); /* If we know the hardlink count, and it's 1, then don't bother */ if (FLAGS_SET(sx->stx_mask, STATX_NLINK) && sx->stx_nlink == 1) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 077233a317822..5e5ed1f620b39 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -6412,6 +6412,7 @@ int tpm2_seal_data( assert(c); assert(data); assert(primary_handle); + POINTER_MAY_BE_NULL(policy); /* This is a generic version of tpm2_seal(), that doesn't imply any policy or any specific * combination of the two keypairs in their marshalling. tpm2_seal() is somewhat specific to the FDE @@ -6477,6 +6478,7 @@ int tpm2_unseal_data( assert(public_blob); assert(private_blob); assert(primary_handle); + assert(ret_data); TPM2B_PUBLIC public; r = tpm2_unmarshal_public(public_blob->iov_base, public_blob->iov_len, &public); @@ -8320,6 +8322,8 @@ int tpm2_pcrlock_policy_load( _cleanup_fclose_ FILE *f = NULL; int r; + assert(ret_policy); + r = tpm2_pcrlock_search_file(path, &f, &discovered_path); if (r == -ENOENT) { *ret_policy = (Tpm2PCRLockPolicy) {}; diff --git a/src/shared/unit-file.c b/src/shared/unit-file.c index f056b679a9ce4..f0babafa5e24f 100644 --- a/src/shared/unit-file.c +++ b/src/shared/unit-file.c @@ -752,6 +752,8 @@ int unit_file_find_fragment( _cleanup_set_free_ Set *names = NULL; int r; + assert(ret_fragment_path); + /* Finds a fragment path, and returns the set of names: * if we have …/foo.service and …/foo-alias.service→foo.service, * and …/foo@.service and …/foo-alias@.service→foo@.service, diff --git a/src/shared/volatile-util.c b/src/shared/volatile-util.c index eaf53ac4ad18a..de498b7b885c2 100644 --- a/src/shared/volatile-util.c +++ b/src/shared/volatile-util.c @@ -9,6 +9,8 @@ int query_volatile_mode(VolatileMode *ret) { _cleanup_free_ char *mode = NULL; int r; + assert(ret); + r = proc_cmdline_get_key("systemd.volatile", PROC_CMDLINE_VALUE_OPTIONAL, &mode); if (r < 0) return r; diff --git a/src/shared/watchdog.c b/src/shared/watchdog.c index 5b113013950f7..e74b4af9b222a 100644 --- a/src/shared/watchdog.c +++ b/src/shared/watchdog.c @@ -75,6 +75,8 @@ static int watchdog_get_pretimeout_governor(char **ret_gov) { _cleanup_free_ char *sys_fn = NULL; int r; + assert(ret_gov); + r = watchdog_get_sysfs_path("pretimeout_governor", &sys_fn); if (r < 0) return r; diff --git a/src/systemctl/systemctl-log-setting.c b/src/systemctl/systemctl-log-setting.c index 845ed748c23cb..e181af4a97aff 100644 --- a/src/systemctl/systemctl-log-setting.c +++ b/src/systemctl/systemctl-log-setting.c @@ -44,6 +44,8 @@ static int service_name_to_dbus(sd_bus *bus, const char *name, char **ret_dbus_n _cleanup_free_ char *bus_name = NULL; int r; + assert(ret_dbus_name); + /* First, look for the BusName= property */ _cleanup_free_ char *dbus_path = unit_dbus_path_from_name(name); if (!dbus_path) diff --git a/src/sysupdate/sysupdate-partition.c b/src/sysupdate/sysupdate-partition.c index 94a69850af57c..62fd7470cfe78 100644 --- a/src/sysupdate/sysupdate-partition.c +++ b/src/sysupdate/sysupdate-partition.c @@ -158,6 +158,7 @@ int find_suitable_partition( int r; assert(device); + POINTER_MAY_BE_NULL(partition_type); assert(ret); r = fdisk_new_context_at(AT_FDCWD, device, /* read_only= */ true, /* sector_size= */ UINT32_MAX, &c); diff --git a/src/sysupdate/sysupdate-resource.c b/src/sysupdate/sysupdate-resource.c index ba1842b2d6c8e..b819fcd5b8586 100644 --- a/src/sysupdate/sysupdate-resource.c +++ b/src/sysupdate/sysupdate-resource.c @@ -477,6 +477,7 @@ static int resource_load_from_web( int r; assert(rr); + POINTER_MAY_BE_NULL(web_cache); ci = web_cache ? web_cache_get_item(*web_cache, rr->path, verify) : NULL; if (ci) { diff --git a/src/sysupdate/sysupdate-transfer.c b/src/sysupdate/sysupdate-transfer.c index 35c0e05083bdf..3ed7a2ae3a488 100644 --- a/src/sysupdate/sysupdate-transfer.c +++ b/src/sysupdate/sysupdate-transfer.c @@ -1009,6 +1009,7 @@ static int callout_context_new(const Transfer *t, const Instance *i, TransferPro assert(t); assert(i); assert(cb); + assert(ret); ctx = new(CalloutContext, 1); if (!ctx) diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index 015043a0a4dcd..1f79e2face5e4 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -480,6 +480,8 @@ static int write_temporary_passwd( int r; assert(c); + assert(ret_tmpfile); + assert(ret_tmpfile_path); if (ordered_hashmap_isempty(c->todo_uids)) goto done; @@ -611,6 +613,8 @@ static int write_temporary_shadow( int r; assert(c); + assert(ret_tmpfile); + assert(ret_tmpfile_path); if (ordered_hashmap_isempty(c->todo_uids)) goto done; @@ -746,6 +750,8 @@ static int write_temporary_group( int r; assert(c); + assert(ret_tmpfile); + assert(ret_tmpfile_path); if (ordered_hashmap_isempty(c->todo_gids) && ordered_hashmap_isempty(c->members)) goto done; @@ -863,6 +869,8 @@ static int write_temporary_gshadow( int r; assert(c); + assert(ret_tmpfile); + assert(ret_tmpfile_path); if (ordered_hashmap_isempty(c->todo_gids) && ordered_hashmap_isempty(c->members)) goto done; diff --git a/src/tmpfiles/offline-passwd.c b/src/tmpfiles/offline-passwd.c index 2334e258cb404..75e9085c26bf0 100644 --- a/src/tmpfiles/offline-passwd.c +++ b/src/tmpfiles/offline-passwd.c @@ -44,6 +44,8 @@ static int populate_uid_cache(const char *root, Hashmap **ret) { _cleanup_hashmap_free_ Hashmap *cache = NULL; int r; + assert(ret); + cache = hashmap_new(&uid_gid_hash_ops); if (!cache) return -ENOMEM; @@ -85,6 +87,8 @@ static int populate_gid_cache(const char *root, Hashmap **ret) { _cleanup_hashmap_free_ Hashmap *cache = NULL; int r; + assert(ret); + cache = hashmap_new(&uid_gid_hash_ops); if (!cache) return -ENOMEM; diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 5d0859a2f2137..7e41f6929924a 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -289,6 +289,7 @@ static int specifier_directory( unsigned i; int r; + assert(ret); assert_cc(ELEMENTSOF(paths_system) == ELEMENTSOF(paths_user)); paths = arg_runtime_scope == RUNTIME_SCOPE_USER ? paths_user : paths_system; diff --git a/src/tpm2-setup/tpm2-swtpm.c b/src/tpm2-setup/tpm2-swtpm.c index 9522420d86645..4ea6517157eec 100644 --- a/src/tpm2-setup/tpm2-swtpm.c +++ b/src/tpm2-setup/tpm2-swtpm.c @@ -32,6 +32,8 @@ static int load_boot_secret(struct iovec *ret) { _cleanup_(iovec_done_erase) struct iovec buf = {}; int r; + assert(ret); + const char *bs = in_initrd() ? "/.extra/boot-secret" : "/run/systemd/stub/boot-secret"; r = read_full_file_full( AT_FDCWD, diff --git a/src/udev/udevadm-monitor.c b/src/udev/udevadm-monitor.c index b7ec2ba1ef281..6f33cc3710cca 100644 --- a/src/udev/udevadm-monitor.c +++ b/src/udev/udevadm-monitor.c @@ -65,6 +65,8 @@ static int setup_monitor(MonitorNetlinkGroup sender, sd_event *event, sd_device_ const char *subsystem, *devtype, *tag; int r; + assert(ret); + r = device_monitor_new_full(&monitor, sender, -EBADF); if (r < 0) return log_error_errno(r, "Failed to create netlink socket: %m"); diff --git a/src/userdb/userdbd-manager.c b/src/userdb/userdbd-manager.c index 8803f3090f963..cf9f2f5c6b8c3 100644 --- a/src/userdb/userdbd-manager.c +++ b/src/userdb/userdbd-manager.c @@ -79,6 +79,8 @@ int manager_new(Manager **ret) { _cleanup_(manager_freep) Manager *m = NULL; int r; + assert(ret); + m = new(Manager, 1); if (!m) return -ENOMEM; diff --git a/src/xdg-autostart-generator/xdg-autostart-service.c b/src/xdg-autostart-generator/xdg-autostart-service.c index ad77e476c83e4..c3ba389dcf4d1 100644 --- a/src/xdg-autostart-generator/xdg-autostart-service.c +++ b/src/xdg-autostart-generator/xdg-autostart-service.c @@ -190,6 +190,9 @@ static int strv_strndup_unescape_and_push( const char *start, const char *end) { + assert(sv); + assert(n); + if (end == start) return 0; From 3b8408a75684329f7826a13f7b4ee614701b38f8 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Sat, 11 Apr 2026 19:52:33 +0200 Subject: [PATCH 0927/1296] sd-varlink: make ret optional in sd_varlink_idl_parse() We have a test failure where the testsuite is calling sd_varlink_idl_parse() with *ret being NULL. This is now an assert error. So we could either fix the test or fix the code Given that it seems genuinely useful to run sd_varlink_idl_parse() without *ret to e.g. just check if the idl is valid I opted to fix the code. --- src/libsystemd/sd-varlink/sd-varlink-idl.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libsystemd/sd-varlink/sd-varlink-idl.c b/src/libsystemd/sd-varlink/sd-varlink-idl.c index 3811b5d03b7e2..be66fb34afc39 100644 --- a/src/libsystemd/sd-varlink/sd-varlink-idl.c +++ b/src/libsystemd/sd-varlink/sd-varlink-idl.c @@ -1173,7 +1173,7 @@ _public_ int sd_varlink_idl_parse( _cleanup_(sd_varlink_interface_freep) sd_varlink_interface *interface = NULL; _cleanup_(varlink_symbol_freep) sd_varlink_symbol *symbol = NULL; - assert_return(ret, -EINVAL); + POINTER_MAY_BE_NULL(ret); enum { STATE_PRE_INTERFACE, @@ -1410,7 +1410,8 @@ _public_ int sd_varlink_idl_parse( if (r < 0) return r; - *ret = TAKE_PTR(interface); + if (ret) + *ret = TAKE_PTR(interface); return 0; } From 9d8d0d412fb58045abf209a9e8d4552e2676eb0c Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Sun, 12 Apr 2026 15:47:48 +0200 Subject: [PATCH 0928/1296] coccinelle: add SIZEOF() macro to work-around sizeof(*private) We have code like `size_t max_size = sizeof(*private)` in three places. This is evaluated at compile time so its safe to use. However the new pointer-deref checker in coccinelle is not smart enough to know this and will flag those as errors. To avoid these false positives we have some options: 1. Reorder so that we do: ```C size_t max_size = 0; assert(private); max_size = sizeof(*private); ``` 2. Use something like `size_t max_size = sizeof(*ASSERT_PTR(private));` 3. Place the assert before the declaration 4. Workaround coccinelle via SIZEOF(*private) that we can then hide via parsing_hacks.h 5. Fix coccinelle (OCaml, hard) 6. ... somehting I missed? None of these is very appealing. I went for (4) but happy about suggestions. --- coccinelle/parsing_hacks.h | 5 +++++ src/fundamental/assert-fundamental.h | 8 ++++++++ src/shared/tpm2-util.c | 6 +++--- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/coccinelle/parsing_hacks.h b/coccinelle/parsing_hacks.h index 774dbb1e6a102..bc84235557719 100644 --- a/coccinelle/parsing_hacks.h +++ b/coccinelle/parsing_hacks.h @@ -99,6 +99,11 @@ #define CMSG_BUFFER_TYPE(x) union { uint8_t align_check[(size) >= CMSG_SPACE(0) && (size) == CMSG_ALIGN(size) ? 1 : -1]; } #define SD_ID128_MAKE(...) ((const sd_id128) {}) +/* sizeof() does not evaluate its argument, so *ptr inside sizeof() is not a real dereference. + * The SIZEOF() macro is an alias for sizeof() that hides the argument from coccinelle to avoid + * false positives from check-pointer-deref.cocci. See assert-fundamental.h for the definition. */ +#define SIZEOF(x) 8 + /* Work around a bug in zlib.h parsing on Fedora (and possibly others) * See: https://github.com/coccinelle/coccinelle/issues/413 */ #define Z_EXPORT diff --git a/src/fundamental/assert-fundamental.h b/src/fundamental/assert-fundamental.h index e7f662512bff5..d3425cb54df0c 100644 --- a/src/fundamental/assert-fundamental.h +++ b/src/fundamental/assert-fundamental.h @@ -105,3 +105,11 @@ static inline int __coverity_check_and_return__(int condition) { * the coccinelle check-pointer-deref warning for parameters that are safely handled before any * dereference (e.g. passed to a NULL-safe helper like iovec_is_set()). */ #define POINTER_MAY_BE_NULL(ptr) ({ (void) (ptr); }) + +/* sizeof() does not evaluate its argument - it is a compile-time constant expression - so *ptr + * inside sizeof() is not a real dereference. However, coccinelle cannot distinguish this from an + * actual dereference, and when sizeof(*ptr) appears in a variable initializer the assert(ptr) that + * follows cannot come first (declarations must precede statements). Use this macro in place + * of sizeof() to avoid the false positive - coccinelle sees SIZEOF() as a function call (via + * parsing_hacks.h) and never looks inside the argument. */ +#define SIZEOF(x) sizeof(x) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 5e5ed1f620b39..02f89c02ba71f 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -2507,7 +2507,7 @@ static int tpm2_load_external( } static int tpm2_marshal_private(const TPM2B_PRIVATE *private, void **ret, size_t *ret_size) { - size_t max_size = sizeof(*private), blob_size = 0; + size_t max_size = SIZEOF(*private), blob_size = 0; _cleanup_free_ void *blob = NULL; TSS2_RC rc; @@ -2550,7 +2550,7 @@ static int tpm2_unmarshal_private(const void *data, size_t size, TPM2B_PRIVATE * } int tpm2_marshal_public(const TPM2B_PUBLIC *public, void **ret, size_t *ret_size) { - size_t max_size = sizeof(*public), blob_size = 0; + size_t max_size = SIZEOF(*public), blob_size = 0; _cleanup_free_ void *blob = NULL; TSS2_RC rc; @@ -2593,7 +2593,7 @@ static int tpm2_unmarshal_public(const void *data, size_t size, TPM2B_PUBLIC *re } int tpm2_marshal_nv_public(const TPM2B_NV_PUBLIC *nv_public, void **ret, size_t *ret_size) { - size_t max_size = sizeof(*nv_public), blob_size = 0; + size_t max_size = SIZEOF(*nv_public), blob_size = 0; _cleanup_free_ void *blob = NULL; TSS2_RC rc; From d830bb1fc132d31b5e82ba3c676051a4000d1538 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Sun, 12 Apr 2026 15:43:35 +0200 Subject: [PATCH 0929/1296] journal: move the {DATA,ENTRY}_SIZE constants to sd-journal So we can access them from the code there as well. --- src/core/unit.h | 2 +- src/coredump/coredump-config.c | 2 +- src/journal/journald-native.c | 1 - src/libsystemd/sd-journal/journal-def.h | 15 +++++++++++++++ src/shared/journal-importer.h | 14 -------------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/core/unit.h b/src/core/unit.h index 380ce7fac8c08..e503c2e344af5 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -10,7 +10,7 @@ #include "install.h" #include "iterator.h" #include "job.h" -#include "journal-importer.h" +#include "journal-def.h" #include "list.h" #include "log.h" #include "log-context.h" diff --git a/src/coredump/coredump-config.c b/src/coredump/coredump-config.c index de2bb68bf3151..cb7bb1adcc485 100644 --- a/src/coredump/coredump-config.c +++ b/src/coredump/coredump-config.c @@ -3,7 +3,7 @@ #include "conf-parser.h" #include "coredump-config.h" #include "format-util.h" -#include "journal-importer.h" +#include "journal-def.h" #include "log.h" #include "string-table.h" #include "string-util.h" diff --git a/src/journal/journald-native.c b/src/journal/journald-native.c index fade424ebcdef..1c36ca9434fa4 100644 --- a/src/journal/journald-native.c +++ b/src/journal/journald-native.c @@ -10,7 +10,6 @@ #include "fd-util.h" #include "format-util.h" #include "iovec-util.h" -#include "journal-importer.h" #include "journal-internal.h" #include "journald-client.h" #include "journald-console.h" diff --git a/src/libsystemd/sd-journal/journal-def.h b/src/libsystemd/sd-journal/journal-def.h index f20c9a357a195..84849a1e9506d 100644 --- a/src/libsystemd/sd-journal/journal-def.h +++ b/src/libsystemd/sd-journal/journal-def.h @@ -6,6 +6,21 @@ #include "sd-forward.h" #include "sparse-endian.h" +/* Make sure not to make this smaller than the maximum coredump size. + * See JOURNAL_SIZE_MAX in coredump-config.h */ +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +#define ENTRY_SIZE_MAX (1024*1024*770u) +#define ENTRY_SIZE_UNPRIV_MAX (1024*1024*32u) +#define DATA_SIZE_MAX (1024*1024*768u) +#else +#define ENTRY_SIZE_MAX (1024*1024*13u) +#define ENTRY_SIZE_UNPRIV_MAX (1024*1024*8u) +#define DATA_SIZE_MAX (1024*1024*11u) +#endif + +/* The maximum number of fields in an entry */ +#define ENTRY_FIELD_COUNT_MAX 1024u + /* * If you change this file you probably should also change its documentation: * diff --git a/src/shared/journal-importer.h b/src/shared/journal-importer.h index f218d80dfd938..21de703781b89 100644 --- a/src/shared/journal-importer.h +++ b/src/shared/journal-importer.h @@ -8,22 +8,8 @@ #include "iovec-wrapper.h" #include "time-util.h" -/* Make sure not to make this smaller than the maximum coredump size. - * See JOURNAL_SIZE_MAX in coredump.c */ -#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -#define ENTRY_SIZE_MAX (1024*1024*770u) -#define ENTRY_SIZE_UNPRIV_MAX (1024*1024*32u) -#define DATA_SIZE_MAX (1024*1024*768u) -#else -#define ENTRY_SIZE_MAX (1024*1024*13u) -#define ENTRY_SIZE_UNPRIV_MAX (1024*1024*8u) -#define DATA_SIZE_MAX (1024*1024*11u) -#endif #define LINE_CHUNK 8*1024u -/* The maximum number of fields in an entry */ -#define ENTRY_FIELD_COUNT_MAX 1024u - typedef struct JournalImporter { int fd; bool passive_fd; From 2cda5f6169e4a03e9860d315e7b4a7b0d61ca11f Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Sun, 12 Apr 2026 16:24:53 +0200 Subject: [PATCH 0930/1296] compress: limit the output to dst_max bytes with LZ4 if set We already do that with other algorithms, so let's make decompress_blob_lz4() consistent with the rest. --- src/basic/compress.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/basic/compress.c b/src/basic/compress.c index 251eb02fbb75a..b2a42f44e94c5 100644 --- a/src/basic/compress.c +++ b/src/basic/compress.c @@ -739,6 +739,8 @@ static int decompress_blob_lz4( size = unaligned_read_le64(src); if (size < 0 || (unsigned) size != unaligned_read_le64(src)) return -EFBIG; + if (dst_max > 0 && (size_t) size > dst_max) + return -ENOBUFS; out = greedy_realloc(dst, size, 1); if (!out) return -ENOMEM; From 31d360fb0b28859aba891aaefb1452f820a5861a Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Sun, 12 Apr 2026 15:02:11 +0200 Subject: [PATCH 0931/1296] journal: limit decompress_blob() output to DATA_SIZE_MAX We already have checks in place during compression that limit the data we compress, so they shouldn't decompress to anything larger than DATA_SIZE_MAX unless they've been tampered with. Let's make this explicit and limit all our decompress_blob() calls in journal-handling code to that limit. One possible scenario this should prevent is when one tries to open and verify a journal file that contains a compression bomb in its payload: $ ls -lh test.journal -rw-rw-r--+ 1 fsumsal fsumsal 1.2M Apr 12 15:07 test.journal $ systemd-run --user --wait --pipe -- build-local/journalctl --verify --file=$PWD/test.journal Running as unit: run-p682422-i4875779.service 000110: Invalid hash (00000000 vs. 11e4948d73bdafdd) 000110: Invalid object contents: Bad message File corruption detected at /home/fsumsal/repos/@systemd/systemd/test.journal:272 (of 1249896 bytes, 0%). FAIL: /home/fsumsal/repos/@systemd/systemd/test.journal (Bad message) Finished with result: exit-code Main processes terminated with: code=exited, status=1/FAILURE Service runtime: 48.051s CPU time consumed: 47.941s Memory peak: 8G (swap: 0B) Same could be, in theory, possible with just `journalctl --file=`, but the reproducer would be a bit more complicated (haven't tried it, yet). Lastly, the change in journal-remote is mostly hardening, as the maximum input size to decompress_blob() there is mandated by MHD's connection memory limit (set to JOURNAL_SERVER_MEMORY_MAX which is 128 KiB at the time of writing), so the possible output size there is already quite limited (e.g. ~800 - 900 MiB for xz-compressed data). --- src/journal-remote/journal-remote-main.c | 2 +- src/libsystemd/sd-journal/journal-file.c | 2 +- src/libsystemd/sd-journal/journal-verify.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/journal-remote/journal-remote-main.c b/src/journal-remote/journal-remote-main.c index fbb53cc42fdd1..9cb84bbe1e64b 100644 --- a/src/journal-remote/journal-remote-main.c +++ b/src/journal-remote/journal-remote-main.c @@ -286,7 +286,7 @@ static int process_http_upload( _cleanup_free_ char *buf = NULL; size_t buf_size; - r = decompress_blob(source->compression, upload_data, *upload_data_size, (void **) &buf, &buf_size, 0); + r = decompress_blob(source->compression, upload_data, *upload_data_size, (void **) &buf, &buf_size, DATA_SIZE_MAX); if (r < 0) return mhd_respondf(connection, r, MHD_HTTP_BAD_REQUEST, "Decompression of received blob failed."); diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index 54c647d75b8eb..a4b270ceb728d 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -1971,7 +1971,7 @@ static int maybe_decompress_payload( return 1; } - r = decompress_blob(compression, payload, size, &f->compress_buffer, &rsize, 0); + r = decompress_blob(compression, payload, size, &f->compress_buffer, &rsize, DATA_SIZE_MAX); if (r < 0) return r; diff --git a/src/libsystemd/sd-journal/journal-verify.c b/src/libsystemd/sd-journal/journal-verify.c index 1b3bf3fca35cb..11532c5f8a809 100644 --- a/src/libsystemd/sd-journal/journal-verify.c +++ b/src/libsystemd/sd-journal/journal-verify.c @@ -126,7 +126,7 @@ static int hash_payload(JournalFile *f, Object *o, uint64_t offset, const uint8_ _cleanup_free_ void *b = NULL; size_t b_size; - r = decompress_blob(c, src, size, &b, &b_size, 0); + r = decompress_blob(c, src, size, &b, &b_size, DATA_SIZE_MAX); if (r < 0) { error_errno(offset, r, "%s decompression failed: %m", compression_to_string(c)); From aa85a742fe5e0816312566a700599496e720246d Mon Sep 17 00:00:00 2001 From: Milan Kyselica Date: Sat, 11 Apr 2026 10:31:16 +0200 Subject: [PATCH 0932/1296] nss-systemd: fix off-by-one in nss_pack_group_record_shadow() nss_count_strv() counts trailing NULL pointers in n. The pointer area then used (n + 1), reserving one slot more than the size check accounted for. Drop the + 1 since n already includes the trailing NULLs, unlike the non-shadow nss_pack_group_record() where n does not. Fixes: https://github.com/systemd/systemd/issues/41591 --- src/nss-systemd/userdb-glue.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/nss-systemd/userdb-glue.c b/src/nss-systemd/userdb-glue.c index 6f1bf1e2af5c3..5bc89d5f9bb69 100644 --- a/src/nss-systemd/userdb-glue.c +++ b/src/nss-systemd/userdb-glue.c @@ -475,7 +475,9 @@ int nss_pack_group_record_shadow( assert(buffer); - p = buffer + sizeof(void*) * (n + 1); /* place member strings right after the ptr array */ + /* n already includes trailing NULL pointers from nss_count_strv(), unlike the + * non-shadow nss_pack_group_record() where n does not include them. */ + p = buffer + sizeof(void*) * n; array = (char**) buffer; /* place ptr array at beginning of buffer, under assumption buffer is aligned */ sgrp->sg_mem = array; From 5f700d148c44063c0f0dbb9fc136866339cd3fa7 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 10 Apr 2026 19:04:04 +0100 Subject: [PATCH 0933/1296] udev/scsi-id: check for invalid chars in various fields received from the kernel Follow-up for 16325b35fa6ecb25f66534a562583ce3b96d52f3 --- src/udev/scsi_id/scsi_id.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/udev/scsi_id/scsi_id.c b/src/udev/scsi_id/scsi_id.c index e3438897199f9..6a31f9c4c8ebd 100644 --- a/src/udev/scsi_id/scsi_id.c +++ b/src/udev/scsi_id/scsi_id.c @@ -389,6 +389,10 @@ static int set_inq_values(struct scsi_id_device *dev_scsi, const char *path) { return 0; } +static bool scsi_string_is_valid(const char *s) { + return !isempty(s) && utf8_is_valid(s) && !string_has_cc(s, /* ok= */ NULL); +} + /* * scsi_id: try to get an id, if one is found, printf it to stdout. * returns a value passed to exit() - 0 if printed an id, else 1. @@ -432,17 +436,17 @@ static int scsi_id(char *maj_min_dev) { udev_replace_chars(serial_str, NULL); printf("ID_SERIAL_SHORT=%s\n", serial_str); } - if (dev_scsi.wwn[0] != '\0') { + if (scsi_string_is_valid(dev_scsi.wwn)) { printf("ID_WWN=0x%s\n", dev_scsi.wwn); - if (dev_scsi.wwn_vendor_extension[0] != '\0') { + if (scsi_string_is_valid(dev_scsi.wwn_vendor_extension)) { printf("ID_WWN_VENDOR_EXTENSION=0x%s\n", dev_scsi.wwn_vendor_extension); printf("ID_WWN_WITH_EXTENSION=0x%s%s\n", dev_scsi.wwn, dev_scsi.wwn_vendor_extension); } else printf("ID_WWN_WITH_EXTENSION=0x%s\n", dev_scsi.wwn); } - if (dev_scsi.tgpt_group[0] != '\0') + if (scsi_string_is_valid(dev_scsi.tgpt_group)) printf("ID_TARGET_PORT=%s\n", dev_scsi.tgpt_group); - if (dev_scsi.unit_serial_number[0] != '\0' && utf8_is_valid(dev_scsi.unit_serial_number) && !string_has_cc(dev_scsi.unit_serial_number, /* ok= */ NULL)) + if (scsi_string_is_valid(dev_scsi.unit_serial_number)) printf("ID_SCSI_SERIAL=%s\n", dev_scsi.unit_serial_number); goto out; } From 06d3f37336ab8dea545521d95ebc6246b29241f0 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 10 Apr 2026 19:35:59 +0100 Subject: [PATCH 0934/1296] udev/scsi-id: check for invalid header from kernel buffer --- src/udev/scsi_id/scsi_serial.c | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/udev/scsi_id/scsi_serial.c b/src/udev/scsi_id/scsi_serial.c index 7de9999257850..23c3b7d786b01 100644 --- a/src/udev/scsi_id/scsi_serial.c +++ b/src/udev/scsi_id/scsi_serial.c @@ -542,7 +542,9 @@ static int check_fill_0x83_prespc3(struct scsi_id_device *dev_scsi, /* serial has been memset to zero before */ j = strlen(serial); /* j = 1; */ - for (i = 0; (i < page_83[3]) && (j < max_len-3); ++i) { + /* Cap reported page length to buffer size in case of malformed responses */ + int page_len = MIN((int)page_83[3], SCSI_INQ_BUFF_LEN - 4); + for (i = 0; (i < page_len) && (j < max_len-3); ++i) { serial[j++] = hexchar(page_83[4+i] >> 4); serial[j++] = hexchar(page_83[4+i]); } @@ -610,12 +612,25 @@ static int do_scsi_page83_inquiry(struct scsi_id_device *dev_scsi, int fd, * Search for a match in the prioritized id_search_list - since WWN ids * come first we can pick up the WWN in check_fill_0x83_id(). */ + + /* Cap reported page length to buffer size in case of malformed responses. + * Below, j can equal page_end, and at that point page_83[j + 3] (the first descriptor data byte) + * must still be readable before the inner bounds check, so page_end + 4 < SCSI_INQ_BUFF_LEN + * requires page_end <= SCSI_INQ_BUFF_LEN - 5. */ + unsigned page_end = MIN(((unsigned)page_83[2] << 8) + (unsigned)page_83[3] + 3U, + (unsigned)SCSI_INQ_BUFF_LEN - 5U); + FOREACH_ELEMENT(search_value, id_search_list) { /* * Examine each descriptor returned. There is normally only * one or a small number of descriptors. */ - for (unsigned j = 4; j <= ((unsigned)page_83[2] << 8) + (unsigned)page_83[3] + 3; j += page_83[j + 3] + 4) { + for (unsigned j = 4; j <= page_end; j += page_83[j + 3] + 4) { + /* Ensure the full descriptor fits within the buffer, including + * fixed-offset accesses up to page_83[7] in the TGTGROUP path + * of check_fill_0x83_id(), so require at least 8 bytes from j */ + if (j + MAX(4U + (unsigned)page_83[j + 3], 8U) > (unsigned)SCSI_INQ_BUFF_LEN) + break; retval = check_fill_0x83_id(dev_scsi, page_83 + j, search_value, serial, serial_short, len, @@ -688,7 +703,9 @@ static int do_scsi_page83_prespc3_inquiry(struct scsi_id_device *dev_scsi, int f * using two bytes of ASCII for each byte * in the page_83. */ - while (i < (page_83[3]+4)) { + /* Cap reported page length to buffer size in case of malformed responses */ + int page_len = MIN((int)page_83[3] + 4, SCSI_INQ_BUFF_LEN); + while (i < page_len && j + 2 < len) { serial[j++] = hexchar(page_83[i] >> 4); serial[j++] = hexchar(page_83[i]); i++; @@ -725,7 +742,8 @@ static int do_scsi_page80_inquiry(struct scsi_id_device *dev_scsi, int fd, * Prepend 'S' to avoid unlikely collision with page 0x83 vendor * specific type where we prepend '0' + vendor + model. */ - len = buf[3]; + /* Cap reported page length to buffer size in case of malformed responses */ + len = MIN((int)buf[3], SCSI_INQ_BUFF_LEN - 4); if (serial) { serial[0] = 'S'; ser_ind = append_vendor_model(dev_scsi, serial + 1); @@ -860,7 +878,10 @@ int scsi_get_serial(struct scsi_id_device *dev_scsi, const char *devname, goto completed; } - for (ind = 4; ind <= page0[3] + 3; ind++) + /* Cap reported page length to buffer size in case of malformed responses */ + int page0_end = MIN((int)page0[3] + 3, SCSI_INQ_BUFF_LEN - 1); + + for (ind = 4; ind <= page0_end; ind++) if (page0[ind] == PAGE_83) if (!do_scsi_page83_inquiry(dev_scsi, fd, dev_scsi->serial, dev_scsi->serial_short, len, dev_scsi->unit_serial_number, dev_scsi->wwn, dev_scsi->wwn_vendor_extension, dev_scsi->tgpt_group)) { @@ -871,7 +892,7 @@ int scsi_get_serial(struct scsi_id_device *dev_scsi, const char *devname, goto completed; } - for (ind = 4; ind <= page0[3] + 3; ind++) + for (ind = 4; ind <= page0_end; ind++) if (page0[ind] == PAGE_80) if (!do_scsi_page80_inquiry(dev_scsi, fd, dev_scsi->serial, dev_scsi->serial_short, len)) { From 7fbdc6a8eba231afc5ac3e747c1281991e6ae90a Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 10 Apr 2026 22:14:45 +0100 Subject: [PATCH 0935/1296] udev/scsi-id: various typing refactorings --- src/udev/scsi_id/scsi.h | 2 +- src/udev/scsi_id/scsi_id.h | 4 +- src/udev/scsi_id/scsi_serial.c | 73 +++++++++++++++++----------------- 3 files changed, 41 insertions(+), 38 deletions(-) diff --git a/src/udev/scsi_id/scsi.h b/src/udev/scsi_id/scsi.h index ebb8ae9008be9..71c532e427ac5 100644 --- a/src/udev/scsi_id/scsi.h +++ b/src/udev/scsi_id/scsi.h @@ -24,7 +24,7 @@ struct scsi_ioctl_command { * as this is a nice value for some devices, especially some of the usb * mass storage devices. */ -#define SCSI_INQ_BUFF_LEN 254 +#define SCSI_INQ_BUFF_LEN 254U /* * SCSI INQUIRY vendor and model (really product) lengths. diff --git a/src/udev/scsi_id/scsi_id.h b/src/udev/scsi_id/scsi_id.h index db49c7f3d955c..984bbc05f4ca6 100644 --- a/src/udev/scsi_id/scsi_id.h +++ b/src/udev/scsi_id/scsi_id.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once +#include "basic-forward.h" + /* * Copyright © IBM Corp. 2003 */ @@ -50,7 +52,7 @@ struct scsi_id_device { int scsi_std_inquiry(struct scsi_id_device *dev_scsi, const char *devname); int scsi_get_serial(struct scsi_id_device *dev_scsi, const char *devname, - int page_code, int len); + int page_code, size_t len); /* * Page code values. diff --git a/src/udev/scsi_id/scsi_serial.c b/src/udev/scsi_id/scsi_serial.c index 23c3b7d786b01..95268635cb637 100644 --- a/src/udev/scsi_id/scsi_serial.c +++ b/src/udev/scsi_id/scsi_serial.c @@ -81,7 +81,7 @@ static const struct scsi_id_search_values id_search_list[] = { #define SG_ERR_CAT_OTHER 99 /* Some other error/warning */ static int do_scsi_page80_inquiry(struct scsi_id_device *dev_scsi, int fd, - char *serial, char *serial_short, int max_len); + char *serial, char *serial_short, size_t max_len); static int sg_err_category_new(int scsi_status, int msg_status, int host_status, int driver_status, const @@ -419,12 +419,12 @@ static int append_vendor_model( * serial number. */ static int check_fill_0x83_id(struct scsi_id_device *dev_scsi, - unsigned char *page_83, + uint8_t *page_83, const struct scsi_id_search_values *id_search, char *serial, char *serial_short, - int max_len, char *wwn, + size_t max_len, char *wwn, char *wwn_vendor_extension, char *tgpt_group) { - int i, j, s, len; + size_t i, j, s, len; /* * ASSOCIATION must be with the device (value 0) @@ -470,7 +470,7 @@ static int check_fill_0x83_id(struct scsi_id_device *dev_scsi, len += VENDOR_LENGTH + MODEL_LENGTH; if (max_len < len) { - log_debug("%s: length %d too short - need %d", + log_debug("%s: length %zu too short - need %zu", dev_scsi->kernel, max_len, len); return 1; } @@ -506,14 +506,14 @@ static int check_fill_0x83_id(struct scsi_id_device *dev_scsi, /* * ASCII descriptor. */ - while (i < (4 + page_83[3])) + while (i < (4U + page_83[3])) serial[j++] = page_83[i++]; } else { /* * Binary descriptor, convert to ASCII, using two bytes of * ASCII for each byte in the page_83. */ - while (i < (4 + page_83[3])) { + while (i < (4U + page_83[3])) { serial[j++] = hexchar(page_83[i] >> 4); serial[j++] = hexchar(page_83[i]); i++; @@ -533,18 +533,20 @@ static int check_fill_0x83_id(struct scsi_id_device *dev_scsi, /* Extract the raw binary from VPD 0x83 pre-SPC devices */ static int check_fill_0x83_prespc3(struct scsi_id_device *dev_scsi, - unsigned char *page_83, + uint8_t page_83[static SCSI_INQ_BUFF_LEN], const struct scsi_id_search_values - *id_search, char *serial, char *serial_short, int max_len) { - int i, j; + *id_search, char *serial, char *serial_short, size_t max_len) { + size_t j; + + assert(max_len > 0); serial[0] = hexchar(SCSI_ID_NAA); /* serial has been memset to zero before */ j = strlen(serial); /* j = 1; */ /* Cap reported page length to buffer size in case of malformed responses */ - int page_len = MIN((int)page_83[3], SCSI_INQ_BUFF_LEN - 4); - for (i = 0; (i < page_len) && (j < max_len-3); ++i) { + size_t page_len = MIN((size_t)page_83[3], SCSI_INQ_BUFF_LEN - 4); + for (size_t i = 0; (i < page_len) && (j + 3 < max_len); ++i) { serial[j++] = hexchar(page_83[4+i] >> 4); serial[j++] = hexchar(page_83[4+i]); } @@ -555,11 +557,11 @@ static int check_fill_0x83_prespc3(struct scsi_id_device *dev_scsi, /* Get device identification VPD page */ static int do_scsi_page83_inquiry(struct scsi_id_device *dev_scsi, int fd, - char *serial, char *serial_short, int len, + char *serial, char *serial_short, size_t len, char *unit_serial_number, char *wwn, char *wwn_vendor_extension, char *tgpt_group) { int retval; - unsigned char page_83[SCSI_INQ_BUFF_LEN]; + uint8_t page_83[SCSI_INQ_BUFF_LEN]; /* also pick up the page 80 serial number */ do_scsi_page80_inquiry(dev_scsi, fd, NULL, unit_serial_number, MAX_SERIAL_LEN); @@ -618,7 +620,7 @@ static int do_scsi_page83_inquiry(struct scsi_id_device *dev_scsi, int fd, * must still be readable before the inner bounds check, so page_end + 4 < SCSI_INQ_BUFF_LEN * requires page_end <= SCSI_INQ_BUFF_LEN - 5. */ unsigned page_end = MIN(((unsigned)page_83[2] << 8) + (unsigned)page_83[3] + 3U, - (unsigned)SCSI_INQ_BUFF_LEN - 5U); + SCSI_INQ_BUFF_LEN - 5U); FOREACH_ELEMENT(search_value, id_search_list) { /* @@ -629,7 +631,7 @@ static int do_scsi_page83_inquiry(struct scsi_id_device *dev_scsi, int fd, /* Ensure the full descriptor fits within the buffer, including * fixed-offset accesses up to page_83[7] in the TGTGROUP path * of check_fill_0x83_id(), so require at least 8 bytes from j */ - if (j + MAX(4U + (unsigned)page_83[j + 3], 8U) > (unsigned)SCSI_INQ_BUFF_LEN) + if (j + MAX(4U + (unsigned)page_83[j + 3], 8U) > SCSI_INQ_BUFF_LEN) break; retval = check_fill_0x83_id(dev_scsi, page_83 + j, search_value, @@ -653,10 +655,10 @@ static int do_scsi_page83_inquiry(struct scsi_id_device *dev_scsi, int fd, * conformant to the SCSI-2 format. */ static int do_scsi_page83_prespc3_inquiry(struct scsi_id_device *dev_scsi, int fd, - char *serial, char *serial_short, int len) { + char *serial, char *serial_short, size_t len) { int retval; - int i, j; - unsigned char page_83[SCSI_INQ_BUFF_LEN]; + size_t i, j; + uint8_t page_83[SCSI_INQ_BUFF_LEN]; memzero(page_83, SCSI_INQ_BUFF_LEN); retval = scsi_inquiry(dev_scsi, fd, 1, PAGE_83, page_83, SCSI_INQ_BUFF_LEN); @@ -704,7 +706,7 @@ static int do_scsi_page83_prespc3_inquiry(struct scsi_id_device *dev_scsi, int f * in the page_83. */ /* Cap reported page length to buffer size in case of malformed responses */ - int page_len = MIN((int)page_83[3] + 4, SCSI_INQ_BUFF_LEN); + size_t page_len = MIN((size_t)page_83[3] + 4, SCSI_INQ_BUFF_LEN); while (i < page_len && j + 2 < len) { serial[j++] = hexchar(page_83[i] >> 4); serial[j++] = hexchar(page_83[i]); @@ -715,12 +717,11 @@ static int do_scsi_page83_prespc3_inquiry(struct scsi_id_device *dev_scsi, int f /* Get unit serial number VPD page */ static int do_scsi_page80_inquiry(struct scsi_id_device *dev_scsi, int fd, - char *serial, char *serial_short, int max_len) { + char *serial, char *serial_short, size_t max_len) { int retval; int ser_ind; - int i; - int len; - unsigned char buf[SCSI_INQ_BUFF_LEN]; + size_t page_len; + uint8_t buf[SCSI_INQ_BUFF_LEN]; memzero(buf, SCSI_INQ_BUFF_LEN); retval = scsi_inquiry(dev_scsi, fd, 1, PAGE_80, buf, SCSI_INQ_BUFF_LEN); @@ -732,10 +733,10 @@ static int do_scsi_page80_inquiry(struct scsi_id_device *dev_scsi, int fd, return 1; } - len = 1 + VENDOR_LENGTH + MODEL_LENGTH + buf[3]; - if (max_len < len) { - log_debug("%s: length %d too short - need %d", - dev_scsi->kernel, max_len, len); + page_len = 1 + VENDOR_LENGTH + MODEL_LENGTH + buf[3]; + if (max_len < page_len) { + log_debug("%s: length %zu too short - need %zu", + dev_scsi->kernel, max_len, page_len); return 1; } /* @@ -743,19 +744,19 @@ static int do_scsi_page80_inquiry(struct scsi_id_device *dev_scsi, int fd, * specific type where we prepend '0' + vendor + model. */ /* Cap reported page length to buffer size in case of malformed responses */ - len = MIN((int)buf[3], SCSI_INQ_BUFF_LEN - 4); + page_len = MIN((size_t)buf[3], SCSI_INQ_BUFF_LEN - 4); if (serial) { serial[0] = 'S'; ser_ind = append_vendor_model(dev_scsi, serial + 1); if (ser_ind < 0) return 1; ser_ind++; /* for the leading 'S' */ - for (i = 4; i < len + 4; i++, ser_ind++) + for (size_t i = 4; i < page_len + 4; i++, ser_ind++) serial[ser_ind] = buf[i]; } if (serial_short) { - memcpy(serial_short, buf + 4, len); - serial_short[len] = '\0'; + memcpy(serial_short, buf + 4, page_len); + serial_short[page_len] = '\0'; } return 0; } @@ -799,11 +800,11 @@ int scsi_std_inquiry(struct scsi_id_device *dev_scsi, const char *devname) { } int scsi_get_serial(struct scsi_id_device *dev_scsi, const char *devname, - int page_code, int len) { - unsigned char page0[SCSI_INQ_BUFF_LEN]; + int page_code, size_t len) { + uint8_t page0[SCSI_INQ_BUFF_LEN]; int fd = -EBADF; int cnt; - int ind; + size_t ind; int retval; memzero(dev_scsi->serial, len); @@ -879,7 +880,7 @@ int scsi_get_serial(struct scsi_id_device *dev_scsi, const char *devname, } /* Cap reported page length to buffer size in case of malformed responses */ - int page0_end = MIN((int)page0[3] + 3, SCSI_INQ_BUFF_LEN - 1); + size_t page0_end = MIN((size_t)page0[3] + 3, SCSI_INQ_BUFF_LEN - 1); for (ind = 4; ind <= page0_end; ind++) if (page0[ind] == PAGE_83) From e700d5134df15a094a2c92bc61392fbaf3c0452b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 12 Apr 2026 21:44:59 +0200 Subject: [PATCH 0936/1296] time-util: encode our assumption that clock_gettime() never can return 0 or USEC_INFINITY We generally assume that valid times returned by clock_gettime() are > 0 and < USEC_INFINITY. If this wouldn't hold all kinds of things would break, because we couldn't distuingish our niche values from regular values anymore. Let's hence encode our assumptions in C, already to help static analyzers and LLMs. Inspired by: https://github.com/systemd/systemd/pull/41601#pullrequestreview-4094645891 --- src/basic/time-util.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/basic/time-util.c b/src/basic/time-util.c index 1e426bb8f988b..78c33c7553ce6 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -49,7 +49,15 @@ usec_t now(clockid_t clock_id) { assert_se(clock_gettime(map_clock_id(clock_id), &ts) == 0); - return timespec_load(&ts); + usec_t n = timespec_load(&ts); + + /* We use both 0 and USEC_INFINITY as niche values. If the current time collides with either, things are + * really weird and really broken. Let's not allow this to go through, it would break too many of our + * assumptions in code. */ + assert(n > 0); + assert(n < USEC_INFINITY); + + return n; } nsec_t now_nsec(clockid_t clock_id) { @@ -57,7 +65,12 @@ nsec_t now_nsec(clockid_t clock_id) { assert_se(clock_gettime(map_clock_id(clock_id), &ts) == 0); - return timespec_load_nsec(&ts); + nsec_t n = timespec_load_nsec(&ts); + + assert(n > 0); + assert(n < NSEC_INFINITY); + + return n; } dual_timestamp* dual_timestamp_now(dual_timestamp *ts) { From 0a8578a6ee64cac7a9bfb490f9f8819d4d31660b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 10 Apr 2026 10:18:12 +0000 Subject: [PATCH 0937/1296] compress: rework decompressor_detect() on top of compression_detect_from_magic() Replace the duplicated magic byte signatures in decompressor_detect() with a call to the new compression_detect_from_magic() helper and use a switch statement to initialize the appropriate decompression context. --- src/basic/compress.c | 74 +++++++++++++++++++++++++------------------- src/basic/compress.h | 3 ++ 2 files changed, 45 insertions(+), 32 deletions(-) diff --git a/src/basic/compress.c b/src/basic/compress.c index 251eb02fbb75a..37cba373fd912 100644 --- a/src/basic/compress.c +++ b/src/basic/compress.c @@ -266,6 +266,24 @@ bool compression_supported(Compression c) { return BIT_SET(supported, c); } +Compression compression_detect_from_magic(const uint8_t data[static COMPRESSION_MAGIC_BYTES_MAX]) { + /* Magic signatures per RFC 1952 (gzip), tukaani.org/xz/xz-file-format.txt (xz), + * RFC 8878 (zstd), lz4/doc/lz4_Frame_format.md (lz4), and the bzip2 file format. + * Make sure to update COMPRESSION_MAGIC_BYTES_MAX if needed when adding a new magic. */ + if (memcmp(data, (const uint8_t[]) { 0x1f, 0x8b }, 2) == 0) + return COMPRESSION_GZIP; + if (memcmp(data, (const uint8_t[]) { 0xfd, '7', 'z', 'X', 'Z', 0x00 }, 6) == 0) + return COMPRESSION_XZ; + if (memcmp(data, (const uint8_t[]) { 0x28, 0xb5, 0x2f, 0xfd }, 4) == 0) + return COMPRESSION_ZSTD; + if (memcmp(data, (const uint8_t[]) { 0x04, 0x22, 0x4d, 0x18 }, 4) == 0) + return COMPRESSION_LZ4; + if (memcmp(data, (const uint8_t[]) { 'B', 'Z', 'h' }, 3) == 0) + return COMPRESSION_BZIP2; + + return _COMPRESSION_INVALID; +} + int dlopen_xz(void) { #if HAVE_XZ SD_ELF_NOTE_DLOPEN( @@ -1748,22 +1766,6 @@ Compression compressor_type(const Compressor *c) { } int decompressor_detect(Decompressor **ret, const void *data, size_t size) { - static const uint8_t xz_signature[] = { - 0xfd, '7', 'z', 'X', 'Z', 0x00 - }; - static const uint8_t lz4_signature[] = { - 0x04, 0x22, 0x4d, 0x18 - }; - static const uint8_t zstd_signature[] = { - 0x28, 0xb5, 0x2f, 0xfd - }; - static const uint8_t gzip_signature[] = { - 0x1f, 0x8b - }; - static const uint8_t bzip2_signature[] = { - 'B', 'Z', 'h' - }; - #if HAVE_XZ || HAVE_LZ4 || HAVE_ZSTD || HAVE_ZLIB || HAVE_BZIP2 int r; #endif @@ -1773,23 +1775,21 @@ int decompressor_detect(Decompressor **ret, const void *data, size_t size) { if (*ret) return 1; - if (size < MAX5(sizeof(xz_signature), - sizeof(gzip_signature), - sizeof(zstd_signature), - sizeof(bzip2_signature), - sizeof(lz4_signature))) + if (size < COMPRESSION_MAGIC_BYTES_MAX) return 0; assert(data); + Compression type = compression_detect_from_magic(data); + _cleanup_(compressor_freep) Decompressor *c = new0(Decompressor, 1); if (!c) return -ENOMEM; - c->type = COMPRESSION_NONE; + switch (type) { #if HAVE_XZ - if (c->type == COMPRESSION_NONE && memcmp(data, xz_signature, sizeof(xz_signature)) == 0) { + case COMPRESSION_XZ: { r = dlopen_xz(); if (r < 0) return r; @@ -1798,12 +1798,12 @@ int decompressor_detect(Decompressor **ret, const void *data, size_t size) { if (xzr != LZMA_OK) return -EIO; - c->type = COMPRESSION_XZ; + break; } #endif #if HAVE_LZ4 - if (c->type == COMPRESSION_NONE && memcmp(data, lz4_signature, sizeof(lz4_signature)) == 0) { + case COMPRESSION_LZ4: { r = dlopen_lz4(); if (r < 0) return r; @@ -1812,12 +1812,12 @@ int decompressor_detect(Decompressor **ret, const void *data, size_t size) { if (sym_LZ4F_isError(rc)) return -ENOMEM; - c->type = COMPRESSION_LZ4; + break; } #endif #if HAVE_ZSTD - if (c->type == COMPRESSION_NONE && memcmp(data, zstd_signature, sizeof(zstd_signature)) == 0) { + case COMPRESSION_ZSTD: { r = dlopen_zstd(); if (r < 0) return r; @@ -1826,12 +1826,12 @@ int decompressor_detect(Decompressor **ret, const void *data, size_t size) { if (!c->d_zstd) return -ENOMEM; - c->type = COMPRESSION_ZSTD; + break; } #endif #if HAVE_ZLIB - if (c->type == COMPRESSION_NONE && memcmp(data, gzip_signature, sizeof(gzip_signature)) == 0) { + case COMPRESSION_GZIP: { r = dlopen_zlib(); if (r < 0) return r; @@ -1840,12 +1840,12 @@ int decompressor_detect(Decompressor **ret, const void *data, size_t size) { if (r != Z_OK) return -EIO; - c->type = COMPRESSION_GZIP; + break; } #endif #if HAVE_BZIP2 - if (c->type == COMPRESSION_NONE && memcmp(data, bzip2_signature, sizeof(bzip2_signature)) == 0) { + case COMPRESSION_BZIP2: { r = dlopen_bzip2(); if (r < 0) return r; @@ -1854,10 +1854,20 @@ int decompressor_detect(Decompressor **ret, const void *data, size_t size) { if (r != BZ_OK) return -EIO; - c->type = COMPRESSION_BZIP2; + break; } #endif + default: + if (type != _COMPRESSION_INVALID) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Detected %s compression, but support is not compiled in.", + compression_to_string(type)); + type = COMPRESSION_NONE; + break; + } + + c->type = type; c->encoding = false; log_debug("Detected compression type: %s", compression_to_string(c->type)); diff --git a/src/basic/compress.h b/src/basic/compress.h index a5d31b3fc2421..1c1e4f0e24b32 100644 --- a/src/basic/compress.h +++ b/src/basic/compress.h @@ -33,6 +33,9 @@ bool compression_supported(Compression c); * intermediate buffers. */ #define COMPRESS_PIPE_BUFFER_SIZE (128U*1024U) +#define COMPRESSION_MAGIC_BYTES_MAX 6U +Compression compression_detect_from_magic(const uint8_t data[static COMPRESSION_MAGIC_BYTES_MAX]); + /* Compressor / Decompressor — opaque push-based streaming compression context */ typedef struct Compressor Compressor; From 4d91b0366afd215b68d0eae741da6a3aab5e3989 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 10 Apr 2026 10:23:22 +0000 Subject: [PATCH 0938/1296] libc: Add kexec_file_load() syscall wrapper Allow tabs in UAPI headers in .gitattributes since they are copied verbatim from the kernel. --- .gitattributes | 1 + README | 2 +- meson.build | 1 + src/include/override/sys/kexec.h | 17 ++++++++ src/include/uapi/linux/kexec.h | 71 ++++++++++++++++++++++++++++++++ src/libc/kexec.c | 11 +++++ src/libc/meson.build | 1 + 7 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 src/include/override/sys/kexec.h create mode 100644 src/include/uapi/linux/kexec.h create mode 100644 src/libc/kexec.c diff --git a/.gitattributes b/.gitattributes index dae59aa844a2e..6c6c4a8beaab1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,5 @@ *.[ch] whitespace=tab-in-indent,trailing-space +src/include/uapi/**/*.[ch] whitespace=trailing-space *.gpg binary generated *.bmp binary *.base64 generated diff --git a/README b/README index 359db5c3f433f..ccb6f86bfe8ea 100644 --- a/README +++ b/README @@ -30,7 +30,7 @@ LICENSE: REQUIREMENTS: Linux kernel ≥ 3.15 for timerfd_create() CLOCK_BOOTTIME support - ≥ 3.17 for memfd_create() and getrandom() + ≥ 3.17 for memfd_create(), getrandom(), and kexec_file_load() (x86-64) ≥ 4.3 for ambient capabilities ≥ 4.5 for pids controller in cgroup v2 ≥ 4.6 for cgroup namespaces diff --git a/meson.build b/meson.build index 1c296073c2996..e0e7102a39f42 100644 --- a/meson.build +++ b/meson.build @@ -605,6 +605,7 @@ foreach ident : [ ['fchmodat2', '''#include '''], # no known header declares fchmodat2 ['bpf', '''#include '''], # no known header declares bpf ['kcmp', '''#include '''], # no known header declares kcmp + ['kexec_file_load', '''#include '''], # no known header declares kexec_file_load ['keyctl', '''#include '''], # no known header declares keyctl ['add_key', '''#include '''], # no known header declares add_key ['request_key', '''#include '''], # no known header declares request_key diff --git a/src/include/override/sys/kexec.h b/src/include/override/sys/kexec.h new file mode 100644 index 0000000000000..4e256bdb6ad38 --- /dev/null +++ b/src/include/override/sys/kexec.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include /* IWYU pragma: export */ +#include + +/* Supported since kernel v3.17 (cb1052581e2bddd6096544f3f944f4e7fdad4c4f). + * Not available on all architectures. */ +#if HAVE_KEXEC_FILE_LOAD || defined __NR_kexec_file_load +# if !HAVE_KEXEC_FILE_LOAD +int missing_kexec_file_load(int kernel_fd, int initrd_fd, unsigned long cmdline_len, const char *cmdline, unsigned long flags); +# define kexec_file_load missing_kexec_file_load +# endif +# define HAVE_KEXEC_FILE_LOAD_SYSCALL 1 +#else +# define HAVE_KEXEC_FILE_LOAD_SYSCALL 0 +#endif diff --git a/src/include/uapi/linux/kexec.h b/src/include/uapi/linux/kexec.h new file mode 100644 index 0000000000000..e26e2110ce5d8 --- /dev/null +++ b/src/include/uapi/linux/kexec.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef LINUX_KEXEC_H +#define LINUX_KEXEC_H + +/* kexec system call - It loads the new kernel to boot into. + * kexec does not sync, or unmount filesystems so if you need + * that to happen you need to do that yourself. + */ + +#include + +/* kexec flags for different usage scenarios */ +#define KEXEC_ON_CRASH 0x00000001 +#define KEXEC_PRESERVE_CONTEXT 0x00000002 +#define KEXEC_UPDATE_ELFCOREHDR 0x00000004 +#define KEXEC_CRASH_HOTPLUG_SUPPORT 0x00000008 +#define KEXEC_ARCH_MASK 0xffff0000 + +/* + * Kexec file load interface flags. + * KEXEC_FILE_UNLOAD : Unload already loaded kexec/kdump image. + * KEXEC_FILE_ON_CRASH : Load/unload operation belongs to kdump image. + * KEXEC_FILE_NO_INITRAMFS : No initramfs is being loaded. Ignore the initrd + * fd field. + * KEXEC_FILE_FORCE_DTB : Force carrying over the current boot's DTB to the new + * kernel on x86. This is already the default behavior on + * some other architectures, like ARM64 and PowerPC. + */ +#define KEXEC_FILE_UNLOAD 0x00000001 +#define KEXEC_FILE_ON_CRASH 0x00000002 +#define KEXEC_FILE_NO_INITRAMFS 0x00000004 +#define KEXEC_FILE_DEBUG 0x00000008 +#define KEXEC_FILE_NO_CMA 0x00000010 +#define KEXEC_FILE_FORCE_DTB 0x00000020 + +/* These values match the ELF architecture values. + * Unless there is a good reason that should continue to be the case. + */ +#define KEXEC_ARCH_DEFAULT ( 0 << 16) +#define KEXEC_ARCH_386 ( 3 << 16) +#define KEXEC_ARCH_68K ( 4 << 16) +#define KEXEC_ARCH_PARISC (15 << 16) +#define KEXEC_ARCH_X86_64 (62 << 16) +#define KEXEC_ARCH_PPC (20 << 16) +#define KEXEC_ARCH_PPC64 (21 << 16) +#define KEXEC_ARCH_IA_64 (50 << 16) +#define KEXEC_ARCH_ARM (40 << 16) +#define KEXEC_ARCH_S390 (22 << 16) +#define KEXEC_ARCH_SH (42 << 16) +#define KEXEC_ARCH_MIPS_LE (10 << 16) +#define KEXEC_ARCH_MIPS ( 8 << 16) +#define KEXEC_ARCH_AARCH64 (183 << 16) +#define KEXEC_ARCH_RISCV (243 << 16) +#define KEXEC_ARCH_LOONGARCH (258 << 16) + +/* The artificial cap on the number of segments passed to kexec_load. */ +#define KEXEC_SEGMENT_MAX 16 + +/* + * This structure is used to hold the arguments that are used when + * loading kernel binaries. + */ +struct kexec_segment { + const void *buf; + __kernel_size_t bufsz; + const void *mem; + __kernel_size_t memsz; +}; + + +#endif /* LINUX_KEXEC_H */ diff --git a/src/libc/kexec.c b/src/libc/kexec.c new file mode 100644 index 0000000000000..122acff956c07 --- /dev/null +++ b/src/libc/kexec.c @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#if !HAVE_KEXEC_FILE_LOAD && defined __NR_kexec_file_load +int missing_kexec_file_load(int kernel_fd, int initrd_fd, unsigned long cmdline_len, const char *cmdline, unsigned long flags) { + return syscall(__NR_kexec_file_load, kernel_fd, initrd_fd, cmdline_len, cmdline, flags); +} +#endif diff --git a/src/libc/meson.build b/src/libc/meson.build index 306512ffd7002..3b7b96d07f219 100644 --- a/src/libc/meson.build +++ b/src/libc/meson.build @@ -4,6 +4,7 @@ libc_wrapper_sources = files( 'bpf.c', 'ioprio.c', 'kcmp.c', + 'kexec.c', 'keyctl.c', 'mempolicy.c', 'mount.c', From e107c7ead030c3af28f83f7d43c922a47104777b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 27 Mar 2026 22:03:14 +0000 Subject: [PATCH 0939/1296] systemctl: replace kexec-tools dependency with direct kexec_file_load() syscall MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the fork+exec of /usr/bin/kexec in load_kexec_kernel() with a direct kexec_file_load() syscall, removing the runtime dependency on kexec-tools for systemctl kexec. The kexec_file_load() syscall (available since Linux 3.17) accepts kernel and initrd file descriptors directly, letting the kernel handle image parsing, segment setup, and purgatory internally. This is much simpler than the older kexec_load() syscall which requires complex userspace setup of memory segments and boot protocol structures — that complexity is the raison d'être of kexec-tools. The implementation follows the established libc wrapper pattern: a missing_kexec_file_load() fallback in src/libc/kexec.c calls the syscall directly when glibc doesn't provide a wrapper (which is currently always the case). The syscall is not available on all architectures — alpha, i386, ia64, m68k, mips, sh, and sparc lack __NR_kexec_file_load — so the wrapper and caller are guarded with HAVE_KEXEC_FILE_LOAD_SYSCALL to compile cleanly everywhere. When kexec_file_load() rejects the kernel image with ENOEXEC (e.g. the image is compressed or wrapped in a PE container that the kernel's kexec handler doesn't understand natively), we attempt to unwrap/decompress and retry. This is effectively the same decompression and extraction logic that already lives in src/ukify/ukify.py (maybe_decompress() and get_zboot_kernel()), now implemented in C so that systemctl can handle it natively without shelling out to external tools: - Compressed kernels (Image.gz, Image.zst, Image.xz, Image.lz4): the format is detected by magic bytes (per RFC 1952, RFC 8878, tukaani.org xz spec, and lz4 frame format spec) and decompressed to a memfd using the existing decompress_stream_*() infrastructure plus the new gzip support from the previous commit. This is primarily needed on arm64 where kexec_file_load() only accepts raw Image files. On x86_64, bzImage is already the native format and works directly. - EFI ZBOOT PE images (vmlinuz.efi): detected by "MZ" + "zimg" magic at the start of the file. The compressed payload offset, size, and compression type are read from the ZBOOT header defined in linux/drivers/firmware/efi/libstub/zboot-header.S. - Unified Kernel Images (UKI): detected as PE files with a .linux section via the existing pe_is_uki() infrastructure. The .linux section (kernel) and optionally .initrd section are extracted to memfds. When a UKI provides an embedded initrd and the boot entry doesn't specify one separately, the embedded initrd is used. The try-first-then-decompress approach means we never decompress unnecessarily: on x86_64 the first kexec_file_load() call succeeds immediately with the raw bzImage, and on architectures where the kernel's kexec handler natively understands PE (like LoongArch with kexec_efi_ops), ZBOOT/UKI images work without decompression too. If kexec_file_load() is unavailable (architectures without the syscall) or all attempts fail, we fall back to forking+execing the kexec binary. This preserves compatibility on architectures like i386 and mips where only the older kexec_load() syscall exists and kexec-tools is needed to handle the complex userspace setup. Co-developed-by: Claude Opus 4.6 --- src/shared/reboot-util.c | 245 +++++++++++++++++++++- src/shared/reboot-util.h | 2 + src/systemctl/systemctl-start-special.c | 72 ++++++- src/test/meson.build | 4 + src/test/test-kexec.c | 261 ++++++++++++++++++++++++ 5 files changed, 572 insertions(+), 12 deletions(-) create mode 100644 src/test/test-kexec.c diff --git a/src/shared/reboot-util.c b/src/shared/reboot-util.c index d9ff532921b38..5e460b1dc517d 100644 --- a/src/shared/reboot-util.c +++ b/src/shared/reboot-util.c @@ -14,19 +14,40 @@ #include #include "errno-util.h" -#include "fd-util.h" #endif #include "alloc-util.h" +#include "compress.h" +#include "copy.h" +#include "fd-util.h" #include "fileio.h" +#include "io-util.h" #include "log.h" +#include "memfd-util.h" +#include "pe-binary.h" #include "proc-cmdline.h" #include "reboot-util.h" +#include "sparse-endian.h" +#include "stat-util.h" #include "string-util.h" #include "umask-util.h" #include "utf8.h" #include "virt.h" +/* ZBOOT header layout — see linux/drivers/firmware/efi/libstub/zboot-header.S */ +struct zboot_header { + le16_t mz_magic; /* 0x00: "MZ" DOS signature */ + le16_t _pad0; + uint8_t zimg_magic[4]; /* 0x04: "zimg" identifier */ + le32_t payload_offset; /* 0x08: offset to compressed payload */ + le32_t payload_size; /* 0x0C: size of compressed payload */ + uint8_t _pad1[8]; + char comp_type[6]; /* 0x18: NUL-terminated compression type (e.g. "gzip", "zstd") */ + uint8_t _pad2[2]; +} _packed_; +assert_cc(sizeof(struct zboot_header) == 0x20); +assert_cc(offsetof(struct zboot_header, comp_type) == 0x18); + int raw_reboot(int cmd, const void *arg) { return syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, cmd, arg); } @@ -246,6 +267,228 @@ int kexec(void) { return 0; } +static int decompress_to_memfd(Compression compression, int fd) { + int r; + + _cleanup_close_ int memfd = memfd_new("kexec-kernel"); + if (memfd < 0) + return log_error_errno(memfd, "Failed to create memfd: %m"); + + r = decompress_stream(compression, fd, memfd, UINT64_MAX); + if (r < 0) + return log_error_errno(r, "Failed to decompress kernel: %m"); + + if (lseek(memfd, 0, SEEK_SET) < 0) + return log_error_errno(errno, "Failed to seek memfd: %m"); + + return TAKE_FD(memfd); +} + +static int decompress_zboot_to_memfd(int fd, uint32_t payload_offset, uint32_t payload_size, const char *comp_type) { + int r; + + Compression c = compression_from_string(comp_type); + if (c < 0) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Unsupported ZBOOT compression type '%s'.", comp_type); + + struct stat st; + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to stat ZBOOT image: %m"); + + r = stat_verify_regular(&st); + if (r < 0) + return log_error_errno(r, "Kernel image is not a regular file: %m"); + + if (payload_offset < 0x20 || + payload_size == 0 || + payload_offset > (uint64_t) st.st_size || + payload_size > (uint64_t) st.st_size - payload_offset) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "ZBOOT payload offset/size invalid."); + + if (payload_size > 256 * 1024 * 1024) /* generous for any compressed kernel */ + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "ZBOOT payload unreasonably large."); + + _cleanup_free_ void *payload = malloc(payload_size); + if (!payload) + return log_oom(); + + ssize_t n = pread(fd, payload, payload_size, payload_offset); + if (n < 0) + return log_error_errno(errno, "Failed to read ZBOOT payload: %m"); + if ((uint32_t) n < payload_size) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read of ZBOOT payload."); + + _cleanup_free_ void *decompressed = NULL; + size_t decompressed_size; + r = decompress_blob(c, payload, payload_size, &decompressed, &decompressed_size, /* dst_max= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to decompress ZBOOT payload: %m"); + + payload = mfree(payload); + + _cleanup_close_ int memfd = memfd_new("kexec-kernel"); + if (memfd < 0) + return log_error_errno(memfd, "Failed to create memfd: %m"); + + r = loop_write(memfd, decompressed, decompressed_size); + if (r < 0) + return log_error_errno(r, "Failed to write decompressed kernel to memfd: %m"); + + if (lseek(memfd, 0, SEEK_SET) < 0) + return log_error_errno(errno, "Failed to seek memfd: %m"); + + return TAKE_FD(memfd); +} + +static int pe_section_to_memfd(int fd, const IMAGE_SECTION_HEADER *section, const char *name) { + int r; + + assert(fd >= 0); + assert(section); + + uint32_t offset = le32toh(section->PointerToRawData), + size = MIN(le32toh(section->VirtualSize), le32toh(section->SizeOfRawData)); + + _cleanup_close_ int memfd = memfd_new(name); + if (memfd < 0) + return log_error_errno(memfd, "Failed to create memfd for PE section '%s': %m", name); + + if (lseek(fd, offset, SEEK_SET) < 0) + return log_error_errno(errno, "Failed to seek to PE section '%s': %m", name); + + r = copy_bytes(fd, memfd, size, /* copy_flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to copy PE section '%s': %m", name); + + if (lseek(memfd, 0, SEEK_SET) < 0) + return log_error_errno(errno, "Failed to seek memfd: %m"); + + return TAKE_FD(memfd); +} + +static int extract_uki(const char *path, int fd, int *ret_kernel_fd, int *ret_initrd_fd) { + int r; + + assert(fd >= 0); + assert(ret_kernel_fd); + + _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL; + _cleanup_free_ PeHeader *pe_header = NULL; + r = pe_load_headers(fd, &dos_header, &pe_header); + if (r < 0) + return log_debug_errno(r, "Not a valid PE file '%s': %m", path); + + _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + r = pe_load_sections(fd, dos_header, pe_header, §ions); + if (r < 0) + return log_debug_errno(r, "Failed to load PE sections from '%s': %m", path); + + if (!pe_is_uki(pe_header, sections)) + return 0; /* Not a UKI */ + + /* FIXME: we currently only extract .linux and .initrd, but sd-stub does a lot more: + * profiles, .cmdline, .dtb/.dtbauto, .ucode, .pcrsig/.pcrpkey, sidecar addons, + * credentials, sysexts/confexts, and TPM PCR measurements. */ + + const IMAGE_SECTION_HEADER *linux_section = pe_header_find_section(pe_header, sections, ".linux"); + if (!linux_section) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "UKI '%s' has no .linux section.", path); + + log_debug("Detected UKI image '%s', extracting .linux section.", path); + + _cleanup_close_ int kernel_memfd = pe_section_to_memfd(fd, linux_section, "kexec-uki-kernel"); + if (kernel_memfd < 0) + return kernel_memfd; + + _cleanup_close_ int initrd_memfd = -EBADF; + if (ret_initrd_fd) { + const IMAGE_SECTION_HEADER *initrd_section = pe_header_find_section(pe_header, sections, ".initrd"); + if (initrd_section) { + log_debug("Extracting .initrd section from UKI '%s'.", path); + + initrd_memfd = pe_section_to_memfd(fd, initrd_section, "kexec-uki-initrd"); + if (initrd_memfd < 0) + return initrd_memfd; + } + } + + *ret_kernel_fd = TAKE_FD(kernel_memfd); + if (ret_initrd_fd) + *ret_initrd_fd = TAKE_FD(initrd_memfd); + + return 1; +} + +int kexec_maybe_decompress_kernel(const char *path, int fd, int *ret_kernel_fd, int *ret_initrd_fd) { + uint8_t magic[8]; + ssize_t n; + int r; + + assert(fd >= 0); + assert(ret_kernel_fd); + + n = pread(fd, magic, sizeof(magic), 0); + if (n < 0) + return log_error_errno(errno, "Failed to read kernel magic from '%s': %m", path); + if ((size_t) n < sizeof(magic)) + /* Too small to detect, pass through as-is */ + return 0; + + if (magic[0] == 'M' && magic[1] == 'Z') { + + if (magic[4] == 'z' && magic[5] == 'i' && magic[6] == 'm' && magic[7] == 'g') { + struct zboot_header h; + + n = pread(fd, &h, sizeof(h), 0); + if (n < 0) + return log_error_errno(errno, "Failed to read ZBOOT header from '%s': %m", path); + if ((size_t) n < sizeof(h)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Short read of ZBOOT header from '%s'.", path); + + char comp_type[sizeof(h.comp_type) + 1]; + memcpy(comp_type, h.comp_type, sizeof(h.comp_type)); + comp_type[sizeof(h.comp_type)] = '\0'; + + uint32_t payload_offset = le32toh(h.payload_offset), + payload_size = le32toh(h.payload_size); + + log_debug("Detected ZBOOT image '%s' (compression=%s, offset=%"PRIu32", size=%"PRIu32")", + path, comp_type, payload_offset, payload_size); + + r = decompress_zboot_to_memfd(fd, payload_offset, payload_size, comp_type); + if (r < 0) + return r; + + *ret_kernel_fd = r; + return 1; + } + + /* MZ but not ZBOOT — check if it's a UKI */ + return extract_uki(path, fd, ret_kernel_fd, ret_initrd_fd); + } + + Compression c = compression_detect_from_magic(magic); + if (c < 0) + /* Not a recognized compressed format, pass through as-is */ + return 0; + + log_debug("Detected %s-compressed kernel '%s', decompressing.", compression_to_string(c), path); + + /* Seek back to start before decompression */ + if (lseek(fd, 0, SEEK_SET) < 0) + return log_error_errno(errno, "Failed to seek kernel fd: %m"); + + r = decompress_to_memfd(c, fd); + if (r < 0) + return r; + + *ret_kernel_fd = r; + return 1; +} + int create_shutdown_run_nologin_or_warn(void) { int r; diff --git a/src/shared/reboot-util.h b/src/shared/reboot-util.h index 4548903a4c311..658d065ce918b 100644 --- a/src/shared/reboot-util.h +++ b/src/shared/reboot-util.h @@ -28,4 +28,6 @@ bool shall_restore_state(void); bool kexec_loaded(void); int kexec(void); +int kexec_maybe_decompress_kernel(const char *path, int fd, int *ret_kernel_fd, int *ret_initrd_fd); + int create_shutdown_run_nologin_or_warn(void); diff --git a/src/systemctl/systemctl-start-special.c b/src/systemctl/systemctl-start-special.c index d947f1f9e4101..b7d10eb891d6c 100644 --- a/src/systemctl/systemctl-start-special.c +++ b/src/systemctl/systemctl-start-special.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include #include #include "sd-bus.h" @@ -8,6 +9,7 @@ #include "bus-error.h" #include "bus-locator.h" #include "efivars.h" +#include "fd-util.h" #include "log.h" #include "parse-util.h" #include "path-util.h" @@ -23,9 +25,6 @@ #include "systemctl-util.h" static int load_kexec_kernel(void) { - _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; - _cleanup_free_ char *kernel = NULL, *initrd = NULL, *options = NULL; - const BootEntry *e; int r; if (kexec_loaded()) { @@ -33,9 +32,7 @@ static int load_kexec_kernel(void) { return 0; } - if (access(KEXEC, X_OK) < 0) - return log_error_errno(errno, KEXEC" is not available: %m"); - + _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; r = boot_config_load_auto(&config, NULL, NULL); if (r == -ENOKEY) /* The call doesn't log about ENOKEY, let's do so here. */ @@ -51,7 +48,7 @@ static int load_kexec_kernel(void) { if (r < 0) return r; - e = boot_config_default_entry(&config); + const BootEntry *e = boot_config_default_entry(&config); if (!e) return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No boot loader entry suitable as default, refusing to guess."); @@ -65,29 +62,82 @@ static int load_kexec_kernel(void) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Boot entry specifies multiple initrds, which is not supported currently."); + _cleanup_free_ char *kernel = NULL; kernel = path_join(e->root, e->kernel); if (!kernel) return log_oom(); + _cleanup_free_ char *initrd = NULL; if (!strv_isempty(e->initrd)) { initrd = path_join(e->root, e->initrd[0]); if (!initrd) return log_oom(); } - options = strv_join(e->options, " "); + _cleanup_free_ char *options = strv_join(e->options, " "); if (!options) return log_oom(); log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, - "%s "KEXEC" --load \"%s\" --append \"%s\"%s%s%s", - arg_dry_run ? "Would run" : "Running", + "%s %s kernel=\"%s\" cmdline=\"%s\"%s%s%s", + arg_dry_run ? "Would call" : "Calling", + HAVE_KEXEC_FILE_LOAD_SYSCALL ? "kexec_file_load()" : "kexec", kernel, options, - initrd ? " --initrd \"" : NULL, strempty(initrd), initrd ? "\"" : ""); + initrd ? " initrd=\"" : "", strempty(initrd), initrd ? "\"" : ""); if (arg_dry_run) return 0; +#if HAVE_KEXEC_FILE_LOAD_SYSCALL + _cleanup_close_ int kernel_fd = open(kernel, O_RDONLY|O_CLOEXEC); + if (kernel_fd < 0) + return log_error_errno(errno, "Failed to open kernel '%s': %m", kernel); + + _cleanup_close_ int initrd_fd = -EBADF; + if (initrd) { + initrd_fd = open(initrd, O_RDONLY|O_CLOEXEC); + if (initrd_fd < 0) + return log_error_errno(errno, "Failed to open initrd '%s': %m", initrd); + } + + unsigned long flags = initrd ? 0 : KEXEC_FILE_NO_INITRAMFS; + + if (kexec_file_load(kernel_fd, initrd_fd, strlen(options) + 1, options, flags) >= 0) + return 0; + + int saved_errno = errno; + + if (saved_errno == ENOEXEC) { + /* The kernel didn't recognize the image format. Try decompressing or extracting the + * kernel (e.g. compressed Image, ZBOOT PE, or UKI) and loading again. */ + log_debug_errno(saved_errno, "Kernel rejected image, trying decompression/extraction: %m"); + + _cleanup_close_ int extracted_kernel_fd = -EBADF, extracted_initrd_fd = -EBADF; + r = kexec_maybe_decompress_kernel( + kernel, kernel_fd, &extracted_kernel_fd, + initrd_fd >= 0 ? NULL : &extracted_initrd_fd); + if (r < 0) + log_debug_errno(r, "Failed to decompress/extract kernel image, ignoring: %m"); + else if (r > 0) { + int final_initrd_fd = initrd_fd >= 0 ? initrd_fd : extracted_initrd_fd; + unsigned long final_flags = final_initrd_fd >= 0 ? 0 : KEXEC_FILE_NO_INITRAMFS; + + if (kexec_file_load(extracted_kernel_fd, final_initrd_fd, strlen(options) + 1, options, final_flags) >= 0) + return 0; + + saved_errno = errno; + } + } + + log_debug_errno(saved_errno, "kexec_file_load() failed, falling back to " KEXEC " binary: %m"); +#endif + + /* Fall back to kexec binary for architectures without kexec_file_load() or when the + * syscall fails (e.g. the kernel's kexec handler doesn't support this image format + * but kexec-tools might via the older kexec_load() code path). */ + if (access(KEXEC, X_OK) < 0) + return log_error_errno(errno, KEXEC " is not available: %m"); + r = pidref_safe_fork( "(kexec)", FORK_WAIT|FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, diff --git a/src/test/meson.build b/src/test/meson.build index 4cb77505f3dce..c93097181580d 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -346,6 +346,10 @@ executables += [ 'sources' : files('test-json.c'), 'dependencies' : libm, }, + test_template + { + 'sources' : files('test-kexec.c'), + 'link_with' : [libshared], + }, test_template + { 'sources' : files('test-libcrypt-util.c'), 'conditions' : ['HAVE_LIBCRYPT'], diff --git a/src/test/test-kexec.c b/src/test/test-kexec.c new file mode 100644 index 0000000000000..2a400f75d0cd8 --- /dev/null +++ b/src/test/test-kexec.c @@ -0,0 +1,261 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "alloc-util.h" +#include "compress.h" +#include "fd-util.h" +#include "io-util.h" +#include "reboot-util.h" +#include "string-util.h" +#include "tests.h" +#include "tmpfile-util.h" +#include "unaligned.h" + +static int find_kernel_image(char **ret) { + struct utsname u; + + ASSERT_OK_ERRNO(uname(&u)); + + /* Kernel image names vary across architectures and distributions: + * vmlinuz — compressed Linux kernel (x86, most distros) + * vmlinux — uncompressed ELF kernel (ppc64, s390) + * Image — uncompressed flat binary (arm64, riscv) + * Image.gz — gzip-compressed Image (arm64) + * zImage — compressed kernel (arm 32-bit) + * vmlinuz.efi — EFI ZBOOT PE wrapper (arm64 with CONFIG_EFI_ZBOOT) */ + static const char *const names[] = { + "vmlinuz", + "vmlinux", + "Image", + "Image.gz", + "zImage", + "vmlinuz.efi", + }; + + /* Try /usr/lib/modules// first (kernel-install convention), + * then /boot/-, then /boot/ */ + for (size_t i = 0; i < ELEMENTSOF(names); i++) { + _cleanup_free_ char *path = NULL; + + path = strjoin("/usr/lib/modules/", u.release, "/", names[i]); + if (!path) + return -ENOMEM; + + if (access(path, R_OK) >= 0) { + *ret = TAKE_PTR(path); + return 0; + } + } + + /* /boot may not be accessible without root, skip gracefully */ + if (access("/boot", R_OK) >= 0) { + for (size_t i = 0; i < ELEMENTSOF(names); i++) { + _cleanup_free_ char *path = NULL; + + path = strjoin("/boot/", names[i], "-", u.release); + if (!path) + return -ENOMEM; + + if (access(path, R_OK) >= 0) { + *ret = TAKE_PTR(path); + return 0; + } + } + + for (size_t i = 0; i < ELEMENTSOF(names); i++) { + _cleanup_free_ char *path = NULL; + + path = strjoin("/boot/", names[i]); + if (!path) + return -ENOMEM; + + if (access(path, R_OK) >= 0) { + *ret = TAKE_PTR(path); + return 0; + } + } + } + + return -ENOENT; +} + +TEST(passthrough_unrecognized) { + /* A file with unrecognized magic should pass through as-is (return 0) */ + _cleanup_close_ int fd = -EBADF; + _cleanup_(unlink_tempfilep) char path[] = "/tmp/test-kexec.XXXXXX"; + + ASSERT_OK(fd = mkostemp_safe(path)); + ASSERT_OK_EQ_ERRNO(write(fd, "HELLO WORLD\0", 12), 12); + ASSERT_OK_ERRNO(lseek(fd, 0, SEEK_SET)); + + _cleanup_close_ int kernel_fd = -EBADF, initrd_fd = -EBADF; + ASSERT_OK_ZERO(kexec_maybe_decompress_kernel(path, fd, &kernel_fd, &initrd_fd)); + ASSERT_EQ(kernel_fd, -EBADF); + ASSERT_EQ(initrd_fd, -EBADF); +} + +TEST(gzip_round_trip) { + _cleanup_close_ int src_fd = -EBADF, gz_fd = -EBADF; + _cleanup_(unlink_tempfilep) char + src_path[] = "/tmp/test-kexec-src.XXXXXX", + gz_path[] = "/tmp/test-kexec-gz.XXXXXX"; + int r; + + r = dlopen_zlib(); + if (r < 0) { + log_tests_skipped("zlib not available"); + return; + } + + /* Create a source file with known content */ + ASSERT_OK(src_fd = mkostemp_safe(src_path)); + char buf[4096]; + memset(buf, 'A', sizeof(buf)); + ASSERT_OK(loop_write(src_fd, buf, sizeof(buf))); + + /* Compress it with gzip */ + ASSERT_OK_ERRNO(lseek(src_fd, 0, SEEK_SET)); + ASSERT_OK(gz_fd = mkostemp_safe(gz_path)); + ASSERT_OK(compress_stream(COMPRESSION_GZIP, src_fd, gz_fd, UINT64_MAX, NULL)); + + /* Feed the gzip file to kexec_maybe_decompress_kernel */ + ASSERT_OK_ERRNO(lseek(gz_fd, 0, SEEK_SET)); + + _cleanup_close_ int kernel_fd = -EBADF, initrd_fd = -EBADF; + ASSERT_OK_POSITIVE(kexec_maybe_decompress_kernel(gz_path, gz_fd, &kernel_fd, &initrd_fd)); + ASSERT_GE(kernel_fd, 0); + ASSERT_EQ(initrd_fd, -EBADF); + + /* Verify the decompressed content matches the original */ + char result[4096]; + ASSERT_OK_EQ_ERRNO(pread(kernel_fd, result, sizeof(result), 0), (ssize_t) sizeof(result)); + ASSERT_EQ(memcmp(buf, result, sizeof(buf)), 0); +} + +TEST(zboot_synthetic) { + /* Construct a minimal ZBOOT header with a gzip-compressed payload */ + _cleanup_close_ int src_fd = -EBADF, gz_fd = -EBADF, zboot_fd = -EBADF; + _cleanup_(unlink_tempfilep) char + src_path[] = "/tmp/test-kexec-zboot-src.XXXXXX", + gz_path[] = "/tmp/test-kexec-zboot-gz.XXXXXX", + zboot_path[] = "/tmp/test-kexec-zboot.XXXXXX"; + int r; + + r = dlopen_zlib(); + if (r < 0) { + log_tests_skipped("zlib not available"); + return; + } + + /* Create and compress a payload */ + char payload[512]; + memset(payload, 'K', sizeof(payload)); + + ASSERT_OK(src_fd = mkostemp_safe(src_path)); + ASSERT_OK(loop_write(src_fd, payload, sizeof(payload))); + ASSERT_OK_ERRNO(lseek(src_fd, 0, SEEK_SET)); + + ASSERT_OK(gz_fd = mkostemp_safe(gz_path)); + ASSERT_OK(compress_stream(COMPRESSION_GZIP, src_fd, gz_fd, UINT64_MAX, NULL)); + + /* Read the compressed data */ + struct stat st; + ASSERT_OK_ERRNO(fstat(gz_fd, &st)); + size_t compressed_size = st.st_size; + _cleanup_free_ void *compressed = malloc(compressed_size); + ASSERT_NOT_NULL(compressed); + ASSERT_OK_EQ_ERRNO(pread(gz_fd, compressed, compressed_size, 0), (ssize_t) compressed_size); + + /* Build the ZBOOT header: + * 0x00: "MZ" + * 0x04: "zimg" + * 0x08: payload offset (LE32) + * 0x0C: payload size (LE32) + * 0x18: "gzip\0" */ + uint8_t header[0x40] = {}; + uint32_t payload_offset = sizeof(header); + + header[0] = 'M'; + header[1] = 'Z'; + memcpy(header + 0x04, "zimg", 4); + unaligned_write_le32(header + 0x08, payload_offset); + unaligned_write_le32(header + 0x0C, (uint32_t) compressed_size); + memcpy(header + 0x18, "gzip", 5); + + ASSERT_OK(zboot_fd = mkostemp_safe(zboot_path)); + ASSERT_OK(loop_write(zboot_fd, header, sizeof(header))); + ASSERT_OK(loop_write(zboot_fd, compressed, compressed_size)); + ASSERT_OK_ERRNO(lseek(zboot_fd, 0, SEEK_SET)); + + /* Test extraction */ + _cleanup_close_ int kernel_fd = -EBADF, initrd_fd = -EBADF; + ASSERT_OK_POSITIVE(kexec_maybe_decompress_kernel(zboot_path, zboot_fd, &kernel_fd, &initrd_fd)); + ASSERT_GE(kernel_fd, 0); + + /* Verify decompressed content matches original payload */ + char result[512]; + ASSERT_OK_EQ_ERRNO(pread(kernel_fd, result, sizeof(result), 0), (ssize_t) sizeof(result)); + ASSERT_EQ(memcmp(payload, result, sizeof(payload)), 0); +} + +TEST(system_kernel) { + _cleanup_free_ char *path = NULL; + _cleanup_close_ int fd = -EBADF; + int r; + + r = find_kernel_image(&path); + if (r < 0) { + log_tests_skipped_errno(r, "No kernel image found on this system"); + return; + } + + log_info("Found kernel image: %s", path); + + fd = open(path, O_RDONLY|O_CLOEXEC); + if (fd < 0) { + log_tests_skipped_errno(errno, "Cannot open kernel image '%s'", path); + return; + } + + _cleanup_close_ int kernel_fd = -EBADF, initrd_fd = -EBADF; + ASSERT_OK(r = kexec_maybe_decompress_kernel(path, fd, &kernel_fd, &initrd_fd)); + + if (r == 0) { + log_info("Kernel image was not compressed (passed through as-is)."); + return; + } + + log_info("Kernel image was decompressed/extracted successfully."); + ASSERT_GE(kernel_fd, 0); + + /* Verify the decompressed result is non-empty and looks plausible */ + struct stat st; + ASSERT_OK_ERRNO(fstat(kernel_fd, &st)); + ASSERT_GT(st.st_size, 0); + log_info("Decompressed kernel size: %zu bytes", (size_t) st.st_size); + + /* Read the first bytes and check for known kernel magic */ + uint8_t magic[8]; + ASSERT_OK_EQ_ERRNO(pread(kernel_fd, magic, sizeof(magic), 0), (ssize_t) sizeof(magic)); + + if (magic[0] == 0x7f && magic[1] == 'E' && magic[2] == 'L' && magic[3] == 'F') + log_info("Decompressed kernel is an ELF image."); + else if (magic[0] == 'M' && magic[1] == 'Z') + log_info("Decompressed kernel is a PE image."); + else + log_info("Decompressed kernel magic: %02x %02x %02x %02x %02x %02x %02x %02x", + magic[0], magic[1], magic[2], magic[3], + magic[4], magic[5], magic[6], magic[7]); + + /* If a UKI initrd was extracted, verify it too */ + if (initrd_fd >= 0) { + ASSERT_OK_ERRNO(fstat(initrd_fd, &st)); + ASSERT_GT(st.st_size, 0); + log_info("Extracted initrd size: %zu bytes", (size_t) st.st_size); + } +} + +DEFINE_TEST_MAIN(LOG_DEBUG); From 4c9ce728e788be00749a8718ff24c56c01ddb4ca Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 27 Mar 2026 23:53:43 +0100 Subject: [PATCH 0940/1296] mkosi: Drop kexec-tools Not needed anymore now that we use kexec_file_load(). --- .github/workflows/unit-tests-musl.yml | 1 - mkosi/mkosi.conf | 1 - .../usr/lib/systemd/system-preset/00-mkosi.preset | 3 --- 3 files changed, 5 deletions(-) diff --git a/.github/workflows/unit-tests-musl.yml b/.github/workflows/unit-tests-musl.yml index 2120eddeeb1dc..a5b619796f2b6 100644 --- a/.github/workflows/unit-tests-musl.yml +++ b/.github/workflows/unit-tests-musl.yml @@ -53,7 +53,6 @@ jobs: iproute2 iptables-dev kbd - kexec-tools kmod kmod-dev libapparmor-dev diff --git a/mkosi/mkosi.conf b/mkosi/mkosi.conf index 3b726d840e519..22547a5a1f948 100644 --- a/mkosi/mkosi.conf +++ b/mkosi/mkosi.conf @@ -102,7 +102,6 @@ Packages= gzip jq kbd - kexec-tools kmod less lsof diff --git a/mkosi/mkosi.extra.common/usr/lib/systemd/system-preset/00-mkosi.preset b/mkosi/mkosi.extra.common/usr/lib/systemd/system-preset/00-mkosi.preset index e87172ad86b2a..d7774e03f64d5 100644 --- a/mkosi/mkosi.extra.common/usr/lib/systemd/system-preset/00-mkosi.preset +++ b/mkosi/mkosi.extra.common/usr/lib/systemd/system-preset/00-mkosi.preset @@ -9,9 +9,6 @@ disable dnsmasq.service disable isc-dhcp-server.service disable isc-dhcp-server6.service -# Pulled in via dracut-network by kexec-tools on Fedora. -disable NetworkManager* - # Make sure dbus-broker is started by default on Debian/Ubuntu. enable dbus-broker.service From 4bbdc8a6a2eaca3b717810bbae0265eb375ab68c Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 22 Dec 2025 11:22:34 +0100 Subject: [PATCH 0941/1296] nspawn: Add --restrict-address-families= option Add a new --restrict-address-families= command line option and corresponding RestrictAddressFamilies= setting for .nspawn files to restrict which socket address families may be used inside a container. Many address families such as AF_VSOCK and AF_NETLINK are not network-namespaced, so restricting access to them in containers improves isolation. The option supports allowlist and denylist modes (via ~ prefix), as well as "none" to block all families, matching the semantics of RestrictAddressFamilies= in unit files. The address family parsing logic is extracted into a shared parse_address_families() helper in parse-helpers.c, which is now also used by config_parse_address_families() in load-fragment.c. This is currently opt-in. In a future version, the default will be changed to restrict address families to AF_INET, AF_INET6 and AF_UNIX. --- NEWS | 7 ++ man/systemd-nspawn.xml | 22 +++++ man/systemd.nspawn.xml | 12 +++ shell-completion/bash/systemd-nspawn | 3 +- shell-completion/zsh/_systemd-nspawn | 1 + src/core/load-fragment.c | 69 +++----------- src/nspawn/nspawn-gperf.gperf | 129 ++++++++++++++------------- src/nspawn/nspawn-seccomp.c | 17 +++- src/nspawn/nspawn-seccomp.h | 7 +- src/nspawn/nspawn-settings.c | 32 +++++++ src/nspawn/nspawn-settings.h | 78 ++++++++-------- src/nspawn/nspawn.c | 27 +++++- src/shared/parse-helpers.c | 58 ++++++++++++ src/shared/parse-helpers.h | 2 + 14 files changed, 300 insertions(+), 164 deletions(-) diff --git a/NEWS b/NEWS index 2d32bd08b4a01..b440af5939618 100644 --- a/NEWS +++ b/NEWS @@ -30,6 +30,13 @@ CHANGES WITH 261 in spe: attestation environments which use hardware CC registers and not the TPM quote. + * systemd-nspawn gained a new --restrict-address-families= option (and + corresponding RestrictAddressFamilies= setting in .nspawn files) to + restrict which socket address families may be used in the container. + This is currently opt-in. In a future version, the default will be + changed to restrict socket address families to AF_INET, AF_INET6 and + AF_UNIX. + New features: * A new tmpfiles.d/root.conf has been added that sets permissions diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 5c7acf51594bc..045aa60db81f7 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -1340,6 +1340,28 @@ After=sys-subsystem-net-devices-ens1.device + + + + Restrict the socket address families accessible to the container. Takes a + space-separated list of address family names, such as AF_INET, + AF_INET6 or AF_UNIX. When prefixed with + ~ the listed address families will be prohibited, otherwise they will be permitted + (allowlisted). Use the special value none to prohibit all address families. This + option may be specified more than once, in which case the configured lists are combined. If both a + positive and a negative list are configured, the negative list takes precedence over the positive + list. + + Note that currently this option defaults to no restrictions, i.e. all address families are + accessible. In a future version of systemd, the default will be changed to restrict address families to + AF_INET, AF_INET6 and AF_UNIX. Use + (with an empty argument) or set + RestrictAddressFamilies= in a .nspawn file to opt out of + filtering explicitly. + + + + diff --git a/man/systemd.nspawn.xml b/man/systemd.nspawn.xml index bf9526df8069f..2927980685250 100644 --- a/man/systemd.nspawn.xml +++ b/man/systemd.nspawn.xml @@ -340,6 +340,18 @@ + + RestrictAddressFamilies= + + Restricts the socket address families accessible to the container. This is equivalent + to the command line switch, and takes the same list + parameter. See + systemd-nspawn1 for + details. + + + + LimitCPU= LimitFSIZE= diff --git a/shell-completion/bash/systemd-nspawn b/shell-completion/bash/systemd-nspawn index 08ff25d906c1f..b39d3cbd6d854 100644 --- a/shell-completion/bash/systemd-nspawn +++ b/shell-completion/bash/systemd-nspawn @@ -77,7 +77,8 @@ _systemd_nspawn() { --pivot-root --property --private-users --private-users-ownership --network-namespace-path --network-ipvlan --network-veth-extra --network-zone -p --port --system-call-filter --overlay --overlay-ro --settings --rlimit --hostname --no-new-privileges --oom-score-adjust --cpu-affinity - --resolv-conf --timezone --root-hash-sig --background --oci-bundle --verity-data' + --resolv-conf --timezone --root-hash-sig --background --oci-bundle --verity-data + --restrict-address-families' ) _init_completion || return diff --git a/shell-completion/zsh/_systemd-nspawn b/shell-completion/zsh/_systemd-nspawn index fa79b7f8d8679..ee28fa74759ab 100644 --- a/shell-completion/zsh/_systemd-nspawn +++ b/shell-completion/zsh/_systemd-nspawn @@ -53,4 +53,5 @@ _arguments \ '--volatile=[Run the system in volatile mode.]:volatile:(no yes state)' \ "--notify-ready=[Control when the ready notification is sent]:options:(yes no)" \ "--suppress-sync=[Control whether to suppress disk synchronization for the container payload]:options:(yes no)" \ + '--restrict-address-families=[Restrict socket address families accessible in the container.]: : _message "address families"' \ '*:: : _normal' diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 274fd82514d4a..52005c8c43600 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -10,7 +10,6 @@ #include "sd-bus.h" #include "sd-messages.h" -#include "af-list.h" #include "all-units.h" #include "alloc-util.h" #include "bpf-program.h" @@ -3474,72 +3473,26 @@ int config_parse_address_families( void *userdata) { ExecContext *c = data; - bool invert = false; + bool is_allowlist = c->address_families_allow_list; int r; assert(filename); assert(lvalue); assert(rvalue); - if (isempty(rvalue)) { - /* Empty assignment resets the list */ - c->address_families = set_free(c->address_families); - c->address_families_allow_list = false; - return 0; - } - - if (streq(rvalue, "none")) { - /* Forbid all address families. */ - c->address_families = set_free(c->address_families); - c->address_families_allow_list = true; + r = parse_address_families(rvalue, &c->address_families, &is_allowlist); + /* Copy back unconditionally: parse_address_families() may have partially populated + * c->address_families before failing, so keep is_allowlist in sync with that state. */ + c->address_families_allow_list = is_allowlist; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse address family, ignoring: %s", rvalue); return 0; } - if (rvalue[0] == '~') { - invert = true; - rvalue++; - } - - if (!c->address_families) { - c->address_families = set_new(NULL); - if (!c->address_families) - return log_oom(); - - c->address_families_allow_list = !invert; - } - - for (const char *p = rvalue;;) { - _cleanup_free_ char *word = NULL; - int af; - - r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Invalid syntax, ignoring: %s", rvalue); - return 0; - } - if (r == 0) - return 0; - - af = af_from_name(word); - if (af < 0) { - log_syntax(unit, LOG_WARNING, filename, line, af, - "Failed to parse address family, ignoring: %s", word); - continue; - } - - /* If we previously wanted to forbid an address family and now - * we want to allow it, then just remove it from the list. - */ - if (!invert == c->address_families_allow_list) { - r = set_put(c->address_families, INT_TO_PTR(af)); - if (r < 0) - return log_oom(); - } else - set_remove(c->address_families, INT_TO_PTR(af)); - } + return 0; } #endif diff --git a/src/nspawn/nspawn-gperf.gperf b/src/nspawn/nspawn-gperf.gperf index cdad70706e605..439e176e458b5 100644 --- a/src/nspawn/nspawn-gperf.gperf +++ b/src/nspawn/nspawn-gperf.gperf @@ -19,67 +19,68 @@ struct ConfigPerfItem; %struct-type %includes %% -Exec.Boot, config_parse_boot, 0, 0 -Exec.Ephemeral, config_parse_tristate, 0, offsetof(Settings, ephemeral) -Exec.ProcessTwo, config_parse_pid2, 0, 0 -Exec.Parameters, config_parse_strv, 0, offsetof(Settings, parameters) -Exec.Environment, config_parse_strv, 0, offsetof(Settings, environment) -Exec.User, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Settings, user) -Exec.Capability, config_parse_capability, 0, offsetof(Settings, capability) -Exec.AmbientCapability, config_parse_capability, 0, offsetof(Settings, ambient_capability) -Exec.DropCapability, config_parse_capability, 0, offsetof(Settings, drop_capability) -Exec.KillSignal, config_parse_signal, 0, offsetof(Settings, kill_signal) -Exec.Personality, config_parse_personality, 0, offsetof(Settings, personality) -Exec.MachineID, config_parse_id128, 0, offsetof(Settings, machine_id) -Exec.WorkingDirectory, config_parse_path, 0, offsetof(Settings, working_directory) -Exec.PivotRoot, config_parse_pivot_root, 0, 0 -Exec.PrivateUsers, config_parse_private_users, 0, 0 -Exec.PrivateUsersDelegate, config_parse_unsigned, 0, offsetof(Settings, delegate_container_ranges) -Exec.NotifyReady, config_parse_tristate, 0, offsetof(Settings, notify_ready) -Exec.SystemCallFilter, config_parse_syscall_filter, 0, 0 -Exec.LimitCPU, config_parse_rlimit, RLIMIT_CPU, offsetof(Settings, rlimit) -Exec.LimitFSIZE, config_parse_rlimit, RLIMIT_FSIZE, offsetof(Settings, rlimit) -Exec.LimitDATA, config_parse_rlimit, RLIMIT_DATA, offsetof(Settings, rlimit) -Exec.LimitSTACK, config_parse_rlimit, RLIMIT_STACK, offsetof(Settings, rlimit) -Exec.LimitCORE, config_parse_rlimit, RLIMIT_CORE, offsetof(Settings, rlimit) -Exec.LimitRSS, config_parse_rlimit, RLIMIT_RSS, offsetof(Settings, rlimit) -Exec.LimitNOFILE, config_parse_rlimit, RLIMIT_NOFILE, offsetof(Settings, rlimit) -Exec.LimitAS, config_parse_rlimit, RLIMIT_AS, offsetof(Settings, rlimit) -Exec.LimitNPROC, config_parse_rlimit, RLIMIT_NPROC, offsetof(Settings, rlimit) -Exec.LimitMEMLOCK, config_parse_rlimit, RLIMIT_MEMLOCK, offsetof(Settings, rlimit) -Exec.LimitLOCKS, config_parse_rlimit, RLIMIT_LOCKS, offsetof(Settings, rlimit) -Exec.LimitSIGPENDING, config_parse_rlimit, RLIMIT_SIGPENDING, offsetof(Settings, rlimit) -Exec.LimitMSGQUEUE, config_parse_rlimit, RLIMIT_MSGQUEUE, offsetof(Settings, rlimit) -Exec.LimitNICE, config_parse_rlimit, RLIMIT_NICE, offsetof(Settings, rlimit) -Exec.LimitRTPRIO, config_parse_rlimit, RLIMIT_RTPRIO, offsetof(Settings, rlimit) -Exec.LimitRTTIME, config_parse_rlimit, RLIMIT_RTTIME, offsetof(Settings, rlimit) -Exec.Hostname, config_parse_hostname, 0, offsetof(Settings, hostname) -Exec.NoNewPrivileges, config_parse_tristate, 0, offsetof(Settings, no_new_privileges) -Exec.OOMScoreAdjust, config_parse_oom_score_adjust, 0, 0 -Exec.CPUAffinity, config_parse_cpu_set, 0, offsetof(Settings, cpu_set) -Exec.ResolvConf, config_parse_resolv_conf, 0, offsetof(Settings, resolv_conf) -Exec.LinkJournal, config_parse_link_journal, 0, 0 -Exec.Timezone, config_parse_timezone_mode, 0, offsetof(Settings, timezone) -Exec.SuppressSync, config_parse_tristate, 0, offsetof(Settings, suppress_sync) -Files.ReadOnly, config_parse_tristate, 0, offsetof(Settings, read_only) -Files.Volatile, config_parse_volatile_mode, 0, offsetof(Settings, volatile_mode) -Files.Bind, config_parse_bind, 0, 0 -Files.BindReadOnly, config_parse_bind, 1, 0 -Files.TemporaryFileSystem, config_parse_tmpfs, 0, 0 -Files.Inaccessible, config_parse_inaccessible, 0, 0 -Files.Overlay, config_parse_overlay, 0, 0 -Files.OverlayReadOnly, config_parse_overlay, 1, 0 -Files.PrivateUsersChown, config_parse_userns_chown, 0, offsetof(Settings, userns_ownership) -Files.PrivateUsersOwnership, config_parse_userns_ownership, 0, offsetof(Settings, userns_ownership) -Files.BindUser, config_parse_bind_user, 0, offsetof(Settings, bind_user) -Files.BindUserShell, config_parse_bind_user_shell, 0, 0 -Network.Private, config_parse_tristate, 0, offsetof(Settings, private_network) -Network.NamespacePath, config_parse_path, 0, offsetof(Settings, network_namespace_path) -Network.Interface, config_parse_network_iface_pair, 0, offsetof(Settings, network_interfaces) -Network.MACVLAN, config_parse_macvlan_iface_pair, 0, offsetof(Settings, network_macvlan) -Network.IPVLAN, config_parse_ipvlan_iface_pair, 0, offsetof(Settings, network_ipvlan) -Network.VirtualEthernet, config_parse_tristate, 0, offsetof(Settings, network_veth) -Network.VirtualEthernetExtra, config_parse_veth_extra, 0, 0 -Network.Bridge, config_parse_ifname, 0, offsetof(Settings, network_bridge) -Network.Zone, config_parse_network_zone, 0, 0 -Network.Port, config_parse_expose_port, 0, 0 +Exec.Boot, config_parse_boot, 0, 0 +Exec.Ephemeral, config_parse_tristate, 0, offsetof(Settings, ephemeral) +Exec.ProcessTwo, config_parse_pid2, 0, 0 +Exec.Parameters, config_parse_strv, 0, offsetof(Settings, parameters) +Exec.Environment, config_parse_strv, 0, offsetof(Settings, environment) +Exec.User, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Settings, user) +Exec.Capability, config_parse_capability, 0, offsetof(Settings, capability) +Exec.AmbientCapability, config_parse_capability, 0, offsetof(Settings, ambient_capability) +Exec.DropCapability, config_parse_capability, 0, offsetof(Settings, drop_capability) +Exec.KillSignal, config_parse_signal, 0, offsetof(Settings, kill_signal) +Exec.Personality, config_parse_personality, 0, offsetof(Settings, personality) +Exec.MachineID, config_parse_id128, 0, offsetof(Settings, machine_id) +Exec.WorkingDirectory, config_parse_path, 0, offsetof(Settings, working_directory) +Exec.PivotRoot, config_parse_pivot_root, 0, 0 +Exec.PrivateUsers, config_parse_private_users, 0, 0 +Exec.PrivateUsersDelegate, config_parse_unsigned, 0, offsetof(Settings, delegate_container_ranges) +Exec.NotifyReady, config_parse_tristate, 0, offsetof(Settings, notify_ready) +Exec.SystemCallFilter, config_parse_syscall_filter, 0, 0 +Exec.LimitCPU, config_parse_rlimit, RLIMIT_CPU, offsetof(Settings, rlimit) +Exec.LimitFSIZE, config_parse_rlimit, RLIMIT_FSIZE, offsetof(Settings, rlimit) +Exec.LimitDATA, config_parse_rlimit, RLIMIT_DATA, offsetof(Settings, rlimit) +Exec.LimitSTACK, config_parse_rlimit, RLIMIT_STACK, offsetof(Settings, rlimit) +Exec.LimitCORE, config_parse_rlimit, RLIMIT_CORE, offsetof(Settings, rlimit) +Exec.LimitRSS, config_parse_rlimit, RLIMIT_RSS, offsetof(Settings, rlimit) +Exec.LimitNOFILE, config_parse_rlimit, RLIMIT_NOFILE, offsetof(Settings, rlimit) +Exec.LimitAS, config_parse_rlimit, RLIMIT_AS, offsetof(Settings, rlimit) +Exec.LimitNPROC, config_parse_rlimit, RLIMIT_NPROC, offsetof(Settings, rlimit) +Exec.LimitMEMLOCK, config_parse_rlimit, RLIMIT_MEMLOCK, offsetof(Settings, rlimit) +Exec.LimitLOCKS, config_parse_rlimit, RLIMIT_LOCKS, offsetof(Settings, rlimit) +Exec.LimitSIGPENDING, config_parse_rlimit, RLIMIT_SIGPENDING, offsetof(Settings, rlimit) +Exec.LimitMSGQUEUE, config_parse_rlimit, RLIMIT_MSGQUEUE, offsetof(Settings, rlimit) +Exec.LimitNICE, config_parse_rlimit, RLIMIT_NICE, offsetof(Settings, rlimit) +Exec.LimitRTPRIO, config_parse_rlimit, RLIMIT_RTPRIO, offsetof(Settings, rlimit) +Exec.LimitRTTIME, config_parse_rlimit, RLIMIT_RTTIME, offsetof(Settings, rlimit) +Exec.Hostname, config_parse_hostname, 0, offsetof(Settings, hostname) +Exec.NoNewPrivileges, config_parse_tristate, 0, offsetof(Settings, no_new_privileges) +Exec.OOMScoreAdjust, config_parse_oom_score_adjust, 0, 0 +Exec.CPUAffinity, config_parse_cpu_set, 0, offsetof(Settings, cpu_set) +Exec.ResolvConf, config_parse_resolv_conf, 0, offsetof(Settings, resolv_conf) +Exec.LinkJournal, config_parse_link_journal, 0, 0 +Exec.Timezone, config_parse_timezone_mode, 0, offsetof(Settings, timezone) +Exec.SuppressSync, config_parse_tristate, 0, offsetof(Settings, suppress_sync) +Exec.RestrictAddressFamilies, config_parse_restrict_address_families, 0, 0 +Files.ReadOnly, config_parse_tristate, 0, offsetof(Settings, read_only) +Files.Volatile, config_parse_volatile_mode, 0, offsetof(Settings, volatile_mode) +Files.Bind, config_parse_bind, 0, 0 +Files.BindReadOnly, config_parse_bind, 1, 0 +Files.TemporaryFileSystem, config_parse_tmpfs, 0, 0 +Files.Inaccessible, config_parse_inaccessible, 0, 0 +Files.Overlay, config_parse_overlay, 0, 0 +Files.OverlayReadOnly, config_parse_overlay, 1, 0 +Files.PrivateUsersChown, config_parse_userns_chown, 0, offsetof(Settings, userns_ownership) +Files.PrivateUsersOwnership, config_parse_userns_ownership, 0, offsetof(Settings, userns_ownership) +Files.BindUser, config_parse_bind_user, 0, offsetof(Settings, bind_user) +Files.BindUserShell, config_parse_bind_user_shell, 0, 0 +Network.Private, config_parse_tristate, 0, offsetof(Settings, private_network) +Network.NamespacePath, config_parse_path, 0, offsetof(Settings, network_namespace_path) +Network.Interface, config_parse_network_iface_pair, 0, offsetof(Settings, network_interfaces) +Network.MACVLAN, config_parse_macvlan_iface_pair, 0, offsetof(Settings, network_macvlan) +Network.IPVLAN, config_parse_ipvlan_iface_pair, 0, offsetof(Settings, network_ipvlan) +Network.VirtualEthernet, config_parse_tristate, 0, offsetof(Settings, network_veth) +Network.VirtualEthernetExtra, config_parse_veth_extra, 0, 0 +Network.Bridge, config_parse_ifname, 0, offsetof(Settings, network_bridge) +Network.Zone, config_parse_network_zone, 0, 0 +Network.Port, config_parse_expose_port, 0, 0 diff --git a/src/nspawn/nspawn-seccomp.c b/src/nspawn/nspawn-seccomp.c index d85a30ee9f9cf..beffd5da8a862 100644 --- a/src/nspawn/nspawn-seccomp.c +++ b/src/nspawn/nspawn-seccomp.c @@ -7,6 +7,7 @@ #include "log.h" #include "nspawn-seccomp.h" #include "seccomp-util.h" +#include "set.h" #include "strv.h" #if HAVE_SECCOMP @@ -172,7 +173,13 @@ static int add_syscall_filters( return 0; } -int setup_seccomp(uint64_t cap_list_retain, char **syscall_allow_list, char **syscall_deny_list) { +int setup_seccomp( + uint64_t cap_list_retain, + char **syscall_allow_list, + char **syscall_deny_list, + Set *restrict_address_families, + bool restrict_address_families_is_allowlist) { + uint32_t arch; int r; @@ -241,12 +248,18 @@ int setup_seccomp(uint64_t cap_list_retain, char **syscall_allow_list, char **sy seccomp_arch_to_string(arch)); } + if (restrict_address_families_is_allowlist || !set_isempty(restrict_address_families)) { + r = seccomp_restrict_address_families(restrict_address_families, restrict_address_families_is_allowlist); + if (r < 0) + return log_error_errno(r, "Failed to install address family filter: %m"); + } + return 0; } #else -int setup_seccomp(uint64_t cap_list_retain, char **syscall_allow_list, char **syscall_deny_list) { +int setup_seccomp(uint64_t cap_list_retain, char **syscall_allow_list, char **syscall_deny_list, Set *restrict_address_families, bool restrict_address_families_is_allowlist) { return 0; } diff --git a/src/nspawn/nspawn-seccomp.h b/src/nspawn/nspawn-seccomp.h index 31520a09300d3..52232ad56aebb 100644 --- a/src/nspawn/nspawn-seccomp.h +++ b/src/nspawn/nspawn-seccomp.h @@ -3,4 +3,9 @@ #include "shared-forward.h" -int setup_seccomp(uint64_t cap_list_retain, char **syscall_allow_list, char **syscall_deny_list); +int setup_seccomp( + uint64_t cap_list_retain, + char **syscall_allow_list, + char **syscall_deny_list, + Set *restrict_address_families, + bool restrict_address_families_is_allowlist); diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c index c058ab28f71de..9abd5024a5049 100644 --- a/src/nspawn/nspawn-settings.c +++ b/src/nspawn/nspawn-settings.c @@ -12,9 +12,11 @@ #include "nspawn-mount.h" #include "nspawn-network.h" #include "nspawn-settings.h" +#include "parse-helpers.h" #include "parse-util.h" #include "process-util.h" #include "rlimit-util.h" +#include "set.h" #include "socket-util.h" #include "string-table.h" #include "string-util.h" @@ -137,6 +139,7 @@ Settings* settings_free(Settings *s) { rlimit_free_all(s->rlimit); free(s->hostname); cpu_set_done(&s->cpu_set); + set_free(s->restrict_address_families); strv_free(s->bind_user); free(s->bind_user_shell); @@ -1054,3 +1057,32 @@ int config_parse_bind_user_shell( return 0; } + +int config_parse_restrict_address_families( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Settings *settings = ASSERT_PTR(data); + int r; + + assert(rvalue); + + r = parse_address_families(rvalue, &settings->restrict_address_families, &settings->restrict_address_families_is_allowlist); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse address family, ignoring: %s", rvalue); + return 0; + } + + return 0; +} diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h index 84c342b83c1eb..c2e079f0563c1 100644 --- a/src/nspawn/nspawn-settings.h +++ b/src/nspawn/nspawn-settings.h @@ -92,43 +92,44 @@ typedef enum ConsoleMode { } ConsoleMode; typedef enum SettingsMask { - SETTING_START_MODE = UINT64_C(1) << 0, - SETTING_ENVIRONMENT = UINT64_C(1) << 1, - SETTING_USER = UINT64_C(1) << 2, - SETTING_CAPABILITY = UINT64_C(1) << 3, - SETTING_KILL_SIGNAL = UINT64_C(1) << 4, - SETTING_PERSONALITY = UINT64_C(1) << 5, - SETTING_MACHINE_ID = UINT64_C(1) << 6, - SETTING_NETWORK = UINT64_C(1) << 7, - SETTING_EXPOSE_PORTS = UINT64_C(1) << 8, - SETTING_READ_ONLY = UINT64_C(1) << 9, - SETTING_VOLATILE_MODE = UINT64_C(1) << 10, - SETTING_CUSTOM_MOUNTS = UINT64_C(1) << 11, - SETTING_WORKING_DIRECTORY = UINT64_C(1) << 12, - SETTING_USERNS = UINT64_C(1) << 13, - SETTING_NOTIFY_READY = UINT64_C(1) << 14, - SETTING_PIVOT_ROOT = UINT64_C(1) << 15, - SETTING_SYSCALL_FILTER = UINT64_C(1) << 16, - SETTING_HOSTNAME = UINT64_C(1) << 17, - SETTING_NO_NEW_PRIVILEGES = UINT64_C(1) << 18, - SETTING_OOM_SCORE_ADJUST = UINT64_C(1) << 19, - SETTING_CPU_AFFINITY = UINT64_C(1) << 20, - SETTING_RESOLV_CONF = UINT64_C(1) << 21, - SETTING_LINK_JOURNAL = UINT64_C(1) << 22, - SETTING_TIMEZONE = UINT64_C(1) << 23, - SETTING_EPHEMERAL = UINT64_C(1) << 24, - SETTING_SLICE = UINT64_C(1) << 25, - SETTING_DIRECTORY = UINT64_C(1) << 26, - SETTING_USE_CGNS = UINT64_C(1) << 27, - SETTING_CLONE_NS_FLAGS = UINT64_C(1) << 28, - SETTING_CONSOLE_MODE = UINT64_C(1) << 29, - SETTING_CREDENTIALS = UINT64_C(1) << 30, - SETTING_BIND_USER = UINT64_C(1) << 31, - SETTING_BIND_USER_SHELL = UINT64_C(1) << 32, - SETTING_SUPPRESS_SYNC = UINT64_C(1) << 33, - SETTING_RLIMIT_FIRST = UINT64_C(1) << 34, /* we define one bit per resource limit here */ - SETTING_RLIMIT_LAST = UINT64_C(1) << (34 + _RLIMIT_MAX - 1), - _SETTINGS_MASK_ALL = (UINT64_C(1) << (34 + _RLIMIT_MAX)) -1, + SETTING_START_MODE = UINT64_C(1) << 0, + SETTING_ENVIRONMENT = UINT64_C(1) << 1, + SETTING_USER = UINT64_C(1) << 2, + SETTING_CAPABILITY = UINT64_C(1) << 3, + SETTING_KILL_SIGNAL = UINT64_C(1) << 4, + SETTING_PERSONALITY = UINT64_C(1) << 5, + SETTING_MACHINE_ID = UINT64_C(1) << 6, + SETTING_NETWORK = UINT64_C(1) << 7, + SETTING_EXPOSE_PORTS = UINT64_C(1) << 8, + SETTING_READ_ONLY = UINT64_C(1) << 9, + SETTING_VOLATILE_MODE = UINT64_C(1) << 10, + SETTING_CUSTOM_MOUNTS = UINT64_C(1) << 11, + SETTING_WORKING_DIRECTORY = UINT64_C(1) << 12, + SETTING_USERNS = UINT64_C(1) << 13, + SETTING_NOTIFY_READY = UINT64_C(1) << 14, + SETTING_PIVOT_ROOT = UINT64_C(1) << 15, + SETTING_SYSCALL_FILTER = UINT64_C(1) << 16, + SETTING_HOSTNAME = UINT64_C(1) << 17, + SETTING_NO_NEW_PRIVILEGES = UINT64_C(1) << 18, + SETTING_OOM_SCORE_ADJUST = UINT64_C(1) << 19, + SETTING_CPU_AFFINITY = UINT64_C(1) << 20, + SETTING_RESOLV_CONF = UINT64_C(1) << 21, + SETTING_LINK_JOURNAL = UINT64_C(1) << 22, + SETTING_TIMEZONE = UINT64_C(1) << 23, + SETTING_EPHEMERAL = UINT64_C(1) << 24, + SETTING_SLICE = UINT64_C(1) << 25, + SETTING_DIRECTORY = UINT64_C(1) << 26, + SETTING_USE_CGNS = UINT64_C(1) << 27, + SETTING_CLONE_NS_FLAGS = UINT64_C(1) << 28, + SETTING_CONSOLE_MODE = UINT64_C(1) << 29, + SETTING_CREDENTIALS = UINT64_C(1) << 30, + SETTING_BIND_USER = UINT64_C(1) << 31, + SETTING_BIND_USER_SHELL = UINT64_C(1) << 32, + SETTING_SUPPRESS_SYNC = UINT64_C(1) << 33, + SETTING_RESTRICT_ADDRESS_FAMILIES = UINT64_C(1) << 34, + SETTING_RLIMIT_FIRST = UINT64_C(1) << 35, /* we define one bit per resource limit here */ + SETTING_RLIMIT_LAST = UINT64_C(1) << (35 + _RLIMIT_MAX - 1), + _SETTINGS_MASK_ALL = (UINT64_C(1) << (35 + _RLIMIT_MAX)) -1, _SETTING_FORCE_ENUM_WIDTH = UINT64_MAX } SettingsMask; @@ -190,6 +191,8 @@ typedef struct Settings { bool link_journal_try; TimezoneMode timezone; int suppress_sync; + Set *restrict_address_families; + bool restrict_address_families_is_allowlist; /* [Files] */ int read_only; @@ -277,6 +280,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_userns_chown); CONFIG_PARSER_PROTOTYPE(config_parse_userns_ownership); CONFIG_PARSER_PROTOTYPE(config_parse_bind_user); CONFIG_PARSER_PROTOTYPE(config_parse_bind_user_shell); +CONFIG_PARSER_PROTOTYPE(config_parse_restrict_address_families); DECLARE_STRING_TABLE_LOOKUP(resolv_conf_mode, ResolvConfMode); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index accf448ea97f2..b6332844db80c 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -89,6 +89,7 @@ #include "nspawn.h" #include "nsresource.h" #include "os-util.h" +#include "parse-helpers.h" #include "osc-context.h" #include "options.h" #include "pager.h" @@ -108,6 +109,7 @@ #include "runtime-scope.h" #include "seccomp-util.h" #include "selinux-util.h" +#include "set.h" #include "shift-uid.h" #include "signal-util.h" #include "siphash24.h" @@ -251,6 +253,8 @@ static char *arg_bind_user_shell = NULL; static bool arg_bind_user_shell_copy = false; static char **arg_bind_user_groups = NULL; static bool arg_suppress_sync = false; +static Set *arg_restrict_address_families = NULL; +static bool arg_restrict_address_families_is_allowlist = false; static char *arg_settings_filename = NULL; static Architecture arg_architecture = _ARCHITECTURE_INVALID; static ImagePolicy *arg_image_policy = NULL; @@ -295,6 +299,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_sysctl, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user_shell, freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user_groups, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_restrict_address_families, set_freep); STATIC_DESTRUCTOR_REGISTER(arg_settings_filename, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); STATIC_DESTRUCTOR_REGISTER(arg_background, freep); @@ -1122,6 +1127,14 @@ static int parse_argv(int argc, char *argv[]) { break; } + OPTION_LONG("restrict-address-families", "LIST", "Restrict socket address families to the given allowlist"): + r = parse_address_families(optarg, &arg_restrict_address_families, &arg_restrict_address_families_is_allowlist); + if (r < 0) + return log_error_errno(r, "Failed to parse --restrict-address-families= argument: %s", optarg); + + arg_settings_mask |= SETTING_RESTRICT_ADDRESS_FAMILIES; + break; + OPTION('Z', "selinux-context", "SECLABEL", "Set the SELinux security context to be used by processes in the container"): arg_selinux_context = arg; @@ -3456,7 +3469,7 @@ static int inner_child( } else #endif { - r = setup_seccomp(arg_caps_retain, arg_syscall_allow_list, arg_syscall_deny_list); + r = setup_seccomp(arg_caps_retain, arg_syscall_allow_list, arg_syscall_deny_list, arg_restrict_address_families, arg_restrict_address_families_is_allowlist); if (r < 0) return r; } @@ -4944,6 +4957,12 @@ static int merge_settings(Settings *settings, const char *path) { settings->suppress_sync >= 0) arg_suppress_sync = settings->suppress_sync; + if (!FLAGS_SET(arg_settings_mask, SETTING_RESTRICT_ADDRESS_FAMILIES) && + (settings->restrict_address_families || settings->restrict_address_families_is_allowlist)) { + set_free_and_replace(arg_restrict_address_families, settings->restrict_address_families); + arg_restrict_address_families_is_allowlist = settings->restrict_address_families_is_allowlist; + } + /* The following properties can only be set through the OCI settings logic, not from the command line, hence we * don't consult arg_settings_mask for them. */ @@ -5976,6 +5995,12 @@ static int run(int argc, char *argv[]) { if (r < 0) goto finish; + if (!FLAGS_SET(arg_settings_mask, SETTING_RESTRICT_ADDRESS_FAMILIES) && !arg_restrict_address_families) + log_notice("Note: in a future version of systemd-nspawn the default set of permitted socket address" + " families will be restricted to AF_INET, AF_INET6 and AF_UNIX." + " Use --restrict-address-families= to configure the set of permitted socket address" + " families, or set RestrictAddressFamilies= in a .nspawn file."); + /* If we're not unsharing the network namespace and are unsharing the user namespace, we won't have * permissions to bind ports in the container, so let's drop the CAP_NET_BIND_SERVICE capability to * indicate that. */ diff --git a/src/shared/parse-helpers.c b/src/shared/parse-helpers.c index 8a61f2e66997b..4e524bef37ed9 100644 --- a/src/shared/parse-helpers.c +++ b/src/shared/parse-helpers.c @@ -11,6 +11,7 @@ #include "parse-helpers.h" #include "parse-util.h" #include "path-util.h" +#include "set.h" #include "string-util.h" #include "utf8.h" @@ -86,6 +87,63 @@ int path_simplify_and_warn( return 0; } +int parse_address_families(const char *rvalue, Set **families, bool *is_allowlist) { + bool invert = false; + int r; + + assert(rvalue); + assert(families); + assert(is_allowlist); + + if (isempty(rvalue)) { + *families = set_free(*families); + *is_allowlist = false; + return 0; + } + + if (streq(rvalue, "none")) { + *families = set_free(*families); + *is_allowlist = true; + return 0; + } + + if (rvalue[0] == '~') { + invert = true; + rvalue++; + } + + if (!*families) { + *families = set_new(NULL); + if (!*families) + return -ENOMEM; + + *is_allowlist = !invert; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE); + if (r == 0) + return 0; + if (r < 0) + return r; + + int af = af_from_name(word); + if (af < 0) + return af; + + /* If we previously wanted to forbid an address family and now we want to allow it, then + * just remove it from the list. */ + if (!invert == *is_allowlist) { + r = set_put(*families, INT_TO_PTR(af)); + if (r < 0) + return r; + } else + set_remove(*families, INT_TO_PTR(af)); + } +} + static int parse_af_token( const char *token, int *family, diff --git a/src/shared/parse-helpers.h b/src/shared/parse-helpers.h index 402147cbf38a5..a906dfdaefdb5 100644 --- a/src/shared/parse-helpers.h +++ b/src/shared/parse-helpers.h @@ -20,6 +20,8 @@ int path_simplify_and_warn( unsigned line, const char *lvalue); +int parse_address_families(const char *rvalue, Set **families, bool *is_allowlist); + int parse_socket_bind_item( const char *str, int *address_family, From 087733e348f060b1c79cf72c9615c706c2c9d851 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 13 Apr 2026 10:29:11 +0200 Subject: [PATCH 0942/1296] core: use JSON_BUILD_CONST_STRING() where appropriate --- src/core/varlink-unit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index 15a425bbb19bc..2404c553d7fc9 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -225,7 +225,7 @@ static int can_clean_build_json(sd_json_variant **ret, const char *name, void *u } if (FLAGS_SET(mask, EXEC_CLEAN_FDSTORE)) { - r = sd_json_variant_append_arrayb(&v, SD_JSON_BUILD_STRING("fdstore")); + r = sd_json_variant_append_arrayb(&v, JSON_BUILD_CONST_STRING("fdstore")); if (r < 0) return r; } From c3c9cc7adb7d6eeb68086c8244086e115a788542 Mon Sep 17 00:00:00 2001 From: Milan Kyselica Date: Sat, 11 Apr 2026 10:25:19 +0200 Subject: [PATCH 0943/1296] boot: fix integer overflow and division by zero in BMP splash parser Bound image dimensions before computing row_size to prevent overflow in the depth * x multiplication on 32-bit. Without this, crafted dimensions like depth=32 x=0x10000001 wrap to a small row_size that passes all subsequent checks. Reject channel masks where all bits are set (popcount == 32), since 1U << 32 is undefined behavior and causes division by zero on architectures where it evaluates to zero. Move the validation before computing derived values for clarity. Use unsigned 1U in shifts to avoid signed integer overflow UB for popcount == 31. Also reject zero-width and zero-height images. Fixes: https://github.com/systemd/systemd/issues/41589 --- src/boot/splash.c | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/boot/splash.c b/src/boot/splash.c index e3365b04df07f..487f212e65c1e 100644 --- a/src/boot/splash.c +++ b/src/boot/splash.c @@ -95,10 +95,17 @@ static EFI_STATUS bmp_parse_header( return EFI_UNSUPPORTED; } - size_t row_size = ((size_t) dib->depth * dib->x + 31) / 32 * 4; - if (file->size - file->offset < dib->y * row_size) + if (dib->x == 0 || dib->y == 0) + return EFI_INVALID_PARAMETER; + + /* Bound dimensions before computing row_size to prevent overflow + * in the (size_t) dib->depth * dib->x multiplication on 32-bit. */ + if (dib->x > (size_t) 64 * 1024 * 1024 / dib->depth || + dib->y > (size_t) 64 * 1024 * 1024 / dib->depth / dib->x) return EFI_INVALID_PARAMETER; - if (row_size * dib->y > 64 * 1024 * 1024) + + size_t row_size = ((size_t) dib->depth * dib->x + 31) / 32 * 4; + if (file->size - file->offset < dib->y * row_size) return EFI_INVALID_PARAMETER; /* check color table */ @@ -145,20 +152,31 @@ static EFI_STATUS read_channel_mask( if (dib->channel_mask_r == 0 || dib->channel_mask_g == 0 || dib->channel_mask_b == 0) return EFI_INVALID_PARAMETER; + /* Reject masks where all bits are set (popcount == 32), since + * 1U << 32 is undefined behavior and causes division by zero + * on architectures where it evaluates to zero. */ + if (popcount(dib->channel_mask_r) >= 32 || + popcount(dib->channel_mask_g) >= 32 || + popcount(dib->channel_mask_b) >= 32) + return EFI_INVALID_PARAMETER; + channel_mask[R] = dib->channel_mask_r; channel_mask[G] = dib->channel_mask_g; channel_mask[B] = dib->channel_mask_b; channel_shift[R] = __builtin_ctz(dib->channel_mask_r); channel_shift[G] = __builtin_ctz(dib->channel_mask_g); channel_shift[B] = __builtin_ctz(dib->channel_mask_b); - channel_scale[R] = 0xff / ((1 << popcount(dib->channel_mask_r)) - 1); - channel_scale[G] = 0xff / ((1 << popcount(dib->channel_mask_g)) - 1); - channel_scale[B] = 0xff / ((1 << popcount(dib->channel_mask_b)) - 1); + channel_scale[R] = 0xff / ((1U << popcount(dib->channel_mask_r)) - 1); + channel_scale[G] = 0xff / ((1U << popcount(dib->channel_mask_g)) - 1); + channel_scale[B] = 0xff / ((1U << popcount(dib->channel_mask_b)) - 1); if (dib->size >= SIZEOF_BMP_DIB_RGBA && dib->channel_mask_a != 0) { + if (popcount(dib->channel_mask_a) >= 32) + return EFI_INVALID_PARAMETER; + channel_mask[A] = dib->channel_mask_a; channel_shift[A] = __builtin_ctz(dib->channel_mask_a); - channel_scale[A] = 0xff / ((1 << popcount(dib->channel_mask_a)) - 1); + channel_scale[A] = 0xff / ((1U << popcount(dib->channel_mask_a)) - 1); } else { channel_mask[A] = 0; channel_shift[A] = 0; From 2c664b953163be5e8e18df3fd73ed7bfae229a37 Mon Sep 17 00:00:00 2001 From: Milan Kyselica Date: Sat, 11 Apr 2026 10:26:13 +0200 Subject: [PATCH 0944/1296] boot: fix loop bound and OOB in devicetree_get_compatible() The loop used the byte offset end (struct_off + struct_size) as the iteration limit, but cursor[i] indexes uint32_t words. This reads past the struct block when end > size_words. Use size_words (struct_size / sizeof(uint32_t)) which is the correct number of words to iterate over. Also fix a pre-existing OOB in the FDT_BEGIN_NODE handler: the guard i >= size_words is always false inside the loop (since the loop condition already ensures i < size_words), so cursor[++i] at the boundary reads one word past the struct block. Use i + 1 >= size_words to check before incrementing. Fixes: https://github.com/systemd/systemd/issues/41590 --- src/boot/devicetree.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/boot/devicetree.c b/src/boot/devicetree.c index 85fc07c49f38b..a9dccd2a57c56 100644 --- a/src/boot/devicetree.c +++ b/src/boot/devicetree.c @@ -141,10 +141,10 @@ static const char* devicetree_get_compatible(const void *dtb) { size_t size_words = struct_size / sizeof(uint32_t); size_t len, name_off, len_words, s; - for (size_t i = 0; i < end; i++) { + for (size_t i = 0; i < size_words; i++) { switch (be32toh(cursor[i])) { case FDT_BEGIN_NODE: - if (i >= size_words || cursor[++i] != 0) + if (i + 1 >= size_words || cursor[++i] != 0) return NULL; break; case FDT_NOP: From 6b0a3ef8c791badafc24537c0c13cb7e85575123 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 13 Apr 2026 10:26:36 +0200 Subject: [PATCH 0945/1296] stat-util: introduce a common (stat|fd)_verify_regular_or_block() helper We already had one in repart.c, let's generalize it, and use it everywhere. --- src/basic/stat-util.c | 22 ++++++++++++++++++++++ src/basic/stat-util.h | 3 +++ src/home/homework-luks.c | 7 +++---- src/import/import-raw.c | 7 ++++--- src/mountfsd/mountwork.c | 14 ++++---------- src/repart/repart.c | 24 ++---------------------- src/shared/loop-util.c | 9 +++------ 7 files changed, 41 insertions(+), 45 deletions(-) diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index d04da52a78818..3e2d191bbbfb4 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -220,6 +220,28 @@ int is_device_node(const char *path) { return verify_stat_at(AT_FDCWD, path, false, stat_verify_device_node, false); } +int stat_verify_regular_or_block(const struct stat *st) { + assert(st); + + if (S_ISDIR(st->st_mode)) + return -EISDIR; + + if (S_ISLNK(st->st_mode)) + return -ELOOP; + + if (!S_ISREG(st->st_mode) && !S_ISBLK(st->st_mode)) + return -EBADFD; + + return 0; +} + +int fd_verify_regular_or_block(int fd) { + if (IN_SET(fd, AT_FDCWD, XAT_FDROOT)) + return -EISDIR; + + return verify_stat_at(fd, /* path= */ NULL, /* follow= */ false, stat_verify_regular_or_block, /* verify= */ true); +} + int dir_is_empty_at(int dir_fd, const char *path, bool ignore_hidden_or_backup) { _cleanup_close_ int fd = -EBADF; struct dirent *buf; diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index 2b50c9c55888d..6eded0d848174 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -31,6 +31,9 @@ int fd_verify_linked(int fd); int stat_verify_device_node(const struct stat *st); int is_device_node(const char *path); +int stat_verify_regular_or_block(const struct stat *st); +int fd_verify_regular_or_block(int fd); + int dir_is_empty_at(int dir_fd, const char *path, bool ignore_hidden_or_backup); static inline int dir_is_empty(const char *path, bool ignore_hidden_or_backup) { return dir_is_empty_at(AT_FDCWD, path, ignore_hidden_or_backup); diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index caa05db26f491..5342bea21e695 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -1250,10 +1250,9 @@ static int open_image_file( if (fstat(image_fd, &st) < 0) return log_error_errno(errno, "Failed to fstat() image file: %m"); - if (!S_ISREG(st.st_mode) && !S_ISBLK(st.st_mode)) - return log_error_errno( - S_ISDIR(st.st_mode) ? SYNTHETIC_ERRNO(EISDIR) : SYNTHETIC_ERRNO(EBADFD), - "Image file %s is not a regular file or block device.", ip); + r = stat_verify_regular_or_block(&st); + if (r < 0) + return log_error_errno(r, "Image file '%s' is not a regular file or block device.", ip); /* Locking block devices doesn't really make sense, as this might interfere with * udev's workings, and these locks aren't network propagated anyway, hence not what diff --git a/src/import/import-raw.c b/src/import/import-raw.c index 05d4bc9c9f62b..2095a0dde57af 100644 --- a/src/import/import-raw.c +++ b/src/import/import-raw.c @@ -22,6 +22,7 @@ #include "pretty-print.h" #include "qcow2-util.h" #include "ratelimit.h" +#include "stat-util.h" #include "string-util.h" #include "terminal-util.h" #include "time-util.h" @@ -309,9 +310,9 @@ static int raw_import_open_disk(RawImport *i) { if (fstat(i->output_fd, &i->output_stat) < 0) return log_error_errno(errno, "Failed to stat() output file: %m"); - if (!S_ISREG(i->output_stat.st_mode) && !S_ISBLK(i->output_stat.st_mode)) - return log_error_errno(SYNTHETIC_ERRNO(EBADFD), - "Target file is not a regular file or block device"); + r = stat_verify_regular_or_block(&i->output_stat); + if (r < 0) + return log_error_errno(r, "Target file is not a regular file or block device."); if (i->offset != UINT64_MAX) { if (lseek(i->output_fd, i->offset, SEEK_SET) < 0) diff --git a/src/mountfsd/mountwork.c b/src/mountfsd/mountwork.c index 3856e9da94c04..a5d34d447906d 100644 --- a/src/mountfsd/mountwork.c +++ b/src/mountfsd/mountwork.c @@ -158,16 +158,10 @@ static int validate_image_fd(int fd, MountImageParameters *p) { assert(fd >= 0); assert(p); - struct stat st; - if (fstat(fd, &st) < 0) - return -errno; - /* Only support regular files and block devices. Let's use stat_verify_regular() here for the nice - * error numbers it generates. */ - if (!S_ISBLK(st.st_mode)) { - r = stat_verify_regular(&st); - if (r < 0) - return r; - } + /* Only support regular files and block devices. */ + r = fd_verify_regular_or_block(fd); + if (r < 0) + return r; fl = fd_verify_safe_flags_full(fd, O_NONBLOCK); if (fl < 0) diff --git a/src/repart/repart.c b/src/repart/repart.c index e9cd0d85699a7..308ae3aba5bad 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -3226,26 +3226,6 @@ static int determine_current_padding( return 0; } -static int verify_regular_or_block(int fd) { - struct stat st; - - assert(fd >= 0); - - if (fstat(fd, &st) < 0) - return -errno; - - if (S_ISDIR(st.st_mode)) - return -EISDIR; - - if (S_ISLNK(st.st_mode)) - return -ELOOP; - - if (!S_ISREG(st.st_mode) && !S_ISBLK(st.st_mode)) - return -EBADFD; - - return 0; -} - static int context_copy_from_one(Context *context, const char *src) { _cleanup_close_ int fd = -EBADF; _cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL; @@ -3261,9 +3241,9 @@ static int context_copy_from_one(Context *context, const char *src) { if (r < 0) return r; - r = verify_regular_or_block(fd); + r = fd_verify_regular_or_block(fd); if (r < 0) - return log_error_errno(r, "%s is not a file nor a block device: %m", src); + return log_error_errno(r, "'%s' is not a file nor a block device: %m", src); r = fdisk_new_context_at(fd, /* path= */ NULL, /* read_only= */ true, /* sector_size= */ UINT32_MAX, &c); if (r < 0) diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c index 9349a98493ed9..1c843661cc44e 100644 --- a/src/shared/loop-util.c +++ b/src/shared/loop-util.c @@ -820,7 +820,6 @@ int loop_device_make_by_path_memory( _cleanup_close_ int fd = -EBADF, mfd = -EBADF; _cleanup_free_ char *fn = NULL; - struct stat st; int r; assert(path); @@ -837,11 +836,9 @@ int loop_device_make_by_path_memory( if (fd < 0) return -errno; - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISREG(st.st_mode) && !S_ISBLK(st.st_mode)) - return -EBADF; + r = fd_verify_regular_or_block(fd); + if (r < 0) + return r; r = path_extract_filename(path, &fn); if (r < 0) From 596c0a259df6ac8f42b90b72a32954e14e754186 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 13 Apr 2026 10:28:54 +0200 Subject: [PATCH 0946/1296] tree-wide: make more use of stat_verify_device_node() --- src/basic/device-nodes.c | 14 +++++++++----- src/core/bpf-devices.c | 6 ++++-- src/nspawn/nspawn.c | 5 +++-- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/basic/device-nodes.c b/src/basic/device-nodes.c index 37af18fce419c..15abe31236968 100644 --- a/src/basic/device-nodes.c +++ b/src/basic/device-nodes.c @@ -6,6 +6,7 @@ #include "device-nodes.h" #include "path-util.h" +#include "stat-util.h" #include "stdio-util.h" #include "string-util.h" #include "utf8.h" @@ -63,6 +64,7 @@ int encode_devnode_name(const char *str, char *str_enc, size_t len) { int devnode_same(const char *a, const char *b) { struct stat sa, sb; + int r; assert(a); assert(b); @@ -72,13 +74,15 @@ int devnode_same(const char *a, const char *b) { if (stat(a, &sa) < 0) return -errno; + r = stat_verify_device_node(&sa); + if (r < 0) + return r; + if (stat(b, &sb) < 0) return -errno; - - if (!S_ISBLK(sa.st_mode) && !S_ISCHR(sa.st_mode)) - return -ENODEV; - if (!S_ISBLK(sb.st_mode) && !S_ISCHR(sb.st_mode)) - return -ENODEV; + r = stat_verify_device_node(&sb); + if (r < 0) + return r; if (((sa.st_mode ^ sb.st_mode) & S_IFMT) != 0) /* both inode same device node type? */ return false; diff --git a/src/core/bpf-devices.c b/src/core/bpf-devices.c index aeb01e8575395..432b3d0162555 100644 --- a/src/core/bpf-devices.c +++ b/src/core/bpf-devices.c @@ -16,6 +16,7 @@ #include "nulstr-util.h" #include "parse-util.h" #include "path-util.h" +#include "stat-util.h" #include "string-util.h" #define PASS_JUMP_OFF 4096 @@ -308,8 +309,9 @@ int bpf_devices_allow_list_device( return log_warning_errno(errno, "Couldn't stat device %s: %m", node); } - if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) - return log_warning_errno(SYNTHETIC_ERRNO(ENODEV), "%s is not a device.", node); + r = stat_verify_device_node(&st); + if (r < 0) + return log_warning_errno(r, "'%s' is not a device node.", node); mode = st.st_mode; rdev = (dev_t) st.st_rdev; diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index b6332844db80c..74364ae1e7d60 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -2049,8 +2049,9 @@ static int copy_devnode_one(const char *dest, const char *node, bool check) { log_debug_errno(errno, "Device node %s does not exist, ignoring.", from); return 0; } - if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) - return log_error_errno(SYNTHETIC_ERRNO(ESTALE), "%s is not a device node.", from); + r = stat_verify_device_node(&st); + if (r < 0) + return log_error_errno(r, "'%s' is not a device node.", from); /* Create the parent directory of the device node. Here, we assume that the path has at most one * subdirectory under /dev/, e.g. /dev/net/tun. */ From 7f2410deb1ae5f093f7c9bb761701e2d36fef388 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 13 Apr 2026 10:41:01 +0200 Subject: [PATCH 0947/1296] stat-util: add shortcut for fd_verify_symlink() We have a similar shortcut in the other fd_verify_xyz() calls, let's add it here too, just for completion's sake. --- src/basic/stat-util.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 3e2d191bbbfb4..3811bd019edb6 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -145,6 +145,9 @@ int stat_verify_symlink(const struct stat *st) { } int fd_verify_symlink(int fd) { + if (IN_SET(fd, AT_FDCWD, XAT_FDROOT)) + return -EISDIR; + return verify_stat_at(fd, /* path= */ NULL, /* follow= */ false, stat_verify_symlink, /* verify= */ true); } From bda7e913372bef6002529eacf47d66c9a1473348 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 13 Apr 2026 10:45:29 +0200 Subject: [PATCH 0948/1296] stat-util: also introduce stat_verify_block() and use it everywhere --- src/basic/stat-util.c | 22 ++++++++++++++++++++++ src/basic/stat-util.h | 3 +++ src/fsck/fsck.c | 8 ++++---- src/hibernate-resume/hibernate-resume.c | 7 ++++--- src/home/homed-home.c | 5 +++-- src/home/homework-luks.c | 15 +++++++-------- src/mount/mount-tool.c | 6 +++--- src/shared/btrfs-util.c | 5 +++-- src/shared/discover-image.c | 8 +++----- src/shared/dissect-image.c | 6 ++++-- src/shared/hibernate-util.c | 5 +++-- src/shared/loop-util.c | 15 +++++++++------ 12 files changed, 68 insertions(+), 37 deletions(-) diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 3811bd019edb6..2bdd8af21dc9e 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -203,6 +203,28 @@ int fd_verify_linked(int fd) { return verify_stat_at(fd, NULL, false, stat_verify_linked, true); } +int stat_verify_block(const struct stat *st) { + assert(st); + + if (S_ISDIR(st->st_mode)) + return -EISDIR; + + if (S_ISLNK(st->st_mode)) + return -ELOOP; + + if (!S_ISBLK(st->st_mode)) + return -ENOTBLK; + + return 0; +} + +int fd_verify_block(int fd) { + if (IN_SET(fd, AT_FDCWD, XAT_FDROOT)) + return -EISDIR; + + return verify_stat_at(fd, /* path= */ NULL, /* follow= */ false, stat_verify_block, /* verify= */ true); +} + int stat_verify_device_node(const struct stat *st) { assert(st); diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index 6eded0d848174..55bc5b6c7eac0 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -28,6 +28,9 @@ int is_socket(const char *path); int stat_verify_linked(const struct stat *st); int fd_verify_linked(int fd); +int stat_verify_block(const struct stat *st); +int fd_verify_block(int fd); + int stat_verify_device_node(const struct stat *st); int is_device_node(const char *path); diff --git a/src/fsck/fsck.c b/src/fsck/fsck.c index 43cc208e598a2..405e9c34fddc4 100644 --- a/src/fsck/fsck.c +++ b/src/fsck/fsck.c @@ -28,6 +28,7 @@ #include "process-util.h" #include "socket-util.h" #include "special.h" +#include "stat-util.h" #include "stdio-util.h" #include "string-table.h" #include "string-util.h" @@ -299,10 +300,9 @@ static int run(int argc, char *argv[]) { if (stat(device, &st) < 0) return log_error_errno(errno, "Failed to stat %s: %m", device); - if (!S_ISBLK(st.st_mode)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "%s is not a block device.", - device); + r = stat_verify_block(&st); + if (r < 0) + return log_error_errno(r, "'%s' is not a block device.", device); r = sd_device_new_from_stat_rdev(&dev, &st); if (r < 0) diff --git a/src/hibernate-resume/hibernate-resume.c b/src/hibernate-resume/hibernate-resume.c index f3cf399e1d84b..2c6eed3248af6 100644 --- a/src/hibernate-resume/hibernate-resume.c +++ b/src/hibernate-resume/hibernate-resume.c @@ -13,6 +13,7 @@ #include "main-func.h" #include "parse-util.h" #include "pretty-print.h" +#include "stat-util.h" #include "static-destruct.h" static HibernateInfo arg_info = {}; @@ -165,9 +166,9 @@ static int run(int argc, char *argv[]) { if (stat(arg_info.device, &st) < 0) return log_error_errno(errno, "Failed to stat resume device '%s': %m", arg_info.device); - if (!S_ISBLK(st.st_mode)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK), - "Resume device '%s' is not a block device.", arg_info.device); + r = stat_verify_block(&st); + if (r < 0) + return log_error_errno(r, "Resume device '%s' is not a block device.", arg_info.device); /* The write shall not return if a resume takes place. */ r = write_resume_config(st.st_rdev, arg_info.offset, arg_info.device); diff --git a/src/home/homed-home.c b/src/home/homed-home.c index 12e0eed2dae32..f467ef7015d3b 100644 --- a/src/home/homed-home.c +++ b/src/home/homed-home.c @@ -3214,8 +3214,9 @@ static int home_get_image_path_seat(Home *h, char **ret) { if (stat(ip, &st) < 0) return -errno; - if (!S_ISBLK(st.st_mode)) - return -ENOTBLK; + r = stat_verify_block(&st); + if (r < 0) + return r; r = sd_device_new_from_stat_rdev(&d, &st); if (r < 0) diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index 5342bea21e695..f105acfa3bf6c 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -202,16 +202,14 @@ static int probe_file_system_by_path(const char *path, char **ret_fstype, sd_id1 } static int block_get_size_by_fd(int fd, uint64_t *ret) { - struct stat st; + int r; assert(fd >= 0); assert(ret); - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISBLK(st.st_mode)) - return -ENOTBLK; + r = fd_verify_block(fd); + if (r < 0) + return r; return blockdev_get_device_size(fd, ret); } @@ -2275,8 +2273,9 @@ int home_create_luks( if (setup->image_fd < 0) return setup->image_fd; - if (!S_ISBLK(st.st_mode)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK), "Device is not a block device, refusing."); + r = stat_verify_block(&st); + if (r < 0) + return log_error_errno(r, "Device is not a block device, refusing."); if (asprintf(&sysfs, "/sys/dev/block/" DEVNUM_FORMAT_STR "/partition", DEVNUM_FORMAT_VAL(st.st_rdev)) < 0) return log_oom(); diff --git a/src/mount/mount-tool.c b/src/mount/mount-tool.c index 768ff349e2713..f1c4c90d76883 100644 --- a/src/mount/mount-tool.c +++ b/src/mount/mount-tool.c @@ -1413,9 +1413,9 @@ static int discover_device(void) { if (S_ISREG(st.st_mode)) return discover_loop_backing_file(); - if (!S_ISBLK(st.st_mode)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unsupported mount source type for --discover: %s", arg_mount_what); + r = stat_verify_block(&st); + if (r < 0) + return log_error_errno(r, "Unsupported mount source type for --discover: %s", arg_mount_what); r = sd_device_new_from_stat_rdev(&d, &st); if (r < 0) diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index 9bd0a74c5fe83..8e41f569ba235 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -149,8 +149,9 @@ int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret) { if (stat((char*) di.path, &st) < 0) return -errno; - if (!S_ISBLK(st.st_mode)) - return -ENOTBLK; + r = stat_verify_block(&st); + if (r < 0) + return r; if (major(st.st_rdev) == 0) return -ENODEV; diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c index 6b9954913498c..1595bb218404a 100644 --- a/src/shared/discover-image.c +++ b/src/shared/discover-image.c @@ -1895,17 +1895,15 @@ int image_read_only(Image *i, bool b, RuntimeScope scope) { case IMAGE_BLOCK: { _cleanup_close_ int fd = -EBADF; - struct stat st; int state = b; fd = open(i->path, O_CLOEXEC|O_RDONLY|O_NONBLOCK|O_NOCTTY); if (fd < 0) return -errno; - if (fstat(fd, &st) < 0) - return -errno; - if (!S_ISBLK(st.st_mode)) - return -ENOTTY; + r = fd_verify_block(fd); + if (r < 0) + return r; if (ioctl(fd, BLKROSET, &state) < 0) return -errno; diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 9817ed87ec129..c5911beb71075 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -2141,14 +2141,16 @@ DissectedImage* dissected_image_unref(DissectedImage *m) { static int is_loop_device(const char *path) { char s[SYS_BLOCK_PATH_MAX("/../loop/")]; struct stat st; + int r; assert(path); if (stat(path, &st) < 0) return -errno; - if (!S_ISBLK(st.st_mode)) - return -ENOTBLK; + r = stat_verify_block(&st); + if (r < 0) + return r; xsprintf_sys_block_path(s, "/loop/", st.st_dev); if (access(s, F_OK) < 0) { diff --git a/src/shared/hibernate-util.c b/src/shared/hibernate-util.c index 001eaa920cc99..c141cdc8c6701 100644 --- a/src/shared/hibernate-util.c +++ b/src/shared/hibernate-util.c @@ -209,8 +209,9 @@ static int swap_entry_get_resume_config(SwapEntry *swap) { return -errno; if (!swap->swapfile) { - if (!S_ISBLK(st.st_mode)) - return -ENOTBLK; + r = stat_verify_block(&st); + if (r < 0) + return r; swap->devno = st.st_rdev; swap->offset = 0; diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c index 1c843661cc44e..e6fe4dbb49d7a 100644 --- a/src/shared/loop-util.c +++ b/src/shared/loop-util.c @@ -378,9 +378,9 @@ static int loop_configure( } static int fd_get_max_discard(int fd, uint64_t *ret) { - struct stat st; char sysfs_path[STRLEN("/sys/dev/block/" ":" "/queue/discard_max_bytes") + DECIMAL_STR_MAX(dev_t) * 2 + 1]; _cleanup_free_ char *buffer = NULL; + struct stat st; int r; assert(ret); @@ -388,8 +388,9 @@ static int fd_get_max_discard(int fd, uint64_t *ret) { if (fstat(ASSERT_FD(fd), &st) < 0) return -errno; - if (!S_ISBLK(st.st_mode)) - return -ENOTBLK; + r = stat_verify_block(&st); + if (r < 0) + return r; xsprintf(sysfs_path, "/sys/dev/block/" DEVNUM_FORMAT_STR "/queue/discard_max_bytes", DEVNUM_FORMAT_VAL(st.st_rdev)); @@ -401,14 +402,16 @@ static int fd_get_max_discard(int fd, uint64_t *ret) { } static int fd_set_max_discard(int fd, uint64_t max_discard) { - struct stat st; char sysfs_path[STRLEN("/sys/dev/block/" ":" "/queue/discard_max_bytes") + DECIMAL_STR_MAX(dev_t) * 2 + 1]; + struct stat st; + int r; if (fstat(ASSERT_FD(fd), &st) < 0) return -errno; - if (!S_ISBLK(st.st_mode)) - return -ENOTBLK; + r = stat_verify_block(&st); + if (r < 0) + return r; xsprintf(sysfs_path, "/sys/dev/block/" DEVNUM_FORMAT_STR "/queue/discard_max_bytes", DEVNUM_FORMAT_VAL(st.st_rdev)); From 8e0756c0b5e020dc8f8b53c3e69fffcb0892f599 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 13 Apr 2026 11:09:25 +0200 Subject: [PATCH 0949/1296] tree-wide: use stat_verify_regular() more --- src/basic/locale-util.c | 6 ++++-- src/shared/hibernate-util.c | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/basic/locale-util.c b/src/basic/locale-util.c index 11e91ab834505..3b869d70f9747 100644 --- a/src/basic/locale-util.c +++ b/src/basic/locale-util.c @@ -16,6 +16,7 @@ #include "path-util.h" #include "process-util.h" #include "set.h" +#include "stat-util.h" #include "string-table.h" #include "string-util.h" #include "strv.h" @@ -115,8 +116,9 @@ static int add_locales_from_archive(Set *locales) { if (fstat(fd, &st) < 0) return -errno; - if (!S_ISREG(st.st_mode)) - return -EBADMSG; + r = stat_verify_regular(&st); + if (r < 0) + return r; if (st.st_size < (off_t) sizeof(struct locarhead)) return -EBADMSG; diff --git a/src/shared/hibernate-util.c b/src/shared/hibernate-util.c index c141cdc8c6701..f9e095f27384d 100644 --- a/src/shared/hibernate-util.c +++ b/src/shared/hibernate-util.c @@ -37,14 +37,16 @@ int read_fiemap(int fd, struct fiemap **ret) { uint32_t result_extents = 0; uint64_t fiemap_start = 0, fiemap_length; const size_t n_extra = DIV_ROUND_UP(sizeof(struct fiemap), sizeof(struct fiemap_extent)); + int r; assert(fd >= 0); assert(ret); if (fstat(fd, &statinfo) < 0) return log_debug_errno(errno, "Cannot determine file size: %m"); - if (!S_ISREG(statinfo.st_mode)) - return -ENOTTY; + r = stat_verify_regular(&statinfo); + if (r < 0) + return r; fiemap_length = statinfo.st_size; /* Zero this out in case we run on a file with no extents */ From 4031a73a4b44e6da040c232ae548340dcb46c5c4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 13 Apr 2026 11:10:03 +0200 Subject: [PATCH 0950/1296] tree-wide: use stat_verify_directory() more --- src/libsystemd/sd-journal/sd-journal.c | 9 +++------ src/shared/btrfs-util.c | 9 +++------ src/shared/chown-recursive.c | 7 +++++-- src/shared/find-esp.c | 15 +++++++++------ src/shared/rm-rf.c | 5 +++-- 5 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/libsystemd/sd-journal/sd-journal.c b/src/libsystemd/sd-journal/sd-journal.c index 3e5185b23f7e0..52b3e616a172e 100644 --- a/src/libsystemd/sd-journal/sd-journal.c +++ b/src/libsystemd/sd-journal/sd-journal.c @@ -2456,7 +2456,6 @@ _public_ int sd_journal_open_files(sd_journal **ret, const char **paths, int fla _public_ int sd_journal_open_directory_fd(sd_journal **ret, int fd, int flags) { _cleanup_(sd_journal_closep) sd_journal *j = NULL; - struct stat st; bool take_fd; int r; @@ -2464,11 +2463,9 @@ _public_ int sd_journal_open_directory_fd(sd_journal **ret, int fd, int flags) { assert_return(fd >= 0, -EBADF); assert_return((flags & ~OPEN_DIRECTORY_FD_ALLOWED_FLAGS) == 0, -EINVAL); - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISDIR(st.st_mode)) - return -EBADFD; + r = fd_verify_directory(fd); + if (r < 0) + return r; take_fd = FLAGS_SET(flags, SD_JOURNAL_TAKE_DIRECTORY_FD); j = journal_new(flags & ~SD_JOURNAL_TAKE_DIRECTORY_FD, NULL, NULL); diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index 8e41f569ba235..cde21bd602965 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -879,18 +879,15 @@ static int subvol_remove_children(int fd, const char *subvolume, uint64_t subvol struct btrfs_ioctl_vol_args vol_args = {}; _cleanup_close_ int subvol_fd = -EBADF; - struct stat st; bool made_writable = false; int r; assert(fd >= 0); assert(subvolume); - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISDIR(st.st_mode)) - return -EINVAL; + r = fd_verify_directory(fd); + if (r < 0) + return r; subvol_fd = openat(fd, subvolume, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW); if (subvol_fd < 0) diff --git a/src/shared/chown-recursive.c b/src/shared/chown-recursive.c index 850e495836b53..a0e885ece9547 100644 --- a/src/shared/chown-recursive.c +++ b/src/shared/chown-recursive.c @@ -9,6 +9,7 @@ #include "fd-util.h" #include "fs-util.h" #include "path-util.h" +#include "stat-util.h" #include "strv.h" #include "user-util.h" #include "xattr-util.h" @@ -144,6 +145,7 @@ int fd_chown_recursive( int duplicated_fd = -EBADF; struct stat st; + int r; /* Note that the slightly different order of fstat() and the checks here and in * path_chown_recursive(). That's because when we open the directory ourselves we can specify @@ -153,8 +155,9 @@ int fd_chown_recursive( if (fstat(fd, &st) < 0) return -errno; - if (!S_ISDIR(st.st_mode)) - return -ENOTDIR; + r = stat_verify_directory(&st); + if (r < 0) + return r; if (!uid_is_valid(uid) && !gid_is_valid(gid) && FLAGS_SET(mask, 07777)) return 0; /* nothing to do */ diff --git a/src/shared/find-esp.c b/src/shared/find-esp.c index 3f490ced714cf..1d13683f1286e 100644 --- a/src/shared/find-esp.c +++ b/src/shared/find-esp.c @@ -297,8 +297,9 @@ static int verify_fsroot_dir( (unprivileged_mode && ERRNO_IS_NEG_PRIVILEGE(r)) ? LOG_DEBUG : LOG_ERR, r, "Failed to determine block device node of \"%s\": %m", path); - if (!S_ISDIR(sx.stx_mode)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "Path \"%s\" is not a directory", path); + r = statx_verify_directory(&sx); + if (r < 0) + return log_error_errno(r, "Path \"%s\" is not a directory", path); if (!FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT)) return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, @@ -477,8 +478,9 @@ int find_esp_and_warn_at_full( if (fstat(fd, &st) < 0) return log_error_errno(errno, "Failed to stat '%s': %m", p); - if (!S_ISDIR(st.st_mode)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "ESP path '%s' is not a directory.", p); + r = stat_verify_directory(&st); + if (r < 0) + return log_error_errno(r, "ESP path '%s' is not a directory.", p); if (ret_path) *ret_path = TAKE_PTR(p); @@ -829,8 +831,9 @@ int find_xbootldr_and_warn_at_full( if (fstat(fd, &st) < 0) return log_error_errno(errno, "Failed to stat '%s': %m", p); - if (!S_ISDIR(st.st_mode)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "XBOOTLDR path '%s' is not a directory.", p); + r = stat_verify_directory(&st); + if (r < 0) + return log_error_errno(r, "XBOOTLDR path '%s' is not a directory.", p); if (ret_path) *ret_path = TAKE_PTR(p); diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c index 8c179bbaca9aa..ede18318abfe6 100644 --- a/src/shared/rm-rf.c +++ b/src/shared/rm-rf.c @@ -37,8 +37,9 @@ static int patch_dirfd_mode( if (fstat(dfd, &st) < 0) return -errno; - if (!S_ISDIR(st.st_mode)) - return -ENOTDIR; + r = stat_verify_directory(&st); + if (r < 0) + return r; if (FLAGS_SET(st.st_mode, 0700)) { /* Already set? */ if (refuse_already_set) From 4eb60e3160a5d00587042514362eb8fb25c6ef5e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 13 Apr 2026 11:10:36 +0200 Subject: [PATCH 0951/1296] socket-netlink: use stat_verify_socket() more --- src/shared/socket-netlink.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/shared/socket-netlink.c b/src/shared/socket-netlink.c index 1d369353b976b..438b2d172b5d7 100644 --- a/src/shared/socket-netlink.c +++ b/src/shared/socket-netlink.c @@ -18,6 +18,7 @@ #include "socket-label.h" #include "socket-netlink.h" #include "socket-util.h" +#include "stat-util.h" #include "string-util.h" int socket_address_parse(SocketAddress *a, const char *s) { @@ -497,8 +498,9 @@ int af_unix_get_qlen(int fd, uint32_t *ret) { struct stat st; if (fstat(fd, &st) < 0) return -errno; - if (!S_ISSOCK(st.st_mode)) - return -ENOTSOCK; + r = stat_verify_socket(&st); + if (r < 0) + return r; _cleanup_(sd_netlink_unrefp) sd_netlink *nl = NULL; r = sd_sock_diag_socket_open(&nl); From aa0da09127f4c5c4a2063cf0bc4e629b878a7e27 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 13 Apr 2026 11:20:57 +0200 Subject: [PATCH 0952/1296] stat-util: also add stat_verify_char() and use it everywhere --- src/basic/stat-util.c | 15 +++++++++++++++ src/basic/stat-util.h | 2 ++ src/basic/terminal-util.c | 12 ++++++++---- src/core/manager.c | 5 ++--- src/shared/watchdog.c | 7 +++++-- 5 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 2bdd8af21dc9e..116b36a346fe6 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -225,6 +225,21 @@ int fd_verify_block(int fd) { return verify_stat_at(fd, /* path= */ NULL, /* follow= */ false, stat_verify_block, /* verify= */ true); } +int stat_verify_char(const struct stat *st) { + assert(st); + + if (S_ISDIR(st->st_mode)) + return -EISDIR; + + if (S_ISLNK(st->st_mode)) + return -ELOOP; + + if (!S_ISCHR(st->st_mode)) + return -EBADFD; + + return 0; +} + int stat_verify_device_node(const struct stat *st) { assert(st); diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index 55bc5b6c7eac0..ec04a2b80cd08 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -31,6 +31,8 @@ int fd_verify_linked(int fd); int stat_verify_block(const struct stat *st); int fd_verify_block(int fd); +int stat_verify_char(const struct stat *st); + int stat_verify_device_node(const struct stat *st); int is_device_node(const char *path); diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index ecdc241247286..d7ff92ae89247 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -1861,6 +1861,8 @@ int terminal_set_cursor_position(int fd, unsigned row, unsigned column) { } static int terminal_verify_same(int input_fd, int output_fd) { + int r; + assert(input_fd >= 0); assert(output_fd >= 0); @@ -1871,15 +1873,17 @@ static int terminal_verify_same(int input_fd, int output_fd) { if (fstat(input_fd, &sti) < 0) return -errno; - if (!S_ISCHR(sti.st_mode)) /* TTYs are character devices */ - return -ENOTTY; + r = stat_verify_char(&sti); /* TTYs are character devices */ + if (r < 0) + return r; struct stat sto; if (fstat(output_fd, &sto) < 0) return -errno; - if (!S_ISCHR(sto.st_mode)) - return -ENOTTY; + r = stat_verify_char(&sto); + if (r < 0) + return r; if (sti.st_rdev != sto.st_rdev) return -ENOLINK; diff --git a/src/core/manager.c b/src/core/manager.c index 73368ec18aec9..8db6c471a1206 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -4519,10 +4519,9 @@ const char* manager_get_confirm_spawn(Manager *m) { goto fail; } - if (!S_ISCHR(st.st_mode)) { - r = -ENOTTY; + r = stat_verify_char(&st); + if (r < 0) goto fail; - } last_errno = 0; return m->confirm_spawn; diff --git a/src/shared/watchdog.c b/src/shared/watchdog.c index 5b113013950f7..8b74bb4bbde6b 100644 --- a/src/shared/watchdog.c +++ b/src/shared/watchdog.c @@ -17,6 +17,7 @@ #include "log.h" #include "path-util.h" #include "ratelimit.h" +#include "stat-util.h" #include "string-util.h" #include "strv.h" #include "time-util.h" @@ -55,6 +56,7 @@ static int saturated_usec_to_sec(usec_t val) { static int watchdog_get_sysfs_path(const char *filename, char **ret_path) { struct stat st; + int r; if (watchdog_fd < 0) return -EBADF; @@ -62,8 +64,9 @@ static int watchdog_get_sysfs_path(const char *filename, char **ret_path) { if (fstat(watchdog_fd, &st)) return -errno; - if (!S_ISCHR(st.st_mode)) - return -EBADF; + r = stat_verify_char(&st); + if (r < 0) + return r; if (asprintf(ret_path, "/sys/dev/char/"DEVNUM_FORMAT_STR"/%s", DEVNUM_FORMAT_VAL(st.st_rdev), filename) < 0) return -ENOMEM; From 7977c1427382b0a3b340e74d198c2dad5b69b2ec Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 13 Apr 2026 13:14:46 +0200 Subject: [PATCH 0953/1296] stat-util: always check S_ISDIR() before S_ISLNK() Check S_ISDIR() before S_ISLINK() for all stat_verify_xyz() helpers first, where we check them. Just to ensure we systematically return the same errors. --- src/basic/stat-util.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 116b36a346fe6..06634b7df60c8 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -157,12 +157,12 @@ int is_symlink(const char *path) { } static int mode_verify_socket(mode_t mode) { - if (S_ISLNK(mode)) - return -ELOOP; - if (S_ISDIR(mode)) return -EISDIR; + if (S_ISLNK(mode)) + return -ELOOP; + if (!S_ISSOCK(mode)) return -ENOTSOCK; @@ -243,12 +243,12 @@ int stat_verify_char(const struct stat *st) { int stat_verify_device_node(const struct stat *st) { assert(st); - if (S_ISLNK(st->st_mode)) - return -ELOOP; - if (S_ISDIR(st->st_mode)) return -EISDIR; + if (S_ISLNK(st->st_mode)) + return -ELOOP; + if (!S_ISBLK(st->st_mode) && !S_ISCHR(st->st_mode)) return -ENOTTY; From 086707ba7957c2ccc074c1d83e27e1fdb196de4e Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Mon, 6 Apr 2026 10:00:37 +0200 Subject: [PATCH 0954/1296] run: allow setting output format This is useful when moving from `--pty` or `--pipe` to using `--verbose`: you can use `--verbose-output=cat` to get similar output on stdout while still having all of the advantages of `--verbose` over the other options. --- man/systemd-run.xml | 10 ++++++++++ src/run/run.c | 16 +++++++++++++++- src/shared/fork-notify.c | 6 +++++- src/shared/fork-notify.h | 3 ++- src/systemctl/systemctl-start-unit.c | 2 +- 5 files changed, 33 insertions(+), 4 deletions(-) diff --git a/man/systemd-run.xml b/man/systemd-run.xml index d18b80faa8afc..bb90f352657ed 100644 --- a/man/systemd-run.xml +++ b/man/systemd-run.xml @@ -434,6 +434,16 @@ + + + + + Controls the formatting of verbose unit log output, see journalctl1 for possible values. + + + + + diff --git a/src/run/run.c b/src/run/run.c index 88a9f41d69a3a..596e12826753c 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -105,6 +105,7 @@ static char **arg_timer_property = NULL; static bool arg_with_timer = false; static bool arg_quiet = false; static bool arg_verbose = false; +static OutputMode arg_output = _OUTPUT_MODE_INVALID; static bool arg_aggressive_gc = false; static char *arg_working_directory = NULL; static char *arg_root_directory = NULL; @@ -182,6 +183,8 @@ static int help(void) { " -P --pipe Pass STDIN/STDOUT/STDERR directly to service\n" " -q --quiet Suppress information messages during runtime\n" " -v --verbose Show unit logs while executing operation\n" + " --output=STRING Controls formatting of verbose logs, see\n" + " journalctl for valid values\n" " --json=pretty|short|off Print unit name and invocation id as JSON\n" " -G --collect Unload unit after it ran, even when failed\n" " -S --shell Invoke a $SHELL interactively\n" @@ -318,6 +321,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_EXEC_USER, ARG_EXEC_GROUP, ARG_NICE, + ARG_OUTPUT, ARG_ON_ACTIVE, ARG_ON_BOOT, ARG_ON_STARTUP, @@ -370,6 +374,7 @@ static int parse_argv(int argc, char *argv[]) { { "pipe", no_argument, NULL, 'P' }, { "quiet", no_argument, NULL, 'q' }, { "verbose", no_argument, NULL, 'v' }, + { "output", required_argument, NULL, ARG_OUTPUT }, { "on-active", required_argument, NULL, ARG_ON_ACTIVE }, { "on-boot", required_argument, NULL, ARG_ON_BOOT }, { "on-startup", required_argument, NULL, ARG_ON_STARTUP }, @@ -542,6 +547,15 @@ static int parse_argv(int argc, char *argv[]) { arg_verbose = true; break; + case ARG_OUTPUT: + if (streq(optarg, "help")) + return DUMP_STRING_TABLE(output_mode, OutputMode, _OUTPUT_MODE_MAX); + + arg_output = output_mode_from_string(optarg); + if (arg_output < 0) + return log_error_errno(arg_output, "Unknown output format '%s'.", optarg); + break; + case ARG_ON_ACTIVE: r = add_timer_property("OnActiveSec", optarg); if (r < 0) @@ -2569,7 +2583,7 @@ static int start_transient_service(sd_bus *bus) { _cleanup_(fork_notify_terminate) PidRef journal_pid = PIDREF_NULL; if (arg_verbose) - (void) journal_fork(arg_runtime_scope, STRV_MAKE(c.unit), &journal_pid); + (void) journal_fork(arg_runtime_scope, STRV_MAKE(c.unit), arg_output, &journal_pid); r = bus_call_with_hint(bus, m, "service", &reply); if (r < 0) diff --git a/src/shared/fork-notify.c b/src/shared/fork-notify.c index 6f87a2fdce2b2..307197ac19701 100644 --- a/src/shared/fork-notify.c +++ b/src/shared/fork-notify.c @@ -205,7 +205,7 @@ void fork_notify_terminate_many(sd_event_source **array, size_t n) { free(array); } -int journal_fork(RuntimeScope scope, char * const* units, PidRef *ret_pidref) { +int journal_fork(RuntimeScope scope, char * const* units, OutputMode output, PidRef *ret_pidref) { assert(scope >= 0); assert(scope < _RUNTIME_SCOPE_MAX); @@ -228,5 +228,9 @@ int journal_fork(RuntimeScope scope, char * const* units, PidRef *ret_pidref) { *u) < 0) return log_oom_debug(); + if (output >= 0) + if (strv_extendf(&argv, "--output=%s", output_mode_to_string(output)) < 0) + return log_oom_debug(); + return fork_notify(argv, ret_pidref); } diff --git a/src/shared/fork-notify.h b/src/shared/fork-notify.h index 103ab78983371..2dbfe368a4664 100644 --- a/src/shared/fork-notify.h +++ b/src/shared/fork-notify.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "output-mode.h" #include "shared-forward.h" int fork_notify(char * const *argv, PidRef *ret_pidref); @@ -9,4 +10,4 @@ void fork_notify_terminate(PidRef *pidref); void fork_notify_terminate_many(sd_event_source **array, size_t n); -int journal_fork(RuntimeScope scope, char * const *units, PidRef *ret_pidref); +int journal_fork(RuntimeScope scope, char * const *units, OutputMode output, PidRef *ret_pidref); diff --git a/src/systemctl/systemctl-start-unit.c b/src/systemctl/systemctl-start-unit.c index 0c8582bdff710..1b6dbd8b14040 100644 --- a/src/systemctl/systemctl-start-unit.c +++ b/src/systemctl/systemctl-start-unit.c @@ -402,7 +402,7 @@ int verb_start(int argc, char *argv[], uintptr_t _data, void *userdata) { ret = enqueue_marked_jobs(bus, w); else { if (arg_verbose) - (void) journal_fork(arg_runtime_scope, names, &journal_pid); + (void) journal_fork(arg_runtime_scope, names, arg_output, &journal_pid); STRV_FOREACH(name, names) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; From ccb5a185e4bc449f6f53ebe55fa37d85669c5cf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 10 Apr 2026 18:07:29 +0200 Subject: [PATCH 0955/1296] executor: convert to the new option parser, define OPTION_COMMON_LOG_* Co-developed-by: Claude Opus 4.6 --- src/core/executor.c | 124 ++++++++++++++----------------------------- src/shared/options.h | 12 +++++ 2 files changed, 53 insertions(+), 83 deletions(-) diff --git a/src/core/executor.c b/src/core/executor.c index 9ca15cf35155f..80d4d8c71ae44 100644 --- a/src/core/executor.c +++ b/src/core/executor.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-messages.h" @@ -18,9 +17,10 @@ #include "fd-util.h" #include "fdset.h" #include "fileio.h" -#include "getopt-defs.h" +#include "format-table.h" #include "label-util.h" #include "log.h" +#include "options.h" #include "parse-util.h" #include "pretty-print.h" #include "selinux-util.h" @@ -32,125 +32,90 @@ STATIC_DESTRUCTOR_REGISTER(arg_serialization, fclosep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd", "1", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...]\n\n" - "%sSandbox and execute processes.%s\n\n" - " -h --help Show this help and exit\n" - " --version Print version string and exit\n" - " --log-target=TARGET Set log target (console, journal,\n" - " journal-or-kmsg,\n" - " kmsg, null)\n" - " --log-level=LEVEL Set log level (debug, info, notice,\n" - " warning, err, crit,\n" - " alert, emerg)\n" - " --log-color=BOOL Highlight important messages\n" - " --log-location=BOOL Include code location in messages\n" - " --log-time=BOOL Prefix messages with current time\n" - " --deserialize=FD Deserialize process config from FD\n" - "\nSee the %s for details.\n", + "%sSandbox and execute processes.%s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - COMMON_GETOPT_ARGS, - ARG_VERSION, - ARG_DESERIALIZE, - }; - - static const struct option options[] = { - { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, - { "log-target", required_argument, NULL, ARG_LOG_TARGET }, - { "log-color", required_argument, NULL, ARG_LOG_COLOR }, - { "log-location", required_argument, NULL, ARG_LOG_LOCATION }, - { "log-time", required_argument, NULL, ARG_LOG_TIME }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "deserialize", required_argument, NULL, ARG_DESERIALIZE }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_LOG_LEVEL: - r = log_set_max_level_from_string(optarg); + OPTION_COMMON_LOG_LEVEL: + r = log_set_max_level_from_string(arg); if (r < 0) - return log_error_errno(r, "Failed to parse log level \"%s\": %m", optarg); - + return log_error_errno(r, "Failed to parse log level \"%s\": %m", arg); break; - case ARG_LOG_TARGET: - r = log_set_target_from_string(optarg); + OPTION_COMMON_LOG_TARGET: + r = log_set_target_from_string(arg); if (r < 0) - return log_error_errno(r, "Failed to parse log target \"%s\": %m", optarg); - + return log_error_errno(r, "Failed to parse log target \"%s\": %m", arg); break; - case ARG_LOG_COLOR: - r = log_show_color_from_string(optarg); + OPTION_COMMON_LOG_COLOR: + r = log_show_color_from_string(arg); if (r < 0) - return log_error_errno( - r, - "Failed to parse log color setting \"%s\": %m", - optarg); - + return log_error_errno(r, "Failed to parse log color setting \"%s\": %m", arg); break; - case ARG_LOG_LOCATION: - r = log_show_location_from_string(optarg); + OPTION_COMMON_LOG_LOCATION: + r = log_show_location_from_string(arg); if (r < 0) - return log_error_errno( - r, - "Failed to parse log location setting \"%s\": %m", - optarg); - + return log_error_errno(r, "Failed to parse log location setting \"%s\": %m", arg); break; - case ARG_LOG_TIME: - r = log_show_time_from_string(optarg); + OPTION_COMMON_LOG_TIME: + r = log_show_time_from_string(arg); if (r < 0) - return log_error_errno( - r, - "Failed to parse log time setting \"%s\": %m", - optarg); - + return log_error_errno(r, "Failed to parse log time setting \"%s\": %m", arg); break; - case ARG_DESERIALIZE: { + OPTION_LONG("deserialize", "FD", "Deserialize process config from FD"): { _cleanup_close_ int fd = -EBADF; FILE *f; - fd = parse_fd(optarg); + fd = parse_fd(arg); if (fd < 0) - return log_error_errno(fd, - "Failed to parse serialization fd \"%s\": %m", - optarg); + return log_error_errno(fd, "Failed to parse serialization fd \"%s\": %m", arg); r = fd_cloexec(fd, /* cloexec= */ true); if (r < 0) - return log_error_errno(r, - "Failed to set serialization fd %d to close-on-exec: %m", + return log_error_errno(r, "Failed to set serialization fd %d to close-on-exec: %m", fd); f = take_fdopen(&fd, "r"); @@ -159,15 +124,8 @@ static int parse_argv(int argc, char *argv[]) { safe_fclose(arg_serialization); arg_serialization = f; - break; } - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (!arg_serialization) diff --git a/src/shared/options.h b/src/shared/options.h index e8256059d15cb..262196c8d95af 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -60,6 +60,18 @@ typedef struct Option { OPTION_LONG("no-pager", NULL, "Do not start a pager") #define OPTION_COMMON_NO_LEGEND \ OPTION_LONG("no-legend", NULL, "Do not show headers and footers") +#define OPTION_COMMON_LOG_LEVEL \ + OPTION_LONG("log-level", "LEVEL", \ + "Set log level (debug, info, notice, warning, err, crit, alert, emerg)") +#define OPTION_COMMON_LOG_TARGET \ + OPTION_LONG("log-target", "TARGET", \ + "Set log target (console, journal, journal-or-kmsg, kmsg, null)") +#define OPTION_COMMON_LOG_COLOR \ + OPTION_LONG("log-color", "BOOL", "Highlight important messages") +#define OPTION_COMMON_LOG_LOCATION \ + OPTION_LONG("log-location", "BOOL", "Include code location in messages") +#define OPTION_COMMON_LOG_TIME \ + OPTION_LONG("log-time", "BOOL", "Prefix messages with current time") #define OPTION_COMMON_CAT_CONFIG \ OPTION_LONG("cat-config", NULL, "Show configuration files") #define OPTION_COMMON_TLDR \ From 47237cf6238e5a8bcbd866cc091d9b1aa0d9c300 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20L=C3=BCke?= Date: Mon, 13 Apr 2026 21:21:39 +0900 Subject: [PATCH 0956/1296] vmspawn: Support RUNTIME_DIRECTORY again In ccecae0efd ("vmspawn: use machine name in runtime directory path") support for RUNTIME_DIRECTORY was dropped which makes it difficult to run systemd-vmspawn in a service unit which doesn't have write access to the regular /run but should use its own managed RUNTIME_DIRECTORY. What worked before was --keep-unit --system but we can't use XDG_RUNTIME_DIR and --user because then --keep-unit breaks which we need because it can't create a scope as there is no session. Switch back to runtime_directory which handles RUNTIME_DIRECTORY and tells us whether we should use it as is without later cleanup or if we need to use the regular path where we create and delete the directory ourselves. --- src/vmspawn/vmspawn.c | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index c1b913217a7cf..fe7d307a637e9 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -2387,7 +2387,13 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return log_oom(); /* Create our runtime directory. We need this for the QEMU config file, TPM state, virtiofsd - * sockets, runtime mounts, and SSH key material. */ + * sockets, runtime mounts, and SSH key material. + * + * Use runtime_directory() (not _generic()) so that when vmspawn runs in a systemd service + * with RuntimeDirectory= set, we pick up $RUNTIME_DIRECTORY and place our stuff into the + * directory the service manager prepared for us. When the env var is unset, we fall back + * to /run/systemd/vmspawn// (or the $XDG_RUNTIME_DIR equivalent in user scope) + * and take care of creation and destruction ourselves. */ _cleanup_free_ char *runtime_dir = NULL, *runtime_dir_suffix = NULL; _cleanup_(rm_rf_physical_and_freep) char *runtime_dir_destroy = NULL; @@ -2395,21 +2401,27 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (!runtime_dir_suffix) return log_oom(); - r = runtime_directory_generic(arg_runtime_scope, runtime_dir_suffix, &runtime_dir); + r = runtime_directory(arg_runtime_scope, runtime_dir_suffix, &runtime_dir); if (r < 0) return log_error_errno(r, "Failed to determine runtime directory: %m"); - - /* If a previous vmspawn instance was killed without cleanup (e.g. SIGKILL), the directory may - * already exist with stale contents. This is harmless: varlink's sockaddr_un_unlink() removes stale - * sockets before bind(), and other files (QEMU config, SSH keys) are created fresh. This matches - * nspawn's approach of not proactively cleaning stale runtime directories. */ - r = mkdir_p(runtime_dir, 0755); - if (r < 0) - return log_error_errno(r, "Failed to create runtime directory '%s': %m", runtime_dir); - - runtime_dir_destroy = strdup(runtime_dir); - if (!runtime_dir_destroy) - return log_oom(); + if (r > 0) { + /* $RUNTIME_DIRECTORY was not set, so we got the fallback path and need to create and + * clean up the directory ourselves. + * + * If a previous vmspawn instance was killed without cleanup (e.g. SIGKILL), the directory may + * already exist with stale contents. This is harmless: varlink's sockaddr_un_unlink() removes stale + * sockets before bind(), and other files (QEMU config, SSH keys) are created fresh. This matches + * nspawn's approach of not proactively cleaning stale runtime directories. */ + r = mkdir_p(runtime_dir, 0755); + if (r < 0) + return log_error_errno(r, "Failed to create runtime directory '%s': %m", runtime_dir); + + runtime_dir_destroy = strdup(runtime_dir); + if (!runtime_dir_destroy) + return log_oom(); + } + /* When $RUNTIME_DIRECTORY is set the service manager created the directory for us and + * will destroy it (or preserve it, per RuntimeDirectoryPreserve=) when the service stops. */ log_debug("Using runtime directory: %s", runtime_dir); From 29c00e02950d9d84c34b41df519435c7749bd05f Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Mon, 13 Apr 2026 02:53:23 -0700 Subject: [PATCH 0957/1296] varlink: move shared enum types to varlink-idl-common Move ExecOutputType, CGroupPressureWatch, EmergencyAction and ManagedOOMMode enum type definitions from varlink-io.systemd.Unit to varlink-idl-common, as these types are shared across multiple varlink interfaces. Co-developed-by: Claude Opus 4.6 --- src/shared/varlink-idl-common.c | 46 ++++++++++++++++++++++++++++ src/shared/varlink-idl-common.h | 4 +++ src/shared/varlink-io.systemd.Unit.c | 46 ---------------------------- src/shared/varlink-io.systemd.Unit.h | 4 --- src/test/test-varlink-idl-unit.c | 1 + 5 files changed, 51 insertions(+), 50 deletions(-) diff --git a/src/shared/varlink-idl-common.c b/src/shared/varlink-idl-common.c index 1dc44cd16c7a7..f8f37c480c4d9 100644 --- a/src/shared/varlink-idl-common.c +++ b/src/shared/varlink-idl-common.c @@ -53,3 +53,49 @@ SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD_BY_TYPE(NICE, ResourceLimit, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD_BY_TYPE(RTPRIO, ResourceLimit, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD_BY_TYPE(RTTIME, ResourceLimit, SD_VARLINK_NULLABLE)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ExecOutputType, + SD_VARLINK_DEFINE_ENUM_VALUE(inherit), + SD_VARLINK_DEFINE_ENUM_VALUE(null), + SD_VARLINK_DEFINE_ENUM_VALUE(tty), + SD_VARLINK_DEFINE_ENUM_VALUE(kmsg), + SD_VARLINK_DEFINE_ENUM_VALUE(kmsg_console), + SD_VARLINK_DEFINE_ENUM_VALUE(journal), + SD_VARLINK_DEFINE_ENUM_VALUE(journal_console), + SD_VARLINK_DEFINE_ENUM_VALUE(socket), + SD_VARLINK_DEFINE_ENUM_VALUE(fd), + SD_VARLINK_DEFINE_ENUM_VALUE(file), + SD_VARLINK_DEFINE_ENUM_VALUE(append), + SD_VARLINK_DEFINE_ENUM_VALUE(truncate)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + CGroupPressureWatch, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(yes), + SD_VARLINK_DEFINE_ENUM_VALUE(auto), + SD_VARLINK_DEFINE_ENUM_VALUE(skip)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ManagedOOMMode, + SD_VARLINK_DEFINE_ENUM_VALUE(auto), + SD_VARLINK_DEFINE_ENUM_VALUE(kill)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + EmergencyAction, + SD_VARLINK_DEFINE_ENUM_VALUE(none), + SD_VARLINK_DEFINE_ENUM_VALUE(exit), + SD_VARLINK_DEFINE_ENUM_VALUE(exit_force), + SD_VARLINK_DEFINE_ENUM_VALUE(reboot), + SD_VARLINK_DEFINE_ENUM_VALUE(reboot_force), + SD_VARLINK_DEFINE_ENUM_VALUE(reboot_immediate), + SD_VARLINK_DEFINE_ENUM_VALUE(poweroff), + SD_VARLINK_DEFINE_ENUM_VALUE(poweroff_force), + SD_VARLINK_DEFINE_ENUM_VALUE(poweroff_immediate), + SD_VARLINK_DEFINE_ENUM_VALUE(soft_reboot), + SD_VARLINK_DEFINE_ENUM_VALUE(soft_reboot_force), + SD_VARLINK_DEFINE_ENUM_VALUE(kexec), + SD_VARLINK_DEFINE_ENUM_VALUE(kexec_force), + SD_VARLINK_DEFINE_ENUM_VALUE(halt), + SD_VARLINK_DEFINE_ENUM_VALUE(halt_force), + SD_VARLINK_DEFINE_ENUM_VALUE(halt_immediate)); diff --git a/src/shared/varlink-idl-common.h b/src/shared/varlink-idl-common.h index de5177de4b3f8..0385752276212 100644 --- a/src/shared/varlink-idl-common.h +++ b/src/shared/varlink-idl-common.h @@ -8,3 +8,7 @@ extern const sd_varlink_symbol vl_type_ProcessId; extern const sd_varlink_symbol vl_type_RateLimit; extern const sd_varlink_symbol vl_type_ResourceLimit; extern const sd_varlink_symbol vl_type_ResourceLimitTable; +extern const sd_varlink_symbol vl_type_ExecOutputType; +extern const sd_varlink_symbol vl_type_CGroupPressureWatch; +extern const sd_varlink_symbol vl_type_ManagedOOMMode; +extern const sd_varlink_symbol vl_type_EmergencyAction; diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c index aaa0374479d61..f13463917a7f1 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -14,21 +14,6 @@ SD_VARLINK_DEFINE_ENUM_TYPE( SD_VARLINK_DEFINE_ENUM_VALUE(data), SD_VARLINK_DEFINE_ENUM_VALUE(file)); -SD_VARLINK_DEFINE_ENUM_TYPE( - ExecOutputType, - SD_VARLINK_DEFINE_ENUM_VALUE(inherit), - SD_VARLINK_DEFINE_ENUM_VALUE(null), - SD_VARLINK_DEFINE_ENUM_VALUE(tty), - SD_VARLINK_DEFINE_ENUM_VALUE(kmsg), - SD_VARLINK_DEFINE_ENUM_VALUE(kmsg_console), - SD_VARLINK_DEFINE_ENUM_VALUE(journal), - SD_VARLINK_DEFINE_ENUM_VALUE(journal_console), - SD_VARLINK_DEFINE_ENUM_VALUE(socket), - SD_VARLINK_DEFINE_ENUM_VALUE(fd), - SD_VARLINK_DEFINE_ENUM_VALUE(file), - SD_VARLINK_DEFINE_ENUM_VALUE(append), - SD_VARLINK_DEFINE_ENUM_VALUE(truncate)); - SD_VARLINK_DEFINE_ENUM_TYPE( ExecUtmpMode, SD_VARLINK_DEFINE_ENUM_VALUE(init), @@ -118,48 +103,17 @@ SD_VARLINK_DEFINE_ENUM_TYPE( SD_VARLINK_DEFINE_ENUM_VALUE(closed), SD_VARLINK_DEFINE_ENUM_VALUE(strict)); -SD_VARLINK_DEFINE_ENUM_TYPE( - ManagedOOMMode, - SD_VARLINK_DEFINE_ENUM_VALUE(auto), - SD_VARLINK_DEFINE_ENUM_VALUE(kill)); - SD_VARLINK_DEFINE_ENUM_TYPE( ManagedOOMPreference, SD_VARLINK_DEFINE_ENUM_VALUE(none), SD_VARLINK_DEFINE_ENUM_VALUE(avoid), SD_VARLINK_DEFINE_ENUM_VALUE(omit)); -SD_VARLINK_DEFINE_ENUM_TYPE( - CGroupPressureWatch, - SD_VARLINK_DEFINE_ENUM_VALUE(no), - SD_VARLINK_DEFINE_ENUM_VALUE(yes), - SD_VARLINK_DEFINE_ENUM_VALUE(auto), - SD_VARLINK_DEFINE_ENUM_VALUE(skip)); - SD_VARLINK_DEFINE_ENUM_TYPE( CollectMode, SD_VARLINK_DEFINE_ENUM_VALUE(inactive), SD_VARLINK_DEFINE_ENUM_VALUE(inactive_or_failed)); -SD_VARLINK_DEFINE_ENUM_TYPE( - EmergencyAction, - SD_VARLINK_DEFINE_ENUM_VALUE(none), - SD_VARLINK_DEFINE_ENUM_VALUE(exit), - SD_VARLINK_DEFINE_ENUM_VALUE(exit_force), - SD_VARLINK_DEFINE_ENUM_VALUE(reboot), - SD_VARLINK_DEFINE_ENUM_VALUE(reboot_force), - SD_VARLINK_DEFINE_ENUM_VALUE(reboot_immediate), - SD_VARLINK_DEFINE_ENUM_VALUE(poweroff), - SD_VARLINK_DEFINE_ENUM_VALUE(poweroff_force), - SD_VARLINK_DEFINE_ENUM_VALUE(poweroff_immediate), - SD_VARLINK_DEFINE_ENUM_VALUE(soft_reboot), - SD_VARLINK_DEFINE_ENUM_VALUE(soft_reboot_force), - SD_VARLINK_DEFINE_ENUM_VALUE(kexec), - SD_VARLINK_DEFINE_ENUM_VALUE(kexec_force), - SD_VARLINK_DEFINE_ENUM_VALUE(halt), - SD_VARLINK_DEFINE_ENUM_VALUE(halt_force), - SD_VARLINK_DEFINE_ENUM_VALUE(halt_immediate)); - SD_VARLINK_DEFINE_ENUM_TYPE( JobMode, SD_VARLINK_DEFINE_ENUM_VALUE(fail), diff --git a/src/shared/varlink-io.systemd.Unit.h b/src/shared/varlink-io.systemd.Unit.h index ad621fdbfe078..96de86cb2e627 100644 --- a/src/shared/varlink-io.systemd.Unit.h +++ b/src/shared/varlink-io.systemd.Unit.h @@ -6,7 +6,6 @@ extern const sd_varlink_interface vl_interface_io_systemd_Unit; extern const sd_varlink_symbol vl_type_ExecInputType; -extern const sd_varlink_symbol vl_type_ExecOutputType; extern const sd_varlink_symbol vl_type_ExecUtmpMode; extern const sd_varlink_symbol vl_type_ExecPreserveMode; extern const sd_varlink_symbol vl_type_ExecKeyringMode; @@ -22,10 +21,7 @@ extern const sd_varlink_symbol vl_type_ProtectControlGroups; extern const sd_varlink_symbol vl_type_PrivatePIDs; extern const sd_varlink_symbol vl_type_PrivateBPF; extern const sd_varlink_symbol vl_type_CGroupDevicePolicy; -extern const sd_varlink_symbol vl_type_ManagedOOMMode; extern const sd_varlink_symbol vl_type_ManagedOOMPreference; -extern const sd_varlink_symbol vl_type_CGroupPressureWatch; extern const sd_varlink_symbol vl_type_CGroupController; extern const sd_varlink_symbol vl_type_CollectMode; -extern const sd_varlink_symbol vl_type_EmergencyAction; extern const sd_varlink_symbol vl_type_JobMode; diff --git a/src/test/test-varlink-idl-unit.c b/src/test/test-varlink-idl-unit.c index 408396ae76410..53b415ee7ec18 100644 --- a/src/test/test-varlink-idl-unit.c +++ b/src/test/test-varlink-idl-unit.c @@ -4,6 +4,7 @@ #include "tests.h" #include "test-varlink-idl-util.h" #include "unit.h" +#include "varlink-idl-common.h" #include "varlink-io.systemd.Unit.h" TEST(unit_enums_idl) { From 8383d033ccac39dd6cbaf32b0122c8b3ec99fe84 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 13 Apr 2026 16:27:10 +0200 Subject: [PATCH 0958/1296] ci: Two claude-review fixes - Use persist-credentials: false for actions/checkout, so we don't leak the github token credentials to subsequent jobs. - Remove one / from the Edit/Write permissions. Currently, with the absolute path from github.workspace, we expand to three slashes while we only need two. --- .github/workflows/claude-review.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index bf20e7d51e9b9..3829313cf97d8 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -150,6 +150,7 @@ jobs: with: # Need full history for git worktree add to work on all PR commits. fetch-depth: 0 + persist-credentials: false - name: Download PR context uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c @@ -200,8 +201,8 @@ jobs: "allow": [ "Bash", "Read", - "Edit(//${{ github.workspace }}/**)", - "Write(//${{ github.workspace }}/**)", + "Edit(/${{ github.workspace }}/**)", + "Write(/${{ github.workspace }}/**)", "Grep", "Glob", "Agent", From b40ed2067fb669540b1a640e293334fd31403676 Mon Sep 17 00:00:00 2001 From: rusty-snake <41237666+rusty-snake@users.noreply.github.com> Date: Mon, 13 Apr 2026 17:41:33 +0200 Subject: [PATCH 0959/1296] docs: fix capability name, it's CAP_MKNOD not CAP_SYS_MKNOD (#41621) --- docs/CONTAINER_INTERFACE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/CONTAINER_INTERFACE.md b/docs/CONTAINER_INTERFACE.md index cb7719557fd17..72f6f4dc7ec3b 100644 --- a/docs/CONTAINER_INTERFACE.md +++ b/docs/CONTAINER_INTERFACE.md @@ -403,9 +403,9 @@ its user to 2 (to effectively disallow `fork()`ing) you cannot run more than one Avahi instance on the entire system... People have been asking to be able to run systemd without `CAP_SYS_ADMIN` and -`CAP_SYS_MKNOD` in the container. This is now supported to some level in +`CAP_MKNOD` in the container. This is now supported to some level in systemd, but we recommend against it (see above). If `CAP_SYS_ADMIN` and -`CAP_SYS_MKNOD` are missing from the container systemd will now gracefully turn +`CAP_MKNOD` are missing from the container systemd will now gracefully turn off `PrivateTmp=`, `PrivateNetwork=`, `ProtectHome=`, `ProtectSystem=` and others, because those capabilities are required to implement these options. The services using these settings (which include many of systemd's own) will hence From 1716467bfa8c7c91d2ae1442425b168111fcbee4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 13 Apr 2026 17:46:00 +0200 Subject: [PATCH 0960/1296] NEWS: pre-announce removal of /run/boot-loader-entries/ support in logind logind could read UAPI.1 Boot Loader Spec entries from /run/boot-loader-entries/ in addition to ESP/XBOOTLDR. This was pretty half-assed, and to my knowledge was never actually used much. Let's remove support for it and simplify our codebase. Let's schedule it for removal via NEWS in a future version, to give people a chance to speak up. --- NEWS | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/NEWS b/NEWS index b440af5939618..451e3f1b79603 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,21 @@ systemd System and Service Manager CHANGES WITH 261 in spe: + Announcements of Future Feature Removals and Incompatible Changes: + + * systemd-logind's integration with the UAPI.1 Boot Loader + Specification (which allows systemctl reboot --boot-loader-entry= + switch to work) so far has supported a special directory + /run/boot-loader-entries/ which allowed defining boot loader entries + outside of the ESP/XBOOTLDR partition for compatibility with legacy + systems that do not natively implement UAPI.1. However, it appears + that (to our knowledge) it is not actually being used by any project + (quite unlike UAPI.1 itself, which found adoption far beyond + systemd), and its implementation is incomplete. With the future 262 + release we intend to remove support for /run/boot-loader-entries/ and + related interfaces, in order to simplify our codebase. Support for + UAPI.1 is – of course – kept in place. + Feature Removals and Incompatible Changes: * systemd-nspawn's --user= option has been renamed to --uid=. The -u From 26fd286210964a76c5e1a52a416626f7dde53936 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 13 Apr 2026 20:21:25 +0100 Subject: [PATCH 0961/1296] core: add missing SELinux access checks when listing units Add mac_selinux_unit_access_check_varlink() to the unit enumeration loop in vl_method_list_units(), silently skipping units the caller is not permitted to see, matching the D-Bus ListUnits behavior. Add mac_selinux_access_check_varlink() to vl_method_describe_manager(). Follow-up for 472abf7bec89caeb1cc413c1de17984ab8ccb5d6 Follow-up for 736349958efe34089131ca88950e2e5bb391d36a --- src/core/varlink-manager.c | 4 ++++ src/core/varlink-unit.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index 997bdc08d0122..6ae837376a970 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -207,6 +207,10 @@ int vl_method_describe_manager(sd_varlink *link, sd_json_variant *parameters, sd if (r != 0) return r; + r = mac_selinux_access_check_varlink(link, "status"); + if (r < 0) + return r; + r = sd_json_buildo( &v, SD_JSON_BUILD_PAIR_CALLBACK("context", manager_context_build_json, manager), diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index 2404c553d7fc9..e8b86845a20e5 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -523,6 +523,10 @@ int vl_method_list_units(sd_varlink *link, sd_json_variant *parameters, sd_varli if (k != unit->id) continue; + r = mac_selinux_unit_access_check_varlink(unit, link, "status"); + if (r < 0) + continue; /* silently skip units the caller is not allowed to see */ + r = list_unit_one(link, unit); if (r < 0) return r; From 04f32dddd7221de01c4da70128bd5fb21bc53427 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 13 Apr 2026 22:11:27 +0100 Subject: [PATCH 0962/1296] core: check selinux access on each unit when listing Units might have different access rules, so check the access on each unit when querying the full list. --- src/core/dbus-manager.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 78cab48f852fc..5a7f70d78bf6f 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -1265,10 +1265,6 @@ static int list_units_filtered(sd_bus_message *message, void *userdata, sd_bus_e /* Anyone can call this method */ - r = mac_selinux_access_check(message, "status", reterr_error); - if (r < 0) - return r; - r = sd_bus_message_new_method_return(message, &reply); if (r < 0) return r; @@ -1281,6 +1277,10 @@ static int list_units_filtered(sd_bus_message *message, void *userdata, sd_bus_e if (k != u->id) continue; + r = mac_selinux_unit_access_check(u, message, "status", /* reterr_error= */ NULL); + if (r < 0) + continue; /* silently skip units the caller is not allowed to see */ + if (!unit_passes_filter(u, states, patterns)) continue; From 4bf9db731445ba72a9e5097561e1883dfe1183d8 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 13 Apr 2026 20:26:22 +0100 Subject: [PATCH 0963/1296] logind: reject wall messages containing control characters method_set_wall_message() and the property setter only checked the message length but not its content. Since wall messages are broadcast to all TTYs, control characters in the message could interfere with terminal state. Reject messages containing control characters other than newline and tab. Follow-up for 9ef15026c0e7e6600372056c43442c99ec53746e Follow-up for e2fa5721c3ee5ea400b99a6463e8c1c257e20415 --- src/login/logind-dbus.c | 50 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index bef9f51aef834..9668bf0609868 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -59,6 +59,7 @@ #include "signal-util.h" #include "sleep-config.h" #include "stdio-util.h" +#include "string-util.h" #include "strv.h" #include "terminal-util.h" #include "tmpfile-util.h" @@ -3582,6 +3583,46 @@ static int property_get_boot_loader_entries( return sd_bus_message_close_container(reply); } +static int wall_message_validate(const char *wall_message, sd_bus_error *error) { + if (strlen(wall_message) > WALL_MESSAGE_MAX) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Wall message too long, maximum permitted length is %u characters.", + WALL_MESSAGE_MAX); + + if (string_has_cc(wall_message, /* ok= */ "\n\t")) + return sd_bus_error_set(error, + SD_BUS_ERROR_INVALID_ARGS, + "Wall message contains control characters, refusing."); + + return 0; +} + +static int property_set_wall_message( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *value, + void *userdata, + sd_bus_error *error) { + + char **p = ASSERT_PTR(userdata); + const char *s; + int r; + + assert(value); + + r = sd_bus_message_read(value, "s", &s); + if (r < 0) + return r; + + r = wall_message_validate(s, error); + if (r < 0) + return r; + + return free_and_strdup_warn(p, empty_to_null(s)); +} + static int method_set_wall_message( sd_bus_message *message, void *userdata, @@ -3598,10 +3639,9 @@ static int method_set_wall_message( if (r < 0) return r; - if (strlen(wall_message) > WALL_MESSAGE_MAX) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, - "Wall message too long, maximum permitted length is %u characters.", - WALL_MESSAGE_MAX); + r = wall_message_validate(wall_message, error); + if (r < 0) + return r; /* Short-circuit the operation if the desired state is already in place, to * avoid an unnecessary polkit permission check. */ @@ -3764,7 +3804,7 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_WRITABLE_PROPERTY("EnableWallMessages", "b", bus_property_get_bool, bus_property_set_bool, offsetof(Manager, wall_messages), 0), - SD_BUS_WRITABLE_PROPERTY("WallMessage", "s", NULL, NULL, offsetof(Manager, wall_message), 0), + SD_BUS_WRITABLE_PROPERTY("WallMessage", "s", NULL, property_set_wall_message, offsetof(Manager, wall_message), 0), SD_BUS_PROPERTY("NAutoVTs", "u", NULL, offsetof(Manager, n_autovts), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("KillOnlyUsers", "as", NULL, offsetof(Manager, kill_only_users), SD_BUS_VTABLE_PROPERTY_CONST), From a9e9288288567beae57337ae903dd3b6c774001c Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 13 Apr 2026 20:38:27 +0100 Subject: [PATCH 0964/1296] machined: pass user as positional argument in machine_default_shell_args() Instead of interpolating the user name directly into the sh -c script body via asprintf %s, pass it as a positional parameter ($1) in a separate argv entry. This avoids the user string being parsed as part of the shell script syntax. Also validate the user name in bus_machine_method_open_shell() with valid_user_group_name(), matching the validation already done on the Varlink path via json_dispatch_const_user_group_name(). Follow-up for 49af9e1368571f4e423cde0fd45ee284451434d1 --- src/machine/machine-dbus.c | 4 ++++ src/machine/machine.c | 22 +++++++++++++--------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index 2dab827d41c51..c096a012d88f6 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -26,6 +26,7 @@ #include "signal-util.h" #include "string-util.h" #include "strv.h" +#include "user-util.h" static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_class, machine_class, MachineClass); static BUS_DEFINE_PROPERTY_GET2(property_get_state, "s", Machine, machine_get_state, machine_state_to_string); @@ -366,6 +367,9 @@ int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bu return r; user = isempty(user) ? "root" : user; + if (!valid_user_group_name(user, VALID_USER_RELAX)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user name '%s'", user); + /* Ensure only root can shell into the root namespace. This is to avoid unprivileged users registering * a process they own in the root user namespace, and then shelling in as root or another user. Note that * the shell operation is privileged and requires 'auth_admin', so we do not need to check the caller's uid, diff --git a/src/machine/machine.c b/src/machine/machine.c index da26bdf7a0711..535128692ece4 100644 --- a/src/machine/machine.c +++ b/src/machine/machine.c @@ -1103,11 +1103,10 @@ int machine_start_shell( char** machine_default_shell_args(const char *user) { _cleanup_strv_free_ char **args = NULL; - int r; assert(user); - args = new0(char*, 3 + 1); + args = new0(char*, 5 + 1); if (!args) return NULL; @@ -1119,14 +1118,19 @@ char** machine_default_shell_args(const char *user) { if (!args[1]) return NULL; - r = asprintf(&args[2], - "shell=$(getent passwd %s 2>/dev/null | { IFS=: read _ _ _ _ _ _ x; echo \"$x\"; })\n"\ - "exec \"${shell:-/bin/sh}\" -l", /* -l is means --login */ - user); - if (r < 0) { - args[2] = NULL; + args[2] = strdup( + "shell=$(getent passwd \"$1\" 2>/dev/null | { IFS=: read _ _ _ _ _ _ x; echo \"$x\"; })\n" + "exec \"${shell:-/bin/sh}\" -l"); /* -l means --login */ + if (!args[2]) + return NULL; + + args[3] = strdup("sh"); /* $0 placeholder for sh -c */ + if (!args[3]) + return NULL; + + args[4] = strdup(user); /* becomes $1 in the script */ + if (!args[4]) return NULL; - } return TAKE_PTR(args); } From f125fc6a22167f3d52c97763e555b2d7d654788e Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 13 Apr 2026 21:02:10 +0100 Subject: [PATCH 0965/1296] journal-upload: also disable VERIFYHOST when --trust=all is used When --trust=all disables CURLOPT_SSL_VERIFYPEER, the residual CURLOPT_SSL_VERIFYHOST check is ineffective since an attacker can present a self-signed certificate with the expected hostname. Disable both for consistency and log that server certificate verification is disabled. Follow-up for 8847551bcbfa8265bae04f567bb1aadc7b480325 --- src/journal-remote/journal-upload.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c index c4eab80a1fc5a..e6cb5dabc2655 100644 --- a/src/journal-remote/journal-upload.c +++ b/src/journal-remote/journal-upload.c @@ -307,10 +307,13 @@ int start_upload(Uploader *u, LOG_ERR, return -EXFULL); } - if (STRPTR_IN_SET(arg_trust, "-", "all")) + if (STRPTR_IN_SET(arg_trust, "-", "all")) { + log_info("Server certificate verification disabled."); easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L, LOG_ERR, return -EUCLEAN); - else if (arg_trust || startswith(u->url, "https://")) + easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L, + LOG_ERR, return -EUCLEAN); + } else if (arg_trust || startswith(u->url, "https://")) easy_setopt(curl, CURLOPT_CAINFO, arg_trust ?: TRUST_FILE, LOG_ERR, return -EXFULL); From 765416cae6129b23ef108e19d0d41f8f59788019 Mon Sep 17 00:00:00 2001 From: Sriman Achanta Date: Mon, 13 Apr 2026 20:08:30 -0400 Subject: [PATCH 0966/1296] hwdb: Add extended SteelSeries Arctis headset device support (#41628) Add USB device IDs for additional SteelSeries Arctis headset models to the sound card hardware database. Newly added device IDs: - Arctis Nova 7x v2 (22AD) - Arctis Nova 7 Diablo IV (22A9) - Arctis Nova 7X (22A4) - Arctis Nova 7X (22A5) - Arctis Nova 7P V2 (22A7) --- hwdb.d/70-sound-card.hwdb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hwdb.d/70-sound-card.hwdb b/hwdb.d/70-sound-card.hwdb index 4c53a861ecc34..03a0a5eefc28c 100644 --- a/hwdb.d/70-sound-card.hwdb +++ b/hwdb.d/70-sound-card.hwdb @@ -69,6 +69,11 @@ usb:v1038p227A* usb:v1038p22A1* usb:v1038p227E* usb:v1038p229E* +usb:v1038p22AD* +usb:v1038p22A9* +usb:v1038p22A4* +usb:v1038p22A5* +usb:v1038p22A7* usb:v1038p12E0* usb:v1038p12E5* SOUND_FORM_FACTOR=headset From 275ec1160b62c90dad3264e484e976213b0ada30 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 11 Apr 2026 00:15:13 +0100 Subject: [PATCH 0967/1296] importd: harden curl file protocol handling With old libcurl versions file:// can get redirects which can be messy, while the new version rejects them. Set an option to explicit block them. --- src/import/curl-util.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/import/curl-util.c b/src/import/curl-util.c index 48842a20b700b..52321d08e1a43 100644 --- a/src/import/curl-util.c +++ b/src/import/curl-util.c @@ -293,6 +293,8 @@ int curl_glue_make(CURL **ret, const char *url, void *userdata) { if (curl_easy_setopt(c, CURLOPT_PROTOCOLS_STR, "HTTP,HTTPS,FILE") != CURLE_OK) #else if (curl_easy_setopt(c, CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS|CURLPROTO_FILE) != CURLE_OK) + return -EIO; + if (curl_easy_setopt(c, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS) != CURLE_OK) #endif return -EIO; From 56836138b0752c1704585a573e4b12dd28f70b9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 10 Apr 2026 23:18:10 +0200 Subject: [PATCH 0968/1296] executor: do not abort on invalid serialization fd E.g. --deserialize=15 would cause the program to abrt in safe_close. But in fact, we shouldn't try to do the close in any case: if the fd is not valid, we should return an error without modifying state. And if it _is_ valid, we set O_CLOEXEC on it, so it'll be closed automatically later. --- src/core/executor.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/core/executor.c b/src/core/executor.c index 80d4d8c71ae44..8089d376fe830 100644 --- a/src/core/executor.c +++ b/src/core/executor.c @@ -16,7 +16,6 @@ #include "exit-status.h" #include "fd-util.h" #include "fdset.h" -#include "fileio.h" #include "format-table.h" #include "label-util.h" #include "log.h" @@ -106,19 +105,19 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION_LONG("deserialize", "FD", "Deserialize process config from FD"): { - _cleanup_close_ int fd = -EBADF; - FILE *f; - - fd = parse_fd(arg); + int fd = parse_fd(arg); if (fd < 0) return log_error_errno(fd, "Failed to parse serialization fd \"%s\": %m", arg); + /* Set O_CLOEXEC and as a side effect, verify that the fd is valid. */ r = fd_cloexec(fd, /* cloexec= */ true); + if (r == -EBADF) + return log_error_errno(r, "Serialization fd %d is not valid.", fd); if (r < 0) return log_error_errno(r, "Failed to set serialization fd %d to close-on-exec: %m", fd); - f = take_fdopen(&fd, "r"); + FILE *f = fdopen(fd, "r"); if (!f) return log_error_errno(errno, "Failed to open serialization fd %d: %m", fd); From 961e92854aac36444c91fab3957b3ed14d91e7f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 13 Apr 2026 14:20:43 +0200 Subject: [PATCH 0969/1296] executor: move reopening of the console after option parsing It seems to be interfering with systemd:check-help-systemd-executor test in CI. In practice, any messages from parse_argv() are going to be from manual invocations, since if called from PID1 the option syntax is going to be correct. So I hope this fixes the redirection of --help but otherwise is of little consequence. --- src/core/executor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/executor.c b/src/core/executor.c index 8089d376fe830..94bb481db43a4 100644 --- a/src/core/executor.c +++ b/src/core/executor.c @@ -156,7 +156,6 @@ static int run(int argc, char *argv[]) { cgroup_context_init(&cgroup_context); /* We might be starting the journal itself, we'll be told by the caller what to do */ - log_set_always_reopen_console(true); log_set_prohibit_ipc(true); log_setup(); @@ -165,6 +164,7 @@ static int run(int argc, char *argv[]) { return r; /* Now that we know the intended log target, allow IPC and open the final log target. */ + log_set_always_reopen_console(true); log_set_prohibit_ipc(false); log_open(); From b5aa7635ce6b78bec6700406aa58ffafc87412a6 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Mon, 13 Apr 2026 02:53:38 -0700 Subject: [PATCH 0970/1296] varlink: add enum types for configuration settings in io.systemd.Manager Convert 8 string fields in the io.systemd.Manager varlink interface to proper enum types: - LogTarget: new enum (console, console_prefixed, kmsg, journal, ...) - DefaultStandardOutput/Error: reuse ExecOutputType from common - DefaultMemory/CPU/IOPressureWatch: reuse CGroupPressureWatch from common - DefaultOOMPolicy: new enum (continue, stop, kill) - CtrlAltDelBurstAction: reuse EmergencyAction from common Output serialization updated to use JSON_BUILD_PAIR_ENUM for automatic underscorification of dash-containing values. Co-developed-by: Claude Opus 4.6 --- src/core/varlink-manager.c | 16 +++++----- src/shared/varlink-io.systemd.Manager.c | 41 +++++++++++++++++++------ src/shared/varlink-io.systemd.Manager.h | 3 ++ src/test/meson.build | 3 ++ src/test/test-varlink-idl-manager.c | 25 +++++++++++++++ 5 files changed, 71 insertions(+), 17 deletions(-) create mode 100644 src/test/test-varlink-idl-manager.c diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index 997bdc08d0122..b0cdf51b70fb7 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -88,10 +88,10 @@ static int manager_context_build_json(sd_json_variant **ret, const char *name, v ASSERT_PTR(ret), SD_JSON_BUILD_PAIR_BOOLEAN("ShowStatus", manager_get_show_status_on(m)), JSON_BUILD_PAIR_CALLBACK_NON_NULL("LogLevel", log_level_build_json, m), - SD_JSON_BUILD_PAIR_STRING("LogTarget", log_target_to_string(log_get_target())), + JSON_BUILD_PAIR_ENUM("LogTarget", log_target_to_string(log_get_target())), JSON_BUILD_PAIR_CALLBACK_NON_NULL("Environment", manager_environment_build_json, m), - SD_JSON_BUILD_PAIR_STRING("DefaultStandardOutput", exec_output_to_string(m->defaults.std_output)), - SD_JSON_BUILD_PAIR_STRING("DefaultStandardError", exec_output_to_string(m->defaults.std_error)), + JSON_BUILD_PAIR_ENUM("DefaultStandardOutput", exec_output_to_string(m->defaults.std_output)), + JSON_BUILD_PAIR_ENUM("DefaultStandardError", exec_output_to_string(m->defaults.std_error)), SD_JSON_BUILD_PAIR_BOOLEAN("ServiceWatchdogs", m->service_watchdogs), JSON_BUILD_PAIR_FINITE_USEC("DefaultTimerAccuracyUSec", m->defaults.timer_accuracy_usec), JSON_BUILD_PAIR_FINITE_USEC("DefaultTimeoutStartUSec", m->defaults.timeout_start_usec), @@ -107,11 +107,11 @@ static int manager_context_build_json(sd_json_variant **ret, const char *name, v SD_JSON_BUILD_PAIR_CALLBACK("DefaultLimits", rlimit_table_build_json, m->defaults.rlimit), SD_JSON_BUILD_PAIR_UNSIGNED("DefaultTasksMax", cgroup_tasks_max_resolve(&m->defaults.tasks_max)), JSON_BUILD_PAIR_FINITE_USEC("DefaultMemoryPressureThresholdUSec", m->defaults.pressure[PRESSURE_MEMORY].threshold_usec), - SD_JSON_BUILD_PAIR_STRING("DefaultMemoryPressureWatch", cgroup_pressure_watch_to_string(m->defaults.pressure[PRESSURE_MEMORY].watch)), + JSON_BUILD_PAIR_ENUM("DefaultMemoryPressureWatch", cgroup_pressure_watch_to_string(m->defaults.pressure[PRESSURE_MEMORY].watch)), JSON_BUILD_PAIR_FINITE_USEC("DefaultCPUPressureThresholdUSec", m->defaults.pressure[PRESSURE_CPU].threshold_usec), - SD_JSON_BUILD_PAIR_STRING("DefaultCPUPressureWatch", cgroup_pressure_watch_to_string(m->defaults.pressure[PRESSURE_CPU].watch)), + JSON_BUILD_PAIR_ENUM("DefaultCPUPressureWatch", cgroup_pressure_watch_to_string(m->defaults.pressure[PRESSURE_CPU].watch)), JSON_BUILD_PAIR_FINITE_USEC("DefaultIOPressureThresholdUSec", m->defaults.pressure[PRESSURE_IO].threshold_usec), - SD_JSON_BUILD_PAIR_STRING("DefaultIOPressureWatch", cgroup_pressure_watch_to_string(m->defaults.pressure[PRESSURE_IO].watch)), + JSON_BUILD_PAIR_ENUM("DefaultIOPressureWatch", cgroup_pressure_watch_to_string(m->defaults.pressure[PRESSURE_IO].watch)), JSON_BUILD_PAIR_FINITE_USEC("RuntimeWatchdogUSec", manager_get_watchdog(m, WATCHDOG_RUNTIME)), JSON_BUILD_PAIR_FINITE_USEC("RebootWatchdogUSec", manager_get_watchdog(m, WATCHDOG_REBOOT)), JSON_BUILD_PAIR_FINITE_USEC("KExecWatchdogUSec", manager_get_watchdog(m, WATCHDOG_KEXEC)), @@ -119,10 +119,10 @@ static int manager_context_build_json(sd_json_variant **ret, const char *name, v JSON_BUILD_PAIR_STRING_NON_EMPTY("RuntimeWatchdogPreGovernor", m->watchdog_pretimeout_governor), JSON_BUILD_PAIR_STRING_NON_EMPTY("WatchdogDevice", watchdog_get_device()), JSON_BUILD_PAIR_FINITE_USEC("TimerSlackNSec", (uint64_t) prctl(PR_GET_TIMERSLACK)), - SD_JSON_BUILD_PAIR_STRING("DefaultOOMPolicy", oom_policy_to_string(m->defaults.oom_policy)), + JSON_BUILD_PAIR_ENUM("DefaultOOMPolicy", oom_policy_to_string(m->defaults.oom_policy)), SD_JSON_BUILD_PAIR_INTEGER("DefaultOOMScoreAdjust", m->defaults.oom_score_adjust), SD_JSON_BUILD_PAIR_BOOLEAN("DefaultRestrictSUIDSGID", m->defaults.restrict_suid_sgid), - SD_JSON_BUILD_PAIR_STRING("CtrlAltDelBurstAction", emergency_action_to_string(m->cad_burst_action)), + JSON_BUILD_PAIR_ENUM("CtrlAltDelBurstAction", emergency_action_to_string(m->cad_burst_action)), SD_JSON_BUILD_PAIR_BOOLEAN("DefaultMemoryZSwapWriteback", m->defaults.memory_zswap_writeback), JSON_BUILD_PAIR_STRING_NON_EMPTY("ConfirmSpawn", manager_get_confirm_spawn(m)), JSON_BUILD_PAIR_STRING_NON_EMPTY("ControlGroup", m->cgroup_root)); diff --git a/src/shared/varlink-io.systemd.Manager.c b/src/shared/varlink-io.systemd.Manager.c index 9ce1b8350abee..0c5ab53702b0d 100644 --- a/src/shared/varlink-io.systemd.Manager.c +++ b/src/shared/varlink-io.systemd.Manager.c @@ -3,6 +3,24 @@ #include "varlink-idl-common.h" #include "varlink-io.systemd.Manager.h" +SD_VARLINK_DEFINE_ENUM_TYPE( + LogTarget, + SD_VARLINK_DEFINE_ENUM_VALUE(console), + SD_VARLINK_DEFINE_ENUM_VALUE(kmsg), + SD_VARLINK_DEFINE_ENUM_VALUE(journal), + SD_VARLINK_DEFINE_ENUM_VALUE(syslog), + SD_VARLINK_DEFINE_ENUM_VALUE(console_prefixed), + SD_VARLINK_DEFINE_ENUM_VALUE(journal_or_kmsg), + SD_VARLINK_DEFINE_ENUM_VALUE(syslog_or_kmsg), + SD_VARLINK_DEFINE_ENUM_VALUE(auto), + SD_VARLINK_DEFINE_ENUM_VALUE(null)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + OOMPolicy, + SD_VARLINK_DEFINE_ENUM_VALUE(continue), + SD_VARLINK_DEFINE_ENUM_VALUE(stop), + SD_VARLINK_DEFINE_ENUM_VALUE(kill)); + static SD_VARLINK_DEFINE_STRUCT_TYPE( LogLevelStruct, SD_VARLINK_FIELD_COMMENT("'console' target log level"), @@ -25,13 +43,13 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#LogColor="), SD_VARLINK_DEFINE_FIELD_BY_TYPE(LogLevel, LogLevelStruct, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#LogColor="), - SD_VARLINK_DEFINE_FIELD(LogTarget, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(LogTarget, LogTarget, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#ManagerEnvironment="), SD_VARLINK_DEFINE_FIELD(Environment, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultStandardOutput="), - SD_VARLINK_DEFINE_FIELD(DefaultStandardOutput, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(DefaultStandardOutput, ExecOutputType, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultStandardError="), - SD_VARLINK_DEFINE_FIELD(DefaultStandardError, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(DefaultStandardError, ExecOutputType, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#ServiceWatchdogs="), SD_VARLINK_DEFINE_FIELD(ServiceWatchdogs, SD_VARLINK_BOOL, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultTimerAccuracySec="), @@ -63,15 +81,15 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultMemoryPressureThresholdUSec="), SD_VARLINK_DEFINE_FIELD(DefaultMemoryPressureThresholdUSec, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultMemoryPressureWatch="), - SD_VARLINK_DEFINE_FIELD(DefaultMemoryPressureWatch, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(DefaultMemoryPressureWatch, CGroupPressureWatch, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultCPUPressureThresholdUSec="), SD_VARLINK_DEFINE_FIELD(DefaultCPUPressureThresholdUSec, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultCPUPressureWatch="), - SD_VARLINK_DEFINE_FIELD(DefaultCPUPressureWatch, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(DefaultCPUPressureWatch, CGroupPressureWatch, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultIOPressureThresholdUSec="), SD_VARLINK_DEFINE_FIELD(DefaultIOPressureThresholdUSec, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultIOPressureWatch="), - SD_VARLINK_DEFINE_FIELD(DefaultIOPressureWatch, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(DefaultIOPressureWatch, CGroupPressureWatch, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#RuntimeWatchdogSec="), SD_VARLINK_DEFINE_FIELD(RuntimeWatchdogUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#RebootWatchdogSec="), @@ -87,13 +105,13 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#TimerSlackNSec="), SD_VARLINK_DEFINE_FIELD(TimerSlackNSec, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultOOMPolicy="), - SD_VARLINK_DEFINE_FIELD(DefaultOOMPolicy, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(DefaultOOMPolicy, OOMPolicy, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultOOMScoreAdjust="), SD_VARLINK_DEFINE_FIELD(DefaultOOMScoreAdjust, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#DefaultRestrictSUIDSGID="), SD_VARLINK_DEFINE_FIELD(DefaultRestrictSUIDSGID, SD_VARLINK_BOOL, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd-system.conf.html#CtrlAltDelBurstAction="), - SD_VARLINK_DEFINE_FIELD(CtrlAltDelBurstAction, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(CtrlAltDelBurstAction, EmergencyAction, 0), SD_VARLINK_FIELD_COMMENT("The console on which systemd asks for confirmation when spawning processes"), SD_VARLINK_DEFINE_FIELD(ConfirmSpawn, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Root of the control group hierarchy that the manager is running in"), @@ -241,4 +259,9 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_ResourceLimit, &vl_type_ResourceLimitTable, &vl_type_RateLimit, - &vl_type_LogLevelStruct); + &vl_type_LogLevelStruct, + &vl_type_LogTarget, + &vl_type_OOMPolicy, + &vl_type_ExecOutputType, + &vl_type_CGroupPressureWatch, + &vl_type_EmergencyAction); diff --git a/src/shared/varlink-io.systemd.Manager.h b/src/shared/varlink-io.systemd.Manager.h index ce411888f92c3..48247dd350679 100644 --- a/src/shared/varlink-io.systemd.Manager.h +++ b/src/shared/varlink-io.systemd.Manager.h @@ -4,3 +4,6 @@ #include "sd-varlink-idl.h" extern const sd_varlink_interface vl_interface_io_systemd_Manager; + +extern const sd_varlink_symbol vl_type_LogTarget; +extern const sd_varlink_symbol vl_type_OOMPolicy; diff --git a/src/test/meson.build b/src/test/meson.build index c93097181580d..241e64748538b 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -500,6 +500,9 @@ executables += [ core_test_template + { 'sources' : files('test-varlink-idl-unit.c'), }, + core_test_template + { + 'sources' : files('test-varlink-idl-manager.c'), + }, test_template + { 'sources' : files('test-watchdog.c'), 'type' : 'unsafe', diff --git a/src/test/test-varlink-idl-manager.c b/src/test/test-varlink-idl-manager.c new file mode 100644 index 0000000000000..da2533b2acd7e --- /dev/null +++ b/src/test/test-varlink-idl-manager.c @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "cgroup.h" +#include "emergency-action.h" +#include "execute.h" +#include "log.h" +#include "tests.h" +#include "test-varlink-idl-util.h" +#include "unit.h" +#include "varlink-idl-common.h" +#include "varlink-io.systemd.Manager.h" + +TEST(manager_enums_idl) { + /* ManagerContext enums */ + TEST_IDL_ENUM(LogTarget, log_target, vl_type_LogTarget); + TEST_IDL_ENUM(OOMPolicy, oom_policy, vl_type_OOMPolicy); + + /* ExecOutput values like "kmsg+console" contain '+' which becomes '_' via underscorify, + * but dashify won't restore it, so from_string round-trip fails. Test to_string direction only. */ + TEST_IDL_ENUM_TO_STRING(ExecOutput, exec_output, vl_type_ExecOutputType); + TEST_IDL_ENUM(CGroupPressureWatch, cgroup_pressure_watch, vl_type_CGroupPressureWatch); + TEST_IDL_ENUM(EmergencyAction, emergency_action, vl_type_EmergencyAction); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); From 2fda0fb4c772a16d9746ad8dcd07a3028c735f21 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Mon, 13 Apr 2026 03:25:21 -0700 Subject: [PATCH 0971/1296] varlink: add enum types for scheduling and mount settings in io.systemd.Unit Convert CPUSchedulingPolicy, IOSchedulingClass, NUMAPolicy and MountFlags fields from plain strings to proper varlink enum types in the io.systemd.Unit interface. Update the corresponding serialization code to use json_underscorify() for correct enum value formatting. Co-developed-by: Claude Opus 4.6 --- src/core/varlink-execute.c | 9 ++++-- src/shared/varlink-io.systemd.Unit.c | 42 +++++++++++++++++++++++++--- src/shared/varlink-io.systemd.Unit.h | 4 +++ src/test/test-varlink-idl-unit.c | 14 ++++++++++ 4 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/core/varlink-execute.c b/src/core/varlink-execute.c index 054a79401d7f3..3e43ad52890a6 100644 --- a/src/core/varlink-execute.c +++ b/src/core/varlink-execute.c @@ -27,6 +27,9 @@ #include "varlink-common.h" #include "varlink-execute.h" +#define JSON_BUILD_PAIR_MOUNT_PROPAGATION_FLAG(name, s) \ + SD_JSON_BUILD_PAIR_CONDITION(!isempty(s), name, JSON_BUILD_STRING_UNDERSCORIFY(s)) + static int working_directory_build_json(sd_json_variant **ret, const char *name, void *userdata) { ExecContext *c = ASSERT_PTR(userdata); @@ -262,7 +265,7 @@ static int cpu_sched_class_build_json(sd_json_variant **ret, const char *name, v if (r < 0) return log_debug_errno(r, "Failed to convert sched policy to string: %m"); - return sd_json_variant_new_string(ret, s); + return sd_json_variant_new_string(ret, json_underscorify(s)); } static int cpu_affinity_build_json(sd_json_variant **ret, const char *name, void *userdata) { @@ -343,7 +346,7 @@ static int ioprio_class_build_json(sd_json_variant **ret, const char *name, void if (r < 0) return log_debug_errno(r, "Failed to convert IO priority class to string: %m"); - return sd_json_variant_new_string(ret, s); + return sd_json_variant_new_string(ret, json_underscorify(s)); } static int exec_dir_build_json(sd_json_variant **ret, const char *name, void *userdata) { @@ -919,7 +922,7 @@ int unit_exec_context_build_json(sd_json_variant **ret, const char *name, void * SD_JSON_BUILD_PAIR_BOOLEAN("RestrictSUIDSGID", c->restrict_suid_sgid), SD_JSON_BUILD_PAIR_BOOLEAN("RemoveIPC", c->remove_ipc), JSON_BUILD_PAIR_TRISTATE_NON_NULL("PrivateMounts", c->private_mounts), - JSON_BUILD_PAIR_STRING_NON_EMPTY("MountFlags", mount_propagation_flag_to_string(c->mount_propagation_flag)), + JSON_BUILD_PAIR_MOUNT_PROPAGATION_FLAG("MountFlags", mount_propagation_flag_to_string(c->mount_propagation_flag)), /* System Call Filtering */ JSON_BUILD_PAIR_CALLBACK_NON_NULL("SystemCallFilter", syscall_filter_build_json, c), diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c index f13463917a7f1..394e7b819b550 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -149,6 +149,36 @@ SD_VARLINK_DEFINE_ENUM_TYPE( SD_VARLINK_DEFINE_ENUM_VALUE(no), SD_VARLINK_DEFINE_ENUM_VALUE(yes)); +SD_VARLINK_DEFINE_ENUM_TYPE( + CPUSchedulingPolicy, + SD_VARLINK_DEFINE_ENUM_VALUE(other), + SD_VARLINK_DEFINE_ENUM_VALUE(batch), + SD_VARLINK_DEFINE_ENUM_VALUE(idle), + SD_VARLINK_DEFINE_ENUM_VALUE(fifo), + SD_VARLINK_DEFINE_ENUM_VALUE(ext), + SD_VARLINK_DEFINE_ENUM_VALUE(rr)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + IOSchedulingClass, + SD_VARLINK_DEFINE_ENUM_VALUE(none), + SD_VARLINK_DEFINE_ENUM_VALUE(realtime), + SD_VARLINK_DEFINE_ENUM_VALUE(best_effort), + SD_VARLINK_DEFINE_ENUM_VALUE(idle)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + NUMAPolicy, + SD_VARLINK_DEFINE_ENUM_VALUE(default), + SD_VARLINK_DEFINE_ENUM_VALUE(preferred), + SD_VARLINK_DEFINE_ENUM_VALUE(bind), + SD_VARLINK_DEFINE_ENUM_VALUE(interleave), + SD_VARLINK_DEFINE_ENUM_VALUE(local)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + MountPropagationFlag, + SD_VARLINK_DEFINE_ENUM_VALUE(shared), + SD_VARLINK_DEFINE_ENUM_VALUE(slave), + SD_VARLINK_DEFINE_ENUM_VALUE(private)); + /* CGroupContext */ static SD_VARLINK_DEFINE_STRUCT_TYPE( CGroupTasksMax, @@ -667,7 +697,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#Nice="), SD_VARLINK_DEFINE_FIELD(Nice, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#CPUSchedulingPolicy="), - SD_VARLINK_DEFINE_FIELD(CPUSchedulingPolicy, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(CPUSchedulingPolicy, CPUSchedulingPolicy, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#CPUSchedulingPriority="), SD_VARLINK_DEFINE_FIELD(CPUSchedulingPriority, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#CPUSchedulingResetOnFork="), @@ -675,11 +705,11 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#CPUAffinity="), SD_VARLINK_DEFINE_FIELD_BY_TYPE(CPUAffinity, CPUAffinity, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#NUMAPolicy="), - SD_VARLINK_DEFINE_FIELD(NUMAPolicy, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(NUMAPolicy, NUMAPolicy, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#NUMAMask="), SD_VARLINK_DEFINE_FIELD(NUMAMask, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#IOSchedulingClass="), - SD_VARLINK_DEFINE_FIELD(IOSchedulingClass, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(IOSchedulingClass, IOSchedulingClass, 0), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#IOSchedulingPriority="), SD_VARLINK_DEFINE_FIELD(IOSchedulingPriority, SD_VARLINK_INT, 0), @@ -781,7 +811,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#PrivateMounts="), SD_VARLINK_DEFINE_FIELD(PrivateMounts, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#MountFlags="), - SD_VARLINK_DEFINE_FIELD(MountFlags, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(MountFlags, MountPropagationFlag, SD_VARLINK_NULLABLE), /* System Call Filtering * https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#System%20Call%20Filtering */ @@ -1258,6 +1288,10 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_ProtectControlGroups, &vl_type_PrivatePIDs, &vl_type_PrivateBPF, + &vl_type_CPUSchedulingPolicy, + &vl_type_IOSchedulingClass, + &vl_type_NUMAPolicy, + &vl_type_MountPropagationFlag, &vl_type_WorkingDirectory, &vl_type_PartitionMountOptions, &vl_type_BindPath, diff --git a/src/shared/varlink-io.systemd.Unit.h b/src/shared/varlink-io.systemd.Unit.h index 96de86cb2e627..f227dc67d6687 100644 --- a/src/shared/varlink-io.systemd.Unit.h +++ b/src/shared/varlink-io.systemd.Unit.h @@ -23,5 +23,9 @@ extern const sd_varlink_symbol vl_type_PrivateBPF; extern const sd_varlink_symbol vl_type_CGroupDevicePolicy; extern const sd_varlink_symbol vl_type_ManagedOOMPreference; extern const sd_varlink_symbol vl_type_CGroupController; +extern const sd_varlink_symbol vl_type_CPUSchedulingPolicy; +extern const sd_varlink_symbol vl_type_IOSchedulingClass; +extern const sd_varlink_symbol vl_type_NUMAPolicy; +extern const sd_varlink_symbol vl_type_MountPropagationFlag; extern const sd_varlink_symbol vl_type_CollectMode; extern const sd_varlink_symbol vl_type_JobMode; diff --git a/src/test/test-varlink-idl-unit.c b/src/test/test-varlink-idl-unit.c index 53b415ee7ec18..f840ff89da747 100644 --- a/src/test/test-varlink-idl-unit.c +++ b/src/test/test-varlink-idl-unit.c @@ -1,6 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "cgroup.h" +#include "ioprio-util.h" +#include "numa-util.h" +#include "process-util.h" #include "tests.h" #include "test-varlink-idl-util.h" #include "unit.h" @@ -26,6 +29,17 @@ TEST(unit_enums_idl) { TEST_IDL_ENUM(PrivatePIDs, private_pids, vl_type_PrivatePIDs); TEST_IDL_ENUM(PrivateBPF, private_bpf, vl_type_PrivateBPF); + /* sched_policy table has gaps (SCHED_IDLE=5, SCHED_EXT=7), so only test from_string direction */ + TEST_IDL_ENUM_FROM_STRING(int, sched_policy, vl_type_CPUSchedulingPolicy); + /* ioprio_class uses _alloc variant for to_string, so only test from_string direction */ + TEST_IDL_ENUM_FROM_STRING(int, ioprio_class, vl_type_IOSchedulingClass); + TEST_IDL_ENUM(int, mpol, vl_type_NUMAPolicy); + + /* mount_propagation_flag has non-standard from_string API, test manually */ + test_enum_to_string_name("shared", &vl_type_MountPropagationFlag); + test_enum_to_string_name("slave", &vl_type_MountPropagationFlag); + test_enum_to_string_name("private", &vl_type_MountPropagationFlag); + /* CGroupContext enums */ TEST_IDL_ENUM(CGroupDevicePolicy, cgroup_device_policy, vl_type_CGroupDevicePolicy); TEST_IDL_ENUM(ManagedOOMMode, managed_oom_mode, vl_type_ManagedOOMMode); From 5d0ac2c23c7063b219df9fce74bc8d8481cb6e7a Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Mon, 13 Apr 2026 03:32:16 -0700 Subject: [PATCH 0972/1296] varlink: add enum types for class and whom fields in io.systemd.Machine Convert the class field (Register input, List output) from plain string to MachineClass enum type, and the whom field (Kill input) to KillWhom enum type. Co-developed-by: Claude Opus 4.6 --- src/machine/machine-varlink.c | 16 ++++++---------- src/machine/machined-varlink.c | 2 +- src/shared/varlink-io.systemd.Machine.c | 22 +++++++++++++++++++--- src/shared/varlink-io.systemd.Machine.h | 3 +++ src/test/meson.build | 4 ++++ src/test/test-varlink-idl-machine.c | 13 +++++++++++++ 6 files changed, 46 insertions(+), 14 deletions(-) create mode 100644 src/test/test-varlink-idl-machine.c diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c index 07a860f6c16ca..a3d3cfcc7e7ee 100644 --- a/src/machine/machine-varlink.c +++ b/src/machine/machine-varlink.c @@ -360,10 +360,12 @@ int vl_method_terminate_internal(sd_varlink *link, sd_json_variant *parameters, return sd_varlink_reply(link, NULL); } +static JSON_DISPATCH_ENUM_DEFINE(dispatch_kill_whom, KillWhom, kill_whom_from_string); + typedef struct MachineKillParameters { const char *name; PidRef pidref; - const char *swhom; + KillWhom whom; int32_t signo; } MachineKillParameters; @@ -376,7 +378,7 @@ static void machine_kill_paramaters_done(MachineKillParameters *p) { int vl_method_kill(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { static const sd_json_dispatch_field dispatch_table[] = { VARLINK_DISPATCH_MACHINE_LOOKUP_FIELDS(MachineKillParameters), - { "whom", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MachineKillParameters, swhom), 0 }, + { "whom", SD_JSON_VARIANT_STRING, dispatch_kill_whom, offsetof(MachineKillParameters, whom), 0 }, { "signal", _SD_JSON_VARIANT_TYPE_INVALID , sd_json_dispatch_signal, offsetof(MachineKillParameters, signo), SD_JSON_MANDATORY }, VARLINK_DISPATCH_POLKIT_FIELD, {} @@ -385,8 +387,8 @@ int vl_method_kill(sd_varlink *link, sd_json_variant *parameters, sd_varlink_met Manager *manager = ASSERT_PTR(userdata); _cleanup_(machine_kill_paramaters_done) MachineKillParameters p = { .pidref = PIDREF_NULL, + .whom = _KILL_WHOM_INVALID, }; - KillWhom whom; int r; assert(link); @@ -403,13 +405,7 @@ int vl_method_kill(sd_varlink *link, sd_json_variant *parameters, sd_varlink_met if (r < 0) return r; - if (isempty(p.swhom)) - whom = KILL_ALL; - else { - whom = kill_whom_from_string(p.swhom); - if (whom < 0) - return sd_varlink_error_invalid_parameter_name(link, "whom"); - } + KillWhom whom = p.whom >= 0 ? p.whom : KILL_ALL; if (manager->runtime_scope != RUNTIME_SCOPE_USER) { r = varlink_verify_polkit_async_full( diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c index 82b8ed93b37c2..ac506ad87f5a9 100644 --- a/src/machine/machined-varlink.c +++ b/src/machine/machined-varlink.c @@ -478,7 +478,7 @@ static int list_machine_one_and_maybe_read_metadata(sd_varlink *link, Machine *m &v, SD_JSON_BUILD_PAIR_STRING("name", m->name), SD_JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(m->id), "id", SD_JSON_BUILD_ID128(m->id)), - SD_JSON_BUILD_PAIR_STRING("class", machine_class_to_string(m->class)), + JSON_BUILD_PAIR_ENUM("class", machine_class_to_string(m->class)), JSON_BUILD_PAIR_STRING_NON_EMPTY("service", m->service), JSON_BUILD_PAIR_STRING_NON_EMPTY("rootDirectory", m->root_directory), JSON_BUILD_PAIR_STRING_NON_EMPTY("unit", m->unit), diff --git a/src/shared/varlink-io.systemd.Machine.c b/src/shared/varlink-io.systemd.Machine.c index 31fb43fbac271..9f6d36ad77c7b 100644 --- a/src/shared/varlink-io.systemd.Machine.c +++ b/src/shared/varlink-io.systemd.Machine.c @@ -11,6 +11,18 @@ SD_VARLINK_DEFINE_INPUT_BY_TYPE(pid, ProcessId, SD_VARLINK_NULLABLE), \ VARLINK_DEFINE_POLKIT_INPUT +SD_VARLINK_DEFINE_ENUM_TYPE( + MachineClass, + SD_VARLINK_DEFINE_ENUM_VALUE(container), + SD_VARLINK_DEFINE_ENUM_VALUE(vm), + SD_VARLINK_DEFINE_ENUM_VALUE(host)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + KillWhom, + SD_VARLINK_DEFINE_ENUM_VALUE(leader), + SD_VARLINK_DEFINE_ENUM_VALUE(supervisor), + SD_VARLINK_DEFINE_ENUM_VALUE(all)); + static SD_VARLINK_DEFINE_ENUM_TYPE( AcquireMetadata, SD_VARLINK_FIELD_COMMENT("Do not include metadata in the output"), @@ -31,7 +43,7 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, 0), SD_VARLINK_DEFINE_INPUT(id, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_INPUT(service, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_DEFINE_INPUT(class, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(class, MachineClass, 0), SD_VARLINK_FIELD_COMMENT("The leader PID as simple positive integer."), SD_VARLINK_DEFINE_INPUT(leader, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The leader PID as ProcessId structure. If both the leader and leaderProcessId parameters are specified they must reference the same process. Typically one would only specify one or the other however. It's generally recommended to specify leaderProcessId as it references a process in a robust way without risk of identifier recycling."), @@ -61,7 +73,7 @@ static SD_VARLINK_DEFINE_METHOD( Kill, VARLINK_DEFINE_MACHINE_LOOKUP_AND_POLKIT_INPUT_FIELDS, SD_VARLINK_FIELD_COMMENT("Identifier that specifies what precisely to send the signal to (either 'leader', 'supervisor', or 'all')."), - SD_VARLINK_DEFINE_INPUT(whom, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(whom, KillWhom, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Numeric UNIX signal integer."), SD_VARLINK_DEFINE_INPUT(signal, SD_VARLINK_INT, 0)); @@ -78,7 +90,7 @@ static SD_VARLINK_DEFINE_METHOD_FULL( SD_VARLINK_FIELD_COMMENT("Name of the software that registered this machine"), SD_VARLINK_DEFINE_OUTPUT(service, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The class of this machine"), - SD_VARLINK_DEFINE_OUTPUT(class, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(class, MachineClass, 0), SD_VARLINK_FIELD_COMMENT("Leader process PID of this machine"), SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(leader, ProcessId, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Supervisor process PID of this machine"), @@ -216,6 +228,10 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_ProcessId, SD_VARLINK_SYMBOL_COMMENT("A timestamp object consisting of both CLOCK_REALTIME and CLOCK_MONOTONIC timestamps"), &vl_type_Timestamp, + SD_VARLINK_SYMBOL_COMMENT("The class of a machine"), + &vl_type_MachineClass, + SD_VARLINK_SYMBOL_COMMENT("What to send a signal to in a machine"), + &vl_type_KillWhom, SD_VARLINK_SYMBOL_COMMENT("A enum field allowing to gracefully get metadata"), &vl_type_AcquireMetadata, SD_VARLINK_SYMBOL_COMMENT("An address object"), diff --git a/src/shared/varlink-io.systemd.Machine.h b/src/shared/varlink-io.systemd.Machine.h index 605a31452642a..2f604c5acba11 100644 --- a/src/shared/varlink-io.systemd.Machine.h +++ b/src/shared/varlink-io.systemd.Machine.h @@ -4,3 +4,6 @@ #include "sd-varlink-idl.h" extern const sd_varlink_interface vl_interface_io_systemd_Machine; + +extern const sd_varlink_symbol vl_type_MachineClass; +extern const sd_varlink_symbol vl_type_KillWhom; diff --git a/src/test/meson.build b/src/test/meson.build index 241e64748538b..fb0aac27f8864 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -503,6 +503,10 @@ executables += [ core_test_template + { 'sources' : files('test-varlink-idl-manager.c'), }, + test_template + { + 'sources' : files('test-varlink-idl-machine.c'), + 'objects' : ['systemd-machined'], + }, test_template + { 'sources' : files('test-watchdog.c'), 'type' : 'unsafe', diff --git a/src/test/test-varlink-idl-machine.c b/src/test/test-varlink-idl-machine.c new file mode 100644 index 0000000000000..1064545fae2d0 --- /dev/null +++ b/src/test/test-varlink-idl-machine.c @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "machine.h" +#include "tests.h" +#include "test-varlink-idl-util.h" +#include "varlink-io.systemd.Machine.h" + +TEST(machine_enums_idl) { + TEST_IDL_ENUM(MachineClass, machine_class, vl_type_MachineClass); + TEST_IDL_ENUM(KillWhom, kill_whom, vl_type_KillWhom); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); From 6518bcf872556dbfcd9ef237f7c9694377de5081 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Mon, 13 Apr 2026 03:56:48 -0700 Subject: [PATCH 0973/1296] varlink: add ManagedOOMMode enum type to io.systemd.oom Convert the mode field in ControlGroup from plain string to the ManagedOOMMode enum type from varlink-idl-common. Register ManagedOOMMode in both io.systemd.oom and io.systemd.ManagedOOM interfaces since both use the ControlGroup struct. Co-developed-by: Claude Opus 4.6 --- src/core/varlink.c | 3 ++- src/shared/varlink-io.systemd.ManagedOOM.c | 2 ++ src/shared/varlink-io.systemd.oom.c | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/core/varlink.c b/src/core/varlink.c index 533d1061b8eb7..7bfc3789a39ba 100644 --- a/src/core/varlink.c +++ b/src/core/varlink.c @@ -4,6 +4,7 @@ #include "constants.h" #include "errno-util.h" +#include "json-util.h" #include "manager.h" #include "metrics.h" #include "path-util.h" @@ -62,7 +63,7 @@ static int build_managed_oom_json_array_element(Unit *u, const char *property, s return -EINVAL; return sd_json_buildo(ret_v, - SD_JSON_BUILD_PAIR_STRING("mode", mode), + JSON_BUILD_PAIR_ENUM("mode", mode), SD_JSON_BUILD_PAIR_STRING("path", crt->cgroup_path), SD_JSON_BUILD_PAIR_STRING("property", property), SD_JSON_BUILD_PAIR_CONDITION(use_limit, "limit", SD_JSON_BUILD_UNSIGNED(c->moom_mem_pressure_limit)), diff --git a/src/shared/varlink-io.systemd.ManagedOOM.c b/src/shared/varlink-io.systemd.ManagedOOM.c index 763b0abfbd886..3e6a66559c0af 100644 --- a/src/shared/varlink-io.systemd.ManagedOOM.c +++ b/src/shared/varlink-io.systemd.ManagedOOM.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "varlink-idl-common.h" #include "varlink-io.systemd.ManagedOOM.h" /* Pull in vl_type_ControlGroup, since both interfaces need it */ @@ -19,6 +20,7 @@ static SD_VARLINK_DEFINE_ERROR(SubscriptionTaken); SD_VARLINK_DEFINE_INTERFACE( io_systemd_ManagedOOM, "io.systemd.ManagedOOM", + &vl_type_ManagedOOMMode, &vl_method_SubscribeManagedOOMCGroups, &vl_type_ControlGroup, &vl_error_SubscriptionTaken); diff --git a/src/shared/varlink-io.systemd.oom.c b/src/shared/varlink-io.systemd.oom.c index 350b933d03d79..80fa50a73a92c 100644 --- a/src/shared/varlink-io.systemd.oom.c +++ b/src/shared/varlink-io.systemd.oom.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "varlink-idl-common.h" #include "varlink-io.systemd.oom.h" /* This is oomd's Varlink service, where oomd is server and systemd --user is the client. @@ -9,7 +10,7 @@ SD_VARLINK_DEFINE_STRUCT_TYPE( ControlGroup, - SD_VARLINK_DEFINE_FIELD(mode, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(mode, ManagedOOMMode, 0), SD_VARLINK_DEFINE_FIELD(path, SD_VARLINK_STRING, 0), SD_VARLINK_DEFINE_FIELD(property, SD_VARLINK_STRING, 0), SD_VARLINK_DEFINE_FIELD(limit, SD_VARLINK_INT, SD_VARLINK_NULLABLE), @@ -22,5 +23,6 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_DEFINE_INTERFACE( io_systemd_oom, "io.systemd.oom", + &vl_type_ManagedOOMMode, &vl_method_ReportManagedOOMCGroups, &vl_type_ControlGroup); From 3dd09ccea214726aa97f8c228528853d2c472a07 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Tue, 14 Apr 2026 02:25:43 -0700 Subject: [PATCH 0974/1296] docs: clarify when to use varlink enum types vs plain strings Add guidance on when a field should use a proper varlink enum type versus remaining a plain string: user-controlled/API fields should be enums, engine-internal state fields may stay as strings. Co-developed-by: Claude Opus 4.6 --- docs/VARLINK.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/VARLINK.md b/docs/VARLINK.md index 844c4ca516bd8..65f1950800b59 100644 --- a/docs/VARLINK.md +++ b/docs/VARLINK.md @@ -63,11 +63,22 @@ SPDX-License-Identifier: LGPL-2.1-or-later * `JSON_DISPATCH_ENUM_DEFINE` - creates a `json_dispatch_*` function that accepts both the original and the underscorified enum value as valid input. + For example, a `LogTarget` field outputs `"journal_or_kmsg"` (underscore + form), but on input both `"journal_or_kmsg"` and `"journal-or-kmsg"` are + accepted. This is handled automatically by `JSON_DISPATCH_ENUM_DEFINE`: + it first tries the value as-is via `_from_string()`, and if that fails, + replaces underscores with dashes and retries. + - An internal enum may be exposed as a simple string field instead of a Varlink enum type when the field is output-only and never provided or controlled by the user. However, such fields should avoid using dashes to prevent breaking changes if they are later converted into enums (see below). + For example, in `io.systemd.Unit`, configuration settings that users select + in unit files (e.g. `ProtectSystem`, `ExecInputType`) should be proper varlink + enum types. Runtime state fields that only the engine determines (e.g. + `ActiveState`, `SubState`) may remain plain strings. + - A varlink string field that has a finite set of possible values may later be converted into an enum without introducing a breaking change. This allows the interface to evolve from loosely defined string values to a more explicit and From 1b533249592b0def69b02fe88d3c989a50ad7eff Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 14 Apr 2026 12:29:52 +0200 Subject: [PATCH 0975/1296] tree-wide: use JSON_BUILD_PAIR_ENUM() more often --- src/import/import-generator.c | 6 +++--- src/import/importd.c | 4 ++-- src/login/logind-varlink.c | 4 ++-- src/login/pam_systemd.c | 4 ++-- src/repart/repart.c | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/import/import-generator.c b/src/import/import-generator.c index 9803ad284140e..f176492a54c93 100644 --- a/src/import/import-generator.c +++ b/src/import/import-generator.c @@ -211,10 +211,10 @@ static int parse_pull_expression(const char *v) { &j, SD_JSON_BUILD_PAIR_STRING("remote", remote), SD_JSON_BUILD_PAIR_STRING("local", local), - SD_JSON_BUILD_PAIR("class", JSON_BUILD_STRING_UNDERSCORIFY(image_class_to_string(class))), - SD_JSON_BUILD_PAIR("type", JSON_BUILD_STRING_UNDERSCORIFY(import_type_to_string(type))), + JSON_BUILD_PAIR_ENUM("class", image_class_to_string(class)), + JSON_BUILD_PAIR_ENUM("type", import_type_to_string(type)), SD_JSON_BUILD_PAIR_BOOLEAN("readOnly", ro), - SD_JSON_BUILD_PAIR("verify", JSON_BUILD_STRING_UNDERSCORIFY(import_verify_to_string(verify))), + JSON_BUILD_PAIR_ENUM("verify", import_verify_to_string(verify)), SD_JSON_BUILD_PAIR_STRING("imageRoot", image_root)); if (r < 0) return log_error_errno(r, "Failed to build import JSON object: %m"); diff --git a/src/import/importd.c b/src/import/importd.c index 5e733f757ab07..c329491386cf0 100644 --- a/src/import/importd.c +++ b/src/import/importd.c @@ -1804,10 +1804,10 @@ static int make_transfer_json(Transfer *t, sd_json_variant **ret) { r = sd_json_buildo(ret, SD_JSON_BUILD_PAIR_UNSIGNED("id", t->id), - SD_JSON_BUILD_PAIR("type", JSON_BUILD_STRING_UNDERSCORIFY(transfer_type_to_string(t->type))), + JSON_BUILD_PAIR_ENUM("type", transfer_type_to_string(t->type)), SD_JSON_BUILD_PAIR_STRING("remote", t->remote), SD_JSON_BUILD_PAIR_STRING("local", t->local), - SD_JSON_BUILD_PAIR("class", JSON_BUILD_STRING_UNDERSCORIFY(image_class_to_string(t->class))), + JSON_BUILD_PAIR_ENUM("class", image_class_to_string(t->class)), SD_JSON_BUILD_PAIR_REAL("percent", transfer_percent_as_double(t))); if (r < 0) return log_error_errno(r, "Failed to build transfer JSON data: %m"); diff --git a/src/login/logind-varlink.c b/src/login/logind-varlink.c index 40dee113c4292..56b02b4eb2eee 100644 --- a/src/login/logind-varlink.c +++ b/src/login/logind-varlink.c @@ -130,8 +130,8 @@ int session_send_create_reply_varlink(Session *s, const sd_bus_error *error) { SD_JSON_BUILD_PAIR_UNSIGNED("UID", s->user->user_record->uid), SD_JSON_BUILD_PAIR_CONDITION(!!s->seat, "Seat", SD_JSON_BUILD_STRING(s->seat ? s->seat->id : NULL)), SD_JSON_BUILD_PAIR_CONDITION(s->vtnr > 0, "VTNr", SD_JSON_BUILD_UNSIGNED(s->vtnr)), - SD_JSON_BUILD_PAIR("Class", JSON_BUILD_STRING_UNDERSCORIFY(session_class_to_string(s->class))), - SD_JSON_BUILD_PAIR("Type", JSON_BUILD_STRING_UNDERSCORIFY(session_type_to_string(s->type)))); + JSON_BUILD_PAIR_ENUM("Class", session_class_to_string(s->class)), + JSON_BUILD_PAIR_ENUM("Type", session_type_to_string(s->type))); } static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_session_class, SessionClass, session_class_from_string); diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index ec862e7d4fd21..6c70b8b1af158 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -1144,8 +1144,8 @@ static int register_session( SD_JSON_BUILD_PAIR_UNSIGNED("UID", ur->uid), JSON_BUILD_PAIR_PIDREF("PID", &pidref), JSON_BUILD_PAIR_STRING_NON_EMPTY("Service", c->service), - SD_JSON_BUILD_PAIR("Type", JSON_BUILD_STRING_UNDERSCORIFY(c->type)), - SD_JSON_BUILD_PAIR("Class", JSON_BUILD_STRING_UNDERSCORIFY(c->class)), + JSON_BUILD_PAIR_ENUM("Type", c->type), + JSON_BUILD_PAIR_ENUM("Class", c->class), JSON_BUILD_PAIR_STRING_NON_EMPTY("Desktop", c->desktop), JSON_BUILD_PAIR_STRING_NON_EMPTY("Seat", c->seat), SD_JSON_BUILD_PAIR_CONDITION(c->vtnr > 0, "VTNr", SD_JSON_BUILD_UNSIGNED(c->vtnr)), diff --git a/src/repart/repart.c b/src/repart/repart.c index 0c708278542c3..b0b92ba2ae075 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -2837,7 +2837,7 @@ static int context_notify( if (c->link) { r = sd_varlink_notifybo( c->link, - SD_JSON_BUILD_PAIR("phase", JSON_BUILD_STRING_UNDERSCORIFY(progress_phase_to_string(phase))), + JSON_BUILD_PAIR_ENUM("phase", progress_phase_to_string(phase)), JSON_BUILD_PAIR_STRING_NON_EMPTY("object", object), JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("progress", percent, UINT_MAX)); if (r < 0) From 01f6d9e39f94674e5525a16f6c020fd34912db24 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Fri, 17 Oct 2025 05:11:09 -0700 Subject: [PATCH 0976/1296] basic: resurrect unit_type_to_capitalized_string() --- src/basic/unit-def.c | 8 ++++++++ src/basic/unit-def.h | 1 + 2 files changed, 9 insertions(+) diff --git a/src/basic/unit-def.c b/src/basic/unit-def.c index a89a81c703a73..57a67af163e31 100644 --- a/src/basic/unit-def.c +++ b/src/basic/unit-def.c @@ -72,6 +72,14 @@ const char* unit_dbus_interface_from_name(const char *name) { return unit_dbus_interface_from_type(t); } +const char* unit_type_to_capitalized_string(UnitType t) { + const char *di = unit_dbus_interface_from_type(t); + if (!di) + return NULL; + + return ASSERT_PTR(startswith(di, "org.freedesktop.systemd1.")); +} + static const char* const unit_type_table[_UNIT_TYPE_MAX] = { [UNIT_SERVICE] = "service", [UNIT_SOCKET] = "socket", diff --git a/src/basic/unit-def.h b/src/basic/unit-def.h index 5fecd3ecec14e..8d05b5b5ed8be 100644 --- a/src/basic/unit-def.h +++ b/src/basic/unit-def.h @@ -321,6 +321,7 @@ void unit_types_list(void); DECLARE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState); DECLARE_STRING_TABLE_LOOKUP(unit_active_state, UnitActiveState); +const char* unit_type_to_capitalized_string(UnitType t); DECLARE_STRING_TABLE_LOOKUP(freezer_state, FreezerState); FreezerState freezer_state_finish(FreezerState state) _const_; From 78ab4d1f7deffd1124a0625fd3a2c815e9e39103 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 14 Apr 2026 15:41:21 +0200 Subject: [PATCH 0977/1296] parse-helpers: Silence coverity warning --- src/shared/parse-helpers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/parse-helpers.c b/src/shared/parse-helpers.c index 4e524bef37ed9..da4b9fd25a397 100644 --- a/src/shared/parse-helpers.c +++ b/src/shared/parse-helpers.c @@ -135,7 +135,7 @@ int parse_address_families(const char *rvalue, Set **families, bool *is_allowlis /* If we previously wanted to forbid an address family and now we want to allow it, then * just remove it from the list. */ - if (!invert == *is_allowlist) { + if (invert != *is_allowlist) { r = set_put(*families, INT_TO_PTR(af)); if (r < 0) return r; From da18a5cfd659daff94b44894a1713f77d64b3fcb Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Mon, 13 Apr 2026 16:06:23 -0400 Subject: [PATCH 0978/1296] test: do not use nanoseconds width specifier in date command Using the format specifier +%s%6N with GNU date is honored, and only prints 6 digits of the nanoseconds portion of the seconds since epoch. The uutils implementation of date does not honor this, and always prints all 9 digits. This is a known bug[1], but can be worked around by adapting this test to use nanoseconds instead of microseconds. [1] https://github.com/uutils/coreutils/issues/11658 --- test/units/TEST-74-AUX-UTILS.busctl.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/units/TEST-74-AUX-UTILS.busctl.sh b/test/units/TEST-74-AUX-UTILS.busctl.sh index b8ae8da568204..875f353b00ea4 100755 --- a/test/units/TEST-74-AUX-UTILS.busctl.sh +++ b/test/units/TEST-74-AUX-UTILS.busctl.sh @@ -146,10 +146,10 @@ busctl get-property -j \ busctl --quiet --timeout=1 --limit-messages=1 --match "interface=org.freedesktop.systemd1.Manager" monitor -START_USEC=$(date +%s%6N) +START_NSEC=$(date +%s%N) busctl --quiet --timeout=500ms --match "interface=io.dontexist.NeverGonnaHappen" monitor -END_USEC=$(date +%s%6N) -USEC=$((END_USEC-START_USEC)) +END_NSEC=$(date +%s%N) +NSEC=$((END_NSEC-START_NSEC)) # Validate that the above was delayed for at least 500ms, but at most 30s (some leeway for slow CIs) -test "$USEC" -gt 500000 -test "$USEC" -lt 30000000 +test "$NSEC" -gt 500000000 +test "$NSEC" -lt 30000000000 From db1ca20591610bfaec80ccddddd42ce74ec185d0 Mon Sep 17 00:00:00 2001 From: roib Date: Tue, 14 Apr 2026 08:06:34 -0700 Subject: [PATCH 0979/1296] docs: update footer to 2026 --- docs/_includes/footer.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_includes/footer.html b/docs/_includes/footer.html index 1800c53ea39b9..da81b1a48d843 100644 --- a/docs/_includes/footer.html +++ b/docs/_includes/footer.html @@ -1,7 +1,7 @@ From e7d1030d771d46d8004d44f33585261b0e48fc43 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 26 Mar 2026 08:41:17 +0100 Subject: [PATCH 0980/1296] TEST-07-PID1: Don't fail in vm without ESP or XBOOTLDR mount --- test/units/TEST-07-PID1.exec-context.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/units/TEST-07-PID1.exec-context.sh b/test/units/TEST-07-PID1.exec-context.sh index 877095609123f..14cc49f29237f 100755 --- a/test/units/TEST-07-PID1.exec-context.sh +++ b/test/units/TEST-07-PID1.exec-context.sh @@ -33,12 +33,14 @@ proc_supports_option() { # in that case instead of complicating the test setup even more */ if [[ -z "${COVERAGE_BUILD_DIR:-}" ]]; then if ! systemd-detect-virt -cq && command -v bootctl >/dev/null; then - boot_path="$(bootctl --print-boot-path)" - esp_path="$(bootctl --print-esp-path)" + boot_path="$(bootctl --print-boot-path)" || : + esp_path="$(bootctl --print-esp-path)" || : # If the mount points are handled by automount units, make sure we trigger # them before proceeding further - ls -l "$boot_path" "$esp_path" + if [[ -n "${boot_path:-}" && -n "${esp_path:-}" ]]; then + ls -l "$boot_path" "$esp_path" + fi fi systemd-run --wait --pipe -p ProtectSystem=yes \ From 9c0abfaf15c1494b8ed3c874342979c56f14e282 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 26 Mar 2026 08:41:36 +0100 Subject: [PATCH 0981/1296] TEST-07-PID1: Use --foreground with timeout Otherwise the test fails if a TTY is attached to stdio. --- test/units/TEST-07-PID1.subgroup-kill.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/units/TEST-07-PID1.subgroup-kill.sh b/test/units/TEST-07-PID1.subgroup-kill.sh index c8eb6539abbd8..274e495af45f4 100755 --- a/test/units/TEST-07-PID1.subgroup-kill.sh +++ b/test/units/TEST-07-PID1.subgroup-kill.sh @@ -36,4 +36,4 @@ systemctl kill user@"$(id -u testuser)".service --kill-subgroup=app.slice/subgro run0 -u testuser systemctl --user is-active subgroup-test.service systemctl kill user@"$(id -u testuser)".service --kill-subgroup=app.slice/subgroup-test.service --kill-whom=cgroup-fail -timeout 60 bash -c 'while run0 -u testuser systemctl --user is-active subgroup-test.service; do sleep 1; done' +timeout --foreground 60 bash -c 'while run0 -u testuser systemctl --user is-active subgroup-test.service; do sleep 1; done' From 518dcfadab8c540cff056a3bd94d5c817e7a17b2 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 26 Mar 2026 09:01:58 +0100 Subject: [PATCH 0982/1296] TEST-13-NSPAWN: Use timeout --foreground in two more places --- test/units/TEST-13-NSPAWN.nspawn-oci.sh | 2 +- test/units/TEST-13-NSPAWN.nspawn.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/units/TEST-13-NSPAWN.nspawn-oci.sh b/test/units/TEST-13-NSPAWN.nspawn-oci.sh index 1fd2a5ad76774..0d85518a1d392 100755 --- a/test/units/TEST-13-NSPAWN.nspawn-oci.sh +++ b/test/units/TEST-13-NSPAWN.nspawn-oci.sh @@ -395,7 +395,7 @@ touch /opt/readonly/foo && exit 1 exit 0 EOF -timeout 30 systemd-nspawn --oci-bundle="$OCI" +timeout --foreground 30 systemd-nspawn --oci-bundle="$OCI" # Test a couple of invalid configs INVALID_SNIPPETS=( diff --git a/test/units/TEST-13-NSPAWN.nspawn.sh b/test/units/TEST-13-NSPAWN.nspawn.sh index 2868cc54ef354..47c19f08c01f2 100755 --- a/test/units/TEST-13-NSPAWN.nspawn.sh +++ b/test/units/TEST-13-NSPAWN.nspawn.sh @@ -578,7 +578,7 @@ ip link | grep wl-renamed1 EOF fi - timeout 30 systemd-nspawn --directory="$root" + timeout --foreground 30 systemd-nspawn --directory="$root" # And now for stuff that needs to run separately # From a8416614b015a629e5554a88129264732370edfb Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 26 Mar 2026 13:57:13 +0100 Subject: [PATCH 0983/1296] TEST-24-CRYPTSETUP: Use virtio-blk-pci Doesn't require a controller. --- test/integration-tests/TEST-24-CRYPTSETUP/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration-tests/TEST-24-CRYPTSETUP/meson.build b/test/integration-tests/TEST-24-CRYPTSETUP/meson.build index bb2b2a15f5ebb..e0ee1bdd32677 100644 --- a/test/integration-tests/TEST-24-CRYPTSETUP/meson.build +++ b/test/integration-tests/TEST-24-CRYPTSETUP/meson.build @@ -15,7 +15,7 @@ integration_tests += [ ], 'qemu-args' : [ '-drive', 'id=keydev,if=none,format=raw,cache=unsafe,file=@0@'.format(meson.project_build_root() / 'mkosi.output/keydev.raw'), - '-device', 'scsi-hd,drive=keydev,serial=keydev', + '-device', 'virtio-blk-pci,drive=keydev,serial=keydev', ], 'mkosi-args' : integration_test_template['mkosi-args'] + [ '--runtime-size=11G', From 014a4d93e00e32da14bc7f21102bbd628af695d4 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 26 Mar 2026 14:22:43 +0100 Subject: [PATCH 0984/1296] TEST-64-UDEV-STORAGE: Add missing scsi controllers --- test/integration-tests/TEST-64-UDEV-STORAGE/meson.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration-tests/TEST-64-UDEV-STORAGE/meson.build b/test/integration-tests/TEST-64-UDEV-STORAGE/meson.build index 0b1b3d9a97f4b..3df914fd4d98c 100644 --- a/test/integration-tests/TEST-64-UDEV-STORAGE/meson.build +++ b/test/integration-tests/TEST-64-UDEV-STORAGE/meson.build @@ -115,7 +115,7 @@ udev_storage_tests += udev_storage_test_template + { } cmdline = [] -qemu_args = [] +qemu_args = ['-device', 'virtio-scsi-pci,id=scsi0'] # Add 16 multipath devices, each backed by 4 paths foreach ndisk : range(16) @@ -137,7 +137,7 @@ udev_storage_tests += udev_storage_test_template + { } cmdline = [] -qemu_args = [] +qemu_args = ['-device', 'virtio-scsi-pci,id=scsi0'] foreach i : range(10) id = f'drivesimultaneousevents@i@' From 72cfcfa0ec2be967833c802fc8237f3eed23994e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 26 Mar 2026 15:40:07 +0100 Subject: [PATCH 0985/1296] TEST-06-SELINUX: Relabel in the initrd rather than at image build time This gets rid of the requirement to run the image build as root. --- .github/workflows/mkosi.yml | 14 -------------- mkosi/mkosi.conf | 3 +-- .../usr/lib/systemd/system-preset/00-mkosi.preset | 2 +- .../mkosi.conf.d/centos-fedora.conf | 1 + mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf | 1 + .../systemd/system/initrd-selinux-relabel.service | 14 ++++++++++++++ test/integration-tests/TEST-06-SELINUX/meson.build | 13 +++++++------ 7 files changed, 25 insertions(+), 23 deletions(-) create mode 100644 mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/initrd-selinux-relabel.service diff --git a/.github/workflows/mkosi.yml b/.github/workflows/mkosi.yml index 5fd1469ba3535..859e50a34ccc8 100644 --- a/.github/workflows/mkosi.yml +++ b/.github/workflows/mkosi.yml @@ -60,7 +60,6 @@ jobs: sanitizers: "" llvm: 0 cflags: "-O2 -D_FORTIFY_SOURCE=3" - relabel: no vm: 1 no_qemu: 0 no_kvm: 0 @@ -71,7 +70,6 @@ jobs: sanitizers: "" llvm: 0 cflags: "-Og" - relabel: no vm: 0 no_qemu: 0 no_kvm: 0 @@ -82,7 +80,6 @@ jobs: sanitizers: "" llvm: 0 cflags: "-Og" - relabel: no vm: 0 no_qemu: 0 no_kvm: 0 @@ -93,7 +90,6 @@ jobs: sanitizers: "" llvm: 0 cflags: "-Og" - relabel: no vm: 0 no_qemu: 1 no_kvm: 1 @@ -104,7 +100,6 @@ jobs: sanitizers: "" llvm: 0 cflags: "-Og" - relabel: no vm: 0 no_qemu: 0 no_kvm: 0 @@ -115,7 +110,6 @@ jobs: sanitizers: "" llvm: 0 cflags: "-Og" - relabel: no vm: 0 no_qemu: 0 no_kvm: 0 @@ -126,7 +120,6 @@ jobs: sanitizers: address,undefined llvm: 1 cflags: "-Og" - relabel: yes vm: 0 no_qemu: 0 no_kvm: 0 @@ -137,7 +130,6 @@ jobs: sanitizers: "" llvm: 0 cflags: "-Og" - relabel: yes vm: 0 no_qemu: 0 no_kvm: 0 @@ -148,7 +140,6 @@ jobs: sanitizers: "" llvm: 0 cflags: "-Og" - relabel: yes vm: 0 no_qemu: 0 no_kvm: 0 @@ -159,7 +150,6 @@ jobs: sanitizers: "" llvm: 0 cflags: "-Og" - relabel: yes vm: 0 no_qemu: 0 no_kvm: 0 @@ -170,7 +160,6 @@ jobs: sanitizers: "" llvm: 0 cflags: "-Og" - relabel: yes vm: 0 no_qemu: 0 no_kvm: 0 @@ -233,9 +222,6 @@ jobs: LLVM=${{ matrix.llvm }} SYSEXT=1 - [Content] - SELinuxRelabel=${{ matrix.relabel }} - [Runtime] RAM=4G EOF diff --git a/mkosi/mkosi.conf b/mkosi/mkosi.conf index 22547a5a1f948..2fc087cb73f40 100644 --- a/mkosi/mkosi.conf +++ b/mkosi/mkosi.conf @@ -59,8 +59,7 @@ ExtraTrees= KernelInitrdModules=default -# Disable relabeling by default as it only matters for TEST-06-SELINUX, takes a non-trivial amount of time -# and results in lots of errors when building images as a regular user. +# Disable relabeling by default as TEST-06-SELINUX handles relabeling itself at runtime. SELinuxRelabel=no # Adding more kernel command line arguments is likely to hit the kernel command line limit (512 bytes) in diff --git a/mkosi/mkosi.extra.common/usr/lib/systemd/system-preset/00-mkosi.preset b/mkosi/mkosi.extra.common/usr/lib/systemd/system-preset/00-mkosi.preset index d7774e03f64d5..4423c3dabd7c2 100644 --- a/mkosi/mkosi.extra.common/usr/lib/systemd/system-preset/00-mkosi.preset +++ b/mkosi/mkosi.extra.common/usr/lib/systemd/system-preset/00-mkosi.preset @@ -46,7 +46,7 @@ disable fstrim.timer disable raid-check.timer disable systemd-tmpfiles-clean.timer -# mkosi relabels the image itself so no need to do it on boot. +# TEST-06-SELINUX handles relabeling itself at runtime. disable selinux-autorelabel-mark.service enable coverage-forwarder.service diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf.d/centos-fedora.conf b/mkosi/mkosi.initrd.conf/mkosi.conf.d/centos-fedora.conf index 2077f0662f899..e753749dc443f 100644 --- a/mkosi/mkosi.initrd.conf/mkosi.conf.d/centos-fedora.conf +++ b/mkosi/mkosi.initrd.conf/mkosi.conf.d/centos-fedora.conf @@ -8,6 +8,7 @@ Distribution=|fedora PrepareScripts=%D/mkosi/mkosi.conf.d/centos-fedora/systemd.prepare Packages= coreutils + policycoreutils swtpm-tools tpm2-tools diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf b/mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf index 92fc255670fa6..c30d970c85a2b 100644 --- a/mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf +++ b/mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf @@ -9,6 +9,7 @@ Packages= btrfs-progs coreutils kmod + policycoreutils tpm2.0-tools VolatilePackages= diff --git a/mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/initrd-selinux-relabel.service b/mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/initrd-selinux-relabel.service new file mode 100644 index 0000000000000..077b36900a2b5 --- /dev/null +++ b/mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/initrd-selinux-relabel.service @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[Unit] +Description=Relabel /sysroot for SELinux + +DefaultDependencies=no +ConditionPathExists=/sysroot/etc/selinux/config +After=initrd-root-fs.target +After=initrd.target initrd-parse-etc.service remote-fs.target +Before=initrd-cleanup.service + +[Service] +Type=oneshot +ExecStart=sh -c '. /sysroot/etc/selinux/config && [ -n "$${SELINUXTYPE}" ] && setfiles -mFr /sysroot -T0 -c /sysroot/etc/selinux/$${SELINUXTYPE}/policy/policy.* /sysroot/etc/selinux/$${SELINUXTYPE}/contexts/files/file_contexts /sysroot' diff --git a/test/integration-tests/TEST-06-SELINUX/meson.build b/test/integration-tests/TEST-06-SELINUX/meson.build index 22f306260dbc2..8dca509b82964 100644 --- a/test/integration-tests/TEST-06-SELINUX/meson.build +++ b/test/integration-tests/TEST-06-SELINUX/meson.build @@ -1,19 +1,20 @@ # SPDX-License-Identifier: LGPL-2.1-or-later -id = find_program('id', required : true) -uid = run_command(id, '-u', check : true).stdout().strip().to_int() - integration_tests += [ integration_test_template + { 'name' : fs.name(meson.current_source_dir()), - 'cmdline' : integration_test_template['cmdline'] + ['selinux=1', 'enforcing=0', 'lsm=selinux'], + 'cmdline' : integration_test_template['cmdline'] + [ + 'selinux=1', + 'enforcing=0', + 'lsm=selinux', + 'rd.systemd.wants=initrd-selinux-relabel.service', + ], # FIXME; Figure out why reboot sometimes hangs with 'linux' firmware. # Use 'auto' to automatically fallback on non-uefi architectures. 'firmware' : 'auto', 'vm' : true, - # Make sure we don't mount anything with virtiofs as otherwise fixfiles will try to relabel + # Make sure we don't mount anything with virtiofs as otherwise setfiles will try to relabel # it. 'mkosi-args' : integration_test_template['mkosi-args'] + ['--runtime-build-sources=no'], - 'enabled' : uid == 0, }, ] From 31441cb782139c19e69ea0037871eff5299bf228 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 5 Apr 2026 21:29:31 +0200 Subject: [PATCH 0986/1296] test-seccomp: Handle environment where sync() is already suppressed We might be running in an nspawn container booted with --suppress-sync, so make sure we handle that scenario gracefully. --- src/test/test-seccomp.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/test/test-seccomp.c b/src/test/test-seccomp.c index d40abf24e4dc6..44682b4a2c023 100644 --- a/src/test/test-seccomp.c +++ b/src/test/test-seccomp.c @@ -1095,20 +1095,23 @@ static void test_seccomp_suppress_sync_child(void) { _cleanup_close_ int fd = -EBADF; ASSERT_OK(tempfn_random("/tmp/seccomp_suppress_sync", NULL, &path)); - ASSERT_OK_ERRNO(fd = open(path, O_RDWR | O_CREAT | O_SYNC | O_CLOEXEC, 0666)); - fd = safe_close(fd); - - ASSERT_ERROR_ERRNO(fdatasync(-1), EBADF); - ASSERT_ERROR_ERRNO(fsync(-1), EBADF); - ASSERT_ERROR_ERRNO(syncfs(-1), EBADF); - - ASSERT_ERROR_ERRNO(fdatasync(INT_MAX), EBADF); - ASSERT_ERROR_ERRNO(fsync(INT_MAX), EBADF); - ASSERT_ERROR_ERRNO(syncfs(INT_MAX), EBADF); + fd = open(path, O_RDWR | O_CREAT | O_SYNC | O_CLOEXEC, 0666); + /* We might be running in an environment where sync() is already suppressed. */ + if (fd >= 0) { + ASSERT_ERROR_ERRNO(fdatasync(-1), EBADF); + ASSERT_ERROR_ERRNO(fsync(-1), EBADF); + ASSERT_ERROR_ERRNO(syncfs(-1), EBADF); + + ASSERT_ERROR_ERRNO(fdatasync(INT_MAX), EBADF); + ASSERT_ERROR_ERRNO(fsync(INT_MAX), EBADF); + ASSERT_ERROR_ERRNO(syncfs(INT_MAX), EBADF); + } else if (errno != EINVAL) + ASSERT_OK_ERRNO(fd); ASSERT_OK(seccomp_suppress_sync()); - ASSERT_ERROR_ERRNO(fd = open(path, O_RDWR | O_CREAT | O_SYNC | O_CLOEXEC, 0666), EINVAL); + fd = safe_close(fd); + fd = ASSERT_ERROR_ERRNO(open(path, O_RDWR | O_CREAT | O_SYNC | O_CLOEXEC, 0666), EINVAL); ASSERT_OK_ERRNO(fdatasync(INT_MAX)); ASSERT_OK_ERRNO(fsync(INT_MAX)); From 0d57260976b87ff213edfe3cb29e352c67e54d48 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 6 Apr 2026 15:11:08 +0200 Subject: [PATCH 0987/1296] TEST-75-RESOLVED: Make sure --suppress-sync is not used --- test/integration-tests/TEST-75-RESOLVED/meson.build | 3 +++ test/integration-tests/integration-test-wrapper.py | 7 ++++++- test/integration-tests/meson.build | 5 +++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/test/integration-tests/TEST-75-RESOLVED/meson.build b/test/integration-tests/TEST-75-RESOLVED/meson.build index 8dec5f37e73a8..988aeb8dc9837 100644 --- a/test/integration-tests/TEST-75-RESOLVED/meson.build +++ b/test/integration-tests/TEST-75-RESOLVED/meson.build @@ -3,5 +3,8 @@ integration_tests += [ integration_test_template + { 'name' : fs.name(meson.current_source_dir()), + # knot uses lmdb which uses O_SYNC which will fail with EINVAL + # when running under --suppress-sync. + 'suppress-sync' : false, }, ] diff --git a/test/integration-tests/integration-test-wrapper.py b/test/integration-tests/integration-test-wrapper.py index 0bbfb6044d434..93e1e0d91b54f 100755 --- a/test/integration-tests/integration-test-wrapper.py +++ b/test/integration-tests/integration-test-wrapper.py @@ -395,6 +395,7 @@ def main() -> None: parser.add_argument('--rtc', action=argparse.BooleanOptionalAction) parser.add_argument('--tpm', action=argparse.BooleanOptionalAction) parser.add_argument('--skip', action=argparse.BooleanOptionalAction) + parser.add_argument('--suppress-sync', action=argparse.BooleanOptionalAction, default=False) parser.add_argument('mkosi_args', nargs='*') args = parser.parse_args() @@ -612,7 +613,11 @@ def main() -> None: '--credential', f"journal.storage={'persistent' if sys.stdin.isatty() else args.storage}", *(['--runtime-build-sources=no', '--register=no'] if not sys.stdin.isatty() else []), 'vm' if vm else 'boot', - *(['--', '--capability=CAP_BPF'] if not vm else []), + *( + ['--', '--capability=CAP_BPF', f'--suppress-sync={"yes" if args.suppress_sync else "no"}'] + if not vm + else [] + ), ] # fmt: skip try: diff --git a/test/integration-tests/meson.build b/test/integration-tests/meson.build index 198371ccae49a..7888283db81cb 100644 --- a/test/integration-tests/meson.build +++ b/test/integration-tests/meson.build @@ -31,6 +31,7 @@ integration_test_template = { 'sanitizer-exclude-regex' : '', 'rtc' : false, 'tpm' : false, + 'suppress-sync' : true, } foreach dirname : [ @@ -139,6 +140,10 @@ foreach integration_test : integration_tests integration_test_args += ['--mkosi', mkosi.full_path()] endif + if integration_test['suppress-sync'] + integration_test_args += ['--suppress-sync'] + endif + integration_test_args += ['--'] if integration_test['cmdline'].length() > 0 From b1d9127c39f918b2f6eb61ed8e8c97ae07ac11c2 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 14 Apr 2026 19:15:48 +0100 Subject: [PATCH 0988/1296] resolved: check for reset-statistics polkit action via D-Bus too The varlink method checks for polkit authorization, so also update the D-Bus method to match it. Follow-up for cf01bbb7a45fb1eec28cd0a813bd68fde413410f --- src/resolve/resolved-bus.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index 9e075277ec196..e20c975de8b38 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -1722,9 +1722,21 @@ static int bus_property_get_resolv_conf_mode( static int bus_method_reset_statistics(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = ASSERT_PTR(userdata); + int r; assert(message); + r = bus_verify_polkit_async( + message, + "org.freedesktop.resolve1.reset-statistics", + /* details= */ NULL, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Polkit will call us back */ + bus_client_log(message, "statistics reset"); dns_manager_reset_statistics(m); From 5cee6c6a92292acbede4c183c29d3e1aafd7c210 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 14 Apr 2026 22:01:02 +0100 Subject: [PATCH 0989/1296] mkosi: update fedora commit reference to 207e2d004468bf79a8bd78182d9b10956edf45c7 * 207e2d0044 Stop building support for openssl engines * 36a234147f Upload sources * 3681163f81 Version 260.1 * 8f4f0f58e3 Version 260 * e3fab23aa0 Version 260~rc4 * e4c1c2100b Version 260~rc3 * 453696813e Fix typo in unit name in %post scriptlet * 154edb7cdb Silence false positive "HWID match failed, no DT blob" error (rhbz#2444759) * 03b6637c35 riscv64 port has LTO disabled * ce1dec6a40 Version 260~rc2 * 809049777c Add patch for symlink creation error * 6ff27708f7 Enable getty@.service through presets * ba7807fbce Drop scriptlet for upgrades from versions <253 * 455f277188 Move support for tpm2 to systemd-udev subpackage * 0183bc784e Version 260~rc1 --- .packit.yml | 2 +- mkosi/mkosi.pkgenv/mkosi.conf.d/centos-fedora.conf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.packit.yml b/.packit.yml index 499b28f7c47fd..97e1e58855048 100644 --- a/.packit.yml +++ b/.packit.yml @@ -39,7 +39,7 @@ jobs: trigger: pull_request fmf_url: https://src.fedoraproject.org/rpms/systemd # This is automatically updated by tools/fetch-distro.py --update fedora - fmf_ref: 23a1c1fed99e152d9c498204175a7643371a822c + fmf_ref: 207e2d004468bf79a8bd78182d9b10956edf45c7 targets: - fedora-rawhide-x86_64 # testing-farm in the Fedora repository is explicitly configured to use testing-farm bare metal runners as diff --git a/mkosi/mkosi.pkgenv/mkosi.conf.d/centos-fedora.conf b/mkosi/mkosi.pkgenv/mkosi.conf.d/centos-fedora.conf index 5bd63a6ce1f75..3ff941ad4f69f 100644 --- a/mkosi/mkosi.pkgenv/mkosi.conf.d/centos-fedora.conf +++ b/mkosi/mkosi.pkgenv/mkosi.conf.d/centos-fedora.conf @@ -9,5 +9,5 @@ Profiles=!hyperscale Environment= GIT_URL=https://src.fedoraproject.org/rpms/systemd.git GIT_BRANCH=rawhide - GIT_COMMIT=23a1c1fed99e152d9c498204175a7643371a822c + GIT_COMMIT=207e2d004468bf79a8bd78182d9b10956edf45c7 PKG_SUBDIR=fedora From 4347c3bcceaf37b3a289e4a7c4b50941f980d66c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 24 Mar 2026 23:30:13 +0100 Subject: [PATCH 0990/1296] report: use the new option and verb macros --- src/report/report.c | 142 ++++++++++++++++++-------------------------- 1 file changed, 57 insertions(+), 85 deletions(-) diff --git a/src/report/report.c b/src/report/report.c index 74366cffdff77..96ff28dccd9b3 100644 --- a/src/report/report.c +++ b/src/report/report.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-event.h" #include "sd-varlink.h" @@ -13,6 +11,7 @@ #include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "path-lookup.h" #include "pretty-print.h" @@ -708,6 +707,10 @@ static int readdir_sources(char **ret_directory, DirectoryEntries **ret) { return m > 0; } +VERB_FULL(verb_metrics, "metrics", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_LIST_METRICS, + "Acquire list of metrics and their values"); +VERB_FULL(verb_metrics, "describe-metrics", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_DESCRIBE_METRICS, + "Describe available metrics"); static int verb_metrics(int argc, char *argv[], uintptr_t data, void *userdata) { Action action = data; int r; @@ -788,6 +791,10 @@ static int verb_metrics(int argc, char *argv[], uintptr_t data, void *userdata) return 0; } +VERB_FULL(verb_facts, "facts", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_LIST_FACTS, + "Acquire list of facts and their values"); +VERB_FULL(verb_facts, "describe-facts", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_DESCRIBE_FACTS, + "Describe available facts"); static int verb_facts(int argc, char *argv[], uintptr_t data, void *userdata) { Action action = data; int r; @@ -868,6 +875,7 @@ static int verb_facts(int argc, char *argv[], uintptr_t data, void *userdata) { return 0; } +VERB_NOARG(verb_list_sources, "list-sources", "Show list of known metrics sources"); static int verb_list_sources(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -925,143 +933,107 @@ static int verb_list_sources(int argc, char *argv[], uintptr_t _data, void *user static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL; int r; r = terminal_urlify_man("systemd-report", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n%5$sAcquire metrics and facts from local sources.%6$s\n" - "\n%3$sCommands:%4$s\n" - " metrics [MATCH...] Acquire list of metrics and their values\n" - " describe-metrics [MATCH...]\n" - " Describe available metrics\n" - " facts [MATCH...] Acquire list of facts and their values\n" - " describe-facts [MATCH...]\n" - " Describe available facts\n" - " list-sources Show list of known metrics sources\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " --user Connect to user service manager\n" - " --system Connect to system service manager (default)\n" - " --json=pretty|short\n" - " Configure JSON output\n" - " -j Equivalent to --json=pretty (on TTY) or --json=short\n" - " (otherwise)\n" - "\nSee the %2$s for details.\n", + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, options, verbs); + + printf("%s [OPTIONS...] COMMAND ...\n" + "\n%sAcquire metrics and facts from local sources.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); + r = table_print_or_warn(verbs); + if (r < 0) + return r; - return 0; -} + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); + printf("\nSee the %s for details.\n", link); + return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_USER, - ARG_SYSTEM, - ARG_JSON, - }; +VERB_COMMON_HELP_HIDDEN(help); - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "json", required_argument, NULL, ARG_JSON }, - {} - }; - - int c, r; +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hj", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_NO_LEGEND: + OPTION_COMMON_NO_LEGEND: arg_legend = false; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Connect to user service manager"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Connect to system service manager (default)"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); if (r <= 0) return r; - break; - case 'j': + OPTION_COMMON_LOWERCASE_J: arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state); return 1; } -static int report_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, 1, 0, verb_help }, - { "metrics", VERB_ANY, VERB_ANY, 0, verb_metrics, ACTION_LIST_METRICS }, - { "describe-metrics", VERB_ANY, VERB_ANY, 0, verb_metrics, ACTION_DESCRIBE_METRICS }, - { "facts", VERB_ANY, VERB_ANY, 0, verb_facts, ACTION_LIST_FACTS }, - { "describe-facts", VERB_ANY, VERB_ANY, 0, verb_facts, ACTION_DESCRIBE_FACTS }, - { "list-sources", VERB_ANY, 1, 0, verb_list_sources }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { + char **args = NULL; int r; log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return report_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); From 0ba59c3d327eff3108904f06de7e55675ac15e4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 9 Feb 2026 12:02:03 +0100 Subject: [PATCH 0991/1296] journal-upload: modernize macro wrapping curl_easy_setopt We cannot use a function, because the type is unknown and we want to stringify the option name, but we can use a block macro to make this a bit nicer, with normal code structure in the caller. --- src/journal-remote/journal-upload.c | 117 +++++++++++++--------------- 1 file changed, 55 insertions(+), 62 deletions(-) diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c index e6cb5dabc2655..e2038dd4b6e4e 100644 --- a/src/journal-remote/journal-upload.c +++ b/src/journal-remote/journal-upload.c @@ -81,16 +81,14 @@ static void close_fd_input(Uploader *u); #define STATE_FILE "/var/lib/systemd/journal-upload/state" -#define easy_setopt(curl, opt, value, level, cmd) \ - do { \ - code = curl_easy_setopt(curl, opt, value); \ - if (code) { \ - log_full(level, \ - "curl_easy_setopt " #opt " failed: %s", \ - curl_easy_strerror(code)); \ - cmd; \ - } \ - } while (0) +#define easy_setopt(curl, log_level, opt, value) ({ \ + CURLcode code = curl_easy_setopt(ASSERT_PTR(curl), opt, value); \ + if (code) \ + log_full(log_level, \ + "curl_easy_setopt %s failed: %s", \ + #opt, curl_easy_strerror(code)); \ + !code; \ +}) DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(CURL*, curl_easy_cleanup, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct curl_slist*, curl_slist_free_all, NULL); @@ -194,8 +192,6 @@ int start_upload(Uploader *u, size_t nmemb, void *userdata), void *data) { - CURLcode code; - assert(u); assert(input_callback); @@ -262,64 +258,63 @@ int start_upload(Uploader *u, "Call to curl_easy_init failed."); /* If configured, set a timeout for the curl operation. */ - if (arg_network_timeout_usec != USEC_INFINITY) - easy_setopt(curl, CURLOPT_TIMEOUT, - (long) DIV_ROUND_UP(arg_network_timeout_usec, USEC_PER_SEC), - LOG_ERR, return -EXFULL); + if (arg_network_timeout_usec != USEC_INFINITY && + !easy_setopt(curl, LOG_ERR, CURLOPT_TIMEOUT, + (long) DIV_ROUND_UP(arg_network_timeout_usec, USEC_PER_SEC))) + return -EXFULL; /* tell it to POST to the URL */ - easy_setopt(curl, CURLOPT_POST, 1L, - LOG_ERR, return -EXFULL); + if (!easy_setopt(curl, LOG_ERR, CURLOPT_POST, 1L)) + return -EXFULL; - easy_setopt(curl, CURLOPT_ERRORBUFFER, u->error, - LOG_ERR, return -EXFULL); + if (!easy_setopt(curl, LOG_ERR, CURLOPT_ERRORBUFFER, u->error)) + return -EXFULL; /* set where to write to */ - easy_setopt(curl, CURLOPT_WRITEFUNCTION, output_callback, - LOG_ERR, return -EXFULL); + if (!easy_setopt(curl, LOG_ERR, CURLOPT_WRITEFUNCTION, output_callback)) + return -EXFULL; - easy_setopt(curl, CURLOPT_WRITEDATA, data, - LOG_ERR, return -EXFULL); + if (!easy_setopt(curl, LOG_ERR, CURLOPT_WRITEDATA, data)) + return -EXFULL; /* set where to read from */ - easy_setopt(curl, CURLOPT_READFUNCTION, input_callback, - LOG_ERR, return -EXFULL); + if (!easy_setopt(curl, LOG_ERR, CURLOPT_READFUNCTION, input_callback)) + return -EXFULL; - easy_setopt(curl, CURLOPT_READDATA, data, - LOG_ERR, return -EXFULL); + if (!easy_setopt(curl, LOG_ERR, CURLOPT_READDATA, data)) + return -EXFULL; /* use our special own mime type and chunked transfer */ - easy_setopt(curl, CURLOPT_HTTPHEADER, u->header, - LOG_ERR, return -EXFULL); + if (!easy_setopt(curl, LOG_ERR, CURLOPT_HTTPHEADER, u->header)) + return -EXFULL; if (DEBUG_LOGGING) /* enable verbose for easier tracing */ - easy_setopt(curl, CURLOPT_VERBOSE, 1L, LOG_WARNING, ); + (void) easy_setopt(curl, LOG_WARNING, CURLOPT_VERBOSE, 1L); - easy_setopt(curl, CURLOPT_USERAGENT, - "systemd-journal-upload " GIT_VERSION, - LOG_WARNING, ); + (void) easy_setopt(curl, LOG_WARNING, + CURLOPT_USERAGENT, "systemd-journal-upload " GIT_VERSION); if (!streq_ptr(arg_key, "-") && (arg_key || startswith(u->url, "https://"))) { - easy_setopt(curl, CURLOPT_SSLKEY, arg_key ?: PRIV_KEY_FILE, - LOG_ERR, return -EXFULL); - easy_setopt(curl, CURLOPT_SSLCERT, arg_cert ?: CERT_FILE, - LOG_ERR, return -EXFULL); + if (!easy_setopt(curl, LOG_ERR, CURLOPT_SSLKEY, arg_key ?: PRIV_KEY_FILE)) + return -EXFULL; + if (!easy_setopt(curl, LOG_ERR, CURLOPT_SSLCERT, arg_cert ?: CERT_FILE)) + return -EXFULL; } if (STRPTR_IN_SET(arg_trust, "-", "all")) { log_info("Server certificate verification disabled."); - easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L, - LOG_ERR, return -EUCLEAN); - easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L, - LOG_ERR, return -EUCLEAN); - } else if (arg_trust || startswith(u->url, "https://")) - easy_setopt(curl, CURLOPT_CAINFO, arg_trust ?: TRUST_FILE, - LOG_ERR, return -EXFULL); + if (!easy_setopt(curl, LOG_ERR, CURLOPT_SSL_VERIFYPEER, 0L)) + return -EUCLEAN; + if (!easy_setopt(curl, LOG_ERR, CURLOPT_SSL_VERIFYHOST, 0L)) + return -EUCLEAN; + } else if (arg_trust || startswith(u->url, "https://")) { + if (!easy_setopt(curl, LOG_ERR, CURLOPT_CAINFO, arg_trust ?: TRUST_FILE)) + return -EXFULL; + } if (arg_key || arg_trust) - easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1, - LOG_WARNING, ); + (void) easy_setopt(curl, LOG_WARNING, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1); u->easy = TAKE_PTR(curl); } else { @@ -330,11 +325,8 @@ int start_upload(Uploader *u, } /* upload to this place */ - code = curl_easy_setopt(u->easy, CURLOPT_URL, u->url); - if (code) - return log_error_errno(SYNTHETIC_ERRNO(EXFULL), - "curl_easy_setopt CURLOPT_URL failed: %s", - curl_easy_strerror(code)); + if (!easy_setopt(u->easy, LOG_ERR, CURLOPT_URL, u->url)) + return -EXFULL; u->uploading = true; @@ -567,15 +559,15 @@ static int update_content_encoding_header(Uploader *u, const CompressionConfig * break; } - if (update_header) { - CURLcode code; - easy_setopt(u->easy, CURLOPT_HTTPHEADER, u->header, LOG_WARNING, return -EXFULL); - } + if (update_header && + !easy_setopt(u->easy, LOG_WARNING, CURLOPT_HTTPHEADER, u->header)) + return -EXFULL; u->compression = cc; if (cc) - log_debug("Using compression algorithm %s with compression level %i.", compression_to_string(cc->algorithm), cc->level); + log_debug("Using compression algorithm %s with compression level %i.", + compression_to_string(cc->algorithm), cc->level); else log_debug("Disabled compression algorithm."); return 0; @@ -644,12 +636,13 @@ static int perform_upload(Uploader *u) { code = curl_easy_perform(u->easy); if (code) { if (u->error[0]) - log_error("Upload to %s failed: %.*s", - u->url, (int) sizeof(u->error), u->error); + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Upload to %s failed: %.*s", + u->url, (int) sizeof(u->error), u->error); else - log_error("Upload to %s failed: %s", - u->url, curl_easy_strerror(code)); - return -EIO; + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Upload to %s failed: %s", + u->url, curl_easy_strerror(code)); } code = curl_easy_getinfo(u->easy, CURLINFO_RESPONSE_CODE, &status); From 693ecaac7e12c120d1323478b2433d77367aa0c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 14 Apr 2026 18:59:07 +0200 Subject: [PATCH 0992/1296] various: fix compilation with openssl-4.0.0-beta1 Various types have been made opaque, so we need to use some accessor functions. --- src/sbsign/sbsign.c | 5 +++-- src/shared/pkcs11-util.c | 15 ++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/sbsign/sbsign.c b/src/sbsign/sbsign.c index ee1c0f77ab906..f54dacf65a49d 100644 --- a/src/sbsign/sbsign.c +++ b/src/sbsign/sbsign.c @@ -265,8 +265,9 @@ static int spc_indirect_data_content_new(const void *digest, size_t digestsz, ui return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get SpcPeImageData object: %s", ERR_error_string(ERR_get_error(), NULL)); - idc->data->value->value.sequence->data = TAKE_PTR(peidraw); - idc->data->value->value.sequence->length = peidrawsz; + if (!ASN1_STRING_set(idc->data->value->value.sequence, peidraw, peidrawsz)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ASN1_STRING data."); + idc->messageDigest->digestAlgorithm->algorithm = OBJ_nid2obj(NID_sha256); if (!idc->messageDigest->digestAlgorithm->algorithm) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get SHA256 object: %s", diff --git a/src/shared/pkcs11-util.c b/src/shared/pkcs11-util.c index 165fefbea1ff8..96b25c4ac36b8 100644 --- a/src/shared/pkcs11-util.c +++ b/src/shared/pkcs11-util.c @@ -560,7 +560,11 @@ int pkcs11_token_read_public_key( return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to init an EVP_PKEY_CTX for EC."); OSSL_PARAM ec_params[8] = { - OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, os->data, os->length) + /* We need to drop the const from the data param, because ec_params is + * modified below. But we'll not modify ec_params[0]. */ + OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, + (unsigned char *) ASN1_STRING_get0_data(os), + ASN1_STRING_length(os)), }; _cleanup_free_ void *order = NULL, *p = NULL, *a = NULL, *b = NULL, *generator = NULL; @@ -663,13 +667,10 @@ int pkcs11_token_read_x509_certificate( CK_OBJECT_HANDLE object, X509 **ret_cert) { - _cleanup_free_ char *t = NULL; CK_ATTRIBUTE attribute = { .type = CKA_VALUE }; CK_RV rv; - _cleanup_(X509_freep) X509 *x509 = NULL; - X509_NAME *name = NULL; int r; assert(ret_cert); @@ -695,15 +696,15 @@ int pkcs11_token_read_x509_certificate( "Failed to read X.509 certificate data off token: %s", sym_p11_kit_strerror(rv)); const unsigned char *p = attribute.pValue; - x509 = d2i_X509(NULL, &p, attribute.ulValueLen); + _cleanup_(X509_freep) X509 *x509 = d2i_X509(NULL, &p, attribute.ulValueLen); if (!x509) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse X.509 certificate."); - name = X509_get_subject_name(x509); + const X509_NAME *name = X509_get_subject_name(x509); if (!name) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to acquire X.509 subject name."); - t = X509_NAME_oneline(name, NULL, 0); + _cleanup_free_ char *t = X509_NAME_oneline(name, NULL, 0); if (!t) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to format X.509 subject name as string."); From 49e2af9277b678b1b3ede1efeac1488016485f33 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 30 Mar 2026 13:51:04 +0200 Subject: [PATCH 0993/1296] shared: add varlink interface definitions for machine instance control Add three varlink interface definitions for the machine instance control hierarchy: - io.systemd.MachineInstance: generic operations applicable to both containers and VMs (PowerOff, Reboot, Pause, Resume, QueryStatus, SubscribeEvents). nspawn could implement this same interface later. - io.systemd.VirtualMachineInstance: VM-specific but VMM-agnostic operations. Empty for now, future home for AddBlockDevice and similar. - io.systemd.QemuMachineInstance: QEMU-specific operations. Defines AcquireQMP() for protocol upgrade to a direct QMP connection. The "Instance" suffix avoids collision with machined's existing io.systemd.Machine interface. Signed-off-by: Christian Brauner (Amutable) --- src/shared/meson.build | 3 ++ .../varlink-io.systemd.MachineInstance.c | 51 +++++++++++++++++++ .../varlink-io.systemd.MachineInstance.h | 6 +++ .../varlink-io.systemd.QemuMachineInstance.c | 17 +++++++ .../varlink-io.systemd.QemuMachineInstance.h | 6 +++ ...arlink-io.systemd.VirtualMachineInstance.c | 9 ++++ ...arlink-io.systemd.VirtualMachineInstance.h | 6 +++ 7 files changed, 98 insertions(+) create mode 100644 src/shared/varlink-io.systemd.MachineInstance.c create mode 100644 src/shared/varlink-io.systemd.MachineInstance.h create mode 100644 src/shared/varlink-io.systemd.QemuMachineInstance.c create mode 100644 src/shared/varlink-io.systemd.QemuMachineInstance.h create mode 100644 src/shared/varlink-io.systemd.VirtualMachineInstance.c create mode 100644 src/shared/varlink-io.systemd.VirtualMachineInstance.h diff --git a/src/shared/meson.build b/src/shared/meson.build index 22dccf0e2a7da..924987b024c82 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -216,6 +216,7 @@ shared_sources = files( 'varlink-io.systemd.Login.c', 'varlink-io.systemd.Machine.c', 'varlink-io.systemd.MachineImage.c', + 'varlink-io.systemd.MachineInstance.c', 'varlink-io.systemd.ManagedOOM.c', 'varlink-io.systemd.Manager.c', 'varlink-io.systemd.Metrics.c', @@ -226,6 +227,7 @@ shared_sources = files( 'varlink-io.systemd.Network.Link.c', 'varlink-io.systemd.PCRExtend.c', 'varlink-io.systemd.PCRLock.c', + 'varlink-io.systemd.QemuMachineInstance.c', 'varlink-io.systemd.Repart.c', 'varlink-io.systemd.Resolve.c', 'varlink-io.systemd.Resolve.Hook.c', @@ -234,6 +236,7 @@ shared_sources = files( 'varlink-io.systemd.Udev.c', 'varlink-io.systemd.Unit.c', 'varlink-io.systemd.UserDatabase.c', + 'varlink-io.systemd.VirtualMachineInstance.c', 'varlink-io.systemd.oom.c', 'varlink-io.systemd.oom.Prekill.c', 'varlink-io.systemd.service.c', diff --git a/src/shared/varlink-io.systemd.MachineInstance.c b/src/shared/varlink-io.systemd.MachineInstance.c new file mode 100644 index 0000000000000..365b6f5f9e1af --- /dev/null +++ b/src/shared/varlink-io.systemd.MachineInstance.c @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.MachineInstance.h" + +static SD_VARLINK_DEFINE_METHOD(Terminate); +static SD_VARLINK_DEFINE_METHOD(PowerOff); +static SD_VARLINK_DEFINE_METHOD(Reboot); +static SD_VARLINK_DEFINE_METHOD(Pause); +static SD_VARLINK_DEFINE_METHOD(Resume); + +static SD_VARLINK_DEFINE_METHOD( + Describe, + SD_VARLINK_FIELD_COMMENT("True iff vCPUs are executing"), + SD_VARLINK_DEFINE_OUTPUT(running, SD_VARLINK_BOOL, 0), + SD_VARLINK_FIELD_COMMENT("Backend-specific state string (e.g. 'running', 'paused', 'shutdown'); 'unknown' if unavailable"), + SD_VARLINK_DEFINE_OUTPUT(status, SD_VARLINK_STRING, 0)); + +static SD_VARLINK_DEFINE_METHOD_FULL( + SubscribeEvents, + SD_VARLINK_REQUIRES_MORE, + SD_VARLINK_FIELD_COMMENT("If specified, only deliver events whose name matches one of these strings; null means all events"), + SD_VARLINK_DEFINE_INPUT(filter, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("Name of the event"), + SD_VARLINK_DEFINE_OUTPUT(event, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Event-specific payload"), + SD_VARLINK_DEFINE_OUTPUT(data, SD_VARLINK_OBJECT, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_ERROR(NotConnected); +static SD_VARLINK_DEFINE_ERROR(NotSupported); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_MachineInstance, + "io.systemd.MachineInstance", + SD_VARLINK_SYMBOL_COMMENT("Forcefully terminate the machine immediately"), + &vl_method_Terminate, + SD_VARLINK_SYMBOL_COMMENT("Request a clean shutdown of the machine"), + &vl_method_PowerOff, + SD_VARLINK_SYMBOL_COMMENT("Reboot the machine"), + &vl_method_Reboot, + SD_VARLINK_SYMBOL_COMMENT("Pause/freeze the machine"), + &vl_method_Pause, + SD_VARLINK_SYMBOL_COMMENT("Resume a paused machine"), + &vl_method_Resume, + SD_VARLINK_SYMBOL_COMMENT("Query the current status of the machine"), + &vl_method_Describe, + SD_VARLINK_SYMBOL_COMMENT("Subscribe to machine events. Returns a stream of events as they occur."), + &vl_method_SubscribeEvents, + SD_VARLINK_SYMBOL_COMMENT("The connection to the machine backend is not available"), + &vl_error_NotConnected, + SD_VARLINK_SYMBOL_COMMENT("The requested operation is not supported"), + &vl_error_NotSupported); diff --git a/src/shared/varlink-io.systemd.MachineInstance.h b/src/shared/varlink-io.systemd.MachineInstance.h new file mode 100644 index 0000000000000..fa473b21510cd --- /dev/null +++ b/src/shared/varlink-io.systemd.MachineInstance.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_MachineInstance; diff --git a/src/shared/varlink-io.systemd.QemuMachineInstance.c b/src/shared/varlink-io.systemd.QemuMachineInstance.c new file mode 100644 index 0000000000000..b03fa2199c487 --- /dev/null +++ b/src/shared/varlink-io.systemd.QemuMachineInstance.c @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.QemuMachineInstance.h" + +static SD_VARLINK_DEFINE_METHOD_FULL( + AcquireQMP, + SD_VARLINK_REQUIRES_UPGRADE); + +static SD_VARLINK_DEFINE_ERROR(AlreadyAcquired); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_QemuMachineInstance, + "io.systemd.QemuMachineInstance", + SD_VARLINK_SYMBOL_COMMENT("Acquire a direct QMP connection to the QEMU instance via protocol upgrade"), + &vl_method_AcquireQMP, + SD_VARLINK_SYMBOL_COMMENT("A QMP connection has already been acquired by another client"), + &vl_error_AlreadyAcquired); diff --git a/src/shared/varlink-io.systemd.QemuMachineInstance.h b/src/shared/varlink-io.systemd.QemuMachineInstance.h new file mode 100644 index 0000000000000..203dacb40c46b --- /dev/null +++ b/src/shared/varlink-io.systemd.QemuMachineInstance.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_QemuMachineInstance; diff --git a/src/shared/varlink-io.systemd.VirtualMachineInstance.c b/src/shared/varlink-io.systemd.VirtualMachineInstance.c new file mode 100644 index 0000000000000..f491418a2c6a6 --- /dev/null +++ b/src/shared/varlink-io.systemd.VirtualMachineInstance.c @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.VirtualMachineInstance.h" + +/* VM-specific control interface. Currently empty — reserved for methods that apply to virtual + * machines generically but not to containers (e.g. snapshot, migration, device hotplug). */ +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_VirtualMachineInstance, + "io.systemd.VirtualMachineInstance"); diff --git a/src/shared/varlink-io.systemd.VirtualMachineInstance.h b/src/shared/varlink-io.systemd.VirtualMachineInstance.h new file mode 100644 index 0000000000000..b6d0600f34aee --- /dev/null +++ b/src/shared/varlink-io.systemd.VirtualMachineInstance.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_VirtualMachineInstance; From fb12d9c5557abae5024cf1210735946903cc2601 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 30 Mar 2026 13:52:08 +0200 Subject: [PATCH 0994/1296] machined: add controlAddress field to Machine.Register and Machine.List Follow the existing sshAddress pattern to add a controlAddress field that allows machine registrants (like vmspawn) to advertise a varlink socket address for direct VM control. machined stores and exposes the address but never connects to it itself. Signed-off-by: Christian Brauner (Amutable) --- src/machine/machine-varlink.c | 1 + src/machine/machine.c | 3 +++ src/machine/machine.h | 1 + src/machine/machined-varlink.c | 1 + src/shared/varlink-io.systemd.Machine.c | 4 ++++ 5 files changed, 10 insertions(+) diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c index a3d3cfcc7e7ee..fcdeeb7ae8b10 100644 --- a/src/machine/machine-varlink.c +++ b/src/machine/machine-varlink.c @@ -142,6 +142,7 @@ int vl_method_register(sd_varlink *link, sd_json_variant *parameters, sd_varlink { "vSockCid", _SD_JSON_VARIANT_TYPE_INVALID, machine_cid, offsetof(Machine, vsock_cid), 0 }, { "sshAddress", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(Machine, ssh_address), SD_JSON_STRICT }, { "sshPrivateKeyPath", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(Machine, ssh_private_key_path), 0 }, + { "controlAddress", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(Machine, control_address), SD_JSON_STRICT }, { "allocateUnit", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(Machine, allocate_unit), 0 }, VARLINK_DISPATCH_POLKIT_FIELD, {} diff --git a/src/machine/machine.c b/src/machine/machine.c index 535128692ece4..63fed79687e5d 100644 --- a/src/machine/machine.c +++ b/src/machine/machine.c @@ -154,6 +154,7 @@ Machine* machine_free(Machine *m) { free(m->netif); free(m->ssh_address); free(m->ssh_private_key_path); + free(m->control_address); return mfree(m); } @@ -245,6 +246,7 @@ int machine_save(Machine *m) { env_file_fputs_assignment(f, "SSH_ADDRESS=", m->ssh_address); env_file_fputs_assignment(f, "SSH_PRIVATE_KEY_PATH=", m->ssh_private_key_path); + env_file_fputs_assignment(f, "CONTROL_ADDRESS=", m->control_address); r = flink_tmpfile(f, temp_path, m->state_file, LINK_TMPFILE_REPLACE); if (r < 0) @@ -338,6 +340,7 @@ int machine_load(Machine *m) { "VSOCK_CID", &vsock_cid, "SSH_ADDRESS", &m->ssh_address, "SSH_PRIVATE_KEY_PATH", &m->ssh_private_key_path, + "CONTROL_ADDRESS", &m->control_address, "UID", &uid); if (r == -ENOENT) return 0; diff --git a/src/machine/machine.h b/src/machine/machine.h index 899218f48d567..6f6183b712d58 100644 --- a/src/machine/machine.h +++ b/src/machine/machine.h @@ -96,6 +96,7 @@ typedef struct Machine { unsigned vsock_cid; char *ssh_address; char *ssh_private_key_path; + char *control_address; LIST_HEAD(Operation, operations); diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c index ac506ad87f5a9..4ab68a77f9e2b 100644 --- a/src/machine/machined-varlink.c +++ b/src/machine/machined-varlink.c @@ -489,6 +489,7 @@ static int list_machine_one_and_maybe_read_metadata(sd_varlink *link, Machine *m JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("vSockCid", m->vsock_cid, VMADDR_CID_ANY), JSON_BUILD_PAIR_STRING_NON_EMPTY("sshAddress", m->ssh_address), JSON_BUILD_PAIR_STRING_NON_EMPTY("sshPrivateKeyPath", m->ssh_private_key_path), + JSON_BUILD_PAIR_STRING_NON_EMPTY("controlAddress", m->control_address), JSON_BUILD_PAIR_VARIANT_NON_NULL("addresses", addr_array), JSON_BUILD_PAIR_STRV_ENV_PAIR_NON_EMPTY("OSRelease", os_release), JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("UIDShift", shift, UID_INVALID), diff --git a/src/shared/varlink-io.systemd.Machine.c b/src/shared/varlink-io.systemd.Machine.c index 9f6d36ad77c7b..da373a3c207dd 100644 --- a/src/shared/varlink-io.systemd.Machine.c +++ b/src/shared/varlink-io.systemd.Machine.c @@ -57,6 +57,8 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_DEFINE_INPUT(vSockCid, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_INPUT(sshAddress, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_INPUT(sshPrivateKeyPath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Varlink socket address for direct machine control. The server at this address is expected to implement io.systemd.MachineInstance and optionally io.systemd.VirtualMachineInstance and io.systemd.QemuMachineInstance."), + SD_VARLINK_DEFINE_INPUT(controlAddress, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Controls whether to allocate a scope unit for the machine to register. If false, the client already took care of that and registered a service/scope specific to the machine."), SD_VARLINK_DEFINE_INPUT(allocateUnit, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), VARLINK_DEFINE_POLKIT_INPUT); @@ -107,6 +109,8 @@ static SD_VARLINK_DEFINE_METHOD_FULL( SD_VARLINK_DEFINE_OUTPUT(sshAddress, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Path to private SSH key"), SD_VARLINK_DEFINE_OUTPUT(sshPrivateKeyPath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Varlink socket address for direct machine control, implementing io.systemd.MachineInstance and optionally further interfaces"), + SD_VARLINK_DEFINE_OUTPUT(controlAddress, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("List of addresses of the machine"), SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(addresses, Address, SD_VARLINK_ARRAY | SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("OS release information of the machine. It contains an array of key value pairs read from the os-release(5) file in the image."), From 88a01ed2e3426e2db84968fe4fd2d182ebd7db11 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Sat, 11 Apr 2026 14:00:07 +0200 Subject: [PATCH 0995/1296] json-stream: expose log helpers for consumers Move json_stream_description(), json_stream_log(), and json_stream_log_errno() from json-stream.c into json-stream.h so that consumers like the QMP client can use the same description-prefixed logging that json-stream itself uses internally. Signed-off-by: Christian Brauner (Amutable) --- src/libsystemd/sd-json/json-stream.c | 10 ---------- src/libsystemd/sd-json/json-stream.h | 11 +++++++++++ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/libsystemd/sd-json/json-stream.c b/src/libsystemd/sd-json/json-stream.c index e8cd2c55ddd3c..d8475ae873424 100644 --- a/src/libsystemd/sd-json/json-stream.c +++ b/src/libsystemd/sd-json/json-stream.c @@ -35,10 +35,6 @@ struct JsonStreamQueueItem { int fds[]; }; -static const char* json_stream_description(const JsonStream *s) { - return (s ? s->description : NULL) ?: "json-stream"; -} - /* Returns the size of the framing delimiter in bytes: strlen(delimiter) for multi-char * delimiters (e.g. "\r\n"), or 1 for the default NUL-byte delimiter (delimiter == NULL). */ static size_t json_stream_delimiter_size(const JsonStream *s) { @@ -54,12 +50,6 @@ static usec_t json_stream_now(const JsonStream *s) { return now(CLOCK_MONOTONIC); } -#define json_stream_log(s, fmt, ...) \ - log_debug("%s: " fmt, json_stream_description(s), ##__VA_ARGS__) - -#define json_stream_log_errno(s, error, fmt, ...) \ - log_debug_errno((error), "%s: " fmt, json_stream_description(s), ##__VA_ARGS__) - sd_json_variant** json_stream_queue_item_get_data(JsonStreamQueueItem *q) { assert(q); return &q->data; diff --git a/src/libsystemd/sd-json/json-stream.h b/src/libsystemd/sd-json/json-stream.h index 92a09d494191a..671b0f8985c9c 100644 --- a/src/libsystemd/sd-json/json-stream.h +++ b/src/libsystemd/sd-json/json-stream.h @@ -6,6 +6,7 @@ #include "sd-forward.h" #include "list.h" +#include "log.h" /* JsonStream provides the transport layer used by sd-varlink (and other consumers like * the QMP client) for exchanging length-delimited JSON messages over a pair of file @@ -132,6 +133,16 @@ void json_stream_done(JsonStream *s); int json_stream_set_description(JsonStream *s, const char *description); const char* json_stream_get_description(const JsonStream *s); +static inline const char* json_stream_description(const JsonStream *s) { + return (s ? s->description : NULL) ?: "json-stream"; +} + +#define json_stream_log(s, fmt, ...) \ + log_debug("%s: " fmt, json_stream_description(s), ##__VA_ARGS__) + +#define json_stream_log_errno(s, error, fmt, ...) \ + log_debug_errno((error), "%s: " fmt, json_stream_description(s), ##__VA_ARGS__) + /* fd ownership */ int json_stream_attach_fds(JsonStream *s, int input_fd, int output_fd); From 50dd55bb6ebcf8ae43490b8db2a917330048f1ba Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 30 Mar 2026 14:02:10 +0200 Subject: [PATCH 0996/1296] shared: add QMP client library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Async QMP client for talking to QEMU's machine monitor from libsystemd-shared. The I/O core (buffered read/write, output queue, event source management) follows sd-varlink's patterns. State machine: INITIAL -> GREETING_RECEIVED -> CAPABILITIES_SENT -> RUNNING | v DISCONNECTED The QMP handshake (greeting + qmp_capabilities) is driven transparently by qmp_client_invoke() through an internal qmp_client_ensure_running() helper, matching sd-bus's bus_ensure_running() pattern. Callers never wait for it explicitly. qmp_client_invoke() is the only command interface: asynchronous, with per-command callback. Slots are tracked in a Set keyed by id; replies are dispatched by id match. SCM_RIGHTS fd passing is bundled through QmpClientArgs and the QMP_CLIENT_ARGS_FD macro. qmp_client_process() and qmp_client_wait() are exposed publicly, mirroring sd_varlink_process() and sd_varlink_wait(). Callers that need to drive the client synchronously — e.g. feature probing before entering the sd_event main loop — can loop on them exactly like varlink_call_internal() does on its varlink equivalents. Other features: - Buffered stream reader for QMP's \r\n-delimited JSON, handling multi-read responses (query-qmp-schema is ~200 KiB). - Fdset id allocation via qmp_client_next_fdset_id(). - Synthetic SHUTDOWN event on unexpected disconnect. - Disconnect detection with callback notification and pending-command cleanup. - -ENOBUFS from the 16 MiB input-buffer cap is treated as recoverable (not a transport error). Signed-off-by: Christian Brauner (Amutable) --- src/shared/meson.build | 1 + src/shared/qmp-client.c | 845 ++++++++++++++++++++++++++++++++++++ src/shared/qmp-client.h | 78 ++++ src/shared/shared-forward.h | 1 + 4 files changed, 925 insertions(+) create mode 100644 src/shared/qmp-client.c create mode 100644 src/shared/qmp-client.h diff --git a/src/shared/meson.build b/src/shared/meson.build index 924987b024c82..705cb955bd6d2 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -165,6 +165,7 @@ shared_sources = files( 'printk-util.c', 'prompt-util.c', 'ptyfwd.c', + 'qmp-client.c', 'qrcode-util.c', 'quota-util.c', 'reboot-util.c', diff --git a/src/shared/qmp-client.c b/src/shared/qmp-client.c new file mode 100644 index 0000000000000..6a92550e727bf --- /dev/null +++ b/src/shared/qmp-client.c @@ -0,0 +1,845 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-event.h" +#include "sd-json.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "hash-funcs.h" +#include "json-stream.h" +#include "json-util.h" +#include "qmp-client.h" +#include "set.h" +#include "siphash24.h" +#include "string-util.h" + +typedef enum QmpClientState { + QMP_CLIENT_HANDSHAKE_INITIAL, /* waiting for QMP greeting */ + QMP_CLIENT_HANDSHAKE_GREETING_RECEIVED, /* greeting received, sending qmp_capabilities */ + QMP_CLIENT_HANDSHAKE_CAPABILITIES_SENT, /* waiting for qmp_capabilities response */ + QMP_CLIENT_RUNNING, /* connected, ready for commands */ + QMP_CLIENT_DISCONNECTED, /* connection closed */ + _QMP_CLIENT_STATE_MAX, + _QMP_CLIENT_STATE_INVALID = -EINVAL, +} QmpClientState; + +/* States routed to dispatch_handshake. */ +#define QMP_CLIENT_STATE_IS_HANDSHAKE(s) \ + IN_SET(s, \ + QMP_CLIENT_HANDSHAKE_INITIAL, \ + QMP_CLIENT_HANDSHAKE_GREETING_RECEIVED, \ + QMP_CLIENT_HANDSHAKE_CAPABILITIES_SENT) + +typedef struct QmpSlot { + uint64_t id; + qmp_command_callback_t callback; + void *userdata; +} QmpSlot; + +struct QmpClient { + unsigned n_ref; + + JsonStream stream; + + sd_event_source *quit_event_source; + sd_event_source *defer_event_source; + + uint64_t next_id; + Set *slots; /* QmpSlot* entries indexed by id, for async dispatch */ + + qmp_event_callback_t event_callback; + void *event_userdata; + qmp_disconnect_callback_t disconnect_callback; + void *disconnect_userdata; + + unsigned next_fdset_id; /* monotonic fdset-id allocator for add-fd */ + + QmpClientState state; + sd_json_variant *current; /* most recently parsed message, pending dispatch */ +}; + +static void qmp_slot_hash_func(const QmpSlot *p, struct siphash *state) { + siphash24_compress_typesafe(p->id, state); +} + +static int qmp_slot_compare_func(const QmpSlot *a, const QmpSlot *b) { + return CMP(a->id, b->id); +} + +DEFINE_PRIVATE_HASH_OPS(qmp_slot_hash_ops, + QmpSlot, qmp_slot_hash_func, qmp_slot_compare_func); + +static void qmp_client_clear(QmpClient *c); + +static QmpClient* qmp_client_destroy(QmpClient *c) { + if (!c) + return NULL; + + qmp_client_clear(c); + + return mfree(c); +} + +DEFINE_PRIVATE_TRIVIAL_REF_FUNC(QmpClient, qmp_client); +DEFINE_TRIVIAL_UNREF_FUNC(QmpClient, qmp_client, qmp_client_destroy); + +static void qmp_client_clear_current(QmpClient *c) { + assert(c); + + c->current = sd_json_variant_unref(c->current); +} + +static void qmp_client_dispatch_event(QmpClient *c, sd_json_variant *v) { + int r; + + assert(c); + assert(v); + + if (!c->event_callback) + return; + + struct { + const char *event; + sd_json_variant *data; + } p = {}; + + static const sd_json_dispatch_field table[] = { + { "event", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, event), SD_JSON_MANDATORY }, + { "data", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, voffsetof(p, data), 0 }, + {}, + }; + + r = sd_json_dispatch(v, table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG|SD_JSON_DEBUG, &p); + if (r < 0) + return; + + r = c->event_callback(c, p.event, p.data, c->event_userdata); + if (r < 0) + json_stream_log_errno(&c->stream, r, "Event callback returned error, ignoring: %m"); +} + +/* QEMU's error "class" is effectively always "GenericError"; only "desc" carries useful info. */ +static const char* qmp_extract_error_description(sd_json_variant *v) { + sd_json_variant *error = sd_json_variant_by_key(v, "error"); + if (!error) + return NULL; + sd_json_variant *desc = sd_json_variant_by_key(error, "desc"); + if (desc) + return sd_json_variant_string(desc); + return "unspecified error"; +} + +/* Returns 1 with id set; 0 if absent (e.g. pre-parse error responses); -EBADMSG on wrong type. */ +static int qmp_extract_response_id(sd_json_variant *v, uint64_t *ret) { + sd_json_variant *id_variant; + + assert(v); + assert(ret); + + id_variant = sd_json_variant_by_key(v, "id"); + if (!id_variant) { + *ret = 0; + return 0; + } + if (!sd_json_variant_is_unsigned(id_variant)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "QMP response 'id' field is not an unsigned integer."); + + *ret = sd_json_variant_unsigned(id_variant); + return 1; +} + +/* Returns 0 on success (ret_result = "return" value), -EIO on QMP error (reterr_desc set). */ +static int qmp_parse_response(sd_json_variant *v, sd_json_variant **ret_result, const char **reterr_desc) { + const char *desc; + + desc = qmp_extract_error_description(v); + if (desc) { + if (reterr_desc) + *reterr_desc = desc; + return -EIO; + } + + if (ret_result) + *ret_result = sd_json_variant_by_key(v, "return"); + return 0; +} + +static int qmp_client_build_command( + QmpClient *c, + const char *command, + sd_json_variant *arguments, + sd_json_variant **ret, + uint64_t *ret_id) { + + uint64_t id; + int r; + + assert(c); + assert(command); + assert(ret); + assert(ret_id); + + id = c->next_id++; + + r = sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_STRING("execute", command), + SD_JSON_BUILD_PAIR_CONDITION(!!arguments, "arguments", SD_JSON_BUILD_VARIANT(arguments)), + SD_JSON_BUILD_PAIR_UNSIGNED("id", id)); + if (r < 0) + return r; + + *ret_id = id; + return 0; +} + +/* Route c->current to event callback or matching async slot. Returns 1 on dispatch. */ +static int qmp_client_dispatch_reply(QmpClient *c) { + sd_json_variant *result = NULL; + const char *desc = NULL; + uint64_t id; + int error, r; + + assert(c); + + if (!c->current) + return 0; + + /* Events have an "event" key */ + if (sd_json_variant_by_key(c->current, "event")) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = TAKE_PTR(c->current); + qmp_client_dispatch_event(c, v); + return 1; + } + + /* Command responses carry an "id" matching a request we sent */ + r = qmp_extract_response_id(c->current, &id); + if (r < 0) { + qmp_client_clear_current(c); + return json_stream_log_errno(&c->stream, r, "Discarding QMP response with malformed id: %m"); + } + if (r == 0) { + qmp_client_clear_current(c); + json_stream_log(&c->stream, "Discarding unrecognized QMP message"); + return 1; + } + + _cleanup_free_ QmpSlot *pending = set_remove(c->slots, &(QmpSlot) { .id = id }); + if (!pending) { + qmp_client_clear_current(c); + json_stream_log(&c->stream, "Discarding QMP response with unknown id %" PRIu64, id); + return 1; + } + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = TAKE_PTR(c->current); + error = qmp_parse_response(v, &result, &desc); + + r = pending->callback(c, result, desc, error, pending->userdata); + if (r < 0) + json_stream_log_errno(&c->stream, r, "Command callback returned error, ignoring: %m"); + + return 1; +} + +/* Fail all pending async commands with the given error. Called on disconnect. */ +static void qmp_client_fail_pending(QmpClient *c, int error) { + QmpSlot *p; + int r; + + assert(c); + + while ((p = set_steal_first(c->slots))) { + r = p->callback(c, /* result= */ NULL, /* error_desc= */ NULL, error, p->userdata); + if (r < 0) + json_stream_log_errno(&c->stream, r, "Command callback returned error, ignoring: %m"); + free(p); + } +} + +/* Synthetic SHUTDOWN on unexpected disconnect so subscribers learn the VM is gone. */ +static void qmp_client_emit_synthetic_shutdown(QmpClient *c) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *data = NULL; + int r; + + assert(c); + + if (!c->event_callback) + return; + + r = sd_json_buildo( + &data, + SD_JSON_BUILD_PAIR_BOOLEAN("guest", false), + SD_JSON_BUILD_PAIR_STRING("reason", "disconnected")); + if (r < 0) { + json_stream_log_errno(&c->stream, r, "Failed to build synthetic SHUTDOWN event data, skipping: %m"); + return; + } + + r = c->event_callback(c, "SHUTDOWN", data, c->event_userdata); + if (r < 0) + json_stream_log_errno(&c->stream, r, "Event callback returned error, ignoring: %m"); +} + +static bool qmp_client_handle_disconnect(QmpClient *c) { + assert(c); + + if (c->state == QMP_CLIENT_DISCONNECTED) + return false; + + c->state = QMP_CLIENT_DISCONNECTED; + + /* Disable defer event source so we don't busy-loop on the EOF condition. */ + if (c->defer_event_source) + (void) sd_event_source_set_enabled(c->defer_event_source, SD_EVENT_OFF); + + qmp_client_fail_pending(c, -ECONNRESET); + qmp_client_emit_synthetic_shutdown(c); + if (c->disconnect_callback) + c->disconnect_callback(c, c->disconnect_userdata); + + return true; +} + +static bool qmp_client_test_disconnect(QmpClient *c) { + assert(c); + + /* Already disconnected? */ + if (c->state == QMP_CLIENT_DISCONNECTED) + return false; + + if (!json_stream_should_disconnect(&c->stream)) + return false; + + return qmp_client_handle_disconnect(c); +} + +/* INITIAL → greeting → GREETING_RECEIVED → qmp_capabilities → CAPABILITIES_SENT → response → RUNNING. */ +static int qmp_client_dispatch_handshake(QmpClient *c) { + int r; + + assert(c); + assert(QMP_CLIENT_STATE_IS_HANDSHAKE(c->state)); + + if (!c->current) + return 0; + + /* Defensive: QEMU shouldn't emit events during capability negotiation, but if one + * arrives, dispatch it as an event rather than mis-parsing it as a handshake reply. */ + if (sd_json_variant_by_key(c->current, "event")) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = TAKE_PTR(c->current); + qmp_client_dispatch_event(c, v); + return 1; + } + + switch (c->state) { + + case QMP_CLIENT_HANDSHAKE_INITIAL: { + /* Waiting for QMP greeting. Take ownership so by_key()'s borrowed pointer + * stays valid through the case scope. */ + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = TAKE_PTR(c->current); + if (!sd_json_variant_by_key(v, "QMP")) + return json_stream_log_errno(&c->stream, SYNTHETIC_ERRNO(EPROTO), + "Expected QMP greeting, got something else"); + + c->state = QMP_CLIENT_HANDSHAKE_GREETING_RECEIVED; + + /* Fall through to immediately send capabilities */ + _fallthrough_; + } + + case QMP_CLIENT_HANDSHAKE_GREETING_RECEIVED: { + /* Send qmp_capabilities command */ + _cleanup_(sd_json_variant_unrefp) sd_json_variant *cmd = NULL; + r = sd_json_buildo( + &cmd, + SD_JSON_BUILD_PAIR_STRING("execute", "qmp_capabilities"), + SD_JSON_BUILD_PAIR_UNSIGNED("id", c->next_id++)); + if (r < 0) + return r; + + r = json_stream_enqueue(&c->stream, cmd); + if (r < 0) + return r; + + c->state = QMP_CLIENT_HANDSHAKE_CAPABILITIES_SENT; + return 1; + } + + case QMP_CLIENT_HANDSHAKE_CAPABILITIES_SENT: { + /* Take ownership so desc (borrowed from v's "error.desc") survives the format string. */ + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = TAKE_PTR(c->current); + const char *desc = NULL; + r = qmp_parse_response(v, /* ret_result= */ NULL, &desc); + if (r < 0) + return json_stream_log_errno(&c->stream, SYNTHETIC_ERRNO(EPROTO), + "qmp_capabilities failed: %s", desc); + + c->state = QMP_CLIENT_RUNNING; + return 1; + } + + default: + assert_not_reached(); + } +} + +static int qmp_client_dispatch(QmpClient *c) { + assert(c); + + if (!c->current) + return 0; + + if (QMP_CLIENT_STATE_IS_HANDSHAKE(c->state)) + return qmp_client_dispatch_handshake(c); + + return qmp_client_dispatch_reply(c); +} + +/* Single step: write → dispatch → parse → read → disconnect. Matches sd_varlink_process(). */ +int qmp_client_process(QmpClient *c) { + int r; + + assert(c); + + if (c->state < 0 || c->state == QMP_CLIENT_DISCONNECTED) + return -ENOTCONN; + + /* Pin against a callback dropping the last ref mid-dispatch. Matches sd_varlink_process(). */ + qmp_client_ref(c); + + /* 1. Write — drain output buffer */ + r = json_stream_write(&c->stream); + if (r < 0) + json_stream_log_errno(&c->stream, r, "Failed to write to QMP socket: %m"); + if (r != 0) + goto finish; + + /* 2. Dispatch — route based on state */ + r = qmp_client_dispatch(c); + if (r < 0) + json_stream_log_errno(&c->stream, r, "Failed to dispatch QMP message: %m"); + if (r != 0) + goto finish; + + /* 3. Parse — extract one complete message into c->current */ + if (!c->current) { + r = json_stream_parse(&c->stream, &c->current); + if (r < 0) + json_stream_log_errno(&c->stream, r, "Failed to parse QMP message: %m"); + if (r != 0) + goto finish; + } + + /* 4. Read — fill input buffer from fd */ + if (!c->current) { + r = json_stream_read(&c->stream); + if (r < 0) + json_stream_log_errno(&c->stream, r, "Failed to read from QMP socket: %m"); + if (r != 0) + goto finish; + } + + /* 5. Test disconnect */ + if (qmp_client_test_disconnect(c)) { + r = 1; + goto finish; + } + +finish: + /* Re-arm defer source on progress so we get called again next iteration. */ + if (r >= 0 && c->defer_event_source) { + int q; + + q = sd_event_source_set_enabled(c->defer_event_source, r > 0 ? SD_EVENT_ON : SD_EVENT_OFF); + if (q < 0) + r = json_stream_log_errno(&c->stream, q, "Failed to enable deferred event source: %m"); + } + + /* -ENOBUFS is the buffered stream's 16 MiB cap, not a transport error — propagate without disconnecting. */ + if (r < 0 && r != -ENOBUFS && c->state != QMP_CLIENT_DISCONNECTED) + qmp_client_handle_disconnect(c); + + qmp_client_unref(c); + return r; +} + +int qmp_client_wait(QmpClient *c, uint64_t timeout_usec) { + assert(c); + + if (c->state == QMP_CLIENT_DISCONNECTED) + return -ENOTCONN; + + return json_stream_wait(&c->stream, timeout_usec); +} + +bool qmp_client_is_idle(QmpClient *c) { + assert(c); + return set_isempty(c->slots); +} + +bool qmp_client_is_disconnected(QmpClient *c) { + assert(c); + return c->state == QMP_CLIENT_DISCONNECTED; +} + +/* Map our state to the transport phase used for POLLIN / salvage / timeout decisions. */ +static JsonStreamPhase qmp_client_phase(void *userdata) { + QmpClient *c = ASSERT_PTR(userdata); + + /* A parsed-but-undispatched message is mid-processing, not waiting on the wire. */ + if (c->current) + return JSON_STREAM_PHASE_OTHER; + + /* During handshake we're waiting for the greeting or qmp_capabilities response. */ + if (QMP_CLIENT_STATE_IS_HANDSHAKE(c->state)) + return JSON_STREAM_PHASE_AWAITING_REPLY; + + /* Running with pending async commands — waiting for their responses. */ + if (c->state == QMP_CLIENT_RUNNING && !set_isempty(c->slots)) + return JSON_STREAM_PHASE_AWAITING_REPLY; + + /* Running with no pending commands — waiting for unsolicited events. */ + if (c->state == QMP_CLIENT_RUNNING) + return JSON_STREAM_PHASE_READING; + + return JSON_STREAM_PHASE_OTHER; +} + +static int qmp_client_dispatch_cb(void *userdata) { + QmpClient *c = ASSERT_PTR(userdata); + return qmp_client_process(c); +} + +static int qmp_client_defer_callback(sd_event_source *source, void *userdata) { + QmpClient *c = ASSERT_PTR(userdata); + + assert(source); + + (void) qmp_client_process(c); + + return 1; +} + +/* Drive handshake to completion. Matches sd-bus's bus_ensure_running(). */ +static int qmp_client_ensure_running(QmpClient *c) { + int r; + + assert(c); + + if (c->state == QMP_CLIENT_RUNNING) + return 1; + + for (;;) { + if (c->state < 0 || c->state == QMP_CLIENT_DISCONNECTED) + return -ENOTCONN; + + r = qmp_client_process(c); + if (r < 0) + return r; + if (c->state == QMP_CLIENT_RUNNING) + return 1; + if (r > 0) + continue; + + r = qmp_client_wait(c, USEC_INFINITY); + if (r < 0) + return r; + } +} + +static void qmp_client_detach_event(QmpClient *c) { + if (!c) + return; + + c->defer_event_source = sd_event_source_disable_unref(c->defer_event_source); + c->quit_event_source = sd_event_source_disable_unref(c->quit_event_source); + json_stream_detach_event(&c->stream); +} + +static void qmp_client_clear(QmpClient *c) { + assert(c); + + qmp_client_handle_disconnect(c); + qmp_client_detach_event(c); + qmp_client_clear_current(c); + json_stream_done(&c->stream); + c->slots = set_free(c->slots); +} + +/* Blocks until output buffer is empty. Matches sd_varlink_flush(). */ +static int qmp_client_flush(QmpClient *c) { + if (!c) + return 0; + + if (c->state == QMP_CLIENT_DISCONNECTED) + return -ENOTCONN; + + return json_stream_flush(&c->stream); +} + +/* Notify callbacks, fire disconnect, detach sources, close fd. Matches sd_varlink_close(). */ +static int qmp_client_close(QmpClient *c) { + if (!c) + return 0; + + /* Take a temporary ref to prevent destruction mid-callback, + * matching sd_varlink_close()'s pattern. */ + qmp_client_ref(c); + qmp_client_clear(c); + qmp_client_unref(c); + + return 1; +} + +static int qmp_client_quit_callback(sd_event_source *source, void *userdata) { + QmpClient *c = ASSERT_PTR(userdata); + + assert(source); + + qmp_client_flush(c); + qmp_client_close(c); + + return 1; +} + +int qmp_client_connect_fd(QmpClient **ret, int fd) { + _cleanup_(qmp_client_unrefp) QmpClient *c = NULL; + int r; + + assert(ret); + assert(fd >= 0); + + c = new(QmpClient, 1); + if (!c) + return -ENOMEM; + + *c = (QmpClient) { + .n_ref = 1, + .state = QMP_CLIENT_HANDSHAKE_INITIAL, + .next_id = 1, + }; + + const JsonStreamParams params = { + .delimiter = "\r\n", + .phase = qmp_client_phase, + .dispatch = qmp_client_dispatch_cb, + .userdata = c, + }; + + r = json_stream_init(&c->stream, ¶ms); + if (r < 0) + return r; + + r = json_stream_connect_fd_pair(&c->stream, fd, fd); + if (r < 0) + return r; + + *ret = TAKE_PTR(c); + return 0; +} + +int qmp_client_attach_event(QmpClient *c, sd_event *event, int64_t priority) { + int r; + + assert(c); + assert(event); + assert(!json_stream_get_event(&c->stream)); + + r = json_stream_attach_event(&c->stream, event, priority); + if (r < 0) + return r; + + sd_event *ev = json_stream_get_event(&c->stream); + + r = sd_event_add_exit(ev, &c->quit_event_source, qmp_client_quit_callback, c); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(c->quit_event_source, priority); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(c->quit_event_source, "qmp-client-quit"); + + r = sd_event_add_defer(ev, &c->defer_event_source, qmp_client_defer_callback, c); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(c->defer_event_source, priority); + if (r < 0) + goto fail; + + r = sd_event_source_set_enabled(c->defer_event_source, SD_EVENT_OFF); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(c->defer_event_source, "qmp-client-defer"); + + return 0; + +fail: + qmp_client_detach_event(c); + return r; +} + +/* Cleanup hook: closes any fds in *args not yet transferred to the stream. */ +static QmpClientArgs* qmp_client_args_close_fds(QmpClientArgs *p) { + assert(p); + close_many_unset(p->fds_consume, p->n_fds); + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(QmpClientArgs*, qmp_client_args_close_fds); + +/* Transfer fds to the stream. On partial failure narrow args to the unstaged tail so + * the caller's cleanup closes only the untransferred fds. */ +static int qmp_client_stage_fds(QmpClient *c, QmpClientArgs *args) { + int r; + + assert(c); + + if (!args || args->n_fds == 0) + return 0; + + assert(args->fds_consume); + + for (size_t i = 0; i < args->n_fds; i++) { + r = json_stream_push_fd(&c->stream, args->fds_consume[i]); + if (r < 0) { + /* Already-staged are owned by the stream; narrow args to the rest. */ + json_stream_reset_pushed_fds(&c->stream); + args->fds_consume = &args->fds_consume[i]; + args->n_fds -= i; + return r; + } + } + + args->n_fds = 0; + return 0; +} + +int qmp_client_invoke( + QmpClient *c, + const char *command, + QmpClientArgs *args, + qmp_command_callback_t callback, + void *userdata) { + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *cmd = NULL; + _cleanup_free_ QmpSlot *pending = NULL; + /* Closes any fds in args not yet handed to the stream on every early-return path; + * TAKE_PTR()'d on the success path below once stage_fds has consumed them. */ + _cleanup_(qmp_client_args_close_fdsp) QmpClientArgs *fds_owner = args; + uint64_t id; + int r; + + assert(c); + assert(command); + assert(callback); + + r = qmp_client_ensure_running(c); + if (r < 0) + return r; + + r = qmp_client_build_command(c, command, args ? args->arguments : NULL, &cmd, &id); + if (r < 0) + return r; + + pending = new(QmpSlot, 1); + if (!pending) + return -ENOMEM; + + *pending = (QmpSlot) { + .id = id, + .callback = callback, + .userdata = userdata, + }; + + r = set_ensure_put(&c->slots, &qmp_slot_hash_ops, pending); + if (r < 0) + return r; + assert(r > 0); + + /* Stage AFTER ensure_running() drained internal enqueues so the next enqueue is ours. */ + r = qmp_client_stage_fds(c, args); + if (r < 0) { + set_remove(c->slots, pending); + return r; + } + + r = json_stream_enqueue(&c->stream, cmd); + if (r < 0) { + json_stream_reset_pushed_fds(&c->stream); + set_remove(c->slots, pending); + return r; + } + + /* Arm defer so process() drains the output on the next iteration. */ + if (c->defer_event_source) + (void) sd_event_source_set_enabled(c->defer_event_source, SD_EVENT_ON); + + TAKE_PTR(pending); + TAKE_PTR(fds_owner); + return 0; +} + +void qmp_client_bind_event(QmpClient *c, qmp_event_callback_t callback, void *userdata) { + assert(c); + c->event_callback = callback; + c->event_userdata = userdata; +} + +void qmp_client_bind_disconnect(QmpClient *c, qmp_disconnect_callback_t callback, void *userdata) { + assert(c); + c->disconnect_callback = callback; + c->disconnect_userdata = userdata; +} + +int qmp_client_set_description(QmpClient *c, const char *description) { + assert(c); + return json_stream_set_description(&c->stream, description); +} + +sd_event* qmp_client_get_event(QmpClient *c) { + assert(c); + return json_stream_get_event(&c->stream); +} + +unsigned qmp_client_next_fdset_id(QmpClient *c) { + assert(c); + return c->next_fdset_id++; +} + +bool qmp_schema_has_member(sd_json_variant *schema, const char *member_name) { + sd_json_variant *entry; + + assert(member_name); + + if (!sd_json_variant_is_array(schema)) + return false; + + JSON_VARIANT_ARRAY_FOREACH(entry, schema) { + if (!sd_json_variant_is_object(entry)) + continue; + + sd_json_variant *meta = sd_json_variant_by_key(entry, "meta-type"); + if (!meta || !streq_ptr(sd_json_variant_string(meta), "object")) + continue; + + sd_json_variant *members = sd_json_variant_by_key(entry, "members"); + if (!sd_json_variant_is_array(members)) + continue; + + sd_json_variant *m; + JSON_VARIANT_ARRAY_FOREACH(m, members) { + if (!sd_json_variant_is_object(m)) + continue; + sd_json_variant *mn = sd_json_variant_by_key(m, "name"); + if (mn && streq_ptr(sd_json_variant_string(mn), member_name)) + return true; + } + } + + return false; +} diff --git a/src/shared/qmp-client.h b/src/shared/qmp-client.h new file mode 100644 index 0000000000000..dbe65162e9722 --- /dev/null +++ b/src/shared/qmp-client.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +typedef int (*qmp_event_callback_t)( + QmpClient *client, + const char *event, + sd_json_variant *data, + void *userdata); + +typedef void (*qmp_disconnect_callback_t)( + QmpClient *client, + void *userdata); + +/* Success: (result, NULL, 0). QMP error: (NULL, desc, -EIO). Transport: (NULL, NULL, -errno). */ +typedef int (*qmp_command_callback_t)( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata); + +/* Bundles arguments + fds for one command. Construct fresh per invoke via the macros below. */ +typedef struct QmpClientArgs { + sd_json_variant *arguments; + int *fds_consume; + size_t n_fds; +} QmpClientArgs; + +#define QMP_CLIENT_ARGS(args_) \ + (&(QmpClientArgs){ .arguments = (args_) }) +#define QMP_CLIENT_ARGS_FD(args_, fd_) \ + (&(QmpClientArgs){ .arguments = (args_), .fds_consume = (int[]){ (fd_) }, .n_fds = 1 }) + +/* Takes ownership of fd; handshake runs lazily on first invoke or from the event loop. */ +int qmp_client_connect_fd(QmpClient **ret, int fd); + +int qmp_client_attach_event(QmpClient *c, sd_event *event, int64_t priority); + +/* Single non-blocking pump step: write → dispatch → parse → read → disconnect. Returns >0 if + * progress was made, 0 if idle, <0 on error (-ENOTCONN on disconnect). Same contract as + * sd_varlink_process(). */ +int qmp_client_process(QmpClient *c); + +/* Block on the transport fd until readable, or until timeout (USEC_INFINITY for no timeout). + * Same contract as sd_varlink_wait(). */ +int qmp_client_wait(QmpClient *c, uint64_t timeout_usec); + +/* True iff there are no outstanding command replies (slots set is empty). Useful as the pump-loop + * exit condition for callers driving the client synchronously via process() + wait(). */ +bool qmp_client_is_idle(QmpClient *c); + +/* True iff the connection is dead. Stable terminal state — once set, it stays set. */ +bool qmp_client_is_disconnected(QmpClient *c); + +/* Async send. Returns 0 on send (callback will fire later), negative errno on failure. */ +int qmp_client_invoke( + QmpClient *client, + const char *command, + QmpClientArgs *args, + qmp_command_callback_t callback, + void *userdata); + +void qmp_client_bind_event(QmpClient *c, qmp_event_callback_t callback, void *userdata); +void qmp_client_bind_disconnect(QmpClient *c, qmp_disconnect_callback_t callback, void *userdata); +int qmp_client_set_description(QmpClient *c, const char *description); +sd_event* qmp_client_get_event(QmpClient *c); +unsigned qmp_client_next_fdset_id(QmpClient *client); + +QmpClient* qmp_client_unref(QmpClient *p); + +DEFINE_TRIVIAL_CLEANUP_FUNC(QmpClient *, qmp_client_unref); + +/* Returns true iff any object entry in schema (result of query-qmp-schema) has a member with this + * name. QEMU's introspection replaces type names with opaque numeric ids, so lookup-by-type-name is + * impossible — but member names are real. Use only when the member name is unique in the schema. */ +bool qmp_schema_has_member(sd_json_variant *schema, const char *member_name); diff --git a/src/shared/shared-forward.h b/src/shared/shared-forward.h index e07b25c056aac..38349d9dbbcc3 100644 --- a/src/shared/shared-forward.h +++ b/src/shared/shared-forward.h @@ -78,6 +78,7 @@ typedef struct MountOptions MountOptions; typedef struct MStack MStack; typedef struct OpenFile OpenFile; typedef struct Pkcs11EncryptedKey Pkcs11EncryptedKey; +typedef struct QmpClient QmpClient; typedef struct Table Table; typedef struct Tpm2Context Tpm2Context; typedef struct Tpm2Handle Tpm2Handle; From 1a7b6fd9dd44d53dd64d895b33ee070e7c9af0b8 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 30 Mar 2026 14:08:40 +0200 Subject: [PATCH 0997/1296] vmspawn: set up varlink bridge infrastructure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create the QMP-to-varlink bridge layer (vmspawn-qmp.{c,h}) and the varlink server layer (vmspawn-varlink.{c,h}). The QMP bridge (VmspawnQmpBridge) owns the QmpClient connection and manages pending background jobs (e.g. blockdev-create continuations). vmspawn_qmp_init() creates the client and attaches it to the event loop. vmspawn_qmp_probe_features() drives io_uring and qcow2 discard-no-unref probes synchronously via a qmp_client_process() + qmp_client_wait() loop — the QMP handshake completes transparently on the first invoke. vmspawn_qmp_start() resumes vCPUs via an async "cont" command. The varlink server (VmspawnVarlinkContext) exposes three interfaces: - io.systemd.MachineInstance: generic machine control (Terminate, PowerOff, Reboot, Pause, Resume, Describe, SubscribeEvents). Method handlers forward to QMP commands asynchronously — the varlink reply is deferred until the QMP response arrives. - io.systemd.VirtualMachineInstance: VM-specific (placeholder for future snapshot/migration methods). - io.systemd.QemuMachineInstance: QEMU-specific (AcquireQMP stub). The server listens on /control with mode 0600. Event streaming follows the importd Pull pattern: SubscribeEvents sends an initial {ready:true} notification, then fans out QMP events to all subscribers. The disconnect handler only unrefs subscriber links (matching resolved's vl_on_notification_disconnect pattern). Introduce the MachineConfig aggregate in vmspawn-qmp.h grouping the per-device info structures (DriveInfos, NetworkInfo, VirtiofsInfos, VsockInfo) together with machine_config_done() that chains the individual done helpers. Callers populate it field-by-field and rely on the _cleanup_ attribute for orderly teardown regardless of which device types the invocation ends up using. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/meson.build | 2 + src/vmspawn/vmspawn-qmp.c | 1059 +++++++++++++++++++++++++++++++++ src/vmspawn/vmspawn-qmp.h | 145 +++++ src/vmspawn/vmspawn-varlink.c | 410 +++++++++++++ src/vmspawn/vmspawn-varlink.h | 19 + 5 files changed, 1635 insertions(+) create mode 100644 src/vmspawn/vmspawn-qmp.c create mode 100644 src/vmspawn/vmspawn-qmp.h create mode 100644 src/vmspawn/vmspawn-varlink.c create mode 100644 src/vmspawn/vmspawn-varlink.h diff --git a/src/vmspawn/meson.build b/src/vmspawn/meson.build index 99bad2d618973..6d08755fedf8b 100644 --- a/src/vmspawn/meson.build +++ b/src/vmspawn/meson.build @@ -7,6 +7,8 @@ endif vmspawn_sources = files( 'vmspawn.c', 'vmspawn-qemu-config.c', + 'vmspawn-qmp.c', + 'vmspawn-varlink.c', 'vmspawn-settings.c', 'vmspawn-scope.c', 'vmspawn-mount.c', diff --git a/src/vmspawn/vmspawn-qmp.c b/src/vmspawn/vmspawn-qmp.c new file mode 100644 index 0000000000000..4171772ee7c84 --- /dev/null +++ b/src/vmspawn/vmspawn-qmp.c @@ -0,0 +1,1059 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "sd-event.h" +#include "sd-json.h" + +#include "alloc-util.h" +#include "blockdev-util.h" +#include "ether-addr-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "json-util.h" +#include "log.h" +#include "qmp-client.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "vmspawn-qmp.h" + +DEFINE_PRIVATE_HASH_OPS_FULL( + pending_job_hash_ops, + char, string_hash_func, string_compare_func, free, + PendingJob, pending_job_free); + +void drive_info_done(DriveInfo *info) { + assert(info); + info->serial = mfree(info->serial); + info->node_name = mfree(info->node_name); + info->pcie_port = mfree(info->pcie_port); + info->fd = safe_close(info->fd); + info->overlay_fd = safe_close(info->overlay_fd); +} + +void drive_infos_done(DriveInfos *infos) { + assert(infos); + FOREACH_ARRAY(d, infos->drives, infos->n_drives) + drive_info_done(d); + infos->drives = mfree(infos->drives); + infos->n_drives = 0; + infos->scsi_pcie_port = mfree(infos->scsi_pcie_port); +} + +void network_info_done(NetworkInfo *info) { + assert(info); + info->ifname = mfree(info->ifname); + info->pcie_port = mfree(info->pcie_port); + info->fd = safe_close(info->fd); +} + +void virtiofs_info_done(VirtiofsInfo *info) { + assert(info); + info->id = mfree(info->id); + info->socket_path = mfree(info->socket_path); + info->tag = mfree(info->tag); + info->pcie_port = mfree(info->pcie_port); +} + +void virtiofs_infos_done(VirtiofsInfos *infos) { + assert(infos); + FOREACH_ARRAY(e, infos->entries, infos->n_entries) + virtiofs_info_done(e); + infos->entries = mfree(infos->entries); + infos->n_entries = 0; +} + +void vsock_info_done(VsockInfo *info) { + assert(info); + info->pcie_port = mfree(info->pcie_port); + info->fd = safe_close(info->fd); +} + +void machine_config_done(MachineConfig *c) { + if (!c) + return; + + drive_infos_done(&c->drives); + network_info_done(&c->network); + virtiofs_infos_done(&c->virtiofs); + vsock_info_done(&c->vsock); +} + +/* Generic async QMP setup-completion callback. The userdata argument carries the + * command name (as a string literal) for logging. On failure, request a clean + * event loop exit so vmspawn shuts down instead of running a VM with missing devices. */ +static int on_qmp_setup_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + const char *label = ASSERT_PTR(userdata); + + assert(client); + + if (error < 0) { + log_error_errno(error, "%s failed: %s", label, strna(error_desc)); + return sd_event_exit(qmp_client_get_event(client), error); + } + + return 0; +} + +/* Send add-fd via SCM_RIGHTS; return /dev/fdset/N. Allocations run before invoke so a late + * OOM cannot orphan an fdset on QEMU's side; *ret_path is only written on full success. */ +static int qmp_fdset_add(QmpClient *qmp, int fd_consume, char **ret_path) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + _cleanup_close_ int fd = fd_consume; + _cleanup_free_ char *path = NULL; + unsigned id; + int r; + + assert(qmp); + assert(fd_consume >= 0); + assert(ret_path); + + id = qmp_client_next_fdset_id(qmp); + + r = sd_json_buildo(&args, SD_JSON_BUILD_PAIR_UNSIGNED("fdset-id", id)); + if (r < 0) + return r; + + if (asprintf(&path, "/dev/fdset/%u", id) < 0) + return -ENOMEM; + + r = qmp_client_invoke(qmp, "add-fd", QMP_CLIENT_ARGS_FD(args, TAKE_FD(fd)), + on_qmp_setup_complete, (void*) "add-fd"); + if (r < 0) + return r; + + *ret_path = TAKE_PTR(path); + return 0; +} + +typedef struct QmpFileNodeParams { + const char *node_name; + const char *filename; + const char *driver; /* "file" or "host_device" */ + QmpDriveFlags flags; +} QmpFileNodeParams; + +/* Build blockdev-add JSON for the protocol-level (file) node */ +static int qmp_build_blockdev_add_file(const QmpFileNodeParams *p, sd_json_variant **ret) { + assert(p); + assert(p->node_name); + assert(p->filename); + assert(p->driver); + assert(ret); + + /* cache.direct=false uses the page cache (QEMU default). cache.no-flush suppresses host + * flush on guest fsync — only safe for ephemeral/extra drives where data loss is acceptable. */ + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_STRING("node-name", p->node_name), + SD_JSON_BUILD_PAIR_STRING("driver", p->driver), + SD_JSON_BUILD_PAIR_STRING("filename", p->filename), + SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(p->flags, QMP_DRIVE_READ_ONLY), "read-only", SD_JSON_BUILD_BOOLEAN(true)), + SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(p->flags, QMP_DRIVE_IO_URING), "aio", JSON_BUILD_CONST_STRING("io_uring")), + SD_JSON_BUILD_PAIR("cache", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_BOOLEAN("direct", false), + SD_JSON_BUILD_PAIR_BOOLEAN("no-flush", FLAGS_SET(p->flags, QMP_DRIVE_NO_FLUSH))))); +} + +typedef struct QmpFormatNodeParams { + const char *node_name; + const char *format; /* "raw", "qcow2", etc. */ + const char *file_node_name; /* reference to the underlying file node */ + const char *backing; /* reference to a backing format node (NULL if none) */ + QmpDriveFlags flags; +} QmpFormatNodeParams; + +/* Build blockdev-add JSON for the format-level node */ +static int qmp_build_blockdev_add_format(const QmpFormatNodeParams *p, sd_json_variant **ret) { + assert(p); + assert(p->node_name); + assert(p->format); + assert(p->file_node_name); + assert(ret); + + /* When "file" is a string (not an object), QEMU interprets it as a reference to an + * existing node-name. The "backing" field likewise references a format-level node. */ + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_STRING("node-name", p->node_name), + SD_JSON_BUILD_PAIR_STRING("driver", p->format), + SD_JSON_BUILD_PAIR_STRING("file", p->file_node_name), + SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(p->flags, QMP_DRIVE_READ_ONLY), "read-only", SD_JSON_BUILD_BOOLEAN(true)), + SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(p->flags, QMP_DRIVE_DISCARD), "discard", JSON_BUILD_CONST_STRING("unmap")), + SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(p->flags, QMP_DRIVE_DISCARD_NO_UNREF), "discard-no-unref", SD_JSON_BUILD_BOOLEAN(true)), + SD_JSON_BUILD_PAIR_CONDITION(!!p->backing, "backing", SD_JSON_BUILD_STRING(p->backing))); +} + +/* Build device_add JSON arguments for a drive */ +static int qmp_build_device_add(const DriveInfo *drive, sd_json_variant **ret) { + assert(drive); + assert(ret); + + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_STRING("driver", drive->disk_driver), + SD_JSON_BUILD_PAIR_STRING("drive", drive->node_name), + SD_JSON_BUILD_PAIR_STRING("id", drive->node_name), + SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(drive->flags, QMP_DRIVE_BOOT), "bootindex", SD_JSON_BUILD_INTEGER(1)), + SD_JSON_BUILD_PAIR_CONDITION(!!drive->serial, "serial", SD_JSON_BUILD_STRING(drive->serial)), + SD_JSON_BUILD_PAIR_CONDITION(STR_IN_SET(drive->disk_driver, "scsi-hd", "scsi-cd"), + "bus", JSON_BUILD_CONST_STRING("vmspawn_scsi.0")), + SD_JSON_BUILD_PAIR_CONDITION( + !STR_IN_SET(drive->disk_driver, "scsi-hd", "scsi-cd") && !!drive->pcie_port, + "bus", SD_JSON_BUILD_STRING(drive->pcie_port))); +} + +/* Issue blockdev-add for a file node. */ +static int qmp_add_file_node(QmpClient *qmp, const QmpFileNodeParams *p) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + int r; + + r = qmp_build_blockdev_add_file(p, &args); + if (r < 0) + return r; + + return qmp_client_invoke(qmp, "blockdev-add", QMP_CLIENT_ARGS(args), on_qmp_setup_complete, (void*) "blockdev-add"); +} + +/* Get the virtual size of an image from the fd directly. For raw images the virtual size + * equals the file/device size. For qcow2 the virtual size is a big-endian uint64 at header + * offset 24 (the "size" field in the qcow2 header). */ +static int get_image_virtual_size(int fd, const char *format, bool is_block_device, uint64_t *ret) { + int r; + + assert(fd >= 0); + assert(format); + assert(ret); + + if (streq(format, "raw")) { + if (is_block_device) + return blockdev_get_device_size(fd, ret); + + struct stat st; + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to stat image: %m"); + + r = stat_verify_regular(&st); + if (r < 0) + return log_error_errno(r, "Raw device is neither a regular file nor a block device"); + + *ret = st.st_size; + return 0; + } + + if (streq(format, "qcow2")) { + uint32_t magic = 0; + ssize_t n = pread(fd, &magic, sizeof(magic), 0); + if (n < 0) + return log_error_errno(errno, "Failed to read qcow2 magic: %m"); + if (n != sizeof(magic) || be32toh(magic) != UINT32_C(0x514649fb)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Not a valid qcow2 image (bad magic)"); + + uint64_t size_be = 0; + n = pread(fd, &size_be, sizeof(size_be), 24); + if (n < 0) + return log_error_errno(errno, "Failed to read qcow2 header: %m"); + if (n != sizeof(size_be)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read on qcow2 header"); + + *ret = be64toh(size_be); + return 0; + } + + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported image format '%s'", format); +} + +/* Ephemeral drive continuation — fired when the blockdev-create job concludes. + * Completes the drive setup by adding the overlay format node and the device. */ +typedef struct EphemeralDriveCtx { + char *node_name; /* overlay format node name (= drive node name) */ + char *overlay_file_node; + char *base_fmt_node; + /* Fields for device_add */ + char *disk_driver; + char *serial; /* NULL if unset */ + char *pcie_port; /* pcie-root-port bus for device_add (NULL on non-PCIe) */ + QmpDriveFlags flags; /* subset: QMP_DRIVE_DISCARD, QMP_DRIVE_DISCARD_NO_UNREF, QMP_DRIVE_BOOT */ +} EphemeralDriveCtx; + +static EphemeralDriveCtx* ephemeral_drive_ctx_free(EphemeralDriveCtx *ctx) { + if (!ctx) + return NULL; + free(ctx->node_name); + free(ctx->overlay_file_node); + free(ctx->base_fmt_node); + free(ctx->disk_driver); + free(ctx->serial); + free(ctx->pcie_port); + return mfree(ctx); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(EphemeralDriveCtx *, ephemeral_drive_ctx_free); + +static void ephemeral_drive_ctx_free_void(void *p) { + ephemeral_drive_ctx_free(p); +} + +static int on_ephemeral_create_concluded(QmpClient *qmp, void *userdata) { + _cleanup_(ephemeral_drive_ctx_freep) EphemeralDriveCtx *ctx = ASSERT_PTR(userdata); + _cleanup_(sd_json_variant_unrefp) sd_json_variant *fmt_args = NULL, *device_args = NULL; + int r; + + /* Open formatted overlay as qcow2 with backing reference */ + QmpFormatNodeParams overlay_fmt_params = { + .node_name = ctx->node_name, + .format = "qcow2", + .file_node_name = ctx->overlay_file_node, + .backing = ctx->base_fmt_node, + .flags = ctx->flags & (QMP_DRIVE_DISCARD|QMP_DRIVE_DISCARD_NO_UNREF), + }; + r = qmp_build_blockdev_add_format(&overlay_fmt_params, &fmt_args); + if (r < 0) + return log_error_errno(r, "Failed to build overlay format JSON for '%s': %m", ctx->node_name); + + r = qmp_client_invoke(qmp, "blockdev-add", QMP_CLIENT_ARGS(fmt_args), on_qmp_setup_complete, (void*) "blockdev-add"); + if (r < 0) + return r; + + /* device_add: attach to virtual hardware. Build a temporary DriveInfo as a + * read-only view into the continuation context to reuse qmp_build_device_add(). */ + const DriveInfo tmp = { + .disk_driver = ctx->disk_driver, + .node_name = ctx->node_name, + .serial = ctx->serial, + .pcie_port = ctx->pcie_port, + .flags = ctx->flags & QMP_DRIVE_BOOT, + .fd = -EBADF, + .overlay_fd = -EBADF, + }; + r = qmp_build_device_add(&tmp, &device_args); + if (r < 0) + return log_error_errno(r, "Failed to build device_add JSON for '%s': %m", ctx->node_name); + + r = qmp_client_invoke(qmp, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_setup_complete, (void*) "device_add"); + if (r < 0) + return r; + + log_debug("Queued ephemeral drive completion for '%s'", ctx->node_name); + return 0; +} + +/* Set up an ephemeral drive: base image (read-only) + anonymous qcow2 overlay (read-write). + * The final steps (overlay format + device_add) are deferred to a job continuation that + * fires when the blockdev-create job concludes. */ +static int qmp_setup_ephemeral_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, DriveInfo *drive) { + int r; + + assert(bridge); + assert(qmp); + assert(drive); + assert(drive->fd >= 0); + assert(drive->overlay_fd >= 0); + + /* Node names: -base-file, -base-fmt, -overlay-file, */ + _cleanup_free_ char *base_file_node = strjoin(drive->node_name, "-base-file"); + _cleanup_free_ char *base_fmt_node = strjoin(drive->node_name, "-base-fmt"); + _cleanup_free_ char *overlay_file_node = strjoin(drive->node_name, "-overlay-file"); + if (!base_file_node || !base_fmt_node || !overlay_file_node) + return log_oom(); + + /* Read virtual size before passing the fd to QEMU (TAKE_FD consumes it) */ + uint64_t virtual_size; + r = get_image_virtual_size(drive->fd, drive->format, FLAGS_SET(drive->flags, QMP_DRIVE_BLOCK_DEVICE), &virtual_size); + if (r < 0) + return r; + + /* Step 1-2: Pass both fds to QEMU */ + _cleanup_free_ char *base_path = NULL; + r = qmp_fdset_add(qmp, TAKE_FD(drive->fd), &base_path); + if (r < 0) + return log_error_errno(r, "Failed to send add-fd for base image '%s': %m", drive->path); + + _cleanup_free_ char *overlay_path = NULL; + r = qmp_fdset_add(qmp, TAKE_FD(drive->overlay_fd), &overlay_path); + if (r < 0) + return log_error_errno(r, "Failed to send add-fd for overlay of '%s': %m", drive->path); + + /* Step 3: Base image file node (read-only) */ + QmpFileNodeParams base_file_params = { + .node_name = base_file_node, + .filename = base_path, + .driver = FLAGS_SET(drive->flags, QMP_DRIVE_BLOCK_DEVICE) ? "host_device" : "file", + .flags = QMP_DRIVE_READ_ONLY | (drive->flags & QMP_DRIVE_NO_FLUSH), + }; + if (FLAGS_SET(bridge->features, VMSPAWN_QMP_FEATURE_IO_URING)) + base_file_params.flags |= QMP_DRIVE_IO_URING; + r = qmp_add_file_node(qmp, &base_file_params); + if (r < 0) + return log_error_errno(r, "Failed to send blockdev-add for base file '%s': %m", drive->path); + + /* Step 4: Base image format node (read-only) */ + QmpFormatNodeParams base_fmt_params = { + .node_name = base_fmt_node, + .format = drive->format, + .file_node_name = base_file_node, + .flags = QMP_DRIVE_READ_ONLY, + }; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *base_fmt_args = NULL; + r = qmp_build_blockdev_add_format(&base_fmt_params, &base_fmt_args); + if (r < 0) + return r; + + r = qmp_client_invoke(qmp, "blockdev-add", QMP_CLIENT_ARGS(base_fmt_args), on_qmp_setup_complete, (void*) "blockdev-add"); + if (r < 0) + return log_error_errno(r, "Failed to send blockdev-add for base format '%s': %m", drive->path); + + /* Step 5: Overlay file node (read-write, no io_uring for anon overlay) */ + QmpFileNodeParams overlay_file_params = { + .node_name = overlay_file_node, + .filename = overlay_path, + .driver = "file", + .flags = QMP_DRIVE_NO_FLUSH, + }; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *overlay_file_args = NULL; + r = qmp_build_blockdev_add_file(&overlay_file_params, &overlay_file_args); + if (r < 0) + return r; + + r = qmp_client_invoke(qmp, "blockdev-add", QMP_CLIENT_ARGS(overlay_file_args), on_qmp_setup_complete, (void*) "blockdev-add"); + if (r < 0) + return log_error_errno(r, "Failed to send blockdev-add for overlay file '%s': %m", drive->path); + + /* Step 6: Fire blockdev-create to format the overlay */ + _cleanup_(sd_json_variant_unrefp) sd_json_variant *create_options = NULL; + r = sd_json_buildo(&create_options, + SD_JSON_BUILD_PAIR_STRING("driver", "qcow2"), + SD_JSON_BUILD_PAIR_STRING("file", overlay_file_node), + SD_JSON_BUILD_PAIR_UNSIGNED("size", virtual_size), + SD_JSON_BUILD_PAIR_STRING("backing-file", base_fmt_node), + SD_JSON_BUILD_PAIR_STRING("backing-fmt", drive->format)); + if (r < 0) + return log_error_errno(r, "Failed to build blockdev-create options: %m"); + + _cleanup_free_ char *job_id = strjoin("create-", drive->node_name); + if (!job_id) + return log_oom(); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *cmd_args = NULL; + r = sd_json_buildo(&cmd_args, + SD_JSON_BUILD_PAIR_STRING("job-id", job_id), + SD_JSON_BUILD_PAIR_VARIANT("options", create_options)); + if (r < 0) + return log_error_errno(r, "Failed to build blockdev-create JSON: %m"); + + /* Register continuation: when the job concludes, fire overlay format + device_add */ + _cleanup_(ephemeral_drive_ctx_freep) EphemeralDriveCtx *ectx = new(EphemeralDriveCtx, 1); + if (!ectx) + return log_oom(); + + QmpDriveFlags ectx_flags = drive->flags & (QMP_DRIVE_DISCARD|QMP_DRIVE_BOOT); + if (FLAGS_SET(drive->flags, QMP_DRIVE_DISCARD) && + FLAGS_SET(bridge->features, VMSPAWN_QMP_FEATURE_DISCARD_NO_UNREF)) + ectx_flags |= QMP_DRIVE_DISCARD_NO_UNREF; + + *ectx = (EphemeralDriveCtx) { + .node_name = strdup(drive->node_name), + .overlay_file_node = strdup(overlay_file_node), + .base_fmt_node = strdup(base_fmt_node), + .disk_driver = strdup(drive->disk_driver), + .serial = drive->serial ? strdup(drive->serial) : NULL, + .pcie_port = drive->pcie_port ? strdup(drive->pcie_port) : NULL, + .flags = ectx_flags, + }; + if (!ectx->node_name || !ectx->overlay_file_node || !ectx->base_fmt_node || + !ectx->disk_driver || (drive->serial && !ectx->serial) || + (drive->pcie_port && !ectx->pcie_port)) + return log_oom(); + + r = vmspawn_qmp_bridge_register_job(bridge, job_id, + on_ephemeral_create_concluded, ectx, + ephemeral_drive_ctx_free_void); + if (r < 0) + return log_error_errno(r, "Failed to register job continuation: %m"); + + TAKE_PTR(ectx); + + r = qmp_client_invoke(qmp, "blockdev-create", QMP_CLIENT_ARGS(cmd_args), on_qmp_setup_complete, (void*) "blockdev-create"); + if (r < 0) + return log_error_errno(r, "Failed to send blockdev-create for '%s': %m", drive->path); + + log_debug("Queued ephemeral drive setup for '%s' (job %s)", drive->path, job_id); + return 0; +} + +/* Set up a regular (non-ephemeral) drive: single file node + format node + device_add. */ +static int qmp_setup_regular_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, DriveInfo *drive) { + int r; + + assert(bridge); + assert(qmp); + assert(drive); + assert(drive->fd >= 0); + + /* Node names: -file, */ + _cleanup_free_ char *file_node_name = strjoin(drive->node_name, "-file"); + if (!file_node_name) + return log_oom(); + + _cleanup_free_ char *fdset_path = NULL; + r = qmp_fdset_add(qmp, TAKE_FD(drive->fd), &fdset_path); + if (r < 0) + return log_error_errno(r, "Failed to send add-fd for '%s': %m", drive->path); + + QmpFileNodeParams file_params = { + .node_name = file_node_name, + .filename = fdset_path, + .driver = FLAGS_SET(drive->flags, QMP_DRIVE_BLOCK_DEVICE) ? "host_device" : "file", + .flags = drive->flags & (QMP_DRIVE_READ_ONLY|QMP_DRIVE_NO_FLUSH), + }; + if (FLAGS_SET(bridge->features, VMSPAWN_QMP_FEATURE_IO_URING)) + file_params.flags |= QMP_DRIVE_IO_URING; + r = qmp_add_file_node(qmp, &file_params); + if (r < 0) + return log_error_errno(r, "Failed to send blockdev-add for '%s': %m", drive->path); + + QmpFormatNodeParams fmt_params = { + .node_name = drive->node_name, + .format = drive->format, + .file_node_name = file_node_name, + .flags = drive->flags & (QMP_DRIVE_READ_ONLY|QMP_DRIVE_DISCARD), + }; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *fmt_args = NULL; + r = qmp_build_blockdev_add_format(&fmt_params, &fmt_args); + if (r < 0) + return r; + + r = qmp_client_invoke(qmp, "blockdev-add", QMP_CLIENT_ARGS(fmt_args), on_qmp_setup_complete, (void*) "blockdev-add"); + if (r < 0) + return log_error_errno(r, "Failed to send blockdev-add format for '%s': %m", drive->path); + + /* device_add: attach to virtual hardware */ + _cleanup_(sd_json_variant_unrefp) sd_json_variant *device_args = NULL; + r = qmp_build_device_add(drive, &device_args); + if (r < 0) + return r; + + r = qmp_client_invoke(qmp, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_setup_complete, (void*) "device_add"); + if (r < 0) + return log_error_errno(r, "Failed to send device_add for '%s': %m", drive->path); + + log_debug("Queued drive setup for '%s'", drive->path); + return 0; +} + +/* Configure a single drive via QMP. Dispatches to ephemeral or regular setup. */ +static int qmp_setup_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, DriveInfo *drive) { + assert(drive); + + if (drive->overlay_fd >= 0) + return qmp_setup_ephemeral_drive(bridge, qmp, drive); + + return qmp_setup_regular_drive(bridge, qmp, drive); +} + +int vmspawn_qmp_setup_network(VmspawnQmpBridge *bridge, NetworkInfo *network) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *netdev_args = NULL, *device_args = NULL; + bool tap_by_fd; + int r; + + assert(bridge); + + QmpClient *qmp = vmspawn_qmp_bridge_get_qmp(bridge); + assert(network); + assert(network->type); + + tap_by_fd = streq(network->type, "tap") && network->fd >= 0; + + /* For TAP-by-fd: pass the TAP fd to QEMU via getfd + SCM_RIGHTS, then reference it by name + * in netdev_add. QEMU stores the received fd under the given fdname and closes it on removal. */ + if (tap_by_fd) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *getfd_args = NULL; + + r = sd_json_buildo( + &getfd_args, + SD_JSON_BUILD_PAIR_STRING("fdname", "vmspawn_tap")); + if (r < 0) + return log_error_errno(r, "Failed to build getfd JSON: %m"); + + r = qmp_client_invoke(qmp, "getfd", QMP_CLIENT_ARGS_FD(getfd_args, TAKE_FD(network->fd)), + on_qmp_setup_complete, (void*) "getfd"); + if (r < 0) + return log_error_errno(r, "Failed to send getfd for TAP fd: %m"); + } + + /* netdev_add: create the network backend */ + r = sd_json_buildo( + &netdev_args, + SD_JSON_BUILD_PAIR_STRING("type", network->type), + SD_JSON_BUILD_PAIR_STRING("id", "net0"), + SD_JSON_BUILD_PAIR_CONDITION(tap_by_fd, + "fd", JSON_BUILD_CONST_STRING("vmspawn_tap")), + SD_JSON_BUILD_PAIR_CONDITION(!tap_by_fd && !!network->ifname, + "ifname", SD_JSON_BUILD_STRING(network->ifname)), + SD_JSON_BUILD_PAIR_CONDITION(!tap_by_fd && streq(network->type, "tap"), + "script", JSON_BUILD_CONST_STRING("no")), + SD_JSON_BUILD_PAIR_CONDITION(!tap_by_fd && streq(network->type, "tap"), + "downscript", JSON_BUILD_CONST_STRING("no"))); + if (r < 0) + return log_error_errno(r, "Failed to build netdev_add JSON: %m"); + + r = qmp_client_invoke(qmp, "netdev_add", QMP_CLIENT_ARGS(netdev_args), on_qmp_setup_complete, (void*) "netdev_add"); + if (r < 0) + return log_error_errno(r, "Failed to send netdev_add: %m"); + + /* device_add: attach NIC frontend */ + r = sd_json_buildo( + &device_args, + SD_JSON_BUILD_PAIR_STRING("driver", "virtio-net-pci"), + SD_JSON_BUILD_PAIR_STRING("netdev", "net0"), + SD_JSON_BUILD_PAIR_STRING("id", "nic0"), + SD_JSON_BUILD_PAIR_CONDITION(network->mac_set, + "mac", SD_JSON_BUILD_STRING(network->mac_set ? ETHER_ADDR_TO_STR(&network->mac) : NULL)), + SD_JSON_BUILD_PAIR_CONDITION(!!network->pcie_port, + "bus", SD_JSON_BUILD_STRING(network->pcie_port))); + if (r < 0) + return log_error_errno(r, "Failed to build NIC device_add JSON: %m"); + + r = qmp_client_invoke(qmp, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_setup_complete, (void*) "device_add"); + if (r < 0) + return log_error_errno(r, "Failed to send NIC device_add: %m"); + + log_debug("Queued %s network setup%s", network->type, tap_by_fd ? " (fd via getfd)" : ""); + return 0; +} + +static int vmspawn_qmp_setup_one_virtiofs(QmpClient *qmp, const VirtiofsInfo *vfs) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *chardev_args = NULL, *device_args = NULL; + int r; + + assert(qmp); + assert(vfs); + assert(vfs->id); + assert(vfs->socket_path); + assert(vfs->tag); + + /* chardev-add: connect to virtiofsd socket. + * ChardevBackend and SocketAddressLegacy are QAPI legacy unions with explicit "data" + * wrapper objects at each level — the nesting is mandatory on the wire. */ + r = sd_json_buildo( + &chardev_args, + SD_JSON_BUILD_PAIR_STRING("id", vfs->id), + SD_JSON_BUILD_PAIR("backend", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("type", "socket"), + SD_JSON_BUILD_PAIR("data", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("addr", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("type", "unix"), + SD_JSON_BUILD_PAIR("data", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("path", vfs->socket_path))))), + SD_JSON_BUILD_PAIR_BOOLEAN("server", false)))))); + if (r < 0) + return log_error_errno(r, "Failed to build chardev-add JSON for '%s': %m", vfs->id); + + r = qmp_client_invoke(qmp, "chardev-add", QMP_CLIENT_ARGS(chardev_args), on_qmp_setup_complete, (void*) "chardev-add"); + if (r < 0) + return log_error_errno(r, "Failed to send chardev-add '%s': %m", vfs->id); + + /* device_add: create vhost-user-fs-pci device */ + r = sd_json_buildo( + &device_args, + SD_JSON_BUILD_PAIR_STRING("driver", "vhost-user-fs-pci"), + SD_JSON_BUILD_PAIR_STRING("id", vfs->id), + SD_JSON_BUILD_PAIR_STRING("chardev", vfs->id), + SD_JSON_BUILD_PAIR_STRING("tag", vfs->tag), + SD_JSON_BUILD_PAIR_UNSIGNED("queue-size", 1024), + SD_JSON_BUILD_PAIR_CONDITION(!!vfs->pcie_port, + "bus", SD_JSON_BUILD_STRING(vfs->pcie_port))); + if (r < 0) + return log_error_errno(r, "Failed to build virtiofs device_add JSON for '%s': %m", vfs->id); + + r = qmp_client_invoke(qmp, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_setup_complete, (void*) "device_add"); + if (r < 0) + return log_error_errno(r, "Failed to send virtiofs device_add '%s': %m", vfs->id); + + log_debug("Queued virtiofs device '%s' (tag=%s)", vfs->id, vfs->tag); + return 0; +} + +int vmspawn_qmp_setup_virtiofs(VmspawnQmpBridge *bridge, const VirtiofsInfos *virtiofs) { + int r; + + assert(bridge); + + QmpClient *qmp = vmspawn_qmp_bridge_get_qmp(bridge); + assert(virtiofs); + + FOREACH_ARRAY(e, virtiofs->entries, virtiofs->n_entries) { + r = vmspawn_qmp_setup_one_virtiofs(qmp, e); + if (r < 0) + return r; + } + + return 0; +} + +int vmspawn_qmp_setup_vsock(VmspawnQmpBridge *bridge, VsockInfo *vsock) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *getfd_args = NULL, *device_args = NULL; + int r; + + assert(bridge); + assert(vsock); + + if (vsock->fd < 0) + return 0; + + QmpClient *qmp = vmspawn_qmp_bridge_get_qmp(bridge); + + /* getfd: pass the vhost-vsock fd to QEMU via SCM_RIGHTS */ + r = sd_json_buildo( + &getfd_args, + SD_JSON_BUILD_PAIR_STRING("fdname", "vmspawn_vsock")); + if (r < 0) + return log_error_errno(r, "Failed to build getfd JSON for VSOCK: %m"); + + r = qmp_client_invoke(qmp, "getfd", QMP_CLIENT_ARGS_FD(getfd_args, TAKE_FD(vsock->fd)), + on_qmp_setup_complete, (void*) "getfd"); + if (r < 0) + return log_error_errno(r, "Failed to send getfd for VSOCK fd: %m"); + + /* device_add: create vhost-vsock-pci device referencing the named fd */ + r = sd_json_buildo( + &device_args, + SD_JSON_BUILD_PAIR_STRING("driver", "vhost-vsock-pci"), + SD_JSON_BUILD_PAIR_STRING("id", "vsock0"), + SD_JSON_BUILD_PAIR_UNSIGNED("guest-cid", vsock->cid), + SD_JSON_BUILD_PAIR_STRING("vhostfd", "vmspawn_vsock"), + SD_JSON_BUILD_PAIR_CONDITION(!!vsock->pcie_port, + "bus", SD_JSON_BUILD_STRING(vsock->pcie_port))); + if (r < 0) + return log_error_errno(r, "Failed to build VSOCK device_add JSON: %m"); + + r = qmp_client_invoke(qmp, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_setup_complete, (void*) "device_add"); + if (r < 0) + return log_error_errno(r, "Failed to send VSOCK device_add: %m"); + + log_debug("Queued vhost-vsock-pci device setup (cid=%u)", vsock->cid); + return 0; +} + +static bool drives_need_scsi_controller(DriveInfos *drives) { + assert(drives); + + FOREACH_ARRAY(d, drives->drives, drives->n_drives) + if (STR_IN_SET(d->disk_driver, "scsi-hd", "scsi-cd")) + return true; + + return false; +} + +static int qmp_setup_scsi_controller(QmpClient *qmp, const char *pcie_port) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + int r; + + r = sd_json_buildo( + &args, + SD_JSON_BUILD_PAIR_STRING("driver", "virtio-scsi-pci"), + SD_JSON_BUILD_PAIR_STRING("id", "vmspawn_scsi"), + SD_JSON_BUILD_PAIR_CONDITION(!!pcie_port, "bus", SD_JSON_BUILD_STRING(pcie_port))); + if (r < 0) + return log_error_errno(r, "Failed to build SCSI controller JSON: %m"); + + r = qmp_client_invoke(qmp, "device_add", QMP_CLIENT_ARGS(args), on_qmp_setup_complete, (void*) "device_add"); + if (r < 0) + return log_error_errno(r, "Failed to send SCSI controller device_add: %m"); + + log_debug("Queued virtio-scsi-pci controller setup"); + return 0; +} + +int vmspawn_qmp_setup_drives(VmspawnQmpBridge *bridge, DriveInfos *drives) { + int r; + + assert(bridge); + assert(drives); + + QmpClient *qmp = vmspawn_qmp_bridge_get_qmp(bridge); + + /* io_uring support was probed during vmspawn_qmp_init(). The cached result in + * bridge->features is passed to each file node setup call. */ + + if (drives_need_scsi_controller(drives)) { + r = qmp_setup_scsi_controller(qmp, drives->scsi_pcie_port); + if (r < 0) + return r; + } + + FOREACH_ARRAY(d, drives->drives, drives->n_drives) { + r = qmp_setup_drive(bridge, qmp, d); + if (r < 0) + return r; + } + + return 0; +} + +PendingJob* pending_job_free(PendingJob *j) { + if (!j) + return NULL; + if (j->free_userdata) + j->free_userdata(j->userdata); + return mfree(j); +} + +VmspawnQmpBridge* vmspawn_qmp_bridge_free(VmspawnQmpBridge *b) { + if (!b) + return NULL; + + hashmap_free(b->pending_jobs); + + qmp_client_unref(b->qmp); + return mfree(b); +} + +int vmspawn_qmp_bridge_register_job( + VmspawnQmpBridge *b, + const char *job_id, + pending_job_callback_t on_concluded, + void *userdata, + pending_job_free_t free_userdata) { + + _cleanup_free_ PendingJob *job = NULL; + _cleanup_free_ char *id = NULL; + int r; + + assert(b); + assert(job_id); + + id = strdup(job_id); + if (!id) + return -ENOMEM; + + job = new(PendingJob, 1); + if (!job) + return -ENOMEM; + + *job = (PendingJob) { + .on_concluded = on_concluded, + .free_userdata = free_userdata, + .userdata = userdata, + }; + + r = hashmap_ensure_put(&b->pending_jobs, &pending_job_hash_ops, id, job); + if (r < 0) + return r; + + TAKE_PTR(id); + TAKE_PTR(job); + return 0; +} + +QmpClient* vmspawn_qmp_bridge_get_qmp(VmspawnQmpBridge *b) { + assert(b); + return b->qmp; +} + +/* Probe-reply convention: ignore -EIO (QMP rejection = "feature absent", log at debug + * and leave the feature flag clear) and transport errors (caught by the post-loop + * qmp_client_is_disconnected() check in vmspawn_qmp_probe_features()). Cleanup calls + * are best-effort — failing to delete a private probe node leaves a harmless /dev/null + * blockdev in QEMU until it exits. */ + +static int on_io_uring_probe_del_reply( + QmpClient *c, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + assert(c); + + if (error_desc) + log_debug("Failed to remove io_uring probe node: %s", error_desc); + return 0; +} + +static int on_io_uring_probe_add_reply( + QmpClient *c, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + VmspawnQmpBridge *bridge = ASSERT_PTR(userdata); + _cleanup_(sd_json_variant_unrefp) sd_json_variant *del_args = NULL; + int r; + + assert(c); + + if (error < 0 && !error_desc) + return log_debug_errno(error, "io_uring probe did not execute: %m"); + if (error_desc) { + log_debug("QEMU does not support aio=io_uring: %s", error_desc); + return 0; + } + + bridge->features |= VMSPAWN_QMP_FEATURE_IO_URING; + log_debug("QEMU supports aio=io_uring"); + + /* Best-effort cleanup; the chained reply keeps the pump busy via the slots set. */ + r = sd_json_buildo(&del_args, + SD_JSON_BUILD_PAIR_STRING("node-name", "__io_uring_probe")); + if (r < 0) + return r; + + return qmp_client_invoke(c, "blockdev-del", QMP_CLIENT_ARGS(del_args), + on_io_uring_probe_del_reply, bridge); +} + +static int probe_io_uring(QmpClient *c, VmspawnQmpBridge *bridge) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + int r; + + assert(c); + assert(bridge); + + r = sd_json_buildo( + &args, + SD_JSON_BUILD_PAIR_STRING("node-name", "__io_uring_probe"), + SD_JSON_BUILD_PAIR_STRING("driver", "file"), + SD_JSON_BUILD_PAIR_STRING("filename", "/dev/null"), + SD_JSON_BUILD_PAIR_BOOLEAN("read-only", true), + SD_JSON_BUILD_PAIR_STRING("aio", "io_uring")); + if (r < 0) + return r; + + return qmp_client_invoke(c, "blockdev-add", QMP_CLIENT_ARGS(args), + on_io_uring_probe_add_reply, bridge); +} + +static int on_probe_schema_reply( + QmpClient *c, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + VmspawnQmpBridge *bridge = ASSERT_PTR(userdata); + + assert(c); + + if (error < 0 && !error_desc) + return log_debug_errno(error, "query-qmp-schema probe did not execute: %m"); + if (error_desc) { + log_debug("query-qmp-schema rejected: %s", error_desc); + return 0; + } + + if (qmp_schema_has_member(result, "discard-no-unref")) { + bridge->features |= VMSPAWN_QMP_FEATURE_DISCARD_NO_UNREF; + log_debug("QEMU supports qcow2 discard-no-unref"); + } else + log_debug("QEMU does not support qcow2 discard-no-unref"); + + return 0; +} + +static int probe_schema(QmpClient *c, VmspawnQmpBridge *bridge) { + assert(c); + assert(bridge); + + return qmp_client_invoke(c, "query-qmp-schema", QMP_CLIENT_ARGS(NULL), + on_probe_schema_reply, bridge); +} + +int vmspawn_qmp_init(VmspawnQmpBridge **ret, int fd, sd_event *event) { + _cleanup_(vmspawn_qmp_bridge_freep) VmspawnQmpBridge *bridge = NULL; + int r; + + assert(ret); + assert(fd >= 0); + assert(event); + + bridge = new0(VmspawnQmpBridge, 1); + if (!bridge) + return log_oom(); + + r = qmp_client_connect_fd(&bridge->qmp, fd); + if (r < 0) + return log_error_errno(r, "Failed to create QMP client: %m"); + + r = qmp_client_set_description(bridge->qmp, "vmspawn-qmp-client"); + if (r < 0) + return log_error_errno(r, "Failed to set QMP client description: %m"); + + r = qmp_client_attach_event(bridge->qmp, event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach QMP client to event loop: %m"); + + *ret = TAKE_PTR(bridge); + return 0; +} + +int vmspawn_qmp_probe_features(VmspawnQmpBridge *bridge) { + int r; + + assert(bridge); + + /* probe_io_uring() and probe_schema() both call qmp_client_invoke(), which internally + * drives the handshake to RUNNING via qmp_client_ensure_running() on its first call. */ + r = probe_io_uring(bridge->qmp, bridge); + if (r < 0) + return log_error_errno(r, "Failed to issue io_uring probe: %m"); + + r = probe_schema(bridge->qmp, bridge); + if (r < 0) + return log_error_errno(r, "Failed to issue schema probe: %m"); + + /* Canonical sync-on-async pump, matching varlink_call_internal(). The QMP client tracks + * outstanding replies in its own slots set; drain until it's idle. */ + while (!qmp_client_is_idle(bridge->qmp)) { + r = qmp_client_process(bridge->qmp); + if (r < 0) + return log_error_errno(r, "QMP probe pump failed: %m"); + if (r > 0) + continue; + + r = qmp_client_wait(bridge->qmp, USEC_INFINITY); + if (r < 0) + return log_error_errno(r, "QMP probe wait failed: %m"); + } + + /* If fail_pending() drained the slots (transport dropped mid-probe), features can't be + * trusted and we have no QMP channel for device setup anyway. */ + if (qmp_client_is_disconnected(bridge->qmp)) + return log_error_errno(SYNTHETIC_ERRNO(ECONNRESET), + "QMP connection dropped during feature probing"); + + return 0; +} + +static int on_cont_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + assert(client); + + if (error < 0) { + log_error_errno(error, "Failed to resume QEMU execution: %s", strna(error_desc)); + return sd_event_exit(qmp_client_get_event(client), error); + } + + return 0; +} + +int vmspawn_qmp_start(VmspawnQmpBridge *bridge) { + assert(bridge); + + return qmp_client_invoke(bridge->qmp, "cont", /* args= */ NULL, on_cont_complete, /* userdata= */ NULL); +} diff --git a/src/vmspawn/vmspawn-qmp.h b/src/vmspawn/vmspawn-qmp.h new file mode 100644 index 0000000000000..8f8c26fb03e7d --- /dev/null +++ b/src/vmspawn/vmspawn-qmp.h @@ -0,0 +1,145 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "shared-forward.h" + +/* Pending job continuation — called when a QMP background job reaches "concluded" state. + * Used by blockdev-create to chain remaining drive setup after the job completes. */ +typedef int (*pending_job_callback_t)(QmpClient *qmp, void *userdata); +typedef void (*pending_job_free_t)(void *userdata); + +typedef struct PendingJob { + pending_job_callback_t on_concluded; + pending_job_free_t free_userdata; + void *userdata; +} PendingJob; + +PendingJob* pending_job_free(PendingJob *j); +DEFINE_TRIVIAL_CLEANUP_FUNC(PendingJob *, pending_job_free); + +typedef enum VmspawnQmpFeatureFlags { + VMSPAWN_QMP_FEATURE_IO_URING = 1u << 0, + VMSPAWN_QMP_FEATURE_DISCARD_NO_UNREF = 1u << 1, +} VmspawnQmpFeatureFlags; + +typedef struct VmspawnQmpBridge { + QmpClient *qmp; + Hashmap *pending_jobs; /* job_id (string, owned) -> PendingJob* */ + VmspawnQmpFeatureFlags features; +} VmspawnQmpBridge; + +VmspawnQmpBridge* vmspawn_qmp_bridge_free(VmspawnQmpBridge *b); +DEFINE_TRIVIAL_CLEANUP_FUNC(VmspawnQmpBridge *, vmspawn_qmp_bridge_free); + +QmpClient* vmspawn_qmp_bridge_get_qmp(VmspawnQmpBridge *b); + +/* Phase 1: Connect to VMM backend. Returns an opaque bridge ready for device setup. */ +int vmspawn_qmp_init(VmspawnQmpBridge **ret, int fd, sd_event *event); + +/* Phase 1b: Feature probing. Fires one-shot QMP commands and drives the client + * synchronously until every reply has been delivered. Populates bridge->features. + * Must run before the device-setup phase; both io_uring and discard-no-unref flags + * are consumed by vmspawn_qmp_setup_drives(). */ +int vmspawn_qmp_probe_features(VmspawnQmpBridge *bridge); + +/* Phase 3: Resume vCPUs. All commands are async — responses arrive during sd_event_loop(). */ +int vmspawn_qmp_start(VmspawnQmpBridge *bridge); + +int vmspawn_qmp_bridge_register_job( + VmspawnQmpBridge *b, + const char *job_id, + pending_job_callback_t on_concluded, + void *userdata, + pending_job_free_t free_userdata); + +typedef enum QmpDriveFlags { + QMP_DRIVE_BLOCK_DEVICE = 1u << 0, + QMP_DRIVE_READ_ONLY = 1u << 1, + QMP_DRIVE_DISCARD = 1u << 2, + QMP_DRIVE_NO_FLUSH = 1u << 3, + QMP_DRIVE_BOOT = 1u << 4, + QMP_DRIVE_IO_URING = 1u << 5, + QMP_DRIVE_DISCARD_NO_UNREF = 1u << 6, /* qcow2 only */ +} QmpDriveFlags; + +/* Drive info for QMP-based drive setup */ +typedef struct DriveInfo { + const char *path; /* kept for logging only — not passed to QEMU */ + const char *format; /* "raw" or "qcow2" */ + const char *disk_driver; /* "virtio-blk-pci", "scsi-hd", "scsi-cd", "nvme" */ + char *serial; /* owned */ + char *node_name; /* owned */ + char *pcie_port; /* owned: pcie-root-port id for device_add bus (NULL on non-PCIe) */ + int fd; /* pre-opened image fd (owned, -EBADF if unused) */ + int overlay_fd; /* pre-opened anonymous overlay fd for ephemeral (owned, -EBADF if unused) */ + QmpDriveFlags flags; +} DriveInfo; + +void drive_info_done(DriveInfo *info); + +typedef struct DriveInfos { + DriveInfo *drives; + size_t n_drives; + char *scsi_pcie_port; /* owned: pcie-root-port id for SCSI controller (NULL if no SCSI or non-PCIe) */ +} DriveInfos; + +void drive_infos_done(DriveInfos *infos); + +/* Network info for QMP-based network setup. Covers privileged TAP (by name), + * nsresourced TAP (by FD via getfd), and user-mode networking. The no-network + * case (-nic none) stays on the QEMU command line. */ +typedef struct NetworkInfo { + const char *type; /* "tap" or "user" — points to a string literal */ + char *ifname; /* owned: TAP interface name (tap by name only, NULL if unset) */ + struct ether_addr mac; /* VM-side MAC address (tap only, valid iff mac_set) */ + bool mac_set; + char *pcie_port; /* owned: pcie-root-port id for device_add bus (NULL on non-PCIe) */ + int fd; /* TAP fd to pass via getfd (tap by fd only, -EBADF if unused) */ +} NetworkInfo; + +void network_info_done(NetworkInfo *info); + +/* Virtiofs device info for QMP-based chardev + device setup */ +typedef struct VirtiofsInfo { + char *id; /* owned: chardev and device id (e.g. "rootdir", "mnt0") */ + char *socket_path; /* owned: virtiofsd listen socket path */ + char *tag; /* owned: virtiofs mount tag visible to guest */ + char *pcie_port; /* owned: pcie-root-port id for device_add bus (NULL on non-PCIe) */ +} VirtiofsInfo; + +void virtiofs_info_done(VirtiofsInfo *info); + +typedef struct VirtiofsInfos { + VirtiofsInfo *entries; + size_t n_entries; +} VirtiofsInfos; + +void virtiofs_infos_done(VirtiofsInfos *infos); + +/* VSOCK device info for QMP-based setup via getfd + device_add */ +typedef struct VsockInfo { + int fd; /* vhost-vsock fd to pass via getfd (-EBADF if unused) */ + unsigned cid; /* guest CID */ + char *pcie_port; /* owned: pcie-root-port id for device_add bus (NULL on non-PCIe) */ +} VsockInfo; + +void vsock_info_done(VsockInfo *info); + +/* Aggregate of the per-device info structures populated before the bridge-based + * device setup phase. Keeps lifetime and cleanup of all device state in one place. */ +typedef struct MachineConfig { + DriveInfos drives; + NetworkInfo network; + VirtiofsInfos virtiofs; + VsockInfo vsock; +} MachineConfig; + +void machine_config_done(MachineConfig *c); + +/* Phase 2: Device setup — call any subset in any order before vmspawn_qmp_start(). */ +int vmspawn_qmp_setup_drives(VmspawnQmpBridge *bridge, DriveInfos *drives); +int vmspawn_qmp_setup_network(VmspawnQmpBridge *bridge, NetworkInfo *network); +int vmspawn_qmp_setup_virtiofs(VmspawnQmpBridge *bridge, const VirtiofsInfos *virtiofs); +int vmspawn_qmp_setup_vsock(VmspawnQmpBridge *bridge, VsockInfo *vsock); diff --git a/src/vmspawn/vmspawn-varlink.c b/src/vmspawn/vmspawn-varlink.c new file mode 100644 index 0000000000000..c73372e7a1a64 --- /dev/null +++ b/src/vmspawn/vmspawn-varlink.c @@ -0,0 +1,410 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "errno-util.h" +#include "hashmap.h" +#include "log.h" +#include "path-util.h" +#include "qmp-client.h" +#include "string-util.h" +#include "strv.h" +#include "varlink-io.systemd.MachineInstance.h" +#include "varlink-io.systemd.QemuMachineInstance.h" +#include "varlink-io.systemd.VirtualMachineInstance.h" +#include "varlink-util.h" +#include "vmspawn-varlink.h" + +DEFINE_PRIVATE_HASH_OPS_FULL( + varlink_subscriber_hash_ops, + void, trivial_hash_func, trivial_compare_func, sd_varlink_close_unref, + char*, strv_free); + +struct VmspawnVarlinkContext { + sd_varlink_server *varlink_server; + VmspawnQmpBridge *bridge; + /* Key: sd_varlink* (ref'd), Value: strv filter (NULL = all events). + * varlink_subscriber_hash_ops handles cleanup of both on removal. */ + Hashmap *subscribed; +}; + +/* Translate a QMP async completion into a varlink error reply */ +static int qmp_error_to_varlink(sd_varlink *link, const char *error_desc, int error) { + assert(link); + + if (ERRNO_IS_DISCONNECT(error)) + return sd_varlink_error(link, "io.systemd.MachineInstance.NotConnected", NULL); + if (error == -EIO) + log_warning("QMP command failed: %s", strna(error_desc)); + return sd_varlink_error_errno(link, error); +} + +/* Shared async completion for simple QMP commands that return no data. + * Errors are translated to varlink replies, not propagated through sd_event. */ +static int on_qmp_simple_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + sd_varlink *link = ASSERT_PTR(userdata); + + assert(client); + + if (error == 0) + (void) sd_varlink_reply(link, NULL); + else + (void) qmp_error_to_varlink(link, error_desc, error); + + sd_varlink_unref(link); + return 0; +} + +static int qmp_execute_varlink_async( + VmspawnVarlinkContext *ctx, + sd_varlink *link, + const char *command, + sd_json_variant *arguments, + qmp_command_callback_t callback) { + + int r; + + sd_varlink_ref(link); + + r = qmp_client_invoke(ctx->bridge->qmp, command, QMP_CLIENT_ARGS(arguments), callback, link); + if (r < 0) + sd_varlink_unref(link); + + return r; +} + +static int qmp_execute_simple_async(sd_varlink *link, VmspawnVarlinkContext *ctx, const char *qmp_command) { + assert(link); + assert(ctx); + assert(qmp_command); + + return qmp_execute_varlink_async(ctx, link, qmp_command, /* arguments= */ NULL, on_qmp_simple_complete); +} + +static int vl_method_terminate(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return qmp_execute_simple_async(link, ASSERT_PTR(userdata), "quit"); +} + +static int vl_method_pause(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return qmp_execute_simple_async(link, ASSERT_PTR(userdata), "stop"); +} + +static int vl_method_resume(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return qmp_execute_simple_async(link, ASSERT_PTR(userdata), "cont"); +} + +static int vl_method_power_off(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return qmp_execute_simple_async(link, ASSERT_PTR(userdata), "system_powerdown"); +} + +static int vl_method_reboot(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return qmp_execute_simple_async(link, ASSERT_PTR(userdata), "system_reset"); +} + +/* Async completion for query-status: extract running/status from QMP result */ +static int on_qmp_describe_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + _cleanup_(sd_varlink_unrefp) sd_varlink *link = ASSERT_PTR(userdata); + + assert(client); + + if (error != 0) { + (void) qmp_error_to_varlink(link, error_desc, error); + return 0; + } + + sd_json_variant *running = sd_json_variant_by_key(result, "running"); + sd_json_variant *status = sd_json_variant_by_key(result, "status"); + + (void) sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR_BOOLEAN("running", running ? sd_json_variant_boolean(running) : false), + SD_JSON_BUILD_PAIR_STRING("status", status && sd_json_variant_is_string(status) ? sd_json_variant_string(status) : "unknown")); + + return 0; +} + +static int vl_method_describe(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + VmspawnVarlinkContext *ctx = ASSERT_PTR(userdata); + + return qmp_execute_varlink_async(ctx, link, "query-status", /* arguments= */ NULL, on_qmp_describe_complete); +} + +static int vl_method_subscribe_events(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + VmspawnVarlinkContext *ctx = ASSERT_PTR(userdata); + _cleanup_strv_free_ char **filter = NULL; + int r; + + /* SD_VARLINK_REQUIRES_MORE in the IDL rejects non-streaming callers before we get here */ + + r = sd_varlink_dispatch(link, parameters, (const sd_json_dispatch_field[]) { + { "filter", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_strv, 0, SD_JSON_NULLABLE }, + {}, + }, &filter); + if (r != 0) + return r; + + sd_varlink_ref(link); + + r = hashmap_ensure_put(&ctx->subscribed, &varlink_subscriber_hash_ops, link, filter); + if (r < 0) { + sd_varlink_unref(link); + return r; + } + + TAKE_PTR(filter); + + r = sd_varlink_notifybo(link, SD_JSON_BUILD_PAIR_STRING("event", "READY")); + if (r < 0) { + strv_free(hashmap_remove(ctx->subscribed, link)); + sd_varlink_close_unref(link); + return r; + } + + return 0; +} + +static int vl_method_acquire_qmp(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return sd_varlink_error_errno(link, -EOPNOTSUPP); +} + +static void vl_disconnect(sd_varlink_server *server, sd_varlink *link, void *userdata) { + VmspawnVarlinkContext *ctx = ASSERT_PTR(userdata); + + assert(server); + assert(link); + + /* Only subscribers hold an extra ref on the link (taken in vl_method_subscribe_events). + * Non-subscriber connections (one-shot commands like Pause, Describe) must not be unref'd + * here — their extra ref is consumed by the async completion callback. Only unref, never + * close — the server handles close after this callback returns (matching resolved's + * vl_on_notification_disconnect pattern). + * + * Use hashmap_remove2() so the returned key (non-NULL iff the entry was present) + * disambiguates "no filter subscriber" (value=NULL) from "not a subscriber". */ + void *removed_key = NULL; + strv_free(hashmap_remove2(ctx->subscribed, link, &removed_key)); + if (!removed_key) + return; + + sd_varlink_unref(link); +} + +static int on_job_dismiss_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + if (error < 0) + log_debug_errno(error, "job-dismiss failed: %s", strna(error_desc)); + + return 0; +} + +static int dispatch_pending_job(VmspawnQmpBridge *bridge, sd_json_variant *data) { + const char *job_id, *status; + int r; + + assert(bridge); + + if (!data) + return 0; + + job_id = sd_json_variant_string(sd_json_variant_by_key(data, "id")); + status = sd_json_variant_string(sd_json_variant_by_key(data, "status")); + + if (!job_id || !streq_ptr(status, "concluded")) + return 0; + + _cleanup_free_ char *key = NULL; + _cleanup_(pending_job_freep) PendingJob *job = hashmap_remove2(bridge->pending_jobs, job_id, (void**) &key); + if (!job) + return 0; + + log_debug("QMP job '%s' concluded, firing continuation", job_id); + + /* Dismiss the concluded job before running the continuation */ + _cleanup_(sd_json_variant_unrefp) sd_json_variant *dismiss_args = NULL; + r = sd_json_buildo(&dismiss_args, SD_JSON_BUILD_PAIR_STRING("id", job_id)); + if (r < 0) + return sd_event_exit(qmp_client_get_event(bridge->qmp), r); + + r = qmp_client_invoke(bridge->qmp, "job-dismiss", QMP_CLIENT_ARGS(dismiss_args), + on_job_dismiss_complete, /* userdata= */ NULL); + if (r < 0) + return sd_event_exit(qmp_client_get_event(bridge->qmp), r); + + if (!job->on_concluded) + return 1; + + r = job->on_concluded(bridge->qmp, TAKE_PTR(job->userdata)); + if (r < 0) { + log_error_errno(r, "Job continuation failed: %m"); + return sd_event_exit(qmp_client_get_event(bridge->qmp), r); + } + + return 1; +} + +static int on_qmp_event( + QmpClient *client, + const char *event, + sd_json_variant *data, + void *userdata) { + + VmspawnVarlinkContext *ctx = ASSERT_PTR(userdata); + _cleanup_(sd_json_variant_unrefp) sd_json_variant *notification = NULL; + sd_varlink *link; + char **filter; + int r; + + assert(client); + assert(event); + + /* Dispatch job status changes to pending continuations (e.g. blockdev-create) */ + if (streq(event, "JOB_STATUS_CHANGE")) + return dispatch_pending_job(ctx->bridge, data); + + if (hashmap_isempty(ctx->subscribed)) + return 0; + + r = sd_json_buildo( + ¬ification, + SD_JSON_BUILD_PAIR_STRING("event", event), + SD_JSON_BUILD_PAIR_CONDITION(!!data, "data", SD_JSON_BUILD_VARIANT(data))); + if (r < 0) { + log_warning_errno(r, "Failed to build event notification, ignoring: %m"); + return 0; + } + + HASHMAP_FOREACH_KEY(filter, link, ctx->subscribed) { + if (filter && !strv_contains(filter, event)) + continue; + + r = sd_varlink_notify(link, notification); + if (r < 0) + log_warning_errno(r, "Failed to notify event subscriber, ignoring: %m"); + } + + return 0; +} + +/* Free all subscriber entries — varlink_subscriber_hash_ops handles + * close + unref for each key and strv_free for each value. */ +static void drain_event_subscribers(Hashmap **subscribed) { + assert(subscribed); + *subscribed = hashmap_free(*subscribed); +} + +static void on_qmp_disconnect(QmpClient *client, void *userdata) { + VmspawnVarlinkContext *ctx = ASSERT_PTR(userdata); + + assert(client); + + log_debug("Backend connection lost"); + + /* Propagate connection loss by closing all subscriber connections */ + drain_event_subscribers(&ctx->subscribed); +} + +int vmspawn_varlink_setup( + VmspawnVarlinkContext **ret, + VmspawnQmpBridge *bridge, + const char *runtime_dir, + char **ret_control_address) { + + _cleanup_(vmspawn_varlink_context_freep) VmspawnVarlinkContext *ctx = NULL; + _cleanup_free_ char *listen_address = NULL; + int r; + + assert(ret); + assert(bridge); + assert(runtime_dir); + + sd_event *event = qmp_client_get_event(bridge->qmp); + assert(event); + + ctx = new0(VmspawnVarlinkContext, 1); + if (!ctx) + return log_oom(); + + /* Create varlink server for VM control */ + r = varlink_server_new(&ctx->varlink_server, + SD_VARLINK_SERVER_INHERIT_USERDATA, + ctx); + if (r < 0) + return log_error_errno(r, "Failed to create varlink server: %m"); + + r = sd_varlink_server_add_interface_many( + ctx->varlink_server, + &vl_interface_io_systemd_MachineInstance, + &vl_interface_io_systemd_VirtualMachineInstance, + &vl_interface_io_systemd_QemuMachineInstance); + if (r < 0) + return log_error_errno(r, "Failed to add varlink interfaces: %m"); + + r = sd_varlink_server_bind_method_many( + ctx->varlink_server, + "io.systemd.MachineInstance.Terminate", vl_method_terminate, + "io.systemd.MachineInstance.PowerOff", vl_method_power_off, + "io.systemd.MachineInstance.Pause", vl_method_pause, + "io.systemd.MachineInstance.Resume", vl_method_resume, + "io.systemd.MachineInstance.Reboot", vl_method_reboot, + "io.systemd.MachineInstance.Describe", vl_method_describe, + "io.systemd.MachineInstance.SubscribeEvents", vl_method_subscribe_events, + "io.systemd.QemuMachineInstance.AcquireQMP", vl_method_acquire_qmp); + if (r < 0) + return log_error_errno(r, "Failed to bind varlink methods: %m"); + + r = sd_varlink_server_bind_disconnect(ctx->varlink_server, vl_disconnect); + if (r < 0) + return log_error_errno(r, "Failed to bind disconnect handler: %m"); + + listen_address = path_join(runtime_dir, "control"); + if (!listen_address) + return log_oom(); + + r = sd_varlink_server_listen_address(ctx->varlink_server, listen_address, 0600); + if (r < 0) + return log_error_errno(r, "Failed to listen on %s: %m", listen_address); + + r = sd_varlink_server_attach_event(ctx->varlink_server, event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach varlink server to event loop: %m"); + + ctx->bridge = bridge; + qmp_client_bind_event(ctx->bridge->qmp, on_qmp_event, ctx); + qmp_client_bind_disconnect(ctx->bridge->qmp, on_qmp_disconnect, ctx); + + log_debug("Varlink control server listening on %s", listen_address); + + if (ret_control_address) + *ret_control_address = TAKE_PTR(listen_address); + + *ret = TAKE_PTR(ctx); + return 0; +} + +VmspawnVarlinkContext* vmspawn_varlink_context_free(VmspawnVarlinkContext *ctx) { + if (!ctx) + return NULL; + + sd_varlink_server_unref(ctx->varlink_server); + vmspawn_qmp_bridge_free(ctx->bridge); + + drain_event_subscribers(&ctx->subscribed); + + return mfree(ctx); +} diff --git a/src/vmspawn/vmspawn-varlink.h b/src/vmspawn/vmspawn-varlink.h new file mode 100644 index 0000000000000..1833416a56dcc --- /dev/null +++ b/src/vmspawn/vmspawn-varlink.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "cleanup-util.h" +#include "shared-forward.h" +#include "vmspawn-qmp.h" + +typedef struct VmspawnVarlinkContext VmspawnVarlinkContext; + +/* Varlink server for VM control on top of an established bridge connection */ +int vmspawn_varlink_setup( + VmspawnVarlinkContext **ret, + VmspawnQmpBridge *bridge, + const char *runtime_dir, + char **ret_control_address); + +VmspawnVarlinkContext* vmspawn_varlink_context_free(VmspawnVarlinkContext *ctx); + +DEFINE_TRIVIAL_CLEANUP_FUNC(VmspawnVarlinkContext *, vmspawn_varlink_context_free); From 6bfa7aa0509a3d612ffef5d56b7663454765e8ac Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 8 Apr 2026 09:02:26 +0200 Subject: [PATCH 0998/1296] vmspawn: pre-allocate PCIe root ports for device hotplug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On PCIe machine types (q35, virt), QMP device_add is always hotplug — even with vCPUs stopped. The root PCIe bus (pcie.0) does not support hotplugging; only pcie-root-port bridges do. Pre-allocate enough root ports in the QEMU config file for all devices that will be set up via QMP, plus 10 spare ports for future runtime hotplug. Add ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS macro to guard PCIe-specific setup on x86, ARM, RISC-V, and LoongArch (the architectures whose QEMU machine type is q35 or virt). Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn-util.h | 15 ++++++++++- src/vmspawn/vmspawn.c | 53 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/vmspawn/vmspawn-util.h b/src/vmspawn/vmspawn-util.h index 4e3e4c131326d..38bb331dfc340 100644 --- a/src/vmspawn/vmspawn-util.h +++ b/src/vmspawn/vmspawn-util.h @@ -59,20 +59,33 @@ # define KERNEL_CMDLINE_SIZE 512 #endif +/* ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS is co-located with QEMU_MACHINE_TYPE so they stay in + * sync: q35 and virt machine types need pcie-root-port bridges for QMP device_add hotplug. + * Exception: m68k's "virt" uses virtio-mmio, not PCIe, so it doesn't need root ports. */ #if defined(__x86_64__) || defined(__i386__) # define QEMU_MACHINE_TYPE "q35" -#elif defined(__arm__) || defined(__aarch64__) || defined(__riscv) || defined(__loongarch64) || defined(__m68k__) +# define ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS 1 +#elif defined(__m68k__) # define QEMU_MACHINE_TYPE "virt" +# define ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS 0 +#elif defined(__arm__) || defined(__aarch64__) || defined(__riscv) || defined(__loongarch64) +# define QEMU_MACHINE_TYPE "virt" +# define ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS 1 #elif defined(__s390__) || defined(__s390x__) # define QEMU_MACHINE_TYPE "s390-ccw-virtio" +# define ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS 0 #elif defined(__powerpc__) || defined(__powerpc64__) # define QEMU_MACHINE_TYPE "pseries" +# define ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS 0 #elif defined(__mips__) # define QEMU_MACHINE_TYPE "malta" +# define ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS 0 #elif defined(__sparc__) # define QEMU_MACHINE_TYPE "sun4u" +# define ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS 0 #else # define QEMU_MACHINE_TYPE "none" +# define ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS 0 #endif #if defined(__arm__) || defined(__aarch64__) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index fe7d307a637e9..587f2da800b11 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -100,6 +100,9 @@ #define DISK_SERIAL_MAX_LEN_SCSI 30 #define DISK_SERIAL_MAX_LEN_NVME 20 +/* Spare pcie-root-ports reserved for future runtime hotplug beyond the pre-wired devices. */ +#define VMSPAWN_PCIE_HOTPLUG_SPARES 10u + /* An enum controlling how auxiliary state for the VM are maintained, i.e. the TPM state and the EFI variable * NVRAM. */ typedef enum StateMode { @@ -3443,6 +3446,56 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (r < 0) return r; + + /* Pre-allocate PCIe root ports for QMP device_add hotplug. On PCIe machine types + * (q35, virt), QMP device_add is always hotplug — the root bus (pcie.0) does not support + * it. Each root port provides one slot for hotplug. We create enough ports for all devices + * that will be set up via QMP, plus VMSPAWN_PCIE_HOTPLUG_SPARES spare ports for future + * runtime hotplug. */ + if (ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS) { + /* Count maximum possible PCI devices: root image + extra drives + SCSI controller + + * network + virtiofs mounts + vsock. The actual count may be lower (e.g. no network, + * no SCSI), but unused ports have negligible overhead. */ + size_t n_pcie_ports = 1 + + arg_extra_drives.n_drives + /* drives */ + 1 + /* SCSI controller */ + 1 + /* network */ + (arg_directory ? 1 : 0) + /* rootdir virtiofs */ + arg_runtime_mounts.n_mounts + /* extra virtiofs mounts */ + 1 + /* vsock */ + VMSPAWN_PCIE_HOTPLUG_SPARES; /* reserved for future hotplug */ + + /* Guard the unsigned subtraction below against future refactors that might drop the + * fixed additions. */ + assert(n_pcie_ports >= VMSPAWN_PCIE_HOTPLUG_SPARES); + + /* QEMU's pcie-root-port chassis/slot are uint8_t — i+1 must fit. */ + if (n_pcie_ports > UINT8_MAX) + return log_error_errno(SYNTHETIC_ERRNO(E2BIG), + "Too many PCIe root ports requested (%zu, max 255). " + "Reduce the number of extra drives or runtime mounts.", + n_pcie_ports); + + size_t n_builtin_ports = n_pcie_ports - VMSPAWN_PCIE_HOTPLUG_SPARES; + for (i = 0; i < n_pcie_ports; i++) { + char id[STRLEN("vmspawn-hotplug-pci-root-port-") + DECIMAL_STR_MAX(size_t)]; + if (i < n_builtin_ports) + xsprintf(id, "vmspawn-pcieport-%zu", i); + else + xsprintf(id, "vmspawn-hotplug-pci-root-port-%zu", i - n_builtin_ports); + + r = qemu_config_section(config_file, "device", id, + "driver", "pcie-root-port"); + if (r < 0) + return r; + r = qemu_config_keyf(config_file, "chassis", "%zu", i + 1); + if (r < 0) + return r; + r = qemu_config_keyf(config_file, "slot", "%zu", i + 1); + if (r < 0) + return r; + } + } /* Finalize the config file and add -readconfig to the cmdline */ r = fflush_and_check(config_file); if (r < 0) From 0d8fb7d543e062efd3186ffe9da6b1e84fa63f95 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 8 Apr 2026 09:02:37 +0200 Subject: [PATCH 0999/1296] vmspawn: QMP-varlink bridge for VM runtime control Create a QMP socketpair for QEMU machine monitor control, configure the QMP chardev+mon via the QEMU config file, and wire up the bridge infrastructure. After fork, vmspawn initializes the QMP bridge, probes QEMU feature support synchronously (driving the QMP handshake to RUNNING transparently), resumes vCPUs, then sets up the varlink server for runtime VM control. The control socket path is passed to machined via the controlAddress field in machine registration. Device configuration still uses the legacy INI config path and will be converted to bridge calls in subsequent commits. Signed-off-by: Christian Brauner (Amutable) --- src/shared/machine-register.c | 1 + src/shared/machine-register.h | 1 + src/vmspawn/vmspawn.c | 85 ++++++++++++++++++++++++++++++++--- 3 files changed, 80 insertions(+), 7 deletions(-) diff --git a/src/shared/machine-register.c b/src/shared/machine-register.c index 924d768877108..a161d1b0508d3 100644 --- a/src/shared/machine-register.c +++ b/src/shared/machine-register.c @@ -187,6 +187,7 @@ int register_machine( SD_JSON_BUILD_PAIR_CONDITION(!!reg->root_directory, "rootDirectory", SD_JSON_BUILD_STRING(reg->root_directory)), SD_JSON_BUILD_PAIR_CONDITION(!!reg->ssh_address, "sshAddress", SD_JSON_BUILD_STRING(reg->ssh_address)), SD_JSON_BUILD_PAIR_CONDITION(!!reg->ssh_private_key_path, "sshPrivateKeyPath", SD_JSON_BUILD_STRING(reg->ssh_private_key_path)), + SD_JSON_BUILD_PAIR_CONDITION(!!reg->control_address, "controlAddress", SD_JSON_BUILD_STRING(reg->control_address)), SD_JSON_BUILD_PAIR_CONDITION(isatty_safe(STDIN_FILENO), "allowInteractiveAuthentication", SD_JSON_BUILD_BOOLEAN(true)), SD_JSON_BUILD_PAIR_CONDITION(reg->allocate_unit, "allocateUnit", SD_JSON_BUILD_BOOLEAN(true)), SD_JSON_BUILD_PAIR_CONDITION(pidref_is_set(reg->pidref), "leaderProcessId", JSON_BUILD_PIDREF(reg->pidref))); diff --git a/src/shared/machine-register.h b/src/shared/machine-register.h index 996bcfe7a2d52..ffec15a8812ab 100644 --- a/src/shared/machine-register.h +++ b/src/shared/machine-register.h @@ -17,6 +17,7 @@ typedef struct MachineRegistration { int local_ifindex; const char *ssh_address; const char *ssh_private_key_path; + const char *control_address; bool allocate_unit; } MachineRegistration; diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 587f2da800b11..8cf2f9112938b 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -53,6 +53,7 @@ #include "machine-credential.h" #include "machine-register.h" #include "main-func.h" +#include "memfd-util.h" #include "mkdir.h" #include "namespace-util.h" #include "netif-util.h" @@ -91,9 +92,11 @@ #include "utf8.h" #include "vmspawn-mount.h" #include "vmspawn-qemu-config.h" +#include "vmspawn-qmp.h" #include "vmspawn-scope.h" #include "vmspawn-settings.h" #include "vmspawn-util.h" +#include "vmspawn-varlink.h" #define VM_TAP_HASH_KEY SD_ID128_MAKE(01,d0,c6,4c,2b,df,24,fb,c0,f8,b2,09,7d,59,b2,93) @@ -2291,6 +2294,38 @@ static int cmdline_add_ovmf(FILE *config_file, const OvmfConfig *ovmf_config, ch return 0; } +/* Create a QMP control socketpair, add QEMU's end to pass_fds, and write the chardev + monitor + * config sections. Returns with bridge_fds populated: [0] is vmspawn's end, [1] is QEMU's end + * (also in pass_fds). FORK_CLOEXEC_OFF clears CLOEXEC on pass_fds in the child. */ +static int qemu_config_add_qmp_monitor(FILE *config_file, int bridge_fds[2], int **pass_fds, size_t *n_pass_fds) { + int r; + + assert(config_file); + assert(bridge_fds); + assert(pass_fds); + assert(n_pass_fds); + + if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, bridge_fds) < 0) + return log_error_errno(errno, "Failed to create QMP socketpair: %m"); + + if (!GREEDY_REALLOC(*pass_fds, *n_pass_fds + 1)) + return log_oom(); + (*pass_fds)[(*n_pass_fds)++] = bridge_fds[1]; + + r = qemu_config_section(config_file, "chardev", "qmp", + "backend", "socket"); + if (r < 0) + return r; + + r = qemu_config_keyf(config_file, "fd", "%d", bridge_fds[1]); + if (r < 0) + return r; + + return qemu_config_section(config_file, "mon", "qmp", + "chardev", "qmp", + "mode", "control"); +} + static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { _cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL; _cleanup_free_ char *qemu_binary = NULL, *mem = NULL; @@ -2389,8 +2424,8 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (asprintf(&mem, "%" PRIu64 "M", DIV_ROUND_UP(arg_ram, U64_MB)) < 0) return log_oom(); - /* Create our runtime directory. We need this for the QEMU config file, TPM state, virtiofsd - * sockets, runtime mounts, and SSH key material. + /* Create our runtime directory. We need this for the QMP varlink control socket, the QEMU + * config file, TPM state, virtiofsd sockets, runtime mounts, and SSH key material. * * Use runtime_directory() (not _generic()) so that when vmspawn runs in a systemd service * with RuntimeDirectory= set, we pick up $RUNTIME_DIRECTORY and place our stuff into the @@ -2525,8 +2560,10 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return r; } - /* Start building the cmdline for items that must remain as command line arguments */ + /* Start building the cmdline for items that must remain as command line arguments. + * -S starts QEMU with vCPUs paused — we set up devices via QMP then resume with "cont". */ cmdline = strv_new(qemu_binary, + "-S", "-no-user-config"); if (!cmdline) return log_oom(); @@ -3446,6 +3483,10 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (r < 0) return r; + _cleanup_close_pair_ int bridge_fds[2] = EBADF_PAIR; + r = qemu_config_add_qmp_monitor(config_file, bridge_fds, &pass_fds, &n_pass_fds); + if (r < 0) + return r; /* Pre-allocate PCIe root ports for QMP device_add hotplug. On PCIe machine types * (q35, virt), QMP device_add is always hotplug — the root bus (pcie.0) does not support @@ -3488,9 +3529,11 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { "driver", "pcie-root-port"); if (r < 0) return r; + r = qemu_config_keyf(config_file, "chassis", "%zu", i + 1); if (r < 0) return r; + r = qemu_config_keyf(config_file, "slot", "%zu", i + 1); if (r < 0) return r; @@ -3538,7 +3581,8 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return log_error_errno(child_pty, "Failed to open PTY slave: %m"); } - _cleanup_(pidref_done) PidRef child_pidref = PIDREF_NULL; + /* SIGTERM, not SIGKILL — let QEMU flush state on error-path early exits. */ + _cleanup_(pidref_done_sigterm_wait) PidRef child_pidref = PIDREF_NULL; r = pidref_safe_fork_full( qemu_binary, child_pty >= 0 ? (const int[]) { child_pty, child_pty, child_pty } : NULL, @@ -3560,10 +3604,36 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { _exit(EXIT_FAILURE); } - /* Close relevant fds we passed to qemu in the parent. We don't need them anymore. */ + /* Close QEMU's end of the QMP socketpair in the parent. We don't need it anymore. */ child_pty = safe_close(child_pty); - child_vsock_fd = safe_close(child_vsock_fd); - tap_fd = safe_close(tap_fd); + bridge_fds[1] = safe_close(bridge_fds[1]); + + /* Connect to VMM backend */ + _cleanup_(vmspawn_qmp_bridge_freep) VmspawnQmpBridge *bridge = NULL; + r = vmspawn_qmp_init(&bridge, bridge_fds[0], event); + if (r < 0) + return r; + + TAKE_FD(bridge_fds[0]); + + /* Probe QEMU feature availability synchronously before device setup consumes the flags. */ + r = vmspawn_qmp_probe_features(bridge); + if (r < 0) + return r; + + /* Resume vCPUs and switch to async event processing */ + r = vmspawn_qmp_start(bridge); + if (r < 0) + return r; + + /* Varlink server for VM control */ + _cleanup_(vmspawn_varlink_context_freep) VmspawnVarlinkContext *varlink_ctx = NULL; + _cleanup_free_ char *control_address = NULL; + r = vmspawn_varlink_setup(&varlink_ctx, bridge, runtime_dir, &control_address); + if (r < 0) + return r; + + TAKE_PTR(bridge); if (!arg_keep_unit) { /* When a new scope is created for this container, then we'll be registered as its controller, in which @@ -3628,6 +3698,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { .vsock_cid = child_cid, .ssh_address = child_cid != VMADDR_CID_ANY ? vm_address : NULL, .ssh_private_key_path = ssh_private_key_path, + .control_address = control_address, .allocate_unit = !arg_keep_unit, }; From cc045f5326a15f5a3ed2b40afc7cc5ff5efcd999 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 7 Apr 2026 14:47:10 +0200 Subject: [PATCH 1000/1296] vmspawn: convert network device setup to bridge Remove the static netdev/nic INI config sections for both privileged TAP, nsresourced TAP, and user-mode networking. Replace them with a NetworkInfo struct that captures the network type, TAP fd or interface name, and MAC address, passed to vmspawn_varlink_setup_network() for runtime configuration via QMP. For the nsresourced TAP path the fd is now passed to QEMU via QMP getfd + SCM_RIGHTS instead of being inherited through pass_fds. Declare the MachineConfig aggregate that this and the following conversion patches populate, zero-initialized with explicit -EBADF for the fd fields so every sub-structure cleans up safely regardless of which device types the invocation ends up using. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn.c | 79 +++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 44 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 8cf2f9112938b..2120df61af970 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -2335,6 +2335,10 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { _cleanup_close_ int notify_sock_fd = -EBADF; _cleanup_strv_free_ char **cmdline = NULL; _cleanup_free_ int *pass_fds = NULL; + _cleanup_(machine_config_done) MachineConfig config = { + .network = { .fd = -EBADF }, + .vsock = { .fd = -EBADF }, + }; sd_event_source **children = NULL; size_t n_children = 0, n_pass_fds = 0; int r; @@ -2573,8 +2577,14 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return log_oom(); _cleanup_close_ int delegate_userns_fd = -EBADF, tap_fd = -EBADF; + _cleanup_free_ char *tap_name = NULL; + struct ether_addr mac_vm = {}; + if (arg_network_stack == NETWORK_STACK_TAP) { if (have_effective_cap(CAP_NET_ADMIN) <= 0) { + /* Without CAP_NET_ADMIN we use nsresourced to create a TAP device. + * The TAP fd is passed to QEMU via QMP getfd + SCM_RIGHTS after + * the handshake, then referenced by name in netdev_add. */ delegate_userns_fd = userns_acquire_self_root(); if (delegate_userns_fd < 0) return log_error_errno(delegate_userns_fd, "Failed to acquire userns: %m"); @@ -2596,65 +2606,39 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (tap_fd < 0) return log_error_errno(tap_fd, "Failed to allocate network tap device: %m"); - r = strv_extend(&cmdline, "-netdev"); - if (r < 0) - return log_oom(); - - r = strv_extendf(&cmdline, "tap,id=net0,fd=%i", tap_fd); - if (r < 0) - return log_oom(); - - r = strv_extend_many(&cmdline, "-device", "virtio-net-pci,netdev=net0"); - if (r < 0) - return log_oom(); - - if (!GREEDY_REALLOC(pass_fds, n_pass_fds + 1)) - return log_oom(); - - pass_fds[n_pass_fds++] = tap_fd; + config.network = (NetworkInfo) { + .type = "tap", + .fd = TAKE_FD(tap_fd), + }; } else { - _cleanup_free_ char *tap_name = NULL; - struct ether_addr mac_vm = {}; - + /* With CAP_NET_ADMIN we create the TAP interface by name. + * Configure via QMP after QEMU starts. */ tap_name = strjoin("vt-", arg_machine); if (!tap_name) return log_oom(); (void) net_shorten_ifname(tap_name, /* check_naming_scheme= */ false); - if (ether_addr_is_null(&arg_network_provided_mac)){ + if (ether_addr_is_null(&arg_network_provided_mac)) { r = net_generate_mac(arg_machine, &mac_vm, VM_TAP_HASH_KEY, 0); if (r < 0) return log_error_errno(r, "Failed to generate predictable MAC address for VM side: %m"); } else mac_vm = arg_network_provided_mac; - r = qemu_config_section(config_file, "netdev", "net0", - "type", "tap", - "ifname", tap_name, - "script", "no", - "downscript", "no"); - if (r < 0) - return r; - - r = qemu_config_section(config_file, "device", "nic0", - "driver", "virtio-net-pci", - "netdev", "net0", - "mac", ETHER_ADDR_TO_STR(&mac_vm)); - if (r < 0) - return r; + config.network = (NetworkInfo) { + .type = "tap", + .ifname = TAKE_PTR(tap_name), + .mac = mac_vm, + .mac_set = true, + .fd = -EBADF, + }; } } else if (arg_network_stack == NETWORK_STACK_USER) { - r = qemu_config_section(config_file, "netdev", "net0", - "type", "user"); - if (r < 0) - return r; - - r = qemu_config_section(config_file, "device", "nic0", - "driver", "virtio-net-pci", - "netdev", "net0"); - if (r < 0) - return r; + config.network = (NetworkInfo) { + .type = "user", + .fd = -EBADF, + }; } else { r = strv_extend_many(&cmdline, "-nic", "none"); if (r < 0) @@ -3621,6 +3605,13 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (r < 0) return r; + /* Device setup — all before resuming vCPUs */ + if (config.network.type) { + r = vmspawn_qmp_setup_network(bridge, &config.network); + if (r < 0) + return r; + } + /* Resume vCPUs and switch to async event processing */ r = vmspawn_qmp_start(bridge); if (r < 0) From 41022693a8f917a0b21ad7c2d689ec5337b17569 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 7 Apr 2026 14:48:06 +0200 Subject: [PATCH 1001/1296] vmspawn: convert VSOCK device setup to bridge Remove the static vsock0 INI config section and the related pass_fds plumbing. Replace with a VsockInfo struct that captures the vhost fd and guest CID, passed to vmspawn_qmp_setup_vsock() for runtime configuration via QMP. The VSOCK fd is now sent to QEMU via QMP getfd + SCM_RIGHTS instead of being inherited. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn.c | 35 ++++++++++------------------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 2120df61af970..c385990b71fd0 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -2695,40 +2695,21 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return r; } - _cleanup_close_ int child_vsock_fd = -EBADF; unsigned child_cid = arg_vsock_cid; if (use_vsock) { - int device_fd = vhost_device_fd; + config.vsock.fd = TAKE_FD(vhost_device_fd); - if (device_fd < 0) { - child_vsock_fd = open("/dev/vhost-vsock", O_RDWR|O_CLOEXEC); - if (child_vsock_fd < 0) + if (config.vsock.fd < 0) { + config.vsock.fd = open("/dev/vhost-vsock", O_RDWR|O_CLOEXEC); + if (config.vsock.fd < 0) return log_error_errno(errno, "Failed to open /dev/vhost-vsock as read/write: %m"); - - device_fd = child_vsock_fd; } - r = vsock_fix_child_cid(device_fd, &child_cid, arg_machine); + r = vsock_fix_child_cid(config.vsock.fd, &child_cid, arg_machine); if (r < 0) return log_error_errno(r, "Failed to fix CID for the guest VSOCK socket: %m"); - r = qemu_config_section(config_file, "device", "vsock0", - "driver", "vhost-vsock-pci"); - if (r < 0) - return r; - - r = qemu_config_keyf(config_file, "guest-cid", "%u", child_cid); - if (r < 0) - return r; - - r = qemu_config_keyf(config_file, "vhostfd", "%d", device_fd); - if (r < 0) - return r; - - if (!GREEDY_REALLOC(pass_fds, n_pass_fds + 1)) - return log_oom(); - - pass_fds[n_pass_fds++] = device_fd; + config.vsock.cid = child_cid; } /* -cpu stays on cmdline since not all flags are supported in config */ @@ -3612,6 +3593,10 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return r; } + r = vmspawn_qmp_setup_vsock(bridge, &config.vsock); + if (r < 0) + return r; + /* Resume vCPUs and switch to async event processing */ r = vmspawn_qmp_start(bridge); if (r < 0) From 723126736ff6c3e725dbe1512e8a6ade14d4f3b2 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 7 Apr 2026 14:50:23 +0200 Subject: [PATCH 1002/1296] vmspawn: convert virtiofs device setup to bridge Remove the static chardev/device INI config sections for both the root filesystem and runtime mount virtiofs instances. Replace with VirtiofsInfos that capture socket paths and tags for each virtiofs mount, passed to vmspawn_varlink_setup_virtiofs() for runtime configuration via QMP. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn.c | 55 ++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index c385990b71fd0..8fdb64786cd9f 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -3017,19 +3017,18 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { pidref_done(&child); children[n_children++] = TAKE_PTR(source); - r = qemu_config_section(config_file, "chardev", "rootdir", - "backend", "socket", - "path", listen_address); - if (r < 0) - return r; + _cleanup_free_ char *id = strdup("rootdir"), *tag = strdup("root"); + if (!id || !tag) + return log_oom(); - r = qemu_config_section(config_file, "device", "rootdir", - "driver", "vhost-user-fs-pci", - "queue-size", "1024", - "chardev", "rootdir", - "tag", "root"); - if (r < 0) - return r; + if (!GREEDY_REALLOC(config.virtiofs.entries, config.virtiofs.n_entries + 1)) + return log_oom(); + + config.virtiofs.entries[config.virtiofs.n_entries++] = (VirtiofsInfo) { + .id = TAKE_PTR(id), + .socket_path = TAKE_PTR(listen_address), + .tag = TAKE_PTR(tag), + }; if (strv_extend(&arg_kernel_cmdline_extra, "root=root rootfstype=virtiofs rw") < 0) return log_oom(); @@ -3146,7 +3145,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { for (size_t j = 0; j < arg_runtime_mounts.n_mounts; j++) { RuntimeMount *m = arg_runtime_mounts.mounts + j; - _cleanup_free_ char *listen_address = NULL; + _cleanup_free_ char *listen_address = NULL, *id = NULL, *tag = NULL; _cleanup_(fork_notify_terminate) PidRef child = PIDREF_NULL; if (!GREEDY_REALLOC(children, n_children + 1)) @@ -3172,23 +3171,12 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { pidref_done(&child); children[n_children++] = TAKE_PTR(source); - _cleanup_free_ char *id = NULL; if (asprintf(&id, "mnt%zu", j) < 0) return log_oom(); - r = qemu_config_section(config_file, "chardev", id, - "backend", "socket", - "path", listen_address); - if (r < 0) - return r; - - r = qemu_config_section(config_file, "device", id, - "driver", "vhost-user-fs-pci", - "queue-size", "1024", - "chardev", id, - "tag", id); - if (r < 0) - return r; + tag = strdup(id); + if (!tag) + return log_oom(); /* fstab uses whitespace as field separator, so octal-escape spaces in paths */ _cleanup_free_ char *escaped_target = octescape_full(m->target, SIZE_MAX, " \t"); @@ -3198,6 +3186,15 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (strextendf(&fstab_extra, "%s %s virtiofs %s,x-initrd.mount\n", id, escaped_target, m->read_only ? "ro" : "rw") < 0) return log_oom(); + + if (!GREEDY_REALLOC(config.virtiofs.entries, config.virtiofs.n_entries + 1)) + return log_oom(); + + config.virtiofs.entries[config.virtiofs.n_entries++] = (VirtiofsInfo) { + .id = TAKE_PTR(id), + .socket_path = TAKE_PTR(listen_address), + .tag = TAKE_PTR(tag), + }; } if (fstab_extra) { @@ -3593,6 +3590,10 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return r; } + r = vmspawn_qmp_setup_virtiofs(bridge, &config.virtiofs); + if (r < 0) + return r; + r = vmspawn_qmp_setup_vsock(bridge, &config.vsock); if (r < 0) return r; From e7d84aa4de9b293c2f48b2f76100befd88d5ab3c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 8 Apr 2026 08:54:23 +0200 Subject: [PATCH 1003/1296] vmspawn: convert drive device setup to bridge Remove the static blockdev/device/snapshot INI config sections and the SCSI controller setup for both the root image drive and extra drives. Replace with DriveInfos that are constructed in the parent after fork: vmspawn opens all image files and passes fds to QEMU via the add-fd path. For ephemeral mode, anonymous overlay files are created via O_TMPFILE or memfd. The resolve_disk_driver() helper maps DiskType to the appropriate QEMU driver name and serial format. The post-fork device-info preparation is split into helpers: prepare_primary_drive() and prepare_extra_drives() for per-drive construction, assign_pcie_ports() for naming the pre-allocated pcie-root-port bridges once every device type is known, and prepare_device_info() that stitches them together against the MachineConfig aggregate. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn.c | 410 +++++++++++++++++++++++++----------------- 1 file changed, 241 insertions(+), 169 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 8fdb64786cd9f..548a083fac5c6 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -100,8 +100,9 @@ #define VM_TAP_HASH_KEY SD_ID128_MAKE(01,d0,c6,4c,2b,df,24,fb,c0,f8,b2,09,7d,59,b2,93) -#define DISK_SERIAL_MAX_LEN_SCSI 30 -#define DISK_SERIAL_MAX_LEN_NVME 20 +#define DISK_SERIAL_MAX_LEN_SCSI 30 +#define DISK_SERIAL_MAX_LEN_NVME 20 +#define DISK_SERIAL_MAX_LEN_VIRTIO_BLK 20 /* Spare pcie-root-ports reserved for future runtime hotplug beyond the pre-wired devices. */ #define VMSPAWN_PCIE_HOTPLUG_SPARES 10u @@ -2326,6 +2327,232 @@ static int qemu_config_add_qmp_monitor(FILE *config_file, int bridge_fds[2], int "mode", "control"); } +static int resolve_disk_driver(DiskType dt, const char *filename, DriveInfo *info) { + int r; + + assert(filename); + assert(info); + + switch (dt) { + case DISK_TYPE_VIRTIO_BLK: + info->disk_driver = "virtio-blk-pci"; + r = disk_serial(filename, DISK_SERIAL_MAX_LEN_VIRTIO_BLK, &info->serial); + if (r < 0) + return r; + break; + case DISK_TYPE_VIRTIO_SCSI: + info->disk_driver = "scsi-hd"; + r = disk_serial(filename, DISK_SERIAL_MAX_LEN_SCSI, &info->serial); + if (r < 0) + return r; + break; + case DISK_TYPE_NVME: + info->disk_driver = "nvme"; + r = disk_serial(filename, DISK_SERIAL_MAX_LEN_NVME, &info->serial); + if (r < 0) + return r; + break; + case DISK_TYPE_VIRTIO_SCSI_CDROM: + info->disk_driver = "scsi-cd"; + info->flags |= QMP_DRIVE_READ_ONLY; + r = disk_serial(filename, DISK_SERIAL_MAX_LEN_SCSI, &info->serial); + if (r < 0) + return r; + break; + default: + assert_not_reached(); + } + + return 0; +} + +static int prepare_primary_drive(const char *runtime_dir, DriveInfos *drives) { + int r; + + assert(runtime_dir); + assert(drives); + + if (!arg_image) + return 0; + + _cleanup_free_ char *image_fn = NULL; + r = path_extract_filename(arg_image, &image_fn); + if (r < 0) + return log_error_errno(r, "Failed to extract filename from path '%s': %m", arg_image); + + DriveInfo *d = &drives->drives[drives->n_drives++]; + *d = (DriveInfo) { .fd = -EBADF, .overlay_fd = -EBADF }; + + r = resolve_disk_driver(arg_image_disk_type, image_fn, d); + if (r < 0) + return log_error_errno(r, "Failed to resolve disk driver for '%s': %m", image_fn); + + int open_flags = ((arg_ephemeral || FLAGS_SET(d->flags, QMP_DRIVE_READ_ONLY)) ? O_RDONLY : O_RDWR) | O_CLOEXEC | O_NOCTTY; + + _cleanup_close_ int image_fd = open(arg_image, open_flags); + if (image_fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", arg_image); + + struct stat st; + if (fstat(image_fd, &st) < 0) + return log_error_errno(errno, "Failed to stat '%s': %m", arg_image); + + r = stat_verify_regular_or_block(&st); + if (r < 0) + return log_error_errno(r, "Expected regular file or block device for image: %s", arg_image); + + d->path = arg_image; + d->format = image_format_to_string(arg_image_format); + d->node_name = strdup("vmspawn"); + if (!d->node_name) + return log_oom(); + d->fd = TAKE_FD(image_fd); + if (S_ISBLK(st.st_mode)) + d->flags |= QMP_DRIVE_BLOCK_DEVICE; + if (arg_discard_disk && !FLAGS_SET(d->flags, QMP_DRIVE_READ_ONLY)) + d->flags |= QMP_DRIVE_DISCARD; + d->flags |= QMP_DRIVE_BOOT; + + /* For ephemeral mode, create an anonymous overlay file. QEMU will format it + * as qcow2 via blockdev-create, so no filesystem path is needed. + * Skip for read-only drives (e.g. CDROM) where overlays are not meaningful. */ + if (arg_ephemeral && !FLAGS_SET(d->flags, QMP_DRIVE_READ_ONLY)) { + _cleanup_close_ int overlay_fd = open(runtime_dir, O_TMPFILE | O_RDWR | O_CLOEXEC, 0600); + if (overlay_fd < 0) { + if (!ERRNO_IS_NOT_SUPPORTED(errno)) + return log_error_errno(errno, "Failed to create ephemeral overlay in '%s': %m", runtime_dir); + + /* Fallback to memfd if O_TMPFILE is not supported */ + overlay_fd = memfd_new("vmspawn-overlay"); + if (overlay_fd < 0) + return log_error_errno(overlay_fd, "Failed to create ephemeral overlay via memfd: %m"); + } + d->overlay_fd = TAKE_FD(overlay_fd); + d->flags |= QMP_DRIVE_NO_FLUSH; + } + + return 0; +} + +static int prepare_extra_drives(DriveInfos *drives) { + int r; + + assert(drives); + + size_t extra_idx = 0; + FOREACH_ARRAY(drive, arg_extra_drives.drives, arg_extra_drives.n_drives) { + _cleanup_free_ char *drive_fn = NULL; + r = path_extract_filename(drive->path, &drive_fn); + if (r < 0) + return log_error_errno(r, "Failed to extract filename from path '%s': %m", drive->path); + + DiskType dt = drive->disk_type >= 0 ? drive->disk_type : arg_image_disk_type; + + DriveInfo *d = &drives->drives[drives->n_drives++]; + *d = (DriveInfo) { .fd = -EBADF, .overlay_fd = -EBADF }; + + r = resolve_disk_driver(dt, drive_fn, d); + if (r < 0) + return log_error_errno(r, "Failed to resolve disk driver for '%s': %m", drive_fn); + + _cleanup_close_ int drive_fd = open(drive->path, (FLAGS_SET(d->flags, QMP_DRIVE_READ_ONLY) ? O_RDONLY : O_RDWR) | O_CLOEXEC | O_NOCTTY); + if (drive_fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", drive->path); + + struct stat drive_st; + if (fstat(drive_fd, &drive_st) < 0) + return log_error_errno(errno, "Failed to stat '%s': %m", drive->path); + r = stat_verify_regular_or_block(&drive_st); + if (r < 0) + return log_error_errno(r, "Expected regular file or block device, not '%s'.", drive->path); + if (S_ISBLK(drive_st.st_mode) && drive->format == IMAGE_FORMAT_QCOW2) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Block device '%s' cannot be used with 'qcow2' format, only 'raw' is supported.", + drive->path); + + d->path = drive->path; + d->format = image_format_to_string(drive->format); + d->fd = TAKE_FD(drive_fd); + if (S_ISBLK(drive_st.st_mode)) + d->flags |= QMP_DRIVE_BLOCK_DEVICE; + d->flags |= QMP_DRIVE_NO_FLUSH; + + if (asprintf(&d->node_name, "vmspawn_extra_%zu", extra_idx++) < 0) + return log_oom(); + } + + return 0; +} + +/* Assign PCIe root port names to devices. The ports were pre-allocated in the config + * file. Each PCI device that will be hotplugged via QMP device_add gets a port. */ +static int assign_pcie_ports(MachineConfig *c) { + assert(c); + + if (!ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS) + return 0; + + DriveInfos *drives = &c->drives; + NetworkInfo *network = &c->network; + VirtiofsInfos *virtiofs = &c->virtiofs; + VsockInfo *vsock = &c->vsock; + + size_t port = 0; + + /* Drives: non-SCSI drives get individual ports, SCSI controller gets one port */ + bool need_scsi = false; + FOREACH_ARRAY(d, drives->drives, drives->n_drives) { + if (STR_IN_SET(d->disk_driver, "scsi-hd", "scsi-cd")) { + need_scsi = true; + continue; + } + if (asprintf(&d->pcie_port, "vmspawn-pcieport-%zu", port++) < 0) + return log_oom(); + } + if (need_scsi) + if (asprintf(&drives->scsi_pcie_port, "vmspawn-pcieport-%zu", port++) < 0) + return log_oom(); + + if (network->type) + if (asprintf(&network->pcie_port, "vmspawn-pcieport-%zu", port++) < 0) + return log_oom(); + + FOREACH_ARRAY(v, virtiofs->entries, virtiofs->n_entries) + if (asprintf(&v->pcie_port, "vmspawn-pcieport-%zu", port++) < 0) + return log_oom(); + + if (vsock->fd >= 0) + if (asprintf(&vsock->pcie_port, "vmspawn-pcieport-%zu", port++) < 0) + return log_oom(); + + return 0; +} + +static int prepare_device_info(const char *runtime_dir, MachineConfig *c) { + int r; + + assert(runtime_dir); + assert(c); + + DriveInfos *drives = &c->drives; + + /* Build drive info for QMP-based setup. vmspawn opens all image files and + * passes fds to QEMU via add-fd — QEMU never needs filesystem access. */ + drives->drives = new0(DriveInfo, 1 + arg_extra_drives.n_drives); + if (!drives->drives) + return log_oom(); + + r = prepare_primary_drive(runtime_dir, drives); + if (r < 0) + return r; + + r = prepare_extra_drives(drives); + if (r < 0) + return r; + + return assign_pcie_ports(c); +} + static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { _cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL; _cleanup_free_ char *qemu_binary = NULL, *mem = NULL; @@ -2851,24 +3078,6 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } } - bool need_scsi_controller = - IN_SET(arg_image_disk_type, DISK_TYPE_VIRTIO_SCSI, DISK_TYPE_VIRTIO_SCSI_CDROM) && arg_image; - if (!need_scsi_controller) - FOREACH_ARRAY(drive, arg_extra_drives.drives, arg_extra_drives.n_drives) { - DiskType dt = drive->disk_type >= 0 ? drive->disk_type : arg_image_disk_type; - if (IN_SET(dt, DISK_TYPE_VIRTIO_SCSI, DISK_TYPE_VIRTIO_SCSI_CDROM)) { - need_scsi_controller = true; - break; - } - } - - if (need_scsi_controller) { - r = qemu_config_section(config_file, "device", "vmspawn_scsi", - "driver", "virtio-scsi-pci"); - if (r < 0) - return r; - } - if (arg_image) { assert(!arg_directory); @@ -2880,74 +3089,6 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { arg_image); } - if (arg_image_disk_type == DISK_TYPE_VIRTIO_SCSI_CDROM) - r = qemu_config_section(config_file, "drive", "vmspawn", - "if", "none", - "file", arg_image, - "format", image_format_to_string(arg_image_format), - "media", "cdrom", - "readonly", "on"); - else - r = qemu_config_section(config_file, "drive", "vmspawn", - "if", "none", - "file", arg_image, - "format", image_format_to_string(arg_image_format), - "discard", on_off(arg_discard_disk), - "snapshot", on_off(arg_ephemeral)); - if (r < 0) - return r; - - _cleanup_free_ char *image_fn = NULL; - r = path_extract_filename(arg_image, &image_fn); - if (r < 0) - return log_error_errno(r, "Failed to extract filename from path '%s': %m", image_fn); - - const char *disk_driver; - _cleanup_free_ char *serial = NULL; - - switch (arg_image_disk_type) { - case DISK_TYPE_VIRTIO_BLK: - disk_driver = "virtio-blk-pci"; - serial = strdup(image_fn); - if (!serial) - return log_oom(); - break; - case DISK_TYPE_VIRTIO_SCSI: - disk_driver = "scsi-hd"; - r = disk_serial(image_fn, DISK_SERIAL_MAX_LEN_SCSI, &serial); - if (r < 0) - return log_oom(); - break; - case DISK_TYPE_NVME: - disk_driver = "nvme"; - r = disk_serial(image_fn, DISK_SERIAL_MAX_LEN_NVME, &serial); - if (r < 0) - return log_oom(); - break; - case DISK_TYPE_VIRTIO_SCSI_CDROM: - disk_driver = "scsi-cd"; - r = disk_serial(image_fn, DISK_SERIAL_MAX_LEN_SCSI, &serial); - if (r < 0) - return log_oom(); - break; - default: - assert_not_reached(); - } - - r = qemu_config_section(config_file, "device", "vmspawn-disk", - "driver", disk_driver, - "drive", "vmspawn", - "bootindex", "1", - "serial", serial); - if (r < 0) - return r; - - if (IN_SET(arg_image_disk_type, DISK_TYPE_VIRTIO_SCSI, DISK_TYPE_VIRTIO_SCSI_CDROM)) { - r = qemu_config_key(config_file, "bus", "vmspawn_scsi.0"); - if (r < 0) - return r; - } - if (arg_image_disk_type != DISK_TYPE_VIRTIO_SCSI_CDROM) { r = grow_image(arg_image, arg_grow_image); if (r < 0) @@ -3034,86 +3175,8 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return log_oom(); } - size_t i = 0; - FOREACH_ARRAY(drive, arg_extra_drives.drives, arg_extra_drives.n_drives) { - if (strv_extend(&cmdline, "-blockdev") < 0) - return log_oom(); - - _cleanup_free_ char *escaped_drive = escape_qemu_value(drive->path); - if (!escaped_drive) - return log_oom(); - - struct stat st; - if (stat(drive->path, &st) < 0) - return log_error_errno(errno, "Failed to stat '%s': %m", drive->path); - - const char *driver = NULL; - if (S_ISREG(st.st_mode)) - driver = "file"; - else if (S_ISBLK(st.st_mode)) { - if (drive->format == IMAGE_FORMAT_QCOW2) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "Block device '%s' cannot be used with 'qcow2' format, only 'raw' is supported.", - drive->path); - driver = "host_device"; - } else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected regular file or block device, not '%s'.", drive->path); - - DiskType dt = drive->disk_type >= 0 ? drive->disk_type : arg_image_disk_type; - - if (strv_extendf(&cmdline, "driver=%s,cache.direct=off,cache.no-flush=on,file.driver=%s,file.filename=%s,node-name=vmspawn_extra_%zu%s", - image_format_to_string(drive->format), driver, escaped_drive, i, - dt == DISK_TYPE_VIRTIO_SCSI_CDROM ? ",read-only=on" : "") < 0) - return log_oom(); - - _cleanup_free_ char *drive_fn = NULL; - r = path_extract_filename(drive->path, &drive_fn); - if (r < 0) - return log_error_errno(r, "Failed to extract filename from path '%s': %m", drive->path); - - _cleanup_free_ char *escaped_drive_fn = escape_qemu_value(drive_fn); - if (!escaped_drive_fn) - return log_oom(); - - if (strv_extend(&cmdline, "-device") < 0) - return log_oom(); - - switch (dt) { - case DISK_TYPE_VIRTIO_BLK: - if (strv_extendf(&cmdline, "virtio-blk-pci,drive=vmspawn_extra_%zu,serial=%s", i++, escaped_drive_fn) < 0) - return log_oom(); - break; - case DISK_TYPE_VIRTIO_SCSI: { - _cleanup_free_ char *serial = NULL; - r = disk_serial(escaped_drive_fn, DISK_SERIAL_MAX_LEN_SCSI, &serial); - if (r < 0) - return log_oom(); - if (strv_extendf(&cmdline, "scsi-hd,bus=vmspawn_scsi.0,drive=vmspawn_extra_%zu,serial=%s", i++, serial) < 0) - return log_oom(); - break; - } - case DISK_TYPE_NVME: { - _cleanup_free_ char *serial = NULL; - r = disk_serial(escaped_drive_fn, DISK_SERIAL_MAX_LEN_NVME, &serial); - if (r < 0) - return log_oom(); - if (strv_extendf(&cmdline, "nvme,drive=vmspawn_extra_%zu,serial=%s", i++, serial) < 0) - return log_oom(); - break; - } - case DISK_TYPE_VIRTIO_SCSI_CDROM: { - _cleanup_free_ char *serial = NULL; - r = disk_serial(escaped_drive_fn, DISK_SERIAL_MAX_LEN_SCSI, &serial); - if (r < 0) - return log_oom(); - if (strv_extendf(&cmdline, "scsi-cd,bus=vmspawn_scsi.0,drive=vmspawn_extra_%zu,serial=%s", i++, serial) < 0) - return log_oom(); - break; - } - default: - assert_not_reached(); - } - } + /* Extra drive validation is done in the post-fork drive info construction loop + * to avoid stat()'ing each drive twice. */ if (!IN_SET(arg_console_mode, CONSOLE_GUI, CONSOLE_HEADLESS)) { r = strv_prepend(&arg_kernel_cmdline_extra, @@ -3480,7 +3543,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { n_pcie_ports); size_t n_builtin_ports = n_pcie_ports - VMSPAWN_PCIE_HOTPLUG_SPARES; - for (i = 0; i < n_pcie_ports; i++) { + for (size_t i = 0; i < n_pcie_ports; i++) { char id[STRLEN("vmspawn-hotplug-pci-root-port-") + DECIMAL_STR_MAX(size_t)]; if (i < n_builtin_ports) xsprintf(id, "vmspawn-pcieport-%zu", i); @@ -3501,6 +3564,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return r; } } + /* Finalize the config file and add -readconfig to the cmdline */ r = fflush_and_check(config_file); if (r < 0) @@ -3570,6 +3634,10 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { child_pty = safe_close(child_pty); bridge_fds[1] = safe_close(bridge_fds[1]); + r = prepare_device_info(runtime_dir, &config); + if (r < 0) + return r; + /* Connect to VMM backend */ _cleanup_(vmspawn_qmp_bridge_freep) VmspawnQmpBridge *bridge = NULL; r = vmspawn_qmp_init(&bridge, bridge_fds[0], event); @@ -3584,6 +3652,10 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return r; /* Device setup — all before resuming vCPUs */ + r = vmspawn_qmp_setup_drives(bridge, &config.drives); + if (r < 0) + return r; + if (config.network.type) { r = vmspawn_qmp_setup_network(bridge, &config.network); if (r < 0) From f26199631c7f00b5113057c785b89629ae9c63d9 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 30 Mar 2026 14:28:29 +0200 Subject: [PATCH 1004/1296] machinectl: add VM control commands Add new verbs for controlling vmspawn VMs: machinectl poweroff machinectl pause machinectl resume Each verb discovers the machine's varlinkAddress via machined's Machine.List, connects directly to vmspawn's varlink socket, and calls the corresponding io.systemd.MachineInstance method. Signed-off-by: Christian Brauner (Amutable) --- src/machine/machinectl.c | 170 +++++++++++++++++++++++++++++++++++---- 1 file changed, 154 insertions(+), 16 deletions(-) diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index d31cfcbbe5481..e8a30a89592a3 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -9,6 +9,7 @@ #include "sd-bus.h" #include "sd-event.h" #include "sd-journal.h" +#include "sd-varlink.h" #include "alloc-util.h" #include "ask-password-agent.h" @@ -45,6 +46,7 @@ #include "pager.h" #include "parse-argument.h" #include "parse-util.h" +#include "path-lookup.h" #include "path-util.h" #include "pidref.h" #include "polkit-agent.h" @@ -1106,34 +1108,73 @@ static int verb_kill_machine(int argc, char *argv[], uintptr_t _data, void *user return 0; } +static int verb_machine_control_one(const char *machine_name, const char *method); + static int verb_reboot_machine(int argc, char *argv[], uintptr_t data, void *userdata) { - if (arg_runner == RUNNER_VMSPAWN) - return log_error_errno( - SYNTHETIC_ERRNO(EOPNOTSUPP), - "%s only support supported for --runner=nspawn", - streq(argv[0], "reboot") ? "Reboot" : "Restart"); + sd_bus *bus = ASSERT_PTR(userdata); + int r; + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + for (int i = 1; i < argc; i++) { + r = verb_machine_control_one(argv[i], "io.systemd.MachineInstance.Reboot"); + if (r >= 0) + continue; + if (r != -EOPNOTSUPP) + return r; - arg_kill_whom = "leader"; - arg_signal = SIGINT; /* sysvinit + systemd */ + /* Container fallback: SIGINT to init (sysvinit + systemd compatible) */ + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = bus_call_method(bus, bus_machine_mgr, "KillMachine", &error, NULL, + "ssi", argv[i], "leader", (int32_t) SIGINT); + if (r < 0) + return log_error_errno(r, "Could not reboot machine '%s': %s", argv[i], bus_error_message(&error, r)); + } - return verb_kill_machine(argc, argv, data, userdata); + return 0; } static int verb_poweroff_machine(int argc, char *argv[], uintptr_t data, void *userdata) { - arg_kill_whom = "leader"; - arg_signal = SIGRTMIN+4; /* only systemd */ + sd_bus *bus = ASSERT_PTR(userdata); + int r; + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - return verb_kill_machine(argc, argv, data, userdata); + for (int i = 1; i < argc; i++) { + /* VM with varlink control socket: QMP graceful powerdown */ + r = verb_machine_control_one(argv[i], "io.systemd.MachineInstance.PowerOff"); + if (r >= 0) + continue; + if (r != -EOPNOTSUPP) + return r; + + /* Not a VM: signal-based poweroff */ + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = bus_call_method(bus, bus_machine_mgr, "KillMachine", &error, NULL, + "ssi", argv[i], "leader", (int32_t) (SIGRTMIN+4)); + if (r < 0) + return log_error_errno(r, "Could not kill machine: %s", bus_error_message(&error, r)); + } + + return 0; } static int verb_terminate_machine(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); for (int i = 1; i < argc; i++) { + /* VM with varlink control socket: QMP quit (immediate termination) */ + r = verb_machine_control_one(argv[i], "io.systemd.MachineInstance.Terminate"); + if (r >= 0) + continue; + if (r != -EOPNOTSUPP) + return r; + + /* Not a VM or no varlink socket: fall back to machined */ + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; r = bus_call_method(bus, bus_machine_mgr, "TerminateMachine", &error, NULL, "s", argv[i]); if (r < 0) return log_error_errno(r, "Could not terminate machine: %s", bus_error_message(&error, r)); @@ -1142,6 +1183,99 @@ static int verb_terminate_machine(int argc, char *argv[], uintptr_t _data, void return 0; } +/* Look up the controlAddress of a machine by calling machined's Machine.List varlink interface. */ +static int machine_get_control_address(const char *machine_name, char **ret) { + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + sd_json_variant *reply = NULL; + const char *error_id = NULL; + int r; + + assert(machine_name); + assert(ret); + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return -EOPNOTSUPP; + + _cleanup_free_ char *p = NULL; + r = runtime_directory_generic(arg_runtime_scope, "systemd/machine/io.systemd.Machine", &p); + if (r < 0) + return log_error_errno(r, "Failed to determine Machine varlink socket path: %m"); + + r = sd_varlink_connect_address(&vl, p); + if (r < 0) + return log_error_errno(r, "Failed to connect to machined varlink: %m"); + + r = sd_varlink_callbo( + vl, + "io.systemd.Machine.List", + &reply, + &error_id, + SD_JSON_BUILD_PAIR_STRING("name", machine_name)); + if (r < 0) + return log_error_errno(r, "Failed to list machine: %m"); + if (error_id) + return log_error_errno(sd_varlink_error_to_errno(error_id, reply), + "Failed to look up machine '%s': %s", machine_name, error_id); + + sd_json_variant *addr = sd_json_variant_by_key(reply, "controlAddress"); + if (!addr || !sd_json_variant_is_string(addr)) + return -EOPNOTSUPP; /* No varlink control socket, caller decides whether to log */ + + char *a = strdup(sd_json_variant_string(addr)); + if (!a) + return log_oom(); + + *ret = a; + return 0; +} + +static int verb_machine_control_one(const char *machine_name, const char *method) { + _cleanup_free_ char *address = NULL; + int r; + + r = machine_get_control_address(machine_name, &address); + if (r < 0) + return r; + + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + r = sd_varlink_connect_address(&vl, address); + if (r < 0) + return log_error_errno(r, "Failed to connect to machine control socket: %m"); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = sd_varlink_call(vl, method, /* parameters= */ NULL, &reply, &error_id); + if (r < 0) + return log_error_errno(r, "Failed to call %s: %m", method); + if (error_id) + return log_error_errno(sd_varlink_error_to_errno(error_id, reply), + "Machine control call failed: %s", error_id); + + return 0; +} + +static int verb_vm_control(int argc, char *argv[], const char *method) { + int r; + + for (int i = 1; i < argc; i++) { + r = verb_machine_control_one(argv[i], method); + if (r == -EOPNOTSUPP) + return log_error_errno(r, "Machine '%s' does not expose a varlink control socket.", argv[i]); + if (r < 0) + return r; + } + + return 0; +} + +static int verb_pause(int argc, char *argv[], uintptr_t _data, void *userdata) { + return verb_vm_control(argc, argv, "io.systemd.MachineInstance.Pause"); +} + +static int verb_resume(int argc, char *argv[], uintptr_t _data, void *userdata) { + return verb_vm_control(argc, argv, "io.systemd.MachineInstance.Resume"); +} + static const char *select_copy_method(bool copy_from, bool force) { if (force) return copy_from ? "CopyFromMachineWithFlags" : "CopyToMachineWithFlags"; @@ -2077,10 +2211,12 @@ static int help(void) { " or on the local host\n" " enable NAME... Enable automatic container start at boot\n" " disable NAME... Disable automatic container start at boot\n" - " poweroff NAME... Power off one or more containers\n" - " reboot NAME... Reboot one or more containers\n" - " terminate NAME... Terminate one or more VMs/containers\n" - " kill NAME... Send signal to processes of a VM/container\n" + " poweroff NAME... Power off one or more machines\n" + " reboot NAME... Reboot one or more machines\n" + " pause NAME... Pause one or more machines\n" + " resume NAME... Resume one or more paused machines\n" + " terminate NAME... Terminate one or more machines\n" + " kill NAME... Send signal to processes of a machine\n" " copy-to NAME PATH [PATH] Copy files from the host to a container\n" " copy-from NAME PATH [PATH] Copy files from a container to the host\n" " bind NAME PATH [PATH] Bind mount a path from the host into a container\n" @@ -2465,6 +2601,8 @@ static int machinectl_main(int argc, char *argv[], sd_bus *bus) { { "poweroff", 2, VERB_ANY, 0, verb_poweroff_machine }, { "stop", 2, VERB_ANY, 0, verb_poweroff_machine }, /* Convenience alias */ { "kill", 2, VERB_ANY, 0, verb_kill_machine }, + { "pause", 2, VERB_ANY, 0, verb_pause }, + { "resume", 2, VERB_ANY, 0, verb_resume }, { "login", VERB_ANY, 2, 0, verb_login_machine }, { "shell", VERB_ANY, VERB_ANY, 0, verb_shell_machine }, { "bind", 3, 4, 0, verb_bind_mount }, From 3b796616cc8e6413fd693546f83c4a799e27a747 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 2 Apr 2026 16:21:21 +0200 Subject: [PATCH 1005/1296] man: document machinectl pause/resume and update poweroff for VMs Add manpage entries for the new pause and resume verbs. Update the poweroff description to cover VMs (ACPI powerdown via QMP) in addition to containers (SIGRTMIN+4). Signed-off-by: Christian Brauner (Amutable) --- man/machinectl.xml | 53 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/man/machinectl.xml b/man/machinectl.xml index e64a20bb1d045..b4fb15b4f93a3 100644 --- a/man/machinectl.xml +++ b/man/machinectl.xml @@ -262,16 +262,17 @@ poweroff NAME - Power off one or more containers. This will - trigger a shutdown by sending SIGRTMIN+4 to the container's init - process, which causes systemd-compatible init systems to shut - down cleanly. Use stop as alias for poweroff. - This operation does not work on containers that do not run a + Power off one or more machines. For VMs managed by + systemd-vmspawn1, + this sends an ACPI powerdown request via QMP. For containers, this + sends SIGRTMIN+4 to the container's init process, which causes + systemd-compatible init systems to shut down cleanly. This + operation does not work on containers that do not run a systemd1-compatible init system, such as sysvinit. Use - terminate (see below) to immediately - terminate a container or VM, without cleanly shutting it - down. + stop as alias for poweroff. + Use terminate (see below) to immediately + terminate a machine without cleanly shutting it down. @@ -279,16 +280,40 @@ reboot NAME - Reboot one or more containers. This will - trigger a reboot by sending SIGINT to the container's init - process, which is roughly equivalent to pressing Ctrl+Alt+Del - on a non-containerized system, and is compatible with - containers running any system manager. Use restart as alias - for reboot. + Reboot one or more machines. For VMs managed by + systemd-vmspawn1, + this sends a system reset request via QMP. For containers, this + sends SIGINT to the container's init process, which is roughly + equivalent to pressing Ctrl+Alt+Del on a non-containerized + system, and is compatible with containers running any system + manager. Use restart as alias for + reboot. + + pause NAME + + Pause one or more machines. For VMs managed by + systemd-vmspawn1, + this freezes vCPU execution at the hypervisor level — the guest + operating system is not notified and does not observe an ACPI suspend. + From the guest's perspective time simply stops until the machine is + resumed with resume. + + + + + + resume NAME + + Resume one or more previously paused machines. + This restarts execution after a pause. + + + + terminate NAME From e2af53db04abaea0cd105b1a0d04e51f2b0f1793 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 2 Apr 2026 16:21:14 +0200 Subject: [PATCH 1006/1296] shell-completion: add bash/zsh completions for machinectl pause/resume Signed-off-by: Christian Brauner (Amutable) --- shell-completion/bash/machinectl | 2 +- shell-completion/zsh/_machinectl | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/shell-completion/bash/machinectl b/shell-completion/bash/machinectl index 78319d91a419c..50b46fb27925b 100644 --- a/shell-completion/bash/machinectl +++ b/shell-completion/bash/machinectl @@ -45,7 +45,7 @@ _machinectl() { local -A VERBS=( [STANDALONE]='list list-images clean pull-tar pull-raw list-transfers cancel-transfer import-fs' - [MACHINES]='status show start stop login shell enable disable poweroff reboot terminate kill + [MACHINES]='status show start stop login shell enable disable poweroff reboot pause resume terminate kill image-status show-image remove export-tar export-raw' [MACHINES_OR_FILES]='edit cat' [MACHINE_ONLY]='clone rename set-limit' diff --git a/shell-completion/zsh/_machinectl b/shell-completion/zsh/_machinectl index d5f7aa9680d6c..31ddf4fca571d 100644 --- a/shell-completion/zsh/_machinectl +++ b/shell-completion/zsh/_machinectl @@ -38,6 +38,8 @@ "disable:Disable automatic container start at boot" "poweroff:Power off one or more VMs/containers" "reboot:Reboot one or more VMs/containers" + "pause:Pause one or more machines" + "resume:Resume one or more previously paused machines" "terminate:Terminate one or more VMs/containers" "kill:Send signal to process or a VM/container" "copy-to:Copy files from the host to a container" @@ -77,7 +79,7 @@ start|enable|disable) _machinectl_images ;; - status|show|poweroff|reboot|terminate|kill) + status|show|poweroff|reboot|pause|resume|terminate|kill) _sd_machines ;; login|shell) From f8a7931d3939ffb1c3b2506c6732b3782e24524c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 30 Mar 2026 22:17:59 +0200 Subject: [PATCH 1007/1296] vmspawn: add integration test for QMP client library Test the QMP client library using a mock QMP server over a socketpair: - test_qmp_client_basic: Verifies full handshake, query-status with response parsing, stop/cont commands, and asynchronous STOP event delivery via the sd-event I/O callback - test_qmp_client_eof: Verifies that the client properly detects server disconnection (EOF) and returns a disconnect error Signed-off-by: Christian Brauner (Amutable) --- src/test/meson.build | 3 + src/test/test-qmp-client.c | 516 +++++++++++++++++++++++++++++++++++++ 2 files changed, 519 insertions(+) create mode 100644 src/test/test-qmp-client.c diff --git a/src/test/meson.build b/src/test/meson.build index fb0aac27f8864..5fcf007f70342 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -431,6 +431,9 @@ executables += [ test_template + { 'sources' : files('test-progress-bar.c'), }, + test_template + { + 'sources' : files('test-qmp-client.c'), + }, test_template + { 'sources' : files('test-qrcode-util.c'), 'dependencies' : libdl, diff --git a/src/test/test-qmp-client.c b/src/test/test-qmp-client.c new file mode 100644 index 0000000000000..9bfe48770f798 --- /dev/null +++ b/src/test/test-qmp-client.c @@ -0,0 +1,516 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "sd-event.h" +#include "sd-json.h" + +#include "errno-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "pidref.h" +#include "process-util.h" +#include "qmp-client.h" +#include "socket-util.h" +#include "string-util.h" +#include "tests.h" + +/* Mock QMP server: runs in the child process of a fork, communicates via one end of a socketpair. */ + +static void mock_qmp_write_json(int fd, sd_json_variant *v) { + _cleanup_free_ char *s = NULL; + + ASSERT_OK(sd_json_variant_format(v, 0, &s)); + ASSERT_NOT_NULL(strextend(&s, "\r\n")); + ASSERT_OK(loop_write(fd, s, SIZE_MAX)); +} + +static void mock_qmp_write_literal(int fd, const char *msg) { + ASSERT_OK(loop_write(fd, msg, SIZE_MAX)); + ASSERT_OK(loop_write(fd, "\r\n", 2)); +} + +/* Read a command from the QMP client, verify it contains the expected command name, extract the id, + * and send a reply with that id. If reply_data is NULL, an empty return object is sent. */ +static void mock_qmp_expect_and_reply(int fd, const char *expected_command, sd_json_variant *reply_data) { + _cleanup_free_ char *buf = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *cmd = NULL, *reply_obj = NULL, *response = NULL; + + buf = ASSERT_NOT_NULL(new(char, 4096)); + + ssize_t n = read(fd, buf, 4095); + assert_se(n > 0); + buf[n] = '\0'; + + ASSERT_OK(sd_json_parse(buf, 0, &cmd, NULL, NULL)); + + sd_json_variant *execute = ASSERT_NOT_NULL(sd_json_variant_by_key(cmd, "execute")); + ASSERT_STREQ(sd_json_variant_string(execute), expected_command); + + sd_json_variant *id = ASSERT_NOT_NULL(sd_json_variant_by_key(cmd, "id")); + + if (!reply_data) + ASSERT_OK(sd_json_variant_new_object(&reply_obj, NULL, 0)); + + ASSERT_OK(sd_json_buildo( + &response, + SD_JSON_BUILD_PAIR("return", SD_JSON_BUILD_VARIANT(reply_data ?: reply_obj)), + SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_VARIANT(id)))); + + mock_qmp_write_json(fd, response); +} + +static _noreturn_ void mock_qmp_server(int fd) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *status_return = NULL; + + /* Send QMP greeting */ + mock_qmp_write_literal(fd, + "{\"QMP\": {\"version\": {\"qemu\": {\"micro\": 0, \"minor\": 2, \"major\": 9}}, \"capabilities\": [\"oob\"]}}"); + + /* Accept qmp_capabilities */ + mock_qmp_expect_and_reply(fd, "qmp_capabilities", NULL); + + /* Accept query-status, reply with running state */ + ASSERT_OK(sd_json_buildo( + &status_return, + SD_JSON_BUILD_PAIR_BOOLEAN("running", true), + SD_JSON_BUILD_PAIR_STRING("status", "running"))); + mock_qmp_expect_and_reply(fd, "query-status", status_return); + + /* Accept stop */ + mock_qmp_expect_and_reply(fd, "stop", NULL); + + /* Send a STOP event */ + mock_qmp_write_literal(fd, + "{\"event\": \"STOP\", \"timestamp\": {\"seconds\": 1234, \"microseconds\": 5678}}"); + + /* Accept cont */ + mock_qmp_expect_and_reply(fd, "cont", NULL); + + /* Close to trigger EOF */ + safe_close(fd); + _exit(EXIT_SUCCESS); +} + +/* Test helper: tracks an async QMP command result and signals completion. */ +typedef struct { + sd_json_variant *result; + char *error_desc; + int error; + bool done; +} QmpTestResult; + +static int on_test_result( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + QmpTestResult *t = ASSERT_PTR(userdata); + + t->error = error; + if (result) + t->result = sd_json_variant_ref(result); + if (error_desc) + t->error_desc = strdup(error_desc); + t->done = true; + return 0; +} + +/* Run the event loop until the test result callback fires. */ +static void qmp_test_wait(sd_event *event, QmpTestResult *t) { + assert(event); + assert(t); + + while (!t->done) + ASSERT_OK(sd_event_run(event, UINT64_MAX)); +} + +static void qmp_test_result_done(QmpTestResult *t) { + assert(t); + + sd_json_variant_unref(t->result); + free(t->error_desc); + *t = (QmpTestResult) {}; +} + +static int test_event_callback( + QmpClient *client, + const char *event, + sd_json_variant *data, + void *userdata) { + + bool *event_received = ASSERT_PTR(userdata); + + /* We may also receive a synthetic SHUTDOWN event when the mock server closes the connection; + * only validate the STOP event we actually care about. */ + if (streq(event, "STOP")) + *event_received = true; + + return 0; +} + +TEST(qmp_client_basic) { + _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(pidref_done) PidRef pid = PIDREF_NULL; + QmpTestResult t = {}; + sd_json_variant *running, *status; + int qmp_fds[2]; + int r; + + ASSERT_OK(sd_event_new(&event)); + + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + + r = pidref_safe_fork("(mock-qmp)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid); + ASSERT_OK(r); + + if (r == 0) { + safe_close(qmp_fds[0]); + mock_qmp_server(qmp_fds[1]); + } + + safe_close(qmp_fds[1]); + + /* Connect then attach to event loop — handshake completes transparently + * inside the first call()/invoke(). */ + ASSERT_OK(qmp_client_connect_fd(&client, qmp_fds[0])); + ASSERT_OK(qmp_client_attach_event(client, event, SD_EVENT_PRIORITY_NORMAL)); + + /* Set event callback to catch STOP event during cont */ + bool event_received = false; + qmp_client_bind_event(client, test_event_callback, &event_received); + + /* Execute query-status */ + ASSERT_OK(qmp_client_invoke(client, "query-status", NULL, on_test_result, &t)); + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + ASSERT_NOT_NULL(t.result); + + running = ASSERT_NOT_NULL(sd_json_variant_by_key(t.result, "running")); + ASSERT_TRUE(sd_json_variant_boolean(running)); + + status = ASSERT_NOT_NULL(sd_json_variant_by_key(t.result, "status")); + ASSERT_STREQ(sd_json_variant_string(status), "running"); + + qmp_test_result_done(&t); + + /* Execute stop */ + ASSERT_OK(qmp_client_invoke(client, "stop", NULL, on_test_result, &t)); + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + qmp_test_result_done(&t); + + /* Execute cont -- the STOP event should be dispatched by the IO callback */ + ASSERT_OK(qmp_client_invoke(client, "cont", NULL, on_test_result, &t)); + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + qmp_test_result_done(&t); + + /* Verify the STOP event was received */ + ASSERT_TRUE(event_received); + + /* Wait for child and verify clean exit */ + siginfo_t si = {}; + ASSERT_OK(pidref_wait_for_terminate(&pid, &si)); + ASSERT_EQ(si.si_code, CLD_EXITED); + ASSERT_EQ(si.si_status, EXIT_SUCCESS); +} + +TEST(qmp_client_eof) { + _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(pidref_done) PidRef pid = PIDREF_NULL; + QmpTestResult t = {}; + int qmp_fds[2]; + int r; + + ASSERT_OK(sd_event_new(&event)); + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + + r = pidref_safe_fork("(mock-qmp-eof)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid); + ASSERT_OK(r); + + if (r == 0) { + safe_close(qmp_fds[0]); + + /* Send greeting and accept capabilities, then die */ + mock_qmp_write_literal(qmp_fds[1], + "{\"QMP\": {\"version\": {\"qemu\": {\"micro\": 0, \"minor\": 0, \"major\": 9}}, \"capabilities\": []}}"); + + mock_qmp_expect_and_reply(qmp_fds[1], "qmp_capabilities", NULL); + + /* Close immediately to trigger EOF */ + safe_close(qmp_fds[1]); + _exit(EXIT_SUCCESS); + } + + safe_close(qmp_fds[1]); + + ASSERT_OK(qmp_client_connect_fd(&client, qmp_fds[0])); + ASSERT_OK(qmp_client_attach_event(client, event, SD_EVENT_PRIORITY_NORMAL)); + + /* Executing a command should fail with a disconnect error because the server + * closed. The handshake may succeed or fail inside invoke() — either way the + * invoke itself or the async callback should report a disconnect. */ + r = qmp_client_invoke(client, "query-status", NULL, on_test_result, &t); + if (r < 0) + ASSERT_TRUE(ERRNO_IS_NEG_DISCONNECT(r)); + else { + qmp_test_wait(event, &t); + ASSERT_TRUE(ERRNO_IS_NEG_DISCONNECT(t.error)); + qmp_test_result_done(&t); + } + + siginfo_t si = {}; + ASSERT_OK(pidref_wait_for_terminate(&pid, &si)); + ASSERT_EQ(si.si_code, CLD_EXITED); + ASSERT_EQ(si.si_status, EXIT_SUCCESS); +} + +/* Read one QMP command from fd (one recvmsg, expecting it fits in the buffer for typical + * test commands). Returns the number of SCM_RIGHTS fds that arrived attached to the read, + * stores the first received fd in *ret_received_fd (or -EBADF if none) and closes any extras, + * and parses the JSON into *ret_cmd. */ +static size_t mock_qmp_recv_command(int fd, sd_json_variant **ret_cmd, int *ret_received_fd) { + char buf[4096]; + char ctrl[CMSG_SPACE(sizeof(int) * 4)]; + struct iovec iov = { .iov_base = buf, .iov_len = sizeof(buf) - 1 }; + struct msghdr mh = { + .msg_iov = &iov, .msg_iovlen = 1, + .msg_control = ctrl, .msg_controllen = sizeof(ctrl), + }; + size_t n_fds = 0; + int received_fd = -EBADF; + + ssize_t n = recvmsg(fd, &mh, MSG_CMSG_CLOEXEC); + assert_se(n > 0); + buf[n] = '\0'; + + struct cmsghdr *cmsg; + CMSG_FOREACH(cmsg, &mh) { + if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) + continue; + size_t k = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + int *fds = (int*) CMSG_DATA(cmsg); + for (size_t i = 0; i < k; i++) { + if (received_fd < 0) + received_fd = fds[i]; + else + safe_close(fds[i]); + } + n_fds += k; + } + + ASSERT_OK(sd_json_parse(buf, 0, ret_cmd, NULL, NULL)); + + if (ret_received_fd) + *ret_received_fd = received_fd; + else if (received_fd >= 0) + safe_close(received_fd); + + return n_fds; +} + +/* Mock QMP server for the fd-on-first-invoke regression. Drives the wire dance: + * greeting → (recv qmp_capabilities, expect 0 fds) → reply → + * (recv add-fd, expect exactly 1 fd) → reply + * Asserts the cmsg fd counts directly so a regression flips the child to + * exit_failure and the parent test fails on the wait-for-terminate. */ +static _noreturn_ void mock_qmp_server_fd_first(int fd) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *cap_cmd = NULL, + *addfd_cmd = NULL, + *cap_reply = NULL, + *addfd_return = NULL, + *addfd_reply = NULL; + size_t n_fds; + int received_fd = -EBADF; + + /* Greeting */ + mock_qmp_write_literal(fd, + "{\"QMP\": {\"version\": {\"qemu\": {\"micro\": 0, \"minor\": 0, \"major\": 9}}, \"capabilities\": []}}"); + + /* Receive qmp_capabilities — must arrive with NO fds attached. */ + n_fds = mock_qmp_recv_command(fd, &cap_cmd, /* ret_received_fd= */ NULL); + ASSERT_EQ(n_fds, (size_t) 0); + ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(cap_cmd, "execute")), "qmp_capabilities"); + + sd_json_variant *cap_id = ASSERT_NOT_NULL(sd_json_variant_by_key(cap_cmd, "id")); + ASSERT_OK(sd_json_buildo( + &cap_reply, + SD_JSON_BUILD_PAIR("return", SD_JSON_BUILD_EMPTY_OBJECT), + SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_VARIANT(cap_id)))); + mock_qmp_write_json(fd, cap_reply); + + /* Receive add-fd — must arrive with EXACTLY ONE fd attached. */ + n_fds = mock_qmp_recv_command(fd, &addfd_cmd, &received_fd); + ASSERT_EQ(n_fds, (size_t) 1); + ASSERT_TRUE(received_fd >= 0); + ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(addfd_cmd, "execute")), "add-fd"); + safe_close(received_fd); + + sd_json_variant *addfd_id = ASSERT_NOT_NULL(sd_json_variant_by_key(addfd_cmd, "id")); + ASSERT_OK(sd_json_buildo( + &addfd_return, + SD_JSON_BUILD_PAIR_UNSIGNED("fdset-id", 0), + SD_JSON_BUILD_PAIR_UNSIGNED("fd", 42))); + ASSERT_OK(sd_json_buildo( + &addfd_reply, + SD_JSON_BUILD_PAIR("return", SD_JSON_BUILD_VARIANT(addfd_return)), + SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_VARIANT(addfd_id)))); + mock_qmp_write_json(fd, addfd_reply); + + safe_close(fd); + _exit(EXIT_SUCCESS); +} + +/* Regression: pass an fd in the very first qmp_client_invoke() against a fresh client + * (lazy-bootstrap state, handshake not yet done). The previous push_fd+invoke split would + * stage the fd on the stream BEFORE qmp_client_ensure_running() drove the handshake; the + * handshake's qmp_capabilities enqueue would then steal the staged fd onto its own + * sendmsg. The new QmpClientArgs API stages fds inside invoke AFTER ensure_running, so + * the fd lands on add-fd's sendmsg as it should. */ +TEST(qmp_client_first_invoke_with_fd) { + _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(pidref_done) PidRef pid = PIDREF_NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + _cleanup_close_ int fd_to_pass = -EBADF; + QmpTestResult t = {}; + int qmp_fds[2]; + int r; + + ASSERT_OK(sd_event_new(&event)); + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + + r = pidref_safe_fork("(mock-qmp-fd-first)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid); + ASSERT_OK(r); + + if (r == 0) { + safe_close(qmp_fds[0]); + mock_qmp_server_fd_first(qmp_fds[1]); + } + + safe_close(qmp_fds[1]); + + /* Open a real fd to pass — /dev/null is universally available. */ + fd_to_pass = open("/dev/null", O_RDWR|O_CLOEXEC); + ASSERT_OK(fd_to_pass); + + ASSERT_OK(qmp_client_connect_fd(&client, qmp_fds[0])); + ASSERT_OK(qmp_client_attach_event(client, event, SD_EVENT_PRIORITY_NORMAL)); + + /* Build add-fd args. The fdset-id value is irrelevant — the mock server only + * cares that the fd arrived with the correct sendmsg. */ + ASSERT_OK(sd_json_buildo(&args, SD_JSON_BUILD_PAIR_UNSIGNED("fdset-id", 0))); + + /* THIS is the previously-broken pattern: very first invoke against the client, + * carrying an fd, with the handshake still pending. */ + ASSERT_OK(qmp_client_invoke(client, "add-fd", + QMP_CLIENT_ARGS_FD(args, TAKE_FD(fd_to_pass)), + on_test_result, &t)); + + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + ASSERT_NOT_NULL(t.result); + qmp_test_result_done(&t); + + /* Wait for the mock server child. If it received fds in the wrong order it + * exited via the test-assertion failure path and si.si_status will be non-zero. */ + siginfo_t si = {}; + ASSERT_OK(pidref_wait_for_terminate(&pid, &si)); + ASSERT_EQ(si.si_code, CLD_EXITED); + ASSERT_EQ(si.si_status, EXIT_SUCCESS); +} + +/* Regression: when qmp_client_invoke() fails before stage_fds runs (e.g. + * ensure_running() returns -ENOTCONN because the peer closed mid-handshake), the + * caller-supplied fds — already TAKE_FD()'d through QMP_CLIENT_ARGS_FD() — must be + * closed inside invoke. Otherwise they leak. */ +TEST(qmp_client_invoke_failure_closes_fds) { + _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + _cleanup_close_ int fd_to_pass = -EBADF; + QmpTestResult t = {}; + int qmp_fds[2]; + int saved_fd_value; + + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + + /* Close the peer end immediately so ensure_running()'s read sees EOF and + * the client transitions straight to DISCONNECTED inside the first invoke. */ + safe_close(qmp_fds[1]); + + ASSERT_OK(qmp_client_connect_fd(&client, qmp_fds[0])); + /* Deliberately do NOT attach to an event loop — invoke uses ensure_running()'s + * synchronous process+wait pump for the handshake. */ + + fd_to_pass = open("/dev/null", O_RDWR|O_CLOEXEC); + ASSERT_OK(fd_to_pass); + saved_fd_value = fd_to_pass; /* remember the int value for the closed-check */ + + ASSERT_OK(sd_json_buildo(&args, SD_JSON_BUILD_PAIR_UNSIGNED("fdset-id", 0))); + + /* invoke must fail because the peer is gone. The TAKE_FD inside the macro + * has already zeroed our local fd_to_pass; if invoke leaked the fd here, + * the fd would stay open in our process. */ + int r = qmp_client_invoke(client, "add-fd", + QMP_CLIENT_ARGS_FD(args, TAKE_FD(fd_to_pass)), + on_test_result, &t); + ASSERT_TRUE(r < 0); + ASSERT_TRUE(ERRNO_IS_NEG_DISCONNECT(r)); + + /* fd_to_pass should now be -EBADF (TAKE_FD'd) and the underlying kernel fd + * should have been closed by the qmp_client_args_close_fds cleanup in + * qmp_client_invoke(). fcntl on the old int returns EBADF only if the slot + * is genuinely free. */ + ASSERT_EQ(fd_to_pass, -EBADF); + ASSERT_EQ(fcntl(saved_fd_value, F_GETFD), -1); + ASSERT_EQ(errno, EBADF); +} + +TEST(qmp_schema_has_member) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *schema = NULL; + + /* QEMU introspection uses opaque numeric type ids ("0", "1", ...) — only member names are + * the actual QAPI strings. Verify we walk all object entries and find the member by name. */ + ASSERT_OK(sd_json_build(&schema, + SD_JSON_BUILD_ARRAY( + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("name", "0"), + SD_JSON_BUILD_PAIR_STRING("meta-type", "object"), + SD_JSON_BUILD_PAIR("members", SD_JSON_BUILD_ARRAY( + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("name", "offset"), + SD_JSON_BUILD_PAIR_STRING("type", "int"))))), + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("name", "SomeEnum"), + SD_JSON_BUILD_PAIR_STRING("meta-type", "enum")), + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("name", "1"), + SD_JSON_BUILD_PAIR_STRING("meta-type", "object"), + SD_JSON_BUILD_PAIR("members", SD_JSON_BUILD_ARRAY( + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("name", "lazy-refcounts"), + SD_JSON_BUILD_PAIR_STRING("type", "bool")), + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("name", "discard-no-unref"), + SD_JSON_BUILD_PAIR_STRING("type", "bool")))))))); + + ASSERT_TRUE(qmp_schema_has_member(schema, "discard-no-unref")); + ASSERT_TRUE(qmp_schema_has_member(schema, "offset")); + ASSERT_FALSE(qmp_schema_has_member(schema, "definitely-not-a-real-field")); + ASSERT_FALSE(qmp_schema_has_member(NULL, "discard-no-unref")); +} + +static int intro(void) { + /* Ignore SIGPIPE so that write() to a closed socket returns EPIPE instead of killing us */ + ASSERT_TRUE(signal(SIGPIPE, SIG_IGN) != SIG_ERR); + return 0; +} + +DEFINE_TEST_MAIN_FULL(LOG_DEBUG, intro, NULL); From 435097c6da04afc2f7717ae3122d7dc8594ac914 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 30 Mar 2026 15:04:42 +0200 Subject: [PATCH 1008/1296] vmspawn: add integration test for QMP client library against real QEMU Add a test that launches QEMU with -machine none (no bootable image needed) and exercises the QMP client library against the real QMP implementation: - test_qmp_client_qemu_handshake_and_schema: sends query-qmp-schema (~200KB response that exercises the buffered multi-read() path) via qmp_client_invoke(), then cleanly shuts down QEMU via quit. The QMP handshake completes transparently inside invoke(). - test_qmp_client_qemu_query_status: validates query-status response parsing, stop/cont command sequencing with id correlation, and state verification between commands The test is automatically skipped when QEMU is not installed. Signed-off-by: Christian Brauner (Amutable) --- src/test/meson.build | 5 + src/test/test-qmp-client-qemu.c | 264 ++++++++++++++++++++++++++++++++ 2 files changed, 269 insertions(+) create mode 100644 src/test/test-qmp-client-qemu.c diff --git a/src/test/meson.build b/src/test/meson.build index 5fcf007f70342..87ea6ae15ef9a 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -434,6 +434,11 @@ executables += [ test_template + { 'sources' : files('test-qmp-client.c'), }, + test_template + { + 'sources' : files('test-qmp-client-qemu.c'), + 'objects' : ['systemd-vmspawn'], + 'conditions' : ['ENABLE_VMSPAWN'], + }, test_template + { 'sources' : files('test-qrcode-util.c'), 'dependencies' : libdl, diff --git a/src/test/test-qmp-client-qemu.c b/src/test/test-qmp-client-qemu.c new file mode 100644 index 0000000000000..ec520cc270a04 --- /dev/null +++ b/src/test/test-qmp-client-qemu.c @@ -0,0 +1,264 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* Integration test for the QMP client library against a real QEMU instance. + * + * Launches QEMU with -machine none (no bootable image needed) to get a live QMP monitor, then exercises the + * client library against it. Validates the blocking handshake, large response buffering (~200KB for + * query-qmp-schema), response correlation by id, and async command execution. + * + * Skipped automatically if QEMU is not installed. */ + +#include +#include + +#include "sd-event.h" +#include "sd-json.h" + +#include "fd-util.h" +#include "pidref.h" +#include "process-util.h" +#include "qmp-client.h" +#include "string-util.h" +#include "tests.h" +#include "time-util.h" +#include "vmspawn-util.h" + +static int start_qemu(const char *qemu_binary, int fd, PidRef *ret) { + _cleanup_free_ char *chardev_arg = NULL; + int r; + + assert(qemu_binary); + assert(fd >= 0); + assert(ret); + + if (asprintf(&chardev_arg, "socket,id=qmp,fd=%d", fd) < 0) + return -ENOMEM; + + r = pidref_safe_fork_full( + "(qemu)", + (const int[3]) { STDIN_FILENO, -EBADF, -EBADF }, + &fd, 1, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE|FORK_CLOEXEC_OFF, + ret); + if (r < 0) + return r; + if (r == 0) { + /* Child */ + execl(qemu_binary, qemu_binary, + "-machine", "none", + "-nographic", + "-nodefaults", + "-chardev", chardev_arg, + "-mon", "chardev=qmp,mode=control", + NULL); + log_error_errno(errno, "Failed to exec %s: %m", qemu_binary); + _exit(EXIT_FAILURE); + } + + return 0; +} + +/* Test helper: tracks an async QMP command result and signals completion. */ +typedef struct { + sd_json_variant *result; + int error; + bool done; +} QmpTestResult; + +static int on_test_result( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + QmpTestResult *t = ASSERT_PTR(userdata); + + t->error = error; + if (result) + t->result = sd_json_variant_ref(result); + t->done = true; + return 0; +} + +static void qmp_test_wait(sd_event *event, QmpTestResult *t) { + assert(event); + assert(t); + + usec_t deadline = usec_add(now(CLOCK_MONOTONIC), 5 * USEC_PER_MINUTE); + + while (!t->done) { + usec_t n = now(CLOCK_MONOTONIC); + ASSERT_LT(n, deadline); + ASSERT_OK(sd_event_run(event, usec_sub_unsigned(deadline, n))); + } +} + +static void qmp_test_result_done(QmpTestResult *t) { + assert(t); + + sd_json_variant_unref(t->result); + *t = (QmpTestResult) {}; +} + +TEST(qmp_client_qemu_handshake_and_schema) { + _cleanup_free_ char *qemu = NULL; + _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL; + QmpTestResult t = {}; + _cleanup_close_pair_ int qmp_fds[2] = EBADF_PAIR; + int r; + + if (find_qemu_binary(&qemu) < 0) { + log_tests_skipped("QEMU not found"); + return; + } + log_info("Using QEMU: %s", qemu); + + ASSERT_OK(sd_event_new(&event)); + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + + ASSERT_OK(start_qemu(qemu, qmp_fds[1], &pidref)); + qmp_fds[1] = safe_close(qmp_fds[1]); + + r = qmp_client_connect_fd(&client, qmp_fds[0]); + if (r < 0) { + log_tests_skipped_errno(r, "QMP connect failed (QEMU may not support -machine none)"); + return; + } + TAKE_FD(qmp_fds[0]); + + ASSERT_OK(qmp_client_attach_event(client, event, SD_EVENT_PRIORITY_NORMAL)); + + /* query-qmp-schema returns ~200KB -- validates the buffered reader handles large multi-read() + * responses correctly. The handshake completes transparently inside invoke(). */ + r = qmp_client_invoke(client, "query-qmp-schema", NULL, on_test_result, &t); + if (r < 0) { + log_tests_skipped_errno(r, "QMP invoke failed (handshake or send)"); + return; + } + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + ASSERT_NOT_NULL(t.result); + ASSERT_TRUE(sd_json_variant_is_array(t.result)); + ASSERT_GT(sd_json_variant_elements(t.result), (size_t) 0); + log_info("query-qmp-schema returned %zu entries", sd_json_variant_elements(t.result)); + + /* Smoke-test the schema walker against the real schema. node-name is on every BlockdevOptions* + * object since blockdev-add was introduced. Don't assert discard-no-unref — CI may have QEMU < 8.1. */ + ASSERT_TRUE(qmp_schema_has_member(t.result, "node-name")); + ASSERT_FALSE(qmp_schema_has_member(t.result, "definitely-not-a-real-field")); + + qmp_test_result_done(&t); + + /* Clean shutdown */ + ASSERT_OK(qmp_client_invoke(client, "quit", NULL, on_test_result, &t)); + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + qmp_test_result_done(&t); + + siginfo_t si = {}; + ASSERT_OK(pidref_wait_for_terminate(&pidref, &si)); + ASSERT_EQ(si.si_code, CLD_EXITED); + ASSERT_EQ(si.si_status, EXIT_SUCCESS); + pidref_done(&pidref); +} + +TEST(qmp_client_qemu_query_status) { + _cleanup_free_ char *qemu = NULL; + _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL; + QmpTestResult t = {}; + sd_json_variant *running, *status; + _cleanup_close_pair_ int qmp_fds[2] = EBADF_PAIR; + int r; + + if (find_qemu_binary(&qemu) < 0) { + log_tests_skipped("QEMU not found"); + return; + } + + ASSERT_OK(sd_event_new(&event)); + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + + ASSERT_OK(start_qemu(qemu, qmp_fds[1], &pidref)); + qmp_fds[1] = safe_close(qmp_fds[1]); + + r = qmp_client_connect_fd(&client, qmp_fds[0]); + if (r < 0) { + log_tests_skipped_errno(r, "QMP connect failed"); + return; + } + TAKE_FD(qmp_fds[0]); + + ASSERT_OK(qmp_client_attach_event(client, event, SD_EVENT_PRIORITY_NORMAL)); + + /* query-status validates response parsing against real QEMU output format. + * The handshake completes transparently inside invoke(). */ + r = qmp_client_invoke(client, "query-status", NULL, on_test_result, &t); + if (r < 0) { + log_tests_skipped_errno(r, "QMP invoke failed (handshake or send)"); + return; + } + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + ASSERT_NOT_NULL(t.result); + + status = ASSERT_NOT_NULL(sd_json_variant_by_key(t.result, "status")); + ASSERT_TRUE(sd_json_variant_is_string(status)); + + running = ASSERT_NOT_NULL(sd_json_variant_by_key(t.result, "running")); + ASSERT_TRUE(sd_json_variant_is_boolean(running)); + + log_info("QEMU status: %s, running: %s", + sd_json_variant_string(status), + true_false(sd_json_variant_boolean(running))); + + qmp_test_result_done(&t); + + /* Test stop + cont to exercise command sequencing and id correlation */ + ASSERT_OK(qmp_client_invoke(client, "stop", NULL, on_test_result, &t)); + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + qmp_test_result_done(&t); + + /* Verify status changed */ + ASSERT_OK(qmp_client_invoke(client, "query-status", NULL, on_test_result, &t)); + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + ASSERT_NOT_NULL(t.result); + + running = ASSERT_NOT_NULL(sd_json_variant_by_key(t.result, "running")); + ASSERT_FALSE(sd_json_variant_boolean(running)); + log_info("After stop: running=%s", true_false(sd_json_variant_boolean(running))); + + qmp_test_result_done(&t); + + ASSERT_OK(qmp_client_invoke(client, "cont", NULL, on_test_result, &t)); + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + qmp_test_result_done(&t); + + /* Clean shutdown */ + ASSERT_OK(qmp_client_invoke(client, "quit", NULL, on_test_result, &t)); + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + qmp_test_result_done(&t); + + siginfo_t si = {}; + ASSERT_OK(pidref_wait_for_terminate(&pidref, &si)); + ASSERT_EQ(si.si_code, CLD_EXITED); + ASSERT_EQ(si.si_status, EXIT_SUCCESS); + pidref_done(&pidref); +} + +static int intro(void) { + /* QEMU dies between our last write and read on the QMP socket — without this we'd + * get killed by the SIGPIPE the kernel raises on write-after-EOF. */ + ASSERT_TRUE(signal(SIGPIPE, SIG_IGN) != SIG_ERR); + return 0; +} + +DEFINE_TEST_MAIN_FULL(LOG_DEBUG, intro, NULL); From d32bc9cd07e61e852c624f228dda8669f540bf6d Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 30 Mar 2026 15:15:17 +0200 Subject: [PATCH 1009/1296] vmspawn: add integration test for machinectl VM control verbs Add TEST-87-AUX-UTILS-VM.vmspawn.sh that validates the QMP-varlink bridge end-to-end using a real QEMU instance: - Launches vmspawn with --directory and --linux for direct kernel boot (no UEFI firmware or bootable image needed) - Waits for machine registration with machined - Verifies varlinkAddress is exposed in Machine.List - Tests machinectl pause, resume, poweroff - Exercises MachineInstance varlink interface directly via varlinkctl: QueryStatus state verification across pause/resume, Pause, Resume Skipped automatically if vmspawn, QEMU, or a bootable kernel is not available. Runs as part of TEST-87-AUX-UTILS-VM in the mkosi integration test suite. Signed-off-by: Christian Brauner (Amutable) --- mkosi/mkosi.conf.d/arch/mkosi.conf | 1 + mkosi/mkosi.conf.d/centos-fedora/mkosi.conf | 1 + mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf | 1 + mkosi/mkosi.conf.d/opensuse/mkosi.conf | 1 + test/units/TEST-87-AUX-UTILS-VM.vmspawn.sh | 319 ++++++++++++++++++++ test/units/util.sh | 33 ++ 6 files changed, 356 insertions(+) create mode 100755 test/units/TEST-87-AUX-UTILS-VM.vmspawn.sh diff --git a/mkosi/mkosi.conf.d/arch/mkosi.conf b/mkosi/mkosi.conf.d/arch/mkosi.conf index 73001894c2214..229cc6394b172 100644 --- a/mkosi/mkosi.conf.d/arch/mkosi.conf +++ b/mkosi/mkosi.conf.d/arch/mkosi.conf @@ -55,3 +55,4 @@ Packages= tpm2-tools # kernel-bootcfg --add-uri= is just too useful virt-firmware + virtiofsd diff --git a/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf b/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf index 739b2d94eb4b5..dd00fa737cfa9 100644 --- a/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf +++ b/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf @@ -70,3 +70,4 @@ Packages= vim-common # kernel-bootcfg --add-uri= is just too useful virt-firmware + virtiofsd diff --git a/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf b/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf index 80dc87213a4eb..8a4e534ddad60 100644 --- a/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf +++ b/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf @@ -75,3 +75,4 @@ Packages= tgt tpm2-tools tzdata + virtiofsd diff --git a/mkosi/mkosi.conf.d/opensuse/mkosi.conf b/mkosi/mkosi.conf.d/opensuse/mkosi.conf index 295ed53c5893d..b0593e3f1ab9d 100644 --- a/mkosi/mkosi.conf.d/opensuse/mkosi.conf +++ b/mkosi/mkosi.conf.d/opensuse/mkosi.conf @@ -91,5 +91,6 @@ Packages= veritysetup # kernel-bootcfg --add-uri= is just too useful virt-firmware + virtiofsd xz zypper diff --git a/test/units/TEST-87-AUX-UTILS-VM.vmspawn.sh b/test/units/TEST-87-AUX-UTILS-VM.vmspawn.sh new file mode 100755 index 0000000000000..52de5b2f208f9 --- /dev/null +++ b/test/units/TEST-87-AUX-UTILS-VM.vmspawn.sh @@ -0,0 +1,319 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# Test vmspawn QMP-varlink bridge and machinectl VM control verbs. +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +if [[ -v ASAN_OPTIONS ]]; then + echo "vmspawn launches QEMU which doesn't work under ASan, skipping" + exit 0 +fi + +if ! command -v systemd-vmspawn >/dev/null 2>&1; then + echo "systemd-vmspawn not found, skipping" + exit 0 +fi + +if ! find_qemu_binary; then + echo "QEMU not found, skipping" + exit 0 +fi + +# --directory= needs virtiofsd (on Fedora it lives in /usr/libexec, not in PATH) +if ! command -v virtiofsd >/dev/null 2>&1 && + ! test -x /usr/libexec/virtiofsd && + ! test -x /usr/lib/virtiofsd; then + echo "virtiofsd not found, skipping" + exit 0 +fi + +# Find a kernel for direct boot +KERNEL="" +for k in /usr/lib/modules/"$(uname -r)"/vmlinuz /boot/vmlinuz-"$(uname -r)" /boot/vmlinuz; do + if [[ -f "$k" ]]; then + KERNEL="$k" + break + fi +done + +if [[ -z "$KERNEL" ]]; then + echo "No kernel found for direct VM boot, skipping" + exit 0 +fi +echo "Using kernel: $KERNEL" + +MACHINE="test-vmspawn-qmp-$$" +WORKDIR="$(mktemp -d)" + +at_exit() { + set +e + + for m in "$MACHINE" "${MACHINE2:-}" "${STRESS_MACHINE:-}"; do + [[ -n "$m" ]] || continue + if machinectl status "$m" &>/dev/null; then + machinectl terminate "$m" 2>/dev/null + timeout 10 bash -c "while machinectl status '$m' &>/dev/null; do sleep .5; done" 2>/dev/null + fi + done + + [[ -n "${SUBSCRIBE_ALL_PID:-}" ]] && kill "$SUBSCRIBE_ALL_PID" 2>/dev/null && wait "$SUBSCRIBE_ALL_PID" 2>/dev/null + [[ -n "${SUBSCRIBE_FILTER_PID:-}" ]] && kill "$SUBSCRIBE_FILTER_PID" 2>/dev/null && wait "$SUBSCRIBE_FILTER_PID" 2>/dev/null + [[ -n "${STRESS_PID:-}" ]] && kill "$STRESS_PID" 2>/dev/null && wait "$STRESS_PID" 2>/dev/null + [[ -n "${VMSPAWN_PID:-}" ]] && kill "$VMSPAWN_PID" 2>/dev/null && wait "$VMSPAWN_PID" 2>/dev/null + [[ -n "${VMSPAWN2_PID:-}" ]] && kill "$VMSPAWN2_PID" 2>/dev/null && wait "$VMSPAWN2_PID" 2>/dev/null + rm -rf "$WORKDIR" +} +trap at_exit EXIT + +# Create a minimal root filesystem. The guest does not need to fully boot -- we only need QEMU running +# with QMP. A trivial init that sleeps is sufficient. +mkdir -p "$WORKDIR/root/sbin" +cat >"$WORKDIR/root/sbin/init" <<'EOF' +#!/bin/sh +exec sleep infinity +EOF +chmod +x "$WORKDIR/root/sbin/init" + +# Wait for a vmspawn machine to register with machined. +# Skips the test gracefully if vmspawn fails due to missing vhost-user-fs support (nested VM). +wait_for_machine() { + local machine="$1" pid="$2" log="$3" + timeout 30 bash -c " + while ! machinectl list --no-legend 2>/dev/null | grep >/dev/null '$machine'; do + if ! kill -0 $pid 2>/dev/null; then + if grep >/dev/null 'virtiofs.*QMP\|vhost-user-fs-pci' '$log'; then + echo 'vhost-user-fs not supported (nested VM?), skipping' + exit 77 + fi + echo 'vmspawn exited before registering' + cat '$log' + exit 1 + fi + sleep .5 + done + " || { + local rc=$? + if [[ $rc -eq 77 ]]; then exit 0; fi + exit "$rc" + } +} + +# Launch vmspawn in the background with direct kernel boot and headless console. +systemd-vmspawn \ + --machine="$MACHINE" \ + --ram=256M \ + --directory="$WORKDIR/root" \ + --linux="$KERNEL" \ + --tpm=no \ + --console=headless \ + &>"$WORKDIR/vmspawn.log" & +VMSPAWN_PID=$! + +wait_for_machine "$MACHINE" "$VMSPAWN_PID" "$WORKDIR/vmspawn.log" +echo "Machine '$MACHINE' registered with machined" + +# Verify that controlAddress is present in Machine.List output +varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List "{\"name\":\"$MACHINE\"}" | grep >/dev/null controlAddress +echo "controlAddress exposed in Machine.List" + +# Exercise the MachineInstance varlink interface directly via varlinkctl. +# Look up the varlink address from machined. Do this BEFORE machinectl poweroff since poweroff +# is destructive (either kills the machine via signal or sends ACPI shutdown). +VARLINK_ADDR=$(varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List "{\"name\":\"$MACHINE\"}" | jq -r '.controlAddress') +assert_neq "$VARLINK_ADDR" "null" + +# Describe should reflect a running VM +STATUS=$(varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Describe '{}') +echo "$STATUS" | jq -e '.running == true' +echo "$STATUS" | jq -e '.status == "running"' +echo "Describe returned running state" + +# Pause, verify, resume via varlinkctl +varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Pause '{}' +STATUS=$(varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Describe '{}') +echo "$STATUS" | jq -e '.running == false' +echo "Verified paused state via Describe" + +varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Resume '{}' +STATUS=$(varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Describe '{}') +echo "$STATUS" | jq -e '.running == true' +echo "Verified resumed state via Describe" + +# --- SubscribeEvents tests --- +# Subscribe to all events in the background, collect output +varlinkctl call --more --timeout=10 "$VARLINK_ADDR" io.systemd.MachineInstance.SubscribeEvents '{}' \ + >"$WORKDIR/events-all.json" 2>&1 & +SUBSCRIBE_ALL_PID=$! +sleep 0.5 + +# Trigger STOP + RESUME events via pause/resume +varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Pause '{}' +sleep 0.2 +varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Resume '{}' +sleep 0.5 + +# Kill the subscriber and check output +kill "$SUBSCRIBE_ALL_PID" 2>/dev/null; wait "$SUBSCRIBE_ALL_PID" 2>/dev/null || true +cat "$WORKDIR/events-all.json" + +# Verify initial READY event +grep >/dev/null '"READY"' "$WORKDIR/events-all.json" +echo "SubscribeEvents sent READY event" + +# Verify we got both STOP and RESUME events +grep >/dev/null '"STOP"' "$WORKDIR/events-all.json" +grep >/dev/null '"RESUME"' "$WORKDIR/events-all.json" +echo "SubscribeEvents received STOP and RESUME events" + +# Test filtered subscription: only STOP events +varlinkctl call --more --timeout=10 "$VARLINK_ADDR" io.systemd.MachineInstance.SubscribeEvents '{"filter":["STOP"]}' \ + >"$WORKDIR/events-filtered.json" 2>&1 & +SUBSCRIBE_FILTER_PID=$! +sleep 0.5 + +# Trigger both events again +varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Pause '{}' +sleep 0.2 +varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Resume '{}' +sleep 0.5 + +kill "$SUBSCRIBE_FILTER_PID" 2>/dev/null; wait "$SUBSCRIBE_FILTER_PID" 2>/dev/null || true +cat "$WORKDIR/events-filtered.json" + +# Should have STOP but not RESUME +grep >/dev/null '"STOP"' "$WORKDIR/events-filtered.json" +(! grep >/dev/null '"RESUME"' "$WORKDIR/events-filtered.json") +echo "Filtered subscription correctly received only STOP events" + +# Test machinectl pause/resume +machinectl pause "$MACHINE" +echo "machinectl pause succeeded" + +machinectl resume "$MACHINE" +echo "machinectl resume succeeded" + +# Test machinectl poweroff -- sends ACPI powerdown via QMP (system_powerdown). +# The guest won't handle it (our init is just 'sleep infinity'), but the QMP command should succeed. +machinectl poweroff "$MACHINE" +echo "machinectl poweroff succeeded" + +# --- Stress test: repeated start/pause/resume/terminate cycles --- +# Exercises the varlink disconnect path, QMP reconnection, and ref counting under repeated use. +# This catches use-after-free and double-close bugs that only manifest after multiple cycles. +machinectl terminate "$MACHINE" 2>/dev/null +timeout 10 bash -c "while machinectl status '$MACHINE' &>/dev/null; do sleep .5; done" 2>/dev/null +timeout 10 bash -c "while kill -0 '$VMSPAWN_PID' 2>/dev/null; do sleep .5; done" 2>/dev/null + +for i in $(seq 1 5); do + echo "Stress cycle $i/5" + + STRESS_MACHINE="test-vmspawn-stress-$i-$$" + systemd-vmspawn \ + --machine="$STRESS_MACHINE" \ + --ram=256M \ + --directory="$WORKDIR/root" \ + --linux="$KERNEL" \ + --tpm=no \ + --console=headless \ + &>"$WORKDIR/vmspawn-stress.log" & + STRESS_PID=$! + + wait_for_machine "$STRESS_MACHINE" "$STRESS_PID" "$WORKDIR/vmspawn-stress.log" + + STRESS_ADDR=$(varlinkctl call /run/systemd/machine/io.systemd.Machine \ + io.systemd.Machine.List "{\"name\":\"$STRESS_MACHINE\"}" | jq -r '.controlAddress') + assert_neq "$STRESS_ADDR" "null" + + # Rapid pause/resume/describe cycles + for _ in $(seq 1 3); do + machinectl pause "$STRESS_MACHINE" + varlinkctl call "$STRESS_ADDR" io.systemd.MachineInstance.Describe '{}' | jq -e '.running == false' >/dev/null + machinectl resume "$STRESS_MACHINE" + varlinkctl call "$STRESS_ADDR" io.systemd.MachineInstance.Describe '{}' | jq -e '.running == true' >/dev/null + done + + machinectl terminate "$STRESS_MACHINE" + timeout 10 bash -c "while machinectl status '$STRESS_MACHINE' &>/dev/null; do sleep .5; done" + timeout 10 bash -c "while kill -0 '$STRESS_PID' 2>/dev/null; do sleep .5; done" + echo "Stress cycle $i/5 passed" +done +echo "All stress cycles passed" + +# Restart a fresh VM for the remaining tests +systemd-vmspawn \ + --machine="$MACHINE" \ + --ram=256M \ + --directory="$WORKDIR/root" \ + --linux="$KERNEL" \ + --tpm=no \ + --console=headless \ + &>"$WORKDIR/vmspawn.log" & +VMSPAWN_PID=$! + +wait_for_machine "$MACHINE" "$VMSPAWN_PID" "$WORKDIR/vmspawn.log" +VARLINK_ADDR=$(varlinkctl call /run/systemd/machine/io.systemd.Machine \ + io.systemd.Machine.List "{\"name\":\"$MACHINE\"}" | jq -r '.controlAddress') +assert_neq "$VARLINK_ADDR" "null" + +# --- Parallel multi-machine dispatch tests --- +# Launch a second VM to test machinectl operating on multiple machines simultaneously. +# Use a separate rootfs so each VM gets independent sidecar state (TPM, EFI NVRAM). +MACHINE2="test-vmspawn-qmp2-$$" + +mkdir -p "$WORKDIR/root2/sbin" +cp "$WORKDIR/root/sbin/init" "$WORKDIR/root2/sbin/init" + +systemd-vmspawn \ + --machine="$MACHINE2" \ + --ram=256M \ + --directory="$WORKDIR/root2" \ + --linux="$KERNEL" \ + --tpm=no \ + --console=headless \ + &>"$WORKDIR/vmspawn2.log" & +VMSPAWN2_PID=$! + +wait_for_machine "$MACHINE2" "$VMSPAWN2_PID" "$WORKDIR/vmspawn2.log" +echo "Second machine '$MACHINE2' registered" + +VARLINK_ADDR2=$(varlinkctl call /run/systemd/machine/io.systemd.Machine \ + io.systemd.Machine.List "{\"name\":\"$MACHINE2\"}" | jq -r '.controlAddress') +assert_neq "$VARLINK_ADDR2" "null" + +# Parallel pause: both machines at once +machinectl pause "$MACHINE" "$MACHINE2" +echo "Parallel pause of two machines succeeded" + +# Verify both are paused +varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Describe '{}' | jq -e '.running == false' +varlinkctl call "$VARLINK_ADDR2" io.systemd.MachineInstance.Describe '{}' | jq -e '.running == false' +echo "Both machines verified paused" + +# Parallel resume +machinectl resume "$MACHINE" "$MACHINE2" +echo "Parallel resume of two machines succeeded" + +# Verify both resumed +varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Describe '{}' | jq -e '.running == true' +varlinkctl call "$VARLINK_ADDR2" io.systemd.MachineInstance.Describe '{}' | jq -e '.running == true' +echo "Both machines verified running" + +# --- Terminate and verify cleanup --- +# Parallel terminate: both machines at once (QMP quit) +machinectl terminate "$MACHINE" "$MACHINE2" +timeout 10 bash -c " + while machinectl status '$MACHINE' &>/dev/null || machinectl status '$MACHINE2' &>/dev/null; do + sleep .5 + done +" +echo "Parallel terminate succeeded, both VMs gone" + +# Both vmspawn processes should have exited +timeout 10 bash -c "while kill -0 '$VMSPAWN_PID' 2>/dev/null; do sleep .5; done" +timeout 10 bash -c "while kill -0 '$VMSPAWN2_PID' 2>/dev/null; do sleep .5; done" +echo "Both vmspawn processes exited" + +echo "All vmspawn QMP-varlink bridge tests passed" diff --git a/test/units/util.sh b/test/units/util.sh index d9e561e79186a..248e676eae062 100755 --- a/test/units/util.sh +++ b/test/units/util.sh @@ -525,3 +525,36 @@ check_nss_module() ( return 0 ) + +find_qemu_binary() { + # Mirrors find_qemu_binary() from src/vmspawn/vmspawn-util.c. + # Returns 0 if a usable QEMU binary exists, 1 otherwise. + for binary in qemu qemu-kvm; do + if command -v "$binary" >/dev/null 2>&1; then + return 0 + fi + done + + if test -x /usr/libexec/qemu-kvm; then + return 0 + fi + + local arch + case "$(uname -m)" in + x86_64) arch=x86_64 ;; + i?86) arch=i386 ;; + aarch64) arch=aarch64 ;; + armv*l|arm*) arch=arm ;; + alpha) arch=alpha ;; + loongarch64) arch=loongarch64 ;; + mips*) arch=mips ;; + parisc*) arch=hppa ;; + ppc64*|ppc*) arch=ppc ;; + riscv32) arch=riscv32 ;; + riscv64) arch=riscv64 ;; + s390x) arch=s390x ;; + *) return 1 ;; + esac + + command -v "qemu-system-$arch" >/dev/null 2>&1 +} From d73628e85deec6c58eb18e608f8969d685b282f7 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 7 Apr 2026 21:40:40 +0200 Subject: [PATCH 1010/1296] vmspawn: add integration test for multi-drive and ephemeral QMP setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Test the async QMP drive pipeline with real QEMU: Test 1 (multi-drive): launches vmspawn with --image plus two --extra-drive flags. This exercises multiple fdset allocations, pipelined blockdev-add commands relying on FIFO ordering, io_uring retry callbacks, and multiple device_add commands — all fired without waiting for responses. Test 2 (ephemeral): launches vmspawn with --image --ephemeral. This exercises the most complex async path: blockdev-create fires a background job, JOB_STATUS_CHANGE events are watched via the event callback, and when the job concludes the deferred continuation fires the overlay format node + device_add. If the continuation fails, the root drive is never attached, the kernel panics, and vmspawn exits without registering — so successful registration proves the pipeline works. Both tests use a raw ext4 image with a minimal init (sleep infinity) and direct kernel boot. No virtiofsd needed. Signed-off-by: Christian Brauner (Amutable) --- .../TEST-87-AUX-UTILS-VM.vmspawn-drives.sh | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100755 test/units/TEST-87-AUX-UTILS-VM.vmspawn-drives.sh diff --git a/test/units/TEST-87-AUX-UTILS-VM.vmspawn-drives.sh b/test/units/TEST-87-AUX-UTILS-VM.vmspawn-drives.sh new file mode 100755 index 0000000000000..5ec2fd2f7bc4c --- /dev/null +++ b/test/units/TEST-87-AUX-UTILS-VM.vmspawn-drives.sh @@ -0,0 +1,188 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# Test vmspawn QMP-based multi-drive setup and ephemeral overlay. +# +# Exercises the async QMP command pipeline with multiple drives: +# - Multiple fdset allocations (counter correctness) +# - Pipelined blockdev-add commands (FIFO ordering) +# - io_uring retry callbacks (if QEMU lacks io_uring support) +# - Multiple device_add commands +# - blockdev-create job watching with deferred continuation (ephemeral) +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +if [[ -v ASAN_OPTIONS ]]; then + echo "vmspawn launches QEMU which doesn't work under ASan, skipping" + exit 0 +fi + +if ! command -v systemd-vmspawn >/dev/null 2>&1; then + echo "systemd-vmspawn not found, skipping" + exit 0 +fi + +if ! find_qemu_binary; then + echo "QEMU not found, skipping" + exit 0 +fi + +if ! command -v mke2fs >/dev/null 2>&1; then + echo "mke2fs not found, skipping" + exit 0 +fi + +# Find a kernel for direct boot +KERNEL="" +for k in /usr/lib/modules/"$(uname -r)"/vmlinuz /boot/vmlinuz-"$(uname -r)" /boot/vmlinuz; do + if [[ -f "$k" ]]; then + KERNEL="$k" + break + fi +done + +if [[ -z "$KERNEL" ]]; then + echo "No kernel found for direct VM boot, skipping" + exit 0 +fi +echo "Using kernel: $KERNEL" + +WORKDIR="$(mktemp -d)" + +at_exit() { + set +e + for m in "${MACHINE_MULTI:-}" "${MACHINE_EPHEMERAL:-}"; do + [[ -n "$m" ]] || continue + if machinectl status "$m" &>/dev/null; then + machinectl terminate "$m" 2>/dev/null + timeout 10 bash -c "while machinectl status '$m' &>/dev/null; do sleep .5; done" 2>/dev/null + fi + done + [[ -n "${VMSPAWN_MULTI_PID:-}" ]] && kill "$VMSPAWN_MULTI_PID" 2>/dev/null && wait "$VMSPAWN_MULTI_PID" 2>/dev/null + [[ -n "${VMSPAWN_EPHEMERAL_PID:-}" ]] && kill "$VMSPAWN_EPHEMERAL_PID" 2>/dev/null && wait "$VMSPAWN_EPHEMERAL_PID" 2>/dev/null + rm -rf "$WORKDIR" +} +trap at_exit EXIT + +# Create a minimal root filesystem directory, then bake it into a raw ext4 image. +# The guest doesn't need to fully boot — 'sleep infinity' keeps QEMU alive for QMP testing. +mkdir -p "$WORKDIR/rootfs/sbin" +cat >"$WORKDIR/rootfs/sbin/init" <<'INITEOF' +#!/bin/sh +exec sleep infinity +INITEOF +chmod +x "$WORKDIR/rootfs/sbin/init" + +truncate -s 256M "$WORKDIR/root.raw" +mke2fs -t ext4 -q -d "$WORKDIR/rootfs" "$WORKDIR/root.raw" + +# Create extra raw drive images (different sizes to be distinguishable) +truncate -s 64M "$WORKDIR/extra1.raw" +truncate -s 32M "$WORKDIR/extra2.raw" + +wait_for_machine() { + local machine="$1" pid="$2" log="$3" + timeout 30 bash -c " + while ! machinectl list --no-legend 2>/dev/null | grep >/dev/null '$machine'; do + if ! kill -0 $pid 2>/dev/null; then + echo 'vmspawn exited before machine registration' + cat '$log' + exit 1 + fi + sleep .5 + done + " +} + +# --- Test 1: Multi-drive setup (root + 2 extra drives) --- +# Verifies that --image with multiple --extra-drive flags works with the async +# QMP pipeline. Three drives means three fdset allocations, three blockdev-add +# file nodes (each with io_uring retry), three blockdev-add format nodes, and +# three device_add commands — all pipelined without waiting for responses. + +MACHINE_MULTI="test-vmspawn-drives-$$" +systemd-vmspawn \ + --machine="$MACHINE_MULTI" \ + --ram=256M \ + --image="$WORKDIR/root.raw" \ + --extra-drive="$WORKDIR/extra1.raw" \ + --extra-drive="$WORKDIR/extra2.raw" \ + --linux="$KERNEL" \ + --tpm=no \ + --console=headless \ + root=/dev/vda rw \ + &>"$WORKDIR/vmspawn-multi.log" & +VMSPAWN_MULTI_PID=$! + +wait_for_machine "$MACHINE_MULTI" "$VMSPAWN_MULTI_PID" "$WORKDIR/vmspawn-multi.log" +echo "Multi-drive machine '$MACHINE_MULTI' registered with machined" + +# Verify varlink control address is present and the VM is running +VARLINK_ADDR=$(varlinkctl call /run/systemd/machine/io.systemd.Machine \ + io.systemd.Machine.List "{\"name\":\"$MACHINE_MULTI\"}" | jq -r '.controlAddress') +assert_neq "$VARLINK_ADDR" "null" + +STATUS=$(varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Describe '{}') +echo "$STATUS" | jq -e '.running == true' +echo "Multi-drive VM running — async QMP drive pipeline succeeded" + +# Verify no on_setup_complete failures in the vmspawn log +if grep -E '(add-fd|blockdev-add|blockdev-create|device_add|getfd|netdev_add|chardev-add) failed:' "$WORKDIR/vmspawn-multi.log"; then + echo "Full vmspawn log:" + cat "$WORKDIR/vmspawn-multi.log" + exit 1 +fi +echo "No QMP device setup errors in log" + +machinectl terminate "$MACHINE_MULTI" +timeout 10 bash -c "while machinectl status '$MACHINE_MULTI' &>/dev/null; do sleep .5; done" +timeout 10 bash -c "while kill -0 '$VMSPAWN_MULTI_PID' 2>/dev/null; do sleep .5; done" +echo "Multi-drive VM terminated cleanly" + +# --- Test 2: Ephemeral overlay (blockdev-create job continuation) --- +# Verifies that --image with --ephemeral works. This is the most complex async +# path: blockdev-create returns immediately, the qcow2 overlay is formatted in a +# background job, JOB_STATUS_CHANGE events are watched, and when the job +# concludes the deferred continuation fires blockdev-add (overlay format) + +# device_add. If any step fails, the root drive is never attached and the kernel +# panics — vmspawn exits without registering. + +MACHINE_EPHEMERAL="test-vmspawn-ephemeral-$$" +systemd-vmspawn \ + --machine="$MACHINE_EPHEMERAL" \ + --ram=256M \ + --image="$WORKDIR/root.raw" \ + --ephemeral \ + --linux="$KERNEL" \ + --tpm=no \ + --console=headless \ + root=/dev/vda rw \ + &>"$WORKDIR/vmspawn-ephemeral.log" & +VMSPAWN_EPHEMERAL_PID=$! + +wait_for_machine "$MACHINE_EPHEMERAL" "$VMSPAWN_EPHEMERAL_PID" "$WORKDIR/vmspawn-ephemeral.log" +echo "Ephemeral machine '$MACHINE_EPHEMERAL' registered with machined" + +VARLINK_ADDR_E=$(varlinkctl call /run/systemd/machine/io.systemd.Machine \ + io.systemd.Machine.List "{\"name\":\"$MACHINE_EPHEMERAL\"}" | jq -r '.controlAddress') +assert_neq "$VARLINK_ADDR_E" "null" + +STATUS_E=$(varlinkctl call "$VARLINK_ADDR_E" io.systemd.MachineInstance.Describe '{}') +echo "$STATUS_E" | jq -e '.running == true' +echo "Ephemeral VM running — blockdev-create job continuation succeeded" + +if grep -E '(add-fd|blockdev-add|blockdev-create|device_add|getfd|netdev_add|chardev-add) failed:' "$WORKDIR/vmspawn-ephemeral.log"; then + echo "Full vmspawn log:" + cat "$WORKDIR/vmspawn-ephemeral.log" + exit 1 +fi +echo "No QMP device setup errors in ephemeral log" + +machinectl terminate "$MACHINE_EPHEMERAL" +timeout 10 bash -c "while machinectl status '$MACHINE_EPHEMERAL' &>/dev/null; do sleep .5; done" +timeout 10 bash -c "while kill -0 '$VMSPAWN_EPHEMERAL_PID' 2>/dev/null; do sleep .5; done" +echo "Ephemeral VM terminated cleanly" + +echo "All vmspawn drive setup tests passed" From 021adb2eee9194d52f87c59b4d1a9a147b8f3be6 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 2 Apr 2026 13:58:55 +0200 Subject: [PATCH 1011/1296] TODO: add some vmspawn todos Signed-off-by: Christian Brauner (Amutable) --- TODO.md | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/TODO.md b/TODO.md index 04ab5971670e3..d641e15551e03 100644 --- a/TODO.md +++ b/TODO.md @@ -2779,6 +2779,88 @@ SPDX-License-Identifier: LGPL-2.1-or-later - translate SIGTERM to clean ACPI shutdown event - implement hotkeys ^]^]r and ^]^]p like nspawn +- **vmspawn disk hotplug:** + - virtio-blk-pci — the simplest path. Each disk is an independent PCI + device. QMP sequence (two steps): + + blockdev-add {driver: "raw", node-name: "disk1", + file: {driver: "file", filename: "/path/to/img"}} + device_add {driver: "virtio-blk-pci", id: "disk1", drive: "disk1"} + + Removal (three steps): + + device_del {id: "disk1"} + ... wait for DEVICE_DELETED event (guest acknowledges unplug) ... + blockdev-del {node-name: "disk1"} + + Works on both i440fx (legacy PCI) and q35 (PCIe) machine types. PCI + address auto-assigned by QEMU — no topology pre-configuration needed. + Each disk independently hotpluggable. Guest sees a virtio block + device (/dev/vdX). Well-tested path — used by libvirt, Incus, and all + major VM managers. No special boot-time setup required. + + - NVMe — two-level model: controller + namespace(s). The controller is + a PCIe device; namespaces live on an internal NVMe bus attached to + the controller. Key limitation: namespaces are NOT hotpluggable — + TYPE_NVME_BUS has no HotplugHandler, so device_add of nvme-ns at + runtime fails with "Bus does not support hotplugging". The only + option is hotplugging the entire controller, which embeds one + namespace via its "drive" property: + + blockdev-add {driver: "raw", node-name: "disk1", + file: {driver: "file", filename: "/path/to/img"}} + device_add {driver: "nvme", id: "disk1", drive: "disk1", serial: "disk1"} + + Same two-step pattern as virtio-blk, with these limitations: + + 1. PCIe-only (implements INTERFACE_PCIE_DEVICE). Does not work on + i440fx. Requires q35 or virt (aarch64). + 2. Requires pre-configured PCIe root ports that exist at boot. + Without them, device_add fails with "no slot/function available". + vmspawn would need to create empty root ports at QEMU startup to + reserve hotplug slots. + 3. No namespace-level granularity. Each hotplugged disk burns a full + PCIe slot (controller + one namespace). Cannot add multiple + namespaces to a single controller at runtime. + 4. Serial property required (up to 20 chars). virtio-blk does not + require it. + + - virtio-scsi — shared virtio-scsi-pci controller with individual + scsi-hd devices attached. Incus uses this as its default bus. The + controller must exist at boot, but individual disks (LUNs) can be + hotplugged onto it without burning PCI slots. Scales better than + virtio-blk when many disks are needed, but adds complexity + (controller management, LUN assignment). + +- **vmspawn AcquireQMP():** implement as id-rewriting proxy with FD + passing. vmspawn acts as a QMP multiplexer. When a client calls + AcquireQMP(): + + 1. Create socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0). + 2. Return one end to the client via sd_varlink_push_fd() + + sd_varlink_reply(). + 3. Add an sd-event I/O source on vmspawn's end for the client socket. + 4. Send a synthetic QMP greeting on the client socket, handle + qmp_capabilities locally. Maybe we don't even need that and just + document that it's a fully initialized connection. + 5. For client commands: read from the client socket, rewrite id to + vmspawn's internal counter (store mapping internal_id -> + (client_fd, original_id_json_variant)), forward to QEMU. + 6. For QEMU responses: match internal id, look up original client id, + rewrite back, send to the correct client socket. + 7. Broadcast QMP events (no id) to all AcquireQMP clients AND to + SubscribeEvents subscribers. + 8. On client EOF: remove the I/O source, clean up id mappings. + + This keeps vmspawn in full control of the QMP connection — VMControl + handlers and multiple AcquireQMP clients can coexist without id + collisions. The server needs SD_VARLINK_SERVER_ALLOW_FD_PASSING_OUTPUT + (already set in machined's pattern). + + AcquireQMP() also requires server-side Varlink protocol upgrades. + mvo's WIP branch: + + - we probably needs .pcrpkeyrd or so as additional PE section in UKIs, which contains a separate public key for PCR values that only apply in the initrd, i.e. in the boot phase "enter-initrd". Then, consumers in userspace From 48326af23a1c9d95f9aa2fd66fcecbc7f90ccff5 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 15 Apr 2026 08:02:07 +0000 Subject: [PATCH 1012/1296] sd-varlink: Don't log successful sentinel error dispatch as a failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sd_varlink_error() deliberately returns a negative errno mapped from the error id on success so callbacks can `return sd_varlink_error(...);` to enqueue the reply and propagate a matching errno at once. When varlink_dispatch_method() dispatches a configured error sentinel itself, it doesn't need that mapping — but it was treating any negative return as a dispatch failure and logging "Failed to process sentinel" even though the error reply had been successfully enqueued. Detect success via the state transition to VARLINK_PROCESSED_METHOD instead, so only genuine enqueue failures are logged. --- src/libsystemd/sd-varlink/sd-varlink.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index 372ede755b5bb..7130be69a4bf5 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -1110,8 +1110,18 @@ static int varlink_dispatch_method(sd_varlink *v) { * and no replies were enqueued by the callback. */ if (sentinel == POINTER_MAX) r = sd_varlink_reply(v, NULL); - else + else { r = sd_varlink_error(v, sentinel, NULL); + /* sd_varlink_error() deliberately returns a negative + * errno mapped from the error id on success (so method + * callbacks can `return sd_varlink_error(...);` to + * enqueue a reply and propagate a matching errno in one + * go). For sentinel dispatch we don't care about that + * mapping — the reply is either enqueued or not, which + * we detect via the state transition instead. */ + if (v->state == VARLINK_PROCESSED_METHOD) + r = 0; + } if (sentinel != POINTER_MAX) free(sentinel); From f921f132254d42b9254cb90e83e49cb867b7d342 Mon Sep 17 00:00:00 2001 From: Jonathan Davies Date: Sun, 15 Mar 2026 22:22:37 +0000 Subject: [PATCH 1013/1296] networkd: add DHCPServer PoolSize and PoolOffset DBus properties Closes https://github.com/systemd/systemd/issues/30011 --- man/org.freedesktop.network1.xml | 25 +++++++++++++-- src/network/networkd-dhcp-server-bus.c | 42 ++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/man/org.freedesktop.network1.xml b/man/org.freedesktop.network1.xml index 0b7a6b5ed3d11..1c3abcad23068 100644 --- a/man/org.freedesktop.network1.xml +++ b/man/org.freedesktop.network1.xml @@ -458,6 +458,10 @@ node /org/freedesktop/network1/link/_1 { interface org.freedesktop.network1.DHCPServer { properties: readonly a(uayayayayt) Leases = [...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly u PoolSize = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly u PoolOffset = ...; }; interface org.freedesktop.DBus.Peer { ... }; interface org.freedesktop.DBus.Introspectable { ... }; @@ -466,8 +470,6 @@ node /org/freedesktop/network1/link/_1 { }; - - @@ -480,10 +482,23 @@ node /org/freedesktop/network1/link/_1 { + + + + - Provides information about leases. + Provides information about the DHCP server. The Leases property contains + the currently active leases. The PoolSize property contains the total number + of addresses in the dynamic address pool. The PoolOffset property contains + the offset from the subnet base address where the pool starts. These correspond to the + PoolSize= and PoolOffset= settings in + systemd.network5. + UINT32_MAX is used as a sentinel value for PoolSize + and PoolOffset to indicate that the information is unavailable (i.e. no + DHCP server is configured or the link is in relay mode), rather than a valid pool size or + offset. @@ -589,6 +604,10 @@ $ gdbus introspect --system \ History + + DHCP Server Object + PoolSize and PoolOffset were added in version 261. + DHCPv4 Client Object State was added in version 255. diff --git a/src/network/networkd-dhcp-server-bus.c b/src/network/networkd-dhcp-server-bus.c index db2ee37f34be6..e5ee6d3f6d5c3 100644 --- a/src/network/networkd-dhcp-server-bus.c +++ b/src/network/networkd-dhcp-server-bus.c @@ -74,6 +74,46 @@ static int property_get_leases( return sd_bus_message_close_container(reply); } +static int property_get_pool_size( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + Link *l = ASSERT_PTR(userdata); + sd_dhcp_server *s; + uint32_t v; + + assert(reply); + + s = l->dhcp_server; + v = s && !sd_dhcp_server_is_in_relay_mode(s) ? s->pool_size : UINT32_MAX; + + return sd_bus_message_append_basic(reply, 'u', &v); +} + +static int property_get_pool_offset( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + Link *l = ASSERT_PTR(userdata); + sd_dhcp_server *s; + uint32_t v; + + assert(reply); + + s = l->dhcp_server; + v = s && !sd_dhcp_server_is_in_relay_mode(s) ? s->pool_offset : UINT32_MAX; + + return sd_bus_message_append_basic(reply, 'u', &v); +} + static int dhcp_server_emit_changed_strv(Link *link, char **properties) { _cleanup_free_ char *path = NULL; @@ -104,6 +144,8 @@ static const sd_bus_vtable dhcp_server_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Leases", "a(uayayayayt)", property_get_leases, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("PoolSize", "u", property_get_pool_size, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PoolOffset", "u", property_get_pool_offset, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_VTABLE_END }; From c87ab483e79dbc4647822822d604b3dd1c3641a2 Mon Sep 17 00:00:00 2001 From: Jonathan Davies Date: Sun, 15 Mar 2026 22:48:24 +0000 Subject: [PATCH 1014/1296] test-network: verify DHCP server PoolSize and PoolOffset DBus properties Add integration test coverage for the PoolSize and PoolOffset properties exposed on the org.freedesktop.network1.DHCPServer DBus interface. --- test/test-network/systemd-networkd-tests.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 03404e6cbeb41..91d97383e79bb 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -931,6 +931,13 @@ def get_dbus_link_path(link): out = out.decode() return out[:-1].split('"')[1] +def get_dhcp_server_property(link, prop): + link_path = get_dbus_link_path(link) + + out = subprocess.check_output(['busctl', 'get-property', 'org.freedesktop.network1', + link_path, 'org.freedesktop.network1.DHCPServer', prop]) + return out.strip().decode() + def get_dhcp_client_state(link, family): link_path = get_dbus_link_path(link) @@ -7293,6 +7300,9 @@ def check_dhcp_server(self, persist_leases='yes'): print(output) self.assertRegex(output, "Offered DHCP leases: 192.168.5.[0-9]*") + self.assertEqual(get_dhcp_server_property('veth-peer', 'PoolSize'), 'u 50') + self.assertEqual(get_dhcp_server_property('veth-peer', 'PoolOffset'), 'u 10') + if persist_leases == 'yes': path = '/var/lib/systemd/network/dhcp-server-lease/veth-peer' elif persist_leases == 'runtime': From cdc8aadaed7bf0bb8721a0eda5817e5c88a1b48b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 31 Mar 2026 12:40:47 +0200 Subject: [PATCH 1015/1296] shared: move src/import/curl-util.h to src/shared/ Move more common definitions in the header file instead of repeating them in bunch of places. src/import/curl-util.[ch] is renamed so that it's shared more naturally with other components. --- src/imds/imdsd.c | 3 +-- src/imds/meson.build | 4 ++-- src/import/meson.build | 4 +--- src/journal-remote/journal-upload.c | 13 +------------ src/{import => shared}/curl-util.c | 0 src/{import => shared}/curl-util.h | 17 +++++++++++++---- src/shared/meson.build | 3 +++ 7 files changed, 21 insertions(+), 23 deletions(-) rename src/{import => shared}/curl-util.c (100%) rename src/{import => shared}/curl-util.h (69%) diff --git a/src/imds/imdsd.c b/src/imds/imdsd.c index c0ab089830350..7831d1c68cb22 100644 --- a/src/imds/imdsd.c +++ b/src/imds/imdsd.c @@ -18,6 +18,7 @@ #include "chase.h" #include "copy.h" #include "creds-util.h" +#include "curl-util.h" #include "device-private.h" #include "dns-rr.h" #include "errno-util.h" @@ -53,8 +54,6 @@ #include "web-util.h" #include "xattr-util.h" -#include "../import/curl-util.h" - /* This implements a client to the AWS' and Azure's "Instance Metadata Service", as well as GCP's "VM * Metadata", i.e.: * diff --git a/src/imds/meson.build b/src/imds/meson.build index f9fa9b5f0fb1a..cb919fec615d8 100644 --- a/src/imds/meson.build +++ b/src/imds/meson.build @@ -11,8 +11,8 @@ executables += [ 'sources' : files( 'imdsd.c', 'imds-util.c' - ) + import_curl_util_c, - 'dependencies' : [ libcurl ], + ) + curl_util_c, + 'dependencies' : [libcurl], }, libexec_template + { 'name' : 'systemd-imds', diff --git a/src/import/meson.build b/src/import/meson.build index a98604d4915b8..1fab9afd0b005 100644 --- a/src/import/meson.build +++ b/src/import/meson.build @@ -1,7 +1,5 @@ # SPDX-License-Identifier: LGPL-2.1-or-later -import_curl_util_c = files('curl-util.c') - if conf.get('ENABLE_IMPORTD') != 1 subdir_done() endif @@ -35,7 +33,7 @@ executables += [ 'pull-oci.c', 'pull-raw.c', 'pull-tar.c', - ) + import_curl_util_c, + ) + curl_util_c, 'objects' : ['systemd-importd'], 'dependencies' : common_deps + [ libopenssl, diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c index e2038dd4b6e4e..88f5cf713985a 100644 --- a/src/journal-remote/journal-upload.c +++ b/src/journal-remote/journal-upload.c @@ -11,6 +11,7 @@ #include "alloc-util.h" #include "build.h" #include "conf-parser.h" +#include "curl-util.h" #include "daemon-util.h" #include "env-file.h" #include "extract-word.h" @@ -81,18 +82,6 @@ static void close_fd_input(Uploader *u); #define STATE_FILE "/var/lib/systemd/journal-upload/state" -#define easy_setopt(curl, log_level, opt, value) ({ \ - CURLcode code = curl_easy_setopt(ASSERT_PTR(curl), opt, value); \ - if (code) \ - log_full(log_level, \ - "curl_easy_setopt %s failed: %s", \ - #opt, curl_easy_strerror(code)); \ - !code; \ -}) - -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(CURL*, curl_easy_cleanup, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct curl_slist*, curl_slist_free_all, NULL); - static size_t output_callback(char *buf, size_t size, size_t nmemb, diff --git a/src/import/curl-util.c b/src/shared/curl-util.c similarity index 100% rename from src/import/curl-util.c rename to src/shared/curl-util.c diff --git a/src/import/curl-util.h b/src/shared/curl-util.h similarity index 69% rename from src/import/curl-util.h rename to src/shared/curl-util.h index b48eeb9c43682..6b922d5003697 100644 --- a/src/import/curl-util.h +++ b/src/shared/curl-util.h @@ -5,6 +5,19 @@ #include "shared-forward.h" +#define easy_setopt(curl, log_level, opt, value) ({ \ + CURLcode code = curl_easy_setopt(ASSERT_PTR(curl), opt, value); \ + if (code) \ + log_full(log_level, \ + "curl_easy_setopt %s failed: %s", \ + #opt, curl_easy_strerror(code)); \ + code == CURLE_OK; \ +}) + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(CURL*, curl_easy_cleanup, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(CURLM*, curl_multi_cleanup, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct curl_slist*, curl_slist_free_all, NULL); + typedef struct CurlGlue CurlGlue; typedef struct CurlGlue { @@ -30,7 +43,3 @@ void curl_glue_remove_and_free(CurlGlue *g, CURL *c); struct curl_slist *curl_slist_new(const char *first, ...) _sentinel_; int curl_header_strdup(const void *contents, size_t sz, const char *field, char **value); int curl_parse_http_time(const char *t, usec_t *ret); - -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(CURL*, curl_easy_cleanup, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(CURLM*, curl_multi_cleanup, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct curl_slist*, curl_slist_free_all, NULL); diff --git a/src/shared/meson.build b/src/shared/meson.build index 22dccf0e2a7da..efa0dc05adf6a 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -256,6 +256,9 @@ if get_option('tests') != 'false' shared_sources += files('tests.c') endif +# A small shared file that is is linked into a few places +curl_util_c = files('curl-util.c') + syscall_list_inc = custom_target( input : syscall_list_txt, output : 'syscall-list.inc', From bdbbb9cf911c1a7daad0ef37400c5710110b66a5 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Fri, 17 Oct 2025 06:43:36 -0700 Subject: [PATCH 1016/1296] shared: exec_command_build_json() --- src/core/varlink-common.c | 22 ++++++++++++++++++++++ src/core/varlink-common.h | 2 +- src/shared/varlink-idl-common.c | 17 +++++++++++++++++ src/shared/varlink-idl-common.h | 1 + 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/core/varlink-common.c b/src/core/varlink-common.c index e388b1633929d..bdef4d5a9471d 100644 --- a/src/core/varlink-common.c +++ b/src/core/varlink-common.c @@ -4,6 +4,7 @@ #include "bus-common-errors.h" #include "cpu-set-util.h" +#include "execute.h" #include "json-util.h" #include "rlimit-util.h" #include "varlink-common.h" @@ -104,3 +105,24 @@ int cpuset_build_json(sd_json_variant **ret, const char *name, void *userdata) { *ret = NULL; return 0; } + +int exec_command_build_json(sd_json_variant **ret, const char *name, void *userdata) { + ExecCommand *cmd = ASSERT_PTR(userdata); + + assert(ret); + + if (isempty(cmd->path)) { + *ret = NULL; + return 0; + } + + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_STRING("path", cmd->path), + JSON_BUILD_PAIR_STRV_NON_EMPTY("arguments", cmd->argv), + SD_JSON_BUILD_PAIR_BOOLEAN("ignoreFailure", FLAGS_SET(cmd->flags, EXEC_COMMAND_IGNORE_FAILURE)), + SD_JSON_BUILD_PAIR_BOOLEAN("privileged", FLAGS_SET(cmd->flags, EXEC_COMMAND_FULLY_PRIVILEGED)), + SD_JSON_BUILD_PAIR_BOOLEAN("noSetuid", FLAGS_SET(cmd->flags, EXEC_COMMAND_NO_SETUID)), + SD_JSON_BUILD_PAIR_BOOLEAN("noEnvExpand", FLAGS_SET(cmd->flags, EXEC_COMMAND_NO_ENV_EXPAND)), + SD_JSON_BUILD_PAIR_BOOLEAN("viaShell", FLAGS_SET(cmd->flags, EXEC_COMMAND_VIA_SHELL))); +} diff --git a/src/core/varlink-common.h b/src/core/varlink-common.h index 82d1458dd9609..fc919dcf36c74 100644 --- a/src/core/varlink-common.h +++ b/src/core/varlink-common.h @@ -6,5 +6,5 @@ int rlimit_build_json(sd_json_variant **ret, const char *name, void *userdata); int rlimit_table_build_json(sd_json_variant **ret, const char *name, void *userdata); int cpuset_build_json(sd_json_variant **ret, const char *name, void *userdata); - const char* varlink_error_id_from_bus_error(const sd_bus_error *e); +int exec_command_build_json(sd_json_variant **ret, const char *name, void *userdata); diff --git a/src/shared/varlink-idl-common.c b/src/shared/varlink-idl-common.c index f8f37c480c4d9..24eec3bfcf48a 100644 --- a/src/shared/varlink-idl-common.c +++ b/src/shared/varlink-idl-common.c @@ -54,6 +54,23 @@ SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD_BY_TYPE(RTPRIO, ResourceLimit, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD_BY_TYPE(RTTIME, ResourceLimit, SD_VARLINK_NULLABLE)); +SD_VARLINK_DEFINE_STRUCT_TYPE( + ExecCommand, + SD_VARLINK_FIELD_COMMENT("Path"), + SD_VARLINK_DEFINE_FIELD(path, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Arguments"), + SD_VARLINK_DEFINE_FIELD(arguments, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Ignore failure of the command"), + SD_VARLINK_DEFINE_FIELD(ignoreFailure, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Run with full privileges"), + SD_VARLINK_DEFINE_FIELD(privileged, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Skip setuid handling"), + SD_VARLINK_DEFINE_FIELD(noSetuid, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Skip environment variable expansion"), + SD_VARLINK_DEFINE_FIELD(noEnvExpand, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Run via shell"), + SD_VARLINK_DEFINE_FIELD(viaShell, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); + SD_VARLINK_DEFINE_ENUM_TYPE( ExecOutputType, SD_VARLINK_DEFINE_ENUM_VALUE(inherit), diff --git a/src/shared/varlink-idl-common.h b/src/shared/varlink-idl-common.h index 0385752276212..fdfbfc7986faa 100644 --- a/src/shared/varlink-idl-common.h +++ b/src/shared/varlink-idl-common.h @@ -8,6 +8,7 @@ extern const sd_varlink_symbol vl_type_ProcessId; extern const sd_varlink_symbol vl_type_RateLimit; extern const sd_varlink_symbol vl_type_ResourceLimit; extern const sd_varlink_symbol vl_type_ResourceLimitTable; +extern const sd_varlink_symbol vl_type_ExecCommand; extern const sd_varlink_symbol vl_type_ExecOutputType; extern const sd_varlink_symbol vl_type_CGroupPressureWatch; extern const sd_varlink_symbol vl_type_ManagedOOMMode; From 2136f32c76d5aa3c932c2592d356e62a99459d90 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Fri, 17 Oct 2025 03:27:29 -0700 Subject: [PATCH 1017/1296] core: implement KillContext for io.systemd.Unit.List + tests --- src/core/meson.build | 1 + src/core/varlink-kill.c | 29 ++++++++++++++++++ src/core/varlink-kill.h | 6 ++++ src/core/varlink-unit.c | 6 ++-- src/shared/varlink-io.systemd.Unit.c | 35 +++++++++++++++++++++- src/shared/varlink-io.systemd.Unit.h | 1 + src/test/test-varlink-idl-unit.c | 4 +++ test/units/TEST-74-AUX-UTILS.varlinkctl.sh | 2 ++ 8 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 src/core/varlink-kill.c create mode 100644 src/core/varlink-kill.h diff --git a/src/core/meson.build b/src/core/meson.build index 391dc45a6b294..24e7a5e366308 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -68,6 +68,7 @@ libcore_sources = files( 'varlink-common.c', 'varlink-dynamic-user.c', 'varlink-execute.c', + 'varlink-kill.c', 'varlink-manager.c', 'varlink-metrics.c', 'varlink-unit.c', diff --git a/src/core/varlink-kill.c b/src/core/varlink-kill.c new file mode 100644 index 0000000000000..8b48da098a7eb --- /dev/null +++ b/src/core/varlink-kill.c @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "json-util.h" +#include "kill.h" +#include "signal-util.h" +#include "varlink-kill.h" + +int unit_kill_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + KillContext *c = userdata; + + assert(ret); + + if (!c) { + *ret = NULL; + return 0; + } + + return sd_json_buildo( + ret, + JSON_BUILD_PAIR_ENUM("KillMode", kill_mode_to_string(c->kill_mode)), + SD_JSON_BUILD_PAIR_STRING("KillSignal", signal_to_string(c->kill_signal)), + SD_JSON_BUILD_PAIR_STRING("RestartKillSignal", signal_to_string(restart_kill_signal(c))), + SD_JSON_BUILD_PAIR_BOOLEAN("SendSIGHUP", c->send_sighup), + SD_JSON_BUILD_PAIR_BOOLEAN("SendSIGKILL", c->send_sigkill), + SD_JSON_BUILD_PAIR_STRING("FinalKillSignal", signal_to_string(c->final_kill_signal)), + SD_JSON_BUILD_PAIR_STRING("WatchdogSignal", signal_to_string(c->watchdog_signal))); +} diff --git a/src/core/varlink-kill.h b/src/core/varlink-kill.h new file mode 100644 index 0000000000000..a894e89ad68d3 --- /dev/null +++ b/src/core/varlink-kill.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" + +int unit_kill_context_build_json(sd_json_variant **ret, const char *name, void *userdata); diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index e8b86845a20e5..124d6d353b68b 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -20,6 +20,7 @@ #include "unit.h" #include "varlink-cgroup.h" #include "varlink-execute.h" +#include "varlink-kill.h" #include "varlink-unit.h" #include "varlink-util.h" @@ -189,11 +190,10 @@ static int unit_context_build_json(sd_json_variant **ret, const char *name, void SD_JSON_BUILD_PAIR_BOOLEAN("DebugInvocation", u->debug_invocation), JSON_BUILD_PAIR_CALLBACK_NON_NULL("CGroup", unit_cgroup_context_build_json, u), - JSON_BUILD_PAIR_CALLBACK_NON_NULL("Exec", unit_exec_context_build_json, u)); + JSON_BUILD_PAIR_CALLBACK_NON_NULL("Exec", unit_exec_context_build_json, u), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("Kill", unit_kill_context_build_json, unit_get_kill_context(u))); // TODO follow up PRs: - // JSON_BUILD_PAIR_CALLBACK_NON_NULL("Exec", exec_context_build_json, u) - // JSON_BUILD_PAIR_CALLBACK_NON_NULL("Kill", kill_context_build_json, u) // Mount/Automount context // Path context // Scope context diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c index 394e7b819b550..fbcdce8e0677f 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -902,6 +902,32 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#UtmpMode="), SD_VARLINK_DEFINE_FIELD_BY_TYPE(UtmpMode, ExecUtmpMode, 0)); +SD_VARLINK_DEFINE_ENUM_TYPE( + KillMode, + SD_VARLINK_DEFINE_ENUM_VALUE(control_group), + SD_VARLINK_DEFINE_ENUM_VALUE(process), + SD_VARLINK_DEFINE_ENUM_VALUE(mixed), + SD_VARLINK_DEFINE_ENUM_VALUE(none)); + +/* KillContext + * https://www.freedesktop.org/software/systemd/man/latest/systemd.kill.html */ +static SD_VARLINK_DEFINE_STRUCT_TYPE( + KillContext, + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.kill.html#KillMode="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(KillMode, KillMode, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.kill.html#KillSignal="), + SD_VARLINK_DEFINE_FIELD(KillSignal, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.kill.html#RestartKillSignal="), + SD_VARLINK_DEFINE_FIELD(RestartKillSignal, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.kill.html#SendSIGHUP="), + SD_VARLINK_DEFINE_FIELD(SendSIGHUP, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.kill.html#SendSIGKILL="), + SD_VARLINK_DEFINE_FIELD(SendSIGKILL, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.kill.html#FinalKillSignal="), + SD_VARLINK_DEFINE_FIELD(FinalKillSignal, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.kill.html#WatchdogSignal="), + SD_VARLINK_DEFINE_FIELD(WatchdogSignal, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + /* UnitContext */ static SD_VARLINK_DEFINE_STRUCT_TYPE( Condition, @@ -1059,7 +1085,10 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The cgroup context of the unit"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(CGroup, CGroupContext, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The exec context of the unit"), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(Exec, ExecContext, SD_VARLINK_NULLABLE)); + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Exec, ExecContext, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The kill context of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Kill, KillContext, SD_VARLINK_NULLABLE)); + static SD_VARLINK_DEFINE_STRUCT_TYPE( ActivationDetails, @@ -1318,6 +1347,10 @@ SD_VARLINK_DEFINE_INTERFACE( SD_VARLINK_SYMBOL_COMMENT("Exec context of a unit"), &vl_type_ExecContext, + /* other contexts */ + &vl_type_KillMode, + &vl_type_KillContext, + /* UnitContext enums */ &vl_type_CollectMode, &vl_type_EmergencyAction, diff --git a/src/shared/varlink-io.systemd.Unit.h b/src/shared/varlink-io.systemd.Unit.h index f227dc67d6687..61abb754bc7c6 100644 --- a/src/shared/varlink-io.systemd.Unit.h +++ b/src/shared/varlink-io.systemd.Unit.h @@ -27,5 +27,6 @@ extern const sd_varlink_symbol vl_type_CPUSchedulingPolicy; extern const sd_varlink_symbol vl_type_IOSchedulingClass; extern const sd_varlink_symbol vl_type_NUMAPolicy; extern const sd_varlink_symbol vl_type_MountPropagationFlag; +extern const sd_varlink_symbol vl_type_KillMode; extern const sd_varlink_symbol vl_type_CollectMode; extern const sd_varlink_symbol vl_type_JobMode; diff --git a/src/test/test-varlink-idl-unit.c b/src/test/test-varlink-idl-unit.c index f840ff89da747..90ff3f6b787c1 100644 --- a/src/test/test-varlink-idl-unit.c +++ b/src/test/test-varlink-idl-unit.c @@ -2,6 +2,7 @@ #include "cgroup.h" #include "ioprio-util.h" +#include "kill.h" #include "numa-util.h" #include "process-util.h" #include "tests.h" @@ -40,6 +41,9 @@ TEST(unit_enums_idl) { test_enum_to_string_name("slave", &vl_type_MountPropagationFlag); test_enum_to_string_name("private", &vl_type_MountPropagationFlag); + /* KillContext enums */ + TEST_IDL_ENUM(KillMode, kill_mode, vl_type_KillMode); + /* CGroupContext enums */ TEST_IDL_ENUM(CGroupDevicePolicy, cgroup_device_policy, vl_type_CGroupDevicePolicy); TEST_IDL_ENUM(ManagedOOMMode, managed_oom_mode, vl_type_ManagedOOMMode); diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh index 782a6dc6e973c..2ec1e0408955d 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -230,6 +230,8 @@ set -o pipefail varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"cgroup": "/init.scope"}' invocation_id="$(systemctl show -P InvocationID systemd-journald.service)" varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"invocationID\": \"$invocation_id\"}" +# test for KillContext +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {"pid": 0}}' | jq -e '.context.Kill' # test io.systemd.Metrics varlinkctl info /run/systemd/report/io.systemd.Manager From 6133fc0e1cb6f79a3b60e812732151b451291496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 09:05:01 +0200 Subject: [PATCH 1018/1296] shared/options: convert mode boolean to an enum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This will make it easier to add new modes of operation later. But I'm happy with how this came out — I think the mode setting is nicer to read then the old bool. --- src/journal/cat.c | 2 +- src/nspawn/nspawn.c | 2 +- src/shared/options.c | 4 +- src/shared/options.h | 15 +++++-- src/test/test-options.c | 90 ++++++++++++++++++++--------------------- src/vmspawn/vmspawn.c | 2 +- 6 files changed, 63 insertions(+), 52 deletions(-) diff --git a/src/journal/cat.c b/src/journal/cat.c index e2419d36ab334..62249663c581b 100644 --- a/src/journal/cat.c +++ b/src/journal/cat.c @@ -59,7 +59,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv, /* stop_at_first_nonoption= */ true }; + OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; const char *arg; int r; diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index accf448ea97f2..92c0c03177cb1 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -586,7 +586,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv, /* stop_at_first_nonoption= */ true }; + OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; const Option *opt; const char *arg; diff --git a/src/shared/options.c b/src/shared/options.c index dc2fb34cf62f2..5d4df946b83fb 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -75,6 +75,8 @@ int option_parse( /* Check and initialize */ if (state->optind == 0) { + assert(state->mode >= 0 && state->mode < _OPTION_PARSER_MODE_MAX); + if (state->argc < 1 || strv_isempty(state->argv)) return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "argv cannot be empty"); @@ -109,7 +111,7 @@ int option_parse( /* Looks like we found an option parameter */ break; - if (state->stop_at_first_nonoption) { + if (state->mode == OPTION_PARSER_STOP_AT_FIRST_NONOPTION) { state->parsing_stopped = true; return 0; } diff --git a/src/shared/options.h b/src/shared/options.h index 262196c8d95af..a10f914da266c 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -92,13 +92,22 @@ typedef struct Option { extern const Option __start_SYSTEMD_OPTIONS[]; extern const Option __stop_SYSTEMD_OPTIONS[]; +typedef enum OptionParserMode { + /* The default mode. This is the implicit default and doesn't have to be specified. */ + OPTION_PARSER_NORMAL = 0, + + /* Same as "+…" for getopt_long — only parse options before the first positional argument. */ + OPTION_PARSER_STOP_AT_FIRST_NONOPTION, + + _OPTION_PARSER_MODE_MAX, +} OptionParserMode; + typedef struct OptionParser { /* Those three should stay first so that it's possible to initialize the struct as { argc, argv } - * or { argc, argv, true/false }. */ + * or { argc, argv, mode }. */ int argc; /* The original argc. */ char **argv; /* The argv array, possibly reordered. */ - bool stop_at_first_nonoption; /* Same as "+…" for getopt_long — only parse options before the first - * positional argument. */ + OptionParserMode mode; bool parsing_stopped; /* We processed "--" or an option that terminates option parsing. */ int optind; /* Position of the parameter being handled. diff --git a/src/test/test-options.c b/src/test/test-options.c index b91ac54884c6b..e6b18d7a7eaeb 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -15,7 +15,7 @@ static void test_option_parse_one( const Option options[], const Entry *entries, char **remaining, - bool stop_at_first_nonoption) { + OptionParserMode mode) { _cleanup_free_ char *joined = strv_join(argv, ", "); log_debug("/* %s(%s) */", __func__, joined); @@ -32,7 +32,7 @@ static void test_option_parse_one( for (const Entry *e = entries; e && (e->long_code || e->short_code != 0); e++) n_entries++; - OptionParser state = { argc, argv, stop_at_first_nonoption }; + OptionParser state = { argc, argv, mode }; const Option *opt; const char *arg; for (int c; (c = option_parse(options, options + n_options, &state, &opt, &arg)) != 0; ) { @@ -101,7 +101,7 @@ TEST(option_parse) { options, NULL, NULL, - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -114,7 +114,7 @@ TEST(option_parse) { "string2", "string3", "string4"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "--", @@ -128,7 +128,7 @@ TEST(option_parse) { "--help", "-h", "string4"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -142,7 +142,7 @@ TEST(option_parse) { "string2", "--", "string4"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -156,7 +156,7 @@ TEST(option_parse) { "string2", "string3", "string4"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "--help"), @@ -166,7 +166,7 @@ TEST(option_parse) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "--help", @@ -179,7 +179,7 @@ TEST(option_parse) { }, STRV_MAKE("string1", "--help"), - true); + OPTION_PARSER_STOP_AT_FIRST_NONOPTION); test_option_parse_one(STRV_MAKE("arg0", "-h"), @@ -189,7 +189,7 @@ TEST(option_parse) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "--help", @@ -206,7 +206,7 @@ TEST(option_parse) { "string2", "string3", "string4"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "-h", @@ -223,7 +223,7 @@ TEST(option_parse) { "string2", "string3", "string4"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -240,7 +240,7 @@ TEST(option_parse) { "string2", "string3", "string4"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -257,7 +257,7 @@ TEST(option_parse) { "string2", "string3", "string4"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -274,7 +274,7 @@ TEST(option_parse) { "string2", "string3", "string4"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -291,7 +291,7 @@ TEST(option_parse) { "string2", "string3", "string4"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "--required1", "reqarg1"), @@ -301,7 +301,7 @@ TEST(option_parse) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "-r", "reqarg1"), @@ -311,7 +311,7 @@ TEST(option_parse) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -324,7 +324,7 @@ TEST(option_parse) { }, STRV_MAKE("string1", "string2"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -335,7 +335,7 @@ TEST(option_parse) { STRV_MAKE("string1", "string2", "-r", "reqarg1"), - true); + OPTION_PARSER_STOP_AT_FIRST_NONOPTION); test_option_parse_one(STRV_MAKE("arg0", "--optional1=optarg1"), @@ -345,7 +345,7 @@ TEST(option_parse) { {} }, NULL, - true); + OPTION_PARSER_STOP_AT_FIRST_NONOPTION); test_option_parse_one(STRV_MAKE("arg0", "--optional1", "string1"), @@ -355,7 +355,7 @@ TEST(option_parse) { {} }, STRV_MAKE("string1"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "-ooptarg1"), @@ -365,7 +365,7 @@ TEST(option_parse) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "-o", "string1"), @@ -375,7 +375,7 @@ TEST(option_parse) { {} }, STRV_MAKE("string1"), - false); + OPTION_PARSER_NORMAL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -445,7 +445,7 @@ TEST(option_parse) { "--help", "--required1", "--optional1"), - false); + OPTION_PARSER_NORMAL); } TEST(option_stops_parsing) { @@ -469,7 +469,7 @@ TEST(option_stops_parsing) { }, STRV_MAKE("--help", "foo"), - false); + OPTION_PARSER_NORMAL); /* Options before --exec are still parsed */ test_option_parse_one(STRV_MAKE("arg0", @@ -485,7 +485,7 @@ TEST(option_stops_parsing) { }, STRV_MAKE("--version", "bar"), - false); + OPTION_PARSER_NORMAL); /* --exec with no trailing args */ test_option_parse_one(STRV_MAKE("arg0", @@ -496,7 +496,7 @@ TEST(option_stops_parsing) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); /* --exec after positional args */ test_option_parse_one(STRV_MAKE("arg0", @@ -513,7 +513,7 @@ TEST(option_stops_parsing) { "--help", "--required", "val"), - false); + OPTION_PARSER_NORMAL); /* "--" after --exec: "--" is still consumed as end-of-options marker. This is needed for * backwards compatibility, systemd-dissect implemented this behaviour. But also, it makes @@ -529,7 +529,7 @@ TEST(option_stops_parsing) { {} }, STRV_MAKE("--help"), - false); + OPTION_PARSER_NORMAL); /* "--" before --exec: "--" terminates first, --exec is positional */ test_option_parse_one(STRV_MAKE("arg0", @@ -540,7 +540,7 @@ TEST(option_stops_parsing) { NULL, STRV_MAKE("--exec", "--help"), - false); + OPTION_PARSER_NORMAL); /* Multiple options then --exec then more option-like args */ test_option_parse_one(STRV_MAKE("arg0", @@ -559,7 +559,7 @@ TEST(option_stops_parsing) { STRV_MAKE("-h", "--required", "val2"), - false); + OPTION_PARSER_NORMAL); } TEST(option_group_marker) { @@ -584,7 +584,7 @@ TEST(option_group_marker) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); /* Check that group marker name is ignored */ test_option_parse_one(STRV_MAKE("arg0", @@ -597,7 +597,7 @@ TEST(option_group_marker) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); /* Verify that the group marker is not mistaken for an option */ test_option_invalid_one(STRV_MAKE("arg0", @@ -625,7 +625,7 @@ TEST(option_group_marker) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); /* Partial match with multiple candidates */ test_option_invalid_one(STRV_MAKE("arg0", @@ -649,7 +649,7 @@ TEST(option_optional_arg) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); /* Long option without = does NOT consume the next arg */ test_option_parse_one(STRV_MAKE("arg0", @@ -660,7 +660,7 @@ TEST(option_optional_arg) { {} }, STRV_MAKE("foo.txt"), - false); + OPTION_PARSER_NORMAL); /* Short option with inline arg */ test_option_parse_one(STRV_MAKE("arg0", @@ -671,7 +671,7 @@ TEST(option_optional_arg) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); /* Short option without inline arg does NOT consume the next arg */ test_option_parse_one(STRV_MAKE("arg0", @@ -682,7 +682,7 @@ TEST(option_optional_arg) { {} }, STRV_MAKE("foo.txt"), - false); + OPTION_PARSER_NORMAL); /* Optional arg option at end of argv */ test_option_parse_one(STRV_MAKE("arg0", @@ -693,7 +693,7 @@ TEST(option_optional_arg) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); /* Mixed: optional arg with other options */ test_option_parse_one(STRV_MAKE("arg0", @@ -708,7 +708,7 @@ TEST(option_optional_arg) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); /* Short combo: -ho (h then o with no arg) */ test_option_parse_one(STRV_MAKE("arg0", @@ -720,7 +720,7 @@ TEST(option_optional_arg) { {} }, STRV_MAKE("pos1"), - false); + OPTION_PARSER_NORMAL); /* Short combo: -hobar (h then o with inline arg "bar") */ test_option_parse_one(STRV_MAKE("arg0", @@ -732,7 +732,7 @@ TEST(option_optional_arg) { {} }, NULL, - false); + OPTION_PARSER_NORMAL); } /* Test the OPTION, OPTION_LONG, OPTION_SHORT, OPTION_FULL, OPTION_GROUP macros @@ -1110,7 +1110,7 @@ TEST(option_optional_arg_consume) { {} }, NULL, - /* stop_at_first_nonoption= */ false); + OPTION_PARSER_NORMAL); /* --user without arg: next arg is an option, so no consumption */ test_option_parse_one(STRV_MAKE("arg0", @@ -1123,7 +1123,7 @@ TEST(option_optional_arg_consume) { {} }, NULL, - /* stop_at_first_nonoption= */ false); + OPTION_PARSER_NORMAL); /* --user without arg: next arg is positional (doesn't start with -). * The option parser returns NULL for the arg. The caller would then diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index c1b913217a7cf..d95a2ccf647cd 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -319,7 +319,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - OptionParser state = { argc, argv, /* stop_at_first_nonoption= */ true }; + OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; const Option *current; const char *arg; From 4efb5d389c9653e3a61e583c64dc3d094eb8911e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20L=C3=BCke?= Date: Thu, 16 Apr 2026 15:24:27 +0900 Subject: [PATCH 1019/1296] sysupdated: don't crash when an mstack machine image is found As soon as machinectl list-images has an mstack entry updatectl fails because systemd-sysupdated crashes with an assertion failing because the mstack case was not handled. For now mstack is not supported as image for sysupdate to operate on and we can skip it. Fixes https://github.com/systemd/systemd/issues/41649 --- src/sysupdate/sysupdated.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sysupdate/sysupdated.c b/src/sysupdate/sysupdated.c index f387494b58051..4c7a759a4dadb 100644 --- a/src/sysupdate/sysupdated.c +++ b/src/sysupdate/sysupdated.c @@ -1882,6 +1882,9 @@ static int manager_enumerate_image_class(Manager *m, TargetClass class) { if (image_is_host(image)) continue; /* We already enroll the host ourselves */ + if (image->type == IMAGE_MSTACK) + continue; /* systemd-sysupdate doesn't support mstack images yet */ + r = target_new(m, class, image->name, image->path, &t); if (r < 0) return r; From 1e3f41e73b035dc5a96281bd243596bc7f0fa6d5 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Fri, 17 Oct 2025 05:13:14 -0700 Subject: [PATCH 1020/1296] core: implement AutomountContext/Runtime for io.systemd.Unit.List + tests --- src/core/meson.build | 1 + src/core/varlink-automount.c | 22 +++++++++++++ src/core/varlink-automount.h | 7 ++++ src/core/varlink-unit.c | 26 ++++++++------- src/shared/varlink-io.systemd.Unit.c | 38 ++++++++++++++++++++-- src/shared/varlink-io.systemd.Unit.h | 1 + src/test/test-varlink-idl-unit.c | 4 +++ test/units/TEST-74-AUX-UTILS.varlinkctl.sh | 5 +++ 8 files changed, 90 insertions(+), 14 deletions(-) create mode 100644 src/core/varlink-automount.c create mode 100644 src/core/varlink-automount.h diff --git a/src/core/meson.build b/src/core/meson.build index 24e7a5e366308..9a91c37112e9f 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -64,6 +64,7 @@ libcore_sources = files( 'unit-serialize.c', 'unit.c', 'varlink.c', + 'varlink-automount.c', 'varlink-cgroup.c', 'varlink-common.c', 'varlink-dynamic-user.c', diff --git a/src/core/varlink-automount.c b/src/core/varlink-automount.c new file mode 100644 index 0000000000000..2bb5fa397d043 --- /dev/null +++ b/src/core/varlink-automount.c @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "automount.h" +#include "json-util.h" +#include "varlink-automount.h" + +int automount_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Automount *a = ASSERT_PTR(AUTOMOUNT(userdata)); + return sd_json_buildo( + ASSERT_PTR(ret), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Where", a->where), + JSON_BUILD_PAIR_STRING_NON_EMPTY("ExtraOptions", a->extra_options), + SD_JSON_BUILD_PAIR_UNSIGNED("DirectoryMode", a->directory_mode), + JSON_BUILD_PAIR_FINITE_USEC("TimeoutIdleUSec", a->timeout_idle_usec)); +} + +int automount_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Automount *a = ASSERT_PTR(AUTOMOUNT(userdata)); + return sd_json_buildo(ASSERT_PTR(ret), JSON_BUILD_PAIR_ENUM("Result", automount_result_to_string(a->result))); +} diff --git a/src/core/varlink-automount.h b/src/core/varlink-automount.h new file mode 100644 index 0000000000000..892f8ba65b9f6 --- /dev/null +++ b/src/core/varlink-automount.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" + +int automount_context_build_json(sd_json_variant **ret, const char *name, void *userdata); +int automount_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata); diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index 124d6d353b68b..5f8d1764d5e24 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -18,6 +18,7 @@ #include "set.h" #include "strv.h" #include "unit.h" +#include "varlink-automount.h" #include "varlink-cgroup.h" #include "varlink-execute.h" #include "varlink-kill.h" @@ -113,6 +114,11 @@ static int unit_context_build_json(sd_json_variant **ret, const char *name, void * If it make sense to place a property into a config/unit file it belongs to Context. * Otherwise it's a 'Runtime'. */ + /* TODO missing callbacks */ + static const sd_json_build_callback_t unit_type_callbacks[_UNIT_TYPE_MAX] = { + [UNIT_AUTOMOUNT] = automount_context_build_json, + }; + return sd_json_buildo( ASSERT_PTR(ret), SD_JSON_BUILD_PAIR_STRING("Type", unit_type_to_string(u->type)), @@ -191,16 +197,8 @@ static int unit_context_build_json(sd_json_variant **ret, const char *name, void JSON_BUILD_PAIR_CALLBACK_NON_NULL("CGroup", unit_cgroup_context_build_json, u), JSON_BUILD_PAIR_CALLBACK_NON_NULL("Exec", unit_exec_context_build_json, u), - JSON_BUILD_PAIR_CALLBACK_NON_NULL("Kill", unit_kill_context_build_json, unit_get_kill_context(u))); - - // TODO follow up PRs: - // Mount/Automount context - // Path context - // Scope context - // Swap context - // Timer context - // Service context - // Socket context + JSON_BUILD_PAIR_CALLBACK_NON_NULL("Kill", unit_kill_context_build_json, unit_get_kill_context(u)), + JSON_BUILD_PAIR_CALLBACK_NON_NULL(unit_type_to_capitalized_string(u->type), unit_type_callbacks[u->type], u)); } static int can_clean_build_json(sd_json_variant **ret, const char *name, void *userdata) { @@ -280,6 +278,11 @@ static int unit_runtime_build_json(sd_json_variant **ret, const char *name, void Unit *u = ASSERT_PTR(userdata); Unit *f = unit_following(u); + /* TODO missing callbacks */ + static const sd_json_build_callback_t unit_type_callbacks[_UNIT_TYPE_MAX] = { + [UNIT_AUTOMOUNT] = automount_runtime_build_json, + }; + return sd_json_buildo( ASSERT_PTR(ret), JSON_BUILD_PAIR_STRING_NON_EMPTY("Following", f ? f->id : NULL), @@ -309,7 +312,8 @@ static int unit_runtime_build_json(sd_json_variant **ret, const char *name, void SD_JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(u->invocation_id), "InvocationID", SD_JSON_BUILD_UUID(u->invocation_id)), JSON_BUILD_PAIR_CALLBACK_NON_NULL("Markers", markers_build_json, &u->markers), JSON_BUILD_PAIR_CALLBACK_NON_NULL("ActivationDetails", activation_details_build_json, u->activation_details), - JSON_BUILD_PAIR_CALLBACK_NON_NULL("CGroup", unit_cgroup_runtime_build_json, u)); + JSON_BUILD_PAIR_CALLBACK_NON_NULL("CGroup", unit_cgroup_runtime_build_json, u), + JSON_BUILD_PAIR_CALLBACK_NON_NULL(unit_type_to_capitalized_string(u->type), unit_type_callbacks[u->type], u)); } static int list_unit_one(sd_varlink *link, Unit *unit) { diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c index fbcdce8e0677f..b11aedfa5af5d 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -928,6 +928,19 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.kill.html#WatchdogSignal="), SD_VARLINK_DEFINE_FIELD(WatchdogSignal, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); +/* AutomountContext + * https://www.freedesktop.org/software/systemd/man/latest/systemd.automount.html */ +static SD_VARLINK_DEFINE_STRUCT_TYPE( + AutomountContext, + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.automount.html#Where="), + SD_VARLINK_DEFINE_FIELD(Where, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.automount.html#ExtraOptions="), + SD_VARLINK_DEFINE_FIELD(ExtraOptions, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.automount.html#DirectoryMode="), + SD_VARLINK_DEFINE_FIELD(DirectoryMode, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.automount.html#TimeoutIdleSec="), + SD_VARLINK_DEFINE_FIELD(TimeoutIdleUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); + /* UnitContext */ static SD_VARLINK_DEFINE_STRUCT_TYPE( Condition, @@ -1087,8 +1100,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The exec context of the unit"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Exec, ExecContext, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The kill context of the unit"), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(Kill, KillContext, SD_VARLINK_NULLABLE)); - + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Kill, KillContext, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The automount context of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Automount, AutomountContext, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_STRUCT_TYPE( ActivationDetails, @@ -1162,6 +1176,19 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The number of processes of this unit killed by systemd-oomd"), SD_VARLINK_DEFINE_FIELD(ManagedOOMKills, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); +SD_VARLINK_DEFINE_ENUM_TYPE( + AutomountResult, + SD_VARLINK_DEFINE_ENUM_VALUE(success), + SD_VARLINK_DEFINE_ENUM_VALUE(resources), + SD_VARLINK_DEFINE_ENUM_VALUE(start_limit_hit), + SD_VARLINK_DEFINE_ENUM_VALUE(mount_start_limit_hit), + SD_VARLINK_DEFINE_ENUM_VALUE(unmounted)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + AutomountRuntime, + SD_VARLINK_FIELD_COMMENT("Result of automount operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, AutomountResult, 0)); + static SD_VARLINK_DEFINE_STRUCT_TYPE( UnitRuntime, SD_VARLINK_FIELD_COMMENT("If not empty, the field contains the name of another unit that this unit follows in state"), @@ -1219,7 +1246,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Provides details about why a unit was activated"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(ActivationDetails, ActivationDetails, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The cgroup runtime of the unit"), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(CGroup, CGroupRuntime, SD_VARLINK_NULLABLE)); + SD_VARLINK_DEFINE_FIELD_BY_TYPE(CGroup, CGroupRuntime, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The automount runtime of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Automount, AutomountRuntime, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD_FULL( List, @@ -1350,6 +1379,9 @@ SD_VARLINK_DEFINE_INTERFACE( /* other contexts */ &vl_type_KillMode, &vl_type_KillContext, + &vl_type_AutomountContext, + &vl_type_AutomountResult, + &vl_type_AutomountRuntime, /* UnitContext enums */ &vl_type_CollectMode, diff --git a/src/shared/varlink-io.systemd.Unit.h b/src/shared/varlink-io.systemd.Unit.h index 61abb754bc7c6..0b3cbf30e5764 100644 --- a/src/shared/varlink-io.systemd.Unit.h +++ b/src/shared/varlink-io.systemd.Unit.h @@ -28,5 +28,6 @@ extern const sd_varlink_symbol vl_type_IOSchedulingClass; extern const sd_varlink_symbol vl_type_NUMAPolicy; extern const sd_varlink_symbol vl_type_MountPropagationFlag; extern const sd_varlink_symbol vl_type_KillMode; +extern const sd_varlink_symbol vl_type_AutomountResult; extern const sd_varlink_symbol vl_type_CollectMode; extern const sd_varlink_symbol vl_type_JobMode; diff --git a/src/test/test-varlink-idl-unit.c b/src/test/test-varlink-idl-unit.c index 90ff3f6b787c1..56a50554d8142 100644 --- a/src/test/test-varlink-idl-unit.c +++ b/src/test/test-varlink-idl-unit.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "automount.h" #include "cgroup.h" #include "ioprio-util.h" #include "kill.h" @@ -51,6 +52,9 @@ TEST(unit_enums_idl) { TEST_IDL_ENUM(CGroupPressureWatch, cgroup_pressure_watch, vl_type_CGroupPressureWatch); TEST_IDL_ENUM(CGroupController, cgroup_controller, vl_type_CGroupController); + /* AutomountRuntime enums */ + TEST_IDL_ENUM(AutomountResult, automount_result, vl_type_AutomountResult); + /* UnitContext enums */ TEST_IDL_ENUM(CollectMode, collect_mode, vl_type_CollectMode); TEST_IDL_ENUM(EmergencyAction, emergency_action, vl_type_EmergencyAction); diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh index 2ec1e0408955d..318034323ba67 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -232,6 +232,11 @@ invocation_id="$(systemctl show -P InvocationID systemd-journald.service)" varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"invocationID\": \"$invocation_id\"}" # test for KillContext varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {"pid": 0}}' | jq -e '.context.Kill' +# test for AutomountContext/Runtime +automount_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "automount" and .runtime.LoadState == "loaded") .context.ID' | grep -v null | tail -n 1) +test -n "$automount_id" +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"name\": \"$automount_id\"}" | jq -e '.context.Automount' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"name\": \"$automount_id\"}" | jq -e '.runtime.Automount' # test io.systemd.Metrics varlinkctl info /run/systemd/report/io.systemd.Manager From bcdf4d0c70a19bd646ad8f162a7142ae19445712 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Fri, 17 Oct 2025 07:59:23 -0700 Subject: [PATCH 1021/1296] core: implement MountContext/Runtime for io.systemd.Unit.List + tests --- src/core/meson.build | 1 + src/core/varlink-mount.c | 55 +++++++++++++++++ src/core/varlink-mount.h | 7 +++ src/core/varlink-unit.c | 3 + src/shared/varlink-io.systemd.Unit.c | 69 +++++++++++++++++++++- src/shared/varlink-io.systemd.Unit.h | 1 + src/test/test-varlink-idl-unit.c | 4 ++ test/units/TEST-74-AUX-UTILS.varlinkctl.sh | 5 ++ 8 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 src/core/varlink-mount.c create mode 100644 src/core/varlink-mount.h diff --git a/src/core/meson.build b/src/core/meson.build index 9a91c37112e9f..c5ef7e48cbd5d 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -72,6 +72,7 @@ libcore_sources = files( 'varlink-kill.c', 'varlink-manager.c', 'varlink-metrics.c', + 'varlink-mount.c', 'varlink-unit.c', ) diff --git a/src/core/varlink-mount.c b/src/core/varlink-mount.c new file mode 100644 index 0000000000000..f4148cafb38e1 --- /dev/null +++ b/src/core/varlink-mount.c @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "json-util.h" +#include "mount.h" +#include "user-util.h" +#include "varlink-common.h" +#include "varlink-mount.h" + +int mount_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Mount *m = ASSERT_PTR(MOUNT(userdata)); + _cleanup_free_ char *what = NULL, *where = NULL, *options = NULL; + + what = mount_get_what_escaped(m); + if (!what) + return -ENOMEM; + + where = mount_get_where_escaped(m); + if (!where) + return -ENOMEM; + + options = mount_get_options_escaped(m); + if (!options) + return -ENOMEM; + + return sd_json_buildo( + ASSERT_PTR(ret), + JSON_BUILD_PAIR_STRING_NON_EMPTY("What", what), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Where", where), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Type", mount_get_fstype(m)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Options", options), + SD_JSON_BUILD_PAIR_BOOLEAN("SloppyOptions", m->sloppy_options), + SD_JSON_BUILD_PAIR_BOOLEAN("LazyUnmount", m->lazy_unmount), + SD_JSON_BUILD_PAIR_BOOLEAN("ReadWriteOnly", m->read_write_only), + SD_JSON_BUILD_PAIR_BOOLEAN("ForceUnmount", m->force_unmount), + SD_JSON_BUILD_PAIR_UNSIGNED("DirectoryMode", m->directory_mode), + JSON_BUILD_PAIR_FINITE_USEC("TimeoutUSec", m->timeout_usec), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecMount", exec_command_build_json, &m->exec_command[MOUNT_EXEC_MOUNT]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecUnmount", exec_command_build_json, &m->exec_command[MOUNT_EXEC_UNMOUNT]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecRemount", exec_command_build_json, &m->exec_command[MOUNT_EXEC_REMOUNT])); +} + +int mount_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Unit *u = ASSERT_PTR(userdata); + Mount *m = ASSERT_PTR(MOUNT(u)); + return sd_json_buildo( + ASSERT_PTR(ret), + SD_JSON_BUILD_PAIR_CONDITION(pidref_is_set(&m->control_pid), "ControlPID", JSON_BUILD_PIDREF(&m->control_pid)), + JSON_BUILD_PAIR_ENUM("Result", mount_result_to_string(m->result)), + JSON_BUILD_PAIR_ENUM("ReloadResult", mount_result_to_string(m->reload_result)), + JSON_BUILD_PAIR_ENUM("CleanResult", mount_result_to_string(m->clean_result)), + SD_JSON_BUILD_PAIR_CONDITION(uid_is_valid(u->ref_uid), "UID", SD_JSON_BUILD_UNSIGNED(u->ref_uid)), + SD_JSON_BUILD_PAIR_CONDITION(gid_is_valid(u->ref_gid), "GID", SD_JSON_BUILD_UNSIGNED(u->ref_gid))); +} diff --git a/src/core/varlink-mount.h b/src/core/varlink-mount.h new file mode 100644 index 0000000000000..30fd92781a5e2 --- /dev/null +++ b/src/core/varlink-mount.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" + +int mount_context_build_json(sd_json_variant **ret, const char *name, void *userdata); +int mount_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata); diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index 5f8d1764d5e24..789b21947c83b 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -22,6 +22,7 @@ #include "varlink-cgroup.h" #include "varlink-execute.h" #include "varlink-kill.h" +#include "varlink-mount.h" #include "varlink-unit.h" #include "varlink-util.h" @@ -117,6 +118,7 @@ static int unit_context_build_json(sd_json_variant **ret, const char *name, void /* TODO missing callbacks */ static const sd_json_build_callback_t unit_type_callbacks[_UNIT_TYPE_MAX] = { [UNIT_AUTOMOUNT] = automount_context_build_json, + [UNIT_MOUNT] = mount_context_build_json, }; return sd_json_buildo( @@ -281,6 +283,7 @@ static int unit_runtime_build_json(sd_json_variant **ret, const char *name, void /* TODO missing callbacks */ static const sd_json_build_callback_t unit_type_callbacks[_UNIT_TYPE_MAX] = { [UNIT_AUTOMOUNT] = automount_runtime_build_json, + [UNIT_MOUNT] = mount_runtime_build_json, }; return sd_json_buildo( diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c index b11aedfa5af5d..422aeb6952b30 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -941,6 +941,37 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.automount.html#TimeoutIdleSec="), SD_VARLINK_DEFINE_FIELD(TimeoutIdleUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); +/* MountContext + * https://www.freedesktop.org/software/systemd/man/latest/systemd.mount.html */ +static SD_VARLINK_DEFINE_STRUCT_TYPE( + MountContext, + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.mount.html#What="), + SD_VARLINK_DEFINE_FIELD(What, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.mount.html#Where="), + SD_VARLINK_DEFINE_FIELD(Where, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.mount.html#Type="), + SD_VARLINK_DEFINE_FIELD(Type, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.mount.html#Options="), + SD_VARLINK_DEFINE_FIELD(Options, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.mount.html#SloppyOptions="), + SD_VARLINK_DEFINE_FIELD(SloppyOptions, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.mount.html#LazyUnmount="), + SD_VARLINK_DEFINE_FIELD(LazyUnmount, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.mount.html#ReadWriteOnly="), + SD_VARLINK_DEFINE_FIELD(ReadWriteOnly, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.mount.html#ForceUnmount="), + SD_VARLINK_DEFINE_FIELD(ForceUnmount, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.mount.html#DirectoryMode="), + SD_VARLINK_DEFINE_FIELD(DirectoryMode, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.mount.html#TimeoutSec="), + SD_VARLINK_DEFINE_FIELD(TimeoutUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Mount command"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecMount, ExecCommand, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Unmount command"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecUnmount, ExecCommand, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Remount command"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecRemount, ExecCommand, SD_VARLINK_NULLABLE)); + /* UnitContext */ static SD_VARLINK_DEFINE_STRUCT_TYPE( Condition, @@ -1102,7 +1133,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The kill context of the unit"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Kill, KillContext, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The automount context of the unit"), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(Automount, AutomountContext, SD_VARLINK_NULLABLE)); + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Automount, AutomountContext, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The mount context of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Mount, MountContext, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_STRUCT_TYPE( ActivationDetails, @@ -1189,6 +1222,32 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Result of automount operation"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, AutomountResult, 0)); +SD_VARLINK_DEFINE_ENUM_TYPE( + MountResult, + SD_VARLINK_DEFINE_ENUM_VALUE(success), + SD_VARLINK_DEFINE_ENUM_VALUE(resources), + SD_VARLINK_DEFINE_ENUM_VALUE(timeout), + SD_VARLINK_DEFINE_ENUM_VALUE(exit_code), + SD_VARLINK_DEFINE_ENUM_VALUE(signal), + SD_VARLINK_DEFINE_ENUM_VALUE(core_dump), + SD_VARLINK_DEFINE_ENUM_VALUE(start_limit_hit), + SD_VARLINK_DEFINE_ENUM_VALUE(protocol)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + MountRuntime, + SD_VARLINK_FIELD_COMMENT("PID of the current mount/remount/etc process running"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ControlPID, ProcessId, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Result of mount operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, MountResult, 0), + SD_VARLINK_FIELD_COMMENT("Result of remount operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ReloadResult, MountResult, 0), + SD_VARLINK_FIELD_COMMENT("Result of cleaning operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(CleanResult, MountResult, 0), + SD_VARLINK_FIELD_COMMENT("Reference UID"), + SD_VARLINK_DEFINE_FIELD(UID, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Reference GID"), + SD_VARLINK_DEFINE_FIELD(GID, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); + static SD_VARLINK_DEFINE_STRUCT_TYPE( UnitRuntime, SD_VARLINK_FIELD_COMMENT("If not empty, the field contains the name of another unit that this unit follows in state"), @@ -1248,7 +1307,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The cgroup runtime of the unit"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(CGroup, CGroupRuntime, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The automount runtime of the unit"), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(Automount, AutomountRuntime, SD_VARLINK_NULLABLE)); + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Automount, AutomountRuntime, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The mount runtime of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Mount, MountRuntime, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD_FULL( List, @@ -1382,6 +1443,10 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_AutomountContext, &vl_type_AutomountResult, &vl_type_AutomountRuntime, + &vl_type_ExecCommand, + &vl_type_MountContext, + &vl_type_MountResult, + &vl_type_MountRuntime, /* UnitContext enums */ &vl_type_CollectMode, diff --git a/src/shared/varlink-io.systemd.Unit.h b/src/shared/varlink-io.systemd.Unit.h index 0b3cbf30e5764..a39407133844c 100644 --- a/src/shared/varlink-io.systemd.Unit.h +++ b/src/shared/varlink-io.systemd.Unit.h @@ -29,5 +29,6 @@ extern const sd_varlink_symbol vl_type_NUMAPolicy; extern const sd_varlink_symbol vl_type_MountPropagationFlag; extern const sd_varlink_symbol vl_type_KillMode; extern const sd_varlink_symbol vl_type_AutomountResult; +extern const sd_varlink_symbol vl_type_MountResult; extern const sd_varlink_symbol vl_type_CollectMode; extern const sd_varlink_symbol vl_type_JobMode; diff --git a/src/test/test-varlink-idl-unit.c b/src/test/test-varlink-idl-unit.c index 56a50554d8142..2f4bb1c9e9156 100644 --- a/src/test/test-varlink-idl-unit.c +++ b/src/test/test-varlink-idl-unit.c @@ -4,6 +4,7 @@ #include "cgroup.h" #include "ioprio-util.h" #include "kill.h" +#include "mount.h" #include "numa-util.h" #include "process-util.h" #include "tests.h" @@ -55,6 +56,9 @@ TEST(unit_enums_idl) { /* AutomountRuntime enums */ TEST_IDL_ENUM(AutomountResult, automount_result, vl_type_AutomountResult); + /* MountRuntime enums */ + TEST_IDL_ENUM(MountResult, mount_result, vl_type_MountResult); + /* UnitContext enums */ TEST_IDL_ENUM(CollectMode, collect_mode, vl_type_CollectMode); TEST_IDL_ENUM(EmergencyAction, emergency_action, vl_type_EmergencyAction); diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh index 318034323ba67..9a22757067f24 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -237,6 +237,11 @@ automount_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.syst test -n "$automount_id" varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"name\": \"$automount_id\"}" | jq -e '.context.Automount' varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"name\": \"$automount_id\"}" | jq -e '.runtime.Automount' +# test for MountContext/Runtime +mount_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "mount" and .runtime.LoadState == "loaded") .context.ID' | grep -v null | tail -n 1) +test -n "$mount_id" +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"name\": \"$mount_id\"}" | jq -e '.context.Mount' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"name\": \"$mount_id\"}" | jq -e '.runtime.Mount' # test io.systemd.Metrics varlinkctl info /run/systemd/report/io.systemd.Manager From f4c07a08950c6dc28f0df4918edd8483cb20bb0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 15 Apr 2026 22:52:06 +0200 Subject: [PATCH 1022/1296] shared/options: add equivalent of "-" in getopt_long The parsing "mode" is specified as an exclusive mode, i.e. a combination of "+" and "-" is not supported. In principle this could be supported, but we don't use that in our code and are unlikely ever to do so. --- src/shared/options.c | 37 +++++++++++--- src/shared/options.h | 14 +++-- src/test/test-options.c | 111 ++++++++++++++++++++++++++++++---------- 3 files changed, 126 insertions(+), 36 deletions(-) diff --git a/src/shared/options.c b/src/shared/options.c index 5d4df946b83fb..fbf9a31181754 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -22,6 +22,7 @@ static bool option_arg_required(const Option *opt) { static bool option_is_metadata(const Option *opt) { /* A metadata entry that is not a real option, like the group marker */ return ASSERT_PTR(opt)->flags & (OPTION_GROUP_MARKER | + OPTION_POSITIONAL_ENTRY | OPTION_HELP_ENTRY | OPTION_HELP_ENTRY_VERBATIM); } @@ -89,9 +90,10 @@ int option_parse( const char *optname = NULL, *optval = NULL; _cleanup_free_ char *_optname = NULL; /* allocated option name */ bool separate_optval = false; + bool handling_positional_arg = false; if (state->short_option_offset == 0) { - /* Skip over non-option parameters */ + /* Handle non-option parameters */ for (;;) { if (state->optind == state->argc) return 0; @@ -116,14 +118,31 @@ int option_parse( return 0; } + if (state->mode == OPTION_PARSER_RETURN_POSITIONAL_ARGS) { + handling_positional_arg = true; + optval = state->argv[state->optind]; + break; + } + state->optind++; } /* Find matching option entry. * First, figure out if we have a long option or a short option. */ - assert(state->argv[state->optind][0] == '-'); + assert(handling_positional_arg || state->argv[state->optind][0] == '-'); + + if (handling_positional_arg) + /* We are supposed to return the positional arg to be handled. */ + for (option = options;; option++) { + /* If OPTION_PARSER_RETURN_POSITIONAL_ARGS is specified, + * OPTION_POSITIONAL must be used. */ + assert(option < options_end); - if (state->argv[state->optind][1] == '-') { + if (FLAGS_SET(option->flags, OPTION_POSITIONAL_ENTRY)) + break; + } + + else if (state->argv[state->optind][1] == '-') { /* We have a long option. */ char *eq = strchr(state->argv[state->optind], '='); if (eq) { @@ -206,11 +225,11 @@ int option_parse( assert(option); - if (optval && !option_takes_arg(option)) + if (!handling_positional_arg && optval && !option_takes_arg(option)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s: option '%s' doesn't allow an argument", program_invocation_short_name, optname); - if (!optval && option_arg_required(option)) { + if (!handling_positional_arg && !optval && option_arg_required(option)) { if (!state->argv[state->optind + 1]) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s: option '%s' requires an argument", @@ -220,7 +239,13 @@ int option_parse( } if (state->short_option_offset == 0) { - /* We're done with this option. Adjust the array and position. */ + /* We're done with this parameter. Adjust the array and position. */ + if (handling_positional_arg) { + /* Sanity check */ + assert(state->positional_offset == state->optind); + assert(!separate_optval); + } + shift_arg(state->argv, state->positional_offset++, state->optind++); if (separate_optval) shift_arg(state->argv, state->positional_offset++, state->optind++); diff --git a/src/shared/options.h b/src/shared/options.h index a10f914da266c..cd9f64fd4949a 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -6,10 +6,11 @@ typedef enum OptionFlags { OPTION_OPTIONAL_ARG = 1U << 0, /* Same as optional_argument in getopt */ - OPTION_STOPS_PARSING = 1U << 1, /* This option acts like "--" */ - OPTION_GROUP_MARKER = 1U << 2, /* Fake option entry to separate groups */ - OPTION_HELP_ENTRY = 1U << 3, /* Fake option entry to insert an additional help line */ - OPTION_HELP_ENTRY_VERBATIM = 1U << 4, /* Same, but use the long_code in the first column as written */ + OPTION_POSITIONAL_ENTRY = 1U << 1, /* The "option" to handle positional arguments */ + OPTION_STOPS_PARSING = 1U << 2, /* This option acts like "--" */ + OPTION_GROUP_MARKER = 1U << 3, /* Fake option entry to separate groups */ + OPTION_HELP_ENTRY = 1U << 4, /* Fake option entry to insert an additional help line */ + OPTION_HELP_ENTRY_VERBATIM = 1U << 5, /* Same, but use the long_code in the first column as written */ } OptionFlags; typedef struct Option { @@ -50,6 +51,7 @@ typedef struct Option { #define OPTION_LONG_FLAGS(fl, lc, mv, h) OPTION_FULL(fl, /* sc= */ 0, lc, mv, h) #define OPTION_SHORT(sc, mv, h) OPTION(sc, /* lc= */ NULL, mv, h) #define OPTION_SHORT_FLAGS(fl, sc, mv, h) OPTION_FULL(fl, sc, /* lc= */ NULL, mv, h) +#define OPTION_POSITIONAL OPTION_FULL(OPTION_POSITIONAL_ENTRY, /* sc= */ 0, "(positional)", /* mv= */ NULL, /* h= */ NULL) #define OPTION_HELP_VERBATIM(lc, h) OPTION_FULL(OPTION_HELP_ENTRY_VERBATIM, /* sc= */ 0, lc, /* mv= */ NULL, h) #define OPTION_COMMON_HELP \ @@ -99,6 +101,10 @@ typedef enum OptionParserMode { /* Same as "+…" for getopt_long — only parse options before the first positional argument. */ OPTION_PARSER_STOP_AT_FIRST_NONOPTION, + /* Same as "-…" for getopt_long — return positional arguments as "options" to be handled by the + * option handler specified with OPTION_POSITIONAL. */ + OPTION_PARSER_RETURN_POSITIONAL_ARGS, + _OPTION_PARSER_MODE_MAX, } OptionParserMode; diff --git a/src/test/test-options.c b/src/test/test-options.c index e6b18d7a7eaeb..05fd4bcbe0761 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -741,7 +741,8 @@ TEST(option_optional_arg) { static void test_macros_parse_one( char **argv, const Entry *entries, - char **remaining) { + char **remaining, + OptionParserMode mode) { _cleanup_free_ char *joined = strv_join(argv, ", "); log_debug("/* %s(%s) */", __func__, joined); @@ -755,7 +756,7 @@ static void test_macros_parse_one( for (const Entry *e = entries; e && (e->long_code || e->short_code != 0); e++) n_entries++; - OptionParser state = { argc, argv }; + OptionParser state = { argc, argv, mode }; const Option *opt; const char *arg; @@ -807,6 +808,9 @@ static void test_macros_parse_one( OPTION_LONG("debug", NULL, "Enable debug mode"): break; + OPTION_POSITIONAL: + break; + default: log_error("Unexpected option id: %d", c); ASSERT_TRUE(false); @@ -828,7 +832,8 @@ TEST(option_macros) { { "help" }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* OPTION: short form */ test_macros_parse_one(STRV_MAKE("arg0", @@ -837,7 +842,8 @@ TEST(option_macros) { { "help" }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* OPTION_LONG: only accessible via long form */ test_macros_parse_one(STRV_MAKE("arg0", @@ -846,7 +852,8 @@ TEST(option_macros) { { "version" }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* OPTION_SHORT: only accessible via short form */ test_macros_parse_one(STRV_MAKE("arg0", @@ -855,7 +862,8 @@ TEST(option_macros) { { .short_code = 'v' }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* OPTION with required arg: long --required=ARG */ test_macros_parse_one(STRV_MAKE("arg0", @@ -864,7 +872,8 @@ TEST(option_macros) { { "required", "val1" }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* OPTION with required arg: long --required ARG */ test_macros_parse_one(STRV_MAKE("arg0", @@ -873,7 +882,8 @@ TEST(option_macros) { { "required", "val1" }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* OPTION with required arg: short -r ARG */ test_macros_parse_one(STRV_MAKE("arg0", @@ -882,7 +892,8 @@ TEST(option_macros) { { "required", "val1" }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* OPTION with required arg: short -rARG */ test_macros_parse_one(STRV_MAKE("arg0", @@ -891,7 +902,8 @@ TEST(option_macros) { { "required", "val1" }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* OPTION_FULL with OPTION_OPTIONAL_ARG: long with = */ test_macros_parse_one(STRV_MAKE("arg0", @@ -900,7 +912,8 @@ TEST(option_macros) { { "optional", "val1" }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* OPTION_FULL with OPTION_OPTIONAL_ARG: long without = doesn't consume next */ test_macros_parse_one(STRV_MAKE("arg0", @@ -909,7 +922,8 @@ TEST(option_macros) { { "optional", NULL }, {} }, - STRV_MAKE("pos1")); + STRV_MAKE("pos1"), + OPTION_PARSER_NORMAL); /* OPTION_FULL with OPTION_OPTIONAL_ARG: short inline */ test_macros_parse_one(STRV_MAKE("arg0", @@ -918,7 +932,8 @@ TEST(option_macros) { { "optional", "val1" }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* OPTION_FULL with OPTION_OPTIONAL_ARG: short without inline */ test_macros_parse_one(STRV_MAKE("arg0", @@ -927,7 +942,8 @@ TEST(option_macros) { { "optional", NULL }, {} }, - STRV_MAKE("pos1")); + STRV_MAKE("pos1"), + OPTION_PARSER_NORMAL); /* OPTION_FULL with OPTION_STOPS_PARSING: stops further option parsing */ test_macros_parse_one(STRV_MAKE("arg0", @@ -939,7 +955,8 @@ TEST(option_macros) { {} }, STRV_MAKE("--help", - "--version")); + "--version"), + OPTION_PARSER_NORMAL); /* OPTION_STOPS_PARSING: options before are still parsed */ test_macros_parse_one(STRV_MAKE("arg0", @@ -953,7 +970,8 @@ TEST(option_macros) { {} }, STRV_MAKE("-h", - "--debug")); + "--debug"), + OPTION_PARSER_NORMAL); /* OPTION_STOPS_PARSING with "--": "--" after exec is still consumed */ test_macros_parse_one(STRV_MAKE("arg0", @@ -964,7 +982,8 @@ TEST(option_macros) { { "exec" }, {} }, - STRV_MAKE("--help")); + STRV_MAKE("--help"), + OPTION_PARSER_NORMAL); /* OPTION_STOPS_PARSING with "--": "--" before exec takes precedence */ test_macros_parse_one(STRV_MAKE("arg0", @@ -975,7 +994,8 @@ TEST(option_macros) { {} }, STRV_MAKE("--exec", - "--help")); + "--help"), + OPTION_PARSER_NORMAL); /* OPTION_GROUP: group marker is transparent to parsing, --debug in Advanced group works */ test_macros_parse_one(STRV_MAKE("arg0", @@ -984,7 +1004,8 @@ TEST(option_macros) { { "debug" }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* Mixed: all macro types together */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1010,7 +1031,8 @@ TEST(option_macros) { {} }, STRV_MAKE("pos1", - "pos2")); + "pos2"), + OPTION_PARSER_NORMAL); /* Short option combos with macros: -hv (help + verbose) */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1020,7 +1042,8 @@ TEST(option_macros) { { .short_code = 'v' }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* Short option combo with required arg: -hrval (help + required with arg "val") */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1030,7 +1053,8 @@ TEST(option_macros) { { "required", "val" }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* Short option combo with optional arg: -hoval (help + optional with arg "val") */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1040,7 +1064,8 @@ TEST(option_macros) { { "optional", "val" }, {} }, - NULL); + NULL, + OPTION_PARSER_NORMAL); /* OPTION_STOPS_PARSING then "--": "--" is still consumed after exec */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1055,7 +1080,8 @@ TEST(option_macros) { {} }, STRV_MAKE("--version", - "-h")); + "-h"), + OPTION_PARSER_NORMAL); /* OPTION_STOPS_PARSING then later "--": "--" is not consumed */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1071,7 +1097,8 @@ TEST(option_macros) { }, STRV_MAKE("--version", "--", - "-h")); + "-h"), + OPTION_PARSER_NORMAL); /* OPTION_STOPS_PARSING then "--" twice: second "--" is not consumed */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1088,7 +1115,39 @@ TEST(option_macros) { }, STRV_MAKE("--", "--version", - "-h")); + "-h"), + OPTION_PARSER_NORMAL); + + /* Basic OPTION_POSITIONAL use */ + test_macros_parse_one(STRV_MAKE("arg0", + "--help", + "arg1", + "--debug", + "arg2"), + (Entry[]) { + { "help" }, + { "(positional)", "arg1" }, + { "debug" }, + { "(positional)", "arg2" }, + {} + }, + NULL, + OPTION_PARSER_RETURN_POSITIONAL_ARGS); + + /* OPTION_POSITIONAL combined with OPTION_STOPS_PARSING */ + test_macros_parse_one(STRV_MAKE("arg0", + "--help", + "arg1", + "--exec", + "arg2"), + (Entry[]) { + { "help" }, + { "(positional)", "arg1" }, + { "exec" }, + {} + }, + STRV_MAKE("arg2"), + OPTION_PARSER_RETURN_POSITIONAL_ARGS); } /* Test the pattern used by nspawn's --user: an optional-arg option that also From 3e716178cc3bcf972d878d3899ad1c977a7d707b Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 14 Apr 2026 20:22:39 +0100 Subject: [PATCH 1023/1296] machined: gate metadata querying behind inspect-machines/images action Ensure only privileged users can call the system scope machined's APIs that get data out of a machine Follow-up for 1bd979dddbb6ed3ffe410d78a7ff80cbb1c42a64 Follow-up for 9153b02bb5030e29d6008992fb74b9028d7c392c --- src/machine/machine-dbus.c | 19 ++++++++++++++++ src/machine/machined-varlink.c | 22 +++++++++++++++++++ src/machine/org.freedesktop.machine1.policy | 23 +++++++++++++++++++- test/units/TEST-13-NSPAWN.unpriv.sh | 24 +++++++++++++++++++++ 4 files changed, 87 insertions(+), 1 deletion(-) diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index c096a012d88f6..a9d15ca5f72b1 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -247,6 +247,25 @@ int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, s assert(message); + if (m->manager->runtime_scope != RUNTIME_SCOPE_USER) { + const char *details[] = { + "machine", m->name, + "verb", "get_os_release", + NULL + }; + + r = bus_verify_polkit_async( + message, + "org.freedesktop.machine1.inspect-machines", + details, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + } + r = machine_get_os_release(m, &l); if (r == -ENONET) return sd_bus_error_set(error, SD_BUS_ERROR_FAILED, "Machine does not contain OS release information."); diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c index 4ab68a77f9e2b..acc2137f830b8 100644 --- a/src/machine/machined-varlink.c +++ b/src/machine/machined-varlink.c @@ -535,6 +535,17 @@ static int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varl if (r != 0) return r; + if (m->runtime_scope != RUNTIME_SCOPE_USER && should_acquire_metadata(p.acquire_metadata)) { + r = varlink_verify_polkit_async( + link, + m->system_bus, + "org.freedesktop.machine1.inspect-machines", + (const char**) STRV_MAKE("name", strna(p.name)), + &m->polkit_registry); + if (r <= 0) + return r; + } + r = sd_varlink_set_sentinel(link, VARLINK_ERROR_MACHINE_NO_SUCH_MACHINE); if (r < 0) return r; @@ -682,6 +693,17 @@ static int vl_method_list_images(sd_varlink *link, sd_json_variant *parameters, if (r != 0) return r; + if (m->runtime_scope != RUNTIME_SCOPE_USER && should_acquire_metadata(p.acquire_metadata)) { + r = varlink_verify_polkit_async( + link, + m->system_bus, + "org.freedesktop.machine1.inspect-images", + (const char**) STRV_MAKE("name", strna(p.image_name)), + &m->polkit_registry); + if (r <= 0) + return r; + } + r = sd_varlink_set_sentinel(link, VARLINK_ERROR_MACHINE_IMAGE_NO_SUCH_IMAGE); if (r < 0) return r; diff --git a/src/machine/org.freedesktop.machine1.policy b/src/machine/org.freedesktop.machine1.policy index d5b8d83d2aade..f8f498f8cfc91 100644 --- a/src/machine/org.freedesktop.machine1.policy +++ b/src/machine/org.freedesktop.machine1.policy @@ -88,7 +88,17 @@ auth_admin auth_admin_keep - org.freedesktop.login1.shell org.freedesktop.login1.login + org.freedesktop.login1.shell org.freedesktop.login1.login org.freedesktop.machine1.inspect-machines + + + + Inspect local virtual machines and containers + Authentication is required to inspect local virtual machines and containers. + + auth_admin + auth_admin + auth_admin_keep + @@ -120,6 +130,17 @@ auth_admin auth_admin_keep + org.freedesktop.machine1.inspect-images + + + + Inspect local virtual machine and container images + Authentication is required to inspect local virtual machine and container images. + + auth_admin + auth_admin + auth_admin_keep + diff --git a/test/units/TEST-13-NSPAWN.unpriv.sh b/test/units/TEST-13-NSPAWN.unpriv.sh index 75a9c1aac070b..e0516449c70b0 100755 --- a/test/units/TEST-13-NSPAWN.unpriv.sh +++ b/test/units/TEST-13-NSPAWN.unpriv.sh @@ -23,6 +23,8 @@ at_exit() { rm -rf /home/testuser/.local/state/machines/inodetest2 ||: rm -rf /home/testuser/.local/state/machines/mangletest ||: machinectl terminate zurps ||: + machinectl terminate exfiltrate ||: + systemctl --user --machine testuser@ stop exfiltrate.service ||: rm -f /etc/polkit-1/rules.d/registermachinetest.rules machinectl terminate nurps ||: machinectl terminate kurps ||: @@ -163,6 +165,28 @@ run0 -u testuser \ systemctl --user --machine testuser@ stop sleep.service test ! -f /shouldnotwork +echo FOO=bar >/tmp/foo +chmod 600 /tmp/foo +run0 -u testuser \ + systemd-run --unit exfiltrate.service --service-type notify --property NotifyAccess=all --user \ + unshare --map-root-user --user --mount \ + bash -c 'mount --bind /tmp/foo /usr/lib/os-release; systemd-notify --ready; exec sleep infinity' +exfiltrate_pid="$(systemctl --machine testuser@.host show --user -P MainPID exfiltrate.service)" +run0 -u testuser \ + varlinkctl \ + call \ + /run/systemd/machine/io.systemd.Machine \ + io.systemd.Machine.Register \ + "{\"name\":\"exfiltrate\", \"class\":\"container\", \"leader\": $exfiltrate_pid}" +exfiltrate_output="$(run0 -u testuser \ + varlinkctl \ + call \ + /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List \ + "{\"name\":\"exfiltrate\",\"acquireMetadata\":\"graceful\"}" 2>&1)" || true +(! echo "$exfiltrate_output" | grep '"name".*"exfiltrate"' >/dev/null) +(! echo "$exfiltrate_output" | grep "FOO=bar" >/dev/null) +systemctl --user --machine testuser@ stop exfiltrate.service + run0 -u testuser mkdir /var/tmp/image-tar run0 -u testuser importctl --user export-tar zurps /var/tmp/image-tar/kurps.tar.gz -m run0 -u testuser importctl --user import-tar /var/tmp/image-tar/kurps.tar.gz -m From 75d43f16866b585d471bbe849d46415434498038 Mon Sep 17 00:00:00 2001 From: kostich Date: Thu, 16 Apr 2026 14:38:09 +0200 Subject: [PATCH 1024/1296] po: Update Serbian and add Serbian Latin translation (#41597) Updates the existing Serbian (`sr`) translation and adds a new Serbian Latin (`sr@latin`) translation, with both locales registered in `po/LINGUAS`. --- po/LINGUAS | 15 +- po/sr.po | 589 +++++++------------------ po/sr@latin.po | 1143 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1304 insertions(+), 443 deletions(-) create mode 100644 po/sr@latin.po diff --git a/po/LINGUAS b/po/LINGUAS index d167d935423ae..283750d34ef3b 100644 --- a/po/LINGUAS +++ b/po/LINGUAS @@ -1,6 +1,8 @@ +ar be be@latin bg +bo ca cs da @@ -22,7 +24,11 @@ it ja ka kab +kk +km +kn ko +kw lt nl pa @@ -35,15 +41,10 @@ si sk sl sr +sr@latin sv tr +ug uk zh_CN zh_TW -kn -ar -km -kw -kk -ug -bo diff --git a/po/sr.po b/po/sr.po index 8b351cd744f21..7ae48d423dba7 100644 --- a/po/sr.po +++ b/po/sr.po @@ -2,12 +2,15 @@ # # Serbian translation of systemd. # Frantisek Sumsal , 2021. +# Марко Костић , 2026 +# msgid "" msgstr "" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2021-02-23 22:40+0000\n" -"Last-Translator: Frantisek Sumsal \n" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: https://github.com/systemd/systemd/issues\n" +"POT-Creation-Date: 2026-03-20 14:34+0000\n" +"PO-Revision-Date: 2026-04-12 18:20+0200\n" +"Last-Translator: Марко Костић \n" "Language-Team: Serbian \n" "Language: sr\n" @@ -16,7 +19,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" -"X-Generator: Weblate 4.4.2\n" +"X-Generator: Poedit 3.9\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -71,259 +74,100 @@ msgstr "" #: src/core/org.freedesktop.systemd1.policy.in:74 msgid "Dump the systemd state without rate limits" -msgstr "" +msgstr "Испиши стање системд-а без ограничења протока" #: src/core/org.freedesktop.systemd1.policy.in:75 -#, fuzzy msgid "" "Authentication is required to dump the systemd state without rate limits." msgstr "" -"Потребно је да се идентификујете да бисте поново учитали стање систем-деа." +"Потребно је потврђивање идентитета за исписивање стања системде-а без " +"ограничења протока." #: src/home/org.freedesktop.home1.policy:13 msgid "Create a home area" -msgstr "" +msgstr "Направи лични простор" #: src/home/org.freedesktop.home1.policy:14 -#, fuzzy msgid "Authentication is required to create a user's home area." msgstr "" -"Потребно је да се идентификујете да бисте поново учитали стање систем-деа." +"Потребно је потврђивање идентитета за прављење личног простора корисника." #: src/home/org.freedesktop.home1.policy:23 msgid "Remove a home area" -msgstr "" +msgstr "Уклони лични простор" #: src/home/org.freedesktop.home1.policy:24 -#, fuzzy msgid "Authentication is required to remove a user's home area." msgstr "" -"Потребно је да се идентификујете да бисте поново учитали стање систем-деа." +"Потребно је потврђивање идентитета за уклањање личног простора корисника." #: src/home/org.freedesktop.home1.policy:33 msgid "Check credentials of a home area" -msgstr "" +msgstr "Провери акредитиве личног простора" #: src/home/org.freedesktop.home1.policy:34 -#, fuzzy msgid "" "Authentication is required to check credentials against a user's home area." msgstr "" -"Потребно је да се идентификујете да бисте управљали покренутим сесијама, " -"корисницима и седиштима." +"Потребно је потврђивање идентитета за проверу акредитива личног простора " +"корисника." #: src/home/org.freedesktop.home1.policy:43 msgid "Update a home area" -msgstr "" +msgstr "Ажурирај лични простор" #: src/home/org.freedesktop.home1.policy:44 -#, fuzzy msgid "Authentication is required to update a user's home area." -msgstr "Потребно је да се идентификујете да бисте закачили уређај на седиште." +msgstr "" +"Потребно је потврђивање идентитета за ажурирање личног простора корисника." #: src/home/org.freedesktop.home1.policy:53 msgid "Update your home area" -msgstr "" +msgstr "Ажурирај свој лични простор" #: src/home/org.freedesktop.home1.policy:54 -#, fuzzy msgid "Authentication is required to update your home area." -msgstr "Потребно је да се идентификујете да бисте закачили уређај на седиште." +msgstr "Потребно је потврђивање идентитета за ажурирање свог личног простора." #: src/home/org.freedesktop.home1.policy:63 msgid "Resize a home area" -msgstr "" +msgstr "Промени величину личног простора" #: src/home/org.freedesktop.home1.policy:64 -#, fuzzy msgid "Authentication is required to resize a user's home area." -msgstr "Потребно је да се идентификујете да бисте поставили зидну поруку" +msgstr "" +"Потребно је потврђивање идентитета за промену величине личног простора " +"корисника." #: src/home/org.freedesktop.home1.policy:73 msgid "Change password of a home area" -msgstr "" +msgstr "Промени лозинку личног простора" #: src/home/org.freedesktop.home1.policy:74 -#, fuzzy msgid "" "Authentication is required to change the password of a user's home area." msgstr "" -"Потребно је да се идентификујете да бисте управљали покренутим сесијама, " -"корисницима и седиштима." +"Потребно је потврђивање идентитета за промену лозинке личног простора " +"корисника." #: src/home/org.freedesktop.home1.policy:83 msgid "Activate a home area" -msgstr "" +msgstr "Активирај лични простор" #: src/home/org.freedesktop.home1.policy:84 -#, fuzzy msgid "Authentication is required to activate a user's home area." msgstr "" -"Потребно је да се идентификујете да бисте поново учитали стање систем-деа." +"Потребно је потврђивање идентитета за активирање личног простора корисника." #: src/home/org.freedesktop.home1.policy:93 msgid "Manage Home Directory Signing Keys" -msgstr "" +msgstr "Управљај кључевима за потписивање личног директоријума" #: src/home/org.freedesktop.home1.policy:94 -#, fuzzy msgid "Authentication is required to manage signing keys for home directories." msgstr "" -"Потребно је да се идентификујете да бисте управљали системским услугама или " -"другим јединицама." - -#: src/home/pam_systemd_home.c:330 -#, c-format -msgid "" -"Home of user %s is currently absent, please plug in the necessary storage " -"device or backing file system." -msgstr "" - -#: src/home/pam_systemd_home.c:335 -#, c-format -msgid "Too frequent login attempts for user %s, try again later." -msgstr "" - -#: src/home/pam_systemd_home.c:347 -msgid "Password: " -msgstr "" - -#: src/home/pam_systemd_home.c:349 -#, c-format -msgid "Password incorrect or not sufficient for authentication of user %s." -msgstr "" - -#: src/home/pam_systemd_home.c:350 -msgid "Sorry, try again: " -msgstr "" - -#: src/home/pam_systemd_home.c:372 -msgid "Recovery key: " -msgstr "" - -#: src/home/pam_systemd_home.c:374 -#, c-format -msgid "" -"Password/recovery key incorrect or not sufficient for authentication of user " -"%s." -msgstr "" - -#: src/home/pam_systemd_home.c:375 -msgid "Sorry, reenter recovery key: " -msgstr "" - -#: src/home/pam_systemd_home.c:395 -#, c-format -msgid "Security token of user %s not inserted." -msgstr "" - -#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 -msgid "Try again with password: " -msgstr "" - -#: src/home/pam_systemd_home.c:398 -#, c-format -msgid "" -"Password incorrect or not sufficient, and configured security token of user " -"%s not inserted." -msgstr "" - -#: src/home/pam_systemd_home.c:418 -msgid "Security token PIN: " -msgstr "" - -#: src/home/pam_systemd_home.c:435 -#, c-format -msgid "Please authenticate physically on security token of user %s." -msgstr "" - -#: src/home/pam_systemd_home.c:446 -#, c-format -msgid "Please confirm presence on security token of user %s." -msgstr "" - -#: src/home/pam_systemd_home.c:457 -#, c-format -msgid "Please verify user on security token of user %s." -msgstr "" - -#: src/home/pam_systemd_home.c:466 -msgid "" -"Security token PIN is locked, please unlock it first. (Hint: Removal and re-" -"insertion might suffice.)" -msgstr "" - -#: src/home/pam_systemd_home.c:474 -#, c-format -msgid "Security token PIN incorrect for user %s." -msgstr "" - -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 -msgid "Sorry, retry security token PIN: " -msgstr "" - -#: src/home/pam_systemd_home.c:493 -#, c-format -msgid "Security token PIN of user %s incorrect (only a few tries left!)" -msgstr "" - -#: src/home/pam_systemd_home.c:512 -#, c-format -msgid "Security token PIN of user %s incorrect (only one try left!)" -msgstr "" - -#: src/home/pam_systemd_home.c:679 -#, c-format -msgid "Home of user %s is currently not active, please log in locally first." -msgstr "" - -#: src/home/pam_systemd_home.c:681 -#, c-format -msgid "Home of user %s is currently locked, please unlock locally first." -msgstr "" - -#: src/home/pam_systemd_home.c:715 -#, c-format -msgid "Too many unsuccessful login attempts for user %s, refusing." -msgstr "" - -#: src/home/pam_systemd_home.c:1012 -msgid "User record is blocked, prohibiting access." -msgstr "" - -#: src/home/pam_systemd_home.c:1016 -msgid "User record is not valid yet, prohibiting access." -msgstr "" - -#: src/home/pam_systemd_home.c:1020 -msgid "User record is not valid anymore, prohibiting access." -msgstr "" - -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 -msgid "User record not valid, prohibiting access." -msgstr "" - -#: src/home/pam_systemd_home.c:1035 -#, c-format -msgid "Too many logins, try again in %s." -msgstr "" - -#: src/home/pam_systemd_home.c:1046 -msgid "Password change required." -msgstr "" - -#: src/home/pam_systemd_home.c:1050 -msgid "Password expired, change required." -msgstr "" - -#: src/home/pam_systemd_home.c:1056 -msgid "Password is expired, but can't change, refusing login." -msgstr "" - -#: src/home/pam_systemd_home.c:1060 -msgid "Password will expire soon, please change." -msgstr "" +"Потребно је потврђивање идентитета за управљање кључевима за потписивање " +"личних директоријума." #: src/hostname/org.freedesktop.hostname1.policy:20 msgid "Set hostname" @@ -357,80 +201,63 @@ msgstr "" #: src/hostname/org.freedesktop.hostname1.policy:51 msgid "Get product UUID" -msgstr "" +msgstr "Добави УУИД производа" #: src/hostname/org.freedesktop.hostname1.policy:52 -#, fuzzy msgid "Authentication is required to get product UUID." -msgstr "Потребно је да се идентификујете да бисте поново учитали „$(unit)“." +msgstr "Потребно је потврђивање идентитета за добављање УУИД-а производа." #: src/hostname/org.freedesktop.hostname1.policy:61 msgid "Get hardware serial number" -msgstr "" +msgstr "Добави серијски број хардвера" #: src/hostname/org.freedesktop.hostname1.policy:62 -#, fuzzy msgid "Authentication is required to get hardware serial number." -msgstr "Потребно је да се идентификујете да бисте поставили системско време." +msgstr "" +"Потребно је потврђивање идентитета за добављање серијског броја хардвера." #: src/hostname/org.freedesktop.hostname1.policy:71 -#, fuzzy msgid "Get system description" -msgstr "Постави системску временску зону" +msgstr "Добави опис система" #: src/hostname/org.freedesktop.hostname1.policy:72 -#, fuzzy msgid "Authentication is required to get system description." -msgstr "" -"Потребно је да се идентификујете да бисте поставили системску временску зону." +msgstr "Потребно је потврђивање идентитета за добављање описа система." #: src/import/org.freedesktop.import1.policy:22 -#, fuzzy msgid "Import a disk image" -msgstr "Увези ВМ или слику контејнера" +msgstr "Увези одраз диска" #: src/import/org.freedesktop.import1.policy:23 -#, fuzzy msgid "Authentication is required to import an image." -msgstr "" -"Потребно је да се идентификујете да бисте увезли виртуелну машину или слику " -"контејнера" +msgstr "Потребно је потврђивање идентитета за увоз одраза." #: src/import/org.freedesktop.import1.policy:32 -#, fuzzy msgid "Export a disk image" -msgstr "Извези ВМ или слику контејнера" +msgstr "Извези одраз диска" #: src/import/org.freedesktop.import1.policy:33 -#, fuzzy msgid "Authentication is required to export disk image." -msgstr "" -"Потребно је да се идентификујете да бисте извезли виртуелну машину или слику " -"контејнера" +msgstr "Потребно је потврђивање идентитета за извоз одраза диска." #: src/import/org.freedesktop.import1.policy:42 -#, fuzzy msgid "Download a disk image" -msgstr "Преузми ВМ или слику контејнера" +msgstr "Преузми одраз диска" #: src/import/org.freedesktop.import1.policy:43 -#, fuzzy msgid "Authentication is required to download a disk image." -msgstr "" -"Потребно је да се идентификујете да бисте преузели виртуелну машину или " -"слику контејнера" +msgstr "Потребно је потврђивање идентитета за преузимање одраза диска." #: src/import/org.freedesktop.import1.policy:52 msgid "Cancel transfer of a disk image" -msgstr "" +msgstr "Откажи пренос одраза диска" #: src/import/org.freedesktop.import1.policy:53 -#, fuzzy msgid "" "Authentication is required to cancel the ongoing transfer of a disk image." msgstr "" -"Потребно је да се идентификујете да бисте управљали покренутим сесијама, " -"корисницима и седиштима." +"Потребно је потврђивање идентитета за отказивање текућег преноса одраза " +"диска." #: src/locale/org.freedesktop.locale1.policy:22 msgid "Set system locale" @@ -593,7 +420,7 @@ msgstr "Дозволи качење уређаја на седишта" #: src/login/org.freedesktop.login1.policy:149 msgid "Authentication is required to attach a device to a seat." -msgstr "Потребно је да се идентификујете да бисте закачили уређај на седиште." +msgstr "Потребно је да се идентификујете да бисте закачили уређај на седиште." #: src/login/org.freedesktop.login1.policy:159 msgid "Flush device to seat attachments" @@ -787,18 +614,17 @@ msgstr "" #: src/login/org.freedesktop.login1.policy:352 msgid "Set the reboot \"reason\" in the kernel" -msgstr "" +msgstr "Постави „разлог“ поновног покретања у језгру" #: src/login/org.freedesktop.login1.policy:353 msgid "Authentication is required to set the reboot \"reason\" in the kernel." msgstr "" "Потребно је да се идентификујете да бисте поставили \"разлог\" за поновно " -"поретање унутар језгра." +"покретање унутар језгра." #: src/login/org.freedesktop.login1.policy:363 -#, fuzzy msgid "Indicate to the firmware to boot to setup interface" -msgstr "Напомени фирмверу да се подигне у режиму подешавања интерфејса" +msgstr "Укажи фирмверу да покрене систем у интерфејсу за подешавање" #: src/login/org.freedesktop.login1.policy:364 msgid "" @@ -811,46 +637,43 @@ msgstr "" #: src/login/org.freedesktop.login1.policy:374 msgid "Indicate to the boot loader to boot to the boot loader menu" msgstr "" +"Укажи учитавачу подизања система да прикаже изборник за подизање система" #: src/login/org.freedesktop.login1.policy:375 -#, fuzzy msgid "" "Authentication is required to indicate to the boot loader to boot to the " "boot loader menu." msgstr "" -"Потребно је да се идентификујете да бисте напоменули фирмверу да се подигне " -"у режиму подешавања интерфејса." +"Потребно је потврђивање идентитета за указивање учитавачу подизања система " +"да прикаже изборник за подизање система." #: src/login/org.freedesktop.login1.policy:385 msgid "Indicate to the boot loader to boot a specific entry" -msgstr "" +msgstr "Укажи учитавачу подизања система да покрене одређени унос" #: src/login/org.freedesktop.login1.policy:386 -#, fuzzy msgid "" "Authentication is required to indicate to the boot loader to boot into a " "specific boot loader entry." msgstr "" -"Потребно је да се идентификујете да бисте напоменули фирмверу да се подигне " -"у режиму подешавања интерфејса." +"Потребно је потврђивање идентитета за указивање учитавачу подизања система " +"да покрене одређени унос учитавача подизања система." #: src/login/org.freedesktop.login1.policy:396 msgid "Set a wall message" msgstr "Постави зидну поруку" #: src/login/org.freedesktop.login1.policy:397 -#, fuzzy msgid "Authentication is required to set a wall message." -msgstr "Потребно је да се идентификујете да бисте поставили зидну поруку" +msgstr "Потребно је потврђивање идентитета за постављање зидне поруке." #: src/login/org.freedesktop.login1.policy:406 msgid "Change Session" -msgstr "" +msgstr "Промени сесију" #: src/login/org.freedesktop.login1.policy:407 -#, fuzzy msgid "Authentication is required to change the virtual terminal." -msgstr "Потребно је да се идентификујете да бисте зауставили систем." +msgstr "Потребно је потврђивање идентитета за промену виртуелног терминала." #: src/machine/org.freedesktop.machine1.policy:22 msgid "Log into a local container" @@ -923,30 +746,26 @@ msgstr "" "машинама и контејнерима." #: src/machine/org.freedesktop.machine1.policy:95 -#, fuzzy msgid "Create a local virtual machine or container" -msgstr "Управљај локалним виртуелним машинама и контејнерима" +msgstr "Направи локалну виртуелну машину или контејнер" #: src/machine/org.freedesktop.machine1.policy:96 -#, fuzzy msgid "" "Authentication is required to create a local virtual machine or container." msgstr "" -"Потребно је да се идентификујете да бисте управљали локалним виртуелним " -"машинама и контејнерима." +"Потребно је потврђивање идентитета за прављење локалне виртуелне машине или " +"контејнера." #: src/machine/org.freedesktop.machine1.policy:106 -#, fuzzy msgid "Register a local virtual machine or container" -msgstr "Управљај локалним виртуелним машинама и контејнерима" +msgstr "Региструј локалну виртуелну машину или контејнер" #: src/machine/org.freedesktop.machine1.policy:107 -#, fuzzy msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" -"Потребно је да се идентификујете да бисте управљали локалним виртуелним " -"машинама и контејнерима." +"Потребно је потврђивање идентитета за регистровање локалне виртуелне машине " +"или контејнера." #: src/machine/org.freedesktop.machine1.policy:116 msgid "Manage local virtual machine and container images" @@ -962,346 +781,324 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" -msgstr "" +msgstr "Постави НТП сервере" #: src/network/org.freedesktop.network1.policy:23 -#, fuzzy msgid "Authentication is required to set NTP servers." -msgstr "Потребно је да се идентификујете да бисте поставили системско време." +msgstr "Потребно је потврђивање идентитета за постављање НТП сервера." #: src/network/org.freedesktop.network1.policy:33 #: src/resolve/org.freedesktop.resolve1.policy:44 -#, fuzzy msgid "Set DNS servers" -msgstr "Региструј DNS-SD услугу" +msgstr "Постави ДНС сервере" #: src/network/org.freedesktop.network1.policy:34 #: src/resolve/org.freedesktop.resolve1.policy:45 -#, fuzzy msgid "Authentication is required to set DNS servers." -msgstr "Потребно је да се идентификујете да бисте регистровали DNS-SD услугу" +msgstr "Потребно је потврђивање идентитета за постављање ДНС сервера." #: src/network/org.freedesktop.network1.policy:44 #: src/resolve/org.freedesktop.resolve1.policy:55 msgid "Set domains" -msgstr "" +msgstr "Постави домене" #: src/network/org.freedesktop.network1.policy:45 #: src/resolve/org.freedesktop.resolve1.policy:56 -#, fuzzy msgid "Authentication is required to set domains." -msgstr "Потребно је да се идентификујете да бисте зауставили „$(unit)“." +msgstr "Потребно је потврђивање идентитета за постављање домена." #: src/network/org.freedesktop.network1.policy:55 #: src/resolve/org.freedesktop.resolve1.policy:66 msgid "Set default route" -msgstr "" +msgstr "Постави подразумевану руту" #: src/network/org.freedesktop.network1.policy:56 #: src/resolve/org.freedesktop.resolve1.policy:67 -#, fuzzy msgid "Authentication is required to set default route." -msgstr "Потребно је да се идентификујете да бисте поставили назив машине." +msgstr "Потребно је потврђивање идентитета за постављање подразумеване руте." #: src/network/org.freedesktop.network1.policy:66 #: src/resolve/org.freedesktop.resolve1.policy:77 msgid "Enable/disable LLMNR" -msgstr "" +msgstr "Омогући/онемогући ЛЛМНР" #: src/network/org.freedesktop.network1.policy:67 #: src/resolve/org.freedesktop.resolve1.policy:78 -#, fuzzy msgid "Authentication is required to enable or disable LLMNR." -msgstr "Потребно је да се идентификујете да бисте успавали систем." +msgstr "" +"Потребно је потврђивање идентитета за омогућавање или онемогућавање ЛЛМНР-а." #: src/network/org.freedesktop.network1.policy:77 #: src/resolve/org.freedesktop.resolve1.policy:88 msgid "Enable/disable multicast DNS" -msgstr "" +msgstr "Омогући/онемогући мултикаст ДНС" #: src/network/org.freedesktop.network1.policy:78 #: src/resolve/org.freedesktop.resolve1.policy:89 -#, fuzzy msgid "Authentication is required to enable or disable multicast DNS." msgstr "" -"Потребно је да се идентификујете да бисте се пријавили у локалног домаћина." +"Потребно је потврђивање идентитета за омогућавање или онемогућавање " +"мултикаст ДНС-а." #: src/network/org.freedesktop.network1.policy:88 #: src/resolve/org.freedesktop.resolve1.policy:99 msgid "Enable/disable DNS over TLS" -msgstr "" +msgstr "Омогући/онемогући ДНС преко ТЛС-а" #: src/network/org.freedesktop.network1.policy:89 #: src/resolve/org.freedesktop.resolve1.policy:100 -#, fuzzy msgid "Authentication is required to enable or disable DNS over TLS." -msgstr "Потребно је да се идентификујете да бисте регистровали DNS-SD услугу" +msgstr "" +"Потребно је потврђивање идентитета за омогућавање или онемогућавање ДНС-а " +"преко ТЛС-а." #: src/network/org.freedesktop.network1.policy:99 #: src/resolve/org.freedesktop.resolve1.policy:110 msgid "Enable/disable DNSSEC" -msgstr "" +msgstr "Омогући/онемогући DNSSEC" #: src/network/org.freedesktop.network1.policy:100 #: src/resolve/org.freedesktop.resolve1.policy:111 -#, fuzzy msgid "Authentication is required to enable or disable DNSSEC." -msgstr "Потребно је да се идентификујете да бисте успавали систем." +msgstr "" +"Потребно је потврђивање идентитета за омогућавање или онемогућавање DNSSEC-" +"а." #: src/network/org.freedesktop.network1.policy:110 #: src/resolve/org.freedesktop.resolve1.policy:121 msgid "Set DNSSEC Negative Trust Anchors" -msgstr "" +msgstr "Постави негативне сидре поверења за DNSSEC" #: src/network/org.freedesktop.network1.policy:111 #: src/resolve/org.freedesktop.resolve1.policy:122 -#, fuzzy msgid "Authentication is required to set DNSSEC Negative Trust Anchors." msgstr "" -"Потребно је да се идентификујете да бисте поставили основни језик система." +"Потребно је потврђивање идентитета за постављање негативних сидара поверења " +"за DNSSEC." #: src/network/org.freedesktop.network1.policy:121 msgid "Revert NTP settings" -msgstr "" +msgstr "Врати НТП подешавања" #: src/network/org.freedesktop.network1.policy:122 -#, fuzzy msgid "Authentication is required to reset NTP settings." -msgstr "Потребно је да се идентификујете да бисте поставили системско време." +msgstr "Потребно је потврђивање идентитета за ресетовање НТП подешавања." #: src/network/org.freedesktop.network1.policy:132 msgid "Revert DNS settings" -msgstr "" +msgstr "Врати ДНС подешавања" #: src/network/org.freedesktop.network1.policy:133 -#, fuzzy msgid "Authentication is required to reset DNS settings." -msgstr "Потребно је да се идентификујете да бисте поставили системско време." +msgstr "Потребно је потврђивање идентитета за ресетовање ДНС подешавања." #: src/network/org.freedesktop.network1.policy:143 msgid "DHCP server sends force renew message" -msgstr "" +msgstr "ДХЦП сервер шаље поруку за присилно обнављање" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy msgid "" "Authentication is required to send a force renew message from the DHCP " "server." -msgstr "Потребно је да се идентификујете да бисте поставили зидну поруку" +msgstr "" +"Потребно је потврђивање идентитета за слање поруке за присилно обнављање са " +"ДХЦП сервера." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" -msgstr "" +msgstr "Обнови динамичке адресе" #: src/network/org.freedesktop.network1.policy:155 -#, fuzzy msgid "Authentication is required to renew dynamic addresses." -msgstr "Потребно је да се идентификујете да бисте поставили зидну поруку" +msgstr "Потребно је потврђивање идентитета за обнављање динамичких адреса." #: src/network/org.freedesktop.network1.policy:165 msgid "Reload network settings" -msgstr "" +msgstr "Поново учитај мрежна подешавања" #: src/network/org.freedesktop.network1.policy:166 -#, fuzzy msgid "Authentication is required to reload network settings." msgstr "" -"Потребно је да се идентификујете да бисте поново учитали стање систем-деа." +"Потребно је потврђивање идентитета за поновно учитавање мрежних подешавања." #: src/network/org.freedesktop.network1.policy:176 msgid "Reconfigure network interface" -msgstr "" +msgstr "Поново подеси мрежни интерфејс" #: src/network/org.freedesktop.network1.policy:177 -#, fuzzy msgid "Authentication is required to reconfigure network interface." -msgstr "Потребно је да се идентификујете да бисте поново покренули систем." +msgstr "" +"Потребно је потврђивање идентитета за поновно подешавање мрежног интерфејса." #: src/network/org.freedesktop.network1.policy:187 msgid "Specify whether persistent storage for systemd-networkd is available" -msgstr "" +msgstr "Наведите да ли је доступно трајно складиште за systemd-networkd" #: src/network/org.freedesktop.network1.policy:188 msgid "" "Authentication is required to specify whether persistent storage for systemd-" "networkd is available." msgstr "" +"Потребно је потврђивање идентитета за навођење да ли је доступно трајно " +"складиште за systemd-networkd." #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Управљај мрежним везама" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" -"Потребно је да се идентификујете да бисте поново учитали стање систем-деа." +msgstr "Потребно је да се идентификујете да бисте управљали мрежним везама." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" -msgstr "" +msgstr "Прегледај одраз преносне услуге" #: src/portable/org.freedesktop.portable1.policy:14 -#, fuzzy msgid "Authentication is required to inspect a portable service image." -msgstr "" -"Потребно је да се идентификујете да бисте увезли виртуелну машину или слику " -"контејнера" +msgstr "Потребно је потврђивање идентитета за преглед одраза преносне услуге." #: src/portable/org.freedesktop.portable1.policy:23 msgid "Attach or detach a portable service image" -msgstr "" +msgstr "Прикачи или откачи одраз преносне услуге" #: src/portable/org.freedesktop.portable1.policy:24 -#, fuzzy msgid "" "Authentication is required to attach or detach a portable service image." -msgstr "Потребно је да се идентификујете да бисте закачили уређај на седиште." +msgstr "" +"Потребно је потврђивање идентитета за прикачивање или откачивање одраза " +"преносне услуге." #: src/portable/org.freedesktop.portable1.policy:34 msgid "Delete or modify portable service image" -msgstr "" +msgstr "Обриши или измени одраз преносне услуге" #: src/portable/org.freedesktop.portable1.policy:35 -#, fuzzy msgid "" "Authentication is required to delete or modify a portable service image." msgstr "" -"Потребно је да се идентификујете да бисте преузели виртуелну машину или " -"слику контејнера" +"Потребно је потврђивање идентитета за брисање или измену одраза преносне " +"услуге." #: src/resolve/org.freedesktop.resolve1.policy:22 msgid "Register a DNS-SD service" msgstr "Региструј DNS-SD услугу" #: src/resolve/org.freedesktop.resolve1.policy:23 -#, fuzzy msgid "Authentication is required to register a DNS-SD service." -msgstr "Потребно је да се идентификујете да бисте регистровали DNS-SD услугу" +msgstr "Потребно је потврђивање идентитета за регистрацију DNS-SD услуге." #: src/resolve/org.freedesktop.resolve1.policy:33 msgid "Unregister a DNS-SD service" msgstr "Укини регистрацију DNS-SD услуге" #: src/resolve/org.freedesktop.resolve1.policy:34 -#, fuzzy msgid "Authentication is required to unregister a DNS-SD service." msgstr "" -"Потребно је да се идентификујете да бисте укинули регистрацију DNS-SD услуге" +"Потребно је потврђивање идентитета за поништавање регистрације DNS-SD услуге." #: src/resolve/org.freedesktop.resolve1.policy:132 msgid "Revert name resolution settings" -msgstr "" +msgstr "Врати подешавања разрешавања назива" #: src/resolve/org.freedesktop.resolve1.policy:133 -#, fuzzy msgid "Authentication is required to reset name resolution settings." msgstr "" -"Потребно је да се идентификујете да бисте поставили подешавања системске " -"тастатуре." +"Потребно је потврђивање идентитета за ресетовање подешавања разрешавања " +"назива." #: src/resolve/org.freedesktop.resolve1.policy:143 msgid "Subscribe query results" -msgstr "" +msgstr "Претплати се на резултате упита" #: src/resolve/org.freedesktop.resolve1.policy:144 -#, fuzzy msgid "Authentication is required to subscribe query results." -msgstr "Потребно је да се идентификујете да бисте обуставили систем." +msgstr "Потребно је потврђивање идентитета за претплату на резултате упита." #: src/resolve/org.freedesktop.resolve1.policy:154 msgid "Subscribe to DNS configuration" -msgstr "" +msgstr "Претплати се на ДНС подешавања" #: src/resolve/org.freedesktop.resolve1.policy:155 -#, fuzzy msgid "Authentication is required to subscribe to DNS configuration." -msgstr "Потребно је да се идентификујете да бисте обуставили систем." +msgstr "Потребно је потврђивање идентитета за претплату на ДНС подешавања." #: src/resolve/org.freedesktop.resolve1.policy:165 msgid "Dump cache" -msgstr "" +msgstr "Испиши кеш" #: src/resolve/org.freedesktop.resolve1.policy:166 -#, fuzzy msgid "Authentication is required to dump cache." -msgstr "Потребно је да се идентификујете да бисте зауставили „$(unit)“." +msgstr "Потребно је потврђивање идентитета за исписивање кеша." #: src/resolve/org.freedesktop.resolve1.policy:176 msgid "Dump server state" -msgstr "" +msgstr "Испиши стање сервера" #: src/resolve/org.freedesktop.resolve1.policy:177 -#, fuzzy msgid "Authentication is required to dump server state." -msgstr "Потребно је да се идентификујете да бисте поставили системско време." +msgstr "Потребно је потврђивање идентитета за исписивање стања сервера." #: src/resolve/org.freedesktop.resolve1.policy:187 msgid "Dump statistics" -msgstr "" +msgstr "Испиши статистику" #: src/resolve/org.freedesktop.resolve1.policy:188 -#, fuzzy msgid "Authentication is required to dump statistics." -msgstr "Потребно је да се идентификујете да бисте зауставили „$(unit)“." +msgstr "Потребно је потврђивање идентитета за исписивање статистике." #: src/resolve/org.freedesktop.resolve1.policy:198 msgid "Reset statistics" -msgstr "" +msgstr "Ресетуј статистику" #: src/resolve/org.freedesktop.resolve1.policy:199 -#, fuzzy msgid "Authentication is required to reset statistics." -msgstr "Потребно је да се идентификујете да бисте поставили системско време." +msgstr "Потребно је потврђивање идентитета за ресетовање статистике." #: src/sysupdate/org.freedesktop.sysupdate1.policy:35 msgid "Check for system updates" -msgstr "" +msgstr "Провери ажурирања система" #: src/sysupdate/org.freedesktop.sysupdate1.policy:36 -#, fuzzy msgid "Authentication is required to check for system updates." -msgstr "Потребно је да се идентификујете да бисте поставили системско време." +msgstr "Потребно је потврђивање идентитета за проверу ажурирања система." #: src/sysupdate/org.freedesktop.sysupdate1.policy:45 msgid "Install system updates" -msgstr "" +msgstr "Инсталирај ажурирања система" #: src/sysupdate/org.freedesktop.sysupdate1.policy:46 -#, fuzzy msgid "Authentication is required to install system updates." -msgstr "Потребно је да се идентификујете да бисте поставили системско време." +msgstr "Потребно је потврђивање идентитета за инсталирање ажурирања система." #: src/sysupdate/org.freedesktop.sysupdate1.policy:55 msgid "Install specific system version" -msgstr "" +msgstr "Инсталирај одређено издање система" #: src/sysupdate/org.freedesktop.sysupdate1.policy:56 -#, fuzzy msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." msgstr "" -"Потребно је да се идентификујете да бисте поставили системску временску зону." +"Потребно је потврђивање идентитета за ажурирање система на одређено (могуће " +"старо) издање." #: src/sysupdate/org.freedesktop.sysupdate1.policy:65 msgid "Cleanup old system updates" -msgstr "" +msgstr "Очисти стара ажурирања система" #: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -#, fuzzy msgid "Authentication is required to cleanup old system updates." -msgstr "Потребно је да се идентификујете да бисте поставили системско време." +msgstr "" +"Потребно је потврђивање идентитета за чишћење старих ажурирања система." #: src/sysupdate/org.freedesktop.sysupdate1.policy:75 msgid "Manage optional features" -msgstr "" +msgstr "Управљај опционим могућностима" #: src/sysupdate/org.freedesktop.sysupdate1.policy:76 -#, fuzzy msgid "Authentication is required to manage optional features." -msgstr "" -"Потребно је да се идентификујете да бисте управљали покренутим сесијама, " -"корисницима и седиштима." +msgstr "Потребно је потврђивање идентитета за управљање опционим могућностима." #: src/timedate/org.freedesktop.timedate1.policy:22 msgid "Set system time" @@ -1344,83 +1141,3 @@ msgid "" msgstr "" "Потребно је да се идентификујете да бисте подесили да ли се време усклађује " "са мреже." - -#: src/core/dbus-unit.c:372 -msgid "Authentication is required to start '$(unit)'." -msgstr "Потребно је да се идентификујете да бисте покренули „$(unit)“." - -#: src/core/dbus-unit.c:373 -msgid "Authentication is required to stop '$(unit)'." -msgstr "Потребно је да се идентификујете да бисте зауставили „$(unit)“." - -#: src/core/dbus-unit.c:374 -msgid "Authentication is required to reload '$(unit)'." -msgstr "Потребно је да се идентификујете да бисте поново учитали „$(unit)“." - -#: src/core/dbus-unit.c:375 src/core/dbus-unit.c:376 -msgid "Authentication is required to restart '$(unit)'." -msgstr "Потребно је да се идентификујете да бисте поново покренули „$(unit)“." - -#: src/core/dbus-unit.c:568 -#, fuzzy -msgid "" -"Authentication is required to send a UNIX signal to the processes of '$" -"(unit)'." -msgstr "" -"Потребно је да се идентификујете да бисте поставили својства за „$(unit)“." - -#: src/core/dbus-unit.c:621 -#, fuzzy -msgid "" -"Authentication is required to send a UNIX signal to the processes of " -"subgroup of '$(unit)'." -msgstr "" -"Потребно је да се идентификујете да бисте поставили својства за „$(unit)“." - -#: src/core/dbus-unit.c:649 -msgid "Authentication is required to reset the \"failed\" state of '$(unit)'." -msgstr "" -"Потребно је да се идентификујете да бисте поново поставили „неуспешно“ стање " -"за „$(unit)“." - -#: src/core/dbus-unit.c:679 -msgid "Authentication is required to set properties on '$(unit)'." -msgstr "" -"Потребно је да се идентификујете да бисте поставили својства за „$(unit)“." - -#: src/core/dbus-unit.c:776 -#, fuzzy -msgid "" -"Authentication is required to delete files and directories associated with '$" -"(unit)'." -msgstr "" -"Потребно је да се идентификујете да бисте поново поставили „неуспешно“ стање " -"за „$(unit)“." - -#: src/core/dbus-unit.c:813 -#, fuzzy -msgid "" -"Authentication is required to freeze or thaw the processes of '$(unit)' unit." -msgstr "" -"Потребно је да се идентификујете да бисте поново поставили „неуспешно“ стање " -"за „$(unit)“." - -#~ msgid "" -#~ "Authentication is required to halt the system while an application asked " -#~ "to inhibit it." -#~ msgstr "" -#~ "Потребно је да се идентификујете да бисте зауставили систем иако програм " -#~ "тражи да се спречи заустављање система." - -#~ msgid "Authentication is required to kill '$(unit)'." -#~ msgstr "Потребно је да се идентификујете да бисте убили „$(unit)“." - -#~ msgid "Press Ctrl+C to cancel all filesystem checks in progress" -#~ msgstr "" -#~ "Притисните Ctrl+C да бисте прекинули све текуће провере система датотека" - -#~ msgid "Checking in progress on %d disk (%3.1f%% complete)" -#~ msgid_plural "Checking in progress on %d disks (%3.1f%% complete)" -#~ msgstr[0] "Провера у току на %d диску (%3.1f%% готово)" -#~ msgstr[1] "Провера у току на %d диска (%3.1f%% готово)" -#~ msgstr[2] "Провера у току на %d дискова (%3.1f%% готово)" diff --git a/po/sr@latin.po b/po/sr@latin.po new file mode 100644 index 0000000000000..92c83300d2316 --- /dev/null +++ b/po/sr@latin.po @@ -0,0 +1,1143 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Serbian Latin translation of systemd. +# Frantisek Sumsal , 2021. +# Marko Kostić , 2026 +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: https://github.com/systemd/systemd/issues\n" +"POT-Creation-Date: 2026-03-20 14:34+0000\n" +"PO-Revision-Date: 2026-04-12 18:20+0200\n" +"Last-Translator: Marko Kostić \n" +"Language-Team: Serbian \n" +"Language: sr@latin\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: Poedit 3.9\n" + +#: src/core/org.freedesktop.systemd1.policy.in:22 +msgid "Send passphrase back to system" +msgstr "Pošalji frazu nazad ka sistemu" + +#: src/core/org.freedesktop.systemd1.policy.in:23 +msgid "" +"Authentication is required to send the entered passphrase back to the system." +msgstr "" +"Potrebno je da se identifikujete da biste poslali frazu nazad u sistem." + +#: src/core/org.freedesktop.systemd1.policy.in:33 +msgid "Manage system services or other units" +msgstr "Upravljaj sistemskim uslugama i drugim jedinicama" + +#: src/core/org.freedesktop.systemd1.policy.in:34 +msgid "Authentication is required to manage system services or other units." +msgstr "" +"Potrebno je da se identifikujete da biste upravljali sistemskim uslugama ili " +"drugim jedinicama." + +#: src/core/org.freedesktop.systemd1.policy.in:43 +msgid "Manage system service or unit files" +msgstr "Upravljaj sistemskom uslugom ili jediničnim datotekama" + +#: src/core/org.freedesktop.systemd1.policy.in:44 +msgid "Authentication is required to manage system service or unit files." +msgstr "" +"Potrebno je da se identifikujete da biste upravljali sistemskom uslugom ili " +"jediničnim datotekama." + +#: src/core/org.freedesktop.systemd1.policy.in:54 +msgid "Set or unset system and service manager environment variables" +msgstr "Menjaj promenljive okruženja na sistemu i unutar upravnika usluga" + +#: src/core/org.freedesktop.systemd1.policy.in:55 +msgid "" +"Authentication is required to set or unset system and service manager " +"environment variables." +msgstr "" +"Potrebno je da se identifikujete da biste menjali promenljive okruženja na " +"sistemu i unutar upravnika usluga." + +#: src/core/org.freedesktop.systemd1.policy.in:64 +msgid "Reload the systemd state" +msgstr "Ponovo učitaj stanje sistem-dea" + +#: src/core/org.freedesktop.systemd1.policy.in:65 +msgid "Authentication is required to reload the systemd state." +msgstr "" +"Potrebno je da se identifikujete da biste ponovo učitali stanje sistem-dea." + +#: src/core/org.freedesktop.systemd1.policy.in:74 +msgid "Dump the systemd state without rate limits" +msgstr "Ispiši stanje sistemd-a bez ograničenja protoka" + +#: src/core/org.freedesktop.systemd1.policy.in:75 +msgid "" +"Authentication is required to dump the systemd state without rate limits." +msgstr "" +"Potrebno je potvrđivanje identiteta za ispisivanje stanja sistemde-a bez " +"ograničenja protoka." + +#: src/home/org.freedesktop.home1.policy:13 +msgid "Create a home area" +msgstr "Napravi lični prostor" + +#: src/home/org.freedesktop.home1.policy:14 +msgid "Authentication is required to create a user's home area." +msgstr "" +"Potrebno je potvrđivanje identiteta za pravljenje ličnog prostora korisnika." + +#: src/home/org.freedesktop.home1.policy:23 +msgid "Remove a home area" +msgstr "Ukloni lični prostor" + +#: src/home/org.freedesktop.home1.policy:24 +msgid "Authentication is required to remove a user's home area." +msgstr "" +"Potrebno je potvrđivanje identiteta za uklanjanje ličnog prostora korisnika." + +#: src/home/org.freedesktop.home1.policy:33 +msgid "Check credentials of a home area" +msgstr "Proveri akreditive ličnog prostora" + +#: src/home/org.freedesktop.home1.policy:34 +msgid "" +"Authentication is required to check credentials against a user's home area." +msgstr "" +"Potrebno je potvrđivanje identiteta za proveru akreditiva ličnog prostora " +"korisnika." + +#: src/home/org.freedesktop.home1.policy:43 +msgid "Update a home area" +msgstr "Ažuriraj lični prostor" + +#: src/home/org.freedesktop.home1.policy:44 +msgid "Authentication is required to update a user's home area." +msgstr "" +"Potrebno je potvrđivanje identiteta za ažuriranje ličnog prostora korisnika." + +#: src/home/org.freedesktop.home1.policy:53 +msgid "Update your home area" +msgstr "Ažuriraj svoj lični prostor" + +#: src/home/org.freedesktop.home1.policy:54 +msgid "Authentication is required to update your home area." +msgstr "Potrebno je potvrđivanje identiteta za ažuriranje svog ličnog prostora." + +#: src/home/org.freedesktop.home1.policy:63 +msgid "Resize a home area" +msgstr "Promeni veličinu ličnog prostora" + +#: src/home/org.freedesktop.home1.policy:64 +msgid "Authentication is required to resize a user's home area." +msgstr "" +"Potrebno je potvrđivanje identiteta za promenu veličine ličnog prostora " +"korisnika." + +#: src/home/org.freedesktop.home1.policy:73 +msgid "Change password of a home area" +msgstr "Promeni lozinku ličnog prostora" + +#: src/home/org.freedesktop.home1.policy:74 +msgid "" +"Authentication is required to change the password of a user's home area." +msgstr "" +"Potrebno je potvrđivanje identiteta za promenu lozinke ličnog prostora " +"korisnika." + +#: src/home/org.freedesktop.home1.policy:83 +msgid "Activate a home area" +msgstr "Aktiviraj lični prostor" + +#: src/home/org.freedesktop.home1.policy:84 +msgid "Authentication is required to activate a user's home area." +msgstr "" +"Potrebno je potvrđivanje identiteta za aktiviranje ličnog prostora korisnika." + +#: src/home/org.freedesktop.home1.policy:93 +msgid "Manage Home Directory Signing Keys" +msgstr "Upravljaj ključevima za potpisivanje ličnog direktorijuma" + +#: src/home/org.freedesktop.home1.policy:94 +msgid "Authentication is required to manage signing keys for home directories." +msgstr "" +"Potrebno je potvrđivanje identiteta za upravljanje ključevima za potpisivanje " +"ličnih direktorijuma." + +#: src/hostname/org.freedesktop.hostname1.policy:20 +msgid "Set hostname" +msgstr "Postavi naziv mašine" + +#: src/hostname/org.freedesktop.hostname1.policy:21 +msgid "Authentication is required to set the local hostname." +msgstr "Potrebno je da se identifikujete da biste postavili naziv mašine." + +#: src/hostname/org.freedesktop.hostname1.policy:30 +msgid "Set static hostname" +msgstr "Postavi statički naziv mašine" + +#: src/hostname/org.freedesktop.hostname1.policy:31 +msgid "" +"Authentication is required to set the statically configured local hostname, " +"as well as the pretty hostname." +msgstr "" +"Potrebno je da se identifikujete da biste postavili statički naziv mašine i " +"da biste postavili lep naziv mašine." + +#: src/hostname/org.freedesktop.hostname1.policy:41 +msgid "Set machine information" +msgstr "Postavi podatke o mašini" + +#: src/hostname/org.freedesktop.hostname1.policy:42 +msgid "Authentication is required to set local machine information." +msgstr "" +"Potrebno je da se identifikujete da biste postavili podatke o lokalnoj " +"mašini." + +#: src/hostname/org.freedesktop.hostname1.policy:51 +msgid "Get product UUID" +msgstr "Dobavi UUID proizvoda" + +#: src/hostname/org.freedesktop.hostname1.policy:52 +msgid "Authentication is required to get product UUID." +msgstr "Potrebno je potvrđivanje identiteta za dobavljanje UUID-a proizvoda." + +#: src/hostname/org.freedesktop.hostname1.policy:61 +msgid "Get hardware serial number" +msgstr "Dobavi serijski broj hardvera" + +#: src/hostname/org.freedesktop.hostname1.policy:62 +msgid "Authentication is required to get hardware serial number." +msgstr "" +"Potrebno je potvrđivanje identiteta za dobavljanje serijskog broja hardvera." + +#: src/hostname/org.freedesktop.hostname1.policy:71 +msgid "Get system description" +msgstr "Dobavi opis sistema" + +#: src/hostname/org.freedesktop.hostname1.policy:72 +msgid "Authentication is required to get system description." +msgstr "Potrebno je potvrđivanje identiteta za dobavljanje opisa sistema." + +#: src/import/org.freedesktop.import1.policy:22 +msgid "Import a disk image" +msgstr "Uvezi odraz diska" + +#: src/import/org.freedesktop.import1.policy:23 +msgid "Authentication is required to import an image." +msgstr "Potrebno je potvrđivanje identiteta za uvoz odraza." + +#: src/import/org.freedesktop.import1.policy:32 +msgid "Export a disk image" +msgstr "Izvezi odraz diska" + +#: src/import/org.freedesktop.import1.policy:33 +msgid "Authentication is required to export disk image." +msgstr "Potrebno je potvrđivanje identiteta za izvoz odraza diska." + +#: src/import/org.freedesktop.import1.policy:42 +msgid "Download a disk image" +msgstr "Preuzmi odraz diska" + +#: src/import/org.freedesktop.import1.policy:43 +msgid "Authentication is required to download a disk image." +msgstr "Potrebno je potvrđivanje identiteta za preuzimanje odraza diska." + +#: src/import/org.freedesktop.import1.policy:52 +msgid "Cancel transfer of a disk image" +msgstr "Otkaži prenos odraza diska" + +#: src/import/org.freedesktop.import1.policy:53 +msgid "" +"Authentication is required to cancel the ongoing transfer of a disk image." +msgstr "" +"Potrebno je potvrđivanje identiteta za otkazivanje tekućeg prenosa odraza " +"diska." + +#: src/locale/org.freedesktop.locale1.policy:22 +msgid "Set system locale" +msgstr "Postavi osnovni jezik sistema" + +#: src/locale/org.freedesktop.locale1.policy:23 +msgid "Authentication is required to set the system locale." +msgstr "" +"Potrebno je da se identifikujete da biste postavili osnovni jezik sistema." + +#: src/locale/org.freedesktop.locale1.policy:33 +msgid "Set system keyboard settings" +msgstr "Postavi podešavanje sistemske tastature" + +#: src/locale/org.freedesktop.locale1.policy:34 +msgid "Authentication is required to set the system keyboard settings." +msgstr "" +"Potrebno je da se identifikujete da biste postavili podešavanja sistemske " +"tastature." + +#: src/login/org.freedesktop.login1.policy:22 +msgid "Allow applications to inhibit system shutdown" +msgstr "Dozvoli programima da spreče gašenje sistema" + +#: src/login/org.freedesktop.login1.policy:23 +msgid "" +"Authentication is required for an application to inhibit system shutdown." +msgstr "" +"Potrebno je da se identifikujete da biste dozvolili programu da spreči " +"gašenje sistema." + +#: src/login/org.freedesktop.login1.policy:33 +msgid "Allow applications to delay system shutdown" +msgstr "Dozvoli programima da odlože gašenje sistema" + +#: src/login/org.freedesktop.login1.policy:34 +msgid "Authentication is required for an application to delay system shutdown." +msgstr "" +"Potrebno je da se identifikujete da biste dozvolili programu da odloži " +"gašenje sistema." + +#: src/login/org.freedesktop.login1.policy:44 +msgid "Allow applications to inhibit system sleep" +msgstr "Dozvoli programima da spreče spavanje sistema" + +#: src/login/org.freedesktop.login1.policy:45 +msgid "Authentication is required for an application to inhibit system sleep." +msgstr "" +"Potrebno je da se identifikujete da biste dozvolili programu da spreči " +"spavanje sistema." + +#: src/login/org.freedesktop.login1.policy:55 +msgid "Allow applications to delay system sleep" +msgstr "Dozvoli programima da odlože spavanje sistema" + +#: src/login/org.freedesktop.login1.policy:56 +msgid "Authentication is required for an application to delay system sleep." +msgstr "" +"Potrebno je da se identifikujete da biste dozvolili programu da odloži " +"spavanje sistema." + +#: src/login/org.freedesktop.login1.policy:65 +msgid "Allow applications to inhibit automatic system suspend" +msgstr "Dozvoli programima da spreče samostalnu obustavu sistema" + +#: src/login/org.freedesktop.login1.policy:66 +msgid "" +"Authentication is required for an application to inhibit automatic system " +"suspend." +msgstr "" +"Potrebno je da se identifikujete da biste dozvolili programu da spreči " +"samostalnu obustavu sistema." + +#: src/login/org.freedesktop.login1.policy:75 +msgid "Allow applications to inhibit system handling of the power key" +msgstr "Dozvoli programima da spreče sistemu upravljanje dugmetom za napajanje" + +#: src/login/org.freedesktop.login1.policy:76 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the power key." +msgstr "" +"Potrebno je da se identifikujete da biste dozvolili programu da spreči " +"sistemu upravljanje dugmetom za napajanje." + +#: src/login/org.freedesktop.login1.policy:86 +msgid "Allow applications to inhibit system handling of the suspend key" +msgstr "Dozvoli programima da spreče sistemu upravljanje dugmetom za obustavu" + +#: src/login/org.freedesktop.login1.policy:87 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the suspend key." +msgstr "" +"Potrebno je da se identifikujete da biste dozvolili programu da spreči " +"sistemu upravljanje dugmetom za obustavu." + +#: src/login/org.freedesktop.login1.policy:97 +msgid "Allow applications to inhibit system handling of the hibernate key" +msgstr "Dozvoli programima da spreče sistemu upravljanje dugmetom za spavanje" + +#: src/login/org.freedesktop.login1.policy:98 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the hibernate key." +msgstr "" +"Potrebno je da se identifikujete da biste dozvolili programu da spreči " +"sistemu upravljanje dugmetom za spavanje." + +#: src/login/org.freedesktop.login1.policy:107 +msgid "Allow applications to inhibit system handling of the lid switch" +msgstr "" +"Dozvoli programima da spreče sistemu da uradi bilo šta prilikom zaklapanja " +"ekrana" + +#: src/login/org.freedesktop.login1.policy:108 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the lid switch." +msgstr "" +"Potrebno je da se identifikujete da biste dozvolili programu da spreči " +"sistemu da uradi bilo šta prilikom zaklapanja ekrana." + +#: src/login/org.freedesktop.login1.policy:117 +msgid "Allow applications to inhibit system handling of the reboot key" +msgstr "" +"Dozvoli programima da spreče sistemu upravljanje dugmetom za ponovno pokretanje" + +#: src/login/org.freedesktop.login1.policy:118 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the reboot key." +msgstr "" +"Potrebno je da se identifikujete da biste dozvolili programu da spreči " +"sistemu upravljanje dugmetom za ponovno pokretanje." + +#: src/login/org.freedesktop.login1.policy:128 +msgid "Allow non-logged-in user to run programs" +msgstr "Dozvoli neprijavljenim korisnicima da pokreću programe" + +#: src/login/org.freedesktop.login1.policy:129 +msgid "Explicit request is required to run programs as a non-logged-in user." +msgstr "" +"Eksplicitan zahtev je potreban da biste pokretali programe kao neprijavljen " +"korisnik." + +#: src/login/org.freedesktop.login1.policy:138 +msgid "Allow non-logged-in users to run programs" +msgstr "Dozvoli neprijavljenim korisnicima da pokreću programe" + +#: src/login/org.freedesktop.login1.policy:139 +msgid "Authentication is required to run programs as a non-logged-in user." +msgstr "" +"Potrebno je da se identifikujete da biste pokretali programe kao neprijavljen " +"korisnik." + +#: src/login/org.freedesktop.login1.policy:148 +msgid "Allow attaching devices to seats" +msgstr "Dozvoli kačenje uređaja na sedišta" + +#: src/login/org.freedesktop.login1.policy:149 +msgid "Authentication is required to attach a device to a seat." +msgstr "Potrebno je da se identifikujete da biste zakačili uređaj na sedište." + +#: src/login/org.freedesktop.login1.policy:159 +msgid "Flush device to seat attachments" +msgstr "Isperi uređaj da bi usedištio zakačeno" + +#: src/login/org.freedesktop.login1.policy:160 +msgid "Authentication is required to reset how devices are attached to seats." +msgstr "" +"Potrebno je da se identifikujete da biste ponovo podesili kako se uređaji " +"kače na sedišta." + +#: src/login/org.freedesktop.login1.policy:169 +msgid "Power off the system" +msgstr "Isključi sistem" + +#: src/login/org.freedesktop.login1.policy:170 +msgid "Authentication is required to power off the system." +msgstr "Potrebno je da se identifikujete da biste isključili sistem." + +#: src/login/org.freedesktop.login1.policy:180 +msgid "Power off the system while other users are logged in" +msgstr "Isključi sistem dok su drugi korisnici prijavljeni" + +#: src/login/org.freedesktop.login1.policy:181 +msgid "" +"Authentication is required to power off the system while other users are " +"logged in." +msgstr "" +"Potrebno je da se identifikujete da biste isključili sistem dok su drugi " +"korisnici prijavljeni." + +#: src/login/org.freedesktop.login1.policy:191 +msgid "Power off the system while an application is inhibiting this" +msgstr "Isključi sistem iako je program zatražio da se spreči gašenje" + +#: src/login/org.freedesktop.login1.policy:192 +msgid "" +"Authentication is required to power off the system while an application is " +"inhibiting this." +msgstr "" +"Potrebno je da se identifikujete da biste isključili sistem iako je program " +"zatražio da se spreči gašenje sistema." + +#: src/login/org.freedesktop.login1.policy:202 +msgid "Reboot the system" +msgstr "Ponovo pokreni sistem" + +#: src/login/org.freedesktop.login1.policy:203 +msgid "Authentication is required to reboot the system." +msgstr "Potrebno je da se identifikujete da biste ponovo pokrenuli sistem." + +#: src/login/org.freedesktop.login1.policy:213 +msgid "Reboot the system while other users are logged in" +msgstr "Ponovo pokreni sistem dok su drugi korisnici prijavljeni" + +#: src/login/org.freedesktop.login1.policy:214 +msgid "" +"Authentication is required to reboot the system while other users are logged " +"in." +msgstr "" +"Potrebno je da se identifikujete da biste ponovo pokrenuli sistem dok su " +"drugi korisnici prijavljeni." + +#: src/login/org.freedesktop.login1.policy:224 +msgid "Reboot the system while an application is inhibiting this" +msgstr "Ponovo pokreni sistem iako je program zatražio da se spreči gašenje" + +#: src/login/org.freedesktop.login1.policy:225 +msgid "" +"Authentication is required to reboot the system while an application is " +"inhibiting this." +msgstr "" +"Potrebno je da se identifikujete da biste ponovo pokrenuli sistem iako je " +"program zatražio da se spreči gašenje sistema." + +#: src/login/org.freedesktop.login1.policy:235 +msgid "Halt the system" +msgstr "Zaustavi sistem" + +#: src/login/org.freedesktop.login1.policy:236 +msgid "Authentication is required to halt the system." +msgstr "Potrebno je da se identifikujete da biste zaustavili sistem." + +#: src/login/org.freedesktop.login1.policy:246 +msgid "Halt the system while other users are logged in" +msgstr "Zaustavi sistem dok su drugi korisnici prijavljeni" + +#: src/login/org.freedesktop.login1.policy:247 +msgid "" +"Authentication is required to halt the system while other users are logged " +"in." +msgstr "" +"Potrebno je da se identifikujete da biste zaustavili sistem dok su drugi " +"korisnici prijavljeni." + +#: src/login/org.freedesktop.login1.policy:257 +msgid "Halt the system while an application is inhibiting this" +msgstr "Zaustavi sistem iako program traži da se spreči zaustavljanje" + +#: src/login/org.freedesktop.login1.policy:258 +msgid "" +"Authentication is required to halt the system while an application is " +"inhibiting this." +msgstr "" +"Potrebno je da se identifikujete da biste obustavili sistem iako je program " +"zatražio da se spreči obustavljanje sistema." + +#: src/login/org.freedesktop.login1.policy:268 +msgid "Suspend the system" +msgstr "Obustavi sistem" + +#: src/login/org.freedesktop.login1.policy:269 +msgid "Authentication is required to suspend the system." +msgstr "Potrebno je da se identifikujete da biste obustavili sistem." + +#: src/login/org.freedesktop.login1.policy:278 +msgid "Suspend the system while other users are logged in" +msgstr "Obustavi sistem dok su drugi korisnici prijavljeni" + +#: src/login/org.freedesktop.login1.policy:279 +msgid "" +"Authentication is required to suspend the system while other users are " +"logged in." +msgstr "" +"Potrebno je da se identifikujete da biste obustavili sistem dok su drugi " +"korisnici prijavljeni." + +#: src/login/org.freedesktop.login1.policy:289 +msgid "Suspend the system while an application is inhibiting this" +msgstr "Obustavite sistem iako program traži da se spreči obustava" + +#: src/login/org.freedesktop.login1.policy:290 +msgid "" +"Authentication is required to suspend the system while an application is " +"inhibiting this." +msgstr "" +"Potrebno je da se identifikujete da biste obustavili sistem iako je program " +"zatražio da se spreči obustava sistema." + +#: src/login/org.freedesktop.login1.policy:300 +msgid "Hibernate the system" +msgstr "Uspavaj sistem" + +#: src/login/org.freedesktop.login1.policy:301 +msgid "Authentication is required to hibernate the system." +msgstr "Potrebno je da se identifikujete da biste uspavali sistem." + +#: src/login/org.freedesktop.login1.policy:310 +msgid "Hibernate the system while other users are logged in" +msgstr "Uspavaj sistem dok su drugi korisnici prijavljeni" + +#: src/login/org.freedesktop.login1.policy:311 +msgid "" +"Authentication is required to hibernate the system while other users are " +"logged in." +msgstr "" +"Potrebno je da se identifikujete da biste uspavali sistem dok su drugi " +"korisnici prijavljeni." + +#: src/login/org.freedesktop.login1.policy:321 +msgid "Hibernate the system while an application is inhibiting this" +msgstr "Uspavaj sistem iako je program zatražio da se spreči spavanje" + +#: src/login/org.freedesktop.login1.policy:322 +msgid "" +"Authentication is required to hibernate the system while an application is " +"inhibiting this." +msgstr "" +"Potrebno je da se identifikujete da biste uspavali sistem iako je program " +"zatražio da se spreči uspavljivanje sistema." + +#: src/login/org.freedesktop.login1.policy:332 +msgid "Manage active sessions, users and seats" +msgstr "Upravljaj pokrenutim sesijama, korisnicima i sedištima" + +#: src/login/org.freedesktop.login1.policy:333 +msgid "Authentication is required to manage active sessions, users and seats." +msgstr "" +"Potrebno je da se identifikujete da biste upravljali pokrenutim sesijama, " +"korisnicima i sedištima." + +#: src/login/org.freedesktop.login1.policy:342 +msgid "Lock or unlock active sessions" +msgstr "Zaključaj ili otključaj pokrenute sesije" + +#: src/login/org.freedesktop.login1.policy:343 +msgid "Authentication is required to lock or unlock active sessions." +msgstr "" +"Potrebno je da se identifikujete da biste zaključavali ili otključavali " +"pokrenute sesije." + +#: src/login/org.freedesktop.login1.policy:352 +msgid "Set the reboot \"reason\" in the kernel" +msgstr "Postavi „razlog“ ponovnog pokretanja u jezgru" + +#: src/login/org.freedesktop.login1.policy:353 +msgid "Authentication is required to set the reboot \"reason\" in the kernel." +msgstr "" +"Potrebno je da se identifikujete da biste postavili \"razlog\" za ponovno " +"pokretanje unutar jezgra." + +#: src/login/org.freedesktop.login1.policy:363 +msgid "Indicate to the firmware to boot to setup interface" +msgstr "Ukaži firmveru da pokrene sistem u interfejsu za podešavanje" + +#: src/login/org.freedesktop.login1.policy:364 +msgid "" +"Authentication is required to indicate to the firmware to boot to setup " +"interface." +msgstr "" +"Potrebno je da se identifikujete da biste napomenuli firmveru da se podigne " +"u režimu podešavanja interfejsa." + +#: src/login/org.freedesktop.login1.policy:374 +msgid "Indicate to the boot loader to boot to the boot loader menu" +msgstr "" +"Ukaži učitavaču podizanja sistema da prikaže izbornik za podizanje sistema" + +#: src/login/org.freedesktop.login1.policy:375 +msgid "" +"Authentication is required to indicate to the boot loader to boot to the " +"boot loader menu." +msgstr "" +"Potrebno je potvrđivanje identiteta za ukazivanje učitavaču podizanja sistema " +"da prikaže izbornik za podizanje sistema." + +#: src/login/org.freedesktop.login1.policy:385 +msgid "Indicate to the boot loader to boot a specific entry" +msgstr "Ukaži učitavaču podizanja sistema da pokrene određeni unos" + +#: src/login/org.freedesktop.login1.policy:386 +msgid "" +"Authentication is required to indicate to the boot loader to boot into a " +"specific boot loader entry." +msgstr "" +"Potrebno je potvrđivanje identiteta za ukazivanje učitavaču podizanja sistema " +"da pokrene određeni unos učitavača podizanja sistema." + +#: src/login/org.freedesktop.login1.policy:396 +msgid "Set a wall message" +msgstr "Postavi zidnu poruku" + +#: src/login/org.freedesktop.login1.policy:397 +msgid "Authentication is required to set a wall message." +msgstr "Potrebno je potvrđivanje identiteta za postavljanje zidne poruke." + +#: src/login/org.freedesktop.login1.policy:406 +msgid "Change Session" +msgstr "Promeni sesiju" + +#: src/login/org.freedesktop.login1.policy:407 +msgid "Authentication is required to change the virtual terminal." +msgstr "Potrebno je potvrđivanje identiteta za promenu virtuelnog terminala." + +#: src/machine/org.freedesktop.machine1.policy:22 +msgid "Log into a local container" +msgstr "Prijavi se u lokalni kontejner" + +#: src/machine/org.freedesktop.machine1.policy:23 +msgid "Authentication is required to log into a local container." +msgstr "" +"Potrebno je da se identifikujete da biste se prijavili u lokalni kontejner." + +#: src/machine/org.freedesktop.machine1.policy:32 +msgid "Log into the local host" +msgstr "Prijavi se u lokalnog domaćina" + +#: src/machine/org.freedesktop.machine1.policy:33 +msgid "Authentication is required to log into the local host." +msgstr "" +"Potrebno je da se identifikujete da biste se prijavili u lokalnog domaćina." + +#: src/machine/org.freedesktop.machine1.policy:42 +msgid "Acquire a shell in a local container" +msgstr "Dobij pristup školjci unutar lokalnog kontejnera" + +#: src/machine/org.freedesktop.machine1.policy:43 +msgid "Authentication is required to acquire a shell in a local container." +msgstr "" +"Potrebno je da se identifikujete da biste dobili pristup školjci unutar " +"lokalnog kontejnera." + +#: src/machine/org.freedesktop.machine1.policy:53 +msgid "Acquire a shell on the local host" +msgstr "Dobij pristup školjci na lokalnom domaćinu" + +#: src/machine/org.freedesktop.machine1.policy:54 +msgid "Authentication is required to acquire a shell on the local host." +msgstr "" +"Potrebno je da se identifikujete da biste dobili pristup školjci na lokalnom " +"domaćinu." + +#: src/machine/org.freedesktop.machine1.policy:64 +msgid "Acquire a pseudo TTY in a local container" +msgstr "Dobij pristup pseudo pisaćoj mašini unutar lokalnog kontejnera" + +#: src/machine/org.freedesktop.machine1.policy:65 +msgid "" +"Authentication is required to acquire a pseudo TTY in a local container." +msgstr "" +"Potrebno je da se identifikujete da biste dobili pristup pseudo pisaćoj " +"mašini unutar lokalnog kontejnera." + +#: src/machine/org.freedesktop.machine1.policy:74 +msgid "Acquire a pseudo TTY on the local host" +msgstr "Dobij pristup pseudo pisaćoj mašini na lokalnom domaćinu" + +#: src/machine/org.freedesktop.machine1.policy:75 +msgid "Authentication is required to acquire a pseudo TTY on the local host." +msgstr "" +"Potrebno je da se identifikujete da biste dobili pristup pseudo pisaćoj " +"mašini na lokalnom domaćinu." + +#: src/machine/org.freedesktop.machine1.policy:84 +msgid "Manage local virtual machines and containers" +msgstr "Upravljaj lokalnim virtuelnim mašinama i kontejnerima" + +#: src/machine/org.freedesktop.machine1.policy:85 +msgid "" +"Authentication is required to manage local virtual machines and containers." +msgstr "" +"Potrebno je da se identifikujete da biste upravljali lokalnim virtuelnim " +"mašinama i kontejnerima." + +#: src/machine/org.freedesktop.machine1.policy:95 +msgid "Create a local virtual machine or container" +msgstr "Napravi lokalnu virtuelnu mašinu ili kontejner" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to create a local virtual machine or container." +msgstr "" +"Potrebno je potvrđivanje identiteta za pravljenje lokalne virtuelne mašine ili " +"kontejnera." + +#: src/machine/org.freedesktop.machine1.policy:106 +msgid "Register a local virtual machine or container" +msgstr "Registruj lokalnu virtuelnu mašinu ili kontejner" + +#: src/machine/org.freedesktop.machine1.policy:107 +msgid "" +"Authentication is required to register a local virtual machine or container." +msgstr "" +"Potrebno je potvrđivanje identiteta za registrovanje lokalne virtuelne mašine " +"ili kontejnera." + +#: src/machine/org.freedesktop.machine1.policy:116 +msgid "Manage local virtual machine and container images" +msgstr "Upravljaj lokalnim virtuelnim mašinama i slikama kontejnera" + +#: src/machine/org.freedesktop.machine1.policy:117 +msgid "" +"Authentication is required to manage local virtual machine and container " +"images." +msgstr "" +"Potrebno je da se identifikujete da biste upravljali lokalnim virtuelnim " +"mašinama i slikama kontejnera." + +#: src/network/org.freedesktop.network1.policy:22 +msgid "Set NTP servers" +msgstr "Postavi NTP servere" + +#: src/network/org.freedesktop.network1.policy:23 +msgid "Authentication is required to set NTP servers." +msgstr "Potrebno je potvrđivanje identiteta za postavljanje NTP servera." + +#: src/network/org.freedesktop.network1.policy:33 +#: src/resolve/org.freedesktop.resolve1.policy:44 +msgid "Set DNS servers" +msgstr "Postavi DNS servere" + +#: src/network/org.freedesktop.network1.policy:34 +#: src/resolve/org.freedesktop.resolve1.policy:45 +msgid "Authentication is required to set DNS servers." +msgstr "Potrebno je potvrđivanje identiteta za postavljanje DNS servera." + +#: src/network/org.freedesktop.network1.policy:44 +#: src/resolve/org.freedesktop.resolve1.policy:55 +msgid "Set domains" +msgstr "Postavi domene" + +#: src/network/org.freedesktop.network1.policy:45 +#: src/resolve/org.freedesktop.resolve1.policy:56 +msgid "Authentication is required to set domains." +msgstr "Potrebno je potvrđivanje identiteta za postavljanje domena." + +#: src/network/org.freedesktop.network1.policy:55 +#: src/resolve/org.freedesktop.resolve1.policy:66 +msgid "Set default route" +msgstr "Postavi podrazumevanu rutu" + +#: src/network/org.freedesktop.network1.policy:56 +#: src/resolve/org.freedesktop.resolve1.policy:67 +msgid "Authentication is required to set default route." +msgstr "Potrebno je potvrđivanje identiteta za postavljanje podrazumevane rute." + +#: src/network/org.freedesktop.network1.policy:66 +#: src/resolve/org.freedesktop.resolve1.policy:77 +msgid "Enable/disable LLMNR" +msgstr "Omogući/onemogući LLMNR" + +#: src/network/org.freedesktop.network1.policy:67 +#: src/resolve/org.freedesktop.resolve1.policy:78 +msgid "Authentication is required to enable or disable LLMNR." +msgstr "" +"Potrebno je potvrđivanje identiteta za omogućavanje ili onemogućavanje LLMNR-a." + +#: src/network/org.freedesktop.network1.policy:77 +#: src/resolve/org.freedesktop.resolve1.policy:88 +msgid "Enable/disable multicast DNS" +msgstr "Omogući/onemogući multikast DNS" + +#: src/network/org.freedesktop.network1.policy:78 +#: src/resolve/org.freedesktop.resolve1.policy:89 +msgid "Authentication is required to enable or disable multicast DNS." +msgstr "" +"Potrebno je potvrđivanje identiteta za omogućavanje ili onemogućavanje " +"multikast DNS-a." + +#: src/network/org.freedesktop.network1.policy:88 +#: src/resolve/org.freedesktop.resolve1.policy:99 +msgid "Enable/disable DNS over TLS" +msgstr "Omogući/onemogući DNS preko TLS-a" + +#: src/network/org.freedesktop.network1.policy:89 +#: src/resolve/org.freedesktop.resolve1.policy:100 +msgid "Authentication is required to enable or disable DNS over TLS." +msgstr "" +"Potrebno je potvrđivanje identiteta za omogućavanje ili onemogućavanje DNS-a " +"preko TLS-a." + +#: src/network/org.freedesktop.network1.policy:99 +#: src/resolve/org.freedesktop.resolve1.policy:110 +msgid "Enable/disable DNSSEC" +msgstr "Omogući/onemogući DNSSEC" + +#: src/network/org.freedesktop.network1.policy:100 +#: src/resolve/org.freedesktop.resolve1.policy:111 +msgid "Authentication is required to enable or disable DNSSEC." +msgstr "" +"Potrebno je potvrđivanje identiteta za omogućavanje ili onemogućavanje DNSSEC-" +"a." + +#: src/network/org.freedesktop.network1.policy:110 +#: src/resolve/org.freedesktop.resolve1.policy:121 +msgid "Set DNSSEC Negative Trust Anchors" +msgstr "Postavi negativne sidre poverenja za DNSSEC" + +#: src/network/org.freedesktop.network1.policy:111 +#: src/resolve/org.freedesktop.resolve1.policy:122 +msgid "Authentication is required to set DNSSEC Negative Trust Anchors." +msgstr "" +"Potrebno je potvrđivanje identiteta za postavljanje negativnih sidara poverenja " +"za DNSSEC." + +#: src/network/org.freedesktop.network1.policy:121 +msgid "Revert NTP settings" +msgstr "Vrati NTP podešavanja" + +#: src/network/org.freedesktop.network1.policy:122 +msgid "Authentication is required to reset NTP settings." +msgstr "Potrebno je potvrđivanje identiteta za resetovanje NTP podešavanja." + +#: src/network/org.freedesktop.network1.policy:132 +msgid "Revert DNS settings" +msgstr "Vrati DNS podešavanja" + +#: src/network/org.freedesktop.network1.policy:133 +msgid "Authentication is required to reset DNS settings." +msgstr "Potrebno je potvrđivanje identiteta za resetovanje DNS podešavanja." + +#: src/network/org.freedesktop.network1.policy:143 +msgid "DHCP server sends force renew message" +msgstr "DHCP server šalje poruku za prisilno obnavljanje" + +#: src/network/org.freedesktop.network1.policy:144 +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "" +"Potrebno je potvrđivanje identiteta za slanje poruke za prisilno obnavljanje sa " +"DHCP servera." + +#: src/network/org.freedesktop.network1.policy:154 +msgid "Renew dynamic addresses" +msgstr "Obnovi dinamičke adrese" + +#: src/network/org.freedesktop.network1.policy:155 +msgid "Authentication is required to renew dynamic addresses." +msgstr "Potrebno je potvrđivanje identiteta za obnavljanje dinamičkih adresa." + +#: src/network/org.freedesktop.network1.policy:165 +msgid "Reload network settings" +msgstr "Ponovo učitaj mrežna podešavanja" + +#: src/network/org.freedesktop.network1.policy:166 +msgid "Authentication is required to reload network settings." +msgstr "" +"Potrebno je potvrđivanje identiteta za ponovno učitavanje mrežnih podešavanja." + +#: src/network/org.freedesktop.network1.policy:176 +msgid "Reconfigure network interface" +msgstr "Ponovo podesi mrežni interfejs" + +#: src/network/org.freedesktop.network1.policy:177 +msgid "Authentication is required to reconfigure network interface." +msgstr "" +"Potrebno je potvrđivanje identiteta za ponovno podešavanje mrežnog interfejsa." + +#: src/network/org.freedesktop.network1.policy:187 +msgid "Specify whether persistent storage for systemd-networkd is available" +msgstr "Navedite da li je dostupno trajno skladište za systemd-networkd" + +#: src/network/org.freedesktop.network1.policy:188 +msgid "" +"Authentication is required to specify whether persistent storage for systemd-" +"networkd is available." +msgstr "" +"Potrebno je potvrđivanje identiteta za navođenje da li je dostupno trajno " +"skladište za systemd-networkd." + +#: src/network/org.freedesktop.network1.policy:198 +msgid "Manage network links" +msgstr "Upravljaj mrežnim vezama" + +#: src/network/org.freedesktop.network1.policy:199 +msgid "Authentication is required to manage network links." +msgstr "Potrebno je da se identifikujete da biste upravljali mrežnim vezama." + +#: src/portable/org.freedesktop.portable1.policy:13 +msgid "Inspect a portable service image" +msgstr "Pregledaj odraz prenosne usluge" + +#: src/portable/org.freedesktop.portable1.policy:14 +msgid "Authentication is required to inspect a portable service image." +msgstr "Potrebno je potvrđivanje identiteta za pregled odraza prenosne usluge." + +#: src/portable/org.freedesktop.portable1.policy:23 +msgid "Attach or detach a portable service image" +msgstr "Prikači ili otkači odraz prenosne usluge" + +#: src/portable/org.freedesktop.portable1.policy:24 +msgid "" +"Authentication is required to attach or detach a portable service image." +msgstr "" +"Potrebno je potvrđivanje identiteta za prikačivanje ili otkačivanje odraza " +"prenosne usluge." + +#: src/portable/org.freedesktop.portable1.policy:34 +msgid "Delete or modify portable service image" +msgstr "Obriši ili izmeni odraz prenosne usluge" + +#: src/portable/org.freedesktop.portable1.policy:35 +msgid "" +"Authentication is required to delete or modify a portable service image." +msgstr "" +"Potrebno je potvrđivanje identiteta za brisanje ili izmenu odraza prenosne " +"usluge." + +#: src/resolve/org.freedesktop.resolve1.policy:22 +msgid "Register a DNS-SD service" +msgstr "Registruj DNS-SD uslugu" + +#: src/resolve/org.freedesktop.resolve1.policy:23 +msgid "Authentication is required to register a DNS-SD service." +msgstr "Potrebno je potvrđivanje identiteta za registraciju DNS-SD usluge." + +#: src/resolve/org.freedesktop.resolve1.policy:33 +msgid "Unregister a DNS-SD service" +msgstr "Ukini registraciju DNS-SD usluge" + +#: src/resolve/org.freedesktop.resolve1.policy:34 +msgid "Authentication is required to unregister a DNS-SD service." +msgstr "" +"Potrebno je potvrđivanje identiteta za poništavanje registracije DNS-SD usluge." + +#: src/resolve/org.freedesktop.resolve1.policy:132 +msgid "Revert name resolution settings" +msgstr "Vrati podešavanja razrešavanja naziva" + +#: src/resolve/org.freedesktop.resolve1.policy:133 +msgid "Authentication is required to reset name resolution settings." +msgstr "" +"Potrebno je potvrđivanje identiteta za resetovanje podešavanja razrešavanja " +"naziva." + +#: src/resolve/org.freedesktop.resolve1.policy:143 +msgid "Subscribe query results" +msgstr "Pretplati se na rezultate upita" + +#: src/resolve/org.freedesktop.resolve1.policy:144 +msgid "Authentication is required to subscribe query results." +msgstr "Potrebno je potvrđivanje identiteta za pretplatu na rezultate upita." + +#: src/resolve/org.freedesktop.resolve1.policy:154 +msgid "Subscribe to DNS configuration" +msgstr "Pretplati se na DNS podešavanja" + +#: src/resolve/org.freedesktop.resolve1.policy:155 +msgid "Authentication is required to subscribe to DNS configuration." +msgstr "Potrebno je potvrđivanje identiteta za pretplatu na DNS podešavanja." + +#: src/resolve/org.freedesktop.resolve1.policy:165 +msgid "Dump cache" +msgstr "Ispiši keš" + +#: src/resolve/org.freedesktop.resolve1.policy:166 +msgid "Authentication is required to dump cache." +msgstr "Potrebno je potvrđivanje identiteta za ispisivanje keša." + +#: src/resolve/org.freedesktop.resolve1.policy:176 +msgid "Dump server state" +msgstr "Ispiši stanje servera" + +#: src/resolve/org.freedesktop.resolve1.policy:177 +msgid "Authentication is required to dump server state." +msgstr "Potrebno je potvrđivanje identiteta za ispisivanje stanja servera." + +#: src/resolve/org.freedesktop.resolve1.policy:187 +msgid "Dump statistics" +msgstr "Ispiši statistiku" + +#: src/resolve/org.freedesktop.resolve1.policy:188 +msgid "Authentication is required to dump statistics." +msgstr "Potrebno je potvrđivanje identiteta za ispisivanje statistike." + +#: src/resolve/org.freedesktop.resolve1.policy:198 +msgid "Reset statistics" +msgstr "Resetuj statistiku" + +#: src/resolve/org.freedesktop.resolve1.policy:199 +msgid "Authentication is required to reset statistics." +msgstr "Potrebno je potvrđivanje identiteta za resetovanje statistike." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +msgid "Check for system updates" +msgstr "Proveri ažuriranja sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +msgid "Authentication is required to check for system updates." +msgstr "Potrebno je potvrđivanje identiteta za proveru ažuriranja sistema." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +msgid "Install system updates" +msgstr "Instaliraj ažuriranja sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +msgid "Authentication is required to install system updates." +msgstr "Potrebno je potvrđivanje identiteta za instaliranje ažuriranja sistema." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +msgid "Install specific system version" +msgstr "Instaliraj određeno izdanje sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +msgid "" +"Authentication is required to update the system to a specific (possibly old) " +"version." +msgstr "" +"Potrebno je potvrđivanje identiteta za ažuriranje sistema na određeno (moguće " +"staro) izdanje." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 +msgid "Cleanup old system updates" +msgstr "Očisti stara ažuriranja sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 +msgid "Authentication is required to cleanup old system updates." +msgstr "" +"Potrebno je potvrđivanje identiteta za čišćenje starih ažuriranja sistema." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +msgid "Manage optional features" +msgstr "Upravljaj opcionim mogućnostima" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +msgid "Authentication is required to manage optional features." +msgstr "Potrebno je potvrđivanje identiteta za upravljanje opcionim mogućnostima." + +#: src/timedate/org.freedesktop.timedate1.policy:22 +msgid "Set system time" +msgstr "Postavi sistemsko vreme" + +#: src/timedate/org.freedesktop.timedate1.policy:23 +msgid "Authentication is required to set the system time." +msgstr "Potrebno je da se identifikujete da biste postavili sistemsko vreme." + +#: src/timedate/org.freedesktop.timedate1.policy:33 +msgid "Set system timezone" +msgstr "Postavi sistemsku vremensku zonu" + +#: src/timedate/org.freedesktop.timedate1.policy:34 +msgid "Authentication is required to set the system timezone." +msgstr "" +"Potrebno je da se identifikujete da biste postavili sistemsku vremensku zonu." + +#: src/timedate/org.freedesktop.timedate1.policy:43 +msgid "Set RTC to local timezone or UTC" +msgstr "" +"Postavi časovnik realnog vremena na lokalnu vremensku zonu ili UTC zonu" + +#: src/timedate/org.freedesktop.timedate1.policy:44 +msgid "" +"Authentication is required to control whether the RTC stores the local or " +"UTC time." +msgstr "" +"Potrebno je da se identifikujete da biste podesili da li RTC čuva lokalno " +"ili UTC vreme." + +#: src/timedate/org.freedesktop.timedate1.policy:53 +msgid "Turn network time synchronization on or off" +msgstr "Uključi ili isključi usklađivanje vremena sa mreže" + +#: src/timedate/org.freedesktop.timedate1.policy:54 +msgid "" +"Authentication is required to control whether network time synchronization " +"shall be enabled." +msgstr "" +"Potrebno je da se identifikujete da biste podesili da li se vreme usklađuje " +"sa mreže." From c108f84a0c89ed41386b67cf3ff5c4e4a5e2963f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 14 Apr 2026 15:06:55 +0200 Subject: [PATCH 1025/1296] cgls: convert to the new option parser arg_names is changed to be a normal strv. In the new parser code, the argument is returned as a const char*. Dropping the const to stuff it into the array would be too ugly. The metavars for optional args are now shown in --help. Co-developed-by: Claude Opus 4.6 --- src/cgls/cgls.c | 138 ++++++++++++++++++++---------------------------- 1 file changed, 57 insertions(+), 81 deletions(-) diff --git a/src/cgls/cgls.c b/src/cgls/cgls.c index c224a892e41ed..1c43543a90142 100644 --- a/src/cgls/cgls.c +++ b/src/cgls/cgls.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-bus.h" @@ -10,8 +9,10 @@ #include "bus-util.h" #include "cgroup-show.h" #include "cgroup-util.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "output-mode.h" #include "pager.h" #include "parse-util.h" @@ -35,155 +36,130 @@ static char **arg_names = NULL; static int arg_full = -1; static const char* arg_machine = NULL; -STATIC_DESTRUCTOR_REGISTER(arg_names, freep); /* don't free the strings */ +STATIC_DESTRUCTOR_REGISTER(arg_names, strv_freep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-cgls", "1", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...] [CGROUP...]\n\n" - "Recursively show control group contents.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " -a --all Show all groups, including empty\n" - " -u --unit Show the subtrees of specified system units\n" - " --user-unit Show the subtrees of specified user units\n" - " -x --xattr=BOOL Show cgroup extended attributes\n" - " -c --cgroup-id=BOOL Show cgroup ID\n" - " -l --full Do not ellipsize output\n" - " -k Include kernel threads in output\n" - " -M --machine=NAME Show container NAME\n" - "\nSee the %s for details.\n", + "%sRecursively show control group contents.%s\n\n", program_invocation_short_name, - link); + ansi_highlight(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_NO_PAGER = 0x100, - ARG_VERSION, - ARG_USER_UNIT, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "all", no_argument, NULL, 'a' }, - { "full", no_argument, NULL, 'l' }, - { "machine", required_argument, NULL, 'M' }, - { "unit", optional_argument, NULL, 'u' }, - { "user-unit", optional_argument, NULL, ARG_USER_UNIT }, - { "xattr", required_argument, NULL, 'x' }, - { "cgroup-id", required_argument, NULL, 'c' }, - {} - }; - - int c, r; - assert(argc >= 1); assert(argv); - /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long() - * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */ - optind = 0; - while ((c = getopt_long(argc, argv, "-hkalM:u::xc", options, NULL)) >= 0) + OptionParser state = { argc, argv, OPTION_PARSER_RETURN_POSITIONAL_ARGS }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case 'a': + OPTION('a', "all", NULL, "Show all groups, including empty"): arg_output_flags |= OUTPUT_SHOW_ALL; break; - case 'u': + OPTION_FULL(OPTION_OPTIONAL_ARG, 'u', "unit", "UNIT", + "Show the subtrees of specified system units"): if (arg_show_unit == SHOW_UNIT_USER) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot combine --unit with --user-unit."); arg_show_unit = SHOW_UNIT_SYSTEM; - if (strv_push(&arg_names, optarg) < 0) /* push optarg if not empty */ + if (strv_extend(&arg_names, arg) < 0) /* push arg if not empty */ return log_oom(); break; - case ARG_USER_UNIT: + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "user-unit", "UNIT", + "Show the subtrees of specified user units"): if (arg_show_unit == SHOW_UNIT_SYSTEM) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot combine --user-unit with --unit."); arg_show_unit = SHOW_UNIT_USER; - if (strv_push(&arg_names, optarg) < 0) /* push optarg if not empty */ - return log_oom(); - break; - - case 1: - /* positional argument */ - if (strv_push(&arg_names, optarg) < 0) + if (strv_extend(&arg_names, arg) < 0) /* push arg if not empty */ return log_oom(); break; - case 'l': - arg_full = true; - break; - - case 'k': - arg_output_flags |= OUTPUT_KERNEL_THREADS; - break; - - case 'M': - arg_machine = optarg; - break; - - case 'x': - if (optarg) { - r = parse_boolean(optarg); + OPTION_FULL(OPTION_OPTIONAL_ARG, 'x', "xattr", "BOOL", + "Show cgroup extended attributes"): + if (arg) { + r = parse_boolean(arg); if (r < 0) - return log_error_errno(r, "Failed to parse --xattr= value: %s", optarg); + return log_error_errno(r, "Failed to parse --xattr= value: %s", arg); } else r = true; SET_FLAG(arg_output_flags, OUTPUT_CGROUP_XATTRS, r); break; - case 'c': - if (optarg) { - r = parse_boolean(optarg); + OPTION_FULL(OPTION_OPTIONAL_ARG, 'c', "cgroup-id", "BOOL", + "Show cgroup ID"): + if (arg) { + r = parse_boolean(arg); if (r < 0) - return log_error_errno(r, "Failed to parse --cgroup-id= value: %s", optarg); + return log_error_errno(r, "Failed to parse --cgroup-id= value: %s", arg); } else r = true; SET_FLAG(arg_output_flags, OUTPUT_CGROUP_ID, r); break; - case '?': - return -EINVAL; + OPTION('l', "full", NULL, "Do not ellipsize output"): + arg_full = true; + break; - default: - assert_not_reached(); + OPTION_SHORT('k', NULL, "Include kernel threads in output"): + arg_output_flags |= OUTPUT_KERNEL_THREADS; + break; + + OPTION_COMMON_MACHINE: + arg_machine = arg; + break; + + OPTION_POSITIONAL: + if (strv_extend(&arg_names, arg) < 0) /* push arg */ + return log_oom(); + break; } if (arg_machine && arg_show_unit != SHOW_UNIT_NONE) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot combine --unit or --user-unit with --machine=."); + assert(option_parser_get_n_args(&state) == 0); + return 1; } From a60f12202f78530a531752da715737d031fa9b3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 10:07:32 +0200 Subject: [PATCH 1026/1296] cgls: fix/update/restore the handling of --xattr and --cgroup-id This is a bit tricky. Previously, the --help string said "-x --xattr=BOOL", which normally means that '-x BOOL' and '--xattr BOOL' and '--xattr=BOOL' are all accepted and equivalent. But actually only the third form was accepted. '-x' should have been and is now documented as "Same as --xattr=true". The man page tried to explain this, but not very strongly. So update the man page to have more emphasis and restore the special behaviour for -x and -c. This is a on old program, so I think in this case, maintaining compatiblity in behaviour is important. --- man/systemd-cgls.xml | 9 +++++---- src/cgls/cgls.c | 10 ++++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/man/systemd-cgls.xml b/man/systemd-cgls.xml index 5280992c8c67c..60cf6fa41aa2c 100644 --- a/man/systemd-cgls.xml +++ b/man/systemd-cgls.xml @@ -114,22 +114,23 @@ + - Controls whether to include information about extended attributes of the listed - control groups in the output. With the long option, expects a boolean value. Defaults to no. + control groups in the output. With the long option only, optionally accepts a boolean value. Defaults + to no. + - Controls whether to include the numeric ID of the listed control groups in the - output. With the long option, expects a boolean value. Defaults to no. + output. With the long option only, optionally accepts a boolean value. Defaults to no. diff --git a/src/cgls/cgls.c b/src/cgls/cgls.c index 1c43543a90142..60d8e7701235b 100644 --- a/src/cgls/cgls.c +++ b/src/cgls/cgls.c @@ -112,8 +112,9 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); break; - OPTION_FULL(OPTION_OPTIONAL_ARG, 'x', "xattr", "BOOL", - "Show cgroup extended attributes"): + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "xattr", "BOOL", + "Show cgroup extended attributes"): {} + OPTION_SHORT('x', NULL, "Same as --xattr=true"): if (arg) { r = parse_boolean(arg); if (r < 0) @@ -124,8 +125,9 @@ static int parse_argv(int argc, char *argv[]) { SET_FLAG(arg_output_flags, OUTPUT_CGROUP_XATTRS, r); break; - OPTION_FULL(OPTION_OPTIONAL_ARG, 'c', "cgroup-id", "BOOL", - "Show cgroup ID"): + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "cgroup-id", "BOOL", + "Show cgroup ID"): {} + OPTION_SHORT('c', NULL, "Same as --cgroup-id=true"): if (arg) { r = parse_boolean(arg); if (r < 0) From 6132ed752a7e375211e86d42d5df21657b4697d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 14 Apr 2026 16:46:38 +0200 Subject: [PATCH 1027/1296] cgtop: reorder option cases to match --help output Co-developed-by: Claude Opus 4.6 --- src/cgtop/cgtop.c | 94 +++++++++++++++++++++++------------------------ 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c index 0c93339a029ec..24a4be64ecc0d 100644 --- a/src/cgtop/cgtop.c +++ b/src/cgtop/cgtop.c @@ -766,6 +766,38 @@ static int parse_argv(int argc, char *argv[]) { case ARG_VERSION: return version(); + case ARG_ORDER: + arg_order = order_from_string(optarg); + if (arg_order < 0) + return log_error_errno(arg_order, + "Invalid argument to --order=: %s", + optarg); + break; + + case 'p': + arg_order = ORDER_PATH; + break; + + case 't': + arg_order = ORDER_TASKS; + break; + + case 'c': + arg_order = ORDER_CPU; + break; + + case 'm': + arg_order = ORDER_MEMORY; + break; + + case 'i': + arg_order = ORDER_IO; + break; + + case 'r': + arg_raw = true; + break; + case ARG_CPU_TYPE: if (optarg) { arg_cpu_type = cpu_type_from_string(optarg); @@ -778,11 +810,20 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_DEPTH: - r = safe_atou(optarg, &arg_depth); + case 'P': + arg_count = COUNT_USERSPACE_PROCESSES; + break; + + case 'k': + arg_count = COUNT_ALL_PROCESSES; + break; + + case ARG_RECURSIVE: + r = parse_boolean_argument("--recursive=", optarg, &arg_recursive); if (r < 0) - return log_error_errno(r, "Failed to parse depth parameter '%s': %m", optarg); + return r; + arg_recursive_unset = !r; break; case 'd': @@ -811,52 +852,11 @@ static int parse_argv(int argc, char *argv[]) { arg_batch = true; break; - case 'r': - arg_raw = true; - break; - - case 'p': - arg_order = ORDER_PATH; - break; - - case 't': - arg_order = ORDER_TASKS; - break; - - case 'c': - arg_order = ORDER_CPU; - break; - - case 'm': - arg_order = ORDER_MEMORY; - break; - - case 'i': - arg_order = ORDER_IO; - break; - - case ARG_ORDER: - arg_order = order_from_string(optarg); - if (arg_order < 0) - return log_error_errno(arg_order, - "Invalid argument to --order=: %s", - optarg); - break; - - case 'k': - arg_count = COUNT_ALL_PROCESSES; - break; - - case 'P': - arg_count = COUNT_USERSPACE_PROCESSES; - break; - - case ARG_RECURSIVE: - r = parse_boolean_argument("--recursive=", optarg, &arg_recursive); + case ARG_DEPTH: + r = safe_atou(optarg, &arg_depth); if (r < 0) - return r; + return log_error_errno(r, "Failed to parse depth parameter '%s': %m", optarg); - arg_recursive_unset = !r; break; case 'M': From 09a62b706b376d48a5dfdaedda457f21146a21cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 14 Apr 2026 15:08:25 +0200 Subject: [PATCH 1028/1296] cgtop: convert to the new option parser A few --help strings are changed to the common option variants. The optional args for --unit, --user-unit, --xattr, --cgroup-id are shown in synopsis. A define is defined for the default of --depth to make things clearer. Co-developed-by: Claude Opus 4.6 --- src/cgtop/cgtop.c | 168 ++++++++++++++++++---------------------------- 1 file changed, 64 insertions(+), 104 deletions(-) diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c index 24a4be64ecc0d..b8194de3d3eb6 100644 --- a/src/cgtop/cgtop.c +++ b/src/cgtop/cgtop.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -10,9 +9,11 @@ #include "cgroup-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "hashmap.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" @@ -72,13 +73,15 @@ typedef enum { _CPU_INVALID = -EINVAL, } CPUType; -static unsigned arg_depth = 3; +#define DEFAULT_MAXIMUM_DEPTH 3 + +static unsigned arg_depth = DEFAULT_MAXIMUM_DEPTH; static unsigned arg_iterations = UINT_MAX; static bool arg_batch = false; static bool arg_raw = false; static usec_t arg_delay = 1*USEC_PER_SEC; -static char* arg_machine = NULL; -static char* arg_root = NULL; +static const char *arg_machine = NULL; +static const char *arg_root = NULL; static bool arg_recursive = true; static bool arg_recursive_unset = false; static PidsCount arg_count = COUNT_PIDS; @@ -687,194 +690,151 @@ static void display(Hashmap *a) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-cgtop", "1", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...] [CGROUP]\n\n" - "Show top control groups by their resource usage.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - - " --order=path|tasks|cpu|memory|io\n" - " Order by specified property\n" - " -p Same as --order=path, order by path\n" - " -t Same as --order=tasks, order by number of\n" - " tasks/processes\n" - " -c Same as --order=cpu, order by CPU load\n" - " -m Same as --order=memory, order by memory load\n" - " -i Same as --order=io, order by IO load\n" - " -r --raw Provide raw (not human-readable) numbers\n" - " --cpu[=percentage]\n" - " Show CPU usage as percentage (default)\n" - " --cpu=time Show CPU usage as time\n" - " -P Count userspace processes instead of tasks (excl. kernel)\n" - " -k Count all processes instead of tasks (incl. kernel)\n" - " --recursive=BOOL Sum up process count recursively\n" - " -d --delay=DELAY Delay between updates\n" - " -n --iterations=N Run for N iterations before exiting\n" - " -1 Shortcut for --iterations=1\n" - " -b --batch Run in batch mode, accepting no input\n" - " --depth=DEPTH Maximum traversal depth (default: %u)\n" - " -M --machine= Show container\n" - "\nSee the %s for details.\n", + "%sShow top control groups by their resource usage.%s\n\n", program_invocation_short_name, - arg_depth, - link); + ansi_highlight(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_DEPTH, - ARG_CPU_TYPE, - ARG_ORDER, - ARG_RECURSIVE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "delay", required_argument, NULL, 'd' }, - { "iterations", required_argument, NULL, 'n' }, - { "batch", no_argument, NULL, 'b' }, - { "raw", no_argument, NULL, 'r' }, - { "depth", required_argument, NULL, ARG_DEPTH }, - { "cpu", optional_argument, NULL, ARG_CPU_TYPE }, - { "order", required_argument, NULL, ARG_ORDER }, - { "recursive", required_argument, NULL, ARG_RECURSIVE }, - { "machine", required_argument, NULL, 'M' }, - {} - }; - - int c, r; - assert(argc >= 1); assert(argv); - while ((c = getopt_long(argc, argv, "hptcmin:brd:kPM:1", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_ORDER: - arg_order = order_from_string(optarg); + OPTION_LONG("order", "PROPERTY", + "Order by specified property (path, tasks, cpu, memory, io)"): + arg_order = order_from_string(arg); if (arg_order < 0) return log_error_errno(arg_order, "Invalid argument to --order=: %s", - optarg); + arg); break; - case 'p': + OPTION_SHORT('p', NULL, "Same as --order=path, order by path"): arg_order = ORDER_PATH; break; - case 't': + OPTION_SHORT('t', NULL, "Same as --order=tasks, order by number of tasks/processes"): arg_order = ORDER_TASKS; break; - case 'c': + OPTION_SHORT('c', NULL, "Same as --order=cpu, order by CPU load"): arg_order = ORDER_CPU; break; - case 'm': + OPTION_SHORT('m', NULL, "Same as --order=memory, order by memory load"): arg_order = ORDER_MEMORY; break; - case 'i': + OPTION_SHORT('i', NULL, "Same as --order=io, order by IO load"): arg_order = ORDER_IO; break; - case 'r': + OPTION('r', "raw", NULL, "Provide raw (not human-readable) numbers"): arg_raw = true; break; - case ARG_CPU_TYPE: - if (optarg) { - arg_cpu_type = cpu_type_from_string(optarg); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "cpu", "percentage|time", + "Show CPU usage as percentage (default) or time"): + if (arg) { + arg_cpu_type = cpu_type_from_string(arg); if (arg_cpu_type < 0) return log_error_errno(arg_cpu_type, "Unknown argument to --cpu=: %s", - optarg); + arg); } else arg_cpu_type = CPU_TIME; - break; - case 'P': + OPTION_SHORT('P', NULL, "Count userspace processes instead of tasks (excl. kernel)"): arg_count = COUNT_USERSPACE_PROCESSES; break; - case 'k': + OPTION_SHORT('k', NULL, "Count all processes instead of tasks (incl. kernel)"): arg_count = COUNT_ALL_PROCESSES; break; - case ARG_RECURSIVE: - r = parse_boolean_argument("--recursive=", optarg, &arg_recursive); + OPTION_LONG("recursive", "BOOL", "Sum up process count recursively"): + r = parse_boolean_argument("--recursive=", arg, &arg_recursive); if (r < 0) return r; arg_recursive_unset = !r; break; - case 'd': - r = parse_sec(optarg, &arg_delay); + OPTION('d', "delay", "DELAY", "Delay between updates"): + r = parse_sec(arg, &arg_delay); if (r < 0) - return log_error_errno(r, "Failed to parse delay parameter '%s': %m", optarg); + return log_error_errno(r, "Failed to parse delay parameter '%s': %m", arg); if (arg_delay <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid delay parameter '%s'", - optarg); - + arg); break; - case 'n': - r = safe_atou(optarg, &arg_iterations); + OPTION('n', "iterations", "N", "Run for N iterations before exiting"): + r = safe_atou(arg, &arg_iterations); if (r < 0) - return log_error_errno(r, "Failed to parse iterations parameter '%s': %m", optarg); - + return log_error_errno(r, "Failed to parse iterations parameter '%s': %m", arg); break; - case '1': + OPTION_SHORT('1', NULL, "Shortcut for --iterations=1"): arg_iterations = 1; break; - case 'b': + OPTION('b', "batch", NULL, "Run in batch mode, accepting no input"): arg_batch = true; break; - case ARG_DEPTH: - r = safe_atou(optarg, &arg_depth); + OPTION_LONG("depth", "DEPTH", + "Maximum traversal depth (default: "STRINGIFY(DEFAULT_MAXIMUM_DEPTH)")"): + r = safe_atou(arg, &arg_depth); if (r < 0) - return log_error_errno(r, "Failed to parse depth parameter '%s': %m", optarg); - + return log_error_errno(r, "Failed to parse depth parameter '%s': %m", arg); break; - case 'M': - arg_machine = optarg; + OPTION_COMMON_MACHINE: + arg_machine = arg; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind == argc - 1) - arg_root = argv[optind]; - else if (optind < argc) + size_t n_args = option_parser_get_n_args(&state); + if (n_args > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments."); + if (n_args == 1) + arg_root = option_parser_get_args(&state)[0]; return 1; } From 4356ba961f3704fa8a1b2b4ca236f4e7c2443b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 14 Apr 2026 16:50:55 +0200 Subject: [PATCH 1029/1296] creds: reorder option cases to match --help output Co-developed-by: Claude Opus 4.6 --- src/creds/creds.c | 56 +++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/creds/creds.c b/src/creds/creds.c index 3b1cc8e86b64c..ed087d8cf1d2a 100644 --- a/src/creds/creds.c +++ b/src/creds/creds.c @@ -949,6 +949,34 @@ static int parse_argv(int argc, char *argv[]) { arg_pretty = true; break; + case ARG_NAME: + if (isempty(optarg)) { + arg_name = NULL; + arg_name_any = true; + break; + } + + if (!credential_name_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid credential name: %s", optarg); + + arg_name = optarg; + arg_name_any = false; + break; + + case ARG_TIMESTAMP: + r = parse_timestamp(optarg, &arg_timestamp); + if (r < 0) + return log_error_errno(r, "Failed to parse timestamp: %s", optarg); + + break; + + case ARG_NOT_AFTER: + r = parse_timestamp(optarg, &arg_not_after); + if (r < 0) + return log_error_errno(r, "Failed to parse --not-after= timestamp: %s", optarg); + + break; + case ARG_WITH_KEY: if (streq(optarg, "help")) { if (arg_legend) @@ -1010,34 +1038,6 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_NAME: - if (isempty(optarg)) { - arg_name = NULL; - arg_name_any = true; - break; - } - - if (!credential_name_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid credential name: %s", optarg); - - arg_name = optarg; - arg_name_any = false; - break; - - case ARG_TIMESTAMP: - r = parse_timestamp(optarg, &arg_timestamp); - if (r < 0) - return log_error_errno(r, "Failed to parse timestamp: %s", optarg); - - break; - - case ARG_NOT_AFTER: - r = parse_timestamp(optarg, &arg_not_after); - if (r < 0) - return log_error_errno(r, "Failed to parse --not-after= timestamp: %s", optarg); - - break; - case ARG_USER: if (!uid_is_valid(arg_uid)) arg_uid = getuid(); From f962b28b40bf3a4ddf667c78a972723b8b15bf4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 14 Apr 2026 15:37:24 +0200 Subject: [PATCH 1030/1296] creds: convert to the new option and verb parsers Metavars in --help are adjusted to make the left column less wide. --no-ask-password and --quiet are now shown in help. Co-developed-by: Claude Opus 4.6 --- src/creds/creds.c | 313 +++++++++++++++++----------------------------- 1 file changed, 117 insertions(+), 196 deletions(-) diff --git a/src/creds/creds.c b/src/creds/creds.c index ed087d8cf1d2a..6988d39ca4113 100644 --- a/src/creds/creds.c +++ b/src/creds/creds.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -24,6 +23,7 @@ #include "log.h" #include "main-func.h" #include "memory-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -305,6 +305,8 @@ static int add_credentials_to_table(Table *t, bool encrypted) { return 1; /* Creds dir set */ } +VERB(verb_list, "list", NULL, VERB_ANY, 1, VERB_DEFAULT, + "Show list of passed credentials"); static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *t = NULL; int r, q; @@ -465,6 +467,8 @@ static int write_blob(FILE *f, const void *data, size_t size) { return 0; } +VERB(verb_cat, "cat", "CREDENTIAL...", 2, VERB_ANY, 0, + "Show contents of specified credentials"); static int verb_cat(int argc, char *argv[], uintptr_t _data, void *userdata) { usec_t timestamp; int r, ret = 0; @@ -551,6 +555,8 @@ static int verb_cat(int argc, char *argv[], uintptr_t _data, void *userdata) { return ret; } +VERB(verb_encrypt, "encrypt", "INPUT OUTPUT", 3, 3, 0, + "Encrypt plaintext credential file and write to ciphertext credential file"); static int verb_encrypt(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(iovec_done_erase) struct iovec plaintext = {}, output = {}; _cleanup_free_ char *base64_buf = NULL, *fname = NULL; @@ -659,6 +665,8 @@ static int verb_encrypt(int argc, char *argv[], uintptr_t _data, void *userdata) return EXIT_SUCCESS; } +VERB(verb_decrypt, "decrypt", "INPUT [OUTPUT]", 2, 3, 0, + "Decrypt ciphertext credential file and write to plaintext credential file"); static int verb_decrypt(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(iovec_done_erase) struct iovec input = {}, plaintext = {}; _cleanup_free_ char *fname = NULL; @@ -741,6 +749,8 @@ static int verb_decrypt(int argc, char *argv[], uintptr_t _data, void *userdata) return EXIT_SUCCESS; } +VERB_NOARG(verb_setup, "setup", + "Generate credentials host key, if not existing yet"); static int verb_setup(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(iovec_done_erase) struct iovec host_key = {}; int r; @@ -754,240 +764,169 @@ static int verb_setup(int argc, char *argv[], uintptr_t _data, void *userdata) { return EXIT_SUCCESS; } +/* For backward compatibility. Hidden from help. */ +VERB(verb_has_tpm2, "has-tpm2", NULL, VERB_ANY, 1, 0, /* help= */ NULL); static int verb_has_tpm2(int argc, char *argv[], uintptr_t _data, void *userdata) { if (!arg_quiet) - log_notice("The 'systemd-creds %1$s' command has been replaced by 'systemd-analyze %1$s'. Redirecting invocation.", argv[optind]); + log_notice("The 'systemd-creds %1$s' command has been replaced by 'systemd-analyze %1$s'. Redirecting invocation.", argv[0]); return verb_has_tpm2_generic(arg_quiet); } static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-creds", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n%5$sDisplay and Process Credentials.%6$s\n" - "\n%3$sCommands:%4$s\n" - " list Show list of passed credentials\n" - " cat CREDENTIAL... Show contents of specified credentials\n" - " setup Generate credentials host key, if not existing yet\n" - " encrypt INPUT OUTPUT Encrypt plaintext credential file and write to\n" - " ciphertext credential file\n" - " decrypt INPUT [OUTPUT] Decrypt ciphertext credential file and write to\n" - " plaintext credential file\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " --json=pretty|short|off\n" - " Generate JSON output\n" - " --system Show credentials passed to system\n" - " --transcode=base64|unbase64|hex|unhex\n" - " Transcode credential data\n" - " --newline=auto|yes|no\n" - " Suffix output with newline\n" - " -p --pretty Output as SetCredentialEncrypted= line\n" - " --name=NAME Override filename included in encrypted credential\n" - " --timestamp=TIME Include specified timestamp in encrypted credential\n" - " --not-after=TIME Include specified invalidation time in encrypted\n" - " credential\n" - " --with-key=host|tpm2|host+tpm2|null|auto|auto-initrd\n" - " Which keys to encrypt with\n" - " -H Shortcut for --with-key=host\n" - " -T Shortcut for --with-key=tpm2\n" - " --tpm2-device=PATH\n" - " Pick TPM2 device\n" - " --tpm2-pcrs=PCR1+PCR2+PCR3+…\n" - " Specify TPM2 PCRs to seal against (fixed hash)\n" - " --tpm2-public-key=PATH\n" - " Specify PEM certificate to seal against\n" - " --tpm2-public-key-pcrs=PCR1+PCR2+PCR3+…\n" - " Specify TPM2 PCRs to seal against (public key)\n" - " --tpm2-signature=PATH\n" - " Specify signature for public key PCR policy\n" - " --user Select user-scoped credential encryption\n" - " --uid=UID Select user for scoped credentials\n" - " --allow-null Allow decrypting credentials with null key\n" - " --refuse-null Refuse decrypting credentials with null key\n" - "\nSee the %2$s for details.\n", + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] COMMAND ...\n\n" + "%sDisplay and Process Credentials.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); - return 0; -} + r = table_print_or_warn(verbs); + if (r < 0) + return r; -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_JSON, - ARG_SYSTEM, - ARG_TRANSCODE, - ARG_NEWLINE, - ARG_WITH_KEY, - ARG_TPM2_DEVICE, - ARG_TPM2_PCRS, - ARG_TPM2_PUBLIC_KEY, - ARG_TPM2_PUBLIC_KEY_PCRS, - ARG_TPM2_SIGNATURE, - ARG_NAME, - ARG_TIMESTAMP, - ARG_NOT_AFTER, - ARG_USER, - ARG_UID, - ARG_ALLOW_NULL, - ARG_REFUSE_NULL, - ARG_NO_ASK_PASSWORD, - }; + r = table_print_or_warn(options); + if (r < 0) + return r; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "json", required_argument, NULL, ARG_JSON }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "transcode", required_argument, NULL, ARG_TRANSCODE }, - { "newline", required_argument, NULL, ARG_NEWLINE }, - { "pretty", no_argument, NULL, 'p' }, - { "with-key", required_argument, NULL, ARG_WITH_KEY }, - { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, - { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS }, - { "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY }, - { "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS }, - { "tpm2-signature", required_argument, NULL, ARG_TPM2_SIGNATURE }, - { "name", required_argument, NULL, ARG_NAME }, - { "timestamp", required_argument, NULL, ARG_TIMESTAMP }, - { "not-after", required_argument, NULL, ARG_NOT_AFTER }, - { "quiet", no_argument, NULL, 'q' }, - { "user", no_argument, NULL, ARG_USER }, - { "uid", required_argument, NULL, ARG_UID }, - { "allow-null", no_argument, NULL, ARG_ALLOW_NULL }, - { "refuse-null", no_argument, NULL, ARG_REFUSE_NULL }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - {} - }; + printf("\nSee the %s for details.\n", link); + return 0; +} - int c, r; +VERB_COMMON_HELP(help); +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hHTpq", options, NULL)) >= 0) { + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_NO_LEGEND: + OPTION_COMMON_NO_LEGEND: arg_legend = false; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); if (r <= 0) return r; - break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Show credentials passed to system"): arg_system = true; break; - case ARG_TRANSCODE: - if (streq(optarg, "help")) { + OPTION_LONG("transcode", "METHOD", + "Transcode credential data (base64, unbase64, hex, unhex)"): + if (streq(arg, "help")) { if (arg_legend) puts("Supported transcode types:"); return DUMP_STRING_TABLE(transcode_mode, TranscodeMode, _TRANSCODE_MAX); } - if (parse_boolean(optarg) == 0) /* If specified as "false", turn transcoding off */ + if (parse_boolean(arg) == 0) /* If specified as "false", turn transcoding off */ arg_transcode = TRANSCODE_OFF; else { TranscodeMode m; - m = transcode_mode_from_string(optarg); + m = transcode_mode_from_string(arg); if (m < 0) return log_error_errno(m, "Failed to parse transcode mode: %m"); arg_transcode = m; } - break; - case ARG_NEWLINE: - r = parse_tristate_argument_with_auto("--newline=", optarg, &arg_newline); + OPTION_LONG("newline", "auto|yes|no", "Suffix output with newline"): + r = parse_tristate_argument_with_auto("--newline=", arg, &arg_newline); if (r < 0) return r; break; - case 'p': + OPTION('p', "pretty", NULL, "Output as SetCredentialEncrypted= line"): arg_pretty = true; break; - case ARG_NAME: - if (isempty(optarg)) { + OPTION_LONG("name", "NAME", + "Override filename included in encrypted credential"): + if (isempty(arg)) { arg_name = NULL; arg_name_any = true; break; } - if (!credential_name_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid credential name: %s", optarg); + if (!credential_name_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid credential name: %s", arg); - arg_name = optarg; + arg_name = arg; arg_name_any = false; break; - case ARG_TIMESTAMP: - r = parse_timestamp(optarg, &arg_timestamp); + OPTION_LONG("timestamp", "TIME", + "Include specified timestamp in encrypted credential"): + r = parse_timestamp(arg, &arg_timestamp); if (r < 0) - return log_error_errno(r, "Failed to parse timestamp: %s", optarg); - + return log_error_errno(r, "Failed to parse timestamp: %s", arg); break; - case ARG_NOT_AFTER: - r = parse_timestamp(optarg, &arg_not_after); + OPTION_LONG("not-after", "TIME", + "Include specified invalidation time in encrypted credential"): + r = parse_timestamp(arg, &arg_not_after); if (r < 0) - return log_error_errno(r, "Failed to parse --not-after= timestamp: %s", optarg); - + return log_error_errno(r, "Failed to parse --not-after= timestamp: %s", arg); break; - case ARG_WITH_KEY: - if (streq(optarg, "help")) { + OPTION_LONG("with-key", "KEY", + "Which keys to encrypt with (host, tpm2, host+tpm2, null, auto, auto-initrd)"): + if (streq(arg, "help")) { if (arg_legend) puts("Supported key types:"); return DUMP_STRING_TABLE(cred_key_type, CredKeyType, _CRED_KEY_TYPE_MAX); } - if (isempty(optarg)) + if (isempty(arg)) arg_with_key = _CRED_AUTO; else { - CredKeyType t = cred_key_type_from_string(optarg); + CredKeyType t = cred_key_type_from_string(arg); if (t < 0) return log_error_errno(t, "Failed to parse key type: %m"); @@ -995,62 +934,63 @@ static int parse_argv(int argc, char *argv[]) { } break; - case 'H': + OPTION_SHORT('H', NULL, "Shortcut for --with-key=host"): arg_with_key = CRED_AES256_GCM_BY_HOST; break; - case 'T': + OPTION_SHORT('T', NULL, "Shortcut for --with-key=tpm2"): arg_with_key = _CRED_AUTO_TPM2; break; - case ARG_TPM2_DEVICE: - if (streq(optarg, "list")) + OPTION_LONG("tpm2-device", "PATH", "Pick TPM2 device"): + if (streq(arg, "list")) return tpm2_list_devices(arg_legend, arg_quiet); - arg_tpm2_device = streq(optarg, "auto") ? NULL : optarg; + arg_tpm2_device = streq(arg, "auto") ? NULL : arg; break; - case ARG_TPM2_PCRS: /* For fixed hash PCR policies only */ - r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_pcr_mask); + OPTION_LONG("tpm2-pcrs", "PCR1+PCR2+PCR3+…", + "Specify TPM2 PCRs to seal against (fixed hash)"): + /* For fixed hash PCR policies only */ + r = tpm2_parse_pcr_argument_to_mask(arg, &arg_tpm2_pcr_mask); if (r < 0) return r; - break; - case ARG_TPM2_PUBLIC_KEY: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_public_key); + OPTION_LONG("tpm2-public-key", "PATH", + "Specify PEM certificate to seal against"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_public_key); if (r < 0) return r; - break; - case ARG_TPM2_PUBLIC_KEY_PCRS: /* For public key PCR policies only */ - r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_public_key_pcr_mask); + OPTION_LONG("tpm2-public-key-pcrs", "PCR1+PCR2+…", + "Specify TPM2 PCRs to seal against (public key)"): + /* For public key PCR policies only */ + r = tpm2_parse_pcr_argument_to_mask(arg, &arg_tpm2_public_key_pcr_mask); if (r < 0) return r; - break; - case ARG_TPM2_SIGNATURE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_signature); + OPTION_LONG("tpm2-signature", "PATH", + "Specify signature for public key PCR policy"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_signature); if (r < 0) return r; - break; - case ARG_USER: + OPTION_LONG("user", NULL, "Select user-scoped credential encryption"): if (!uid_is_valid(arg_uid)) arg_uid = getuid(); - break; - case ARG_UID: - if (isempty(optarg)) + OPTION_LONG("uid", "UID", "Select user for scoped credentials"): + if (isempty(arg)) arg_uid = UID_INVALID; - else if (streq(optarg, "self")) + else if (streq(arg, "self")) arg_uid = getuid(); else { - const char *name = optarg; + const char *name = arg; r = get_user_creds( &name, @@ -1061,35 +1001,30 @@ static int parse_argv(int argc, char *argv[]) { /* flags= */ 0); if (r < 0) return log_error_errno(r, "Failed to resolve user '%s': %s", - optarg, STRERROR_USER(r)); + arg, STRERROR_USER(r)); } break; - case ARG_ALLOW_NULL: + OPTION_LONG("allow-null", NULL, + "Allow decrypting credentials with null key"): arg_credential_flags &= ~CREDENTIAL_REFUSE_NULL; arg_credential_flags |= CREDENTIAL_ALLOW_NULL; break; - case ARG_REFUSE_NULL: + OPTION_LONG("refuse-null", NULL, + "Refuse decrypting credentials with null key"): arg_credential_flags |= CREDENTIAL_REFUSE_NULL; arg_credential_flags &= ~CREDENTIAL_ALLOW_NULL; break; - case ARG_NO_ASK_PASSWORD: + OPTION_COMMON_NO_ASK_PASSWORD: arg_ask_password = false; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress informational messages"): arg_quiet = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } SET_FLAG(arg_credential_flags, CREDENTIAL_IPC_ALLOW_INTERACTIVE, arg_ask_password); @@ -1118,25 +1053,10 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m"); arg_varlink = r; + *ret_args = option_parser_get_args(&state); return 1; } -static int creds_main(int argc, char *argv[]) { - - static const Verb verbs[] = { - { "list", VERB_ANY, 1, VERB_DEFAULT, verb_list }, - { "cat", 2, VERB_ANY, 0, verb_cat }, - { "encrypt", 3, 3, 0, verb_encrypt }, - { "decrypt", 2, 3, 0, verb_decrypt }, - { "setup", VERB_ANY, 1, 0, verb_setup }, - { "help", VERB_ANY, 1, 0, verb_help }, - { "has-tpm2", VERB_ANY, 1, 0, verb_has_tpm2 }, /* for backward compatibility */ - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - #define TIMESTAMP_FRESH_MAX (30*USEC_PER_SEC) static bool timestamp_is_fresh(usec_t x) { @@ -1552,14 +1472,15 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; if (arg_varlink) return vl_server(); - return creds_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); From edc8e93830e3c122fc02501a0984d729bb7b650a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 14 Apr 2026 16:53:56 +0200 Subject: [PATCH 1031/1296] firstboot: reorder option cases to match --help output Co-developed-by: Claude Opus 4.6 --- src/firstboot/firstboot.c | 78 +++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 8cb81e7f06e70..4720af570746b 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -1449,6 +1449,32 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_HOSTNAME: + if (!hostname_is_valid(optarg, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Host name %s is not valid.", optarg); + + r = free_and_strdup(&arg_hostname, optarg); + if (r < 0) + return log_oom(); + + hostname_cleanup(arg_hostname); + break; + + case ARG_SETUP_MACHINE_ID: + r = sd_id128_randomize(&arg_machine_id); + if (r < 0) + return log_error_errno(r, "Failed to generate randomized machine ID: %m"); + + break; + + case ARG_MACHINE_ID: + r = sd_id128_from_string(optarg, &arg_machine_id); + if (r < 0) + return log_error_errno(r, "Failed to parse machine id %s.", optarg); + + break; + case ARG_ROOT_PASSWORD: r = free_and_strdup(&arg_root_password, optarg); if (r < 0) @@ -1482,32 +1508,6 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_HOSTNAME: - if (!hostname_is_valid(optarg, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Host name %s is not valid.", optarg); - - r = free_and_strdup(&arg_hostname, optarg); - if (r < 0) - return log_oom(); - - hostname_cleanup(arg_hostname); - break; - - case ARG_SETUP_MACHINE_ID: - r = sd_id128_randomize(&arg_machine_id); - if (r < 0) - return log_error_errno(r, "Failed to generate randomized machine ID: %m"); - - break; - - case ARG_MACHINE_ID: - r = sd_id128_from_string(optarg, &arg_machine_id); - if (r < 0) - return log_error_errno(r, "Failed to parse machine id %s.", optarg); - - break; - case ARG_KERNEL_COMMAND_LINE: r = free_and_strdup(&arg_kernel_cmdline, optarg); if (r < 0) @@ -1515,12 +1515,6 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_PROMPT: - arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname = - arg_prompt_root_password = arg_prompt_root_shell = true; - arg_prompt_keymap_auto = false; - break; - case ARG_PROMPT_LOCALE: arg_prompt_locale = true; break; @@ -1550,9 +1544,10 @@ static int parse_argv(int argc, char *argv[]) { arg_prompt_root_shell = true; break; - case ARG_COPY: - arg_copy_locale = arg_copy_keymap = arg_copy_timezone = arg_copy_root_password = - arg_copy_root_shell = true; + case ARG_PROMPT: + arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname = + arg_prompt_root_password = arg_prompt_root_shell = true; + arg_prompt_keymap_auto = false; break; case ARG_COPY_LOCALE: @@ -1575,6 +1570,11 @@ static int parse_argv(int argc, char *argv[]) { arg_copy_root_shell = true; break; + case ARG_COPY: + arg_copy_locale = arg_copy_keymap = arg_copy_timezone = arg_copy_root_password = + arg_copy_root_shell = true; + break; + case ARG_FORCE: arg_force = true; break; @@ -1598,10 +1598,6 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_RESET: - arg_reset = true; - break; - case ARG_MUTE_CONSOLE: r = parse_boolean_argument("--mute-console=", optarg, &arg_mute_console); if (r < 0) @@ -1609,6 +1605,10 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_RESET: + arg_reset = true; + break; + case '?': return -EINVAL; From 5a2777c93ee79313232279f74447af7df937602c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 14 Apr 2026 16:27:35 +0200 Subject: [PATCH 1032/1296] firstboot: convert to the new option parser The descriptions for boolean options are adjusted to work better with BOOL as the metavar. Description of --copy is fixed, fixup for f649325ba73a7165a14c1f1134b30b12a96d3718. Co-developed-by: Claude Opus 4.6 --- src/firstboot/firstboot.c | 293 +++++++++++--------------------------- 1 file changed, 84 insertions(+), 209 deletions(-) diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 4720af570746b..3cfe6ed7f2523 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include "sd-bus.h" @@ -24,6 +23,7 @@ #include "errno-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "fs-util.h" #include "glyph-util.h" #include "hostname-util.h" @@ -38,6 +38,7 @@ #include "main-func.h" #include "memory-util.h" #include "mount-util.h" +#include "options.h" #include "os-util.h" #include "parse-argument.h" #include "parse-util.h" @@ -1240,380 +1241,254 @@ static int process_reset(int rfd) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-firstboot", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...]\n" - "\n%3$sConfigures basic settings of the system.%4$s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --root=PATH Operate on an alternate filesystem root\n" - " --image=PATH Operate on disk image as filesystem root\n" - " --image-policy=POLICY Specify disk image dissection policy\n" - " --locale=LOCALE Set primary locale (LANG=)\n" - " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n" - " --keymap=KEYMAP Set keymap\n" - " --timezone=TIMEZONE Set timezone\n" - " --hostname=NAME Set hostname\n" - " --setup-machine-id Set a random machine ID\n" - " --machine-id=ID Set specified machine ID\n" - " --root-password=PASSWORD Set root password from plaintext password\n" - " --root-password-file=FILE Set root password from file\n" - " --root-password-hashed=HASH Set root password from hashed password\n" - " --root-shell=SHELL Set root shell\n" - " --kernel-command-line=CMDLINE\n" - " Set kernel command line\n" - " --prompt-locale Prompt the user for locale settings\n" - " --prompt-keymap Prompt the user for keymap settings\n" - " --prompt-keymap-auto Prompt the user for keymap settings if invoked\n" - " on local console\n" - " --prompt-timezone Prompt the user for timezone\n" - " --prompt-hostname Prompt the user for hostname\n" - " --prompt-root-password Prompt the user for root password\n" - " --prompt-root-shell Prompt the user for root shell\n" - " --prompt Prompt for all of the above\n" - " --copy-locale Copy locale from host\n" - " --copy-keymap Copy keymap from host\n" - " --copy-timezone Copy timezone from host\n" - " --copy-root-password Copy root password from host\n" - " --copy-root-shell Copy root shell from host\n" - " --copy Copy locale, keymap, timezone, root password\n" - " --force Overwrite existing files\n" - " --delete-root-password Delete root password\n" - " --welcome=no Disable the welcome text\n" - " --chrome=no Don't show color bar at top and bottom of\n" - " terminal\n" - " --mute-console=yes Tell kernel/PID 1 to not write to the console\n" - " while running\n" - " --reset Remove existing files\n" - "\nSee the %2$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n\n" + "%sConfigures basic settings of the system.%s\n\n", program_invocation_short_name, - link, ansi_highlight(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_LOCALE, - ARG_LOCALE_MESSAGES, - ARG_KEYMAP, - ARG_TIMEZONE, - ARG_HOSTNAME, - ARG_SETUP_MACHINE_ID, - ARG_MACHINE_ID, - ARG_ROOT_PASSWORD, - ARG_ROOT_PASSWORD_FILE, - ARG_ROOT_PASSWORD_HASHED, - ARG_ROOT_SHELL, - ARG_KERNEL_COMMAND_LINE, - ARG_PROMPT, - ARG_PROMPT_LOCALE, - ARG_PROMPT_KEYMAP, - ARG_PROMPT_KEYMAP_AUTO, - ARG_PROMPT_TIMEZONE, - ARG_PROMPT_HOSTNAME, - ARG_PROMPT_ROOT_PASSWORD, - ARG_PROMPT_ROOT_SHELL, - ARG_COPY, - ARG_COPY_LOCALE, - ARG_COPY_KEYMAP, - ARG_COPY_TIMEZONE, - ARG_COPY_ROOT_PASSWORD, - ARG_COPY_ROOT_SHELL, - ARG_FORCE, - ARG_DELETE_ROOT_PASSWORD, - ARG_WELCOME, - ARG_CHROME, - ARG_RESET, - ARG_MUTE_CONSOLE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "locale", required_argument, NULL, ARG_LOCALE }, - { "locale-messages", required_argument, NULL, ARG_LOCALE_MESSAGES }, - { "keymap", required_argument, NULL, ARG_KEYMAP }, - { "timezone", required_argument, NULL, ARG_TIMEZONE }, - { "hostname", required_argument, NULL, ARG_HOSTNAME }, - { "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID }, - { "machine-id", required_argument, NULL, ARG_MACHINE_ID }, - { "root-password", required_argument, NULL, ARG_ROOT_PASSWORD }, - { "root-password-file", required_argument, NULL, ARG_ROOT_PASSWORD_FILE }, - { "root-password-hashed", required_argument, NULL, ARG_ROOT_PASSWORD_HASHED }, - { "root-shell", required_argument, NULL, ARG_ROOT_SHELL }, - { "kernel-command-line", required_argument, NULL, ARG_KERNEL_COMMAND_LINE }, - { "prompt", no_argument, NULL, ARG_PROMPT }, - { "prompt-locale", no_argument, NULL, ARG_PROMPT_LOCALE }, - { "prompt-keymap", no_argument, NULL, ARG_PROMPT_KEYMAP }, - { "prompt-keymap-auto", no_argument, NULL, ARG_PROMPT_KEYMAP_AUTO }, - { "prompt-timezone", no_argument, NULL, ARG_PROMPT_TIMEZONE }, - { "prompt-hostname", no_argument, NULL, ARG_PROMPT_HOSTNAME }, - { "prompt-root-password", no_argument, NULL, ARG_PROMPT_ROOT_PASSWORD }, - { "prompt-root-shell", no_argument, NULL, ARG_PROMPT_ROOT_SHELL }, - { "copy", no_argument, NULL, ARG_COPY }, - { "copy-locale", no_argument, NULL, ARG_COPY_LOCALE }, - { "copy-keymap", no_argument, NULL, ARG_COPY_KEYMAP }, - { "copy-timezone", no_argument, NULL, ARG_COPY_TIMEZONE }, - { "copy-root-password", no_argument, NULL, ARG_COPY_ROOT_PASSWORD }, - { "copy-root-shell", no_argument, NULL, ARG_COPY_ROOT_SHELL }, - { "force", no_argument, NULL, ARG_FORCE }, - { "delete-root-password", no_argument, NULL, ARG_DELETE_ROOT_PASSWORD }, - { "welcome", required_argument, NULL, ARG_WELCOME }, - { "chrome", required_argument, NULL, ARG_CHROME }, - { "reset", no_argument, NULL, ARG_RESET }, - { "mute-console", required_argument, NULL, ARG_MUTE_CONSOLE }, - {} - }; - - int r, c; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_ROOT: - r = parse_path_argument(optarg, true, &arg_root); + OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): + r = parse_path_argument(arg, true, &arg_root); if (r < 0) return r; break; - case ARG_IMAGE: - r = parse_path_argument(optarg, false, &arg_image); + OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"): + r = parse_path_argument(arg, false, &arg_image); if (r < 0) return r; break; - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); + OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): + r = parse_image_policy_argument(arg, &arg_image_policy); if (r < 0) return r; break; - case ARG_LOCALE: - r = free_and_strdup(&arg_locale, optarg); + OPTION_LONG("locale", "LOCALE", "Set primary locale (LANG=)"): + r = free_and_strdup(&arg_locale, arg); if (r < 0) return log_oom(); - break; - case ARG_LOCALE_MESSAGES: - r = free_and_strdup(&arg_locale_messages, optarg); + OPTION_LONG("locale-messages", "LOCALE", "Set message locale (LC_MESSAGES=)"): + r = free_and_strdup(&arg_locale_messages, arg); if (r < 0) return log_oom(); - break; - case ARG_KEYMAP: - if (!keymap_is_valid(optarg)) + OPTION_LONG("keymap", "KEYMAP", "Set keymap"): + if (!keymap_is_valid(arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Keymap %s is not valid.", optarg); + "Keymap %s is not valid.", arg); - r = free_and_strdup(&arg_keymap, optarg); + r = free_and_strdup(&arg_keymap, arg); if (r < 0) return log_oom(); - break; - case ARG_TIMEZONE: - if (!timezone_is_valid(optarg, LOG_ERR)) + OPTION_LONG("timezone", "TIMEZONE", "Set timezone"): + if (!timezone_is_valid(arg, LOG_ERR)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Timezone %s is not valid.", optarg); + "Timezone %s is not valid.", arg); - r = free_and_strdup(&arg_timezone, optarg); + r = free_and_strdup(&arg_timezone, arg); if (r < 0) return log_oom(); - break; - case ARG_HOSTNAME: - if (!hostname_is_valid(optarg, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK)) + OPTION_LONG("hostname", "NAME", "Set hostname"): + if (!hostname_is_valid(arg, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Host name %s is not valid.", optarg); + "Host name %s is not valid.", arg); - r = free_and_strdup(&arg_hostname, optarg); + r = free_and_strdup(&arg_hostname, arg); if (r < 0) return log_oom(); hostname_cleanup(arg_hostname); break; - case ARG_SETUP_MACHINE_ID: + OPTION_LONG("setup-machine-id", NULL, "Set a random machine ID"): r = sd_id128_randomize(&arg_machine_id); if (r < 0) return log_error_errno(r, "Failed to generate randomized machine ID: %m"); - break; - case ARG_MACHINE_ID: - r = sd_id128_from_string(optarg, &arg_machine_id); + OPTION_LONG("machine-id", "ID", "Set specified machine ID"): + r = sd_id128_from_string(arg, &arg_machine_id); if (r < 0) - return log_error_errno(r, "Failed to parse machine id %s.", optarg); - + return log_error_errno(r, "Failed to parse machine id %s.", arg); break; - case ARG_ROOT_PASSWORD: - r = free_and_strdup(&arg_root_password, optarg); + OPTION_LONG("root-password", "PASSWORD", "Set root password from plaintext password"): + r = free_and_strdup(&arg_root_password, arg); if (r < 0) return log_oom(); arg_root_password_is_hashed = false; break; - case ARG_ROOT_PASSWORD_FILE: + OPTION_LONG("root-password-file", "FILE", "Set root password from file"): arg_root_password = mfree(arg_root_password); - r = read_one_line_file(optarg, &arg_root_password); + r = read_one_line_file(arg, &arg_root_password); if (r < 0) - return log_error_errno(r, "Failed to read %s: %m", optarg); + return log_error_errno(r, "Failed to read %s: %m", arg); arg_root_password_is_hashed = false; break; - case ARG_ROOT_PASSWORD_HASHED: - r = free_and_strdup(&arg_root_password, optarg); + OPTION_LONG("root-password-hashed", "HASH", "Set root password from hashed password"): + r = free_and_strdup(&arg_root_password, arg); if (r < 0) return log_oom(); arg_root_password_is_hashed = true; break; - case ARG_ROOT_SHELL: - r = free_and_strdup(&arg_root_shell, optarg); + OPTION_LONG("root-shell", "SHELL", "Set root shell"): + r = free_and_strdup(&arg_root_shell, arg); if (r < 0) return log_oom(); - break; - case ARG_KERNEL_COMMAND_LINE: - r = free_and_strdup(&arg_kernel_cmdline, optarg); + OPTION_LONG("kernel-command-line", "CMDLINE", "Set kernel command line"): + r = free_and_strdup(&arg_kernel_cmdline, arg); if (r < 0) return log_oom(); - break; - case ARG_PROMPT_LOCALE: + OPTION_LONG("prompt-locale", NULL, "Prompt the user for locale settings"): arg_prompt_locale = true; break; - case ARG_PROMPT_KEYMAP: + OPTION_LONG("prompt-keymap", NULL, "Prompt the user for keymap settings"): arg_prompt_keymap = true; arg_prompt_keymap_auto = false; break; - case ARG_PROMPT_KEYMAP_AUTO: + OPTION_LONG("prompt-keymap-auto", NULL, + "Prompt the user for keymap settings if invoked on local console"): arg_prompt_keymap_auto = true; break; - case ARG_PROMPT_TIMEZONE: + OPTION_LONG("prompt-timezone", NULL, "Prompt the user for timezone"): arg_prompt_timezone = true; break; - case ARG_PROMPT_HOSTNAME: + OPTION_LONG("prompt-hostname", NULL, "Prompt the user for hostname"): arg_prompt_hostname = true; break; - case ARG_PROMPT_ROOT_PASSWORD: + OPTION_LONG("prompt-root-password", NULL, "Prompt the user for root password"): arg_prompt_root_password = true; break; - case ARG_PROMPT_ROOT_SHELL: + OPTION_LONG("prompt-root-shell", NULL, "Prompt the user for root shell"): arg_prompt_root_shell = true; break; - case ARG_PROMPT: + OPTION_LONG("prompt", NULL, "Prompt for all of the above"): arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = arg_prompt_root_shell = true; arg_prompt_keymap_auto = false; break; - case ARG_COPY_LOCALE: + OPTION_LONG("copy-locale", NULL, "Copy locale from host"): arg_copy_locale = true; break; - case ARG_COPY_KEYMAP: + OPTION_LONG("copy-keymap", NULL, "Copy keymap from host"): arg_copy_keymap = true; break; - case ARG_COPY_TIMEZONE: + OPTION_LONG("copy-timezone", NULL, "Copy timezone from host"): arg_copy_timezone = true; break; - case ARG_COPY_ROOT_PASSWORD: + OPTION_LONG("copy-root-password", NULL, "Copy root password from host"): arg_copy_root_password = true; break; - case ARG_COPY_ROOT_SHELL: + OPTION_LONG("copy-root-shell", NULL, "Copy root shell from host"): arg_copy_root_shell = true; break; - case ARG_COPY: + OPTION_LONG("copy", NULL, "Copy all of the above"): arg_copy_locale = arg_copy_keymap = arg_copy_timezone = arg_copy_root_password = arg_copy_root_shell = true; break; - case ARG_FORCE: + OPTION_LONG("force", NULL, "Overwrite existing files"): arg_force = true; break; - case ARG_DELETE_ROOT_PASSWORD: + OPTION_LONG("delete-root-password", NULL, "Delete root password"): arg_delete_root_password = true; break; - case ARG_WELCOME: - r = parse_boolean(optarg); + OPTION_LONG("welcome", "BOOL", "Whether to show the welcome text"): + r = parse_boolean(arg); if (r < 0) - return log_error_errno(r, "Failed to parse --welcome= argument: %s", optarg); + return log_error_errno(r, "Failed to parse --welcome= argument: %s", arg); arg_welcome = r; break; - case ARG_CHROME: - r = parse_boolean_argument("--chrome=", optarg, &arg_chrome); + OPTION_LONG("chrome", "BOOL", + "Whether to show a color bar at top and bottom of terminal"): + r = parse_boolean_argument("--chrome=", arg, &arg_chrome); if (r < 0) return r; - break; - case ARG_MUTE_CONSOLE: - r = parse_boolean_argument("--mute-console=", optarg, &arg_mute_console); + OPTION_LONG("mute-console", "BOOL", + "Whether to disallow kernel/PID 1 writes to the console while running"): + r = parse_boolean_argument("--mute-console=", arg, &arg_mute_console); if (r < 0) return r; - break; - case ARG_RESET: + OPTION_LONG("reset", NULL, "Remove existing files"): arg_reset = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_delete_root_password && (arg_copy_root_password || arg_root_password || arg_prompt_root_password)) From 30863f491b2833599dc8773d2b5f83f92eb3cea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 14 Apr 2026 16:31:15 +0200 Subject: [PATCH 1033/1296] firstboot: use free_and_strdup_warn in parse_argv Co-developed-by: Claude Opus 4.6 --- src/firstboot/firstboot.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 3cfe6ed7f2523..22c6a42eb9701 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -1302,15 +1302,15 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION_LONG("locale", "LOCALE", "Set primary locale (LANG=)"): - r = free_and_strdup(&arg_locale, arg); + r = free_and_strdup_warn(&arg_locale, arg); if (r < 0) - return log_oom(); + return r; break; OPTION_LONG("locale-messages", "LOCALE", "Set message locale (LC_MESSAGES=)"): - r = free_and_strdup(&arg_locale_messages, arg); + r = free_and_strdup_warn(&arg_locale_messages, arg); if (r < 0) - return log_oom(); + return r; break; OPTION_LONG("keymap", "KEYMAP", "Set keymap"): @@ -1318,9 +1318,9 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Keymap %s is not valid.", arg); - r = free_and_strdup(&arg_keymap, arg); + r = free_and_strdup_warn(&arg_keymap, arg); if (r < 0) - return log_oom(); + return r; break; OPTION_LONG("timezone", "TIMEZONE", "Set timezone"): @@ -1328,9 +1328,9 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Timezone %s is not valid.", arg); - r = free_and_strdup(&arg_timezone, arg); + r = free_and_strdup_warn(&arg_timezone, arg); if (r < 0) - return log_oom(); + return r; break; OPTION_LONG("hostname", "NAME", "Set hostname"): @@ -1338,9 +1338,9 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Host name %s is not valid.", arg); - r = free_and_strdup(&arg_hostname, arg); + r = free_and_strdup_warn(&arg_hostname, arg); if (r < 0) - return log_oom(); + return r; hostname_cleanup(arg_hostname); break; @@ -1358,9 +1358,9 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION_LONG("root-password", "PASSWORD", "Set root password from plaintext password"): - r = free_and_strdup(&arg_root_password, arg); + r = free_and_strdup_warn(&arg_root_password, arg); if (r < 0) - return log_oom(); + return r; arg_root_password_is_hashed = false; break; @@ -1376,23 +1376,23 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION_LONG("root-password-hashed", "HASH", "Set root password from hashed password"): - r = free_and_strdup(&arg_root_password, arg); + r = free_and_strdup_warn(&arg_root_password, arg); if (r < 0) - return log_oom(); + return r; arg_root_password_is_hashed = true; break; OPTION_LONG("root-shell", "SHELL", "Set root shell"): - r = free_and_strdup(&arg_root_shell, arg); + r = free_and_strdup_warn(&arg_root_shell, arg); if (r < 0) - return log_oom(); + return r; break; OPTION_LONG("kernel-command-line", "CMDLINE", "Set kernel command line"): - r = free_and_strdup(&arg_kernel_cmdline, arg); + r = free_and_strdup_warn(&arg_kernel_cmdline, arg); if (r < 0) - return log_oom(); + return r; break; OPTION_LONG("prompt-locale", NULL, "Prompt the user for locale settings"): From 086daddfdf12c6bd746159aed011dd2f5c00993b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 09:20:31 +0200 Subject: [PATCH 1034/1296] firstboot: use parse_boolean_argument in one more place This was pointed out in review. --- src/firstboot/firstboot.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 22c6a42eb9701..ea4dd5f184038 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -41,7 +41,6 @@ #include "options.h" #include "os-util.h" #include "parse-argument.h" -#include "parse-util.h" #include "password-quality-util.h" #include "path-util.h" #include "plymouth-util.h" @@ -1465,11 +1464,9 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION_LONG("welcome", "BOOL", "Whether to show the welcome text"): - r = parse_boolean(arg); + r = parse_boolean_argument("--welcome=", arg, &arg_welcome); if (r < 0) - return log_error_errno(r, "Failed to parse --welcome= argument: %s", arg); - - arg_welcome = r; + return r; break; OPTION_LONG("chrome", "BOOL", From b63373a65657ef610622590a00dd5a9452873e9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 31 Mar 2026 13:08:03 +0200 Subject: [PATCH 1035/1296] meson: use a convenience lib for curl-util.c Previously we compiled curl-util.c at least two times, and then also shared it using the extract+object. Let's build a static "convenience lib" for it. (Using extract+object everywhere is not possible because the different places where it is used are conditionalized independently so we don't have a single "source" that is always available.) --- src/imds/meson.build | 3 ++- src/import/meson.build | 3 ++- src/shared/meson.build | 18 +++++++++++++++--- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/imds/meson.build b/src/imds/meson.build index cb919fec615d8..29fa878eaba43 100644 --- a/src/imds/meson.build +++ b/src/imds/meson.build @@ -11,7 +11,8 @@ executables += [ 'sources' : files( 'imdsd.c', 'imds-util.c' - ) + curl_util_c, + ), + 'link_with' : [libcurlutil_static, libshared], 'dependencies' : [libcurl], }, libexec_template + { diff --git a/src/import/meson.build b/src/import/meson.build index 1fab9afd0b005..13c90d7937fed 100644 --- a/src/import/meson.build +++ b/src/import/meson.build @@ -33,8 +33,9 @@ executables += [ 'pull-oci.c', 'pull-raw.c', 'pull-tar.c', - ) + curl_util_c, + ), 'objects' : ['systemd-importd'], + 'link_with' : [libcurlutil_static, libshared], 'dependencies' : common_deps + [ libopenssl, ], diff --git a/src/shared/meson.build b/src/shared/meson.build index efa0dc05adf6a..8fb14f9fef69c 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -256,9 +256,6 @@ if get_option('tests') != 'false' shared_sources += files('tests.c') endif -# A small shared file that is is linked into a few places -curl_util_c = files('curl-util.c') - syscall_list_inc = custom_target( input : syscall_list_txt, output : 'syscall-list.inc', @@ -458,3 +455,18 @@ libshared_fdisk = static_library( userspace], c_args : ['-fvisibility=default'], build_by_default : false) + +# A small shared file that is linked into a few places. +# It is not part of libshared because this code needs libcurl and +# we don't want to link libshared to libcurl. +if conf.get('HAVE_LIBCURL') == 1 + libcurlutil_static = static_library( + 'curl-util', + 'curl-util.c', + implicit_include_directories : false, + dependencies : [userspace, libcurl], + include_directories : includes, + build_by_default : false) +else + libcurlutil_static = [] +endif From 93ee4308d99847f67f638f314a186b8f15bf08dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 7 Apr 2026 13:25:08 +0200 Subject: [PATCH 1036/1296] journal-upload: require TLS 1.2 as the minimum version RFC 8996 says: > This document formally deprecates Transport Layer Security (TLS) > versions 1.0 (RFC 2246) and 1.1 (RFC 4346). Accordingly, those > documents have been moved to Historic status. These versions lack > support for current and recommended cryptographic algorithms and > mechanisms, and various government and industry profiles of > applications using TLS now mandate avoiding these old TLS versions. > TLS version 1.2 became the recommended version for IETF protocols in > 2008 (subsequently being obsoleted by TLS version 1.3 in 2018), > providing sufficient time to transition away from older versions. > Removing support for older versions from implementations reduces the > attack surface, reduces opportunity for misconfiguration, and > streamlines library and product maintenance. This code probably only talks to our own receiver which uses libmicrohttpd. That in turn delegates to GnuTLS, which supports 1.2, 1.3, 3.0, etc. --- src/journal-remote/journal-upload.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c index 88f5cf713985a..99de0fc93f57f 100644 --- a/src/journal-remote/journal-upload.c +++ b/src/journal-remote/journal-upload.c @@ -302,8 +302,8 @@ int start_upload(Uploader *u, return -EXFULL; } - if (arg_key || arg_trust) - (void) easy_setopt(curl, LOG_WARNING, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1); + if (startswith(u->url, "https://")) + (void) easy_setopt(curl, LOG_WARNING, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); u->easy = TAKE_PTR(curl); } else { From 2d2b4cc009f65082dfd28ac5b2a8be1ec68a06aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 7 Apr 2026 13:32:33 +0200 Subject: [PATCH 1037/1296] shared/facts: use SD_JSON_BUILD_PAIR_VARIANT in one more place Suggested in review by Claude. --- src/shared/facts.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/facts.c b/src/shared/facts.c index 7554126e2b808..fa16c7b7cb8ff 100644 --- a/src/shared/facts.c +++ b/src/shared/facts.c @@ -123,7 +123,7 @@ static int fact_build_send(FactFamilyContext *context, const char *object, sd_js return sd_varlink_replybo(context->link, SD_JSON_BUILD_PAIR_STRING("name", context->fact_family->name), JSON_BUILD_PAIR_STRING_NON_EMPTY("object", object), - SD_JSON_BUILD_PAIR("value", SD_JSON_BUILD_VARIANT(value))); + SD_JSON_BUILD_PAIR_VARIANT("value", value)); } int fact_build_send_string(FactFamilyContext *context, const char *object, const char *value) { From 267b16f33c5636617927f15d7ae6b945c862a587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 14 Apr 2026 08:28:28 +0200 Subject: [PATCH 1038/1296] basic/iovec-wrapper: drop unused code All non-test users iovec_wrapper define the struct as a field in a bigger structure, so we never free it individually. Let's simplify the code and assume it is never null. --- src/basic/iovec-wrapper.c | 23 +---------------------- src/basic/iovec-wrapper.h | 6 ------ src/test/test-log.c | 7 +++---- 3 files changed, 4 insertions(+), 32 deletions(-) diff --git a/src/basic/iovec-wrapper.c b/src/basic/iovec-wrapper.c index b4519f808d5e9..7f91f5e893b8a 100644 --- a/src/basic/iovec-wrapper.c +++ b/src/basic/iovec-wrapper.c @@ -7,10 +7,6 @@ #include "iovec-wrapper.h" #include "string-util.h" -struct iovec_wrapper *iovw_new(void) { - return new0(struct iovec_wrapper, 1); -} - void iovw_done(struct iovec_wrapper *iovw) { assert(iovw); @@ -27,22 +23,6 @@ void iovw_done_free(struct iovec_wrapper *iovw) { iovw_done(iovw); } -struct iovec_wrapper *iovw_free_free(struct iovec_wrapper *iovw) { - if (!iovw) - return NULL; - - iovw_done_free(iovw); - return mfree(iovw); -} - -struct iovec_wrapper *iovw_free(struct iovec_wrapper *iovw) { - if (!iovw) - return NULL; - - iovw_done(iovw); - return mfree(iovw); -} - int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len) { assert(iovw); @@ -118,8 +98,7 @@ void iovw_rebase(struct iovec_wrapper *iovw, void *old, void *new) { } size_t iovw_size(const struct iovec_wrapper *iovw) { - if (!iovw) - return 0; + assert(iovw); return iovec_total_size(iovw->iovec, iovw->count); } diff --git a/src/basic/iovec-wrapper.h b/src/basic/iovec-wrapper.h index 94b39feb15250..bfe739ecf1ed9 100644 --- a/src/basic/iovec-wrapper.h +++ b/src/basic/iovec-wrapper.h @@ -8,12 +8,6 @@ struct iovec_wrapper { size_t count; }; -struct iovec_wrapper *iovw_new(void); -struct iovec_wrapper *iovw_free(struct iovec_wrapper *iovw); -struct iovec_wrapper *iovw_free_free(struct iovec_wrapper *iovw); - -DEFINE_TRIVIAL_CLEANUP_FUNC(struct iovec_wrapper*, iovw_free_free); - void iovw_done_free(struct iovec_wrapper *iovw); void iovw_done(struct iovec_wrapper *iovw); diff --git a/src/test/test-log.c b/src/test/test-log.c index f77ed205a7086..a62f54006e20f 100644 --- a/src/test/test-log.c +++ b/src/test/test-log.c @@ -261,13 +261,12 @@ static void test_log_context(void) { IOVEC_MAKE_STRING("ABC=def"), IOVEC_MAKE_STRING("GHI=jkl"), }; - _cleanup_free_ struct iovec_wrapper *iovw = NULL; - ASSERT_NOT_NULL(iovw = iovw_new()); - ASSERT_OK(iovw_consume(iovw, strdup("MNO=pqr"), STRLEN("MNO=pqr") + 1)); + struct iovec_wrapper iovw = {}; + ASSERT_OK(iovw_consume(&iovw, strdup("MNO=pqr"), STRLEN("MNO=pqr") + 1)); LOG_CONTEXT_PUSH_IOV(iov, ELEMENTSOF(iov)); LOG_CONTEXT_PUSH_IOV(iov, ELEMENTSOF(iov)); - LOG_CONTEXT_CONSUME_IOV(iovw->iovec, iovw->count); + LOG_CONTEXT_CONSUME_IOV(iovw.iovec, iovw.count); LOG_CONTEXT_PUSH("STU=vwx"); ASSERT_EQ(log_context_num_contexts(), 3U); From b22daa97e1608d865ce76ed72fd6b7bd59ccbf70 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Thu, 16 Apr 2026 11:59:36 +0200 Subject: [PATCH 1039/1296] string-util: check for overflow in strrep() This simply mirrors the same overflow check we already have in strrepa(), in case someone passed us a sufficiently long string. strrep() is currently used only in tests, so this is just hardening. --- src/basic/string-util.c | 17 +++++++++++------ src/basic/string-util.h | 2 +- src/test/test-string-util.c | 2 ++ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/basic/string-util.c b/src/basic/string-util.c index bd79dbfd24730..9b63516ce0908 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -977,22 +977,27 @@ int strextendf_with_separator(char **x, const char *separator, const char *forma return -ENOMEM; } -char* strrep(const char *s, unsigned n) { - char *r, *p; +char* strrep(const char *s, size_t n) { + char *ret, *p; size_t l; assert(s); l = strlen(s); - p = r = malloc(l * n + 1); - if (!r) + if (!MUL_ASSIGN_SAFE(&l, n)) + return NULL; + if (!INC_SAFE(&l, 1)) + return NULL; + + p = ret = malloc(l); + if (!ret) return NULL; - for (unsigned i = 0; i < n; i++) + for (size_t i = 0; i < n; i++) p = stpcpy(p, s); *p = 0; - return r; + return ret; } int split_pair(const char *s, const char *sep, char **ret_first, char **ret_second) { diff --git a/src/basic/string-util.h b/src/basic/string-util.h index 0143c37a656e2..5ab4dd9016dd2 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -193,7 +193,7 @@ int strextendf_with_separator(char **x, const char *separator, const char *forma }) #define strprepend(x, ...) strprepend_with_separator(x, NULL, __VA_ARGS__) -char* strrep(const char *s, unsigned n); +char* strrep(const char *s, size_t n); #define strrepa(s, n) \ ({ \ diff --git a/src/test/test-string-util.c b/src/test/test-string-util.c index 4f12ec710c11e..094983a1724b8 100644 --- a/src/test/test-string-util.c +++ b/src/test/test-string-util.c @@ -325,6 +325,8 @@ TEST(strrep) { ASSERT_STREQ(onea, "waldo"); ASSERT_STREQ(threea, "waldowaldowaldo"); + + ASSERT_NULL(strrep("waldo", SIZE_MAX - 1)); } TEST(string_has_cc) { From 143af68ee15607aced66e9f5aba55899b3f4e505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 18:32:13 +0200 Subject: [PATCH 1040/1296] oomctl: convert to the new option and verb parsers --help is the same, except for whitespace and common option descriptions. Co-developed-by: Claude Opus 4.6 --- src/oom/oomctl.c | 101 ++++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 53 deletions(-) diff --git a/src/oom/oomctl.c b/src/oom/oomctl.c index ed394e42d8c48..703b9c3f0eed2 100644 --- a/src/oom/oomctl.c +++ b/src/oom/oomctl.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-bus.h" #include "alloc-util.h" @@ -9,8 +7,10 @@ #include "bus-error.h" #include "bus-locator.h" #include "bus-message-util.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pager.h" #include "pretty-print.h" #include "verbs.h" @@ -19,6 +19,7 @@ static PagerFlags arg_pager_flags = 0; static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; pager_open(arg_pager_flags); @@ -27,29 +28,45 @@ static int help(void) { if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%2$sManage or inspect the userspace OOM killer.%3$s\n" - "\n%4$sCommands:%5$s\n" - " dump Output the current state of systemd-oomd\n" - "\n%4$sOptions:%5$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - "\nSee the %6$s for details.\n", + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] COMMAND ...\n\n" + "%sManage or inspect the userspace OOM killer.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, ansi_highlight(), ansi_normal(), ansi_underline(), - ansi_normal(), - link); + ansi_normal()); + + r = table_print_or_warn(verbs); + if (r < 0) + return r; + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} +VERB_COMMON_HELP_HIDDEN(help); +VERB(verb_dump_state, "dump", NULL, VERB_ANY, 1, VERB_DEFAULT, + "Output the current state of systemd-oomd"); static int verb_dump_state(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; @@ -69,64 +86,42 @@ static int verb_dump_state(int argc, char *argv[], uintptr_t _data, void *userda return bus_message_dump_fd(reply); } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - {} - }; - - int c; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - return help(); + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: - return version(); + OPTION_COMMON_VERSION: + return version(); - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; } + *ret_args = option_parser_get_args(&state); return 1; } static int run(int argc, char* argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "dump", VERB_ANY, 1, VERB_DEFAULT, verb_dump_state }, - {} - }; - int r; log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); From aeee8b952700c86f7af388cccb37407d8a4db5ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 18:33:32 +0200 Subject: [PATCH 1041/1296] oomd: convert to the new option parser --help output is idential except for indentation. Co-developed-by: Claude Opus 4.6 --- src/oom/oomd.c | 64 ++++++++++++++++++++------------------------------ 1 file changed, 25 insertions(+), 39 deletions(-) diff --git a/src/oom/oomd.c b/src/oom/oomd.c index b1d3efb8f5733..54a3bb7a1d562 100644 --- a/src/oom/oomd.c +++ b/src/oom/oomd.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-event.h" #include "alloc-util.h" @@ -11,11 +9,13 @@ #include "cgroup-util.h" #include "daemon-util.h" #include "fileio.h" +#include "format-table.h" #include "log.h" #include "main-func.h" #include "oomd-conf.h" #include "oomd-manager.h" #include "oomd-manager-bus.h" +#include "options.h" #include "parse-util.h" #include "pretty-print.h" #include "psi-util.h" @@ -24,74 +24,60 @@ static bool arg_dry_run = false; static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-oomd", "8", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...]\n\n" - "Run the userspace out-of-memory (OOM) killer.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --dry-run Only print destructive actions instead of doing them\n" - " --bus-introspect=PATH Write D-Bus XML introspection data\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - link); + "Run the userspace out-of-memory (OOM) killer.\n\n", + program_invocation_short_name); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_DRY_RUN, - ARG_BUS_INTROSPECT, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "dry-run", no_argument, NULL, ARG_DRY_RUN }, - { "bus-introspect", required_argument, NULL, ARG_BUS_INTROSPECT }, - {} - }; - - int c; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_DRY_RUN: + OPTION_LONG("dry-run", NULL, + "Only print destructive actions instead of doing them"): arg_dry_run = true; break; - case ARG_BUS_INTROSPECT: + OPTION_LONG("bus-introspect", "PATH", + "Write D-Bus XML introspection data"): return bus_introspect_implementations( stdout, - optarg, + arg, BUS_IMPLEMENTATIONS(&manager_object, &log_control_object)); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind < argc) + if (option_parser_get_n_args(&state) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); From 33a9e2dfcca92010ed1423ef1136e5161cc55eb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 18:34:53 +0200 Subject: [PATCH 1042/1296] path-tool: convert to the new option parser --help output is identical except for indentation and common option strings. Co-developed-by: Claude Opus 4.6 --- src/path/path-tool.c | 74 +++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 43 deletions(-) diff --git a/src/path/path-tool.c b/src/path/path-tool.c index 62eade3b05dfa..797a7d06af895 100644 --- a/src/path/path-tool.c +++ b/src/path/path-tool.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-path.h" @@ -8,12 +7,15 @@ #include "alloc-util.h" #include "build.h" #include "errno-util.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pager.h" #include "pretty-print.h" #include "sort-util.h" #include "string-util.h" +#include "strv.h" static const char *arg_suffix = NULL; static PagerFlags arg_pager_flags = 0; @@ -172,72 +174,57 @@ static int print_path(const char *n) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-path", "1", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...] [NAME...]\n" - "\n%sShow system and user paths.%s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --suffix=SUFFIX Suffix to append to paths\n" - " --no-pager Do not pipe output into a pager\n" - "\nSee the %s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] [NAME...]\n\n" + "%sShow system and user paths.%s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_SUFFIX, - ARG_NO_PAGER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "suffix", required_argument, NULL, ARG_SUFFIX }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - {} - }; - - int c; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_SUFFIX: - arg_suffix = optarg; + OPTION_LONG("suffix", "SUFFIX", "Suffix to append to paths"): + arg_suffix = arg; break; - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state); return 1; } @@ -246,15 +233,16 @@ static int run(int argc, char* argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - if (optind >= argc) + if (strv_isempty(args)) return list_paths(); - for (int i = optind; i < argc; i++) - RET_GATHER(r, print_path(argv[i])); + STRV_FOREACH(i, args) + RET_GATHER(r, print_path(*i)); return r; } From 8d803844ae6af020ecdce8abf604cef6e0615e7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 18:47:33 +0200 Subject: [PATCH 1043/1296] pcrextend: convert to the new option parser --help is identical except for indentation and common option strings. Co-developed-by: Claude Opus 4.6 --- src/pcrextend/pcrextend.c | 164 ++++++++++++++++---------------------- 1 file changed, 67 insertions(+), 97 deletions(-) diff --git a/src/pcrextend/pcrextend.c b/src/pcrextend/pcrextend.c index e35f579e63738..639331ba97dc8 100644 --- a/src/pcrextend/pcrextend.c +++ b/src/pcrextend/pcrextend.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-json.h" #include "sd-messages.h" #include "sd-varlink.h" @@ -10,8 +8,10 @@ #include "build.h" #include "efi-loader.h" #include "escape.h" +#include "format-table.h" #include "json-util.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "pcrextend-util.h" #include "pretty-print.h" @@ -42,95 +42,63 @@ STATIC_DESTRUCTOR_REGISTER(arg_nvpcr_name, freep); #define EXTENSION_STRING_SAFE_LIMIT 1024 -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-pcrextend", "8", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%1$s [OPTIONS...] WORD\n" "%1$s [OPTIONS...] --file-system=PATH\n" "%1$s [OPTIONS...] --machine-id\n" "%1$s [OPTIONS...] --product-id\n" - "\n%5$sExtend a TPM2 PCR with boot phase, machine ID, or file system ID.%6$s\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version\n" - " --bank=DIGEST Select TPM PCR bank (SHA1, SHA256)\n" - " --pcr=INDEX Select TPM PCR index (0…23)\n" - " --nvpcr=NAME Select TPM PCR mode nvindex name\n" - " --tpm2-device=PATH Use specified TPM2 device\n" - " --graceful Exit gracefully if no TPM2 device is found\n" - " --file-system=PATH Measure UUID/labels of file system into PCR 15\n" - " --machine-id Measure machine ID into PCR 15\n" - " --product-id Measure SMBIOS product ID into NvPCR 'hardware'\n" - " --early Run in early boot mode, without access to /var/\n" - " --event-type=TYPE Event type to include in the event log\n" - "\nSee the %2$s for details.\n", + "\n%2$sExtend a TPM2 PCR with boot phase, machine ID, or file system ID.%3$s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), ansi_normal()); - return 0; -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_BANK, - ARG_PCR, - ARG_NVPCR, - ARG_TPM2_DEVICE, - ARG_GRACEFUL, - ARG_FILE_SYSTEM, - ARG_MACHINE_ID, - ARG_PRODUCT_ID, - ARG_EARLY, - ARG_EVENT_TYPE, - }; + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "bank", required_argument, NULL, ARG_BANK }, - { "pcr", required_argument, NULL, ARG_PCR }, - { "nvpcr", required_argument, NULL, ARG_NVPCR }, - { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, - { "graceful", no_argument, NULL, ARG_GRACEFUL }, - { "file-system", required_argument, NULL, ARG_FILE_SYSTEM }, - { "machine-id", no_argument, NULL, ARG_MACHINE_ID }, - { "product-id", no_argument, NULL, ARG_PRODUCT_ID }, - { "early", no_argument, NULL, ARG_EARLY }, - { "event-type", required_argument, NULL, ARG_EVENT_TYPE }, - {} - }; + r = table_print_or_warn(options); + if (r < 0) + return r; - int c, r; + printf("\nSee the %s for details.\n", link); + return 0; +} +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - help(0, NULL, NULL); - return 0; + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_BANK: { + OPTION_LONG("bank", "DIGEST", "Select TPM PCR bank (SHA1, SHA256)"): { const EVP_MD *implementation; - implementation = EVP_get_digestbyname(optarg); + implementation = EVP_get_digestbyname(arg); if (!implementation) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", arg); if (strv_extend(&arg_banks, EVP_MD_name(implementation)) < 0) return log_oom(); @@ -138,36 +106,36 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_PCR: - if (isempty(optarg)) { + OPTION_LONG("pcr", "INDEX", "Select TPM PCR index (0…23)"): + if (isempty(arg)) { arg_pcr_mask = 0; break; } - r = tpm2_pcr_index_from_string(optarg); + r = tpm2_pcr_index_from_string(arg); if (r < 0) - return log_error_errno(r, "Failed to parse PCR index: %s", optarg); + return log_error_errno(r, "Failed to parse PCR index: %s", arg); arg_pcr_mask |= INDEX_TO_MASK(uint32_t, r); break; - case ARG_NVPCR: - if (!tpm2_nvpcr_name_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "NvPCR name is not valid: %s", optarg); + OPTION_LONG("nvpcr", "NAME", "Select TPM PCR mode nvindex name"): + if (!tpm2_nvpcr_name_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "NvPCR name is not valid: %s", arg); - r = free_and_strdup_warn(&arg_nvpcr_name, optarg); + r = free_and_strdup_warn(&arg_nvpcr_name, arg); if (r < 0) return r; break; - case ARG_TPM2_DEVICE: { + OPTION_LONG("tpm2-device", "PATH", "Use specified TPM2 device"): { _cleanup_free_ char *device = NULL; - if (streq(optarg, "list")) + if (streq(arg, "list")) return tpm2_list_devices(/* legend= */ true, /* quiet= */ false); - if (!streq(optarg, "auto")) { - device = strdup(optarg); + if (!streq(arg, "auto")) { + device = strdup(arg); if (!device) return log_oom(); } @@ -176,43 +144,41 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_GRACEFUL: + OPTION_LONG("graceful", NULL, + "Exit gracefully if no TPM2 device is found"): arg_graceful = true; break; - case ARG_FILE_SYSTEM: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_file_system); + OPTION_LONG("file-system", "PATH", + "Measure UUID/labels of file system into PCR 15"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_file_system); if (r < 0) return r; - break; - case ARG_MACHINE_ID: + OPTION_LONG("machine-id", NULL, "Measure machine ID into PCR 15"): arg_machine_id = true; break; - case ARG_PRODUCT_ID: + OPTION_LONG("product-id", NULL, + "Measure SMBIOS product ID into NvPCR 'hardware'"): arg_product_id = true; break; - case ARG_EARLY: + OPTION_LONG("early", NULL, + "Run in early boot mode, without access to /var/"): arg_early = true; break; - case ARG_EVENT_TYPE: - if (streq(optarg, "help")) + OPTION_LONG("event-type", "TYPE", + "Event type to include in the event log"): + if (streq(arg, "help")) return DUMP_STRING_TABLE(tpm2_userspace_event_type, Tpm2UserspaceEventType, _TPM2_USERSPACE_EVENT_TYPE_MAX); - arg_event_type = tpm2_userspace_event_type_from_string(optarg); + arg_event_type = tpm2_userspace_event_type_from_string(arg); if (arg_event_type < 0) - return log_error_errno(arg_event_type, "Failed to parse --event-type= argument: %s", optarg); + return log_error_errno(arg_event_type, "Failed to parse --event-type= argument: %s", arg); break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (!!arg_file_system + arg_machine_id + arg_product_id > 1) @@ -237,6 +203,7 @@ static int parse_argv(int argc, char *argv[]) { return r; } + *ret_args = option_parser_get_args(&state); return 1; } @@ -482,15 +449,18 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; + size_t n_args = strv_length(args); + if (arg_varlink) return vl_server(); /* Invocation as Varlink service */ if (arg_file_system) { - if (optind != argc) + if (n_args != 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument."); r = pcrextend_file_system_word(arg_file_system, &word, NULL); @@ -501,7 +471,7 @@ static int run(int argc, char *argv[]) { } else if (arg_machine_id) { - if (optind != argc) + if (n_args != 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument."); r = pcrextend_machine_id_word(&word); @@ -512,7 +482,7 @@ static int run(int argc, char *argv[]) { } else if (arg_product_id) { - if (optind != argc) + if (n_args != 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument."); r = pcrextend_product_id_word(&word); @@ -521,10 +491,10 @@ static int run(int argc, char *argv[]) { event = TPM2_EVENT_PRODUCT_ID; } else { - if (optind+1 != argc) + if (n_args != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single argument."); - word = strdup(argv[optind]); + word = strdup(args[0]); if (!word) return log_oom(); From 011233858520fe607eeff29cfc92d40cf71a6002 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 16 Apr 2026 19:04:43 +0200 Subject: [PATCH 1044/1296] ci: Switch PR review workflow to Opus 4.7 via Mantle endpoint Opus 4.7 is in research preview on Bedrock and the Invoke API rejects the beta headers Claude Code sends ("invalid beta flag"). Enable the Mantle endpoint, which serves Claude via the native Anthropic API shape and accepts those headers, and switch the model ID to the Mantle form (no region prefix or version suffix). --- .github/workflows/claude-review.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 3829313cf97d8..aacada555e8eb 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -192,6 +192,7 @@ jobs: env: CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: "1" CLAUDE_CODE_USE_BEDROCK: "1" + CLAUDE_CODE_USE_MANTLE: "1" run: | mkdir -p ~/.claude @@ -369,7 +370,7 @@ jobs: PROMPT claude \ - --model us.anthropic.claude-opus-4-6-v1 \ + --model anthropic.claude-opus-4-7 \ --effort max \ --max-turns 200 \ --setting-sources user \ From 335cc8f39c75c2b7cdab8c52fe4c378929556f7e Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Thu, 16 Apr 2026 11:13:27 +0200 Subject: [PATCH 1045/1296] timesync: verify the actual size of the received data iov.iov_len doesn't change after calling recvmsg() so it remains set to sizeof(ntpmsg), which makes the check for a short packet always false. Let's fix that by checking the actual size of the received data instead. --- src/timesync/timesyncd-manager.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/timesync/timesyncd-manager.c b/src/timesync/timesyncd-manager.c index 79a9a629c1410..e4f471e2aa5dc 100644 --- a/src/timesync/timesyncd-manager.c +++ b/src/timesync/timesyncd-manager.c @@ -437,7 +437,7 @@ static int manager_receive_response(sd_event_source *source, int fd, uint32_t re } /* Too short packet? */ - if (iov.iov_len < sizeof(struct ntp_msg)) { + if ((size_t) len < sizeof(struct ntp_msg)) { log_warning("Invalid response from server. Disconnecting."); return manager_connect(m); } From f32af5c69ab71176a70c187b380e9689de834fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 13 Apr 2026 21:41:32 +0200 Subject: [PATCH 1046/1296] basic/iovec-wrapper: add iovw_append and iovw_to_cstring Existing iovw_append is renamed to iovw_append_iovw. iovw_consume is made noninline. --- src/basic/iovec-wrapper.c | 53 ++++++++++++++++++++++++++++++- src/basic/iovec-wrapper.h | 15 +++------ src/coredump/coredump-backtrace.c | 2 +- 3 files changed, 57 insertions(+), 13 deletions(-) diff --git a/src/basic/iovec-wrapper.c b/src/basic/iovec-wrapper.c index 7f91f5e893b8a..408985763eaeb 100644 --- a/src/basic/iovec-wrapper.c +++ b/src/basic/iovec-wrapper.c @@ -41,6 +41,28 @@ int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len) { return 0; } +int iovw_append(struct iovec_wrapper *iovw, const void *data, size_t len) { + if (len == 0) + return 0; + + void *c = memdup(data, len); + if (!c) + return -ENOMEM; + + return iovw_put(iovw, c, len); +} + +int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len) { + /* Move data into iovw or free on error */ + int r; + + r = iovw_put(iovw, data, len); + if (r < 0) + free(data); + + return r; +} + int iovw_put_string_field_full(struct iovec_wrapper *iovw, bool replace, const char *field, const char *value) { _cleanup_free_ char *x = NULL; int r; @@ -103,7 +125,7 @@ size_t iovw_size(const struct iovec_wrapper *iovw) { return iovec_total_size(iovw->iovec, iovw->count); } -int iovw_append(struct iovec_wrapper *target, const struct iovec_wrapper *source) { +int iovw_append_iovw(struct iovec_wrapper *target, const struct iovec_wrapper *source) { size_t original_count; int r; @@ -139,3 +161,32 @@ int iovw_append(struct iovec_wrapper *target, const struct iovec_wrapper *source target->count = original_count; return r; } + +char* iovw_to_cstring(const struct iovec_wrapper *iovw) { + size_t size; + char *p, *ans; + + assert(iovw); + + /* Squish a series of iovecs into a C string. Embedded NULs are not allowed. + * The caller is expected to filter them out when populating the data. */ + + size = iovw_size(iovw); + if (size == SIZE_MAX) + return NULL; /* Prevent theoretical overflow */ + size ++; + + p = ans = new(char, size); + if (!ans) + return NULL; + + FOREACH_ARRAY(iovec, iovw->iovec, iovw->count) { + assert(!memchr(iovec->iov_base, 0, iovec->iov_len)); + + p = mempcpy(p, iovec->iov_base, iovec->iov_len); + } + + *p = '\0'; + + return ans; +} diff --git a/src/basic/iovec-wrapper.h b/src/basic/iovec-wrapper.h index bfe739ecf1ed9..6f0c9af43c9b5 100644 --- a/src/basic/iovec-wrapper.h +++ b/src/basic/iovec-wrapper.h @@ -12,16 +12,8 @@ void iovw_done_free(struct iovec_wrapper *iovw); void iovw_done(struct iovec_wrapper *iovw); int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len); -static inline int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len) { - /* Move data into iovw or free on error */ - int r; - - r = iovw_put(iovw, data, len); - if (r < 0) - free(data); - - return r; -} +int iovw_append(struct iovec_wrapper *iovw, const void *data, size_t len); +int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len); static inline bool iovw_isempty(const struct iovec_wrapper *iovw) { return !iovw || iovw->count == 0; @@ -40,4 +32,5 @@ int iovw_put_string_fieldf_full(struct iovec_wrapper *iovw, bool replace, const int iovw_put_string_field_free(struct iovec_wrapper *iovw, const char *field, char *value); void iovw_rebase(struct iovec_wrapper *iovw, void *old, void *new); size_t iovw_size(const struct iovec_wrapper *iovw); -int iovw_append(struct iovec_wrapper *target, const struct iovec_wrapper *source); +int iovw_append_iovw(struct iovec_wrapper *target, const struct iovec_wrapper *source); +char* iovw_to_cstring(const struct iovec_wrapper *iovw); diff --git a/src/coredump/coredump-backtrace.c b/src/coredump/coredump-backtrace.c index 0a2dec2313632..b8aa880f8e440 100644 --- a/src/coredump/coredump-backtrace.c +++ b/src/coredump/coredump-backtrace.c @@ -50,7 +50,7 @@ int coredump_backtrace(int argc, char *argv[]) { } else { /* The imported iovecs are not supposed to be freed by us so let's copy and merge them at the * end of the array. */ - r = iovw_append(&context.iovw, &importer.iovw); + r = iovw_append_iovw(&context.iovw, &importer.iovw); if (r < 0) return r; } From 5adccf5034a95caaa259908ad4759d52751967ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 15 Apr 2026 00:48:31 +0200 Subject: [PATCH 1047/1296] test: add test-iovec-wrapper Tests the old code in iovec-wrapper and the two new functions. Co-developed-by: Claude Opus 4.6 --- src/test/meson.build | 1 + src/test/test-iovec-wrapper.c | 245 ++++++++++++++++++++++++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 src/test/test-iovec-wrapper.c diff --git a/src/test/meson.build b/src/test/meson.build index fb0aac27f8864..0963924492847 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -130,6 +130,7 @@ simple_tests += files( 'test-install-root.c', 'test-io-util.c', 'test-iovec-util.c', + 'test-iovec-wrapper.c', 'test-journal-importer.c', 'test-kbd-util.c', 'test-label.c', diff --git a/src/test/test-iovec-wrapper.c b/src/test/test-iovec-wrapper.c new file mode 100644 index 0000000000000..35b939657226b --- /dev/null +++ b/src/test/test-iovec-wrapper.c @@ -0,0 +1,245 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "alloc-util.h" +#include "iovec-wrapper.h" +#include "tests.h" + +TEST(iovw_put) { + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + + /* Zero-length insertions are no-ops and do not touch the data pointer */ + ASSERT_OK_ZERO(iovw_put(&iovw, NULL, 0)); + ASSERT_OK_ZERO(iovw_put(&iovw, (char*) "foo", 0)); + ASSERT_EQ(iovw.count, 0U); + + ASSERT_OK(iovw_put(&iovw, (char*) "foo", 3)); + ASSERT_OK(iovw_put(&iovw, (char*) "barbar", 6)); + ASSERT_OK(iovw_put(&iovw, (char*) "q", 1)); + ASSERT_EQ(iovw.count, 3U); + + ASSERT_EQ(iovw.iovec[0].iov_len, 3U); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "foo", 3), 0); + ASSERT_EQ(iovw.iovec[1].iov_len, 6U); + ASSERT_EQ(memcmp(iovw.iovec[1].iov_base, "barbar", 6), 0); + ASSERT_EQ(iovw.iovec[2].iov_len, 1U); + ASSERT_EQ(memcmp(iovw.iovec[2].iov_base, "q", 1), 0); +} + +TEST(iovw_append) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + + /* iovw_append copies the data; the wrapper owns the copies. */ + char buf[4] = { 'o', 'n', 'e', '\0' }; + ASSERT_OK(iovw_append(&iovw, buf, 3)); + ASSERT_EQ(iovw.count, 1U); + ASSERT_EQ(iovw.iovec[0].iov_len, 3U); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "one", 3), 0); + + /* Insert with a NUL */ + ASSERT_OK_ZERO(iovw_append(&iovw, buf, 4)); + ASSERT_EQ(iovw.count, 2U); + ASSERT_EQ(iovw.iovec[1].iov_len, 4U); + ASSERT_EQ(memcmp(iovw.iovec[1].iov_base, "one\0", 4), 0); + + /* Mutating the caller's buffer does not affect what's stored */ + memset(buf, 'X', sizeof buf); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "one", 3), 0); +} + +TEST(iovw_consume) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + + char *p = strdup("consumed"); + ASSERT_NOT_NULL(p); + ASSERT_OK(iovw_consume(&iovw, p, strlen(p))); + ASSERT_EQ(iovw.count, 1U); + /* iovw_consume moves ownership in place, no copy */ + ASSERT_PTR_EQ(iovw.iovec[0].iov_base, p); + + /* Zero-length: iovw_put returns 0 without adding anything, and does not free the payload. + * Confirm by strdup'ing something and explicitly freeing it afterwards. */ + _cleanup_free_ char *q = strdup(""); + ASSERT_NOT_NULL(q); + ASSERT_OK_ZERO(iovw_consume(&iovw, q, 0)); + ASSERT_EQ(iovw.count, 1U); +} + +TEST(iovw_isempty) { + ASSERT_TRUE(iovw_isempty(NULL)); + + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + ASSERT_TRUE(iovw_isempty(&iovw)); + + ASSERT_OK(iovw_put(&iovw, (char*) "x", 1)); + ASSERT_FALSE(iovw_isempty(&iovw)); +} + +TEST(iovw_put_string_field) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + + ASSERT_OK(iovw_put_string_field(&iovw, "FOO=", "bar")); + ASSERT_OK(iovw_put_string_field(&iovw, "BAZ=", "quux")); + ASSERT_EQ(iovw.count, 2U); + + ASSERT_EQ(iovw.iovec[0].iov_len, strlen("FOO=bar")); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "FOO=bar", strlen("FOO=bar")), 0); + ASSERT_EQ(iovw.iovec[1].iov_len, strlen("BAZ=quux")); + ASSERT_EQ(memcmp(iovw.iovec[1].iov_base, "BAZ=quux", strlen("BAZ=quux")), 0); + + /* Non-replacing put: a second FOO= just appends rather than replacing */ + ASSERT_OK(iovw_put_string_field(&iovw, "FOO=", "second")); + ASSERT_EQ(iovw.count, 3U); + ASSERT_EQ(iovw.iovec[0].iov_len, strlen("FOO=bar")); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "FOO=bar", strlen("FOO=bar")), 0); + ASSERT_EQ(iovw.iovec[2].iov_len, strlen("FOO=second")); + ASSERT_EQ(memcmp(iovw.iovec[2].iov_base, "FOO=second", strlen("FOO=second")), 0); +} + +TEST(iovw_replace_string_field) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + + /* If the field does not exist yet, replace acts like put */ + ASSERT_OK(iovw_replace_string_field(&iovw, "A=", "1")); + ASSERT_EQ(iovw.count, 1U); + ASSERT_EQ(iovw.iovec[0].iov_len, strlen("A=1")); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "A=1", strlen("A=1")), 0); + + /* Replacing an existing field updates it in place */ + ASSERT_OK(iovw_replace_string_field(&iovw, "A=", "twentytwo")); + ASSERT_EQ(iovw.count, 1U); + ASSERT_EQ(iovw.iovec[0].iov_len, strlen("A=twentytwo")); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "A=twentytwo", strlen("A=twentytwo")), 0); + + /* Distinct field still appends */ + ASSERT_OK(iovw_replace_string_field(&iovw, "B=", "x")); + ASSERT_EQ(iovw.count, 2U); +} + +TEST(iovw_put_string_fieldf) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + + ASSERT_OK(iovw_put_string_fieldf(&iovw, "N=", "%d-%s", 42, "answer")); + ASSERT_EQ(iovw.count, 1U); + ASSERT_EQ(iovw.iovec[0].iov_len, strlen("N=42-answer")); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "N=42-answer", strlen("N=42-answer")), 0); + + /* Replacing variant */ + ASSERT_OK(iovw_replace_string_fieldf(&iovw, "N=", "%d", 7)); + ASSERT_EQ(iovw.count, 1U); + ASSERT_EQ(iovw.iovec[0].iov_len, strlen("N=7")); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "N=7", strlen("N=7")), 0); +} + +TEST(iovw_put_string_field_free) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + + /* iovw_put_string_field_free takes ownership of the value string (frees it on return). */ + char *v = strdup("hello"); + ASSERT_NOT_NULL(v); + ASSERT_OK(iovw_put_string_field_free(&iovw, "K=", v)); + ASSERT_EQ(iovw.count, 1U); + ASSERT_EQ(iovw.iovec[0].iov_len, strlen("K=hello")); + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "K=hello", strlen("K=hello")), 0); +} + +TEST(iovw_rebase) { + /* iovw_rebase shifts all iov_base pointers from an old base to a new base. Fabricate a + * stand-in "old base" and "new base" and a wrapper with offsets pointing into the old + * base, then verify they get rewritten to point into the new base. */ + + uint8_t old_base[64] = {}, new_base[64] = {}; + for (size_t i = 0; i < sizeof old_base; i++) { + old_base[i] = (uint8_t) i; + new_base[i] = (uint8_t) (100 + i); + } + + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + + ASSERT_OK(iovw_put(&iovw, old_base + 0, 4)); + ASSERT_OK(iovw_put(&iovw, old_base + 10, 2)); + ASSERT_OK(iovw_put(&iovw, old_base + 30, 8)); + ASSERT_EQ(iovw.count, 3U); + + iovw_rebase(&iovw, old_base, new_base); + + ASSERT_PTR_EQ(iovw.iovec[0].iov_base, new_base + 0); + ASSERT_PTR_EQ(iovw.iovec[1].iov_base, new_base + 10); + ASSERT_PTR_EQ(iovw.iovec[2].iov_base, new_base + 30); + + /* Lengths are preserved */ + ASSERT_EQ(iovw.iovec[0].iov_len, 4U); + ASSERT_EQ(iovw.iovec[1].iov_len, 2U); + ASSERT_EQ(iovw.iovec[2].iov_len, 8U); + + /* And the contents through the new base match what we staged there */ + ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, new_base + 0, 4), 0); + ASSERT_EQ(memcmp(iovw.iovec[1].iov_base, new_base + 10, 2), 0); + ASSERT_EQ(memcmp(iovw.iovec[2].iov_base, new_base + 30, 8), 0); +} + +TEST(iovw_size) { + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + ASSERT_EQ(iovw_size(&iovw), 0U); + + ASSERT_OK(iovw_put(&iovw, (char*) "abcd", 4)); + ASSERT_OK(iovw_put(&iovw, (char*) "efghij", 6)); + ASSERT_OK(iovw_put(&iovw, (char*) "kl", 2)); + ASSERT_EQ(iovw_size(&iovw), 12U); +} + +TEST(iovw_append_iovw) { + _cleanup_(iovw_done_free) struct iovec_wrapper target = {}; + _cleanup_(iovw_done) struct iovec_wrapper source = {}; + + /* Appending an empty/NULL source is a no-op */ + ASSERT_OK_ZERO(iovw_append_iovw(&target, NULL)); + ASSERT_OK_ZERO(iovw_append_iovw(&target, &source)); + ASSERT_EQ(target.count, 0U); + + ASSERT_OK(iovw_put(&source, (char*) "one", 3)); + ASSERT_OK(iovw_put(&source, (char*) "twotwo", 6)); + ASSERT_EQ(source.count, 2U); + + /* Pre-seed target with one entry to check that append adds on top rather than replacing */ + char *seed = strdup("zero"); + ASSERT_NOT_NULL(seed); + ASSERT_OK(iovw_put(&target, seed, strlen(seed))); + + ASSERT_OK(iovw_append_iovw(&target, &source)); + ASSERT_EQ(target.count, 3U); + + /* Appended entries must be fresh copies, not aliases of the source entries */ + ASSERT_TRUE(target.iovec[1].iov_base != source.iovec[0].iov_base); + ASSERT_TRUE(target.iovec[2].iov_base != source.iovec[1].iov_base); + + ASSERT_EQ(target.iovec[1].iov_len, 3U); + ASSERT_EQ(memcmp(target.iovec[1].iov_base, "one", 3), 0); + ASSERT_EQ(target.iovec[2].iov_len, 6U); + ASSERT_EQ(memcmp(target.iovec[2].iov_base, "twotwo", 6), 0); + + /* Source is unchanged */ + ASSERT_EQ(source.count, 2U); +} + +TEST(iovw_to_cstring) { + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + _cleanup_free_ char *s; + + /* Empty wrapper → empty string */ + s = iovw_to_cstring(&iovw); + ASSERT_NOT_NULL(s); + ASSERT_STREQ(s, ""); + s = mfree(s); + + ASSERT_OK(iovw_put(&iovw, (char*) "foo", 3)); + ASSERT_OK(iovw_put(&iovw, (char*) "/", 1)); + ASSERT_OK(iovw_put(&iovw, (char*) "bar", 3)); + + s = iovw_to_cstring(&iovw); + ASSERT_NOT_NULL(s); + ASSERT_STREQ(s, "foo/bar"); +} + +DEFINE_TEST_MAIN(LOG_INFO); From 067aa9b767954d134b6f69a5b97ebbd19bbb9697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 13 Apr 2026 21:46:12 +0200 Subject: [PATCH 1048/1296] test-string-util: test empty_to_null on a char array Unfortunately empty_to_null(t) where t is char[] fails. But it works with &t[0]. --- src/test/test-string-util.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/test/test-string-util.c b/src/test/test-string-util.c index 4f12ec710c11e..31b3d862f4fbb 100644 --- a/src/test/test-string-util.c +++ b/src/test/test-string-util.c @@ -617,6 +617,28 @@ TEST(split_pair) { ASSERT_STREQ(b, "="); } +TEST(empty_to_null) { + const char *s = "asdf", *n = NULL, *e = ""; + char *t = (char*) "asdf"; + const char p[] = "asdf"; + char q[] = "asdf"; + + /* empty_to_null cannot be used with constant strings, e.g. + * empty_to_null("") fails with 'error: cast specifies array type'. */ + + ASSERT_NULL(empty_to_null(NULL)); + ASSERT_NULL(empty_to_null(n)); + ASSERT_NULL(empty_to_null(e)); + ASSERT_STREQ(empty_to_null(s), "asdf"); + ASSERT_NULL(empty_to_null(s + 4)); + ASSERT_STREQ(empty_to_null(t), "asdf"); + ASSERT_NULL(empty_to_null(t + 4)); + ASSERT_STREQ(empty_to_null(&p[0]), "asdf"); + ASSERT_NULL(empty_to_null(&p[0] + 4)); + ASSERT_STREQ(empty_to_null(&q[0]), "asdf"); + ASSERT_NULL(empty_to_null(&q[0] + 4)); +} + TEST(first_word) { assert_se(first_word("Hello", "")); assert_se(first_word("Hello", "Hello")); From c861496cd3c90b93673e6f5431496325fef8a572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 13 Apr 2026 21:46:44 +0200 Subject: [PATCH 1049/1296] shared/curl-util: add curl_append_to_header --- src/imds/imdsd.c | 18 ++++++------------ src/shared/curl-util.c | 17 +++++++++++++++++ src/shared/curl-util.h | 1 + 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/imds/imdsd.c b/src/imds/imdsd.c index 7831d1c68cb22..2b7bf113353cf 100644 --- a/src/imds/imdsd.c +++ b/src/imds/imdsd.c @@ -1112,20 +1112,14 @@ static int context_acquire_data(Context *c) { if (!token_header) return context_log_oom(c); - struct curl_slist *n = curl_slist_append(c->request_header_data, token_header); - if (!n) - return context_log_oom(c); - - c->request_header_data = n; + r = curl_append_to_header(&c->request_header_data, STRV_MAKE(token_header)); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to create curl header: %m"); } - STRV_FOREACH(i, arg_extra_header) { - struct curl_slist *n = curl_slist_append(c->request_header_data, *i); - if (!n) - return context_log_oom(c); - - c->request_header_data = n; - } + r = curl_append_to_header(&c->request_header_data, arg_extra_header); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to create curl header: %m"); if (c->request_header_data) if (curl_easy_setopt(c->curl_data, CURLOPT_HTTPHEADER, c->request_header_data) != CURLE_OK) diff --git a/src/shared/curl-util.c b/src/shared/curl-util.c index 52321d08e1a43..405c083afc712 100644 --- a/src/shared/curl-util.c +++ b/src/shared/curl-util.c @@ -8,6 +8,7 @@ #include "hashmap.h" #include "log.h" #include "string-util.h" +#include "strv.h" #include "time-util.h" #include "version.h" @@ -407,3 +408,19 @@ int curl_parse_http_time(const char *t, usec_t *ret) { return 0; } + +int curl_append_to_header(struct curl_slist **list, char **headers) { + /* This function leaves 'list' modified on partial failure. + * Input/output param list may point to NULL. */ + + assert(list); + + STRV_FOREACH(h, headers) { + struct curl_slist *l = curl_slist_append(*list, *h); + if (!l) + return -ENOMEM; + *list = l; + } + + return 0; +} diff --git a/src/shared/curl-util.h b/src/shared/curl-util.h index 6b922d5003697..03b6ba7d21451 100644 --- a/src/shared/curl-util.h +++ b/src/shared/curl-util.h @@ -43,3 +43,4 @@ void curl_glue_remove_and_free(CurlGlue *g, CURL *c); struct curl_slist *curl_slist_new(const char *first, ...) _sentinel_; int curl_header_strdup(const void *contents, size_t sz, const char *field, char **value); int curl_parse_http_time(const char *t, usec_t *ret); +int curl_append_to_header(struct curl_slist **list, char **headers); From 5bbbe210a4e3856385d95e16074d8aa98cff909b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 6 Mar 2026 11:36:13 +0100 Subject: [PATCH 1050/1296] report: add basic upload functionality --- src/report/meson.build | 7 +- src/report/report-upload.c | 200 +++++++++++++++++++++++++++++++++++++ src/report/report.c | 84 +++++++++++----- src/report/report.h | 35 +++++++ 4 files changed, 302 insertions(+), 24 deletions(-) create mode 100644 src/report/report-upload.c create mode 100644 src/report/report.h diff --git a/src/report/meson.build b/src/report/meson.build index 36227bed9f56b..6e0c231be3245 100644 --- a/src/report/meson.build +++ b/src/report/meson.build @@ -4,7 +4,12 @@ executables += [ libexec_template + { 'name' : 'systemd-report', 'public' : true, - 'sources' : files('report.c'), + 'sources' : files( + 'report.c', + 'report-upload.c', + ), + 'link_with' : [libcurlutil_static, libshared], + 'dependencies' : [libcurl], }, libexec_template + { diff --git a/src/report/report-upload.c b/src/report/report-upload.c new file mode 100644 index 0000000000000..897d7d1159f91 --- /dev/null +++ b/src/report/report-upload.c @@ -0,0 +1,200 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "log.h" +#include "report.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" +#include "utf8.h" +#include "version.h" + +#if HAVE_LIBCURL +#include "curl-util.h" +#include /* Sadly this fails if ordered first. */ + +static size_t output_callback(char *buf, + size_t size, + size_t nmemb, + void *userp) { + + Context *context = ASSERT_PTR(userp); + int r; + + assert(size == 1); /* The docs say that this is always true. */ + + log_debug("Got an answer from the server (%zu bytes)", nmemb); + + if (nmemb != 0) { + if (memchr(buf, 0, nmemb)) { + log_warning("Server answer contains an embedded NUL, refusing."); + return 0; + } + + r = iovw_append(&context->upload_answer, buf, nmemb); + if (r < 0) { + log_warning("Failed to store server answer (%zu bytes): out of memory", nmemb); + return 0; /* Returning < nmemb signals failure */ + } + } + + return nmemb; +} + +static int build_json_report(Context *context, sd_json_variant **ret) { + /* Convert the variant array to a JSON report. */ + + assert(context); + assert(ret); + + usec_t ts = now(CLOCK_REALTIME); + int r; + + const char *ident; + if (IN_SET(context->action, ACTION_LIST_METRICS, ACTION_DESCRIBE_METRICS)) + ident = "metrics"; + else if (IN_SET(context->action, ACTION_LIST_FACTS, ACTION_DESCRIBE_FACTS)) + ident = "facts"; + else + assert_not_reached(); + + r = sd_json_buildo(ret, + SD_JSON_BUILD_PAIR("timestamp", + SD_JSON_BUILD_STRING(FORMAT_TIMESTAMP_STYLE(ts, TIMESTAMP_UTC))), + SD_JSON_BUILD_PAIR(ident, + SD_JSON_BUILD_VARIANT_ARRAY(context->metrics, context->n_metrics))); + if (r < 0) + return log_error_errno(r, "Failed to build JSON data: %m"); + return 0; +} +#endif + +int upload_collected(Context *context) { +#if HAVE_LIBCURL + _cleanup_(curl_slist_free_allp) struct curl_slist *header = NULL; + char error[CURL_ERROR_SIZE] = {}; + _cleanup_free_ char *json = NULL; + int r; + + { + /* Convert our variant array to a JSON report. + * We won't need the JSON structure again, so free it quickly. */ + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *vl = NULL; + r = build_json_report(context, &vl); + if (r < 0) + return r; + + r = sd_json_variant_format(vl, /* flags= */ 0, &json); + if (r < 0) + return log_error_errno(r, "Failed to format JSON data: %m"); + } + + r = curl_append_to_header(&header, + STRV_MAKE("Content-Type: application/json", + "Accept: application/json")); + if (r < 0) + return log_error_errno(r, "Failed to create curl header: %m"); + + _cleanup_(curl_easy_cleanupp) CURL *curl = curl_easy_init(); + if (!curl) + return log_error_errno(SYNTHETIC_ERRNO(ENOSR), + "Call to curl_easy_init failed."); + + /* If configured, set a timeout for the curl operation. */ + if (arg_network_timeout_usec != USEC_INFINITY && + !easy_setopt(curl, LOG_ERR, CURLOPT_TIMEOUT, + (long) DIV_ROUND_UP(arg_network_timeout_usec, USEC_PER_SEC))) + return -EXFULL; + + /* Tell it to POST to the URL */ + if (!easy_setopt(curl, LOG_ERR, CURLOPT_POST, 1L)) + return -EXFULL; + + if (!easy_setopt(curl, LOG_ERR, CURLOPT_ERRORBUFFER, error)) + return -EXFULL; + + /* Where to write to */ + if (!easy_setopt(curl, LOG_ERR, CURLOPT_WRITEFUNCTION, output_callback)) + return -EXFULL; + + if (!easy_setopt(curl, LOG_ERR, CURLOPT_WRITEDATA, context)) + return -EXFULL; + + if (!easy_setopt(curl, LOG_ERR, CURLOPT_HTTPHEADER, header)) + return -EXFULL; + + if (DEBUG_LOGGING) + /* enable verbose for easier tracing */ + (void) easy_setopt(curl, LOG_WARNING, CURLOPT_VERBOSE, 1L); + + (void) easy_setopt(curl, LOG_WARNING, + CURLOPT_USERAGENT, "systemd-report " GIT_VERSION); + + if (!streq_ptr(arg_key, "-") && (arg_key || startswith(arg_url, "https://"))) { + if (!easy_setopt(curl, LOG_ERR, CURLOPT_SSLKEY, arg_key ?: REPORT_PRIV_KEY_FILE)) + return -EXFULL; + if (!easy_setopt(curl, LOG_ERR, CURLOPT_SSLCERT, arg_cert ?: REPORT_CERT_FILE)) + return -EXFULL; + } + + if (STRPTR_IN_SET(arg_trust, "-", "all")) { + log_info("Server certificate verification disabled."); + if (!easy_setopt(curl, LOG_ERR, CURLOPT_SSL_VERIFYPEER, 0L)) + return -EUCLEAN; + if (!easy_setopt(curl, LOG_ERR, CURLOPT_SSL_VERIFYHOST, 0L)) + return -EUCLEAN; + } else if (arg_trust || startswith(arg_url, "https://")) { + if (!easy_setopt(curl, LOG_ERR, CURLOPT_CAINFO, arg_trust ?: REPORT_TRUST_FILE)) + return -EXFULL; + } + + if (startswith(arg_url, "https://")) + (void) easy_setopt(curl, LOG_WARNING, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); + + /* Upload to this place */ + if (!easy_setopt(curl, LOG_ERR, CURLOPT_URL, arg_url)) + return -EXFULL; + + if (!easy_setopt(curl, LOG_ERR, CURLOPT_POSTFIELDS, json)) + return -EXFULL; + + CURLcode code = curl_easy_perform(curl); + if (code != CURLE_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Upload to %s failed: %s", arg_url, + empty_to_null(&error[0]) ?: curl_easy_strerror(code)); + + long status; + code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); + if (code != CURLE_OK) + return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), + "Failed to retrieve response code: %s", + curl_easy_strerror(code)); + + _cleanup_free_ char *ans = iovw_to_cstring(&context->upload_answer); + if (!ans) + return log_oom(); + + if (!utf8_is_valid(ans)) + return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), + "Upload to %s failed with code %ld and an invalid UTF-8 answer.", + arg_url, status); + + if (status >= 300) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Upload to %s failed with code %ld: %s", + arg_url, status, strna(ans)); + if (status < 200) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Upload to %s finished with unexpected code %ld: %s", + arg_url, status, strna(ans)); + log_info("Upload to %s finished successfully with code %ld: %s", + arg_url, status, strna(ans)); + return 0; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Compiled without libcurl."); +#endif +} diff --git a/src/report/report.c b/src/report/report.c index 96ff28dccd9b3..b02a48b28b46a 100644 --- a/src/report/report.c +++ b/src/report/report.c @@ -16,6 +16,7 @@ #include "path-lookup.h" #include "pretty-print.h" #include "recurse-dir.h" +#include "report.h" #include "runtime-scope.h" #include "set.h" #include "sort-util.h" @@ -35,27 +36,17 @@ static bool arg_legend = true; static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; static char **arg_matches = NULL; +char *arg_url = NULL; +char *arg_key = NULL; +char *arg_cert = NULL; +char *arg_trust = NULL; +usec_t arg_network_timeout_usec = TIMEOUT_USEC; STATIC_DESTRUCTOR_REGISTER(arg_matches, strv_freep); - -typedef enum Action { - ACTION_LIST_METRICS, - ACTION_DESCRIBE_METRICS, - ACTION_LIST_FACTS, - ACTION_DESCRIBE_FACTS, - _ACTION_MAX, - _ACTION_INVALID = -EINVAL, -} Action; - -/* The structure for collected "metrics" or "facts". The fields - * are prefixed with just "metrics" for brevity. */ -typedef struct Context { - Action action; - sd_event *event; - Set *link_infos; - sd_json_variant **metrics; /* Collected metrics or facts for sorting */ - size_t n_metrics, n_skipped_metrics, n_invalid_metrics; -} Context; +STATIC_DESTRUCTOR_REGISTER(arg_url, freep); +STATIC_DESTRUCTOR_REGISTER(arg_key, freep); +STATIC_DESTRUCTOR_REGISTER(arg_cert, freep); +STATIC_DESTRUCTOR_REGISTER(arg_trust, freep); typedef struct LinkInfo { Context *context; @@ -76,9 +67,12 @@ static void context_done(Context *context) { if (!context) return; - set_free(context->link_infos); + context->event = sd_event_unref(context->event); + context->link_infos = set_free(context->link_infos); sd_json_variant_unref_many(context->metrics, context->n_metrics); - sd_event_unref(context->event); + context->metrics = NULL; + context->n_metrics = 0; + iovw_done_free(&context->upload_answer); } DEFINE_TRIVIAL_CLEANUP_FUNC(LinkInfo*, link_info_free); @@ -569,6 +563,7 @@ static int output_collected(Context *context) { } _cleanup_(table_unrefp) Table *table = NULL; + switch(context->action) { case ACTION_LIST_METRICS: @@ -771,7 +766,10 @@ static int verb_metrics(int argc, char *argv[], uintptr_t data, void *userdata) if (r < 0) return log_error_errno(r, "Failed to run event loop: %m"); - r = output_collected(&context); + if (arg_url) + r = upload_collected(&context); + else + r = output_collected(&context); if (r < 0) return r; } @@ -855,7 +853,10 @@ static int verb_facts(int argc, char *argv[], uintptr_t data, void *userdata) { if (r < 0) return log_error_errno(r, "Failed to run event loop: %m"); - r = output_collected(&context); + if (arg_url) + r = upload_collected(&context); + else + r = output_collected(&context); if (r < 0) return r; } @@ -1017,8 +1018,45 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_COMMON_LOWERCASE_J: arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; break; + + OPTION_LONG("url", "URL", + "Upload to this address"): + r = free_and_strdup_warn(&arg_url, arg); + if (r < 0) + return r; + break; + + OPTION_LONG("key", "FILENAME", + "Specify key in PEM format (default: \"" REPORT_PRIV_KEY_FILE "\")"): + r = free_and_strdup_warn(&arg_key, arg); + if (r < 0) + return r; + break; + + OPTION_LONG("cert", "FILENAME", + "Specify certificate in PEM format (default: \"" REPORT_CERT_FILE "\")"): + r = free_and_strdup_warn(&arg_cert, arg); + if (r < 0) + return r; + break; + + OPTION_LONG("trust", "FILENAME|all", + "Specify CA certificate or disable checking (default: \"" REPORT_TRUST_FILE "\")"): + r = free_and_strdup_warn(&arg_trust, arg); + if (r < 0) + return r; + break; + + OPTION_LONG("network-timeout", "SEC", "Specify timeout for network upload operation"): + r = parse_sec(arg, &arg_network_timeout_usec); + if (r < 0) + return log_error_errno(r, "Failed to parse --network-timeout value: %s", arg); + break; } + if ((arg_url || arg_key || arg_cert || arg_trust) && !HAVE_LIBCURL) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Compiled without libcurl."); + *ret_args = option_parser_get_args(&state); return 1; } diff --git a/src/report/report.h b/src/report/report.h new file mode 100644 index 0000000000000..69c9c5851b7a3 --- /dev/null +++ b/src/report/report.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +#include "iovec-wrapper.h" + +#define REPORT_PRIV_KEY_FILE CERTIFICATE_ROOT "/private/systemd-report.pem" +#define REPORT_CERT_FILE CERTIFICATE_ROOT "/certs/systemd-report.pem" +#define REPORT_TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem" + +extern char *arg_url, *arg_key, *arg_cert, *arg_trust; +extern usec_t arg_network_timeout_usec; + +typedef enum Action { + ACTION_LIST_METRICS, + ACTION_DESCRIBE_METRICS, + ACTION_LIST_FACTS, + ACTION_DESCRIBE_FACTS, + _ACTION_MAX, + _ACTION_INVALID = -EINVAL, +} Action; + +/* The structure for collected "metrics" or "facts". The fields + * are prefixed with just "metrics" for brevity. */ +typedef struct Context { + Action action; + sd_event *event; + Set *link_infos; + sd_json_variant **metrics; /* Collected metrics or facts for sorting */ + size_t n_metrics, n_skipped_metrics, n_invalid_metrics; + struct iovec_wrapper upload_answer; +} Context; + +int upload_collected(Context *context); From fca491e03642ada516fe8386397aa166c92c284e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 17:31:59 +0200 Subject: [PATCH 1051/1296] shared/webutil: reorder .c to match .h, mark one more function as _pure_ --- src/shared/web-util.c | 26 +++++++++++++------------- src/shared/web-util.h | 4 +--- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/shared/web-util.c b/src/shared/web-util.c index 628f0805bd3f8..23f3004700b46 100644 --- a/src/shared/web-util.c +++ b/src/shared/web-util.c @@ -5,19 +5,6 @@ #include "utf8.h" #include "web-util.h" -bool http_etag_is_valid(const char *etag) { - if (isempty(etag)) - return false; - - if (!endswith(etag, "\"")) - return false; - - if (!STARTSWITH_SET(etag, "\"", "W/\"")) - return false; - - return true; -} - bool http_url_is_valid(const char *url) { const char *p; @@ -62,3 +49,16 @@ bool documentation_url_is_valid(const char *url) { return ascii_is_valid(p); } + +bool http_etag_is_valid(const char *etag) { + if (isempty(etag)) + return false; + + if (!endswith(etag, "\"")) + return false; + + if (!STARTSWITH_SET(etag, "\"", "W/\"")) + return false; + + return true; +} diff --git a/src/shared/web-util.h b/src/shared/web-util.h index 68f868ab0a24e..8a2f5c537bcef 100644 --- a/src/shared/web-util.h +++ b/src/shared/web-util.h @@ -5,7 +5,5 @@ bool http_url_is_valid(const char *url) _pure_; bool file_url_is_valid(const char *url) _pure_; - bool documentation_url_is_valid(const char *url) _pure_; - -bool http_etag_is_valid(const char *etag); +bool http_etag_is_valid(const char *etag) _pure_; From ce400ef2d956d268a72b1a7ebbc61d344697e4c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 1 Apr 2026 07:32:46 +0200 Subject: [PATCH 1052/1296] report: add option to inject additional HTTP headers This is useful when debugging things. The option is named and implements the same logic as imdsd. --- src/imds/imdsd.c | 4 ---- src/report/report-upload.c | 4 ++++ src/report/report.c | 19 ++++++++++++++++++- src/report/report.h | 1 + src/shared/web-util.c | 7 +++++++ src/shared/web-util.h | 1 + 6 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/imds/imdsd.c b/src/imds/imdsd.c index 2b7bf113353cf..5ed7d1df338b7 100644 --- a/src/imds/imdsd.c +++ b/src/imds/imdsd.c @@ -2243,10 +2243,6 @@ static bool http_header_name_valid(const char *a) { return a && ascii_is_valid(a) && !string_has_cc(a, /* ok= */ NULL) && !strchr(a, ':'); } -static bool http_header_valid(const char *a) { - return a && ascii_is_valid(a) && !string_has_cc(a, /* ok= */ NULL) && strchr(a, ':'); -} - static int parse_argv(int argc, char *argv[]) { int r; diff --git a/src/report/report-upload.c b/src/report/report-upload.c index 897d7d1159f91..3022bd3049387 100644 --- a/src/report/report-upload.c +++ b/src/report/report-upload.c @@ -97,6 +97,10 @@ int upload_collected(Context *context) { if (r < 0) return log_error_errno(r, "Failed to create curl header: %m"); + r = curl_append_to_header(&header, arg_extra_headers); + if (r < 0) + return log_error_errno(r, "Failed to create curl header: %m"); + _cleanup_(curl_easy_cleanupp) CURL *curl = curl_easy_init(); if (!curl) return log_error_errno(SYNTHETIC_ERRNO(ENOSR), diff --git a/src/report/report.c b/src/report/report.c index b02a48b28b46a..c45c4da7ea63e 100644 --- a/src/report/report.c +++ b/src/report/report.c @@ -26,6 +26,7 @@ #include "time-util.h" #include "varlink-idl-util.h" #include "verbs.h" +#include "web-util.h" #define METRICS_OR_FACTS_MAX 4096U #define METRICS_OR_FACTS_LINKS_MAX 128U @@ -40,6 +41,7 @@ char *arg_url = NULL; char *arg_key = NULL; char *arg_cert = NULL; char *arg_trust = NULL; +char **arg_extra_headers = NULL; usec_t arg_network_timeout_usec = TIMEOUT_USEC; STATIC_DESTRUCTOR_REGISTER(arg_matches, strv_freep); @@ -47,6 +49,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_url, freep); STATIC_DESTRUCTOR_REGISTER(arg_key, freep); STATIC_DESTRUCTOR_REGISTER(arg_cert, freep); STATIC_DESTRUCTOR_REGISTER(arg_trust, freep); +STATIC_DESTRUCTOR_REGISTER(arg_extra_headers, strv_freep); typedef struct LinkInfo { Context *context; @@ -1052,9 +1055,23 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { if (r < 0) return log_error_errno(r, "Failed to parse --network-timeout value: %s", arg); break; + + OPTION_LONG("extra-header", "NAME: VALUE", + "Inject additional header into the upload request"): + if (isempty(arg)) { + arg_extra_headers = strv_free(arg_extra_headers); + break; + } + + if (!http_header_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header: %s", arg); + + if (strv_extend(&arg_extra_headers, arg) < 0) + return log_oom(); + break; } - if ((arg_url || arg_key || arg_cert || arg_trust) && !HAVE_LIBCURL) + if ((arg_url || arg_key || arg_cert || arg_trust || arg_extra_headers) && !HAVE_LIBCURL) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Compiled without libcurl."); *ret_args = option_parser_get_args(&state); diff --git a/src/report/report.h b/src/report/report.h index 69c9c5851b7a3..4d7b5bdd3f0bb 100644 --- a/src/report/report.h +++ b/src/report/report.h @@ -10,6 +10,7 @@ #define REPORT_TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem" extern char *arg_url, *arg_key, *arg_cert, *arg_trust; +extern char **arg_extra_headers; extern usec_t arg_network_timeout_usec; typedef enum Action { diff --git a/src/shared/web-util.c b/src/shared/web-util.c index 23f3004700b46..bfcea982a9dfc 100644 --- a/src/shared/web-util.c +++ b/src/shared/web-util.c @@ -50,6 +50,13 @@ bool documentation_url_is_valid(const char *url) { return ascii_is_valid(p); } +bool http_header_valid(const char *header) { + return header && + ascii_is_valid(header) && + !string_has_cc(header, /* ok= */ NULL) && + strchr(header, ':'); +} + bool http_etag_is_valid(const char *etag) { if (isempty(etag)) return false; diff --git a/src/shared/web-util.h b/src/shared/web-util.h index 8a2f5c537bcef..ec154c107aebc 100644 --- a/src/shared/web-util.h +++ b/src/shared/web-util.h @@ -6,4 +6,5 @@ bool http_url_is_valid(const char *url) _pure_; bool file_url_is_valid(const char *url) _pure_; bool documentation_url_is_valid(const char *url) _pure_; +bool http_header_valid(const char *header) _pure_; bool http_etag_is_valid(const char *etag) _pure_; From 0f3bd778e03965c11d6b67f33c8d030576cb2b0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 7 Apr 2026 14:09:25 +0200 Subject: [PATCH 1053/1296] test: add HTTP upload test for systemd-report Add a fake HTTP server (fake-report-server.py) that accepts JSON POST requests and validates the report structure, and test cases in TEST-74-AUX-UTILS.report.sh that exercise plain HTTP upload of both metrics and facts. Co-developed-by: Claude Opus 4.6 --- mkosi/mkosi.sanitizers/mkosi.postinst | 1 + .../fake-report-server.py | 56 +++++++++++++++++++ test/units/TEST-74-AUX-UTILS.report.sh | 14 +++++ 3 files changed, 71 insertions(+) create mode 100755 test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py diff --git a/mkosi/mkosi.sanitizers/mkosi.postinst b/mkosi/mkosi.sanitizers/mkosi.postinst index 17c7d7bad90a4..118433125462b 100755 --- a/mkosi/mkosi.sanitizers/mkosi.postinst +++ b/mkosi/mkosi.sanitizers/mkosi.postinst @@ -45,6 +45,7 @@ wrap=( /usr/lib/systemd/tests/testdata/TEST-74-AUX-UTILS.units/proxy-echo.py /usr/libexec/polkit-1/polkitd /usr/lib/systemd/tests/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-imds.py + /usr/lib/systemd/tests/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py agetty btrfs capsh diff --git a/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py b/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py new file mode 100755 index 0000000000000..45cc34fa53303 --- /dev/null +++ b/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py @@ -0,0 +1,56 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: LGPL-2.1-or-later + +import json, os, socket +from http.server import BaseHTTPRequestHandler, HTTPServer + +def sd_notify(state: str) -> bool: + notify_socket = os.environ.get("NOTIFY_SOCKET") + if not notify_socket: + return False + if notify_socket.startswith("@"): + notify_socket = "\0" + notify_socket[1:] + try: + with socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) as sock: + sock.sendto(state.encode(), notify_socket) + except OSError: + return False + return True + +class Handler(BaseHTTPRequestHandler): + def do_POST(self): + length = int(self.headers.get("Content-Length", 0)) + body = self.rfile.read(length) + + # Validate JSON structure + try: + data = json.loads(body) + except json.JSONDecodeError: + self.send_error(400, "Invalid JSON") + return + + print(f"JSON: {s if len(s := str(data)) < 80 else s[:40] + '…' + s[-40:]}") + + if "metrics" not in data and "facts" not in data: + self.send_error(400, "Missing 'metrics' or 'facts' field") + return + + response = json.dumps({"status": "ok"}).encode() + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", len(response)) + self.end_headers() + self.wfile.write(response) + + def log_message(self, fmt, *args): + print(f"{self.address_string()} - {fmt % args}") + +PORT = 8089 + +server = HTTPServer(("", PORT), Handler) +print(f"Serving on http://localhost:{PORT}/") +try: + sd_notify("READY=1") + server.serve_forever() +except KeyboardInterrupt: + print("\nStopped.") diff --git a/test/units/TEST-74-AUX-UTILS.report.sh b/test/units/TEST-74-AUX-UTILS.report.sh index f92f1ed75078d..af134e980a215 100755 --- a/test/units/TEST-74-AUX-UTILS.report.sh +++ b/test/units/TEST-74-AUX-UTILS.report.sh @@ -65,3 +65,17 @@ systemctl start systemd-report-basic.socket # Test facts via direct Varlink call on existing socket varlinkctl --more call /run/systemd/report/io.systemd.Basic io.systemd.Facts.List {} varlinkctl --more call /run/systemd/report/io.systemd.Basic io.systemd.Facts.Describe {} + +# Test HTTP upload (plain http) +at_exit() { + set +e + systemctl stop fake-report-server +} +trap at_exit EXIT + +systemd-run -p Type=notify --unit=fake-report-server \ + /usr/lib/systemd/tests/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py +systemctl status fake-report-server + +"$REPORT" metrics --url=http://localhost:8089/ +"$REPORT" facts --url=http://localhost:8089/ From b7ac32c2c38af30e27bb754cfce899a66209514d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 7 Apr 2026 14:34:53 +0200 Subject: [PATCH 1054/1296] test: add HTTPS upload test for systemd-report Extend fake-report-server.py with optional --cert, --key, --port arguments for TLS support. Add a test case that generates a self-signed certificate and tests HTTPS upload of metrics and facts. Also exercise the --header param. Co-developed-by: Claude Opus 4.6 --- .../fake-report-server.py | 22 +++++++++++++++---- test/units/TEST-74-AUX-UTILS.report.sh | 21 +++++++++++++++--- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py b/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py index 45cc34fa53303..4875a00bada6a 100755 --- a/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py +++ b/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py @@ -1,7 +1,7 @@ #!/usr/bin/python3 # SPDX-License-Identifier: LGPL-2.1-or-later -import json, os, socket +import argparse, json, os, socket, ssl from http.server import BaseHTTPRequestHandler, HTTPServer def sd_notify(state: str) -> bool: @@ -22,6 +22,10 @@ def do_POST(self): length = int(self.headers.get("Content-Length", 0)) body = self.rfile.read(length) + # Check optional attribute + if auth := self.headers.get("Authorization"): + print(f"Authorization: {auth}") + # Validate JSON structure try: data = json.loads(body) @@ -45,10 +49,20 @@ def do_POST(self): def log_message(self, fmt, *args): print(f"{self.address_string()} - {fmt % args}") -PORT = 8089 +parser = argparse.ArgumentParser() +parser.add_argument("--port", type=int, default=8089) +parser.add_argument("--cert", help="TLS certificate file") +parser.add_argument("--key", help="TLS private key file") +args = parser.parse_args() -server = HTTPServer(("", PORT), Handler) -print(f"Serving on http://localhost:{PORT}/") +server = HTTPServer(("", args.port), Handler) +if args.cert and args.key: + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + ctx.load_cert_chain(args.cert, args.key) + server.socket = ctx.wrap_socket(server.socket, server_side=True) + print(f"Serving on https://localhost:{args.port}/") +else: + print(f"Serving on http://localhost:{args.port}/") try: sd_notify("READY=1") server.serve_forever() diff --git a/test/units/TEST-74-AUX-UTILS.report.sh b/test/units/TEST-74-AUX-UTILS.report.sh index af134e980a215..53b83c4dd9477 100755 --- a/test/units/TEST-74-AUX-UTILS.report.sh +++ b/test/units/TEST-74-AUX-UTILS.report.sh @@ -67,15 +67,30 @@ varlinkctl --more call /run/systemd/report/io.systemd.Basic io.systemd.Facts.Lis varlinkctl --more call /run/systemd/report/io.systemd.Basic io.systemd.Facts.Describe {} # Test HTTP upload (plain http) +FAKE_SERVER=/usr/lib/systemd/tests/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py +CERTDIR=$(mktemp -d) + at_exit() { set +e - systemctl stop fake-report-server + systemctl stop fake-report-server fake-report-server-tls + rm -rf "$CERTDIR" } trap at_exit EXIT -systemd-run -p Type=notify --unit=fake-report-server \ - /usr/lib/systemd/tests/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py +systemd-run -p Type=notify --unit=fake-report-server "$FAKE_SERVER" systemctl status fake-report-server "$REPORT" metrics --url=http://localhost:8089/ "$REPORT" facts --url=http://localhost:8089/ + +# Test HTTPS upload with generated TLS certificates +openssl req -x509 -newkey rsa:2048 -keyout "$CERTDIR/server.key" -out "$CERTDIR/server.crt" \ + -days 1 -nodes -subj "/CN=localhost" 2>/dev/null + +systemd-run -p Type=notify --unit=fake-report-server-tls \ + "$FAKE_SERVER" --cert="$CERTDIR/server.crt" --key="$CERTDIR/server.key" --port=8090 +systemctl status fake-report-server-tls + +"$REPORT" metrics --url=https://localhost:8090/ --key=- --trust="$CERTDIR/server.crt" +"$REPORT" facts --url=https://localhost:8090/ --key=- --trust="$CERTDIR/server.crt" \ + --extra-header='Authorization: Bearer magic string' From baeb764635bda5d9bbec57b107f26efb6b115727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 17:54:03 +0200 Subject: [PATCH 1055/1296] report: limit server answer to 1 MiB As suggested in review. --- src/report/report-upload.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/report/report-upload.c b/src/report/report-upload.c index 3022bd3049387..218742f540cc7 100644 --- a/src/report/report-upload.c +++ b/src/report/report-upload.c @@ -14,6 +14,8 @@ #include "curl-util.h" #include /* Sadly this fails if ordered first. */ +#define SERVER_ANSWER_MAX (1*1024*1024u) + static size_t output_callback(char *buf, size_t size, size_t nmemb, @@ -27,6 +29,13 @@ static size_t output_callback(char *buf, log_debug("Got an answer from the server (%zu bytes)", nmemb); if (nmemb != 0) { + size_t new_size = size_add(iovw_size(&context->upload_answer), nmemb); + + if (new_size > SERVER_ANSWER_MAX) { + log_warning("Server answer too long (%zu > %u), refusing.", new_size, SERVER_ANSWER_MAX); + return 0; + } + if (memchr(buf, 0, nmemb)) { log_warning("Server answer contains an embedded NUL, refusing."); return 0; From 9d8f4a469b7e2228fb3c9865da4b0ff6e2246cd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 17 Apr 2026 08:41:30 +0200 Subject: [PATCH 1056/1296] ssh-issue: convert to the new option parser --make-vsock and --rm-vsock are now described in --help, not only shown in synopsis. Co-developed-by: Claude Opus 4.7 --- src/ssh-generator/ssh-issue.c | 67 +++++++++++++++-------------------- 1 file changed, 28 insertions(+), 39 deletions(-) diff --git a/src/ssh-generator/ssh-issue.c b/src/ssh-generator/ssh-issue.c index d2f02bd3d8a4d..d4e2443d1d91d 100644 --- a/src/ssh-generator/ssh-issue.c +++ b/src/ssh-generator/ssh-issue.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -8,10 +7,12 @@ #include "ansi-color.h" #include "build.h" #include "fd-util.h" +#include "format-table.h" #include "fs-util.h" #include "log.h" #include "main-func.h" #include "mkdir.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" #include "ssh-util.h" @@ -31,84 +32,72 @@ STATIC_DESTRUCTOR_REGISTER(arg_issue_path, freep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-ssh-issue", "1", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...] --make-vsock\n" - "%s [OPTIONS...] --rm-vsock\n" - "\n%sCreate ssh /run/issue.d/ file reporting VSOCK address.%s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --issue-path=PATH Change path to /run/issue.d/50-ssh-vsock.issue\n" - "\nSee the %s for details.\n", - program_invocation_short_name, + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%1$s [OPTIONS...] --make-vsock\n" + "%1$s [OPTIONS...] --rm-vsock\n" + "\n%2$sCreate ssh /run/issue.d/ file reporting VSOCK address.%3$s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_MAKE_VSOCK = 0x100, - ARG_RM_VSOCK, - ARG_ISSUE_PATH, - ARG_VERSION, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "make-vsock", no_argument, NULL, ARG_MAKE_VSOCK }, - { "rm-vsock", no_argument, NULL, ARG_RM_VSOCK }, - { "issue-path", required_argument, NULL, ARG_ISSUE_PATH }, - {} - }; - - int c, r; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_MAKE_VSOCK: + OPTION_LONG("make-vsock", NULL, "Generate the issue file (default)"): arg_action = ACTION_MAKE_VSOCK; break; - case ARG_RM_VSOCK: + OPTION_LONG("rm-vsock", NULL, "Remove the issue file"): arg_action = ACTION_RM_VSOCK; break; - case ARG_ISSUE_PATH: - if (empty_or_dash(optarg)) { + OPTION_LONG("issue-path", "PATH", + "Change path to /run/issue.d/50-ssh-vsock.issue"): + if (empty_or_dash(arg)) { arg_issue_path = mfree(arg_issue_path); arg_issue_stdout = true; break; } - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_issue_path); + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_issue_path); if (r < 0) return r; arg_issue_stdout = false; break; } - } if (!arg_issue_path && !arg_issue_stdout) { arg_issue_path = strdup("/run/issue.d/50-ssh-vsock.issue"); From 07e0805f1abe42d5582ce357c01c477192bfc065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 23:42:48 +0200 Subject: [PATCH 1057/1296] random-seed: convert to "verbs", use the new option+verb code --help output is the same except for indentation. Co-developed-by: Claude Opus 4.7 --- src/random-seed/random-seed-tool.c | 97 +++++++++++++++--------------- 1 file changed, 49 insertions(+), 48 deletions(-) diff --git a/src/random-seed/random-seed-tool.c b/src/random-seed/random-seed-tool.c index b539c1f654fb6..3e40fcdddaf36 100644 --- a/src/random-seed/random-seed-tool.c +++ b/src/random-seed/random-seed-tool.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include @@ -14,18 +13,20 @@ #include "build.h" #include "errno-util.h" #include "fd-util.h" +#include "format-table.h" #include "fs-util.h" #include "io-util.h" #include "log.h" #include "main-func.h" #include "mkdir.h" +#include "options.h" #include "parse-util.h" #include "pretty-print.h" #include "random-util.h" #include "sha256.h" -#include "string-table.h" #include "string-util.h" #include "sync-util.h" +#include "verbs.h" #include "xattr-util.h" typedef enum SeedAction { @@ -296,75 +297,75 @@ static int save_seed_file( return 0; } -static int help(int argc, char *argv[], void *userdata) { +static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-random-seed", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND\n" - "\n%5$sLoad and save the system random seed at boot and shutdown.%6$s\n" - "\n%3$sCommands:%4$s\n" - " load Load a random seed saved on disk into the kernel entropy pool\n" - " save Save a new random seed on disk\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\nSee the %2$s for details.\n", + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] COMMAND\n" + "\n%sLoad and save the system random seed at boot and shutdown.%s\n" + "\nCommands:\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), ansi_normal()); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + printf("\nOptions:\n"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static const char* const seed_action_table[_ACTION_MAX] = { - [ACTION_LOAD] = "load", - [ACTION_SAVE] = "save", -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(seed_action, SeedAction); +VERB_FULL(verb_set_action, "load", NULL, VERB_ANY, 1, 0, ACTION_LOAD, + "Load a random seed saved on disk into the kernel entropy pool"); +VERB_FULL(verb_set_action, "save", NULL, VERB_ANY, 1, 0, ACTION_SAVE, + "Save a new random seed on disk"); +static int verb_set_action(int argc, char *argv[], uintptr_t data, void *userdata) { + arg_action = data; + return 0; +} static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {} - }; - - int c; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - return help(0, NULL, NULL); - case ARG_VERSION: - return version(); - case '?': - return -EINVAL; - default: - assert_not_reached(); - } + OPTION_COMMON_HELP: + return help(); - if (optind + 1 != argc) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program requires one argument."); + OPTION_COMMON_VERSION: + return version(); + } - arg_action = seed_action_from_string(argv[optind]); - if (arg_action < 0) - return log_error_errno(arg_action, "Unknown action '%s'", argv[optind]); + r = dispatch_verb_with_args(option_parser_get_args(&state), NULL); + if (r < 0) + return r; return 1; } From 5961237fde7eaa30dcde99e07147e0bbb7246fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 17 Apr 2026 08:00:58 +0200 Subject: [PATCH 1058/1296] socket-proxyd: convert to the new option parser --help output is identical in content. --help/--version as now first in the list, as is usual. Co-developed-by: Claude Opus 4.7 --- src/socket-proxy/socket-proxyd.c | 98 +++++++++++++------------------- 1 file changed, 41 insertions(+), 57 deletions(-) diff --git a/src/socket-proxy/socket-proxyd.c b/src/socket-proxy/socket-proxyd.c index e1eec1dd41c82..82250a0b06ff1 100644 --- a/src/socket-proxy/socket-proxyd.c +++ b/src/socket-proxy/socket-proxyd.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -15,8 +14,10 @@ #include "errno-util.h" #include "event-util.h" #include "fd-util.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-util.h" #include "pretty-print.h" #include "resolve-private.h" @@ -24,6 +25,7 @@ #include "socket-forward.h" #include "socket-util.h" #include "string-util.h" +#include "strv.h" #include "time-util.h" static unsigned arg_connections_max = 256; @@ -382,8 +384,8 @@ static int add_listen_socket(Context *context, int fd) { } static int help(void) { - _cleanup_free_ char *link = NULL; - _cleanup_free_ char *time_link = NULL; + _cleanup_free_ char *link = NULL, *time_link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-socket-proxyd", "8", &link); @@ -393,61 +395,49 @@ static int help(void) { if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%1$s [HOST:PORT]\n" - "%1$s [SOCKET]\n\n" - "%2$sBidirectionally proxy local sockets to another (possibly remote) socket.%3$s\n\n" - " -c --connections-max= Set the maximum number of connections to be accepted\n" - " --exit-idle-time= Exit when without a connection for this duration. See\n" - " the %4$s for time span format\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\nSee the %5$s for details.\n", + "%1$s [SOCKET]\n" + "\n%2$sBidirectionally proxy local sockets to another (possibly remote) socket.%3$s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - time_link, - link); + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee %s for --exit-idle-time= time span format.\n" + "See the %s for details.\n", + time_link, link); return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_EXIT_IDLE, - ARG_IGNORE_ENV - }; - - static const struct option options[] = { - { "connections-max", required_argument, NULL, 'c' }, - { "exit-idle-time", required_argument, NULL, ARG_EXIT_IDLE }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {} - }; - - int c, r; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "c:h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'c': - r = safe_atou(optarg, &arg_connections_max); - if (r < 0) { - log_error("Failed to parse --connections-max= argument: %s", optarg); - return r; - } + OPTION('c', "connections-max", "NUMBER", + "Set the maximum number of connections to be accepted"): + r = safe_atou(arg, &arg_connections_max); + if (r < 0) + return log_error_errno(r, "Failed to parse --connections-max= argument: %s", arg); if (arg_connections_max < 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -455,28 +445,22 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_EXIT_IDLE: - r = parse_sec(optarg, &arg_exit_idle_time); + OPTION_LONG("exit-idle-time", "TIME", + "Exit when without a connection for this duration"): + r = parse_sec(arg, &arg_exit_idle_time); if (r < 0) - return log_error_errno(r, "Failed to parse --exit-idle-time= argument: %s", optarg); + return log_error_errno(r, "Failed to parse --exit-idle-time= argument: %s", arg); break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind >= argc) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Not enough parameters."); - - if (argc != optind+1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Too many parameters."); + char **args = option_parser_get_args(&state); + size_t n = strv_length(args); + if (n < 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not enough parameters."); + if (n > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many parameters."); - arg_remote_host = argv[optind]; + arg_remote_host = args[0]; return 1; } From 252783981bca0783045fd89205dd8e6e24ca29c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 17 Apr 2026 09:07:31 +0200 Subject: [PATCH 1059/1296] ssh-issue: replace verb options by normal verbs The interface of this program was rather strange. It took an option that specified what to do, but that option behaved exactly like a verb. Let's change the interface to the more modern style with verbs. Since the inteface was documented in the man page, provide a compat shim to handle the old options. (In practice, I doubt anybody will notice the change. But since it was documented, it's easier to provide the compat then to think too much whether it is actually needed. I think we can drop it an year or so.) --- man/systemd-ssh-issue.xml | 28 ++-- src/ssh-generator/ssh-generator.c | 4 +- src/ssh-generator/ssh-issue.c | 237 ++++++++++++++++-------------- 3 files changed, 142 insertions(+), 127 deletions(-) diff --git a/man/systemd-ssh-issue.xml b/man/systemd-ssh-issue.xml index 4e887796764e0..f71a50f755d73 100644 --- a/man/systemd-ssh-issue.xml +++ b/man/systemd-ssh-issue.xml @@ -23,15 +23,22 @@ - /usr/lib/systemd/systemd-ssh-issue - /usr/lib/systemd/systemd-ssh-issue + /usr/lib/systemd/systemd-ssh-issue + OPTIONS + make-vsock + + + /usr/lib/systemd/systemd-ssh-issue + OPTIONS + rm-vsock Description - systemd-ssh-issue is a small tool that generates a + systemd-ssh-issue is a small tool that generates (when called with + make-vsock) and removes (when called with rm-vsock) a /run/issue.d/50-ssh-vsock.issue drop-in file in case AF_VSOCK support is available in the kernel and the VM environment. The file contains brief information about how to contact the local system via SSH-over-AF_VSOCK, in particular it reports the @@ -49,21 +56,6 @@ The following options are understood: - - - Generates the issue file. This command has no effect if called on systems lacking - AF_VSOCK support. - - - - - - - Removes the issue file if it exists. - - - - Changes the path to the issue file to write to/remove. If not specified, defaults to diff --git a/src/ssh-generator/ssh-generator.c b/src/ssh-generator/ssh-generator.c index 3923710b5cca0..bc01250a55b12 100644 --- a/src/ssh-generator/ssh-generator.c +++ b/src/ssh-generator/ssh-generator.c @@ -238,8 +238,8 @@ static int add_vsock_socket( "sshd-vsock.socket", "vsock::22", "AF_VSOCK", - "ExecStartPost=-/usr/lib/systemd/systemd-ssh-issue --make-vsock\n" - "ExecStopPre=-/usr/lib/systemd/systemd-ssh-issue --rm-vsock\n", + "ExecStartPost=-/usr/lib/systemd/systemd-ssh-issue make-vsock\n" + "ExecStopPre=-/usr/lib/systemd/systemd-ssh-issue rm-vsock\n", /* with_ssh_access_target_dependency= */ true); if (r < 0) return r; diff --git a/src/ssh-generator/ssh-issue.c b/src/ssh-generator/ssh-issue.c index d4e2443d1d91d..f1000d60684a9 100644 --- a/src/ssh-generator/ssh-issue.c +++ b/src/ssh-generator/ssh-issue.c @@ -17,38 +17,134 @@ #include "pretty-print.h" #include "ssh-util.h" #include "string-util.h" +#include "strv.h" #include "tmpfile-util.h" +#include "verbs.h" #include "virt.h" -static enum { - ACTION_MAKE_VSOCK, - ACTION_RM_VSOCK, -} arg_action = ACTION_MAKE_VSOCK; - static char *arg_issue_path = NULL; static bool arg_issue_stdout = false; STATIC_DESTRUCTOR_REGISTER(arg_issue_path, freep); +static int acquire_cid(unsigned *ret_cid) { + int r; + + assert(ret_cid); + + Virtualization v = detect_virtualization(); + if (v < 0) + return log_error_errno(v, "Failed to detect if we run in a VM: %m"); + if (!VIRTUALIZATION_IS_VM(v)) { + /* NB: if we are running in a container inside a VM, then we'll *not* do AF_VSOCK stuff */ + log_debug("Not running in a VM, not creating issue file."); + *ret_cid = 0; + return 0; + } + + r = vsock_open_or_warn(/* ret= */ NULL); + if (r <= 0) + return r; + + return vsock_get_local_cid_or_warn(ret_cid); +} + +VERB_NOARG(verb_make_vsock, "make-vsock", + "Generate the issue file"); +static int verb_make_vsock(int argc, char *argv[], uintptr_t _data, void *_userdata) { + unsigned cid; + int r; + + r = acquire_cid(&cid); + if (r < 0) + return r; + if (r == 0) { + log_debug("Not running in a VSOCK enabled VM, skipping."); + return 0; + } + + _cleanup_(unlink_and_freep) char *t = NULL; + _cleanup_(fclosep) FILE *f = NULL; + FILE *out; + + if (arg_issue_path) { + r = mkdir_parents(arg_issue_path, 0755); + if (r < 0) + return log_error_errno(r, "Failed to create parent directories of '%s': %m", arg_issue_path); + + r = fopen_tmpfile_linkable(arg_issue_path, O_WRONLY|O_CLOEXEC, &t, &f); + if (r < 0) + return log_error_errno(r, "Failed to create '%s': %m", arg_issue_path); + + out = f; + } else + out = stdout; + + fprintf(out, + "Try contacting this VM's SSH server via 'ssh vsock%%%u' from host.\n" + "\n", cid); + + if (f) { + if (fchmod(fileno(f), 0644) < 0) + return log_error_errno(errno, "Failed to adjust access mode of '%s': %m", arg_issue_path); + + r = flink_tmpfile(f, t, arg_issue_path, LINK_TMPFILE_REPLACE); + if (r < 0) + return log_error_errno(r, "Failed to move '%s' into place: %m", arg_issue_path); + } + + return 0; +} + +VERB_NOARG(verb_rm_vsock, "rm-vsock", + "Remove the issue file"); +static int verb_rm_vsock(int argc, char *argv[], uintptr_t _data, void *_userdata) { + if (arg_issue_path) { + if (unlink(arg_issue_path) < 0) { + if (errno != ENOENT) + return log_error_errno(errno, "Failed to remove '%s': %m", arg_issue_path); + + log_debug_errno(errno, "File '%s' does not exist, no operation executed.", arg_issue_path); + } else + log_debug("Successfully removed '%s'.", arg_issue_path); + } else + log_notice("STDOUT selected for issue file, not removing."); + + return 0; +} + static int help(void) { _cleanup_free_ char *link = NULL; - _cleanup_(table_unrefp) Table *options = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-ssh-issue", "1", &link); if (r < 0) return log_oom(); + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + r = option_parser_get_help_table(&options); if (r < 0) return r; - printf("%1$s [OPTIONS...] --make-vsock\n" - "%1$s [OPTIONS...] --rm-vsock\n" - "\n%2$sCreate ssh /run/issue.d/ file reporting VSOCK address.%3$s\n\n", + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] COMMAND\n" + "\n%sCreate/remove ssh /run/issue.d/ file reporting VSOCK address.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - ansi_highlight(), - ansi_normal()); + ansi_highlight(), ansi_normal(), + ansi_underline(), ansi_normal()); + + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + printf("\n%sOptions:%s\n", + ansi_underline(), ansi_normal()); r = table_print_or_warn(options); if (r < 0) @@ -58,15 +154,17 @@ static int help(void) { return 0; } -static int parse_argv(int argc, char *argv[]) { +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); OptionParser state = { argc, argv }; - const char *arg; + const Option *opt; + const char *arg, *verb = NULL; int r; - FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + FOREACH_OPTION_FULL(&state, c, &opt, &arg, /* on_error= */ return c) switch (c) { OPTION_COMMON_HELP: @@ -75,12 +173,9 @@ static int parse_argv(int argc, char *argv[]) { OPTION_COMMON_VERSION: return version(); - OPTION_LONG("make-vsock", NULL, "Generate the issue file (default)"): - arg_action = ACTION_MAKE_VSOCK; - break; - - OPTION_LONG("rm-vsock", NULL, "Remove the issue file"): - arg_action = ACTION_RM_VSOCK; + OPTION_LONG("make-vsock", NULL, /* help= */ NULL): {} + OPTION_LONG("rm-vsock", NULL, /* help= */ NULL): + verb = opt->long_code; break; OPTION_LONG("issue-path", "PATH", @@ -105,104 +200,32 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); } - return 1; -} - -static int acquire_cid(unsigned *ret_cid) { - int r; - - assert(ret_cid); - - Virtualization v = detect_virtualization(); - if (v < 0) - return log_error_errno(v, "Failed to detect if we run in a VM: %m"); - if (!VIRTUALIZATION_IS_VM(v)) { - /* NB: if we are running in a container inside a VM, then we'll *not* do AF_VSOCK stuff */ - log_debug("Not running in a VM, not creating issue file."); - *ret_cid = 0; - return 0; - } - - r = vsock_open_or_warn(/* ret= */ NULL); - if (r <= 0) - return r; + char **args; + if (verb) { + if (option_parser_get_n_args(&state) > 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid use of compat option --make-vsock/--rm-vsock."); + log_warning("Options --make-vsock/--rm-vsock have been replaced by make-vsock/rm-vsock verbs."); + args = strv_new(verb); + } else + args = strv_copy(option_parser_get_args(&state)); + if (!args) + return log_oom(); - return vsock_get_local_cid_or_warn(ret_cid); + *ret_args = args; + return 1; } static int run(int argc, char* argv[]) { + _cleanup_strv_free_ char **args = NULL; int r; log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - switch (arg_action) { - case ACTION_MAKE_VSOCK: { - unsigned cid; - - r = acquire_cid(&cid); - if (r < 0) - return r; - if (r == 0) { - log_debug("Not running in a VSOCK enabled VM, skipping."); - break; - } - - _cleanup_(unlink_and_freep) char *t = NULL; - _cleanup_(fclosep) FILE *f = NULL; - FILE *out; - - if (arg_issue_path) { - r = mkdir_parents(arg_issue_path, 0755); - if (r < 0) - return log_error_errno(r, "Failed to create parent directories of '%s': %m", arg_issue_path); - - r = fopen_tmpfile_linkable(arg_issue_path, O_WRONLY|O_CLOEXEC, &t, &f); - if (r < 0) - return log_error_errno(r, "Failed to create '%s': %m", arg_issue_path); - - out = f; - } else - out = stdout; - - fprintf(out, - "Try contacting this VM's SSH server via 'ssh vsock%%%u' from host.\n" - "\n", cid); - - if (f) { - if (fchmod(fileno(f), 0644) < 0) - return log_error_errno(errno, "Failed to adjust access mode of '%s': %m", arg_issue_path); - - r = flink_tmpfile(f, t, arg_issue_path, LINK_TMPFILE_REPLACE); - if (r < 0) - return log_error_errno(r, "Failed to move '%s' into place: %m", arg_issue_path); - } - - break; - } - - case ACTION_RM_VSOCK: - if (arg_issue_path) { - if (unlink(arg_issue_path) < 0) { - if (errno != ENOENT) - return log_error_errno(errno, "Failed to remove '%s': %m", arg_issue_path); - - log_debug_errno(errno, "File '%s' does not exist, no operation executed.", arg_issue_path); - } else - log_debug("Successfully removed '%s'.", arg_issue_path); - } else - log_notice("STDOUT selected for issue file, not removing."); - - break; - - default: - assert_not_reached(); - } - - return 0; + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); From 376d5ccba116673c3f1f469a02b25aca247e9613 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Fri, 17 Apr 2026 11:52:41 +0200 Subject: [PATCH 1060/1296] dissect-image: fix path building for non-raw images If the passed in image path didn't end with .raw, we'd return an empty string + suffix instead of the intended image + suffix path. Follow-up for 89e62e0bd3cb72915b705b5e2da1834e4d8aea9f. --- src/shared/dissect-image.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index e33e78d31026d..2293cd5acbb6a 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -3728,7 +3728,7 @@ static char *build_auxiliary_path(const char *image, const char *suffix) { e = endswith(image, ".raw"); if (!e) - return strjoin(e, suffix); + return strjoin(image, suffix); n = new(char, e - image + strlen(suffix) + 1); if (!n) From 13b6ba97753cabeda9bf25ec4c64502c9bf9aa2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 17 Apr 2026 12:19:40 +0200 Subject: [PATCH 1061/1296] basic/iovec-wrapper: fix potential memleak on error Also reorder the functions in the call stack order. --- src/basic/iovec-wrapper.c | 22 +++++++++++----------- src/basic/iovec-wrapper.h | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/basic/iovec-wrapper.c b/src/basic/iovec-wrapper.c index 408985763eaeb..bd4b9a1040243 100644 --- a/src/basic/iovec-wrapper.c +++ b/src/basic/iovec-wrapper.c @@ -41,17 +41,6 @@ int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len) { return 0; } -int iovw_append(struct iovec_wrapper *iovw, const void *data, size_t len) { - if (len == 0) - return 0; - - void *c = memdup(data, len); - if (!c) - return -ENOMEM; - - return iovw_put(iovw, c, len); -} - int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len) { /* Move data into iovw or free on error */ int r; @@ -63,6 +52,17 @@ int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len) { return r; } +int iovw_append(struct iovec_wrapper *iovw, const void *data, size_t len) { + if (len == 0) + return 0; + + void *c = memdup(data, len); + if (!c) + return -ENOMEM; + + return iovw_consume(iovw, c, len); +} + int iovw_put_string_field_full(struct iovec_wrapper *iovw, bool replace, const char *field, const char *value) { _cleanup_free_ char *x = NULL; int r; diff --git a/src/basic/iovec-wrapper.h b/src/basic/iovec-wrapper.h index 6f0c9af43c9b5..eaa859af06d4a 100644 --- a/src/basic/iovec-wrapper.h +++ b/src/basic/iovec-wrapper.h @@ -12,8 +12,8 @@ void iovw_done_free(struct iovec_wrapper *iovw); void iovw_done(struct iovec_wrapper *iovw); int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len); -int iovw_append(struct iovec_wrapper *iovw, const void *data, size_t len); int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len); +int iovw_append(struct iovec_wrapper *iovw, const void *data, size_t len); static inline bool iovw_isempty(const struct iovec_wrapper *iovw) { return !iovw || iovw->count == 0; From 45c20448d5e1ee751b214e4d79179121801a35cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 17 Apr 2026 12:25:58 +0200 Subject: [PATCH 1062/1296] basic/iovec-wrapper: use iovw_append in one more place --- src/basic/iovec-wrapper.c | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/basic/iovec-wrapper.c b/src/basic/iovec-wrapper.c index bd4b9a1040243..a4604f9a3cbb0 100644 --- a/src/basic/iovec-wrapper.c +++ b/src/basic/iovec-wrapper.c @@ -126,7 +126,6 @@ size_t iovw_size(const struct iovec_wrapper *iovw) { } int iovw_append_iovw(struct iovec_wrapper *target, const struct iovec_wrapper *source) { - size_t original_count; int r; assert(target); @@ -136,18 +135,10 @@ int iovw_append_iovw(struct iovec_wrapper *target, const struct iovec_wrapper *s if (iovw_isempty(source)) return 0; - original_count = target->count; + size_t original_count = target->count; FOREACH_ARRAY(iovec, source->iovec, source->count) { - void *dup; - - dup = memdup(iovec->iov_base, iovec->iov_len); - if (!dup) { - r = -ENOMEM; - goto rollback; - } - - r = iovw_consume(target, dup, iovec->iov_len); + r = iovw_append(target, iovec->iov_base, iovec->iov_len); if (r < 0) goto rollback; } From 5853d0a53378ed973d8c006531846717ae55090a Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Thu, 16 Apr 2026 15:55:10 +0200 Subject: [PATCH 1063/1296] scsi_id: use safe_atoi() instead of plain atoi() --- src/udev/scsi_id/scsi_id.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/udev/scsi_id/scsi_id.c b/src/udev/scsi_id/scsi_id.c index 6a31f9c4c8ebd..ba7a7fe5f522d 100644 --- a/src/udev/scsi_id/scsi_id.c +++ b/src/udev/scsi_id/scsi_id.c @@ -15,6 +15,7 @@ #include "extract-word.h" #include "fd-util.h" #include "fileio.h" +#include "parse-util.h" #include "scsi_id.h" #include "string-util.h" #include "strv.h" @@ -234,7 +235,7 @@ static void help(void) { static int set_options(int argc, char **argv, char *maj_min_dev) { - int option; + int option, r; /* * optind is a global extern used by getopt. Since we can call @@ -279,7 +280,11 @@ static int set_options(int argc, char **argv, break; case 's': - sg_version = atoi(optarg); + r = safe_atoi(optarg, &sg_version); + if (r < 0) + return log_error_errno(r, + "Invalid SG version '%s'", + optarg); if (sg_version < 3 || sg_version > 4) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown SG version '%s'", From 4977c00e2a7efda3a7be2136fe2fed4de6777565 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Thu, 16 Apr 2026 15:48:16 +0200 Subject: [PATCH 1064/1296] udev-builtin: add a couple of asserts --- src/udev/udev-builtin-usb_id.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/udev/udev-builtin-usb_id.c b/src/udev/udev-builtin-usb_id.c index 61250b7072fe0..cfbea9d9819dc 100644 --- a/src/udev/udev-builtin-usb_id.c +++ b/src/udev/udev-builtin-usb_id.c @@ -22,6 +22,9 @@ static void set_usb_iftype(char *to, int if_class_num, size_t len) { const char *type = "generic"; + assert(to); + assert(len > 0); + switch (if_class_num) { case 1: type = "audio"; @@ -71,6 +74,8 @@ static int set_usb_mass_storage_ifsubtype(char *to, const char *from, size_t len int type_num = 0; const char *type = "generic"; + assert(to); + if (safe_atoi(from, &type_num) >= 0) switch (type_num) { case 1: /* RBC devices */ @@ -98,6 +103,8 @@ static void set_scsi_type(char *to, const char *from, size_t len) { unsigned type_num; const char *type = "generic"; + assert(to); + if (safe_atou(from, &type_num) >= 0) switch (type_num) { case 0: @@ -143,6 +150,10 @@ static int dev_if_packed_info(sd_device *dev, char *ifs_str, size_t len) { uint8_t iInterface; } _packed_; + assert(dev); + assert(ifs_str); + assert(len >= 2); + r = sd_device_get_syspath(dev, &syspath); if (r < 0) return r; From 50d4e25f37fa8860ff3a0457f36362d6953fbfd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20L=C3=BCke?= Date: Thu, 16 Apr 2026 15:10:02 +0900 Subject: [PATCH 1065/1296] vmspawn: catch unsupported growing of qcow2 images For qcow2 images it's not enough to grow the file. Since we probably don't want to shell out to qemu-img either let's just error out to make the user aware that growing needs to be done manually. --- src/vmspawn/vmspawn.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 548a083fac5c6..c970cd4ba631b 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -4001,6 +4001,10 @@ static int verify_arguments(void) { log_warning("--grow-image has no effect with --image-disk-type=scsi-cd (CD-ROMs are read-only)."); } + if (arg_grow_image && arg_image_format == IMAGE_FORMAT_QCOW2) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--grow-image is not supported for qcow2 images, use 'qemu-img resize FILE SIZE'."); + return 0; } From d651df8283ce62d2f03f8abe5cd4798bd1b8bf58 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Fri, 17 Apr 2026 14:55:29 +0200 Subject: [PATCH 1066/1296] nspawn: avoid passing NULL to log_syntax() If range is NULL (i.e. when PrivateUsers= doesn't contain ':'), both later error paths will then pass NULL to log_syntax(): ~# cat foo.nspawn [Exec] PrivateUsers=9999999999999999999 ~# SYSTEMD_LOG_LEVEL=debug systemd-nspawn -D foo |& grep foo.nspawn Found settings file: /root/foo.nspawn /root/foo.nspawn:2: UID/GID shift invalid, ignoring: (null) or ~# cat foo.nspawn [Exec] PrivateUsers=4294967294 ~ # SYSTEMD_LOG_LEVEL=debug systemd-nspawn -D foo |& grep foo.nspawn Found settings file: /root/foo.nspawn /root/foo.nspawn:2: UID/GID shift and range combination invalid, ignoring: (null) Let's just use rvalue in both of these cases instead. --- src/nspawn/nspawn-settings.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c index 9abd5024a5049..30c603394c1fe 100644 --- a/src/nspawn/nspawn-settings.c +++ b/src/nspawn/nspawn-settings.c @@ -720,12 +720,12 @@ int config_parse_private_users( r = parse_uid(shift, &sh); if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, "UID/GID shift invalid, ignoring: %s", range); + log_syntax(unit, LOG_WARNING, filename, line, r, "UID/GID shift invalid, ignoring: %s", rvalue); return 0; } if (!userns_shift_range_valid(sh, rn)) { - log_syntax(unit, LOG_WARNING, filename, line, 0, "UID/GID shift and range combination invalid, ignoring: %s", range); + log_syntax(unit, LOG_WARNING, filename, line, 0, "UID/GID shift and range combination invalid, ignoring: %s", rvalue); return 0; } From 2e79f8aa4103665d573da6d19def76d23139cdb9 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 16 Apr 2026 23:50:07 +0100 Subject: [PATCH 1067/1296] systemctl: add --kernel-cmdline= argument Allows appending kernel command line arguments, like kexec-tool does. This is especially needed for the integration tests, as mkosi adds a bunch of options that are needed for the test suite to work, and it breaks without them. --- man/systemctl.xml | 15 +++++++++++++++ shell-completion/bash/systemctl.in | 2 +- src/systemctl/systemctl-start-special.c | 6 ++++++ src/systemctl/systemctl.c | 21 +++++++++++++++++++++ src/systemctl/systemctl.h | 1 + 5 files changed, 44 insertions(+), 1 deletion(-) diff --git a/man/systemctl.xml b/man/systemctl.xml index 2a542ba466a0d..8b8d1065556f2 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -2795,6 +2795,21 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err + + + + + When used with kexec, append the specified string to the kernel command + line options of the kexec kernel. The kernel command line is taken from the boot loader entry of + the currently booted kernel (as selected automatically when no kexec kernel is preloaded, see + kexec above). This string is appended verbatim, separated from the existing + options by a single space. systemctl kexec will fail if this option is specified + when a kexec kernel is already loaded. + + + + + diff --git a/shell-completion/bash/systemctl.in b/shell-completion/bash/systemctl.in index c34c7fb10ebc2..9f52115b7328a 100644 --- a/shell-completion/bash/systemctl.in +++ b/shell-completion/bash/systemctl.in @@ -137,7 +137,7 @@ _systemctl () { --show-transaction -T --mkdir --read-only' [ARG]='--host -H --kill-whom --property -p -P --signal -s --type -t --state --job-mode --root --preset-mode -n --lines -o --output -M --machine --message --timestamp --check-inhibitors --what - --image --boot-loader-menu --boot-loader-entry --reboot-argument --drop-in' + --image --boot-loader-menu --boot-loader-entry --reboot-argument --kernel-cmdline --drop-in' ) if __contains_word "--user" ${COMP_WORDS[*]}; then diff --git a/src/systemctl/systemctl-start-special.c b/src/systemctl/systemctl-start-special.c index b7d10eb891d6c..fd38c678ea69a 100644 --- a/src/systemctl/systemctl-start-special.c +++ b/src/systemctl/systemctl-start-special.c @@ -28,6 +28,9 @@ static int load_kexec_kernel(void) { int r; if (kexec_loaded()) { + if (arg_kernel_cmdline) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--kernel-cmdline= specified but kexec kernel already loaded"); log_debug("Kexec kernel already loaded."); return 0; } @@ -78,6 +81,9 @@ static int load_kexec_kernel(void) { if (!options) return log_oom(); + if (!isempty(arg_kernel_cmdline) && !strextend_with_separator(&options, " ", arg_kernel_cmdline)) + return log_oom(); + log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, "%s %s kernel=\"%s\" cmdline=\"%s\"%s%s%s", arg_dry_run ? "Would call" : "Calling", diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 3d3ed98fcd2ed..5c9a24b119ade 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -27,6 +27,7 @@ #include "systemctl-compat-shutdown.h" #include "systemctl-logind.h" #include "time-util.h" +#include "utf8.h" char **arg_types = NULL; char **arg_states = NULL; @@ -68,6 +69,7 @@ char *arg_image = NULL; usec_t arg_when = 0; bool arg_stdin = false; const char *arg_reboot_argument = NULL; +char *arg_kernel_cmdline = NULL; enum action arg_action = ACTION_SYSTEMCTL; BusTransport arg_transport = BUS_TRANSPORT_LOCAL; const char *arg_host = NULL; @@ -98,6 +100,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_kill_whom, unsetp); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_reboot_argument, unsetp); +STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline, freep); STATIC_DESTRUCTOR_REGISTER(arg_host, unsetp); STATIC_DESTRUCTOR_REGISTER(arg_boot_loader_entry, unsetp); STATIC_DESTRUCTOR_REGISTER(arg_clean_what, strv_freep); @@ -305,6 +308,9 @@ static int systemctl_help(void) { " Boot into a specific boot loader entry on next boot\n" " --reboot-argument=ARG\n" " Specify argument string to pass to reboot()\n" + " --kernel-cmdline=CMDLINE\n" + " Append to the kernel command line when loading the\n" + " kernel from the booted boot loader entry\n" " --plain Print unit dependencies as a list instead of a tree\n" " --timestamp=FORMAT Change format of printed timestamps (pretty, unix,\n" " us, utc, us+utc)\n" @@ -434,6 +440,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { ARG_WAIT, ARG_WHAT, ARG_REBOOT_ARG, + ARG_KERNEL_CMDLINE, ARG_TIMESTAMP_STYLE, ARG_READ_ONLY, ARG_MKDIR, @@ -505,6 +512,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { { "show-transaction", no_argument, NULL, 'T' }, { "what", required_argument, NULL, ARG_WHAT }, { "reboot-argument", required_argument, NULL, ARG_REBOOT_ARG }, + { "kernel-cmdline", required_argument, NULL, ARG_KERNEL_CMDLINE }, { "timestamp", required_argument, NULL, ARG_TIMESTAMP_STYLE }, { "read-only", no_argument, NULL, ARG_READ_ONLY }, { "mkdir", no_argument, NULL, ARG_MKDIR }, @@ -960,6 +968,19 @@ static int systemctl_parse_argv(int argc, char *argv[]) { arg_reboot_argument = optarg; break; + case ARG_KERNEL_CMDLINE: + if (!utf8_is_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--kernel-cmdline= argument is not valid UTF-8: %s", optarg); + if (string_has_cc(optarg, NULL)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--kernel-cmdline= argument contains control characters: %s", optarg); + + r = free_and_strdup_warn(&arg_kernel_cmdline, optarg); + if (r < 0) + return r; + break; + case ARG_TIMESTAMP_STYLE: if (streq(optarg, "help")) return DUMP_STRING_TABLE(timestamp_style, TimestampStyle, _TIMESTAMP_STYLE_MAX); diff --git a/src/systemctl/systemctl.h b/src/systemctl/systemctl.h index 8d4b1a614da28..bc52e96fe3bfd 100644 --- a/src/systemctl/systemctl.h +++ b/src/systemctl/systemctl.h @@ -76,6 +76,7 @@ extern char *arg_image; extern usec_t arg_when; extern bool arg_stdin; extern const char *arg_reboot_argument; +extern char *arg_kernel_cmdline; extern enum action arg_action; extern BusTransport arg_transport; extern const char *arg_host; From e6fbf51fd69fa9cc7bd8998bc17dca8d05d0293a Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 17 Apr 2026 14:16:21 +0100 Subject: [PATCH 1068/1296] Revert "ci: Switch PR review workflow to Opus 4.7 via Mantle endpoint" This reverts commit 011233858520fe607eeff29cfc92d40cf71a6002. --- .github/workflows/claude-review.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index aacada555e8eb..3829313cf97d8 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -192,7 +192,6 @@ jobs: env: CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: "1" CLAUDE_CODE_USE_BEDROCK: "1" - CLAUDE_CODE_USE_MANTLE: "1" run: | mkdir -p ~/.claude @@ -370,7 +369,7 @@ jobs: PROMPT claude \ - --model anthropic.claude-opus-4-7 \ + --model us.anthropic.claude-opus-4-6-v1 \ --effort max \ --max-turns 200 \ --setting-sources user \ From c859d41232b3bcff9f9b01f1281c1b202f15d0b2 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Fri, 17 Apr 2026 15:51:40 +0200 Subject: [PATCH 1069/1296] namespace: don't log misleading error in the r > 0 path fd_is_fs_type() returns < 0 for errors, 0 for false, and > 0 for true, so in the r > branch we'd most likely report EPERM together with the error message which is misleading. --- src/core/namespace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/namespace.c b/src/core/namespace.c index ff4592b8b90cd..1412ccce8bbcf 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -3662,7 +3662,7 @@ static int unpeel_get_fd(const char *mount_path, int *ret_fd) { _exit(EXIT_FAILURE); } if (r > 0) { - log_debug_errno(r, "'%s' is still an overlay after opening mount tree: %m", mount_path); + log_debug("'%s' is still an overlay after opening mount tree", mount_path); _exit(EXIT_FAILURE); } From c83c21a05450cb17f7a7682e687ba4d3fc794fc3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 17 Apr 2026 15:12:05 +0200 Subject: [PATCH 1070/1296] man: drop redundant word from varlinkctl man page --- man/varlinkctl.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/varlinkctl.xml b/man/varlinkctl.xml index adf26b8fe6150..72f1983c9ef29 100644 --- a/man/varlinkctl.xml +++ b/man/varlinkctl.xml @@ -64,7 +64,7 @@ varlinkctl OPTIONS - --exec call + --exec call ADDRESS METHOD From a93fa2c6b45c6b18f0cb3a16a793edb71ae6b444 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 17 Apr 2026 12:19:58 +0200 Subject: [PATCH 1071/1296] boot: never auto-boot a menu entry with the non-default profile When figuring out which menu entry to pick by default, let's not consider any with a profile number > 0. This reflects that fact that additional profiles are generally used for debug/recovery/factory-reset/storage target mode boots, and those should never be auto-selected. Hence do a simple check: if profile != 0, simply do not consider the entry as a default. We might eventually want to beef this up, and add a property one can set in the profile metadata that controls this behaviour, but for now let's just do a this simple fix. --- src/boot/boot.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/boot/boot.c b/src/boot/boot.c index 2492f474b405b..34ba4f2b7e4bf 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -1918,11 +1918,15 @@ static void config_select_default_entry(Config *config) { } /* select the first suitable entry */ - for (i = 0; i < config->n_entries; i++) + for (i = 0; i < config->n_entries; i++) { + if (config->entries[i]->profile > 0) + continue; /* For now, never select any non-default profile */ + if (LOADER_TYPE_MAY_AUTO_SELECT(config->entries[i]->type)) { config->idx_default = i; return; } + } /* If no configured entry to select from was found, enable the menu. */ config->idx_default = 0; From c40f254cca5e96b876b90e20ced69c33115940c3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 17 Apr 2026 14:58:46 +0200 Subject: [PATCH 1072/1296] boot: gracefully handle LoadFile() implementations that return EFI_SUCCESS with a NULL buffer LoadFile() with a NULL buffer is supposed to return the file size without acquiring the data and return EFI_BUFFER_TOO_SMALL. However it appears some firmware returns EFI_SUCCESS in case the file is empty, i.e. the file size returned is zero. And I guess that's even fine. Let's handle this gracefully hence. --- src/boot/boot.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/boot/boot.c b/src/boot/boot.c index 34ba4f2b7e4bf..df5ce31fa3a3f 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -2719,7 +2719,12 @@ static EFI_STATUS expand_path( if (IN_SET(err, EFI_NOT_FOUND, EFI_INVALID_PARAMETER)) continue; /* Skip over LoadFile() handles that after all don't consider themselves * appropriate for this kind of path */ - if (err != EFI_BUFFER_TOO_SMALL) { + if (!IN_SET(err, EFI_SUCCESS, EFI_BUFFER_TOO_SMALL)) { + /* NB: firmwares are supposed to return EFI_BUFFER_TOO_SMALL whenever we pass a NULL + * buffer. But for compatibility with quirky firmwares let's be lenient for the + * special case of a zero sized file: the firmware might return EFI_SUCCESS here and + * initialize the size to zero, as a buffer is not actually necessary for that + * case. */ log_warning_status(err, "Failed to get file via LoadFile() protocol, ignoring: %m"); continue; } From 023f88a6ab76b9784e9b6c6396227f1490c1a8c2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 17 Apr 2026 14:56:15 +0200 Subject: [PATCH 1073/1296] parse-util: add safe_atou64_full() --- src/basic/parse-util.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h index d92577c0fbeff..0ee4da2126b76 100644 --- a/src/basic/parse-util.h +++ b/src/basic/parse-util.h @@ -83,6 +83,11 @@ static inline int safe_atou64(const char *s, uint64_t *ret_u) { return safe_atollu(s, (unsigned long long*) ret_u); } +static inline int safe_atou64_full(const char *s, unsigned base, uint64_t *ret_u) { + assert_cc(sizeof(uint64_t) == sizeof(unsigned long long)); + return safe_atollu_full(s, base, (unsigned long long*) ret_u); +} + static inline int safe_atoi64(const char *s, int64_t *ret_i) { assert_cc(sizeof(int64_t) == sizeof(long long)); return safe_atolli(s, (long long*) ret_i); From 2561159d397d8cb70e14d7e2b4a7b722a02854d6 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 15 Apr 2026 16:05:37 +0200 Subject: [PATCH 1074/1296] string-util: add new strrstr_no_case() call --- src/basic/string-util.c | 11 +++++++++++ src/basic/string-util.h | 4 ++++ src/test/test-string-util.c | 23 +++++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/src/basic/string-util.c b/src/basic/string-util.c index 9b63516ce0908..c3298bc8eeebd 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -1530,6 +1530,17 @@ char* strrstr_internal(const char *haystack, const char *needle) { return NULL; } +char* strrstr_no_case_internal(const char *haystack, const char *needle) { + if (!haystack || !needle) + return NULL; + + for (const char *p = strchr(haystack, 0); p > haystack; p--) + if (startswith_no_case(p, needle)) + return (char*) p; + + return startswith_no_case(haystack, needle) ? (char*) haystack : NULL; +} + size_t str_common_prefix(const char *a, const char *b) { assert(a); assert(b); diff --git a/src/basic/string-util.h b/src/basic/string-util.h index 5ab4dd9016dd2..65fe5072b3092 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -312,4 +312,8 @@ char* strrstr_internal(const char *haystack, const char *needle) _pure_; #define strrstr(haystack, needle) \ const_generic(haystack, strrstr_internal(haystack, needle)) +char* strrstr_no_case_internal(const char *haystack, const char *needle) _pure_; +#define strrstr_no_case(haystack, needle) \ + const_generic(haystack, strrstr_no_case_internal(haystack, needle)) + size_t str_common_prefix(const char *a, const char *b) _pure_; diff --git a/src/test/test-string-util.c b/src/test/test-string-util.c index 8490f34f696eb..5707569e7e17d 100644 --- a/src/test/test-string-util.c +++ b/src/test/test-string-util.c @@ -535,6 +535,29 @@ TEST(endswith_no_case) { assert_se(!endswith_no_case("foobar", "FOOBARFOOFOO")); } +TEST(strrstr_no_case) { + ASSERT_STREQ(strrstr_no_case("fooBARfoobar", "bar"), "bar"); + ASSERT_STREQ(strrstr_no_case("fooBARfoobar", "BAR"), "bar"); + ASSERT_STREQ(strrstr_no_case("fooBARfoobar", "bAR"), "bar"); + ASSERT_STREQ(strrstr_no_case("fooBARfoobar", "FOO"), "foobar"); + ASSERT_STREQ(strrstr_no_case("fooBARfoobar", "foo"), "foobar"); + ASSERT_STREQ(strrstr_no_case("fooBARfoobar", "FoO"), "foobar"); + ASSERT_STREQ(strrstr_no_case("aXaXa", "x"), "Xa"); + ASSERT_STREQ(strrstr_no_case("aXaXa", "X"), "Xa"); + ASSERT_STREQ(strrstr_no_case("xHello", "hello"), "Hello"); + ASSERT_STREQ(strrstr_no_case("Hello", "l"), "lo"); + ASSERT_STREQ(strrstr_no_case("Hello", ""), ""); + ASSERT_STREQ(strrstr_no_case("", ""), ""); + ASSERT_STREQ(strrstr_no_case("FOO", "foo"), "FOO"); + ASSERT_STREQ(strrstr_no_case("hello", "hello"), "hello"); + ASSERT_STREQ(strrstr_no_case("X", "x"), "X"); + + ASSERT_NULL(strrstr_no_case("hello", "xyz")); + ASSERT_NULL(strrstr_no_case("", "x")); + ASSERT_NULL(strrstr_no_case(NULL, "x")); + ASSERT_NULL(strrstr_no_case("x", NULL)); +} + TEST(delete_chars) { char *s, input[] = " hello, waldo. abc"; From d7b6d89d903758f60a9ead5349257f59b3184955 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 15 Apr 2026 16:06:04 +0200 Subject: [PATCH 1075/1296] string-util: add minor optimization to strrstr() --- src/basic/string-util.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/basic/string-util.c b/src/basic/string-util.c index c3298bc8eeebd..6168855a7579c 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -1519,13 +1519,17 @@ char* strrstr_internal(const char *haystack, const char *needle) { /* Special case: for the empty string we return the very last possible occurrence, i.e. *after* the * last char, not before. */ - if (*needle == 0) + if (needle[0] == 0) return (char*) strchr(haystack, 0); + /* Special case: for single character strings, just use optimized strrchr() */ + if (needle[1] == 0) + return (char*) strrchr(haystack, needle[0]); + for (const char *p = strstr(haystack, needle), *q; p; p = q) { q = strstr(p + 1, needle); if (!q) - return (char *) p; + return (char*) p; } return NULL; } From 7e8882a96fdfcfd863beffadac494038387453c2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 13 Apr 2026 17:57:10 +0200 Subject: [PATCH 1076/1296] stat-util: add vfs_free_bytes() The casts and the right fields to multiply aren't entirely trivial, let's add a helper for it. --- src/basic/stat-util.c | 17 +++++++++++++++++ src/basic/stat-util.h | 2 ++ src/coredump/coredump-submit.c | 6 +++--- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 06634b7df60c8..e0dc59a863bad 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -898,3 +898,20 @@ mode_t inode_type_from_string(const char *s) { return MODE_INVALID; } + +int vfs_free_bytes(int fd, uint64_t *ret) { + assert(fd >= 0); + assert(ret); + + /* Safely returns the current available disk space (for root, i.e. including any space reserved for + * root) of the disk referenced by the fd, converted to bytes. */ + + struct statvfs sv; + if (fstatvfs(fd, &sv) < 0) + return -errno; + + if (!MUL_SAFE(ret, (uint64_t) sv.f_frsize, (uint64_t) sv.f_bfree)) + return -ERANGE; + + return 0; +} diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index ec04a2b80cd08..de9ee03f44034 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -161,3 +161,5 @@ static inline bool inode_type_can_hardlink(mode_t m) { * type). */ return IN_SET(m & S_IFMT, S_IFSOCK, S_IFLNK, S_IFREG, S_IFBLK, S_IFCHR, S_IFIFO); } + +int vfs_free_bytes(int fd, uint64_t *ret); diff --git a/src/coredump/coredump-submit.c b/src/coredump/coredump-submit.c index 6ce03cdec0770..42d92a32e9c6d 100644 --- a/src/coredump/coredump-submit.c +++ b/src/coredump/coredump-submit.c @@ -305,7 +305,6 @@ static int save_external_coredump( if (storage_on_tmpfs && config->compress) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; uint64_t cgroup_limit = UINT64_MAX; - struct statvfs sv; /* If we can't get the cgroup limit, just ignore it, but don't fail, * try anyway with the config settings. */ @@ -335,8 +334,9 @@ static int save_external_coredump( /* tmpfs might get full quickly, so check the available space too. But don't worry about * errors here, failing to access the storage location will be better logged when writing to * it. */ - if (fstatvfs(fd, &sv) >= 0) - max_size = MIN((uint64_t)sv.f_frsize * (uint64_t)sv.f_bfree, max_size); + uint64_t free_bytes; + if (vfs_free_bytes(fd, &free_bytes) >= 0) + max_size = MIN(free_bytes, max_size); /* Impose a lower minimum, otherwise we will miss the basic headers. */ max_size = MAX(PROCESS_SIZE_MIN, max_size); /* Ensure we can always switch to compressing on the fly in case we are running out of space From 7756f0434cbd0a100d422282294ad4cb81bf01f1 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 10 Apr 2026 08:02:14 +0900 Subject: [PATCH 1077/1296] network/json: fix error handling --- src/network/networkd-json.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/network/networkd-json.c b/src/network/networkd-json.c index d109fdc473201..0c3bb5b8fb4bb 100644 --- a/src/network/networkd-json.c +++ b/src/network/networkd-json.c @@ -1420,13 +1420,12 @@ static int dhcp_client_private_options_append_json(Link *link, sd_json_variant * return 0; LIST_FOREACH(options, option, link->dhcp_lease->private_options) { - r = sd_json_variant_append_arraybo( &array, SD_JSON_BUILD_PAIR_UNSIGNED("Option", option->tag), SD_JSON_BUILD_PAIR_HEX("PrivateOptionData", option->data, option->length)); if (r < 0) - return 0; + return r; } return json_variant_set_field_non_null(v, "PrivateOptions", array); } From 1c8158412feea2f52c5dc9c144d5396b863d17f3 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 10 Apr 2026 08:02:41 +0900 Subject: [PATCH 1078/1296] network/json: drop unnecessary return value assignment --- src/network/networkd-json.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/network/networkd-json.c b/src/network/networkd-json.c index 0c3bb5b8fb4bb..04ad5cfaedd29 100644 --- a/src/network/networkd-json.c +++ b/src/network/networkd-json.c @@ -1431,23 +1431,19 @@ static int dhcp_client_private_options_append_json(Link *link, sd_json_variant * } static int dhcp_client_id_append_json(Link *link, sd_json_variant **v) { - const sd_dhcp_client_id *client_id; - const void *data; - size_t l; - int r; - assert(link); assert(v); if (!link->dhcp_client) return 0; - r = sd_dhcp_client_get_client_id(link->dhcp_client, &client_id); - if (r < 0) + const sd_dhcp_client_id *client_id; + if (sd_dhcp_client_get_client_id(link->dhcp_client, &client_id) < 0) return 0; - r = sd_dhcp_client_id_get_raw(client_id, &data, &l); - if (r < 0) + const void *data; + size_t l; + if (sd_dhcp_client_id_get_raw(client_id, &data, &l) < 0) return 0; return sd_json_variant_merge_objectbo(v, SD_JSON_BUILD_PAIR_BYTE_ARRAY("ClientIdentifier", data, l)); From f85415498cf3800bef26b1092096e658a8211e97 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 13 Apr 2026 22:50:26 +0200 Subject: [PATCH 1079/1296] bootctl: drop redundant log message If unprivileged_mode is false then verify_esp() will treat access errors like any other and log about them. Here we set it to false, hence there's no point to log a 2nd time. --- src/bootctl/bootctl-cleanup.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/bootctl/bootctl-cleanup.c b/src/bootctl/bootctl-cleanup.c index 66e0005605843..15c8d08f20d34 100644 --- a/src/bootctl/bootctl-cleanup.c +++ b/src/bootctl/bootctl-cleanup.c @@ -98,8 +98,6 @@ int verb_cleanup(int argc, char *argv[], uintptr_t _data, void *userdata) { /* ret_psize= */ NULL, /* ret_uuid= */ NULL, &esp_devid); - if (r == -EACCES) /* We really need the ESP path for this call, hence also log about access errors */ - return log_error_errno(r, "Failed to determine ESP location: %m"); if (r < 0) return r; @@ -107,8 +105,6 @@ int verb_cleanup(int argc, char *argv[], uintptr_t _data, void *userdata) { /* unprivileged_mode= */ false, /* ret_uuid= */ NULL, &xbootldr_devid); - if (r == -EACCES) - return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m"); if (r < 0) return r; From b54af7ce9a61df973fb269e6c6d5d7c217b8a0ac Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 17 Apr 2026 16:34:34 +0200 Subject: [PATCH 1080/1296] bootctl: make bootspec-util.c independent of bootctl.c This changes boot_config_load_and_select() to also take the root path as input, just like the ESP and XBOOTLDR path. This has the benefit of making the whole file independent of bootctl.c, which means we can link it into a separate test, and is preparatory work for a follow-up commit. --- src/bootctl/bootctl-cleanup.c | 2 +- src/bootctl/bootctl-status.c | 12 +++++++----- src/bootctl/bootctl-unlink.c | 1 + src/bootctl/bootspec-util.c | 6 +++--- src/bootctl/bootspec-util.h | 2 +- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/bootctl/bootctl-cleanup.c b/src/bootctl/bootctl-cleanup.c index 15c8d08f20d34..e654bca10497c 100644 --- a/src/bootctl/bootctl-cleanup.c +++ b/src/bootctl/bootctl-cleanup.c @@ -109,7 +109,7 @@ int verb_cleanup(int argc, char *argv[], uintptr_t _data, void *userdata) { return r; _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; - r = boot_config_load_and_select(&config, arg_esp_path, esp_devid, arg_xbootldr_path, xbootldr_devid); + r = boot_config_load_and_select(&config, arg_root, arg_esp_path, esp_devid, arg_xbootldr_path, xbootldr_devid); if (r < 0) return r; diff --git a/src/bootctl/bootctl-status.c b/src/bootctl/bootctl-status.c index 2d694885e176a..76e62847f36eb 100644 --- a/src/bootctl/bootctl-status.c +++ b/src/bootctl/bootctl-status.c @@ -615,9 +615,11 @@ int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { if (arg_esp_path || arg_xbootldr_path) { _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; - k = boot_config_load_and_select(&config, - arg_esp_path, esp_devid, - arg_xbootldr_path, xbootldr_devid); + k = boot_config_load_and_select( + &config, + arg_root, + arg_esp_path, esp_devid, + arg_xbootldr_path, xbootldr_devid); RET_GATHER(r, k); if (k >= 0) @@ -654,7 +656,7 @@ int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) { if (r < 0) return r; - r = boot_config_load_and_select(&config, arg_esp_path, esp_devid, arg_xbootldr_path, xbootldr_devid); + r = boot_config_load_and_select(&config, arg_root, arg_esp_path, esp_devid, arg_xbootldr_path, xbootldr_devid); if (r < 0) return r; @@ -700,7 +702,7 @@ int vl_method_list_boot_entries(sd_varlink *link, sd_json_variant *parameters, s if (r < 0) return r; - r = boot_config_load_and_select(&config, arg_esp_path, esp_devid, arg_xbootldr_path, xbootldr_devid); + r = boot_config_load_and_select(&config, arg_root, arg_esp_path, esp_devid, arg_xbootldr_path, xbootldr_devid); if (r < 0) return r; diff --git a/src/bootctl/bootctl-unlink.c b/src/bootctl/bootctl-unlink.c index 428c751f06279..b5cc839798973 100644 --- a/src/bootctl/bootctl-unlink.c +++ b/src/bootctl/bootctl-unlink.c @@ -227,6 +227,7 @@ int verb_unlink(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; r = boot_config_load_and_select( &config, + arg_root, arg_esp_path, esp_devid, arg_xbootldr_path, diff --git a/src/bootctl/bootspec-util.c b/src/bootctl/bootspec-util.c index ec3339600bb10..b96687430ca32 100644 --- a/src/bootctl/bootspec-util.c +++ b/src/bootctl/bootspec-util.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "bootctl.h" #include "bootspec-util.h" #include "devnum-util.h" #include "efi-loader.h" @@ -10,6 +9,7 @@ int boot_config_load_and_select( BootConfig *config, + const char *root, const char *esp_path, dev_t esp_devid, const char *xbootldr_path, @@ -25,7 +25,7 @@ int boot_config_load_and_select( if (r < 0) return r; - if (!arg_root) { + if (!root) { _cleanup_strv_free_ char **efi_entries = NULL; r = efi_loader_get_entries(&efi_entries); @@ -37,5 +37,5 @@ int boot_config_load_and_select( (void) boot_config_augment_from_loader(config, efi_entries, /* auto_only= */ false); } - return boot_config_select_special_entries(config, /* skip_efivars= */ !!arg_root); + return boot_config_select_special_entries(config, /* skip_efivars= */ !!root); } diff --git a/src/bootctl/bootspec-util.h b/src/bootctl/bootspec-util.h index a00e002caafdc..51dac12b9f44b 100644 --- a/src/bootctl/bootspec-util.h +++ b/src/bootctl/bootspec-util.h @@ -3,4 +3,4 @@ #include "bootspec.h" -int boot_config_load_and_select(BootConfig *config, const char *esp_path, dev_t esp_devid, const char *xbootldr_path, dev_t xbootldr_devid); +int boot_config_load_and_select(BootConfig *config, const char *root, const char *esp_path, dev_t esp_devid, const char *xbootldr_path, dev_t xbootldr_devid); From 34b9dfe1be6f16eaf702250190d0b189ec63abee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 17 Apr 2026 08:00:54 +0200 Subject: [PATCH 1081/1296] sleep: convert to "verbs", using the new option+verb macros We had verb-like dispatch, but done in a manual way. We have a fairly heavy preperation steps that wraps all operations in the same way, so we don't want to call the operation implementation functions directly. But let's use the generic verb machinery and pass the state directly using the userdata pointer and the recently added verb data pointer. --help output is substantially the same, but options are now in a new section below the verbs. --- src/sleep/sleep.c | 191 ++++++++++++++++++++++++---------------------- 1 file changed, 99 insertions(+), 92 deletions(-) diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c index 43aaede5b023a..38b38c62f8da5 100644 --- a/src/sleep/sleep.c +++ b/src/sleep/sleep.c @@ -5,7 +5,6 @@ ***/ #include -#include #include #include #include @@ -33,21 +32,22 @@ #include "exec-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "hibernate-util.h" #include "io-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "os-util.h" #include "pretty-print.h" #include "sleep-config.h" #include "special.h" #include "strv.h" #include "time-util.h" +#include "verbs.h" #define DEFAULT_HIBERNATE_DELAY_USEC_NO_BATTERY (2 * USEC_PER_HOUR) -static SleepOperation arg_operation = _SLEEP_OPERATION_INVALID; - #if ENABLE_EFI static int determine_auto_swap(sd_device *device) { _cleanup_(sd_device_unrefp) sd_device *origin = NULL; @@ -235,15 +235,16 @@ static int lock_all_homes(void) { static int execute( const SleepConfig *sleep_config, + SleepOperation main_operation, SleepOperation operation, const char *action) { const char *arguments[] = { NULL, "pre", - /* NB: we use 'arg_operation' instead of 'operation' here, as we want to communicate the overall - * operation here, not the specific one, in case of s2h. */ - sleep_operation_to_string(arg_operation), + /* NB: we use 'main_operation' instead of 'operation' here, as we want to communicate + * the overall operation here, not the specific one, in case of s2h. */ + sleep_operation_to_string(main_operation), NULL }; static const char* const dirs[] = { @@ -325,19 +326,19 @@ static int execute( log_struct(LOG_INFO, LOG_MESSAGE_ID(SD_MESSAGE_SLEEP_START_STR), LOG_MESSAGE("Performing sleep operation '%s'...", sleep_operation_to_string(operation)), - LOG_ITEM("SLEEP=%s", sleep_operation_to_string(arg_operation))); + LOG_ITEM("SLEEP=%s", sleep_operation_to_string(main_operation))); r = write_state(state_fd, sleep_config->states[operation]); if (r < 0) log_struct_errno(LOG_ERR, r, LOG_MESSAGE_ID(SD_MESSAGE_SLEEP_STOP_STR), LOG_MESSAGE("Failed to put system to sleep. System resumed again: %m"), - LOG_ITEM("SLEEP=%s", sleep_operation_to_string(arg_operation))); + LOG_ITEM("SLEEP=%s", sleep_operation_to_string(main_operation))); else log_struct(LOG_INFO, LOG_MESSAGE_ID(SD_MESSAGE_SLEEP_STOP_STR), - LOG_MESSAGE("System returned from sleep operation '%s'.", sleep_operation_to_string(arg_operation)), - LOG_ITEM("SLEEP=%s", sleep_operation_to_string(arg_operation))); + LOG_MESSAGE("System returned from sleep operation '%s'.", sleep_operation_to_string(main_operation)), + LOG_ITEM("SLEEP=%s", sleep_operation_to_string(main_operation))); arguments[1] = "post"; (void) execute_directories( @@ -396,7 +397,7 @@ static int check_wakeup_type(void) { return false; } -static int custom_timer_suspend(const SleepConfig *sleep_config) { +static int custom_timer_suspend(const SleepConfig *sleep_config, SleepOperation main_operation) { usec_t hibernate_timestamp; int r; @@ -456,7 +457,7 @@ static int custom_timer_suspend(const SleepConfig *sleep_config) { if (timerfd_settime(tfd, 0, &ts, NULL) < 0) return log_error_errno(errno, "Error setting battery estimate timer: %m"); - r = execute(sleep_config, SLEEP_SUSPEND, NULL); + r = execute(sleep_config, main_operation, SLEEP_SUSPEND, NULL); if (r < 0) return r; @@ -506,7 +507,7 @@ static int custom_timer_suspend(const SleepConfig *sleep_config) { return 1; } -static int execute_s2h(const SleepConfig *sleep_config) { +static int execute_s2h(const SleepConfig *sleep_config, SleepOperation main_operation) { _cleanup_close_ int tfd = -EBADF; usec_t hibernate_timestamp = 0; int r; @@ -559,7 +560,7 @@ static int execute_s2h(const SleepConfig *sleep_config) { } log_debug("Attempting to suspend..."); - r = execute(sleep_config, SLEEP_SUSPEND, NULL); + r = execute(sleep_config, main_operation, SLEEP_SUSPEND, NULL); if (r < 0) return r; @@ -592,7 +593,7 @@ static int execute_s2h(const SleepConfig *sleep_config) { return 0; } } else { - r = custom_timer_suspend(sleep_config); + r = custom_timer_suspend(sleep_config, main_operation); if (r < 0) return log_debug_errno(r, "Suspend cycle with manual battery discharge rate estimation failed: %m"); if (r == 0) @@ -602,11 +603,11 @@ static int execute_s2h(const SleepConfig *sleep_config) { /* For above custom timer, if 1 is returned, system will directly hibernate */ log_debug("Attempting to hibernate"); - r = execute(sleep_config, SLEEP_HIBERNATE, NULL); + r = execute(sleep_config, main_operation, SLEEP_HIBERNATE, NULL); if (r < 0) { log_notice("Couldn't hibernate, will try to suspend again."); - r = execute(sleep_config, SLEEP_SUSPEND, "suspend-after-failed-hibernate"); + r = execute(sleep_config, main_operation, SLEEP_SUSPEND, "suspend-after-failed-hibernate"); if (r < 0) return r; } @@ -616,95 +617,63 @@ static int execute_s2h(const SleepConfig *sleep_config) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-suspend.service", "8", &link); if (r < 0) return log_oom(); - printf("%s COMMAND\n\n" - "Suspend the system, hibernate the system, or both.\n\n" - " -h --help Show this help and exit\n" - " --version Print version string and exit\n" - "\nCommands:\n" - " suspend Suspend the system\n" - " hibernate Hibernate the system\n" - " hybrid-sleep Both hibernate and suspend the system\n" - " suspend-then-hibernate Initially suspend and then hibernate\n" - " the system after a fixed period of time or\n" - " when battery is low\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - link); - - return 0; -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - switch (c) { - - case 'h': - return help(); - - case ARG_VERSION: - return version(); + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; - case '?': - return -EINVAL; + r = option_parser_get_help_table(&options); + if (r < 0) + return r; - default: - assert_not_reached(); + (void) table_sync_column_widths(0, verbs, options); - } + printf("%s [OPTIONS…] COMMAND\n" + "\n%sSuspend the system, hibernate the system, or both.%s\n" + "\nCommands:\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal()); - if (argc - optind != 1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Usage: %s COMMAND", - program_invocation_short_name); + r = table_print_or_warn(verbs); + if (r < 0) + return r; - arg_operation = sleep_operation_from_string(argv[optind]); - if (arg_operation < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown command '%s'.", argv[optind]); + printf("\nOptions:\n"); + r = table_print_or_warn(options); + if (r < 0) + return r; - return 1 /* work to do */; + printf("\nSee the %s for details.\n", link); + return 0; } -static int run(int argc, char *argv[]) { +VERB_FULL(verb_operate, "suspend", NULL, VERB_ANY, 1, 0, SLEEP_SUSPEND, + "Suspend the system"); +VERB_FULL(verb_operate, "hibernate", NULL, VERB_ANY, 1, 0, SLEEP_HIBERNATE, + "Hibernate the system"); +VERB_FULL(verb_operate, "hybrid-sleep", NULL, VERB_ANY, 1, 0, SLEEP_HYBRID_SLEEP, + "Both hibernate and suspend the system"); +VERB_FULL(verb_operate, "suspend-then-hibernate", NULL, VERB_ANY, 1, 0, SLEEP_SUSPEND_THEN_HIBERNATE, + "Initially suspend and then hibernate the system after a fixed period of time or when battery is low"); +static int verb_operate(int argc, char *argv[], uintptr_t data, void *userdata) { _cleanup_(unit_freezer_freep) UnitFreezer *user_slice_freezer = NULL; - _cleanup_(sleep_config_freep) SleepConfig *sleep_config = NULL; + SleepOperation operation = data; + const SleepConfig *sleep_config = ASSERT_PTR(userdata); int r; - log_setup(); + assert(0 <= operation && operation < _SLEEP_OPERATION_MAX); - r = parse_argv(argc, argv); - if (r <= 0) - return r; - - r = parse_sleep_config(&sleep_config); - if (r < 0) - return r; - - if (!sleep_config->allow[arg_operation]) + if (!sleep_config->allow[operation]) return log_error_errno(SYNTHETIC_ERRNO(EACCES), "Sleep operation \"%s\" is disabled by configuration, refusing.", - sleep_operation_to_string(arg_operation)); + sleep_operation_to_string(operation)); /* Freeze the user sessions */ r = getenv_bool("SYSTEMD_SLEEP_FREEZE_USER_SESSIONS"); @@ -721,14 +690,14 @@ static int run(int argc, char *argv[]) { "This is not recommended, and might result in unexpected behavior, particularly\n" "in suspend-then-hibernate operations or setups with encrypted home directories."); - switch (arg_operation) { + switch (operation) { case SLEEP_SUSPEND_THEN_HIBERNATE: - r = execute_s2h(sleep_config); + r = execute_s2h(sleep_config, operation); break; case SLEEP_HYBRID_SLEEP: - r = execute(sleep_config, SLEEP_HYBRID_SLEEP, NULL); + r = execute(sleep_config, operation, SLEEP_HYBRID_SLEEP, NULL); if (r < 0) { /* If we can't hybrid sleep, then let's try to suspend at least. After all, the user * asked us to do both: suspend + hibernate, and it's almost certainly the @@ -736,14 +705,13 @@ static int run(int argc, char *argv[]) { log_notice_errno(r, "Couldn't hybrid sleep, will try to suspend instead: %m"); - r = execute(sleep_config, SLEEP_SUSPEND, "suspend-after-failed-hybrid-sleep"); + r = execute(sleep_config, operation, SLEEP_SUSPEND, "suspend-after-failed-hybrid-sleep"); } - break; case SLEEP_SUSPEND: case SLEEP_HIBERNATE: - r = execute(sleep_config, arg_operation, NULL); + r = execute(sleep_config, operation, operation, NULL); break; default: @@ -756,4 +724,43 @@ static int run(int argc, char *argv[]) { return r; } +static int parse_argv(int argc, char *argv[], char ***ret_args) { + assert(argc >= 0); + assert(argv); + assert(ret_args); + + OptionParser state = { argc, argv }; + + FOREACH_OPTION(&state, c, /* arg= */ NULL, /* on_error= */ return c) + switch (c) { + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + } + + *ret_args = option_parser_get_args(&state); + return 1; +} + +static int run(int argc, char *argv[]) { + _cleanup_(sleep_config_freep) SleepConfig *sleep_config = NULL; + char **args = NULL; + int r; + + log_setup(); + + r = parse_argv(argc, argv, &args); + if (r <= 0) + return r; + + r = parse_sleep_config(&sleep_config); + if (r < 0) + return r; + + return dispatch_verb_with_args(args, sleep_config); +} + DEFINE_MAIN_FUNCTION(run); From f46ad8b4ba8a5aef0bb628bd5dcd7ec43283e9a1 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 17 Apr 2026 15:43:38 +0900 Subject: [PATCH 1082/1296] mountpoint-util: initialize mnt_id for name_to_handle_at(AT_HANDLE_MNT_ID_UNIQUE) Suppress the following message: ``` $ sudo valgrind --leak-check=full build/networkctl dhcp-lease wlp59s0 ==175708== Memcheck, a memory error detector ==175708== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al. ==175708== Using Valgrind-3.26.0 and LibVEX; rerun with -h for copyright info ==175708== Command: build/networkctl status wlp59s0 ==175708== ==175708== Conditional jump or move depends on uninitialised value(s) ==175708== at 0x4BC33D1: inode_same_at (stat-util.c:610) ==175708== by 0x4BF1972: inode_same (stat-util.h:86) ==175708== by 0x4BF48FE: running_in_chroot (virt.c:817) ==175708== by 0x4B16643: running_in_chroot_or_offline (verbs.c:37) ==175708== by 0x4B175CE: _dispatch_verb_with_args (verbs.c:136) ==175708== by 0x4B17868: dispatch_verb (verbs.c:160) ==175708== by 0x407CBB: networkctl_main (networkctl.c:249) ==175708== by 0x407D06: run (networkctl.c:263) ==175708== by 0x407D39: main (networkctl.c:266) ==175708== ``` Not sure if it is an issue in valgrind or glibc, but at least there is nothing we can do except for working around it. --- src/basic/mountpoint-util.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/basic/mountpoint-util.c b/src/basic/mountpoint-util.c index c09ac7bf84fe9..958e34bc5326f 100644 --- a/src/basic/mountpoint-util.c +++ b/src/basic/mountpoint-util.c @@ -84,7 +84,13 @@ int name_to_handle_at_loop( h->handle_bytes = n; if (ret_unique_mnt_id) { - uint64_t mnt_id; + /* Here, explicitly initialize mnt_id, otherwise valgrind complains: + * + * ==175708== Conditional jump or move depends on uninitialised value(s) + * ==175708== at 0x4BC33D1: inode_same_at (stat-util.c:610) + * ==175708== by 0x4BF1972: inode_same (stat-util.h:86) + */ + uint64_t mnt_id = 0; /* The kernel will still use this as uint64_t pointer */ r = name_to_handle_at(fd, path, h, (int *) &mnt_id, flags|AT_HANDLE_MNT_ID_UNIQUE); From 8bb669712e581c933605e6551c4dc785d27e5cbf Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 17 Apr 2026 08:41:50 +0900 Subject: [PATCH 1083/1296] string-util: make make_cstring() take void* rather than char* It is typically used for making C string embedded in a binary data. Hence, the input pointer may not be char*. --- src/basic/string-util.c | 6 +++--- src/basic/string-util.h | 2 +- src/libsystemd-network/dhcp6-option.c | 2 +- src/libsystemd-network/sd-dns-resolver.c | 4 ++-- src/shared/dns-packet.c | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/basic/string-util.c b/src/basic/string-util.c index 9b63516ce0908..f7a0bb4474a3b 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -1292,7 +1292,7 @@ char* string_replace_char(char *str, char old_char, char new_char) { return str; } -int make_cstring(const char *s, size_t n, MakeCStringMode mode, char **ret) { +int make_cstring(const void *s, size_t n, MakeCStringMode mode, char **ret) { char *b; assert(s || n == 0); @@ -1311,11 +1311,11 @@ int make_cstring(const char *s, size_t n, MakeCStringMode mode, char **ret) { b = new0(char, 1); } else { - const char *nul; + const uint8_t *nul; nul = memchr(s, 0, n); if (nul) { - if (nul < s + n - 1 || /* embedded NUL? */ + if (nul < (const uint8_t*) s + n - 1 || /* embedded NUL? */ mode == MAKE_CSTRING_REFUSE_TRAILING_NUL) return -EINVAL; diff --git a/src/basic/string-util.h b/src/basic/string-util.h index 5ab4dd9016dd2..270b9d907a808 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -271,7 +271,7 @@ typedef enum MakeCStringMode { _MAKE_CSTRING_MODE_INVALID = -1, } MakeCStringMode; -int make_cstring(const char *s, size_t n, MakeCStringMode mode, char **ret); +int make_cstring(const void *s, size_t n, MakeCStringMode mode, char **ret); size_t strspn_from_end(const char *str, const char *accept) _pure_; diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index 1508d89781350..d62fd70588923 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -548,7 +548,7 @@ int dhcp6_option_parse_string(const uint8_t *data, size_t data_len, char **ret) return 0; } - r = make_cstring((const char *) data, data_len, MAKE_CSTRING_REFUSE_TRAILING_NUL, &string); + r = make_cstring(data, data_len, MAKE_CSTRING_REFUSE_TRAILING_NUL, &string); if (r < 0) return r; diff --git a/src/libsystemd-network/sd-dns-resolver.c b/src/libsystemd-network/sd-dns-resolver.c index 605397cf97ed5..8285c5cb57640 100644 --- a/src/libsystemd-network/sd-dns-resolver.c +++ b/src/libsystemd-network/sd-dns-resolver.c @@ -252,8 +252,8 @@ int dnr_parse_svc_params(const uint8_t *option, size_t len, sd_dns_resolver *res return -EBADMSG; case DNS_SVC_PARAM_KEY_DOHPATH: - r = make_cstring((const char*) &option[offset], plen, - MAKE_CSTRING_REFUSE_TRAILING_NUL, &dohpath); + r = make_cstring(&option[offset], plen, + MAKE_CSTRING_REFUSE_TRAILING_NUL, &dohpath); if (ERRNO_IS_NEG_RESOURCE(r)) return r; if (r < 0) diff --git a/src/shared/dns-packet.c b/src/shared/dns-packet.c index f250f40edff9f..c8c54e7988afc 100644 --- a/src/shared/dns-packet.c +++ b/src/shared/dns-packet.c @@ -2861,7 +2861,7 @@ int dns_packet_ede_rcode(DnsPacket *p, int *ret_ede_rcode, char **ret_ede_msg) { return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "EDNS0 truncated EDE info code."); - r = make_cstring((char *) d + 6, length - 2U, MAKE_CSTRING_ALLOW_TRAILING_NUL, &msg); + r = make_cstring(d + 6, length - 2U, MAKE_CSTRING_ALLOW_TRAILING_NUL, &msg); if (r < 0) return log_debug_errno(r, "Invalid EDE text in opt."); From 4d2847dcc2083afe8460bec839b0e0940818d8dd Mon Sep 17 00:00:00 2001 From: Tobias Stoeckmann Date: Fri, 17 Apr 2026 21:48:53 +0200 Subject: [PATCH 1084/1296] man: Fix NOTES formatting The NOTES section in os-release(5) contains an unusual formatting. Switch function and ulink tags and remove a newline within ulink text to keep the entry formatting in sync with others. Also, this preserves the formatting within the text itself. Signed-off-by: Tobias Stoeckmann --- man/os-release.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/man/os-release.xml b/man/os-release.xml index 8b652d78f0253..94617ae243d6f 100644 --- a/man/os-release.xml +++ b/man/os-release.xml @@ -750,8 +750,9 @@ VERSION_ID=32 - See docs for - platform.freedesktop_os_release for more details. + See docs for platform.freedesktop_os_release + for more details. From 587064b3f3417e4e7f66d577d7d1725e31b955f1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 20 Mar 2026 22:07:51 +0100 Subject: [PATCH 1085/1296] boot: switch initrd_register() to use _cleanup_free_ and other tweaks --- src/boot/initrd.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/boot/initrd.c b/src/boot/initrd.c index d8cbe7deed425..569b757be7a23 100644 --- a/src/boot/initrd.c +++ b/src/boot/initrd.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "efi-log.h" +#include "efi.h" #include "initrd.h" #include "iovec-util-fundamental.h" #include "proto/device-path.h" @@ -72,7 +74,6 @@ EFI_STATUS initrd_register( EFI_STATUS err; EFI_DEVICE_PATH *dp = (EFI_DEVICE_PATH *) &efi_initrd_device_path; EFI_HANDLE handle; - struct initrd_loader *loader; assert(ret_initrd_handle); @@ -82,15 +83,14 @@ EFI_STATUS initrd_register( if (!iovec_is_set(initrd)) return EFI_SUCCESS; - /* check if a LINUX_INITRD_MEDIA_GUID DevicePath is already registered. - LocateDevicePath checks for the "closest DevicePath" and returns its handle, - where as InstallMultipleProtocolInterfaces only matches identical DevicePaths. - */ + /* Check if a LINUX_INITRD_MEDIA_GUID DevicePath is already registered. LocateDevicePath checks for + * the "closest DevicePath" and returns its handle, whereas InstallMultipleProtocolInterfaces() only + * matches identical DevicePaths. */ err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), &dp, &handle); if (err != EFI_NOT_FOUND) /* InitrdMedia is already registered */ return EFI_ALREADY_STARTED; - loader = xnew(struct initrd_loader, 1); + _cleanup_free_ struct initrd_loader *loader = xnew(struct initrd_loader, 1); *loader = (struct initrd_loader) { .load_file.LoadFile = initrd_load_file, .data = *initrd, @@ -98,14 +98,17 @@ EFI_STATUS initrd_register( /* create a new handle and register the LoadFile2 protocol with the InitrdMediaPath on it */ err = BS->InstallMultipleProtocolInterfaces( - ret_initrd_handle, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), - &efi_initrd_device_path, MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), - loader, - NULL); + ret_initrd_handle, + MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), &efi_initrd_device_path, + MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), loader, + /* sentinel= */ NULL); if (err != EFI_SUCCESS) - free(loader); + return log_debug_status(err, "Failed to install new initrd device: %m"); + + log_debug("Installed new initrd of size %zu.", loader->data.iov_len); - return err; + TAKE_PTR(loader); + return EFI_SUCCESS; } EFI_STATUS initrd_unregister(EFI_HANDLE initrd_handle) { From 9879a192224fea214b77a37a98a049b45851cb42 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 7 Apr 2026 22:25:24 +0200 Subject: [PATCH 1086/1296] boot: minor clean-ups in initrd_unregister() --- src/boot/initrd.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/boot/initrd.c b/src/boot/initrd.c index 569b757be7a23..4babe1671d557 100644 --- a/src/boot/initrd.c +++ b/src/boot/initrd.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "efi-log.h" -#include "efi.h" #include "initrd.h" #include "iovec-util-fundamental.h" #include "proto/device-path.h" @@ -112,16 +111,16 @@ EFI_STATUS initrd_register( } EFI_STATUS initrd_unregister(EFI_HANDLE initrd_handle) { - EFI_STATUS err; struct initrd_loader *loader; + EFI_STATUS err; if (!initrd_handle) return EFI_SUCCESS; - /* get the LoadFile2 protocol that we allocated earlier */ + /* Get the LoadFile2 protocol that we allocated earlier */ err = BS->HandleProtocol(initrd_handle, MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), (void **) &loader); if (err != EFI_SUCCESS) - return err; + return log_debug_status(err, "Failed to acquire LoadFile2 protocol on our own initrd handle: %m"); /* uninstall all protocols thus destroying the handle */ err = BS->UninstallMultipleProtocolInterfaces( @@ -130,9 +129,8 @@ EFI_STATUS initrd_unregister(EFI_HANDLE initrd_handle) { loader, NULL); if (err != EFI_SUCCESS) - return err; + return log_debug_status(err, "Failed to uninstall LoadFile2 protocol from our own initrd handle: %m"); - initrd_handle = NULL; free(loader); return EFI_SUCCESS; } From 03cf3c4ff96e217b78d03e72dfa6e18433b9bf04 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 15 Apr 2026 16:18:53 +0200 Subject: [PATCH 1087/1296] bootspec: make boot_filename_extract_tries() ready for use outside of bootspec.c --- src/shared/bootspec.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index 9bd91f988d50b..2d9906acb9b87 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -231,13 +231,13 @@ static int parse_tries(const char *fname, const char **p, unsigned *ret) { d = strndup(*p, n); if (!d) - return log_oom(); + return -ENOMEM; r = safe_atou_full(d, 10, &tries); - if (r >= 0 && tries > INT_MAX) /* sd-boot allows INT_MAX, let's use the same limit */ - r = -ERANGE; if (r < 0) - return log_error_errno(r, "Failed to parse tries counter of filename '%s': %m", fname); + return r; + if (tries > INT_MAX) /* sd-boot allows INT_MAX, let's use the same limit */ + return -ERANGE; *p = *p + n; *ret = tries; @@ -257,8 +257,6 @@ int boot_filename_extract_tries( assert(fname); assert(ret_stripped); - assert(ret_tries_left); - assert(ret_tries_done); /* Be liberal with suffix, only insist on a dot. After all we want to cover any capitalization here * (vfat is case insensitive after all), and at least .efi and .conf as suffix. */ @@ -292,24 +290,29 @@ int boot_filename_extract_tries( stripped = strndup(fname, m - fname); if (!stripped) - return log_oom(); + return -ENOMEM; if (!strextend(&stripped, suffix)) - return log_oom(); + return -ENOMEM; *ret_stripped = TAKE_PTR(stripped); - *ret_tries_left = tries_left; - *ret_tries_done = tries_done; + if (ret_tries_left) + *ret_tries_left = tries_left; + if (ret_tries_done) + *ret_tries_done = tries_done; return 0; nothing: stripped = strdup(fname); if (!stripped) - return log_oom(); + return -ENOMEM; *ret_stripped = TAKE_PTR(stripped); - *ret_tries_left = *ret_tries_done = UINT_MAX; + if (ret_tries_left) + *ret_tries_left = UINT_MAX; + if (ret_tries_done) + *ret_tries_done = UINT_MAX; return 0; } @@ -335,7 +338,7 @@ static int boot_entry_load_type1( r = boot_filename_extract_tries(fname, &tmp.id, &tmp.tries_left, &tmp.tries_done); if (r < 0) - return r; + return log_error_errno(r, "Failed to extract tries counters from '%s': %m", fname); if (!efi_loader_entry_name_valid(tmp.id)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", fname); @@ -778,7 +781,7 @@ static int boot_entry_load_unified( r = boot_filename_extract_tries(fname, &tmp.id, &tmp.tries_left, &tmp.tries_done); if (r < 0) - return r; + return log_error_errno(r, "Failed to extract tries counters from '%s': %m", fname); if (!efi_loader_entry_name_valid(tmp.id)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", tmp.id); From a08ff701f01a475ac8833b6764c081d69145a160 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 15 Apr 2026 16:19:43 +0200 Subject: [PATCH 1088/1296] bootspec: improve documentation around id/file BootEntry fields --- src/shared/bootspec.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h index f325dcae25154..951a81f08c6c4 100644 --- a/src/shared/bootspec.h +++ b/src/shared/bootspec.h @@ -34,11 +34,11 @@ typedef struct BootEntry { BootEntryType type; BootEntrySource source; bool reported_by_loader; - char *id; /* This is the file basename (including extension!) */ - char *id_old; /* Old-style ID, for deduplication purposes. */ - char *id_without_profile; /* id without profile suffixed */ - char *path; /* This is the full path to the drop-in file */ - char *root; /* The root path in which the drop-in was found, i.e. to which 'kernel', 'efi' and 'initrd' are relative */ + char *id; /* This is the file basename (including extension, but with tries counters stripped) */ + char *id_old; /* Old-style ID, for deduplication purposes (for type1: same as the regular id, but with extension stripped too). */ + char *id_without_profile; /* ID without profile suffixed */ + char *path; /* This is the full path to the drop-in file (i.e. prefixed with 'root' field below) */ + char *root; /* The root path in which the drop-in was found, i.e. to which 'kernel', 'efi' and 'initrd' are relative */ char *title; char *show_title; char *sort_key; From b51c133a4eeeaf7f9c93811fed78c46465e92276 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 18 Mar 2026 19:10:01 +0900 Subject: [PATCH 1089/1296] random-util: introduce random_bytes_allocate_iovec() helper function It is similar to crypto_random_bytes_allocate_iovec(), but possibly insecure. --- src/basic/random-util.c | 13 +++++++++++++ src/basic/random-util.h | 8 ++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/basic/random-util.c b/src/basic/random-util.c index d05be6fa501ad..0a562238dd173 100644 --- a/src/basic/random-util.c +++ b/src/basic/random-util.c @@ -96,6 +96,19 @@ void random_bytes(void *p, size_t n) { fallback_random_bytes(p, n); } +int random_bytes_allocate_iovec(size_t n, struct iovec *ret) { + assert(ret); + + void *p = malloc(MAX(n, 1U)); + if (!p) + return -ENOMEM; + + random_bytes(p, n); + + *ret = IOVEC_MAKE(TAKE_PTR(p), n); + return 0; +} + int crypto_random_bytes(void *p, size_t n) { assert(p || n == 0); diff --git a/src/basic/random-util.h b/src/basic/random-util.h index c0ed0488e74c8..d65c472031704 100644 --- a/src/basic/random-util.h +++ b/src/basic/random-util.h @@ -3,8 +3,12 @@ #include "basic-forward.h" -void random_bytes(void *p, size_t n) _nonnull_if_nonzero_(1, 2); /* Returns random bytes suitable for most uses, but may be insecure sometimes. */ -int crypto_random_bytes(void *p, size_t n) _nonnull_if_nonzero_(1, 2); /* Returns secure random bytes after waiting for the RNG to initialize. */ +/* Returns random bytes suitable for most uses, but may be insecure sometimes. */ +void random_bytes(void *p, size_t n) _nonnull_if_nonzero_(1, 2); +int random_bytes_allocate_iovec(size_t n, struct iovec *ret); + +/* Returns secure random bytes after waiting for the RNG to initialize. */ +int crypto_random_bytes(void *p, size_t n) _nonnull_if_nonzero_(1, 2); int crypto_random_bytes_allocate_iovec(size_t n, struct iovec *ret); static inline uint64_t random_u64(void) { From fa6eb2e016a53d741ec48a784a8ea075ec3d4fe0 Mon Sep 17 00:00:00 2001 From: Nandakumar Raghavan Date: Mon, 6 Apr 2026 10:42:51 +0000 Subject: [PATCH 1090/1296] repart: add EncryptKDF= option for LUKS2 partitions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit systemd-repart currently creates LUKS2 encrypted partitions using libcryptsetup's default KDF (Argon2id), which requires ~1GB of memory during key derivation. This is too much for memory-constrained environments such as kdump with limited crashkernel memory, where luksOpen fails due to insufficient memory. Add an EncryptKDF= option to repart.d partition definitions that allows selecting the KDF type. Supported values are: - "argon2id" — Argon2id with libcryptsetup-benchmarked parameters - "pbkdf2" — PBKDF2 with libcryptsetup-benchmarked parameters - "minimal" — PBKDF2 with SHA-512, 1000 iterations, no benchmarking, matching the existing cryptsetup_set_minimal_pbkdf() behaviour used for TPM2-sealed keys When not specified, the libcryptsetup default (argon2id) is used, preserving existing behaviour. The KDF type is applied via sym_crypt_set_pbkdf_type() after sym_crypt_format() and before any keyslots are added. --- man/repart.d.xml | 24 +++++++++++++++++++++++ src/repart/repart.c | 47 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/man/repart.d.xml b/man/repart.d.xml index 217692d813551..028929ac9e842 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -695,6 +695,30 @@ + + EncryptKDF= + + Specifies the key derivation function (KDF) to use for LUKS2 encryption keyslots. + Takes one of argon2id, pbkdf2, or minimal. + If not specified, the default is determined by the cryptsetup library (typically + argon2id). This option has no effect if Encrypt= is + off. + + When set to argon2id or pbkdf2, the specified KDF is + used with parameters benchmarked by the cryptsetup library. When set to minimal, + PBKDF2 is used with SHA-512, 1000 iterations, and no benchmarking — this is appropriate for + high-entropy keys (e.g. generated by a hardware key manager or sealed to a TPM) where the KDF only + needs to satisfy the LUKS2 format requirement, not strengthen a weak passphrase. + + Note that Argon2-based KDFs may require significant memory (up to 1GB) during key derivation. + In memory-constrained environments such as kdump with limited crashkernel memory, + minimal or pbkdf2 may be more appropriate. When + Encrypt= includes tpm2, the TPM2 keyslot always uses a minimal + PBKDF2 configuration regardless of this setting. + + + + Verity= diff --git a/src/repart/repart.c b/src/repart/repart.c index b0b92ba2ae075..b8443fab1951a 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -290,6 +290,14 @@ typedef enum IntegrityAlg { _INTEGRITY_ALG_INVALID = -EINVAL, } IntegrityAlg; +typedef enum EncryptKDF { + ENCRYPT_KDF_ARGON2ID, + ENCRYPT_KDF_PBKDF2, + ENCRYPT_KDF_MINIMAL, + _ENCRYPT_KDF_MAX, + _ENCRYPT_KDF_INVALID = -EINVAL, +} EncryptKDF; + typedef enum VerityMode { VERITY_OFF, VERITY_DATA, @@ -472,6 +480,7 @@ typedef struct Partition { size_t tpm2_n_hash_pcr_values; IntegrityMode integrity; IntegrityAlg integrity_alg; + EncryptKDF encrypt_kdf; VerityMode verity; char *verity_match_key; MinimizeMode minimize; @@ -598,6 +607,12 @@ static const char *integrity_alg_table[_INTEGRITY_ALG_MAX] = { [INTEGRITY_ALG_HMAC_SHA512] = "hmac-sha512", }; +static const char *encrypt_kdf_table[_ENCRYPT_KDF_MAX] = { + [ENCRYPT_KDF_ARGON2ID] = "argon2id", + [ENCRYPT_KDF_PBKDF2] = "pbkdf2", + [ENCRYPT_KDF_MINIMAL] = "minimal", +}; + static const char *verity_mode_table[_VERITY_MODE_MAX] = { [VERITY_OFF] = "off", [VERITY_DATA] = "data", @@ -636,6 +651,7 @@ DEFINE_PRIVATE_STRING_TABLE_LOOKUP(append_mode, AppendMode); DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(encrypt_mode, EncryptMode, ENCRYPT_KEY_FILE); DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(integrity_mode, IntegrityMode, INTEGRITY_INLINE); DEFINE_PRIVATE_STRING_TABLE_LOOKUP(integrity_alg, IntegrityAlg); +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(encrypt_kdf, EncryptKDF); DEFINE_PRIVATE_STRING_TABLE_LOOKUP(verity_mode, VerityMode); DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(minimize_mode, MinimizeMode, MINIMIZE_BEST); DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(progress_phase, ProgressPhase); @@ -738,6 +754,7 @@ static Partition *partition_new(Context *c) { .progress_ratelimit = { 100 * USEC_PER_MSEC, 1 }, .fs_sector_size = UINT64_MAX, .discard = -1, + .encrypt_kdf = _ENCRYPT_KDF_INVALID, }; return p; @@ -2735,6 +2752,7 @@ static int config_parse_key_file( static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_integrity, integrity_mode, IntegrityMode, INTEGRITY_OFF); static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_integrity_alg, integrity_alg, IntegrityAlg, INTEGRITY_ALG_HMAC_SHA256); +static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_encrypt_kdf, encrypt_kdf, EncryptKDF, _ENCRYPT_KDF_INVALID); static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_verity, verity_mode, VerityMode, VERITY_OFF); static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_minimize, minimize_mode, MinimizeMode, MINIMIZE_OFF); @@ -2892,6 +2910,7 @@ static int partition_read_definition( { "Partition", "KeyFile", config_parse_key_file, 0, p }, { "Partition", "Integrity", config_parse_integrity, 0, &p->integrity }, { "Partition", "IntegrityAlgorithm", config_parse_integrity_alg, 0, &p->integrity_alg }, + { "Partition", "EncryptKDF", config_parse_encrypt_kdf, 0, &p->encrypt_kdf }, { "Partition", "Compression", config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, &p->compression }, { "Partition", "CompressionLevel", config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, &p->compression_level }, { "Partition", "SupplementFor", config_parse_string, 0, &p->supplement_for_name }, @@ -3066,6 +3085,10 @@ static int partition_read_definition( log_syntax(NULL, LOG_WARNING, path, 1, 0, "Discard=yes has no effect with Encrypt=off."); + if (p->encrypt == ENCRYPT_OFF && p->encrypt_kdf >= 0) + log_syntax(NULL, LOG_WARNING, path, 1, 0, + "EncryptKDF= has no effect with Encrypt=off."); + if (p->encrypt != ENCRYPT_OFF && p->integrity == INTEGRITY_INLINE && p->discard > 0) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), "Integrity=inline is incompatible with Discard=yes."); @@ -5266,6 +5289,30 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta if (r < 0) return log_error_errno(r, "Failed to LUKS2 format future partition: %m"); + /* If an explicit KDF is configured, apply it before adding any keyslots. */ + if (p->encrypt_kdf >= 0) { + if (p->encrypt_kdf == ENCRYPT_KDF_MINIMAL) { + /* Minimal PBKDF2 with sha512, 1000 iterations, no benchmarking — appropriate + * for high-entropy keys where the KDF only satisfies the LUKS2 format requirement + * (e.g. kdump with crashkernel=512MB). */ + r = cryptsetup_set_minimal_pbkdf(cd); + } else { + /* For argon2id or pbkdf2, set the type and let libcryptsetup benchmark + * and determine the parameters. */ + static const struct crypt_pbkdf_type argon2id_pbkdf = { + .type = "argon2id", + }; + static const struct crypt_pbkdf_type pbkdf2_pbkdf = { + .type = "pbkdf2", + }; + + r = sym_crypt_set_pbkdf_type(cd, p->encrypt_kdf == ENCRYPT_KDF_ARGON2ID ? + &argon2id_pbkdf : &pbkdf2_pbkdf); + } + if (r < 0) + return log_error_errno(r, "Failed to set KDF type: %m"); + } + bool allow_discards = p->integrity != INTEGRITY_INLINE && (arg_discard ? p->discard != 0 : p->discard > 0); if (allow_discards) { uint32_t flags; From c8f5a564089a497fb5b1232696c188e1e78b7359 Mon Sep 17 00:00:00 2001 From: Sebastian Bernardt Date: Sat, 18 Apr 2026 21:19:08 +1000 Subject: [PATCH 1091/1296] mailmap: name change --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 9d35e1efd6496..a9c0ec7e87971 100644 --- a/.mailmap +++ b/.mailmap @@ -180,6 +180,7 @@ Salvo Tomaselli Sandy Carter Scott James Remnant Scott James Remnant +Sebastian Bernardt Seraphime Kirkovski Shawn Landden Shawn Landden From 366e1d264a6d1c2aa96d85bf6dd80be2bbd65f72 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 10 Apr 2026 13:05:48 +0900 Subject: [PATCH 1092/1296] sd-dhcp-client: fix memleak of sd_dhcp_client.timeout_ipv6_only_mode This also drops unnecessary zero assignments. --- src/libsystemd-network/sd-dhcp-client.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index a02f8db7cb8ec..19f85e09df945 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -2526,10 +2526,11 @@ static sd_dhcp_client* dhcp_client_free(sd_dhcp_client *client) { client_initialize(client); - client->timeout_resend = sd_event_source_unref(client->timeout_resend); - client->timeout_t1 = sd_event_source_unref(client->timeout_t1); - client->timeout_t2 = sd_event_source_unref(client->timeout_t2); - client->timeout_expire = sd_event_source_unref(client->timeout_expire); + sd_event_source_unref(client->timeout_resend); + sd_event_source_unref(client->timeout_t1); + sd_event_source_unref(client->timeout_t2); + sd_event_source_unref(client->timeout_expire); + sd_event_source_unref(client->timeout_ipv6_only_mode); sd_dhcp_client_detach_event(client); From 043358d852499f61756d6eb76bff0ea286ab0b95 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 23 Mar 2026 23:04:05 +0900 Subject: [PATCH 1093/1296] iovec-util: introduce IOVEC_SHIFT() macro and friends --- src/fundamental/iovec-util-fundamental.h | 19 +++++++++++++ src/test/test-iovec-util.c | 34 ++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/fundamental/iovec-util-fundamental.h b/src/fundamental/iovec-util-fundamental.h index 707274f86e53f..5b693742f3a7c 100644 --- a/src/fundamental/iovec-util-fundamental.h +++ b/src/fundamental/iovec-util-fundamental.h @@ -23,6 +23,25 @@ struct iovec { .iov_len = (len), \ } +static inline struct iovec* iovec_shift(const struct iovec *iovec, size_t shift, struct iovec *ret) { + assert(iovec); + assert(ret); + + /* This returns an empty iovec when 'shift' is larger or equals to the input iovec length. + * The 'iovec' and 'ret' can point to the same object. */ + + *ret = IOVEC_MAKE(iovec->iov_len > shift ? (uint8_t*) iovec->iov_base + shift : NULL, + LESS_BY(iovec->iov_len, shift)); + return ret; +} + +#define IOVEC_SHIFT(iov, shift) \ + *iovec_shift(iov, shift, &(struct iovec){}) + +static inline struct iovec* iovec_inc(struct iovec *iovec, size_t shift) { + return iovec_shift(iovec, shift, iovec); +} + static inline void iovec_done(struct iovec *iovec) { /* A _cleanup_() helper that frees the iov_base in the iovec */ assert(iovec); diff --git a/src/test/test-iovec-util.c b/src/test/test-iovec-util.c index e091463a93423..68255071e861d 100644 --- a/src/test/test-iovec-util.c +++ b/src/test/test-iovec-util.c @@ -4,6 +4,40 @@ #include "memory-util.h" #include "tests.h" +TEST(iovec_shift) { + const struct iovec iov = CONST_IOVEC_MAKE_STRING("54321"); + + ASSERT_EQ(iovec_memcmp(&IOVEC_SHIFT(&iov, 0), &CONST_IOVEC_MAKE_STRING("54321")), 0); + ASSERT_EQ(iovec_memcmp(&IOVEC_SHIFT(&iov, 1), &CONST_IOVEC_MAKE_STRING("4321")), 0); + ASSERT_EQ(iovec_memcmp(&IOVEC_SHIFT(&iov, 2), &CONST_IOVEC_MAKE_STRING("321")), 0); + ASSERT_EQ(iovec_memcmp(&IOVEC_SHIFT(&iov, 3), &CONST_IOVEC_MAKE_STRING("21")), 0); + ASSERT_EQ(iovec_memcmp(&IOVEC_SHIFT(&iov, 4), &CONST_IOVEC_MAKE_STRING("1")), 0); + ASSERT_FALSE(iovec_is_set(&IOVEC_SHIFT(&iov, 5))); + ASSERT_FALSE(iovec_is_set(&IOVEC_SHIFT(&iov, 6))); + ASSERT_FALSE(iovec_is_set(&IOVEC_SHIFT(&iov, 7))); + + const struct iovec empty = {}; + ASSERT_FALSE(iovec_is_set(&IOVEC_SHIFT(&empty, 0))); + ASSERT_FALSE(iovec_is_set(&IOVEC_SHIFT(&empty, 1))); +} + +TEST(iovec_inc) { + struct iovec iov = IOVEC_MAKE_STRING("54321"); + + ASSERT_EQ(iovec_memcmp(iovec_inc(&iov, 0), &CONST_IOVEC_MAKE_STRING("54321")), 0); + ASSERT_EQ(iovec_memcmp(iovec_inc(&iov, 1), &CONST_IOVEC_MAKE_STRING("4321")), 0); + ASSERT_EQ(iovec_memcmp(iovec_inc(&iov, 1), &CONST_IOVEC_MAKE_STRING("321")), 0); + ASSERT_EQ(iovec_memcmp(iovec_inc(&iov, 1), &CONST_IOVEC_MAKE_STRING("21")), 0); + ASSERT_EQ(iovec_memcmp(iovec_inc(&iov, 1), &CONST_IOVEC_MAKE_STRING("1")), 0); + ASSERT_FALSE(iovec_is_set(iovec_inc(&iov, 1))); + ASSERT_FALSE(iovec_is_set(iovec_inc(&iov, 1))); + ASSERT_FALSE(iovec_is_set(iovec_inc(&iov, 1))); + + struct iovec empty = {}; + ASSERT_FALSE(iovec_is_set(iovec_inc(&empty, 0))); + ASSERT_FALSE(iovec_is_set(iovec_inc(&empty, 1))); +} + TEST(iovec_memcmp) { struct iovec iov1 = CONST_IOVEC_MAKE_STRING("abcdef"), iov2 = IOVEC_MAKE_STRING("bcdefg"), empty = {}; From ce81c7dd74637e297c4f493533863c996d0ebd39 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 17 Apr 2026 22:52:18 +0900 Subject: [PATCH 1094/1296] iovec-wrapper: fix memleak in iovw_consume() when len == 0 This makes even when len == 0, the input buffer is freed. The behavior is consistent with strv_consume() and friends. --- src/basic/iovec-wrapper.c | 4 ++-- src/test/test-iovec-wrapper.c | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/basic/iovec-wrapper.c b/src/basic/iovec-wrapper.c index a4604f9a3cbb0..2b302b96ab0b2 100644 --- a/src/basic/iovec-wrapper.c +++ b/src/basic/iovec-wrapper.c @@ -38,7 +38,7 @@ int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len) { return -ENOMEM; iovw->iovec[iovw->count++] = IOVEC_MAKE(data, len); - return 0; + return 1; } int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len) { @@ -46,7 +46,7 @@ int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len) { int r; r = iovw_put(iovw, data, len); - if (r < 0) + if (r <= 0) free(data); return r; diff --git a/src/test/test-iovec-wrapper.c b/src/test/test-iovec-wrapper.c index 35b939657226b..1f4585a129f40 100644 --- a/src/test/test-iovec-wrapper.c +++ b/src/test/test-iovec-wrapper.c @@ -38,7 +38,7 @@ TEST(iovw_append) { ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "one", 3), 0); /* Insert with a NUL */ - ASSERT_OK_ZERO(iovw_append(&iovw, buf, 4)); + ASSERT_OK(iovw_append(&iovw, buf, 4)); ASSERT_EQ(iovw.count, 2U); ASSERT_EQ(iovw.iovec[1].iov_len, 4U); ASSERT_EQ(memcmp(iovw.iovec[1].iov_base, "one\0", 4), 0); @@ -58,10 +58,9 @@ TEST(iovw_consume) { /* iovw_consume moves ownership in place, no copy */ ASSERT_PTR_EQ(iovw.iovec[0].iov_base, p); - /* Zero-length: iovw_put returns 0 without adding anything, and does not free the payload. - * Confirm by strdup'ing something and explicitly freeing it afterwards. */ - _cleanup_free_ char *q = strdup(""); - ASSERT_NOT_NULL(q); + /* Zero-length: iovw_put returns 0 without adding anything. Even in that case, iovw_consume() frees + * the payload. Confirm by strdup'ing something to verify that when running with sanitizer/valgrind. */ + char *q = ASSERT_NOT_NULL(strdup("")); ASSERT_OK_ZERO(iovw_consume(&iovw, q, 0)); ASSERT_EQ(iovw.count, 1U); } From a4e0f041570f5f4fb6b486ab1fff99c839a9d73f Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 17 Apr 2026 22:53:56 +0900 Subject: [PATCH 1095/1296] iovec-wrapper: introduce iovw_compare() and iovw_equal() --- src/basic/iovec-wrapper.c | 22 ++++++++++++++ src/basic/iovec-wrapper.h | 5 +++ src/test/test-iovec-wrapper.c | 57 +++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+) diff --git a/src/basic/iovec-wrapper.c b/src/basic/iovec-wrapper.c index 2b302b96ab0b2..a76087b859e83 100644 --- a/src/basic/iovec-wrapper.c +++ b/src/basic/iovec-wrapper.c @@ -23,6 +23,28 @@ void iovw_done_free(struct iovec_wrapper *iovw) { iovw_done(iovw); } +int iovw_compare(const struct iovec_wrapper *a, const struct iovec_wrapper *b) { + int r; + + if (a == b) + return 0; + + if (!a || !b) + return CMP(!!a, !!b); + + /* Note, this performs structural (element-by-element) comparison, not content-based comparison. + * Two wrappers with identical concatenated content but different element boundaries + * (e.g., ["fo","o"] vs ["f","oo"]) will not compare as equal. */ + + for (size_t i = 0, n = MIN(a->count, b->count); i < n; i++) { + r = iovec_memcmp(a->iovec + i, b->iovec + i); + if (r != 0) + return r; + } + + return CMP(a->count, b->count); +} + int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len) { assert(iovw); diff --git a/src/basic/iovec-wrapper.h b/src/basic/iovec-wrapper.h index eaa859af06d4a..d2437b60f1925 100644 --- a/src/basic/iovec-wrapper.h +++ b/src/basic/iovec-wrapper.h @@ -11,6 +11,11 @@ struct iovec_wrapper { void iovw_done_free(struct iovec_wrapper *iovw); void iovw_done(struct iovec_wrapper *iovw); +int iovw_compare(const struct iovec_wrapper *a, const struct iovec_wrapper *b) _pure_; +static inline bool iovw_equal(const struct iovec_wrapper *a, const struct iovec_wrapper *b) { + return iovw_compare(a, b) == 0; +} + int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len); int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len); int iovw_append(struct iovec_wrapper *iovw, const void *data, size_t len); diff --git a/src/test/test-iovec-wrapper.c b/src/test/test-iovec-wrapper.c index 1f4585a129f40..168217f16c237 100644 --- a/src/test/test-iovec-wrapper.c +++ b/src/test/test-iovec-wrapper.c @@ -3,9 +3,66 @@ #include #include "alloc-util.h" +#include "iovec-util.h" #include "iovec-wrapper.h" #include "tests.h" +TEST(iovw_compare) { + _cleanup_(iovw_done) struct iovec_wrapper a1 = {}, a2 = {}, b = {}, c = {}, d = {}, e = {}; + + ASSERT_OK(iovw_put(&a1, (char*) "foo", 3)); + ASSERT_OK(iovw_put(&a1, (char*) "aaaaa", 5)); + + ASSERT_OK(iovw_put(&a2, (char*) "foo", 3)); + ASSERT_OK(iovw_put(&a2, (char*) "aaaaa", 5)); + + ASSERT_OK(iovw_put(&b, (char*) "foo", 3)); + ASSERT_OK(iovw_put(&b, (char*) "bbbbb", 5)); + + ASSERT_OK(iovw_put(&c, (char*) "foo", 3)); + + ASSERT_OK(iovw_put(&d, (char*) "fooaa", 5)); + ASSERT_OK(iovw_put(&d, (char*) "aaa", 3)); + + ASSERT_EQ(iovw_compare(&a1, &a1), 0); + ASSERT_EQ(iovw_compare(&a1, &a2), 0); + ASSERT_EQ(iovw_compare(&a2, &a1), 0); + ASSERT_LT(iovw_compare(&a1, &b), 0); + ASSERT_GT(iovw_compare(&b, &a1), 0); + ASSERT_EQ(iovw_compare(&b, &b), 0); + ASSERT_GT(iovw_compare(&a1, &c), 0); + ASSERT_LT(iovw_compare(&c, &a1), 0); + ASSERT_EQ(iovw_compare(&c, &c), 0); + ASSERT_LT(iovw_compare(&a1, &d), 0); + ASSERT_GT(iovw_compare(&d, &a1), 0); + ASSERT_EQ(iovw_compare(&d, &d), 0); + ASSERT_GT(iovw_compare(&a1, &e), 0); + ASSERT_LT(iovw_compare(&e, &a1), 0); + ASSERT_EQ(iovw_compare(&e, &e), 0); + ASSERT_GT(iovw_compare(&a1, NULL), 0); + ASSERT_LT(iovw_compare(NULL, &a1), 0); + ASSERT_EQ(iovw_compare(NULL, NULL), 0); + + ASSERT_TRUE(iovw_equal(&a1, &a1)); + ASSERT_TRUE(iovw_equal(&a1, &a2)); + ASSERT_TRUE(iovw_equal(&a2, &a1)); + ASSERT_FALSE(iovw_equal(&a1, &b)); + ASSERT_FALSE(iovw_equal(&b, &a1)); + ASSERT_TRUE(iovw_equal(&b, &b)); + ASSERT_FALSE(iovw_equal(&a1, &c)); + ASSERT_FALSE(iovw_equal(&c, &a1)); + ASSERT_TRUE(iovw_equal(&c, &c)); + ASSERT_FALSE(iovw_equal(&a1, &d)); + ASSERT_FALSE(iovw_equal(&d, &a1)); + ASSERT_TRUE(iovw_equal(&d, &d)); + ASSERT_FALSE(iovw_equal(&a1, &e)); + ASSERT_FALSE(iovw_equal(&e, &a1)); + ASSERT_TRUE(iovw_equal(&e, &e)); + ASSERT_FALSE(iovw_equal(&a1, NULL)); + ASSERT_FALSE(iovw_equal(NULL, &a1)); + ASSERT_TRUE(iovw_equal(NULL, NULL)); +} + TEST(iovw_put) { _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; From d49c509d9b2246025e546e70b4ab911a89106f49 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 17 Apr 2026 22:54:54 +0900 Subject: [PATCH 1096/1296] iovec-wrapper: introduce iovw_concat() This is similar to iovw_to_cstring(), but allows embedded NUL, as this just concat multiple iovec, the result may not be a string. Now, iovw_to_cstring() internally uses iovw_concat(). --- src/basic/iovec-wrapper.c | 48 +++++++++++++++++++++-------------- src/basic/iovec-wrapper.h | 1 + src/test/test-iovec-wrapper.c | 18 +++++++++++++ 3 files changed, 48 insertions(+), 19 deletions(-) diff --git a/src/basic/iovec-wrapper.c b/src/basic/iovec-wrapper.c index a76087b859e83..a7bcc95df5ee8 100644 --- a/src/basic/iovec-wrapper.c +++ b/src/basic/iovec-wrapper.c @@ -175,31 +175,41 @@ int iovw_append_iovw(struct iovec_wrapper *target, const struct iovec_wrapper *s return r; } -char* iovw_to_cstring(const struct iovec_wrapper *iovw) { - size_t size; - char *p, *ans; - +int iovw_concat(const struct iovec_wrapper *iovw, struct iovec *ret) { assert(iovw); + assert(ret); - /* Squish a series of iovecs into a C string. Embedded NULs are not allowed. - * The caller is expected to filter them out when populating the data. */ + /* Squish a series of iovecs into a single iovec. */ - size = iovw_size(iovw); - if (size == SIZE_MAX) - return NULL; /* Prevent theoretical overflow */ - size ++; + size_t len = iovw_size(iovw); + if (len == SIZE_MAX) + return -E2BIG; /* Prevent theoretical overflow */ - p = ans = new(char, size); - if (!ans) - return NULL; + /* Always allocate one more byte to make the result usable as a NUL-terminated string. */ + _cleanup_free_ uint8_t *buf = malloc(len + 1); + if (!buf) + return -ENOMEM; + + uint8_t *p = buf; + FOREACH_ARRAY(i, iovw->iovec, iovw->count) + p = mempcpy(p, i->iov_base, i->iov_len); - FOREACH_ARRAY(iovec, iovw->iovec, iovw->count) { - assert(!memchr(iovec->iov_base, 0, iovec->iov_len)); + *p = 0; - p = mempcpy(p, iovec->iov_base, iovec->iov_len); - } + *ret = IOVEC_MAKE(TAKE_PTR(buf), len); + return 0; +} + +char* iovw_to_cstring(const struct iovec_wrapper *iovw) { + assert(iovw); - *p = '\0'; + /* Squish a series of iovecs into a C string. Embedded NULs are not allowed. + * The caller is expected to filter them out when populating the data. */ + + _cleanup_(iovec_done) struct iovec iov = {}; + if (iovw_concat(iovw, &iov) < 0) + return NULL; - return ans; + assert(!memchr(iov.iov_base, 0, iov.iov_len)); + return TAKE_PTR(iov.iov_base); } diff --git a/src/basic/iovec-wrapper.h b/src/basic/iovec-wrapper.h index d2437b60f1925..cbf40b725af9b 100644 --- a/src/basic/iovec-wrapper.h +++ b/src/basic/iovec-wrapper.h @@ -38,4 +38,5 @@ int iovw_put_string_field_free(struct iovec_wrapper *iovw, const char *field, ch void iovw_rebase(struct iovec_wrapper *iovw, void *old, void *new); size_t iovw_size(const struct iovec_wrapper *iovw); int iovw_append_iovw(struct iovec_wrapper *target, const struct iovec_wrapper *source); +int iovw_concat(const struct iovec_wrapper *iovw, struct iovec *ret); char* iovw_to_cstring(const struct iovec_wrapper *iovw); diff --git a/src/test/test-iovec-wrapper.c b/src/test/test-iovec-wrapper.c index 168217f16c237..8d05617154759 100644 --- a/src/test/test-iovec-wrapper.c +++ b/src/test/test-iovec-wrapper.c @@ -279,6 +279,24 @@ TEST(iovw_append_iovw) { ASSERT_EQ(source.count, 2U); } +TEST(iovw_concat) { + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + + /* Empty wrapper -> empty string with 0 length */ + _cleanup_(iovec_done) struct iovec iov = {}; + ASSERT_OK(iovw_concat(&iovw, &iov)); + ASSERT_FALSE(iovec_is_set(&iov)); + ASSERT_STREQ(iov.iov_base, ""); + iovec_done(&iov); + + ASSERT_OK(iovw_put(&iovw, (char*) "foo", 3)); + ASSERT_OK(iovw_put(&iovw, (char*) "\0", 1)); + ASSERT_OK(iovw_put(&iovw, (char*) "bar", 4)); + + ASSERT_OK(iovw_concat(&iovw, &iov)); + ASSERT_EQ(iovec_memcmp(&iov, &IOVEC_MAKE("foo\0bar\0", 8)), 0); +} + TEST(iovw_to_cstring) { _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; _cleanup_free_ char *s; From 6d14614f4d4efe14af6a79766c5b509d2c2d2596 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 17 Apr 2026 23:03:11 +0900 Subject: [PATCH 1097/1296] iovec-wrapper: rename iovw_append() to iovw_extend() The naming is consistent with strv_extend(). This also - introduces tiny iovw_extend_iov() wrapper, - refuse when the source and target points to the same object, - check the final count before extending in iovw_extend_iovw(). --- src/basic/iovec-wrapper.c | 76 +++++++++++++--------- src/basic/iovec-wrapper.h | 5 +- src/coredump/coredump-backtrace.c | 2 +- src/report/report-upload.c | 2 +- src/test/test-iovec-wrapper.c | 101 +++++++++++++++++++----------- 5 files changed, 115 insertions(+), 71 deletions(-) diff --git a/src/basic/iovec-wrapper.c b/src/basic/iovec-wrapper.c index a7bcc95df5ee8..f1b64dc0d5ad4 100644 --- a/src/basic/iovec-wrapper.c +++ b/src/basic/iovec-wrapper.c @@ -74,7 +74,7 @@ int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len) { return r; } -int iovw_append(struct iovec_wrapper *iovw, const void *data, size_t len) { +int iovw_extend(struct iovec_wrapper *iovw, const void *data, size_t len) { if (len == 0) return 0; @@ -85,6 +85,52 @@ int iovw_append(struct iovec_wrapper *iovw, const void *data, size_t len) { return iovw_consume(iovw, c, len); } +int iovw_extend_iov(struct iovec_wrapper *iovw, const struct iovec *iov) { + assert(iovw); + + if (!iovec_is_set(iov)) + return 0; + + return iovw_extend(iovw, iov->iov_base, iov->iov_len); +} + +int iovw_extend_iovw(struct iovec_wrapper *iovw, const struct iovec_wrapper *source) { + int r; + + assert(iovw); + + /* This duplicates the source and merges it into the iovw. */ + + if (iovw_isempty(source)) + return 0; + + /* iovw->iovec will be reallocated in the loop below, hence source cannot point to the same object. */ + if (iovw == source) + return -EINVAL; + + if (iovw->count > SIZE_MAX - source->count) + return -E2BIG; + if (iovw->count + source->count > IOV_MAX) + return -E2BIG; + + size_t original_count = iovw->count; + + FOREACH_ARRAY(iovec, source->iovec, source->count) { + r = iovw_extend_iov(iovw, iovec); + if (r < 0) + goto rollback; + } + + return 0; + +rollback: + for (size_t i = original_count; i < iovw->count; i++) + iovec_done(iovw->iovec + i); + + iovw->count = original_count; + return r; +} + int iovw_put_string_field_full(struct iovec_wrapper *iovw, bool replace, const char *field, const char *value) { _cleanup_free_ char *x = NULL; int r; @@ -147,34 +193,6 @@ size_t iovw_size(const struct iovec_wrapper *iovw) { return iovec_total_size(iovw->iovec, iovw->count); } -int iovw_append_iovw(struct iovec_wrapper *target, const struct iovec_wrapper *source) { - int r; - - assert(target); - - /* This duplicates the source and merges it into the target. */ - - if (iovw_isempty(source)) - return 0; - - size_t original_count = target->count; - - FOREACH_ARRAY(iovec, source->iovec, source->count) { - r = iovw_append(target, iovec->iov_base, iovec->iov_len); - if (r < 0) - goto rollback; - } - - return 0; - -rollback: - for (size_t i = original_count; i < target->count; i++) - iovec_done(target->iovec + i); - - target->count = original_count; - return r; -} - int iovw_concat(const struct iovec_wrapper *iovw, struct iovec *ret) { assert(iovw); assert(ret); diff --git a/src/basic/iovec-wrapper.h b/src/basic/iovec-wrapper.h index cbf40b725af9b..88d30c40df17d 100644 --- a/src/basic/iovec-wrapper.h +++ b/src/basic/iovec-wrapper.h @@ -18,7 +18,9 @@ static inline bool iovw_equal(const struct iovec_wrapper *a, const struct iovec_ int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len); int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len); -int iovw_append(struct iovec_wrapper *iovw, const void *data, size_t len); +int iovw_extend(struct iovec_wrapper *iovw, const void *data, size_t len); +int iovw_extend_iov(struct iovec_wrapper *iovw, const struct iovec *iov); +int iovw_extend_iovw(struct iovec_wrapper *iovw, const struct iovec_wrapper *source); static inline bool iovw_isempty(const struct iovec_wrapper *iovw) { return !iovw || iovw->count == 0; @@ -37,6 +39,5 @@ int iovw_put_string_fieldf_full(struct iovec_wrapper *iovw, bool replace, const int iovw_put_string_field_free(struct iovec_wrapper *iovw, const char *field, char *value); void iovw_rebase(struct iovec_wrapper *iovw, void *old, void *new); size_t iovw_size(const struct iovec_wrapper *iovw); -int iovw_append_iovw(struct iovec_wrapper *target, const struct iovec_wrapper *source); int iovw_concat(const struct iovec_wrapper *iovw, struct iovec *ret); char* iovw_to_cstring(const struct iovec_wrapper *iovw); diff --git a/src/coredump/coredump-backtrace.c b/src/coredump/coredump-backtrace.c index b8aa880f8e440..9af7b5adb9613 100644 --- a/src/coredump/coredump-backtrace.c +++ b/src/coredump/coredump-backtrace.c @@ -50,7 +50,7 @@ int coredump_backtrace(int argc, char *argv[]) { } else { /* The imported iovecs are not supposed to be freed by us so let's copy and merge them at the * end of the array. */ - r = iovw_append_iovw(&context.iovw, &importer.iovw); + r = iovw_extend_iovw(&context.iovw, &importer.iovw); if (r < 0) return r; } diff --git a/src/report/report-upload.c b/src/report/report-upload.c index 218742f540cc7..1744d0d91dda6 100644 --- a/src/report/report-upload.c +++ b/src/report/report-upload.c @@ -41,7 +41,7 @@ static size_t output_callback(char *buf, return 0; } - r = iovw_append(&context->upload_answer, buf, nmemb); + r = iovw_extend(&context->upload_answer, buf, nmemb); if (r < 0) { log_warning("Failed to store server answer (%zu bytes): out of memory", nmemb); return 0; /* Returning < nmemb signals failure */ diff --git a/src/test/test-iovec-wrapper.c b/src/test/test-iovec-wrapper.c index 8d05617154759..51ff3689359a6 100644 --- a/src/test/test-iovec-wrapper.c +++ b/src/test/test-iovec-wrapper.c @@ -84,18 +84,23 @@ TEST(iovw_put) { ASSERT_EQ(memcmp(iovw.iovec[2].iov_base, "q", 1), 0); } -TEST(iovw_append) { +TEST(iovw_extend) { _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; - /* iovw_append copies the data; the wrapper owns the copies. */ + /* Appending an empty/NULL source is a no-op */ + ASSERT_OK_ZERO(iovw_extend(&iovw, NULL, 0)); + ASSERT_OK_ZERO(iovw_extend(&iovw, "foo", 0)); + ASSERT_EQ(iovw.count, 0U); + + /* iovw_extend() copies the data; the wrapper owns the copies. */ char buf[4] = { 'o', 'n', 'e', '\0' }; - ASSERT_OK(iovw_append(&iovw, buf, 3)); + ASSERT_OK(iovw_extend(&iovw, buf, 3)); ASSERT_EQ(iovw.count, 1U); ASSERT_EQ(iovw.iovec[0].iov_len, 3U); ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "one", 3), 0); /* Insert with a NUL */ - ASSERT_OK(iovw_append(&iovw, buf, 4)); + ASSERT_OK(iovw_extend(&iovw, buf, 4)); ASSERT_EQ(iovw.count, 2U); ASSERT_EQ(iovw.iovec[1].iov_len, 4U); ASSERT_EQ(memcmp(iovw.iovec[1].iov_base, "one\0", 4), 0); @@ -105,6 +110,60 @@ TEST(iovw_append) { ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "one", 3), 0); } +TEST(iovw_extend_iov) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + + /* Appending an empty/NULL source is a no-op */ + ASSERT_OK_ZERO(iovw_extend_iov(&iovw, NULL)); + ASSERT_OK_ZERO(iovw_extend_iov(&iovw, &(struct iovec) {})); + ASSERT_EQ(iovw.count, 0U); + + ASSERT_OK(iovw_extend_iov(&iovw, &IOVEC_MAKE_STRING("aaa"))); + ASSERT_OK(iovw_extend_iov(&iovw, &IOVEC_MAKE_STRING("bbb"))); + ASSERT_OK(iovw_extend_iov(&iovw, &IOVEC_MAKE_STRING("ccc"))); + ASSERT_EQ(iovw.count, 3U); + ASSERT_EQ(iovec_memcmp(&iovw.iovec[0], &IOVEC_MAKE_STRING("aaa")), 0); + ASSERT_EQ(iovec_memcmp(&iovw.iovec[1], &IOVEC_MAKE_STRING("bbb")), 0); + ASSERT_EQ(iovec_memcmp(&iovw.iovec[2], &IOVEC_MAKE_STRING("ccc")), 0); +} + +TEST(iovw_extend_iovw) { + _cleanup_(iovw_done_free) struct iovec_wrapper target = {}; + _cleanup_(iovw_done) struct iovec_wrapper source = {}; + + /* Appending an empty/NULL source is a no-op */ + ASSERT_OK_ZERO(iovw_extend_iovw(&target, NULL)); + ASSERT_OK_ZERO(iovw_extend_iovw(&target, &source)); + ASSERT_EQ(target.count, 0U); + + ASSERT_OK(iovw_put(&source, (char*) "one", 3)); + ASSERT_OK(iovw_put(&source, (char*) "twotwo", 6)); + ASSERT_EQ(source.count, 2U); + + /* Pre-seed target with one entry to check that append adds on top rather than replacing */ + char *seed = strdup("zero"); + ASSERT_NOT_NULL(seed); + ASSERT_OK(iovw_put(&target, seed, strlen(seed))); + + ASSERT_OK(iovw_extend_iovw(&target, &source)); + ASSERT_EQ(target.count, 3U); + + /* Appended entries must be fresh copies, not aliases of the source entries */ + ASSERT_TRUE(target.iovec[1].iov_base != source.iovec[0].iov_base); + ASSERT_TRUE(target.iovec[2].iov_base != source.iovec[1].iov_base); + + ASSERT_EQ(target.iovec[1].iov_len, 3U); + ASSERT_EQ(memcmp(target.iovec[1].iov_base, "one", 3), 0); + ASSERT_EQ(target.iovec[2].iov_len, 6U); + ASSERT_EQ(memcmp(target.iovec[2].iov_base, "twotwo", 6), 0); + + /* Source is unchanged */ + ASSERT_EQ(source.count, 2U); + + /* Cannot pass the same objects */ + ASSERT_ERROR(iovw_extend_iovw(&target, &target), EINVAL); +} + TEST(iovw_consume) { _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; @@ -245,40 +304,6 @@ TEST(iovw_size) { ASSERT_EQ(iovw_size(&iovw), 12U); } -TEST(iovw_append_iovw) { - _cleanup_(iovw_done_free) struct iovec_wrapper target = {}; - _cleanup_(iovw_done) struct iovec_wrapper source = {}; - - /* Appending an empty/NULL source is a no-op */ - ASSERT_OK_ZERO(iovw_append_iovw(&target, NULL)); - ASSERT_OK_ZERO(iovw_append_iovw(&target, &source)); - ASSERT_EQ(target.count, 0U); - - ASSERT_OK(iovw_put(&source, (char*) "one", 3)); - ASSERT_OK(iovw_put(&source, (char*) "twotwo", 6)); - ASSERT_EQ(source.count, 2U); - - /* Pre-seed target with one entry to check that append adds on top rather than replacing */ - char *seed = strdup("zero"); - ASSERT_NOT_NULL(seed); - ASSERT_OK(iovw_put(&target, seed, strlen(seed))); - - ASSERT_OK(iovw_append_iovw(&target, &source)); - ASSERT_EQ(target.count, 3U); - - /* Appended entries must be fresh copies, not aliases of the source entries */ - ASSERT_TRUE(target.iovec[1].iov_base != source.iovec[0].iov_base); - ASSERT_TRUE(target.iovec[2].iov_base != source.iovec[1].iov_base); - - ASSERT_EQ(target.iovec[1].iov_len, 3U); - ASSERT_EQ(memcmp(target.iovec[1].iov_base, "one", 3), 0); - ASSERT_EQ(target.iovec[2].iov_len, 6U); - ASSERT_EQ(memcmp(target.iovec[2].iov_base, "twotwo", 6), 0); - - /* Source is unchanged */ - ASSERT_EQ(source.count, 2U); -} - TEST(iovw_concat) { _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; From f71dcac4dc6184e269d1a5766075409b35ff4072 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 17 Apr 2026 23:06:28 +0900 Subject: [PATCH 1098/1296] iovec-wrapper: introduce several more helper functions --- src/basic/iovec-wrapper.c | 49 +++++++++++++++++++ src/basic/iovec-wrapper.h | 3 ++ src/test/test-iovec-wrapper.c | 91 +++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+) diff --git a/src/basic/iovec-wrapper.c b/src/basic/iovec-wrapper.c index f1b64dc0d5ad4..59b1addaaf266 100644 --- a/src/basic/iovec-wrapper.c +++ b/src/basic/iovec-wrapper.c @@ -63,6 +63,36 @@ int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len) { return 1; } +int iovw_put_iov(struct iovec_wrapper *iovw, const struct iovec *iov) { + assert(iovw); + + if (!iov) + return 0; + + return iovw_put(iovw, iov->iov_base, iov->iov_len); +} + +int iovw_put_iovw(struct iovec_wrapper *iovw, const struct iovec_wrapper *source) { + assert(iovw); + + if (iovw_isempty(source)) + return 0; + + /* We will reallocate iovw->iovec, hence the source cannot point to the same object. */ + if (iovw == source) + return -EINVAL; + + if (iovw->count > SIZE_MAX - source->count) + return -E2BIG; + if (iovw->count + source->count > IOV_MAX) + return -E2BIG; + + if (!GREEDY_REALLOC_APPEND(iovw->iovec, iovw->count, source->iovec, source->count)) + return -ENOMEM; + + return 0; +} + int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len) { /* Move data into iovw or free on error */ int r; @@ -74,6 +104,25 @@ int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len) { return r; } +int iovw_consume_iov(struct iovec_wrapper *iovw, struct iovec *iov) { + int r; + + assert(iovw); + + if (!iov) + return 0; + + r = iovw_put_iov(iovw, iov); + if (r <= 0) + iovec_done(iov); + else + /* On success, iov->iov_base is now owned by iovw. Let's emptify iov, but do not call + * iovec_done(), of course. */ + *iov = (struct iovec) {}; + + return r; +} + int iovw_extend(struct iovec_wrapper *iovw, const void *data, size_t len) { if (len == 0) return 0; diff --git a/src/basic/iovec-wrapper.h b/src/basic/iovec-wrapper.h index 88d30c40df17d..26b7f5ad4be30 100644 --- a/src/basic/iovec-wrapper.h +++ b/src/basic/iovec-wrapper.h @@ -17,7 +17,10 @@ static inline bool iovw_equal(const struct iovec_wrapper *a, const struct iovec_ } int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len); +int iovw_put_iov(struct iovec_wrapper *iovw, const struct iovec *iov); +int iovw_put_iovw(struct iovec_wrapper *iovw, const struct iovec_wrapper *source); int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len); +int iovw_consume_iov(struct iovec_wrapper *iovw, struct iovec *iov); int iovw_extend(struct iovec_wrapper *iovw, const void *data, size_t len); int iovw_extend_iov(struct iovec_wrapper *iovw, const struct iovec *iov); int iovw_extend_iovw(struct iovec_wrapper *iovw, const struct iovec_wrapper *source); diff --git a/src/test/test-iovec-wrapper.c b/src/test/test-iovec-wrapper.c index 51ff3689359a6..6764d2e78eb6b 100644 --- a/src/test/test-iovec-wrapper.c +++ b/src/test/test-iovec-wrapper.c @@ -84,6 +84,67 @@ TEST(iovw_put) { ASSERT_EQ(memcmp(iovw.iovec[2].iov_base, "q", 1), 0); } +TEST(iovw_put_iov) { + /* iovw_put_iov() does not copy the input, hence do not use iovw_done_free */ + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + + /* Appending an empty/NULL source is a no-op */ + ASSERT_OK_ZERO(iovw_put_iov(&iovw, NULL)); + ASSERT_OK_ZERO(iovw_put_iov(&iovw, &(struct iovec) {})); + ASSERT_EQ(iovw.count, 0U); + + ASSERT_OK(iovw_put_iov(&iovw, &IOVEC_MAKE_STRING("aaa"))); + ASSERT_OK(iovw_put_iov(&iovw, &IOVEC_MAKE_STRING("bbb"))); + ASSERT_OK(iovw_put_iov(&iovw, &IOVEC_MAKE_STRING("ccc"))); + ASSERT_EQ(iovw.count, 3U); + ASSERT_EQ(iovec_memcmp(&iovw.iovec[0], &IOVEC_MAKE_STRING("aaa")), 0); + ASSERT_EQ(iovec_memcmp(&iovw.iovec[1], &IOVEC_MAKE_STRING("bbb")), 0); + ASSERT_EQ(iovec_memcmp(&iovw.iovec[2], &IOVEC_MAKE_STRING("ccc")), 0); +} + +TEST(iovw_put_iovw) { + _cleanup_(iovw_done) struct iovec_wrapper target = {}, source = {}; + + /* Appending an empty/NULL source is a no-op */ + ASSERT_OK_ZERO(iovw_put_iovw(&target, NULL)); + ASSERT_OK_ZERO(iovw_put_iovw(&target, &source)); + ASSERT_EQ(target.count, 0U); + + ASSERT_OK(iovw_put_iov(&source, &IOVEC_MAKE_STRING("aaa"))); + ASSERT_OK(iovw_put_iov(&source, &IOVEC_MAKE_STRING("bbb"))); + ASSERT_OK(iovw_put_iov(&source, &IOVEC_MAKE_STRING("ccc"))); + ASSERT_EQ(source.count, 3U); + + /* Pre-seed target with one entry to check that append adds on top rather than replacing */ + ASSERT_OK(iovw_put_iov(&target, &IOVEC_MAKE_STRING("xxx"))); + ASSERT_OK(iovw_put_iov(&target, &IOVEC_MAKE_STRING("yyy"))); + ASSERT_OK(iovw_put_iov(&target, &IOVEC_MAKE_STRING("zzz"))); + ASSERT_EQ(target.count, 3U); + + ASSERT_OK(iovw_put_iovw(&target, &source)); + ASSERT_EQ(target.count, 6U); + ASSERT_EQ(iovec_memcmp(&target.iovec[0], &IOVEC_MAKE_STRING("xxx")), 0); + ASSERT_EQ(iovec_memcmp(&target.iovec[1], &IOVEC_MAKE_STRING("yyy")), 0); + ASSERT_EQ(iovec_memcmp(&target.iovec[2], &IOVEC_MAKE_STRING("zzz")), 0); + ASSERT_EQ(iovec_memcmp(&target.iovec[3], &IOVEC_MAKE_STRING("aaa")), 0); + ASSERT_EQ(iovec_memcmp(&target.iovec[4], &IOVEC_MAKE_STRING("bbb")), 0); + ASSERT_EQ(iovec_memcmp(&target.iovec[5], &IOVEC_MAKE_STRING("ccc")), 0); + + /* iovw_put_iovw() does not copy data, hence the pointers must be equal */ + ASSERT_PTR_EQ(target.iovec[3].iov_base, source.iovec[0].iov_base); + ASSERT_PTR_EQ(target.iovec[4].iov_base, source.iovec[1].iov_base); + ASSERT_PTR_EQ(target.iovec[5].iov_base, source.iovec[2].iov_base); + + /* Source is unchanged */ + ASSERT_EQ(source.count, 3U); + ASSERT_EQ(iovec_memcmp(&source.iovec[0], &IOVEC_MAKE_STRING("aaa")), 0); + ASSERT_EQ(iovec_memcmp(&source.iovec[1], &IOVEC_MAKE_STRING("bbb")), 0); + ASSERT_EQ(iovec_memcmp(&source.iovec[2], &IOVEC_MAKE_STRING("ccc")), 0); + + /* Cannot pass the same objects */ + ASSERT_ERROR(iovw_put_iovw(&target, &target), EINVAL); +} + TEST(iovw_extend) { _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; @@ -181,6 +242,36 @@ TEST(iovw_consume) { ASSERT_EQ(iovw.count, 1U); } +TEST(iovw_consume_iov) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + + ASSERT_OK_ZERO(iovw_consume_iov(&iovw, NULL)); + ASSERT_EQ(iovw.count, 0U); + + ASSERT_OK_ZERO(iovw_consume_iov(&iovw, &(struct iovec) {})); + ASSERT_EQ(iovw.count, 0U); + + struct iovec iov = { + .iov_base = ASSERT_NOT_NULL(strdup("consumed")), + .iov_len = strlen("consumed"), + }; + ASSERT_OK(iovw_consume_iov(&iovw, &iov)); + ASSERT_EQ(iovw.count, 1U); + /* iovw_consume_iov takes the ownership of the buffer, and emptifies the iovec. */ + ASSERT_NULL(iov.iov_base); + ASSERT_EQ(iov.iov_len, 0U); + + iov = (struct iovec) { + .iov_base = ASSERT_NOT_NULL(strdup("")), + .iov_len = 0, + }; + ASSERT_OK_ZERO(iovw_consume_iov(&iovw, &iov)); + ASSERT_EQ(iovw.count, 1U); + /* zero length iovec is also freed */ + ASSERT_NULL(iov.iov_base); + ASSERT_EQ(iov.iov_len, 0U); +} + TEST(iovw_isempty) { ASSERT_TRUE(iovw_isempty(NULL)); From 20558ddb5aa37f2e511d787c9f1be1152985865c Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 18 Apr 2026 07:19:27 +0900 Subject: [PATCH 1099/1296] iovec-util: introduce iovec_equal() --- src/basic/iovec-util.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/basic/iovec-util.h b/src/basic/iovec-util.h index 00cbb89a7790b..21aad9edfc196 100644 --- a/src/basic/iovec-util.h +++ b/src/basic/iovec-util.h @@ -38,6 +38,9 @@ char* set_iovec_string_field_free(struct iovec *iovec, size_t *n_iovec, const ch void iovec_array_free(struct iovec *iovec, size_t n_iovec) _nonnull_if_nonzero_(1, 2); int iovec_memcmp(const struct iovec *a, const struct iovec *b) _pure_; +static inline bool iovec_equal(const struct iovec *a, const struct iovec *b) { + return iovec_memcmp(a, b) == 0; +} struct iovec* iovec_memdup(const struct iovec *source, struct iovec *ret); From 036ea5b14ff6e9c9a1283dc4e00d17048bb60456 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 19 Apr 2026 03:30:49 +0900 Subject: [PATCH 1100/1296] tree-wide: use iovec_equal() --- coccinelle/memcmp.cocci | 19 ++++++++++++++++++ src/core/namespace.c | 2 +- src/cryptenroll/cryptenroll-tpm2.c | 6 +++--- src/import/pull-job.c | 2 +- src/import/pull-oci.c | 2 +- src/shared/dissect-image.c | 6 +++--- src/shared/tpm2-util.c | 6 +++--- src/sysupdate/sysupdate-resource.c | 2 +- src/test/test-creds.c | 2 +- src/test/test-fileio.c | 10 +++++----- src/test/test-iovec-wrapper.c | 32 +++++++++++++++--------------- src/test/test-json.c | 4 ++-- src/test/test-log.c | 10 +++++----- src/test/test-tpm2.c | 2 +- 14 files changed, 62 insertions(+), 43 deletions(-) create mode 100644 coccinelle/memcmp.cocci diff --git a/coccinelle/memcmp.cocci b/coccinelle/memcmp.cocci new file mode 100644 index 0000000000000..aa0effa4f8c4b --- /dev/null +++ b/coccinelle/memcmp.cocci @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Disable this transformation for iovec-util.h and the unit test */ +@ depends on !(file in "src/basic/iovec-util.h") + && !(file in "src/test/test-iovec-util.c") @ +expression a, b; +@@ +( +- iovec_memcmp(a, b) == 0 ++ iovec_equal(a, b) +| +- iovec_memcmp(a, b) != 0 ++ !iovec_equal(a, b) +| +- ASSERT_EQ(iovec_memcmp(a, b), 0) ++ ASSERT_TRUE(iovec_equal(a, b)) +| +- ASSERT_NE(iovec_memcmp(a, b), 0) ++ ASSERT_FALSE(iovec_equal(a, b)) +) diff --git a/src/core/namespace.c b/src/core/namespace.c index ff4592b8b90cd..bd27cbe2b5c70 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -1049,7 +1049,7 @@ static bool verity_has_later_duplicates(MountList *ml, const MountEntry *needle) for (const MountEntry *m = needle + 1; m < ml->mounts + ml->n_mounts; m++) { if (m->mode != MOUNT_EXTENSION_IMAGE) continue; - if (iovec_memcmp(&m->verity.root_hash, &needle->verity.root_hash) == 0) + if (iovec_equal(&m->verity.root_hash, &needle->verity.root_hash)) return true; } diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c index 50abca43639b8..9badd9fd6760f 100644 --- a/src/cryptenroll/cryptenroll-tpm2.c +++ b/src/cryptenroll/cryptenroll-tpm2.c @@ -72,7 +72,7 @@ static int search_policy_hash( if (r < 0) return log_error_errno(r, "Invalid hex data in 'tpm2-policy-hash' field item : %m"); - if (iovec_memcmp(policy_hash + j, &thash) != 0) { + if (!iovec_equal(policy_hash + j, &thash)) { match = false; break; } @@ -91,7 +91,7 @@ static int search_policy_hash( if (r < 0) return log_error_errno(r, "Invalid hex data in 'tpm2-policy-hash' field: %m"); - if (iovec_memcmp(policy_hash + 0, &thash) == 0) + if (iovec_equal(policy_hash + 0, &thash)) return keyslot; /* Found entry with same hash. */ } } @@ -566,7 +566,7 @@ int enroll_tpm2(struct crypt_device *cd, if (r < 0) return log_error_errno(r, "Failed to unseal secret using TPM2: %m"); - if (iovec_memcmp(&secret, &secret2) != 0) + if (!iovec_equal(&secret, &secret2)) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 seal/unseal verification failed."); } diff --git a/src/import/pull-job.c b/src/import/pull-job.c index 385043dda8003..e847d5e2fbe41 100644 --- a/src/import/pull-job.c +++ b/src/import/pull-job.c @@ -298,7 +298,7 @@ void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { } if (iovec_is_set(&j->expected_checksum) && - iovec_memcmp(&j->checksum, &j->expected_checksum) != 0) { + !iovec_equal(&j->checksum, &j->expected_checksum)) { r = log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Checksum of downloaded resource does not match expected checksum, yikes."); goto finish; } diff --git a/src/import/pull-oci.c b/src/import/pull-oci.c index 4ae933928ff5f..f4878e3c87d31 100644 --- a/src/import/pull-oci.c +++ b/src/import/pull-oci.c @@ -1195,7 +1195,7 @@ static int oci_pull_make_local(OciPull *i) { return r; if (!iovec_is_set(&i->config) || - iovec_memcmp(&i->config, &IOVEC_MAKE_STRING("{}")) == 0) + iovec_equal(&i->config, &IOVEC_MAKE_STRING("{}"))) log_info("Image has no configuration, not saving."); else { r = oci_pull_save_nspawn_settings(i); diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index e33e78d31026d..894fabf7a6eee 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -1422,7 +1422,7 @@ static int dissect_image( /* ret_root_hash_sig= */ NULL); if (r < 0) return r; - if (iovec_memcmp(&verity->root_hash, &root_hash) != 0) { + if (!iovec_equal(&verity->root_hash, &root_hash)) { if (DEBUG_LOGGING) { _cleanup_free_ char *found = NULL, *expected = NULL; @@ -3023,7 +3023,7 @@ static int verity_can_reuse( r = sym_crypt_volume_key_get(cd, CRYPT_ANY_SLOT, root_hash_existing.iov_base, &root_hash_existing.iov_len, NULL, 0); if (r < 0) return log_debug_errno(r, "Error opening verity device, crypt_volume_key_get failed: %m"); - if (iovec_memcmp(&verity->root_hash, &root_hash_existing) != 0) + if (!iovec_equal(&verity->root_hash, &root_hash_existing)) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Error opening verity device, it already exists but root hashes are different."); /* Ensure that, if signatures are supported, we only reuse the device if the previous mount used the @@ -4048,7 +4048,7 @@ int dissected_image_load_verity_sig_partition( /* Check if specified root hash matches if it is specified */ if (iovec_is_set(&verity->root_hash) && - iovec_memcmp(&verity->root_hash, &root_hash) != 0) { + !iovec_equal(&verity->root_hash, &root_hash)) { _cleanup_free_ char *a = NULL, *b = NULL; a = hexmem(root_hash.iov_base, root_hash.iov_len); diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 02f89c02ba71f..bddf7a74bfe11 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -7176,7 +7176,7 @@ static int tpm2_nvpcr_write_anchor_secret( if (r < 0) { if (r != -ENOENT) return log_error_errno(r, "Failed to read '%s' file: %m", joined); - } else if (iovec_memcmp(&existing, credential) == 0) { + } else if (iovec_equal(&existing, credential)) { log_debug("Anchor secret file '%s' already matches expectations, not updating.", joined); return 0; } else @@ -8460,8 +8460,8 @@ int tpm2_pcrlock_policy_from_credentials( continue; } - if ((!srk || iovec_memcmp(srk, &loaded_policy.srk_handle) == 0) && - (!nv || iovec_memcmp(nv, &loaded_policy.nv_handle) == 0)) { + if ((!srk || iovec_equal(srk, &loaded_policy.srk_handle)) && + (!nv || iovec_equal(nv, &loaded_policy.nv_handle))) { *ret = TAKE_STRUCT(loaded_policy); return 1; } diff --git a/src/sysupdate/sysupdate-resource.c b/src/sysupdate/sysupdate-resource.c index b819fcd5b8586..a9ef81f71da66 100644 --- a/src/sysupdate/sysupdate-resource.c +++ b/src/sysupdate/sysupdate-resource.c @@ -430,7 +430,7 @@ static int process_magic_file( /* Even if we ignore if people have non-empty files for this file, let's nonetheless warn about it, * so that people fix it. After all we want to retain liberty to maybe one day place some useful data * inside it */ - if (iovec_memcmp(&IOVEC_MAKE(expected_hash, sizeof(expected_hash)), hash) != 0) + if (!iovec_equal(&IOVEC_MAKE(expected_hash, sizeof(expected_hash)), hash)) log_warning("Hash of best before marker file '%s' has unexpected value, proceeding anyway.", fn); usec_t best_before; diff --git a/src/test/test-creds.c b/src/test/test-creds.c index 5cad608093341..e380550a5a70c 100644 --- a/src/test/test-creds.c +++ b/src/test/test-creds.c @@ -177,7 +177,7 @@ static void test_encrypt_decrypt_with(sd_id128_t mode, uid_t uid) { &decrypted); ASSERT_OK(r); - ASSERT_EQ(iovec_memcmp(&plaintext, &decrypted), 0); + ASSERT_TRUE(iovec_equal(&plaintext, &decrypted)); } static bool try_tpm2(void) { diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index 575e2c52ed7df..a752b1c7cda23 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -702,13 +702,13 @@ TEST(write_data_file_atomic_at) { _cleanup_(iovec_done) struct iovec ra = {}; ASSERT_OK(read_full_file("/tmp/wdfa", (char**) &ra.iov_base, &ra.iov_len)); - ASSERT_EQ(iovec_memcmp(&a, &ra), 0); + ASSERT_TRUE(iovec_equal(&a, &ra)); ASSERT_OK_ERRNO(unlink("/tmp/wdfa")); ASSERT_OK(write_data_file_atomic_at(XAT_FDROOT, "tmp/wdfa", &a, /* flags= */ 0)); iovec_done(&ra); ASSERT_OK(read_full_file("/tmp/wdfa", (char**) &ra.iov_base, &ra.iov_len)); - ASSERT_EQ(iovec_memcmp(&a, &ra), 0); + ASSERT_TRUE(iovec_equal(&a, &ra)); ASSERT_OK_ERRNO(unlink("/tmp/wdfa")); ASSERT_ERROR(write_data_file_atomic_at(AT_FDCWD, NULL, &a, /* flags= */ 0), EINVAL); @@ -724,13 +724,13 @@ TEST(write_data_file_atomic_at) { ASSERT_OK(write_data_file_atomic_at(AT_FDCWD, "wdfa", &a, /* flags= */ 0)); iovec_done(&ra); ASSERT_OK(read_full_file("/tmp/wdfa", (char**) &ra.iov_base, &ra.iov_len)); - ASSERT_EQ(iovec_memcmp(&a, &ra), 0); + ASSERT_TRUE(iovec_equal(&a, &ra)); ASSERT_OK_ERRNO(unlink("/tmp/wdfa")); ASSERT_OK(write_data_file_atomic_at(XAT_FDROOT, "tmp/wdfa", &a, /* flags= */ 0)); iovec_done(&ra); ASSERT_OK(read_full_file("/tmp/wdfa", (char**) &ra.iov_base, &ra.iov_len)); - ASSERT_EQ(iovec_memcmp(&a, &ra), 0); + ASSERT_TRUE(iovec_equal(&a, &ra)); ASSERT_OK_ERRNO(unlink("/tmp/wdfa")); ASSERT_OK_ERRNO(chdir(cwd)); @@ -739,7 +739,7 @@ TEST(write_data_file_atomic_at) { ASSERT_OK(write_data_file_atomic_at(XAT_FDROOT, "tmp/zzz/wdfa", &a, WRITE_DATA_FILE_MKDIR_0755)); iovec_done(&ra); ASSERT_OK(read_full_file("/tmp/zzz/wdfa", (char**) &ra.iov_base, &ra.iov_len)); - ASSERT_EQ(iovec_memcmp(&a, &ra), 0); + ASSERT_TRUE(iovec_equal(&a, &ra)); ASSERT_OK_ERRNO(unlink("/tmp/zzz/wdfa")); ASSERT_ERROR(write_data_file_atomic_at(AT_FDCWD, "/tmp/zzz", &a, /* flags= */ 0), EEXIST); diff --git a/src/test/test-iovec-wrapper.c b/src/test/test-iovec-wrapper.c index 6764d2e78eb6b..791a041ab8d6f 100644 --- a/src/test/test-iovec-wrapper.c +++ b/src/test/test-iovec-wrapper.c @@ -97,9 +97,9 @@ TEST(iovw_put_iov) { ASSERT_OK(iovw_put_iov(&iovw, &IOVEC_MAKE_STRING("bbb"))); ASSERT_OK(iovw_put_iov(&iovw, &IOVEC_MAKE_STRING("ccc"))); ASSERT_EQ(iovw.count, 3U); - ASSERT_EQ(iovec_memcmp(&iovw.iovec[0], &IOVEC_MAKE_STRING("aaa")), 0); - ASSERT_EQ(iovec_memcmp(&iovw.iovec[1], &IOVEC_MAKE_STRING("bbb")), 0); - ASSERT_EQ(iovec_memcmp(&iovw.iovec[2], &IOVEC_MAKE_STRING("ccc")), 0); + ASSERT_TRUE(iovec_equal(&iovw.iovec[0], &IOVEC_MAKE_STRING("aaa"))); + ASSERT_TRUE(iovec_equal(&iovw.iovec[1], &IOVEC_MAKE_STRING("bbb"))); + ASSERT_TRUE(iovec_equal(&iovw.iovec[2], &IOVEC_MAKE_STRING("ccc"))); } TEST(iovw_put_iovw) { @@ -123,12 +123,12 @@ TEST(iovw_put_iovw) { ASSERT_OK(iovw_put_iovw(&target, &source)); ASSERT_EQ(target.count, 6U); - ASSERT_EQ(iovec_memcmp(&target.iovec[0], &IOVEC_MAKE_STRING("xxx")), 0); - ASSERT_EQ(iovec_memcmp(&target.iovec[1], &IOVEC_MAKE_STRING("yyy")), 0); - ASSERT_EQ(iovec_memcmp(&target.iovec[2], &IOVEC_MAKE_STRING("zzz")), 0); - ASSERT_EQ(iovec_memcmp(&target.iovec[3], &IOVEC_MAKE_STRING("aaa")), 0); - ASSERT_EQ(iovec_memcmp(&target.iovec[4], &IOVEC_MAKE_STRING("bbb")), 0); - ASSERT_EQ(iovec_memcmp(&target.iovec[5], &IOVEC_MAKE_STRING("ccc")), 0); + ASSERT_TRUE(iovec_equal(&target.iovec[0], &IOVEC_MAKE_STRING("xxx"))); + ASSERT_TRUE(iovec_equal(&target.iovec[1], &IOVEC_MAKE_STRING("yyy"))); + ASSERT_TRUE(iovec_equal(&target.iovec[2], &IOVEC_MAKE_STRING("zzz"))); + ASSERT_TRUE(iovec_equal(&target.iovec[3], &IOVEC_MAKE_STRING("aaa"))); + ASSERT_TRUE(iovec_equal(&target.iovec[4], &IOVEC_MAKE_STRING("bbb"))); + ASSERT_TRUE(iovec_equal(&target.iovec[5], &IOVEC_MAKE_STRING("ccc"))); /* iovw_put_iovw() does not copy data, hence the pointers must be equal */ ASSERT_PTR_EQ(target.iovec[3].iov_base, source.iovec[0].iov_base); @@ -137,9 +137,9 @@ TEST(iovw_put_iovw) { /* Source is unchanged */ ASSERT_EQ(source.count, 3U); - ASSERT_EQ(iovec_memcmp(&source.iovec[0], &IOVEC_MAKE_STRING("aaa")), 0); - ASSERT_EQ(iovec_memcmp(&source.iovec[1], &IOVEC_MAKE_STRING("bbb")), 0); - ASSERT_EQ(iovec_memcmp(&source.iovec[2], &IOVEC_MAKE_STRING("ccc")), 0); + ASSERT_TRUE(iovec_equal(&source.iovec[0], &IOVEC_MAKE_STRING("aaa"))); + ASSERT_TRUE(iovec_equal(&source.iovec[1], &IOVEC_MAKE_STRING("bbb"))); + ASSERT_TRUE(iovec_equal(&source.iovec[2], &IOVEC_MAKE_STRING("ccc"))); /* Cannot pass the same objects */ ASSERT_ERROR(iovw_put_iovw(&target, &target), EINVAL); @@ -183,9 +183,9 @@ TEST(iovw_extend_iov) { ASSERT_OK(iovw_extend_iov(&iovw, &IOVEC_MAKE_STRING("bbb"))); ASSERT_OK(iovw_extend_iov(&iovw, &IOVEC_MAKE_STRING("ccc"))); ASSERT_EQ(iovw.count, 3U); - ASSERT_EQ(iovec_memcmp(&iovw.iovec[0], &IOVEC_MAKE_STRING("aaa")), 0); - ASSERT_EQ(iovec_memcmp(&iovw.iovec[1], &IOVEC_MAKE_STRING("bbb")), 0); - ASSERT_EQ(iovec_memcmp(&iovw.iovec[2], &IOVEC_MAKE_STRING("ccc")), 0); + ASSERT_TRUE(iovec_equal(&iovw.iovec[0], &IOVEC_MAKE_STRING("aaa"))); + ASSERT_TRUE(iovec_equal(&iovw.iovec[1], &IOVEC_MAKE_STRING("bbb"))); + ASSERT_TRUE(iovec_equal(&iovw.iovec[2], &IOVEC_MAKE_STRING("ccc"))); } TEST(iovw_extend_iovw) { @@ -410,7 +410,7 @@ TEST(iovw_concat) { ASSERT_OK(iovw_put(&iovw, (char*) "bar", 4)); ASSERT_OK(iovw_concat(&iovw, &iov)); - ASSERT_EQ(iovec_memcmp(&iov, &IOVEC_MAKE("foo\0bar\0", 8)), 0); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_MAKE("foo\0bar\0", 8))); } TEST(iovw_to_cstring) { diff --git a/src/test/test-json.c b/src/test/test-json.c index 1bd5d94f987d9..1785ef416f5b1 100644 --- a/src/test/test-json.c +++ b/src/test/test-json.c @@ -1206,8 +1206,8 @@ TEST(json_iovec) { assert_se(json_variant_unbase64_iovec(sd_json_variant_by_key(j, "nr1"), &a) >= 0); assert_se(json_variant_unhex_iovec(sd_json_variant_by_key(j, "nr2"), &b) >= 0); - assert_se(iovec_memcmp(&iov1, &a) == 0); - assert_se(iovec_memcmp(&iov2, &b) == 0); + assert_se(iovec_equal(&iov1, &a)); + assert_se(iovec_equal(&iov2, &b)); assert_se(iovec_memcmp(&iov2, &a) < 0); assert_se(iovec_memcmp(&iov1, &b) > 0); } diff --git a/src/test/test-log.c b/src/test/test-log.c index a62f54006e20f..fb23776b211d2 100644 --- a/src/test/test-log.c +++ b/src/test/test-log.c @@ -104,9 +104,9 @@ static void test_log_format_iovec_sentinel( for (size_t i = 0; i < n; i++) if (i < m) - ASSERT_EQ(iovec_memcmp(&iovec[i], &IOVEC_MAKE_STRING(v[i])), 0); + ASSERT_TRUE(iovec_equal(&iovec[i], &IOVEC_MAKE_STRING(v[i]))); else { - ASSERT_EQ(iovec_memcmp(&iovec[i], &IOVEC_MAKE_STRING(expected[i - m])), 0); + ASSERT_TRUE(iovec_equal(&iovec[i], &IOVEC_MAKE_STRING(expected[i - m]))); free(iovec[i].iov_base); } @@ -122,12 +122,12 @@ static void test_log_format_iovec_sentinel( for (size_t i = 0; i < n; i++) if (i < m) - ASSERT_EQ(iovec_memcmp(&iovec[i], &IOVEC_MAKE_STRING(v[i])), 0); + ASSERT_TRUE(iovec_equal(&iovec[i], &IOVEC_MAKE_STRING(v[i]))); else if ((i - m) % 2 == 0) { - ASSERT_EQ(iovec_memcmp(&iovec[i], &IOVEC_MAKE_STRING(expected[(i - m) / 2])), 0); + ASSERT_TRUE(iovec_equal(&iovec[i], &IOVEC_MAKE_STRING(expected[(i - m) / 2]))); free(iovec[i].iov_base); } else - ASSERT_EQ(iovec_memcmp(&iovec[i], &IOVEC_MAKE_STRING("\n")), 0); + ASSERT_TRUE(iovec_equal(&iovec[i], &IOVEC_MAKE_STRING("\n"))); } #define test_log_format_iovec_one(...) \ diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c index a6164f2677d52..78e2d234ab5c7 100644 --- a/src/test/test-tpm2.c +++ b/src/test/test-tpm2.c @@ -1271,7 +1271,7 @@ static void check_seal_unseal_for_handle(Tpm2Context *c, TPM2_HANDLE handle) { &srk, &unsealed_secret) >= 0); - assert_se(iovec_memcmp(&secret, &unsealed_secret) == 0); + assert_se(iovec_equal(&secret, &unsealed_secret)); } static void check_seal_unseal(Tpm2Context *c) { From 16f5a2992d3babdcbba319abaf9617db5ce1c924 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 19 Apr 2026 07:33:17 +0900 Subject: [PATCH 1101/1296] resolve: add missing OOM check --- src/resolve/resolved-dnstls.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/resolve/resolved-dnstls.c b/src/resolve/resolved-dnstls.c index 09dda4508b81f..bfc6a72183199 100644 --- a/src/resolve/resolved-dnstls.c +++ b/src/resolve/resolved-dnstls.c @@ -333,6 +333,9 @@ ssize_t dnstls_stream_writev(DnsStream *stream, const struct iovec *iov, size_t single buffer. Suboptimal, but better than multiple SSL_write calls. */ count = iovec_total_size(iov, iovcnt); buf = new(char, count); + if (!buf) + return -ENOMEM; + for (size_t i = 0, pos = 0; i < iovcnt; pos += iov[i].iov_len, i++) memcpy(buf + pos, iov[i].iov_base, iov[i].iov_len); From 9628b3118628343f6e156d49b9f2de040f6d675b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 18 Apr 2026 07:21:38 +0900 Subject: [PATCH 1102/1296] iovec-util: add overflow check in iovec_total_size() Mostly theoretical. But for safety. --- src/basic/iovec-util.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/basic/iovec-util.c b/src/basic/iovec-util.c index ec8af57e6424f..cd2c1a736e828 100644 --- a/src/basic/iovec-util.c +++ b/src/basic/iovec-util.c @@ -21,8 +21,11 @@ size_t iovec_total_size(const struct iovec *iovec, size_t n) { assert(iovec || n == 0); - FOREACH_ARRAY(j, iovec, n) + FOREACH_ARRAY(j, iovec, n) { + if (j->iov_len > SIZE_MAX - sum) + return SIZE_MAX; /* Indicate overflow. */ sum += j->iov_len; + } return sum; } From fa021e57513cf573d8cc932d90f0bd86a486589f Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 19 Apr 2026 03:41:42 +0900 Subject: [PATCH 1103/1296] tree-wide: check result of iovec_total_size() --- src/libsystemd/sd-bus/bus-message.c | 20 ++++++++++---------- src/resolve/resolved-dnstls.c | 15 ++++++++------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/libsystemd/sd-bus/bus-message.c b/src/libsystemd/sd-bus/bus-message.c index 041dc4821655d..94be969f7f420 100644 --- a/src/libsystemd/sd-bus/bus-message.c +++ b/src/libsystemd/sd-bus/bus-message.c @@ -1498,9 +1498,6 @@ _public_ int sd_bus_message_append_string_iovec( const struct iovec *iov, unsigned n /* should be size_t, but is API now… 😞 */) { - size_t size; - unsigned i; - char *p; int r; assert_return(m, -EINVAL); @@ -1508,13 +1505,16 @@ _public_ int sd_bus_message_append_string_iovec( assert_return(iov || n == 0, -EINVAL); assert_return(!m->poisoned, -ESTALE); - size = iovec_total_size(iov, n); + size_t size = iovec_total_size(iov, n); + if (size == SIZE_MAX) + return -ENOBUFS; + char *p; r = sd_bus_message_append_string_space(m, size, &p); if (r < 0) return r; - for (i = 0; i < n; i++) { + for (unsigned i = 0; i < n; i++) { if (iov[i].iov_base) memcpy(p, iov[i].iov_base, iov[i].iov_len); @@ -2160,9 +2160,6 @@ _public_ int sd_bus_message_append_array_iovec( const struct iovec *iov, unsigned n /* should be size_t, but is API now… 😞 */) { - size_t size; - unsigned i; - void *p; int r; assert_return(m, -EINVAL); @@ -2171,13 +2168,16 @@ _public_ int sd_bus_message_append_array_iovec( assert_return(iov || n == 0, -EINVAL); assert_return(!m->poisoned, -ESTALE); - size = iovec_total_size(iov, n); + size_t size = iovec_total_size(iov, n); + if (size == SIZE_MAX) + return -ENOBUFS; + void *p; r = sd_bus_message_append_array_space(m, type, size, &p); if (r < 0) return r; - for (i = 0; i < n; i++) { + for (unsigned i = 0; i < n; i++) { if (iov[i].iov_base) memcpy(p, iov[i].iov_base, iov[i].iov_len); diff --git a/src/resolve/resolved-dnstls.c b/src/resolve/resolved-dnstls.c index bfc6a72183199..042077ab066dd 100644 --- a/src/resolve/resolved-dnstls.c +++ b/src/resolve/resolved-dnstls.c @@ -317,29 +317,30 @@ static ssize_t dnstls_stream_write(DnsStream *stream, const char *buf, size_t co } ssize_t dnstls_stream_writev(DnsStream *stream, const struct iovec *iov, size_t iovcnt) { - _cleanup_free_ char *buf = NULL; - size_t count; - assert(stream); assert(stream->encrypted); assert(stream->dnstls_data.ssl); assert(iov); - assert(iovec_total_size(iov, iovcnt) > 0); + + size_t size = iovec_total_size(iov, iovcnt); + if (size == 0) + return -EINVAL; + if (size == SIZE_MAX) + return -ENOBUFS; if (iovcnt == 1) return dnstls_stream_write(stream, iov[0].iov_base, iov[0].iov_len); /* As of now, OpenSSL cannot accumulate multiple writes, so join into a single buffer. Suboptimal, but better than multiple SSL_write calls. */ - count = iovec_total_size(iov, iovcnt); - buf = new(char, count); + _cleanup_free_ char *buf = new(char, size); if (!buf) return -ENOMEM; for (size_t i = 0, pos = 0; i < iovcnt; pos += iov[i].iov_len, i++) memcpy(buf + pos, iov[i].iov_base, iov[i].iov_len); - return dnstls_stream_write(stream, buf, count); + return dnstls_stream_write(stream, buf, size); } ssize_t dnstls_stream_read(DnsStream *stream, void *buf, size_t count) { From c651876cc9cd660f5c4a27ee0b0197d3db1398f7 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 18 Mar 2026 02:00:08 +0900 Subject: [PATCH 1104/1296] dhcp-protocol: Option Overload (52) DHCP option value takes flags --- src/libsystemd-network/dhcp-protocol.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/libsystemd-network/dhcp-protocol.h b/src/libsystemd-network/dhcp-protocol.h index 425f730894d2b..659936c88a65e 100644 --- a/src/libsystemd-network/dhcp-protocol.h +++ b/src/libsystemd-network/dhcp-protocol.h @@ -89,10 +89,12 @@ enum { DHCPTLS = 18, /* [RFC7724] */ }; -enum { - DHCP_OVERLOAD_FILE = 1, - DHCP_OVERLOAD_SNAME = 2, -}; +typedef enum { + DHCP_OVERLOAD_NONE = 0, + DHCP_OVERLOAD_FILE = 1 << 0, + DHCP_OVERLOAD_SNAME = 1 << 1, + _DHCP_OVERLOAD_ALL = DHCP_OVERLOAD_FILE | DHCP_OVERLOAD_SNAME, +} DHCPOptionOverload; #define DHCP_MAX_FQDN_LENGTH 255 From 68553bb03a85058cd4e48c07eea62b67d476b20b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 18 Mar 2026 02:04:08 +0900 Subject: [PATCH 1105/1296] dhcp-protocol: introduce DHCPMessageHeader struct It is similar to DHCPMessage, but does not have the tail 'options' variable array. Then, we can use it in another struct. --- src/libsystemd-network/dhcp-protocol.h | 7 ++++++ src/libsystemd-network/test-dhcp-server.c | 30 +++++++++++------------ 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/libsystemd-network/dhcp-protocol.h b/src/libsystemd-network/dhcp-protocol.h index 659936c88a65e..14ccde10ddee7 100644 --- a/src/libsystemd-network/dhcp-protocol.h +++ b/src/libsystemd-network/dhcp-protocol.h @@ -35,12 +35,19 @@ uint8_t file[128]; \ be32_t magic; +struct DHCPMessageHeader { + DHCP_MESSAGE_HEADER_DEFINITION; +} _packed_; + +typedef struct DHCPMessageHeader DHCPMessageHeader; + struct DHCPMessage { DHCP_MESSAGE_HEADER_DEFINITION; uint8_t options[]; } _packed_; typedef struct DHCPMessage DHCPMessage; +assert_cc(sizeof(DHCPMessageHeader) == offsetof(DHCPMessage, options)); struct DHCPPacket { struct iphdr ip; diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c index e84d800ad8198..3dc49491269b1 100644 --- a/src/libsystemd-network/test-dhcp-server.c +++ b/src/libsystemd-network/test-dhcp-server.c @@ -83,9 +83,7 @@ static int test_basic(bool bind_to_interface) { static void test_message_handler(void) { _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; struct { - struct { - DHCP_MESSAGE_HEADER_DEFINITION; - } _packed_ message; + DHCPMessageHeader header; struct { uint8_t code; uint8_t length; @@ -113,11 +111,11 @@ static void test_message_handler(void) { } _packed_ option_hostname; uint8_t end; } _packed_ test = { - .message.op = BOOTREQUEST, - .message.htype = ARPHRD_ETHER, - .message.hlen = ETHER_ADDR_LEN, - .message.xid = htobe32(0x12345678), - .message.chaddr = { 'A', 'B', 'C', 'D', 'E', 'F' }, + .header.op = BOOTREQUEST, + .header.htype = ARPHRD_ETHER, + .header.hlen = ETHER_ADDR_LEN, + .header.xid = htobe32(0x12345678), + .header.chaddr = { 'A', 'B', 'C', 'D', 'E', 'F' }, .option_type.code = SD_DHCP_OPTION_MESSAGE_TYPE, .option_type.length = 1, .option_type.type = DHCP_DISCOVER, @@ -168,19 +166,19 @@ static void test_message_handler(void) { test.option_type.type = DHCP_DISCOVER; ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); - test.message.op = 0; + test.header.op = 0; ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL)); - test.message.op = BOOTREQUEST; + test.header.op = BOOTREQUEST; ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); - test.message.htype = 0; + test.header.htype = 0; ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); - test.message.htype = ARPHRD_ETHER; + test.header.htype = ARPHRD_ETHER; ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); - test.message.hlen = 0; + test.header.hlen = 0; ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), EBADMSG); - test.message.hlen = ETHER_ADDR_LEN; + test.header.hlen = ETHER_ADDR_LEN; ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); test.option_type.type = DHCP_REQUEST; @@ -246,7 +244,7 @@ static void test_message_handler(void) { ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_ACK); /* release the bound static lease */ - test.message.ciaddr = htobe32(INADDR_LOOPBACK + 31); + test.header.ciaddr = htobe32(INADDR_LOOPBACK + 31); test.option_type.type = DHCP_RELEASE; ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL)); @@ -261,7 +259,7 @@ static void test_message_handler(void) { ASSERT_OK(sd_dhcp_server_start(server)); /* request a new non-static address */ - test.message.ciaddr = 0; + test.header.ciaddr = 0; test.option_type.type = DHCP_REQUEST; test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 29); ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_ACK); From 19a4b5184e0c9012fd2e63914c684730eab27329 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 18 Mar 2026 19:19:18 +0900 Subject: [PATCH 1106/1296] dhcp-protocol: define BOOTP_MESSAGE_SIZE It will be used later. --- src/libsystemd-network/dhcp-protocol.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libsystemd-network/dhcp-protocol.h b/src/libsystemd-network/dhcp-protocol.h index 14ccde10ddee7..7dccd585a80a9 100644 --- a/src/libsystemd-network/dhcp-protocol.h +++ b/src/libsystemd-network/dhcp-protocol.h @@ -65,6 +65,10 @@ typedef struct DHCPPacket DHCPPacket; #define DHCP_MIN_PACKET_SIZE (DHCP_MIN_MESSAGE_SIZE + DHCP_IP_UDP_SIZE) #define DHCP_MAGIC_COOKIE (uint32_t)(0x63825363) +/* The size of BOOTP message. The BOOTP message does not have the magic field, but has the 64-byte + * vendor-specific area. */ +#define BOOTP_MESSAGE_SIZE (offsetof(DHCPMessageHeader, magic) + 64) + enum { DHCP_PORT_SERVER = 67, DHCP_PORT_CLIENT = 68, From 6fd89f305ab4b50b1d8ce62d6a3ff4c32c59c6f8 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 22 Mar 2026 01:55:22 +0900 Subject: [PATCH 1107/1296] dhcp-protocol: rename several dhcp message types These are unused, hence we can freely rename them. --- src/libsystemd-network/dhcp-protocol.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/libsystemd-network/dhcp-protocol.h b/src/libsystemd-network/dhcp-protocol.h index 7dccd585a80a9..10f9153aeb5ae 100644 --- a/src/libsystemd-network/dhcp-protocol.h +++ b/src/libsystemd-network/dhcp-protocol.h @@ -89,15 +89,15 @@ enum { DHCP_RELEASE = 7, /* [RFC2132] */ DHCP_INFORM = 8, /* [RFC2132] */ DHCP_FORCERENEW = 9, /* [RFC3203] */ - DHCPLEASEQUERY = 10, /* [RFC4388] */ - DHCPLEASEUNASSIGNED = 11, /* [RFC4388] */ - DHCPLEASEUNKNOWN = 12, /* [RFC4388] */ - DHCPLEASEACTIVE = 13, /* [RFC4388] */ - DHCPBULKLEASEQUERY = 14, /* [RFC6926] */ - DHCPLEASEQUERYDONE = 15, /* [RFC6926] */ - DHCPACTIVELEASEQUERY = 16, /* [RFC7724] */ - DHCPLEASEQUERYSTATUS = 17, /* [RFC7724] */ - DHCPTLS = 18, /* [RFC7724] */ + DHCP_LEASEQUERY = 10, /* [RFC4388] */ + DHCP_LEASEUNASSIGNED = 11, /* [RFC4388] */ + DHCP_LEASEUNKNOWN = 12, /* [RFC4388] */ + DHCP_LEASEACTIVE = 13, /* [RFC4388] */ + DHCP_BULKLEASEQUERY = 14, /* [RFC6926] */ + DHCP_LEASEQUERYDONE = 15, /* [RFC6926] */ + DHCP_ACTIVELEASEQUERY = 16, /* [RFC7724] */ + DHCP_LEASEQUERYSTATUS = 17, /* [RFC7724] */ + DHCP_TLS = 18, /* [RFC7724] */ }; typedef enum { From 5c907b2b2bb0a60f239cd316236b212ee4497e76 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 22 Mar 2026 02:07:30 +0900 Subject: [PATCH 1108/1296] dhcp-protocol: introduce {bootp,dhcp}_message_type_to_string() --- src/libsystemd-network/dhcp-protocol.c | 34 ++++++++++++++++++++++++++ src/libsystemd-network/dhcp-protocol.h | 18 ++++++++++---- src/libsystemd-network/meson.build | 1 + 3 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 src/libsystemd-network/dhcp-protocol.c diff --git a/src/libsystemd-network/dhcp-protocol.c b/src/libsystemd-network/dhcp-protocol.c new file mode 100644 index 0000000000000..976f9832d6f04 --- /dev/null +++ b/src/libsystemd-network/dhcp-protocol.c @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "dhcp-protocol.h" +#include "string-table.h" + +static const char * const bootp_message_type_table[_BOOTP_MESSAGE_TYPE_MAX] = { + [BOOTREQUEST] = "BOOTREQUEST", + [BOOTREPLY] = "BOOTREPLY", +}; + +DEFINE_STRING_TABLE_LOOKUP_TO_STRING(bootp_message_type, BOOTPMessageType); + +static const char * const dhcp_message_type_table[_DHCP_MESSAGE_TYPE_MAX] = { + [DHCP_DISCOVER] = "DISCOVER", + [DHCP_OFFER] = "OFFER", + [DHCP_REQUEST] = "REQUEST", + [DHCP_DECLINE] = "DECLINE", + [DHCP_ACK] = "ACK", + [DHCP_NAK] = "NAK", + [DHCP_RELEASE] = "RELEASE", + [DHCP_INFORM] = "INFORM", + [DHCP_FORCERENEW] = "FORCERENEW", + [DHCP_LEASEQUERY] = "LEASEQUERY", + [DHCP_LEASEUNASSIGNED] = "LEASEUNASSIGNED", + [DHCP_LEASEUNKNOWN] = "LEASEUNKNOWN", + [DHCP_LEASEACTIVE] = "LEASEACTIVE", + [DHCP_BULKLEASEQUERY] = "BULKLEASEQUERY", + [DHCP_LEASEQUERYDONE] = "LEASEQUERYDONE", + [DHCP_ACTIVELEASEQUERY] = "ACTIVELEASEQUERY", + [DHCP_LEASEQUERYSTATUS] = "LEASEQUERYSTATUS", + [DHCP_TLS] = "TLS", +}; + +DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_message_type, DHCPMessageType); diff --git a/src/libsystemd-network/dhcp-protocol.h b/src/libsystemd-network/dhcp-protocol.h index 10f9153aeb5ae..1b5842f0af329 100644 --- a/src/libsystemd-network/dhcp-protocol.h +++ b/src/libsystemd-network/dhcp-protocol.h @@ -8,7 +8,7 @@ #include #include -#include "sd-dhcp-protocol.h" +#include "sd-dhcp-protocol.h" /* IWYU pragma: export */ #include "sd-forward.h" #include "sparse-endian.h" @@ -74,12 +74,16 @@ enum { DHCP_PORT_CLIENT = 68, }; -enum { +typedef enum { BOOTREQUEST = 1, BOOTREPLY = 2, -}; + _BOOTP_MESSAGE_TYPE_MAX, + _BOOTP_MESSAGE_TYPE_INVALID = -EINVAL, +} BOOTPMessageType; -enum { +DECLARE_STRING_TABLE_LOOKUP_TO_STRING(bootp_message_type, BOOTPMessageType); + +typedef enum { DHCP_DISCOVER = 1, /* [RFC2132] */ DHCP_OFFER = 2, /* [RFC2132] */ DHCP_REQUEST = 3, /* [RFC2132] */ @@ -98,7 +102,11 @@ enum { DHCP_ACTIVELEASEQUERY = 16, /* [RFC7724] */ DHCP_LEASEQUERYSTATUS = 17, /* [RFC7724] */ DHCP_TLS = 18, /* [RFC7724] */ -}; + _DHCP_MESSAGE_TYPE_MAX, + _DHCP_MESSAGE_TYPE_INVALID = -EINVAL, +} DHCPMessageType; + +DECLARE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_message_type, DHCPMessageType); typedef enum { DHCP_OVERLOAD_NONE = 0, diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index e0012abe0bcf3..2eeabc075e04f 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -5,6 +5,7 @@ libsystemd_network_sources = files( 'dhcp-network.c', 'dhcp-option.c', 'dhcp-packet.c', + 'dhcp-protocol.c', 'dhcp6-network.c', 'dhcp6-option.c', 'dhcp6-protocol.c', From cf90b869b36cd62498bf8d9563950398a2aadd5b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 19 Apr 2026 08:23:24 +0900 Subject: [PATCH 1109/1296] sd-dhcp-protocol: rename SD_DHCP_OPTION_HOME_AGENT_ADDRESSES -> SD_DHCP_OPTION_HOME_AGENT_ADDRESS All other similar options are named in singular. --- src/systemd/sd-dhcp-protocol.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systemd/sd-dhcp-protocol.h b/src/systemd/sd-dhcp-protocol.h index c25498502dac9..1d3b635c31eff 100644 --- a/src/systemd/sd-dhcp-protocol.h +++ b/src/systemd/sd-dhcp-protocol.h @@ -91,7 +91,7 @@ enum { SD_DHCP_OPTION_NIS_SERVER_ADDR = 65, /* [RFC2132] */ SD_DHCP_OPTION_BOOT_SERVER_NAME = 66, /* [RFC2132] */ SD_DHCP_OPTION_BOOT_FILENAME = 67, /* [RFC2132] */ - SD_DHCP_OPTION_HOME_AGENT_ADDRESSES = 68, /* [RFC2132] */ + SD_DHCP_OPTION_HOME_AGENT_ADDRESS = 68, /* [RFC2132] */ SD_DHCP_OPTION_SMTP_SERVER = 69, /* [RFC2132] */ SD_DHCP_OPTION_POP3_SERVER = 70, /* [RFC2132] */ SD_DHCP_OPTION_NNTP_SERVER = 71, /* [RFC2132] */ From 06188d2663403c687bf409557ff855537cfcf706 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 17 Apr 2026 03:58:12 +0900 Subject: [PATCH 1110/1296] dhcp-protocol: introduce dhcp_option_code_to_string() --- src/libsystemd-network/dhcp-protocol.c | 156 +++++++++++++++++++++++++ src/libsystemd-network/dhcp-protocol.h | 2 + 2 files changed, 158 insertions(+) diff --git a/src/libsystemd-network/dhcp-protocol.c b/src/libsystemd-network/dhcp-protocol.c index 976f9832d6f04..3a22e0f225aaa 100644 --- a/src/libsystemd-network/dhcp-protocol.c +++ b/src/libsystemd-network/dhcp-protocol.c @@ -32,3 +32,159 @@ static const char * const dhcp_message_type_table[_DHCP_MESSAGE_TYPE_MAX] = { }; DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_message_type, DHCPMessageType); + +static const char * const dhcp_option_code_table[] = { + [SD_DHCP_OPTION_PAD] = "pad", + [SD_DHCP_OPTION_SUBNET_MASK] = "subnet mask", + [SD_DHCP_OPTION_TIME_OFFSET] = "time offset", + [SD_DHCP_OPTION_ROUTER] = "router", + [SD_DHCP_OPTION_TIME_SERVER] = "time server", + [SD_DHCP_OPTION_NAME_SERVER] = "name server", + [SD_DHCP_OPTION_DOMAIN_NAME_SERVER] = "domain name server", + [SD_DHCP_OPTION_LOG_SERVER] = "log server", + [SD_DHCP_OPTION_QUOTES_SERVER] = "quotes server", + [SD_DHCP_OPTION_LPR_SERVER] = "LPR server", + [SD_DHCP_OPTION_IMPRESS_SERVER] = "impress server", + [SD_DHCP_OPTION_RLP_SERVER] = "RLP server", + [SD_DHCP_OPTION_HOST_NAME] = "hostname", + [SD_DHCP_OPTION_BOOT_FILE_SIZE] = "boot file size", + [SD_DHCP_OPTION_MERIT_DUMP_FILE] = "merit dump file", + [SD_DHCP_OPTION_DOMAIN_NAME] = "domain name", + [SD_DHCP_OPTION_SWAP_SERVER] = "swap server", + [SD_DHCP_OPTION_ROOT_PATH] = "root path", + [SD_DHCP_OPTION_EXTENSION_FILE] = "extension file", + [SD_DHCP_OPTION_FORWARD] = "IP forwarding", + [SD_DHCP_OPTION_SOURCE_ROUTE] = "source routing", + [SD_DHCP_OPTION_POLICY_FILTER] = "policy filter", + [SD_DHCP_OPTION_MAX_DATAGRAM_ASSEMBLY] = "max datagram assembly", + [SD_DHCP_OPTION_DEFAULT_IP_TTL] = "default IP TTL", + [SD_DHCP_OPTION_MTU_TIMEOUT] = "MTU timeout", + [SD_DHCP_OPTION_MTU_PLATEAU] = "MTU plateau", + [SD_DHCP_OPTION_MTU_INTERFACE] = "MTU size", + [SD_DHCP_OPTION_MTU_SUBNET] = "MTU subnet", + [SD_DHCP_OPTION_BROADCAST] = "broadcast address", + [SD_DHCP_OPTION_MASK_DISCOVERY] = "mask discovery", + [SD_DHCP_OPTION_MASK_SUPPLIER] = "mask supplier", + [SD_DHCP_OPTION_ROUTER_DISCOVERY] = "router discovery", + [SD_DHCP_OPTION_ROUTER_REQUEST] = "router request", + [SD_DHCP_OPTION_STATIC_ROUTE] = "static route", + [SD_DHCP_OPTION_TRAILERS] = "trailers", + [SD_DHCP_OPTION_ARP_TIMEOUT] = "ARP timeout", + [SD_DHCP_OPTION_ETHERNET] = "Ethernet encapsulation", + [SD_DHCP_OPTION_DEFAULT_TCP_TTL] = "default TCP TTL", + [SD_DHCP_OPTION_KEEPALIVE_TIME] = "keepalive time", + [SD_DHCP_OPTION_KEEPALIVE_DATA] = "keepalive data", + [SD_DHCP_OPTION_NIS_DOMAIN] = "NIS domain", + [SD_DHCP_OPTION_NIS_SERVER] = "NIS server", + [SD_DHCP_OPTION_NTP_SERVER] = "NTP server", + [SD_DHCP_OPTION_VENDOR_SPECIFIC] = "vendor specific", + [SD_DHCP_OPTION_NETBIOS_NAME_SERVER] = "NETBIOS name server", + [SD_DHCP_OPTION_NETBIOS_DIST_SERVER] = "NETBIOS distribution server", + [SD_DHCP_OPTION_NETBIOS_NODE_TYPE] = "NETBIOS node type", + [SD_DHCP_OPTION_NETBIOS_SCOPE] = "NETBIOS scope", + [SD_DHCP_OPTION_X_WINDOW_FONT] = "X Window font", + [SD_DHCP_OPTION_X_WINDOW_MANAGER] = "X Window manager", + [SD_DHCP_OPTION_REQUESTED_IP_ADDRESS] = "requested IP address", + [SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME] = "lease time", + [SD_DHCP_OPTION_OVERLOAD] = "overload", + [SD_DHCP_OPTION_MESSAGE_TYPE] = "message type", + [SD_DHCP_OPTION_SERVER_IDENTIFIER] = "server identifier", + [SD_DHCP_OPTION_PARAMETER_REQUEST_LIST] = "parameter request list", + [SD_DHCP_OPTION_ERROR_MESSAGE] = "error message", + [SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE] = "max message size", + [SD_DHCP_OPTION_RENEWAL_TIME] = "renewal time", + [SD_DHCP_OPTION_REBINDING_TIME] = "rebinding time", + [SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER] = "vendor class identifier", + [SD_DHCP_OPTION_CLIENT_IDENTIFIER] = "client identifier", + [SD_DHCP_OPTION_NETWARE_IP_DOMAIN] = "NetWare IP domain", + [SD_DHCP_OPTION_NETWARE_IP_OPTION] = "NetWare IP option", + [SD_DHCP_OPTION_NIS_DOMAIN_NAME] = "NIS+ v3 domain", + [SD_DHCP_OPTION_NIS_SERVER_ADDR] = "NIS+ v3 server", + [SD_DHCP_OPTION_BOOT_SERVER_NAME] = "TFTP server name", + [SD_DHCP_OPTION_BOOT_FILENAME] = "boot file name", + [SD_DHCP_OPTION_HOME_AGENT_ADDRESS] = "home agent address", + [SD_DHCP_OPTION_SMTP_SERVER] = "SMTP server", + [SD_DHCP_OPTION_POP3_SERVER] = "POP3 server", + [SD_DHCP_OPTION_NNTP_SERVER] = "NNTP server", + [SD_DHCP_OPTION_WWW_SERVER] = "WWW server", + [SD_DHCP_OPTION_FINGER_SERVER] = "finger server", + [SD_DHCP_OPTION_IRC_SERVER] = "IRC server", + [SD_DHCP_OPTION_STREETTALK_SERVER] = "StreetTalk server", + [SD_DHCP_OPTION_STDA_SERVER] = "STDA server", + [SD_DHCP_OPTION_USER_CLASS] = "user class", + [SD_DHCP_OPTION_DIRECTORY_AGENT] = "directory agent", + [SD_DHCP_OPTION_SERVICE_SCOPE] = "service scope", + [SD_DHCP_OPTION_RAPID_COMMIT] = "rapid commit", + [SD_DHCP_OPTION_FQDN] = "FQDN", + [SD_DHCP_OPTION_RELAY_AGENT_INFORMATION] = "relay agent information", + [SD_DHCP_OPTION_ISNS] = "iSNS", + [SD_DHCP_OPTION_NDS_SERVER] = "NDS server", + [SD_DHCP_OPTION_NDS_TREE_NAME] = "NDS tree name", + [SD_DHCP_OPTION_NDS_CONTEXT] = "NDS context", + [SD_DHCP_OPTION_BCMCS_CONTROLLER_DOMAIN_NAME] = "BCMCS controller domain name", + [SD_DHCP_OPTION_BCMCS_CONTROLLER_ADDRESS] = "BCMCS controller address", + [SD_DHCP_OPTION_AUTHENTICATION] = "authentication", + [SD_DHCP_OPTION_CLIENT_LAST_TRANSACTION_TIME] = "client last transaction time", + [SD_DHCP_OPTION_ASSOCIATED_IP] = "associated IP", + [SD_DHCP_OPTION_CLIENT_SYSTEM] = "client system", + [SD_DHCP_OPTION_CLIENT_NDI] = "client NDI", + [SD_DHCP_OPTION_LDAP] = "LDAP", + [SD_DHCP_OPTION_UUID] = "UUID", + [SD_DHCP_OPTION_USER_AUTHENTICATION] = "user authentication", + [SD_DHCP_OPTION_GEOCONF_CIVIC] = "geoconf civic", + [SD_DHCP_OPTION_POSIX_TIMEZONE] = "posix timezone", + [SD_DHCP_OPTION_TZDB_TIMEZONE] = "tzdb timezone", + [SD_DHCP_OPTION_IPV6_ONLY_PREFERRED] = "IPv6-only preferred", + [SD_DHCP_OPTION_DHCP4O6_SOURCE_ADDRESS] = "DHCPv4 over DHCPv6 source address", + [SD_DHCP_OPTION_NETINFO_ADDRESS] = "Netinfo address", + [SD_DHCP_OPTION_NETINFO_TAG] = "Netinfo tag", + [SD_DHCP_OPTION_DHCP_CAPTIVE_PORTAL] = "captive portal", + [SD_DHCP_OPTION_AUTO_CONFIG] = "auto config", + [SD_DHCP_OPTION_NAME_SERVICE_SEARCH] = "name service search", + [SD_DHCP_OPTION_SUBNET_SELECTION] = "subnet selection", + [SD_DHCP_OPTION_DOMAIN_SEARCH] = "domain search", + [SD_DHCP_OPTION_SIP_SERVER] = "SIP server", + [SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE] = "classless static route", + [SD_DHCP_OPTION_CABLELABS_CLIENT_CONFIGURATION] = "CableLabs client configuration", + [SD_DHCP_OPTION_GEOCONF] = "geoconf", + [SD_DHCP_OPTION_VENDOR_CLASS] = "vendor class", + [SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION] = "vendor specific information", + [SD_DHCP_OPTION_PANA_AGENT] = "PANA agent", + [SD_DHCP_OPTION_LOST_SERVER_FQDN] = "LoST server", + [SD_DHCP_OPTION_CAPWAP_AC_ADDRESS] = "CAPWAP access controller address", + [SD_DHCP_OPTION_MOS_ADDRESS] = "MoS address", + [SD_DHCP_OPTION_MOS_FQDN] = "MoS FQDN", + [SD_DHCP_OPTION_SIP_SERVICE_DOMAIN] = "SIP service domain", + [SD_DHCP_OPTION_ANDSF_ADDRESS] = "ANDSF address", + [SD_DHCP_OPTION_SZTP_REDIRECT] = "SZTP server", + [SD_DHCP_OPTION_GEOLOC] = "geospatial location", + [SD_DHCP_OPTION_FORCERENEW_NONCE_CAPABLE] = "forcerenew nonce capable", + [SD_DHCP_OPTION_RDNSS_SELECTION] = "RDNSS selection", + [SD_DHCP_OPTION_DOTS_RI] = "DOTS agent name", + [SD_DHCP_OPTION_DOTS_ADDRESS] = "DOTS agent address", + [SD_DHCP_OPTION_TFTP_SERVER_ADDRESS] = "TFTP server address", + [SD_DHCP_OPTION_STATUS_CODE] = "status code", + [SD_DHCP_OPTION_BASE_TIME] = "base time", + [SD_DHCP_OPTION_START_TIME_OF_STATE] = "start time of state", + [SD_DHCP_OPTION_QUERY_START_TIME] = "query start time", + [SD_DHCP_OPTION_QUERY_END_TIME] = "query end time", + [SD_DHCP_OPTION_DHCP_STATE] = "DHCP state", + [SD_DHCP_OPTION_DATA_SOURCE] = "data source", + [SD_DHCP_OPTION_PCP_SERVER] = "PCP server", + [SD_DHCP_OPTION_PORT_PARAMS] = "port parameter", + [SD_DHCP_OPTION_MUD_URL] = "MUD URL", + [SD_DHCP_OPTION_V4_DNR] = "encrypted DNS server", + [SD_DHCP_OPTION_PXELINUX_MAGIC] = "PXELinux magic", + [SD_DHCP_OPTION_CONFIGURATION_FILE] = "configuration file", + [SD_DHCP_OPTION_PATH_PREFIX] = "path prefix", + [SD_DHCP_OPTION_REBOOT_TIME] = "reboot time", + [SD_DHCP_OPTION_6RD] = "6rd", + [SD_DHCP_OPTION_ACCESS_DOMAIN] = "access network domain", + [SD_DHCP_OPTION_SUBNET_ALLOCATION] = "subnet allocation", + [SD_DHCP_OPTION_VIRTUAL_SUBNET_SELECTION] = "virtual subnet selection", + [SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE] = "(private) classless static route", + [SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY] = "(private) proxy autodiscovery", + [SD_DHCP_OPTION_END] = "end", +}; + +DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_option_code, int); diff --git a/src/libsystemd-network/dhcp-protocol.h b/src/libsystemd-network/dhcp-protocol.h index 1b5842f0af329..c2df5a574a89e 100644 --- a/src/libsystemd-network/dhcp-protocol.h +++ b/src/libsystemd-network/dhcp-protocol.h @@ -123,3 +123,5 @@ enum { DHCP_FQDN_FLAG_E = (1 << 2), DHCP_FQDN_FLAG_N = (1 << 3), }; + +DECLARE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_option_code, int); From 5034852c6cd0b4957b4c074893c30005c76be73d Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 19 Apr 2026 08:10:14 +0900 Subject: [PATCH 1111/1296] iovec-util: rename iovec_increment() -> iovec_inc_many() Also, - use iovec_inc() in the loop, - do not return true when input is NULL or all iovec are empty. --- src/basic/iovec-util.c | 15 ++++---- src/basic/iovec-util.h | 2 +- src/basic/log.c | 2 +- src/fuzz/fuzz-varlink.c | 2 +- src/libsystemd/sd-daemon/sd-daemon.c | 2 +- src/resolve/resolved-dns-stream.c | 2 +- src/test/test-iovec-util.c | 54 ++++++++++++++++++++++++++++ 7 files changed, 67 insertions(+), 12 deletions(-) diff --git a/src/basic/iovec-util.c b/src/basic/iovec-util.c index cd2c1a736e828..dab734b9010f1 100644 --- a/src/basic/iovec-util.c +++ b/src/basic/iovec-util.c @@ -30,29 +30,30 @@ size_t iovec_total_size(const struct iovec *iovec, size_t n) { return sum; } -bool iovec_increment(struct iovec *iovec, size_t n, size_t k) { +bool iovec_inc_many(struct iovec *iovec, size_t n, size_t k) { assert(iovec || n == 0); /* Returns true if there is nothing else to send (bytes written cover all of the iovec), * false if there's still work to do. */ + bool have = false; FOREACH_ARRAY(j, iovec, n) { - size_t sub; - if (j->iov_len == 0) continue; if (k == 0) return false; - sub = MIN(j->iov_len, k); - j->iov_len -= sub; - j->iov_base = (uint8_t*) j->iov_base + sub; + size_t sub = MIN(j->iov_len, k); + iovec_inc(j, sub); k -= sub; + + have = have || iovec_is_set(j); } assert(k == 0); /* Anything else would mean that we wrote more bytes than available, * or the kernel reported writing more bytes than sent. */ - return true; + + return !have; } struct iovec* iovec_make_string(struct iovec *iovec, const char *s) { diff --git a/src/basic/iovec-util.h b/src/basic/iovec-util.h index 21aad9edfc196..c8261861a0ff7 100644 --- a/src/basic/iovec-util.h +++ b/src/basic/iovec-util.h @@ -11,7 +11,7 @@ extern const struct iovec iovec_empty; /* Points to an empty, but valid (i.e. size_t iovec_total_size(const struct iovec *iovec, size_t n) _nonnull_if_nonzero_(1, 2); -bool iovec_increment(struct iovec *iovec, size_t n, size_t k) _nonnull_if_nonzero_(1, 2); +bool iovec_inc_many(struct iovec *iovec, size_t n, size_t k) _nonnull_if_nonzero_(1, 2); struct iovec* iovec_make_string(struct iovec *iovec, const char *s); diff --git a/src/basic/log.c b/src/basic/log.c index 473d0bd70f5a0..d8b441bfadf21 100644 --- a/src/basic/log.c +++ b/src/basic/log.c @@ -557,7 +557,7 @@ static int write_to_syslog( if (!syslog_is_stream) break; - if (iovec_increment(iovec, ELEMENTSOF(iovec), n)) + if (iovec_inc_many(iovec, ELEMENTSOF(iovec), n)) break; } diff --git a/src/fuzz/fuzz-varlink.c b/src/fuzz/fuzz-varlink.c index fb1584a2ea6bf..7bd7e5ab920e9 100644 --- a/src/fuzz/fuzz-varlink.c +++ b/src/fuzz/fuzz-varlink.c @@ -41,7 +41,7 @@ static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userd else assert_se(errno == EAGAIN); } else - iovec_increment(iov, 1, n); + iovec_inc(iov, n); } if (revents & EPOLLIN) { diff --git a/src/libsystemd/sd-daemon/sd-daemon.c b/src/libsystemd/sd-daemon/sd-daemon.c index 2937ac569c321..da5242c3b799d 100644 --- a/src/libsystemd/sd-daemon/sd-daemon.c +++ b/src/libsystemd/sd-daemon/sd-daemon.c @@ -621,7 +621,7 @@ static int pid_notify_with_fds_internal( msghdr.msg_control = NULL; msghdr.msg_controllen = 0; } - } while (!iovec_increment(msghdr.msg_iov, msghdr.msg_iovlen, n)); + } while (!iovec_inc_many(msghdr.msg_iov, msghdr.msg_iovlen, n)); if (address.sockaddr.sa.sa_family == AF_VSOCK && IN_SET(type, SOCK_STREAM, SOCK_SEQPACKET)) { /* For AF_VSOCK, we need to close the socket to signal the end of the message. */ diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c index e10605538a124..b1d1b0069c028 100644 --- a/src/resolve/resolved-dns-stream.c +++ b/src/resolve/resolved-dns-stream.c @@ -347,7 +347,7 @@ static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *use IOVEC_MAKE(DNS_PACKET_DATA(s->write_packet), s->write_packet->size), }; - iovec_increment(iov, ELEMENTSOF(iov), s->n_written); + iovec_inc_many(iov, ELEMENTSOF(iov), s->n_written); ssize_t ss = dns_stream_writev(s, iov, ELEMENTSOF(iov), 0); if (ss < 0) { diff --git a/src/test/test-iovec-util.c b/src/test/test-iovec-util.c index 68255071e861d..bd73be1ea76e6 100644 --- a/src/test/test-iovec-util.c +++ b/src/test/test-iovec-util.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "iovec-util.h" +#include "iovec-wrapper.h" #include "memory-util.h" #include "tests.h" @@ -38,6 +39,59 @@ TEST(iovec_inc) { ASSERT_FALSE(iovec_is_set(iovec_inc(&empty, 1))); } +TEST(iovec_inc_many) { + ASSERT_TRUE(iovec_inc_many(NULL, 0, 0)); + ASSERT_TRUE(iovec_inc_many(&(struct iovec) {}, 0, 0)); + ASSERT_TRUE(iovec_inc_many(&(struct iovec) {}, 1, 0)); + + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + ASSERT_OK(iovw_put_iov(&iovw, &IOVEC_MAKE_STRING("aaa"))); + ASSERT_OK(iovw_put_iov(&iovw, &IOVEC_MAKE_STRING("bbb"))); + ASSERT_OK(iovw_put_iov(&iovw, &IOVEC_MAKE_STRING("ccc"))); + + ASSERT_FALSE(iovec_inc_many(iovw.iovec, iovw.count, 0)); + ASSERT_TRUE(iovec_equal(&iovw.iovec[0], &IOVEC_MAKE_STRING("aaa"))); + ASSERT_TRUE(iovec_equal(&iovw.iovec[1], &IOVEC_MAKE_STRING("bbb"))); + ASSERT_TRUE(iovec_equal(&iovw.iovec[2], &IOVEC_MAKE_STRING("ccc"))); + + ASSERT_FALSE(iovec_inc_many(iovw.iovec, iovw.count, 1)); + ASSERT_TRUE(iovec_equal(&iovw.iovec[0], &IOVEC_MAKE_STRING("aa"))); + ASSERT_TRUE(iovec_equal(&iovw.iovec[1], &IOVEC_MAKE_STRING("bbb"))); + ASSERT_TRUE(iovec_equal(&iovw.iovec[2], &IOVEC_MAKE_STRING("ccc"))); + + ASSERT_FALSE(iovec_inc_many(iovw.iovec, iovw.count, 3)); + ASSERT_FALSE(iovec_is_set(&iovw.iovec[0])); + ASSERT_NULL(iovw.iovec[0].iov_base); + ASSERT_EQ(iovw.iovec[0].iov_len, 0u); + ASSERT_TRUE(iovec_equal(&iovw.iovec[1], &IOVEC_MAKE_STRING("bb"))); + ASSERT_TRUE(iovec_equal(&iovw.iovec[2], &IOVEC_MAKE_STRING("ccc"))); + + ASSERT_FALSE(iovec_inc_many(iovw.iovec, iovw.count, 4)); + ASSERT_NULL(iovw.iovec[0].iov_base); + ASSERT_EQ(iovw.iovec[0].iov_len, 0u); + ASSERT_NULL(iovw.iovec[1].iov_base); + ASSERT_EQ(iovw.iovec[1].iov_len, 0u); + ASSERT_TRUE(iovec_equal(&iovw.iovec[2], &IOVEC_MAKE_STRING("c"))); + + ASSERT_TRUE(iovec_inc_many(iovw.iovec, iovw.count, 1)); + ASSERT_NULL(iovw.iovec[0].iov_base); + ASSERT_EQ(iovw.iovec[0].iov_len, 0u); + ASSERT_NULL(iovw.iovec[1].iov_base); + ASSERT_EQ(iovw.iovec[1].iov_len, 0u); + ASSERT_NULL(iovw.iovec[2].iov_base); + ASSERT_EQ(iovw.iovec[2].iov_len, 0u); + + ASSERT_TRUE(iovec_inc_many(iovw.iovec, iovw.count, 0)); + ASSERT_NULL(iovw.iovec[0].iov_base); + ASSERT_EQ(iovw.iovec[0].iov_len, 0u); + ASSERT_NULL(iovw.iovec[1].iov_base); + ASSERT_EQ(iovw.iovec[1].iov_len, 0u); + ASSERT_NULL(iovw.iovec[2].iov_base); + ASSERT_EQ(iovw.iovec[2].iov_len, 0u); + + ASSERT_SIGNAL(iovec_inc_many(iovw.iovec, iovw.count, 1), SIGABRT); +} + TEST(iovec_memcmp) { struct iovec iov1 = CONST_IOVEC_MAKE_STRING("abcdef"), iov2 = IOVEC_MAKE_STRING("bcdefg"), empty = {}; From 774a9f440bebeea960b69bb46109d72b3d7b8667 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Fri, 17 Apr 2026 19:52:53 +0200 Subject: [PATCH 1112/1296] strxcpyx: add a paranoia check for vsnprintf()'s return value vsnprintf() can, under some circumstances, return negative value, namely during encoding errors when converting wchars to multi-byte characters. This would then wreak havoc in the arithmetics we do following the vsnprintf() call. However, since we never do any wchar shenanigans in our code it should never happen. Let's encode this assumption into the code as an assert(), similarly how we already do this in other places (like strextendf_with_separator()). --- src/basic/strxcpyx.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/basic/strxcpyx.c b/src/basic/strxcpyx.c index dc40d620e7e6b..f8410f7d0c11d 100644 --- a/src/basic/strxcpyx.c +++ b/src/basic/strxcpyx.c @@ -64,6 +64,8 @@ size_t strpcpyf_full(char **dest, size_t size, bool *ret_truncated, const char * i = vsnprintf(*dest, size, src, va); va_end(va); + assert(i >= 0); + if (i < (int) size) { *dest += i; size -= i; From 6182d0c66654594c65c680bc0e486d8bbcb359f5 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Sat, 18 Apr 2026 19:22:40 +0200 Subject: [PATCH 1113/1296] import: fix an always-true assert() --- src/import/pull-tar.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/import/pull-tar.c b/src/import/pull-tar.c index a235c2fba3dd6..fe18636eb7d9d 100644 --- a/src/import/pull-tar.c +++ b/src/import/pull-tar.c @@ -445,7 +445,7 @@ static void tar_pull_job_on_finished(PullJob *j) { else if (j == p->settings_job) log_info_errno(j->error, "Settings file could not be retrieved, proceeding without."); else - assert("unexpected job"); + assert_not_reached(); } /* This is invoked if either the download completed successfully, or the download was skipped because From a77a95665fba06861125a4a62ffed8ccd75f37f2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 20 Apr 2026 09:35:33 +0200 Subject: [PATCH 1114/1296] sd-json: make sure SD_JSON_BUILD_STRING_UNDERSCORIFY() can deal with NULL strings SD_JSON_BUILD_STRING() and everything else can deal with it, make sure SD_JSON_BUILD_STRING_UNDERSCORIFY() can too. --- src/libsystemd/sd-json/sd-json.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index 1fd006c7d9572..2c46a23632f7b 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -3595,7 +3595,7 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) { if (current->n_suppress == 0) { _cleanup_free_ char *c = NULL; - if (command == _JSON_BUILD_STRING_UNDERSCORIFY) { + if (command == _JSON_BUILD_STRING_UNDERSCORIFY && p) { c = strdup(p); if (!c) { r = -ENOMEM; From ab6feb11ed814c06723b55f5d57831f1fcb40ba8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 14 Apr 2026 14:05:56 +0200 Subject: [PATCH 1115/1296] sd-json: add JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY() helper it's the combination of JSON_BUILD_PAIR_STRING_NON_EMPTY and JSON_BUILD_PAIR_STRING_UNDERSCORIFY --- src/libsystemd/sd-json/json-util.h | 2 ++ src/libsystemd/sd-json/sd-json.c | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/libsystemd/sd-json/json-util.h b/src/libsystemd/sd-json/json-util.h index 4ed0b708f8ac3..cea2d368b43db 100644 --- a/src/libsystemd/sd-json/json-util.h +++ b/src/libsystemd/sd-json/json-util.h @@ -170,6 +170,7 @@ enum { _JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL, _JSON_BUILD_PAIR_FINITE_USEC, _JSON_BUILD_PAIR_STRING_NON_EMPTY, + _JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY, _JSON_BUILD_PAIR_STRV_NON_EMPTY, _JSON_BUILD_PAIR_STRV_ENV_PAIR_NON_EMPTY, _JSON_BUILD_PAIR_VARIANT_NON_NULL, @@ -219,6 +220,7 @@ enum { #define JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL(name, u, eq) _JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL, (const char*) { name }, (uint64_t) { u }, (uint64_t) { eq } #define JSON_BUILD_PAIR_FINITE_USEC(name, u) _JSON_BUILD_PAIR_FINITE_USEC, (const char*) { name }, (usec_t) { u } #define JSON_BUILD_PAIR_STRING_NON_EMPTY(name, s) _JSON_BUILD_PAIR_STRING_NON_EMPTY, (const char*) { name }, (const char*) { s } +#define JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY(name, s) _JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY, (const char*) { name }, (const char*) { s } #define JSON_BUILD_PAIR_STRV_NON_EMPTY(name, l) _JSON_BUILD_PAIR_STRV_NON_EMPTY, (const char*) { name }, (char**) { l } #define JSON_BUILD_PAIR_STRV_ENV_PAIR_NON_EMPTY(name, l) _JSON_BUILD_PAIR_STRV_ENV_PAIR_NON_EMPTY, (const char*) { name }, (char**) { l } #define JSON_BUILD_PAIR_VARIANT_NON_NULL(name, v) _JSON_BUILD_PAIR_VARIANT_NON_NULL, (const char*) { name }, (sd_json_variant*) { v } diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index 2c46a23632f7b..4c39b5660a649 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -4573,7 +4573,8 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) { break; } - case _JSON_BUILD_PAIR_STRING_NON_EMPTY: { + case _JSON_BUILD_PAIR_STRING_NON_EMPTY: + case _JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY: { const char *n, *s; if (current->expect != EXPECT_OBJECT_KEY) { @@ -4589,7 +4590,16 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) { if (r < 0) goto finish; - r = sd_json_variant_new_string(&add_more, s); + if (command == _JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY) { + _cleanup_free_ char *c = strdup(s); + if (!c) { + r = -ENOMEM; + goto finish; + } + + r = sd_json_variant_new_string(&add_more, json_underscorify(c)); + } else + r = sd_json_variant_new_string(&add_more, s); if (r < 0) goto finish; } From f5575d9eb519968f48a15e8e9c5a4d8ea1d154fb Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 14 Apr 2026 15:15:05 +0200 Subject: [PATCH 1116/1296] mountfsd: generate properly underscored designator json strings Let's make sure we generate data that will actually pass the IDL checks, and use underscores for designator names. (This is a bugfix) --- src/mountfsd/mountwork.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mountfsd/mountwork.c b/src/mountfsd/mountwork.c index a5d34d447906d..42860db13479e 100644 --- a/src/mountfsd/mountwork.c +++ b/src/mountfsd/mountwork.c @@ -748,7 +748,7 @@ static int vl_method_mount_image( r = sd_json_variant_append_arraybo( &aj, - SD_JSON_BUILD_PAIR_STRING("designator", partition_designator_to_string(d)), + JSON_BUILD_PAIR_ENUM("designator", partition_designator_to_string(d)), SD_JSON_BUILD_PAIR_BOOLEAN("writable", pp->rw), SD_JSON_BUILD_PAIR_BOOLEAN("growFileSystem", pp->growfs), SD_JSON_BUILD_PAIR_CONDITION(pp->partno > 0, "partitionNumber", SD_JSON_BUILD_INTEGER(pp->partno)), From 40ab0df557437f8463cdbfd5710da0c45e40d326 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 14 Apr 2026 15:16:47 +0200 Subject: [PATCH 1117/1296] networkd: gnerate proper underscored enums in varlink interface AFAICS none of the states actually user dashes/underscores, but let's prepare for the future and be fully correct here. --- src/network/networkd-manager-varlink.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/network/networkd-manager-varlink.c b/src/network/networkd-manager-varlink.c index 71ec695339632..d12847b089f43 100644 --- a/src/network/networkd-manager-varlink.c +++ b/src/network/networkd-manager-varlink.c @@ -54,11 +54,11 @@ static int vl_method_get_states(sd_varlink *link, sd_json_variant *parameters, s return sd_varlink_replybo( link, - SD_JSON_BUILD_PAIR_STRING("AddressState", link_address_state_to_string(m->address_state)), - SD_JSON_BUILD_PAIR_STRING("IPv4AddressState", link_address_state_to_string(m->ipv4_address_state)), - SD_JSON_BUILD_PAIR_STRING("IPv6AddressState", link_address_state_to_string(m->ipv6_address_state)), + JSON_BUILD_PAIR_ENUM("AddressState", link_address_state_to_string(m->address_state)), + JSON_BUILD_PAIR_ENUM("IPv4AddressState", link_address_state_to_string(m->ipv4_address_state)), + JSON_BUILD_PAIR_ENUM("IPv6AddressState", link_address_state_to_string(m->ipv6_address_state)), SD_JSON_BUILD_PAIR_STRING("CarrierState", link_carrier_state_to_string(m->carrier_state)), - SD_JSON_BUILD_PAIR_CONDITION(m->online_state >= 0, "OnlineState", SD_JSON_BUILD_STRING(link_online_state_to_string(m->online_state))), + SD_JSON_BUILD_PAIR_CONDITION(m->online_state >= 0, "OnlineState", JSON_BUILD_STRING_UNDERSCORIFY(link_online_state_to_string(m->online_state))), SD_JSON_BUILD_PAIR_STRING("OperationalState", link_operstate_to_string(m->operational_state))); } From 112f5317d1ce3cef8c2b35ed1201593db6f1cb65 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 14 Apr 2026 15:18:04 +0200 Subject: [PATCH 1118/1296] resolved: let's generate enum fields properly too AFAICS none of the enums here uses dashes, hence this should not actually have any effect except for being more correct. --- src/network/networkd-json.c | 8 ++++---- src/resolve/resolved-dns-scope.c | 3 ++- src/resolve/resolved-manager.c | 6 +++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/network/networkd-json.c b/src/network/networkd-json.c index 04ad5cfaedd29..d5bed7718a0c5 100644 --- a/src/network/networkd-json.c +++ b/src/network/networkd-json.c @@ -1544,10 +1544,10 @@ int link_build_json(Link *link, sd_json_variant **ret) { SD_JSON_BUILD_PAIR_STRING("AdministrativeState", link_state_to_string(link->state)), SD_JSON_BUILD_PAIR_STRING("OperationalState", link_operstate_to_string(link->operstate)), SD_JSON_BUILD_PAIR_STRING("CarrierState", link_carrier_state_to_string(link->carrier_state)), - SD_JSON_BUILD_PAIR_STRING("AddressState", link_address_state_to_string(link->address_state)), - SD_JSON_BUILD_PAIR_STRING("IPv4AddressState", link_address_state_to_string(link->ipv4_address_state)), - SD_JSON_BUILD_PAIR_STRING("IPv6AddressState", link_address_state_to_string(link->ipv6_address_state)), - SD_JSON_BUILD_PAIR_STRING("OnlineState", link_online_state_to_string(link->online_state))); + JSON_BUILD_PAIR_ENUM("AddressState", link_address_state_to_string(link->address_state)), + JSON_BUILD_PAIR_ENUM("IPv4AddressState", link_address_state_to_string(link->ipv4_address_state)), + JSON_BUILD_PAIR_ENUM("IPv6AddressState", link_address_state_to_string(link->ipv6_address_state)), + JSON_BUILD_PAIR_ENUM("OnlineState", link_online_state_to_string(link->online_state))); if (r < 0) return r; diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 2e360a8513e91..89b13b0f1d619 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -16,6 +16,7 @@ #include "errno-util.h" #include "fd-util.h" #include "hostname-util.h" +#include "json-util.h" #include "log.h" #include "random-util.h" #include "resolved-dns-browse-services.h" @@ -1814,7 +1815,7 @@ int dns_scope_to_json(DnsScope *scope, bool with_cache, sd_json_variant **ret) { return sd_json_buildo( ret, - SD_JSON_BUILD_PAIR_STRING("protocol", dns_protocol_to_string(scope->protocol)), + JSON_BUILD_PAIR_ENUM("protocol", dns_protocol_to_string(scope->protocol)), SD_JSON_BUILD_PAIR_CONDITION(scope->family != AF_UNSPEC, "family", SD_JSON_BUILD_INTEGER(scope->family)), SD_JSON_BUILD_PAIR_CONDITION(!!scope->link, "ifindex", SD_JSON_BUILD_INTEGER(dns_scope_ifindex(scope))), SD_JSON_BUILD_PAIR_CONDITION(!!scope->link, "ifname", SD_JSON_BUILD_STRING(dns_scope_ifname(scope))), diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index f16364fe6a480..0a52e922c4ff5 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -2156,9 +2156,9 @@ static int dns_configuration_json_append( JSON_BUILD_PAIR_CONDITION_BOOLEAN(dnssec_mode >= 0, "dnssecSupported", dnssec_supported), JSON_BUILD_PAIR_STRING_NON_EMPTY("dnssec", dnssec_mode_to_string(dnssec_mode)), JSON_BUILD_PAIR_STRING_NON_EMPTY("dnsOverTLS", dns_over_tls_mode_to_string(dns_over_tls_mode)), - JSON_BUILD_PAIR_STRING_NON_EMPTY("llmnr", resolve_support_to_string(llmnr_support)), - JSON_BUILD_PAIR_STRING_NON_EMPTY("mDNS", resolve_support_to_string(mdns_support)), - JSON_BUILD_PAIR_STRING_NON_EMPTY("resolvConfMode", resolv_conf_mode_to_string(resolv_conf_mode)), + JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY("llmnr", resolve_support_to_string(llmnr_support)), + JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY("mDNS", resolve_support_to_string(mdns_support)), + JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY("resolvConfMode", resolv_conf_mode_to_string(resolv_conf_mode)), JSON_BUILD_PAIR_VARIANT_NON_NULL("scopes", scopes_json)); } From e869c83367388a3dc8d5ec8ca6820edc422b58c2 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Sun, 19 Apr 2026 16:24:55 +0200 Subject: [PATCH 1119/1296] test: append .journal to unpacked corrupted journals Otherwise `journalctl --directory=` skips over them in the second part of the test. --- test/units/TEST-04-JOURNAL.corrupted-journals.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/units/TEST-04-JOURNAL.corrupted-journals.sh b/test/units/TEST-04-JOURNAL.corrupted-journals.sh index 479011ea78f43..3dd0fc6dde56a 100755 --- a/test/units/TEST-04-JOURNAL.corrupted-journals.sh +++ b/test/units/TEST-04-JOURNAL.corrupted-journals.sh @@ -9,7 +9,7 @@ REMOTE_OUT="$(mktemp -d)" unzstd --stdout "/usr/lib/systemd/tests/testdata/test-journals/afl-corrupted-journals.tar.zst" | tar -xC "$JOURNAL_DIR/" while read -r file; do filename="${file##*/}" - unzstd "$file" -o "$JOURNAL_DIR/${filename%*.zst}" + unzstd "$file" -o "$JOURNAL_DIR/${filename%.zst}.journal" done < <(find /usr/lib/systemd/tests/testdata/test-journals/corrupted/ -name "*.zst") # First, try each of them sequentially. Skip this part when running with plain # QEMU, as it is excruciatingly slow From 35eb598af26f66c94d7403f6170d5cae438871fb Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Sun, 19 Apr 2026 15:47:11 +0200 Subject: [PATCH 1120/1296] compress: gracefully handle a truncated ZSTD frame If a journal file contains a truncated ZSTD frame (i.e. a frame with Frame_Content_Size > 0, but with not enough data in Data_Block), ZSTD_decompressStream() would return a non-zero, non-error value. This would then skip the error path in the ZSTD_isError() branch and we'd hit the following assert: $ build-local/journalctl -o cat --file zstd-truncated.journal Assertion 'output.pos >= prefix_len + 1' failed at src/basic/compress.c:1236, function decompress_startswith_zstd(). Aborting. Aborted (core dumped) build-local/journalctl -o cat --file zstd-truncated.journal Let's handle this situation gracefully and return EBADMSG instead. Also, add another journalctl invocation to the corrupted-journals test that goes through the sd_journal_get_data() -> decompress_startswith_zstd() code path which, among other things, covers the issue when run on the provided journal file. --- src/basic/compress.c | 4 +++- .../corrupted/zstd-truncated-frame.zst | Bin 0 -> 189 bytes test/units/TEST-04-JOURNAL.corrupted-journals.sh | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 test/test-journals/corrupted/zstd-truncated-frame.zst diff --git a/src/basic/compress.c b/src/basic/compress.c index 72e336bc68965..4beaa0f6bd22f 100644 --- a/src/basic/compress.c +++ b/src/basic/compress.c @@ -1233,7 +1233,9 @@ static int decompress_startswith_zstd( log_debug("ZSTD decoder failed: %s", sym_ZSTD_getErrorName(k)); return zstd_ret_to_errno(k); } - assert(output.pos >= prefix_len + 1); + + if (output.pos < prefix_len + 1) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "ZSTD decoded less data than indicated, probably corrupted stream."); return memcmp(*buffer, prefix, prefix_len) == 0 && ((const uint8_t*) *buffer)[prefix_len] == extra; diff --git a/test/test-journals/corrupted/zstd-truncated-frame.zst b/test/test-journals/corrupted/zstd-truncated-frame.zst new file mode 100644 index 0000000000000000000000000000000000000000..66db974981fd8d645211f66f0733eac1d2a76422 GIT binary patch literal 189 zcmdPcs{c3T1Vb$=gA2P)fOoKmN00{-0|NsO5Hm3-KhT@$an*g=?4UeL{i|(K3?EB^ zKQBDxEYoH*zu*(6QQ6OH#afp#JQi&VyZvTX>trPXMur^>E0`G>9ONWaxY!(=_*XDS zFfy{Zz2CR?9)kwZGKG`?0Y)~32u7d/dev/null || [[ $? -lt 124 ]] timeout 10 journalctl --file="$file" --verify >/dev/null || [[ $? -lt 124 ]] timeout 10 journalctl --file="$file" --output=export >/dev/null || [[ $? -lt 124 ]] + timeout 10 journalctl --file="$file" --grep=. >/dev/null || [[ $? -lt 124 ]] timeout 10 journalctl --file="$file" --fields >/dev/null || [[ $? -lt 124 ]] timeout 10 journalctl --file="$file" --list-boots >/dev/null || [[ $? -lt 124 ]] if [[ -x /usr/lib/systemd/systemd-journal-remote ]]; then @@ -36,6 +37,7 @@ fi timeout 30 journalctl --directory="$JOURNAL_DIR" --boot >/dev/null || [[ $? -lt 124 ]] timeout 30 journalctl --directory="$JOURNAL_DIR" --verify >/dev/null || [[ $? -lt 124 ]] timeout 30 journalctl --directory="$JOURNAL_DIR" --output=export >/dev/null || [[ $? -lt 124 ]] +timeout 30 journalctl --directory="$JOURNAL_DIR" --grep=. >/dev/null || [[ $? -lt 124 ]] timeout 30 journalctl --directory="$JOURNAL_DIR" --fields >/dev/null || [[ $? -lt 124 ]] timeout 30 journalctl --directory="$JOURNAL_DIR" --list-boots >/dev/null || [[ $? -lt 124 ]] if [[ -x /usr/lib/systemd/systemd-journal-remote ]]; then From 3297add53843080766d22e5bc93245e95c83697c Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Sun, 19 Apr 2026 17:26:30 +0200 Subject: [PATCH 1121/1296] compress: simplify the condition a bit Simply mirror the format we've already established in decompress_blob_zstd(). --- src/basic/compress.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/basic/compress.c b/src/basic/compress.c index 4beaa0f6bd22f..979e07ac80957 100644 --- a/src/basic/compress.c +++ b/src/basic/compress.c @@ -1229,11 +1229,8 @@ static int decompress_startswith_zstd( size_t k; k = sym_ZSTD_decompressStream(dctx, &output, &input); - if (sym_ZSTD_isError(k)) { - log_debug("ZSTD decoder failed: %s", sym_ZSTD_getErrorName(k)); - return zstd_ret_to_errno(k); - } - + if (sym_ZSTD_isError(k)) + return log_debug_errno(zstd_ret_to_errno(k), "ZSTD decoder failed: %s", sym_ZSTD_getErrorName(k)); if (output.pos < prefix_len + 1) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "ZSTD decoded less data than indicated, probably corrupted stream."); From 5159388230574da1b9b91137a4b1f2fba9e6a729 Mon Sep 17 00:00:00 2001 From: mukunda katta Date: Sun, 19 Apr 2026 21:29:54 -0700 Subject: [PATCH 1122/1296] nspawn,shared/nsresource: fix copy-paste errno logging args In nspawn.c's run_container() the child_netns_fd = receive_one_fd(...) failure path logged 'r' instead of the negative errno returned in child_netns_fd, so the actual error from receive_one_fd was being overwritten by whatever 'r' happened to hold. The other receive_one_fd call sites in the same function use the returned fd variable directly (mntns_fd, etc.), so align this one. In shared/nsresource.c's nsresource_add_cgroup() the cgroup_fd_idx = sd_varlink_push_dup_fd(...) failure path logged userns_fd_idx, which is the previous successful push's index, not the negative errno we just got from pushing cgroup_fd. Log cgroup_fd_idx instead. Both were flagged by static analysis (#41709) and match the immediately preceding userns_fd-path pattern that was presumably copy-pasted. Refs #41709. --- src/nspawn/nspawn.c | 2 +- src/shared/nsresource.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 8db17968ab41d..438346a5456e4 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -5364,7 +5364,7 @@ static int run_container( assert(child_netns_fd < 0); child_netns_fd = receive_one_fd(fd_inner_socket_pair[0], 0); if (child_netns_fd < 0) - return log_error_errno(r, "Failed to receive child network namespace: %m"); + return log_error_errno(child_netns_fd, "Failed to receive child network namespace: %m"); } r = move_network_interfaces(child_netns_fd, arg_network_interfaces); diff --git a/src/shared/nsresource.c b/src/shared/nsresource.c index 6f70bcaf1f3cb..7a1c33446b811 100644 --- a/src/shared/nsresource.c +++ b/src/shared/nsresource.c @@ -270,7 +270,7 @@ int nsresource_add_cgroup(sd_varlink *vl, int userns_fd, int cgroup_fd) { cgroup_fd_idx = sd_varlink_push_dup_fd(vl, cgroup_fd); if (cgroup_fd_idx < 0) - return log_debug_errno(userns_fd_idx, "Failed to push cgroup fd into varlink connection: %m"); + return log_debug_errno(cgroup_fd_idx, "Failed to push cgroup fd into varlink connection: %m"); sd_json_variant *reply = NULL; r = sd_varlink_callbo( From 89d705a892b3476de14e548f3f9b0af96207d4b0 Mon Sep 17 00:00:00 2001 From: Felix Pehla <29adc1fd92@gmail.com> Date: Wed, 15 Apr 2026 14:35:58 +0200 Subject: [PATCH 1123/1296] man/fstab-generator: fix option list and make formatting consistent Add "overlay", which is already mentioned further down below, to the list of possible options. Consistently use for possible values of systemd.volatile=, rather than or no special formatting. Use yes/no rather than true/false as boolean since that is what's used everywhere else and I'm already touching the lines anyway. --- man/systemd-fstab-generator.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/man/systemd-fstab-generator.xml b/man/systemd-fstab-generator.xml index 43f573dd722fe..8e684b29863d8 100644 --- a/man/systemd-fstab-generator.xml +++ b/man/systemd-fstab-generator.xml @@ -214,12 +214,12 @@ systemd.volatile= Controls whether the system shall boot up in volatile mode. Takes a boolean argument or the - special value . + special values state and overlay. - If false (the default), this generator makes no changes to the mount tree and the system is booted up in - normal mode. + If no (the default), this generator makes no changes to the mount tree and the system + is booted up in normal mode. - If true the generator ensures + If yes the generator ensures systemd-volatile-root.service8 is run in the initrd. This service changes the mount table before transitioning to the host system, so that a volatile memory file system (tmpfs) is used as root directory, with only @@ -228,7 +228,7 @@ and lost at shutdown, as /etc/ and /var/ will be served from the (initially unpopulated) volatile memory file system. - If set to the generator will leave the root directory mount point unaltered, + If set to state the generator will leave the root directory mount point unaltered, however will mount a tmpfs file system to /var/. In this mode the normal system configuration (i.e. the contents of /etc/) is in effect (and may be modified during system runtime), however the system state (i.e. the contents of /var/) is reset at boot and From ad18f634b6a6fc8e927e55366f272251548b60d1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 20 Apr 2026 09:59:56 +0200 Subject: [PATCH 1124/1296] parse-util: rework safe_atou64() as wrapper around safe_atou64_full() Follow-up for: 023f88a6ab76b9784e9b6c6396227f1490c1a8c2 Claude complained... --- src/basic/parse-util.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h index 0ee4da2126b76..6df08915f639c 100644 --- a/src/basic/parse-util.h +++ b/src/basic/parse-util.h @@ -78,16 +78,15 @@ static inline int safe_atollu(const char *s, unsigned long long *ret_llu) { return safe_atollu_full(s, 0, ret_llu); } -static inline int safe_atou64(const char *s, uint64_t *ret_u) { - assert_cc(sizeof(uint64_t) == sizeof(unsigned long long)); - return safe_atollu(s, (unsigned long long*) ret_u); -} - static inline int safe_atou64_full(const char *s, unsigned base, uint64_t *ret_u) { assert_cc(sizeof(uint64_t) == sizeof(unsigned long long)); return safe_atollu_full(s, base, (unsigned long long*) ret_u); } +static inline int safe_atou64(const char *s, uint64_t *ret_u) { + return safe_atou64_full(s, 0, ret_u); +} + static inline int safe_atoi64(const char *s, int64_t *ret_i) { assert_cc(sizeof(int64_t) == sizeof(long long)); return safe_atolli(s, (long long*) ret_i); From 3a2d828e7214322721935c2a04f0c20a76078256 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 20 Apr 2026 12:35:12 +0200 Subject: [PATCH 1125/1296] update TODO --- TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index d641e15551e03..a178e8b9fb1a1 100644 --- a/TODO.md +++ b/TODO.md @@ -2750,7 +2750,7 @@ SPDX-License-Identifier: LGPL-2.1-or-later - Varlinkification of the following command line tools, to open them up to other programs via IPC: - - coredumpcl + - coredumpctl - systemd-bless-boot - systemd-measure - systemd-cryptenroll (to allow UIs to enroll FIDO2 keys and such) From c955e24916f8d6bce09589979dc62833ccd88853 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Mon, 20 Apr 2026 12:14:41 +0200 Subject: [PATCH 1126/1296] test: re-enable sync in TEST-25-IMPORT Newer tar started using openat2() via open_subdir() to address CVE-2025-45582 [0]. Now, gnulib, that tar uses, provides the openat2() syscall in two ways [1]: 1) If glibc doesn't provide openat2(), it provides its own version in openat2.c, that tries to call openat2() syscall first, and if it returns ENOSYS, it emulates the function in userspace. 2) If glibc provides openat2(), it uses that directly, without providing any fallback on ENOSYS. Quite recently our test suite started calling nspawn with --suppress-sync=yes. This means that we call seccomp_suppress_sync(), which eventually calls block_open_flag(), that blocks the openat2() syscall completely and refuses it with ENOSYS as this syscall can't be sensibly filtered (see the openat2()-relevant comments in block_open_flag() and seccomp_restrict_sxid()). And when glibc provides openat2(), there's no fallback, so the ENOSYS bubbles up to the user as: TEST-25-IMPORT.sh[163]: + tar xzf /var/tmp/scratch.tar.gz TEST-25-IMPORT.sh[163]: tar: ./adirectory/athirdfile: Cannot open: Function not implemented TEST-25-IMPORT.sh[163]: tar: Exiting with failure status due to previous errors Let's mitigate this by re-enabling sync for TEST-25-IMPORT, at least for now. [0] https://cgit.git.savannah.gnu.org/cgit/tar.git/commit/?id=75b03fdff48916bd0654677ed21379bdb0db016d [1] https://cgit.git.savannah.gnu.org/cgit/gnulib.git/commit/?id=0b97ffdf32bdab909d02449043447237273df75e --- test/integration-tests/TEST-25-IMPORT/meson.build | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/integration-tests/TEST-25-IMPORT/meson.build b/test/integration-tests/TEST-25-IMPORT/meson.build index 8dec5f37e73a8..3e651107ff5fc 100644 --- a/test/integration-tests/TEST-25-IMPORT/meson.build +++ b/test/integration-tests/TEST-25-IMPORT/meson.build @@ -3,5 +3,8 @@ integration_tests += [ integration_test_template + { 'name' : fs.name(meson.current_source_dir()), + # Newer tar started using openat2() (without openat() fallback) which + # we currently filter out completely in nspawn with --suppress-sync + 'suppress-sync' : false, }, ] From a4256a9b31361c5d41b4eb3290cca78a71245abe Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 20 Apr 2026 11:41:53 +0200 Subject: [PATCH 1127/1296] sd-path: expose XDG 'projects' user dir As per: https://blog.tenstral.net/2026/04/hello-projects-directory.html --- man/sd_path_lookup.xml | 1 + src/libsystemd/sd-path/sd-path.c | 3 +++ src/path/path-tool.c | 1 + src/systemd/sd-path.h | 2 ++ test/units/TEST-74-AUX-UTILS.path.sh | 4 +++- 5 files changed, 10 insertions(+), 1 deletion(-) diff --git a/man/sd_path_lookup.xml b/man/sd_path_lookup.xml index 9190e6b00a49a..07592e71cebfc 100644 --- a/man/sd_path_lookup.xml +++ b/man/sd_path_lookup.xml @@ -68,6 +68,7 @@ SD_PATH_USER_PUBLIC, SD_PATH_USER_TEMPLATES, SD_PATH_USER_DESKTOP, + SD_PATH_USER_PROJECTS, SD_PATH_SEARCH_BINARIES, SD_PATH_SEARCH_BINARIES_DEFAULT, diff --git a/src/libsystemd/sd-path/sd-path.c b/src/libsystemd/sd-path/sd-path.c index e009a71bea0fd..eebcd20b6f9c2 100644 --- a/src/libsystemd/sd-path/sd-path.c +++ b/src/libsystemd/sd-path/sd-path.c @@ -282,6 +282,9 @@ static int get_path(uint64_t type, char **buffer, const char **ret) { case SD_PATH_USER_DESKTOP: return from_xdg_user_dir("XDG_DESKTOP_DIR", buffer, ret); + case SD_PATH_USER_PROJECTS: + return from_xdg_user_dir("XDG_PROJECTS_DIR", buffer, ret); + case SD_PATH_SYSTEMD_UTIL: *ret = PREFIX_NOSLASH "/lib/systemd"; return 0; diff --git a/src/path/path-tool.c b/src/path/path-tool.c index 797a7d06af895..1920ff8d60028 100644 --- a/src/path/path-tool.c +++ b/src/path/path-tool.c @@ -61,6 +61,7 @@ static const char* const path_table[_SD_PATH_MAX] = { [SD_PATH_USER_PUBLIC] = "user-public", [SD_PATH_USER_TEMPLATES] = "user-templates", [SD_PATH_USER_DESKTOP] = "user-desktop", + [SD_PATH_USER_PROJECTS] = "user-projects", [SD_PATH_SEARCH_BINARIES] = "search-binaries", [SD_PATH_SEARCH_BINARIES_DEFAULT] = "search-binaries-default", diff --git a/src/systemd/sd-path.h b/src/systemd/sd-path.h index 2718cf8266475..299fb20adea72 100644 --- a/src/systemd/sd-path.h +++ b/src/systemd/sd-path.h @@ -132,6 +132,8 @@ __extension__ enum { SD_PATH_USER_CREDENTIAL_STORE_ENCRYPTED, SD_PATH_USER_SEARCH_CREDENTIAL_STORE_ENCRYPTED, + SD_PATH_USER_PROJECTS, + _SD_PATH_MAX, _SD_PATH_INVALID = UINT64_MAX }; diff --git a/test/units/TEST-74-AUX-UTILS.path.sh b/test/units/TEST-74-AUX-UTILS.path.sh index 4547f53e24d81..14f7ef8ec062b 100755 --- a/test/units/TEST-74-AUX-UTILS.path.sh +++ b/test/units/TEST-74-AUX-UTILS.path.sh @@ -39,6 +39,7 @@ XDG_DOCUMENTS_DIR="$HOME/top/secret/documents" XDG_MUSIC_DIR="/tmp/vaporwave" XDG_PICTURES_DIR="$HOME/Pictures" XDG_VIDEOS_DIR="$HOME/🤔" +XDG_PROJECTS_DIR="$HOME/my-projects" EOF systemd-path --help @@ -66,12 +67,13 @@ assert_eq "$(systemd-path user-pictures)" "/root/Pictures" assert_eq "$(systemd-path user-public)" "/root/cat-pictures" assert_eq "$(systemd-path user-templates)" "/templates" assert_eq "$(systemd-path user-videos)" "/root/🤔" +assert_eq "$(systemd-path user-projects)" "/root/my-projects" # Remove the user-dirs.dir file and check the defaults rm -fv "$USER_DIRS_CONF" [[ ! -e "$USER_DIRS_CONF" ]] assert_eq "$(systemd-path user-desktop)" "/root/Desktop" -for dir in "" documents download music pictures public templates videos; do +for dir in "" documents download music pictures public templates videos projects; do assert_eq "$(systemd-path "user${dir:+-$dir}")" "/root" done From 54ed5c5806fecc5acedb3c7a2d02289501cea0af Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 14 Apr 2026 09:32:32 +0200 Subject: [PATCH 1128/1296] btrfs-util: make sure btrfs_get_block_device_at() works when called without path --- src/shared/btrfs-util.c | 3 +-- src/shared/btrfs-util.h | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index cde21bd602965..2095d803f59cf 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -101,8 +101,7 @@ int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret) { uint64_t id; int r; - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); - assert(path); + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); assert(ret); fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); diff --git a/src/shared/btrfs-util.h b/src/shared/btrfs-util.h index 55fb07656a59d..afa54cde4c434 100644 --- a/src/shared/btrfs-util.h +++ b/src/shared/btrfs-util.h @@ -54,7 +54,7 @@ static inline int btrfs_get_block_device(const char *path, dev_t *ret) { return btrfs_get_block_device_at(AT_FDCWD, path, ret); } static inline int btrfs_get_block_device_fd(int fd, dev_t *ret) { - return btrfs_get_block_device_at(fd, "", ret); + return btrfs_get_block_device_at(fd, NULL, ret); } int btrfs_defrag_fd(int fd); From 4712d70669d6d8987db8d64f294151a9e1305aeb Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 16 Apr 2026 05:44:44 +0200 Subject: [PATCH 1129/1296] chase: tighten flags checks in chase_and_unlinkat() Some flags don't reasonably apply to chase_and_unlinkat() (because we open the parent inode of an inode to delete, which is always a dir), hence let's catch these flags when misused. (I ran into this, and it was very confusing to debug, hence let's make it easier) --- src/basic/chase.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/basic/chase.c b/src/basic/chase.c index 82946eae5f199..5abb4bc4307bb 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -1098,7 +1098,7 @@ int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int r; assert(path); - assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT))); + assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT|CHASE_MUST_BE_SOCKET|CHASE_MUST_BE_REGULAR|CHASE_MUST_BE_DIRECTORY|CHASE_EXTRACT_FILENAME|CHASE_MKDIR_0755))); fd = chase_and_open(path, root, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p); if (fd < 0) @@ -1312,7 +1312,7 @@ int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int int r; assert(path); - assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT))); + assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT|CHASE_MUST_BE_SOCKET|CHASE_MUST_BE_REGULAR|CHASE_MUST_BE_DIRECTORY|CHASE_EXTRACT_FILENAME|CHASE_MKDIR_0755))); fd = chase_and_openat(dir_fd, path, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p); if (fd < 0) From 853d0649d584d78dde4dc40df711a4f0dc79e69e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 13 Apr 2026 18:04:29 +0200 Subject: [PATCH 1130/1296] find-esp: return pinned fd to ESP/XBOOTLDR The reworks the ESP/XBOOTLDR logic to pin the ESP/XBOOTLDR via an fd, and return that as optional return parameter. So far we only pinned the parent dir of the ESP/XBOOTLDR, which was useful when verifying that ESP/XBOOTLDR is actually a mount point by comparing mount ids. This however became obsolete with a98a6eb95cc980edab4b0f9c59e6573edc7ffe0c. Hence, let's clean this up, and pin the inode we really care about and return it. --- src/bless-boot/bless-boot.c | 2 + src/bootctl/bootctl-cleanup.c | 2 + src/bootctl/bootctl-install.c | 138 ++++++---------- src/bootctl/bootctl-random-seed.c | 14 +- src/bootctl/bootctl-random-seed.h | 2 +- src/bootctl/bootctl-status.c | 19 ++- src/bootctl/bootctl-unlink.c | 2 + src/bootctl/bootctl.c | 59 +++++-- src/bootctl/bootctl.h | 4 +- src/kernel-install/kernel-install.c | 6 +- src/shared/bootspec.c | 2 + src/shared/creds-util.c | 6 +- src/shared/find-esp.c | 240 ++++++++++++++-------------- src/shared/find-esp.h | 24 +-- src/sysupdate/sysupdate-resource.c | 4 +- src/tpm2-setup/tpm2-swtpm.c | 44 +++-- 16 files changed, 294 insertions(+), 274 deletions(-) diff --git a/src/bless-boot/bless-boot.c b/src/bless-boot/bless-boot.c index 86525f359a102..33fbdbb760832 100644 --- a/src/bless-boot/bless-boot.c +++ b/src/bless-boot/bless-boot.c @@ -115,6 +115,7 @@ static int acquire_path(void) { /* path= */ NULL, /* unprivileged_mode= */ false, &esp_path, + /* ret_fd= */ NULL, /* ret_part= */ NULL, /* ret_pstart= */ NULL, /* ret_psize= */ NULL, @@ -128,6 +129,7 @@ static int acquire_path(void) { /* path= */ NULL, /* unprivileged_mode= */ false, &xbootldr_path, + /* ret_fd= */ NULL, /* ret_uuid= */ NULL, &xbootldr_devid); if (r < 0 && r != -ENOKEY) diff --git a/src/bootctl/bootctl-cleanup.c b/src/bootctl/bootctl-cleanup.c index e654bca10497c..1e8819bea1813 100644 --- a/src/bootctl/bootctl-cleanup.c +++ b/src/bootctl/bootctl-cleanup.c @@ -93,6 +93,7 @@ int verb_cleanup(int argc, char *argv[], uintptr_t _data, void *userdata) { r = acquire_esp(/* unprivileged_mode= */ false, /* graceful= */ false, + /* ret_fd= */ NULL, /* ret_part= */ NULL, /* ret_pstart= */ NULL, /* ret_psize= */ NULL, @@ -103,6 +104,7 @@ int verb_cleanup(int argc, char *argv[], uintptr_t _data, void *userdata) { r = acquire_xbootldr( /* unprivileged_mode= */ false, + /* ret_fd= */ NULL, /* ret_uuid= */ NULL, &xbootldr_devid); if (r < 0) diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index fc89ce143b94b..96bc9213cf6b2 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -163,6 +163,7 @@ static int install_context_from_cmdline( r = acquire_esp(/* unprivileged_mode= */ false, b.graceful, + &b.esp_fd, &b.esp_part, &b.esp_pstart, &b.esp_psize, @@ -189,6 +190,7 @@ static int install_context_from_cmdline( r = acquire_xbootldr( /* unprivileged_mode= */ false, + &b.xbootldr_fd, /* ret_uuid= */ NULL, /* ret_devid= */ NULL); if (r < 0) @@ -213,55 +215,16 @@ static int install_context_from_cmdline( return !!ret->esp_path; /* return positive if we found an ESP */ } -static int acquire_esp_fd(InstallContext *c) { - int r; - - assert(c); - - if (c->esp_fd >= 0) - return c->esp_fd; - - assert(c->esp_path); - - _cleanup_free_ char *j = path_join(c->root, c->esp_path); - if (!j) - return log_oom(); - - r = chaseat(c->root_fd, - c->esp_path, - CHASE_AT_RESOLVE_IN_ROOT|CHASE_TRIGGER_AUTOFS|CHASE_MUST_BE_DIRECTORY, - /* ret_path= */ NULL, - &c->esp_fd); - if (r < 0) - return log_error_errno(r, "Failed to open ESP '%s': %m", j); - - return c->esp_fd; -} - static int acquire_dollar_boot_fd(InstallContext *c) { - int r; - assert(c); if (c->xbootldr_fd >= 0) return c->xbootldr_fd; - if (!c->xbootldr_path) - return acquire_esp_fd(c); - - _cleanup_free_ char *j = path_join(c->root, c->xbootldr_path); - if (!j) - return log_oom(); - - r = chaseat(c->root_fd, - c->xbootldr_path, - CHASE_AT_RESOLVE_IN_ROOT|CHASE_TRIGGER_AUTOFS|CHASE_MUST_BE_DIRECTORY, - /* ret_path= */ NULL, - &c->xbootldr_fd); - if (r < 0) - return log_error_errno(r, "Failed to open XBOOTLDR '%s': %m", j); + if (c->esp_fd >= 0) + return c->esp_fd; - return c->xbootldr_fd; + return log_error_errno(SYNTHETIC_ERRNO(EBADF), "Cannot access $BOOT, as neither ESP nor XBOOTLDR have been found."); } static const char* dollar_boot_path(InstallContext *c) { @@ -639,9 +602,8 @@ static int update_efi_boot_binaries( assert(c); assert(source_path); - int esp_fd = acquire_esp_fd(c); - if (esp_fd < 0) - return esp_fd; + if (c->esp_fd < 0) + return c->esp_fd; _cleanup_free_ char *j = path_join(c->root, c->esp_path); if (!j) @@ -649,7 +611,7 @@ static int update_efi_boot_binaries( _cleanup_closedir_ DIR *d = NULL; r = chase_and_opendirat( - esp_fd, + c->esp_fd, "/EFI/BOOT", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, @@ -738,16 +700,15 @@ static int copy_one_file( return log_error_errno(source_fd, "Failed to resolve path '%s': %m", sp); } - int esp_fd = acquire_esp_fd(c); - if (esp_fd < 0) - return esp_fd; + if (c->esp_fd < 0) + return c->esp_fd; _cleanup_free_ char *j = path_join(c->root, c->esp_path); if (!j) return log_oom(); _cleanup_close_ int dest_parent_fd = -EBADF; - r = chaseat(esp_fd, + r = chaseat(c->esp_fd, "/EFI/systemd", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, @@ -778,7 +739,7 @@ static int copy_one_file( ascii_strupper(boot_dot_efi); _cleanup_close_ int default_dest_parent_fd = -EBADF; - r = chaseat(esp_fd, + r = chaseat(c->esp_fd, "/EFI/BOOT", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, @@ -875,16 +836,15 @@ static int install_loader_config(InstallContext *c) { assert(c); assert(c->make_entry_directory >= 0); - int esp_fd = acquire_esp_fd(c); - if (esp_fd < 0) - return esp_fd; + if (c->esp_fd < 0) + return c->esp_fd; _cleanup_free_ char *j = path_join(c->root, c->esp_path); if (!j) return log_oom(); _cleanup_close_ int loader_dir_fd = -EBADF; - r = chaseat(esp_fd, + r = chaseat(c->esp_fd, "loader", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, @@ -1071,16 +1031,15 @@ static int install_secure_boot_auto_enroll(InstallContext *c) { return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert X.509 certificate to DER: %s", ERR_error_string(ERR_get_error(), NULL)); - int esp_fd = acquire_esp_fd(c); - if (esp_fd < 0) - return esp_fd; + if (c->esp_fd < 0) + return c->esp_fd; _cleanup_free_ char *j = path_join(c->root, c->esp_path); if (!j) return log_oom(); _cleanup_close_ int keys_fd = -EBADF; - r = chaseat(esp_fd, + r = chaseat(c->esp_fd, "loader/keys/auto", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, @@ -1385,16 +1344,15 @@ static int install_variables( assert(c); - int esp_fd = acquire_esp_fd(c); - if (esp_fd < 0) - return esp_fd; + if (c->esp_fd < 0) + return c->esp_fd; _cleanup_free_ char *j = path_join(c->root, c->esp_path); if (!j) return log_oom(); r = chase_and_accessat( - esp_fd, + c->esp_fd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_REGULAR, F_OK, @@ -1422,7 +1380,7 @@ static int install_variables( if (c->operation == INSTALL_NEW || !existing) { _cleanup_free_ char *description = NULL; - r = pick_efi_boot_option_description(esp_fd, &description); + r = pick_efi_boot_option_description(c->esp_fd, &description); if (r < 0) return r; @@ -1474,12 +1432,11 @@ static int are_we_installed(InstallContext *c) { if (!p) return log_oom(); - int esp_fd = acquire_esp_fd(c); - if (esp_fd < 0) - return esp_fd; + if (c->esp_fd < 0) + return c->esp_fd; _cleanup_close_ int fd = chase_and_openat( - esp_fd, + c->esp_fd, "/EFI/systemd", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, O_RDONLY|O_CLOEXEC|O_DIRECTORY, @@ -1582,9 +1539,8 @@ static int run_install(InstallContext *c) { const char *arch = arg_arch_all ? "" : get_efi_arch(); - int esp_fd = acquire_esp_fd(c); - if (esp_fd < 0) - return esp_fd; + if (c->esp_fd < 0) + return c->esp_fd; _cleanup_free_ char *j = path_join(c->root, c->esp_path); if (!j) @@ -1604,7 +1560,7 @@ static int run_install(InstallContext *c) { * we'll drop-in our files (unless there are newer ones already), but we won't create * the directories for them in the first place. */ - r = create_subdirs(j, esp_fd, esp_subdirs); + r = create_subdirs(j, c->esp_fd, esp_subdirs); if (r < 0) return r; @@ -1631,7 +1587,7 @@ static int run_install(InstallContext *c) { return r; if (arg_install_random_seed && !c->root) { - r = install_random_seed(c->esp_path); + r = install_random_seed(c->esp_path, c->esp_fd); if (r < 0) return r; } @@ -1689,9 +1645,8 @@ static int remove_boot_efi(InstallContext *c) { assert(c); - int esp_fd = acquire_esp_fd(c); - if (esp_fd < 0) - return esp_fd; + if (c->esp_fd < 0) + return c->esp_fd; _cleanup_free_ char *w = path_join(c->root, c->esp_path); if (!w) @@ -1700,7 +1655,7 @@ static int remove_boot_efi(InstallContext *c) { _cleanup_closedir_ DIR *d = NULL; _cleanup_free_ char *p = NULL; r = chase_and_opendirat( - esp_fd, + c->esp_fd, "/EFI/BOOT", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, &p, @@ -1898,15 +1853,14 @@ int verb_remove(int argc, char *argv[], uintptr_t _data, void *userdata) { if (r < 0) return r; - int esp_fd = acquire_esp_fd(&c); - if (esp_fd < 0) - return esp_fd; + if (c.esp_fd < 0) + return c.esp_fd; _cleanup_free_ char *j = path_join(c.root, c.esp_path); if (!j) return log_oom(); - int dollar_boot_fd = acquire_dollar_boot_fd(&c); /* this will initialize .xbootldr_fd */ + int dollar_boot_fd = acquire_dollar_boot_fd(&c); if (dollar_boot_fd < 0) return dollar_boot_fd; @@ -1915,23 +1869,23 @@ int verb_remove(int argc, char *argv[], uintptr_t _data, void *userdata) { return log_oom(); r = remove_binaries(&c); - RET_GATHER(r, unlink_inode(j, esp_fd, "/loader/loader.conf", S_IFREG)); - RET_GATHER(r, unlink_inode(j, esp_fd, "/loader/random-seed", S_IFREG)); - RET_GATHER(r, unlink_inode(j, esp_fd, "/loader/entries.srel", S_IFREG)); + RET_GATHER(r, unlink_inode(j, c.esp_fd, "/loader/loader.conf", S_IFREG)); + RET_GATHER(r, unlink_inode(j, c.esp_fd, "/loader/random-seed", S_IFREG)); + RET_GATHER(r, unlink_inode(j, c.esp_fd, "/loader/entries.srel", S_IFREG)); FOREACH_STRING(db, "PK.auth", "KEK.auth", "db.auth") { _cleanup_free_ char *p = path_join("/loader/keys/auto", db); if (!p) return log_oom(); - RET_GATHER(r, unlink_inode(j, esp_fd, p, S_IFREG)); + RET_GATHER(r, unlink_inode(j, c.esp_fd, p, S_IFREG)); } - RET_GATHER(r, unlink_inode(j, esp_fd, "/loader/keys/auto", S_IFDIR)); - RET_GATHER(r, unlink_inode(j, esp_fd, "/loader/entries.srel", S_IFREG)); + RET_GATHER(r, unlink_inode(j, c.esp_fd, "/loader/keys/auto", S_IFDIR)); + RET_GATHER(r, unlink_inode(j, c.esp_fd, "/loader/entries.srel", S_IFREG)); - RET_GATHER(r, remove_subdirs(j, esp_fd, esp_subdirs)); - RET_GATHER(r, remove_subdirs(j, esp_fd, dollar_boot_subdirs)); - RET_GATHER(r, remove_entry_directory(&c, j, esp_fd)); + RET_GATHER(r, remove_subdirs(j, c.esp_fd, esp_subdirs)); + RET_GATHER(r, remove_subdirs(j, c.esp_fd, dollar_boot_subdirs)); + RET_GATHER(r, remove_entry_directory(&c, j, c.esp_fd)); if (c.xbootldr_fd >= 0) { /* Remove a subset of these also from the XBOOTLDR partition if it exists */ @@ -2066,6 +2020,7 @@ int vl_method_install( /* path= */ NULL, /* unprivileged_mode= */ false, &p.context.esp_path, + &p.context.esp_fd, &p.context.esp_part, &p.context.esp_pstart, &p.context.esp_psize, @@ -2080,7 +2035,8 @@ int vl_method_install( p.context.root_fd, /* path= */ NULL, /* unprivileged_mode= */ false, - &p.context.xbootldr_path); + &p.context.xbootldr_path, + &p.context.xbootldr_fd); if (r == -ENOKEY) log_debug_errno(r, "Didn't find an XBOOTLDR partition, using ESP as $BOOT."); else if (r < 0) diff --git a/src/bootctl/bootctl-random-seed.c b/src/bootctl/bootctl-random-seed.c index 2ef491c54f84e..be33d9f950fbf 100644 --- a/src/bootctl/bootctl-random-seed.c +++ b/src/bootctl/bootctl-random-seed.c @@ -111,8 +111,8 @@ static int set_system_token(void) { return 0; } -int install_random_seed(const char *esp) { - _cleanup_close_ int esp_fd = -EBADF, loader_dir_fd = -EBADF, fd = -EBADF; +int install_random_seed(const char *esp, int esp_fd) { + _cleanup_close_ int loader_dir_fd = -EBADF, fd = -EBADF; _cleanup_free_ char *tmp = NULL; uint8_t buffer[RANDOM_EFI_SEED_SIZE]; struct sha256_ctx hash_state; @@ -120,16 +120,13 @@ int install_random_seed(const char *esp) { int r; assert(esp); + assert(esp_fd >= 0); assert_cc(RANDOM_EFI_SEED_SIZE == SHA256_DIGEST_SIZE); if (!arg_install_random_seed) return 0; - esp_fd = open(esp, O_DIRECTORY|O_RDONLY|O_CLOEXEC); - if (esp_fd < 0) - return log_error_errno(errno, "Failed to open ESP directory '%s': %m", esp); - (void) random_seed_verify_permissions(esp_fd, S_IFDIR); loader_dir_fd = open_mkdir_at(esp_fd, "loader", O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_NOFOLLOW, 0775); @@ -204,7 +201,8 @@ int install_random_seed(const char *esp) { int verb_random_seed(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; - r = find_esp_and_warn(arg_root, arg_esp_path, /* unprivileged_mode= */ false, &arg_esp_path); + _cleanup_close_ int esp_fd = -EBADF; + r = find_esp_and_warn(arg_root, arg_esp_path, /* unprivileged_mode= */ false, &arg_esp_path, &esp_fd); if (r == -ENOKEY) { /* find_esp_and_warn() doesn't warn about ENOKEY, so let's do that on our own */ if (arg_graceful() == ARG_GRACEFUL_NO) @@ -216,7 +214,7 @@ int verb_random_seed(int argc, char *argv[], uintptr_t _data, void *userdata) { if (r < 0) return r; - r = install_random_seed(arg_esp_path); + r = install_random_seed(arg_esp_path, esp_fd); if (r < 0) return r; diff --git a/src/bootctl/bootctl-random-seed.h b/src/bootctl/bootctl-random-seed.h index 722c511b74808..1764668b3a3d5 100644 --- a/src/bootctl/bootctl-random-seed.h +++ b/src/bootctl/bootctl-random-seed.h @@ -3,6 +3,6 @@ #include "shared-forward.h" -int install_random_seed(const char *esp); +int install_random_seed(const char *esp, int esp_fd); int verb_random_seed(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/bootctl/bootctl-status.c b/src/bootctl/bootctl-status.c index 76e62847f36eb..2c0eb4d1d00d4 100644 --- a/src/bootctl/bootctl-status.c +++ b/src/bootctl/bootctl-status.c @@ -334,6 +334,7 @@ int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { r = acquire_esp(/* unprivileged_mode= */ -1, /* graceful= */ false, + /* ret_fd= */ NULL, /* ret_part= */ NULL, /* ret_pstart= */ NULL, /* ret_psize= */ NULL, @@ -352,6 +353,7 @@ int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { r = acquire_xbootldr( /* unprivileged_mode= */ -1, + /* ret_fd= */ NULL, &xbootldr_uuid, &xbootldr_devid); if (arg_print_dollar_boot_path) { @@ -644,13 +646,24 @@ int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) { (void) touch_variables(); - r = acquire_esp(/* unprivileged_mode= */ -1, /* graceful= */ false, NULL, NULL, NULL, NULL, &esp_devid); + r = acquire_esp(/* unprivileged_mode= */ -1, + /* graceful= */ false, + /* ret_fd= */ NULL, + /* ret_part= */ NULL, + /* ret_pstart= */ NULL, + /* ret_psize= */ NULL, + /* ret_uuid= */ NULL, + &esp_devid); if (r == -EACCES) /* We really need the ESP path for this call, hence also log about access errors */ return log_error_errno(r, "Failed to determine ESP location: %m"); if (r < 0) return r; - r = acquire_xbootldr(/* unprivileged_mode= */ -1, NULL, &xbootldr_devid); + r = acquire_xbootldr( + /* unprivileged_mode= */ -1, + /* ret_fd= */ NULL, + /* ret_uuid= */ NULL, + &xbootldr_devid); if (r == -EACCES) return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m"); if (r < 0) @@ -683,6 +696,7 @@ int vl_method_list_boot_entries(sd_varlink *link, sd_json_variant *parameters, s r = acquire_esp(/* unprivileged_mode= */ false, /* graceful= */ false, + /* ret_fd= */ NULL, /* ret_part= */ NULL, /* ret_pstart= */ NULL, /* ret_psize= */ NULL, @@ -695,6 +709,7 @@ int vl_method_list_boot_entries(sd_varlink *link, sd_json_variant *parameters, s r = acquire_xbootldr( /* unprivileged_mode= */ false, + /* ret_fd= */ NULL, /* ret_uuid= */ NULL, &xbootldr_devid); if (r == -EACCES) diff --git a/src/bootctl/bootctl-unlink.c b/src/bootctl/bootctl-unlink.c index b5cc839798973..0d0e7ad076b60 100644 --- a/src/bootctl/bootctl-unlink.c +++ b/src/bootctl/bootctl-unlink.c @@ -205,6 +205,7 @@ int verb_unlink(int argc, char *argv[], uintptr_t _data, void *userdata) { r = acquire_esp(/* unprivileged_mode= */ false, /* graceful= */ false, + /* ret_fd= */ NULL, /* ret_part= */ NULL, /* ret_pstart= */ NULL, /* ret_psize= */ NULL, @@ -217,6 +218,7 @@ int verb_unlink(int argc, char *argv[], uintptr_t _data, void *userdata) { r = acquire_xbootldr( /* unprivileged_mode= */ false, + /* ret_fd= */ NULL, /* ret_uuid= */ NULL, &xbootldr_devid); if (r == -EACCES) diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index ef1116ced72bf..239a0c9273073 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -21,6 +21,7 @@ #include "efi-loader.h" #include "efivars.h" #include "escape.h" +#include "fd-util.h" #include "find-esp.h" #include "format-table.h" #include "image-policy.h" @@ -100,16 +101,16 @@ static const char* const install_source_table[_INSTALL_SOURCE_MAX] = { DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(install_source, InstallSource); -int acquire_esp( - int unprivileged_mode, +int acquire_esp(int unprivileged_mode, bool graceful, + int *ret_fd, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid) { - char *np; + _cleanup_free_ char *np = NULL; int r; /* Find the ESP, and log about errors. Note that find_esp_and_warn() will log in all error cases on @@ -118,7 +119,7 @@ int acquire_esp( * we simply eat up the error here, so that --list and --status work too, without noise about * this). */ - r = find_esp_and_warn_full(arg_root, arg_esp_path, unprivileged_mode, &np, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid); + r = find_esp_and_warn_full(arg_root, arg_esp_path, unprivileged_mode, &np, ret_fd, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid); if (r == -ENOKEY) { if (graceful) return log_full_errno(arg_quiet ? LOG_DEBUG : LOG_INFO, r, @@ -134,27 +135,44 @@ int acquire_esp( free_and_replace(arg_esp_path, np); log_debug("Using EFI System Partition at %s.", arg_esp_path); - return 0; + return 1; /* for symmetry with acquire_xbootldr() below: found */ } int acquire_xbootldr( int unprivileged_mode, + int *ret_fd, sd_id128_t *ret_uuid, dev_t *ret_devid) { - char *np; int r; - r = find_xbootldr_and_warn_full(arg_root, arg_xbootldr_path, unprivileged_mode, &np, ret_uuid, ret_devid); - if (r == -ENOKEY || path_equal(np, arg_esp_path)) { - log_debug("Didn't find an XBOOTLDR partition, using the ESP as $BOOT."); + _cleanup_free_ char *np = NULL; + _cleanup_close_ int fd = -EBADF; + r = find_xbootldr_and_warn_full( + arg_root, + arg_xbootldr_path, + unprivileged_mode, + &np, + ret_fd ? &fd : NULL, + ret_uuid, + ret_devid); + if (r == -ENOKEY || (r >= 0 && arg_esp_path && path_equal(np, arg_esp_path))) { + + if (arg_esp_path) + log_debug("Didn't find an XBOOTLDR partition, using the ESP as $BOOT."); + else + log_debug("Found neither an XBOOTLDR partition, nor an ESP."); + arg_xbootldr_path = mfree(arg_xbootldr_path); + if (ret_fd) + *ret_fd = -EBADF; if (ret_uuid) *ret_uuid = SD_ID128_NULL; if (ret_devid) *ret_devid = 0; - return 0; + + return 0; /* not found */ } if (r < 0) return r; @@ -162,7 +180,10 @@ int acquire_xbootldr( free_and_replace(arg_xbootldr_path, np); log_debug("Using XBOOTLDR partition at %s as $BOOT.", arg_xbootldr_path); - return 1; + if (ret_fd) + *ret_fd = TAKE_FD(fd); + + return 1; /* found */ } static int print_loader_or_stub_path(void) { @@ -199,9 +220,14 @@ static int print_loader_or_stub_path(void) { } sd_id128_t esp_uuid; - r = acquire_esp(/* unprivileged_mode= */ false, /* graceful= */ false, - /* ret_part= */ NULL, /* ret_pstart= */ NULL, /* ret_psize= */ NULL, - &esp_uuid, /* ret_devid= */ NULL); + r = acquire_esp(/* unprivileged_mode= */ false, + /* graceful= */ false, + /* ret_fd= */ NULL, + /* ret_part= */ NULL, + /* ret_pstart= */ NULL, + /* ret_psize= */ NULL, + &esp_uuid, + /* ret_devid= */ NULL); if (r < 0) return r; @@ -211,7 +237,10 @@ static int print_loader_or_stub_path(void) { else if (arg_print_stub_path) { /* In case of the stub, also look for things in the xbootldr partition */ sd_id128_t xbootldr_uuid; - r = acquire_xbootldr(/* unprivileged_mode= */ false, &xbootldr_uuid, /* ret_devid= */ NULL); + r = acquire_xbootldr(/* unprivileged_mode= */ false, + /* ret_fd= */ NULL, + &xbootldr_uuid, + /* ret_devid= */ NULL); if (r < 0) return r; diff --git a/src/bootctl/bootctl.h b/src/bootctl/bootctl.h index 07e98f8559491..d3d6583c0241d 100644 --- a/src/bootctl/bootctl.h +++ b/src/bootctl/bootctl.h @@ -58,8 +58,8 @@ static inline const char* arg_dollar_boot_path(void) { GracefulMode arg_graceful(void); -int acquire_esp(int unprivileged_mode, bool graceful, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid); -int acquire_xbootldr(int unprivileged_mode, sd_id128_t *ret_uuid, dev_t *ret_devid); +int acquire_esp(int unprivileged_mode, bool graceful, int *ret_fd, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid); +int acquire_xbootldr(int unprivileged_mode, int *ret_fd, sd_id128_t *ret_uuid, dev_t *ret_devid); /* EFI_BOOT_OPTION_DESCRIPTION_MAX sets the maximum length for the boot option description * stored in NVRAM. The UEFI spec does not specify a minimum or maximum length for this diff --git a/src/kernel-install/kernel-install.c b/src/kernel-install/kernel-install.c index 8c0abba4207ad..aeded46c22d9f 100644 --- a/src/kernel-install/kernel-install.c +++ b/src/kernel-install/kernel-install.c @@ -570,7 +570,8 @@ static int context_acquire_xbootldr(Context *c) { /* rfd= */ c->rfd, /* path= */ arg_xbootldr_path, /* unprivileged_mode= */ -1, - /* ret_path= */ &c->boot_root); + /* ret_path= */ &c->boot_root, + /* ret_fd= */ NULL); if (r == -ENOKEY) { log_debug_errno(r, "Couldn't find an XBOOTLDR partition."); return 0; @@ -594,7 +595,8 @@ static int context_acquire_esp(Context *c) { /* rfd= */ c->rfd, /* path= */ arg_esp_path, /* unprivileged_mode= */ -1, - /* ret_path= */ &c->boot_root); + /* ret_path= */ &c->boot_root, + /* ret_fd= */ NULL); if (r == -ENOKEY) { log_debug_errno(r, "Couldn't find EFI system partition, ignoring."); return 0; diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index 2d9906acb9b87..c3774a5235fad 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -1597,6 +1597,7 @@ int boot_config_load_auto( override_esp_path, /* unprivileged_mode= */ false, &esp_where, + /* ret_fd= */ NULL, /* ret_part= */ NULL, /* ret_pstart= */ NULL, /* ret_psize= */ NULL, @@ -1610,6 +1611,7 @@ int boot_config_load_auto( override_xbootldr_path, /* unprivileged_mode= */ false, &xbootldr_where, + /* ret_fd= */ NULL, /* ret_uuid= */ NULL, &xbootldr_devid); if (r < 0 && r != -ENOKEY) diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index 3ff214a09a361..d3383aebb6faf 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -1709,7 +1709,8 @@ int get_global_boot_credentials_path(char **ret) { /* root= */ NULL, /* path= */ NULL, /* unprivileged_mode= */ false, - &path); + &path, + /* ret_fd= */ NULL); if (r < 0) { if (r != -ENOKEY) return log_error_errno(r, "Failed to find XBOOTLDR partition: %m"); @@ -1718,7 +1719,8 @@ int get_global_boot_credentials_path(char **ret) { /* root= */ NULL, /* path= */ NULL, /* unprivileged_mode= */ false, - &path); + &path, + /* ret_fd= */ NULL); if (r < 0) { if (r != -ENOKEY) return log_error_errno(r, "Failed to find ESP partition: %m"); diff --git a/src/shared/find-esp.c b/src/shared/find-esp.c index 1d13683f1286e..d29719785fedd 100644 --- a/src/shared/find-esp.c +++ b/src/shared/find-esp.c @@ -19,7 +19,6 @@ #include "errno-util.h" #include "fd-util.h" #include "find-esp.h" -#include "mount-util.h" #include "parse-util.h" #include "path-util.h" #include "stat-util.h" @@ -260,33 +259,24 @@ static int verify_esp_udev( } static int verify_fsroot_dir( - int dir_fd, const char *path, + int fd, VerifyESPFlags flags, dev_t *ret_dev) { bool searching = FLAGS_SET(flags, VERIFY_ESP_SEARCHING), unprivileged_mode = FLAGS_SET(flags, VERIFY_ESP_UNPRIVILEGED_MODE); - _cleanup_free_ char *f = NULL; - struct statx sx; int r; /* Checks if the specified directory is at the root of its file system, and returns device * major/minor of the device, if it is. */ - assert(dir_fd >= 0); assert(path); + assert(fd >= 0); - /* We pass the full path from the root directory file descriptor so we can use it for logging, but - * dir_fd points to the parent directory of the final component of the given path, so we extract the - * filename and operate on that. */ - - r = path_extract_filename(path, &f); - if (r < 0 && r != -EADDRNOTAVAIL) - return log_error_errno(r, "Failed to extract filename of \"%s\": %m", path); - - r = xstatx_full(dir_fd, f, - AT_SYMLINK_NOFOLLOW, + struct statx sx; + r = xstatx_full(fd, /* path= */ NULL, + /* statx_flags= */ 0, /* xstatx_flags= */ 0, STATX_TYPE|STATX_INO, /* optional_mask = */ 0, @@ -310,7 +300,7 @@ static int verify_fsroot_dir( return 0; if (sx.stx_dev_major == 0) /* Hmm, maybe a btrfs device, and the caller asked for the backing device? Then let's try to get it. */ - return btrfs_get_block_device_at(dir_fd, strempty(f), ret_dev); + return btrfs_get_block_device_fd(fd, ret_dev); *ret_dev = makedev(sx.stx_dev_major, sx.stx_dev_minor); return 0; @@ -320,6 +310,7 @@ static int verify_esp( int rfd, const char *path, char **ret_path, + int *ret_fd, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, @@ -329,9 +320,6 @@ static int verify_esp( bool searching = FLAGS_SET(flags, VERIFY_ESP_SEARCHING), unprivileged_mode = FLAGS_SET(flags, VERIFY_ESP_UNPRIVILEGED_MODE); - _cleanup_free_ char *p = NULL; - _cleanup_close_ int pfd = -EBADF; - dev_t devid = 0; int r; assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); @@ -347,87 +335,71 @@ static int verify_esp( /* Non-root user can only check the status, so if an error occurred in the following, it does not cause any * issues. Let's also, silence the error messages. */ - r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_TRIGGER_AUTOFS, &p, &pfd); + _cleanup_free_ char *p = NULL; + _cleanup_close_ int fd = -EBADF; + r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_TRIGGER_AUTOFS, &p, &fd); if (r < 0) return log_full_errno((searching && r == -ENOENT) || (unprivileged_mode && ERRNO_IS_PRIVILEGE(r)) ? LOG_DEBUG : LOG_ERR, - r, "Failed to open parent directory of \"%s\": %m", path); + r, "Failed to open directory \"%s\": %m", path); if (!FLAGS_SET(flags, VERIFY_ESP_SKIP_FSTYPE_CHECK)) { - _cleanup_free_ char *f = NULL; - struct statfs sfs; - - r = path_extract_filename(p, &f); - if (r < 0 && r != -EADDRNOTAVAIL) - return log_error_errno(r, "Failed to extract filename of \"%s\": %m", p); - /* Trigger any automounts so that xstatfsat() operates on the mount instead of the mountpoint - * directory. */ - r = trigger_automount_at(pfd, f); + r = fd_is_fs_type(fd, MSDOS_SUPER_MAGIC); if (r < 0) - return log_error_errno(r, "Failed to trigger automount at \"%s\": %m", p); - - r = xstatfsat(pfd, strempty(f), &sfs); - if (r < 0) - /* If we are searching for the mount point, don't generate a log message if we can't find the path */ - return log_full_errno((searching && r == -ENOENT) || - (unprivileged_mode && r == -EACCES) ? LOG_DEBUG : LOG_ERR, r, + return log_full_errno((unprivileged_mode && r == -EACCES) ? LOG_DEBUG : LOG_ERR, r, "Failed to check file system type of \"%s\": %m", p); - - if (!F_TYPE_EQUAL(sfs.f_type, MSDOS_SUPER_MAGIC)) + if (!r) return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV), "File system \"%s\" is not a FAT EFI System Partition (ESP) file system.", p); } - r = verify_fsroot_dir(pfd, p, flags, FLAGS_SET(flags, VERIFY_ESP_SKIP_DEVICE_CHECK) ? NULL : &devid); + dev_t devid = 0; + r = verify_fsroot_dir(p, fd, flags, FLAGS_SET(flags, VERIFY_ESP_SKIP_DEVICE_CHECK) ? NULL : &devid); if (r < 0) return r; /* In a container we don't have access to block devices, skip this part of the verification, we trust * the container manager set everything up correctly on its own. */ - if (FLAGS_SET(flags, VERIFY_ESP_SKIP_DEVICE_CHECK)) - goto finish; + if (FLAGS_SET(flags, VERIFY_ESP_SKIP_DEVICE_CHECK)) { - if (devnum_is_zero(devid)) - return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, - SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV), - "Could not determine backing block device of directory \"%s\" (btrfs RAID?).", p); + if (ret_part) + *ret_part = 0; + if (ret_pstart) + *ret_pstart = 0; + if (ret_psize) + *ret_psize = 0; + if (ret_uuid) + *ret_uuid = SD_ID128_NULL; - /* If we are unprivileged we ask udev for the metadata about the partition. If we are privileged we - * use blkid instead. Why? Because this code is called from 'bootctl' which is pretty much an - * emergency recovery tool that should also work when udev isn't up (i.e. from the emergency shell), - * however blkid can't work if we have no privileges to access block devices directly, which is why - * we use udev in that case. */ - if (unprivileged_mode) - r = verify_esp_udev(devid, flags, ret_part, ret_pstart, ret_psize, ret_uuid); - else - r = verify_esp_blkid(devid, flags, ret_part, ret_pstart, ret_psize, ret_uuid); - if (r < 0) - return r; + } else { + if (devnum_is_zero(devid)) + return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, + SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV), + "Could not determine backing block device of directory \"%s\" (btrfs RAID?).", p); + + /* If we are unprivileged we ask udev for the metadata about the partition. If we are privileged we + * use blkid instead. Why? Because this code is called from 'bootctl' which is pretty much an + * emergency recovery tool that should also work when udev isn't up (i.e. from the emergency shell), + * however blkid can't work if we have no privileges to access block devices directly, which is why + * we use udev in that case. */ + if (unprivileged_mode) + r = verify_esp_udev(devid, flags, ret_part, ret_pstart, ret_psize, ret_uuid); + else + r = verify_esp_blkid(devid, flags, ret_part, ret_pstart, ret_psize, ret_uuid); + if (r < 0) + return r; + } if (ret_path) *ret_path = TAKE_PTR(p); + if (ret_fd) + *ret_fd = TAKE_FD(fd); if (ret_devid) *ret_devid = devid; return 0; - -finish: - if (ret_path) - *ret_path = TAKE_PTR(p); - if (ret_part) - *ret_part = 0; - if (ret_pstart) - *ret_pstart = 0; - if (ret_psize) - *ret_psize = 0; - if (ret_uuid) - *ret_uuid = SD_ID128_NULL; - if (ret_devid) - *ret_devid = 0; - - return 0; } int find_esp_and_warn_at_full( @@ -435,6 +407,7 @@ int find_esp_and_warn_at_full( const char *path, int unprivileged_mode, char **ret_path, + int *ret_fd, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, @@ -455,7 +428,7 @@ int find_esp_and_warn_at_full( flags = verify_esp_flags_init(unprivileged_mode, "SYSTEMD_RELAX_ESP_CHECKS"); if (path) - return verify_esp(rfd, path, ret_path, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid, flags); + return verify_esp(rfd, path, ret_path, ret_fd, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid, flags); path = getenv("SYSTEMD_ESP_PATH"); if (path) { @@ -484,6 +457,8 @@ int find_esp_and_warn_at_full( if (ret_path) *ret_path = TAKE_PTR(p); + if (ret_fd) + *ret_fd = TAKE_FD(fd); if (ret_part) *ret_part = 0; if (ret_pstart) @@ -499,7 +474,15 @@ int find_esp_and_warn_at_full( } FOREACH_STRING(dir, "/efi", "/boot", "/boot/efi") { - r = verify_esp(rfd, dir, ret_path, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid, + r = verify_esp(rfd, + dir, + ret_path, + ret_fd, + ret_part, + ret_pstart, + ret_psize, + ret_uuid, + ret_devid, flags | VERIFY_ESP_SEARCHING); if (r >= 0) return 0; @@ -516,20 +499,16 @@ int find_esp_and_warn_full( const char *path, int unprivileged_mode, char **ret_path, + int *ret_fd, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid) { - _cleanup_close_ int rfd = -EBADF; - _cleanup_free_ char *p = NULL; - uint32_t part; - uint64_t pstart, psize; - sd_id128_t uuid; - dev_t devid; int r; + _cleanup_close_ int rfd = -EBADF; if (empty_or_root(root)) rfd = XAT_FDROOT; else { @@ -538,11 +517,18 @@ int find_esp_and_warn_full( return -errno; } + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *p = NULL; + uint32_t part; + uint64_t pstart, psize; + sd_id128_t uuid; + dev_t devid; r = find_esp_and_warn_at_full( rfd, path, unprivileged_mode, ret_path ? &p : NULL, + ret_fd ? &fd : NULL, ret_part ? &part : NULL, ret_pstart ? &pstart : NULL, ret_psize ? &psize : NULL, @@ -556,6 +542,8 @@ int find_esp_and_warn_full( if (r < 0) return r; } + if (ret_fd) + *ret_fd = TAKE_FD(fd); if (ret_part) *ret_part = part; if (ret_pstart) @@ -734,64 +722,59 @@ static int verify_xbootldr( const char *path, VerifyESPFlags flags, char **ret_path, + int *ret_fd, sd_id128_t *ret_uuid, dev_t *ret_devid) { - _cleanup_free_ char *p = NULL; - _cleanup_close_ int pfd = -EBADF; bool searching = FLAGS_SET(flags, VERIFY_ESP_SEARCHING), unprivileged_mode = FLAGS_SET(flags, VERIFY_ESP_UNPRIVILEGED_MODE); - dev_t devid = 0; int r; assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); assert(path); - r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_TRIGGER_AUTOFS, &p, &pfd); + _cleanup_free_ char *p = NULL; + _cleanup_close_ int fd = -EBADF; + r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_TRIGGER_AUTOFS, &p, &fd); if (r < 0) return log_full_errno((searching && r == -ENOENT) || (unprivileged_mode && ERRNO_IS_PRIVILEGE(r)) ? LOG_DEBUG : LOG_ERR, - r, "Failed to open parent directory of \"%s\": %m", path); + r, "Failed to open directory \"%s\": %m", path); - r = verify_fsroot_dir(pfd, p, flags, FLAGS_SET(flags, VERIFY_ESP_SKIP_DEVICE_CHECK) ? NULL : &devid); + dev_t devid = 0; + r = verify_fsroot_dir(p, fd, flags, FLAGS_SET(flags, VERIFY_ESP_SKIP_DEVICE_CHECK) ? NULL : &devid); if (r < 0) return r; - if (FLAGS_SET(flags, VERIFY_ESP_SKIP_DEVICE_CHECK)) - goto finish; - - if (devnum_is_zero(devid)) - return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, - SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV), - "Could not determine backing block device of directory \"%s\" (btrfs RAID?).%s", - p, - searching ? "" : - "\nHint: set $SYSTEMD_RELAX_XBOOTLDR_CHECKS=yes environment variable " - "to bypass this and further verifications for the directory."); - - if (unprivileged_mode) - r = verify_xbootldr_udev(devid, flags, ret_uuid); - else - r = verify_xbootldr_blkid(devid, flags, ret_uuid); - if (r < 0) - return r; + if (FLAGS_SET(flags, VERIFY_ESP_SKIP_DEVICE_CHECK)) { + if (ret_uuid) + *ret_uuid = SD_ID128_NULL; + } else { + if (devnum_is_zero(devid)) + return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, + SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV), + "Could not determine backing block device of directory \"%s\" (btrfs RAID?).%s", + p, + searching ? "" : + "\nHint: set $SYSTEMD_RELAX_XBOOTLDR_CHECKS=yes environment variable " + "to bypass this and further verifications for the directory."); + + if (unprivileged_mode) + r = verify_xbootldr_udev(devid, flags, ret_uuid); + else + r = verify_xbootldr_blkid(devid, flags, ret_uuid); + if (r < 0) + return r; + } if (ret_path) *ret_path = TAKE_PTR(p); + if (ret_fd) + *ret_fd = TAKE_FD(fd); if (ret_devid) *ret_devid = devid; return 0; - -finish: - if (ret_path) - *ret_path = TAKE_PTR(p); - if (ret_uuid) - *ret_uuid = SD_ID128_NULL; - if (ret_devid) - *ret_devid = 0; - - return 0; } int find_xbootldr_and_warn_at_full( @@ -799,6 +782,7 @@ int find_xbootldr_and_warn_at_full( const char *path, int unprivileged_mode, char **ret_path, + int *ret_fd, sd_id128_t *ret_uuid, dev_t *ret_devid) { @@ -812,7 +796,7 @@ int find_xbootldr_and_warn_at_full( flags = verify_esp_flags_init(unprivileged_mode, "SYSTEMD_RELAX_XBOOTLDR_CHECKS"); if (path) - return verify_xbootldr(rfd, path, flags, ret_path, ret_uuid, ret_devid); + return verify_xbootldr(rfd, path, flags, ret_path, ret_fd, ret_uuid, ret_devid); path = getenv("SYSTEMD_XBOOTLDR_PATH"); if (path) { @@ -837,6 +821,8 @@ int find_xbootldr_and_warn_at_full( if (ret_path) *ret_path = TAKE_PTR(p); + if (ret_fd) + *ret_fd = TAKE_FD(fd); if (ret_uuid) *ret_uuid = SD_ID128_NULL; if (ret_devid) @@ -845,7 +831,14 @@ int find_xbootldr_and_warn_at_full( return 0; } - r = verify_xbootldr(rfd, "/boot", flags | VERIFY_ESP_SEARCHING, ret_path, ret_uuid, ret_devid); + r = verify_xbootldr( + rfd, + "/boot", + flags | VERIFY_ESP_SEARCHING, + ret_path, + ret_fd, + ret_uuid, + ret_devid); if (r < 0) { if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL, -ENOTDIR, -ENOTTY)) /* This one is not it */ return r; @@ -861,15 +854,13 @@ int find_xbootldr_and_warn_full( const char *path, int unprivileged_mode, char **ret_path, + int *ret_fd, sd_id128_t *ret_uuid, dev_t *ret_devid) { - _cleanup_close_ int rfd = -EBADF; - _cleanup_free_ char *p = NULL; - sd_id128_t uuid; - dev_t devid; int r; + _cleanup_close_ int rfd = -EBADF; if (empty_or_root(root)) rfd = XAT_FDROOT; else { @@ -878,11 +869,16 @@ int find_xbootldr_and_warn_full( return -errno; } + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *p = NULL; + sd_id128_t uuid; + dev_t devid; r = find_xbootldr_and_warn_at_full( rfd, path, unprivileged_mode, ret_path ? &p : NULL, + ret_fd ? &fd : NULL, ret_uuid ? &uuid : NULL, ret_devid ? &devid : NULL); if (r < 0) @@ -893,6 +889,8 @@ int find_xbootldr_and_warn_full( if (r < 0) return r; } + if (ret_fd) + *ret_fd = TAKE_FD(fd); if (ret_uuid) *ret_uuid = uuid; if (ret_devid) diff --git a/src/shared/find-esp.h b/src/shared/find-esp.h index 30b7c4a76117e..ad02bcd8f3f60 100644 --- a/src/shared/find-esp.h +++ b/src/shared/find-esp.h @@ -4,22 +4,22 @@ #include "shared-forward.h" -int find_esp_and_warn_at_full(int rfd, const char *path, int unprivileged_mode, char **ret_path, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid); -int find_esp_and_warn_full(const char *root, const char *path, int unprivileged_mode, char **ret_path, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid); +int find_esp_and_warn_at_full(int rfd, const char *path, int unprivileged_mode, char **ret_path, int *ret_fd, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid); +int find_esp_and_warn_full(const char *root, const char *path, int unprivileged_mode, char **ret_path, int *ret_fd, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid); -static inline int find_esp_and_warn_at(int rfd, const char *path, int unprivileged_mode, char **ret_path) { - return find_esp_and_warn_at_full(rfd, path, unprivileged_mode, ret_path, NULL, NULL, NULL, NULL, NULL); +static inline int find_esp_and_warn_at(int rfd, const char *path, int unprivileged_mode, char **ret_path, int *ret_fd) { + return find_esp_and_warn_at_full(rfd, path, unprivileged_mode, ret_path, ret_fd, NULL, NULL, NULL, NULL, NULL); } -static inline int find_esp_and_warn(const char *root, const char *path, int unprivileged_mode, char **ret_path) { - return find_esp_and_warn_full(root, path, unprivileged_mode, ret_path, NULL, NULL, NULL, NULL, NULL); +static inline int find_esp_and_warn(const char *root, const char *path, int unprivileged_mode, char **ret_path, int *ret_fd) { + return find_esp_and_warn_full(root, path, unprivileged_mode, ret_path, ret_fd, NULL, NULL, NULL, NULL, NULL); } -int find_xbootldr_and_warn_at_full(int rfd, const char *path, int unprivileged_mode, char **ret_path, sd_id128_t *ret_uuid, dev_t *ret_devid); -int find_xbootldr_and_warn_full(const char *root, const char *path, int unprivileged_mode, char **ret_path, sd_id128_t *ret_uuid, dev_t *ret_devid); +int find_xbootldr_and_warn_at_full(int rfd, const char *path, int unprivileged_mode, char **ret_path, int *ret_fd, sd_id128_t *ret_uuid, dev_t *ret_devid); +int find_xbootldr_and_warn_full(const char *root, const char *path, int unprivileged_mode, char **ret_path, int *ret_fd, sd_id128_t *ret_uuid, dev_t *ret_devid); -static inline int find_xbootldr_and_warn_at(int rfd, const char *path, int unprivileged_mode, char **ret_path) { - return find_xbootldr_and_warn_at_full(rfd, path, unprivileged_mode, ret_path, NULL, NULL); +static inline int find_xbootldr_and_warn_at(int rfd, const char *path, int unprivileged_mode, char **ret_path, int *ret_fd) { + return find_xbootldr_and_warn_at_full(rfd, path, unprivileged_mode, ret_path, ret_fd, NULL, NULL); } -static inline int find_xbootldr_and_warn(const char *root, const char *path, int unprivileged_mode, char **ret_path) { - return find_xbootldr_and_warn_full(root, path, unprivileged_mode, ret_path, NULL, NULL); +static inline int find_xbootldr_and_warn(const char *root, const char *path, int unprivileged_mode, char **ret_path, int *ret_fd) { + return find_xbootldr_and_warn_full(root, path, unprivileged_mode, ret_path, ret_fd, NULL, NULL); } diff --git a/src/sysupdate/sysupdate-resource.c b/src/sysupdate/sysupdate-resource.c index b819fcd5b8586..5865a39e2f1f9 100644 --- a/src/sysupdate/sysupdate-resource.c +++ b/src/sysupdate/sysupdate-resource.c @@ -839,9 +839,9 @@ int resource_resolve_path( } else { /* boot, esp, or xbootldr */ r = 0; if (IN_SET(rr->path_relative_to, PATH_RELATIVE_TO_BOOT, PATH_RELATIVE_TO_XBOOTLDR)) - r = find_xbootldr_and_warn(root, /* path= */ NULL, /* unprivileged_mode= */ -1, &relative_to); + r = find_xbootldr_and_warn(root, /* path= */ NULL, /* unprivileged_mode= */ -1, &relative_to, /* ret_fd= */ NULL); if (r == -ENOKEY || rr->path_relative_to == PATH_RELATIVE_TO_ESP) - r = find_esp_and_warn(root, /* path= */ NULL, /* unprivileged_mode= */ -1, &relative_to); + r = find_esp_and_warn(root, /* path= */ NULL, /* unprivileged_mode= */ -1, &relative_to, /* ret_fd= */ NULL); if (r < 0) return log_error_errno(r, "Failed to resolve $BOOT: %m"); log_debug("Resolved $BOOT to '%s'", relative_to); diff --git a/src/tpm2-setup/tpm2-swtpm.c b/src/tpm2-setup/tpm2-swtpm.c index 4ea6517157eec..71ad6b5e9d1c7 100644 --- a/src/tpm2-setup/tpm2-swtpm.c +++ b/src/tpm2-setup/tpm2-swtpm.c @@ -147,32 +147,44 @@ static int run(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Failed to find 'swtpm' binary: %m"); - _cleanup_free_ char *_esp = NULL; - const char *esp; - if (in_initrd()) + _cleanup_free_ char *state_dir = NULL; + _cleanup_close_ int state_fd = -EBADF; + if (in_initrd()) { /* The early ESP support uses only a single mount point, we do not need to search for it. */ - esp = "/sysefi"; - else { + r = chase("/loader/swtpm", + "/sysefi", + CHASE_PREFIX_ROOT|CHASE_TRIGGER_AUTOFS|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + &state_dir, + &state_fd); + if (r < 0) + return log_error_errno(r, "Failed to open swtpm state directory in ESP: %m"); + } else { + _cleanup_free_ char *esp_path = NULL; + _cleanup_close_ int esp_fd = -EBADF; r = find_esp_and_warn( /* root= */ NULL, /* path= */ NULL, /* unprivileged_mode= */ false, - &_esp); + &esp_path, + &esp_fd); if (r == -ENOKEY) /* This one find_esp_and_warn() doesn't actually log about. */ return log_error_errno(r, "No ESP discovered."); if (r < 0) return r; - esp = _esp; - } - _cleanup_free_ char *state_dir = NULL; - _cleanup_close_ int state_fd = -EBADF; - r = chase("/loader/swtpm", - esp, CHASE_PREFIX_ROOT|CHASE_TRIGGER_AUTOFS|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, - &state_dir, - &state_fd); - if (r < 0) - return log_error_errno(r, "Failed to open swtpm state directory in ESP: %m"); + _cleanup_free_ char *unprefixed_state_dir = NULL; + r = chaseat(esp_fd, + "/loader/swtpm", + CHASE_TRIGGER_AUTOFS|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + &unprefixed_state_dir, + &state_fd); + if (r < 0) + return log_error_errno(r, "Failed to open swtpm state directory in ESP: %m"); + + state_dir = path_join(esp_path, unprefixed_state_dir); + if (!state_dir) + return log_oom(); + } _cleanup_(unlink_and_freep) char *secret = NULL; r = prepare_secret(runtime_dir, &secret); From 8ad4adcb6f900c333da2cb6ac63e21f581d2ce4f Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 13 Apr 2026 08:18:04 +0000 Subject: [PATCH 1131/1296] json-stream: hide JsonStreamQueueItem as an implementation detail The json-stream API previously exposed JsonStreamQueueItem and several functions operating on it (json_stream_make_queue_item(), json_stream_enqueue_item(), json_stream_queue_item_free(), json_stream_queue_item_get_data()). These existed solely to support sd-varlink's "defer-and-modify" pattern for streaming replies, where a reply is held back so its "continues" field can be set before transmission. This is a varlink protocol concern that should not leak into the generic transport layer. Similarly, the fd pushing API (json_stream_push_fd(), json_stream_reset_pushed_fds()) and the pushed_fds state lived inside JsonStream, even though fd-to-message association is a protocol-level concern managed entirely by sd-varlink. Rework the API so that: - JsonStreamQueueItem and all its functions become static to json-stream.c. The only output API is now json_stream_enqueue_full() (accepting explicit fds) and the inline json_stream_enqueue() wrapper for the common no-fds case. - The pushed_fds state moves from JsonStream into sd_varlink, where sd_varlink_push_fd() and sd_varlink_reset_fds() manage it directly. - The deferred reply in sd-varlink changes from a JsonStreamQueueItem* to a plain sd_json_variant* plus a separate previous_fds/n_previous_fds pair, keeping the protocol-specific bookkeeping in sd-varlink where it belongs. - A new varlink_enqueue() helper wraps json_stream_enqueue_full() with the varlink connection's pushed fds, transferring fd ownership to the queue item on success. qmp-client.c is fixed to use the new API as well. --- src/libsystemd/sd-json/json-stream.c | 82 ++----------------- src/libsystemd/sd-json/json-stream.h | 32 +++----- src/libsystemd/sd-varlink/sd-varlink.c | 86 ++++++++++++++------ src/libsystemd/sd-varlink/varlink-internal.h | 7 +- src/shared/qmp-client.c | 43 ++-------- 5 files changed, 94 insertions(+), 156 deletions(-) diff --git a/src/libsystemd/sd-json/json-stream.c b/src/libsystemd/sd-json/json-stream.c index d8475ae873424..1900d1c7da4d3 100644 --- a/src/libsystemd/sd-json/json-stream.c +++ b/src/libsystemd/sd-json/json-stream.c @@ -50,12 +50,7 @@ static usec_t json_stream_now(const JsonStream *s) { return now(CLOCK_MONOTONIC); } -sd_json_variant** json_stream_queue_item_get_data(JsonStreamQueueItem *q) { - assert(q); - return &q->data; -} - -JsonStreamQueueItem* json_stream_queue_item_free(JsonStreamQueueItem *q) { +static JsonStreamQueueItem* json_stream_queue_item_free(JsonStreamQueueItem *q) { if (!q) return NULL; @@ -160,10 +155,6 @@ static void json_stream_clear(JsonStream *s) { s->output_fds = mfree(s->output_fds); s->n_output_fds = 0; - close_many(s->pushed_fds, s->n_pushed_fds); - s->pushed_fds = mfree(s->pushed_fds); - s->n_pushed_fds = 0; - LIST_CLEAR(queue, s->output_queue, json_stream_queue_item_free); s->output_queue_tail = NULL; s->n_output_queue = 0; @@ -883,30 +874,6 @@ int json_stream_flush(JsonStream *s) { return ret; } -int json_stream_push_fd(JsonStream *s, int fd) { - int i; - - assert(s); - assert(fd >= 0); - - if (s->n_pushed_fds >= SCM_MAX_FD) /* Kernel doesn't support more than 253 fds per message */ - return -ENOBUFS; - - if (!GREEDY_REALLOC(s->pushed_fds, s->n_pushed_fds + 1)) - return -ENOMEM; - - i = (int) s->n_pushed_fds; - s->pushed_fds[s->n_pushed_fds++] = fd; - return i; -} - -void json_stream_reset_pushed_fds(JsonStream *s) { - assert(s); - - close_many(s->pushed_fds, s->n_pushed_fds); - s->n_pushed_fds = 0; -} - int json_stream_peek_input_fd(const JsonStream *s, size_t i) { assert(s); @@ -1060,57 +1027,26 @@ static int json_stream_format_queue(JsonStream *s) { return 0; } -int json_stream_enqueue_item(JsonStream *s, JsonStreamQueueItem *q) { - assert(s); - assert(q); - - if (s->n_output_queue >= s->queue_max) - return -ENOBUFS; - - LIST_INSERT_AFTER(queue, s->output_queue, s->output_queue_tail, q); - s->output_queue_tail = q; - s->n_output_queue++; - return 0; -} - -int json_stream_enqueue(JsonStream *s, sd_json_variant *m) { - JsonStreamQueueItem *q; - +int json_stream_enqueue_full(JsonStream *s, sd_json_variant *m, const int fds[], size_t n_fds) { assert(s); assert(m); + assert(fds || n_fds == 0); - /* Fast path: no fds pending and no items currently queued — append directly into the + /* Fast path: no fds and no items currently queued — append directly into the * output buffer to avoid the queue allocation. */ - if (s->n_pushed_fds == 0 && !s->output_queue) + if (n_fds == 0 && !s->output_queue) return json_stream_format_json(s, m); if (s->n_output_queue >= s->queue_max) return -ENOBUFS; - q = json_stream_queue_item_new(m, s->pushed_fds, s->n_pushed_fds); + JsonStreamQueueItem *q = json_stream_queue_item_new(m, fds, n_fds); if (!q) return -ENOMEM; - s->n_pushed_fds = 0; /* fds belong to the queue entry now */ - - assert_se(json_stream_enqueue_item(s, q) >= 0); - return 0; -} - -int json_stream_make_queue_item(JsonStream *s, sd_json_variant *m, JsonStreamQueueItem **ret) { - JsonStreamQueueItem *q; - - assert(s); - assert(m); - assert(ret); - - q = json_stream_queue_item_new(m, s->pushed_fds, s->n_pushed_fds); - if (!q) - return -ENOMEM; - - s->n_pushed_fds = 0; /* fds belong to the queue entry now */ - - *ret = q; + LIST_INSERT_AFTER(queue, s->output_queue, s->output_queue_tail, q); + s->output_queue_tail = q; + s->n_output_queue++; return 0; } diff --git a/src/libsystemd/sd-json/json-stream.h b/src/libsystemd/sd-json/json-stream.h index 671b0f8985c9c..b502c98676e12 100644 --- a/src/libsystemd/sd-json/json-stream.h +++ b/src/libsystemd/sd-json/json-stream.h @@ -119,9 +119,6 @@ typedef struct JsonStream { JsonStreamQueueItem *output_queue_tail; size_t n_output_queue; - int *pushed_fds; - size_t n_pushed_fds; - JsonStreamFlags flags; } JsonStream; @@ -174,23 +171,18 @@ bool json_stream_should_disconnect(const JsonStream *s); int json_stream_set_allow_fd_passing_input(JsonStream *s, bool enabled, bool with_sockopt); int json_stream_set_allow_fd_passing_output(JsonStream *s, bool enabled); -/* Output: enqueue a JSON variant. Fast path concatenates into the output buffer; if - * pushed_fds are present or the queue is non-empty the message is queued instead, so that - * fd-to-message boundaries are preserved. */ -int json_stream_enqueue(JsonStream *s, sd_json_variant *m); - -/* Allocate a queue item carrying `m` and the currently pushed fds. The pushed fds are - * transferred to the new item; on success n_pushed_fds is reset to 0. The caller may - * later submit the item via json_stream_enqueue_item() or free it. */ -int json_stream_make_queue_item(JsonStream *s, sd_json_variant *m, JsonStreamQueueItem **ret); -int json_stream_enqueue_item(JsonStream *s, JsonStreamQueueItem *q); -JsonStreamQueueItem* json_stream_queue_item_free(JsonStreamQueueItem *q); -DEFINE_TRIVIAL_CLEANUP_FUNC(JsonStreamQueueItem*, json_stream_queue_item_free); -sd_json_variant** json_stream_queue_item_get_data(JsonStreamQueueItem *q); - -/* fd push/peek/take */ -int json_stream_push_fd(JsonStream *s, int fd); -void json_stream_reset_pushed_fds(JsonStream *s); +/* Output: enqueue a JSON variant together with an optional set of file descriptors. Fast + * path concatenates into the output buffer when fds is empty and the queue is empty; if fds + * are present or the queue is non-empty the message is queued instead, so that + * fd-to-message boundaries are preserved. The queue item copies the fd values; On success, + * ownership of the fd values transfers to the queue item (the caller must free its array + * without closing the fds). On failure, the fds remain untouched and the caller retains + * ownership. */ +int json_stream_enqueue_full(JsonStream *s, sd_json_variant *m, const int fds[], size_t n_fds); + +static inline int json_stream_enqueue(JsonStream *s, sd_json_variant *m) { + return json_stream_enqueue_full(s, m, NULL, 0); +} int json_stream_peek_input_fd(const JsonStream *s, size_t i); int json_stream_take_input_fd(JsonStream *s, size_t i); diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index 7130be69a4bf5..2a5f677ef37d0 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -590,7 +590,10 @@ static void varlink_clear_current(sd_varlink *v) { json_stream_close_input_fds(&v->stream); - v->previous = json_stream_queue_item_free(v->previous); + v->previous = sd_json_variant_unref(v->previous); + close_many(v->previous_fds, v->n_previous_fds); + v->previous_fds = mfree(v->previous_fds); + v->n_previous_fds = 0; if (v->sentinel != POINTER_MAX) v->sentinel = mfree(v->sentinel); else @@ -602,14 +605,17 @@ static void varlink_clear(sd_varlink *v) { /* Detach event sources first so the kernel no longer has epoll watches on the * stream's fds, then free the stream — json_stream_done() closes the input/output - * fds, the cached peer_pidfd, the received input fds, the queued output fds, and - * the pushed fds. */ + * fds, the cached peer_pidfd, the received input fds, and the queued output fds. */ sd_varlink_detach_event(v); varlink_clear_current(v); json_stream_done(&v->stream); + close_many(v->pushed_fds, v->n_pushed_fds); + v->pushed_fds = mfree(v->pushed_fds); + v->n_pushed_fds = 0; + pidref_done_sigterm_wait(&v->exec_pidref); } @@ -642,6 +648,20 @@ static int varlink_test_disconnect(sd_varlink *v) { return 1; } +static int varlink_enqueue(sd_varlink *v, sd_json_variant *m) { + int r; + + assert(v); + assert(m); + + r = json_stream_enqueue_full(&v->stream, m, v->pushed_fds, v->n_pushed_fds); + if (r >= 0) + v->n_pushed_fds = 0; /* fds belong to the queue entry now */ + /* We don't free v->pushed_fds so it can be reused for the next message. */ + + return r; +} + static int varlink_write(sd_varlink *v) { assert(v); @@ -1098,9 +1118,11 @@ static int varlink_dispatch_method(sd_varlink *v) { r = sd_varlink_error_errno(v, r); } else if (v->sentinel) { if (v->previous) { - r = json_stream_enqueue_item(&v->stream, v->previous); + r = json_stream_enqueue_full(&v->stream, v->previous, v->previous_fds, v->n_previous_fds); if (r >= 0) { - TAKE_PTR(v->previous); + v->previous = sd_json_variant_unref(v->previous); + v->previous_fds = mfree(v->previous_fds); + v->n_previous_fds = 0; varlink_set_state(v, VARLINK_PROCESSED_METHOD); } } else { @@ -1538,7 +1560,7 @@ _public_ int sd_varlink_send(sd_varlink *v, const char *method, sd_json_variant if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - r = json_stream_enqueue(&v->stream, m); + r = varlink_enqueue(v, m); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); @@ -1585,7 +1607,7 @@ _public_ int sd_varlink_invoke(sd_varlink *v, const char *method, sd_json_varian if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - r = json_stream_enqueue(&v->stream, m); + r = varlink_enqueue(v, m); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); @@ -1636,7 +1658,7 @@ _public_ int sd_varlink_observe(sd_varlink *v, const char *method, sd_json_varia if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - r = json_stream_enqueue(&v->stream, m); + r = varlink_enqueue(v, m); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); @@ -1683,7 +1705,7 @@ static int varlink_call_internal(sd_varlink *v, sd_json_variant *request) { * that we can assign a new reply shortly. */ varlink_clear_current(v); - r = json_stream_enqueue(&v->stream, request); + r = varlink_enqueue(v, request); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); @@ -1991,7 +2013,7 @@ _public_ int sd_varlink_collect_full( if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - r = json_stream_enqueue(&v->stream, m); + r = varlink_enqueue(v, m); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); @@ -2159,23 +2181,28 @@ _public_ int sd_varlink_reply(sd_varlink *v, sd_json_variant *parameters) { if (more && v->sentinel) { if (v->previous) { - r = sd_json_variant_set_field_boolean(json_stream_queue_item_get_data(v->previous), "continues", true); + r = sd_json_variant_set_field_boolean(&v->previous, "continues", true); if (r < 0) return r; - r = json_stream_enqueue_item(&v->stream, v->previous); + r = json_stream_enqueue_full(&v->stream, v->previous, v->previous_fds, v->n_previous_fds); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); + + v->previous = sd_json_variant_unref(v->previous); + v->previous_fds = mfree(v->previous_fds); + v->n_previous_fds = 0; } - r = json_stream_make_queue_item(&v->stream, m, &v->previous); - if (r < 0) - return r; + v->previous = sd_json_variant_ref(m); + v->previous_fds = TAKE_PTR(v->pushed_fds); + v->n_previous_fds = v->n_pushed_fds; + v->n_pushed_fds = 0; return 1; } - r = json_stream_enqueue(&v->stream, m); + r = varlink_enqueue(v, m); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); @@ -2255,7 +2282,7 @@ _public_ int sd_varlink_reply_and_upgrade(sd_varlink *v, sd_json_variant *parame if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - r = json_stream_enqueue(&v->stream, m); + r = varlink_enqueue(v, m); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); @@ -2289,7 +2316,8 @@ _public_ int sd_varlink_reset_fds(sd_varlink *v) { * rollback the fds. Note that this is implicitly called whenever an error reply is sent, see * below. */ - json_stream_reset_pushed_fds(&v->stream); + close_many(v->pushed_fds, v->n_pushed_fds); + v->n_pushed_fds = 0; return 0; } @@ -2308,18 +2336,20 @@ _public_ int sd_varlink_error(sd_varlink *v, const char *error_id, sd_json_varia return varlink_log_errno(v, SYNTHETIC_ERRNO(EBUSY), "Connection busy."); if (v->previous) { - r = sd_json_variant_set_field_boolean(json_stream_queue_item_get_data(v->previous), "continues", true); + r = sd_json_variant_set_field_boolean(&v->previous, "continues", true); if (r < 0) return r; /* If we have a previous reply still ready make sure we queue it before the error. We only * ever set "previous" if we're in a streaming method so we pass more=true unconditionally * here as we know we're still going to queue an error afterwards. */ - r = json_stream_enqueue_item(&v->stream, v->previous); + r = json_stream_enqueue_full(&v->stream, v->previous, v->previous_fds, v->n_previous_fds); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); - TAKE_PTR(v->previous); + v->previous = sd_json_variant_unref(v->previous); + v->previous_fds = mfree(v->previous_fds); + v->n_previous_fds = 0; } /* Reset the list of pushed file descriptors before sending an error reply. We do this here to @@ -2350,7 +2380,7 @@ _public_ int sd_varlink_error(sd_varlink *v, const char *error_id, sd_json_varia if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - r = json_stream_enqueue(&v->stream, m); + r = varlink_enqueue(v, m); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); @@ -2490,7 +2520,7 @@ _public_ int sd_varlink_notify(sd_varlink *v, sd_json_variant *parameters) { if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - r = json_stream_enqueue(&v->stream, m); + r = varlink_enqueue(v, m); if (r < 0) return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); @@ -2722,7 +2752,15 @@ _public_ int sd_varlink_push_fd(sd_varlink *v, int fd) { if (!json_stream_flags_set(&v->stream, JSON_STREAM_ALLOW_FD_PASSING_OUTPUT)) return -EPERM; - return json_stream_push_fd(&v->stream, fd); + if (v->n_pushed_fds >= SCM_MAX_FD) /* Kernel doesn't support more than 253 fds per message, refuse early hence */ + return -ENOBUFS; + + if (!GREEDY_REALLOC(v->pushed_fds, v->n_pushed_fds + 1)) + return -ENOMEM; + + int i = (int) v->n_pushed_fds; + v->pushed_fds[v->n_pushed_fds++] = fd; + return i; } _public_ int sd_varlink_push_dup_fd(sd_varlink *v, int fd) { diff --git a/src/libsystemd/sd-varlink/varlink-internal.h b/src/libsystemd/sd-varlink/varlink-internal.h index 966afd0b06cff..8087c2c432464 100644 --- a/src/libsystemd/sd-varlink/varlink-internal.h +++ b/src/libsystemd/sd-varlink/varlink-internal.h @@ -94,7 +94,12 @@ typedef struct sd_varlink { sd_varlink_reply_flags_t current_reply_flags; sd_varlink_symbol *current_method; - JsonStreamQueueItem *previous; + int *pushed_fds; + size_t n_pushed_fds; + + sd_json_variant *previous; + int *previous_fds; + size_t n_previous_fds; char *sentinel; /* Per-call protocol-upgrade marker: set when the *current* method call carries the diff --git a/src/shared/qmp-client.c b/src/shared/qmp-client.c index 6a92550e727bf..dec965a46032b 100644 --- a/src/shared/qmp-client.c +++ b/src/shared/qmp-client.c @@ -692,33 +692,6 @@ static QmpClientArgs* qmp_client_args_close_fds(QmpClientArgs *p) { DEFINE_TRIVIAL_CLEANUP_FUNC(QmpClientArgs*, qmp_client_args_close_fds); -/* Transfer fds to the stream. On partial failure narrow args to the unstaged tail so - * the caller's cleanup closes only the untransferred fds. */ -static int qmp_client_stage_fds(QmpClient *c, QmpClientArgs *args) { - int r; - - assert(c); - - if (!args || args->n_fds == 0) - return 0; - - assert(args->fds_consume); - - for (size_t i = 0; i < args->n_fds; i++) { - r = json_stream_push_fd(&c->stream, args->fds_consume[i]); - if (r < 0) { - /* Already-staged are owned by the stream; narrow args to the rest. */ - json_stream_reset_pushed_fds(&c->stream); - args->fds_consume = &args->fds_consume[i]; - args->n_fds -= i; - return r; - } - } - - args->n_fds = 0; - return 0; -} - int qmp_client_invoke( QmpClient *c, const char *command, @@ -728,8 +701,8 @@ int qmp_client_invoke( _cleanup_(sd_json_variant_unrefp) sd_json_variant *cmd = NULL; _cleanup_free_ QmpSlot *pending = NULL; - /* Closes any fds in args not yet handed to the stream on every early-return path; - * TAKE_PTR()'d on the success path below once stage_fds has consumed them. */ + /* Closes any fds in args on every early-return path; TAKE_PTR()'d on the success path + * below once json_stream_enqueue_full() has taken ownership of them. */ _cleanup_(qmp_client_args_close_fdsp) QmpClientArgs *fds_owner = args; uint64_t id; int r; @@ -761,16 +734,10 @@ int qmp_client_invoke( return r; assert(r > 0); - /* Stage AFTER ensure_running() drained internal enqueues so the next enqueue is ours. */ - r = qmp_client_stage_fds(c, args); - if (r < 0) { - set_remove(c->slots, pending); - return r; - } - - r = json_stream_enqueue(&c->stream, cmd); + r = json_stream_enqueue_full(&c->stream, cmd, + args ? args->fds_consume : NULL, + args ? args->n_fds : 0); if (r < 0) { - json_stream_reset_pushed_fds(&c->stream); set_remove(c->slots, pending); return r; } From 1b787f20cfb307d1848dc6a479643f6caadde24d Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Mon, 20 Apr 2026 17:10:57 +0200 Subject: [PATCH 1132/1296] test: convert sd-journal tests to the new test macros So we can, hopefully, debug issues like #40551 more easily. --- src/libsystemd/sd-journal/test-catalog.c | 77 +++++----- .../sd-journal/test-journal-append.c | 23 ++- src/libsystemd/sd-journal/test-journal-enum.c | 8 +- src/libsystemd/sd-journal/test-journal-file.c | 6 +- .../sd-journal/test-journal-flush.c | 47 +++---- src/libsystemd/sd-journal/test-journal-init.c | 24 ++-- .../sd-journal/test-journal-match.c | 58 ++++---- src/libsystemd/sd-journal/test-journal-send.c | 82 +++++------ .../sd-journal/test-journal-stream.c | 87 ++++++------ .../sd-journal/test-journal-verify.c | 48 +++---- src/libsystemd/sd-journal/test-journal.c | 133 +++++++++--------- src/libsystemd/sd-journal/test-mmap-cache.c | 36 ++--- 12 files changed, 297 insertions(+), 332 deletions(-) diff --git a/src/libsystemd/sd-journal/test-catalog.c b/src/libsystemd/sd-journal/test-catalog.c index 51e113b3fc61b..09f05a6b90528 100644 --- a/src/libsystemd/sd-journal/test-catalog.c +++ b/src/libsystemd/sd-journal/test-catalog.c @@ -27,11 +27,10 @@ static OrderedHashmap* test_import(const char* contents, ssize_t size, int code) if (size < 0) size = strlen(contents); - fd = mkostemp_safe(name); - assert_se(fd >= 0); - assert_se(write(fd, contents, size) == size); + ASSERT_OK(fd = mkostemp_safe(name)); + ASSERT_EQ(write(fd, contents, size), size); - assert_se(catalog_import_file(&h, fd, name) == code); + ASSERT_EQ(catalog_import_file(&h, fd, name), code); return h; } @@ -40,7 +39,7 @@ static void test_catalog_import_invalid(void) { _cleanup_ordered_hashmap_free_ OrderedHashmap *h = NULL; h = test_import("xxx", -1, -EINVAL); - assert_se(ordered_hashmap_isempty(h)); + ASSERT_TRUE(ordered_hashmap_isempty(h)); } static void test_catalog_import_badid(void) { @@ -68,12 +67,12 @@ static void test_catalog_import_one(void) { "payload\n"; h = test_import(input, -1, 0); - assert_se(ordered_hashmap_size(h) == 1); + ASSERT_EQ(ordered_hashmap_size(h), 1u); ORDERED_HASHMAP_FOREACH(payload, h) { printf("expect: %s\n", expect); printf("actual: %s\n", payload); - assert_se(streq(expect, payload)); + ASSERT_STREQ(expect, payload); } } @@ -103,10 +102,10 @@ static void test_catalog_import_merge(void) { "override payload\n"; h = test_import(input, -1, 0); - assert_se(ordered_hashmap_size(h) == 1); + ASSERT_EQ(ordered_hashmap_size(h), 1u); ORDERED_HASHMAP_FOREACH(payload, h) - assert_se(streq(combined, payload)); + ASSERT_STREQ(combined, payload); } static void test_catalog_import_merge_no_body(void) { @@ -134,62 +133,56 @@ static void test_catalog_import_merge_no_body(void) { "payload\n"; h = test_import(input, -1, 0); - assert_se(ordered_hashmap_size(h) == 1); + ASSERT_EQ(ordered_hashmap_size(h), 1u); ORDERED_HASHMAP_FOREACH(payload, h) - assert_se(streq(combined, payload)); + ASSERT_STREQ(combined, payload); } static void test_catalog_update(const char *database) { - int r; - /* Test what happens if there are no files. */ - r = catalog_update(database, NULL, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(catalog_update(database, NULL, NULL)); /* Test what happens if there are no files in the directory. */ - r = catalog_update(database, NULL, no_catalog_dirs); - assert_se(r == 0); + ASSERT_OK_ZERO(catalog_update(database, NULL, no_catalog_dirs)); /* Make sure that we at least have some files loaded or the * catalog_list below will fail. */ - r = catalog_update(database, NULL, (const char * const *) catalog_dirs); - assert_se(r == 0); + ASSERT_OK_ZERO(catalog_update(database, NULL, (const char * const *) catalog_dirs)); } static void test_catalog_file_lang(void) { _cleanup_free_ char *lang = NULL, *lang2 = NULL, *lang3 = NULL, *lang4 = NULL; - assert_se(catalog_file_lang("systemd.de_DE.catalog", &lang) == 1); - assert_se(streq(lang, "de_DE")); + ASSERT_EQ(catalog_file_lang("systemd.de_DE.catalog", &lang), 1); + ASSERT_STREQ(lang, "de_DE"); - assert_se(catalog_file_lang("systemd..catalog", &lang2) == 0); - assert_se(lang2 == NULL); + ASSERT_OK_ZERO(catalog_file_lang("systemd..catalog", &lang2)); + ASSERT_NULL(lang2); - assert_se(catalog_file_lang("systemd.fr.catalog", &lang2) == 1); - assert_se(streq(lang2, "fr")); + ASSERT_EQ(catalog_file_lang("systemd.fr.catalog", &lang2), 1); + ASSERT_STREQ(lang2, "fr"); - assert_se(catalog_file_lang("systemd.fr.catalog.gz", &lang3) == 0); - assert_se(lang3 == NULL); + ASSERT_OK_ZERO(catalog_file_lang("systemd.fr.catalog.gz", &lang3)); + ASSERT_NULL(lang3); - assert_se(catalog_file_lang("systemd.01234567890123456789012345678901.catalog", &lang3) == 0); - assert_se(lang3 == NULL); + ASSERT_OK_ZERO(catalog_file_lang("systemd.01234567890123456789012345678901.catalog", &lang3)); + ASSERT_NULL(lang3); - assert_se(catalog_file_lang("systemd.0123456789012345678901234567890.catalog", &lang3) == 1); - assert_se(streq(lang3, "0123456789012345678901234567890")); + ASSERT_EQ(catalog_file_lang("systemd.0123456789012345678901234567890.catalog", &lang3), 1); + ASSERT_STREQ(lang3, "0123456789012345678901234567890"); - assert_se(catalog_file_lang("/x/y/systemd.catalog", &lang4) == 0); - assert_se(lang4 == NULL); + ASSERT_OK_ZERO(catalog_file_lang("/x/y/systemd.catalog", &lang4)); + ASSERT_NULL(lang4); - assert_se(catalog_file_lang("/x/y/systemd.ru_RU.catalog", &lang4) == 1); - assert_se(streq(lang4, "ru_RU")); + ASSERT_EQ(catalog_file_lang("/x/y/systemd.ru_RU.catalog", &lang4), 1); + ASSERT_STREQ(lang4, "ru_RU"); } int main(int argc, char *argv[]) { _cleanup_(unlink_tempfilep) char database[] = "/tmp/test-catalog.XXXXXX"; _cleanup_close_ int fd = -EBADF; _cleanup_free_ char *text = NULL; - int r; setlocale(LC_ALL, "de_DE.UTF-8"); @@ -199,7 +192,7 @@ int main(int argc, char *argv[]) { * If it is not, e.g. installed by systemd-tests package, then use installed catalogs. */ catalog_dirs = STRV_MAKE(get_catalog_dir()); - assert_se(access(catalog_dirs[0], F_OK) >= 0); + ASSERT_OK_ERRNO(access(catalog_dirs[0], F_OK)); log_notice("Using catalog directory '%s'", catalog_dirs[0]); test_catalog_file_lang(); @@ -210,17 +203,15 @@ int main(int argc, char *argv[]) { test_catalog_import_merge(); test_catalog_import_merge_no_body(); - assert_se((fd = mkostemp_safe(database)) >= 0); + ASSERT_OK(fd = mkostemp_safe(database)); test_catalog_update(database); - r = catalog_list(NULL, database, true); - assert_se(r >= 0); + ASSERT_OK(catalog_list(NULL, database, true)); - r = catalog_list(NULL, database, false); - assert_se(r >= 0); + ASSERT_OK(catalog_list(NULL, database, false)); - assert_se(catalog_get(database, SD_MESSAGE_COREDUMP, &text) >= 0); + ASSERT_OK(catalog_get(database, SD_MESSAGE_COREDUMP, &text)); printf(">>>%s<<<\n", text); return 0; diff --git a/src/libsystemd/sd-journal/test-journal-append.c b/src/libsystemd/sd-journal/test-journal-append.c index b155a7fd7ebef..605839169c615 100644 --- a/src/libsystemd/sd-journal/test-journal-append.c +++ b/src/libsystemd/sd-journal/test-journal-append.c @@ -45,15 +45,14 @@ static int journal_corrupt_and_append(uint64_t start_offset, uint64_t step) { uint64_t start, end; int r; - mmap_cache = mmap_cache_new(); - assert_se(mmap_cache); + ASSERT_NOT_NULL(mmap_cache = mmap_cache_new()); /* journal_file_open() requires a valid machine id */ if (sd_id128_get_machine(NULL) < 0) return log_tests_skipped("No valid machine ID found"); - assert_se(mkdtemp_malloc("/tmp/journal-append-XXXXXX", &tempdir) >= 0); - assert_se(chdir(tempdir) >= 0); + ASSERT_OK(mkdtemp_malloc("/tmp/journal-append-XXXXXX", &tempdir)); + ASSERT_OK_ERRNO(chdir(tempdir)); (void) chattr_path(tempdir, FS_NOCOW_FL, FS_NOCOW_FL); log_debug("Opening journal %s/system.journal", tempdir); @@ -72,13 +71,13 @@ static int journal_corrupt_and_append(uint64_t start_offset, uint64_t step) { if (r < 0) return log_error_errno(r, "Failed to open the journal: %m"); - assert_se(mj); + ASSERT_NOT_NULL(mj); /* Add a couple of initial messages */ for (int i = 0; i < 10; i++) { _cleanup_free_ char *message = NULL; - assert_se(asprintf(&message, "MESSAGE=Initial message %d", i) >= 0); + ASSERT_OK_ERRNO(asprintf(&message, "MESSAGE=Initial message %d", i)); r = journal_append_message(mj, message); if (r < 0) return log_error_errno(r, "Failed to write to the journal: %m"); @@ -101,11 +100,9 @@ static int journal_corrupt_and_append(uint64_t start_offset, uint64_t step) { uint8_t b; /* Flip a bit in the journal file */ - r = pread(mj->fd, &b, 1, offset); - assert_se(r == 1); + ASSERT_EQ(pread(mj->fd, &b, 1, offset), 1); b |= 0x1; - r = pwrite(mj->fd, &b, 1, offset); - assert_se(r == 1); + ASSERT_EQ(pwrite(mj->fd, &b, 1, offset), 1); /* Close and reopen the journal to flush all caches and remap * the corrupted journal */ @@ -130,7 +127,7 @@ static int journal_corrupt_and_append(uint64_t start_offset, uint64_t step) { } /* Try to write something to the (possibly corrupted) journal */ - assert_se(asprintf(&message, "MESSAGE=Hello world %" PRIu64, offset) >= 0); + ASSERT_OK_ERRNO(asprintf(&message, "MESSAGE=Hello world %" PRIu64, offset)); r = journal_append_message(mj, message); if (r < 0) { /* We care only about crashes or sanitizer errors, @@ -173,8 +170,8 @@ int main(int argc, char *argv[]) { {} }; - assert_se(argc >= 0); - assert_se(argv); + ASSERT_GE(argc, 0); + ASSERT_NOT_NULL(argv); while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) switch (c) { diff --git a/src/libsystemd/sd-journal/test-journal-enum.c b/src/libsystemd/sd-journal/test-journal-enum.c index cc17ea1891866..1b01f8f615b01 100644 --- a/src/libsystemd/sd-journal/test-journal-enum.c +++ b/src/libsystemd/sd-journal/test-journal-enum.c @@ -12,16 +12,16 @@ int main(int argc, char *argv[]) { test_setup_logging(LOG_DEBUG); - assert_se(sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE) >= 0); + ASSERT_OK(sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE)); - assert_se(sd_journal_add_match(j, "_TRANSPORT=syslog", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "_UID=0", SIZE_MAX) >= 0); + ASSERT_OK(sd_journal_add_match(j, "_TRANSPORT=syslog", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "_UID=0", SIZE_MAX)); SD_JOURNAL_FOREACH_BACKWARDS(j) { const void *d; size_t l; - assert_se(sd_journal_get_data(j, "MESSAGE", &d, &l) >= 0); + ASSERT_OK(sd_journal_get_data(j, "MESSAGE", &d, &l)); printf("%.*s\n", (int) l, (char*) d); diff --git a/src/libsystemd/sd-journal/test-journal-file.c b/src/libsystemd/sd-journal/test-journal-file.c index 52b1328fb08e9..79d1caf15d3a9 100644 --- a/src/libsystemd/sd-journal/test-journal-file.c +++ b/src/libsystemd/sd-journal/test-journal-file.c @@ -16,11 +16,11 @@ static void test_journal_file_parse_uid_from_filename_simple( log_info("testing %s", path); r = journal_file_parse_uid_from_filename(path, &uid); - assert_se(r == expected_error); + ASSERT_EQ(r, expected_error); if (r < 0) - assert_se(uid == UID_INVALID); + ASSERT_EQ(uid, UID_INVALID); else - assert_se(uid == expected_uid); + ASSERT_EQ(uid, expected_uid); } TEST(journal_file_parse_uid_from_filename) { diff --git a/src/libsystemd/sd-journal/test-journal-flush.c b/src/libsystemd/sd-journal/test-journal-flush.c index 0301dd8f69ded..04c92416a5cdf 100644 --- a/src/libsystemd/sd-journal/test-journal-flush.c +++ b/src/libsystemd/sd-journal/test-journal-flush.c @@ -108,14 +108,13 @@ static void test_journal_flush_one(int argc, char *argv[]) { unsigned n, limit; int r; - assert_se(m = mmap_cache_new()); - assert_se(mkdtemp_malloc("/var/tmp/test-journal-flush.XXXXXX", &dn) >= 0); + ASSERT_NOT_NULL(m = mmap_cache_new()); + ASSERT_OK(mkdtemp_malloc("/var/tmp/test-journal-flush.XXXXXX", &dn)); (void) chattr_path(dn, FS_NOCOW_FL, FS_NOCOW_FL); - assert_se(fn = path_join(dn, "test.journal")); + ASSERT_NOT_NULL(fn = path_join(dn, "test.journal")); - r = journal_file_open(-EBADF, fn, O_CREAT|O_RDWR, 0, 0644, 0, NULL, m, NULL, &new_journal); - assert_se(r >= 0); + ASSERT_OK(journal_file_open(-EBADF, fn, O_CREAT|O_RDWR, 0, 0644, 0, NULL, m, NULL, &new_journal)); if (argc > 1) r = sd_journal_open_files(&j, (const char **) strv_skip(argv, 1), SD_JOURNAL_ASSUME_IMMUTABLE); @@ -124,7 +123,7 @@ static void test_journal_flush_one(int argc, char *argv[]) { if (r < 0) r = sd_journal_open(&j, SD_JOURNAL_ASSUME_IMMUTABLE); } - assert_se(r == 0); + ASSERT_OK_ZERO(r); sd_journal_set_data_threshold(j, 0); @@ -135,21 +134,21 @@ static void test_journal_flush_one(int argc, char *argv[]) { JournalFile *f; f = j->current_file; - assert_se(f && f->current_offset > 0); + ASSERT_TRUE(f && f->current_offset > 0); r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); if (r < 0) log_error_errno(r, "journal_file_move_to_object failed: %m"); - assert_se(r >= 0); + ASSERT_OK(r); r = journal_file_copy_entry(f, new_journal, o, f->current_offset, NULL, NULL); if (r < 0) log_warning_errno(r, "journal_file_copy_entry failed: %m"); - assert_se(r >= 0 || - IN_SET(r, -EBADMSG, /* corrupted file */ - -EPROTONOSUPPORT, /* unsupported compression */ - -EIO, /* file rotated */ - -EREMCHG)); /* clock rollback */ + ASSERT_TRUE(r >= 0 || + IN_SET(r, -EBADMSG, /* corrupted file */ + -EPROTONOSUPPORT, /* unsupported compression */ + -EIO, /* file rotated */ + -EREMCHG)); /* clock rollback */ if (++n >= limit) break; @@ -160,43 +159,43 @@ static void test_journal_flush_one(int argc, char *argv[]) { /* Open the new journal before archiving and offlining the file. */ sd_journal_close(j); - assert_se(sd_journal_open_directory(&j, dn, SD_JOURNAL_ASSUME_IMMUTABLE) >= 0); + ASSERT_OK(sd_journal_open_directory(&j, dn, SD_JOURNAL_ASSUME_IMMUTABLE)); /* Read the online journal. */ - assert_se(sd_journal_seek_tail(j) >= 0); - assert_se(sd_journal_step_one(j, 0) > 0); + ASSERT_OK(sd_journal_seek_tail(j)); + ASSERT_OK_POSITIVE(sd_journal_step_one(j, 0)); printf("current_journal: %s (%i)\n", j->current_file->path, j->current_file->fd); - assert_se(show_journal_entry(stdout, j, OUTPUT_EXPORT, 0, 0, NULL, NULL, NULL, &(dual_timestamp) {}, &(sd_id128_t) {}) >= 0); + ASSERT_OK(show_journal_entry(stdout, j, OUTPUT_EXPORT, 0, 0, NULL, NULL, NULL, &(dual_timestamp) {}, &(sd_id128_t) {})); uint64_t p; - assert_se(journal_file_tail_end_by_mmap(j->current_file, &p) >= 0); + ASSERT_OK(journal_file_tail_end_by_mmap(j->current_file, &p)); for (uint64_t q = ALIGN64(p + 1); q < (uint64_t) j->current_file->last_stat.st_size; q = ALIGN64(q + 1)) { Object *o; r = journal_file_move_to_object(j->current_file, OBJECT_UNUSED, q, &o); - assert_se(IN_SET(r, -EBADMSG, -EADDRNOTAVAIL)); + ASSERT_TRUE(IN_SET(r, -EBADMSG, -EADDRNOTAVAIL)); } /* Archive and offline file. */ - assert_se(journal_file_archive(new_journal, NULL) >= 0); - assert_se(journal_file_set_offline(new_journal, /* wait= */ true) >= 0); + ASSERT_OK(journal_file_archive(new_journal, NULL)); + ASSERT_OK(journal_file_set_offline(new_journal, /* wait= */ true)); /* Read the archived and offline journal. */ for (uint64_t q = ALIGN64(p + 1); q < (uint64_t) j->current_file->last_stat.st_size; q = ALIGN64(q + 1)) { Object *o; r = journal_file_move_to_object(j->current_file, OBJECT_UNUSED, q, &o); - assert_se(IN_SET(r, -EBADMSG, -EADDRNOTAVAIL, -EIDRM)); + ASSERT_TRUE(IN_SET(r, -EBADMSG, -EADDRNOTAVAIL, -EIDRM)); } } TEST(journal_flush) { - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1)); test_journal_flush_one(saved_argc, saved_argv); } TEST(journal_flush_compact) { - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1)); test_journal_flush_one(saved_argc, saved_argv); } diff --git a/src/libsystemd/sd-journal/test-journal-init.c b/src/libsystemd/sd-journal/test-journal-init.c index 11f510642076f..47ab2a72ad38f 100644 --- a/src/libsystemd/sd-journal/test-journal-init.c +++ b/src/libsystemd/sd-journal/test-journal-init.c @@ -28,41 +28,39 @@ int main(int argc, char *argv[]) { log_info("Running %d loops", I); - assert_se(mkdtemp(t)); + ASSERT_NOT_NULL(mkdtemp(t)); (void) chattr_path(t, FS_NOCOW_FL, FS_NOCOW_FL); for (i = 0; i < I; i++) { - r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE); - assert_se(r == 0); + ASSERT_OK_ZERO(sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE)); sd_journal_close(j); - r = sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE); - assert_se(r == 0); + ASSERT_OK_ZERO(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); - assert_se(sd_journal_seek_head(j) == 0); - assert_se(j->current_location.type == LOCATION_HEAD); + ASSERT_OK_ZERO(sd_journal_seek_head(j)); + ASSERT_EQ(j->current_location.type, (LocationType) LOCATION_HEAD); r = pidref_safe_fork("(journal-fork-test)", FORK_WAIT|FORK_LOG, NULL); if (r == 0) { - assert_se(j); + ASSERT_NOT_NULL(j); ASSERT_RETURN_EXPECTED_SE(sd_journal_get_realtime_usec(j, NULL) == -ECHILD); ASSERT_RETURN_EXPECTED_SE(sd_journal_seek_tail(j) == -ECHILD); - assert_se(j->current_location.type == LOCATION_HEAD); + ASSERT_EQ(j->current_location.type, (LocationType) LOCATION_HEAD); sd_journal_close(j); _exit(EXIT_SUCCESS); } - assert_se(r >= 0); + ASSERT_OK(r); sd_journal_close(j); j = NULL; - ASSERT_RETURN_EXPECTED(assert_se(sd_journal_open_directory(&j, t, SD_JOURNAL_LOCAL_ONLY) == -EINVAL)); - assert_se(j == NULL); + ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_journal_open_directory(&j, t, SD_JOURNAL_LOCAL_ONLY), EINVAL)); + ASSERT_NULL(j); } - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + ASSERT_OK(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL)); return 0; } diff --git a/src/libsystemd/sd-journal/test-journal-match.c b/src/libsystemd/sd-journal/test-journal-match.c index 2b3886445de86..0c6b2946ef752 100644 --- a/src/libsystemd/sd-journal/test-journal-match.c +++ b/src/libsystemd/sd-journal/test-journal-match.c @@ -14,46 +14,46 @@ int main(int argc, char *argv[]) { test_setup_logging(LOG_DEBUG); - assert_se(sd_journal_open(&j, SD_JOURNAL_ASSUME_IMMUTABLE) >= 0); + ASSERT_OK(sd_journal_open(&j, SD_JOURNAL_ASSUME_IMMUTABLE)); - assert_se(sd_journal_add_match(j, "foobar", SIZE_MAX) < 0); - assert_se(sd_journal_add_match(j, "foobar=waldo", SIZE_MAX) < 0); - assert_se(sd_journal_add_match(j, "", SIZE_MAX) < 0); - assert_se(sd_journal_add_match(j, "=", SIZE_MAX) < 0); - assert_se(sd_journal_add_match(j, "=xxxxx", SIZE_MAX) < 0); - assert_se(sd_journal_add_match(j, (uint8_t[4]){'A', '=', '\1', '\2'}, 4) >= 0); - assert_se(sd_journal_add_match(j, (uint8_t[5]){'B', '=', 'C', '\0', 'D'}, 5) >= 0); - assert_se(sd_journal_add_match(j, "HALLO=WALDO", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "QUUX=mmmm", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "QUUX=xxxxx", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "HALLO=", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "QUUX=xxxxx", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "QUUX=yyyyy", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "PIFF=paff", SIZE_MAX) >= 0); + ASSERT_FAIL(sd_journal_add_match(j, "foobar", SIZE_MAX)); + ASSERT_FAIL(sd_journal_add_match(j, "foobar=waldo", SIZE_MAX)); + ASSERT_FAIL(sd_journal_add_match(j, "", SIZE_MAX)); + ASSERT_FAIL(sd_journal_add_match(j, "=", SIZE_MAX)); + ASSERT_FAIL(sd_journal_add_match(j, "=xxxxx", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, (uint8_t[4]){'A', '=', '\1', '\2'}, 4)); + ASSERT_OK(sd_journal_add_match(j, (uint8_t[5]){'B', '=', 'C', '\0', 'D'}, 5)); + ASSERT_OK(sd_journal_add_match(j, "HALLO=WALDO", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "QUUX=mmmm", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "QUUX=xxxxx", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "HALLO=", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "QUUX=xxxxx", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "QUUX=yyyyy", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "PIFF=paff", SIZE_MAX)); - assert_se(sd_journal_add_disjunction(j) >= 0); + ASSERT_OK(sd_journal_add_disjunction(j)); - assert_se(sd_journal_add_match(j, "ONE=one", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "ONE=two", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "TWO=two", SIZE_MAX) >= 0); + ASSERT_OK(sd_journal_add_match(j, "ONE=one", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "ONE=two", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "TWO=two", SIZE_MAX)); - assert_se(sd_journal_add_conjunction(j) >= 0); + ASSERT_OK(sd_journal_add_conjunction(j)); - assert_se(sd_journal_add_match(j, "L4_1=yes", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "L4_1=ok", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "L4_2=yes", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "L4_2=ok", SIZE_MAX) >= 0); + ASSERT_OK(sd_journal_add_match(j, "L4_1=yes", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "L4_1=ok", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "L4_2=yes", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "L4_2=ok", SIZE_MAX)); - assert_se(sd_journal_add_disjunction(j) >= 0); + ASSERT_OK(sd_journal_add_disjunction(j)); - assert_se(sd_journal_add_match(j, "L3=yes", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "L3=ok", SIZE_MAX) >= 0); + ASSERT_OK(sd_journal_add_match(j, "L3=yes", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "L3=ok", SIZE_MAX)); - assert_se(t = journal_make_match_string(j)); + ASSERT_NOT_NULL(t = journal_make_match_string(j)); printf("resulting match expression is: %s\n", t); - assert_se(streq(t, "(((L3=ok OR L3=yes) OR ((L4_2=ok OR L4_2=yes) AND (L4_1=ok OR L4_1=yes))) AND ((TWO=two AND (ONE=two OR ONE=one)) OR (PIFF=paff AND (QUUX=yyyyy OR QUUX=xxxxx OR QUUX=mmmm) AND (HALLO= OR HALLO=WALDO) AND B=C\\000D AND A=\\001\\002)))")); + ASSERT_STREQ(t, "(((L3=ok OR L3=yes) OR ((L4_2=ok OR L4_2=yes) AND (L4_1=ok OR L4_1=yes))) AND ((TWO=two AND (ONE=two OR ONE=one)) OR (PIFF=paff AND (QUUX=yyyyy OR QUUX=xxxxx OR QUUX=mmmm) AND (HALLO= OR HALLO=WALDO) AND B=C\\000D AND A=\\001\\002)))"); return 0; } diff --git a/src/libsystemd/sd-journal/test-journal-send.c b/src/libsystemd/sd-journal/test-journal-send.c index e4959521f6ac5..7f3a5024dc2bf 100644 --- a/src/libsystemd/sd-journal/test-journal-send.c +++ b/src/libsystemd/sd-journal/test-journal-send.c @@ -10,18 +10,18 @@ #include "tests.h" TEST(journal_print) { - assert_se(sd_journal_print(LOG_INFO, "XXX") == 0); - assert_se(sd_journal_print(LOG_INFO, "%s", "YYY") == 0); - assert_se(sd_journal_print(LOG_INFO, "X%4094sY", "ZZZ") == 0); - assert_se(sd_journal_print(LOG_INFO, "X%*sY", (int) LONG_LINE_MAX - 8 - 3, "ZZZ") == 0); - assert_se(sd_journal_print(LOG_INFO, "X%*sY", (int) LONG_LINE_MAX - 8 - 2, "ZZZ") == -ENOBUFS); + ASSERT_OK_ZERO(sd_journal_print(LOG_INFO, "XXX")); + ASSERT_OK_ZERO(sd_journal_print(LOG_INFO, "%s", "YYY")); + ASSERT_OK_ZERO(sd_journal_print(LOG_INFO, "X%4094sY", "ZZZ")); + ASSERT_OK_ZERO(sd_journal_print(LOG_INFO, "X%*sY", (int) LONG_LINE_MAX - 8 - 3, "ZZZ")); + ASSERT_ERROR(sd_journal_print(LOG_INFO, "X%*sY", (int) LONG_LINE_MAX - 8 - 2, "ZZZ"), ENOBUFS); } TEST(journal_send) { _cleanup_free_ char *huge = NULL; #define HUGE_SIZE (4096*1024) - assert_se(huge = malloc(HUGE_SIZE)); + ASSERT_NOT_NULL(huge = malloc(HUGE_SIZE)); /* utf-8 and non-utf-8, message-less and message-ful iovecs */ struct iovec graph1[] = { @@ -37,59 +37,59 @@ TEST(journal_send) { {(char*) "MESSAGE=graph\n", STRLEN("MESSAGE=graph\n")} }; - assert_se(sd_journal_print(LOG_INFO, "piepapo") == 0); + ASSERT_OK_ZERO(sd_journal_print(LOG_INFO, "piepapo")); - assert_se(sd_journal_send("MESSAGE=foobar", - "VALUE=%i", 7, - NULL) == 0); + ASSERT_OK_ZERO(sd_journal_send("MESSAGE=foobar", + "VALUE=%i", 7, + NULL)); errno = ENOENT; - assert_se(sd_journal_perror("Foobar") == 0); + ASSERT_OK_ZERO(sd_journal_perror("Foobar")); - assert_se(sd_journal_perror("") == 0); + ASSERT_OK_ZERO(sd_journal_perror("")); memcpy(huge, "HUGE=", STRLEN("HUGE=")); memset(&huge[STRLEN("HUGE=")], 'x', HUGE_SIZE - STRLEN("HUGE=") - 1); huge[HUGE_SIZE - 1] = '\0'; - assert_se(sd_journal_send("MESSAGE=Huge field attached", - huge, - NULL) == 0); + ASSERT_OK_ZERO(sd_journal_send("MESSAGE=Huge field attached", + huge, + NULL)); - assert_se(sd_journal_send("MESSAGE=uiui", - "VALUE=A", - "VALUE=B", - "VALUE=C", - "SINGLETON=1", - "OTHERVALUE=X", - "OTHERVALUE=Y", - "WITH_BINARY=this is a binary value \a", - NULL) == 0); + ASSERT_OK_ZERO(sd_journal_send("MESSAGE=uiui", + "VALUE=A", + "VALUE=B", + "VALUE=C", + "SINGLETON=1", + "OTHERVALUE=X", + "OTHERVALUE=Y", + "WITH_BINARY=this is a binary value \a", + NULL)); syslog(LOG_NOTICE, "Hello World!"); - assert_se(sd_journal_print(LOG_NOTICE, "Hello World") == 0); + ASSERT_OK_ZERO(sd_journal_print(LOG_NOTICE, "Hello World")); - assert_se(sd_journal_send("MESSAGE=Hello World!", - "MESSAGE_ID=52fb62f99e2c49d89cfbf9d6de5e3555", - "PRIORITY=5", - "HOME=%s", getenv("HOME"), - "TERM=%s", getenv("TERM"), - "PAGE_SIZE=%li", sysconf(_SC_PAGESIZE), - "N_CPUS=%li", sysconf(_SC_NPROCESSORS_ONLN), - NULL) == 0); + ASSERT_OK_ZERO(sd_journal_send("MESSAGE=Hello World!", + "MESSAGE_ID=52fb62f99e2c49d89cfbf9d6de5e3555", + "PRIORITY=5", + "HOME=%s", getenv("HOME"), + "TERM=%s", getenv("TERM"), + "PAGE_SIZE=%li", sysconf(_SC_PAGESIZE), + "N_CPUS=%li", sysconf(_SC_NPROCESSORS_ONLN), + NULL)); - assert_se(sd_journal_sendv(graph1, 1) == 0); - assert_se(sd_journal_sendv(graph2, 1) == 0); - assert_se(sd_journal_sendv(message1, 1) == 0); - assert_se(sd_journal_sendv(message2, 1) == 0); + ASSERT_OK_ZERO(sd_journal_sendv(graph1, 1)); + ASSERT_OK_ZERO(sd_journal_sendv(graph2, 1)); + ASSERT_OK_ZERO(sd_journal_sendv(message1, 1)); + ASSERT_OK_ZERO(sd_journal_sendv(message2, 1)); /* test without location fields */ #undef sd_journal_sendv - assert_se(sd_journal_sendv(graph1, 1) == 0); - assert_se(sd_journal_sendv(graph2, 1) == 0); - assert_se(sd_journal_sendv(message1, 1) == 0); - assert_se(sd_journal_sendv(message2, 1) == 0); + ASSERT_OK_ZERO(sd_journal_sendv(graph1, 1)); + ASSERT_OK_ZERO(sd_journal_sendv(graph2, 1)); + ASSERT_OK_ZERO(sd_journal_sendv(message1, 1)); + ASSERT_OK_ZERO(sd_journal_sendv(message2, 1)); /* The above syslog() opens a fd which is stored in libc, and the valgrind reports the fd is * leaked when we do not call closelog(). */ diff --git a/src/libsystemd/sd-journal/test-journal-stream.c b/src/libsystemd/sd-journal/test-journal-stream.c index efd4eb0a630b9..de576962ccef2 100644 --- a/src/libsystemd/sd-journal/test-journal-stream.c +++ b/src/libsystemd/sd-journal/test-journal-stream.c @@ -15,12 +15,12 @@ #include "tests.h" #include "time-util.h" -#define N_ENTRIES 200 +#define N_ENTRIES 200u static void verify_contents(sd_journal *j, unsigned skip) { unsigned i; - assert_se(j); + ASSERT_NOT_NULL(j); i = 0; SD_JOURNAL_FOREACH(j) { @@ -29,32 +29,32 @@ static void verify_contents(sd_journal *j, unsigned skip) { size_t l; unsigned u = 0; - assert_se(sd_journal_get_cursor(j, &k) >= 0); + ASSERT_OK(sd_journal_get_cursor(j, &k)); printf("cursor: %s\n", k); free(k); - assert_se(sd_journal_get_data(j, "MAGIC", &d, &l) >= 0); + ASSERT_OK(sd_journal_get_data(j, "MAGIC", &d, &l)); printf("\t%.*s\n", (int) l, (const char*) d); - assert_se(sd_journal_get_data(j, "NUMBER", &d, &l) >= 0); - assert_se(k = strndup(d, l)); + ASSERT_OK(sd_journal_get_data(j, "NUMBER", &d, &l)); + ASSERT_NOT_NULL(k = strndup(d, l)); printf("\t%s\n", k); if (skip > 0) { - assert_se(safe_atou(k + 7, &u) >= 0); - assert_se(i == u); + ASSERT_OK(safe_atou(k + 7, &u)); + ASSERT_EQ(i, u); i += skip; } free(k); - assert_se(sd_journal_get_cursor(j, &c) >= 0); - assert_se(sd_journal_test_cursor(j, c) > 0); + ASSERT_OK(sd_journal_get_cursor(j, &c)); + ASSERT_OK_POSITIVE(sd_journal_test_cursor(j, c)); free(c); } if (skip > 0) - assert_se(i == N_ENTRIES); + ASSERT_EQ(i, N_ENTRIES); } static void run_test(void) { @@ -68,16 +68,15 @@ static void run_test(void) { size_t l; dual_timestamp previous_ts = DUAL_TIMESTAMP_NULL; - m = mmap_cache_new(); - assert_se(m != NULL); + ASSERT_NOT_NULL(m = mmap_cache_new()); - assert_se(mkdtemp(t)); - assert_se(chdir(t) >= 0); + ASSERT_NOT_NULL(mkdtemp(t)); + ASSERT_OK_ERRNO(chdir(t)); (void) chattr_path(t, FS_NOCOW_FL, FS_NOCOW_FL); - assert_se(journal_file_open(-EBADF, "one.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &one) == 0); - assert_se(journal_file_open(-EBADF, "two.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &two) == 0); - assert_se(journal_file_open(-EBADF, "three.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &three) == 0); + ASSERT_OK_ZERO(journal_file_open(-EBADF, "one.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &one)); + ASSERT_OK_ZERO(journal_file_open(-EBADF, "two.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &two)); + ASSERT_OK_ZERO(journal_file_open(-EBADF, "three.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &three)); for (i = 0; i < N_ENTRIES; i++) { char *p, *q; @@ -94,20 +93,20 @@ static void run_test(void) { previous_ts = ts; - assert_se(asprintf(&p, "NUMBER=%u", i) >= 0); + ASSERT_OK_ERRNO(asprintf(&p, "NUMBER=%u", i)); iovec[0] = IOVEC_MAKE(p, strlen(p)); - assert_se(asprintf(&q, "MAGIC=%s", i % 5 == 0 ? "quux" : "waldo") >= 0); + ASSERT_OK_ERRNO(asprintf(&q, "MAGIC=%s", i % 5 == 0 ? "quux" : "waldo")); iovec[1] = IOVEC_MAKE(q, strlen(q)); if (i % 10 == 0) - assert_se(journal_file_append_entry(three, &ts, NULL, iovec, 2, NULL, NULL, NULL, NULL) == 0); + ASSERT_OK_ZERO(journal_file_append_entry(three, &ts, NULL, iovec, 2, NULL, NULL, NULL, NULL)); else { if (i % 3 == 0) - assert_se(journal_file_append_entry(two, &ts, NULL, iovec, 2, NULL, NULL, NULL, NULL) == 0); + ASSERT_OK_ZERO(journal_file_append_entry(two, &ts, NULL, iovec, 2, NULL, NULL, NULL, NULL)); - assert_se(journal_file_append_entry(one, &ts, NULL, iovec, 2, NULL, NULL, NULL, NULL) == 0); + ASSERT_OK_ZERO(journal_file_append_entry(one, &ts, NULL, iovec, 2, NULL, NULL, NULL, NULL)); } free(p); @@ -118,27 +117,27 @@ static void run_test(void) { (void) journal_file_offline_close(two); (void) journal_file_offline_close(three); - assert_se(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE) >= 0); + ASSERT_OK(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); - assert_se(sd_journal_add_match(j, "MAGIC=quux", SIZE_MAX) >= 0); + ASSERT_OK(sd_journal_add_match(j, "MAGIC=quux", SIZE_MAX)); SD_JOURNAL_FOREACH_BACKWARDS(j) { _cleanup_free_ char *c; - assert_se(sd_journal_get_data(j, "NUMBER", &data, &l) >= 0); + ASSERT_OK(sd_journal_get_data(j, "NUMBER", &data, &l)); printf("\t%.*s\n", (int) l, (const char*) data); - assert_se(sd_journal_get_cursor(j, &c) >= 0); - assert_se(sd_journal_test_cursor(j, c) > 0); + ASSERT_OK(sd_journal_get_cursor(j, &c)); + ASSERT_OK_POSITIVE(sd_journal_test_cursor(j, c)); } SD_JOURNAL_FOREACH(j) { _cleanup_free_ char *c; - assert_se(sd_journal_get_data(j, "NUMBER", &data, &l) >= 0); + ASSERT_OK(sd_journal_get_data(j, "NUMBER", &data, &l)); printf("\t%.*s\n", (int) l, (const char*) data); - assert_se(sd_journal_get_cursor(j, &c) >= 0); - assert_se(sd_journal_test_cursor(j, c) > 0); + ASSERT_OK(sd_journal_get_cursor(j, &c)); + ASSERT_OK_POSITIVE(sd_journal_test_cursor(j, c)); } sd_journal_flush_matches(j); @@ -146,9 +145,9 @@ static void run_test(void) { verify_contents(j, 1); printf("NEXT TEST\n"); - assert_se(sd_journal_add_match(j, "MAGIC=quux", SIZE_MAX) >= 0); + ASSERT_OK(sd_journal_add_match(j, "MAGIC=quux", SIZE_MAX)); - assert_se(z = journal_make_match_string(j)); + ASSERT_NOT_NULL(z = journal_make_match_string(j)); printf("resulting match expression is: %s\n", z); free(z); @@ -156,22 +155,22 @@ static void run_test(void) { printf("NEXT TEST\n"); sd_journal_flush_matches(j); - assert_se(sd_journal_add_match(j, "MAGIC=waldo", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "NUMBER=10", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "NUMBER=11", SIZE_MAX) >= 0); - assert_se(sd_journal_add_match(j, "NUMBER=12", SIZE_MAX) >= 0); + ASSERT_OK(sd_journal_add_match(j, "MAGIC=waldo", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "NUMBER=10", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "NUMBER=11", SIZE_MAX)); + ASSERT_OK(sd_journal_add_match(j, "NUMBER=12", SIZE_MAX)); - assert_se(z = journal_make_match_string(j)); + ASSERT_NOT_NULL(z = journal_make_match_string(j)); printf("resulting match expression is: %s\n", z); free(z); verify_contents(j, 0); - assert_se(sd_journal_query_unique(j, "NUMBER") >= 0); + ASSERT_OK(sd_journal_query_unique(j, "NUMBER")); SD_JOURNAL_FOREACH_UNIQUE(j, data, l) printf("%.*s\n", (int) l, (const char*) data); - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + ASSERT_OK(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL)); } int main(int argc, char *argv[]) { @@ -184,16 +183,16 @@ int main(int argc, char *argv[]) { /* Run this test multiple times with different configurations of features. */ - assert_se(setenv("SYSTEMD_JOURNAL_KEYED_HASH", "0", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_KEYED_HASH", "0", 1)); run_test(); - assert_se(setenv("SYSTEMD_JOURNAL_KEYED_HASH", "1", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_KEYED_HASH", "1", 1)); run_test(); - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1)); run_test(); - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1)); run_test(); return 0; diff --git a/src/libsystemd/sd-journal/test-journal-verify.c b/src/libsystemd/sd-journal/test-journal-verify.c index bc66a96aee008..2d797a18f3d3f 100644 --- a/src/libsystemd/sd-journal/test-journal-verify.c +++ b/src/libsystemd/sd-journal/test-journal-verify.c @@ -22,19 +22,15 @@ static void bit_toggle(const char *fn, uint64_t p) { uint8_t b; - ssize_t r; int fd; - fd = open(fn, O_RDWR|O_CLOEXEC); - assert_se(fd >= 0); + ASSERT_OK_ERRNO(fd = open(fn, O_RDWR|O_CLOEXEC)); - r = pread(fd, &b, 1, p/8); - assert_se(r == 1); + ASSERT_EQ(pread(fd, &b, 1, p/8), 1); b ^= 1 << (p % 8); - r = pwrite(fd, &b, 1, p/8); - assert_se(r == 1); + ASSERT_EQ(pwrite(fd, &b, 1, p/8), 1); safe_close(fd); } @@ -44,8 +40,7 @@ static int raw_verify(const char *fn, const char *verification_key) { JournalFile *f; int r; - m = mmap_cache_new(); - assert_se(m != NULL); + ASSERT_NOT_NULL(m = mmap_cache_new()); r = journal_file_open( /* fd= */ -EBADF, @@ -77,8 +72,7 @@ static int run_test(const char *verification_key, ssize_t max_iterations) { uint64_t start, end; int r; - m = mmap_cache_new(); - assert_se(m != NULL); + ASSERT_NOT_NULL(m = mmap_cache_new()); /* journal_file_open() requires a valid machine id */ if (sd_id128_get_machine(NULL) < 0) @@ -86,13 +80,13 @@ static int run_test(const char *verification_key, ssize_t max_iterations) { test_setup_logging(LOG_DEBUG); - assert_se(mkdtemp(t)); - assert_se(chdir(t) >= 0); + ASSERT_NOT_NULL(mkdtemp(t)); + ASSERT_OK_ERRNO(chdir(t)); (void) chattr_path(t, FS_NOCOW_FL, FS_NOCOW_FL); log_info("Generating a test journal"); - assert_se(journal_file_open( + ASSERT_OK_ZERO(journal_file_open( /* fd= */ -EBADF, "test.journal", O_RDWR|O_CREAT, @@ -102,7 +96,7 @@ static int run_test(const char *verification_key, ssize_t max_iterations) { /* metrics= */ NULL, m, /* template= */ NULL, - &df) == 0); + &df)); for (size_t n = 0; n < N_ENTRIES; n++) { _cleanup_free_ char *test = NULL; @@ -110,9 +104,9 @@ static int run_test(const char *verification_key, ssize_t max_iterations) { struct dual_timestamp ts; dual_timestamp_now(&ts); - assert_se(asprintf(&test, "RANDOM=%li", random() % RANDOM_RANGE)); + ASSERT_OK_ERRNO(asprintf(&test, "RANDOM=%li", random() % RANDOM_RANGE)); iovec = IOVEC_MAKE_STRING(test); - assert_se(journal_file_append_entry( + ASSERT_OK_ZERO(journal_file_append_entry( df, &ts, /* boot_id= */ NULL, @@ -121,14 +115,14 @@ static int run_test(const char *verification_key, ssize_t max_iterations) { /* seqnum= */ NULL, /* seqnum_id= */ NULL, /* ret_object= */ NULL, - /* ret_offset= */ NULL) == 0); + /* ret_offset= */ NULL)); } (void) journal_file_offline_close(df); log_info("Verifying with key: %s", strna(verification_key)); - assert_se(journal_file_open( + ASSERT_OK_ZERO(journal_file_open( /* fd= */ -EBADF, "test.journal", O_RDONLY, @@ -138,11 +132,11 @@ static int run_test(const char *verification_key, ssize_t max_iterations) { /* metrics= */ NULL, m, /* template= */ NULL, - &f) == 0); + &f)); journal_file_print_header(f); journal_file_dump(f); - assert_se(journal_file_verify(f, verification_key, &from, &to, &total, true) >= 0); + ASSERT_OK(journal_file_verify(f, verification_key, &from, &to, &total, true)); if (verification_key && JOURNAL_HEADER_SEALED(f->header)) log_info("=> Validated from %s to %s, %s missing", @@ -151,7 +145,7 @@ static int run_test(const char *verification_key, ssize_t max_iterations) { FORMAT_TIMESPAN(total > to ? total - to : 0, 0)); (void) journal_file_close(f); - assert_se(stat("test.journal", &st) >= 0); + ASSERT_OK_ERRNO(stat("test.journal", &st)); start = 38448 * 8 + 0; end = max_iterations < 0 ? (uint64_t)st.st_size * 8 : start + max_iterations; @@ -171,7 +165,7 @@ static int run_test(const char *verification_key, ssize_t max_iterations) { bit_toggle("test.journal", p); } - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + ASSERT_OK(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL)); return 0; } @@ -187,10 +181,10 @@ int main(int argc, char *argv[]) { max_iterations = -1; } - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1)); run_test(verification_key, max_iterations); - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1)); run_test(verification_key, max_iterations); #if HAVE_GCRYPT @@ -199,10 +193,10 @@ int main(int argc, char *argv[]) { if (argc <= 1) { verification_key = "c262bd-85187f-0b1b04-877cc5/1c7af8-35a4e900"; - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1)); run_test(verification_key, max_iterations); - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1)); run_test(verification_key, max_iterations); } #endif diff --git a/src/libsystemd/sd-journal/test-journal.c b/src/libsystemd/sd-journal/test-journal.c index fb0a02d9bfbbf..b3eaac28aa79b 100644 --- a/src/libsystemd/sd-journal/test-journal.c +++ b/src/libsystemd/sd-journal/test-journal.c @@ -17,8 +17,8 @@ static bool arg_keep = false; static void mkdtemp_chdir_chattr(char *path) { - assert_se(mkdtemp(path)); - assert_se(chdir(path) >= 0); + ASSERT_NOT_NULL(mkdtemp(path)); + ASSERT_OK_ERRNO(chdir(path)); /* Speed up things a bit on btrfs, ensuring that CoW is turned off for all files created in our * directory during the test run */ @@ -36,71 +36,70 @@ static void test_non_empty_one(void) { sd_id128_t fake_boot_id; char t[] = "/var/tmp/journal-XXXXXX"; - m = mmap_cache_new(); - assert_se(m != NULL); + ASSERT_NOT_NULL(m = mmap_cache_new()); mkdtemp_chdir_chattr(t); - assert_se(journal_file_open(-EBADF, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f) == 0); + ASSERT_OK_ZERO(journal_file_open(-EBADF, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f)); - assert_se(dual_timestamp_now(&ts)); - assert_se(sd_id128_randomize(&fake_boot_id) == 0); + ASSERT_NOT_NULL(dual_timestamp_now(&ts)); + ASSERT_OK_ZERO(sd_id128_randomize(&fake_boot_id)); iovec = IOVEC_MAKE_STRING(test); - assert_se(journal_file_append_entry(f, &ts, NULL, &iovec, 1, NULL, NULL, NULL, NULL) == 0); + ASSERT_OK_ZERO(journal_file_append_entry(f, &ts, NULL, &iovec, 1, NULL, NULL, NULL, NULL)); iovec = IOVEC_MAKE_STRING(test2); - assert_se(journal_file_append_entry(f, &ts, NULL, &iovec, 1, NULL, NULL, NULL, NULL) == 0); + ASSERT_OK_ZERO(journal_file_append_entry(f, &ts, NULL, &iovec, 1, NULL, NULL, NULL, NULL)); iovec = IOVEC_MAKE_STRING(test); - assert_se(journal_file_append_entry(f, &ts, &fake_boot_id, &iovec, 1, NULL, NULL, NULL, NULL) == 0); + ASSERT_OK_ZERO(journal_file_append_entry(f, &ts, &fake_boot_id, &iovec, 1, NULL, NULL, NULL, NULL)); #if HAVE_GCRYPT journal_file_append_tag(f); #endif journal_file_dump(f); - assert_se(journal_file_next_entry(f, 0, DIRECTION_DOWN, &o, &p) == 1); - assert_se(le64toh(o->entry.seqnum) == 1); + ASSERT_EQ(journal_file_next_entry(f, 0, DIRECTION_DOWN, &o, &p), 1); + ASSERT_EQ(le64toh(o->entry.seqnum), UINT64_C(1)); - assert_se(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p) == 1); - assert_se(le64toh(o->entry.seqnum) == 2); + ASSERT_EQ(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p), 1); + ASSERT_EQ(le64toh(o->entry.seqnum), UINT64_C(2)); - assert_se(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p) == 1); - assert_se(le64toh(o->entry.seqnum) == 3); - assert_se(sd_id128_equal(o->entry.boot_id, fake_boot_id)); + ASSERT_EQ(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p), 1); + ASSERT_EQ(le64toh(o->entry.seqnum), UINT64_C(3)); + ASSERT_EQ_ID128(o->entry.boot_id, fake_boot_id); - assert_se(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p) == 0); + ASSERT_OK_ZERO(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p)); - assert_se(journal_file_next_entry(f, 0, DIRECTION_DOWN, &o, &p) == 1); - assert_se(le64toh(o->entry.seqnum) == 1); + ASSERT_EQ(journal_file_next_entry(f, 0, DIRECTION_DOWN, &o, &p), 1); + ASSERT_EQ(le64toh(o->entry.seqnum), UINT64_C(1)); - assert_se(journal_file_find_data_object(f, test, strlen(test), &d, NULL) == 1); - assert_se(journal_file_move_to_entry_for_data(f, d, DIRECTION_DOWN, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 1); + ASSERT_EQ(journal_file_find_data_object(f, test, strlen(test), &d, NULL), 1); + ASSERT_EQ(journal_file_move_to_entry_for_data(f, d, DIRECTION_DOWN, &o, NULL), 1); + ASSERT_EQ(le64toh(o->entry.seqnum), UINT64_C(1)); - assert_se(journal_file_move_to_entry_for_data(f, d, DIRECTION_UP, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 3); + ASSERT_EQ(journal_file_move_to_entry_for_data(f, d, DIRECTION_UP, &o, NULL), 1); + ASSERT_EQ(le64toh(o->entry.seqnum), UINT64_C(3)); - assert_se(journal_file_find_data_object(f, test2, strlen(test2), &d, NULL) == 1); - assert_se(journal_file_move_to_entry_for_data(f, d, DIRECTION_UP, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 2); + ASSERT_EQ(journal_file_find_data_object(f, test2, strlen(test2), &d, NULL), 1); + ASSERT_EQ(journal_file_move_to_entry_for_data(f, d, DIRECTION_UP, &o, NULL), 1); + ASSERT_EQ(le64toh(o->entry.seqnum), UINT64_C(2)); - assert_se(journal_file_move_to_entry_for_data(f, d, DIRECTION_DOWN, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 2); + ASSERT_EQ(journal_file_move_to_entry_for_data(f, d, DIRECTION_DOWN, &o, NULL), 1); + ASSERT_EQ(le64toh(o->entry.seqnum), UINT64_C(2)); - assert_se(journal_file_find_data_object(f, "quux", 4, &d, NULL) == 0); + ASSERT_OK_ZERO(journal_file_find_data_object(f, "quux", 4, &d, NULL)); - assert_se(journal_file_move_to_entry_by_seqnum(f, 1, DIRECTION_DOWN, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 1); + ASSERT_EQ(journal_file_move_to_entry_by_seqnum(f, 1, DIRECTION_DOWN, &o, NULL), 1); + ASSERT_EQ(le64toh(o->entry.seqnum), UINT64_C(1)); - assert_se(journal_file_move_to_entry_by_seqnum(f, 3, DIRECTION_DOWN, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 3); + ASSERT_EQ(journal_file_move_to_entry_by_seqnum(f, 3, DIRECTION_DOWN, &o, NULL), 1); + ASSERT_EQ(le64toh(o->entry.seqnum), UINT64_C(3)); - assert_se(journal_file_move_to_entry_by_seqnum(f, 2, DIRECTION_DOWN, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 2); + ASSERT_EQ(journal_file_move_to_entry_by_seqnum(f, 2, DIRECTION_DOWN, &o, NULL), 1); + ASSERT_EQ(le64toh(o->entry.seqnum), UINT64_C(2)); - assert_se(journal_file_move_to_entry_by_seqnum(f, 10, DIRECTION_DOWN, &o, NULL) == 0); + ASSERT_OK_ZERO(journal_file_move_to_entry_by_seqnum(f, 10, DIRECTION_DOWN, &o, NULL)); journal_file_rotate(&f, m, JOURNAL_SEAL|JOURNAL_COMPRESS, UINT64_MAX, NULL); journal_file_rotate(&f, m, JOURNAL_SEAL|JOURNAL_COMPRESS, UINT64_MAX, NULL); @@ -114,17 +113,17 @@ static void test_non_empty_one(void) { else { journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + ASSERT_OK(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL)); } puts("------------------------------------------------------------"); } TEST(non_empty) { - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1)); test_non_empty_one(); - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1)); test_non_empty_one(); } @@ -133,15 +132,14 @@ static void test_empty_one(void) { JournalFile *f1, *f2, *f3, *f4; char t[] = "/var/tmp/journal-XXXXXX"; - m = mmap_cache_new(); - assert_se(m != NULL); + ASSERT_NOT_NULL(m = mmap_cache_new()); mkdtemp_chdir_chattr(t); - assert_se(journal_file_open(-EBADF, "test.journal", O_RDWR|O_CREAT, 0, 0666, UINT64_MAX, NULL, m, NULL, &f1) == 0); - assert_se(journal_file_open(-EBADF, "test-compress.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &f2) == 0); - assert_se(journal_file_open(-EBADF, "test-seal.journal", O_RDWR|O_CREAT, JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f3) == 0); - assert_se(journal_file_open(-EBADF, "test-seal-compress.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f4) == 0); + ASSERT_OK_ZERO(journal_file_open(-EBADF, "test.journal", O_RDWR|O_CREAT, 0, 0666, UINT64_MAX, NULL, m, NULL, &f1)); + ASSERT_OK_ZERO(journal_file_open(-EBADF, "test-compress.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &f2)); + ASSERT_OK_ZERO(journal_file_open(-EBADF, "test-seal.journal", O_RDWR|O_CREAT, JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f3)); + ASSERT_OK_ZERO(journal_file_open(-EBADF, "test-seal-compress.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f4)); journal_file_print_header(f1); puts(""); @@ -159,7 +157,7 @@ static void test_empty_one(void) { else { journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + ASSERT_OK(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL)); } (void) journal_file_offline_close(f1); @@ -169,10 +167,10 @@ static void test_empty_one(void) { } TEST(empty) { - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1)); test_empty_one(); - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1)); test_empty_one(); } @@ -187,21 +185,19 @@ static bool check_compressed(uint64_t compress_threshold, uint64_t data_size) { char t[] = "/var/tmp/journal-XXXXXX"; char data[2048] = "FIELD="; bool is_compressed; - int r; - assert_se(data_size <= sizeof(data)); + ASSERT_LE(data_size, sizeof(data)); - m = mmap_cache_new(); - assert_se(m != NULL); + ASSERT_NOT_NULL(m = mmap_cache_new()); mkdtemp_chdir_chattr(t); - assert_se(journal_file_open(-EBADF, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, compress_threshold, NULL, m, NULL, &f) == 0); + ASSERT_OK_ZERO(journal_file_open(-EBADF, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, compress_threshold, NULL, m, NULL, &f)); dual_timestamp_now(&ts); iovec = IOVEC_MAKE(data, data_size); - assert_se(journal_file_append_entry(f, &ts, NULL, &iovec, 1, NULL, NULL, NULL, NULL) == 0); + ASSERT_OK_ZERO(journal_file_append_entry(f, &ts, NULL, &iovec, 1, NULL, NULL, NULL, NULL)); #if HAVE_GCRYPT journal_file_append_tag(f); @@ -212,12 +208,11 @@ static bool check_compressed(uint64_t compress_threshold, uint64_t data_size) { * decompression for us. */ p = le64toh(f->header->header_size); for (;;) { - r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &o); - assert_se(r == 0); + ASSERT_OK_ZERO(journal_file_move_to_object(f, OBJECT_UNUSED, p, &o)); if (o->object.type == OBJECT_DATA) break; - assert_se(p < le64toh(f->header->tail_object_offset)); + ASSERT_LT(p, le64toh(f->header->tail_object_offset)); p = p + ALIGN64(le64toh(o->object.size)); } @@ -232,7 +227,7 @@ static bool check_compressed(uint64_t compress_threshold, uint64_t data_size) { else { journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + ASSERT_OK(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL)); } puts("------------------------------------------------------------"); @@ -245,26 +240,26 @@ static void test_min_compress_size_one(void) { * carefully */ /* DEFAULT_MIN_COMPRESS_SIZE is 512 */ - assert_se(!check_compressed(UINT64_MAX, 255)); - assert_se(check_compressed(UINT64_MAX, 513)); + ASSERT_FALSE(check_compressed(UINT64_MAX, 255)); + ASSERT_TRUE(check_compressed(UINT64_MAX, 513)); /* compress everything */ - assert_se(check_compressed(0, 96)); - assert_se(check_compressed(8, 96)); + ASSERT_TRUE(check_compressed(0, 96)); + ASSERT_TRUE(check_compressed(8, 96)); /* Ensure we don't try to compress less than 8 bytes */ - assert_se(!check_compressed(0, 7)); + ASSERT_FALSE(check_compressed(0, 7)); /* check boundary conditions */ - assert_se(check_compressed(256, 256)); - assert_se(!check_compressed(256, 255)); + ASSERT_TRUE(check_compressed(256, 256)); + ASSERT_FALSE(check_compressed(256, 255)); } TEST(min_compress_size) { - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1)); test_min_compress_size_one(); - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1)); test_min_compress_size_one(); } #endif diff --git a/src/libsystemd/sd-journal/test-mmap-cache.c b/src/libsystemd/sd-journal/test-mmap-cache.c index dc7e334247836..b59d5177f6dc8 100644 --- a/src/libsystemd/sd-journal/test-mmap-cache.c +++ b/src/libsystemd/sd-journal/test-mmap-cache.c @@ -10,49 +10,41 @@ int main(int argc, char *argv[]) { MMapFileDescriptor *fx; - int x, y, z, r; + int x, y, z; char px[] = "/tmp/testmmapXXXXXXX", py[] = "/tmp/testmmapYXXXXXX", pz[] = "/tmp/testmmapZXXXXXX"; MMapCache *m; void *p, *q; test_setup_logging(LOG_DEBUG); - assert_se(m = mmap_cache_new()); + ASSERT_NOT_NULL(m = mmap_cache_new()); - x = mkostemp_safe(px); - assert_se(x >= 0); + ASSERT_OK(x = mkostemp_safe(px)); (void) unlink(px); - assert_se(mmap_cache_add_fd(m, x, PROT_READ, &fx) > 0); + ASSERT_OK_POSITIVE(mmap_cache_add_fd(m, x, PROT_READ, &fx)); - y = mkostemp_safe(py); - assert_se(y >= 0); + ASSERT_OK(y = mkostemp_safe(py)); (void) unlink(py); - z = mkostemp_safe(pz); - assert_se(z >= 0); + ASSERT_OK(z = mkostemp_safe(pz)); (void) unlink(pz); - r = mmap_cache_fd_get(fx, 0, false, 1, 2, NULL, &p); - assert_se(r >= 0); + ASSERT_OK(mmap_cache_fd_get(fx, 0, false, 1, 2, NULL, &p)); - r = mmap_cache_fd_get(fx, 0, false, 2, 2, NULL, &q); - assert_se(r >= 0); + ASSERT_OK(mmap_cache_fd_get(fx, 0, false, 2, 2, NULL, &q)); - assert_se((uint8_t*) p + 1 == (uint8_t*) q); + ASSERT_PTR_EQ((uint8_t*) p + 1, (uint8_t*) q); - r = mmap_cache_fd_get(fx, 1, false, 3, 2, NULL, &q); - assert_se(r >= 0); + ASSERT_OK(mmap_cache_fd_get(fx, 1, false, 3, 2, NULL, &q)); - assert_se((uint8_t*) p + 2 == (uint8_t*) q); + ASSERT_PTR_EQ((uint8_t*) p + 2, (uint8_t*) q); - r = mmap_cache_fd_get(fx, 0, false, 16ULL*1024ULL*1024ULL, 2, NULL, &p); - assert_se(r >= 0); + ASSERT_OK(mmap_cache_fd_get(fx, 0, false, 16ULL*1024ULL*1024ULL, 2, NULL, &p)); - r = mmap_cache_fd_get(fx, 1, false, 16ULL*1024ULL*1024ULL+1, 2, NULL, &q); - assert_se(r >= 0); + ASSERT_OK(mmap_cache_fd_get(fx, 1, false, 16ULL*1024ULL*1024ULL+1, 2, NULL, &q)); - assert_se((uint8_t*) p + 1 == (uint8_t*) q); + ASSERT_PTR_EQ((uint8_t*) p + 1, (uint8_t*) q); mmap_cache_fd_free(fx); mmap_cache_unref(m); From c99e93ce0a195eafe988b1769729c526ca68352b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 17 Apr 2026 16:06:23 +0200 Subject: [PATCH 1133/1296] shared/curl-util: load libcurl via dlopen Convert curl-util to the dlopen pattern used by other optional shared libraries in libshared (libarchive, pcre2, idn, ...). Declare the curl API entry points with DLSYM_PROTOTYPE, resolve them in a dlopen_curl() helper, and call the sym_* wrappers from callers. curl_glue_new() now loads the library on first use, so consumers going through CurlGlue pick this up automatically; journal-upload and report-upload call dlopen_curl() directly since they use curl without the glue layer. With this in place curl-util can live in libshared itself, linked only against libcurl's headers (via libcurl_cflags). The libcurlutil_static convenience library and the libcurl link dependency on systemd-imdsd, systemd-pull, systemd-journal-upload and systemd-report go away. Also move the easy_setopt() helper macro next to the DLSYM declarations so all consumers use a single sym-prefixed definition, and add a dlopen_curl() check to test-dlopen-so. --- meson.build | 1 + src/imds/imdsd.c | 44 ++++----- src/imds/meson.build | 20 ++--- src/import/meson.build | 17 +--- src/import/pull-job.c | 38 ++++---- src/journal-remote/journal-upload.c | 36 ++++---- src/journal-remote/meson.build | 2 +- src/report/meson.build | 2 - src/report/report-upload.c | 15 ++-- src/shared/curl-util.c | 134 +++++++++++++++++++++------- src/shared/curl-util.h | 60 ++++++++++--- src/shared/meson.build | 17 +--- src/test/test-dlopen-so.c | 2 + 13 files changed, 237 insertions(+), 151 deletions(-) diff --git a/meson.build b/meson.build index e0e7102a39f42..087539037995a 100644 --- a/meson.build +++ b/meson.build @@ -1296,6 +1296,7 @@ endforeach libcurl = dependency('libcurl', version : '>= 7.32.0', required : get_option('libcurl')) +libcurl_cflags = libcurl.partial_dependency(includes: true, compile_args: true) conf.set10('HAVE_LIBCURL', libcurl.found()) conf.set10('CURL_NO_OLDIES', conf.get('BUILD_MODE_DEVELOPER') == 1) diff --git a/src/imds/imdsd.c b/src/imds/imdsd.c index 5ed7d1df338b7..211565880c2ac 100644 --- a/src/imds/imdsd.c +++ b/src/imds/imdsd.c @@ -257,9 +257,9 @@ static void context_reset_for_refresh(Context *c) { c->curl_data = NULL; } - curl_slist_free_all(c->request_header_token); + sym_curl_slist_free_all(c->request_header_token); c->request_header_token = NULL; - curl_slist_free_all(c->request_header_data); + sym_curl_slist_free_all(c->request_header_data); c->request_header_data = NULL; c->cache_fd = safe_close(c->cache_fd); @@ -723,9 +723,9 @@ static int context_acquire_http_status(Context *c, CURL *curl, long *ret_status) */ long status; - CURLcode code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); + CURLcode code = sym_curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); if (code != CURLE_OK) - return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to retrieve response code: %s", curl_easy_strerror(code)); + return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to retrieve response code: %s", sym_curl_easy_strerror(code)); context_log(c, LOG_DEBUG, "Got HTTP error code %li.", status); @@ -906,7 +906,7 @@ static void curl_glue_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { /* Called whenever libcurl did its thing and reports a download being complete or having failed */ Context *c = NULL; - if (curl_easy_getinfo(curl, CURLINFO_PRIVATE, (char**) &c) != CURLE_OK) + if (sym_curl_easy_getinfo(curl, CURLINFO_PRIVATE, (char**) &c) != CURLE_OK) return; switch (result) { @@ -927,7 +927,7 @@ static void curl_glue_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { case CURLE_GOT_NOTHING: case CURLE_SEND_ERROR: case CURLE_RECV_ERROR: - context_log(c, LOG_INFO, "Connection error from curl: %s", curl_easy_strerror(result)); + context_log(c, LOG_INFO, "Connection error from curl: %s", sym_curl_easy_strerror(result)); /* Automatically retry on some transient errors from curl itself */ r = context_schedule_retry(c); @@ -939,7 +939,7 @@ static void curl_glue_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { default: return context_fail_full( c, - context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EHOSTDOWN), "Transfer failed: %s", curl_easy_strerror(result)), + context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EHOSTDOWN), "Transfer failed: %s", sym_curl_easy_strerror(result)), "io.systemd.InstanceMetadata.CommunicationFailure"); } @@ -1122,25 +1122,25 @@ static int context_acquire_data(Context *c) { return context_log_errno(c, LOG_ERR, r, "Failed to create curl header: %m"); if (c->request_header_data) - if (curl_easy_setopt(c->curl_data, CURLOPT_HTTPHEADER, c->request_header_data) != CURLE_OK) + if (sym_curl_easy_setopt(c->curl_data, CURLOPT_HTTPHEADER, c->request_header_data) != CURLE_OK) return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set HTTP request header."); - if (curl_easy_setopt(c->curl_data, CURLOPT_WRITEFUNCTION, data_write_callback) != CURLE_OK) + if (sym_curl_easy_setopt(c->curl_data, CURLOPT_WRITEFUNCTION, data_write_callback) != CURLE_OK) return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL write function."); - if (curl_easy_setopt(c->curl_data, CURLOPT_WRITEDATA, c) != CURLE_OK) + if (sym_curl_easy_setopt(c->curl_data, CURLOPT_WRITEDATA, c) != CURLE_OK) return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL write function userdata."); - if (curl_easy_setopt(c->curl_data, CURLOPT_SOCKOPTFUNCTION, setsockopt_callback) != CURLE_OK) + if (sym_curl_easy_setopt(c->curl_data, CURLOPT_SOCKOPTFUNCTION, setsockopt_callback) != CURLE_OK) return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL setsockopt function."); - if (curl_easy_setopt(c->curl_data, CURLOPT_SOCKOPTDATA, c) != CURLE_OK) + if (sym_curl_easy_setopt(c->curl_data, CURLOPT_SOCKOPTDATA, c) != CURLE_OK) return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL setsockopt function userdata."); - if (curl_easy_setopt(c->curl_data, CURLOPT_LOCALPORT, 1L) != CURLE_OK) + if (sym_curl_easy_setopt(c->curl_data, CURLOPT_LOCALPORT, 1L) != CURLE_OK) return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL setsockopt local port"); - if (curl_easy_setopt(c->curl_data, CURLOPT_LOCALPORTRANGE, 1023L) != CURLE_OK) + if (sym_curl_easy_setopt(c->curl_data, CURLOPT_LOCALPORTRANGE, 1023L) != CURLE_OK) return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL setsockopt local port range"); r = curl_glue_add(c->glue, c->curl_data); @@ -1216,22 +1216,22 @@ static int context_acquire_token(Context *c) { return context_log_oom(c); } - if (curl_easy_setopt(c->curl_token, CURLOPT_HTTPHEADER, c->request_header_token) != CURLE_OK) + if (sym_curl_easy_setopt(c->curl_token, CURLOPT_HTTPHEADER, c->request_header_token) != CURLE_OK) return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set HTTP request header."); - if (curl_easy_setopt(c->curl_token, CURLOPT_CUSTOMREQUEST, "PUT") != CURLE_OK) + if (sym_curl_easy_setopt(c->curl_token, CURLOPT_CUSTOMREQUEST, "PUT") != CURLE_OK) return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set HTTP request method."); - if (curl_easy_setopt(c->curl_token, CURLOPT_WRITEFUNCTION, token_write_callback) != CURLE_OK) + if (sym_curl_easy_setopt(c->curl_token, CURLOPT_WRITEFUNCTION, token_write_callback) != CURLE_OK) return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL write function."); - if (curl_easy_setopt(c->curl_token, CURLOPT_WRITEDATA, c) != CURLE_OK) + if (sym_curl_easy_setopt(c->curl_token, CURLOPT_WRITEDATA, c) != CURLE_OK) return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL write function userdata."); - if (curl_easy_setopt(c->curl_token, CURLOPT_SOCKOPTFUNCTION, setsockopt_callback) != CURLE_OK) + if (sym_curl_easy_setopt(c->curl_token, CURLOPT_SOCKOPTFUNCTION, setsockopt_callback) != CURLE_OK) return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL setsockopt function."); - if (curl_easy_setopt(c->curl_token, CURLOPT_SOCKOPTDATA, c) != CURLE_OK) + if (sym_curl_easy_setopt(c->curl_token, CURLOPT_SOCKOPTDATA, c) != CURLE_OK) return context_log_errno(c, LOG_ERR, SYNTHETIC_ERRNO(EIO), "Failed to set CURL setsockopt function userdata."); r = curl_glue_add(c->glue, c->curl_token); @@ -3070,6 +3070,10 @@ static int run(int argc, char* argv[]) { if (r <= 0) return r; + r = dlopen_curl(); + if (r < 0) + return r; + r = environment_server_info(); if (r < 0) return r; diff --git a/src/imds/meson.build b/src/imds/meson.build index 29fa878eaba43..9295c8cf620c9 100644 --- a/src/imds/meson.build +++ b/src/imds/meson.build @@ -8,27 +8,19 @@ executables += [ libexec_template + { 'name' : 'systemd-imdsd', 'public' : true, - 'sources' : files( - 'imdsd.c', - 'imds-util.c' - ), - 'link_with' : [libcurlutil_static, libshared], - 'dependencies' : [libcurl], + 'sources' : files('imdsd.c'), + 'extract' : files('imds-util.c'), }, libexec_template + { 'name' : 'systemd-imds', 'public' : true, - 'sources' : files( - 'imds-tool.c', - 'imds-util.c' - ), + 'sources' : files('imds-tool.c'), + 'objects' : ['systemd-imdsd'], }, generator_template + { 'name' : 'systemd-imds-generator', - 'sources' : files( - 'imds-generator.c', - 'imds-util.c' - ), + 'sources' : files('imds-generator.c'), + 'objects' : ['systemd-imdsd'], }, ] diff --git a/src/import/meson.build b/src/import/meson.build index 13c90d7937fed..63e632a6cd1fb 100644 --- a/src/import/meson.build +++ b/src/import/meson.build @@ -4,10 +4,6 @@ if conf.get('ENABLE_IMPORTD') != 1 subdir_done() endif -common_deps = [ - libcurl, -] - executables += [ libexec_template + { 'name' : 'systemd-importd', @@ -21,7 +17,7 @@ executables += [ 'import-common.c', 'qcow2-util.c', ), - 'dependencies' : [common_deps, threads], + 'dependencies' : threads, }, libexec_template + { 'name' : 'systemd-pull', @@ -35,10 +31,7 @@ executables += [ 'pull-tar.c', ), 'objects' : ['systemd-importd'], - 'link_with' : [libcurlutil_static, libshared], - 'dependencies' : common_deps + [ - libopenssl, - ], + 'dependencies' : libopenssl, }, libexec_template + { 'name' : 'systemd-import', @@ -49,7 +42,6 @@ executables += [ 'import-tar.c', ), 'objects' : ['systemd-importd'], - 'dependencies' : common_deps, }, libexec_template + { 'name' : 'systemd-import-fs', @@ -58,7 +50,6 @@ executables += [ 'import-fs.c', ), 'objects' : ['systemd-importd'], - 'dependencies' : common_deps, }, libexec_template + { 'name' : 'systemd-export', @@ -69,14 +60,12 @@ executables += [ 'export-raw.c', ), 'objects' : ['systemd-importd'], - 'dependencies' : common_deps, }, executable_template + { 'name' : 'importctl', 'public' : true, 'sources' : files('importctl.c'), 'objects': ['systemd-importd'], - 'dependencies' : common_deps, }, generator_template + { 'name' : 'systemd-import-generator', @@ -89,13 +78,11 @@ executables += [ test_template + { 'sources' : files('test-qcow2.c'), 'objects' : ['systemd-importd'], - 'dependencies' : common_deps, 'type' : 'manual', }, test_template + { 'sources' : files('test-oci-util.c'), 'objects': ['systemd-importd'], - 'dependencies' : common_deps, }, ] diff --git a/src/import/pull-job.c b/src/import/pull-job.c index 385043dda8003..3cc9c0d692690 100644 --- a/src/import/pull-job.c +++ b/src/import/pull-job.c @@ -52,7 +52,7 @@ PullJob* pull_job_unref(PullJob *j) { pull_job_close_disk_fd(j); curl_glue_remove_and_free(j->glue, j->curl); - curl_slist_free_all(j->request_header); + sym_curl_slist_free_all(j->request_header); j->compress = compressor_free(j->compress); @@ -164,13 +164,13 @@ void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { CURLcode code; int r; - if (curl_easy_getinfo(curl, CURLINFO_PRIVATE, (char **)&j) != CURLE_OK) + if (sym_curl_easy_getinfo(curl, CURLINFO_PRIVATE, (char **)&j) != CURLE_OK) return; if (!j || IN_SET(j->state, PULL_JOB_DONE, PULL_JOB_FAILED)) return; - code = curl_easy_getinfo(curl, CURLINFO_SCHEME, &scheme); + code = sym_curl_easy_getinfo(curl, CURLINFO_SCHEME, &scheme); if (code != CURLE_OK || !scheme) { r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve URL scheme."); goto finish; @@ -197,16 +197,16 @@ void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { } if (result != CURLE_OK) { - r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Transfer failed: %s", curl_easy_strerror(result)); + r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Transfer failed: %s", sym_curl_easy_strerror(result)); goto finish; } if (STRCASE_IN_SET(scheme, "HTTP", "HTTPS")) { long status; - code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); + code = sym_curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); if (code != CURLE_OK) { - r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve response code: %s", curl_easy_strerror(code)); + r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve response code: %s", sym_curl_easy_strerror(code)); goto finish; } @@ -236,9 +236,9 @@ void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { if (r < 0) goto finish; - code = curl_easy_getinfo(j->curl, CURLINFO_RESPONSE_CODE, &status); + code = sym_curl_easy_getinfo(j->curl, CURLINFO_RESPONSE_CODE, &status); if (code != CURLE_OK) { - r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve response code: %s", curl_easy_strerror(code)); + r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve response code: %s", sym_curl_easy_strerror(code)); goto finish; } @@ -589,9 +589,9 @@ static size_t pull_job_header_callback(void *contents, size_t size, size_t nmemb assert(j->state == PULL_JOB_ANALYZING); - code = curl_easy_getinfo(j->curl, CURLINFO_RESPONSE_CODE, &status); + code = sym_curl_easy_getinfo(j->curl, CURLINFO_RESPONSE_CODE, &status); if (code != CURLE_OK) { - r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve response code: %s", curl_easy_strerror(code)); + r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve response code: %s", sym_curl_easy_strerror(code)); goto fail; } @@ -781,7 +781,7 @@ int pull_job_add_request_header(PullJob *j, const char *hdr) { if (j->request_header) { struct curl_slist *l; - l = curl_slist_append(j->request_header, hdr); + l = sym_curl_slist_append(j->request_header, hdr); if (!l) return -ENOMEM; @@ -824,29 +824,29 @@ int pull_job_begin(PullJob *j) { } if (j->request_header) { - if (curl_easy_setopt(j->curl, CURLOPT_HTTPHEADER, j->request_header) != CURLE_OK) + if (sym_curl_easy_setopt(j->curl, CURLOPT_HTTPHEADER, j->request_header) != CURLE_OK) return -EIO; } - if (curl_easy_setopt(j->curl, CURLOPT_WRITEFUNCTION, pull_job_write_callback) != CURLE_OK) + if (sym_curl_easy_setopt(j->curl, CURLOPT_WRITEFUNCTION, pull_job_write_callback) != CURLE_OK) return -EIO; - if (curl_easy_setopt(j->curl, CURLOPT_WRITEDATA, j) != CURLE_OK) + if (sym_curl_easy_setopt(j->curl, CURLOPT_WRITEDATA, j) != CURLE_OK) return -EIO; - if (curl_easy_setopt(j->curl, CURLOPT_HEADERFUNCTION, pull_job_header_callback) != CURLE_OK) + if (sym_curl_easy_setopt(j->curl, CURLOPT_HEADERFUNCTION, pull_job_header_callback) != CURLE_OK) return -EIO; - if (curl_easy_setopt(j->curl, CURLOPT_HEADERDATA, j) != CURLE_OK) + if (sym_curl_easy_setopt(j->curl, CURLOPT_HEADERDATA, j) != CURLE_OK) return -EIO; - if (curl_easy_setopt(j->curl, CURLOPT_XFERINFOFUNCTION, pull_job_progress_callback) != CURLE_OK) + if (sym_curl_easy_setopt(j->curl, CURLOPT_XFERINFOFUNCTION, pull_job_progress_callback) != CURLE_OK) return -EIO; - if (curl_easy_setopt(j->curl, CURLOPT_XFERINFODATA, j) != CURLE_OK) + if (sym_curl_easy_setopt(j->curl, CURLOPT_XFERINFODATA, j) != CURLE_OK) return -EIO; - if (curl_easy_setopt(j->curl, CURLOPT_NOPROGRESS, 0L) != CURLE_OK) + if (sym_curl_easy_setopt(j->curl, CURLOPT_NOPROGRESS, 0L) != CURLE_OK) return -EIO; r = curl_glue_add(j->glue, j->curl); diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c index 99de0fc93f57f..cd24dec34e348 100644 --- a/src/journal-remote/journal-upload.c +++ b/src/journal-remote/journal-upload.c @@ -188,16 +188,16 @@ int start_upload(Uploader *u, _cleanup_(curl_slist_free_allp) struct curl_slist *h = NULL; struct curl_slist *l; - h = curl_slist_append(NULL, "Content-Type: application/vnd.fdo.journal"); + h = sym_curl_slist_append(NULL, "Content-Type: application/vnd.fdo.journal"); if (!h) return log_oom(); - l = curl_slist_append(h, "Transfer-Encoding: chunked"); + l = sym_curl_slist_append(h, "Transfer-Encoding: chunked"); if (!l) return log_oom(); h = l; - l = curl_slist_append(h, "Accept: text/plain"); + l = sym_curl_slist_append(h, "Accept: text/plain"); if (!l) return log_oom(); h = l; @@ -207,7 +207,7 @@ int start_upload(Uploader *u, if (!header) return log_oom(); - l = curl_slist_append(h, header); + l = sym_curl_slist_append(h, header); if (!l) return log_oom(); h = l; @@ -229,7 +229,7 @@ int start_upload(Uploader *u, if (!header) return log_oom(); - l = curl_slist_append(h, header); + l = sym_curl_slist_append(h, header); if (!l) return log_oom(); h = l; @@ -241,7 +241,7 @@ int start_upload(Uploader *u, if (!u->easy) { _cleanup_(curl_easy_cleanupp) CURL *curl = NULL; - curl = curl_easy_init(); + curl = sym_curl_easy_init(); if (!curl) return log_error_errno(SYNTHETIC_ERRNO(ENOSR), "Call to curl_easy_init failed."); @@ -485,8 +485,10 @@ static int setup_uploader(Uploader *u, const char *url, const char *state_file) static void destroy_uploader(Uploader *u) { assert(u); - curl_easy_cleanup(u->easy); - curl_slist_free_all(u->header); + if (sym_curl_easy_cleanup) + sym_curl_easy_cleanup(u->easy); + if (sym_curl_slist_free_all) + sym_curl_slist_free_all(u->header); free(u->answer); free(u->last_cursor); @@ -527,7 +529,7 @@ static int update_content_encoding_header(Uploader *u, const CompressionConfig * /* If Content-Encoding header is not found, append new one. */ if (!found) { - struct curl_slist *l = curl_slist_append(u->header, header); + struct curl_slist *l = sym_curl_slist_append(u->header, header); if (!l) return log_oom(); u->header = l; @@ -543,7 +545,7 @@ static int update_content_encoding_header(Uploader *u, const CompressionConfig * else u->header = TAKE_PTR(l->next); - curl_slist_free_all(l); + sym_curl_slist_free_all(l); update_header = true; break; } @@ -573,7 +575,7 @@ static int parse_accept_encoding_header(Uploader *u) { return update_content_encoding_header(u, NULL); struct curl_header *header; - CURLHcode hcode = curl_easy_header(u->easy, "Accept-Encoding", 0, CURLH_HEADER, -1, &header); + CURLHcode hcode = sym_curl_easy_header(u->easy, "Accept-Encoding", 0, CURLH_HEADER, -1, &header); if (hcode != CURLHE_OK) goto not_found; @@ -622,7 +624,7 @@ static int perform_upload(Uploader *u) { assert(u); u->watchdog_timestamp = now(CLOCK_MONOTONIC); - code = curl_easy_perform(u->easy); + code = sym_curl_easy_perform(u->easy); if (code) { if (u->error[0]) return log_error_errno(SYNTHETIC_ERRNO(EIO), @@ -631,14 +633,14 @@ static int perform_upload(Uploader *u) { else return log_error_errno(SYNTHETIC_ERRNO(EIO), "Upload to %s failed: %s", - u->url, curl_easy_strerror(code)); + u->url, sym_curl_easy_strerror(code)); } - code = curl_easy_getinfo(u->easy, CURLINFO_RESPONSE_CODE, &status); + code = sym_curl_easy_getinfo(u->easy, CURLINFO_RESPONSE_CODE, &status); if (code) return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "Failed to retrieve response code: %s", - curl_easy_strerror(code)); + sym_curl_easy_strerror(code)); if (status >= 300) return log_error_errno(SYNTHETIC_ERRNO(EIO), @@ -922,6 +924,10 @@ static int run(int argc, char **argv) { if (r <= 0) return r; + r = dlopen_curl(); + if (r < 0) + return r; + r = compression_configs_mangle(&arg_compression); if (r < 0) return r; diff --git a/src/journal-remote/meson.build b/src/journal-remote/meson.build index 51261d000d9e4..c8d97526d378a 100644 --- a/src/journal-remote/meson.build +++ b/src/journal-remote/meson.build @@ -65,7 +65,7 @@ executables += [ 'sources' : systemd_journal_upload_sources, 'extract' : systemd_journal_upload_extract_sources, 'objects' : ['systemd-journal-remote'], - 'dependencies' : common_deps + [libcurl], + 'dependencies' : common_deps, }, test_template + { 'sources' : files('test-journal-header-util.c'), diff --git a/src/report/meson.build b/src/report/meson.build index 6e0c231be3245..5f05382999eb8 100644 --- a/src/report/meson.build +++ b/src/report/meson.build @@ -8,8 +8,6 @@ executables += [ 'report.c', 'report-upload.c', ), - 'link_with' : [libcurlutil_static, libshared], - 'dependencies' : [libcurl], }, libexec_template + { diff --git a/src/report/report-upload.c b/src/report/report-upload.c index 1744d0d91dda6..e70f8efa3430f 100644 --- a/src/report/report-upload.c +++ b/src/report/report-upload.c @@ -12,7 +12,6 @@ #if HAVE_LIBCURL #include "curl-util.h" -#include /* Sadly this fails if ordered first. */ #define SERVER_ANSWER_MAX (1*1024*1024u) @@ -86,6 +85,10 @@ int upload_collected(Context *context) { _cleanup_free_ char *json = NULL; int r; + r = dlopen_curl(); + if (r < 0) + return r; + { /* Convert our variant array to a JSON report. * We won't need the JSON structure again, so free it quickly. */ @@ -110,7 +113,7 @@ int upload_collected(Context *context) { if (r < 0) return log_error_errno(r, "Failed to create curl header: %m"); - _cleanup_(curl_easy_cleanupp) CURL *curl = curl_easy_init(); + _cleanup_(curl_easy_cleanupp) CURL *curl = sym_curl_easy_init(); if (!curl) return log_error_errno(SYNTHETIC_ERRNO(ENOSR), "Call to curl_easy_init failed."); @@ -173,18 +176,18 @@ int upload_collected(Context *context) { if (!easy_setopt(curl, LOG_ERR, CURLOPT_POSTFIELDS, json)) return -EXFULL; - CURLcode code = curl_easy_perform(curl); + CURLcode code = sym_curl_easy_perform(curl); if (code != CURLE_OK) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Upload to %s failed: %s", arg_url, - empty_to_null(&error[0]) ?: curl_easy_strerror(code)); + empty_to_null(&error[0]) ?: sym_curl_easy_strerror(code)); long status; - code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); + code = sym_curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); if (code != CURLE_OK) return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "Failed to retrieve response code: %s", - curl_easy_strerror(code)); + sym_curl_easy_strerror(code)); _cleanup_free_ char *ans = iovw_to_cstring(&context->upload_answer); if (!ans) diff --git a/src/shared/curl-util.c b/src/shared/curl-util.c index 405c083afc712..59f9f16ae3776 100644 --- a/src/shared/curl-util.c +++ b/src/shared/curl-util.c @@ -1,9 +1,14 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "curl-util.h" + +#if HAVE_LIBCURL + +#include "sd-dlopen.h" #include "sd-event.h" #include "alloc-util.h" -#include "curl-util.h" +#include "dlfcn-util.h" #include "fd-util.h" #include "hashmap.h" #include "log.h" @@ -12,6 +17,62 @@ #include "time-util.h" #include "version.h" +static void *curl_dl = NULL; + +DLSYM_PROTOTYPE(curl_easy_cleanup) = NULL; +DLSYM_PROTOTYPE(curl_easy_getinfo) = NULL; +DLSYM_PROTOTYPE(curl_easy_init) = NULL; +DLSYM_PROTOTYPE(curl_easy_perform) = NULL; +DLSYM_PROTOTYPE(curl_easy_setopt) = NULL; +DLSYM_PROTOTYPE(curl_easy_strerror) = NULL; +#if LIBCURL_VERSION_NUM >= 0x075300 +DLSYM_PROTOTYPE(curl_easy_header) = NULL; +#endif +DLSYM_PROTOTYPE(curl_getdate) = NULL; +DLSYM_PROTOTYPE(curl_multi_add_handle) = NULL; +DLSYM_PROTOTYPE(curl_multi_assign) = NULL; +DLSYM_PROTOTYPE(curl_multi_cleanup) = NULL; +DLSYM_PROTOTYPE(curl_multi_info_read) = NULL; +DLSYM_PROTOTYPE(curl_multi_init) = NULL; +DLSYM_PROTOTYPE(curl_multi_remove_handle) = NULL; +DLSYM_PROTOTYPE(curl_multi_setopt) = NULL; +DLSYM_PROTOTYPE(curl_multi_socket_action) = NULL; +DLSYM_PROTOTYPE(curl_slist_append) = NULL; +DLSYM_PROTOTYPE(curl_slist_free_all) = NULL; + +int dlopen_curl(void) { + SD_ELF_NOTE_DLOPEN( + "curl", + "Support for downloading and uploading files over HTTP", + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libcurl.so.4"); + + return dlopen_many_sym_or_warn( + &curl_dl, + "libcurl.so.4", + LOG_DEBUG, + DLSYM_ARG(curl_easy_cleanup), + DLSYM_ARG(curl_easy_getinfo), + DLSYM_ARG(curl_easy_init), + DLSYM_ARG(curl_easy_perform), + DLSYM_ARG(curl_easy_setopt), + DLSYM_ARG(curl_easy_strerror), +#if LIBCURL_VERSION_NUM >= 0x075300 + DLSYM_ARG(curl_easy_header), +#endif + DLSYM_ARG(curl_getdate), + DLSYM_ARG(curl_multi_add_handle), + DLSYM_ARG(curl_multi_assign), + DLSYM_ARG(curl_multi_cleanup), + DLSYM_ARG(curl_multi_info_read), + DLSYM_ARG(curl_multi_init), + DLSYM_ARG(curl_multi_remove_handle), + DLSYM_ARG(curl_multi_setopt), + DLSYM_ARG(curl_multi_socket_action), + DLSYM_ARG(curl_slist_append), + DLSYM_ARG(curl_slist_free_all)); +} + static void curl_glue_check_finished(CurlGlue *g) { int r; @@ -26,7 +87,7 @@ static void curl_glue_check_finished(CurlGlue *g) { CURLMsg *msg; int k = 0; - msg = curl_multi_info_read(g->curl, &k); + msg = sym_curl_multi_info_read(g->curl, &k); if (!msg) return; @@ -52,7 +113,7 @@ static int curl_glue_on_io(sd_event_source *s, int fd, uint32_t revents, void *u else action = 0; - if (curl_multi_socket_action(g->curl, fd, action, &k) != CURLM_OK) + if (sym_curl_multi_socket_action(g->curl, fd, action, &k) != CURLM_OK) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to propagate IO event."); @@ -105,7 +166,7 @@ static int curl_glue_socket_callback(CURL *curl, curl_socket_t s, int action, vo if (sd_event_add_io(g->event, &io, s, events, curl_glue_on_io, g) < 0) return -1; - if (curl_multi_assign(g->curl, s, io) != CURLM_OK) + if (sym_curl_multi_assign(g->curl, s, io) != CURLM_OK) return -1; (void) sd_event_source_set_description(io, "curl-io"); @@ -127,7 +188,7 @@ static int curl_glue_on_timer(sd_event_source *s, uint64_t usec, void *userdata) assert(s); - if (curl_multi_socket_action(g->curl, CURL_SOCKET_TIMEOUT, 0, &k) != CURLM_OK) + if (sym_curl_multi_socket_action(g->curl, CURL_SOCKET_TIMEOUT, 0, &k) != CURLM_OK) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to propagate timeout."); @@ -189,7 +250,7 @@ CurlGlue *curl_glue_unref(CurlGlue *g) { return NULL; if (g->curl) - curl_multi_cleanup(g->curl); + sym_curl_multi_cleanup(g->curl); while ((io = hashmap_steal_first(g->ios))) sd_event_source_unref(io); @@ -210,6 +271,10 @@ int curl_glue_new(CurlGlue **glue, sd_event *event) { assert(glue); + r = dlopen_curl(); + if (r < 0) + return r; + if (event) e = sd_event_ref(event); else { @@ -218,7 +283,7 @@ int curl_glue_new(CurlGlue **glue, sd_event *event) { return r; } - c = curl_multi_init(); + c = sym_curl_multi_init(); if (!c) return -ENOMEM; @@ -231,16 +296,16 @@ int curl_glue_new(CurlGlue **glue, sd_event *event) { .curl = TAKE_PTR(c), }; - if (curl_multi_setopt(g->curl, CURLMOPT_SOCKETDATA, g) != CURLM_OK) + if (sym_curl_multi_setopt(g->curl, CURLMOPT_SOCKETDATA, g) != CURLM_OK) return -EINVAL; - if (curl_multi_setopt(g->curl, CURLMOPT_SOCKETFUNCTION, curl_glue_socket_callback) != CURLM_OK) + if (sym_curl_multi_setopt(g->curl, CURLMOPT_SOCKETFUNCTION, curl_glue_socket_callback) != CURLM_OK) return -EINVAL; - if (curl_multi_setopt(g->curl, CURLMOPT_TIMERDATA, g) != CURLM_OK) + if (sym_curl_multi_setopt(g->curl, CURLMOPT_TIMERDATA, g) != CURLM_OK) return -EINVAL; - if (curl_multi_setopt(g->curl, CURLMOPT_TIMERFUNCTION, curl_glue_timer_callback) != CURLM_OK) + if (sym_curl_multi_setopt(g->curl, CURLMOPT_TIMERFUNCTION, curl_glue_timer_callback) != CURLM_OK) return -EINVAL; r = sd_event_add_defer(g->event, &g->defer, curl_glue_on_defer, g); @@ -257,45 +322,50 @@ int curl_glue_new(CurlGlue **glue, sd_event *event) { int curl_glue_make(CURL **ret, const char *url, void *userdata) { _cleanup_(curl_easy_cleanupp) CURL *c = NULL; const char *useragent; + int r; assert(ret); assert(url); - c = curl_easy_init(); + r = dlopen_curl(); + if (r < 0) + return r; + + c = sym_curl_easy_init(); if (!c) return -ENOMEM; if (DEBUG_LOGGING) - (void) curl_easy_setopt(c, CURLOPT_VERBOSE, 1L); + (void) sym_curl_easy_setopt(c, CURLOPT_VERBOSE, 1L); - if (curl_easy_setopt(c, CURLOPT_URL, url) != CURLE_OK) + if (sym_curl_easy_setopt(c, CURLOPT_URL, url) != CURLE_OK) return -EIO; - if (curl_easy_setopt(c, CURLOPT_PRIVATE, userdata) != CURLE_OK) + if (sym_curl_easy_setopt(c, CURLOPT_PRIVATE, userdata) != CURLE_OK) return -EIO; useragent = strjoina(program_invocation_short_name, "/" GIT_VERSION); - if (curl_easy_setopt(c, CURLOPT_USERAGENT, useragent) != CURLE_OK) + if (sym_curl_easy_setopt(c, CURLOPT_USERAGENT, useragent) != CURLE_OK) return -EIO; - if (curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK) + if (sym_curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK) return -EIO; - if (curl_easy_setopt(c, CURLOPT_NOSIGNAL, 1L) != CURLE_OK) + if (sym_curl_easy_setopt(c, CURLOPT_NOSIGNAL, 1L) != CURLE_OK) return -EIO; - if (curl_easy_setopt(c, CURLOPT_LOW_SPEED_TIME, 60L) != CURLE_OK) + if (sym_curl_easy_setopt(c, CURLOPT_LOW_SPEED_TIME, 60L) != CURLE_OK) return -EIO; - if (curl_easy_setopt(c, CURLOPT_LOW_SPEED_LIMIT, 30L) != CURLE_OK) + if (sym_curl_easy_setopt(c, CURLOPT_LOW_SPEED_LIMIT, 30L) != CURLE_OK) return -EIO; #if LIBCURL_VERSION_NUM >= 0x075500 /* libcurl 7.85.0 */ - if (curl_easy_setopt(c, CURLOPT_PROTOCOLS_STR, "HTTP,HTTPS,FILE") != CURLE_OK) + if (sym_curl_easy_setopt(c, CURLOPT_PROTOCOLS_STR, "HTTP,HTTPS,FILE") != CURLE_OK) #else - if (curl_easy_setopt(c, CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS|CURLPROTO_FILE) != CURLE_OK) + if (sym_curl_easy_setopt(c, CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS|CURLPROTO_FILE) != CURLE_OK) return -EIO; - if (curl_easy_setopt(c, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS) != CURLE_OK) + if (sym_curl_easy_setopt(c, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS) != CURLE_OK) #endif return -EIO; @@ -307,7 +377,7 @@ int curl_glue_add(CurlGlue *g, CURL *c) { assert(g); assert(c); - if (curl_multi_add_handle(g->curl, c) != CURLM_OK) + if (sym_curl_multi_add_handle(g->curl, c) != CURLM_OK) return -EIO; return 0; @@ -320,9 +390,9 @@ void curl_glue_remove_and_free(CurlGlue *g, CURL *c) { return; if (g->curl) - curl_multi_remove_handle(g->curl, c); + sym_curl_multi_remove_handle(g->curl, c); - curl_easy_cleanup(c); + sym_curl_easy_cleanup(c); } struct curl_slist *curl_slist_new(const char *first, ...) { @@ -332,7 +402,7 @@ struct curl_slist *curl_slist_new(const char *first, ...) { if (!first) return NULL; - l = curl_slist_append(NULL, first); + l = sym_curl_slist_append(NULL, first); if (!l) return NULL; @@ -346,10 +416,10 @@ struct curl_slist *curl_slist_new(const char *first, ...) { if (!i) break; - n = curl_slist_append(l, i); + n = sym_curl_slist_append(l, i); if (!n) { va_end(ap); - curl_slist_free_all(l); + sym_curl_slist_free_all(l); return NULL; } @@ -397,7 +467,7 @@ int curl_parse_http_time(const char *t, usec_t *ret) { assert(t); assert(ret); - time_t v = curl_getdate(t, NULL); + time_t v = sym_curl_getdate(t, NULL); if (v == (time_t) -1) return -EINVAL; @@ -416,7 +486,7 @@ int curl_append_to_header(struct curl_slist **list, char **headers) { assert(list); STRV_FOREACH(h, headers) { - struct curl_slist *l = curl_slist_append(*list, *h); + struct curl_slist *l = sym_curl_slist_append(*list, *h); if (!l) return -ENOMEM; *list = l; @@ -424,3 +494,5 @@ int curl_append_to_header(struct curl_slist **list, char **headers) { return 0; } + +#endif diff --git a/src/shared/curl-util.h b/src/shared/curl-util.h index 03b6ba7d21451..4ca3faf602828 100644 --- a/src/shared/curl-util.h +++ b/src/shared/curl-util.h @@ -1,22 +1,44 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include - #include "shared-forward.h" -#define easy_setopt(curl, log_level, opt, value) ({ \ - CURLcode code = curl_easy_setopt(ASSERT_PTR(curl), opt, value); \ - if (code) \ - log_full(log_level, \ - "curl_easy_setopt %s failed: %s", \ - #opt, curl_easy_strerror(code)); \ - code == CURLE_OK; \ -}) +#if HAVE_LIBCURL +#include /* IWYU pragma: export */ + +#include "dlfcn-util.h" + +extern DLSYM_PROTOTYPE(curl_easy_cleanup); +extern DLSYM_PROTOTYPE(curl_easy_getinfo); +extern DLSYM_PROTOTYPE(curl_easy_init); +extern DLSYM_PROTOTYPE(curl_easy_perform); +extern DLSYM_PROTOTYPE(curl_easy_setopt); +extern DLSYM_PROTOTYPE(curl_easy_strerror); +#if LIBCURL_VERSION_NUM >= 0x075300 +extern DLSYM_PROTOTYPE(curl_easy_header); +#endif +extern DLSYM_PROTOTYPE(curl_getdate); +extern DLSYM_PROTOTYPE(curl_multi_add_handle); +extern DLSYM_PROTOTYPE(curl_multi_assign); +extern DLSYM_PROTOTYPE(curl_multi_cleanup); +extern DLSYM_PROTOTYPE(curl_multi_info_read); +extern DLSYM_PROTOTYPE(curl_multi_init); +extern DLSYM_PROTOTYPE(curl_multi_remove_handle); +extern DLSYM_PROTOTYPE(curl_multi_setopt); +extern DLSYM_PROTOTYPE(curl_multi_socket_action); +extern DLSYM_PROTOTYPE(curl_slist_append); +extern DLSYM_PROTOTYPE(curl_slist_free_all); + +int dlopen_curl(void); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(CURL*, curl_easy_cleanup, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(CURLM*, curl_multi_cleanup, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct curl_slist*, curl_slist_free_all, NULL); +#define easy_setopt(curl, log_level, opt, value) ({ \ + CURLcode code = sym_curl_easy_setopt(ASSERT_PTR(curl), opt, value); \ + if (code) \ + log_full(log_level, \ + "curl_easy_setopt %s failed: %s", \ + #opt, sym_curl_easy_strerror(code)); \ + code == CURLE_OK; \ +}) typedef struct CurlGlue CurlGlue; @@ -44,3 +66,15 @@ struct curl_slist *curl_slist_new(const char *first, ...) _sentinel_; int curl_header_strdup(const void *contents, size_t sz, const char *field, char **value); int curl_parse_http_time(const char *t, usec_t *ret); int curl_append_to_header(struct curl_slist **list, char **headers); + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(CURL*, sym_curl_easy_cleanup, curl_easy_cleanupp, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(CURLM*, sym_curl_multi_cleanup, curl_multi_cleanupp, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct curl_slist*, sym_curl_slist_free_all, curl_slist_free_allp, NULL); + +#else + +static inline int dlopen_curl(void) { + return -EOPNOTSUPP; +} + +#endif diff --git a/src/shared/meson.build b/src/shared/meson.build index d99f0f24838b0..0e45584c6dd15 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -53,6 +53,7 @@ shared_sources = files( 'cryptsetup-fido2.c', 'cryptsetup-tpm2.c', 'cryptsetup-util.c', + 'curl-util.c', 'daemon-util.c', 'data-fd-util.c', 'dev-setup.c', @@ -396,6 +397,7 @@ libshared_deps = [threads, libbpf_cflags, libcrypt_cflags, libcryptsetup_cflags, + libcurl_cflags, libdl, libdw_cflags, libelf_cflags, @@ -459,18 +461,3 @@ libshared_fdisk = static_library( userspace], c_args : ['-fvisibility=default'], build_by_default : false) - -# A small shared file that is linked into a few places. -# It is not part of libshared because this code needs libcurl and -# we don't want to link libshared to libcurl. -if conf.get('HAVE_LIBCURL') == 1 - libcurlutil_static = static_library( - 'curl-util', - 'curl-util.c', - implicit_include_directories : false, - dependencies : [userspace, libcurl], - include_directories : includes, - build_by_default : false) -else - libcurlutil_static = [] -endif diff --git a/src/test/test-dlopen-so.c b/src/test/test-dlopen-so.c index 89d211263058f..a1f9212e37807 100644 --- a/src/test/test-dlopen-so.c +++ b/src/test/test-dlopen-so.c @@ -6,6 +6,7 @@ #include "bpf-dlopen.h" #include "compress.h" #include "cryptsetup-util.h" +#include "curl-util.h" #include "elf-util.h" #include "gcrypt-util.h" #include "idn-util.h" @@ -45,6 +46,7 @@ static int run(int argc, char **argv) { ASSERT_DLOPEN(dlopen_bzip2, HAVE_BZIP2); ASSERT_DLOPEN(dlopen_bpf, HAVE_LIBBPF); ASSERT_DLOPEN(dlopen_cryptsetup, HAVE_LIBCRYPTSETUP); + ASSERT_DLOPEN(dlopen_curl, HAVE_LIBCURL); ASSERT_DLOPEN(dlopen_dw, HAVE_ELFUTILS); ASSERT_DLOPEN(dlopen_elf, HAVE_ELFUTILS); ASSERT_DLOPEN(dlopen_gcrypt, HAVE_GCRYPT); From 2251f726b3d9839fcc7cc026ef3b5891ea0bced4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 20 Apr 2026 17:49:31 +0200 Subject: [PATCH 1134/1296] bootctl: add --print-efi-architecture switch This is extremely useful for our own test cases, since acquiring the right EFI architecture string is otherwise a bit nasty. --- man/bootctl.xml | 9 +++++++++ shell-completion/bash/bootctl | 1 + shell-completion/zsh/_bootctl | 1 + src/bootctl/bootctl.c | 15 +++++++++++++-- src/bootctl/bootctl.h | 1 + test/units/TEST-87-AUX-UTILS-VM.bootctl.sh | 7 +++++++ 6 files changed, 32 insertions(+), 2 deletions(-) diff --git a/man/bootctl.xml b/man/bootctl.xml index 5f53023c83d27..558891eaa1169 100644 --- a/man/bootctl.xml +++ b/man/bootctl.xml @@ -406,6 +406,15 @@ + + + + Print the EFI architecture string of the local firmware. This is useful to + generically format filenames such as bootx64.efi that + include the local firmware architecture in the name. + + + Controls whether to touch the firmware's boot loader list stored in EFI variables, diff --git a/shell-completion/bash/bootctl b/shell-completion/bash/bootctl index 31aaa1caceebd..d7714731c2aac 100644 --- a/shell-completion/bash/bootctl +++ b/shell-completion/bash/bootctl @@ -35,6 +35,7 @@ _bootctl() { [STANDALONE]='-h --help --version -p --print-esp-path -x --print-boot-path --print-loader-path --print-stub-path -R --print-root-device -RR + --print-efi-architecture --no-pager --graceful -q --quiet --all-architectures --dry-run' [ARG]='--esp-path --boot-path --root --image --image-policy --install-source diff --git a/shell-completion/zsh/_bootctl b/shell-completion/zsh/_bootctl index 447612856197f..f7ed2a8e4148a 100644 --- a/shell-completion/zsh/_bootctl +++ b/shell-completion/zsh/_bootctl @@ -74,6 +74,7 @@ _arguments \ '--boot-path=[Path to the $BOOT partition]:path:_directories' \ '(-p --print-esp-path)'{-p,--print-esp-path}'[Print path to the EFI system partition]' \ '(-x --print-boot-path)'{-x,--print-boot-path}'[Print path to the $BOOT partition]' \ + '--print-efi-architecture[Print the EFI architecture identifier]' \ '--make-machine-id-directory=[Control creation and deletion of the top-level machine ID directory.]:options:(yes no auto)' \ '--no-pager[Do not pipe output into a pager]' \ '--graceful[Do not fail when locating ESP or writing fails]' \ diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index 239a0c9273073..308ff6a106d65 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -15,6 +15,7 @@ #include "bootctl-status.h" #include "bootctl-uki.h" #include "bootctl-unlink.h" +#include "bootctl-util.h" #include "build.h" #include "devnum-util.h" #include "dissect-image.h" @@ -52,6 +53,7 @@ bool arg_print_esp_path = false; bool arg_print_dollar_boot_path = false; bool arg_print_loader_path = false; bool arg_print_stub_path = false; +bool arg_print_efi_architecture = false; unsigned arg_print_root_device = 0; int arg_touch_variables = -1; bool arg_install_random_seed = true; @@ -450,6 +452,10 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { arg_print_root_device++; break; + OPTION_LONG("print-efi-architecture", NULL, "Print the local EFI architecture string"): + arg_print_efi_architecture = true; + break; + OPTION_GROUP("Options"): break; @@ -639,9 +645,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { char **args = option_parser_get_args(&state); - if (!!arg_print_esp_path + !!arg_print_dollar_boot_path + (arg_print_root_device > 0) + arg_print_loader_path + arg_print_stub_path > 1) + if (!!arg_print_esp_path + !!arg_print_dollar_boot_path + (arg_print_root_device > 0) + arg_print_loader_path + arg_print_stub_path + arg_print_efi_architecture > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--print-esp-path/-p, --print-boot-path/-x, --print-root-device=/-R, --print-loader-path, --print-stub-path cannot be combined."); + "--print-esp-path/-p, --print-boot-path/-x, --print-root-device=/-R, --print-loader-path, --print-stub-path, --print-efi-architecture cannot be combined."); if ((arg_root || arg_image) && args[0] && !STR_IN_SET(args[0], "status", "list", "install", "update", "remove", "is-installed", "random-seed", "unlink", "cleanup")) @@ -761,6 +767,11 @@ static int run(int argc, char *argv[]) { if (arg_print_loader_path || arg_print_stub_path) return print_loader_or_stub_path(); + if (arg_print_efi_architecture) { + printf("%s\n", get_efi_arch()); + return 0; + } + /* Open up and mount the image */ if (arg_image) { assert(!arg_root); diff --git a/src/bootctl/bootctl.h b/src/bootctl/bootctl.h index d3d6583c0241d..d0daab9dd12b3 100644 --- a/src/bootctl/bootctl.h +++ b/src/bootctl/bootctl.h @@ -24,6 +24,7 @@ extern bool arg_print_dollar_boot_path; extern bool arg_print_loader_path; extern bool arg_print_stub_path; extern unsigned arg_print_root_device; +extern bool arg_print_efi_architecture; extern int arg_touch_variables; extern bool arg_install_random_seed; extern PagerFlags arg_pager_flags; diff --git a/test/units/TEST-87-AUX-UTILS-VM.bootctl.sh b/test/units/TEST-87-AUX-UTILS-VM.bootctl.sh index 7d26541ffa6f4..440c3e5edfbcc 100755 --- a/test/units/TEST-87-AUX-UTILS-VM.bootctl.sh +++ b/test/units/TEST-87-AUX-UTILS-VM.bootctl.sh @@ -101,7 +101,14 @@ basic_tests() { testcase_bootctl_basic() { assert_in "$(bootctl --print-esp-path)" "^(/boot/|/efi)$" assert_in "$(bootctl --print-boot-path)" "^(/boot/|/efi)$" + bootctl --print-root-device + bootctl --print-root-device --print-root-device + bootctl --print-esp-path + bootctl --print-boot-path + bootctl --print-loader-path + bootctl --print-stub-path + bootctl --print-efi-architecture basic_tests } From db3bfa0c4cd7f5246ca73d399c8f9e2e337bd313 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Mon, 20 Apr 2026 18:02:42 +0100 Subject: [PATCH 1135/1296] sysupdate: Emit READY=1 status when installing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `READY=1` is already correctly emitted when acquiring an update, but was forgotten to be emitted when subsequently installing that update. That meant that the state tracking code in `sysupdated` and hence `updatectl` could not properly report the progress of the install operation, and hence printed “Already up-to-date” after a successful update installation, rather than “Done”. Add a test to catch this in future. Signed-off-by: Philip Withnall Fixes: https://github.com/systemd/systemd/issues/41502 --- src/sysupdate/sysupdate.c | 4 +++- test/units/TEST-72-SYSUPDATE.sh | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 9c469fbe856c4..76b6507f9a438 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -1202,7 +1202,9 @@ static int context_install( } (void) sd_notifyf(/* unset_environment=*/ false, - "STATUS=Installing '%s'.", us->version); + "READY=1\n" + "X_SYSUPDATE_VERSION=%s\n" + "STATUS=Installing '%s'.", us->version, us->version); for (size_t i = 0; i < c->n_transfers; i++) { Instance *inst = us->instances[i]; diff --git a/test/units/TEST-72-SYSUPDATE.sh b/test/units/TEST-72-SYSUPDATE.sh index b929485bd5b6c..27268c250b5e6 100755 --- a/test/units/TEST-72-SYSUPDATE.sh +++ b/test/units/TEST-72-SYSUPDATE.sh @@ -373,7 +373,9 @@ EOF if $have_updatectl; then systemctl start systemd-sysupdated "$SYSUPDATE" --verify=no check-new - updatectl update + 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" From 038aaba475719cbb1ba63b78a16ca87e6df51598 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 15 Apr 2026 22:55:59 +0000 Subject: [PATCH 1136/1296] fs-util: teach xopenat_full() about XO_SOCKET and XO_TRIGGER_AUTOMOUNT XO_SOCKET asks for the opened inode to be verified as a socket, analogous to the existing XO_REGULAR. A new fd_verify_socket() helper is added to stat-util to perform the check on an opened fd. XO_TRIGGER_AUTOMOUNT asks O_PATH opens to trigger automounts by going via open_tree() without OPEN_TREE_CLONE, falling back to openat() if the kernel or sandbox rejects open_tree(). --- src/basic/fs-util.c | 83 +++++++++++++++++++++++++++++++++++++++-- src/basic/fs-util.h | 10 +++-- src/basic/stat-util.c | 7 ++++ src/basic/stat-util.h | 1 + src/test/test-fs-util.c | 60 +++++++++++++++++++++++++++++ 5 files changed, 154 insertions(+), 7 deletions(-) diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index f790ca4e136b5..d041a9afcff77 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "alloc-util.h" @@ -1130,6 +1131,45 @@ int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, b } } +static int openat_with_automount(int dir_fd, const char *path, int open_flags, mode_t mode) { + /* When XO_TRIGGER_AUTOMOUNT is set we want to trigger automounts on the path. open() with O_PATH + * does not do that, so we use open_tree() without OPEN_TREE_CLONE which is equivalent to open() with + * O_PATH except that it does trigger automounts. Some sandboxes reject open_tree() with EPERM or + * ENOSYS, in which case we fall back to plain openat(): autofs wouldn't work inside a restricted + * mount namespace anyway. */ + + static bool can_open_tree = true; + int r; + + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(path); + + if (can_open_tree) { + r = RET_NERRNO(open_tree(dir_fd, path, + OPEN_TREE_CLOEXEC | + (FLAGS_SET(open_flags, O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0))); + if (r >= 0) { + /* open_tree() doesn't honor O_DIRECTORY, so enforce it ourselves to match + * the openat() fallback's behavior. */ + if (FLAGS_SET(open_flags, O_DIRECTORY)) { + int q = fd_verify_directory(r); + if (q < 0) { + safe_close(r); + return q; + } + } + + return r; + } + if (r != -EPERM && !ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return r; + + can_open_tree = false; + } + + return RET_NERRNO(openat(dir_fd, path, open_flags, mode)); +} + int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode) { _cleanup_close_ int fd = -EBADF; bool made_dir = false, made_file = false; @@ -1137,8 +1177,19 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); - /* An inode cannot be both a directory and a regular file at the same time. */ + /* An inode can only be one of a directory, a regular file or a socket at the same time. */ assert(!(FLAGS_SET(open_flags, O_DIRECTORY) && FLAGS_SET(xopen_flags, XO_REGULAR))); + assert(!(FLAGS_SET(xopen_flags, XO_REGULAR) && FLAGS_SET(xopen_flags, XO_SOCKET))); + assert(!(FLAGS_SET(open_flags, O_DIRECTORY) && FLAGS_SET(xopen_flags, XO_SOCKET))); + /* Sockets cannot be open()ed, only pinned via O_PATH. */ + assert(!FLAGS_SET(xopen_flags, XO_SOCKET) || FLAGS_SET(open_flags, O_PATH)); + /* XO_TRIGGER_AUTOMOUNT requires O_PATH and does not support creating inodes. XO_SUBVOLUME + * requires O_CREAT, and XO_NOCOW needs a writable fd for its chattr ioctl, so neither is + * compatible with XO_TRIGGER_AUTOMOUNT. */ + assert(!FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT) || + (FLAGS_SET(open_flags, O_PATH) && !FLAGS_SET(open_flags, O_CREAT))); + assert(!(FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT) && FLAGS_SET(xopen_flags, XO_SUBVOLUME))); + assert(!(FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT) && FLAGS_SET(xopen_flags, XO_NOCOW))); /* This is like openat(), but has a few tricks up its sleeves, extending behaviour: * @@ -1153,6 +1204,10 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ * * • if XO_REGULAR is specified will return an error if inode is not a regular file. * + * • if XO_SOCKET is specified will return an error if inode is not a socket. + * + * • if XO_TRIGGER_AUTOMOUNT is specified O_PATH fds will trigger automounts. + * * • If mode is specified as MODE_INVALID, we'll use 0755 for dirs, and 0644 for regular files. * * • The dir fd can be passed as XAT_FDROOT, in which case any relative paths will be taken relative to the root fs. @@ -1170,6 +1225,12 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ return r; } + if (FLAGS_SET(xopen_flags, XO_SOCKET)) { + r = fd_verify_socket(dir_fd); + if (r < 0) + return r; + } + return fd_reopen(dir_fd, open_flags & ~O_NOFOLLOW); } @@ -1217,9 +1278,11 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ * first */ if (FLAGS_SET(open_flags, O_PATH)) { - fd = openat(dir_fd, path, open_flags, mode); + fd = FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT) ? + openat_with_automount(dir_fd, path, open_flags, mode) : + RET_NERRNO(openat(dir_fd, path, open_flags, mode)); if (fd < 0) { - r = -errno; + r = fd; goto error; } @@ -1266,7 +1329,15 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ } } } + } else if (FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT)) { + fd = openat_with_automount(dir_fd, path, open_flags, mode); + if (fd < 0) { + r = fd; + goto error; + } } else { + /* XO_SOCKET also lands here: it requires O_PATH (see asserts above) so openat() pins + * the inode without connecting, and fd_verify_socket() below enforces the type. */ fd = openat_report_new(dir_fd, path, open_flags, mode, &made_file); if (fd < 0) { r = fd; @@ -1274,6 +1345,12 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ } } + if (FLAGS_SET(xopen_flags, XO_SOCKET)) { + r = fd_verify_socket(fd); + if (r < 0) + goto error; + } + if (call_label_ops_post) { call_label_ops_post = false; diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index d75c253dbb46a..c33a084d3fdbf 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -109,10 +109,12 @@ int posix_fallocate_loop(int fd, uint64_t offset, uint64_t size); int parse_cifs_service(const char *s, char **ret_host, char **ret_service, char **ret_path); typedef enum XOpenFlags { - XO_LABEL = 1 << 0, /* When creating: relabel */ - XO_SUBVOLUME = 1 << 1, /* When creating as directory: make it a subvolume */ - XO_NOCOW = 1 << 2, /* Enable NOCOW mode after opening */ - XO_REGULAR = 1 << 3, /* Fail if the inode is not a regular file */ + XO_LABEL = 1 << 0, /* When creating: relabel */ + XO_SUBVOLUME = 1 << 1, /* When creating as directory: make it a subvolume */ + XO_NOCOW = 1 << 2, /* Enable NOCOW mode after opening */ + XO_REGULAR = 1 << 3, /* Fail if the inode is not a regular file */ + XO_SOCKET = 1 << 4, /* Fail if the inode is not a socket */ + XO_TRIGGER_AUTOMOUNT = 1 << 5, /* Trigger automounts via open_tree(). Requires O_PATH. */ } XOpenFlags; int open_mkdir_at_full(int dirfd, const char *path, int flags, XOpenFlags xopen_flags, mode_t mode); diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index e0dc59a863bad..bbfb8a9d1879b 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -181,6 +181,13 @@ int statx_verify_socket(const struct statx *stx) { return mode_verify_socket(stx->stx_mode); } +int fd_verify_socket(int fd) { + if (IN_SET(fd, AT_FDCWD, XAT_FDROOT)) + return -EISDIR; + + return verify_stat_at(fd, /* path= */ NULL, /* follow= */ false, stat_verify_socket, /* verify= */ true); +} + int is_socket(const char *path) { assert(!isempty(path)); return verify_stat_at(AT_FDCWD, path, /* follow= */ true, stat_verify_socket, /* verify= */ false); diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index de9ee03f44034..267d6ed7b410c 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -23,6 +23,7 @@ int is_symlink(const char *path); int stat_verify_socket(const struct stat *st); int statx_verify_socket(const struct statx *stx); +int fd_verify_socket(int fd); int is_socket(const char *path); int stat_verify_linked(const struct stat *st); diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index 7cb720938ccef..d04fbc6768b20 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -16,6 +16,7 @@ #include "process-util.h" #include "random-util.h" #include "rm-rf.h" +#include "socket-util.h" #include "stat-util.h" #include "string-util.h" #include "strv.h" @@ -740,6 +741,65 @@ TEST(xopenat_regular) { assert_se(unlink("/tmp/xopenat-regular-test") >= 0); } +TEST(xopenat_socket) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int tfd = -EBADF, fd = -EBADF; + + ASSERT_OK(tfd = mkdtemp_open(NULL, 0, &t)); + + /* Create a Unix domain socket via bind(). */ + fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); + ASSERT_OK(fd); + + const char *sockpath = strjoina(t, "/test.sock"); + union sockaddr_union sa = { .un.sun_family = AF_UNIX }; + strncpy(sa.un.sun_path, sockpath, sizeof(sa.un.sun_path) - 1); + ASSERT_OK_ERRNO(bind(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sockpath) + 1)); + fd = safe_close(fd); + + /* XO_SOCKET requires O_PATH. */ + fd = xopenat_full(tfd, "test.sock", O_PATH|O_CLOEXEC, XO_SOCKET, 0); + ASSERT_OK(fd); + fd = safe_close(fd); + + /* Reopen via empty path should also work. */ + fd = ASSERT_OK(xopenat_full(tfd, "test.sock", O_PATH|O_CLOEXEC, 0, 0)); + _cleanup_close_ int fd2 = xopenat_full(fd, NULL, O_PATH|O_CLOEXEC, XO_SOCKET, 0); + ASSERT_OK(fd2); + fd = safe_close(fd); + + /* Non-socket inodes must be rejected. */ + ASSERT_OK_ERRNO(mkdirat(tfd, "dir", 0755)); + ASSERT_ERROR(xopenat_full(tfd, "dir", O_PATH|O_CLOEXEC, XO_SOCKET, 0), EISDIR); + + fd = ASSERT_OK_ERRNO(openat(tfd, "reg", O_CREAT|O_CLOEXEC, 0600)); + fd = safe_close(fd); + ASSERT_ERROR(xopenat_full(tfd, "reg", O_PATH|O_CLOEXEC, XO_SOCKET, 0), ENOTSOCK); + + /* Reopen via empty path of a non-socket fd must also be rejected. */ + fd = ASSERT_OK(xopenat_full(tfd, "reg", O_PATH|O_CLOEXEC, 0, 0)); + ASSERT_ERROR(xopenat_full(fd, NULL, O_PATH|O_CLOEXEC, XO_SOCKET, 0), ENOTSOCK); + fd = safe_close(fd); + + fd = ASSERT_OK(xopenat_full(tfd, "dir", O_PATH|O_CLOEXEC, 0, 0)); + ASSERT_ERROR(xopenat_full(fd, NULL, O_PATH|O_CLOEXEC, XO_SOCKET, 0), EISDIR); + fd = safe_close(fd); +} + +TEST(xopenat_trigger_automount) { + _cleanup_close_ int fd = -EBADF; + + /* We can't easily set up an autofs mount in a test, but we can verify that + * XO_TRIGGER_AUTOMOUNT works on a regular path and produces the same inode as a + * plain O_PATH open. */ + fd = xopenat_full(AT_FDCWD, "/usr", O_PATH|O_CLOEXEC|O_DIRECTORY, XO_TRIGGER_AUTOMOUNT, 0); + ASSERT_OK(fd); + + _cleanup_close_ int fd2 = xopenat_full(AT_FDCWD, "/usr", O_PATH|O_CLOEXEC|O_DIRECTORY, 0, 0); + ASSERT_OK(fd2); + ASSERT_OK_POSITIVE(fd_inode_same(fd, fd2)); +} + TEST(xopenat_lock_full) { _cleanup_(rm_rf_physical_and_freep) char *t = NULL; _cleanup_close_ int tfd = -EBADF, fd = -EBADF; From 79862d33a059a914bd90313b7cdf4fb731a4de34 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 15 Apr 2026 22:51:46 +0000 Subject: [PATCH 1137/1296] chase: add explicit root_fd parameter to chaseat() and drop CHASE_AT_RESOLVE_IN_ROOT Split the single directory fd that chaseat() used to take into two separate fds: a root_fd that sets the chroot boundary (symlinks may not escape it, absolute symlinks resolve relative to it), and a dir_fd that path resolution starts from. This makes the chroot semantics of chaseat() explicit at every call site instead of encoding them in the CHASE_AT_RESOLVE_IN_ROOT flag, which is removed. It also decouples the starting directory from the root boundary, so callers can descend from any inode inside the tree without having to reopen the root separately. XAT_FDROOT passed as root_fd means "no containment" (host root); as dir_fd it means "start at root_fd". For a smoother transition, AT_FDCWD is also accepted as root_fd and treated as XAT_FDROOT. When root_fd points to a directory that is actually the host root, it is normalized to XAT_FDROOT up front so the existing shortcut path can kick in. Absolute paths returned by chaseat() are now relative to root_fd, and relative paths are relative to dir_fd. The result is absolute only when there is no chroot boundary (root_fd is XAT_FDROOT), or when an absolute symlink made resolution jump out of the dir_fd subtree; otherwise callers get a relative path they can feed straight back into an openat()-style call against dir_fd. Specifically, when dir_fd == root_fd and we're not operating on the host's root directory, we return a relative path even if we received an absolute path or resolved an absolute symlink to allow passing the path directly to openat() style functions. We do this to not have to go modify every caller of chaseat() to make sure they deal properly with any absolute paths they might receive. Only when root_fd != dir_fd do we have to return an absolute path to indicate that the path is relative to root_fd and not dir_fd. The shortcut that skips the per-component walk is reworked around a new chase_xopenat() helper that funnels CHASE_NOFOLLOW, CHASE_MUST_BE_* and CHASE_TRIGGER_AUTOFS through xopenat_full()'s O_NOFOLLOW, O_DIRECTORY, XO_REGULAR, XO_SOCKET and XO_TRIGGER_AUTOMOUNT flags. As a result these flags no longer force us off the shortcut and can be dropped from CHASE_NO_SHORTCUT_MASK, and the old openat_opath_with_automount() helper goes away. A CHASE_MUST_BE_ANY alias is introduced for shortcut callers (stat/access paths) that don't go through xopenat_full() and still need to bail on those flags locally. All *_and_* helpers built on top of chaseat() (chase_and_openat, chase_and_opendirat, chase_and_statat, chase_and_accessat, chase_and_fopenat_unlocked, chase_and_unlinkat, chase_and_open_parent_at) gain the same root_fd parameter, and every call site in the tree is ported to the new signature. chase_and_open() is also fixed to correctly handle CHASE_EXTRACT_FILENAME without CHASE_PARENT. --- src/basic/chase.c | 526 +++++++++---------- src/basic/chase.h | 32 +- src/basic/conf-files.c | 8 +- src/basic/fileio.c | 3 +- src/basic/mkdir.c | 2 +- src/basic/os-util.c | 8 +- src/basic/user-util.c | 2 +- src/bootctl/bootctl-install.c | 51 +- src/core/exec-invoke.c | 2 +- src/core/service.c | 2 +- src/firstboot/firstboot.c | 32 +- src/kernel-install/kernel-install.c | 20 +- src/libsystemd/sd-id128/sd-id128.c | 2 +- src/mountfsd/mountwork.c | 2 +- src/portable/portable.c | 2 +- src/shared/boot-entry.c | 2 +- src/shared/bootspec.c | 2 +- src/shared/btrfs-util.c | 4 +- src/shared/conf-parser.c | 4 +- src/shared/discover-image.c | 8 +- src/shared/find-esp.c | 8 +- src/shared/mstack.c | 4 +- src/shared/tar-util.c | 8 +- src/shared/tests.h | 12 + src/shared/vpick.c | 7 +- src/sysext/sysext.c | 2 +- src/test/test-chase.c | 767 +++++++++++++++++++--------- src/tpm2-setup/tpm2-swtpm.c | 1 + 28 files changed, 874 insertions(+), 649 deletions(-) diff --git a/src/basic/chase.c b/src/basic/chase.c index 5abb4bc4307bb..50ab4e3c47495 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include "alloc-util.h" @@ -18,18 +17,59 @@ #include "strv.h" #include "user-util.h" +/* Flags that prevent us from taking any of the early shortcuts: either they change the path resolution + * semantics (e.g. CHASE_NONEXISTENT, CHASE_PARENT, CHASE_STEP) or ask for per-component validation that a + * single open() cannot provide (e.g. CHASE_SAFE, CHASE_NO_AUTOFS, CHASE_PROHIBIT_SYMLINKS). + * + * Notably, the following are *not* listed here: + * - CHASE_TRIGGER_AUTOFS: plain open() already triggers automounts, and O_PATH shortcuts can use + * XO_TRIGGER_AUTOMOUNT to tell xopenat_full() to use open_tree() instead. + * - CHASE_MUST_BE_{DIRECTORY,REGULAR,SOCKET}: xopenat_full() can enforce these via O_DIRECTORY, + * XO_REGULAR and XO_SOCKET. Shortcut callers that don't go through xopenat_full() (stat/access + * paths) must include CHASE_MUST_BE_ANY in their local mask to still bail on these. */ #define CHASE_NO_SHORTCUT_MASK \ (CHASE_NONEXISTENT | \ CHASE_NO_AUTOFS | \ - CHASE_TRIGGER_AUTOFS | \ CHASE_SAFE | \ CHASE_STEP | \ CHASE_PROHIBIT_SYMLINKS | \ CHASE_PARENT | \ - CHASE_MKDIR_0755 | \ - CHASE_MUST_BE_DIRECTORY | \ - CHASE_MUST_BE_REGULAR | \ - CHASE_MUST_BE_SOCKET) + CHASE_MKDIR_0755) + +#define CHASE_MUST_BE_ANY \ + (CHASE_MUST_BE_DIRECTORY|CHASE_MUST_BE_REGULAR|CHASE_MUST_BE_SOCKET) + +static int chase_statx(int fd, struct statx *ret) { + return xstatx_full(fd, + /* path= */ NULL, + /* statx_flags= */ 0, + XSTATX_MNT_ID_BEST, + STATX_TYPE|STATX_UID|STATX_INO, + /* optional_mask= */ 0, + /* mandatory_attributes= */ 0, + ret); +} + +static int chase_xopenat(int dir_fd, const char *path, ChaseFlags chase_flags, int open_flags, XOpenFlags xopen_flags) { + /* Wrapper around xopenat_full() that translates CHASE_NOFOLLOW, CHASE_MUST_BE_* and + * CHASE_TRIGGER_AUTOFS into their xopenat_full() counterparts. Used by shortcuts that want to open + * the final target of a chase operation: they all want O_NOFOLLOW honoured, MUST_BE_* verified on + * the opened inode, and automounts triggered if requested. */ + + if (FLAGS_SET(chase_flags, CHASE_NOFOLLOW)) + open_flags |= O_NOFOLLOW; + if (FLAGS_SET(chase_flags, CHASE_MUST_BE_DIRECTORY)) + open_flags |= O_DIRECTORY; + if (FLAGS_SET(chase_flags, CHASE_MUST_BE_REGULAR)) + xopen_flags |= XO_REGULAR; + if (FLAGS_SET(chase_flags, CHASE_MUST_BE_SOCKET)) + xopen_flags |= XO_SOCKET; + /* Only needed for O_PATH since plain open() already triggers automounts */ + if (FLAGS_SET(chase_flags, CHASE_TRIGGER_AUTOFS) && FLAGS_SET(open_flags, O_PATH)) + xopen_flags |= XO_TRIGGER_AUTOMOUNT; + + return xopenat_full(dir_fd, path, open_flags, xopen_flags, MODE_INVALID); +} static bool uid_unsafe_transition(uid_t a, uid_t b) { /* Returns true if the transition from a to b is safe, i.e. that we never transition from @@ -108,95 +148,40 @@ static int log_prohibited_symlink(int fd, ChaseFlags flags) { strna(n1)); } -static int openat_opath_with_automount(int dir_fd, const char *path, bool automount) { - static bool can_open_tree = true; - int r; - - /* Pin an inode via O_PATH semantics. Sounds pretty obvious to do this, right? You just do open() - * with O_PATH, and there you go. But uh, it's not that easy. open() via O_PATH does not trigger - * automounts, but we may want that when CHASE_TRIGGER_AUTOFS is set. But thankfully there's - * a way out: the newer open_tree() call, when specified without OPEN_TREE_CLONE actually is fully - * equivalent to open() with O_PATH – except for one thing: it triggers automounts. - * - * As it turns out some sandboxes prohibit open_tree(), and return EPERM or ENOSYS if we call it. - * But since autofs does not work inside of mount namespace anyway, let's simply handle this - * as gracefully as we can, and fall back to classic openat() if we see EPERM/ENOSYS. */ - - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); - assert(path); - - if (automount && can_open_tree) { - r = RET_NERRNO(open_tree(dir_fd, path, AT_SYMLINK_NOFOLLOW|OPEN_TREE_CLOEXEC)); - if (r >= 0 || (r != -EPERM && !ERRNO_IS_NEG_NOT_SUPPORTED(r))) - return r; - - can_open_tree = false; - } - - return RET_NERRNO(openat(dir_fd, path, O_PATH|O_NOFOLLOW|O_CLOEXEC)); -} - -static int chaseat_needs_absolute(int dir_fd, const char *path) { - if (dir_fd < 0) - return path_is_absolute(path); - - return dir_fd_is_root(dir_fd); -} - -int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd) { - _cleanup_free_ char *buffer = NULL, *done = NULL; - _cleanup_close_ int fd = -EBADF, root_fd = -EBADF; - bool exists = true, append_trail_slash = false; - struct statx root_stx, stx; - bool need_absolute = false; /* allocate early to avoid compiler warnings around goto */ - const char *todo; - unsigned mask = STATX_TYPE|STATX_UID|STATX_INO; +int chaseat(int root_fd, int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd) { int r; assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT)); assert(!FLAGS_SET(flags, CHASE_STEP|CHASE_EXTRACT_FILENAME)); assert(!FLAGS_SET(flags, CHASE_NO_AUTOFS|CHASE_TRIGGER_AUTOFS)); assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); + assert(root_fd >= 0 || IN_SET(root_fd, AT_FDCWD, XAT_FDROOT)); + /* AT_FDCWD for dir_fd is only allowed when there is no chroot boundary: otherwise the current + * working directory might live outside root_fd's subtree. */ + assert(dir_fd != AT_FDCWD || IN_SET(root_fd, AT_FDCWD, XAT_FDROOT)); if (FLAGS_SET(flags, CHASE_STEP)) assert(!ret_fd); - if (isempty(path)) - path = "."; - - /* This function resolves symlinks of the path relative to the given directory file descriptor. If - * CHASE_AT_RESOLVE_IN_ROOT is specified and a directory file descriptor is provided, symlinks - * are resolved relative to the given directory file descriptor. Otherwise, they are resolved - * relative to the root directory of the host. + /* This function resolves symlinks of the path relative to the given directory file descriptor. + * The root directory file descriptor sets the chroot boundary: symlinks may not escape it, and + * absolute symlinks encountered during resolution are resolved relative to it. When the root fd is + * XAT_FDROOT, symlinks are resolved relative to the host's root directory with no containment. * - * Note that when a positive directory file descriptor is provided and CHASE_AT_RESOLVE_IN_ROOT is - * specified and we find an absolute symlink, it is resolved relative to given directory file - * descriptor and not the root of the host. Also, when following relative symlinks, this functions - * ensures they cannot be used to "escape" the given directory file descriptor. If a positive - * directory file descriptor is provided, the "path" parameter is always interpreted relative to the - * given directory file descriptor, even if it is absolute. If the given directory file descriptor is - * AT_FDCWD and "path" is absolute, it is interpreted relative to the root directory of the host. + * The given path is always resolved starting at dir_fd, regardless of whether it is absolute or + * relative. The leading slashes of an absolute path are ignored. The only exceptions are + * dir_fd == XAT_FDROOT (which starts resolution at root_fd) and dir_fd == AT_FDCWD with an absolute + * path (which starts resolution at "/" rather than the current working directory). * - * When "dir_fd" points to a non-root directory and CHASE_AT_RESOLVE_IN_ROOT is set, this function - * always returns a relative path in "ret_path", even if "path" is an absolute path, because openat() - * like functions generally ignore the directory fd if they are provided with an absolute path. When - * CHASE_AT_RESOLVE_IN_ROOT is not set, then this returns relative path to the specified file - * descriptor if all resolved symlinks are relative, otherwise absolute path will be returned. When - * "dir_fd" is AT_FDCWD and "path" is an absolute path, we return an absolute path in "ret_path" - * because otherwise, if the caller passes the returned relative path to another openat() like - * function, it would be resolved relative to the current working directory instead of to "/". + * Note that we do not verify that dir_fd actually points to a descendant of root_fd. If dir_fd + * lies outside the root_fd subtree, ".." traversal and absolute symlinks may still be clamped to + * root_fd, leading to surprising results. Callers must ensure the relationship themselves. * - * Summary about the result path: - * - "dir_fd" points to the root directory - * → result will be absolute - * - "dir_fd" points to a non-root directory, and CHASE_AT_RESOLVE_IN_ROOT is set - * → relative - * - "dir_fd" points to a non-root directory, and CHASE_AT_RESOLVE_IN_ROOT is not set - * → relative when all resolved symlinks are relative, otherwise absolute - * - "dir_fd" is AT_FDCWD, and "path" is absolute - * → absolute - * - "dir_fd" is AT_FDCWD, and "path" is relative - * → relative when all resolved symlinks are relative, otherwise absolute + * Absolute paths returned by this function are relative to the given root file descriptor. Relative + * paths returned by this function are relative to the given directory file descriptor. The result is + * absolute when root_fd is XAT_FDROOT (i.e. there is no chroot boundary, so openat()-like callers + * need an absolute path to reach the host inode), or when an absolute symlink made us jump to a + * different subtree than the one dir_fd points into. Otherwise the result is relative. * * Algorithmically this operates on two path buffers: "done" are the components of the path we * already processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we @@ -204,11 +189,6 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int * its special meaning each time. We always keep an O_PATH fd to the component we are currently * processing, thus keeping lookup races to a minimum. * - * Suggested usage: whenever you want to canonicalize a path, use this function. Pass the absolute - * path you got as-is: fully qualified and relative to your host's root. Optionally, specify the - * "dir_fd" parameter to tell this function what to do when encountering a symlink with an absolute - * path as directory: resolve it relative to the given directory file descriptor. - * * There are five ways to invoke this function: * * 1. Without CHASE_STEP or ret_fd: in this case the path is resolved and the normalized path is @@ -239,127 +219,116 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int * the mount point is emitted. CHASE_WARN cannot be used in PID 1. */ - _cleanup_close_ int _dir_fd = -EBADF; + /* We treat AT_FDCWD as XAT_FDROOT for a more seamless migration for all callers of chaseat() before + * it was reworked to support separate root_fd and dir_fd arguments. */ + if (root_fd == AT_FDCWD) + root_fd = XAT_FDROOT; + else { + r = dir_fd_is_root(root_fd); + if (r < 0) + return r; + if (r > 0) + root_fd = XAT_FDROOT; + } + + /* If dir_fd points to the host's root directory and there is no chroot boundary, normalize it + * to XAT_FDROOT so the shortcut path can kick in. */ r = dir_fd_is_root(dir_fd); if (r < 0) return r; - if (r > 0) { - /* Shortcut the common case where no root dir is specified, and no special flags are given to - * a regular open() */ - if (!ret_path && - (flags & (CHASE_STEP|CHASE_NO_AUTOFS|CHASE_NONEXISTENT|CHASE_SAFE|CHASE_WARN|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) { - _cleanup_free_ char *slash_path = NULL; - - if (!path_is_absolute(path)) { - slash_path = strjoin("/", path); - if (!slash_path) - return -ENOMEM; - } + if (r > 0 && root_fd == XAT_FDROOT) + dir_fd = XAT_FDROOT; - /* We use open_tree() rather than regular open() here, because it gives us direct - * control over automount behaviour, and otherwise is equivalent to open() with - * O_PATH */ - fd = open_tree(-EBADF, slash_path ?: path, OPEN_TREE_CLOEXEC|(FLAGS_SET(flags, CHASE_TRIGGER_AUTOFS) ? 0 : AT_NO_AUTOMOUNT)); - if (fd < 0) - return -errno; + /* dir_fd == XAT_FDROOT means "start at root_fd". An absolute path is always resolved relative to + * root_fd, regardless of what dir_fd points to. */ + if (dir_fd == XAT_FDROOT || path_is_absolute(path)) + dir_fd = root_fd; - r = xstatx_full(fd, - /* path= */ NULL, - /* statx_flags= */ 0, - XSTATX_MNT_ID_BEST, - mask, - /* optional_mask= */ 0, - /* mandatory_attributes= */ 0, - &stx); - if (r < 0) - return r; + if (isempty(path)) + path = "."; - exists = true; - goto success; - } + bool append_trail_slash = false; + if (ENDSWITH_SET(path, "/", "/.")) { + flags |= CHASE_MUST_BE_DIRECTORY; + if (FLAGS_SET(flags, CHASE_TRAIL_SLASH)) + append_trail_slash = true; + } else if (dot_or_dot_dot(path) || endswith(path, "/..")) + flags |= CHASE_MUST_BE_DIRECTORY; - _dir_fd = open("/", O_DIRECTORY|O_RDONLY|O_CLOEXEC); - if (_dir_fd < 0) - return -errno; + if (FLAGS_SET(flags, CHASE_PARENT)) + flags |= CHASE_MUST_BE_DIRECTORY; - dir_fd = _dir_fd; - flags &= ~CHASE_AT_RESOLVE_IN_ROOT; - } + /* If multiple flags are set now, fail immediately */ + if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY) + FLAGS_SET(flags, CHASE_MUST_BE_REGULAR) + FLAGS_SET(flags, CHASE_MUST_BE_SOCKET) > 1) + return -EBADSLT; + + if (root_fd == XAT_FDROOT && !ret_path && (flags & CHASE_NO_SHORTCUT_MASK) == 0) { + /* Shortcut the common case where we don't have a real root boundary and no fancy features + * are requested: open the target directly via xopenat_full() which applies any MUST_BE_* + * verification and automount triggering for us. */ - if (!ret_path && ret_fd && (flags & (CHASE_AT_RESOLVE_IN_ROOT|CHASE_NO_SHORTCUT_MASK)) == 0) { - /* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root - * set and doesn't care about any of the other special features we provide either. */ - r = openat(dir_fd, path, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0)); + r = chase_xopenat(dir_fd, path, flags, O_PATH|O_CLOEXEC, /* xopen_flags= */ 0); if (r < 0) - return -errno; + return r; - *ret_fd = r; - return 0; - } + if (ret_fd) + *ret_fd = r; + else + safe_close(r); - buffer = strdup(path); - if (!buffer) - return -ENOMEM; + return 1; + } - /* If we receive an absolute path together with AT_FDCWD, we need to return an absolute path, because - * a relative path would be interpreted relative to the current working directory. Also, let's make - * the result absolute when the file descriptor of the root directory is specified. */ - r = chaseat_needs_absolute(dir_fd, path); - if (r < 0) - return r; + /* Decide whether to return an absolute or relative path. + * + * We return an absolute path only when there is no chroot boundary (root_fd == XAT_FDROOT) + * and resolution starts from root — i.e. either dir_fd was XAT_FDROOT or path is absolute, + * both of which caused dir_fd = root_fd above. In every other case we return a relative + * path so the result keeps working when fed to an openat()-style call against dir_fd, + * which would ignore dir_fd if handed an absolute path. + * + * When root_fd != XAT_FDROOT and an absolute symlink later causes resolution to escape + * dir_fd, the loop below rebases onto root_fd and switches to an absolute result at that + * point — it is not handled here. + */ + bool need_absolute = (root_fd == XAT_FDROOT || dir_fd != root_fd) && (dir_fd == XAT_FDROOT || path_is_absolute(path)); - need_absolute = r; + _cleanup_free_ char *done = NULL; if (need_absolute) { done = strdup("/"); if (!done) return -ENOMEM; } - /* If a positive directory file descriptor is provided, always resolve the given path relative to it, - * regardless of whether it is absolute or not. If we get AT_FDCWD, follow regular openat() - * semantics, if the path is relative, resolve against the current working directory. Otherwise, - * resolve against root. */ - fd = openat(dir_fd, done ?: ".", O_CLOEXEC|O_DIRECTORY|O_PATH); + _cleanup_close_ int fd = xopenat(dir_fd, NULL, O_CLOEXEC|O_DIRECTORY|O_PATH); if (fd < 0) - return -errno; + return fd; - r = xstatx_full(fd, - /* path= */ NULL, - /* statx_flags= */ 0, - XSTATX_MNT_ID_BEST, - mask, - /* optional_mask= */ 0, - /* mandatory_attributes= */ 0, - &stx); + struct statx stx; + r = chase_statx(fd, &stx); if (r < 0) return r; - root_stx = stx; /* remember stat data of the root, so that we can recognize it later */ - - /* If we get AT_FDCWD, we always resolve symlinks relative to the host's root. Only if a positive - * directory file descriptor is provided we will look at CHASE_AT_RESOLVE_IN_ROOT to determine - * whether to resolve symlinks in it or not. */ - if (dir_fd >= 0 && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) - root_fd = openat(dir_fd, ".", O_CLOEXEC|O_DIRECTORY|O_PATH); - else - root_fd = open("/", O_CLOEXEC|O_DIRECTORY|O_PATH); - if (root_fd < 0) - return -errno; - if (ENDSWITH_SET(buffer, "/", "/.")) { - flags |= CHASE_MUST_BE_DIRECTORY; - if (FLAGS_SET(flags, CHASE_TRAIL_SLASH)) - append_trail_slash = true; - } else if (dot_or_dot_dot(buffer) || endswith(buffer, "/..")) - flags |= CHASE_MUST_BE_DIRECTORY; - - if (FLAGS_SET(flags, CHASE_PARENT)) - flags |= CHASE_MUST_BE_DIRECTORY; + /* Remember stat data of the root, so that we can recognize it later during .. handling. Only + * needed when there is an actual chroot boundary — with root_fd == XAT_FDROOT the boundary + * check in the .. loop below is skipped and root_stx is never consulted. */ + struct statx root_stx; + if (root_fd != XAT_FDROOT) { + if (root_fd == dir_fd) + root_stx = stx; + else { + r = chase_statx(root_fd, &root_stx); + if (r < 0) + return r; + } + } - /* If multiple flags are set now, fail immediately */ - if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY) + FLAGS_SET(flags, CHASE_MUST_BE_REGULAR) + FLAGS_SET(flags, CHASE_MUST_BE_SOCKET) > 1) - return -EBADSLT; + _cleanup_free_ char *buffer = strdup(path); + if (!buffer) + return -ENOMEM; - todo = buffer; + const char *todo = buffer; + bool exists = true; for (unsigned n_steps = 0;; n_steps++) { _cleanup_free_ char *first = NULL; _cleanup_close_ int child = -EBADF; @@ -392,9 +361,13 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int * inode/mount identity check. The latter is load-bearing if concurrent access of the * root tree we operate in is allowed, where an inode is moved up the tree while we * look at it, and thus get the current path wrong and think we are deeper down than - * we actually are. */ - if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) { - bool is_root = empty_or_root(done); + * we actually are. + * + * The path-based fast path is only valid when the caller started at the root fd: + * otherwise 'done' being empty just means we haven't descended past the starting + * dir_fd, not that we're at the chroot boundary. */ + if (root_fd != XAT_FDROOT) { + bool is_root = root_fd == dir_fd && empty_or_root(done); if (!is_root && statx_inode_same(&stx, &root_stx)) { r = statx_mount_same(&stx, &root_stx); if (r < 0) @@ -413,14 +386,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd_parent < 0) return -errno; - r = xstatx_full(fd_parent, - /* path= */ NULL, - /* statx_flags= */ 0, - XSTATX_MNT_ID_BEST, - mask, - /* optional_mask= */ 0, - /* mandatory_attributes= */ 0, - &stx_parent); + r = chase_statx(fd_parent, &stx_parent); if (r < 0) return r; @@ -447,18 +413,19 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int assert(!need_absolute); done = mfree(done); } else if (r == -EADDRNOTAVAIL) { - /* 'done' is "/". This branch should be already handled in the above. */ - assert(!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)); + /* 'done' is "/". This branch should already be handled above via the + * is_root check. */ assert_not_reached(); } else if (r == -EINVAL) { - /* 'done' is an empty string, ends with '..', or an invalid path. */ + /* 'done' is empty (we haven't descended past the starting dir_fd yet), or + * ends with '..'. In both cases we're traversing above the starting point + * (valid when root_fd is XAT_FDROOT, or when dir_fd was below root_fd to + * start with), so record another '..' in 'done'. */ assert(!need_absolute); - assert(!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)); - if (!path_is_valid(done)) + if (!isempty(done) && !path_is_valid(done)) return -EINVAL; - /* If we're at the top of "dir_fd", start appending ".." to "done". */ if (!path_extend(&done, "..")) return -ENOMEM; } else @@ -486,14 +453,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd_grandparent < 0) return -errno; - r = xstatx_full(fd_grandparent, - /* path= */ NULL, - /* statx_flags= */ 0, - XSTATX_MNT_ID_BEST, - mask, - /* optional_mask= */ 0, - /* mandatory_attributes= */ 0, - &stx_grandparent); + r = chase_statx(fd_grandparent, &stx_grandparent); if (r < 0) return r; @@ -517,7 +477,10 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int } /* Otherwise let's pin it by file descriptor, via O_PATH. */ - child = r = openat_opath_with_automount(fd, first, /* automount= */ FLAGS_SET(flags, CHASE_TRIGGER_AUTOFS)); + child = r = xopenat_full(fd, first, + O_PATH|O_NOFOLLOW|O_CLOEXEC, + FLAGS_SET(flags, CHASE_TRIGGER_AUTOFS) ? XO_TRIGGER_AUTOMOUNT : 0, + MODE_INVALID); if (r < 0) { if (r != -ENOENT) return r; @@ -546,15 +509,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int return r; } - /* ... and then check what it actually is. */ - r = xstatx_full(child, - /* path= */ NULL, - /* statx_flags= */ 0, - XSTATX_MNT_ID_BEST, - mask, - /* optional_mask= */ 0, - /* mandatory_attributes= */ 0, - &stx_child); + r = chase_statx(child, &stx_child); if (r < 0) return r; @@ -592,14 +547,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd < 0) return fd; - r = xstatx_full(fd, - /* path= */ NULL, - /* statx_flags= */ 0, - XSTATX_MNT_ID_BEST, - mask, - /* optional_mask= */ 0, - /* mandatory_attributes= */ 0, - &stx); + r = chase_statx(fd, &stx); if (r < 0) return r; @@ -611,9 +559,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int return log_unsafe_transition(child, fd, path, flags); } - /* When CHASE_AT_RESOLVE_IN_ROOT is not set, now the chased path may be - * outside of the specified dir_fd. Let's make the result absolute. */ - if (!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) + if (dir_fd != root_fd) need_absolute = true; r = free_and_strdup(&done, need_absolute ? "/" : NULL); @@ -647,7 +593,6 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int close_and_replace(fd, child); } -success: if (exists) { if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY)) { r = statx_verify_directory(&stx); @@ -755,14 +700,9 @@ int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, return r; /* A root directory of "/" or "" is identical to "/". */ - if (empty_or_root(root)) { + if (empty_or_root(root)) root = "/"; - - /* When the root directory is "/", we will drop CHASE_AT_RESOLVE_IN_ROOT in chaseat(), - * hence below is not necessary, but let's shortcut. */ - flags &= ~CHASE_AT_RESOLVE_IN_ROOT; - - } else { + else { r = path_make_absolute_cwd(root, &root_abs); if (r < 0) return r; @@ -779,8 +719,6 @@ int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, if (!absolute) return -ENOMEM; } - - flags |= CHASE_AT_RESOLVE_IN_ROOT; } if (!absolute) { @@ -804,7 +742,7 @@ int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, return -errno; } - r = chaseat(fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL); + r = chaseat(fd, fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL); if (r < 0) return r; @@ -927,36 +865,34 @@ int chase_and_open( _cleanup_close_ int path_fd = -EBADF; _cleanup_free_ char *p = NULL, *fname = NULL; + const char *open_name = NULL; int r; assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); - XOpenFlags xopen_flags = 0; - if (FLAGS_SET(chase_flags, CHASE_MUST_BE_DIRECTORY)) - open_flags |= O_DIRECTORY; - if (FLAGS_SET(chase_flags, CHASE_MUST_BE_REGULAR)) - xopen_flags |= XO_REGULAR; - if (empty_or_root(root) && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) /* Shortcut this call if none of the special features of this call are requested */ - return xopenat_full(AT_FDCWD, path, - open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0), - xopen_flags, - MODE_INVALID); + return chase_xopenat(AT_FDCWD, path, chase_flags, open_flags, /* xopen_flags= */ 0); r = chase(path, root, (CHASE_PARENT|chase_flags)&~CHASE_MUST_BE_REGULAR, &p, &path_fd); if (r < 0) return r; assert(path_fd >= 0); - if (!FLAGS_SET(chase_flags, CHASE_PARENT) && - !FLAGS_SET(chase_flags, CHASE_EXTRACT_FILENAME)) { - r = chase_extract_filename(p, root, &fname); - if (r < 0) - return r; + if (!FLAGS_SET(chase_flags, CHASE_PARENT)) { + if (FLAGS_SET(chase_flags, CHASE_EXTRACT_FILENAME)) + /* chase() with CHASE_EXTRACT_FILENAME already returns just the filename in + * p — use it directly without redundant extraction. */ + open_name = p; + else { + r = chase_extract_filename(p, root, &fname); + if (r < 0) + return r; + open_name = fname; + } } - r = xopenat_full(path_fd, strempty(fname), open_flags|O_NOFOLLOW, xopen_flags, MODE_INVALID); + r = chase_xopenat(path_fd, strempty(open_name), chase_flags, open_flags|O_NOFOLLOW, /* xopen_flags= */ 0); if (r < 0) return r; @@ -1010,8 +946,10 @@ int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, c assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); assert(ret_stat); - if (empty_or_root(root) && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) - /* Shortcut this call if none of the special features of this call are requested */ + if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_SHORTCUT_MASK|CHASE_MUST_BE_ANY)) == 0) + /* Shortcut this call if none of the special features of this call are requested. We can't + * take the shortcut if CHASE_MUST_BE_* is set because fstatat() alone does not verify the + * inode type. */ return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)); @@ -1037,8 +975,8 @@ int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, assert(path); assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); - if (empty_or_root(root) && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) - /* Shortcut this call if none of the special features of this call are requested */ + if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_SHORTCUT_MASK|CHASE_MUST_BE_ANY)) == 0) + /* Shortcut this call if none of the special features of this call are requested. */ return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)); @@ -1130,6 +1068,7 @@ int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_f } int chase_and_openat( + int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, @@ -1138,39 +1077,33 @@ int chase_and_openat( _cleanup_close_ int path_fd = -EBADF; _cleanup_free_ char *p = NULL, *fname = NULL; + const char *open_name = NULL; int r; assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_MUST_BE_SOCKET))); - XOpenFlags xopen_flags = 0; - if (FLAGS_SET(chase_flags, CHASE_MUST_BE_DIRECTORY)) - open_flags |= O_DIRECTORY; - if (FLAGS_SET(chase_flags, CHASE_MUST_BE_REGULAR)) - xopen_flags |= XO_REGULAR; - - if (dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) + if (root_fd == XAT_FDROOT && dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) /* Shortcut this call if none of the special features of this call are requested */ - return xopenat_full(dir_fd, path, - open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0), - xopen_flags, - MODE_INVALID); + return chase_xopenat(dir_fd, path, chase_flags, open_flags, /* xopen_flags= */ 0); - r = chaseat(dir_fd, path, (chase_flags|CHASE_PARENT)&~CHASE_MUST_BE_REGULAR, &p, &path_fd); + r = chaseat(root_fd, dir_fd, path, (chase_flags|CHASE_PARENT)&~CHASE_MUST_BE_REGULAR, &p, &path_fd); if (r < 0) return r; if (!FLAGS_SET(chase_flags, CHASE_PARENT)) { - r = path_extract_filename(p, &fname); - if (r < 0 && r != -EADDRNOTAVAIL) - return r; + if (FLAGS_SET(chase_flags, CHASE_EXTRACT_FILENAME)) + /* chaseat() with CHASE_EXTRACT_FILENAME already returns just the filename in + * p — use it directly without redundant extraction. */ + open_name = p; + else { + r = path_extract_filename(p, &fname); + if (r < 0 && r != -EADDRNOTAVAIL) + return r; + open_name = fname; + } } - r = xopenat_full( - path_fd, - strempty(fname), - open_flags|O_NOFOLLOW, - xopen_flags, - MODE_INVALID); + r = chase_xopenat(path_fd, strempty(open_name), chase_flags, open_flags|O_NOFOLLOW, /* xopen_flags= */ 0); if (r < 0) return r; @@ -1180,7 +1113,7 @@ int chase_and_openat( return r; } -int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) { +int chase_and_opendirat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) { _cleanup_close_ int path_fd = -EBADF; _cleanup_free_ char *p = NULL; DIR *d; @@ -1189,7 +1122,7 @@ int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, ch assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_MUST_BE_REGULAR|CHASE_MUST_BE_SOCKET))); assert(ret_dir); - if (dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) { + if (root_fd == XAT_FDROOT && dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) { /* Shortcut this call if none of the special features of this call are requested */ d = opendir(path); if (!d) @@ -1199,7 +1132,7 @@ int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, ch return 0; } - r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); + r = chaseat(root_fd, dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); if (r < 0) return r; assert(path_fd >= 0); @@ -1215,7 +1148,7 @@ int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, ch return 0; } -int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) { +int chase_and_statat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) { _cleanup_close_ int path_fd = -EBADF; _cleanup_free_ char *p = NULL; int r; @@ -1224,12 +1157,14 @@ int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); assert(ret_stat); - if (dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) - /* Shortcut this call if none of the special features of this call are requested */ + if (root_fd == XAT_FDROOT && dir_fd == AT_FDCWD && !ret_path && (chase_flags & (CHASE_NO_SHORTCUT_MASK|CHASE_MUST_BE_ANY)) == 0) + /* Shortcut this call if none of the special features of this call are requested. We can't + * take the shortcut if CHASE_MUST_BE_* is set because fstatat() alone does not verify the + * inode type. */ return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)); - r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); + r = chaseat(root_fd, dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); if (r < 0) return r; assert(path_fd >= 0); @@ -1243,7 +1178,7 @@ int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char return 0; } -int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path) { +int chase_and_accessat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path) { _cleanup_close_ int path_fd = -EBADF; _cleanup_free_ char *p = NULL; int r; @@ -1251,12 +1186,12 @@ int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int assert(path); assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); - if (dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) - /* Shortcut this call if none of the special features of this call are requested */ + if (root_fd == XAT_FDROOT && dir_fd == AT_FDCWD && !ret_path && (chase_flags & (CHASE_NO_SHORTCUT_MASK|CHASE_MUST_BE_ANY)) == 0) + /* Shortcut this call if none of the special features of this call are requested. */ return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)); - r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); + r = chaseat(root_fd, dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); if (r < 0) return r; assert(path_fd >= 0); @@ -1272,6 +1207,7 @@ int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int } int chase_and_fopenat_unlocked( + int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, @@ -1292,7 +1228,7 @@ int chase_and_fopenat_unlocked( if (mode_flags < 0) return mode_flags; - fd = chase_and_openat(dir_fd, path, chase_flags, mode_flags, ret_path ? &final_path : NULL); + fd = chase_and_openat(root_fd, dir_fd, path, chase_flags, mode_flags, ret_path ? &final_path : NULL); if (fd < 0) return fd; @@ -1306,7 +1242,7 @@ int chase_and_fopenat_unlocked( return 0; } -int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path) { +int chase_and_unlinkat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path) { _cleanup_free_ char *p = NULL, *fname = NULL; _cleanup_close_ int fd = -EBADF; int r; @@ -1314,7 +1250,7 @@ int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int assert(path); assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT|CHASE_MUST_BE_SOCKET|CHASE_MUST_BE_REGULAR|CHASE_MUST_BE_DIRECTORY|CHASE_EXTRACT_FILENAME|CHASE_MKDIR_0755))); - fd = chase_and_openat(dir_fd, path, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p); + fd = chase_and_openat(root_fd, dir_fd, path, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p); if (fd < 0) return fd; @@ -1331,12 +1267,12 @@ int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int return 0; } -int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename) { +int chase_and_open_parent_at(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename) { int pfd, r; assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); - r = chaseat(dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd); + r = chaseat(root_fd, dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd); if (r < 0) return r; diff --git a/src/basic/chase.h b/src/basic/chase.h index d779658ba15f8..afb50fa61ec7c 100644 --- a/src/basic/chase.h +++ b/src/basic/chase.h @@ -15,21 +15,19 @@ typedef enum ChaseFlags { * right-most component refers to symlink, return O_PATH fd of the symlink. */ CHASE_WARN = 1 << 8, /* Emit an appropriate warning when an error is encountered. * Note: this may do an NSS lookup, hence this flag cannot be used in PID 1. */ - CHASE_AT_RESOLVE_IN_ROOT = 1 << 9, /* Same as openat2()'s RESOLVE_IN_ROOT flag, symlinks are resolved - * relative to the given directory fd instead of root. */ - CHASE_PROHIBIT_SYMLINKS = 1 << 10, /* Refuse all symlinks */ - CHASE_PARENT = 1 << 11, /* Chase the parent directory of the given path. Note that the + CHASE_PROHIBIT_SYMLINKS = 1 << 9, /* Refuse all symlinks */ + CHASE_PARENT = 1 << 10, /* Chase the parent directory of the given path. Note that the * full path is still stored in ret_path and only the returned * file descriptor will point to the parent directory. Note that * the result path is the root or '.', then the file descriptor * also points to the result path even if this flag is set. * When this specified, chase() will succeed with 1 even if the * file points to the last path component does not exist. */ - CHASE_MKDIR_0755 = 1 << 12, /* Create any missing directories in the given path. */ - CHASE_EXTRACT_FILENAME = 1 << 13, /* Only return the last component of the resolved path */ - CHASE_MUST_BE_DIRECTORY = 1 << 14, /* Fail if returned inode fd is not a dir */ - CHASE_MUST_BE_REGULAR = 1 << 15, /* Fail if returned inode fd is not a regular file */ - CHASE_MUST_BE_SOCKET = 1 << 16, /* Fail if returned inode fd is not a socket */ + CHASE_MKDIR_0755 = 1 << 11, /* Create any missing directories in the given path. */ + CHASE_EXTRACT_FILENAME = 1 << 12, /* Only return the last component of the resolved path */ + CHASE_MUST_BE_DIRECTORY = 1 << 13, /* Fail if returned inode fd is not a dir */ + CHASE_MUST_BE_REGULAR = 1 << 14, /* Fail if returned inode fd is not a regular file */ + CHASE_MUST_BE_SOCKET = 1 << 15, /* Fail if returned inode fd is not a socket */ } ChaseFlags; int statx_unsafe_transition(const struct statx *a, const struct statx *b); @@ -51,12 +49,12 @@ int chase_and_fopen_unlocked(const char *path, const char *root, ChaseFlags chas int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path); int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_flags, char **ret_filename); -int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd); +int chaseat(int root_fd, int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd); -int chase_and_openat(int dir_fd, const char *path, ChaseFlags chase_flags, int open_flags, char **ret_path); -int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir); -int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat); -int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path); -int chase_and_fopenat_unlocked(int dir_fd, const char *path, ChaseFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file); -int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path); -int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename); +int chase_and_openat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, int open_flags, char **ret_path); +int chase_and_opendirat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir); +int chase_and_statat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat); +int chase_and_accessat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path); +int chase_and_fopenat_unlocked(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file); +int chase_and_unlinkat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path); +int chase_and_open_parent_at(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename); diff --git a/src/basic/conf-files.c b/src/basic/conf-files.c index 4db486d8ada7e..d4ff194d4cce4 100644 --- a/src/basic/conf-files.c +++ b/src/basic/conf-files.c @@ -128,7 +128,7 @@ static bool conf_files_need_stat(ConfFilesFlags flags) { } static ChaseFlags conf_files_chase_flags(ConfFilesFlags flags) { - ChaseFlags chase_flags = CHASE_AT_RESOLVE_IN_ROOT; + ChaseFlags chase_flags = 0; if (!conf_files_need_stat(flags) || FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_SYMLINK)) /* Even if no verification is requested, let's unconditionally call chaseat(), @@ -164,7 +164,7 @@ static int conf_file_chase_and_verify( root = empty_to_root(root); - r = chaseat(rfd, path, conf_files_chase_flags(flags), &resolved_path, &fd); + r = chaseat(rfd, rfd, path, conf_files_chase_flags(flags), &resolved_path, &fd); if (r < 0) return log_full_errno(log_level, r, "Failed to chase '%s%s': %m", root, skip_leading_slash(original_path)); @@ -306,7 +306,7 @@ int conf_file_new_at( if (r < 0 && r != -EDESTADDRREQ) return log_full_errno(log_level, r, "Failed to extract directory from '%s': %m", path); if (r >= 0) { - r = chaseat(rfd, dirpath, + r = chaseat(rfd, rfd, dirpath, CHASE_MUST_BE_DIRECTORY | conf_files_chase_flags(flags), &resolved_dirpath, /* ret_fd= */ NULL); if (r < 0) @@ -637,7 +637,7 @@ static int conf_files_list_impl( _cleanup_closedir_ DIR *dir = NULL; _cleanup_free_ char *path = NULL; - r = chase_and_opendirat(rfd, *p, CHASE_AT_RESOLVE_IN_ROOT, &path, &dir); + r = chase_and_opendirat(rfd, rfd, *p, 0, &path, &dir); if (r < 0) { if (r != -ENOENT) log_full_errno(conf_files_log_level(flags), r, diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 2dfa37f20bcc7..661667a6b2a1e 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -1684,7 +1684,8 @@ int write_data_file_atomic_at( _cleanup_close_ int mfd = -EBADF; if (dn) { /* If there's a directory component, readjust our position */ - r = chaseat(dir_fd, + r = chaseat(XAT_FDROOT, + dir_fd, dn, FLAGS_SET(flags, WRITE_DATA_FILE_MKDIR_0755) ? CHASE_MKDIR_0755 : 0, /* ret_path= */ NULL, diff --git a/src/basic/mkdir.c b/src/basic/mkdir.c index 837880baa2ec2..7b353542e342f 100644 --- a/src/basic/mkdir.c +++ b/src/basic/mkdir.c @@ -46,7 +46,7 @@ int mkdirat_safe_internal( if ((flags & MKDIR_FOLLOW_SYMLINK) && S_ISLNK(st.st_mode)) { _cleanup_free_ char *p = NULL; - r = chaseat(dir_fd, path, CHASE_NONEXISTENT, &p, NULL); + r = chaseat(XAT_FDROOT, dir_fd, path, CHASE_NONEXISTENT, &p, NULL); if (r < 0) return r; if (r == 0) diff --git a/src/basic/os-util.c b/src/basic/os-util.c index 66bab1bcee9d2..06b476f1344a8 100644 --- a/src/basic/os-util.c +++ b/src/basic/os-util.c @@ -172,10 +172,10 @@ int open_os_release_at(int rfd, char **ret_path, int *ret_fd) { e = secure_getenv("SYSTEMD_OS_RELEASE"); if (e) - return chaseat(rfd, e, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd); + return chaseat(rfd, rfd, e, /* flags= */ 0, ret_path, ret_fd); FOREACH_STRING(path, "/etc/os-release", "/usr/lib/os-release") { - r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd); + r = chaseat(rfd, rfd, path, /* flags= */ 0, ret_path, ret_fd); if (r != -ENOENT) return r; } @@ -238,7 +238,7 @@ int open_extension_release_at( return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "The extension name %s is invalid.", extension); p = strjoina(image_class_release_info[image_class].release_file_path_prefix, extension); - r = chaseat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd); + r = chaseat(rfd, rfd, p, /* flags= */ 0, ret_path, ret_fd); log_full_errno_zerook(LOG_DEBUG, MIN(r, 0), "Checking for %s: %m", p); if (r != -ENOENT) return r; @@ -249,7 +249,7 @@ int open_extension_release_at( * xattr is checked to ensure the author of the image considers it OK if names do not match. */ p = image_class_release_info[image_class].release_file_directory; - r = chase_and_opendirat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, &dir_path, &dir); + r = chase_and_opendirat(rfd, rfd, p, /* chase_flags= */ 0, &dir_path, &dir); if (r < 0) return log_debug_errno(r, "Cannot open %s, ignoring: %m", p); diff --git a/src/basic/user-util.c b/src/basic/user-util.c index 93a3852879b29..b735d27474272 100644 --- a/src/basic/user-util.c +++ b/src/basic/user-util.c @@ -185,7 +185,7 @@ const char* default_root_shell_at(int rfd) { assert(rfd >= 0 || rfd == AT_FDCWD); - int r = chaseat(rfd, DEFAULT_USER_SHELL, CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL); + int r = chaseat(rfd, rfd, DEFAULT_USER_SHELL, /* flags= */ 0, NULL, NULL); if (r < 0 && r != -ENOENT) log_debug_errno(r, "Failed to look up shell '%s': %m", DEFAULT_USER_SHELL); if (r > 0) diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index 96bc9213cf6b2..a8ac742b9a760 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -276,9 +276,10 @@ static int load_etc_machine_info(InstallContext *c) { _cleanup_close_ int fd = chase_and_openat( + c->root_fd, c->root_fd, "/etc/machine-info", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_REGULAR, + CHASE_MUST_BE_REGULAR, O_RDONLY|O_CLOEXEC, /* ret_path= */ NULL); if (fd == -ENOENT) @@ -392,8 +393,9 @@ static int settle_make_entry_directory(InstallContext *c) { _cleanup_close_ int fd = -EBADF; r = chaseat(c->root_fd, + c->root_fd, "/etc/machine-id", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_REGULAR, + CHASE_MUST_BE_REGULAR, /* ret_path= */ NULL, &fd); if (r < 0) @@ -546,8 +548,9 @@ static int mkdir_one(const char *root, int root_fd, const char *path) { return log_oom(); r = chaseat(root_fd, + root_fd, path, - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, /* ret_fd= */ NULL); if (r < 0) @@ -611,9 +614,10 @@ static int update_efi_boot_binaries( _cleanup_closedir_ DIR *d = NULL; r = chase_and_opendirat( + c->esp_fd, c->esp_fd, "/EFI/BOOT", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &d); if (r == -ENOENT) @@ -679,9 +683,10 @@ static int copy_one_file( _cleanup_close_ int source_fd = -EBADF; if (IN_SET(c->install_source, INSTALL_SOURCE_AUTO, INSTALL_SOURCE_IMAGE)) { source_fd = chase_and_openat( + c->root_fd, c->root_fd, sp, - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_REGULAR, + CHASE_MUST_BE_REGULAR, O_RDONLY|O_CLOEXEC, &source_path); if (source_fd < 0 && (source_fd != -ENOENT || c->install_source != INSTALL_SOURCE_AUTO)) @@ -709,8 +714,9 @@ static int copy_one_file( _cleanup_close_ int dest_parent_fd = -EBADF; r = chaseat(c->esp_fd, + c->esp_fd, "/EFI/systemd", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &dest_parent_fd); if (r < 0) @@ -740,8 +746,9 @@ static int copy_one_file( _cleanup_close_ int default_dest_parent_fd = -EBADF; r = chaseat(c->esp_fd, + c->esp_fd, "/EFI/BOOT", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &default_dest_parent_fd); if (r < 0) @@ -778,9 +785,10 @@ static int install_binaries( _cleanup_closedir_ DIR *d = NULL; if (IN_SET(c->install_source, INSTALL_SOURCE_AUTO, INSTALL_SOURCE_IMAGE)) { r = chase_and_opendirat( + c->root_fd, c->root_fd, BOOTLIBDIR, - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_DIRECTORY, + CHASE_MUST_BE_DIRECTORY, &source_path, &d); if (r < 0 && (r != -ENOENT || c->install_source != INSTALL_SOURCE_AUTO)) @@ -845,8 +853,9 @@ static int install_loader_config(InstallContext *c) { _cleanup_close_ int loader_dir_fd = -EBADF; r = chaseat(c->esp_fd, + c->esp_fd, "loader", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &loader_dir_fd); if (r < 0) @@ -899,8 +908,9 @@ static int install_loader_specification(InstallContext *c) { _cleanup_close_ int loader_dir_fd = -EBADF; r = chaseat(dollar_boot_fd, + dollar_boot_fd, "loader", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &loader_dir_fd); if (r < 0) @@ -973,8 +983,9 @@ static int install_entry_token(InstallContext *c) { _cleanup_close_ int dfd = -EBADF; r = chaseat(c->root_fd, + c->root_fd, confdir, - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &dfd); if (r < 0) @@ -1040,8 +1051,9 @@ static int install_secure_boot_auto_enroll(InstallContext *c) { _cleanup_close_ int keys_fd = -EBADF; r = chaseat(c->esp_fd, + c->esp_fd, "loader/keys/auto", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &keys_fd); if (r < 0) @@ -1352,9 +1364,10 @@ static int install_variables( return log_oom(); r = chase_and_accessat( + c->esp_fd, c->esp_fd, path, - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_REGULAR, + CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_REGULAR, F_OK, /* ret_path= */ NULL); if (r == -ENOENT) @@ -1436,9 +1449,10 @@ static int are_we_installed(InstallContext *c) { return c->esp_fd; _cleanup_close_ int fd = chase_and_openat( + c->esp_fd, c->esp_fd, "/EFI/systemd", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, O_RDONLY|O_CLOEXEC|O_DIRECTORY, /* ret_path= */ NULL); if (fd == -ENOENT) @@ -1655,9 +1669,10 @@ static int remove_boot_efi(InstallContext *c) { _cleanup_closedir_ DIR *d = NULL; _cleanup_free_ char *p = NULL; r = chase_and_opendirat( + c->esp_fd, c->esp_fd, "/EFI/BOOT", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, &p, &d); if (r == -ENOENT) @@ -1725,9 +1740,10 @@ static int unlink_inode(const char *root, int root_fd, const char *path, mode_t return log_oom(); r = chase_and_unlinkat( + root_fd, root_fd, path, - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS, + CHASE_PROHIBIT_SYMLINKS, S_ISDIR(type) ? AT_REMOVEDIR : 0, /* ret_path= */ NULL); if (r < 0) { @@ -1773,8 +1789,9 @@ static int remove_binaries(InstallContext *c) { _cleanup_close_ int efi_fd = -EBADF; r = chaseat(c->esp_fd, + c->esp_fd, "EFI", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &efi_fd); if (r < 0) { diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 2138367218ba9..c663532e00e09 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -4152,7 +4152,7 @@ static int apply_working_directory( r = chase(wd, runtime->ephemeral_copy ?: context->root_directory, - CHASE_PREFIX_ROOT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_TRIGGER_AUTOFS, + CHASE_PREFIX_ROOT|CHASE_TRIGGER_AUTOFS, /* ret_path= */ NULL, &dfd); if (r >= 0) diff --git a/src/core/service.c b/src/core/service.c index 63e659942188f..aa00413dc304a 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -5911,7 +5911,7 @@ int service_determine_exec_selinux_label(Service *s, char **ret) { _cleanup_free_ char *path = NULL; if (s->exec_context.root_directory_as_fd) - r = chaseat(s->root_directory_fd, c->path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_TRIGGER_AUTOFS, &path, NULL); + r = chaseat(s->root_directory_fd, s->root_directory_fd, c->path, CHASE_TRIGGER_AUTOFS, &path, NULL); else r = chase(c->path, s->exec_context.root_directory, CHASE_PREFIX_ROOT|CHASE_TRIGGER_AUTOFS, &path, NULL); if (r < 0) { diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index ea4dd5f184038..3006d93407255 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -339,8 +339,8 @@ static int process_locale(int rfd, sd_varlink **mute_console_link) { assert(rfd >= 0); - pfd = chase_and_open_parent_at(rfd, etc_locale_conf(), - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, + pfd = chase_and_open_parent_at(rfd, rfd, etc_locale_conf(), + CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, &f); if (pfd < 0) return log_error_errno(pfd, "Failed to chase /etc/locale.conf: %m"); @@ -474,8 +474,8 @@ static int process_keymap(int rfd, sd_varlink **mute_console_link) { assert(rfd >= 0); - pfd = chase_and_open_parent_at(rfd, etc_vconsole_conf(), - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, + pfd = chase_and_open_parent_at(rfd, rfd, etc_vconsole_conf(), + CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, &f); if (pfd < 0) return log_error_errno(pfd, "Failed to chase /etc/vconsole.conf: %m"); @@ -590,8 +590,8 @@ static int process_timezone(int rfd, sd_varlink **mute_console_link) { assert(rfd >= 0); - pfd = chase_and_open_parent_at(rfd, etc_localtime(), - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, + pfd = chase_and_open_parent_at(rfd, rfd, etc_localtime(), + CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, &f); if (pfd < 0) return log_error_errno(pfd, "Failed to chase /etc/localtime: %m"); @@ -703,9 +703,7 @@ static int process_hostname(int rfd, sd_varlink **mute_console_link) { assert(rfd >= 0); - pfd = chase_and_open_parent_at(rfd, etc_hostname(), - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN, - &f); + pfd = chase_and_open_parent_at(rfd, rfd, etc_hostname(), CHASE_MKDIR_0755|CHASE_WARN, &f); if (pfd < 0) return log_error_errno(pfd, "Failed to chase /etc/hostname: %m"); @@ -738,8 +736,8 @@ static int process_machine_id(int rfd) { assert(rfd >= 0); - pfd = chase_and_open_parent_at(rfd, "/etc/machine-id", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, + pfd = chase_and_open_parent_at(rfd, rfd, "/etc/machine-id", + CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, &f); if (pfd < 0) return log_error_errno(pfd, "Failed to chase /etc/machine-id: %m"); @@ -848,7 +846,7 @@ static int find_shell(int rfd, const char *path) { if (!valid_shell(path)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s is not a valid shell", path); - r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL); + r = chaseat(rfd, rfd, path, /* flags= */ 0, /* ret_path= */ NULL, /* ret_fd= */ NULL); if (r < 0) return log_error_errno(r, "Failed to resolve shell %s: %m", path); @@ -1052,8 +1050,8 @@ static int process_root_account(int rfd, sd_varlink **mute_console_link) { assert(rfd >= 0); - pfd = chase_and_open_parent_at(rfd, "/etc/passwd", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, + pfd = chase_and_open_parent_at(rfd, rfd, "/etc/passwd", + CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, NULL); if (pfd < 0) return log_error_errno(pfd, "Failed to chase /etc/passwd: %m"); @@ -1169,8 +1167,8 @@ static int process_kernel_cmdline(int rfd) { assert(rfd >= 0); - pfd = chase_and_open_parent_at(rfd, "/etc/kernel/cmdline", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, + pfd = chase_and_open_parent_at(rfd, rfd, "/etc/kernel/cmdline", + CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, &f); if (pfd < 0) return log_error_errno(pfd, "Failed to chase /etc/kernel/cmdline: %m"); @@ -1202,7 +1200,7 @@ static int reset_one(int rfd, const char *path) { assert(rfd >= 0); assert(path); - pfd = chase_and_open_parent_at(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_WARN|CHASE_NOFOLLOW, &f); + pfd = chase_and_open_parent_at(rfd, rfd, path, CHASE_WARN|CHASE_NOFOLLOW, &f); if (pfd == -ENOENT) return 0; if (pfd < 0) diff --git a/src/kernel-install/kernel-install.c b/src/kernel-install/kernel-install.c index aeded46c22d9f..331923e9e5578 100644 --- a/src/kernel-install/kernel-install.c +++ b/src/kernel-install/kernel-install.c @@ -348,7 +348,7 @@ static int context_set_path(Context *c, const char *s, const char *source, const return 0; if (c->rfd >= 0) { - r = chaseat(c->rfd, s, CHASE_AT_RESOLVE_IN_ROOT, &p, /* ret_fd= */ NULL); + r = chaseat(c->rfd, c->rfd, s, /* flags= */ 0, &p, /* ret_fd= */ NULL); if (r < 0) return log_warning_errno(r, "Failed to chase path %s for %s specified via %s, ignoring: %m", s, name, source); @@ -396,7 +396,7 @@ static int context_set_path_strv(Context *c, char* const* strv, const char *sour char *p; if (c->rfd >= 0) { - r = chaseat(c->rfd, *s, CHASE_AT_RESOLVE_IN_ROOT, &p, /* ret_fd= */ NULL); + r = chaseat(c->rfd, c->rfd, *s, /* flags= */ 0, &p, /* ret_fd= */ NULL); if (r < 0) return log_warning_errno(r, "Failed to chase path %s for %s specified via %s: %m", *s, name, source); @@ -503,7 +503,7 @@ static int context_load_machine_info(Context *c) { return 0; } - r = chase_and_fopenat_unlocked(c->rfd, path, CHASE_AT_RESOLVE_IN_ROOT, "re", NULL, &f); + r = chase_and_fopenat_unlocked(c->rfd, c->rfd, path, /* chase_flags= */ 0, "re", NULL, &f); if (r == -ENOENT) return 0; if (r < 0) @@ -631,7 +631,7 @@ static int context_ensure_boot_root(Context *c) { /* If all else fails, use /boot. */ if (c->rfd >= 0) { - r = chaseat(c->rfd, "/boot", CHASE_AT_RESOLVE_IN_ROOT, &c->boot_root, /* ret_fd= */ NULL); + r = chaseat(c->rfd, c->rfd, "/boot", 0, &c->boot_root, /* ret_fd= */ NULL); if (r < 0) return log_error_errno(r, "Failed to chase '/boot/': %m"); } else { @@ -794,7 +794,7 @@ static int context_ensure_layout(Context *c) { return log_oom(); _cleanup_fclose_ FILE *f = NULL; - r = chase_and_fopenat_unlocked(c->rfd, srel_path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_REGULAR, "re", /* ret_path= */ NULL, &f); + r = chase_and_fopenat_unlocked(c->rfd, c->rfd, srel_path, CHASE_MUST_BE_REGULAR, "re", /* ret_path= */ NULL, &f); if (r < 0) { if (r != -ENOENT) return log_error_errno(r, "Failed to open '%s': %m", srel_path); @@ -824,7 +824,7 @@ static int context_ensure_layout(Context *c) { if (!entry_token_path) return log_oom(); - r = chaseat(c->rfd, entry_token_path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, /* ret_fd= */ NULL); + r = chaseat(c->rfd, c->rfd, entry_token_path, CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, /* ret_fd= */ NULL); if (r < 0) { if (!IN_SET(r, -ENOENT, -ENOTDIR)) return log_error_errno(r, "Failed to check if '%s' exists and is a directory: %m", entry_token_path); @@ -916,7 +916,7 @@ static int context_make_entry_dir(Context *c) { return 0; log_debug("mkdir -p %s", c->entry_dir); - fd = chase_and_openat(c->rfd, c->entry_dir, CHASE_AT_RESOLVE_IN_ROOT | CHASE_MKDIR_0755, + fd = chase_and_openat(c->rfd, c->rfd, c->entry_dir, CHASE_MKDIR_0755, O_CLOEXEC | O_CREAT | O_DIRECTORY | O_PATH, NULL); if (fd < 0) return log_error_errno(fd, "Failed to make directory '%s': %m", c->entry_dir); @@ -940,7 +940,7 @@ static int context_remove_entry_dir(Context *c) { return 0; log_debug("rm -rf %s", c->entry_dir); - fd = chase_and_openat(c->rfd, c->entry_dir, CHASE_AT_RESOLVE_IN_ROOT, O_CLOEXEC | O_DIRECTORY, &p); + fd = chase_and_openat(c->rfd, c->rfd, c->entry_dir, /* chase_flags= */ 0, O_CLOEXEC | O_DIRECTORY, &p); if (fd < 0) { if (IN_SET(fd, -ENOTDIR, -ENOENT)) return 0; @@ -1237,7 +1237,7 @@ static int verb_add_all(int argc, char *argv[], uintptr_t _data, void *userdata) if (r < 0) return r; - fd = chase_and_openat(c.rfd, "/usr/lib/modules", CHASE_AT_RESOLVE_IN_ROOT, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL); + fd = chase_and_openat(c.rfd, c.rfd, "/usr/lib/modules", /* chase_flags= */ 0, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL); if (fd < 0) return log_error_errno(fd, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root)); @@ -1467,7 +1467,7 @@ static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) { if (r < 0) return r; - fd = chase_and_openat(c.rfd, "/usr/lib/modules", CHASE_AT_RESOLVE_IN_ROOT, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL); + fd = chase_and_openat(c.rfd, c.rfd, "/usr/lib/modules", /* chase_flags= */ 0, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL); if (fd < 0) return log_error_errno(fd, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root)); diff --git a/src/libsystemd/sd-id128/sd-id128.c b/src/libsystemd/sd-id128/sd-id128.c index f2a7209a257dc..852b01ab1a3f5 100644 --- a/src/libsystemd/sd-id128/sd-id128.c +++ b/src/libsystemd/sd-id128/sd-id128.c @@ -147,7 +147,7 @@ int id128_get_machine_at(int rfd, sd_id128_t *ret) { return sd_id128_get_machine(ret); _cleanup_close_ int fd = - chase_and_openat(rfd, "/etc/machine-id", CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_REGULAR, O_RDONLY|O_CLOEXEC|O_NOCTTY, /* ret_path= */ NULL); + chase_and_openat(rfd, rfd, "/etc/machine-id", CHASE_MUST_BE_REGULAR, O_RDONLY|O_CLOEXEC|O_NOCTTY, /* ret_path= */ NULL); if (fd < 0) return fd; diff --git a/src/mountfsd/mountwork.c b/src/mountfsd/mountwork.c index 42860db13479e..54a5203da2cc6 100644 --- a/src/mountfsd/mountwork.c +++ b/src/mountfsd/mountwork.c @@ -242,7 +242,7 @@ static int verify_trusted_image_fd_by_path(int fd) { if (!filename_is_valid(e)) continue; - r = chaseat(dir_fd, e, CHASE_SAFE|CHASE_TRIGGER_AUTOFS, NULL, &inode_fd); + r = chaseat(XAT_FDROOT, dir_fd, e, CHASE_SAFE|CHASE_TRIGGER_AUTOFS, NULL, &inode_fd); if (r < 0) return log_error_errno(r, "Couldn't verify that specified image '%s' is in search path '%s': %m", p, s); diff --git a/src/portable/portable.c b/src/portable/portable.c index 5e60ad4694fda..8b2be8cead636 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -363,7 +363,7 @@ static int extract_now( _cleanup_free_ char *relative = NULL, *resolved = NULL; _cleanup_closedir_ DIR *d = NULL; - r = chase_and_opendirat(rfd, *i, CHASE_AT_RESOLVE_IN_ROOT, &relative, &d); + r = chase_and_opendirat(rfd, rfd, *i, /* chase_flags= */ 0, &relative, &d); if (r < 0) { log_debug_errno(r, "Failed to open unit path '%s', ignoring: %m", *i); continue; diff --git a/src/shared/boot-entry.c b/src/shared/boot-entry.c index c9e966ba04ea8..ce7580749113f 100644 --- a/src/shared/boot-entry.c +++ b/src/shared/boot-entry.c @@ -32,7 +32,7 @@ static int entry_token_load_one(int rfd, const char *dir, BootEntryTokenType *ty if (!p) return log_oom(); - r = chase_and_fopenat_unlocked(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, "re", NULL, &f); + r = chase_and_fopenat_unlocked(rfd, rfd, p, /* chase_flags= */ 0, "re", NULL, &f); if (r == -ENOENT) return 0; if (r < 0) diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index c3774a5235fad..3338d75f660df 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -1126,7 +1126,7 @@ static int boot_entries_find_unified_addons( assert(ret_addons); assert(config); - r = chase_and_opendirat(d_fd, addon_dir, CHASE_AT_RESOLVE_IN_ROOT, &full, &d); + r = chase_and_opendirat(d_fd, d_fd, addon_dir, /* chase_flags= */ 0, &full, &d); if (r == -ENOENT) return 0; if (r < 0) diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index 2095d803f59cf..bb3e28f6bbba4 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -1014,7 +1014,7 @@ int btrfs_subvol_remove_at(int dir_fd, const char *path, BtrfsRemoveFlags flags) assert(path); - fd = chase_and_openat(dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_CLOEXEC, &subvolume); + fd = chase_and_openat(XAT_FDROOT, dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_CLOEXEC, &subvolume); if (fd < 0) return fd; @@ -1427,7 +1427,7 @@ int btrfs_subvol_snapshot_at_full( if (old_fd < 0) return old_fd; - new_fd = chase_and_openat(dir_fdt, to, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_CLOEXEC, &subvolume); + new_fd = chase_and_openat(XAT_FDROOT, dir_fdt, to, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_CLOEXEC, &subvolume); if (new_fd < 0) return new_fd; diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c index b448032939d53..ba0bc4ad8c442 100644 --- a/src/shared/conf-parser.c +++ b/src/shared/conf-parser.c @@ -509,7 +509,7 @@ static int config_parse_many_files( /* Pin and stat() all dropins */ STRV_FOREACH(fn, files) { _cleanup_fclose_ FILE *f = NULL; - r = chase_and_fopenat_unlocked(root_fd, *fn, CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_REGULAR, "re", /* ret_path= */ NULL, &f); + r = chase_and_fopenat_unlocked(root_fd, root_fd, *fn, CHASE_MUST_BE_REGULAR, "re", /* ret_path= */ NULL, &f); if (r == -ENOENT) continue; if (r < 0) @@ -544,7 +544,7 @@ static int config_parse_many_files( /* First process the first found main config file. */ STRV_FOREACH(fn, conf_files) { _cleanup_fclose_ FILE *f = NULL; - r = chase_and_fopenat_unlocked(root_fd, *fn, CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_REGULAR, "re", /* ret_path= */ NULL, &f); + r = chase_and_fopenat_unlocked(root_fd, root_fd, *fn, CHASE_MUST_BE_REGULAR, "re", /* ret_path= */ NULL, &f); if (r == -ENOENT) continue; if (r < 0) diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c index 1595bb218404a..0410b523baafd 100644 --- a/src/shared/discover-image.c +++ b/src/shared/discover-image.c @@ -891,7 +891,7 @@ int image_find(RuntimeScope scope, _cleanup_closedir_ DIR *d = NULL; _cleanup_free_ char *search_path = NULL; - r = chase_and_opendirat(rfd, *s, CHASE_AT_RESOLVE_IN_ROOT, &search_path, &d); + r = chase_and_opendirat(rfd, rfd, *s, /* chase_flags= */ 0, &search_path, &d); if (r == -ENOENT) continue; if (r < 0) @@ -907,7 +907,7 @@ int image_find(RuntimeScope scope, return -ENOMEM; /* Follow symlinks only inside given root */ - r = chaseat(rfd, fname_path, CHASE_AT_RESOLVE_IN_ROOT, &chased_path, &fd); + r = chaseat(rfd, rfd, fname_path, /* flags= */ 0, &chased_path, &fd); if (r == -ENOENT) continue; if (r < 0) @@ -1097,7 +1097,7 @@ int image_discover( _cleanup_closedir_ DIR *d = NULL; _cleanup_free_ char *search_path = NULL; - r = chase_and_opendirat(rfd, *s, CHASE_AT_RESOLVE_IN_ROOT, &search_path, &d); + r = chase_and_opendirat(rfd, rfd, *s, /* chase_flags= */ 0, &search_path, &d); if (r == -ENOENT) continue; if (r < 0) @@ -1121,7 +1121,7 @@ int image_discover( return -ENOMEM; /* Follow symlinks only inside given root */ - r = chaseat(rfd, fname_path, CHASE_AT_RESOLVE_IN_ROOT, &chased_path, &fd); + r = chaseat(rfd, rfd, fname_path, /* flags= */ 0, &chased_path, &fd); if (r == -ENOENT) continue; if (r < 0) diff --git a/src/shared/find-esp.c b/src/shared/find-esp.c index d29719785fedd..3497ca6da3c67 100644 --- a/src/shared/find-esp.c +++ b/src/shared/find-esp.c @@ -337,7 +337,7 @@ static int verify_esp( _cleanup_free_ char *p = NULL; _cleanup_close_ int fd = -EBADF; - r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_TRIGGER_AUTOFS, &p, &fd); + r = chaseat(rfd, rfd, path, CHASE_TRIGGER_AUTOFS, &p, &fd); if (r < 0) return log_full_errno((searching && r == -ENOENT) || (unprivileged_mode && ERRNO_IS_PRIVILEGE(r)) ? LOG_DEBUG : LOG_ERR, @@ -441,7 +441,7 @@ int find_esp_and_warn_at_full( "$SYSTEMD_ESP_PATH does not refer to an absolute path, refusing to use it: \"%s\"", path); - r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_TRIGGER_AUTOFS, &p, &fd); + r = chaseat(rfd, rfd, path, CHASE_TRIGGER_AUTOFS, &p, &fd); if (r < 0) return log_error_errno(r, "Failed to resolve path \"%s\": %m", path); @@ -735,7 +735,7 @@ static int verify_xbootldr( _cleanup_free_ char *p = NULL; _cleanup_close_ int fd = -EBADF; - r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_TRIGGER_AUTOFS, &p, &fd); + r = chaseat(rfd, rfd, path, CHASE_TRIGGER_AUTOFS, &p, &fd); if (r < 0) return log_full_errno((searching && r == -ENOENT) || (unprivileged_mode && ERRNO_IS_PRIVILEGE(r)) ? LOG_DEBUG : LOG_ERR, @@ -809,7 +809,7 @@ int find_xbootldr_and_warn_at_full( "$SYSTEMD_XBOOTLDR_PATH does not refer to an absolute path, refusing to use it: \"%s\"", path); - r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_TRIGGER_AUTOFS, &p, &fd); + r = chaseat(rfd, rfd, path, CHASE_TRIGGER_AUTOFS, &p, &fd); if (r < 0) return log_error_errno(r, "Failed to resolve path \"%s\": %m", p); diff --git a/src/shared/mstack.c b/src/shared/mstack.c index 32e95fd23096e..cc3cb9a75f7cb 100644 --- a/src/shared/mstack.c +++ b/src/shared/mstack.c @@ -1032,7 +1032,7 @@ int mstack_bind_mounts( if (mstack->usr_mount_fd >= 0) { _cleanup_close_ int subdir_fd = -EBADF; - r = chaseat(root_fd, "usr", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &subdir_fd); + r = chaseat(root_fd, root_fd, "usr", CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &subdir_fd); if (r < 0) return log_debug_errno(r, "Failed to open mount point inode '%s': %m", where); @@ -1051,7 +1051,7 @@ int mstack_bind_mounts( assert(m->mount_fd >= 0); _cleanup_close_ int subdir_fd = -EBADF; - r = chaseat(root_fd, m->where, CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &subdir_fd); + r = chaseat(root_fd, root_fd, m->where, CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &subdir_fd); if (r < 0) return log_debug_errno(r, "Failed to open mount point inode '%s': %m", m->where); diff --git a/src/shared/tar-util.c b/src/shared/tar-util.c index 3f7dc88a6a9af..dad6337b7588c 100644 --- a/src/shared/tar-util.c +++ b/src/shared/tar-util.c @@ -922,8 +922,8 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { "Invalid hardlink path name '%s' in entry, refusing.", target); _cleanup_close_ int target_fd = -EBADF; - r = chaseat(tree_fd, target, - CHASE_PROHIBIT_SYMLINKS|CHASE_AT_RESOLVE_IN_ROOT|CHASE_NOFOLLOW, + r = chaseat(tree_fd, tree_fd, target, + CHASE_PROHIBIT_SYMLINKS|CHASE_NOFOLLOW, /* ret_path= */ NULL, &target_fd); if (r < 0) return log_error_errno( @@ -953,8 +953,8 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { _cleanup_close_ int target_parent_fd = -EBADF; _cleanup_free_ char *target_filename = NULL; - r = chaseat(tree_fd, target, - CHASE_PROHIBIT_SYMLINKS|CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_EXTRACT_FILENAME|CHASE_NOFOLLOW, + r = chaseat(tree_fd, tree_fd, target, + CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_EXTRACT_FILENAME|CHASE_NOFOLLOW, &target_filename, &target_parent_fd); if (r < 0) return log_error_errno(r, "Failed to find inode '%s' which shall be hardlinked as '%s': %m", diff --git a/src/shared/tests.h b/src/shared/tests.h index adf1b0f689b03..9a2b7c0412825 100644 --- a/src/shared/tests.h +++ b/src/shared/tests.h @@ -477,6 +477,18 @@ _noreturn_ void log_test_failed_internal(const char *file, int line, const char }) #endif +#ifdef __COVERITY__ +# define ASSERT_PATH_EQ(expr1, expr2) __coverity_check__(path_equal((expr1), (expr2))) +#else +# define ASSERT_PATH_EQ(expr1, expr2) \ + ({ \ + const char *_expr1 = (expr1), *_expr2 = (expr2); \ + if (!path_equal(_expr1, _expr2)) \ + log_test_failed("Expected \"%s == %s\", got \"%s != %s\"", \ + #expr1, #expr2, strnull(_expr1), strnull(_expr2)); \ + }) +#endif + #ifdef __COVERITY__ # define ASSERT_NOT_STREQ(expr1, expr2) __coverity_check__(!streq_ptr((expr1), (expr2))) #else diff --git a/src/shared/vpick.c b/src/shared/vpick.c index 38ceb225cd5ae..b68991cda19b6 100644 --- a/src/shared/vpick.c +++ b/src/shared/vpick.c @@ -197,8 +197,9 @@ static int pin_choice( if (inode_fd < 0 || FLAGS_SET(flags, PICK_RESOLVE)) { r = chaseat(toplevel_fd, + toplevel_fd, inode_path, - CHASE_AT_RESOLVE_IN_ROOT, + /* flags= */ 0, FLAGS_SET(flags, PICK_RESOLVE) ? &resolved_path : NULL, inode_fd < 0 ? &inode_fd : NULL); if (r < 0) @@ -327,7 +328,7 @@ static int make_choice( assert(ret); if (inode_fd < 0) { - r = chaseat(toplevel_fd, inode_path, CHASE_AT_RESOLVE_IN_ROOT, NULL, &inode_fd); + r = chaseat(toplevel_fd, toplevel_fd, inode_path, /* flags= */ 0, NULL, &inode_fd); if (r < 0) return r; } @@ -344,7 +345,7 @@ static int make_choice( return log_oom_debug(); _cleanup_close_ int object_fd = -EBADF; - r = chaseat(toplevel_fd, p, CHASE_AT_RESOLVE_IN_ROOT, &object_path, &object_fd); + r = chaseat(toplevel_fd, toplevel_fd, p, /* flags= */ 0, &object_path, &object_fd); if (r == -ENOENT) { *ret = PICK_RESULT_NULL; return 0; diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 0ac35d1b56b8d..0475de3e6e996 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -1056,7 +1056,7 @@ static int resolve_mutable_directory( /* This also creates, e.g., /var/lib/extensions.mutable/usr if needed and all parent * directories plus it also works when the last part is a symlink to the real /usr but we * can't use chase_and_open here because it does not behave the same. */ - r = chase(path, root, CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY|CHASE_PREFIX_ROOT, /* ret_path */ NULL, &path_fd); + r = chase(path, root, CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY|CHASE_PREFIX_ROOT, /* ret_path */ NULL, &path_fd); if (r < 0) return log_error_errno(r, "Failed to chase/create base directory '%s/%s': %m", strempty(root), skip_leading_slash(path)); diff --git a/src/test/test-chase.c b/src/test/test-chase.c index 721f56a250663..ed07ac5cef68d 100644 --- a/src/test/test-chase.c +++ b/src/test/test-chase.c @@ -27,10 +27,10 @@ static void test_chase_extract_filename_one(const char *path, const char *root, log_debug("/* %s(path=%s, root=%s) */", __func__, path, strnull(root)); - assert_se(chase(path, root, CHASE_EXTRACT_FILENAME, &ret1, NULL) > 0); + ASSERT_OK_POSITIVE(chase(path, root, CHASE_EXTRACT_FILENAME, &ret1, NULL)); ASSERT_STREQ(ret1, expected); - assert_se(chase(path, root, 0, &ret2, NULL) > 0); + ASSERT_OK_POSITIVE(chase(path, root, 0, &ret2, NULL)); ASSERT_OK(chase_extract_filename(ret2, root, &fname)); ASSERT_STREQ(fname, expected); } @@ -41,97 +41,84 @@ TEST(chase) { char *temp; const char *top, *p, *pslash, *q, *qslash; struct stat st; - int r; temp = strjoina(arg_test_dir ?: "/tmp", "/test-chase.XXXXXX"); - assert_se(mkdtemp(temp)); + ASSERT_NOT_NULL(mkdtemp(temp)); top = strjoina(temp, "/top"); ASSERT_OK(mkdir(top, 0700)); p = strjoina(top, "/dot"); if (symlink(".", p) < 0) { - assert_se(IN_SET(errno, EINVAL, ENOSYS, ENOTTY, EPERM)); + ASSERT_TRUE(IN_SET(errno, EINVAL, ENOSYS, ENOTTY, EPERM)); log_tests_skipped_errno(errno, "symlink() not possible"); goto cleanup; }; p = strjoina(top, "/dotdot"); - ASSERT_OK(symlink("..", p)); + ASSERT_OK_ERRNO(symlink("..", p)); p = strjoina(top, "/dotdota"); - ASSERT_OK(symlink("../a", p)); + ASSERT_OK_ERRNO(symlink("../a", p)); p = strjoina(temp, "/a"); - ASSERT_OK(symlink("b", p)); + ASSERT_OK_ERRNO(symlink("b", p)); p = strjoina(temp, "/b"); - ASSERT_OK(symlink("/usr", p)); + ASSERT_OK_ERRNO(symlink("/usr", p)); p = strjoina(temp, "/start"); - ASSERT_OK(symlink("top/dot/dotdota", p)); + ASSERT_OK_ERRNO(symlink("top/dot/dotdota", p)); /* Paths that use symlinks underneath the "root" */ - r = chase(p, NULL, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, "/usr")); + ASSERT_OK_POSITIVE(chase(p, NULL, 0, &result, NULL)); + ASSERT_PATH_EQ(result, "/usr"); result = mfree(result); - r = chase(p, "/.//../../../", 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, "/usr")); + ASSERT_OK_POSITIVE(chase(p, "/.//../../../", 0, &result, NULL)); + ASSERT_PATH_EQ(result, "/usr"); result = mfree(result); pslash = strjoina(p, "/"); - r = chase(pslash, NULL, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, "/usr/")); + ASSERT_OK_POSITIVE(chase(pslash, NULL, 0, &result, NULL)); + ASSERT_PATH_EQ(result, "/usr/"); result = mfree(result); - r = chase(p, temp, 0, &result, NULL); - assert_se(r == -ENOENT); - - r = chase(pslash, temp, 0, &result, NULL); - assert_se(r == -ENOENT); + ASSERT_ERROR(chase(p, temp, 0, &result, NULL), ENOENT); + ASSERT_ERROR(chase(pslash, temp, 0, &result, NULL), ENOENT); q = strjoina(temp, "/usr"); - r = chase(p, temp, CHASE_NONEXISTENT, &result, NULL); - assert_se(r == 0); - assert_se(path_equal(result, q)); + ASSERT_OK_ZERO(chase(p, temp, CHASE_NONEXISTENT, &result, NULL)); + ASSERT_PATH_EQ(result, q); result = mfree(result); qslash = strjoina(q, "/"); - r = chase(pslash, temp, CHASE_NONEXISTENT, &result, NULL); - assert_se(r == 0); - assert_se(path_equal(result, qslash)); + ASSERT_OK_ZERO(chase(pslash, temp, CHASE_NONEXISTENT, &result, NULL)); + ASSERT_PATH_EQ(result, qslash); result = mfree(result); - ASSERT_OK(mkdir(q, 0700)); + ASSERT_OK_ERRNO(mkdir(q, 0700)); - r = chase(p, temp, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, q)); + ASSERT_OK_POSITIVE(chase(p, temp, 0, &result, NULL)); + ASSERT_PATH_EQ(result, q); result = mfree(result); - r = chase(pslash, temp, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, qslash)); + ASSERT_OK_POSITIVE(chase(pslash, temp, 0, &result, NULL)); + ASSERT_PATH_EQ(result, qslash); result = mfree(result); p = strjoina(temp, "/slash"); - assert_se(symlink("/", p) >= 0); + ASSERT_OK_ERRNO(symlink("/", p)); - r = chase(p, NULL, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, "/")); + ASSERT_OK_POSITIVE(chase(p, NULL, 0, &result, NULL)); + ASSERT_PATH_EQ(result, "/"); result = mfree(result); - r = chase(p, temp, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, temp)); + ASSERT_OK_POSITIVE(chase(p, temp, 0, &result, NULL)); + ASSERT_PATH_EQ(result, temp); result = mfree(result); /* Tests for CHASE_EXTRACT_FILENAME and chase_extract_filename() */ @@ -150,205 +137,182 @@ TEST(chase) { /* Paths that would "escape" outside of the "root" */ p = strjoina(temp, "/6dots"); - ASSERT_OK(symlink("../../..", p)); + ASSERT_OK_ERRNO(symlink("../../..", p)); - r = chase(p, temp, 0, &result, NULL); - assert_se(r > 0 && path_equal(result, temp)); + ASSERT_OK_POSITIVE(chase(p, temp, 0, &result, NULL)); + ASSERT_PATH_EQ(result, temp); result = mfree(result); p = strjoina(temp, "/6dotsusr"); - ASSERT_OK(symlink("../../../usr", p)); + ASSERT_OK_ERRNO(symlink("../../../usr", p)); - r = chase(p, temp, 0, &result, NULL); - assert_se(r > 0 && path_equal(result, q)); + ASSERT_OK_POSITIVE(chase(p, temp, 0, &result, NULL)); + ASSERT_PATH_EQ(result, q); result = mfree(result); p = strjoina(temp, "/top/8dotsusr"); - ASSERT_OK(symlink("../../../../usr", p)); + ASSERT_OK_ERRNO(symlink("../../../../usr", p)); - r = chase(p, temp, 0, &result, NULL); - assert_se(r > 0 && path_equal(result, q)); + ASSERT_OK_POSITIVE(chase(p, temp, 0, &result, NULL)); + ASSERT_PATH_EQ(result, q); result = mfree(result); /* Paths that contain repeated slashes */ p = strjoina(temp, "/slashslash"); - ASSERT_OK(symlink("///usr///", p)); + ASSERT_OK_ERRNO(symlink("///usr///", p)); - r = chase(p, NULL, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, "/usr")); + ASSERT_OK_POSITIVE(chase(p, NULL, 0, &result, NULL)); + ASSERT_PATH_EQ(result, "/usr"); ASSERT_STREQ(result, "/usr"); /* we guarantee that we drop redundant slashes */ result = mfree(result); - r = chase(p, temp, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, q)); + ASSERT_OK_POSITIVE(chase(p, temp, 0, &result, NULL)); + ASSERT_PATH_EQ(result, q); result = mfree(result); /* Paths underneath the "root" with different UIDs while using CHASE_SAFE */ if (geteuid() == 0 && !userns_has_single_user()) { p = strjoina(temp, "/user"); - ASSERT_OK(mkdir(p, 0755)); - ASSERT_OK(chown(p, UID_NOBODY, GID_NOBODY)); + ASSERT_OK_ERRNO(mkdir(p, 0755)); + ASSERT_OK_ERRNO(chown(p, UID_NOBODY, GID_NOBODY)); q = strjoina(temp, "/user/root"); - ASSERT_OK(mkdir(q, 0755)); + ASSERT_OK_ERRNO(mkdir(q, 0755)); p = strjoina(q, "/link"); - ASSERT_OK(symlink("/", p)); + ASSERT_OK_ERRNO(symlink("/", p)); /* Fail when user-owned directories contain root-owned subdirectories. */ - r = chase(p, temp, CHASE_SAFE, &result, NULL); - assert_se(r == -ENOLINK); + ASSERT_ERROR(chase(p, temp, CHASE_SAFE, &result, NULL), ENOLINK); result = mfree(result); /* Allow this when the user-owned directories are all in the "root". */ - r = chase(p, q, CHASE_SAFE, &result, NULL); - assert_se(r > 0); + ASSERT_OK_POSITIVE(chase(p, q, CHASE_SAFE, &result, NULL)); result = mfree(result); } /* Paths using . */ - r = chase("/etc/./.././", NULL, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, "/")); + ASSERT_OK_POSITIVE(chase("/etc/./.././", NULL, 0, &result, NULL)); + ASSERT_PATH_EQ(result, "/"); result = mfree(result); - r = chase("/etc/./.././", "/etc", 0, &result, NULL); - assert_se(r > 0 && path_equal(result, "/etc")); + ASSERT_OK_POSITIVE(chase("/etc/./.././", "/etc", 0, &result, NULL)); + ASSERT_PATH_EQ(result, "/etc"); result = mfree(result); - r = chase("/../.././//../../etc", NULL, 0, &result, NULL); - assert_se(r > 0); + ASSERT_OK_POSITIVE(chase("/../.././//../../etc", NULL, 0, &result, NULL)); ASSERT_STREQ(result, "/etc"); result = mfree(result); - r = chase("/../.././//../../test-chase.fsldajfl", NULL, CHASE_NONEXISTENT, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase("/../.././//../../test-chase.fsldajfl", NULL, CHASE_NONEXISTENT, &result, NULL)); ASSERT_STREQ(result, "/test-chase.fsldajfl"); result = mfree(result); - r = chase("/../.././//../../etc", "/", CHASE_PREFIX_ROOT, &result, NULL); - assert_se(r > 0); + ASSERT_OK_POSITIVE(chase("/../.././//../../etc", "/", CHASE_PREFIX_ROOT, &result, NULL)); ASSERT_STREQ(result, "/etc"); result = mfree(result); - r = chase("/../.././//../../test-chase.fsldajfl", "/", CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase("/../.././//../../test-chase.fsldajfl", "/", CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &result, NULL)); ASSERT_STREQ(result, "/test-chase.fsldajfl"); result = mfree(result); - r = chase("/.path/with/dot", temp, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &result, NULL); - ASSERT_OK(r); + ASSERT_OK(chase("/.path/with/dot", temp, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &result, NULL)); q = strjoina(temp, "/.path/with/dot"); ASSERT_STREQ(result, q); result = mfree(result); - r = chase("/etc/machine-id/foo", NULL, 0, &result, NULL); - assert_se(IN_SET(r, -ENOTDIR, -ENOENT)); + ASSERT_TRUE(IN_SET(chase("/etc/machine-id/foo", NULL, 0, &result, NULL), -ENOTDIR, -ENOENT)); result = mfree(result); /* Path that loops back to self */ p = strjoina(temp, "/recursive-symlink"); - ASSERT_OK(symlink("recursive-symlink", p)); - r = chase(p, NULL, 0, &result, NULL); - assert_se(r == -ELOOP); + ASSERT_OK_ERRNO(symlink("recursive-symlink", p)); + ASSERT_ERROR(chase(p, NULL, 0, &result, NULL), ELOOP); /* Path which doesn't exist */ p = strjoina(temp, "/idontexist"); - r = chase(p, NULL, 0, &result, NULL); - assert_se(r == -ENOENT); - - r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL); - assert_se(r == 0); - assert_se(path_equal(result, p)); + ASSERT_ERROR(chase(p, NULL, 0, &result, NULL), ENOENT); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_NONEXISTENT, &result, NULL)); + ASSERT_PATH_EQ(result, p); result = mfree(result); p = strjoina(temp, "/idontexist/meneither"); - r = chase(p, NULL, 0, &result, NULL); - assert_se(r == -ENOENT); + ASSERT_ERROR(chase(p, NULL, 0, &result, NULL), ENOENT); - r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL); - assert_se(r == 0); - assert_se(path_equal(result, p)); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_NONEXISTENT, &result, NULL)); + ASSERT_PATH_EQ(result, p); result = mfree(result); /* Relative paths */ ASSERT_OK(safe_getcwd(&pwd)); - ASSERT_OK(chdir(temp)); + ASSERT_OK_ERRNO(chdir(temp)); p = "this/is/a/relative/path"; - r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_NONEXISTENT, &result, NULL)); p = strjoina(temp, "/", p); - assert_se(path_equal(result, p)); + ASSERT_PATH_EQ(result, p); result = mfree(result); p = "this/is/a/relative/path"; - r = chase(p, temp, CHASE_NONEXISTENT, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase(p, temp, CHASE_NONEXISTENT, &result, NULL)); p = strjoina(temp, "/", p); - assert_se(path_equal(result, p)); + ASSERT_PATH_EQ(result, p); result = mfree(result); - assert_se(chdir(pwd) >= 0); + ASSERT_OK_ERRNO(chdir(pwd)); /* Path which doesn't exist, but contains weird stuff */ p = strjoina(temp, "/idontexist/.."); - r = chase(p, NULL, 0, &result, NULL); - assert_se(r == -ENOENT); + ASSERT_ERROR(chase(p, NULL, 0, &result, NULL), ENOENT); - r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL); - assert_se(r == -ENOENT); + ASSERT_ERROR(chase(p, NULL, CHASE_NONEXISTENT, &result, NULL), ENOENT); p = strjoina(temp, "/target"); q = strjoina(temp, "/top"); - assert_se(symlink(q, p) >= 0); + ASSERT_OK_ERRNO(symlink(q, p)); p = strjoina(temp, "/target/idontexist"); - r = chase(p, NULL, 0, &result, NULL); - assert_se(r == -ENOENT); + ASSERT_ERROR(chase(p, NULL, 0, &result, NULL), ENOENT); if (geteuid() == 0 && !userns_has_single_user()) { p = strjoina(temp, "/priv1"); - ASSERT_OK(mkdir(p, 0755)); + ASSERT_OK_ERRNO(mkdir(p, 0755)); q = strjoina(p, "/priv2"); - ASSERT_OK(mkdir(q, 0755)); + ASSERT_OK_ERRNO(mkdir(q, 0755)); ASSERT_OK(chase(q, NULL, CHASE_SAFE, NULL, NULL)); - ASSERT_OK(chown(q, UID_NOBODY, GID_NOBODY)); + ASSERT_OK_ERRNO(chown(q, UID_NOBODY, GID_NOBODY)); ASSERT_OK(chase(q, NULL, CHASE_SAFE, NULL, NULL)); ASSERT_OK(chown(p, UID_NOBODY, GID_NOBODY)); ASSERT_OK(chase(q, NULL, CHASE_SAFE, NULL, NULL)); - assert_se(chown(q, 0, 0) >= 0); - assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) == -ENOLINK); + ASSERT_OK_ERRNO(chown(q, 0, 0)); + ASSERT_ERROR(chase(q, NULL, CHASE_SAFE, NULL, NULL), ENOLINK); - ASSERT_OK(rmdir(q)); - ASSERT_OK(symlink("/etc/passwd", q)); - assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) == -ENOLINK); + ASSERT_OK_ERRNO(rmdir(q)); + ASSERT_OK_ERRNO(symlink("/etc/passwd", q)); + ASSERT_ERROR(chase(q, NULL, CHASE_SAFE, NULL, NULL), ENOLINK); - assert_se(chown(p, 0, 0) >= 0); + ASSERT_OK_ERRNO(chown(p, 0, 0)); ASSERT_OK(chase(q, NULL, CHASE_SAFE, NULL, NULL)); } p = strjoina(temp, "/machine-id-test"); - ASSERT_OK(symlink("/usr/../etc/./machine-id", p)); + ASSERT_OK_ERRNO(symlink("/usr/../etc/./machine-id", p)); - r = chase(p, NULL, 0, NULL, &pfd); - if (r != -ENOENT && sd_id128_get_machine(NULL) >= 0) { + if (chase(p, NULL, 0, NULL, &pfd) != -ENOENT && sd_id128_get_machine(NULL) >= 0) { _cleanup_close_ int fd = -EBADF; sd_id128_t a, b; @@ -360,112 +324,187 @@ TEST(chase) { ASSERT_OK(id128_read_fd(fd, ID128_FORMAT_PLAIN, &a)); ASSERT_OK(sd_id128_get_machine(&b)); - assert_se(sd_id128_equal(a, b)); + ASSERT_TRUE(sd_id128_equal(a, b)); } - assert_se(lstat(p, &st) >= 0); - r = chase_and_unlink(p, NULL, 0, 0, &result); - assert_se(r == 0); - assert_se(path_equal(result, p)); + ASSERT_OK_ERRNO(lstat(p, &st)); + ASSERT_OK_ZERO(chase_and_unlink(p, NULL, 0, 0, &result)); + ASSERT_PATH_EQ(result, p); result = mfree(result); - assert_se(lstat(p, &st) == -1 && errno == ENOENT); + ASSERT_ERROR_ERRNO(lstat(p, &st), ENOENT); /* Test CHASE_NOFOLLOW */ p = strjoina(temp, "/target"); q = strjoina(temp, "/symlink"); - assert_se(symlink(p, q) >= 0); - r = chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd); - ASSERT_OK(r); - ASSERT_OK(pfd); - assert_se(path_equal(result, q)); - ASSERT_OK(fstat(pfd, &st)); - assert_se(S_ISLNK(st.st_mode)); + ASSERT_OK_ERRNO(symlink(p, q)); + ASSERT_OK(chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd)); + ASSERT_PATH_EQ(result, q); + ASSERT_OK_ERRNO(fstat(pfd, &st)); + ASSERT_TRUE(S_ISLNK(st.st_mode)); result = mfree(result); pfd = safe_close(pfd); /* s1 -> s2 -> nonexistent */ q = strjoina(temp, "/s1"); - ASSERT_OK(symlink("s2", q)); + ASSERT_OK_ERRNO(symlink("s2", q)); p = strjoina(temp, "/s2"); - ASSERT_OK(symlink("nonexistent", p)); - r = chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd); - ASSERT_OK(r); - ASSERT_OK(pfd); - assert_se(path_equal(result, q)); - ASSERT_OK(fstat(pfd, &st)); - assert_se(S_ISLNK(st.st_mode)); + ASSERT_OK_ERRNO(symlink("nonexistent", p)); + ASSERT_OK(chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd)); + ASSERT_PATH_EQ(result, q); + ASSERT_OK_ERRNO(fstat(pfd, &st)); + ASSERT_TRUE(S_ISLNK(st.st_mode)); result = mfree(result); pfd = safe_close(pfd); /* Test CHASE_STEP */ p = strjoina(temp, "/start"); - r = chase(p, NULL, CHASE_STEP, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_STEP, &result, NULL)); p = strjoina(temp, "/top/dot/dotdota"); ASSERT_STREQ(p, result); result = mfree(result); - r = chase(p, NULL, CHASE_STEP, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_STEP, &result, NULL)); p = strjoina(temp, "/top/dotdota"); ASSERT_STREQ(p, result); result = mfree(result); - r = chase(p, NULL, CHASE_STEP, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_STEP, &result, NULL)); p = strjoina(temp, "/top/../a"); ASSERT_STREQ(p, result); result = mfree(result); - r = chase(p, NULL, CHASE_STEP, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_STEP, &result, NULL)); p = strjoina(temp, "/a"); ASSERT_STREQ(p, result); result = mfree(result); - r = chase(p, NULL, CHASE_STEP, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_STEP, &result, NULL)); p = strjoina(temp, "/b"); ASSERT_STREQ(p, result); result = mfree(result); - r = chase(p, NULL, CHASE_STEP, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_STEP, &result, NULL)); ASSERT_STREQ(result, "/usr"); result = mfree(result); - r = chase("/usr", NULL, CHASE_STEP, &result, NULL); - assert_se(r > 0); + ASSERT_OK_POSITIVE(chase("/usr", NULL, CHASE_STEP, &result, NULL)); ASSERT_STREQ(result, "/usr"); result = mfree(result); /* Make sure that symlinks in the "root" path are not resolved, but those below are */ p = strjoina("/etc/..", temp, "/self"); - assert_se(symlink(".", p) >= 0); + ASSERT_OK_ERRNO(symlink(".", p)); q = strjoina(p, "/top/dot/dotdota"); - r = chase(q, p, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(path_startswith(result, p), "usr")); + ASSERT_OK_POSITIVE(chase(q, p, 0, &result, NULL)); + ASSERT_PATH_EQ(path_startswith(result, p), "usr"); result = mfree(result); /* Test CHASE_PROHIBIT_SYMLINKS */ - assert_se(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG); - assert_se(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG); - assert_se(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG); - assert_se(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG); - assert_se(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG); - assert_se(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG); + ASSERT_ERROR(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL), EREMCHG); + ASSERT_ERROR(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL), EREMCHG); + ASSERT_ERROR(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL), EREMCHG); + ASSERT_ERROR(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL), EREMCHG); + ASSERT_ERROR(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL), EREMCHG); + ASSERT_ERROR(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL), EREMCHG); cleanup: ASSERT_OK(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL)); } +TEST(chase_and_open) { + _cleanup_free_ char *result = NULL; + _cleanup_close_ int fd = -EBADF; + + /* Test chase_and_open() with various CHASE_PARENT / CHASE_EXTRACT_FILENAME combinations. */ + + /* No CHASE_PARENT, no CHASE_EXTRACT_FILENAME, with ret_path — opens the target, returns full path. */ + fd = ASSERT_OK(chase_and_open("/usr/lib", NULL, 0, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_STREQ(result, "/usr/lib"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT with ret_path — opens parent dir, returns full path including final component. */ + fd = ASSERT_OK(chase_and_open("/usr/lib", NULL, CHASE_PARENT, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "lib", F_OK, 0)); + ASSERT_STREQ(result, "/usr/lib"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT|CHASE_EXTRACT_FILENAME — opens parent dir, returns just the filename. */ + fd = ASSERT_OK(chase_and_open("/usr/lib", NULL, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "lib", F_OK, 0)); + ASSERT_STREQ(result, "lib"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_EXTRACT_FILENAME only — opens the target itself, returns just the filename. */ + fd = ASSERT_OK(chase_and_open("/usr/lib", NULL, CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_STREQ(result, "lib"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_EXTRACT_FILENAME on a regular file (regression test for a bug where chase_and_open() + * reopened the parent directory instead of the target file). */ + fd = ASSERT_OK(chase_and_open("/etc/os-release", NULL, CHASE_EXTRACT_FILENAME, O_PATH|O_CLOEXEC, &result)); + ASSERT_STREQ(result, "os-release"); + ASSERT_OK(fd_verify_regular(fd)); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT through a symlink — symlink is followed, parent of the target is opened. */ + fd = ASSERT_OK(chase_and_open("/etc/os-release", NULL, CHASE_PARENT, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_NOT_NULL(result); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT|CHASE_NOFOLLOW through a symlink — symlink is NOT followed, parent of the + * symlink is opened. */ + fd = ASSERT_OK(chase_and_open("/etc/os-release", NULL, CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "os-release", F_OK, AT_SYMLINK_NOFOLLOW)); + ASSERT_STREQ(result, "/etc/os-release"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME through a symlink — parent of the symlink + * is opened, returns just the symlink name. */ + fd = ASSERT_OK(chase_and_open("/etc/os-release", NULL, CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "os-release", F_OK, AT_SYMLINK_NOFOLLOW)); + ASSERT_STREQ(result, "os-release"); + fd = safe_close(fd); + result = mfree(result); + + /* When the resolved path equals the root directory itself, the filename should be "." — not + * the basename of the root directory. This is the edge case that chase_extract_filename() + * handles by stripping the root prefix before extracting, which plain path_extract_filename() + * would get wrong. */ + _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; + _cleanup_close_ int tfd = -EBADF; + + tfd = ASSERT_OK(mkdtemp_open(NULL, 0, &tmpdir)); + /* Create a symlink to "/" — when chased under tmpdir as root, it resolves to tmpdir itself. */ + ASSERT_OK_ERRNO(symlinkat("/", tfd, "to_root")); + + _cleanup_free_ char *link_path = ASSERT_NOT_NULL(path_join(tmpdir, "to_root")); + fd = ASSERT_OK(chase_and_open(link_path, tmpdir, 0, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_PATH_EQ(result, tmpdir); + fd = safe_close(fd); + result = mfree(result); +} + TEST(chaseat) { _cleanup_(rm_rf_physical_and_freep) char *t = NULL; - _cleanup_close_ int tfd = -EBADF, fd = -EBADF; + _cleanup_close_ int tfd = -EBADF, fd = -EBADF, fd2 = -EBADF; _cleanup_free_ char *result = NULL; _cleanup_closedir_ DIR *dir = NULL; _cleanup_fclose_ FILE *f = NULL; @@ -474,13 +513,13 @@ TEST(chaseat) { ASSERT_OK(tfd = mkdtemp_open(NULL, 0, &t)); - /* Test that AT_FDCWD with CHASE_AT_RESOLVE_IN_ROOT resolves against / and not the current working + /* Test that AT_FDCWD resolves against / and not the current working * directory. */ - ASSERT_OK(symlinkat("/usr", tfd, "abc")); + ASSERT_OK_ERRNO(symlinkat("/usr", tfd, "abc")); p = strjoina(t, "/abc"); - ASSERT_OK(chaseat(AT_FDCWD, p, CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + ASSERT_OK(chaseat(XAT_FDROOT, AT_FDCWD, p, 0, &result, NULL)); ASSERT_STREQ(result, "/usr"); result = mfree(result); @@ -489,11 +528,24 @@ TEST(chaseat) { fd = open("/", O_CLOEXEC | O_DIRECTORY | O_PATH); ASSERT_OK(fd); - ASSERT_OK(chaseat(fd, p, 0, &result, NULL)); + ASSERT_OK(chaseat(XAT_FDROOT, fd, p, 0, &result, NULL)); ASSERT_STREQ(result, "/usr"); result = mfree(result); - ASSERT_OK(chaseat(fd, p, CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + /* (XAT_FDROOT, fd-of-/, relative): fd points to "/" which is the host root, so root_fd is + * normalized to XAT_FDROOT internally. A relative path resolves from "/". Result is absolute. */ + ASSERT_OK(chaseat(XAT_FDROOT, fd, "usr", 0, &result, &fd2)); + ASSERT_STREQ(result, "/usr"); + ASSERT_TRUE(inode_same_at(fd2, NULL, AT_FDCWD, "/usr", AT_EMPTY_PATH)); + result = mfree(result); + fd2 = safe_close(fd2); + + /* Same without ret_path to exercise the shortcut. */ + ASSERT_OK(chaseat(XAT_FDROOT, fd, "usr", 0, NULL, &fd2)); + ASSERT_TRUE(inode_same_at(fd2, NULL, AT_FDCWD, "/usr", AT_EMPTY_PATH)); + fd2 = safe_close(fd2); + + ASSERT_OK(chaseat(fd, fd, p, 0, &result, NULL)); ASSERT_STREQ(result, "/usr"); result = mfree(result); @@ -501,12 +553,12 @@ TEST(chaseat) { /* Same but with XAT_FDROOT */ _cleanup_close_ int found_fd1 = -EBADF; - ASSERT_OK(chaseat(XAT_FDROOT, p, 0, &result, &found_fd1)); + ASSERT_OK(chaseat(XAT_FDROOT, XAT_FDROOT, p, 0, &result, &found_fd1)); ASSERT_STREQ(result, "/usr"); result = mfree(result); _cleanup_close_ int found_fd2 = -EBADF; - ASSERT_OK(chaseat(XAT_FDROOT, p, CHASE_AT_RESOLVE_IN_ROOT, &result, &found_fd2)); + ASSERT_OK(chaseat(XAT_FDROOT, XAT_FDROOT, p, 0, &result, &found_fd2)); ASSERT_STREQ(result, "/usr"); result = mfree(result); assert(fd_inode_same(found_fd1, found_fd2) > 0); @@ -514,12 +566,12 @@ TEST(chaseat) { /* Do the same XAT_FDROOT tests again, this time without querying the path, so that the open_tree() * shortcut can work */ _cleanup_close_ int found_fd3 = -EBADF; - ASSERT_OK(chaseat(XAT_FDROOT, p, 0, NULL, &found_fd3)); + ASSERT_OK(chaseat(XAT_FDROOT, XAT_FDROOT, p, 0, NULL, &found_fd3)); assert(fd_inode_same(found_fd1, found_fd3) > 0); assert(fd_inode_same(found_fd2, found_fd3) > 0); _cleanup_close_ int found_fd4 = -EBADF; - ASSERT_OK(chaseat(XAT_FDROOT, p, CHASE_AT_RESOLVE_IN_ROOT, NULL, &found_fd4)); + ASSERT_OK(chaseat(XAT_FDROOT, XAT_FDROOT, p, 0, NULL, &found_fd4)); assert(fd_inode_same(found_fd1, found_fd4) > 0); assert(fd_inode_same(found_fd2, found_fd4) > 0); assert(fd_inode_same(found_fd3, found_fd4) > 0); @@ -529,102 +581,138 @@ TEST(chaseat) { found_fd3 = safe_close(found_fd3); found_fd4 = safe_close(found_fd4); - /* If the file descriptor does not point to the root directory, the result will be relative - * unless the result is outside of the specified file descriptor. */ + /* (XAT_FDROOT, XAT_FDROOT, relative): relative path from host root. XAT_FDROOT as dir_fd + * redirects to root_fd which is also XAT_FDROOT (/), so "usr" resolves to /usr. Result is + * absolute because root_fd == XAT_FDROOT. */ + ASSERT_OK(chaseat(XAT_FDROOT, XAT_FDROOT, "usr", 0, &result, NULL)); + ASSERT_STREQ(result, "/usr"); + result = mfree(result); + + /* Same without ret_path so the shortcut can fire. */ + ASSERT_OK(chaseat(XAT_FDROOT, XAT_FDROOT, "usr", 0, NULL, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, AT_FDCWD, "/usr", AT_EMPTY_PATH)); + fd = safe_close(fd); + + /* (XAT_FDROOT, AT_FDCWD, relative): relative path from current working directory. */ + _cleanup_free_ char *cwd_saved = NULL; + ASSERT_OK(safe_getcwd(&cwd_saved)); + + ASSERT_OK_ERRNO(chdir(t)); - ASSERT_OK(chaseat(tfd, "abc", 0, &result, NULL)); + ASSERT_OK(chaseat(XAT_FDROOT, AT_FDCWD, "abc", 0, &result, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, AT_FDCWD, "/usr", AT_EMPTY_PATH)); ASSERT_STREQ(result, "/usr"); + fd = safe_close(fd); + result = mfree(result); + + /* Same without ret_path to exercise the shortcut. */ + ASSERT_OK(chaseat(XAT_FDROOT, AT_FDCWD, "abc", 0, NULL, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, AT_FDCWD, "/usr", AT_EMPTY_PATH)); + fd = safe_close(fd); + + /* A plain file (no symlink indirection) should also work. */ + fd = ASSERT_OK_ERRNO(openat(tfd, "cwd_test", O_CREAT|O_CLOEXEC, 0600)); + fd = safe_close(fd); + + ASSERT_OK(chaseat(XAT_FDROOT, AT_FDCWD, "cwd_test", 0, &result, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, tfd, "cwd_test", AT_EMPTY_PATH)); + fd = safe_close(fd); result = mfree(result); - ASSERT_OK(chaseat(tfd, "/abc", 0, &result, NULL)); + ASSERT_OK_ERRNO(chdir(cwd_saved)); + + /* If the file descriptor does not point to the root directory, the result will be relative + * unless the result is outside of the specified file descriptor. */ + + ASSERT_OK(chaseat(XAT_FDROOT, tfd, "abc", 0, &result, NULL)); ASSERT_STREQ(result, "/usr"); result = mfree(result); - assert_se(chaseat(tfd, "abc", CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL) == -ENOENT); - assert_se(chaseat(tfd, "/abc", CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL) == -ENOENT); + ASSERT_ERROR(chaseat(tfd, tfd, "abc", 0, NULL, NULL), ENOENT); + ASSERT_ERROR(chaseat(tfd, tfd, "/abc", 0, NULL, NULL), ENOENT); - ASSERT_OK(chaseat(tfd, "abc", CHASE_AT_RESOLVE_IN_ROOT | CHASE_NONEXISTENT, &result, NULL)); + ASSERT_OK(chaseat(tfd, tfd, "abc", CHASE_NONEXISTENT, &result, NULL)); ASSERT_STREQ(result, "usr"); result = mfree(result); - ASSERT_OK(chaseat(tfd, "/abc", CHASE_AT_RESOLVE_IN_ROOT | CHASE_NONEXISTENT, &result, NULL)); + ASSERT_OK(chaseat(tfd, tfd, "/abc", CHASE_NONEXISTENT, &result, NULL)); ASSERT_STREQ(result, "usr"); result = mfree(result); /* Test that absolute path or not are the same when resolving relative to a directory file * descriptor and that we always get a relative path back. */ - ASSERT_OK(fd = openat(tfd, "def", O_CREAT|O_CLOEXEC, 0700)); + fd = ASSERT_OK_ERRNO(openat(tfd, "def", O_CREAT|O_CLOEXEC, 0700)); fd = safe_close(fd); - ASSERT_OK(symlinkat("/def", tfd, "qed")); - ASSERT_OK(chaseat(tfd, "qed", CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + ASSERT_OK_ERRNO(symlinkat("/def", tfd, "qed")); + ASSERT_OK(chaseat(tfd, tfd, "qed", 0, &result, NULL)); ASSERT_STREQ(result, "def"); result = mfree(result); - ASSERT_OK(chaseat(tfd, "/qed", CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + ASSERT_OK(chaseat(tfd, tfd, "/qed", 0, &result, NULL)); ASSERT_STREQ(result, "def"); result = mfree(result); - /* Valid directory file descriptor without CHASE_AT_RESOLVE_IN_ROOT should resolve symlinks against + /* Valid directory file descriptor should resolve symlinks against * host's root. */ - assert_se(chaseat(tfd, "/qed", 0, NULL, NULL) == -ENOENT); + ASSERT_ERROR(chaseat(XAT_FDROOT, tfd, "/qed", 0, NULL, NULL), ENOENT); /* Test CHASE_PARENT */ - ASSERT_OK(fd = open_mkdir_at(tfd, "chase", O_CLOEXEC, 0755)); - ASSERT_OK(symlinkat("/def", fd, "parent")); + fd = ASSERT_OK(open_mkdir_at(tfd, "chase", O_CLOEXEC, 0755)); + ASSERT_OK_ERRNO(symlinkat("/def", fd, "parent")); fd = safe_close(fd); /* Make sure that when we chase a symlink parent directory, that we chase the parent directory of the * symlink target and not the symlink itself. But if we add CHASE_NOFOLLOW, we get the parent * directory of the symlink itself. */ - ASSERT_OK(chaseat(tfd, "chase/parent", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd)); - ASSERT_OK(faccessat(fd, "def", F_OK, 0)); + ASSERT_OK(chaseat(tfd, tfd, "chase/parent", CHASE_PARENT, &result, &fd)); + ASSERT_OK_ERRNO(faccessat(fd, "def", F_OK, 0)); ASSERT_STREQ(result, "def"); fd = safe_close(fd); result = mfree(result); - ASSERT_OK(chaseat(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW, &result, &fd)); - ASSERT_OK(faccessat(fd, "parent", F_OK, AT_SYMLINK_NOFOLLOW)); + ASSERT_OK(chaseat(tfd, tfd, "chase/parent", CHASE_PARENT|CHASE_NOFOLLOW, &result, &fd)); + ASSERT_OK_ERRNO(faccessat(fd, "parent", F_OK, AT_SYMLINK_NOFOLLOW)); ASSERT_STREQ(result, "chase/parent"); fd = safe_close(fd); result = mfree(result); - ASSERT_OK(chaseat(tfd, "chase", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd)); - ASSERT_OK(faccessat(fd, "chase", F_OK, 0)); + ASSERT_OK(chaseat(tfd, tfd, "chase", CHASE_PARENT, &result, &fd)); + ASSERT_OK_ERRNO(faccessat(fd, "chase", F_OK, 0)); ASSERT_STREQ(result, "chase"); fd = safe_close(fd); result = mfree(result); - ASSERT_OK(chaseat(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + ASSERT_OK(chaseat(tfd, tfd, "/", CHASE_PARENT, &result, NULL)); ASSERT_STREQ(result, "."); result = mfree(result); - assert_se(chaseat(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + ASSERT_OK(chaseat(tfd, tfd, ".", CHASE_PARENT, &result, NULL)); ASSERT_STREQ(result, "."); result = mfree(result); /* Test CHASE_MKDIR_0755 */ - ASSERT_OK(chaseat(tfd, "m/k/d/i/r", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL)); - ASSERT_OK(faccessat(tfd, "m/k/d/i", F_OK, 0)); - assert_se(RET_NERRNO(faccessat(tfd, "m/k/d/i/r", F_OK, 0)) == -ENOENT); + ASSERT_OK(chaseat(XAT_FDROOT, tfd, "m/k/d/i/r", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL)); + ASSERT_OK_ERRNO(faccessat(tfd, "m/k/d/i", F_OK, 0)); + ASSERT_ERROR(RET_NERRNO(faccessat(tfd, "m/k/d/i/r", F_OK, 0)), ENOENT); ASSERT_STREQ(result, "m/k/d/i/r"); result = mfree(result); - ASSERT_OK(chaseat(tfd, "m/../q", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL)); - ASSERT_OK(faccessat(tfd, "m", F_OK, 0)); - assert_se(RET_NERRNO(faccessat(tfd, "q", F_OK, 0)) == -ENOENT); + ASSERT_OK(chaseat(XAT_FDROOT, tfd, "m/../q", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL)); + ASSERT_OK_ERRNO(faccessat(tfd, "m", F_OK, 0)); + ASSERT_ERROR(RET_NERRNO(faccessat(tfd, "q", F_OK, 0)), ENOENT); ASSERT_STREQ(result, "q"); result = mfree(result); - assert_se(chaseat(tfd, "i/../p", CHASE_MKDIR_0755|CHASE_NONEXISTENT, NULL, NULL) == -ENOENT); + ASSERT_ERROR(chaseat(XAT_FDROOT, tfd, "i/../p", CHASE_MKDIR_0755|CHASE_NONEXISTENT, NULL, NULL), ENOENT); /* Test CHASE_MKDIR_0755|CHASE_PARENT — creates intermediate dirs but not the final component */ - ASSERT_OK(chaseat(tfd, "mkp/a/r/e/n/t/file", CHASE_MKDIR_0755|CHASE_PARENT, &result, &fd)); - ASSERT_OK(faccessat(tfd, "mkp/a/r/e/n/t", F_OK, 0)); - assert_se(RET_NERRNO(faccessat(tfd, "mkp/a/r/e/n/t/file", F_OK, 0)) == -ENOENT); + ASSERT_OK(chaseat(XAT_FDROOT, tfd, "mkp/a/r/e/n/t/file", CHASE_MKDIR_0755|CHASE_PARENT, &result, &fd)); + ASSERT_OK_ERRNO(faccessat(tfd, "mkp/a/r/e/n/t", F_OK, 0)); + ASSERT_ERROR(RET_NERRNO(faccessat(tfd, "mkp/a/r/e/n/t/file", F_OK, 0)), ENOENT); ASSERT_OK(fd_verify_directory(fd)); fd = safe_close(fd); ASSERT_STREQ(result, "mkp/a/r/e/n/t/file"); @@ -632,68 +720,133 @@ TEST(chaseat) { /* Test CHASE_EXTRACT_FILENAME */ - ASSERT_OK(chaseat(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME, &result, &fd)); - ASSERT_OK(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW)); + ASSERT_OK(chaseat(tfd, tfd, "chase/parent", CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME, &result, &fd)); + ASSERT_OK_ERRNO(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW)); ASSERT_STREQ(result, "parent"); fd = safe_close(fd); result = mfree(result); - ASSERT_OK(chaseat(tfd, "chase", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, &fd)); - ASSERT_OK(faccessat(fd, result, F_OK, 0)); + ASSERT_OK(chaseat(tfd, tfd, "chase", CHASE_PARENT|CHASE_EXTRACT_FILENAME, &result, &fd)); + ASSERT_OK_ERRNO(faccessat(fd, result, F_OK, 0)); ASSERT_STREQ(result, "chase"); fd = safe_close(fd); result = mfree(result); - ASSERT_OK(chaseat(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL)); + ASSERT_OK(chaseat(tfd, tfd, "/", CHASE_PARENT|CHASE_EXTRACT_FILENAME, &result, NULL)); ASSERT_STREQ(result, "."); result = mfree(result); - ASSERT_OK(chaseat(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL)); + ASSERT_OK(chaseat(tfd, tfd, ".", CHASE_PARENT|CHASE_EXTRACT_FILENAME, &result, NULL)); ASSERT_STREQ(result, "."); result = mfree(result); - ASSERT_OK(chaseat(tfd, NULL, CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL)); + ASSERT_OK(chaseat(tfd, tfd, NULL, CHASE_PARENT|CHASE_EXTRACT_FILENAME, &result, NULL)); ASSERT_STREQ(result, "."); result = mfree(result); /* Test chase_and_openat() */ - fd = chase_and_openat(tfd, "o/p/e/n/f/i/l/e", CHASE_MKDIR_0755, O_CREAT|O_EXCL|O_CLOEXEC, NULL); - ASSERT_OK(fd); + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, "o/p/e/n/f/i/l/e", CHASE_MKDIR_0755, O_CREAT|O_EXCL|O_CLOEXEC, NULL)); ASSERT_OK(fd_verify_regular(fd)); fd = safe_close(fd); - fd = chase_and_openat(tfd, "o/p/e/n/d/i/r", CHASE_MKDIR_0755, O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, NULL); - ASSERT_OK(fd); + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, "o/p/e/n/d/i/r", CHASE_MKDIR_0755, O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, NULL)); ASSERT_OK(fd_verify_directory(fd)); fd = safe_close(fd); - fd = chase_and_openat(tfd, NULL, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result); - ASSERT_OK(fd); + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, NULL, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); ASSERT_STREQ(result, "."); fd = safe_close(fd); result = mfree(result); /* Test chase_and_openat() with CHASE_MKDIR_0755|CHASE_PARENT — opens parent dir */ - fd = chase_and_openat(tfd, "mkopen/p/a/r/file.txt", CHASE_MKDIR_0755|CHASE_PARENT, O_RDONLY|O_CLOEXEC, NULL); - ASSERT_OK(fd); + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, "mkopen/p/a/r/file.txt", CHASE_MKDIR_0755|CHASE_PARENT, O_RDONLY|O_CLOEXEC, NULL)); ASSERT_OK(fd_verify_directory(fd)); ASSERT_OK(faccessat(tfd, "mkopen/p/a/r", F_OK, 0)); - assert_se(RET_NERRNO(faccessat(tfd, "mkopen/p/a/r/file.txt", F_OK, 0)) == -ENOENT); + ASSERT_ERROR(RET_NERRNO(faccessat(tfd, "mkopen/p/a/r/file.txt", F_OK, 0)), ENOENT); fd = safe_close(fd); /* Test chase_and_openat() with CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY + O_CREAT — creates and opens target dir */ - fd = chase_and_openat(tfd, "mkopen/d/i/r/target", CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, O_CREAT|O_RDONLY|O_CLOEXEC, NULL); - ASSERT_OK(fd); + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, "mkopen/d/i/r/target", CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, O_CREAT|O_RDONLY|O_CLOEXEC, NULL)); ASSERT_OK(fd_verify_directory(fd)); ASSERT_OK(faccessat(tfd, "mkopen/d/i/r/target", F_OK, 0)); fd = safe_close(fd); + /* Test chase_and_openat() with various CHASE_PARENT / CHASE_EXTRACT_FILENAME combinations */ + + /* No CHASE_PARENT, no CHASE_EXTRACT_FILENAME, with ret_path — opens the target, returns full path. */ + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, "o/p/e/n/d/i/r", 0, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_STREQ(result, "o/p/e/n/d/i/r"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT with ret_path — opens parent dir, returns full path including final component. */ + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, "o/p/e/n/d/i/r", CHASE_PARENT, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "r", F_OK, 0)); + ASSERT_STREQ(result, "o/p/e/n/d/i/r"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT|CHASE_EXTRACT_FILENAME with a real multi-component path — opens parent dir, + * returns just the filename. */ + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, "o/p/e/n/d/i/r", CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "r", F_OK, 0)); + ASSERT_STREQ(result, "r"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_EXTRACT_FILENAME only (without CHASE_PARENT) — opens the target itself, returns just + * the filename. */ + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, "o/p/e/n/d/i/r", CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_STREQ(result, "r"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT through a symlink — the symlink is followed, parent of the target is opened. + * "chase/parent" where parent→/def: resolves to /def, parent is the root dir. */ + fd = ASSERT_OK(chase_and_openat(tfd, tfd, "chase/parent", CHASE_PARENT, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "def", F_OK, 0)); + ASSERT_STREQ(result, "def"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT|CHASE_EXTRACT_FILENAME through a symlink — parent of the target is opened, + * returns just the target filename. */ + fd = ASSERT_OK(chase_and_openat(tfd, tfd, "chase/parent", CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "def", F_OK, 0)); + ASSERT_STREQ(result, "def"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT|CHASE_NOFOLLOW through a symlink — the symlink is NOT followed, parent of the + * symlink is opened. */ + fd = ASSERT_OK(chase_and_openat(tfd, tfd, "chase/parent", CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "parent", F_OK, AT_SYMLINK_NOFOLLOW)); + ASSERT_STREQ(result, "chase/parent"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME through a symlink — parent of the symlink + * is opened, returns just the symlink name. */ + fd = ASSERT_OK(chase_and_openat(tfd, tfd, "chase/parent", CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "parent", F_OK, AT_SYMLINK_NOFOLLOW)); + ASSERT_STREQ(result, "parent"); + fd = safe_close(fd); + result = mfree(result); + /* Test chase_and_openatdir() */ - ASSERT_OK(chase_and_opendirat(tfd, "o/p/e/n/d/i", 0, &result, &dir)); + ASSERT_OK(chase_and_opendirat(XAT_FDROOT, tfd, "o/p/e/n/d/i", 0, &result, &dir)); FOREACH_DIRENT(de, dir, assert_not_reached()) ASSERT_STREQ(de->d_name, "r"); ASSERT_STREQ(result, "o/p/e/n/d/i"); @@ -701,57 +854,165 @@ TEST(chaseat) { /* Test chase_and_statat() */ - ASSERT_OK(chase_and_statat(tfd, "o/p", 0, &result, &st)); + ASSERT_OK(chase_and_statat(XAT_FDROOT, tfd, "o/p", 0, &result, &st)); ASSERT_OK(stat_verify_directory(&st)); ASSERT_STREQ(result, "o/p"); result = mfree(result); /* Test chase_and_accessat() */ - ASSERT_OK(chase_and_accessat(tfd, "o/p/e", 0, F_OK, &result)); + ASSERT_OK(chase_and_accessat(XAT_FDROOT, tfd, "o/p/e", 0, F_OK, &result)); ASSERT_STREQ(result, "o/p/e"); result = mfree(result); /* Test chase_and_fopenat_unlocked() */ - ASSERT_OK(chase_and_fopenat_unlocked(tfd, "o/p/e/n/f/i/l/e", 0, "re", &result, &f)); - assert_se(fread(&(char[1]) {}, 1, 1, f) == 0); - assert_se(feof(f)); + ASSERT_OK(chase_and_fopenat_unlocked(XAT_FDROOT, tfd, "o/p/e/n/f/i/l/e", 0, "re", &result, &f)); + ASSERT_EQ(fread(&(char[1]) {}, 1, 1, f), 0u); + ASSERT_TRUE(feof(f)); f = safe_fclose(f); ASSERT_STREQ(result, "o/p/e/n/f/i/l/e"); result = mfree(result); /* Test chase_and_unlinkat() */ - ASSERT_OK(chase_and_unlinkat(tfd, "o/p/e/n/f/i/l/e", 0, 0, &result)); + ASSERT_OK(chase_and_unlinkat(XAT_FDROOT, tfd, "o/p/e/n/f/i/l/e", 0, 0, &result)); ASSERT_STREQ(result, "o/p/e/n/f/i/l/e"); result = mfree(result); /* Test chase_and_open_parent_at() */ - ASSERT_OK(fd = chase_and_open_parent_at(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_NOFOLLOW, &result)); - ASSERT_OK(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW)); + fd = ASSERT_OK(chase_and_open_parent_at(XAT_FDROOT, tfd, "chase/parent", CHASE_NOFOLLOW, &result)); + ASSERT_OK_ERRNO(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW)); ASSERT_STREQ(result, "parent"); fd = safe_close(fd); result = mfree(result); - ASSERT_OK(fd = chase_and_open_parent_at(tfd, "chase", CHASE_AT_RESOLVE_IN_ROOT, &result)); - ASSERT_OK(faccessat(fd, result, F_OK, 0)); + fd = ASSERT_OK(chase_and_open_parent_at(XAT_FDROOT, tfd, "chase", 0, &result)); + ASSERT_OK_ERRNO(faccessat(fd, result, F_OK, 0)); ASSERT_STREQ(result, "chase"); fd = safe_close(fd); result = mfree(result); - ASSERT_OK(fd = chase_and_open_parent_at(tfd, "/", CHASE_AT_RESOLVE_IN_ROOT, &result)); + fd = ASSERT_OK(chase_and_open_parent_at(XAT_FDROOT, tfd, "/", 0, &result)); ASSERT_STREQ(result, "."); fd = safe_close(fd); result = mfree(result); - ASSERT_OK(fd = chase_and_open_parent_at(tfd, ".", CHASE_AT_RESOLVE_IN_ROOT, &result)); + fd = ASSERT_OK(chase_and_open_parent_at(XAT_FDROOT, tfd, ".", 0, &result)); ASSERT_STREQ(result, "."); fd = safe_close(fd); result = mfree(result); } +TEST(chaseat_separate_root_and_dir) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int root_fd = -EBADF, sub_fd = -EBADF, fd = -EBADF; + _cleanup_free_ char *result = NULL; + + /* Exercise chaseat() with root_fd != dir_fd. The root marks the chroot boundary (symlinks may + * not escape it, absolute symlinks resolve to it), while dir_fd is the starting directory for + * relative paths. */ + + root_fd = ASSERT_OK(mkdtemp_open(NULL, 0, &t)); + + /* Create a file at the root and a subdirectory containing another file. */ + ASSERT_OK_ERRNO(mkdirat(root_fd, "sub", 0755)); + sub_fd = ASSERT_OK_ERRNO(openat(root_fd, "sub", O_CLOEXEC|O_DIRECTORY|O_PATH)); + + fd = ASSERT_OK_ERRNO(openat(root_fd, "outside", O_CREAT|O_CLOEXEC, 0600)); + fd = safe_close(fd); + + fd = ASSERT_OK_ERRNO(openat(sub_fd, "inside", O_CREAT|O_CLOEXEC, 0600)); + fd = safe_close(fd); + + /* Relative lookup from sub_fd under root_fd finds sub's own files. */ + ASSERT_OK(chaseat(root_fd, sub_fd, "inside", 0, &result, NULL)); + ASSERT_STREQ(result, "inside"); + result = mfree(result); + + /* Absolute path with dir_fd=sub_fd and root_fd set: path is relative to root_fd so "/inside" finds + * nothing. */ + ASSERT_ERROR(chaseat(root_fd, sub_fd, "/inside", 0, &result, NULL), ENOENT); + ASSERT_OK_ZERO(chaseat(root_fd, sub_fd, "/inside", CHASE_NONEXISTENT, &result, NULL)); + result = mfree(result); + + /* "../outside" from sub_fd goes up one level (within root), finds root's file. */ + ASSERT_OK(chaseat(root_fd, sub_fd, "../outside", 0, &result, NULL)); + ASSERT_STREQ(result, "../outside"); + result = mfree(result); + + /* "../../../outside" cannot escape above root_fd — clamped. Still resolves to root's file. */ + ASSERT_OK(chaseat(root_fd, sub_fd, "../../../outside", 0, &result, NULL)); + ASSERT_STREQ(result, "../outside"); + result = mfree(result); + + /* Absolute symlink inside sub pointing at "/outside" — with root_fd set, /outside resolves to + * root_fd/outside, not the host's /outside. */ + ASSERT_OK_ERRNO(symlinkat("/outside", sub_fd, "escape_abs")); + ASSERT_OK(chaseat(root_fd, sub_fd, "escape_abs", 0, &result, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, root_fd, "outside", AT_EMPTY_PATH)); + ASSERT_STREQ(result, "/outside"); + result = mfree(result); + fd = safe_close(fd); + + /* Relative symlink trying to escape via many ".." — also clamped to root. */ + ASSERT_OK_ERRNO(symlinkat("../../../../../outside", sub_fd, "escape_rel")); + ASSERT_OK(chaseat(root_fd, sub_fd, "escape_rel", 0, &result, NULL)); + ASSERT_STREQ(result, "../outside"); + result = mfree(result); + + /* Symlink pointing to an absolute host path that does NOT exist under our root must fail, not + * leak to the host. /etc almost always exists on the host; under our tmp root it doesn't. */ + ASSERT_OK_ERRNO(symlinkat("/etc", sub_fd, "escape_host")); + ASSERT_ERROR(chaseat(root_fd, sub_fd, "escape_host/hosts", 0, NULL, NULL), ENOENT); + + /* Chasing just ".." from root_fd itself stays at root. */ + ASSERT_OK(chaseat(root_fd, root_fd, "..", 0, &result, NULL)); + ASSERT_STREQ(result, "."); + result = mfree(result); + + /* (real-fd, XAT_FDROOT, relative): XAT_FDROOT as dir_fd redirects to root_fd, so relative + * paths start at root_fd. Result is relative because root_fd is a non-host-root fd and + * dir_fd (after redirection) equals root_fd. */ + ASSERT_OK(chaseat(root_fd, XAT_FDROOT, "sub/inside", 0, &result, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, sub_fd, "inside", AT_EMPTY_PATH)); + ASSERT_STREQ(result, "sub/inside"); + fd = safe_close(fd); + result = mfree(result); + + /* (real-fd, XAT_FDROOT, absolute): same as relative — absolute paths also resolve from + * root_fd. Leading slash is stripped. */ + ASSERT_OK(chaseat(root_fd, XAT_FDROOT, "/sub/inside", 0, &result, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, sub_fd, "inside", AT_EMPTY_PATH)); + ASSERT_STREQ(result, "sub/inside"); + fd = safe_close(fd); + result = mfree(result); + + /* (real-fd, XAT_FDROOT, absolute) resolving to root: "/outside" lives directly under + * root_fd. */ + ASSERT_OK(chaseat(root_fd, XAT_FDROOT, "/outside", 0, &result, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, root_fd, "outside", AT_EMPTY_PATH)); + ASSERT_STREQ(result, "outside"); + fd = safe_close(fd); + result = mfree(result); + + /* (real-fd, XAT_FDROOT) with an absolute symlink: the symlink target "/outside" resolves + * relative to root_fd, not the host root. Since dir_fd == root_fd (XAT_FDROOT was redirected), + * the result stays relative. */ + ASSERT_OK(chaseat(root_fd, XAT_FDROOT, "sub/escape_abs", 0, &result, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, root_fd, "outside", AT_EMPTY_PATH)); + ASSERT_STREQ(result, "outside"); + fd = safe_close(fd); + result = mfree(result); + + /* (real-fd, XAT_FDROOT) with non-existent path. */ + ASSERT_OK_ZERO(chaseat(root_fd, XAT_FDROOT, "/nonexistent", CHASE_NONEXISTENT, &result, NULL)); + ASSERT_STREQ(result, "nonexistent"); + result = mfree(result); + ASSERT_ERROR(chaseat(root_fd, XAT_FDROOT, "/nonexistent", 0, NULL, NULL), ENOENT); +} + TEST(chaseat_prefix_root) { _cleanup_free_ char *cwd = NULL, *ret = NULL, *expected = NULL; @@ -773,7 +1034,7 @@ TEST(chaseat_prefix_root) { ret = mfree(ret); ASSERT_OK(chaseat_prefix_root("hoge", "a/b//./c///", &ret)); - assert_se(expected = path_join(cwd, "a/b/c/hoge")); + expected = ASSERT_NOT_NULL(path_join(cwd, "a/b/c/hoge")); ASSERT_STREQ(ret, expected); ret = mfree(ret); @@ -784,8 +1045,8 @@ TEST(chaseat_prefix_root) { ret = mfree(ret); - assert_se(chaseat_prefix_root("./hoge/aaa/../././b", "a/b//./c///", &ret) >= 0); - assert_se(expected = path_join(cwd, "a/b/c/hoge/aaa/../././b")); + ASSERT_OK(chaseat_prefix_root("./hoge/aaa/../././b", "a/b//./c///", &ret)); + expected = ASSERT_NOT_NULL(path_join(cwd, "a/b/c/hoge/aaa/../././b")); ASSERT_STREQ(ret, expected); } @@ -794,9 +1055,9 @@ TEST(trailing_dot_dot) { _cleanup_close_ int fd = -EBADF; ASSERT_OK(chase("/usr/..", NULL, CHASE_PARENT, &path, &fd)); - assert_se(path_equal(path, "/")); + ASSERT_PATH_EQ(path, "/"); ASSERT_OK(fd_get_path(fd, &fdpath)); - assert_se(path_equal(fdpath, "/")); + ASSERT_PATH_EQ(fdpath, "/"); path = mfree(path); fdpath = mfree(fdpath); @@ -811,9 +1072,9 @@ TEST(trailing_dot_dot) { _cleanup_free_ char *expected1 = ASSERT_PTR(path_join(t, "a/b/c")); _cleanup_free_ char *expected2 = ASSERT_PTR(path_join(t, "a/b")); - assert_se(path_equal(path, expected1)); + ASSERT_PATH_EQ(path, expected1); ASSERT_OK(fd_get_path(fd, &fdpath)); - assert_se(path_equal(fdpath, expected2)); + ASSERT_PATH_EQ(fdpath, expected2); } TEST(use_chase_as_mkdir_p) { diff --git a/src/tpm2-setup/tpm2-swtpm.c b/src/tpm2-setup/tpm2-swtpm.c index 71ad6b5e9d1c7..db5ec09312b5e 100644 --- a/src/tpm2-setup/tpm2-swtpm.c +++ b/src/tpm2-setup/tpm2-swtpm.c @@ -174,6 +174,7 @@ static int run(int argc, char *argv[]) { _cleanup_free_ char *unprefixed_state_dir = NULL; r = chaseat(esp_fd, + esp_fd, "/loader/swtpm", CHASE_TRIGGER_AUTOFS|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, &unprefixed_state_dir, From a066396fbc72d06e503acaf38899f949edf16c4c Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 20 Apr 2026 07:32:46 +0000 Subject: [PATCH 1138/1296] fdisk-util: load libfdisk via dlopen Convert fdisk-util to the dlopen pattern used by other optional shared libraries in libshared. Declare the libfdisk API entry points with DLSYM_PROTOTYPE, resolve them in a dlopen_fdisk() helper, and call the sym_* wrappers from the homework, sysupdate and repart binaries that use them. With this in place fdisk-util can live in libshared itself, linked only against libfdisk's headers (via libfdisk_cflags). The libshared_fdisk convenience library and the libfdisk link dependency on systemd-homework, systemd-sysupdate, systemd-repart and systemd-repart.standalone go away. Also add a dlopen_fdisk() check to test-dlopen-so. --- meson.build | 2 +- src/home/homework-luks.c | 98 +++++++------ src/home/meson.build | 6 +- src/repart/meson.build | 9 +- src/repart/repart.c | 213 +++++++++++++++------------- src/shared/fdisk-util.c | 160 +++++++++++++++++++-- src/shared/fdisk-util.h | 81 ++++++++++- src/shared/meson.build | 14 +- src/sysupdate/meson.build | 6 +- src/sysupdate/sysupdate-partition.c | 56 ++++---- src/sysupdate/sysupdate-resource.c | 10 +- src/test/meson.build | 1 + src/test/test-dlopen-so.c | 2 + 13 files changed, 441 insertions(+), 217 deletions(-) diff --git a/meson.build b/meson.build index 087539037995a..2b717e23966f6 100644 --- a/meson.build +++ b/meson.build @@ -1156,8 +1156,8 @@ libmount_cflags = libmount.partial_dependency(includes: true, compile_args: true libfdisk = dependency('fdisk', version : '>= 2.32', - disabler : true, required : get_option('fdisk')) +libfdisk_cflags = libfdisk.partial_dependency(includes: true, compile_args: true) conf.set10('HAVE_LIBFDISK', libfdisk.found()) # This prefers pwquality if both are enabled or auto. diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index 878ff5e905f6e..633d54b087810 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -1919,11 +1919,11 @@ static int make_partition_table( assert(ret_size); assert(ret_disk_uuid); - t = fdisk_new_parttype(); + t = sym_fdisk_new_parttype(); if (!t) return log_oom(); - r = fdisk_parttype_set_typestr(t, SD_GPT_USER_HOME_STR); + r = sym_fdisk_parttype_set_typestr(t, SD_GPT_USER_HOME_STR); if (r < 0) return log_error_errno(r, "Failed to initialize partition type: %m"); @@ -1931,27 +1931,27 @@ static int make_partition_table( if (r < 0) return log_error_errno(r, "Failed to open device: %m"); - r = fdisk_create_disklabel(c, "gpt"); + r = sym_fdisk_create_disklabel(c, "gpt"); if (r < 0) return log_error_errno(r, "Failed to create GPT disk label: %m"); - p = fdisk_new_partition(); + p = sym_fdisk_new_partition(); if (!p) return log_oom(); - r = fdisk_partition_set_type(p, t); + r = sym_fdisk_partition_set_type(p, t); if (r < 0) return log_error_errno(r, "Failed to set partition type: %m"); - r = fdisk_partition_partno_follow_default(p, 1); + r = sym_fdisk_partition_partno_follow_default(p, 1); if (r < 0) return log_error_errno(r, "Failed to place partition at first free partition index: %m"); /* Use same sector size as the fdisk context when converting to bytes */ - fdisk_sector_size = fdisk_get_sector_size(c); + fdisk_sector_size = sym_fdisk_get_sector_size(c); assert(fdisk_sector_size > 0); - first_lba = fdisk_get_first_lba(c); /* Boundary where usable space starts */ + first_lba = sym_fdisk_get_first_lba(c); /* Boundary where usable space starts */ assert(first_lba <= UINT64_MAX / fdisk_sector_size); start = DISK_SIZE_ROUND_UP(first_lba * fdisk_sector_size); @@ -1960,38 +1960,38 @@ static int make_partition_table( if (start == UINT64_MAX) return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Overflow while rounding up start LBA."); - last_lba = fdisk_get_last_lba(c); /* One sector before boundary where usable space ends */ + last_lba = sym_fdisk_get_last_lba(c); /* One sector before boundary where usable space ends */ assert(last_lba < UINT64_MAX / fdisk_sector_size); end = DISK_SIZE_ROUND_DOWN((last_lba + 1) * fdisk_sector_size); if (end <= start) return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Resulting partition size zero or negative."); - r = fdisk_partition_set_start(p, start / fdisk_sector_size); + r = sym_fdisk_partition_set_start(p, start / fdisk_sector_size); if (r < 0) return log_error_errno(r, "Failed to place partition at offset %" PRIu64 ": %m", start); - r = fdisk_partition_set_size(p, (end - start) / fdisk_sector_size); + r = sym_fdisk_partition_set_size(p, (end - start) / fdisk_sector_size); if (r < 0) return log_error_errno(r, "Failed to end partition at offset %" PRIu64 ": %m", end); - r = fdisk_partition_set_name(p, label); + r = sym_fdisk_partition_set_name(p, label); if (r < 0) return log_error_errno(r, "Failed to set partition name: %m"); - r = fdisk_partition_set_uuid(p, SD_ID128_TO_UUID_STRING(uuid)); + r = sym_fdisk_partition_set_uuid(p, SD_ID128_TO_UUID_STRING(uuid)); if (r < 0) return log_error_errno(r, "Failed to set partition UUID: %m"); - r = fdisk_add_partition(c, p, NULL); + r = sym_fdisk_add_partition(c, p, NULL); if (r < 0) return log_error_errno(r, "Failed to add partition: %m"); - r = fdisk_write_disklabel(c); + r = sym_fdisk_write_disklabel(c); if (r < 0) return log_error_errno(r, "Failed to write disk label: %m"); - r = fdisk_get_disklabel_id(c, &disk_uuid_as_string); + r = sym_fdisk_get_disklabel_id(c, &disk_uuid_as_string); if (r < 0) return log_error_errno(r, "Failed to determine disk label UUID: %m"); @@ -1999,17 +1999,17 @@ static int make_partition_table( if (r < 0) return log_error_errno(r, "Failed to parse disk label UUID: %m"); - r = fdisk_get_partition(c, 0, &q); + r = sym_fdisk_get_partition(c, 0, &q); if (r < 0) return log_error_errno(r, "Failed to read created partition metadata: %m"); - assert(fdisk_partition_has_start(q)); - offset = fdisk_partition_get_start(q); + assert(sym_fdisk_partition_has_start(q)); + offset = sym_fdisk_partition_get_start(q); if (offset > UINT64_MAX / fdisk_sector_size) return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Partition offset too large."); - assert(fdisk_partition_has_size(q)); - size = fdisk_partition_get_size(q); + assert(sym_fdisk_partition_has_size(q)); + size = sym_fdisk_partition_get_size(q); if (size > UINT64_MAX / fdisk_sector_size) return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Partition size too large."); @@ -2207,6 +2207,10 @@ int home_create_luks( assert(setup->image_fd < 0); assert(ret_home); + r = dlopen_fdisk(); + if (r < 0) + return r; + r = dlopen_cryptsetup(); if (r < 0) return r; @@ -2806,10 +2810,10 @@ static int prepare_resize_partition( if (r < 0) return log_error_errno(r, "Failed to open device: %m"); - if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) + if (!sym_fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) return log_error_errno(SYNTHETIC_ERRNO(ENOMEDIUM), "Disk has no GPT partition table."); - r = fdisk_get_disklabel_id(c, &disk_uuid_as_string); + r = sym_fdisk_get_disklabel_id(c, &disk_uuid_as_string); if (r < 0) return log_error_errno(r, "Failed to acquire disk UUID: %m"); @@ -2817,34 +2821,36 @@ static int prepare_resize_partition( if (r < 0) return log_error_errno(r, "Failed to parse disk UUID: %m"); - r = fdisk_get_partitions(c, &t); + r = sym_fdisk_get_partitions(c, &t); if (r < 0) return log_error_errno(r, "Failed to acquire partition table: %m"); - n_partitions = fdisk_table_get_nents(t); + n_partitions = sym_fdisk_table_get_nents(t); for (size_t i = 0; i < n_partitions; i++) { struct fdisk_partition *p; uint64_t fdisk_sector_size; - p = fdisk_table_get_partition(t, i); + p = sym_fdisk_table_get_partition(t, i); if (!p) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata."); - if (fdisk_partition_is_used(p) <= 0) + if (sym_fdisk_partition_is_used(p) <= 0) continue; - if (fdisk_partition_has_start(p) <= 0 || fdisk_partition_has_size(p) <= 0 || fdisk_partition_has_end(p) <= 0) + if (sym_fdisk_partition_has_start(p) <= 0 || + sym_fdisk_partition_has_size(p) <= 0 || + sym_fdisk_partition_has_end(p) <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found partition without a size."); - fdisk_sector_size = fdisk_get_sector_size(c); + fdisk_sector_size = sym_fdisk_get_sector_size(c); assert(fdisk_sector_size > 0); - if (fdisk_partition_get_start(p) == partition_offset / fdisk_sector_size && - fdisk_partition_get_size(p) == old_partition_size / fdisk_sector_size) { + if (sym_fdisk_partition_get_start(p) == partition_offset / fdisk_sector_size && + sym_fdisk_partition_get_size(p) == old_partition_size / fdisk_sector_size) { if (found) return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "Partition found twice, refusing."); found = p; - } else if (fdisk_partition_get_end(p) > partition_offset / fdisk_sector_size) + } else if (sym_fdisk_partition_get_end(p) > partition_offset / fdisk_sector_size) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't extend, not last partition in image."); } @@ -2876,12 +2882,12 @@ static int get_maximum_partition_size( return log_error_errno(r, "Failed to create fdisk context: %m"); /* Get the probed sector size by fdisk */ - fdisk_sector_size = fdisk_get_sector_size(c); - start_lba = fdisk_partition_get_start(p); + fdisk_sector_size = sym_fdisk_get_sector_size(c); + start_lba = sym_fdisk_partition_get_start(p); assert(start_lba <= UINT64_MAX / fdisk_sector_size); start = start_lba * fdisk_sector_size; - last_lba = fdisk_get_last_lba(c); /* One sector before boundary where usable space ends */ + last_lba = sym_fdisk_get_last_lba(c); /* One sector before boundary where usable space ends */ assert(last_lba < UINT64_MAX / fdisk_sector_size); end = DISK_SIZE_ROUND_DOWN((last_lba + 1) * fdisk_sector_size); @@ -2898,14 +2904,14 @@ static int ask_cb(struct fdisk_context *c, struct fdisk_ask *ask, void *userdata assert(c); - switch (fdisk_ask_get_type(ask)) { + switch (sym_fdisk_ask_get_type(ask)) { case FDISK_ASKTYPE_STRING: result = new(char, 37); if (!result) return log_oom(); - fdisk_ask_string_set_result(ask, sd_id128_to_uuid_string(*(sd_id128_t*) userdata, result)); + sym_fdisk_ask_string_set_result(ask, sd_id128_to_uuid_string(*(sd_id128_t*) userdata, result)); break; default: @@ -2943,31 +2949,31 @@ static int apply_resize_partition( return log_error_errno(r, "Failed to open device: %m"); /* Before writing our partition patch the final size in */ - r = fdisk_partition_size_explicit(p, 1); + r = sym_fdisk_partition_size_explicit(p, 1); if (r < 0) return log_error_errno(r, "Failed to enable explicit partition size: %m"); - r = fdisk_partition_set_size(p, new_partition_size / ssz); + r = sym_fdisk_partition_set_size(p, new_partition_size / ssz); if (r < 0) return log_error_errno(r, "Failed to change partition size: %m"); - r = fdisk_create_disklabel(c, "gpt"); + r = sym_fdisk_create_disklabel(c, "gpt"); if (r < 0) return log_error_errno(r, "Failed to create GPT disk label: %m"); - r = fdisk_apply_table(c, t); + r = sym_fdisk_apply_table(c, t); if (r < 0) return log_error_errno(r, "Failed to apply partition table: %m"); - r = fdisk_set_ask(c, ask_cb, &disk_uuids); + r = sym_fdisk_set_ask(c, ask_cb, &disk_uuids); if (r < 0) return log_error_errno(r, "Failed to set libfdisk query function: %m"); - r = fdisk_set_disklabel_id(c); + r = sym_fdisk_set_disklabel_id(c); if (r < 0) return log_error_errno(r, "Failed to change disklabel ID: %m"); - r = fdisk_write_disklabel(c); + r = sym_fdisk_write_disklabel(c); if (r < 0) return log_error_errno(r, "Failed to write disk label: %m"); @@ -3240,6 +3246,10 @@ int home_resize_luks( assert(user_record_storage(h) == USER_LUKS); assert(setup); + r = dlopen_fdisk(); + if (r < 0) + return r; + r = dlopen_cryptsetup(); if (r < 0) return r; diff --git a/src/home/meson.build b/src/home/meson.build index 1efee1619ef9c..b051bf580c803 100644 --- a/src/home/meson.build +++ b/src/home/meson.build @@ -73,13 +73,9 @@ executables += [ 'name' : 'systemd-homework', 'sources' : systemd_homework_sources, 'objects' : ['systemd-homed'], - 'link_with' : [ - libshared, - libshared_fdisk - ], 'dependencies' : [ libblkid_cflags, - libfdisk, + libfdisk_cflags, libopenssl, libp11kit_cflags, threads, diff --git a/src/repart/meson.build b/src/repart/meson.build index 92c7d37da5af8..b7c70be068574 100644 --- a/src/repart/meson.build +++ b/src/repart/meson.build @@ -12,13 +12,9 @@ executables += [ 'repart.c', 'iso9660.c', ), - 'link_with' : [ - libshared, - libshared_fdisk, - ], 'dependencies' : [ libblkid_cflags, - libfdisk, + libfdisk_cflags, libmount_cflags, libopenssl, threads, @@ -31,13 +27,12 @@ executables += [ 'link_with' : [ libc_wrapper_static, libbasic_static, - libshared_fdisk, libshared_static, libsystemd_static, ], 'dependencies' : [ libblkid_cflags, - libfdisk, + libfdisk_cflags, libmount_cflags, libopenssl, threads, diff --git a/src/repart/repart.c b/src/repart/repart.c index b8443fab1951a..d66eea69875ec 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -793,9 +793,9 @@ static Partition* partition_free(Partition *p) { strv_free(p->drop_in_files); if (p->current_partition) - fdisk_unref_partition(p->current_partition); + sym_fdisk_unref_partition(p->current_partition); if (p->new_partition) - fdisk_unref_partition(p->new_partition); + sym_fdisk_unref_partition(p->new_partition); if (p->copy_blocks_path_is_our_file) unlink_and_free(p->copy_blocks_path); @@ -976,7 +976,7 @@ static Context* context_free(Context *context) { context_free_free_areas(context); if (context->fdisk_context) - fdisk_unref_context(context->fdisk_context); + sym_fdisk_unref_context(context->fdisk_context); safe_close(context->backing_fd); if (context->node_is_our_file) @@ -3198,31 +3198,31 @@ static int determine_current_padding( assert(p); assert(ret); - if (!fdisk_partition_has_end(p)) + if (!sym_fdisk_partition_has_end(p)) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Partition has no end."); - offset = fdisk_partition_get_end(p); + offset = sym_fdisk_partition_get_end(p); assert(offset < UINT64_MAX); offset++; /* The end is one sector before the next partition or padding. */ assert(offset < UINT64_MAX / secsz); offset *= secsz; - n_partitions = fdisk_table_get_nents(t); + n_partitions = sym_fdisk_table_get_nents(t); for (size_t i = 0; i < n_partitions; i++) { struct fdisk_partition *q; uint64_t start; - q = fdisk_table_get_partition(t, i); + q = sym_fdisk_table_get_partition(t, i); if (!q) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata."); - if (fdisk_partition_is_used(q) <= 0) + if (sym_fdisk_partition_is_used(q) <= 0) continue; - if (!fdisk_partition_has_start(q)) + if (!sym_fdisk_partition_has_start(q)) continue; - start = fdisk_partition_get_start(q); + start = sym_fdisk_partition_get_start(q); assert(start < UINT64_MAX / secsz); start *= secsz; @@ -3232,7 +3232,7 @@ static int determine_current_padding( if (next == UINT64_MAX) { /* No later partition? In that case check the end of the usable area */ - next = fdisk_get_last_lba(c); + next = sym_fdisk_get_last_lba(c); assert(next < UINT64_MAX); next++; /* The last LBA is one sector before the end */ @@ -3274,21 +3274,21 @@ static int context_copy_from_one(Context *context, const char *src) { if (r < 0) return log_error_errno(r, "Failed to create fdisk context: %m"); - secsz = fdisk_get_sector_size(c); - grainsz = fdisk_get_grain_size(c); + secsz = sym_fdisk_get_sector_size(c); + grainsz = sym_fdisk_get_grain_size(c); /* Insist on a power of two, and that it's a multiple of 512, i.e. the traditional sector size. */ if (secsz < 512 || !ISPOWEROF2(secsz)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Sector size %lu is not a power of two larger than 512? Refusing.", secsz); - if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) + if (!sym_fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), "Cannot copy from disk %s with no GPT disk label.", src); - r = fdisk_get_partitions(c, &t); + r = sym_fdisk_get_partitions(c, &t); if (r < 0) return log_error_errno(r, "Failed to acquire partition table: %m"); - n_partitions = fdisk_table_get_nents(t); + n_partitions = sym_fdisk_table_get_nents(t); for (size_t i = 0; i < n_partitions; i++) { _cleanup_(partition_freep) Partition *np = NULL; _cleanup_free_ char *label_copy = NULL; @@ -3298,16 +3298,16 @@ static int context_copy_from_one(Context *context, const char *src) { sd_id128_t ptid, id; GptPartitionType type; - p = fdisk_table_get_partition(t, i); + p = sym_fdisk_table_get_partition(t, i); if (!p) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata."); - if (fdisk_partition_is_used(p) <= 0) + if (sym_fdisk_partition_is_used(p) <= 0) continue; - if (fdisk_partition_has_start(p) <= 0 || - fdisk_partition_has_size(p) <= 0 || - fdisk_partition_has_partno(p) <= 0) + if (sym_fdisk_partition_has_start(p) <= 0 || + sym_fdisk_partition_has_size(p) <= 0 || + sym_fdisk_partition_has_partno(p) <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found a partition without a position, size or number."); r = fdisk_partition_get_type_as_id128(p, &ptid); @@ -3320,18 +3320,18 @@ static int context_copy_from_one(Context *context, const char *src) { if (r < 0) return log_error_errno(r, "Failed to query partition UUID: %m"); - label = fdisk_partition_get_name(p); + label = sym_fdisk_partition_get_name(p); if (!isempty(label)) { label_copy = strdup(label); if (!label_copy) return log_oom(); } - sz = fdisk_partition_get_size(p); + sz = sym_fdisk_partition_get_size(p); assert(sz <= UINT64_MAX/secsz); sz *= secsz; - start = fdisk_partition_get_start(p); + start = sym_fdisk_partition_get_start(p); assert(start <= UINT64_MAX/secsz); start *= secsz; @@ -3606,14 +3606,14 @@ static int fdisk_ask_cb(struct fdisk_context *c, struct fdisk_ask *ask, void *da _cleanup_free_ char *ids = NULL; int r; - if (fdisk_ask_get_type(ask) != FDISK_ASKTYPE_STRING) + if (sym_fdisk_ask_get_type(ask) != FDISK_ASKTYPE_STRING) return -EINVAL; ids = new(char, SD_ID128_UUID_STRING_MAX); if (!ids) return -ENOMEM; - r = fdisk_ask_string_set_result(ask, sd_id128_to_uuid_string(*(sd_id128_t*) data, ids)); + r = sym_fdisk_ask_string_set_result(ask, sd_id128_to_uuid_string(*(sd_id128_t*) data, ids)); if (r < 0) return r; @@ -3624,15 +3624,15 @@ static int fdisk_ask_cb(struct fdisk_context *c, struct fdisk_ask *ask, void *da static int fdisk_set_disklabel_id_by_uuid(struct fdisk_context *c, sd_id128_t id) { int r; - r = fdisk_set_ask(c, fdisk_ask_cb, &id); + r = sym_fdisk_set_ask(c, fdisk_ask_cb, &id); if (r < 0) return r; - r = fdisk_set_disklabel_id(c); + r = sym_fdisk_set_disklabel_id(c); if (r < 0) return r; - return fdisk_set_ask(c, NULL, NULL); + return sym_fdisk_set_ask(c, NULL, NULL); } static int derive_uuid(sd_id128_t base, const char *token, sd_id128_t *ret) { @@ -3693,13 +3693,13 @@ static int context_load_partition_table(Context *context) { context_notify(context, PROGRESS_LOADING_TABLE, /* object= */ NULL, UINT_MAX); - c = fdisk_new_context(); + c = sym_fdisk_new_context(); if (!c) return log_oom(); if (arg_sector_size > 0) { fs_secsz = arg_sector_size; - r = fdisk_save_user_sector_size(c, /* phy= */ 0, arg_sector_size); + r = sym_fdisk_save_user_sector_size(c, /* phy= */ 0, arg_sector_size); } else { uint32_t ssz; struct stat st; @@ -3732,14 +3732,14 @@ static int context_load_partition_table(Context *context) { } } - r = fdisk_save_user_sector_size(c, /* phy= */ 0, ssz); + r = sym_fdisk_save_user_sector_size(c, /* phy= */ 0, ssz); } if (r < 0) return log_error_errno(r, "Failed to set sector size: %m"); /* libfdisk doesn't have an API to operate on arbitrary fds, hence reopen the fd going via the * /proc/self/fd/ magic path if we have an existing fd. Open the original file otherwise. */ - r = fdisk_assign_device( + r = sym_fdisk_assign_device( c, context->backing_fd >= 0 ? FORMAT_PROC_FD_PATH(context->backing_fd) : context->node, context->dry_run); @@ -3758,7 +3758,7 @@ static int context_load_partition_table(Context *context) { if (S_ISREG(st.st_mode) && st.st_size == 0) { /* Use the fallback values if we have no better idea */ - context->sector_size = fdisk_get_sector_size(c); + context->sector_size = sym_fdisk_get_sector_size(c); context->default_fs_sector_size = fs_secsz; context->grain_size = determine_grain_size(context->sector_size); return /* from_scratch= */ true; @@ -3771,7 +3771,7 @@ static int context_load_partition_table(Context *context) { if (context->backing_fd < 0) { /* If we have no fd referencing the device yet, make a copy of the fd now, so that we have one */ - r = context_open_and_lock_backing_fd(FORMAT_PROC_FD_PATH(fdisk_get_devfd(c)), + r = context_open_and_lock_backing_fd(FORMAT_PROC_FD_PATH(sym_fdisk_get_devfd(c)), context->dry_run ? LOCK_SH : LOCK_EX, &context->backing_fd); if (r < 0) @@ -3783,7 +3783,7 @@ static int context_load_partition_table(Context *context) { * it for all our needs. Note that the values we use ourselves always are in bytes though, thus mean * the same thing universally. Also note that regardless what kind of sector size is in use we'll * place partitions at multiples of 4K. */ - unsigned long secsz = fdisk_get_sector_size(c); + unsigned long secsz = sym_fdisk_get_sector_size(c); /* Insist on a power of two, and that it's a multiple of 512, i.e. the traditional sector size. */ if (secsz < 512 || !ISPOWEROF2(secsz)) @@ -3799,14 +3799,14 @@ static int context_load_partition_table(Context *context) { case EMPTY_REFUSE: /* Refuse empty disks, insist on an existing GPT partition table */ - if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) + if (!sym_fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) return log_notice_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has no GPT disk label, not repartitioning.", context->node); break; case EMPTY_REQUIRE: /* Require an empty disk, refuse any existing partition table */ - r = fdisk_has_label(c); + r = sym_fdisk_has_label(c); if (r < 0) return log_error_errno(r, "Failed to determine whether disk %s has a disk label: %m", context->node); if (r > 0) @@ -3817,11 +3817,11 @@ static int context_load_partition_table(Context *context) { case EMPTY_ALLOW: /* Allow both an empty disk and an existing partition table, but only GPT */ - r = fdisk_has_label(c); + r = sym_fdisk_has_label(c); if (r < 0) return log_error_errno(r, "Failed to determine whether disk %s has a disk label: %m", context->node); if (r > 0) { - if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) + if (!sym_fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) return log_notice_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has non-GPT disk label, not repartitioning.", context->node); } else from_scratch = true; @@ -3839,7 +3839,7 @@ static int context_load_partition_table(Context *context) { } if (from_scratch) { - r = fdisk_create_disklabel(c, "gpt"); + r = sym_fdisk_create_disklabel(c, "gpt"); if (r < 0) return log_error_errno(r, "Failed to create GPT disk label: %m"); @@ -3854,7 +3854,7 @@ static int context_load_partition_table(Context *context) { goto add_initial_free_area; } - r = fdisk_get_disklabel_id(c, &disk_uuid_string); + r = sym_fdisk_get_disklabel_id(c, &disk_uuid_string); if (r < 0) return log_error_errno(r, "Failed to get current GPT disk label UUID: %m"); @@ -3864,17 +3864,17 @@ static int context_load_partition_table(Context *context) { if (r < 0) return log_error_errno(r, "Failed to acquire disk GPT uuid: %m"); - r = fdisk_set_disklabel_id(c); + r = sym_fdisk_set_disklabel_id(c); if (r < 0) return log_error_errno(r, "Failed to set GPT disk label: %m"); } else if (r < 0) return log_error_errno(r, "Failed to parse current GPT disk label UUID: %m"); - r = fdisk_get_partitions(c, &t); + r = sym_fdisk_get_partitions(c, &t); if (r < 0) return log_error_errno(r, "Failed to acquire partition table: %m"); - n_partitions = fdisk_table_get_nents(t); + n_partitions = sym_fdisk_table_get_nents(t); for (size_t i = 0; i < n_partitions; i++) { _cleanup_free_ char *label_copy = NULL; Partition *last = NULL; @@ -3885,16 +3885,16 @@ static int context_load_partition_table(Context *context) { sd_id128_t ptid, id; size_t partno; - p = fdisk_table_get_partition(t, i); + p = sym_fdisk_table_get_partition(t, i); if (!p) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata."); - if (fdisk_partition_is_used(p) <= 0) + if (sym_fdisk_partition_is_used(p) <= 0) continue; - if (fdisk_partition_has_start(p) <= 0 || - fdisk_partition_has_size(p) <= 0 || - fdisk_partition_has_partno(p) <= 0) + if (sym_fdisk_partition_has_start(p) <= 0 || + sym_fdisk_partition_has_size(p) <= 0 || + sym_fdisk_partition_has_partno(p) <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found a partition without a position, size or number."); r = fdisk_partition_get_type_as_id128(p, &ptid); @@ -3905,22 +3905,22 @@ static int context_load_partition_table(Context *context) { if (r < 0) return log_error_errno(r, "Failed to query partition UUID: %m"); - label = fdisk_partition_get_name(p); + label = sym_fdisk_partition_get_name(p); if (!isempty(label)) { label_copy = strdup(label); if (!label_copy) return log_oom(); } - sz = fdisk_partition_get_size(p); + sz = sym_fdisk_partition_get_size(p); assert(sz <= UINT64_MAX/secsz); sz *= secsz; - start = fdisk_partition_get_start(p); + start = sym_fdisk_partition_get_start(p); assert(start <= UINT64_MAX/secsz); start *= secsz; - partno = fdisk_partition_get_partno(p); + partno = sym_fdisk_partition_get_partno(p); if (left_boundary == UINT64_MAX || left_boundary > start) left_boundary = start; @@ -3941,7 +3941,7 @@ static int context_load_partition_table(Context *context) { pp->current_label = TAKE_PTR(label_copy); pp->current_partition = p; - fdisk_ref_partition(p); + sym_fdisk_ref_partition(p); r = determine_current_padding(c, t, p, secsz, grainsz, &pp->current_padding); if (r < 0) @@ -3974,7 +3974,7 @@ static int context_load_partition_table(Context *context) { np->current_label = TAKE_PTR(label_copy); np->current_partition = p; - fdisk_ref_partition(p); + sym_fdisk_ref_partition(p); r = determine_current_padding(c, t, p, secsz, grainsz, &np->current_padding); if (r < 0) @@ -3996,15 +3996,15 @@ static int context_load_partition_table(Context *context) { p->supplement_for->suppressing = NULL; add_initial_free_area: - nsectors = fdisk_get_nsectors(c); + nsectors = sym_fdisk_get_nsectors(c); assert(nsectors <= UINT64_MAX/secsz); nsectors *= secsz; - first_lba = fdisk_get_first_lba(c); + first_lba = sym_fdisk_get_first_lba(c); assert(first_lba <= UINT64_MAX/secsz); first_lba *= secsz; - last_lba = fdisk_get_last_lba(c); + last_lba = sym_fdisk_get_last_lba(c); assert(last_lba < UINT64_MAX); last_lba++; assert(last_lba <= UINT64_MAX/secsz); @@ -4072,12 +4072,12 @@ static void context_unload_partition_table(Context *context) { p->offset = UINT64_MAX; if (p->current_partition) { - fdisk_unref_partition(p->current_partition); + sym_fdisk_unref_partition(p->current_partition); p->current_partition = NULL; } if (p->new_partition) { - fdisk_unref_partition(p->new_partition); + sym_fdisk_unref_partition(p->new_partition); p->new_partition = NULL; } @@ -4098,7 +4098,7 @@ static void context_unload_partition_table(Context *context) { context->total = UINT64_MAX; if (context->fdisk_context) { - fdisk_unref_context(context->fdisk_context); + sym_fdisk_unref_context(context->fdisk_context); context->fdisk_context = NULL; } @@ -4235,7 +4235,7 @@ static int context_dump_partitions(Context *context) { activity = "resize"; label = partition_label(p); - partname = p->partno != UINT64_MAX ? fdisk_partname(context->node, p->partno+1) : NULL; + partname = p->partno != UINT64_MAX ? sym_fdisk_partname(context->node, p->partno+1) : NULL; r = format_size_change(p->current_size, p->new_size, &size_change); if (r < 0) @@ -4402,7 +4402,7 @@ static int partition_hint(const Partition *p, const char *node, char **ret) { } if (p->partno != UINT64_MAX) { - buf = fdisk_partname(node, p->partno+1); + buf = sym_fdisk_partname(node, p->partno+1); goto done; } @@ -4623,7 +4623,7 @@ static int context_wipe_range(Context *context, uint64_t offset, uint64_t size) return log_oom(); errno = 0; - r = sym_blkid_probe_set_device(probe, fdisk_get_devfd(context->fdisk_context), offset, size); + r = sym_blkid_probe_set_device(probe, sym_fdisk_get_devfd(context->fdisk_context), offset, size); if (r < 0) return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to allocate device probe for wiping."); @@ -4687,7 +4687,7 @@ static int context_discard_range( if (size <= 0) return 0; - assert_se((fd = fdisk_get_devfd(context->fdisk_context)) >= 0); + assert_se((fd = sym_fdisk_get_devfd(context->fdisk_context)) >= 0); if (fstat(fd, &st) < 0) return -errno; @@ -4784,7 +4784,7 @@ static int context_discard_gap_after(Context *context, Partition *p) { * existing partitions may be before that so ensure the gap * starts at the first actually usable lba */ - gap = fdisk_get_first_lba(context->fdisk_context) * context->sector_size; + gap = sym_fdisk_get_first_lba(context->fdisk_context) * context->sector_size; LIST_FOREACH(partitions, q, context->partitions) { if (q->dropped) @@ -4801,7 +4801,7 @@ static int context_discard_gap_after(Context *context, Partition *p) { } if (next == UINT64_MAX) { - next = (fdisk_get_last_lba(context->fdisk_context) + 1) * context->sector_size; + next = (sym_fdisk_get_last_lba(context->fdisk_context) + 1) * context->sector_size; if (gap > next) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Partition end beyond disk end."); } @@ -4998,7 +4998,7 @@ static int prepare_temporary_file(Context *context, PartitionTarget *t, uint64_t return log_error_errno(fd, "Failed to create temporary file: %m"); if (context->fdisk_context) { - r = read_attr_fd(fdisk_get_devfd(context->fdisk_context), &attrs); + r = read_attr_fd(sym_fdisk_get_devfd(context->fdisk_context), &attrs); if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r)) log_warning_errno(r, "Failed to read file attributes of %s, ignoring: %m", context->node); @@ -5039,7 +5039,7 @@ static int partition_target_prepare( assert(p); assert(ret); - assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0); + assert_se((whole_fd = sym_fdisk_get_devfd(context->fdisk_context)) >= 0); t = new(PartitionTarget, 1); if (!t) @@ -5114,7 +5114,7 @@ static int partition_target_sync(Context *context, Partition *p, PartitionTarget assert(p); assert(t); - assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0); + assert_se((whole_fd = sym_fdisk_get_devfd(context->fdisk_context)) >= 0); log_info("Syncing future partition %"PRIu64" contents to disk.", p->partno); @@ -5954,7 +5954,7 @@ static int partition_format_verity_sig(Context *context, Partition *p) { (void) partition_hint(p, context->node, &hint); - assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0); + assert_se((whole_fd = sym_fdisk_get_devfd(context->fdisk_context)) >= 0); _cleanup_(iovec_done) struct iovec sig_free = {}; const struct iovec *roothash, *sig; @@ -7424,7 +7424,7 @@ static int set_gpt_flags(struct fdisk_partition *q, uint64_t flags) { return r; } - return fdisk_partition_set_attrs(q, strempty(a)); + return sym_fdisk_partition_set_attrs(q, strempty(a)); } static uint64_t partition_merge_flags(Partition *p) { @@ -7497,11 +7497,11 @@ static int context_mangle_partitions(Context *context) { assert(p->new_size >= p->current_size); assert(p->new_size % context->sector_size == 0); - r = fdisk_partition_size_explicit(p->current_partition, true); + r = sym_fdisk_partition_size_explicit(p->current_partition, true); if (r < 0) return log_error_errno(r, "Failed to enable explicit sizing: %m"); - r = fdisk_partition_set_size(p->current_partition, p->new_size / context->sector_size); + r = sym_fdisk_partition_set_size(p->current_partition, p->new_size / context->sector_size); if (r < 0) return log_error_errno(r, "Failed to grow partition: %m"); @@ -7510,7 +7510,7 @@ static int context_mangle_partitions(Context *context) { } if (!sd_id128_equal(p->new_uuid, p->current_uuid)) { - r = fdisk_partition_set_uuid(p->current_partition, SD_ID128_TO_UUID_STRING(p->new_uuid)); + r = sym_fdisk_partition_set_uuid(p->current_partition, SD_ID128_TO_UUID_STRING(p->new_uuid)); if (r < 0) return log_error_errno(r, "Failed to set partition UUID: %m"); @@ -7519,7 +7519,7 @@ static int context_mangle_partitions(Context *context) { } if (!streq_ptr(p->new_label, p->current_label)) { - r = fdisk_partition_set_name(p->current_partition, strempty(p->new_label)); + r = sym_fdisk_partition_set_name(p->current_partition, strempty(p->new_label)); if (r < 0) return log_error_errno(r, "Failed to set partition label: %m"); @@ -7530,7 +7530,7 @@ static int context_mangle_partitions(Context *context) { if (changed) { assert(!PARTITION_IS_FOREIGN(p)); /* never touch foreign partitions */ - r = fdisk_set_partition(context->fdisk_context, p->partno, p->current_partition); + r = sym_fdisk_set_partition(context->fdisk_context, p->partno, p->current_partition); if (r < 0) return log_error_errno(r, "Failed to update partition: %m"); } @@ -7543,43 +7543,43 @@ static int context_mangle_partitions(Context *context) { assert(p->new_size % context->sector_size == 0); assert(p->new_label); - t = fdisk_new_parttype(); + t = sym_fdisk_new_parttype(); if (!t) return log_oom(); - r = fdisk_parttype_set_typestr(t, SD_ID128_TO_UUID_STRING(p->type.uuid)); + r = sym_fdisk_parttype_set_typestr(t, SD_ID128_TO_UUID_STRING(p->type.uuid)); if (r < 0) return log_error_errno(r, "Failed to initialize partition type: %m"); - q = fdisk_new_partition(); + q = sym_fdisk_new_partition(); if (!q) return log_oom(); - r = fdisk_partition_set_type(q, t); + r = sym_fdisk_partition_set_type(q, t); if (r < 0) return log_error_errno(r, "Failed to set partition type: %m"); - r = fdisk_partition_size_explicit(q, true); + r = sym_fdisk_partition_size_explicit(q, true); if (r < 0) return log_error_errno(r, "Failed to enable explicit sizing: %m"); - r = fdisk_partition_set_start(q, p->offset / context->sector_size); + r = sym_fdisk_partition_set_start(q, p->offset / context->sector_size); if (r < 0) return log_error_errno(r, "Failed to position partition: %m"); - r = fdisk_partition_set_size(q, p->new_size / context->sector_size); + r = sym_fdisk_partition_set_size(q, p->new_size / context->sector_size); if (r < 0) return log_error_errno(r, "Failed to grow partition: %m"); - r = fdisk_partition_set_partno(q, p->partno); + r = sym_fdisk_partition_set_partno(q, p->partno); if (r < 0) return log_error_errno(r, "Failed to set partition number: %m"); - r = fdisk_partition_set_uuid(q, SD_ID128_TO_UUID_STRING(p->new_uuid)); + r = sym_fdisk_partition_set_uuid(q, SD_ID128_TO_UUID_STRING(p->new_uuid)); if (r < 0) return log_error_errno(r, "Failed to set partition UUID: %m"); - r = fdisk_partition_set_name(q, strempty(p->new_label)); + r = sym_fdisk_partition_set_name(q, strempty(p->new_label)); if (r < 0) return log_error_errno(r, "Failed to set partition label: %m"); @@ -7590,7 +7590,7 @@ static int context_mangle_partitions(Context *context) { log_info("Adding new partition %" PRIu64 " to partition table.", p->partno); - r = fdisk_add_partition(context->fdisk_context, q, NULL); + r = sym_fdisk_add_partition(context->fdisk_context, q, NULL); if (r < 0) return log_error_errno(r, "Failed to add partition: %m"); @@ -7735,7 +7735,7 @@ static int context_split(Context *context) { continue; if (fd < 0) { - assert_se((fd = fdisk_get_devfd(context->fdisk_context)) >= 0); + assert_se((fd = sym_fdisk_get_devfd(context->fdisk_context)) >= 0); r = read_attr_fd(fd, &attrs); if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r)) @@ -8058,7 +8058,7 @@ static int context_verify_eltorito_overlap(Context *context) { return 0; /* Check how many GPT partition entries can be stored. */ - size_t nents = fdisk_get_npartitions(context->fdisk_context); + size_t nents = sym_fdisk_get_npartitions(context->fdisk_context); /* The GPT contains * - 1 unused block (protective MBR) * - GPT header @@ -8073,7 +8073,7 @@ static int context_verify_eltorito_overlap(Context *context) { * there, we should still not overlap with it since a partition could be added later. * It is unexpected for tools to change the first lba in the GPT header. So this should be safe. */ - if (fdisk_get_first_lba(context->fdisk_context) * context->sector_size < (ISO9660_START+ISO9660_SIZE)*ISO9660_BLOCK_SIZE) + if (sym_fdisk_get_first_lba(context->fdisk_context) * context->sector_size < (ISO9660_START+ISO9660_SIZE)*ISO9660_BLOCK_SIZE) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "El Torito is overlapping with the first partition block."); return 0; @@ -8143,7 +8143,7 @@ static int context_write_partition_table(Context *context) { } } - r = fdisk_get_partitions(context->fdisk_context, &original_table); + r = sym_fdisk_get_partitions(context->fdisk_context, &original_table); if (r < 0) return log_error_errno(r, "Failed to acquire partition table: %m"); @@ -8169,11 +8169,11 @@ static int context_write_partition_table(Context *context) { (void) context_notify(context, PROGRESS_WRITING_TABLE, /* object= */ NULL, UINT_MAX); - r = fdisk_write_disklabel(context->fdisk_context); + r = sym_fdisk_write_disklabel(context->fdisk_context); if (r < 0) return log_error_errno(r, "Failed to write partition table: %m"); - capable = blockdev_partscan_enabled_fd(fdisk_get_devfd(context->fdisk_context)); + capable = blockdev_partscan_enabled_fd(sym_fdisk_get_devfd(context->fdisk_context)); if (capable == -ENOTBLK) log_debug("Not telling kernel to reread partition table, since we are not operating on a block device."); else if (capable < 0) @@ -8182,7 +8182,7 @@ static int context_write_partition_table(Context *context) { log_info("Informing kernel about changed partitions..."); (void) context_notify(context, PROGRESS_REREADING_TABLE, /* object= */ NULL, UINT_MAX); - r = reread_partition_table_fd(fdisk_get_devfd(context->fdisk_context), /* flags= */ 0); + r = reread_partition_table_fd(sym_fdisk_get_devfd(context->fdisk_context), /* flags= */ 0); if (r < 0) return log_error_errno(r, "Failed to reread partition table: %m"); } else @@ -8218,7 +8218,14 @@ static int context_write_eltorito(Context *context) { log_info("Writing El Torito boot catalog."); - r = write_eltorito(fdisk_get_devfd(context->fdisk_context), usec, utc, esp_offset / ISO9660_BLOCK_SIZE, arg_eltorito_system, arg_eltorito_volume, arg_eltorito_publisher); + r = write_eltorito( + sym_fdisk_get_devfd(context->fdisk_context), + usec, + utc, + esp_offset / ISO9660_BLOCK_SIZE, + arg_eltorito_system, + arg_eltorito_volume, + arg_eltorito_publisher); if (r < 0) return log_error_errno(r, "Failed to write El Torito boot catalog: %m"); @@ -8279,7 +8286,7 @@ static int context_factory_reset(Context *context) { log_info("Removing partition %" PRIu64 " for factory reset.", p->partno); - r = fdisk_delete_partition(context->fdisk_context, p->partno); + r = sym_fdisk_delete_partition(context->fdisk_context, p->partno); if (r < 0) return log_error_errno(r, "Failed to remove partition %" PRIu64 ": %m", p->partno); @@ -8291,7 +8298,7 @@ static int context_factory_reset(Context *context) { return 0; } - r = fdisk_write_disklabel(context->fdisk_context); + r = sym_fdisk_write_disklabel(context->fdisk_context); if (r < 0) return log_error_errno(r, "Failed to write disk label: %m"); @@ -10666,7 +10673,7 @@ static int resize_pt(int fd, uint64_t sector_size) { if (r < 0) return log_error_errno(r, "Failed to open device '%s': %m", FORMAT_PROC_FD_PATH(fd)); - r = fdisk_has_label(c); + r = sym_fdisk_has_label(c); if (r < 0) return log_error_errno(r, "Failed to determine whether disk '%s' has a disk label: %m", FORMAT_PROC_FD_PATH(fd)); if (r == 0) { @@ -10674,7 +10681,7 @@ static int resize_pt(int fd, uint64_t sector_size) { return 0; } - r = fdisk_write_disklabel(c); + r = sym_fdisk_write_disklabel(c); if (r < 0) return log_error_errno(r, "Failed to write resized partition table: %m"); @@ -11211,6 +11218,10 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + r = dlopen_fdisk(); + if (r < 0) + return r; + #if HAVE_LIBCRYPTSETUP cryptsetup_enable_logging(NULL); #endif diff --git a/src/shared/fdisk-util.c b/src/shared/fdisk-util.c index 2a0b7d765b360..8b1cb3c80f0fa 100644 --- a/src/shared/fdisk-util.c +++ b/src/shared/fdisk-util.c @@ -1,16 +1,156 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "fdisk-util.h" + +#if HAVE_LIBFDISK + +#include "sd-dlopen.h" + #include "alloc-util.h" #include "bitfield.h" #include "dissect-image.h" +#include "dlfcn-util.h" #include "extract-word.h" #include "fd-util.h" -#include "fdisk-util.h" #include "log.h" #include "parse-util.h" #include "string-util.h" -#if HAVE_LIBFDISK +static void *fdisk_dl = NULL; + +DLSYM_PROTOTYPE(fdisk_add_partition) = NULL; +DLSYM_PROTOTYPE(fdisk_apply_table) = NULL; +DLSYM_PROTOTYPE(fdisk_ask_get_type) = NULL; +DLSYM_PROTOTYPE(fdisk_ask_string_set_result) = NULL; +DLSYM_PROTOTYPE(fdisk_assign_device) = NULL; +DLSYM_PROTOTYPE(fdisk_create_disklabel) = NULL; +DLSYM_PROTOTYPE(fdisk_delete_partition) = NULL; +DLSYM_PROTOTYPE(fdisk_get_devfd) = NULL; +DLSYM_PROTOTYPE(fdisk_get_disklabel_id) = NULL; +DLSYM_PROTOTYPE(fdisk_get_first_lba) = NULL; +DLSYM_PROTOTYPE(fdisk_get_grain_size) = NULL; +DLSYM_PROTOTYPE(fdisk_get_last_lba) = NULL; +DLSYM_PROTOTYPE(fdisk_get_npartitions) = NULL; +DLSYM_PROTOTYPE(fdisk_get_nsectors) = NULL; +DLSYM_PROTOTYPE(fdisk_get_partition) = NULL; +DLSYM_PROTOTYPE(fdisk_get_partitions) = NULL; +DLSYM_PROTOTYPE(fdisk_get_sector_size) = NULL; +DLSYM_PROTOTYPE(fdisk_has_label) = NULL; +DLSYM_PROTOTYPE(fdisk_is_labeltype) = NULL; +DLSYM_PROTOTYPE(fdisk_new_context) = NULL; +DLSYM_PROTOTYPE(fdisk_new_partition) = NULL; +DLSYM_PROTOTYPE(fdisk_new_parttype) = NULL; +DLSYM_PROTOTYPE(fdisk_partname) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_get_attrs) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_get_end) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_get_name) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_get_partno) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_get_size) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_get_start) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_get_type) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_get_uuid) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_has_end) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_has_partno) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_has_size) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_has_start) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_is_used) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_partno_follow_default) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_set_attrs) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_set_name) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_set_partno) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_set_size) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_set_start) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_set_type) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_set_uuid) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_size_explicit) = NULL; +DLSYM_PROTOTYPE(fdisk_partition_to_string) = NULL; +DLSYM_PROTOTYPE(fdisk_parttype_get_string) = NULL; +DLSYM_PROTOTYPE(fdisk_parttype_set_typestr) = NULL; +DLSYM_PROTOTYPE(fdisk_ref_partition) = NULL; +DLSYM_PROTOTYPE(fdisk_save_user_sector_size) = NULL; +DLSYM_PROTOTYPE(fdisk_set_ask) = NULL; +DLSYM_PROTOTYPE(fdisk_set_disklabel_id) = NULL; +DLSYM_PROTOTYPE(fdisk_set_partition) = NULL; +DLSYM_PROTOTYPE(fdisk_table_get_nents) = NULL; +DLSYM_PROTOTYPE(fdisk_table_get_partition) = NULL; +DLSYM_PROTOTYPE(fdisk_unref_context) = NULL; +DLSYM_PROTOTYPE(fdisk_unref_partition) = NULL; +DLSYM_PROTOTYPE(fdisk_unref_parttype) = NULL; +DLSYM_PROTOTYPE(fdisk_unref_table) = NULL; +DLSYM_PROTOTYPE(fdisk_write_disklabel) = NULL; + +int dlopen_fdisk(void) { + SD_ELF_NOTE_DLOPEN( + "fdisk", + "Support for reading and writing partition tables", + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libfdisk.so.1"); + + return dlopen_many_sym_or_warn( + &fdisk_dl, + "libfdisk.so.1", + LOG_DEBUG, + DLSYM_ARG(fdisk_add_partition), + DLSYM_ARG(fdisk_apply_table), + DLSYM_ARG(fdisk_ask_get_type), + DLSYM_ARG(fdisk_ask_string_set_result), + DLSYM_ARG(fdisk_assign_device), + DLSYM_ARG(fdisk_create_disklabel), + DLSYM_ARG(fdisk_delete_partition), + DLSYM_ARG(fdisk_get_devfd), + DLSYM_ARG(fdisk_get_disklabel_id), + DLSYM_ARG(fdisk_get_first_lba), + DLSYM_ARG(fdisk_get_grain_size), + DLSYM_ARG(fdisk_get_last_lba), + DLSYM_ARG(fdisk_get_npartitions), + DLSYM_ARG(fdisk_get_nsectors), + DLSYM_ARG(fdisk_get_partition), + DLSYM_ARG(fdisk_get_partitions), + DLSYM_ARG(fdisk_get_sector_size), + DLSYM_ARG(fdisk_has_label), + DLSYM_ARG(fdisk_is_labeltype), + DLSYM_ARG(fdisk_new_context), + DLSYM_ARG(fdisk_new_partition), + DLSYM_ARG(fdisk_new_parttype), + DLSYM_ARG(fdisk_partname), + DLSYM_ARG(fdisk_partition_get_attrs), + DLSYM_ARG(fdisk_partition_get_end), + DLSYM_ARG(fdisk_partition_get_name), + DLSYM_ARG(fdisk_partition_get_partno), + DLSYM_ARG(fdisk_partition_get_size), + DLSYM_ARG(fdisk_partition_get_start), + DLSYM_ARG(fdisk_partition_get_type), + DLSYM_ARG(fdisk_partition_get_uuid), + DLSYM_ARG(fdisk_partition_has_end), + DLSYM_ARG(fdisk_partition_has_partno), + DLSYM_ARG(fdisk_partition_has_size), + DLSYM_ARG(fdisk_partition_has_start), + DLSYM_ARG(fdisk_partition_is_used), + DLSYM_ARG(fdisk_partition_partno_follow_default), + DLSYM_ARG(fdisk_partition_set_attrs), + DLSYM_ARG(fdisk_partition_set_name), + DLSYM_ARG(fdisk_partition_set_partno), + DLSYM_ARG(fdisk_partition_set_size), + DLSYM_ARG(fdisk_partition_set_start), + DLSYM_ARG(fdisk_partition_set_type), + DLSYM_ARG(fdisk_partition_set_uuid), + DLSYM_ARG(fdisk_partition_size_explicit), + DLSYM_ARG(fdisk_partition_to_string), + DLSYM_ARG(fdisk_parttype_get_string), + DLSYM_ARG(fdisk_parttype_set_typestr), + DLSYM_ARG(fdisk_ref_partition), + DLSYM_ARG(fdisk_save_user_sector_size), + DLSYM_ARG(fdisk_set_ask), + DLSYM_ARG(fdisk_set_disklabel_id), + DLSYM_ARG(fdisk_set_partition), + DLSYM_ARG(fdisk_table_get_nents), + DLSYM_ARG(fdisk_table_get_partition), + DLSYM_ARG(fdisk_unref_context), + DLSYM_ARG(fdisk_unref_partition), + DLSYM_ARG(fdisk_unref_parttype), + DLSYM_ARG(fdisk_unref_table), + DLSYM_ARG(fdisk_write_disklabel)); +} int fdisk_new_context_at( int dir_fd, @@ -34,7 +174,7 @@ int fdisk_new_context_at( dir_fd = fd; } - c = fdisk_new_context(); + c = sym_fdisk_new_context(); if (!c) return -ENOMEM; @@ -45,12 +185,12 @@ int fdisk_new_context_at( } if (sector_size != 0) { - r = fdisk_save_user_sector_size(c, /* phy= */ 0, sector_size); + r = sym_fdisk_save_user_sector_size(c, /* phy= */ 0, sector_size); if (r < 0) return r; } - r = fdisk_assign_device(c, FORMAT_PROC_FD_PATH(dir_fd), read_only); + r = sym_fdisk_assign_device(c, FORMAT_PROC_FD_PATH(dir_fd), read_only); if (r < 0) return r; @@ -64,7 +204,7 @@ int fdisk_partition_get_uuid_as_id128(struct fdisk_partition *p, sd_id128_t *ret assert(p); assert(ret); - ids = fdisk_partition_get_uuid(p); + ids = sym_fdisk_partition_get_uuid(p); if (!ids) return -ENXIO; @@ -78,11 +218,11 @@ int fdisk_partition_get_type_as_id128(struct fdisk_partition *p, sd_id128_t *ret assert(p); assert(ret); - pt = fdisk_partition_get_type(p); + pt = sym_fdisk_partition_get_type(p); if (!pt) return -ENXIO; - pts = fdisk_parttype_get_string(pt); + pts = sym_fdisk_parttype_get_string(pt); if (!pts) return -ENXIO; @@ -99,7 +239,7 @@ int fdisk_partition_get_attrs_as_uint64(struct fdisk_partition *pa, uint64_t *re /* Retrieve current flags as uint64_t mask */ - a = fdisk_partition_get_attrs(pa); + a = sym_fdisk_partition_get_attrs(pa); if (!a) { *ret = 0; return 0; @@ -161,7 +301,7 @@ int fdisk_partition_set_attrs_as_uint64(struct fdisk_partition *pa, uint64_t fla return r; } - return fdisk_partition_set_attrs(pa, strempty(attrs)); + return sym_fdisk_partition_set_attrs(pa, strempty(attrs)); } #endif diff --git a/src/shared/fdisk-util.h b/src/shared/fdisk-util.h index 98a2ed9948fbf..d3d7fda33b476 100644 --- a/src/shared/fdisk-util.h +++ b/src/shared/fdisk-util.h @@ -1,16 +1,81 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "shared-forward.h" + #if HAVE_LIBFDISK #include /* IWYU pragma: export */ -#include "shared-forward.h" +#include "dlfcn-util.h" + +extern DLSYM_PROTOTYPE(fdisk_add_partition); +extern DLSYM_PROTOTYPE(fdisk_apply_table); +extern DLSYM_PROTOTYPE(fdisk_ask_get_type); +extern DLSYM_PROTOTYPE(fdisk_ask_string_set_result); +extern DLSYM_PROTOTYPE(fdisk_assign_device); +extern DLSYM_PROTOTYPE(fdisk_create_disklabel); +extern DLSYM_PROTOTYPE(fdisk_delete_partition); +extern DLSYM_PROTOTYPE(fdisk_get_devfd); +extern DLSYM_PROTOTYPE(fdisk_get_disklabel_id); +extern DLSYM_PROTOTYPE(fdisk_get_first_lba); +extern DLSYM_PROTOTYPE(fdisk_get_grain_size); +extern DLSYM_PROTOTYPE(fdisk_get_last_lba); +extern DLSYM_PROTOTYPE(fdisk_get_npartitions); +extern DLSYM_PROTOTYPE(fdisk_get_nsectors); +extern DLSYM_PROTOTYPE(fdisk_get_partition); +extern DLSYM_PROTOTYPE(fdisk_get_partitions); +extern DLSYM_PROTOTYPE(fdisk_get_sector_size); +extern DLSYM_PROTOTYPE(fdisk_has_label); +extern DLSYM_PROTOTYPE(fdisk_is_labeltype); +extern DLSYM_PROTOTYPE(fdisk_new_context); +extern DLSYM_PROTOTYPE(fdisk_new_partition); +extern DLSYM_PROTOTYPE(fdisk_new_parttype); +extern DLSYM_PROTOTYPE(fdisk_partname); +extern DLSYM_PROTOTYPE(fdisk_partition_get_attrs); +extern DLSYM_PROTOTYPE(fdisk_partition_get_end); +extern DLSYM_PROTOTYPE(fdisk_partition_get_name); +extern DLSYM_PROTOTYPE(fdisk_partition_get_partno); +extern DLSYM_PROTOTYPE(fdisk_partition_get_size); +extern DLSYM_PROTOTYPE(fdisk_partition_get_start); +extern DLSYM_PROTOTYPE(fdisk_partition_get_type); +extern DLSYM_PROTOTYPE(fdisk_partition_get_uuid); +extern DLSYM_PROTOTYPE(fdisk_partition_has_end); +extern DLSYM_PROTOTYPE(fdisk_partition_has_partno); +extern DLSYM_PROTOTYPE(fdisk_partition_has_size); +extern DLSYM_PROTOTYPE(fdisk_partition_has_start); +extern DLSYM_PROTOTYPE(fdisk_partition_is_used); +extern DLSYM_PROTOTYPE(fdisk_partition_partno_follow_default); +extern DLSYM_PROTOTYPE(fdisk_partition_set_attrs); +extern DLSYM_PROTOTYPE(fdisk_partition_set_name); +extern DLSYM_PROTOTYPE(fdisk_partition_set_partno); +extern DLSYM_PROTOTYPE(fdisk_partition_set_size); +extern DLSYM_PROTOTYPE(fdisk_partition_set_start); +extern DLSYM_PROTOTYPE(fdisk_partition_set_type); +extern DLSYM_PROTOTYPE(fdisk_partition_set_uuid); +extern DLSYM_PROTOTYPE(fdisk_partition_size_explicit); +extern DLSYM_PROTOTYPE(fdisk_partition_to_string); +extern DLSYM_PROTOTYPE(fdisk_parttype_get_string); +extern DLSYM_PROTOTYPE(fdisk_parttype_set_typestr); +extern DLSYM_PROTOTYPE(fdisk_ref_partition); +extern DLSYM_PROTOTYPE(fdisk_save_user_sector_size); +extern DLSYM_PROTOTYPE(fdisk_set_ask); +extern DLSYM_PROTOTYPE(fdisk_set_disklabel_id); +extern DLSYM_PROTOTYPE(fdisk_set_partition); +extern DLSYM_PROTOTYPE(fdisk_table_get_nents); +extern DLSYM_PROTOTYPE(fdisk_table_get_partition); +extern DLSYM_PROTOTYPE(fdisk_unref_context); +extern DLSYM_PROTOTYPE(fdisk_unref_partition); +extern DLSYM_PROTOTYPE(fdisk_unref_parttype); +extern DLSYM_PROTOTYPE(fdisk_unref_table); +extern DLSYM_PROTOTYPE(fdisk_write_disklabel); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct fdisk_context*, fdisk_unref_context, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct fdisk_partition*, fdisk_unref_partition, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct fdisk_parttype*, fdisk_unref_parttype, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct fdisk_table*, fdisk_unref_table, NULL); +int dlopen_fdisk(void); + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct fdisk_context*, sym_fdisk_unref_context, fdisk_unref_contextp, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct fdisk_partition*, sym_fdisk_unref_partition, fdisk_unref_partitionp, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct fdisk_parttype*, sym_fdisk_unref_parttype, fdisk_unref_parttypep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct fdisk_table*, sym_fdisk_unref_table, fdisk_unref_tablep, NULL); int fdisk_new_context_at(int dir_fd, const char *path, bool read_only, uint32_t sector_size, struct fdisk_context **ret); @@ -20,4 +85,10 @@ int fdisk_partition_get_type_as_id128(struct fdisk_partition *p, sd_id128_t *ret int fdisk_partition_get_attrs_as_uint64(struct fdisk_partition *pa, uint64_t *ret); int fdisk_partition_set_attrs_as_uint64(struct fdisk_partition *pa, uint64_t flags); +#else + +static inline int dlopen_fdisk(void) { + return -EOPNOTSUPP; +} + #endif diff --git a/src/shared/meson.build b/src/shared/meson.build index 0e45584c6dd15..89c550504de8a 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -79,6 +79,7 @@ shared_sources = files( 'extension-util.c', 'factory-reset.c', 'facts.c', + 'fdisk-util.c', 'fdset.c', 'fido2-util.c', 'find-esp.c', @@ -401,6 +402,7 @@ libshared_deps = [threads, libdl, libdw_cflags, libelf_cflags, + libfdisk_cflags, libfido2_cflags, libgcrypt_cflags, libidn2_cflags, @@ -449,15 +451,3 @@ libshared = shared_library( userspace], install : true, install_dir : pkglibdir) - -shared_fdisk_sources = files('fdisk-util.c') - -libshared_fdisk = static_library( - 'shared-fdisk', - shared_fdisk_sources, - include_directories : includes, - implicit_include_directories : false, - dependencies : [libfdisk, - userspace], - c_args : ['-fvisibility=default'], - build_by_default : false) diff --git a/src/sysupdate/meson.build b/src/sysupdate/meson.build index 6875834e0fcba..68ac0e14ee89c 100644 --- a/src/sysupdate/meson.build +++ b/src/sysupdate/meson.build @@ -27,12 +27,8 @@ executables += [ 'conditions' : ['ENABLE_SYSUPDATE'], 'sources' : systemd_sysupdate_sources, 'extract' : systemd_sysupdate_extract_sources, - 'link_with' : [ - libshared, - libshared_fdisk, - ], 'dependencies' : [ - libfdisk, + libfdisk_cflags, libopenssl, threads, ], diff --git a/src/sysupdate/sysupdate-partition.c b/src/sysupdate/sysupdate-partition.c index 62fd7470cfe78..118bd71e1817a 100644 --- a/src/sysupdate/sysupdate-partition.c +++ b/src/sysupdate/sysupdate-partition.c @@ -77,32 +77,32 @@ int read_partition_info( assert(t); assert(ret); - p = fdisk_table_get_partition(t, i); + p = sym_fdisk_table_get_partition(t, i); if (!p) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata."); - if (fdisk_partition_is_used(p) <= 0) { + if (sym_fdisk_partition_is_used(p) <= 0) { *ret = (PartitionInfo) PARTITION_INFO_NULL; return 0; /* not found! */ } - if (fdisk_partition_has_partno(p) <= 0 || - fdisk_partition_has_start(p) <= 0 || - fdisk_partition_has_size(p) <= 0) + if (sym_fdisk_partition_has_partno(p) <= 0 || + sym_fdisk_partition_has_start(p) <= 0 || + sym_fdisk_partition_has_size(p) <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found a partition without a number, position or size."); - partno = fdisk_partition_get_partno(p); + partno = sym_fdisk_partition_get_partno(p); - start = fdisk_partition_get_start(p); - ssz = fdisk_get_sector_size(c); + start = sym_fdisk_partition_get_start(p); + ssz = sym_fdisk_get_sector_size(c); assert(start <= UINT64_MAX / ssz); start *= ssz; - size = fdisk_partition_get_size(p); + size = sym_fdisk_partition_get_size(p); assert(size <= UINT64_MAX / ssz); size *= ssz; - label = fdisk_partition_get_name(p); + label = sym_fdisk_partition_get_name(p); if (!label) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found a partition without a label."); @@ -118,7 +118,7 @@ int read_partition_info( if (r < 0) return log_error_errno(r, "Failed to get partition flags: %m"); - r = fdisk_partition_to_string(p, c, FDISK_FIELD_DEVICE, &device); + r = sym_fdisk_partition_to_string(p, c, FDISK_FIELD_DEVICE, &device); if (r != 0) return log_error_errno(r, "Failed to get partition device name: %m"); @@ -161,18 +161,22 @@ int find_suitable_partition( POINTER_MAY_BE_NULL(partition_type); assert(ret); + r = dlopen_fdisk(); + if (r < 0) + return r; + r = fdisk_new_context_at(AT_FDCWD, device, /* read_only= */ true, /* sector_size= */ UINT32_MAX, &c); if (r < 0) return log_error_errno(r, "Failed to create fdisk context from '%s': %m", device); - if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) + if (!sym_fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has no GPT disk label, not suitable.", device); - r = fdisk_get_partitions(c, &t); + r = sym_fdisk_get_partitions(c, &t); if (r < 0) return log_error_errno(r, "Failed to acquire partition table: %m"); - n_partitions = fdisk_table_get_nents(t); + n_partitions = sym_fdisk_table_get_nents(t); for (size_t i = 0; i < n_partitions; i++) { _cleanup_(partition_info_destroy) PartitionInfo pinfo = PARTITION_INFO_NULL; @@ -226,11 +230,15 @@ int patch_partition( if (change == 0) /* Nothing to do */ return 0; + r = dlopen_fdisk(); + if (r < 0) + return r; + r = fdisk_new_context_at(AT_FDCWD, device, /* read_only= */ false, /* sector_size= */ UINT32_MAX, &c); if (r < 0) return log_error_errno(r, "Failed to create fdisk context from '%s': %m", device); - assert_se((fd = fdisk_get_devfd(c)) >= 0); + assert_se((fd = sym_fdisk_get_devfd(c)) >= 0); /* Make sure udev doesn't read the device while we make changes (this lock is released automatically * by the kernel when the fd is closed, i.e. when the fdisk context is freed, hence no explicit @@ -238,21 +246,21 @@ int patch_partition( if (flock(fd, LOCK_EX) < 0) return log_error_errno(errno, "Failed to lock block device '%s': %m", device); - if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) + if (!sym_fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has no GPT disk label, not suitable.", device); - r = fdisk_get_partition(c, info->partno, &pa); + r = sym_fdisk_get_partition(c, info->partno, &pa); if (r < 0) return log_error_errno(r, "Failed to read partition %zu of GPT label of '%s': %m", info->partno, device); if (change & PARTITION_LABEL) { - r = fdisk_partition_set_name(pa, info->label); + r = sym_fdisk_partition_set_name(pa, info->label); if (r < 0) return log_error_errno(r, "Failed to update partition label: %m"); } if (change & PARTITION_UUID) { - r = fdisk_partition_set_uuid(pa, SD_ID128_TO_UUID_STRING(info->uuid)); + r = sym_fdisk_partition_set_uuid(pa, SD_ID128_TO_UUID_STRING(info->uuid)); if (r < 0) return log_error_errno(r, "Failed to update partition UUID: %m"); } @@ -260,15 +268,15 @@ int patch_partition( if (change & PARTITION_TYPE) { _cleanup_(fdisk_unref_parttypep) struct fdisk_parttype *pt = NULL; - pt = fdisk_new_parttype(); + pt = sym_fdisk_new_parttype(); if (!pt) return log_oom(); - r = fdisk_parttype_set_typestr(pt, SD_ID128_TO_UUID_STRING(info->type)); + r = sym_fdisk_parttype_set_typestr(pt, SD_ID128_TO_UUID_STRING(info->type)); if (r < 0) return log_error_errno(r, "Failed to initialize partition type: %m"); - r = fdisk_partition_set_type(pa, pt); + r = sym_fdisk_partition_set_type(pa, pt); if (r < 0) return log_error_errno(r, "Failed to update partition type: %m"); } @@ -328,11 +336,11 @@ int patch_partition( } } - r = fdisk_set_partition(c, info->partno, pa); + r = sym_fdisk_set_partition(c, info->partno, pa); if (r < 0) return log_error_errno(r, "Failed to update partition: %m"); - r = fdisk_write_disklabel(c); + r = sym_fdisk_write_disklabel(c); if (r < 0) return log_error_errno(r, "Failed to write updated partition table: %m"); diff --git a/src/sysupdate/sysupdate-resource.c b/src/sysupdate/sysupdate-resource.c index 5865a39e2f1f9..acd9604f2945d 100644 --- a/src/sysupdate/sysupdate-resource.c +++ b/src/sysupdate/sysupdate-resource.c @@ -229,18 +229,22 @@ static int resource_load_from_blockdev(Resource *rr) { assert(rr); + r = dlopen_fdisk(); + if (r < 0) + return r; + r = fdisk_new_context_at(AT_FDCWD, rr->path, /* read_only= */ true, /* sector_size= */ UINT32_MAX, &c); if (r < 0) return log_error_errno(r, "Failed to create fdisk context from '%s': %m", rr->path); - if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) + if (!sym_fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has no GPT disk label, not suitable.", rr->path); - r = fdisk_get_partitions(c, &t); + r = sym_fdisk_get_partitions(c, &t); if (r < 0) return log_error_errno(r, "Failed to acquire partition table: %m"); - n_partitions = fdisk_table_get_nents(t); + n_partitions = sym_fdisk_table_get_nents(t); for (size_t i = 0; i < n_partitions; i++) { _cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL; _cleanup_(partition_info_destroy) PartitionInfo pinfo = PARTITION_INFO_NULL; diff --git a/src/test/meson.build b/src/test/meson.build index 99c32cc6db5ec..7089d623d185a 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -301,6 +301,7 @@ executables += [ 'sources' : files('test-dlopen-so.c'), 'dependencies' : [ libblkid_cflags, + libfdisk_cflags, libkmod_cflags, libmount_cflags, libp11kit_cflags, diff --git a/src/test/test-dlopen-so.c b/src/test/test-dlopen-so.c index a1f9212e37807..a9121ae9f9730 100644 --- a/src/test/test-dlopen-so.c +++ b/src/test/test-dlopen-so.c @@ -8,6 +8,7 @@ #include "cryptsetup-util.h" #include "curl-util.h" #include "elf-util.h" +#include "fdisk-util.h" #include "gcrypt-util.h" #include "idn-util.h" #include "libarchive-util.h" @@ -49,6 +50,7 @@ static int run(int argc, char **argv) { ASSERT_DLOPEN(dlopen_curl, HAVE_LIBCURL); ASSERT_DLOPEN(dlopen_dw, HAVE_ELFUTILS); ASSERT_DLOPEN(dlopen_elf, HAVE_ELFUTILS); + ASSERT_DLOPEN(dlopen_fdisk, HAVE_LIBFDISK); ASSERT_DLOPEN(dlopen_gcrypt, HAVE_GCRYPT); ASSERT_DLOPEN(dlopen_idn, HAVE_LIBIDN2); ASSERT_DLOPEN(dlopen_libacl, HAVE_ACL); From c363bb18699e09e263ab5632e364a1aea4082dfc Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 20 Mar 2026 22:41:08 +0100 Subject: [PATCH 1139/1296] boot: change initrd_register() so that it replaces any previously registered LINUX_INITRD_MEDIA device So far, if an initrd is already registered we'd silently not register one again. Let's make this more reliable and systematic, and register ours, overriding what is previously set. (Note, in a later commit we'll incorporate any previously set initrd, which hence makes this all incremental instead of destructive as it might appear now) --- src/boot/initrd.c | 63 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/src/boot/initrd.c b/src/boot/initrd.c index 4babe1671d557..b81334ea06a30 100644 --- a/src/boot/initrd.c +++ b/src/boot/initrd.c @@ -71,8 +71,6 @@ EFI_STATUS initrd_register( EFI_HANDLE *ret_initrd_handle) { EFI_STATUS err; - EFI_DEVICE_PATH *dp = (EFI_DEVICE_PATH *) &efi_initrd_device_path; - EFI_HANDLE handle; assert(ret_initrd_handle); @@ -82,12 +80,43 @@ EFI_STATUS initrd_register( if (!iovec_is_set(initrd)) return EFI_SUCCESS; - /* Check if a LINUX_INITRD_MEDIA_GUID DevicePath is already registered. LocateDevicePath checks for - * the "closest DevicePath" and returns its handle, whereas InstallMultipleProtocolInterfaces() only - * matches identical DevicePaths. */ - err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), &dp, &handle); - if (err != EFI_NOT_FOUND) /* InitrdMedia is already registered */ - return EFI_ALREADY_STARTED; + /* We want to override the LINUX_INITRD_MEDIA device, let's hence first unregister any existing + * one. We don't really expect multiple of these to be registered, but who knows? Let's kill all we + * can find. */ + for (unsigned attempt = 0;; attempt++) { + + if (attempt >= 16) + return log_debug_status(EFI_DEVICE_ERROR, "Unable to free LINUX_INITRD_MEDIA device path after %u attempts, giving up.", attempt); + + EFI_DEVICE_PATH *dp = (EFI_DEVICE_PATH *) &efi_initrd_device_path; + EFI_HANDLE handle = NULL; + err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), &dp, &handle); + if (err == EFI_NOT_FOUND) /* Yay! All gone */ + break; + if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to locate LINUX_INITRD_MEDIA device: %m"); + + /* Get the *actually* installed pointer for the device path */ + err = BS->HandleProtocol(handle, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void**) &dp); + if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to acquire DevicePath protocol on LINUX_INITRD_MEDIA device: %m"); + + /* Take away the device path protocol */ + err = BS->UninstallMultipleProtocolInterfaces( + handle, + MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), dp, + /* sentinel= */ NULL); + if (err != EFI_SUCCESS) + return log_debug_status(err, "Unable to release DevicePath protocol from old handle: %m"); + + /* NB: we leave the handle around (and thus leave the LoadFile2 protocol installed), because + * the owner might be unhappy if we destroy it for them. It will no longer have the device + * path we want to take possession of on it though. The assumption here is that whoever + * registered the device path is OK with the device path being taken away, even if it might + * not be OK with the handle being invalidated as a whole. */ + + log_debug("Successfully unregistered previous LINUX_INITRD_MEDIA device."); + } _cleanup_free_ struct initrd_loader *loader = xnew(struct initrd_loader, 1); *loader = (struct initrd_loader) { @@ -122,12 +151,20 @@ EFI_STATUS initrd_unregister(EFI_HANDLE initrd_handle) { if (err != EFI_SUCCESS) return log_debug_status(err, "Failed to acquire LoadFile2 protocol on our own initrd handle: %m"); - /* uninstall all protocols thus destroying the handle */ + /* We uninstall the DevicePath and the LoadFile2 protocol in separate steps. That's because we want + * to gracefully handle the former (because it's OK if something else takes over the device path), + * but be strict on the latter, because that's genuinely ours */ + + (void) BS->UninstallMultipleProtocolInterfaces( + initrd_handle, + MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), &efi_initrd_device_path, + /* sentinel= */ NULL); + + /* This second call will also invalidate the handle, because it should be the last protocol on the handle */ err = BS->UninstallMultipleProtocolInterfaces( - initrd_handle, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), - &efi_initrd_device_path, MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), - loader, - NULL); + initrd_handle, + MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), loader, + /* sentinel= */ NULL); if (err != EFI_SUCCESS) return log_debug_status(err, "Failed to uninstall LoadFile2 protocol from our own initrd handle: %m"); From 426134b0e7112a09eae941d97f68b2fa1b76c4b7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 20 Mar 2026 21:48:12 +0100 Subject: [PATCH 1140/1296] stub: load previous initrd that is already configured, too This changes the initrd combination logic to also include any initrd already configured via the "LINUX_INITRD_MEDIA_GUID" device in the initrd we pass to the linux kernel. Or in other words: with this systemd-stub starts operating purely incremental: it will extend any previously installed initrd with its own stuff, so that both the previous initrd(s) and systemd-stub's are in effect. --- src/boot/initrd.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/boot/initrd.h | 2 ++ src/boot/stub.c | 44 +++++++++++++++++++++++++++++++------------- 3 files changed, 77 insertions(+), 13 deletions(-) diff --git a/src/boot/initrd.c b/src/boot/initrd.c index b81334ea06a30..4780715e7929d 100644 --- a/src/boot/initrd.c +++ b/src/boot/initrd.c @@ -171,3 +171,47 @@ EFI_STATUS initrd_unregister(EFI_HANDLE initrd_handle) { free(loader); return EFI_SUCCESS; } + +EFI_STATUS initrd_read_previous(struct iovec *ret_initrd) { + EFI_STATUS err; + + /* If there's already an initrd registered, read it out, so that we can incorporate it in ours */ + + assert(ret_initrd); + + /* Get from the device path to the handle */ + EFI_DEVICE_PATH *dp = (EFI_DEVICE_PATH *) &efi_initrd_device_path; + EFI_HANDLE handle; + err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), &dp, &handle); + if (err != EFI_SUCCESS) + return err; + + /* Get from the handle to the protocol */ + EFI_LOAD_FILE2_PROTOCOL *protocol = NULL; + err = BS->HandleProtocol(handle, MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), (void**) &protocol); + if (err != EFI_SUCCESS) + return err; + + size_t size = 0; + err = protocol->LoadFile(protocol, dp, /* bootPolicy= */ false, &size, /* Buffer= */ NULL); + if (err == EFI_SUCCESS) /* Success? Kinda unexpected given we set Buffer to NULL, but it probably + * means, that the file is zero-sized, let's treat it as such. */ + size = 0; + else if (err != EFI_BUFFER_TOO_SMALL) + return err; + + if (size == 0) + return EFI_NOT_FOUND; /* Treat empty initrds like missing ones */ + + _cleanup_free_ void *data = xmalloc(size); + err = protocol->LoadFile(protocol, dp, /* bootPolicy= */ false, &size, data); + if (err != EFI_SUCCESS) + return err; + + *ret_initrd = (struct iovec) { + .iov_base = TAKE_PTR(data), + .iov_len = size, + }; + + return EFI_SUCCESS; +} diff --git a/src/boot/initrd.h b/src/boot/initrd.h index 50987c497d667..d34f8ef4c4ff7 100644 --- a/src/boot/initrd.h +++ b/src/boot/initrd.h @@ -13,3 +13,5 @@ static inline void cleanup_initrd(EFI_HANDLE *initrd_handle) { (void) initrd_unregister(*initrd_handle); *initrd_handle = NULL; } + +EFI_STATUS initrd_read_previous(struct iovec *ret_initrd); diff --git a/src/boot/stub.c b/src/boot/stub.c index e2a8569cb33d7..664ee7cb851d5 100644 --- a/src/boot/stub.c +++ b/src/boot/stub.c @@ -10,6 +10,7 @@ #include "efi-string.h" #include "export-vars.h" #include "graphics.h" +#include "initrd.h" #include "iovec-util-fundamental.h" #include "linux.h" #include "measure.h" @@ -32,13 +33,10 @@ /* The list of initrds we combine into one, in the order we want to merge them */ enum { - /* The first two are part of the PE binary */ - INITRD_UCODE, - INITRD_BASE, - - /* The rest are dynamically generated, and hence in dynamic memory */ - _INITRD_DYNAMIC_FIRST, - INITRD_CREDENTIAL = _INITRD_DYNAMIC_FIRST, + INITRD_UCODE, /* Part of the PE binary */ + INITRD_PREVIOUS, /* initrd already configured via the EFI protocol before we were invoked */ + INITRD_BASE, /* Part of the PE binary */ + INITRD_CREDENTIAL, INITRD_GLOBAL_CREDENTIAL, INITRD_SYSEXT, INITRD_GLOBAL_SYSEXT, @@ -52,6 +50,8 @@ enum { _INITRD_MAX, }; +#define INITRD_IS_STATIC(idx) IN_SET(idx, INITRD_UCODE, INITRD_BASE) + /* magic string to find in the binary image */ DECLARE_NOALLOC_SECTION(".sdmagic", "#### LoaderInfo: systemd-stub " GIT_VERSION " ####"); @@ -550,6 +550,21 @@ static void extend_initrds( iovec_array_extend(all_initrds, n_all_initrds, *i); } +static void acquire_previous_initrd(struct iovec initrds[static _INITRD_MAX]) { + EFI_STATUS err; + + /* NB: the assumption here is that any previously installed initrd are measured by whatever + * registered them, and we just pass them on here. */ + + err = initrd_read_previous(initrds + INITRD_PREVIOUS); + if (err == EFI_NOT_FOUND) + log_debug_status(err, "No previous initrd registered."); + else if (err != EFI_SUCCESS) + log_warning_status(err, "Failed to read previously registered initrd, ignoring."); + else + log_debug("Successfully loaded previously registered initrd (%zu bytes).", initrds[INITRD_PREVIOUS].iov_len); +} + static EFI_STATUS load_addons( EFI_HANDLE stub_image, EFI_LOADED_IMAGE_PROTOCOL *loaded_image, @@ -821,10 +836,11 @@ static void cmdline_append_and_measure_smbios(char16_t **cmdline, int *parameter static void initrds_free(struct iovec (*initrds)[_INITRD_MAX]) { assert(initrds); - /* Free the dynamic initrds, but leave the non-dynamic ones around */ + /* Free the non-static initrds, but leave the static (i.e. PE embedded) ones around */ - for (size_t i = _INITRD_DYNAMIC_FIRST; i < _INITRD_MAX; i++) - iovec_done((*initrds) + i); + for (size_t i = 0; i < _INITRD_MAX; i++) + if (!INITRD_IS_STATIC(i)) + iovec_done((*initrds) + i); } static void generate_sidecar_initrds( @@ -1309,6 +1325,7 @@ static EFI_STATUS run(EFI_HANDLE image) { install_addon_devicetrees(&dt_state, dt_addons, n_dt_addons, ¶meters_measured); /* Generate & find all initrds */ + acquire_previous_initrd(initrds); generate_sidecar_initrds(loaded_image, initrds, ¶meters_measured, &sysext_measured, &confext_measured); generate_embedded_initrds(loaded_image, sections, initrds); generate_boot_secret_initrd(boot_secret, initrds); @@ -1319,9 +1336,10 @@ static EFI_STATUS run(EFI_HANDLE image) { * We want addons to take precedence over the base initrds, so the order is: * 1. Ucode addons * 2. UKI ucode - * 3. UKI initrd - * 4. Generated initrds - * 5. initrd addons */ + * 3. Previous initrds + * 4. UKI initrd + * 5. Generated initrds + * 6. initrd addons */ measure_and_append_ucode_addons(&all_initrds, &n_all_initrds, ucode_addons, n_ucode_addons, ¶meters_measured); extend_initrds(initrds, &all_initrds, &n_all_initrds); measure_and_append_initrd_addons(&all_initrds, &n_all_initrds, initrd_addons, n_initrd_addons, ¶meters_measured); From 0e98e6ccd1a4ebb0835e4ced407c89bed5847aa9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Mar 2026 18:11:45 +0100 Subject: [PATCH 1141/1296] boot: introduce a common structure for cpio target dirs There are only a few target dirs we place resources in when generating on-the-fly initrd cpios. These dirs have very specific attributes. Instead of repeating this everywhere, let's encapsulate them in a new explicit structure, that we can reuse at various places. This is preparation for placing extra resources of Type #1 entry also in them without having to encode access modes at multiple places redundantly. --- src/boot/cpio.c | 104 ++++++++++++++++++++++++++++++++++-------------- src/boot/cpio.h | 23 ++++++++--- src/boot/stub.c | 32 ++++----------- 3 files changed, 99 insertions(+), 60 deletions(-) diff --git a/src/boot/cpio.c b/src/boot/cpio.c index 7ad4b470fae02..77bf7a8352603 100644 --- a/src/boot/cpio.c +++ b/src/boot/cpio.c @@ -56,8 +56,7 @@ static EFI_STATUS pack_cpio_one( const char16_t *fname, const void *contents, size_t contents_size, - const char *target_dir_prefix, - uint32_t access_mode, + const CpioTarget *target, uint32_t *inode_counter, void **cpio_buffer, size_t *cpio_buffer_size) { @@ -67,7 +66,7 @@ static EFI_STATUS pack_cpio_one( assert(fname); assert(contents || contents_size == 0); - assert(target_dir_prefix); + assert(target); assert(inode_counter); assert(cpio_buffer); assert(cpio_buffer_size); @@ -84,7 +83,7 @@ static EFI_STATUS pack_cpio_one( l = 6 + 13*8 + 1 + 1; /* Fixed CPIO header size, slash separator, and NUL byte after the file name */ - target_dir_prefix_size = strlen8(target_dir_prefix); + target_dir_prefix_size = strlen8(target->directory); if (l > SIZE_MAX - target_dir_prefix_size) return EFI_OUT_OF_RESOURCES; l += target_dir_prefix_size; @@ -121,11 +120,11 @@ static EFI_STATUS pack_cpio_one( a = mempcpy(a, "070701", 6); /* magic ID */ - a = write_cpio_word(a, (*inode_counter)++); /* inode */ - a = write_cpio_word(a, access_mode | 0100000 /* = S_IFREG */); /* mode */ - a = write_cpio_word(a, 0); /* uid */ - a = write_cpio_word(a, 0); /* gid */ - a = write_cpio_word(a, 1); /* nlink */ + a = write_cpio_word(a, (*inode_counter)++); /* inode */ + a = write_cpio_word(a, target->access_mode | 0100000 /* = S_IFREG */); /* mode */ + a = write_cpio_word(a, 0); /* uid */ + a = write_cpio_word(a, 0); /* gid */ + a = write_cpio_word(a, 1); /* nlink */ /* Note: we don't make any attempt to propagate the mtime here, for two reasons: it's a mess given * that FAT usually is assumed to operate with timezoned timestamps, while UNIX does not. More @@ -141,7 +140,7 @@ static EFI_STATUS pack_cpio_one( a = write_cpio_word(a, target_dir_prefix_size + fname_size + 2); /* fname size */ a = write_cpio_word(a, 0); /* "crc" */ - a = mempcpy(a, target_dir_prefix, target_dir_prefix_size); + a = mempcpy(a, target->directory, target_dir_prefix_size); *(a++) = '/'; a = mangle_filename(a, fname); @@ -226,15 +225,14 @@ static EFI_STATUS pack_cpio_dir( } static EFI_STATUS pack_cpio_prefix( - const char *path, - uint32_t dir_mode, + const CpioTarget *target, uint32_t *inode_counter, void **cpio_buffer, size_t *cpio_buffer_size) { EFI_STATUS err; - assert(path); + assert(target); assert(inode_counter); assert(cpio_buffer); assert(cpio_buffer_size); @@ -243,7 +241,7 @@ static EFI_STATUS pack_cpio_prefix( * (similar to mkdir -p behaviour) all leading paths are created with 0555 access mode, only the * final dir is created with the specified directory access mode. */ - for (const char *p = path;;) { + for (const char *p = target->directory;;) { const char *e; e = strchr8(p, '/'); @@ -253,7 +251,7 @@ static EFI_STATUS pack_cpio_prefix( if (e > p) { _cleanup_free_ char *t = NULL; - t = xstrndup8(path, e - path); + t = xstrndup8(target->directory, e - target->directory); if (!t) return EFI_OUT_OF_RESOURCES; @@ -265,7 +263,7 @@ static EFI_STATUS pack_cpio_prefix( p = e + 1; } - return pack_cpio_dir(path, dir_mode, inode_counter, cpio_buffer, cpio_buffer_size); + return pack_cpio_dir(target->directory, target->dir_mode, inode_counter, cpio_buffer, cpio_buffer_size); } static EFI_STATUS pack_cpio_trailer( @@ -307,9 +305,7 @@ EFI_STATUS pack_cpio( const char16_t *dropin_dir, const char16_t *match_suffix, const char16_t *exclude_suffix, - const char *target_dir_prefix, - uint32_t dir_mode, - uint32_t access_mode, + const CpioTarget *target, uint32_t tpm_pcr, const char16_t *tpm_description, struct iovec *ret_buffer, @@ -325,7 +321,7 @@ EFI_STATUS pack_cpio( EFI_STATUS err; assert(loaded_image); - assert(target_dir_prefix); + assert(target); assert(ret_buffer); if (!loaded_image->DeviceHandle) @@ -400,7 +396,7 @@ EFI_STATUS pack_cpio( /* Generate the leading directory inodes right before adding the first files, to the * archive. Otherwise the cpio archive cannot be unpacked, since the leading dirs won't exist. */ - err = pack_cpio_prefix(target_dir_prefix, dir_mode, &inode, &buffer, &buffer_size); + err = pack_cpio_prefix(target, &inode, &buffer, &buffer_size); if (err != EFI_SUCCESS) return log_error_status(err, "Failed to pack cpio prefix: %m"); @@ -417,8 +413,7 @@ EFI_STATUS pack_cpio( err = pack_cpio_one( items[i], content, contentsize, - target_dir_prefix, - access_mode, + target, &inode, &buffer, &buffer_size); if (err != EFI_SUCCESS) @@ -453,10 +448,8 @@ EFI_STATUS pack_cpio( EFI_STATUS pack_cpio_literal( const void *data, size_t data_size, - const char *target_dir_prefix, + const CpioTarget *target, const char16_t *target_filename, - uint32_t dir_mode, - uint32_t access_mode, uint32_t tpm_pcr, const char16_t *tpm_description, struct iovec *ret_buffer, @@ -468,22 +461,21 @@ EFI_STATUS pack_cpio_literal( EFI_STATUS err; assert(data || data_size == 0); - assert(target_dir_prefix); + assert(target); assert(target_filename); assert(ret_buffer); /* Generate the leading directory inodes right before adding the first files, to the * archive. Otherwise the cpio archive cannot be unpacked, since the leading dirs won't exist. */ - err = pack_cpio_prefix(target_dir_prefix, dir_mode, &inode, &buffer, &buffer_size); + err = pack_cpio_prefix(target, &inode, &buffer, &buffer_size); if (err != EFI_SUCCESS) return log_error_status(err, "Failed to pack cpio prefix: %m"); err = pack_cpio_one( target_filename, data, data_size, - target_dir_prefix, - access_mode, + target, &inode, &buffer, &buffer_size); if (err != EFI_SUCCESS) @@ -505,3 +497,55 @@ EFI_STATUS pack_cpio_literal( *ret_buffer = IOVEC_MAKE(TAKE_PTR(buffer), buffer_size); return EFI_SUCCESS; } + +/* The following are canonical definitions of the various cpio target directories we place resources in. We + * define them here in a single canonical list of targets because we need to reuse them at various places + * (well, some of them at least), and we don't want the access modes to deviate slightly on each use. */ + +const CpioTarget cpio_target_credentials = { + .directory = ".extra/credentials", + .dir_mode = 0500, + .access_mode = 0400, +}; + +const CpioTarget cpio_target_global_credentials = { + .directory = ".extra/global_credentials", + .dir_mode = 0500, + .access_mode = 0400, +}; + +const CpioTarget cpio_target_sysext = { + .directory = ".extra/sysext", + .dir_mode = 0555, + .access_mode = 0444, +}; + +const CpioTarget cpio_target_global_sysext = { + .directory = ".extra/global_sysext", + .dir_mode = 0555, + .access_mode = 0444, +}; + +const CpioTarget cpio_target_confext = { + .directory = ".extra/confext", + .dir_mode = 0555, + .access_mode = 0444, +}; + +const CpioTarget cpio_target_global_confext = { + .directory = ".extra/global_confext", + .dir_mode = 0555, + .access_mode = 0444, +}; + +const CpioTarget cpio_target_meta = { + .directory = ".extra", + .dir_mode = 0555, + .access_mode = 0444, +}; + +const CpioTarget cpio_target_meta_secret = { + .directory = ".extra", + .dir_mode = 0555, + .access_mode = 0400, +}; diff --git a/src/boot/cpio.h b/src/boot/cpio.h index bb741278fdc24..f5c7b9fdec035 100644 --- a/src/boot/cpio.h +++ b/src/boot/cpio.h @@ -4,14 +4,18 @@ #include "efi.h" #include "proto/loaded-image.h" +typedef struct CpioTarget { + const char *directory; /* Path to directory where to place resources */ + uint32_t dir_mode; /* Access mode for the directory */ + uint32_t access_mode; /* Access mode for the files in the directory */ +} CpioTarget; + EFI_STATUS pack_cpio( EFI_LOADED_IMAGE_PROTOCOL *loaded_image, const char16_t *dropin_dir, const char16_t *match_suffix, const char16_t *exclude_suffix, - const char *target_dir_prefix, - uint32_t dir_mode, - uint32_t access_mode, + const CpioTarget *target, uint32_t tpm_pcr, const char16_t *tpm_description, struct iovec *ret_buffer, @@ -20,11 +24,18 @@ EFI_STATUS pack_cpio( EFI_STATUS pack_cpio_literal( const void *data, size_t data_size, - const char *target_dir_prefix, + const CpioTarget *target, const char16_t *target_filename, - uint32_t dir_mode, - uint32_t access_mode, uint32_t tpm_pcr, const char16_t *tpm_description, struct iovec *ret_buffer, bool *ret_measured); + +extern const CpioTarget cpio_target_credentials; +extern const CpioTarget cpio_target_global_credentials; +extern const CpioTarget cpio_target_sysext; +extern const CpioTarget cpio_target_global_sysext; +extern const CpioTarget cpio_target_confext; +extern const CpioTarget cpio_target_global_confext; +extern const CpioTarget cpio_target_meta; +extern const CpioTarget cpio_target_meta_secret; diff --git a/src/boot/stub.c b/src/boot/stub.c index 664ee7cb851d5..00ffa2889c5fd 100644 --- a/src/boot/stub.c +++ b/src/boot/stub.c @@ -862,9 +862,7 @@ static void generate_sidecar_initrds( /* dropin_dir= */ NULL, u".cred", /* exclude_suffix= */ NULL, - ".extra/credentials", - /* dir_mode= */ 0500, - /* access_mode= */ 0400, + &cpio_target_credentials, /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, u"Credentials initrd", initrds + INITRD_CREDENTIAL, @@ -875,9 +873,7 @@ static void generate_sidecar_initrds( u"\\loader\\credentials", u".cred", /* exclude_suffix= */ NULL, - ".extra/global_credentials", - /* dir_mode= */ 0500, - /* access_mode= */ 0400, + &cpio_target_global_credentials, /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, u"Global credentials initrd", initrds + INITRD_GLOBAL_CREDENTIAL, @@ -888,9 +884,7 @@ static void generate_sidecar_initrds( /* dropin_dir= */ NULL, u".raw", /* ideally we'd pick up only *.sysext.raw here, but for compat we pick up *.raw instead … */ u".confext.raw", /* … but then exclude *.confext.raw again */ - ".extra/sysext", - /* dir_mode= */ 0555, - /* access_mode= */ 0444, + &cpio_target_sysext, /* tpm_pcr= */ TPM2_PCR_SYSEXTS, u"System extension initrd", initrds + INITRD_SYSEXT, @@ -901,9 +895,7 @@ static void generate_sidecar_initrds( u"\\loader\\extensions", u".raw", /* as above */ u".confext.raw", - ".extra/global_sysext", - /* dir_mode= */ 0555, - /* access_mode= */ 0444, + &cpio_target_global_sysext, /* tpm_pcr= */ TPM2_PCR_SYSEXTS, u"Global system extension initrd", initrds + INITRD_GLOBAL_SYSEXT, @@ -914,9 +906,7 @@ static void generate_sidecar_initrds( /* dropin_dir= */ NULL, u".confext.raw", /* exclude_suffix= */ NULL, - ".extra/confext", - /* dir_mode= */ 0555, - /* access_mode= */ 0444, + &cpio_target_confext, /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, u"Configuration extension initrd", initrds + INITRD_CONFEXT, @@ -927,9 +917,7 @@ static void generate_sidecar_initrds( u"\\loader\\extensions", u".confext.raw", /* exclude_suffix= */ NULL, - ".extra/global_confext", - /* dir_mode= */ 0555, - /* access_mode= */ 0444, + &cpio_target_global_confext, /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, u"Global configuration extension initrd", initrds + INITRD_GLOBAL_CONFEXT, @@ -980,10 +968,8 @@ static void generate_embedded_initrds( (void) pack_cpio_literal( (const uint8_t*) loaded_image->ImageBase + sections[t->section].memory_offset, sections[t->section].memory_size, - ".extra", + &cpio_target_meta, t->filename, - /* dir_mode= */ 0555, - /* access_mode= */ 0444, /* tpm_pcr= */ UINT32_MAX, /* tpm_description= */ NULL, initrds + t->initrd_index, @@ -1004,10 +990,8 @@ static void generate_boot_secret_initrd( (void) pack_cpio_literal( boot_secret, BOOT_SECRET_SIZE, - ".extra", + &cpio_target_meta_secret, u"boot-secret", - /* dir_mode= */ 0555, - /* access_mode= */ 0400, /* tpm_pcr= */ UINT32_MAX, /* tpm_description= */ NULL, initrds + INITRD_BOOT_SECRET, From f6b5fcc3cfbeae5a685899628a4688357f0fc354 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Mar 2026 18:15:13 +0100 Subject: [PATCH 1142/1296] boot: export more helpers from cpio.c We want to reuse this later in systemd-boot, hence make these helpers public. --- src/boot/cpio.c | 6 +++--- src/boot/cpio.h | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/boot/cpio.c b/src/boot/cpio.c index 77bf7a8352603..81792b00a89f4 100644 --- a/src/boot/cpio.c +++ b/src/boot/cpio.c @@ -52,7 +52,7 @@ static char *pad4(char *p, const char *start) { return p; } -static EFI_STATUS pack_cpio_one( +EFI_STATUS pack_cpio_one( const char16_t *fname, const void *contents, size_t contents_size, @@ -224,7 +224,7 @@ static EFI_STATUS pack_cpio_dir( return EFI_SUCCESS; } -static EFI_STATUS pack_cpio_prefix( +EFI_STATUS pack_cpio_prefix( const CpioTarget *target, uint32_t *inode_counter, void **cpio_buffer, @@ -266,7 +266,7 @@ static EFI_STATUS pack_cpio_prefix( return pack_cpio_dir(target->directory, target->dir_mode, inode_counter, cpio_buffer, cpio_buffer_size); } -static EFI_STATUS pack_cpio_trailer( +EFI_STATUS pack_cpio_trailer( void **cpio_buffer, size_t *cpio_buffer_size) { diff --git a/src/boot/cpio.h b/src/boot/cpio.h index f5c7b9fdec035..3c311bc714d28 100644 --- a/src/boot/cpio.h +++ b/src/boot/cpio.h @@ -10,6 +10,25 @@ typedef struct CpioTarget { uint32_t access_mode; /* Access mode for the files in the directory */ } CpioTarget; +EFI_STATUS pack_cpio_one( + const char16_t *fname, + const void *contents, + size_t contents_size, + const CpioTarget *target, + uint32_t *inode_counter, + void **cpio_buffer, + size_t *cpio_buffer_size); + +EFI_STATUS pack_cpio_prefix( + const CpioTarget *target, + uint32_t *inode_counter, + void **cpio_buffer, + size_t *cpio_buffer_size); + +EFI_STATUS pack_cpio_trailer( + void **cpio_buffer, + size_t *cpio_buffer_size); + EFI_STATUS pack_cpio( EFI_LOADED_IMAGE_PROTOCOL *loaded_image, const char16_t *dropin_dir, From 9d7fa23d38bff0b2986e0ee754a210b336ffd409 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Mar 2026 18:09:01 +0100 Subject: [PATCH 1143/1296] boot: share combine_initrds() between stub/boot We'd like to use combine_initrds() later in systemd-boot, hence move it out of stub.c and into shared code. --- src/boot/initrd.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ src/boot/initrd.h | 4 ++++ src/boot/stub.c | 44 -------------------------------------------- 3 files changed, 49 insertions(+), 44 deletions(-) diff --git a/src/boot/initrd.c b/src/boot/initrd.c index 4780715e7929d..044e011f60e89 100644 --- a/src/boot/initrd.c +++ b/src/boot/initrd.c @@ -215,3 +215,48 @@ EFI_STATUS initrd_read_previous(struct iovec *ret_initrd) { return EFI_SUCCESS; } + +EFI_STATUS combine_initrds( + const struct iovec initrds[], size_t n_initrds, + Pages *ret_initrd_pages, size_t *ret_initrd_size) { + + size_t n = 0; + + /* Combine initrds by concatenation in memory */ + + assert(initrds || n_initrds == 0); + assert(ret_initrd_pages); + assert(ret_initrd_size); + + FOREACH_ARRAY(i, initrds, n_initrds) { + /* some initrds (the ones from UKI sections) need padding, pad all to be safe */ + size_t initrd_size = ALIGN4(i->iov_len); + if (n > SIZE_MAX - initrd_size) + return EFI_OUT_OF_RESOURCES; + + n += initrd_size; + } + + _cleanup_pages_ Pages pages = xmalloc_initrd_pages(n); + uint8_t *p = PHYSICAL_ADDRESS_TO_POINTER(pages.addr); + + FOREACH_ARRAY(i, initrds, n_initrds) { + size_t pad; + + p = mempcpy(p, i->iov_base, i->iov_len); + + pad = ALIGN4(i->iov_len) - i->iov_len; + if (pad == 0) + continue; + + memzero(p, pad); + p += pad; + } + + assert(PHYSICAL_ADDRESS_TO_POINTER(pages.addr + n) == p); + + *ret_initrd_pages = TAKE_STRUCT(pages); + *ret_initrd_size = n; + + return EFI_SUCCESS; +} diff --git a/src/boot/initrd.h b/src/boot/initrd.h index d34f8ef4c4ff7..cfcb8d1c6f59b 100644 --- a/src/boot/initrd.h +++ b/src/boot/initrd.h @@ -2,6 +2,8 @@ #pragma once #include "efi.h" +#include "iovec-util-fundamental.h" +#include "util.h" EFI_STATUS initrd_register( const struct iovec *initrd, @@ -15,3 +17,5 @@ static inline void cleanup_initrd(EFI_HANDLE *initrd_handle) { } EFI_STATUS initrd_read_previous(struct iovec *ret_initrd); + +EFI_STATUS combine_initrds(const struct iovec initrds[], size_t n_initrds, Pages *ret_initrd_pages, size_t *ret_initrd_size); diff --git a/src/boot/stub.c b/src/boot/stub.c index 00ffa2889c5fd..8632a603a21de 100644 --- a/src/boot/stub.c +++ b/src/boot/stub.c @@ -102,50 +102,6 @@ static void combine_measured_flag(int *value, int measured) { *value = *value < 0 ? measured : *value && measured; } -/* Combine initrds by concatenation in memory */ -static EFI_STATUS combine_initrds( - const struct iovec initrds[], size_t n_initrds, - Pages *ret_initrd_pages, size_t *ret_initrd_size) { - - size_t n = 0; - - assert(initrds || n_initrds == 0); - assert(ret_initrd_pages); - assert(ret_initrd_size); - - FOREACH_ARRAY(i, initrds, n_initrds) { - /* some initrds (the ones from UKI sections) need padding, pad all to be safe */ - size_t initrd_size = ALIGN4(i->iov_len); - if (n > SIZE_MAX - initrd_size) - return EFI_OUT_OF_RESOURCES; - - n += initrd_size; - } - - _cleanup_pages_ Pages pages = xmalloc_initrd_pages(n); - uint8_t *p = PHYSICAL_ADDRESS_TO_POINTER(pages.addr); - - FOREACH_ARRAY(i, initrds, n_initrds) { - size_t pad; - - p = mempcpy(p, i->iov_base, i->iov_len); - - pad = ALIGN4(i->iov_len) - i->iov_len; - if (pad == 0) - continue; - - memzero(p, pad); - p += pad; - } - - assert(PHYSICAL_ADDRESS_TO_POINTER(pages.addr + n) == p); - - *ret_initrd_pages = TAKE_STRUCT(pages); - *ret_initrd_size = n; - - return EFI_SUCCESS; -} - static void export_stub_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image, unsigned profile) { static const uint64_t stub_features = EFI_STUB_FEATURE_REPORT_BOOT_PARTITION | /* We set LoaderDevicePartUUID */ From a84bbd15d964d02fd0f5d688865b39eeeae38d99 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 21 Apr 2026 09:20:21 +0200 Subject: [PATCH 1144/1296] ci: Restore severity prefix on claude-review inline comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit a65ebc3ff9 ("claude-review: improve review quality for large PRs") dropped the `Claude: ****: ` prefix from posted inline comments on the theory that Claude was also adding the severity into `body`, producing duplicates. But nothing in the prompt or schema actually asks the subagent to include severity in `body` — severity is a separate structured field. The result is that inline comments no longer show must-fix/suggestion/nit classification. Restore the prefix in the posting step, and add an explicit instruction to the subagent prompt telling it not to repeat severity inside `body` so the two don't collide. Signed-off-by: Christian Brauner (Amutable) --- .github/workflows/claude-review.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 3829313cf97d8..e319214bf087f 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -285,6 +285,10 @@ jobs: - Instructions to return ONLY a raw JSON array of findings. No markdown, no explanation, no code fences — just the JSON array. If there are no findings, return `[]`. + - Instructions that `severity` is a separate structured field — do NOT + repeat it inside `body` (no "must-fix:", "**suggestion**:", etc. + prefix). The posting step adds the severity label itself, so + including it in `body` produces duplicates. ## Phase 2: Collect, deduplicate, and summarize @@ -488,7 +492,7 @@ jobs: ...(c.side != null && { side: c.side }), ...(c.start_line != null && { start_line: c.start_line }), ...(c.start_side != null && { start_side: c.start_side }), - body: c.body, + body: `Claude: **${c.severity}**: ${c.body}`, }); posted++; } catch (e) { From 812aa57d2cbcb037219552c48d4ec1a6754892aa Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 16 Apr 2026 09:03:24 +0200 Subject: [PATCH 1145/1296] string-util: beef up string_is_safe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This tightens the checks of string_is_safe() and then adds flags to relax certain aspects of it. This does alter the rules on certain strings we pass a bit. We mostly tighten the rules (but I think it's find and good) but we relax them on others. I let claude review the changes in behaviour for the various call sites that I made. It summarized things in this table: ╭───────────────────────────────────────────────────┬──────────────────────────────────────────────╮ │ CALL SITE │ EFFECTIVE DELTA │ ├───────────────────────────────────────────────────┼──────────────────────────────────────────────┤ │ src/basic/syslog-util log_namespace_name_valid │ +UTF-8 required (globs already blocked) │ │ src/bootctl --efi-boot-option-description │ RELAXED: '\' and quotes now permitted │ │ src/core/dbus-manager pretimeout governor │ +UTF-8, +no-globs │ │ src/core/load-fragment ExecStart= path │ +UTF-8, +no-globs │ │ src/core/main pretimeout governor (kcmdline) │ +UTF-8, +no-globs │ │ src/core/service sd_notify STATUS= │ +no-globs (ASCII-only preserved) │ │ src/home/homectl --= │ empty now REJECTED; +UTF-8 │ │ src/libsystemd-network dhcp_option_parse_string │ (equivalent, just explicit) │ │ src/libsystemd-network sd_dhcp_server boot_fname │ ""→NULL coerced; else equivalent │ │ src/libsystemd/journal SYSLOG_IDENTIFIER fb │ +UTF-8, +no-globs │ │ src/libsystemd/sd-json SD_JSON_STRICT strings │ +UTF-8 required │ │ src/login/logind session desktop= │ +UTF-8 required │ │ src/pcrlock EFI variable string │ +UTF-8 │ │ src/pcrlock EFI action string │ RELAXED: empty + '\' now ok; +UTF-8 │ │ src/resolve dns-delegate id (from filename) │ +UTF-8, +no-globs │ │ src/shared/boot-entry boot_entry_token_valid │ (equivalent) │ │ src/shared/conf-parser section header │ +UTF-8, +no-globs │ │ src/shared/conf-parser CONFIG_PARSE_STRING_SAFE │ +UTF-8 required │ │ src/shared/kbd-util keymap_is_valid │ (equivalent; folded into STRING_FILENAME) │ │ src/shared/tpm2 nvpcr name │ +UTF-8 required │ │ src/shared/vconsole x11 layout/model/variant/opt │ +UTF-8, +no-globs │ │ src/systemctl --kernel-cmdline= │ +0x7f DEL rejected; empty path split out │ │ src/veritysetup salt= │ RELAXED: safety check removed entirely │ │ src/vmspawn --ssh-key-type= │ +UTF-8 required │ ╰───────────────────────────────────────────────────┴──────────────────────────────────────────────╯ --- src/basic/string-util.c | 33 ++++-- src/basic/string-util.h | 12 +- src/basic/syslog-util.c | 12 +- src/bootctl/bootctl.c | 3 +- src/core/dbus-manager.c | 7 +- src/core/load-fragment.c | 2 +- src/core/main.c | 2 +- src/core/manager.c | 16 ++- src/core/service.c | 2 +- src/home/homectl.c | 2 +- src/libsystemd-network/dhcp-option.c | 2 +- src/libsystemd-network/sd-dhcp-server.c | 4 +- src/libsystemd/sd-journal/journal-send.c | 10 +- src/libsystemd/sd-json/sd-json.c | 6 +- src/login/logind-dbus.c | 2 +- src/pcrlock/pcrlock.c | 4 +- src/resolve/resolved-dns-delegate.c | 4 +- src/shared/boot-entry.c | 3 +- src/shared/conf-parser.c | 6 +- src/shared/kbd-util.c | 13 +-- src/shared/tpm2-util.c | 3 +- src/shared/vconsole-util.c | 8 +- src/systemctl/systemctl.c | 15 +-- src/test/test-string-util.c | 142 +++++++++++++++++++++++ src/veritysetup/veritysetup.c | 3 - src/vmspawn/vmspawn.c | 7 +- 26 files changed, 238 insertions(+), 85 deletions(-) diff --git a/src/basic/string-util.c b/src/basic/string-util.c index a2bbaf1ea8fbb..4d930839232d7 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -1099,25 +1099,40 @@ int strdup_to_full(char **ret, const char *src) { } }; -bool string_is_safe(const char *p) { - if (!p) +bool string_is_safe(const char *p, StringSafeFlags flags) { + + /* Baseline checks are: + * • No control characters (i.e. 0…31 + 127) + * • UTF-8 valid (well, technically we skip this test if STRING_ASCII is set, since that is a tighter test) + */ + + if (FLAGS_SET(flags, STRING_ALLOW_EMPTY) ? !p : isempty(p)) return false; - /* Checks if the specified string contains no quotes or control characters */ + if (!FLAGS_SET(flags, STRING_ASCII) && !utf8_is_valid(p)) + return false; for (const char *t = p; *t; t++) { - if (*t > 0 && *t < ' ') /* no control characters */ + if ((*t > 0 && *t < ' ') || *t == 0x7f) /* never allow control characters */ + return false; + + if (!FLAGS_SET(flags, STRING_ALLOW_BACKSLASHES) && *t == '\\') return false; - if (strchr(QUOTES "\\\x7f", *t)) + if (!FLAGS_SET(flags, STRING_ALLOW_QUOTES) && strchr(QUOTES, *t)) + return false; + + if (!FLAGS_SET(flags, STRING_ALLOW_GLOBS) && strchr(GLOB_CHARS, *t)) + return false; + + if (FLAGS_SET(flags, STRING_ASCII) && (uint8_t) *t >= 0x80) return false; } - return true; -} + if (FLAGS_SET(flags, STRING_FILENAME) && !filename_is_valid(p)) + return false; -bool string_is_safe_ascii(const char *p) { - return ascii_is_valid(p) && string_is_safe(p); + return true; } char* str_realloc(char *p) { diff --git a/src/basic/string-util.h b/src/basic/string-util.h index 820b8688c2229..17eaf5d9a6a82 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -220,8 +220,16 @@ static inline int strdup_to(char **ret, const char *src) { return r < 0 ? r : 0; /* Suppress return value of 1. */ } -bool string_is_safe(const char *p) _pure_; -bool string_is_safe_ascii(const char *p) _pure_; +typedef enum StringSafeFlags { + STRING_ASCII = 1 << 0, /* Verify string is 7-Bit ASCII (rather than just UTF-8) */ + STRING_ALLOW_EMPTY = 1 << 1, /* Allow empty strings */ + STRING_ALLOW_BACKSLASHES = 1 << 2, /* Allow backslashes (\) */ + STRING_ALLOW_QUOTES = 1 << 3, /* Allow quotes (" or ') */ + STRING_ALLOW_GLOBS = 1 << 4, /* Allow globs (?, * or [) */ + STRING_FILENAME = 1 << 5, /* Verify the string is valid as regular filename */ +} StringSafeFlags; + +bool string_is_safe(const char *p, StringSafeFlags flags) _pure_; DISABLE_WARNING_STRINGOP_TRUNCATION; static inline void strncpy_exact(char *buf, const char *src, size_t buf_len) { diff --git a/src/basic/syslog-util.c b/src/basic/syslog-util.c index fd910ca76d450..bd1bb65282332 100644 --- a/src/basic/syslog-util.c +++ b/src/basic/syslog-util.c @@ -4,9 +4,7 @@ #include "sd-id128.h" -#include "glob-util.h" #include "hexdecoct.h" -#include "path-util.h" #include "string-table.h" #include "string-util.h" #include "syslog-util.h" @@ -111,7 +109,8 @@ bool log_namespace_name_valid(const char *s) { * (so that /var/log/journal/. can be created based on it). Also make sure it * is suitable as unit instance name, and does not contain fishy characters. */ - if (!filename_is_valid(s)) + /* Let's avoid globbing for now */ + if (!string_is_safe(s, STRING_FILENAME)) return false; if (strlen(s) > LOG_NAMESPACE_MAX) @@ -120,12 +119,5 @@ bool log_namespace_name_valid(const char *s) { if (!unit_instance_is_valid(s)) return false; - if (!string_is_safe(s)) - return false; - - /* Let's avoid globbing for now */ - if (string_is_glob(s)) - return false; - return true; } diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index 308ff6a106d65..00ae512668e7f 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -39,7 +39,6 @@ #include "string-table.h" #include "string-util.h" #include "strv.h" -#include "utf8.h" #include "varlink-io.systemd.BootControl.h" #include "varlink-util.h" #include "verbs.h" @@ -573,7 +572,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("efi-boot-option-description", "DESCRIPTION", "Description of the entry in the boot option list"): - if (isempty(arg) || !(string_is_safe(arg) && utf8_is_valid(arg))) { + if (!string_is_safe(arg, STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES|STRING_ALLOW_GLOBS)) { _cleanup_free_ char *escaped = cescape(arg); return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid --efi-boot-option-description=: %s", strna(escaped)); diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 5a7f70d78bf6f..1bc73e7b434c9 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -387,13 +387,16 @@ static int property_set_pretimeout_watchdog_governor( sd_bus_error *reterr_error) { Manager *m = ASSERT_PTR(userdata); - char *governor; + const char *governor; int r; r = sd_bus_message_read(value, "s", &governor); if (r < 0) return r; - if (!string_is_safe(governor)) + + if (isempty(governor)) + governor = NULL; + else if (!string_is_safe(governor, /* flags= */ 0)) return -EINVAL; return manager_override_watchdog_pretimeout_governor(m, governor); diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 52005c8c43600..1fae521333f79 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -990,7 +990,7 @@ int config_parse_exec( ignore ? ", ignoring" : "", rvalue); return ignore ? 0 : -ENOEXEC; } - if (!string_is_safe(path)) { + if (!string_is_safe(path, /* flags= */ 0)) { log_syntax(unit, ignore ? LOG_WARNING : LOG_ERR, filename, line, 0, "Executable path contains special characters%s: %s", ignore ? ", ignoring" : "", path); diff --git a/src/core/main.c b/src/core/main.c index b4022105e88b4..e89cb9da3d1f3 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -502,7 +502,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat return 0; } - if (!string_is_safe(value)) { + if (!string_is_safe(value, /* flags= */ 0)) { log_warning("Watchdog pretimeout governor '%s' is not valid, ignoring.", value); return 0; } diff --git a/src/core/manager.c b/src/core/manager.c index 01841a97d6d22..56ee66e5bd292 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -3576,12 +3576,14 @@ int manager_set_watchdog_pretimeout_governor(Manager *m, const char *governor) { if (MANAGER_IS_USER(m)) return 0; + governor = empty_to_null(governor); + if (streq_ptr(m->watchdog_pretimeout_governor, governor)) return 0; - p = strdup(governor); - if (!p) - return -ENOMEM; + r = strdup_to(&p, governor); + if (r < 0) + return r; r = watchdog_setup_pretimeout_governor(governor); if (r < 0) @@ -3599,12 +3601,14 @@ int manager_override_watchdog_pretimeout_governor(Manager *m, const char *govern if (MANAGER_IS_USER(m)) return 0; + governor = empty_to_null(governor); + if (streq_ptr(m->watchdog_pretimeout_governor_overridden, governor)) return 0; - p = strdup(governor); - if (!p) - return -ENOMEM; + r = strdup_to(&p, governor); + if (r < 0) + return r; r = watchdog_setup_pretimeout_governor(governor); if (r < 0) diff --git a/src/core/service.c b/src/core/service.c index 63e659942188f..b2a656a6fe84d 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -5210,7 +5210,7 @@ static void service_notify_message( e = empty_to_null(e); - if (e && !string_is_safe_ascii(e)) { + if (e && !string_is_safe(e, STRING_ASCII)) { _cleanup_free_ char *escaped = cescape(e); log_unit_warning(u, "Got invalid %s string, ignoring: %s", i->tag, strna(escaped)); } else if (free_and_strdup_warn(status_error, e) > 0) diff --git a/src/home/homectl.c b/src/home/homectl.c index 194e73b491749..4ebf47ca9e75a 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -4666,7 +4666,7 @@ static int parse_argv(int argc, char *argv[]) { IN_SET(c, ARG_STORAGE, ARG_FS_TYPE) ? &arg_identity_extra_this_machine : &arg_identity_extra; - if (!isempty(optarg) && !string_is_safe(optarg)) + if (!string_is_safe(optarg, STRING_ALLOW_GLOBS)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Parameter for field %s not valid: %s", field, optarg); diff --git a/src/libsystemd-network/dhcp-option.c b/src/libsystemd-network/dhcp-option.c index c78e3cdad9d72..a195a4b098353 100644 --- a/src/libsystemd-network/dhcp-option.c +++ b/src/libsystemd-network/dhcp-option.c @@ -427,7 +427,7 @@ int dhcp_option_parse_string(const uint8_t *option, size_t len, char **ret) { if (r < 0) return r; - if (!string_is_safe(string) || !utf8_is_valid(string)) + if (!string_is_safe(string, STRING_ALLOW_EMPTY|STRING_ALLOW_GLOBS)) return -EINVAL; *ret = TAKE_PTR(string); diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index 34ed3e10d33bc..fa0b830196983 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -271,7 +271,9 @@ int sd_dhcp_server_set_boot_server_name(sd_dhcp_server *server, const char *name int sd_dhcp_server_set_boot_filename(sd_dhcp_server *server, const char *filename) { assert_return(server, -EINVAL); - if (filename && !string_is_safe_ascii(filename)) + if (isempty(filename)) + filename = NULL; + else if (!string_is_safe(filename, STRING_ASCII|STRING_ALLOW_GLOBS)) return -EINVAL; return free_and_strdup(&server->boot_filename, filename); diff --git a/src/libsystemd/sd-journal/journal-send.c b/src/libsystemd/sd-journal/journal-send.c index 5c7b007b131b2..931e669a08926 100644 --- a/src/libsystemd/sd-journal/journal-send.c +++ b/src/libsystemd/sd-journal/journal-send.c @@ -273,13 +273,11 @@ _public_ int sd_journal_sendv(const struct iovec *iov, int n) { } if (!have_syslog_identifier && - string_is_safe(program_invocation_short_name)) { + string_is_safe(program_invocation_short_name, /* flags= */ 0)) { - /* Implicitly add program_invocation_short_name, if it - * is not set explicitly. We only do this for - * program_invocation_short_name, and nothing else - * since everything else is much nicer to retrieve - * from the outside. */ + /* Implicitly add program_invocation_short_name, if it is not set explicitly. We only do this + * for program_invocation_short_name, and nothing else since everything else is much nicer to + * retrieve from the outside. */ w[j++] = IOVEC_MAKE_STRING("SYSLOG_IDENTIFIER="); w[j++] = IOVEC_MAKE_STRING(program_invocation_short_name); diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index 4c39b5660a649..4c541275c42c5 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -5652,7 +5652,7 @@ _public_ int sd_json_dispatch_const_string(const char *name, sd_json_variant *va if (!sd_json_variant_is_string(variant)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); - if ((flags & SD_JSON_STRICT) && !string_is_safe(sd_json_variant_string(variant))) + if ((flags & SD_JSON_STRICT) && !string_is_safe(sd_json_variant_string(variant), STRING_ALLOW_EMPTY|STRING_ALLOW_GLOBS)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' contains unsafe characters, refusing.", strna(name)); *s = sd_json_variant_string(variant); @@ -5675,7 +5675,7 @@ _public_ int sd_json_dispatch_strv(const char *name, sd_json_variant *variant, s /* Let's be flexible here: accept a single string in place of a single-item array */ if (sd_json_variant_is_string(variant)) { - if ((flags & SD_JSON_STRICT) && !string_is_safe(sd_json_variant_string(variant))) + if ((flags & SD_JSON_STRICT) && !string_is_safe(sd_json_variant_string(variant), STRING_ALLOW_EMPTY|STRING_ALLOW_GLOBS)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' contains unsafe characters, refusing.", strna(name)); l = strv_new(sd_json_variant_string(variant)); @@ -5693,7 +5693,7 @@ _public_ int sd_json_dispatch_strv(const char *name, sd_json_variant *variant, s if (!sd_json_variant_is_string(e)) return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a string."); - if ((flags & SD_JSON_STRICT) && !string_is_safe(sd_json_variant_string(e))) + if ((flags & SD_JSON_STRICT) && !string_is_safe(sd_json_variant_string(e), STRING_ALLOW_EMPTY|STRING_ALLOW_GLOBS)) return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' contains unsafe characters, refusing.", strna(name)); r = strv_extend(&l, sd_json_variant_string(e)); diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 9668bf0609868..14208bc1496e9 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -1155,7 +1155,7 @@ static int manager_create_session_by_bus( if (isempty(desktop)) desktop = NULL; else { - if (!string_is_safe(desktop)) + if (!string_is_safe(desktop, STRING_ALLOW_GLOBS)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid desktop string %s", desktop); } diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index a6ec5c23d03a2..fd8b9e95a4fd0 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -543,7 +543,7 @@ static int event_log_record_parse_variable_data( if (!p) return log_oom_debug(); - if (!string_is_safe(p)) + if (!string_is_safe(p, STRING_ALLOW_GLOBS)) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Unsafe EFI variable string in record."); *ret_variable_uuid = efi_guid_to_id128(vdata->variableName); @@ -627,7 +627,7 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { if (r < 0) return log_error_errno(r, "Failed to make C string from EFI action string: %m"); - if (!string_is_safe(d)) { + if (!string_is_safe(d, STRING_ALLOW_GLOBS|STRING_ALLOW_EMPTY|STRING_ALLOW_BACKSLASHES)) { log_warning("Unsafe EFI action string in record, ignoring."); goto invalid; } diff --git a/src/resolve/resolved-dns-delegate.c b/src/resolve/resolved-dns-delegate.c index eee2daab94b94..db34916a29771 100644 --- a/src/resolve/resolved-dns-delegate.c +++ b/src/resolve/resolved-dns-delegate.c @@ -172,8 +172,8 @@ static int dns_delegate_load(Manager *m, const char *path) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "DNS delegate file name does not end in .dns-delegate, refusing: %s", fn); _cleanup_free_ char *id = strndup(fn, e - fn); - if (!string_is_safe(id)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "DNS delegate file name contains weird characters, refusing: %s", fn); + if (!string_is_safe(id, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "DNS delegate file name is invalid, refusing: %s", fn); _cleanup_free_ char *dropin_dirname = strjoin(id, ".dns-delegate.d"); if (!dropin_dirname) diff --git a/src/shared/boot-entry.c b/src/shared/boot-entry.c index c9e966ba04ea8..b0dac0d782539 100644 --- a/src/shared/boot-entry.c +++ b/src/shared/boot-entry.c @@ -11,10 +11,9 @@ #include "string-table.h" #include "string-util.h" #include "strv.h" -#include "utf8.h" bool boot_entry_token_valid(const char *p) { - return utf8_is_valid(p) && string_is_safe(p) && filename_is_valid(p); + return string_is_safe(p, STRING_FILENAME); } static int entry_token_load_one(int rfd, const char *dir, BootEntryTokenType *type, char **token) { diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c index b448032939d53..feaed66cbe264 100644 --- a/src/shared/conf-parser.c +++ b/src/shared/conf-parser.c @@ -217,8 +217,8 @@ static int parse_line( if (!n) return log_oom(); - if (!string_is_safe(n)) - return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EBADMSG), "Bad characters in section header '%s'", l); + if (!string_is_safe(n, /* flags= */ 0)) + return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EBADMSG), "Section header invalid '%s'", l); if (sections && !nulstr_contains(sections, n)) { bool ignore; @@ -1244,7 +1244,7 @@ int config_parse_string( return 1; } - if (FLAGS_SET(ltype, CONFIG_PARSE_STRING_SAFE) && !string_is_safe(rvalue)) { + if (FLAGS_SET(ltype, CONFIG_PARSE_STRING_SAFE) && !string_is_safe(rvalue, STRING_ALLOW_GLOBS)) { _cleanup_free_ char *escaped = NULL; escaped = cescape(rvalue); diff --git a/src/shared/kbd-util.c b/src/shared/kbd-util.c index 84031df784354..28ea2e8612e5d 100644 --- a/src/shared/kbd-util.c +++ b/src/shared/kbd-util.c @@ -5,12 +5,10 @@ #include "errno-util.h" #include "kbd-util.h" #include "log.h" -#include "path-util.h" #include "recurse-dir.h" #include "set.h" #include "string-util.h" #include "strv.h" -#include "utf8.h" #define KBD_KEYMAP_DIRS \ "/usr/share/keymaps/", \ @@ -129,21 +127,12 @@ int get_keymaps(char ***ret) { } bool keymap_is_valid(const char *name) { - if (isempty(name)) + if (!string_is_safe(name, STRING_FILENAME)) return false; if (strlen(name) >= 128) return false; - if (!utf8_is_valid(name)) - return false; - - if (!filename_is_valid(name)) - return false; - - if (!string_is_safe(name)) - return false; - return true; } diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 02f89c02ba71f..fa0fa71130b57 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -9489,7 +9489,6 @@ DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_FALLBACK(tpm2_pcr_index, int, TPM2_P DEFINE_STRING_TABLE_LOOKUP_TO_STRING(tpm2_pcr_index, int); bool tpm2_nvpcr_name_is_valid(const char *name) { - return filename_is_valid(name) && - string_is_safe(name) && + return string_is_safe(name, STRING_FILENAME) && tpm2_pcr_index_from_string(name) < 0; /* don't allow nvpcrs to be name like pcrs */ } diff --git a/src/shared/vconsole-util.c b/src/shared/vconsole-util.c index cd623bebdbbb6..6e8c17561e8a7 100644 --- a/src/shared/vconsole-util.c +++ b/src/shared/vconsole-util.c @@ -87,10 +87,10 @@ bool x11_context_is_safe(const X11Context *xc) { assert(xc); return - (!xc->layout || string_is_safe(xc->layout)) && - (!xc->model || string_is_safe(xc->model)) && - (!xc->variant || string_is_safe(xc->variant)) && - (!xc->options || string_is_safe(xc->options)); + (!xc->layout || string_is_safe(xc->layout, /* flags= */ 0)) && + (!xc->model || string_is_safe(xc->model, /* flags= */ 0)) && + (!xc->variant || string_is_safe(xc->variant, /* flags= */ 0)) && + (!xc->options || string_is_safe(xc->options, /* flags= */ 0)); } bool x11_context_equal(const X11Context *a, const X11Context *b) { diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 5c9a24b119ade..e5e7b412f568d 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -27,7 +27,6 @@ #include "systemctl-compat-shutdown.h" #include "systemctl-logind.h" #include "time-util.h" -#include "utf8.h" char **arg_types = NULL; char **arg_states = NULL; @@ -969,16 +968,18 @@ static int systemctl_parse_argv(int argc, char *argv[]) { break; case ARG_KERNEL_CMDLINE: - if (!utf8_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--kernel-cmdline= argument is not valid UTF-8: %s", optarg); - if (string_has_cc(optarg, NULL)) + if (isempty(optarg)) { + arg_kernel_cmdline = mfree(arg_kernel_cmdline); + break; + } + + if (!string_is_safe(optarg, STRING_ALLOW_GLOBS|STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--kernel-cmdline= argument contains control characters: %s", optarg); + "--kernel-cmdline= argument contains invalid characters: %s", optarg); r = free_and_strdup_warn(&arg_kernel_cmdline, optarg); if (r < 0) - return r; + return r; break; case ARG_TIMESTAMP_STYLE: diff --git a/src/test/test-string-util.c b/src/test/test-string-util.c index 5707569e7e17d..6e07d727636ee 100644 --- a/src/test/test-string-util.c +++ b/src/test/test-string-util.c @@ -1495,4 +1495,146 @@ TEST(str_common_prefix) { ASSERT_EQ(str_common_prefix("systemd-networkd", ""), 0U); } +TEST(string_is_safe) { + /* NULL is always rejected, regardless of flags. */ + ASSERT_FALSE(string_is_safe(NULL, 0)); + ASSERT_FALSE(string_is_safe(NULL, STRING_ALLOW_EMPTY)); + ASSERT_FALSE(string_is_safe(NULL, STRING_ASCII)); + ASSERT_FALSE(string_is_safe(NULL, STRING_ALLOW_BACKSLASHES)); + ASSERT_FALSE(string_is_safe(NULL, STRING_ALLOW_QUOTES)); + ASSERT_FALSE(string_is_safe(NULL, STRING_ALLOW_GLOBS)); + ASSERT_FALSE(string_is_safe(NULL, STRING_FILENAME)); + + /* Baseline (flags=0): rejects empty, backslashes, quotes, globs, control chars and invalid UTF-8. + * Plain alphanumerics/whitespace and valid UTF-8 accepted. */ + ASSERT_TRUE(string_is_safe("hello", 0)); + ASSERT_TRUE(string_is_safe("hello world", 0)); + ASSERT_TRUE(string_is_safe("über", 0)); /* valid UTF-8 allowed */ + ASSERT_TRUE(string_is_safe("ünïcödé", 0)); + + ASSERT_FALSE(string_is_safe("", 0)); /* empty rejected by default */ + ASSERT_FALSE(string_is_safe("a\\b", 0)); /* backslash rejected by default */ + ASSERT_FALSE(string_is_safe("\"", 0)); /* double quote rejected by default */ + ASSERT_FALSE(string_is_safe("'", 0)); /* single quote rejected by default */ + ASSERT_FALSE(string_is_safe("*", 0)); /* glob rejected by default */ + ASSERT_FALSE(string_is_safe("?", 0)); /* glob rejected by default */ + ASSERT_FALSE(string_is_safe("[", 0)); /* glob rejected by default */ + ASSERT_FALSE(string_is_safe("abc\x01", 0)); /* control char */ + ASSERT_FALSE(string_is_safe("\t", 0)); + ASSERT_FALSE(string_is_safe("\n", 0)); + ASSERT_FALSE(string_is_safe("abc\x1f", 0)); + ASSERT_FALSE(string_is_safe("abc\x7f", 0)); /* DEL */ + ASSERT_FALSE(string_is_safe("ab\xc3\x28", 0)); /* invalid UTF-8 continuation */ + ASSERT_FALSE(string_is_safe("\xff", 0)); /* not valid UTF-8 */ + + /* STRING_ALLOW_EMPTY. */ + ASSERT_TRUE(string_is_safe("", STRING_ALLOW_EMPTY)); + ASSERT_TRUE(string_is_safe("x", STRING_ALLOW_EMPTY)); + ASSERT_TRUE(string_is_safe("hello", STRING_ALLOW_EMPTY)); + ASSERT_FALSE(string_is_safe(NULL, STRING_ALLOW_EMPTY)); + + /* STRING_ASCII: high bytes rejected, low ASCII accepted, control chars still rejected. + * Empty is still rejected by default; backslashes/quotes/globs still rejected by default. */ + ASSERT_TRUE(string_is_safe("hello", STRING_ASCII)); + ASSERT_TRUE(string_is_safe("hello world 123!@#$%^&()", STRING_ASCII)); + ASSERT_FALSE(string_is_safe("", STRING_ASCII)); + ASSERT_FALSE(string_is_safe("über", STRING_ASCII)); + ASSERT_FALSE(string_is_safe("\x80", STRING_ASCII)); + ASSERT_FALSE(string_is_safe("\xff", STRING_ASCII)); + ASSERT_FALSE(string_is_safe("abc\x01", STRING_ASCII)); + ASSERT_FALSE(string_is_safe("abc\x7f", STRING_ASCII)); + ASSERT_FALSE(string_is_safe("a\\b", STRING_ASCII)); + ASSERT_FALSE(string_is_safe("a\"b", STRING_ASCII)); + ASSERT_FALSE(string_is_safe("a*b", STRING_ASCII)); + + /* STRING_ALLOW_BACKSLASHES: backslashes allowed, quotes/globs still rejected. */ + ASSERT_TRUE(string_is_safe("hello", STRING_ALLOW_BACKSLASHES)); + ASSERT_TRUE(string_is_safe("hello world", STRING_ALLOW_BACKSLASHES)); + ASSERT_TRUE(string_is_safe("\\", STRING_ALLOW_BACKSLASHES)); + ASSERT_TRUE(string_is_safe("a\\b", STRING_ALLOW_BACKSLASHES)); + ASSERT_TRUE(string_is_safe("foo\\", STRING_ALLOW_BACKSLASHES)); + ASSERT_TRUE(string_is_safe("\\foo", STRING_ALLOW_BACKSLASHES)); + ASSERT_TRUE(string_is_safe("foo\\nbar", STRING_ALLOW_BACKSLASHES)); /* literal backslash, not newline */ + ASSERT_FALSE(string_is_safe("\"", STRING_ALLOW_BACKSLASHES)); /* quotes still rejected */ + ASSERT_FALSE(string_is_safe("*", STRING_ALLOW_BACKSLASHES)); /* globs still rejected */ + + /* STRING_ALLOW_QUOTES: quotes allowed, backslashes/globs still rejected. */ + ASSERT_TRUE(string_is_safe("hello", STRING_ALLOW_QUOTES)); + ASSERT_TRUE(string_is_safe("hello world", STRING_ALLOW_QUOTES)); + ASSERT_TRUE(string_is_safe("\"", STRING_ALLOW_QUOTES)); + ASSERT_TRUE(string_is_safe("'", STRING_ALLOW_QUOTES)); + ASSERT_TRUE(string_is_safe("hello\"world", STRING_ALLOW_QUOTES)); + ASSERT_TRUE(string_is_safe("it's", STRING_ALLOW_QUOTES)); + ASSERT_FALSE(string_is_safe("a\\b", STRING_ALLOW_QUOTES)); /* backslashes still rejected */ + ASSERT_FALSE(string_is_safe("*", STRING_ALLOW_QUOTES)); /* globs still rejected */ + + /* STRING_ALLOW_GLOBS: globs allowed, backslashes/quotes still rejected. */ + ASSERT_TRUE(string_is_safe("hello", STRING_ALLOW_GLOBS)); + ASSERT_TRUE(string_is_safe("ab]c", STRING_ALLOW_GLOBS)); /* ']' is not in GLOB_CHARS anyway */ + ASSERT_TRUE(string_is_safe("*", STRING_ALLOW_GLOBS)); + ASSERT_TRUE(string_is_safe("?", STRING_ALLOW_GLOBS)); + ASSERT_TRUE(string_is_safe("[", STRING_ALLOW_GLOBS)); + ASSERT_TRUE(string_is_safe("foo*bar", STRING_ALLOW_GLOBS)); + ASSERT_TRUE(string_is_safe("foo?bar", STRING_ALLOW_GLOBS)); + ASSERT_TRUE(string_is_safe("foo[bar", STRING_ALLOW_GLOBS)); + ASSERT_FALSE(string_is_safe("\"", STRING_ALLOW_GLOBS)); /* quotes still rejected */ + ASSERT_FALSE(string_is_safe("a\\b", STRING_ALLOW_GLOBS)); /* backslashes still rejected */ + + /* STRING_FILENAME: rejects empty, ".", "..", and strings with '/'. */ + ASSERT_TRUE(string_is_safe("hello", STRING_FILENAME)); + ASSERT_TRUE(string_is_safe("hello.txt", STRING_FILENAME)); + ASSERT_TRUE(string_is_safe("...", STRING_FILENAME)); + ASSERT_TRUE(string_is_safe(".hidden", STRING_FILENAME)); + ASSERT_FALSE(string_is_safe("", STRING_FILENAME)); + ASSERT_FALSE(string_is_safe(".", STRING_FILENAME)); + ASSERT_FALSE(string_is_safe("..", STRING_FILENAME)); + ASSERT_FALSE(string_is_safe("/", STRING_FILENAME)); + ASSERT_FALSE(string_is_safe("/foo", STRING_FILENAME)); + ASSERT_FALSE(string_is_safe("foo/bar", STRING_FILENAME)); + + /* Pairwise combinations. */ + ASSERT_TRUE(string_is_safe("", STRING_ALLOW_EMPTY | STRING_ASCII)); + ASSERT_FALSE(string_is_safe("über", STRING_ALLOW_EMPTY | STRING_ASCII)); + ASSERT_TRUE(string_is_safe("hello", STRING_ALLOW_EMPTY | STRING_ASCII)); + + ASSERT_TRUE(string_is_safe("ab\"cd", STRING_ALLOW_QUOTES | STRING_ALLOW_GLOBS)); + ASSERT_TRUE(string_is_safe("ab*cd", STRING_ALLOW_QUOTES | STRING_ALLOW_GLOBS)); + ASSERT_TRUE(string_is_safe("ab'*cd", STRING_ALLOW_QUOTES | STRING_ALLOW_GLOBS)); + ASSERT_FALSE(string_is_safe("ab\\cd", STRING_ALLOW_QUOTES | STRING_ALLOW_GLOBS)); /* backslash still rejected */ + + ASSERT_TRUE(string_is_safe("hello.txt", STRING_FILENAME)); + ASSERT_FALSE(string_is_safe("", STRING_FILENAME)); + ASSERT_FALSE(string_is_safe("foo/bar", STRING_FILENAME)); + + ASSERT_TRUE(string_is_safe("foo?bar", STRING_ASCII | STRING_ALLOW_GLOBS)); + ASSERT_FALSE(string_is_safe("foo\"bar", STRING_ASCII | STRING_ALLOW_GLOBS)); /* quotes still rejected */ + ASSERT_FALSE(string_is_safe("über", STRING_ASCII | STRING_ALLOW_GLOBS)); + + ASSERT_TRUE(string_is_safe("foo\\bar", STRING_ALLOW_BACKSLASHES)); + ASSERT_FALSE(string_is_safe("foo\"bar", STRING_ALLOW_BACKSLASHES)); /* quotes still rejected */ + ASSERT_FALSE(string_is_safe("foo*bar", STRING_ALLOW_BACKSLASHES)); /* globs still rejected */ + ASSERT_TRUE(string_is_safe("foo\\\"bar", STRING_ALLOW_BACKSLASHES | STRING_ALLOW_QUOTES)); + ASSERT_TRUE(string_is_safe("foo\\bar", STRING_ALLOW_BACKSLASHES | STRING_ALLOW_QUOTES)); + ASSERT_TRUE(string_is_safe("foo\"bar", STRING_ALLOW_BACKSLASHES | STRING_ALLOW_QUOTES)); + + /* All allow flags combined: only baseline (control chars, invalid UTF-8) and STRING_FILENAME apply. */ + StringSafeFlags all = STRING_ALLOW_EMPTY | STRING_ASCII | STRING_ALLOW_BACKSLASHES | STRING_ALLOW_QUOTES | STRING_ALLOW_GLOBS | STRING_FILENAME; + ASSERT_TRUE(string_is_safe("hello.txt", all)); + ASSERT_TRUE(string_is_safe("foo-bar_baz.conf", all)); + ASSERT_TRUE(string_is_safe("a", all)); + ASSERT_TRUE(string_is_safe("foo\\bar", all)); /* backslash allowed */ + ASSERT_TRUE(string_is_safe("foo\"bar", all)); /* quote allowed */ + ASSERT_TRUE(string_is_safe("foo'bar", all)); /* quote allowed */ + ASSERT_TRUE(string_is_safe("foo*bar", all)); /* glob allowed */ + ASSERT_TRUE(string_is_safe("foo?bar", all)); /* glob allowed */ + ASSERT_TRUE(string_is_safe("foo[bar", all)); /* glob allowed */ + ASSERT_FALSE(string_is_safe("", all)); /* fails STRING_FILENAME */ + ASSERT_FALSE(string_is_safe("über", all)); /* fails STRING_ASCII */ + ASSERT_FALSE(string_is_safe("foo/bar", all)); /* fails STRING_FILENAME */ + ASSERT_FALSE(string_is_safe(".", all)); /* fails STRING_FILENAME */ + ASSERT_FALSE(string_is_safe("..", all)); /* fails STRING_FILENAME */ + ASSERT_FALSE(string_is_safe("foo\x01""bar", all)); /* fails baseline control-char check */ + ASSERT_FALSE(string_is_safe(NULL, all)); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/veritysetup/veritysetup.c b/src/veritysetup/veritysetup.c index 4a244ae83dc42..2e44aab963357 100644 --- a/src/veritysetup/veritysetup.c +++ b/src/veritysetup/veritysetup.c @@ -221,9 +221,6 @@ static int parse_options(const char *options) { arg_hash_offset = off; } else if ((val = startswith(word, "salt="))) { - if (!string_is_safe(val)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "salt= is not valid."); - if (isempty(val)) { arg_salt = mfree(arg_salt); arg_salt_size = 32; diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 5c70809e47c95..5c0847b481472 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -851,7 +851,12 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION_LONG("ssh-key-type", "TYPE", "Choose what type of SSH key to pass"): - if (!string_is_safe(arg)) + if (isempty(arg)) { + arg_ssh_key_type = mfree(arg_ssh_key_type); + break; + } + + if (!string_is_safe(arg, STRING_ALLOW_GLOBS)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value for --ssh-key-type=: %s", arg); r = free_and_strdup_warn(&arg_ssh_key_type, arg); From 4d92c72b819bd6d54dfbbb12e4cd25de5053714c Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Tue, 21 Apr 2026 12:07:02 +0200 Subject: [PATCH 1146/1296] test: avoid using external commands in trap handlers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In #39675 the reported fail was as follows: 5580s [ 247.559994] TEST-13-NSPAWN.sh[1858]: Exported 93%. 5580s [ 247.659002] TEST-13-NSPAWN.sh[1858]: Exported 95%. 5580s [ 247.785893] TEST-13-NSPAWN.sh[1858]: Operation completed successfully. 5580s [ 247.923727] TEST-13-NSPAWN.sh[1858]: Exiting. 5580s [ 258.300406] TEST-13-NSPAWN.sh[1074]: + machinectl import-raw /var/tmp/container-export.raw container-raw-reimport 5580s [ 258.323328] TEST-13-NSPAWN.sh[1884]: The 'machinectl import-raw' command has been replaced by 'importctl -m import-raw'. Redirecting invocation. 5580s [ 258.659982] TEST-13-NSPAWN.sh[1884]: Failed to transfer image: Remote peer disconnected 5580s [ 258.734218] TEST-13-NSPAWN.sh[1074]: + at_exit Turns out that the real reason behind this fail is that the machine was under heavy load due to a busy-loop from the stub init. The cause of this is a bug in bash, where running commands that fork (i.e. not built-ins) can cause a permanent busy-loop due to a desync in trap handling if you send the signals to the bash process _just right_: [ 90.855318] TEST-13-NSPAWN.sh[1074]: + machinectl poweroff long-running long-running long-running [ 90.855318] TEST-13-NSPAWN.sh[1074]: + machinectl reboot long-running long-running long-running [ 90.928980] systemd-nspawn[1679]: ++ touch /poweroff [ 90.928980] systemd-nspawn[1679]: +++ touch /reboot [ 90.928980] systemd-nspawn[1679]: + : [ 90.928980] systemd-nspawn[1679]: + : [ 90.928980] systemd-nspawn[1679]: + wait [ 90.928980] systemd-nspawn[1679]: + : [ 90.928980] systemd-nspawn[1679]: + : [ 90.928980] systemd-nspawn[1679]: + wait [ 90.928980] systemd-nspawn[1679]: + : [ 90.928980] systemd-nspawn[1679]: + : [ 90.928980] systemd-nspawn[1679]: + wait ... $ journalctl --file TEST-13-NSPAWN-1.journal -o short-monotonic --no-hostname --grep "^\+ wait$" | wc -l 349734 So the stub-init was hammering the machine in a tight endless loop, which then caused systemd-importd to timeout when talking to D-Bus: [ 258.300096] TEST-13-NSPAWN.sh[1074]: + machinectl import-raw /var/tmp/container-export.raw container-raw-reimport ... [ 258.415319] systemd-importd[1859]: Unable to request name, failing connection: Method call timed out [ 258.483662] systemd-importd[1859]: Bus n/a: changing state RUNNING → CLOSING [ 258.605442] systemd-importd[1859]: Bus n/a: changing state CLOSING → CLOSED [ 258.659958] TEST-13-NSPAWN.sh[1884]: Failed to transfer image: Remote peer disconnected Given this is not our issue, let's work around it by using just built-ins from the trap handlers, which are not susceptible to this bug. Resolves: #39675 --- test/units/TEST-13-NSPAWN.machined.sh | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/test/units/TEST-13-NSPAWN.machined.sh b/test/units/TEST-13-NSPAWN.machined.sh index 34307f3c8b162..de51daa24c73d 100755 --- a/test/units/TEST-13-NSPAWN.machined.sh +++ b/test/units/TEST-13-NSPAWN.machined.sh @@ -44,21 +44,27 @@ set -x PID=0 -trap 'touch /terminate; kill 0' RTMIN+3 -trap 'touch /poweroff' RTMIN+4 -trap 'touch /reboot' INT -trap 'touch /trap' TRAP +# Use only builtins in trap handlers to avoid forking. External commands +# (like touch) cause bash to enter wait_for() for the child, and a nested +# signal arriving during that wait triggers a bash bug where +# run_interrupt_trap() clears catch_flag while other traps are still +# pending, creating an orphaned pending_traps[] entry that makes 'wait' +# busy-loop indefinitely. +trap ': >/terminate; kill 0' RTMIN+3 +trap ': >/poweroff' RTMIN+4 +trap ': >/reboot' INT +trap ': >/trap' TRAP trap 'exit 0' TERM trap 'kill $PID' EXIT # We need to wait for the sleep process asynchronously in order to allow # bash to process signals sleep infinity & +PID=$! # notify that the process is ready -touch /ready +: >/ready -PID=$! while :; do wait || : done @@ -332,11 +338,11 @@ trap 'kill $PID' EXIT # We need to wait for the sleep process asynchronously in order to allow # bash to process signals sleep infinity & +PID=$! # notify that the process is ready -touch /ready +: >/ready -PID=$! while :; do wait || : done From f128314b12e8933a6f030a6ffea3f1fa843f15a1 Mon Sep 17 00:00:00 2001 From: Weblate Translation Memory Date: Wed, 22 Apr 2026 10:58:48 +0000 Subject: [PATCH 1147/1296] po: Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (266 of 266 strings) Co-authored-by: Weblate Translation Memory Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/pt_BR/ Translation: systemd/main --- po/pt_BR.po | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/po/pt_BR.po b/po/pt_BR.po index 11efeb5f04c48..3bf194f8cfba1 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -9,12 +9,14 @@ # Gabriel Elyas , 2024. # Fábio Rodrigues Ribeiro , 2024. # "Geraldo S. Simião Kutz" , 2024. +# Weblate Translation Memory , 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" -"PO-Revision-Date: 2025-10-29 18:54+0000\n" -"Last-Translator: Rafael Fontenelle \n" +"PO-Revision-Date: 2026-04-22 10:58+0000\n" +"Last-Translator: Weblate Translation Memory \n" "Language-Team: Portuguese (Brazil) \n" "Language: pt_BR\n" @@ -22,7 +24,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 5.13.3\n" +"X-Generator: Weblate 5.17\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1102,7 +1104,7 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" +msgstr "A autenticação é necessária para recarregar as configurações de rede." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" From a96f9e69afdce8cffe4460502a63ea5d3e6ca0fc Mon Sep 17 00:00:00 2001 From: Rafael Fontenelle Date: Wed, 22 Apr 2026 10:58:48 +0000 Subject: [PATCH 1148/1296] po: Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (266 of 266 strings) Co-authored-by: Rafael Fontenelle Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/pt_BR/ Translation: systemd/main --- po/pt_BR.po | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/po/pt_BR.po b/po/pt_BR.po index 3bf194f8cfba1..4fba725bb92e5 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -3,7 +3,7 @@ # Brazilian Portuguese translation for systemd. # Enrico Nicoletto , 2014. # Filipe Brandenburger , 2018. -# Rafael Fontenelle , 2015-2020, 2025. +# Rafael Fontenelle , 2015-2020, 2025, 2026. # Gustavo Costa , 2021. # Tiago Rocha Cunha , 2024. # Gabriel Elyas , 2024. @@ -15,8 +15,7 @@ msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-03-06 03:46+0900\n" "PO-Revision-Date: 2026-04-22 10:58+0000\n" -"Last-Translator: Weblate Translation Memory \n" +"Last-Translator: Rafael Fontenelle \n" "Language-Team: Portuguese (Brazil) \n" "Language: pt_BR\n" @@ -1053,12 +1052,12 @@ msgid "DHCP server sends force renew message" msgstr "Servidor DHCP envia mensagem de renovação forçada" #: src/network/org.freedesktop.network1.policy:144 -#, fuzzy -#| msgid "Authentication is required to send force renew message." msgid "" "Authentication is required to send a force renew message from the DHCP " "server." -msgstr "É necessária autenticação para enviar mensagem de renovação forçada." +msgstr "" +"É necessária autenticação para enviar uma mensagem de renovação forçada a " +"partir do servidor DHCP." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" @@ -1100,11 +1099,11 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Gerenciar conexões de rede" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "A autenticação é necessária para recarregar as configurações de rede." +msgstr "A autenticação é necessária para gerenciar conexões de rede." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" From f57d2b5bdd65e099d8b5003cfc3c51f3106e01d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 09:45:11 +0200 Subject: [PATCH 1149/1296] hwdb: convert to the new option and verb parsers Verbs are reordered to show 'query' above 'update'. I think this makes more sense. Co-developed-by: Claude Opus 4.6 --- src/hwdb/hwdb.c | 110 ++++++++++++++++++++++++------------------------ 1 file changed, 54 insertions(+), 56 deletions(-) diff --git a/src/hwdb/hwdb.c b/src/hwdb/hwdb.c index 3b407bb070127..141387da4faf7 100644 --- a/src/hwdb/hwdb.c +++ b/src/hwdb/hwdb.c @@ -1,13 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "alloc-util.h" #include "build.h" +#include "format-table.h" #include "hwdb-util.h" #include "label-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pretty-print.h" #include "verbs.h" @@ -15,10 +15,14 @@ static const char *arg_hwdb_bin_dir = NULL; static const char *arg_root = NULL; static bool arg_strict = false; +VERB(verb_query, "query", "MODALIAS", 2, 2, 0, + "Query database and print result"); static int verb_query(int argc, char *argv[], uintptr_t _data, void *userdata) { return hwdb_query(argv[1], arg_root); } +VERB_NOARG(verb_update, "update", + "Update the hwdb database"); static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata) { if (hwdb_bypass()) return 0; @@ -28,99 +32,93 @@ static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata) static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-hwdb", "8", &link); if (r < 0) return log_oom(); + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + printf("%s [OPTIONS...] COMMAND ...\n\n" "%sUpdate or query the hardware database.%s\n" - "\nCommands:\n" - " update Update the hwdb database\n" - " query MODALIAS Query database and print result\n" - "\nOptions:\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -s --strict When updating, return non-zero exit value on any parsing error\n" - " --usr Generate in " UDEVLIBEXECDIR " instead of /etc/udev\n" - " -r --root=PATH Alternative root path in the filesystem\n" - "\nSee the %s for details.\n", + "\n%sCommands:%s\n", program_invocation_short_name, ansi_highlight(), ansi_normal(), - link); + ansi_underline(), + ansi_normal()); - return 0; -} + r = table_print_or_warn(verbs); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_USR, - }; + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "usr", no_argument, NULL, ARG_USR }, - { "strict", no_argument, NULL, 's' }, - { "root", required_argument, NULL, 'r' }, - {} - }; + r = table_print_or_warn(options); + if (r < 0) + return r; - int c; + printf("\nSee the %s for details.\n", link); + return 0; +} +VERB_COMMON_HELP_HIDDEN(help); + +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); + + OptionParser state = { argc, argv }; + const char *arg; - while ((c = getopt_long(argc, argv, "sr:h", options, NULL)) >= 0) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_USR: - arg_hwdb_bin_dir = UDEVLIBEXECDIR; - break; - - case 's': + OPTION('s', "strict", NULL, + "When updating, return non-zero exit value on any parsing error"): arg_strict = true; break; - case 'r': - arg_root = optarg; + OPTION('r', "root", "PATH", "Alternative root path in the filesystem"): + arg_root = arg; break; - case '?': - return -EINVAL; - - default: - assert_not_reached(); + OPTION_LONG("usr", NULL, + "Generate in " UDEVLIBEXECDIR " instead of /etc/udev"): + arg_hwdb_bin_dir = UDEVLIBEXECDIR; + break; } + *ret_args = option_parser_get_args(&state); return 1; } -static int hwdb_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "update", 1, 1, 0, verb_update }, - { "query", 2, 2, 0, verb_query }, - {}, - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { int r; log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -128,7 +126,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - return hwdb_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); From b16a974ebe525ede40faa782516461df148ee2e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 10:19:17 +0200 Subject: [PATCH 1150/1296] export: convert to the new option and verb parsers --help is the same except for whitespace. Co-developed-by: Claude Opus 4.6 --- src/import/export.c | 127 +++++++++++++++++++------------------------- 1 file changed, 56 insertions(+), 71 deletions(-) diff --git a/src/import/export.c b/src/import/export.c index 389d5428bf2fc..71f892feb001f 100644 --- a/src/import/export.c +++ b/src/import/export.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -13,9 +12,11 @@ #include "export-raw.h" #include "export-tar.h" #include "fd-util.h" +#include "format-table.h" #include "import-common.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "runtime-scope.h" #include "signal-util.h" #include "string-util.h" @@ -44,6 +45,8 @@ static void on_tar_finished(TarExport *export, int error, void *userdata) { sd_event_exit(event, ABS(error)); } +VERB(verb_export_tar, "tar", "NAME [FILE]", 2, 3, 0, + "Export a TAR image"); static int verb_export_tar(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(tar_export_unrefp) TarExport *export = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; @@ -125,6 +128,8 @@ static void on_raw_finished(RawExport *export, int error, void *userdata) { sd_event_exit(event, ABS(error)); } +VERB(verb_export_raw, "raw", "NAME [FILE]", 2, 3, 0, + "Export a RAW image"); static int verb_export_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(raw_export_unrefp) RawExport *export = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; @@ -189,126 +194,106 @@ static int verb_export_raw(int argc, char *argv[], uintptr_t _data, void *userda } static int help(void) { - printf("%1$s [OPTIONS...] {COMMAND} ...\n" - "\n%4$sExport disk images.%5$s\n" - "\n%2$sCommands:%3$s\n" - " tar NAME [FILE] Export a TAR image\n" - " raw NAME [FILE] Export a RAW image\n" - "\n%2$sOptions:%3$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --format=FORMAT Select format\n" - " --class=CLASS Select image class (machine, sysext, confext,\n" - " portable)\n" - " --system Operate in per-system mode\n" - " --user Operate in per-user mode\n", + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; + int r; + + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "%sExport disk images.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); - return 0; -} - -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} + r = table_print_or_warn(verbs); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); - enum { - ARG_VERSION = 0x100, - ARG_FORMAT, - ARG_CLASS, - ARG_SYSTEM, - ARG_USER, - }; + r = table_print_or_warn(options); + if (r < 0) + return r; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "format", required_argument, NULL, ARG_FORMAT }, - { "class", required_argument, NULL, ARG_CLASS }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - {} - }; + return 0; +} - int c; +VERB_COMMON_HELP_HIDDEN(help); +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_FORMAT: - arg_compress = compression_from_string_harder(optarg); + OPTION_LONG("format", "FORMAT", "Select format"): + arg_compress = compression_from_string_harder(arg); if (arg_compress < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown format: %s", optarg); + "Unknown format: %s", arg); break; - case ARG_CLASS: - arg_class = image_class_from_string(optarg); + OPTION_LONG("class", "CLASS", + "Select image class (machine, sysext, confext, portable)"): + arg_class = image_class_from_string(arg); if (arg_class < 0) - return log_error_errno(arg_class, "Failed to parse --class= argument: %s", optarg); - + return log_error_errno(arg_class, "Failed to parse --class= argument: %s", arg); break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Operate in per-system mode"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Operate in per-user mode"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_runtime_scope == RUNTIME_SCOPE_USER) arg_import_flags |= IMPORT_FOREIGN_UID; + *ret_args = option_parser_get_args(&state); return 1; } -static int export_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "tar", 2, 3, 0, verb_export_tar }, - { "raw", 2, 3, 0, verb_export_raw }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { int r; setlocale(LC_ALL, ""); log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; (void) ignore_signals(SIGPIPE); - return export_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); From 94ec4b98438e65dc8741045c213ab21f2abe2bf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 10:20:39 +0200 Subject: [PATCH 1151/1296] import-fs: convert to the new option and verb parsers --help is the same except for whitespace. Co-developed-by: Claude Opus 4.6 --- src/import/import-fs.c | 168 ++++++++++++++++------------------------- 1 file changed, 66 insertions(+), 102 deletions(-) diff --git a/src/import/import-fs.c b/src/import/import-fs.c index 4b2394d4147ba..3605320300d3a 100644 --- a/src/import/import-fs.c +++ b/src/import/import-fs.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -11,6 +10,7 @@ #include "copy.h" #include "discover-image.h" #include "fd-util.h" +#include "format-table.h" #include "format-util.h" #include "import-common.h" #include "import-util.h" @@ -18,6 +18,7 @@ #include "log.h" #include "main-func.h" #include "mkdir-label.h" +#include "options.h" #include "parse-argument.h" #include "path-util.h" #include "ratelimit.h" @@ -107,6 +108,8 @@ static int progress_bytes(uint64_t nbytes, uint64_t bps, void *userdata) { return 0; } +VERB(verb_import_fs, "run", "DIRECTORY [NAME]", 2, 3, 0, + "Import a directory"); static int verb_import_fs(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(rm_rf_subvolume_and_freep) char *temp_path = NULL; _cleanup_(progress_info_free) ProgressInfo progress = { .bps = UINT64_MAX }; @@ -266,145 +269,115 @@ static int verb_import_fs(int argc, char *argv[], uintptr_t _data, void *userdat } static int help(void) { - printf("%1$s [OPTIONS...] {COMMAND} ...\n" - "\n%4$sImport container images from a file system directories.%5$s\n" - "\n%2$sCommands:%3$s\n" - " run DIRECTORY [NAME] Import a directory\n" - "\n%2$sOptions:%3$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --force Force creation of image\n" - " --image-root=PATH Image root directory\n" - " --read-only Create a read-only image\n" - " --direct Import directly to specified directory\n" - " --btrfs-subvol=BOOL Controls whether to create a btrfs subvolume\n" - " instead of a directory\n" - " --btrfs-quota=BOOL Controls whether to set up quota for btrfs\n" - " subvolume\n" - " --sync=BOOL Controls whether to sync() before completing\n" - " --class=CLASS Select image class (machine, sysext, confext,\n" - " portable)\n" - " --system Operate in per-system mode\n" - " --user Operate in per-user mode\n", + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; + int r; + + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "%sImport container images from file system directories.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); - return 0; -} + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); + return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_FORCE, - ARG_IMAGE_ROOT, - ARG_READ_ONLY, - ARG_DIRECT, - ARG_BTRFS_SUBVOL, - ARG_BTRFS_QUOTA, - ARG_SYNC, - ARG_CLASS, - ARG_SYSTEM, - ARG_USER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "force", no_argument, NULL, ARG_FORCE }, - { "image-root", required_argument, NULL, ARG_IMAGE_ROOT }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - { "direct", no_argument, NULL, ARG_DIRECT }, - { "btrfs-subvol", required_argument, NULL, ARG_BTRFS_SUBVOL }, - { "btrfs-quota", required_argument, NULL, ARG_BTRFS_QUOTA }, - { "sync", required_argument, NULL, ARG_SYNC }, - { "class", required_argument, NULL, ARG_CLASS }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - {} - }; - - int c, r; +VERB_COMMON_HELP_HIDDEN(help); +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_FORCE: + OPTION_LONG("force", NULL, "Force creation of image"): arg_force = true; break; - case ARG_IMAGE_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image_root); + OPTION_LONG("image-root", "PATH", "Image root directory"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image_root); if (r < 0) return r; - break; - case ARG_READ_ONLY: + OPTION_LONG("read-only", NULL, "Create a read-only image"): arg_read_only = true; break; - case ARG_DIRECT: + OPTION_LONG("direct", NULL, "Import directly to specified directory"): arg_direct = true; break; - case ARG_BTRFS_SUBVOL: - r = parse_boolean_argument("--btrfs-subvol=", optarg, &arg_btrfs_subvol); + OPTION_LONG("btrfs-subvol", "BOOL", + "Controls whether to create a btrfs subvolume instead of a directory"): + r = parse_boolean_argument("--btrfs-subvol=", arg, &arg_btrfs_subvol); if (r < 0) return r; - break; - case ARG_BTRFS_QUOTA: - r = parse_boolean_argument("--btrfs-quota=", optarg, &arg_btrfs_quota); + OPTION_LONG("btrfs-quota", "BOOL", + "Controls whether to set up quota for btrfs subvolume"): + r = parse_boolean_argument("--btrfs-quota=", arg, &arg_btrfs_quota); if (r < 0) return r; - break; - case ARG_SYNC: - r = parse_boolean_argument("--sync=", optarg, &arg_sync); + OPTION_LONG("sync", "BOOL", "Controls whether to sync() before completing"): + r = parse_boolean_argument("--sync=", arg, &arg_sync); if (r < 0) return r; - break; - case ARG_CLASS: - arg_class = image_class_from_string(optarg); + OPTION_LONG("class", "CLASS", + "Select image class (machine, sysext, confext, portable)"): + arg_class = image_class_from_string(arg); if (arg_class < 0) - return log_error_errno(arg_class, "Failed to parse --class= argument: %s", optarg); - + return log_error_errno(arg_class, "Failed to parse --class= argument: %s", arg); break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Operate in per-system mode"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Operate in per-user mode"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (!arg_image_root) { @@ -413,31 +386,22 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(r, "Failed to pick image root: %m"); } + *ret_args = option_parser_get_args(&state); return 1; } -static int import_fs_main(int argc, char *argv[]) { - - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "run", 2, 3, 0, verb_import_fs }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { int r; setlocale(LC_ALL, ""); log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return import_fs_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); From 2a94920ab242652c8f0da41846b50cd1df04ad83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 10:22:01 +0200 Subject: [PATCH 1152/1296] growfs: convert to the new option parser --help is the same except for whitespace and common option strings. Co-developed-by: Claude Opus 4.6 --- src/growfs/growfs.c | 62 ++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/src/growfs/growfs.c b/src/growfs/growfs.c index 8481257ab7166..e7a0ca385bcef 100644 --- a/src/growfs/growfs.c +++ b/src/growfs/growfs.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include "alloc-util.h" @@ -13,10 +12,12 @@ #include "devnum-util.h" #include "dissect-image.h" #include "fd-util.h" +#include "format-table.h" #include "format-util.h" #include "log.h" #include "main-func.h" #include "mountpoint-util.h" +#include "options.h" #include "pretty-print.h" #include "resize-fs.h" #include "string-util.h" @@ -132,67 +133,60 @@ static int maybe_resize_underlying_device( static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-growfs@.service", "8", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...] /path/to/mountpoint\n\n" - "Grow filesystem or encrypted payload to device size.\n\n" - "Options:\n" - " -h --help Show this help and exit\n" - " --version Print version string and exit\n" - " -n --dry-run Just print what would be done\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - link); + "Grow filesystem or encrypted payload to device size.\n", + program_invocation_short_name); + + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - }; - - int c; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version" , no_argument, NULL, ARG_VERSION }, - { "dry-run", no_argument, NULL, 'n' }, - {} - }; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hn", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'n': + OPTION('n', "dry-run", NULL, "Just print what would be done"): arg_dry_run = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind + 1 != argc) + if (option_parser_get_n_args(&state) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s expects exactly one argument (the mount point).", program_invocation_short_name); - arg_target = argv[optind]; + arg_target = option_parser_get_args(&state)[0]; return 1; } From 1db7a4d3b365aa39ab500615a06ea8e1c54b7394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 10:23:25 +0200 Subject: [PATCH 1153/1296] hibernate-resume: convert to the new option parser --help is the same except for whitespace. Co-developed-by: Claude Opus 4.6 --- src/hibernate-resume/hibernate-resume.c | 80 ++++++++++++------------- 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/src/hibernate-resume/hibernate-resume.c b/src/hibernate-resume/hibernate-resume.c index 2c6eed3248af6..cc48bf22fb3dc 100644 --- a/src/hibernate-resume/hibernate-resume.c +++ b/src/hibernate-resume/hibernate-resume.c @@ -1,20 +1,22 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" #include "build.h" #include "devnum-util.h" +#include "format-table.h" #include "hibernate-resume-config.h" #include "hibernate-util.h" #include "initrd-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-util.h" #include "pretty-print.h" #include "stat-util.h" #include "static-destruct.h" +#include "strv.h" static HibernateInfo arg_info = {}; static bool arg_clear = false; @@ -23,70 +25,59 @@ STATIC_DESTRUCTOR_REGISTER(arg_info, hibernate_info_done); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-hibernate-resume", "8", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...] [DEVICE [OFFSET]]\n" - "\n%sInitiate resume from hibernation.%s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --clear Clear hibernation storage information from EFI and exit\n" - "\nSee the %s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] [DEVICE [OFFSET]]\n\n" + "%sInitiate resume from hibernation.%s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_CLEAR, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "clear", no_argument, NULL, ARG_CLEAR }, - {} - }; - - int c; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_CLEAR: + OPTION_LONG("clear", NULL, + "Clear hibernation storage information from EFI and exit"): arg_clear = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (argc > optind && arg_clear) + if (option_parser_get_n_args(&state) > 0 && arg_clear) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extraneous arguments specified with --clear, refusing."); + *ret_args = option_parser_get_args(&state); return 1; } @@ -130,11 +121,14 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - if (argc - optind > 2) + size_t n_args = strv_length(args); + + if (n_args > 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program expects zero, one, or two arguments."); umask(0022); @@ -146,7 +140,7 @@ static int run(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Not running in initrd, refusing to initiate resume from hibernation."); - if (argc <= optind) { + if (n_args == 0) { r = setup_hibernate_info_and_warn(); if (r <= 0) return r; @@ -154,12 +148,12 @@ static int run(int argc, char *argv[]) { if (arg_info.efi) (void) clear_efi_hibernate_location_and_warn(); } else { - arg_info.device = ASSERT_PTR(argv[optind]); + arg_info.device = ASSERT_PTR(args[0]); - if (argc - optind == 2) { - r = safe_atou64(argv[optind + 1], &arg_info.offset); + if (n_args == 2) { + r = safe_atou64(args[1], &arg_info.offset); if (r < 0) - return log_error_errno(r, "Failed to parse resume offset %s: %m", argv[optind + 1]); + return log_error_errno(r, "Failed to parse resume offset %s: %m", args[1]); } } From 53ea569d8af18e6c3de44573e81c6543d587c2fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 10:27:43 +0200 Subject: [PATCH 1154/1296] test-ndisc-send: convert to the new option parser Co-developed-by: Claude Opus 4.6 --- src/libsystemd-network/test-ndisc-send.c | 161 ++++++++--------------- 1 file changed, 58 insertions(+), 103 deletions(-) diff --git a/src/libsystemd-network/test-ndisc-send.c b/src/libsystemd-network/test-ndisc-send.c index 247cc0ec16cfb..e0bbcfe6c243a 100644 --- a/src/libsystemd-network/test-ndisc-send.c +++ b/src/libsystemd-network/test-ndisc-send.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-ndisc-protocol.h" @@ -17,6 +16,7 @@ #include "ndisc-option.h" #include "netlink-util.h" #include "network-common.h" +#include "options.h" #include "parse-util.h" #include "set.h" #include "string-util.h" @@ -72,197 +72,158 @@ static int parse_preference(const char *str) { } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_RA_HOP_LIMIT, - ARG_RA_MANAGED, - ARG_RA_OTHER, - ARG_RA_HOME_AGENT, - ARG_RA_PREFERENCE, - ARG_RA_LIFETIME, - ARG_RA_REACHABLE, - ARG_RA_RETRANSMIT, - ARG_NA_ROUTER, - ARG_NA_SOLICITED, - ARG_NA_OVERRIDE, - ARG_TARGET_ADDRESS, - ARG_REDIRECT_DESTINATION, - ARG_OPTION_SOURCE_LL, - ARG_OPTION_TARGET_LL, - ARG_OPTION_REDIRECTED_HEADER, - ARG_OPTION_MTU, - }; - - static const struct option options[] = { - { "version", no_argument, NULL, ARG_VERSION }, - { "interface", required_argument, NULL, 'i' }, - { "type", required_argument, NULL, 't' }, - { "dest", required_argument, NULL, 'd' }, - /* For Router Advertisement */ - { "hop-limit", required_argument, NULL, ARG_RA_HOP_LIMIT }, - { "managed", required_argument, NULL, ARG_RA_MANAGED }, - { "other", required_argument, NULL, ARG_RA_OTHER }, - { "home-agent", required_argument, NULL, ARG_RA_HOME_AGENT }, - { "preference", required_argument, NULL, ARG_RA_PREFERENCE }, - { "lifetime", required_argument, NULL, ARG_RA_LIFETIME }, - { "reachable-time", required_argument, NULL, ARG_RA_REACHABLE }, - { "retransmit-timer", required_argument, NULL, ARG_RA_RETRANSMIT }, - /* For Neighbor Advertisement */ - { "is-router", required_argument, NULL, ARG_NA_ROUTER }, - { "is-solicited", required_argument, NULL, ARG_NA_SOLICITED }, - { "is-override", required_argument, NULL, ARG_NA_OVERRIDE }, - /* For Neighbor Solicit, Neighbor Advertisement, and Redirect */ - { "target-address", required_argument, NULL, ARG_TARGET_ADDRESS }, - /* For Redirect */ - { "redirect-destination", required_argument, NULL, ARG_REDIRECT_DESTINATION }, - /* Options */ - { "source-ll-address", required_argument, NULL, ARG_OPTION_SOURCE_LL }, - { "target-ll-address", required_argument, NULL, ARG_OPTION_TARGET_LL }, - { "redirected-header", required_argument, NULL, ARG_OPTION_REDIRECTED_HEADER }, - { "mtu", required_argument, NULL, ARG_OPTION_MTU }, - {} - }; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "i:t:d:", options, NULL)) >= 0) { + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'i': - r = rtnl_resolve_interface_or_warn(&rtnl, optarg); + OPTION('i', "interface", "INTERFACE", "Network interface"): + r = rtnl_resolve_interface_or_warn(&rtnl, arg); if (r < 0) return r; arg_ifindex = r; break; - case 't': - r = parse_icmp6_type(optarg); + OPTION('t', "type", "TYPE", "ICMPv6 message type"): + r = parse_icmp6_type(arg); if (r < 0) return log_error_errno(r, "Failed to parse message type: %m"); arg_icmp6_type = r; break; - case 'd': - r = in_addr_from_string(AF_INET6, optarg, &arg_dest); + OPTION('d', "dest", "ADDRESS", "Destination address"): + r = in_addr_from_string(AF_INET6, arg, &arg_dest); if (r < 0) return log_error_errno(r, "Failed to parse destination address: %m"); if (!in6_addr_is_link_local(&arg_dest.in6)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "The destination address %s is not a link-local address.", optarg); + "The destination address %s is not a link-local address.", arg); break; - case ARG_RA_HOP_LIMIT: - r = safe_atou8(optarg, &arg_hop_limit); + OPTION_GROUP("Router Advertisement"): {} + + OPTION_LONG("hop-limit", "LIMIT", "Hop limit"): + r = safe_atou8(arg, &arg_hop_limit); if (r < 0) return log_error_errno(r, "Failed to parse hop limit: %m"); break; - case ARG_RA_MANAGED: - r = parse_boolean(optarg); + OPTION_LONG("managed", "BOOL", "Managed flag"): + r = parse_boolean(arg); if (r < 0) return log_error_errno(r, "Failed to parse managed flag: %m"); SET_FLAG(arg_ra_flags, ND_RA_FLAG_MANAGED, r); break; - case ARG_RA_OTHER: - r = parse_boolean(optarg); + OPTION_LONG("other", "BOOL", "Other flag"): + r = parse_boolean(arg); if (r < 0) return log_error_errno(r, "Failed to parse other flag: %m"); SET_FLAG(arg_ra_flags, ND_RA_FLAG_OTHER, r); break; - case ARG_RA_HOME_AGENT: - r = parse_boolean(optarg); + OPTION_LONG("home-agent", "BOOL", "Home-agent flag"): + r = parse_boolean(arg); if (r < 0) return log_error_errno(r, "Failed to parse home-agent flag: %m"); SET_FLAG(arg_ra_flags, ND_RA_FLAG_HOME_AGENT, r); break; - case ARG_RA_PREFERENCE: - r = parse_preference(optarg); + OPTION_LONG("preference", "PREF", "Preference"): + r = parse_preference(arg); if (r < 0) return log_error_errno(r, "Failed to parse preference: %m"); arg_preference = r; break; - case ARG_RA_LIFETIME: - r = parse_sec(optarg, &arg_lifetime); + OPTION_LONG("lifetime", "SECS", "Lifetime"): + r = parse_sec(arg, &arg_lifetime); if (r < 0) return log_error_errno(r, "Failed to parse lifetime: %m"); break; - case ARG_RA_REACHABLE: - r = parse_sec(optarg, &arg_reachable); + OPTION_LONG("reachable-time", "SECS", "Reachable time"): + r = parse_sec(arg, &arg_reachable); if (r < 0) return log_error_errno(r, "Failed to parse reachable time: %m"); break; - case ARG_RA_RETRANSMIT: - r = parse_sec(optarg, &arg_retransmit); + OPTION_LONG("retransmit-timer", "SECS", "Retransmit timer"): + r = parse_sec(arg, &arg_retransmit); if (r < 0) return log_error_errno(r, "Failed to parse retransmit timer: %m"); break; - case ARG_NA_ROUTER: - r = parse_boolean(optarg); + OPTION_GROUP("Neighbor Advertisement"): {} + + OPTION_LONG("is-router", "BOOL", "Router flag"): + r = parse_boolean(arg); if (r < 0) return log_error_errno(r, "Failed to parse is-router flag: %m"); SET_FLAG(arg_na_flags, ND_NA_FLAG_ROUTER, r); break; - case ARG_NA_SOLICITED: - r = parse_boolean(optarg); + OPTION_LONG("is-solicited", "BOOL", "Solicited flag"): + r = parse_boolean(arg); if (r < 0) return log_error_errno(r, "Failed to parse is-solicited flag: %m"); SET_FLAG(arg_na_flags, ND_NA_FLAG_SOLICITED, r); break; - case ARG_NA_OVERRIDE: - r = parse_boolean(optarg); + OPTION_LONG("is-override", "BOOL", "Override flag"): + r = parse_boolean(arg); if (r < 0) return log_error_errno(r, "Failed to parse is-override flag: %m"); SET_FLAG(arg_na_flags, ND_NA_FLAG_OVERRIDE, r); break; - case ARG_TARGET_ADDRESS: - r = in_addr_from_string(AF_INET6, optarg, &arg_target_address); + OPTION_GROUP("Neighbor Solicit/Advertisement and Redirect"): {} + + OPTION_LONG("target-address", "ADDRESS", "Target address"): + r = in_addr_from_string(AF_INET6, arg, &arg_target_address); if (r < 0) return log_error_errno(r, "Failed to parse target address: %m"); break; - case ARG_REDIRECT_DESTINATION: - r = in_addr_from_string(AF_INET6, optarg, &arg_redirect_destination); + OPTION_GROUP("Redirect"): {} + + OPTION_LONG("redirect-destination", "ADDRESS", "Redirect destination address"): + r = in_addr_from_string(AF_INET6, arg, &arg_redirect_destination); if (r < 0) return log_error_errno(r, "Failed to parse destination address: %m"); break; - case ARG_OPTION_SOURCE_LL: - r = parse_boolean(optarg); + OPTION_GROUP("NDisc Options"): {} + + OPTION_LONG("source-ll-address", "BOOL", "Include source link-layer address"): + r = parse_boolean(arg); if (r < 0) return log_error_errno(r, "Failed to parse source LL address option: %m"); arg_set_source_mac = r; break; - case ARG_OPTION_TARGET_LL: - r = parse_ether_addr(optarg, &arg_target_mac); + OPTION_LONG("target-ll-address", "ADDRESS", "Target link-layer address"): + r = parse_ether_addr(arg, &arg_target_mac); if (r < 0) return log_error_errno(r, "Failed to parse target LL address option: %m"); arg_set_target_mac = true; break; - case ARG_OPTION_REDIRECTED_HEADER: { + OPTION_LONG("redirected-header", "BASE64", "Redirected header (base64)"): { _cleanup_free_ void *p = NULL; size_t len; - r = unbase64mem(optarg, &p, &len); + r = unbase64mem(arg, &p, &len); if (r < 0) return log_error_errno(r, "Failed to parse redirected header: %m"); @@ -272,20 +233,14 @@ static int parse_argv(int argc, char *argv[]) { arg_redirected_header = TAKE_PTR(p); break; } - case ARG_OPTION_MTU: - r = safe_atou32(optarg, &arg_mtu); + + OPTION_LONG("mtu", "MTU", "MTU"): + r = safe_atou32(arg, &arg_mtu); if (r < 0) return log_error_errno(r, "Failed to parse MTU: %m"); arg_set_mtu = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } if (arg_ifindex <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--interface/-i option is mandatory."); From 58e2c1aa6dc57fec9b994a5572fc3d86abcd95f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 10:30:46 +0200 Subject: [PATCH 1155/1296] test-journal-append: convert to the new option parser The help string is adjusted/reworded. In particular, [a, b) is used as notation to show a closed-open range, instead of the unusual --- .../sd-journal/test-journal-append.c | 104 ++++++++---------- 1 file changed, 44 insertions(+), 60 deletions(-) diff --git a/src/libsystemd/sd-journal/test-journal-append.c b/src/libsystemd/sd-journal/test-journal-append.c index 605839169c615..a07634a249c00 100644 --- a/src/libsystemd/sd-journal/test-journal-append.c +++ b/src/libsystemd/sd-journal/test-journal-append.c @@ -1,15 +1,16 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include "chattr-util.h" +#include "format-table.h" #include "iovec-util.h" #include "journal-file-util.h" #include "log.h" #include "mmap-cache.h" +#include "options.h" #include "parse-util.h" #include "random-util.h" #include "rm-rf.h" @@ -146,98 +147,81 @@ int main(int argc, char *argv[]) { uint64_t iteration_step = 1; uint64_t corrupt_step = 31; bool sequential = false, run_one = false; - int c, r; + int r; test_setup_logging(LOG_DEBUG); - enum { - ARG_START_OFFSET = 0x1000, - ARG_ITERATIONS, - ARG_ITERATION_STEP, - ARG_CORRUPT_STEP, - ARG_SEQUENTIAL, - ARG_RUN_ONE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "start-offset", required_argument, NULL, ARG_START_OFFSET }, - { "iterations", required_argument, NULL, ARG_ITERATIONS }, - { "iteration-step", required_argument, NULL, ARG_ITERATION_STEP }, - { "corrupt-step", required_argument, NULL, ARG_CORRUPT_STEP }, - { "sequential", no_argument, NULL, ARG_SEQUENTIAL }, - { "run-one", required_argument, NULL, ARG_RUN_ONE }, - {} - }; - - ASSERT_GE(argc, 0); - ASSERT_NOT_NULL(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: { + _cleanup_(table_unrefp) Table *options = NULL; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("Syntax:\n" " %s [OPTION...]\n" - "Options:\n" - " --start-offset=OFFSET Offset at which to start corrupting the journal\n" - " (default: random offset is picked, unless\n" - " --sequential is used - in that case we use 0 + iteration)\n" - " --iterations=ITER Number of iterations to perform before exiting\n" - " (default: 100)\n" - " --iteration-step=STEP Iteration step (default: 1)\n" - " --corrupt-step=STEP Corrupt every n-th byte starting from OFFSET (default: 31)\n" - " --sequential Go through offsets sequentially instead of picking\n" - " a random one on each iteration. If set, we go through\n" - " offsets <0; ITER), or Date: Thu, 16 Apr 2026 10:30:51 +0200 Subject: [PATCH 1156/1296] keyutil: convert to the new option and verb parsers --help is reorderded slightly and argument specifications are moved to improve table formatting. Co-developed-by: Claude Opus 4.6 --- src/keyutil/keyutil.c | 184 ++++++++++++++++++------------------------ 1 file changed, 78 insertions(+), 106 deletions(-) diff --git a/src/keyutil/keyutil.c b/src/keyutil/keyutil.c index 516527aa2b5ee..dcdd26422674f 100644 --- a/src/keyutil/keyutil.c +++ b/src/keyutil/keyutil.c @@ -1,16 +1,16 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "alloc-util.h" #include "ask-password-api.h" #include "build.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "fs-util.h" #include "log.h" #include "main-func.h" #include "openssl-util.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" #include "string-util.h" @@ -25,7 +25,7 @@ static char *arg_certificate_source = NULL; static CertificateSourceType arg_certificate_source_type = OPENSSL_CERTIFICATE_SOURCE_FILE; static char *arg_signature = NULL; static char *arg_content = NULL; -static char *arg_hash_algorithm = NULL; +static const char *arg_hash_algorithm = NULL; static char *arg_output = NULL; STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep); @@ -38,164 +38,137 @@ STATIC_DESTRUCTOR_REGISTER(arg_output, freep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-keyutil", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n%5$sPerform various operations on private keys and certificates.%6$s\n" - "\n%3$sCommands:%4$s\n" - " validate Load and validate the given certificate and private key\n" - " extract-public Extract a public key\n" - " extract-certificate Extract a certificate\n" - " pkcs7 Generate a PKCS#7 signature\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version\n" - " --private-key=KEY Private key in PEM format\n" - " --private-key-source=file|provider:PROVIDER|engine:ENGINE\n" - " Specify how to use KEY for --private-key=. Allows\n" - " an OpenSSL engine/provider to be used for signing\n" - " --certificate=PATH|URI\n" - " PEM certificate to use for signing, or a provider\n" - " specific designation if --certificate-source= is used\n" - " --certificate-source=file|provider:PROVIDER\n" - " Specify how to interpret the certificate from\n" - " --certificate=. Allows the certificate to be loaded\n" - " from an OpenSSL provider\n" - " --content=PATH Raw data content to embed in PKCS#7 signature\n" - " --signature=PATH PKCS#1 signature to embed in PKCS#7 signature\n" - " --hash-algorithm=ALGORITHM\n" - " Hash algorithm used to create the PKCS#1 signature\n" - " --output=PATH Where to write the PKCS#7 signature\n" - "\nSee the %2$s for details.\n", + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] COMMAND ...\n\n" + "%sPerform various operations on private keys and certificates.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); - return 0; -} + r = table_print_or_warn(verbs); + if (r < 0) + return r; -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); + return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_PRIVATE_KEY, - ARG_PRIVATE_KEY_SOURCE, - ARG_CERTIFICATE, - ARG_CERTIFICATE_SOURCE, - ARG_SIGNATURE, - ARG_CONTENT, - ARG_HASH_ALGORITHM, - ARG_OUTPUT, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, - { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, - { "certificate", required_argument, NULL, ARG_CERTIFICATE }, - { "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE }, - { "signature", required_argument, NULL, ARG_SIGNATURE }, - { "content", required_argument, NULL, ARG_CONTENT }, - { "hash-algorithm", required_argument, NULL, ARG_HASH_ALGORITHM }, - { "output", required_argument, NULL, ARG_OUTPUT }, - {} - }; - - int c, r; +VERB_COMMON_HELP_HIDDEN(help); +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); + + OptionParser state = { argc, argv }; + const char *arg; + int r; - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_PRIVATE_KEY: - r = free_and_strdup_warn(&arg_private_key, optarg); + OPTION_LONG("private-key", "KEY", "Private key in PEM format"): + r = free_and_strdup_warn(&arg_private_key, arg); if (r < 0) return r; - break; - case ARG_PRIVATE_KEY_SOURCE: + OPTION_LONG("private-key-source", "SOURCE", + "Specify how to use KEY for --private-key= " + "(file, provider:PROVIDER, engine:ENGINE)"): r = parse_openssl_key_source_argument( - optarg, + arg, &arg_private_key_source, &arg_private_key_source_type); if (r < 0) return r; - break; - case ARG_CERTIFICATE: - r = free_and_strdup_warn(&arg_certificate, optarg); + OPTION_LONG("certificate", "PATH|URI", + "PEM certificate to use for signing, " + "or a provider-specific designation if --certificate-source= is used"): + r = free_and_strdup_warn(&arg_certificate, arg); if (r < 0) return r; break; - case ARG_CERTIFICATE_SOURCE: + OPTION_LONG("certificate-source", "SOURCE", + "Specify how to interpret the certificate from --certificate= " + "(file, provider:PROVIDER)"): r = parse_openssl_certificate_source_argument( - optarg, + arg, &arg_certificate_source, &arg_certificate_source_type); if (r < 0) return r; break; - case ARG_SIGNATURE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_signature); + OPTION_LONG("signature", "PATH", "PKCS#1 signature to embed in PKCS#7 signature"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_signature); if (r < 0) return r; - break; - case ARG_CONTENT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_content); + OPTION_LONG("content", "PATH", "Raw data content to embed in PKCS#7 signature"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_content); if (r < 0) return r; - break; - case ARG_HASH_ALGORITHM: - arg_hash_algorithm = optarg; + OPTION_LONG("hash-algorithm", "ALGORITHM", + "Hash algorithm used to create the PKCS#1 signature"): + arg_hash_algorithm = arg; break; - case ARG_OUTPUT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_output); + OPTION_LONG("output", "PATH", "Where to write the PKCS#7 signature"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_output); if (r < 0) return r; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_private_key_source && !arg_certificate) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "When using --private-key-source=, --certificate= must be specified."); + *ret_args = option_parser_get_args(&state); return 1; } +VERB_NOARG(verb_validate, "validate", + "Load and validate the given certificate and private key"); static int verb_validate(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(X509_freep) X509 *certificate = NULL; _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL; @@ -251,6 +224,9 @@ static int verb_validate(int argc, char *argv[], uintptr_t _data, void *userdata return 0; } +VERB_NOARG(verb_extract_public, "extract-public", + "Extract a public key"); +VERB(verb_extract_public, "public", NULL, VERB_ANY, 1, 0, NULL); /* Deprecated alias */ static int verb_extract_public(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(EVP_PKEY_freep) EVP_PKEY *public_key = NULL; int r; @@ -318,6 +294,8 @@ static int verb_extract_public(int argc, char *argv[], uintptr_t _data, void *us return 0; } +VERB_NOARG(verb_extract_certificate, "extract-certificate", + "Extract a certificate"); static int verb_extract_certificate(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(X509_freep) X509 *certificate = NULL; int r; @@ -345,6 +323,8 @@ static int verb_extract_certificate(int argc, char *argv[], uintptr_t _data, voi return 0; } +VERB(verb_pkcs7, "pkcs7", NULL, VERB_ANY, VERB_ANY, 0, + "Generate a PKCS#7 signature"); static int verb_pkcs7(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(X509_freep) X509 *certificate = NULL; _cleanup_free_ char *pkcs1 = NULL; @@ -429,24 +409,16 @@ static int verb_pkcs7(int argc, char *argv[], uintptr_t _data, void *userdata) { } static int run(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "validate", VERB_ANY, 1, 0, verb_validate }, - { "extract-public", VERB_ANY, 1, 0, verb_extract_public }, - { "public", VERB_ANY, 1, 0, verb_extract_public }, /* Deprecated but kept for backwards compat. */ - { "extract-certificate", VERB_ANY, 1, 0, verb_extract_certificate }, - { "pkcs7", VERB_ANY, VERB_ANY, 0, verb_pkcs7 }, - {} - }; int r; log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); From ecf09a39eda7312152de0edd8f3fd32e92924b01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 11:42:50 +0200 Subject: [PATCH 1157/1296] machine-id-setup: convert to the new option parser --help is the same except for whitespace changes and that "option commands" are ordered in the usual style with "--help" first. Co-developed-by: Claude Opus 4.6 --- src/machine-id-setup/machine-id-setup-main.c | 110 +++++++++---------- 1 file changed, 51 insertions(+), 59 deletions(-) diff --git a/src/machine-id-setup/machine-id-setup-main.c b/src/machine-id-setup/machine-id-setup-main.c index 70774dcdd1b85..4fb70821123ae 100644 --- a/src/machine-id-setup/machine-id-setup-main.c +++ b/src/machine-id-setup/machine-id-setup-main.c @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" #include "build.h" #include "dissect-image.h" +#include "format-table.h" #include "id128-util.h" #include "image-policy.h" #include "log.h" @@ -13,6 +13,7 @@ #include "machine-id-setup.h" #include "main-func.h" #include "mount-util.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" @@ -28,104 +29,95 @@ STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *commands = NULL, *options = NULL; int r; r = terminal_urlify_man("systemd-machine-id-setup", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...]\n" - "\n%2$sInitialize /etc/machine-id from a random source.%4$s\n" - "\n%3$sCommands:%4$s\n" - " --commit Commit transient ID\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\n%3$sOptions:%4$s\n" - " --root=PATH Operate on an alternate filesystem root\n" - " --image=PATH Operate on disk image as filesystem root\n" - " --image-policy=POLICY Specify disk image dissection policy\n" - " --print Print used machine ID\n" - "\nSee the %5$s for details.\n", + r = option_parser_get_help_table(&commands); + if (r < 0) + return r; + + r = option_parser_get_help_table_group("Options", &options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, commands, options); + + printf("%s [OPTIONS...]\n\n" + "%sInitialize /etc/machine-id from a random source.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, ansi_highlight(), - ansi_underline(), ansi_normal(), - link); + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(commands); + if (r < 0) + return r; + printf("\n%sOptions:%s\n", + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_COMMIT, - ARG_PRINT, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "commit", no_argument, NULL, ARG_COMMIT }, - { "print", no_argument, NULL, ARG_PRINT }, - {} - }; - - int c, r; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_ROOT: - r = parse_path_argument(optarg, true, &arg_root); - if (r < 0) - return r; + OPTION_LONG("commit", NULL, "Commit transient ID"): + arg_commit = true; break; - case ARG_IMAGE: - r = parse_path_argument(optarg, false, &arg_image); + OPTION_GROUP("Options"): {} + + OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): + r = parse_path_argument(arg, true, &arg_root); if (r < 0) return r; break; - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); + OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"): + r = parse_path_argument(arg, false, &arg_image); if (r < 0) return r; break; - case ARG_COMMIT: - arg_commit = true; + OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): + r = parse_image_policy_argument(arg, &arg_image_policy); + if (r < 0) + return r; break; - case ARG_PRINT: + OPTION_LONG("print", NULL, "Print used machine ID"): arg_print = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind < argc) + if (option_parser_get_n_args(&state) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extraneous arguments"); From f0c3bb330d0074f57758b681bf2a5916b94a5311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 11:51:14 +0200 Subject: [PATCH 1158/1296] modules-load: convert to the new option parser --help is the same except for common option strings and alignment. Co-developed-by: Claude Opus 4.6 --- src/modules-load/modules-load.c | 61 +++++++++++++++------------------ 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/src/modules-load/modules-load.c b/src/modules-load/modules-load.c index e8aeb67f1fbcc..5435de4e2e988 100644 --- a/src/modules-load/modules-load.c +++ b/src/modules-load/modules-load.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -15,10 +14,12 @@ #include "errno-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "log.h" #include "macro.h" #include "main-func.h" #include "module-util.h" +#include "options.h" #include "ordered-set.h" #include "parse-util.h" #include "pretty-print.h" @@ -331,53 +332,46 @@ static unsigned determine_num_worker_threads(unsigned n_modules) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; + int r; if (terminal_urlify_man("systemd-modules-load.service", "8", &link) < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n" - "Loads statically configured kernel modules.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - link); + "Loads statically configured kernel modules.\n\n", + program_invocation_short_name); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {} - }; - - int c; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + + FOREACH_OPTION(&state, c, /* arg= */ NULL, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state); return 1; } @@ -395,7 +389,8 @@ static int run(int argc, char *argv[]) { char *module; int ret = 0, r; - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -407,9 +402,9 @@ static int run(int argc, char *argv[]) { if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); - if (argc > optind) { - for (int i = optind; i < argc; i++) { - r = apply_file_from_path(argv[i], &module_set); + if (!strv_isempty(args)) { + STRV_FOREACH(i, args) { + r = apply_file_from_path(*i, &module_set); if (r < 0) RET_GATHER(ret, r); } From 4bd794d97417aa9d64e392ad0e3bc335a7aaa108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 11:57:17 +0200 Subject: [PATCH 1159/1296] mute-console: convert to the new option parser --help is identical except for whitespace. Co-developed-by: Claude Opus 4.6 --- src/mute-console/mute-console.c | 68 ++++++++++++--------------------- 1 file changed, 25 insertions(+), 43 deletions(-) diff --git a/src/mute-console/mute-console.c b/src/mute-console/mute-console.c index b18e79d622c2e..b40f86fafb877 100644 --- a/src/mute-console/mute-console.c +++ b/src/mute-console/mute-console.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-bus.h" @@ -15,8 +14,10 @@ #include "bus-util.h" #include "daemon-util.h" #include "errno-util.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" #include "printk-util.h" @@ -30,79 +31,60 @@ static bool arg_varlink = false; static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-mute-console", "1", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...]\n" - "\n%sMute status output to the console.%s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --kernel=BOOL Mute kernel log output\n" - " --pid1=BOOL Mute PID 1 status output\n" - "\nSee the %s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n\n" + "%sMute status output to the console.%s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_KERNEL, - ARG_PID1, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "kernel", required_argument, NULL, ARG_KERNEL }, - { "pid1", required_argument, NULL, ARG_PID1 }, - {} - }; - - int c, r; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_PID1: - r = parse_boolean_argument("--pid1=", optarg, &arg_mute_pid1); + OPTION_LONG("kernel", "BOOL", "Mute kernel log output"): + r = parse_boolean_argument("--kernel=", arg, &arg_mute_kernel); if (r < 0) return r; - break; - case ARG_KERNEL: - r = parse_boolean_argument("--kernel=", optarg, &arg_mute_kernel); + OPTION_LONG("pid1", "BOOL", "Mute PID 1 status output"): + r = parse_boolean_argument("--pid1=", arg, &arg_mute_pid1); if (r < 0) return r; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT); if (r < 0) From 91aa31c18cd0753bb29315a61042aa24402f11f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 22 Apr 2026 14:18:50 +0200 Subject: [PATCH 1160/1296] oomctl: fix coccinelle check --- src/oom/oomctl.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/oom/oomctl.c b/src/oom/oomctl.c index 703b9c3f0eed2..51dfe1f1a40a3 100644 --- a/src/oom/oomctl.c +++ b/src/oom/oomctl.c @@ -89,6 +89,7 @@ static int verb_dump_state(int argc, char *argv[], uintptr_t _data, void *userda static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); OptionParser state = { argc, argv }; const char *arg; From 6dccf54cd646fe0621b4f256e7d61ad2fec2cbe6 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 22 Apr 2026 13:55:37 +0100 Subject: [PATCH 1161/1296] mkosi: trim verity.sig json files to remove NUL padding before passing to jq MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit jq started rejecting input that has NUL bytes to fix some security issues, so we need to trim the verity.sig json files, which are spat out with the NUL bytes padding from the GPT partition content. ‣ Running postinstall script /home/runner/work/systemd/systemd/mkosi/mkosi.postinst.chroot… jq: parse error: Invalid numeric literal at EOF at line 1, column 16384 ‣ "/work/postinst final" returned non-zero exit code 5. https://github.com/jqlang/jq/commit/6374ae0bcdfe33a18eb0ae6db28493b1f34a0a5b --- mkosi/mkosi.postinst.chroot | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mkosi/mkosi.postinst.chroot b/mkosi/mkosi.postinst.chroot index eb6d9170252a3..de67ddfdc85b4 100755 --- a/mkosi/mkosi.postinst.chroot +++ b/mkosi/mkosi.postinst.chroot @@ -27,8 +27,9 @@ mountpoint -q /etc/resolv.conf && umount /etc/resolv.conf rm -f /etc/resolv.conf for f in "$BUILDROOT"/usr/share/*.verity.sig; do - jq --join-output '.rootHash' "$f" >"${f%.verity.sig}.roothash" - jq --join-output '.signature' "$f" | base64 --decode >"${f%.verity.sig}.roothash.p7s" + # jq started refusing input with NUL bytes padding + strings "$f" | jq --join-output '.rootHash' >"${f%.verity.sig}.roothash" + strings "$f" | jq --join-output '.signature' | base64 --decode >"${f%.verity.sig}.roothash.p7s" done # We want /var/log/journal to be created on first boot so it can be created with the right chattr settings by From b4053a6c4df51b7b36e8413f241dbda61c8d9df1 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 22 Apr 2026 13:56:00 +0100 Subject: [PATCH 1162/1296] mkosi: update debian commit reference to 94af257c72ac3e9bf20e324ff31c3bd5d8197f0e * 94af257c72 d/t/control: pull libmicrohttpd-dev for unit-tests suite * 08263f18a4 d/t/control: pull libfdisk-dev for test suites * e54175a0a4 Install new files for upstream build --- mkosi/mkosi.pkgenv/mkosi.conf.d/debian-ubuntu.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkosi/mkosi.pkgenv/mkosi.conf.d/debian-ubuntu.conf b/mkosi/mkosi.pkgenv/mkosi.conf.d/debian-ubuntu.conf index 97607f9b59862..f46a0a0372322 100644 --- a/mkosi/mkosi.pkgenv/mkosi.conf.d/debian-ubuntu.conf +++ b/mkosi/mkosi.pkgenv/mkosi.conf.d/debian-ubuntu.conf @@ -9,5 +9,5 @@ Environment= GIT_URL=https://salsa.debian.org/systemd-team/systemd.git GIT_SUBDIR=debian GIT_BRANCH=debian/master - GIT_COMMIT=23ef56be0050f78be704f288ed1ce30ace47cbfe + GIT_COMMIT=94af257c72ac3e9bf20e324ff31c3bd5d8197f0e PKG_SUBDIR=debian From 4a8bf2e33fe86628f052180a904ff5ae53a2b82f Mon Sep 17 00:00:00 2001 From: Heran Yang Date: Wed, 22 Apr 2026 15:27:17 +0800 Subject: [PATCH 1163/1296] Add IMDS configuration for TencentCloud and Alibaba ECS --- hwdb.d/40-imds.hwdb | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/hwdb.d/40-imds.hwdb b/hwdb.d/40-imds.hwdb index 397a32b42fd31..21f58aac15c5e 100644 --- a/hwdb.d/40-imds.hwdb +++ b/hwdb.d/40-imds.hwdb @@ -103,3 +103,29 @@ dmi:*:svnScaleway:* IMDS_ADDRESS_IPV4=169.254.42.42 IMDS_ADDRESS_IPV6=fd00:42::42 IMDS_KEY_USERDATA=/user_data + +# https://www.tencentcloud.com/document/product/213/4934?lang=en +dmi:*:svnTencentCloud:* + IMDS_VENDOR=tencent-cloud + IMDS_DATA_URL=http://metadata.tencentyun.com + IMDS_KEY_HOSTNAME=/meta-data/hostname + IMDS_KEY_REGION=/meta-data/placement/region + IMDS_KEY_ZONE=/meta-data/placement/zone + IMDS_KEY_IPV4_PUBLIC=/meta-data/public-ipv4 + IMDS_KEY_SSH_KEY=/meta-data/public-keys/0/openssh-key + IMDS_KEY_USERDATA=/user-data + +# https://help.aliyun.com/zh/ecs/user-guide/view-instance-metadata +dmi:*:svnAlibabaCloud:* + IMDS_VENDOR=alibaba-ecs + IMDS_TOKEN_URL=http://100.100.100.200/latest/api/token + IMDS_REFRESH_HEADER_NAME=X-aliyun-ecs-metadata-token-ttl-seconds + IMDS_DATA_URL=http://100.100.100.200/latest + IMDS_TOKEN_HEADER_NAME=X-aliyun-ecs-metadata-token + IMDS_ADDRESS_IPV4=100.100.100.200 + IMDS_KEY_HOSTNAME=/meta-data/hostname + IMDS_KEY_REGION=/meta-data/region-id + IMDS_KEY_ZONE=/meta-data/zone-id + IMDS_KEY_IPV4_PUBLIC=/meta-data/eipv4 + IMDS_KEY_SSH_KEY=/meta-data/public-keys/0/openssh-key + IMDS_KEY_USERDATA=/user-data From 72e894c5909ab6da226182596c80db8183abadbd Mon Sep 17 00:00:00 2001 From: Kajus Naujokaitis Date: Wed, 22 Apr 2026 14:45:32 +0300 Subject: [PATCH 1164/1296] localed-util: respect env var when writing vconsole.conf --- src/locale/localed-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locale/localed-util.c b/src/locale/localed-util.c index 3fe039a053061..4cddfc32d9103 100644 --- a/src/locale/localed-util.c +++ b/src/locale/localed-util.c @@ -319,7 +319,7 @@ int vconsole_write_data(Context *c) { return 0; } - r = write_vconsole_conf(AT_FDCWD, "/etc/vconsole.conf", l); + r = write_vconsole_conf(AT_FDCWD, etc_vconsole_conf(), l); if (r < 0) return r; From 85b5acde869baa51f5618fa503eafac3dccbf379 Mon Sep 17 00:00:00 2001 From: Chris Hofer Date: Mon, 20 Apr 2026 16:55:38 +0200 Subject: [PATCH 1165/1296] build: Compile fuzz-journald-util.c only if want_fuzz_tests fuzz-journald-util.c is compiled unconditionally even though fuzzing tests aren't enabled. Only build it if fuzzing tests are configured. This also ensure that the functions it uses from src/shared/tests.c are available. Fixes 32bd43d768a4bdd54481c5e37ce9ea3d1009a824 Closes #39984 Signed-off-by: Chris Hofer --- src/journal/meson.build | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/journal/meson.build b/src/journal/meson.build index 5f64304219447..1bec605b0ccf0 100644 --- a/src/journal/meson.build +++ b/src/journal/meson.build @@ -19,11 +19,16 @@ systemd_journald_extract_sources = files( 'journald-syslog.c', 'journald-varlink.c', 'journald-wall.c', - # Build fuzz-journald.c as part of systemd-journald so we only compile it once instead of once per - # fuzz test. - 'fuzz-journald-util.c', ) +if want_fuzz_tests + # Build fuzz-journald-util.c as part of systemd-journald so we only + # compile it once instead of once per fuzz test. + systemd_journald_extract_sources += files( + 'fuzz-journald-util.c', + ) +endif + journald_gperf_c = custom_target( input : 'journald-gperf.gperf', output : 'journald-gperf.c', From e6fc73350f9485064302e687b964f70b28b2e4f6 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 21 Apr 2026 20:31:09 +0000 Subject: [PATCH 1166/1296] bpf: move all programs into src/bpf/ and consolidate meson logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The six .bpf.c files and their shared .bpf.h headers now live directly under src/bpf/, rather than scattered across src/core/bpf//, src/network/bpf// and src/nsresourced/bpf//. All BPF compilation logic — the BPF_FRAMEWORK determination (clang/gcc/ bpftool/llvm-strip lookups), flag and command construction, vmlinux.h handling, the bpf_programs list and the loop that builds the unstripped object, the stripped object and the skeleton header for each program — moves into src/bpf/meson.build. The top-level meson.build only keeps option handling and the libbpf dependency. subdir('src/bpf') is pulled up before config.h generation so that BPF_FRAMEWORK, HAVE_VMLINUX_H and ENABLE_SYSCTL_BPF land in conf in time. Skeleton wrapper headers (-skel.h) are now emitted by a configure_file() template (src/bpf/bpf-skel-wrapper.h.in) at meson setup, replacing the previously checked-in shims of the same name. Consumer #include paths are flattened: "bpf//-skel.h" becomes "-skel.h", "bpf//-api.bpf.h" becomes "-api.bpf.h". src/bpf is added to includes so the shared BPF headers resolve. sysctl-write-event.h, now shared between userspace (networkd-sysctl.c) and BPF (sysctl-monitor.bpf.c) from a single location, gains guarded includes so pid_t and uint64_t resolve on both sides: vmlinux.h in the BPF case (selected via __bpf__), stdint.h + sys/types.h otherwise. --- meson.build | 337 +-------------- .../bpf/bind-iface => bpf}/bind-iface.bpf.c | 0 .../bpf-skel-wrapper.h.in} | 5 +- src/bpf/meson.build | 385 ++++++++++++++++++ .../bpf/restrict-fs => bpf}/restrict-fs.bpf.c | 0 .../restrict-ifaces.bpf.c | 0 .../socket-bind => bpf}/socket-bind-api.bpf.h | 0 .../bpf/socket-bind => bpf}/socket-bind.bpf.c | 0 .../sysctl-monitor.bpf.c | 0 .../sysctl-write-event.h | 7 + .../userns-restrict.bpf.c | 2 +- src/core/bpf-bind-iface.c | 2 +- src/core/bpf-restrict-fs.c | 2 +- src/core/bpf-restrict-ifaces.c | 2 +- src/core/bpf-socket-bind.c | 4 +- src/core/bpf/bind-iface/bind-iface-skel.h | 17 - src/core/bpf/bind-iface/meson.build | 23 -- src/core/bpf/restrict-fs/meson.build | 23 -- src/core/bpf/restrict-fs/restrict-fs-skel.h | 17 - src/core/bpf/restrict-ifaces/meson.build | 23 -- .../restrict-ifaces/restrict-ifaces-skel.h | 17 - src/core/bpf/socket-bind/meson.build | 23 -- src/core/bpf/socket-bind/socket-bind-skel.h | 17 - src/core/meson.build | 13 +- src/network/bpf/sysctl-monitor/meson.build | 24 -- .../bpf/sysctl-monitor/sysctl-monitor-skel.h | 17 - src/network/meson.build | 9 +- src/network/networkd-sysctl.c | 4 +- .../bpf/userns-restrict/meson.build | 24 -- src/nsresourced/meson.build | 11 +- src/nsresourced/nsresourced-manager.c | 2 +- src/nsresourced/userns-restrict.c | 2 +- src/version/meson.build | 1 + 33 files changed, 433 insertions(+), 580 deletions(-) rename src/{core/bpf/bind-iface => bpf}/bind-iface.bpf.c (100%) rename src/{nsresourced/bpf/userns-restrict/userns-restrict-skel.h => bpf/bpf-skel-wrapper.h.in} (78%) create mode 100644 src/bpf/meson.build rename src/{core/bpf/restrict-fs => bpf}/restrict-fs.bpf.c (100%) rename src/{core/bpf/restrict-ifaces => bpf}/restrict-ifaces.bpf.c (100%) rename src/{core/bpf/socket-bind => bpf}/socket-bind-api.bpf.h (100%) rename src/{core/bpf/socket-bind => bpf}/socket-bind.bpf.c (100%) rename src/{network/bpf/sysctl-monitor => bpf}/sysctl-monitor.bpf.c (100%) rename src/{network/bpf/sysctl-monitor => bpf}/sysctl-write-event.h (94%) rename src/{nsresourced/bpf/userns-restrict => bpf}/userns-restrict.bpf.c (99%) delete mode 100644 src/core/bpf/bind-iface/bind-iface-skel.h delete mode 100644 src/core/bpf/bind-iface/meson.build delete mode 100644 src/core/bpf/restrict-fs/meson.build delete mode 100644 src/core/bpf/restrict-fs/restrict-fs-skel.h delete mode 100644 src/core/bpf/restrict-ifaces/meson.build delete mode 100644 src/core/bpf/restrict-ifaces/restrict-ifaces-skel.h delete mode 100644 src/core/bpf/socket-bind/meson.build delete mode 100644 src/core/bpf/socket-bind/socket-bind-skel.h delete mode 100644 src/network/bpf/sysctl-monitor/meson.build delete mode 100644 src/network/bpf/sysctl-monitor/sysctl-monitor-skel.h delete mode 100644 src/nsresourced/bpf/userns-restrict/meson.build diff --git a/meson.build b/meson.build index 2b717e23966f6..287456ad6eb5a 100644 --- a/meson.build +++ b/meson.build @@ -1067,86 +1067,6 @@ libbpf = dependency('libbpf', libbpf_cflags = libbpf.partial_dependency(includes: true, compile_args: true) conf.set10('HAVE_LIBBPF', libbpf.found()) -if not libbpf.found() - conf.set10('BPF_FRAMEWORK', false) -else - clang_found = false - clang_supports_bpf = false - bpf_gcc_found = false - bpftool_strip = false - deps_found = false - - if bpf_compiler == 'clang' - # Support 'versioned' clang/llvm-strip binaries, as seen on Debian/Ubuntu - # (like clang-10/llvm-strip-10) - if meson.is_cross_build() or cc.get_id() != 'clang' or cc.cmd_array()[0].contains('afl-clang') or cc.cmd_array()[0].contains('hfuzz-clang') - r = find_program('clang', - required : bpf_framework, - version : '>= 10.0.0') - clang_found = r.found() - if clang_found - clang = r.full_path() - endif - else - clang_found = true - clang = cc.cmd_array() - endif - - if clang_found - # Check if 'clang -target bpf' is supported. - clang_supports_bpf = run_command(clang, '-target', 'bpf', '--print-supported-cpus', check : false).returncode() == 0 - endif - if bpf_framework.enabled() and not clang_supports_bpf - error('bpf-framework was enabled but clang does not support bpf') - endif - elif bpf_compiler == 'gcc' - bpf_gcc = find_program('bpf-gcc', - 'bpf-none-gcc', - 'bpf-unknown-none-gcc', - required : true, - version : '>= 13.1.0') - bpf_gcc_found = bpf_gcc.found() - endif - - if clang_supports_bpf or bpf_gcc_found - # Debian installs this in /usr/sbin/ which is not in $PATH. - # We check for 'bpftool' first, honouring $PATH, and in /usr/sbin/ for Debian. - # We use 'bpftool gen object' subcommand for bpftool strip, it was added by d80b2fcbe0a023619e0fc73112f2a02c2662f6ab (v5.13). - bpftool = find_program('bpftool', - '/usr/sbin/bpftool', - required : bpf_framework.enabled() and bpf_compiler == 'gcc', - version : bpf_compiler == 'gcc' ? '>= 7.0.0' : '>= 5.13.0') - - if bpftool.found() - bpftool_strip = true - deps_found = true - elif bpf_compiler == 'clang' - # We require the 'bpftool gen skeleton' subcommand, it was added by 985ead416df39d6fe8e89580cc1db6aa273e0175 (v5.6). - bpftool = find_program('bpftool', - '/usr/sbin/bpftool', - required : bpf_framework, - version : '>= 5.6.0') - endif - - # We use `llvm-strip` as a fallback if `bpftool gen object` strip support is not available. - if not bpftool_strip and bpftool.found() and clang_supports_bpf - if not meson.is_cross_build() - llvm_strip_bin = run_command(clang, '--print-prog-name', 'llvm-strip', - check : true).stdout().strip() - else - llvm_strip_bin = 'llvm-strip' - endif - llvm_strip = find_program(llvm_strip_bin, - required : bpf_framework, - version : '>= 10.0.0') - deps_found = llvm_strip.found() - endif - endif - - # Can build BPF program from source code in restricted C - conf.set10('BPF_FRAMEWORK', deps_found) -endif - libmount = dependency('mount', version : fuzzer_build ? '>= 0' : '>= 2.30', required : get_option('libmount')) @@ -1730,171 +1650,6 @@ endif ##################################################################### -if conf.get('BPF_FRAMEWORK') == 1 - bpf_clang_flags = [ - '-std=gnu17', - '-Wno-compare-distinct-pointer-types', - '-Wno-microsoft-anon-tag', - '-fms-extensions', - '-fno-stack-protector', - '-O2', - '-target', - 'bpf', - '-g', - '-c', - ] - - bpf_gcc_flags = [ - '-std=gnu17', - '-fms-extensions', - '-fno-stack-protector', - '-fno-ssa-phiopt', - '-O2', - '-mcpu=v3', - '-mco-re', - '-gbtf', - '-c', - ] - - # If c_args contains these flags copy them along with the values, in order to avoid breaking - # reproducible builds and other functionality - propagate_cflags = [ - '-ffile-prefix-map=', - '-fdebug-prefix-map=', - '-fmacro-prefix-map=', - '--sysroot=', - ] - - foreach opt : c_args - foreach flag : propagate_cflags - if opt.startswith(flag) - bpf_clang_flags += [opt] - bpf_gcc_flags += [opt] - break - endif - endforeach - endforeach - - # Generate defines that are appropriate to tell the compiler what architecture - # we're compiling for. By default we just map meson's cpu_family to ____. - # This dictionary contains the exceptions where this doesn't work. - # - # C.f. https://mesonbuild.com/Reference-tables.html#cpu-families - # and src/basic/missing_syscall_def.h. - - # Start with older ABI. When define is missing, we're likely targeting that. - ppc64_elf_version = '1' - - if host_machine.cpu_family() == 'ppc64' - # cc doesn't have to be bpf_compiler, but they should be targeting the same ABI - call_elf_value = cc.get_define('_CALL_ELF') - if call_elf_value != '' - ppc64_elf_version = call_elf_value - endif - endif - - cpu_arch_defines = { - 'ppc' : ['-D__powerpc__', '-D__TARGET_ARCH_powerpc'], - 'ppc64' : ['-D__powerpc64__', '-D__TARGET_ARCH_powerpc', '-D_CALL_ELF=' + ppc64_elf_version], - 'riscv32' : ['-D__riscv', '-D__riscv_xlen=32', '-D__TARGET_ARCH_riscv'], - 'riscv64' : ['-D__riscv', '-D__riscv_xlen=64', '-D__TARGET_ARCH_riscv'], - 'x86' : ['-D__i386__', '-D__TARGET_ARCH_x86'], - 's390x' : ['-D__s390__', '-D__s390x__', '-D__TARGET_ARCH_s390'], - - # For arm, assume hardware fp is available. - 'arm' : ['-D__arm__', '-D__ARM_PCS_VFP', '-D__TARGET_ARCH_arm'], - 'loongarch64' : ['-D__loongarch__', '-D__loongarch_grlen=64', '-D__TARGET_ARCH_loongarch'] - } - - bpf_arch_flags = cpu_arch_defines.get(host_machine.cpu_family(), - ['-D__@0@__'.format(host_machine.cpu_family())]) - if bpf_compiler == 'gcc' - bpf_arch_flags += ['-m' + host_machine.endian() + '-endian'] - endif - - libbpf_include_dir = libbpf.get_variable(pkgconfig : 'includedir') - - bpf_o_unstripped_cmd = [] - if bpf_compiler == 'clang' - bpf_o_unstripped_cmd += [ - clang, - bpf_clang_flags, - bpf_arch_flags, - ] - elif bpf_compiler == 'gcc' - bpf_o_unstripped_cmd += [ - bpf_gcc, - bpf_gcc_flags, - bpf_arch_flags, - ] - endif - - bpf_o_unstripped_cmd += ['-I.', '-include', 'config.h'] - - if cc.get_id() == 'gcc' or meson.is_cross_build() - if cc.get_id() != 'gcc' - warning('Cross compiler is not gcc. Guessing the target triplet for bpf likely fails.') - endif - target_triplet_cmd = run_command(cc.cmd_array(), '-print-multiarch', check: false) - else - # clang does not support -print-multiarch (D133170) and its -dump-machine - # does not match multiarch. Query gcc instead. - target_triplet_cmd = run_command('gcc', '-print-multiarch', check: false) - endif - if target_triplet_cmd.returncode() == 0 - sysroot = meson.get_external_property('sys_root', '/') - target_triplet = target_triplet_cmd.stdout().strip() - target_include_dir = sysroot / 'usr' / 'include' - target_triple_include_dir = target_include_dir / target_triplet - isystem_dir = '' - if fs.is_dir(target_triple_include_dir) - isystem_dir = target_triple_include_dir - elif fs.is_dir(target_include_dir) - isystem_dir = target_include_dir - endif - if isystem_dir != '' - bpf_o_unstripped_cmd += [ - '-isystem', isystem_dir - ] - endif - endif - - bpf_o_unstripped_cmd += [ - '-idirafter', - libbpf_include_dir, - '@INPUT@', - '-o', - '@OUTPUT@' - ] - - if bpftool_strip - bpf_o_cmd = [ - bpftool, - 'gen', - 'object', - '@OUTPUT@', - '@INPUT@' - ] - elif bpf_compiler == 'clang' - bpf_o_cmd = [ - llvm_strip, - '-g', - '@INPUT@', - '-o', - '@OUTPUT@' - ] - endif - - skel_h_cmd = [ - bpftool, - 'gen', - 'skeleton', - '@INPUT@' - ] -endif - -##################################################################### - efi_arch = { 'aarch64' : 'aa64', 'arm' : 'arm', @@ -1932,77 +1687,6 @@ conf.set10('ENABLE_UKIFY', want_ukify) ##################################################################### -use_provided_vmlinux_h = false -use_generated_vmlinux_h = false -provided_vmlinux_h_path = get_option('vmlinux-h-path') - -# For the more complex BPF programs we really want a vmlinux.h (which is arch -# specific, but only somewhat bound to kernel version). Ideally the kernel -# development headers would ship that, but right now they don't. Hence address -# this in two ways: -# -# 1. Provide a vmlinux.h at build time -# 2. Generate the file on the fly where possible (which requires /sys/ to be mounted) -# -# We generally prefer the former (to support reproducible builds), but will -# fallback to the latter. - -if conf.get('BPF_FRAMEWORK') == 1 - enable_vmlinux_h = get_option('vmlinux-h') - - if enable_vmlinux_h == 'auto' - if provided_vmlinux_h_path != '' - use_provided_vmlinux_h = true - elif fs.exists('/sys/kernel/btf/vmlinux') and \ - bpftool.found() and \ - (host_machine.cpu_family() == build_machine.cpu_family()) and \ - host_machine.cpu_family() in ['x86_64', 'aarch64'] - - # We will only generate a vmlinux.h from the running - # kernel if the host and build machine are of the same - # family. Also for now we focus on x86_64 and aarch64, - # since other archs don't seem to be ready yet. - - use_generated_vmlinux_h = true - endif - elif enable_vmlinux_h == 'provided' - use_provided_vmlinux_h = true - elif enable_vmlinux_h == 'generated' - if not fs.exists('/sys/kernel/btf/vmlinux') - error('BTF data from kernel not available (/sys/kernel/btf/vmlinux missing), cannot generate vmlinux.h, but was asked to.') - endif - if not bpftool.found() - error('bpftool not available, cannot generate vmlinux.h, but was asked to.') - endif - use_generated_vmlinux_h = true - endif -endif - -vmlinux_h_dependency = [] -if use_provided_vmlinux_h - if not fs.exists(provided_vmlinux_h_path) - error('Path to provided vmlinux.h does not exist.') - endif - bpf_o_unstripped_cmd += ['-I' + fs.parent(provided_vmlinux_h_path)] - message(f'Using provided @provided_vmlinux_h_path@') -elif use_generated_vmlinux_h - vmlinux_h_dependency = custom_target( - output: 'vmlinux.h', - command : [ bpftool, 'btf', 'dump', 'file', '/sys/kernel/btf/vmlinux', 'format', 'c' ], - capture : true) - - bpf_o_unstripped_cmd += ['-I' + fs.parent(vmlinux_h_dependency.full_path())] - message('Using generated @0@'.format(vmlinux_h_dependency.full_path())) -else - message('Using neither provided nor generated vmlinux.h, some features will not be available.') -endif - -conf.set10('HAVE_VMLINUX_H', use_provided_vmlinux_h or use_generated_vmlinux_h) - -conf.set10('ENABLE_SYSCTL_BPF', conf.get('HAVE_VMLINUX_H') == 1 and libbpf.version().version_compare('>= 0.7')) - -##################################################################### - version_tag = get_option('version-tag') if version_tag == '' version_tag = meson.project_version() @@ -2010,8 +1694,11 @@ endif conf.set_quoted('VERSION_TAG', version_tag) +generated_sources = [] + subdir('tools') subdir('src/version') +subdir('src/bpf') shared_lib_tag = get_option('shared-lib-tag') if shared_lib_tag == '' @@ -2051,7 +1738,6 @@ executables = [] executables_by_name = {} objects_by_name = {} fuzzer_exes = [] -generated_sources = [version_h, vmlinux_h_dependency] sources = [] # binaries that have --help and are intended for use by humans, @@ -2118,7 +1804,13 @@ libsystemd_includes = [basic_includes, include_directories( 'src/libsystemd/sd-resolve', 'src/libsystemd/sd-varlink')] -includes = [libsystemd_includes, include_directories('src/shared')] +includes = [ + libsystemd_includes, + include_directories( + 'src/shared', + 'src/bpf', + ), +] subdir('po') subdir('catalog') @@ -2519,11 +2211,18 @@ foreach dict : executables exe_sources = dict.get('sources', []) + dict.get('extract', []) + foreach bpf_name : dict.get('bpf_programs', []) + if bpf_name in bpf_programs_by_name + exe_sources += bpf_programs_by_name[bpf_name] + endif + endforeach + kwargs = {} foreach key, val : dict if key in ['name', 'dbus', 'public', 'conditions', 'type', 'suite', 'timeout', 'parallel', 'objects', 'sources', 'extract', - 'include_directories', 'build_by_default', 'install'] + 'include_directories', 'build_by_default', 'install', + 'bpf_programs'] continue endif diff --git a/src/core/bpf/bind-iface/bind-iface.bpf.c b/src/bpf/bind-iface.bpf.c similarity index 100% rename from src/core/bpf/bind-iface/bind-iface.bpf.c rename to src/bpf/bind-iface.bpf.c diff --git a/src/nsresourced/bpf/userns-restrict/userns-restrict-skel.h b/src/bpf/bpf-skel-wrapper.h.in similarity index 78% rename from src/nsresourced/bpf/userns-restrict/userns-restrict-skel.h rename to src/bpf/bpf-skel-wrapper.h.in index 7ed12dea9577c..3dc3cede3e2e1 100644 --- a/src/nsresourced/bpf/userns-restrict/userns-restrict-skel.h +++ b/src/bpf/bpf-skel-wrapper.h.in @@ -7,12 +7,13 @@ * fine given that LGPL-2.1-or-later downgrades to GPL if needed. */ -#include "bpf-dlopen.h" /* IWYU pragma: keep */ +#include "bpf-dlopen.h" /* IWYU pragma: keep */ /* libbpf is used via dlopen(), so rename symbols */ #define bpf_object__attach_skeleton sym_bpf_object__attach_skeleton #define bpf_object__destroy_skeleton sym_bpf_object__destroy_skeleton +#define bpf_object__detach_skeleton sym_bpf_object__detach_skeleton #define bpf_object__load_skeleton sym_bpf_object__load_skeleton #define bpf_object__open_skeleton sym_bpf_object__open_skeleton -#include "bpf/userns-restrict/userns-restrict.skel.h" /* IWYU pragma: export */ +#include "@NAME@.bpf.skel.h" /* IWYU pragma: export */ diff --git a/src/bpf/meson.build b/src/bpf/meson.build new file mode 100644 index 0000000000000..54b3027a97cea --- /dev/null +++ b/src/bpf/meson.build @@ -0,0 +1,385 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +if not libbpf.found() + conf.set10('BPF_FRAMEWORK', false) +else + clang_found = false + clang_supports_bpf = false + bpf_gcc_found = false + bpftool_strip = false + deps_found = false + + if bpf_compiler == 'clang' + # Support 'versioned' clang/llvm-strip binaries, as seen on Debian/Ubuntu + # (like clang-10/llvm-strip-10) + if meson.is_cross_build() or cc.get_id() != 'clang' or cc.cmd_array()[0].contains('afl-clang') or cc.cmd_array()[0].contains('hfuzz-clang') + r = find_program('clang', + required : bpf_framework, + version : '>= 10.0.0') + clang_found = r.found() + if clang_found + clang = r.full_path() + endif + else + clang_found = true + clang = cc.cmd_array() + endif + + if clang_found + # Check if 'clang -target bpf' is supported. + clang_supports_bpf = run_command(clang, '-target', 'bpf', '--print-supported-cpus', check : false).returncode() == 0 + endif + if bpf_framework.enabled() and not clang_supports_bpf + error('bpf-framework was enabled but clang does not support bpf') + endif + elif bpf_compiler == 'gcc' + bpf_gcc = find_program('bpf-gcc', + 'bpf-none-gcc', + 'bpf-unknown-none-gcc', + required : true, + version : '>= 13.1.0') + bpf_gcc_found = bpf_gcc.found() + endif + + if clang_supports_bpf or bpf_gcc_found + # Debian installs this in /usr/sbin/ which is not in $PATH. + # We check for 'bpftool' first, honouring $PATH, and in /usr/sbin/ for Debian. + # We use 'bpftool gen object' subcommand for bpftool strip, it was added by d80b2fcbe0a023619e0fc73112f2a02c2662f6ab (v5.13). + bpftool = find_program('bpftool', + '/usr/sbin/bpftool', + required : bpf_framework.enabled() and bpf_compiler == 'gcc', + version : bpf_compiler == 'gcc' ? '>= 7.0.0' : '>= 5.13.0') + + if bpftool.found() + bpftool_strip = true + deps_found = true + elif bpf_compiler == 'clang' + # We require the 'bpftool gen skeleton' subcommand, it was added by 985ead416df39d6fe8e89580cc1db6aa273e0175 (v5.6). + bpftool = find_program('bpftool', + '/usr/sbin/bpftool', + required : bpf_framework, + version : '>= 5.6.0') + endif + + # We use `llvm-strip` as a fallback if `bpftool gen object` strip support is not available. + if not bpftool_strip and bpftool.found() and clang_supports_bpf + if not meson.is_cross_build() + llvm_strip_bin = run_command(clang, '--print-prog-name', 'llvm-strip', + check : true).stdout().strip() + else + llvm_strip_bin = 'llvm-strip' + endif + llvm_strip = find_program(llvm_strip_bin, + required : bpf_framework, + version : '>= 10.0.0') + deps_found = llvm_strip.found() + endif + endif + + # Can build BPF program from source code in restricted C + conf.set10('BPF_FRAMEWORK', deps_found) +endif + +if conf.get('BPF_FRAMEWORK') == 1 + bpf_clang_flags = [ + '-std=gnu17', + '-Wno-compare-distinct-pointer-types', + '-Wno-microsoft-anon-tag', + '-fms-extensions', + '-fno-stack-protector', + '-O2', + '-target', + 'bpf', + '-g', + '-c', + ] + + bpf_gcc_flags = [ + '-std=gnu17', + '-fms-extensions', + '-fno-stack-protector', + '-fno-ssa-phiopt', + '-O2', + '-mcpu=v3', + '-mco-re', + '-gbtf', + '-c', + ] + + # If c_args contains these flags copy them along with the values, in order to avoid breaking + # reproducible builds and other functionality + propagate_cflags = [ + '-ffile-prefix-map=', + '-fdebug-prefix-map=', + '-fmacro-prefix-map=', + '--sysroot=', + ] + + foreach opt : c_args + foreach flag : propagate_cflags + if opt.startswith(flag) + bpf_clang_flags += [opt] + bpf_gcc_flags += [opt] + break + endif + endforeach + endforeach + + # Generate defines that are appropriate to tell the compiler what architecture + # we're compiling for. By default we just map meson's cpu_family to ____. + # This dictionary contains the exceptions where this doesn't work. + # + # C.f. https://mesonbuild.com/Reference-tables.html#cpu-families + # and src/basic/missing_syscall_def.h. + + # Start with older ABI. When define is missing, we're likely targeting that. + ppc64_elf_version = '1' + + if host_machine.cpu_family() == 'ppc64' + # cc doesn't have to be bpf_compiler, but they should be targeting the same ABI + call_elf_value = cc.get_define('_CALL_ELF') + if call_elf_value != '' + ppc64_elf_version = call_elf_value + endif + endif + + cpu_arch_defines = { + 'ppc' : ['-D__powerpc__', '-D__TARGET_ARCH_powerpc'], + 'ppc64' : ['-D__powerpc64__', '-D__TARGET_ARCH_powerpc', '-D_CALL_ELF=' + ppc64_elf_version], + 'riscv32' : ['-D__riscv', '-D__riscv_xlen=32', '-D__TARGET_ARCH_riscv'], + 'riscv64' : ['-D__riscv', '-D__riscv_xlen=64', '-D__TARGET_ARCH_riscv'], + 'x86' : ['-D__i386__', '-D__TARGET_ARCH_x86'], + 's390x' : ['-D__s390__', '-D__s390x__', '-D__TARGET_ARCH_s390'], + + # For arm, assume hardware fp is available. + 'arm' : ['-D__arm__', '-D__ARM_PCS_VFP', '-D__TARGET_ARCH_arm'], + 'loongarch64' : ['-D__loongarch__', '-D__loongarch_grlen=64', '-D__TARGET_ARCH_loongarch'] + } + + bpf_arch_flags = cpu_arch_defines.get(host_machine.cpu_family(), + ['-D__@0@__'.format(host_machine.cpu_family())]) + if bpf_compiler == 'gcc' + bpf_arch_flags += ['-m' + host_machine.endian() + '-endian'] + endif + + libbpf_include_dir = libbpf.get_variable(pkgconfig : 'includedir') + + bpf_o_unstripped_cmd = [] + if bpf_compiler == 'clang' + bpf_o_unstripped_cmd += [ + clang, + bpf_clang_flags, + bpf_arch_flags, + ] + elif bpf_compiler == 'gcc' + bpf_o_unstripped_cmd += [ + bpf_gcc, + bpf_gcc_flags, + bpf_arch_flags, + ] + endif + + bpf_o_unstripped_cmd += ['-I.', '-include', 'config.h'] + + if cc.get_id() == 'gcc' or meson.is_cross_build() + if cc.get_id() != 'gcc' + warning('Cross compiler is not gcc. Guessing the target triplet for bpf likely fails.') + endif + target_triplet_cmd = run_command(cc.cmd_array(), '-print-multiarch', check: false) + else + # clang does not support -print-multiarch (D133170) and its -dump-machine + # does not match multiarch. Query gcc instead. + target_triplet_cmd = run_command('gcc', '-print-multiarch', check: false) + endif + if target_triplet_cmd.returncode() == 0 + sysroot = meson.get_external_property('sys_root', '/') + target_triplet = target_triplet_cmd.stdout().strip() + target_include_dir = sysroot / 'usr' / 'include' + target_triple_include_dir = target_include_dir / target_triplet + isystem_dir = '' + if fs.is_dir(target_triple_include_dir) + isystem_dir = target_triple_include_dir + elif fs.is_dir(target_include_dir) + isystem_dir = target_include_dir + endif + if isystem_dir != '' + bpf_o_unstripped_cmd += [ + '-isystem', isystem_dir + ] + endif + endif + + bpf_o_unstripped_cmd += [ + '-idirafter', + libbpf_include_dir, + '@INPUT@', + '-o', + '@OUTPUT@' + ] + + if bpftool_strip + bpf_o_cmd = [ + bpftool, + 'gen', + 'object', + '@OUTPUT@', + '@INPUT@' + ] + elif bpf_compiler == 'clang' + bpf_o_cmd = [ + llvm_strip, + '-g', + '@INPUT@', + '-o', + '@OUTPUT@' + ] + endif + + skel_h_cmd = [ + bpftool, + 'gen', + 'skeleton', + '@INPUT@' + ] +endif + +use_provided_vmlinux_h = false +use_generated_vmlinux_h = false +provided_vmlinux_h_path = get_option('vmlinux-h-path') + +# For the more complex BPF programs we really want a vmlinux.h (which is arch +# specific, but only somewhat bound to kernel version). Ideally the kernel +# development headers would ship that, but right now they don't. Hence address +# this in two ways: +# +# 1. Provide a vmlinux.h at build time +# 2. Generate the file on the fly where possible (which requires /sys/ to be mounted) +# +# We generally prefer the former (to support reproducible builds), but will +# fallback to the latter. + +if conf.get('BPF_FRAMEWORK') == 1 + enable_vmlinux_h = get_option('vmlinux-h') + + if enable_vmlinux_h == 'auto' + if provided_vmlinux_h_path != '' + use_provided_vmlinux_h = true + elif fs.exists('/sys/kernel/btf/vmlinux') and \ + bpftool.found() and \ + (host_machine.cpu_family() == build_machine.cpu_family()) and \ + host_machine.cpu_family() in ['x86_64', 'aarch64'] + + # We will only generate a vmlinux.h from the running + # kernel if the host and build machine are of the same + # family. Also for now we focus on x86_64 and aarch64, + # since other archs don't seem to be ready yet. + + use_generated_vmlinux_h = true + endif + elif enable_vmlinux_h == 'provided' + use_provided_vmlinux_h = true + elif enable_vmlinux_h == 'generated' + if not fs.exists('/sys/kernel/btf/vmlinux') + error('BTF data from kernel not available (/sys/kernel/btf/vmlinux missing), cannot generate vmlinux.h, but was asked to.') + endif + if not bpftool.found() + error('bpftool not available, cannot generate vmlinux.h, but was asked to.') + endif + use_generated_vmlinux_h = true + endif +endif + +vmlinux_h_dependency = [] +if use_provided_vmlinux_h + if not fs.exists(provided_vmlinux_h_path) + error('Path to provided vmlinux.h does not exist.') + endif + bpf_o_unstripped_cmd += ['-I' + fs.parent(provided_vmlinux_h_path)] + message(f'Using provided @provided_vmlinux_h_path@') +elif use_generated_vmlinux_h + vmlinux_h_dependency = custom_target( + output: 'vmlinux.h', + command : [ bpftool, 'btf', 'dump', 'file', '/sys/kernel/btf/vmlinux', 'format', 'c' ], + capture : true) + + bpf_o_unstripped_cmd += ['-I' + fs.parent(vmlinux_h_dependency.full_path())] + generated_sources += vmlinux_h_dependency + message('Using generated @0@'.format(vmlinux_h_dependency.full_path())) +else + message('Using neither provided nor generated vmlinux.h, some features will not be available.') +endif + +conf.set10('HAVE_VMLINUX_H', use_provided_vmlinux_h or use_generated_vmlinux_h) + +conf.set10('ENABLE_SYSCTL_BPF', conf.get('HAVE_VMLINUX_H') == 1 and libbpf.version().version_compare('>= 0.7')) + +bpf_programs = [ + { + 'source' : files('bind-iface.bpf.c'), + 'condition' : 'BPF_FRAMEWORK', + }, + { + 'source' : files('restrict-fs.bpf.c'), + 'condition' : 'BPF_FRAMEWORK', + }, + { + 'source' : files('restrict-ifaces.bpf.c'), + 'condition' : 'BPF_FRAMEWORK', + }, + { + 'source' : files('socket-bind.bpf.c'), + 'condition' : 'BPF_FRAMEWORK', + }, + { + 'source' : files('sysctl-monitor.bpf.c'), + 'condition' : 'ENABLE_SYSCTL_BPF', + 'depends' : vmlinux_h_dependency, + }, + { + 'source' : files('userns-restrict.bpf.c'), + 'condition' : 'HAVE_VMLINUX_H', + 'depends' : vmlinux_h_dependency, + }, +] + +bpf_programs_by_name = {} + +foreach program : bpf_programs + if conf.get(program['condition']) != 1 + continue + endif + + source = program['source'][0] + name = fs.stem(fs.stem(source)) + + bpf_o_unstripped = custom_target( + input : source, + output : name + '.bpf.unstripped.o', + command : bpf_o_unstripped_cmd, + depends : program.get('depends', [])) + + bpf_o = custom_target( + input : bpf_o_unstripped, + output : name + '.bpf.o', + command : bpf_o_cmd) + + skel_h = custom_target( + input : bpf_o, + output : name + '.bpf.skel.h', + command : skel_h_cmd, + capture : true) + + # The wrapper is written at meson setup time and found via the + # include path, so we don't need to list it as a build-time source. + # Keeping it out of bpf_programs_by_name also keeps it out of the + # clang-tidy per-source test loop, which would otherwise fall back + # to a BPF compile_commands.json entry (no -Isrc/shared) and fail + # to resolve bpf-dlopen.h. + configure_file( + input : 'bpf-skel-wrapper.h.in', + output : name + '-skel.h', + configuration : { 'NAME' : name }) + + bpf_programs_by_name += { name : skel_h } + generated_sources += skel_h +endforeach diff --git a/src/core/bpf/restrict-fs/restrict-fs.bpf.c b/src/bpf/restrict-fs.bpf.c similarity index 100% rename from src/core/bpf/restrict-fs/restrict-fs.bpf.c rename to src/bpf/restrict-fs.bpf.c diff --git a/src/core/bpf/restrict-ifaces/restrict-ifaces.bpf.c b/src/bpf/restrict-ifaces.bpf.c similarity index 100% rename from src/core/bpf/restrict-ifaces/restrict-ifaces.bpf.c rename to src/bpf/restrict-ifaces.bpf.c diff --git a/src/core/bpf/socket-bind/socket-bind-api.bpf.h b/src/bpf/socket-bind-api.bpf.h similarity index 100% rename from src/core/bpf/socket-bind/socket-bind-api.bpf.h rename to src/bpf/socket-bind-api.bpf.h diff --git a/src/core/bpf/socket-bind/socket-bind.bpf.c b/src/bpf/socket-bind.bpf.c similarity index 100% rename from src/core/bpf/socket-bind/socket-bind.bpf.c rename to src/bpf/socket-bind.bpf.c diff --git a/src/network/bpf/sysctl-monitor/sysctl-monitor.bpf.c b/src/bpf/sysctl-monitor.bpf.c similarity index 100% rename from src/network/bpf/sysctl-monitor/sysctl-monitor.bpf.c rename to src/bpf/sysctl-monitor.bpf.c diff --git a/src/network/bpf/sysctl-monitor/sysctl-write-event.h b/src/bpf/sysctl-write-event.h similarity index 94% rename from src/network/bpf/sysctl-monitor/sysctl-write-event.h rename to src/bpf/sysctl-write-event.h index 77b71fb4f9c27..c0603638d99d0 100644 --- a/src/network/bpf/sysctl-monitor/sysctl-write-event.h +++ b/src/bpf/sysctl-write-event.h @@ -2,6 +2,13 @@ #pragma once +#ifdef __bpf__ +#include "vmlinux.h" +#else +#include +#include +#endif + #ifndef TASK_COMM_LEN #define TASK_COMM_LEN 16 #endif diff --git a/src/nsresourced/bpf/userns-restrict/userns-restrict.bpf.c b/src/bpf/userns-restrict.bpf.c similarity index 99% rename from src/nsresourced/bpf/userns-restrict/userns-restrict.bpf.c rename to src/bpf/userns-restrict.bpf.c index d70493fe7af5f..54473d7210ea6 100644 --- a/src/nsresourced/bpf/userns-restrict/userns-restrict.bpf.c +++ b/src/bpf/userns-restrict.bpf.c @@ -15,7 +15,7 @@ #include "vmlinux.h" -#include +#include /* IWYU pragma: keep */ #include #include #include diff --git a/src/core/bpf-bind-iface.c b/src/core/bpf-bind-iface.c index fdd2e1d8e6318..462bb7d81fc5f 100644 --- a/src/core/bpf-bind-iface.c +++ b/src/core/bpf-bind-iface.c @@ -14,7 +14,7 @@ /* libbpf, clang, llvm and bpftool compile time dependencies are satisfied */ #include "bpf-dlopen.h" #include "bpf-link.h" -#include "bpf/bind-iface/bind-iface-skel.h" +#include "bind-iface-skel.h" static struct bind_iface_bpf *bind_iface_bpf_free(struct bind_iface_bpf *obj) { bind_iface_bpf__destroy(obj); diff --git a/src/core/bpf-restrict-fs.c b/src/core/bpf-restrict-fs.c index 8df2f5235c8d9..c7689073ddf74 100644 --- a/src/core/bpf-restrict-fs.c +++ b/src/core/bpf-restrict-fs.c @@ -17,7 +17,7 @@ /* libbpf, clang and llc compile time dependencies are satisfied */ #include "bpf-dlopen.h" #include "bpf-link.h" -#include "bpf/restrict-fs/restrict-fs-skel.h" +#include "restrict-fs-skel.h" #define CGROUP_HASH_SIZE_MAX 2048 diff --git a/src/core/bpf-restrict-ifaces.c b/src/core/bpf-restrict-ifaces.c index a1bac8301be34..1445c7cd5fe9d 100644 --- a/src/core/bpf-restrict-ifaces.c +++ b/src/core/bpf-restrict-ifaces.c @@ -16,7 +16,7 @@ #include "bpf-dlopen.h" #include "bpf-link.h" -#include "bpf/restrict-ifaces/restrict-ifaces-skel.h" +#include "restrict-ifaces-skel.h" static struct restrict_ifaces_bpf *restrict_ifaces_bpf_free(struct restrict_ifaces_bpf *obj) { restrict_ifaces_bpf__destroy(obj); diff --git a/src/core/bpf-socket-bind.c b/src/core/bpf-socket-bind.c index b7158841fa02d..dd4a73a519ceb 100644 --- a/src/core/bpf-socket-bind.c +++ b/src/core/bpf-socket-bind.c @@ -11,8 +11,8 @@ /* libbpf, clang, llvm and bpftool compile time dependencies are satisfied */ #include "bpf-dlopen.h" #include "bpf-link.h" -#include "bpf/socket-bind/socket-bind-api.bpf.h" -#include "bpf/socket-bind/socket-bind-skel.h" +#include "socket-bind-api.bpf.h" +#include "socket-bind-skel.h" static struct socket_bind_bpf *socket_bind_bpf_free(struct socket_bind_bpf *obj) { /* socket_bind_bpf__destroy handles object == NULL case */ diff --git a/src/core/bpf/bind-iface/bind-iface-skel.h b/src/core/bpf/bind-iface/bind-iface-skel.h deleted file mode 100644 index 2ec63ca887dd1..0000000000000 --- a/src/core/bpf/bind-iface/bind-iface-skel.h +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -/* The SPDX header above is actually correct in claiming this was - * LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that - * compatible with GPL we will claim this to be GPL however, which should be - * fine given that LGPL-2.1-or-later downgrades to GPL if needed. - */ - -#include "bpf-dlopen.h" /* IWYU pragma: keep */ - -/* libbpf is used via dlopen(), so rename symbols */ -#define bpf_object__open_skeleton sym_bpf_object__open_skeleton -#define bpf_object__load_skeleton sym_bpf_object__load_skeleton -#define bpf_object__destroy_skeleton sym_bpf_object__destroy_skeleton - -#include "bpf/bind-iface/bind-iface.skel.h" /* IWYU pragma: export */ diff --git a/src/core/bpf/bind-iface/meson.build b/src/core/bpf/bind-iface/meson.build deleted file mode 100644 index 222cac16b08ab..0000000000000 --- a/src/core/bpf/bind-iface/meson.build +++ /dev/null @@ -1,23 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later - -if conf.get('BPF_FRAMEWORK') != 1 - subdir_done() -endif - -bind_network_interface_bpf_o_unstripped = custom_target( - input : 'bind-iface.bpf.c', - output : 'bind-iface.bpf.unstripped.o', - command : bpf_o_unstripped_cmd) - -bind_network_interface_bpf_o = custom_target( - input : bind_network_interface_bpf_o_unstripped, - output : 'bind-iface.bpf.o', - command : bpf_o_cmd) - -bind_network_interface_skel_h = custom_target( - input : bind_network_interface_bpf_o, - output : 'bind-iface.skel.h', - command : skel_h_cmd, - capture : true) - -generated_sources += bind_network_interface_skel_h diff --git a/src/core/bpf/restrict-fs/meson.build b/src/core/bpf/restrict-fs/meson.build deleted file mode 100644 index 41fc130acebe0..0000000000000 --- a/src/core/bpf/restrict-fs/meson.build +++ /dev/null @@ -1,23 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later - -if conf.get('BPF_FRAMEWORK') != 1 - subdir_done() -endif - -restrict_fs_bpf_o_unstripped = custom_target( - input : 'restrict-fs.bpf.c', - output : 'restrict-fs.bpf.unstripped.o', - command : bpf_o_unstripped_cmd) - -restrict_fs_bpf_o = custom_target( - input : restrict_fs_bpf_o_unstripped, - output : 'restrict-fs.bpf.o', - command : bpf_o_cmd) - -restrict_fs_skel_h = custom_target( - input : restrict_fs_bpf_o, - output : 'restrict-fs.skel.h', - command : skel_h_cmd, - capture : true) - -generated_sources += restrict_fs_skel_h diff --git a/src/core/bpf/restrict-fs/restrict-fs-skel.h b/src/core/bpf/restrict-fs/restrict-fs-skel.h deleted file mode 100644 index 06935f2cd83b5..0000000000000 --- a/src/core/bpf/restrict-fs/restrict-fs-skel.h +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -/* The SPDX header above is actually correct in claiming this was - * LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that - * compatible with GPL we will claim this to be GPL however, which should be - * fine given that LGPL-2.1-or-later downgrades to GPL if needed. - */ - -#include "bpf-dlopen.h" /* IWYU pragma: keep */ - -/* libbpf is used via dlopen(), so rename symbols */ -#define bpf_object__open_skeleton sym_bpf_object__open_skeleton -#define bpf_object__load_skeleton sym_bpf_object__load_skeleton -#define bpf_object__destroy_skeleton sym_bpf_object__destroy_skeleton - -#include "bpf/restrict-fs/restrict-fs.skel.h" /* IWYU pragma: export */ diff --git a/src/core/bpf/restrict-ifaces/meson.build b/src/core/bpf/restrict-ifaces/meson.build deleted file mode 100644 index f9ef4c753e4ff..0000000000000 --- a/src/core/bpf/restrict-ifaces/meson.build +++ /dev/null @@ -1,23 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later - -if conf.get('BPF_FRAMEWORK') != 1 - subdir_done() -endif - -restrict_ifaces_bpf_o_unstripped = custom_target( - input : 'restrict-ifaces.bpf.c', - output : 'restrict-ifaces.bpf.unstripped.o', - command : bpf_o_unstripped_cmd) - -restrict_ifaces_bpf_o = custom_target( - input : restrict_ifaces_bpf_o_unstripped, - output : 'restrict-ifaces.bpf.o', - command : bpf_o_cmd) - -restrict_ifaces_skel_h = custom_target( - input : restrict_ifaces_bpf_o, - output : 'restrict-ifaces.skel.h', - command : skel_h_cmd, - capture : true) - -generated_sources += restrict_ifaces_skel_h diff --git a/src/core/bpf/restrict-ifaces/restrict-ifaces-skel.h b/src/core/bpf/restrict-ifaces/restrict-ifaces-skel.h deleted file mode 100644 index 53ba2e5b5a020..0000000000000 --- a/src/core/bpf/restrict-ifaces/restrict-ifaces-skel.h +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -/* The SPDX header above is actually correct in claiming this was - * LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that - * compatible with GPL we will claim this to be GPL however, which should be - * fine given that LGPL-2.1-or-later downgrades to GPL if needed. - */ - -#include "bpf-dlopen.h" /* IWYU pragma: keep */ - -/* libbpf is used via dlopen(), so rename symbols */ -#define bpf_object__open_skeleton sym_bpf_object__open_skeleton -#define bpf_object__load_skeleton sym_bpf_object__load_skeleton -#define bpf_object__destroy_skeleton sym_bpf_object__destroy_skeleton - -#include "bpf/restrict-ifaces/restrict-ifaces.skel.h" /* IWYU pragma: export */ diff --git a/src/core/bpf/socket-bind/meson.build b/src/core/bpf/socket-bind/meson.build deleted file mode 100644 index ec9d34637b3ab..0000000000000 --- a/src/core/bpf/socket-bind/meson.build +++ /dev/null @@ -1,23 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later - -if conf.get('BPF_FRAMEWORK') != 1 - subdir_done() -endif - -socket_bind_bpf_o_unstripped = custom_target( - input : 'socket-bind.bpf.c', - output : 'socket-bind.bpf.unstripped.o', - command : bpf_o_unstripped_cmd) - -socket_bind_bpf_o = custom_target( - input : socket_bind_bpf_o_unstripped, - output : 'socket-bind.bpf.o', - command : bpf_o_cmd) - -socket_bind_skel_h = custom_target( - input : socket_bind_bpf_o, - output : 'socket-bind.skel.h', - command : skel_h_cmd, - capture : true) - -generated_sources += socket_bind_skel_h diff --git a/src/core/bpf/socket-bind/socket-bind-skel.h b/src/core/bpf/socket-bind/socket-bind-skel.h deleted file mode 100644 index d83dd89e52962..0000000000000 --- a/src/core/bpf/socket-bind/socket-bind-skel.h +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -/* The SPDX header above is actually correct in claiming this was - * LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that - * compatible with GPL we will claim this to be GPL however, which should be - * fine given that LGPL-2.1-or-later downgrades to GPL if needed. - */ - -#include "bpf-dlopen.h" /* IWYU pragma: keep */ - -/* libbpf is used via dlopen(), so rename symbols */ -#define bpf_object__open_skeleton sym_bpf_object__open_skeleton -#define bpf_object__load_skeleton sym_bpf_object__load_skeleton -#define bpf_object__destroy_skeleton sym_bpf_object__destroy_skeleton - -#include "bpf/socket-bind/socket-bind.skel.h" /* IWYU pragma: export */ diff --git a/src/core/meson.build b/src/core/meson.build index c5ef7e48cbd5d..d24fc3b6d3788 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -76,17 +76,10 @@ libcore_sources = files( 'varlink-unit.c', ) -subdir('bpf/socket-bind') -subdir('bpf/restrict-fs') -subdir('bpf/restrict-ifaces') -subdir('bpf/bind-iface') - if conf.get('BPF_FRAMEWORK') == 1 - libcore_sources += [ - socket_bind_skel_h, - restrict_fs_skel_h, - restrict_ifaces_skel_h, - bind_network_interface_skel_h] + foreach name : ['socket-bind', 'restrict-fs', 'restrict-ifaces', 'bind-iface'] + libcore_sources += bpf_programs_by_name[name] + endforeach endif sources += libcore_sources diff --git a/src/network/bpf/sysctl-monitor/meson.build b/src/network/bpf/sysctl-monitor/meson.build deleted file mode 100644 index 8b0de886743f1..0000000000000 --- a/src/network/bpf/sysctl-monitor/meson.build +++ /dev/null @@ -1,24 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later - -if conf.get('ENABLE_SYSCTL_BPF') != 1 - subdir_done() -endif - -sysctl_monitor_bpf_o_unstripped = custom_target( - input : 'sysctl-monitor.bpf.c', - output : 'sysctl-monitor.bpf.unstripped.o', - command : bpf_o_unstripped_cmd, - depends : vmlinux_h_dependency) - -sysctl_monitor_bpf_o = custom_target( - input : sysctl_monitor_bpf_o_unstripped, - output : 'sysctl-monitor.bpf.o', - command : bpf_o_cmd) - -sysctl_monitor_skel_h = custom_target( - input : sysctl_monitor_bpf_o, - output : 'sysctl-monitor.skel.h', - command : skel_h_cmd, - capture : true) - -generated_sources += sysctl_monitor_skel_h diff --git a/src/network/bpf/sysctl-monitor/sysctl-monitor-skel.h b/src/network/bpf/sysctl-monitor/sysctl-monitor-skel.h deleted file mode 100644 index e1ebc3e509a2c..0000000000000 --- a/src/network/bpf/sysctl-monitor/sysctl-monitor-skel.h +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -/* The SPDX header above is actually correct in claiming this was - * LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that - * compatible with GPL we will claim this to be GPL however, which should be - * fine given that LGPL-2.1-or-later downgrades to GPL if needed. - */ - -#include "bpf-dlopen.h" /* IWYU pragma: keep */ - -/* libbpf is used via dlopen(), so rename symbols */ -#define bpf_object__destroy_skeleton sym_bpf_object__destroy_skeleton -#define bpf_object__load_skeleton sym_bpf_object__load_skeleton -#define bpf_object__open_skeleton sym_bpf_object__open_skeleton - -#include "bpf/sysctl-monitor/sysctl-monitor.skel.h" /* IWYU pragma: export */ diff --git a/src/network/meson.build b/src/network/meson.build index 00361a0017ed9..134416bcc8dd8 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -1,7 +1,5 @@ # SPDX-License-Identifier: LGPL-2.1-or-later -subdir('bpf/sysctl-monitor') - systemd_networkd_sources = files( 'networkd.c' ) @@ -144,10 +142,6 @@ networkctl_sources = files( networkd_network_gperf_gperf = files('networkd-network-gperf.gperf') networkd_netdev_gperf_gperf = files('netdev/netdev-gperf.gperf') -if conf.get('ENABLE_SYSCTL_BPF') == 1 - systemd_networkd_extract_sources += sysctl_monitor_skel_h -endif - networkd_gperf_c = custom_target( input : 'networkd-gperf.gperf', output : 'networkd-gperf.c', @@ -209,6 +203,9 @@ executables += [ networkd_link_with, ], 'dependencies' : threads, + 'bpf_programs': [ + 'sysctl-monitor', + ] }, libexec_template + { 'name' : 'systemd-networkd-wait-online', diff --git a/src/network/networkd-sysctl.c b/src/network/networkd-sysctl.c index e5f5c07ff165e..81e49c860d2ab 100644 --- a/src/network/networkd-sysctl.c +++ b/src/network/networkd-sysctl.c @@ -30,8 +30,8 @@ #if ENABLE_SYSCTL_BPF #include "bpf-link.h" -#include "bpf/sysctl-monitor/sysctl-monitor-skel.h" -#include "bpf/sysctl-monitor/sysctl-write-event.h" +#include "sysctl-monitor-skel.h" +#include "sysctl-write-event.h" static struct sysctl_monitor_bpf* sysctl_monitor_bpf_free(struct sysctl_monitor_bpf *obj) { sysctl_monitor_bpf__destroy(obj); diff --git a/src/nsresourced/bpf/userns-restrict/meson.build b/src/nsresourced/bpf/userns-restrict/meson.build deleted file mode 100644 index e6bcc51add313..0000000000000 --- a/src/nsresourced/bpf/userns-restrict/meson.build +++ /dev/null @@ -1,24 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later - -if conf.get('HAVE_VMLINUX_H') != 1 - subdir_done() -endif - -userns_restrict_bpf_o_unstripped = custom_target( - input : 'userns-restrict.bpf.c', - output : 'userns-restrict.bpf.unstripped.o', - command : bpf_o_unstripped_cmd, - depends : vmlinux_h_dependency) - -userns_restrict_bpf_o = custom_target( - input : userns_restrict_bpf_o_unstripped, - output : 'userns-restrict.bpf.o', - command : bpf_o_cmd) - -userns_restrict_skel_h = custom_target( - input : userns_restrict_bpf_o, - output : 'userns-restrict.skel.h', - command : skel_h_cmd, - capture : true) - -generated_sources += userns_restrict_skel_h diff --git a/src/nsresourced/meson.build b/src/nsresourced/meson.build index 6b6ae1558c0f7..881fd911e418a 100644 --- a/src/nsresourced/meson.build +++ b/src/nsresourced/meson.build @@ -4,8 +4,6 @@ if conf.get('ENABLE_NSRESOURCED') != 1 subdir_done() endif -subdir('bpf/userns-restrict') - systemd_nsresourced_sources = files( 'nsresourced-manager.c', 'nsresourced.c', @@ -21,29 +19,26 @@ test_userns_restrict_sources = files( 'test-userns-restrict.c' ) -if conf.get('HAVE_VMLINUX_H') == 1 - systemd_nsresourced_sources += userns_restrict_skel_h - systemd_nsresourcework_sources += userns_restrict_skel_h - test_userns_restrict_sources += userns_restrict_skel_h -endif - executables += [ libexec_template + { 'name' : 'systemd-nsresourced', 'sources' : systemd_nsresourced_sources, 'extract' : systemd_nsresourced_extract_sources, 'dependencies' : threads, + 'bpf_programs': ['userns-restrict'], }, libexec_template + { 'name' : 'systemd-nsresourcework', 'sources' : systemd_nsresourcework_sources, 'dependencies' : threads, 'objects' : ['systemd-nsresourced'], + 'bpf_programs': ['userns-restrict'], }, test_template + { 'sources' : test_userns_restrict_sources, 'conditions' : ['HAVE_VMLINUX_H'], 'objects' : ['systemd-nsresourced'], + 'bpf_programs': ['userns-restrict'], }, ] diff --git a/src/nsresourced/nsresourced-manager.c b/src/nsresourced/nsresourced-manager.c index 3102563617e1c..cceaa9c378e74 100644 --- a/src/nsresourced/nsresourced-manager.c +++ b/src/nsresourced/nsresourced-manager.c @@ -8,7 +8,7 @@ #include "bpf-dlopen.h" #if HAVE_VMLINUX_H #include "bpf-link.h" -#include "bpf/userns-restrict/userns-restrict-skel.h" +#include "userns-restrict-skel.h" #endif #include "build-path.h" #include "common-signal.h" diff --git a/src/nsresourced/userns-restrict.c b/src/nsresourced/userns-restrict.c index 5012276c8268b..d9b7940f63085 100644 --- a/src/nsresourced/userns-restrict.c +++ b/src/nsresourced/userns-restrict.c @@ -3,7 +3,7 @@ #include #if HAVE_VMLINUX_H -#include "bpf/userns-restrict/userns-restrict-skel.h" +#include "userns-restrict-skel.h" #endif #include "bpf-dlopen.h" diff --git a/src/version/meson.build b/src/version/meson.build index 54db791ccf9ca..a0b03f41a65e8 100644 --- a/src/version/meson.build +++ b/src/version/meson.build @@ -14,3 +14,4 @@ version_h = custom_target('version', ]) version_include = include_directories('.') userspace_sources += [version_h] +generated_sources += [version_h] From b779c52d4c26dc3d241458106495ca844de7fa96 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 21 Apr 2026 20:31:22 +0000 Subject: [PATCH 1167/1296] bpf: register compile_commands.json entries for bpf programs compile_commands.json is generated by ninja from c_COMPILER rules, so BPF programs (built via custom_target and thus emitted as CUSTOM_COMMAND rules) never show up there. Clangd consequently falls back to heuristics when opening .bpf.c files, with poor diagnostic fidelity. Register a meson postconf script per BPF program that upserts an entry into compile_commands.json using the same argv meson constructed for the custom_target. The script runs after meson has written the DB, substitutes @INPUT@/@OUTPUT@, and keys entries by source path so repeated reconfigures don't accumulate duplicates. --- src/bpf/merge-bpf-compdb.py | 40 +++++++++++++++++++++++++++++++++++++ src/bpf/meson.build | 12 +++++++++++ 2 files changed, 52 insertions(+) create mode 100755 src/bpf/merge-bpf-compdb.py diff --git a/src/bpf/merge-bpf-compdb.py b/src/bpf/merge-bpf-compdb.py new file mode 100755 index 0000000000000..c93e685afd942 --- /dev/null +++ b/src/bpf/merge-bpf-compdb.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: LGPL-2.1-or-later + +import json +import os +import sys + + +def main() -> int: + build_root = os.environ['MESON_BUILD_ROOT'] + + sep = sys.argv.index('--') + sources = sys.argv[1:sep] + command = sys.argv[sep + 1:] + + db_path = os.path.join(build_root, 'compile_commands.json') + try: + with open(db_path) as f: + db = json.load(f) + except FileNotFoundError: + db = [] + + sources_set = set(sources) + db = [entry for entry in db if entry['file'] not in sources_set] + + for source in sources: + db.append({ + 'directory': build_root, + 'file': source, + 'arguments': [source if a == '@INPUT@' else a for a in command], + }) + + with open(db_path, 'w') as f: + json.dump(db, f, indent=2) + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/bpf/meson.build b/src/bpf/meson.build index 54b3027a97cea..32ab32e93dcdb 100644 --- a/src/bpf/meson.build +++ b/src/bpf/meson.build @@ -343,6 +343,7 @@ bpf_programs = [ ] bpf_programs_by_name = {} +bpf_sources = [] foreach program : bpf_programs if conf.get(program['condition']) != 1 @@ -350,6 +351,7 @@ foreach program : bpf_programs endif source = program['source'][0] + # Strip .bpf.c extension name = fs.stem(fs.stem(source)) bpf_o_unstripped = custom_target( @@ -382,4 +384,14 @@ foreach program : bpf_programs bpf_programs_by_name += { name : skel_h } generated_sources += skel_h + bpf_sources += source endforeach + +if bpf_sources.length() > 0 + meson.add_postconf_script( + python, + files('merge-bpf-compdb.py'), + bpf_sources, + '--', + bpf_o_unstripped_cmd) +endif From e9afaaeaacdba5bab2ddda4293106a0278d13f80 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 21 Apr 2026 20:31:34 +0000 Subject: [PATCH 1168/1296] bpf: suppress false-positive clang-tidy/clangd diagnostics under src/bpf clang-tidy's misc-use-internal-linkage fires on BPF map declarations (they have the SEC(".maps") attribute and must retain external linkage so bpftool gen skeleton can resolve them as ELF symbols), and its misc-include-cleaner flags errno.h as unused even where a /* IWYU pragma: keep */ is present. clangd's own unused-includes analysis emits the equivalent diagnostic independently of clang-tidy. Add src/bpf/.clang-tidy and src/bpf/.clangd that inherit the parent configs and scope these suppressions to BPF sources only. --- src/bpf/.clang-tidy | 7 +++++++ src/bpf/.clangd | 5 +++++ 2 files changed, 12 insertions(+) create mode 100644 src/bpf/.clang-tidy create mode 100644 src/bpf/.clangd diff --git a/src/bpf/.clang-tidy b/src/bpf/.clang-tidy new file mode 100644 index 0000000000000..43b0c59f49477 --- /dev/null +++ b/src/bpf/.clang-tidy @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +--- +InheritParentConfig: true +Checks: '-misc-use-internal-linkage' +CheckOptions: + misc-include-cleaner.IgnoreHeaders: 'errno\.h' +... diff --git a/src/bpf/.clangd b/src/bpf/.clangd new file mode 100644 index 0000000000000..ec2f81817b928 --- /dev/null +++ b/src/bpf/.clangd @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +Diagnostics: + Includes: + IgnoreHeader: [errno\.h] From 6958d4c79da3c2f8ff482897721fbd4930325b3e Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 22 Apr 2026 15:07:31 +0100 Subject: [PATCH 1169/1296] oomctl: add missing assert flagged by coccinelle FAIL: check-pointer-deref.cocci found issues in /home/runner/work/systemd/systemd/src/oom/: diff -u -p /home/runner/work/systemd/systemd/src/oom/oomctl.c /tmp/nothing/oomctl.c --- /home/runner/work/systemd/systemd/src/oom/oomctl.c +++ /tmp/nothing/oomctl.c @@ -107,7 +107,6 @@ static int parse_argv(int argc, char *ar break; } - *ret_args = option_parser_get_args(&state); return 1; } Coccinelle check(s) failed. For each flagged dereference, either: - Add assert(param)/ASSERT_PTR(param) at the top of the function (if the parameter must not be NULL) - Add an if (param) guard before the dereference (if NULL is valid) - Add POINTER_MAY_BE_NULL(param) if NULL is okay for param Follow-up for 143af68ee15607aced66e9f5aba55899b3f4e505 --- src/oom/oomctl.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/oom/oomctl.c b/src/oom/oomctl.c index 703b9c3f0eed2..51dfe1f1a40a3 100644 --- a/src/oom/oomctl.c +++ b/src/oom/oomctl.c @@ -89,6 +89,7 @@ static int verb_dump_state(int argc, char *argv[], uintptr_t _data, void *userda static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); OptionParser state = { argc, argv }; const char *arg; From ba59dd5eb6dbb24b73a648cdbc3e4d65f8442e01 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 22 Apr 2026 16:24:55 +0200 Subject: [PATCH 1170/1296] util-linux: Drop required version back to v2.27.1 It was bumped in a40d93400759c8eb46a2ec8702735bde2333812a but this is hardly load bearing stuff so let's document the version we actually require rather than the version that makes a hardly load bearing feature work properly, especially since v2.41 is extremely new and requiring distributions to have that is just unrealistic. This doesn't actually change anything materially except documentation, but it keeps us honest about depending on stuff from newer util-linux because we happen to document reliance on an extremely new version. --- README | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README b/README index ccb6f86bfe8ea..ddcb863f4ada2 100644 --- a/README +++ b/README @@ -264,9 +264,9 @@ REQUIREMENTS: During runtime, you need the following additional dependencies: - util-linux >= v2.41 required (including but not limited to: mount, - umount, swapon, swapoff, sulogin, - agetty, fsck) + util-linux >= v2.27.1 required (including but not limited to: mount, + umount, swapon, swapoff, sulogin, + agetty, fsck) dbus >= 1.4.0 (strictly speaking optional, but recommended) NOTE: If using dbus < 1.9.18, you should override the default policy directory (--with-dbuspolicydir=/etc/dbus-1/system.d). From d49f3f287a0bf72b5b473980cf435f0c0c2413d0 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 21 Apr 2026 10:56:21 +0000 Subject: [PATCH 1171/1296] dlopen: take log_level argument and log in fallback stubs Every dlopen_xxx() helper now takes an int log_level argument. It is passed through to dlopen_many_sym_or_warn() (which in turn propagates it to dlopen_verbose() for the library-not-installed case), and is used by the fallback stub when support for the library is not compiled in to emit a " support is not compiled in." message at the caller's level. Callers pass LOG_DEBUG when gracefully degrading, or a higher level when the failure should surface, and no longer need to log redundantly at the call site. As part of this, dlopen_bpf_full() (which already took a log_level) is merged into dlopen_bpf() rather than keeping both. The static inline fallbacks used to live in the headers, which required pulling log.h in from every header that declared a dlopen_xxx(). Move them into the .c files instead: the declaration is always outside the #if HAVE_XXX block, the impl sits outside the outer #if HAVE_XXX wrap with its own internal #if HAVE_XXX/#else/#endif, and apparmor-util.c, idn-util.c, libmount-util.c and pam-util.c are now always compiled so they can host their stubs. --- src/analyze/analyze-security.c | 4 +- src/basic/compress.c | 97 ++++++++-------- src/basic/compress.h | 10 +- src/basic/dlfcn-util.c | 11 +- src/basic/dlfcn-util.h | 2 +- src/basic/gcrypt-util.c | 10 +- src/basic/gcrypt-util.h | 2 +- src/core/bpf-bind-iface.c | 2 +- src/core/bpf-restrict-fs.c | 2 +- src/core/bpf-restrict-ifaces.c | 2 +- src/core/bpf-socket-bind.c | 2 +- src/core/exec-invoke.c | 16 +-- src/core/execute.c | 6 +- src/core/main.c | 2 +- src/core/mount.c | 2 +- src/core/namespace.c | 2 +- src/core/selinux-setup.c | 6 +- src/creds/creds.c | 2 +- src/dissect/dissect.c | 4 +- src/growfs/growfs.c | 4 +- src/home/homework-luks.c | 20 ++-- src/home/homework.c | 2 +- src/home/pam_systemd_home.c | 8 +- src/imds/imdsd.c | 2 +- src/import/import-common.c | 4 +- src/import/test-tar.c | 2 +- src/journal-remote/journal-upload.c | 2 +- src/libsystemd/sd-device/test-sd-device.c | 2 +- src/locale/xkbcommon-util.c | 6 +- src/login/pam_systemd.c | 2 +- src/login/pam_systemd_loadkey.c | 2 +- src/network/networkd-sysctl.c | 2 +- src/nspawn/nspawn-oci.c | 2 +- src/nspawn/nspawn.c | 6 +- src/nsresourced/userns-restrict.c | 2 +- src/portable/portable.c | 4 +- src/repart/repart.c | 22 ++-- src/report/report-upload.c | 2 +- src/resolve/resolved-util.c | 2 +- src/shared/acl-util.c | 26 +++-- src/shared/acl-util.h | 8 +- src/shared/apparmor-util.c | 58 ++++++---- src/shared/apparmor-util.h | 7 +- src/shared/blkid-util.c | 106 ++++++++--------- src/shared/blkid-util.h | 8 +- src/shared/bpf-dlopen.c | 4 +- src/shared/bpf-dlopen.h | 5 +- src/shared/bpf-link.c | 2 +- src/shared/cryptsetup-util.c | 15 +-- src/shared/cryptsetup-util.h | 2 +- src/shared/curl-util.c | 77 +++++++------ src/shared/curl-util.h | 10 +- src/shared/dissect-image.c | 10 +- src/shared/dns-domain.c | 2 +- src/shared/elf-util.c | 18 +-- src/shared/elf-util.h | 4 +- src/shared/fdisk-util.c | 13 ++- src/shared/fdisk-util.h | 10 +- src/shared/find-esp.c | 8 +- src/shared/idn-util.c | 17 ++- src/shared/idn-util.h | 8 +- src/shared/libarchive-util.c | 12 +- src/shared/libarchive-util.h | 7 +- src/shared/libaudit-util.c | 11 +- src/shared/libaudit-util.h | 2 +- src/shared/libcrypt-util.c | 113 ++++++++++--------- src/shared/libcrypt-util.h | 6 +- src/shared/libfido2-util.c | 27 ++--- src/shared/libfido2-util.h | 2 +- src/shared/libmount-util.c | 104 +++++++++-------- src/shared/libmount-util.h | 7 +- src/shared/meson.build | 20 +--- src/shared/module-util.c | 59 +++++----- src/shared/module-util.h | 7 +- src/shared/mount-util.c | 4 +- src/shared/pam-util.c | 70 +++++++----- src/shared/pam-util.h | 10 +- src/shared/password-quality-util-passwdqc.c | 14 ++- src/shared/password-quality-util-passwdqc.h | 2 +- src/shared/password-quality-util-pwquality.c | 14 ++- src/shared/password-quality-util-pwquality.h | 2 +- src/shared/pcre2-util.c | 9 +- src/shared/pcre2-util.h | 2 +- src/shared/pcrextend-util.c | 2 +- src/shared/pkcs11-util.c | 33 +++--- src/shared/pkcs11-util.h | 2 +- src/shared/qrcode-util.c | 9 +- src/shared/qrcode-util.h | 2 +- src/shared/reread-partition-table.c | 2 +- src/shared/seccomp-util.c | 93 +++++++-------- src/shared/seccomp-util.h | 7 +- src/shared/selinux-util.c | 24 ++-- src/shared/selinux-util.h | 7 +- src/shared/shift-uid.c | 2 +- src/shared/tar-util.c | 12 +- src/shared/tpm2-util.c | 84 +++++++------- src/shared/tpm2-util.h | 2 +- src/shutdown/detach-swap.c | 4 +- src/sysext/sysext.c | 8 +- src/sysupdate/sysupdate-partition.c | 4 +- src/sysupdate/sysupdate-resource.c | 2 +- src/test/test-dlopen-so.c | 12 +- src/test/test-execute.c | 2 +- src/test/test-kexec.c | 4 +- src/test/test-load-fragment.c | 2 +- src/test/test-namespace.c | 2 +- src/test/test-netlink-manual.c | 4 +- src/tmpfiles/tmpfiles.c | 4 +- src/udev/test-udev-rule-runner.c | 2 +- src/udev/udev-builtin-blkid.c | 2 +- src/udev/udevd.c | 10 +- src/validatefs/validatefs.c | 4 +- 112 files changed, 796 insertions(+), 755 deletions(-) diff --git a/src/analyze/analyze-security.c b/src/analyze/analyze-security.c index c1b2948e6ca3a..08bff2cf9e9d2 100644 --- a/src/analyze/analyze-security.c +++ b/src/analyze/analyze-security.c @@ -605,7 +605,7 @@ static int assess_system_call_filter( uint64_t b; int r; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) { *ret_badness = UINT64_MAX; *ret_description = NULL; @@ -2578,7 +2578,7 @@ static int get_security_info(Unit *u, ExecContext *c, CGroupContext *g, Security info->_umask = c->umask; #if HAVE_SECCOMP - if (dlopen_libseccomp() >= 0) { + if (dlopen_libseccomp(LOG_DEBUG) >= 0) { SET_FOREACH(key, c->syscall_archs) { const char *name; diff --git a/src/basic/compress.c b/src/basic/compress.c index 979e07ac80957..8386bdb1b1df7 100644 --- a/src/basic/compress.c +++ b/src/basic/compress.c @@ -284,7 +284,7 @@ Compression compression_detect_from_magic(const uint8_t data[static COMPRESSION_ return _COMPRESSION_INVALID; } -int dlopen_xz(void) { +int dlopen_xz(int log_level) { #if HAVE_XZ SD_ELF_NOTE_DLOPEN( "lzma", @@ -294,7 +294,7 @@ int dlopen_xz(void) { return dlopen_many_sym_or_warn( &lzma_dl, - "liblzma.so.5", LOG_DEBUG, + "liblzma.so.5", log_level, DLSYM_ARG(lzma_code), DLSYM_ARG(lzma_easy_encoder), DLSYM_ARG(lzma_end), @@ -302,11 +302,12 @@ int dlopen_xz(void) { DLSYM_ARG(lzma_lzma_preset), DLSYM_ARG(lzma_stream_decoder)); #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "lzma support is not compiled in."); #endif } -int dlopen_lz4(void) { +int dlopen_lz4(int log_level) { #if HAVE_LZ4 SD_ELF_NOTE_DLOPEN( "lz4", @@ -316,7 +317,7 @@ int dlopen_lz4(void) { return dlopen_many_sym_or_warn( &lz4_dl, - "liblz4.so.1", LOG_DEBUG, + "liblz4.so.1", log_level, DLSYM_ARG(LZ4F_compressBegin), DLSYM_ARG(LZ4F_compressBound), DLSYM_ARG(LZ4F_compressEnd), @@ -333,11 +334,12 @@ int dlopen_lz4(void) { DLSYM_ARG(LZ4_decompress_safe_partial), DLSYM_ARG(LZ4_versionNumber)); #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "lz4 support is not compiled in."); #endif } -int dlopen_zstd(void) { +int dlopen_zstd(int log_level) { #if HAVE_ZSTD SD_ELF_NOTE_DLOPEN( "zstd", @@ -347,7 +349,7 @@ int dlopen_zstd(void) { return dlopen_many_sym_or_warn( &zstd_dl, - "libzstd.so.1", LOG_DEBUG, + "libzstd.so.1", log_level, DLSYM_ARG(ZSTD_getErrorCode), DLSYM_ARG(ZSTD_compress), DLSYM_ARG(ZSTD_getFrameContentSize), @@ -365,11 +367,12 @@ int dlopen_zstd(void) { DLSYM_ARG(ZSTD_createDCtx), DLSYM_ARG(ZSTD_createCCtx)); #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "zstd support is not compiled in."); #endif } -int dlopen_zlib(void) { +int dlopen_zlib(int log_level) { #if HAVE_ZLIB SD_ELF_NOTE_DLOPEN( "zlib", @@ -379,7 +382,7 @@ int dlopen_zlib(void) { return dlopen_many_sym_or_warn( &zlib_dl, - "libz.so.1", LOG_DEBUG, + "libz.so.1", log_level, DLSYM_ARG(deflateInit2_), DLSYM_ARG(deflate), DLSYM_ARG(deflateEnd), @@ -387,11 +390,12 @@ int dlopen_zlib(void) { DLSYM_ARG(inflate), DLSYM_ARG(inflateEnd)); #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "zlib support is not compiled in."); #endif } -int dlopen_bzip2(void) { +int dlopen_bzip2(int log_level) { #if HAVE_BZIP2 SD_ELF_NOTE_DLOPEN( "bzip2", @@ -401,7 +405,7 @@ int dlopen_bzip2(void) { return dlopen_many_sym_or_warn( &bzip2_dl, - "libbz2.so.1", LOG_DEBUG, + "libbz2.so.1", log_level, DLSYM_ARG(BZ2_bzCompressInit), DLSYM_ARG(BZ2_bzCompress), DLSYM_ARG(BZ2_bzCompressEnd), @@ -409,7 +413,8 @@ int dlopen_bzip2(void) { DLSYM_ARG(BZ2_bzDecompress), DLSYM_ARG(BZ2_bzDecompressEnd)); #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "bzip2 support is not compiled in."); #endif } @@ -440,7 +445,7 @@ static int compress_blob_xz( size_t out_pos = 0; int r; - r = dlopen_xz(); + r = dlopen_xz(LOG_DEBUG); if (r < 0) return r; @@ -485,7 +490,7 @@ static int compress_blob_lz4( #if HAVE_LZ4 int r; - r = dlopen_lz4(); + r = dlopen_lz4(LOG_DEBUG); if (r < 0) return r; /* Returns < 0 if we couldn't compress the data or the @@ -533,7 +538,7 @@ static int compress_blob_zstd( size_t k; int r; - r = dlopen_zstd(); + r = dlopen_zstd(LOG_DEBUG); if (r < 0) return r; @@ -560,7 +565,7 @@ static int compress_blob_gzip(const void *src, uint64_t src_size, #if HAVE_ZLIB int r; - r = dlopen_zlib(); + r = dlopen_zlib(LOG_DEBUG); if (r < 0) return r; @@ -609,7 +614,7 @@ static int compress_blob_bzip2( #if HAVE_BZIP2 int r; - r = dlopen_bzip2(); + r = dlopen_bzip2(LOG_DEBUG); if (r < 0) return r; @@ -677,7 +682,7 @@ static int decompress_blob_xz( #if HAVE_XZ int r; - r = dlopen_xz(); + r = dlopen_xz(LOG_DEBUG); if (r < 0) return r; @@ -744,7 +749,7 @@ static int decompress_blob_lz4( char* out; int r, size; /* LZ4 uses int for size */ - r = dlopen_lz4(); + r = dlopen_lz4(LOG_DEBUG); if (r < 0) return r; @@ -790,7 +795,7 @@ static int decompress_blob_zstd( uint64_t size; int r; - r = dlopen_zstd(); + r = dlopen_zstd(LOG_DEBUG); if (r < 0) return r; @@ -847,7 +852,7 @@ static int decompress_blob_gzip( #if HAVE_ZLIB int r; - r = dlopen_zlib(); + r = dlopen_zlib(LOG_DEBUG); if (r < 0) return r; @@ -914,7 +919,7 @@ static int decompress_blob_bzip2( #if HAVE_BZIP2 int r; - r = dlopen_bzip2(); + r = dlopen_bzip2(LOG_DEBUG); if (r < 0) return r; @@ -1010,7 +1015,7 @@ int decompress_zlib_raw( #if HAVE_ZLIB int r; - r = dlopen_zlib(); + r = dlopen_zlib(LOG_DEBUG); if (r < 0) return r; @@ -1061,7 +1066,7 @@ static int decompress_startswith_xz( #if HAVE_XZ int r; - r = dlopen_xz(); + r = dlopen_xz(LOG_DEBUG); if (r < 0) return r; @@ -1128,7 +1133,7 @@ static int decompress_startswith_lz4( size_t allocated; int r; - r = dlopen_lz4(); + r = dlopen_lz4(LOG_DEBUG); if (r < 0) return r; @@ -1200,7 +1205,7 @@ static int decompress_startswith_zstd( #if HAVE_ZSTD int r; - r = dlopen_zstd(); + r = dlopen_zstd(LOG_DEBUG); if (r < 0) return r; @@ -1257,7 +1262,7 @@ static int decompress_startswith_gzip( #if HAVE_ZLIB int r; - r = dlopen_zlib(); + r = dlopen_zlib(LOG_DEBUG); if (r < 0) return r; @@ -1324,7 +1329,7 @@ static int decompress_startswith_bzip2( #if HAVE_BZIP2 int r; - r = dlopen_bzip2(); + r = dlopen_bzip2(LOG_DEBUG); if (r < 0) return r; @@ -1561,7 +1566,7 @@ static int decompressor_new(Decompressor **ret, Compression type) { #if HAVE_XZ case COMPRESSION_XZ: - r = dlopen_xz(); + r = dlopen_xz(LOG_DEBUG); if (r < 0) return r; @@ -1572,7 +1577,7 @@ static int decompressor_new(Decompressor **ret, Compression type) { #if HAVE_LZ4 case COMPRESSION_LZ4: { - r = dlopen_lz4(); + r = dlopen_lz4(LOG_DEBUG); if (r < 0) return r; @@ -1586,7 +1591,7 @@ static int decompressor_new(Decompressor **ret, Compression type) { #if HAVE_ZSTD case COMPRESSION_ZSTD: - r = dlopen_zstd(); + r = dlopen_zstd(LOG_DEBUG); if (r < 0) return r; @@ -1598,7 +1603,7 @@ static int decompressor_new(Decompressor **ret, Compression type) { #if HAVE_ZLIB case COMPRESSION_GZIP: - r = dlopen_zlib(); + r = dlopen_zlib(LOG_DEBUG); if (r < 0) return r; @@ -1610,7 +1615,7 @@ static int decompressor_new(Decompressor **ret, Compression type) { #if HAVE_BZIP2 case COMPRESSION_BZIP2: - r = dlopen_bzip2(); + r = dlopen_bzip2(LOG_DEBUG); if (r < 0) return r; @@ -1791,7 +1796,7 @@ int decompressor_detect(Decompressor **ret, const void *data, size_t size) { #if HAVE_XZ case COMPRESSION_XZ: { - r = dlopen_xz(); + r = dlopen_xz(LOG_DEBUG); if (r < 0) return r; @@ -1805,7 +1810,7 @@ int decompressor_detect(Decompressor **ret, const void *data, size_t size) { #if HAVE_LZ4 case COMPRESSION_LZ4: { - r = dlopen_lz4(); + r = dlopen_lz4(LOG_DEBUG); if (r < 0) return r; @@ -1819,7 +1824,7 @@ int decompressor_detect(Decompressor **ret, const void *data, size_t size) { #if HAVE_ZSTD case COMPRESSION_ZSTD: { - r = dlopen_zstd(); + r = dlopen_zstd(LOG_DEBUG); if (r < 0) return r; @@ -1833,7 +1838,7 @@ int decompressor_detect(Decompressor **ret, const void *data, size_t size) { #if HAVE_ZLIB case COMPRESSION_GZIP: { - r = dlopen_zlib(); + r = dlopen_zlib(LOG_DEBUG); if (r < 0) return r; @@ -1847,7 +1852,7 @@ int decompressor_detect(Decompressor **ret, const void *data, size_t size) { #if HAVE_BZIP2 case COMPRESSION_BZIP2: { - r = dlopen_bzip2(); + r = dlopen_bzip2(LOG_DEBUG); if (r < 0) return r; @@ -2092,7 +2097,7 @@ int compressor_new(Compressor **ret, Compression type) { #if HAVE_XZ case COMPRESSION_XZ: { - r = dlopen_xz(); + r = dlopen_xz(LOG_DEBUG); if (r < 0) return r; @@ -2107,7 +2112,7 @@ int compressor_new(Compressor **ret, Compression type) { #if HAVE_LZ4 case COMPRESSION_LZ4: { - r = dlopen_lz4(); + r = dlopen_lz4(LOG_DEBUG); if (r < 0) return r; @@ -2133,7 +2138,7 @@ int compressor_new(Compressor **ret, Compression type) { #if HAVE_ZSTD case COMPRESSION_ZSTD: - r = dlopen_zstd(); + r = dlopen_zstd(LOG_DEBUG); if (r < 0) return r; @@ -2156,7 +2161,7 @@ int compressor_new(Compressor **ret, Compression type) { #if HAVE_ZLIB case COMPRESSION_GZIP: - r = dlopen_zlib(); + r = dlopen_zlib(LOG_DEBUG); if (r < 0) return r; @@ -2176,7 +2181,7 @@ int compressor_new(Compressor **ret, Compression type) { #if HAVE_BZIP2 case COMPRESSION_BZIP2: - r = dlopen_bzip2(); + r = dlopen_bzip2(LOG_DEBUG); if (r < 0) return r; diff --git a/src/basic/compress.h b/src/basic/compress.h index 1c1e4f0e24b32..45584b7a6d13d 100644 --- a/src/basic/compress.h +++ b/src/basic/compress.h @@ -80,11 +80,11 @@ int compress_stream(Compression type, int fdf, int fdt, uint64_t max_bytes, uint int decompress_stream(Compression type, int fdf, int fdt, uint64_t max_bytes); int decompress_stream_by_filename(const char *filename, int fdf, int fdt, uint64_t max_bytes); -int dlopen_xz(void); -int dlopen_lz4(void); -int dlopen_zstd(void); -int dlopen_zlib(void); -int dlopen_bzip2(void); +int dlopen_xz(int log_level); +int dlopen_lz4(int log_level); +int dlopen_zstd(int log_level); +int dlopen_zlib(int log_level); +int dlopen_bzip2(int log_level); static inline const char* default_compression_extension(void) { return compression_extension_to_string(DEFAULT_COMPRESSION) ?: ""; diff --git a/src/basic/dlfcn-util.c b/src/basic/dlfcn-util.c index 23bd1f666e32b..86ec2d28fd5a0 100644 --- a/src/basic/dlfcn-util.c +++ b/src/basic/dlfcn-util.c @@ -47,7 +47,7 @@ int dlsym_many_or_warn_sentinel(void *dl, int log_level, ...) { return r; } -int dlopen_verbose(void **dlp, const char *filename) { +int dlopen_verbose(void **dlp, const char *filename, int log_level) { int r; assert(dlp); @@ -58,10 +58,9 @@ int dlopen_verbose(void **dlp, const char *filename) { _cleanup_(dlclosep) void *dl = NULL; const char *dle = NULL; r = dlopen_safe(filename, &dl, &dle); - if (r < 0) { - log_debug_errno(r, "Shared library '%s' is not available: %s", filename, dle ?: STRERROR(r)); - return -EOPNOTSUPP; /* Turn into recognizable error */ - } + if (r < 0) + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "Shared library '%s' is not available: %s", filename, dle ?: STRERROR(r)); log_debug("Loaded shared library '%s' via dlopen().", filename); *dlp = TAKE_PTR(dl); @@ -77,7 +76,7 @@ int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_l return 0; /* Already loaded */ _cleanup_(dlclosep) void *dl = NULL; - r = dlopen_verbose(&dl, filename); + r = dlopen_verbose(&dl, filename, log_level); if (r < 0) return r; diff --git a/src/basic/dlfcn-util.h b/src/basic/dlfcn-util.h index 40de1379055f2..ccf0ec6be3b26 100644 --- a/src/basic/dlfcn-util.h +++ b/src/basic/dlfcn-util.h @@ -11,7 +11,7 @@ static inline void dlclosep(void **dlp) { safe_dlclose(*dlp); } -int dlopen_verbose(void **dlp, const char *filename); +int dlopen_verbose(void **dlp, const char *filename, int log_level); int dlsym_many_or_warn_sentinel(void *dl, int log_level, ...) _sentinel_; int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_level, ...) _sentinel_; diff --git a/src/basic/gcrypt-util.c b/src/basic/gcrypt-util.c index 79cab18822ffa..34444811ad3af 100644 --- a/src/basic/gcrypt-util.c +++ b/src/basic/gcrypt-util.c @@ -5,6 +5,7 @@ #include "sd-dlopen.h" #include "gcrypt-util.h" +#include "log.h" /* IWYU pragma: keep */ #if HAVE_GCRYPT @@ -44,7 +45,7 @@ DLSYM_PROTOTYPE(gcry_randomize) = NULL; DLSYM_PROTOTYPE(gcry_strerror) = NULL; #endif -int dlopen_gcrypt(void) { +int dlopen_gcrypt(int log_level) { #if HAVE_GCRYPT SD_ELF_NOTE_DLOPEN( "gcrypt", @@ -54,7 +55,7 @@ int dlopen_gcrypt(void) { return dlopen_many_sym_or_warn( &gcrypt_dl, - "libgcrypt.so.20", LOG_DEBUG, + "libgcrypt.so.20", log_level, DLSYM_ARG(gcry_control), DLSYM_ARG(gcry_check_version), DLSYM_ARG(gcry_md_close), @@ -88,7 +89,8 @@ int dlopen_gcrypt(void) { DLSYM_ARG(gcry_randomize), DLSYM_ARG(gcry_strerror)); #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "gcrypt support is not compiled in."); #endif } @@ -96,7 +98,7 @@ int initialize_libgcrypt(bool secmem) { #if HAVE_GCRYPT int r; - r = dlopen_gcrypt(); + r = dlopen_gcrypt(LOG_DEBUG); if (r < 0) return r; diff --git a/src/basic/gcrypt-util.h b/src/basic/gcrypt-util.h index 147a174da0026..0f45fad205b28 100644 --- a/src/basic/gcrypt-util.h +++ b/src/basic/gcrypt-util.h @@ -4,7 +4,7 @@ #include "basic-forward.h" -int dlopen_gcrypt(void); +int dlopen_gcrypt(int log_level); int initialize_libgcrypt(bool secmem); diff --git a/src/core/bpf-bind-iface.c b/src/core/bpf-bind-iface.c index 462bb7d81fc5f..74cb2b3d52374 100644 --- a/src/core/bpf-bind-iface.c +++ b/src/core/bpf-bind-iface.c @@ -31,7 +31,7 @@ int bpf_bind_network_interface_supported(void) { if (supported >= 0) return supported; - if (dlopen_bpf_full(LOG_WARNING) < 0) + if (dlopen_bpf(LOG_WARNING) < 0) return (supported = false); obj = bind_iface_bpf__open(); diff --git a/src/core/bpf-restrict-fs.c b/src/core/bpf-restrict-fs.c index c7689073ddf74..e60e6ca7efcfe 100644 --- a/src/core/bpf-restrict-fs.c +++ b/src/core/bpf-restrict-fs.c @@ -91,7 +91,7 @@ bool bpf_restrict_fs_supported(bool initialize) { if (!initialize) return false; - if (dlopen_bpf_full(LOG_WARNING) < 0) + if (dlopen_bpf(LOG_WARNING) < 0) return (supported = false); r = lsm_supported("bpf"); diff --git a/src/core/bpf-restrict-ifaces.c b/src/core/bpf-restrict-ifaces.c index 1445c7cd5fe9d..3c73a682a3d38 100644 --- a/src/core/bpf-restrict-ifaces.c +++ b/src/core/bpf-restrict-ifaces.c @@ -86,7 +86,7 @@ int bpf_restrict_ifaces_supported(void) { if (supported >= 0) return supported; - if (dlopen_bpf_full(LOG_WARNING) < 0) + if (dlopen_bpf(LOG_WARNING) < 0) return (supported = false); r = prepare_restrict_ifaces_bpf(NULL, true, NULL, &obj); diff --git a/src/core/bpf-socket-bind.c b/src/core/bpf-socket-bind.c index dd4a73a519ceb..87e7d60c055a9 100644 --- a/src/core/bpf-socket-bind.c +++ b/src/core/bpf-socket-bind.c @@ -125,7 +125,7 @@ int bpf_socket_bind_supported(void) { _cleanup_(socket_bind_bpf_freep) struct socket_bind_bpf *obj = NULL; int r; - if (dlopen_bpf_full(LOG_WARNING) < 0) + if (dlopen_bpf(LOG_WARNING) < 0) return false; r = prepare_socket_bind_bpf(/* unit= */ NULL, /* allow_rules= */ NULL, /* deny_rules= */ NULL, &obj); diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 2138367218ba9..bcc24922aec2c 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -1359,9 +1359,9 @@ static int setup_pam( * parent process will exec() the actual daemon. We do things this way to ensure that the main PID of * the daemon is the one we initially fork()ed. */ - r = dlopen_libpam(); + r = dlopen_libpam(LOG_ERR); if (r < 0) - return log_error_errno(r, "PAM support not available: %m"); + return r; r = barrier_create(&barrier); if (r < 0) @@ -1625,7 +1625,7 @@ static bool seccomp_allows_drop_privileges(const ExecContext *c) { assert(c); /* No libseccomp, all is fine */ - if (dlopen_libseccomp() < 0) + if (dlopen_libseccomp(LOG_DEBUG) < 0) return true; /* No syscall filter, we are allowed to drop privileges */ @@ -1927,7 +1927,7 @@ static int apply_restrict_filesystems(const ExecContext *c, const ExecParameters } /* We are in a new binary, so dl-open again */ - r = dlopen_bpf(); + r = dlopen_bpf(LOG_DEBUG); if (r < 0) return r; @@ -6026,10 +6026,10 @@ int exec_invoke( } /* Load a bunch of libraries we'll possibly need later, before we turn off dlopen() */ - (void) dlopen_bpf(); - (void) dlopen_cryptsetup(); - (void) dlopen_libmount(); - (void) dlopen_libseccomp(); + (void) dlopen_bpf(LOG_DEBUG); + (void) dlopen_cryptsetup(LOG_DEBUG); + (void) dlopen_libmount(LOG_DEBUG); + (void) dlopen_libseccomp(LOG_DEBUG); /* Let's now disable further dlopen()ing of libraries, since we are about to do namespace * shenanigans, and do not want to mix resources from host and namespace */ diff --git a/src/core/execute.c b/src/core/execute.c index dea0699ba438a..cfd63fe3038fa 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -1491,7 +1491,7 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) { fputc('~', f); #if HAVE_SECCOMP - if (dlopen_libseccomp() >= 0) { + if (dlopen_libseccomp(LOG_DEBUG) >= 0) { void *id, *val; bool first = true; HASHMAP_FOREACH_KEY(val, id, c->syscall_filter) { @@ -1910,7 +1910,7 @@ char** exec_context_get_syscall_filter(const ExecContext *c) { assert(c); #if HAVE_SECCOMP - if (dlopen_libseccomp() < 0) + if (dlopen_libseccomp(LOG_DEBUG) < 0) return strv_new(NULL); void *id, *val; @@ -1979,7 +1979,7 @@ char** exec_context_get_syscall_log(const ExecContext *c) { assert(c); #if HAVE_SECCOMP - if (dlopen_libseccomp() < 0) + if (dlopen_libseccomp(LOG_DEBUG) < 0) return strv_new(NULL); void *id, *val; diff --git a/src/core/main.c b/src/core/main.c index e89cb9da3d1f3..3bdce441a85bb 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -3378,7 +3378,7 @@ int main(int argc, char *argv[]) { } /* Building without libmount is allowed, but if it is compiled in, then we must be able to load it */ - r = dlopen_libmount(); + r = dlopen_libmount(LOG_DEBUG); if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r)) { error_message = "Failed to load libmount.so"; goto finish; diff --git a/src/core/mount.c b/src/core/mount.c index 46e157af206c1..967274b950b67 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -2408,7 +2408,7 @@ static int mount_test_startable(Unit *u) { } static bool mount_supported(void) { - return dlopen_libmount() >= 0; + return dlopen_libmount(LOG_DEBUG) >= 0; } static int mount_subsystem_ratelimited(Manager *m) { diff --git a/src/core/namespace.c b/src/core/namespace.c index 1412ccce8bbcf..95b77e4fbf1f4 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -3984,7 +3984,7 @@ int refresh_extensions_in_namespace( if (r > 0) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Target namespace is not separate, cannot reload extensions"); - (void) dlopen_cryptsetup(); + (void) dlopen_cryptsetup(LOG_DEBUG); extension_dir = path_join(p->private_namespace_dir, "unit-extensions"); if (!extension_dir) diff --git a/src/core/selinux-setup.c b/src/core/selinux-setup.c index 17905de2c7f6a..d06c9f0293167 100644 --- a/src/core/selinux-setup.c +++ b/src/core/selinux-setup.c @@ -17,11 +17,9 @@ int mac_selinux_setup(bool *loaded_policy) { assert(loaded_policy); - r = dlopen_libselinux(); - if (r < 0) { - log_debug_errno(r, "No SELinux library available, skipping setup."); + r = dlopen_libselinux(LOG_DEBUG); + if (r < 0) return 0; - } mac_selinux_disable_logging(); diff --git a/src/creds/creds.c b/src/creds/creds.c index 6988d39ca4113..a133a27dd2b07 100644 --- a/src/creds/creds.c +++ b/src/creds/creds.c @@ -188,7 +188,7 @@ static int is_tmpfs_with_noswap(dev_t devno) { _cleanup_(mnt_free_tablep) struct libmnt_table *table = NULL; int r; - r = dlopen_libmount(); + r = dlopen_libmount(LOG_DEBUG); if (r < 0) return r; diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index b3fe28e2cf97e..973a954acd1c2 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -494,9 +494,9 @@ static int parse_argv(int argc, char *argv[]) { break; OPTION_LONG("make-archive", NULL, "Convert the DDI to an archive file"): - r = dlopen_libarchive(); + r = dlopen_libarchive(LOG_ERR); if (r < 0) - return log_error_errno(r, "Archive support not available (compiled without libarchive, or libarchive not installed?)."); + return r; arg_action = ACTION_MAKE_ARCHIVE; break; diff --git a/src/growfs/growfs.c b/src/growfs/growfs.c index 8481257ab7166..0d4097be38c6d 100644 --- a/src/growfs/growfs.c +++ b/src/growfs/growfs.c @@ -32,9 +32,9 @@ static int resize_crypt_luks_device(dev_t devno, const char *fstype, dev_t main_ uint64_t size; int r; - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_ERR); if (r < 0) - return log_error_errno(r, "Cannot resize LUKS device: %m"); + return r; main_devfd = r = device_open_from_devnum(S_IFBLK, main_devno, O_RDONLY|O_CLOEXEC, &main_devpath); if (r < 0) diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index 633d54b087810..3bf993ef53f2e 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -146,7 +146,7 @@ static int probe_file_system_by_fd( assert(ret_fstype); assert(ret_uuid); - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_DEBUG); if (r < 0) return r; @@ -529,7 +529,7 @@ static int acquire_open_luks_device( assert(setup); assert(!setup->crypt_device); - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_DEBUG); if (r < 0) return r; @@ -684,7 +684,7 @@ static int luks_validate( assert(ret_size); assert(sector_size > 0); - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_DEBUG); if (r < 0) return r; @@ -1289,7 +1289,7 @@ int home_setup_luks( assert(setup); assert(user_record_storage(h) == USER_LUKS); - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_DEBUG); if (r < 0) return r; @@ -1590,7 +1590,7 @@ int home_activate_luks( assert(setup); assert(ret_home); - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_DEBUG); if (r < 0) return r; @@ -2207,11 +2207,11 @@ int home_create_luks( assert(setup->image_fd < 0); assert(ret_home); - r = dlopen_fdisk(); + r = dlopen_fdisk(LOG_DEBUG); if (r < 0) return r; - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_DEBUG); if (r < 0) return r; @@ -3246,11 +3246,11 @@ int home_resize_luks( assert(user_record_storage(h) == USER_LUKS); assert(setup); - r = dlopen_fdisk(); + r = dlopen_fdisk(LOG_DEBUG); if (r < 0) return r; - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_DEBUG); if (r < 0) return r; @@ -3708,7 +3708,7 @@ int home_passwd_luks( assert(user_record_storage(h) == USER_LUKS); assert(setup); - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_DEBUG); if (r < 0) return r; diff --git a/src/home/homework.c b/src/home/homework.c index a6e7d3751a1cc..578d5cce914f3 100644 --- a/src/home/homework.c +++ b/src/home/homework.c @@ -1331,7 +1331,7 @@ static int determine_default_storage(UserStorage *ret) { if (r < 0) log_warning_errno(r, "Failed to determine if %s is encrypted, ignoring: %m", get_home_root()); - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_DEBUG); if (r < 0) log_info("Not using '%s' storage, since libcryptsetup could not be loaded.", user_storage_to_string(USER_LUKS)); else { diff --git a/src/home/pam_systemd_home.c b/src/home/pam_systemd_home.c index e8d7282cbf82f..1de66b4c0325c 100644 --- a/src/home/pam_systemd_home.c +++ b/src/home/pam_systemd_home.c @@ -789,7 +789,7 @@ _public_ PAM_EXTERN int pam_sm_authenticate( bool debug = false; int r; - r = dlopen_libpam(); + r = dlopen_libpam(LOG_DEBUG); if (r < 0) return PAM_SERVICE_ERR; @@ -854,7 +854,7 @@ _public_ PAM_EXTERN int pam_sm_open_session( bool debug = false; int r; - r = dlopen_libpam(); + r = dlopen_libpam(LOG_DEBUG); if (r < 0) return PAM_SERVICE_ERR; @@ -972,7 +972,7 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( usec_t t; int r; - r = dlopen_libpam(); + r = dlopen_libpam(LOG_DEBUG); if (r < 0) return PAM_SERVICE_ERR; @@ -1091,7 +1091,7 @@ _public_ PAM_EXTERN int pam_sm_chauthtok( bool debug = false; int r; - r = dlopen_libpam(); + r = dlopen_libpam(LOG_DEBUG); if (r < 0) return PAM_SERVICE_ERR; diff --git a/src/imds/imdsd.c b/src/imds/imdsd.c index 211565880c2ac..8ab3498656602 100644 --- a/src/imds/imdsd.c +++ b/src/imds/imdsd.c @@ -3070,7 +3070,7 @@ static int run(int argc, char* argv[]) { if (r <= 0) return r; - r = dlopen_curl(); + r = dlopen_curl(LOG_DEBUG); if (r < 0) return r; diff --git a/src/import/import-common.c b/src/import/import-common.c index 948cd82988ab2..5f17084f9fd94 100644 --- a/src/import/import-common.c +++ b/src/import/import-common.c @@ -32,7 +32,7 @@ int import_fork_tar_x(int tree_fd, int userns_fd, PidRef *ret_pid) { assert(tree_fd >= 0); assert(ret_pid); - r = dlopen_libarchive(); + r = dlopen_libarchive(LOG_DEBUG); if (r < 0) return r; @@ -99,7 +99,7 @@ int import_fork_tar_c(int tree_fd, int userns_fd, PidRef *ret_pid) { assert(tree_fd >= 0); assert(ret_pid); - r = dlopen_libarchive(); + r = dlopen_libarchive(LOG_DEBUG); if (r < 0) return r; diff --git a/src/import/test-tar.c b/src/import/test-tar.c index c0e55c3597edc..b7cfed9bf3c16 100644 --- a/src/import/test-tar.c +++ b/src/import/test-tar.c @@ -26,7 +26,7 @@ static int run(int argc, char **argv) { else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown operation '%s'.", argv[1]); - r = dlopen_libarchive(); + r = dlopen_libarchive(LOG_DEBUG); if (r < 0) return r; diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c index cd24dec34e348..23bb48f687620 100644 --- a/src/journal-remote/journal-upload.c +++ b/src/journal-remote/journal-upload.c @@ -924,7 +924,7 @@ static int run(int argc, char **argv) { if (r <= 0) return r; - r = dlopen_curl(); + r = dlopen_curl(LOG_DEBUG); if (r < 0) return r; diff --git a/src/libsystemd/sd-device/test-sd-device.c b/src/libsystemd/sd-device/test-sd-device.c index df1a6bc600785..bf62e9082b56e 100644 --- a/src/libsystemd/sd-device/test-sd-device.c +++ b/src/libsystemd/sd-device/test-sd-device.c @@ -898,7 +898,7 @@ static int intro(void) { if (path_is_mount_point("/sys") <= 0) return log_tests_skipped("/sys/ is not mounted"); - r = dlopen_libmount(); + r = dlopen_libmount(LOG_DEBUG); if (r < 0) return log_tests_skipped("libmount not available."); diff --git a/src/locale/xkbcommon-util.c b/src/locale/xkbcommon-util.c index c0810331a9e1f..a55316c73a940 100644 --- a/src/locale/xkbcommon-util.c +++ b/src/locale/xkbcommon-util.c @@ -16,14 +16,14 @@ DLSYM_PROTOTYPE(xkb_context_set_log_fn) = NULL; DLSYM_PROTOTYPE(xkb_keymap_new_from_names) = NULL; DLSYM_PROTOTYPE(xkb_keymap_unref) = NULL; -static int dlopen_xkbcommon(void) { +static int dlopen_xkbcommon(int log_level) { SD_ELF_NOTE_DLOPEN( "xkbcommon", "Support for keyboard locale descriptions", SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libxkbcommon.so.0"); return dlopen_many_sym_or_warn( - &xkbcommon_dl, "libxkbcommon.so.0", LOG_DEBUG, + &xkbcommon_dl, "libxkbcommon.so.0", log_level, DLSYM_ARG(xkb_context_new), DLSYM_ARG(xkb_context_unref), DLSYM_ARG(xkb_context_set_log_fn), @@ -57,7 +57,7 @@ int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, /* Compile keymap from RMLVO information to check out its validity */ - r = dlopen_xkbcommon(); + r = dlopen_xkbcommon(LOG_DEBUG); if (r < 0) return r; diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index 6c70b8b1af158..4e571e705655d 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -1750,7 +1750,7 @@ _public_ PAM_EXTERN int pam_sm_open_session( assert(pamh); - r = dlopen_libpam(); + r = dlopen_libpam(LOG_DEBUG); if (r < 0) return PAM_SERVICE_ERR; diff --git a/src/login/pam_systemd_loadkey.c b/src/login/pam_systemd_loadkey.c index 3e0df8c86b31b..93ccddc25b6a7 100644 --- a/src/login/pam_systemd_loadkey.c +++ b/src/login/pam_systemd_loadkey.c @@ -19,7 +19,7 @@ _public_ PAM_EXTERN int pam_sm_authenticate( assert(pamh); - r = dlopen_libpam(); + r = dlopen_libpam(LOG_DEBUG); if (r < 0) return PAM_SERVICE_ERR; diff --git a/src/network/networkd-sysctl.c b/src/network/networkd-sysctl.c index 81e49c860d2ab..0801ba977a643 100644 --- a/src/network/networkd-sysctl.c +++ b/src/network/networkd-sysctl.c @@ -108,7 +108,7 @@ int manager_install_sysctl_monitor(Manager *manager) { assert(manager); - r = dlopen_bpf(); + r = dlopen_bpf(LOG_DEBUG); if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) return log_debug_errno(r, "sysctl monitor disabled, as BPF support is not available."); if (r < 0) diff --git a/src/nspawn/nspawn-oci.c b/src/nspawn/nspawn-oci.c index 5cbc58969f186..bd28a67e6b14c 100644 --- a/src/nspawn/nspawn-oci.c +++ b/src/nspawn/nspawn-oci.c @@ -1826,7 +1826,7 @@ static int oci_seccomp(const char *name, sd_json_variant *v, sd_json_dispatch_fl if (r < 0) return json_log(def, flags, r, "Unknown default action: %s", sd_json_variant_string(def)); - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return json_log(def, flags, r, "No support for libseccomp: %m"); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 438346a5456e4..c7ccfd49963d3 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -5961,9 +5961,9 @@ static int run(int argc, char *argv[]) { if (arg_cleanup) return do_cleanup(); - (void) dlopen_libmount(); - (void) dlopen_libseccomp(); - (void) dlopen_libselinux(); + (void) dlopen_libmount(LOG_DEBUG); + (void) dlopen_libseccomp(LOG_DEBUG); + (void) dlopen_libselinux(LOG_DEBUG); r = cg_has_legacy(); if (r < 0) diff --git a/src/nsresourced/userns-restrict.c b/src/nsresourced/userns-restrict.c index d9b7940f63085..11bb2e7d8fd36 100644 --- a/src/nsresourced/userns-restrict.c +++ b/src/nsresourced/userns-restrict.c @@ -68,7 +68,7 @@ int userns_restrict_install( if (r == 0) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "bpf-lsm not supported, can't lock down user namespace."); - r = dlopen_bpf(); + r = dlopen_bpf(LOG_DEBUG); if (r < 0) return r; diff --git a/src/portable/portable.c b/src/portable/portable.c index 5e60ad4694fda..ac84fb58d14d3 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -610,8 +610,8 @@ static int portable_extract_by_path( * there, and extract the metadata we need. The metadata is sent from the child back to us. */ /* Load some libraries before we fork workers off that want to use them */ - (void) dlopen_cryptsetup(); - (void) dlopen_libmount(); + (void) dlopen_cryptsetup(LOG_DEBUG); + (void) dlopen_libmount(LOG_DEBUG); r = mkdtemp_malloc("/tmp/inspect-XXXXXX", &tmpdir); if (r < 0) diff --git a/src/repart/repart.c b/src/repart/repart.c index d66eea69875ec..e307bfe13f280 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -3022,7 +3022,7 @@ static int partition_read_definition( "Cannot format %s filesystem without source files, refusing.", p->format); if (p->verity != VERITY_OFF || p->encrypt != ENCRYPT_OFF) { - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_DEBUG); if (r < 0) return log_syntax(NULL, LOG_ERR, path, 1, r, "libcryptsetup not found, Verity=/Encrypt= are not supported: %m"); @@ -4614,9 +4614,9 @@ static int context_wipe_range(Context *context, uint64_t offset, uint64_t size) assert(offset != UINT64_MAX); assert(size != UINT64_MAX); - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_ERR); if (r < 0) - return log_error_errno(r, "Failed to load libblkid: %m"); + return r; probe = sym_blkid_new_probe(); if (!probe) @@ -5200,9 +5200,9 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta assert(p); assert(p->encrypt != ENCRYPT_OFF); - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_ERR); if (r < 0) - return log_error_errno(r, "libcryptsetup not found, cannot encrypt: %m"); + return r; log_info("Encrypting future partition %" PRIu64 "...", p->partno); @@ -5733,9 +5733,9 @@ static int partition_format_verity_hash( (void) partition_hint(p, node, &hint); - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_ERR); if (r < 0) - return log_error_errno(r, "libcryptsetup not found, cannot setup verity: %m"); + return r; if (!node) { r = partition_target_prepare(context, p, p->new_size, /* need_path= */ true, &t); @@ -6918,7 +6918,7 @@ static int partition_populate_filesystem(Context *context, Partition *p, const c * appear in the host namespace. Hence we fork a child that has its own file system namespace and * detached mount propagation. */ - (void) dlopen_libmount(); + (void) dlopen_libmount(LOG_DEBUG); r = pidref_safe_fork( "(sd-copy)", @@ -8358,9 +8358,9 @@ static int resolve_copy_blocks_auto_candidate( return log_error_errno(r, "Failed to open block device " DEVNUM_FORMAT_STR ": %m", DEVNUM_FORMAT_VAL(whole_devno)); - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_ERR); if (r < 0) - return log_error_errno(r, "Failed to find libblkid: %m"); + return r; b = sym_blkid_new_probe(); if (!b) @@ -11218,7 +11218,7 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - r = dlopen_fdisk(); + r = dlopen_fdisk(LOG_DEBUG); if (r < 0) return r; diff --git a/src/report/report-upload.c b/src/report/report-upload.c index e70f8efa3430f..c64bf86e13336 100644 --- a/src/report/report-upload.c +++ b/src/report/report-upload.c @@ -85,7 +85,7 @@ int upload_collected(Context *context) { _cleanup_free_ char *json = NULL; int r; - r = dlopen_curl(); + r = dlopen_curl(LOG_DEBUG); if (r < 0) return r; diff --git a/src/resolve/resolved-util.c b/src/resolve/resolved-util.c index eec28197676c9..094ef00698513 100644 --- a/src/resolve/resolved-util.c +++ b/src/resolve/resolved-util.c @@ -36,7 +36,7 @@ int resolve_system_hostname(char **full_hostname, char **first_label) { #if HAVE_LIBIDN2 _cleanup_free_ char *utf8 = NULL; - if (dlopen_idn() >= 0) { + if (dlopen_idn(LOG_DEBUG) >= 0) { r = sym_idn2_to_unicode_8z8z(label, &utf8, 0); if (r != IDN2_OK) return log_debug_errno(SYNTHETIC_ERRNO(EUCLEAN), diff --git a/src/shared/acl-util.c b/src/shared/acl-util.c index 07206bdb5f61c..92d920e171a72 100644 --- a/src/shared/acl-util.c +++ b/src/shared/acl-util.c @@ -10,6 +10,7 @@ #include "errno-util.h" #include "extract-word.h" #include "fd-util.h" +#include "log.h" /* IWYU pragma: keep */ #include "set.h" #include "string-util.h" #include "strv.h" @@ -44,8 +45,10 @@ DLSYM_PROTOTYPE(acl_set_permset); DLSYM_PROTOTYPE(acl_set_qualifier); DLSYM_PROTOTYPE(acl_set_tag_type); DLSYM_PROTOTYPE(acl_to_any_text); +#endif -int dlopen_libacl(void) { +int dlopen_libacl(int log_level) { +#if HAVE_ACL SD_ELF_NOTE_DLOPEN( "acl", "Support for file Access Control Lists (ACLs)", @@ -55,7 +58,7 @@ int dlopen_libacl(void) { return dlopen_many_sym_or_warn( &libacl_dl, "libacl.so.1", - LOG_DEBUG, + log_level, DLSYM_ARG(acl_add_perm), DLSYM_ARG(acl_calc_mask), DLSYM_ARG(acl_copy_entry), @@ -82,8 +85,13 @@ int dlopen_libacl(void) { DLSYM_ARG(acl_set_qualifier), DLSYM_ARG(acl_set_tag_type), DLSYM_ARG(acl_to_any_text)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libacl support is not compiled in."); +#endif } +#if HAVE_ACL int devnode_acl(int fd, const Set *uids) { _cleanup_set_free_ Set *found = NULL; bool changed = false; @@ -91,7 +99,7 @@ int devnode_acl(int fd, const Set *uids) { assert(fd >= 0); - r = dlopen_libacl(); + r = dlopen_libacl(LOG_DEBUG); if (r < 0) return r; @@ -336,7 +344,7 @@ int acl_search_groups(const char *path, char ***ret_groups) { assert(path); - r = dlopen_libacl(); + r = dlopen_libacl(LOG_DEBUG); if (r < 0) return r; @@ -413,7 +421,7 @@ int parse_acl( if (!split) return -ENOMEM; - r = dlopen_libacl(); + r = dlopen_libacl(LOG_DEBUG); if (r < 0) return r; @@ -591,7 +599,7 @@ int acls_for_file(const char *path, acl_type_t type, acl_t acl, acl_t *ret) { assert(path); - r = dlopen_libacl(); + r = dlopen_libacl(LOG_DEBUG); if (r < 0) return r; @@ -650,7 +658,7 @@ int fd_add_uid_acl_permission( assert(fd >= 0); assert(uid_is_valid(uid)); - r = dlopen_libacl(); + r = dlopen_libacl(LOG_DEBUG); if (r < 0) return r; @@ -716,7 +724,7 @@ int fd_acl_make_read_only(int fd) { /* Safely drops all W bits from all relevant ACL entries of the file, without changing entries which * are masked by the ACL mask */ - r = dlopen_libacl(); + r = dlopen_libacl(LOG_DEBUG); if (r < 0) goto maybe_fallback; @@ -804,7 +812,7 @@ int fd_acl_make_writable(int fd) { /* Safely adds the writable bit to the owner's ACL entry of this inode. (And only the owner's! – This * not the obvious inverse of fd_acl_make_read_only() hence!) */ - r = dlopen_libacl(); + r = dlopen_libacl(LOG_DEBUG); if (r < 0) goto maybe_fallback; diff --git a/src/shared/acl-util.h b/src/shared/acl-util.h index 1b74101ae44a6..5e2aeb94a9f63 100644 --- a/src/shared/acl-util.h +++ b/src/shared/acl-util.h @@ -36,8 +36,6 @@ extern DLSYM_PROTOTYPE(acl_set_qualifier); extern DLSYM_PROTOTYPE(acl_set_tag_type); extern DLSYM_PROTOTYPE(acl_to_any_text); -int dlopen_libacl(void); - int devnode_acl(int fd, const Set *uids); int calc_acl_mask_if_needed(acl_t *acl_p); @@ -85,10 +83,6 @@ typedef unsigned acl_type_t; #define ACL_TYPE_ACCESS (0x8000) #define ACL_TYPE_DEFAULT (0x4000) -static inline int dlopen_libacl(void) { - return -EOPNOTSUPP; -} - static inline int devnode_acl(int fd, const Set *uids) { return -EOPNOTSUPP; } @@ -98,6 +92,8 @@ static inline int fd_add_uid_acl_permission(int fd, uid_t uid, unsigned mask) { } #endif +int dlopen_libacl(int log_level); + int fd_acl_make_read_only(int fd); int fd_acl_make_writable(int fd); diff --git a/src/shared/apparmor-util.c b/src/shared/apparmor-util.c index e24f4315a0246..5f01bfae01651 100644 --- a/src/shared/apparmor-util.c +++ b/src/shared/apparmor-util.c @@ -1,13 +1,16 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "apparmor-util.h" +#include "log.h" + +#if HAVE_APPARMOR + #include #include "sd-dlopen.h" #include "alloc-util.h" -#include "apparmor-util.h" #include "fileio.h" -#include "log.h" #include "parse-util.h" static void *libapparmor_dl = NULL; @@ -21,27 +24,6 @@ DLSYM_PROTOTYPE(aa_policy_cache_new) = NULL; DLSYM_PROTOTYPE(aa_policy_cache_replace_all) = NULL; DLSYM_PROTOTYPE(aa_policy_cache_unref) = NULL; -int dlopen_libapparmor(void) { - SD_ELF_NOTE_DLOPEN( - "apparmor", - "Support for AppArmor policies", - SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, - "libapparmor.so.1"); - - return dlopen_many_sym_or_warn( - &libapparmor_dl, - "libapparmor.so.1", - LOG_DEBUG, - DLSYM_ARG(aa_change_onexec), - DLSYM_ARG(aa_change_profile), - DLSYM_ARG(aa_features_new_from_kernel), - DLSYM_ARG(aa_features_unref), - DLSYM_ARG(aa_policy_cache_dir_path_preview), - DLSYM_ARG(aa_policy_cache_new), - DLSYM_ARG(aa_policy_cache_replace_all), - DLSYM_ARG(aa_policy_cache_unref)); -} - bool mac_apparmor_use(void) { static int cached_use = -1; int r; @@ -63,8 +45,36 @@ bool mac_apparmor_use(void) { if (r <= 0) return (cached_use = false); - if (dlopen_libapparmor() < 0) + if (dlopen_libapparmor(LOG_DEBUG) < 0) return (cached_use = false); return (cached_use = true); } + +#endif + +int dlopen_libapparmor(int log_level) { +#if HAVE_APPARMOR + SD_ELF_NOTE_DLOPEN( + "apparmor", + "Support for AppArmor policies", + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + "libapparmor.so.1"); + + return dlopen_many_sym_or_warn( + &libapparmor_dl, + "libapparmor.so.1", + log_level, + DLSYM_ARG(aa_change_onexec), + DLSYM_ARG(aa_change_profile), + DLSYM_ARG(aa_features_new_from_kernel), + DLSYM_ARG(aa_features_unref), + DLSYM_ARG(aa_policy_cache_dir_path_preview), + DLSYM_ARG(aa_policy_cache_new), + DLSYM_ARG(aa_policy_cache_replace_all), + DLSYM_ARG(aa_policy_cache_unref)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libapparmor support is not compiled in."); +#endif +} diff --git a/src/shared/apparmor-util.h b/src/shared/apparmor-util.h index 06d6bf30e27c2..e87ba84504d7d 100644 --- a/src/shared/apparmor-util.h +++ b/src/shared/apparmor-util.h @@ -19,14 +19,11 @@ extern DLSYM_PROTOTYPE(aa_policy_cache_unref); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(aa_features*, sym_aa_features_unref, aa_features_unrefp, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(aa_policy_cache*, sym_aa_policy_cache_unref, aa_policy_cache_unrefp, NULL); - -int dlopen_libapparmor(void); bool mac_apparmor_use(void); #else -static inline int dlopen_libapparmor(void) { - return -EOPNOTSUPP; -} static inline bool mac_apparmor_use(void) { return false; } #endif + +int dlopen_libapparmor(int log_level); diff --git a/src/shared/blkid-util.c b/src/shared/blkid-util.c index f1b7ccdfeb93f..18bf100d064d1 100644 --- a/src/shared/blkid-util.c +++ b/src/shared/blkid-util.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-dlopen.h" #include "sd-id128.h" #include "blkid-util.h" +#include "log.h" /* IWYU pragma: keep */ #include "parse-util.h" #include "string-util.h" @@ -49,55 +48,6 @@ DLSYM_PROTOTYPE(blkid_probe_set_sectorsize) = NULL; DLSYM_PROTOTYPE(blkid_probe_set_superblocks_flags) = NULL; DLSYM_PROTOTYPE(blkid_safe_string) = NULL; -int dlopen_libblkid(void) { - SD_ELF_NOTE_DLOPEN( - "blkid", - "Support for block device identification", - SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, - "libblkid.so.1"); - - return dlopen_many_sym_or_warn( - &libblkid_dl, - "libblkid.so.1", - LOG_DEBUG, - DLSYM_ARG(blkid_do_fullprobe), - DLSYM_ARG(blkid_do_probe), - DLSYM_ARG(blkid_do_safeprobe), - DLSYM_ARG(blkid_do_wipe), - DLSYM_ARG(blkid_encode_string), - DLSYM_ARG(blkid_free_probe), - DLSYM_ARG(blkid_new_probe), - DLSYM_ARG(blkid_new_probe_from_filename), - DLSYM_ARG(blkid_partition_get_flags), - DLSYM_ARG(blkid_partition_get_name), - DLSYM_ARG(blkid_partition_get_partno), - DLSYM_ARG(blkid_partition_get_size), - DLSYM_ARG(blkid_partition_get_start), - DLSYM_ARG(blkid_partition_get_type), - DLSYM_ARG(blkid_partition_get_type_string), - DLSYM_ARG(blkid_partition_get_uuid), - DLSYM_ARG(blkid_partlist_devno_to_partition), - DLSYM_ARG(blkid_partlist_get_partition), - DLSYM_ARG(blkid_partlist_numof_partitions), - DLSYM_ARG(blkid_probe_enable_partitions), - DLSYM_ARG(blkid_probe_enable_superblocks), - DLSYM_ARG(blkid_probe_filter_superblocks_type), - DLSYM_ARG(blkid_probe_filter_superblocks_usage), - DLSYM_ARG(blkid_probe_get_fd), - DLSYM_ARG(blkid_probe_get_partitions), - DLSYM_ARG(blkid_probe_get_size), - DLSYM_ARG(blkid_probe_get_value), - DLSYM_ARG(blkid_probe_is_wholedisk), - DLSYM_ARG(blkid_probe_lookup_value), - DLSYM_ARG(blkid_probe_numof_values), - DLSYM_ARG(blkid_probe_set_device), - DLSYM_ARG(blkid_probe_set_hint), - DLSYM_ARG(blkid_probe_set_partitions_flags), - DLSYM_ARG(blkid_probe_set_sectorsize), - DLSYM_ARG(blkid_probe_set_superblocks_flags), - DLSYM_ARG(blkid_safe_string)); -} - int blkid_partition_get_uuid_id128(blkid_partition p, sd_id128_t *ret) { const char *s; @@ -146,3 +96,57 @@ int blkid_probe_lookup_value_u64(blkid_probe b, const char *field, uint64_t *ret return safe_atou64(u, ret); } #endif + +int dlopen_libblkid(int log_level) { +#if HAVE_BLKID + SD_ELF_NOTE_DLOPEN( + "blkid", + "Support for block device identification", + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + "libblkid.so.1"); + + return dlopen_many_sym_or_warn( + &libblkid_dl, + "libblkid.so.1", + log_level, + DLSYM_ARG(blkid_do_fullprobe), + DLSYM_ARG(blkid_do_probe), + DLSYM_ARG(blkid_do_safeprobe), + DLSYM_ARG(blkid_do_wipe), + DLSYM_ARG(blkid_encode_string), + DLSYM_ARG(blkid_free_probe), + DLSYM_ARG(blkid_new_probe), + DLSYM_ARG(blkid_new_probe_from_filename), + DLSYM_ARG(blkid_partition_get_flags), + DLSYM_ARG(blkid_partition_get_name), + DLSYM_ARG(blkid_partition_get_partno), + DLSYM_ARG(blkid_partition_get_size), + DLSYM_ARG(blkid_partition_get_start), + DLSYM_ARG(blkid_partition_get_type), + DLSYM_ARG(blkid_partition_get_type_string), + DLSYM_ARG(blkid_partition_get_uuid), + DLSYM_ARG(blkid_partlist_devno_to_partition), + DLSYM_ARG(blkid_partlist_get_partition), + DLSYM_ARG(blkid_partlist_numof_partitions), + DLSYM_ARG(blkid_probe_enable_partitions), + DLSYM_ARG(blkid_probe_enable_superblocks), + DLSYM_ARG(blkid_probe_filter_superblocks_type), + DLSYM_ARG(blkid_probe_filter_superblocks_usage), + DLSYM_ARG(blkid_probe_get_fd), + DLSYM_ARG(blkid_probe_get_partitions), + DLSYM_ARG(blkid_probe_get_size), + DLSYM_ARG(blkid_probe_get_value), + DLSYM_ARG(blkid_probe_is_wholedisk), + DLSYM_ARG(blkid_probe_lookup_value), + DLSYM_ARG(blkid_probe_numof_values), + DLSYM_ARG(blkid_probe_set_device), + DLSYM_ARG(blkid_probe_set_hint), + DLSYM_ARG(blkid_probe_set_partitions_flags), + DLSYM_ARG(blkid_probe_set_sectorsize), + DLSYM_ARG(blkid_probe_set_superblocks_flags), + DLSYM_ARG(blkid_safe_string)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libblkid support is not compiled in."); +#endif +} diff --git a/src/shared/blkid-util.h b/src/shared/blkid-util.h index 09502eadc4e9a..718a0f15a917f 100644 --- a/src/shared/blkid-util.h +++ b/src/shared/blkid-util.h @@ -46,8 +46,6 @@ extern DLSYM_PROTOTYPE(blkid_probe_set_sectorsize); extern DLSYM_PROTOTYPE(blkid_probe_set_superblocks_flags); extern DLSYM_PROTOTYPE(blkid_safe_string); -int dlopen_libblkid(void); - DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(blkid_probe, sym_blkid_free_probe, blkid_free_probep, NULL); int blkid_partition_get_uuid_id128(blkid_partition p, sd_id128_t *ret); @@ -65,8 +63,6 @@ enum { int blkid_probe_lookup_value_id128(blkid_probe b, const char *field, sd_id128_t *ret); int blkid_probe_lookup_value_u64(blkid_probe b, const char *field, uint64_t *ret); -#else -static inline int dlopen_libblkid(void) { - return -EOPNOTSUPP; -} #endif + +int dlopen_libblkid(int log_level); diff --git a/src/shared/bpf-dlopen.c b/src/shared/bpf-dlopen.c index c8e2e7ce4d812..1d2fdef781eea 100644 --- a/src/shared/bpf-dlopen.c +++ b/src/shared/bpf-dlopen.c @@ -76,7 +76,7 @@ static int bpf_print_func(enum libbpf_print_level level, const char *fmt, va_lis return log_internalv(LOG_DEBUG, errno, NULL, 0, NULL, fmt, ap); } -int dlopen_bpf_full(int log_level) { +int dlopen_bpf(int log_level) { static int cached = 0; int r; @@ -210,7 +210,7 @@ int bpf_get_error_translated(const void *ptr) { #else -int dlopen_bpf_full(int log_level) { +int dlopen_bpf(int log_level) { return log_once_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), "libbpf support is not compiled in, cgroup BPF features disabled."); } diff --git a/src/shared/bpf-dlopen.h b/src/shared/bpf-dlopen.h index ecae890035436..b3d14f9b5f437 100644 --- a/src/shared/bpf-dlopen.h +++ b/src/shared/bpf-dlopen.h @@ -51,7 +51,4 @@ int bpf_get_error_translated(const void *ptr); #endif -int dlopen_bpf_full(int log_level); -static inline int dlopen_bpf(void) { - return dlopen_bpf_full(LOG_DEBUG); -} +int dlopen_bpf(int log_level); diff --git a/src/shared/bpf-link.c b/src/shared/bpf-link.c index 72d374c235997..95f7256a56795 100644 --- a/src/shared/bpf-link.c +++ b/src/shared/bpf-link.c @@ -9,7 +9,7 @@ bool bpf_can_link_program(struct bpf_program *prog) { assert(prog); - if (dlopen_bpf() < 0) + if (dlopen_bpf(LOG_DEBUG) < 0) return false; /* Pass invalid cgroup fd intentionally. */ diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c index ba7910d864cc0..fd5ffd706dca5 100644 --- a/src/shared/cryptsetup-util.c +++ b/src/shared/cryptsetup-util.c @@ -98,7 +98,7 @@ void cryptsetup_enable_logging(struct crypt_device *cd) { * endless loop, but isn't because we break it via the check for 'cryptsetup_dl' early in * dlopen_cryptsetup(). */ - if (dlopen_cryptsetup() < 0) + if (dlopen_cryptsetup(LOG_DEBUG) < 0) return; /* If this fails, let's gracefully ignore the issue, this is just debug logging after * all, and if this failed we already generated a debug log message that should help * to track things down. */ @@ -124,7 +124,7 @@ int cryptsetup_set_minimal_pbkdf(struct crypt_device *cd) { /* Sets a minimal PKBDF in case we already have a high entropy key. */ - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_DEBUG); if (r < 0) return r; @@ -155,7 +155,7 @@ int cryptsetup_get_token_as_json( * -EMEDIUMTYPE → "verify_type" specified and doesn't match token's type */ - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_DEBUG); if (r < 0) return r; @@ -188,7 +188,7 @@ int cryptsetup_add_token_json(struct crypt_device *cd, sd_json_variant *v) { _cleanup_free_ char *text = NULL; int r; - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_DEBUG); if (r < 0) return r; @@ -268,7 +268,7 @@ int cryptsetup_get_volume_key_id( } #endif -int dlopen_cryptsetup(void) { +int dlopen_cryptsetup(int log_level) { #if HAVE_LIBCRYPTSETUP int r; @@ -284,7 +284,7 @@ int dlopen_cryptsetup(void) { "libcryptsetup.so.12"); r = dlopen_many_sym_or_warn( - &cryptsetup_dl, "libcryptsetup.so.12", LOG_DEBUG, + &cryptsetup_dl, "libcryptsetup.so.12", log_level, DLSYM_ARG(crypt_activate_by_passphrase), DLSYM_ARG(crypt_activate_by_signed_key), DLSYM_ARG(crypt_activate_by_volume_key), @@ -355,7 +355,8 @@ int dlopen_cryptsetup(void) { return 1; #else - return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "cryptsetup support is not compiled in."); + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libcryptsetup support is not compiled in."); #endif } diff --git a/src/shared/cryptsetup-util.h b/src/shared/cryptsetup-util.h index 2e3ffe4c9e384..27e704869fe5a 100644 --- a/src/shared/cryptsetup-util.h +++ b/src/shared/cryptsetup-util.h @@ -73,7 +73,7 @@ int cryptsetup_get_volume_key_id(struct crypt_device *cd, const char *volume_nam size_t volume_key_size, char **ret); #endif -int dlopen_cryptsetup(void); +int dlopen_cryptsetup(int log_level); int cryptsetup_get_keyslot_from_token(sd_json_variant *v); diff --git a/src/shared/curl-util.c b/src/shared/curl-util.c index 59f9f16ae3776..9254b83dd74fb 100644 --- a/src/shared/curl-util.c +++ b/src/shared/curl-util.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "curl-util.h" +#include "log.h" #if HAVE_LIBCURL @@ -11,7 +12,6 @@ #include "dlfcn-util.h" #include "fd-util.h" #include "hashmap.h" -#include "log.h" #include "string-util.h" #include "strv.h" #include "time-util.h" @@ -40,39 +40,6 @@ DLSYM_PROTOTYPE(curl_multi_socket_action) = NULL; DLSYM_PROTOTYPE(curl_slist_append) = NULL; DLSYM_PROTOTYPE(curl_slist_free_all) = NULL; -int dlopen_curl(void) { - SD_ELF_NOTE_DLOPEN( - "curl", - "Support for downloading and uploading files over HTTP", - SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, - "libcurl.so.4"); - - return dlopen_many_sym_or_warn( - &curl_dl, - "libcurl.so.4", - LOG_DEBUG, - DLSYM_ARG(curl_easy_cleanup), - DLSYM_ARG(curl_easy_getinfo), - DLSYM_ARG(curl_easy_init), - DLSYM_ARG(curl_easy_perform), - DLSYM_ARG(curl_easy_setopt), - DLSYM_ARG(curl_easy_strerror), -#if LIBCURL_VERSION_NUM >= 0x075300 - DLSYM_ARG(curl_easy_header), -#endif - DLSYM_ARG(curl_getdate), - DLSYM_ARG(curl_multi_add_handle), - DLSYM_ARG(curl_multi_assign), - DLSYM_ARG(curl_multi_cleanup), - DLSYM_ARG(curl_multi_info_read), - DLSYM_ARG(curl_multi_init), - DLSYM_ARG(curl_multi_remove_handle), - DLSYM_ARG(curl_multi_setopt), - DLSYM_ARG(curl_multi_socket_action), - DLSYM_ARG(curl_slist_append), - DLSYM_ARG(curl_slist_free_all)); -} - static void curl_glue_check_finished(CurlGlue *g) { int r; @@ -271,7 +238,7 @@ int curl_glue_new(CurlGlue **glue, sd_event *event) { assert(glue); - r = dlopen_curl(); + r = dlopen_curl(LOG_DEBUG); if (r < 0) return r; @@ -327,7 +294,7 @@ int curl_glue_make(CURL **ret, const char *url, void *userdata) { assert(ret); assert(url); - r = dlopen_curl(); + r = dlopen_curl(LOG_DEBUG); if (r < 0) return r; @@ -496,3 +463,41 @@ int curl_append_to_header(struct curl_slist **list, char **headers) { } #endif + +int dlopen_curl(int log_level) { +#if HAVE_LIBCURL + SD_ELF_NOTE_DLOPEN( + "curl", + "Support for downloading and uploading files over HTTP", + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libcurl.so.4"); + + return dlopen_many_sym_or_warn( + &curl_dl, + "libcurl.so.4", + log_level, + DLSYM_ARG(curl_easy_cleanup), + DLSYM_ARG(curl_easy_getinfo), + DLSYM_ARG(curl_easy_init), + DLSYM_ARG(curl_easy_perform), + DLSYM_ARG(curl_easy_setopt), + DLSYM_ARG(curl_easy_strerror), +#if LIBCURL_VERSION_NUM >= 0x075300 + DLSYM_ARG(curl_easy_header), +#endif + DLSYM_ARG(curl_getdate), + DLSYM_ARG(curl_multi_add_handle), + DLSYM_ARG(curl_multi_assign), + DLSYM_ARG(curl_multi_cleanup), + DLSYM_ARG(curl_multi_info_read), + DLSYM_ARG(curl_multi_init), + DLSYM_ARG(curl_multi_remove_handle), + DLSYM_ARG(curl_multi_setopt), + DLSYM_ARG(curl_multi_socket_action), + DLSYM_ARG(curl_slist_append), + DLSYM_ARG(curl_slist_free_all)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libcurl support is not compiled in."); +#endif +} diff --git a/src/shared/curl-util.h b/src/shared/curl-util.h index 4ca3faf602828..112649f371ba7 100644 --- a/src/shared/curl-util.h +++ b/src/shared/curl-util.h @@ -29,8 +29,6 @@ extern DLSYM_PROTOTYPE(curl_multi_socket_action); extern DLSYM_PROTOTYPE(curl_slist_append); extern DLSYM_PROTOTYPE(curl_slist_free_all); -int dlopen_curl(void); - #define easy_setopt(curl, log_level, opt, value) ({ \ CURLcode code = sym_curl_easy_setopt(ASSERT_PTR(curl), opt, value); \ if (code) \ @@ -71,10 +69,6 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(CURL*, sym_curl_easy_cleanup, curl_easy_ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(CURLM*, sym_curl_multi_cleanup, curl_multi_cleanupp, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct curl_slist*, sym_curl_slist_free_all, curl_slist_free_allp, NULL); -#else - -static inline int dlopen_curl(void) { - return -EOPNOTSUPP; -} - #endif + +int dlopen_curl(int log_level); diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index e33e78d31026d..043c7060fb3de 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -239,7 +239,7 @@ int probe_filesystem_full( assert(fd >= 0 || path); assert(ret_fstype); - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_DEBUG); if (r < 0) return r; @@ -1076,7 +1076,7 @@ static int dissect_image( } } - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_DEBUG); if (r < 0) return r; @@ -2945,7 +2945,7 @@ static int decrypt_partition( if (!FLAGS_SET(policy_flags, PARTITION_POLICY_ENCRYPTED)) return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), "Attempted to unlock partition via LUKS, but it's prohibited."); - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_DEBUG); if (r < 0) return r; @@ -3313,7 +3313,7 @@ static int verity_partition( return 0; } - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_DEBUG); if (r < 0) return r; @@ -4176,7 +4176,7 @@ int dissected_image_acquire_metadata( assert(m); - r = dlopen_libmount(); + r = dlopen_libmount(LOG_DEBUG); if (r < 0) return r; diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index 727e1d0625ba3..c90da5fc465f2 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -1267,7 +1267,7 @@ int dns_name_apply_idna(const char *name, char **ret) { #if HAVE_LIBIDN2 int r; - r = dlopen_idn(); + r = dlopen_idn(LOG_DEBUG); if (r == -EOPNOTSUPP) { *ret = NULL; return 0; diff --git a/src/shared/elf-util.c b/src/shared/elf-util.c index be9e673a511d9..97188ca41483e 100644 --- a/src/shared/elf-util.c +++ b/src/shared/elf-util.c @@ -91,7 +91,7 @@ static DLSYM_PROTOTYPE(gelf_getnote) = NULL; #endif -int dlopen_dw(void) { +int dlopen_dw(int log_level) { #if HAVE_ELFUTILS int r; @@ -102,7 +102,7 @@ int dlopen_dw(void) { "libdw.so.1"); r = dlopen_many_sym_or_warn( - &dw_dl, "libdw.so.1", LOG_DEBUG, + &dw_dl, "libdw.so.1", log_level, DLSYM_ARG(dwarf_getscopes), DLSYM_ARG(dwarf_getscopes_die), DLSYM_ARG(dwarf_tag), @@ -141,11 +141,12 @@ int dlopen_dw(void) { return 1; #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libdw support is not compiled in."); #endif } -int dlopen_elf(void) { +int dlopen_elf(int log_level) { #if HAVE_ELFUTILS int r; @@ -156,7 +157,7 @@ int dlopen_elf(void) { "libelf.so.1"); r = dlopen_many_sym_or_warn( - &elf_dl, "libelf.so.1", LOG_DEBUG, + &elf_dl, "libelf.so.1", log_level, DLSYM_ARG(elf_begin), DLSYM_ARG(elf_end), DLSYM_ARG(elf_getphdrnum), @@ -173,7 +174,8 @@ int dlopen_elf(void) { return 1; #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libelf support is not compiled in."); #endif } @@ -824,11 +826,11 @@ int parse_elf_object( assert(fd >= 0); - r = dlopen_dw(); + r = dlopen_dw(LOG_DEBUG); if (r < 0) return r; - r = dlopen_elf(); + r = dlopen_elf(LOG_DEBUG); if (r < 0) return r; diff --git a/src/shared/elf-util.h b/src/shared/elf-util.h index b5c3db80ee217..e9d9e959dab3d 100644 --- a/src/shared/elf-util.h +++ b/src/shared/elf-util.h @@ -3,8 +3,8 @@ #include "shared-forward.h" -int dlopen_dw(void); -int dlopen_elf(void); +int dlopen_dw(int log_level); +int dlopen_elf(int log_level); /* Parse an ELF object in a forked process, so that errors while iterating over * untrusted and potentially malicious data do not propagate to the main caller's process. diff --git a/src/shared/fdisk-util.c b/src/shared/fdisk-util.c index 8b1cb3c80f0fa..5eaa91160acd4 100644 --- a/src/shared/fdisk-util.c +++ b/src/shared/fdisk-util.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "fdisk-util.h" +#include "log.h" #if HAVE_LIBFDISK @@ -12,7 +13,6 @@ #include "dlfcn-util.h" #include "extract-word.h" #include "fd-util.h" -#include "log.h" #include "parse-util.h" #include "string-util.h" @@ -78,8 +78,10 @@ DLSYM_PROTOTYPE(fdisk_unref_partition) = NULL; DLSYM_PROTOTYPE(fdisk_unref_parttype) = NULL; DLSYM_PROTOTYPE(fdisk_unref_table) = NULL; DLSYM_PROTOTYPE(fdisk_write_disklabel) = NULL; +#endif -int dlopen_fdisk(void) { +int dlopen_fdisk(int log_level) { +#if HAVE_LIBFDISK SD_ELF_NOTE_DLOPEN( "fdisk", "Support for reading and writing partition tables", @@ -89,7 +91,7 @@ int dlopen_fdisk(void) { return dlopen_many_sym_or_warn( &fdisk_dl, "libfdisk.so.1", - LOG_DEBUG, + log_level, DLSYM_ARG(fdisk_add_partition), DLSYM_ARG(fdisk_apply_table), DLSYM_ARG(fdisk_ask_get_type), @@ -150,8 +152,13 @@ int dlopen_fdisk(void) { DLSYM_ARG(fdisk_unref_parttype), DLSYM_ARG(fdisk_unref_table), DLSYM_ARG(fdisk_write_disklabel)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libfdisk support is not compiled in."); +#endif } +#if HAVE_LIBFDISK int fdisk_new_context_at( int dir_fd, const char *path, diff --git a/src/shared/fdisk-util.h b/src/shared/fdisk-util.h index d3d7fda33b476..bdac11c5071aa 100644 --- a/src/shared/fdisk-util.h +++ b/src/shared/fdisk-util.h @@ -70,8 +70,6 @@ extern DLSYM_PROTOTYPE(fdisk_unref_parttype); extern DLSYM_PROTOTYPE(fdisk_unref_table); extern DLSYM_PROTOTYPE(fdisk_write_disklabel); -int dlopen_fdisk(void); - DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct fdisk_context*, sym_fdisk_unref_context, fdisk_unref_contextp, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct fdisk_partition*, sym_fdisk_unref_partition, fdisk_unref_partitionp, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct fdisk_parttype*, sym_fdisk_unref_parttype, fdisk_unref_parttypep, NULL); @@ -85,10 +83,6 @@ int fdisk_partition_get_type_as_id128(struct fdisk_partition *p, sd_id128_t *ret int fdisk_partition_get_attrs_as_uint64(struct fdisk_partition *pa, uint64_t *ret); int fdisk_partition_set_attrs_as_uint64(struct fdisk_partition *pa, uint64_t flags); -#else - -static inline int dlopen_fdisk(void) { - return -EOPNOTSUPP; -} - #endif + +int dlopen_fdisk(int log_level); diff --git a/src/shared/find-esp.c b/src/shared/find-esp.c index d29719785fedd..fd232d51d0152 100644 --- a/src/shared/find-esp.c +++ b/src/shared/find-esp.c @@ -75,9 +75,9 @@ static int verify_esp_blkid( const char *v; int r; - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_DEBUG); if (r < 0) - return log_debug_errno(r, "No libblkid support: %m"); + return r; r = devname_from_devnum(S_IFBLK, devid, &node); if (r < 0) @@ -572,9 +572,9 @@ static int verify_xbootldr_blkid( const char *type, *v; int r; - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_DEBUG); if (r < 0) - return log_debug_errno(r, "No libblkid support: %m"); + return r; r = devname_from_devnum(S_IFBLK, devid, &node); if (r < 0) diff --git a/src/shared/idn-util.c b/src/shared/idn-util.c index a1b4a6c49873c..01512b95dda41 100644 --- a/src/shared/idn-util.c +++ b/src/shared/idn-util.c @@ -1,17 +1,22 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "sd-dlopen.h" - #include "idn-util.h" #include "log.h" /* IWYU pragma: keep */ +#if HAVE_LIBIDN2 + +#include "sd-dlopen.h" + static void* idn_dl = NULL; DLSYM_PROTOTYPE(idn2_lookup_u8) = NULL; const char *(*sym_idn2_strerror)(int rc) _const_ = NULL; DLSYM_PROTOTYPE(idn2_to_unicode_8z8z) = NULL; -int dlopen_idn(void) { +#endif + +int dlopen_idn(int log_level) { +#if HAVE_LIBIDN2 SD_ELF_NOTE_DLOPEN( "idn", "Support for internationalized domain names", @@ -19,8 +24,12 @@ int dlopen_idn(void) { "libidn2.so.0"); return dlopen_many_sym_or_warn( - &idn_dl, "libidn2.so.0", LOG_DEBUG, + &idn_dl, "libidn2.so.0", log_level, DLSYM_ARG(idn2_lookup_u8), DLSYM_ARG(idn2_strerror), DLSYM_ARG(idn2_to_unicode_8z8z)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libidn2 support is not compiled in."); +#endif } diff --git a/src/shared/idn-util.h b/src/shared/idn-util.h index c971be9aefebe..918f8c0a57fbd 100644 --- a/src/shared/idn-util.h +++ b/src/shared/idn-util.h @@ -11,10 +11,6 @@ extern DLSYM_PROTOTYPE(idn2_lookup_u8); extern const char *(*sym_idn2_strerror)(int rc) _const_; extern DLSYM_PROTOTYPE(idn2_to_unicode_8z8z); - -int dlopen_idn(void); -#else -static inline int dlopen_idn(void) { - return -EOPNOTSUPP; -} #endif + +int dlopen_idn(int log_level); diff --git a/src/shared/libarchive-util.c b/src/shared/libarchive-util.c index 02b6b36b3c244..2ef0b37959b93 100644 --- a/src/shared/libarchive-util.c +++ b/src/shared/libarchive-util.c @@ -5,6 +5,7 @@ #include "sd-dlopen.h" #include "libarchive-util.h" +#include "log.h" /* IWYU pragma: keep */ #include "user-util.h" /* IWYU pragma: keep */ #if HAVE_LIBARCHIVE @@ -79,8 +80,10 @@ DLSYM_PROTOTYPE(archive_write_open_FILE) = NULL; DLSYM_PROTOTYPE(archive_write_open_fd) = NULL; DLSYM_PROTOTYPE(archive_write_set_format_filter_by_ext) = NULL; DLSYM_PROTOTYPE(archive_write_set_format_pax) = NULL; +#endif -int dlopen_libarchive(void) { +int dlopen_libarchive(int log_level) { +#if HAVE_LIBARCHIVE SD_ELF_NOTE_DLOPEN( "archive", "Support for decompressing archive files", @@ -90,7 +93,7 @@ int dlopen_libarchive(void) { return dlopen_many_sym_or_warn( &libarchive_dl, "libarchive.so.13", - LOG_DEBUG, + log_level, DLSYM_ARG(archive_entry_acl_add_entry), DLSYM_ARG(archive_entry_acl_next), DLSYM_ARG(archive_entry_acl_reset), @@ -152,8 +155,13 @@ int dlopen_libarchive(void) { DLSYM_ARG(archive_write_open_fd), DLSYM_ARG(archive_write_set_format_filter_by_ext), DLSYM_ARG(archive_write_set_format_pax)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libarchive support is not compiled in."); +#endif } +#if HAVE_LIBARCHIVE /* libarchive uses its own file type macros. They happen to be defined the same way as the Linux ones, and * we'd like to rely on it. Let's verify this first though. */ assert_cc(S_IFDIR == AE_IFDIR); diff --git a/src/shared/libarchive-util.h b/src/shared/libarchive-util.h index db1c31d2239f7..f1f78aad5d1eb 100644 --- a/src/shared/libarchive-util.h +++ b/src/shared/libarchive-util.h @@ -79,16 +79,13 @@ extern DLSYM_PROTOTYPE(archive_write_open_fd); extern DLSYM_PROTOTYPE(archive_write_set_format_filter_by_ext); extern DLSYM_PROTOTYPE(archive_write_set_format_pax); -int dlopen_libarchive(void); - DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct archive_entry*, sym_archive_entry_free, archive_entry_freep, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct archive*, sym_archive_write_free, archive_write_freep, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct archive*, sym_archive_read_free, archive_read_freep, NULL); #else -static inline int dlopen_libarchive(void) { - return -EOPNOTSUPP; -} #endif + +int dlopen_libarchive(int log_level); diff --git a/src/shared/libaudit-util.c b/src/shared/libaudit-util.c index bf5791955bbba..478d3d33b6518 100644 --- a/src/shared/libaudit-util.c +++ b/src/shared/libaudit-util.c @@ -23,7 +23,7 @@ DLSYM_PROTOTYPE(audit_log_user_comm_message) = NULL; static DLSYM_PROTOTYPE(audit_open) = NULL; #endif -int dlopen_libaudit(void) { +int dlopen_libaudit(int log_level) { #if HAVE_AUDIT SD_ELF_NOTE_DLOPEN( "audit", @@ -34,14 +34,15 @@ int dlopen_libaudit(void) { return dlopen_many_sym_or_warn( &libaudit_dl, "libaudit.so.1", - LOG_DEBUG, + log_level, DLSYM_ARG(audit_close), DLSYM_ARG(audit_log_acct_message), DLSYM_ARG(audit_log_user_avc_message), DLSYM_ARG(audit_log_user_comm_message), DLSYM_ARG(audit_open)); #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libaudit support is not compiled in."); #endif } @@ -93,7 +94,7 @@ bool use_audit(void) { if (cached_use >= 0) return cached_use; - if (dlopen_libaudit() < 0) + if (dlopen_libaudit(LOG_DEBUG) < 0) return (cached_use = false); _cleanup_close_ int fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_AUDIT); @@ -141,7 +142,7 @@ int open_audit_fd_or_warn(void) { #if HAVE_AUDIT int r; - r = dlopen_libaudit(); + r = dlopen_libaudit(LOG_DEBUG); if (r < 0) return r; diff --git a/src/shared/libaudit-util.h b/src/shared/libaudit-util.h index 759b1c757497a..8bb05a2fbd3c1 100644 --- a/src/shared/libaudit-util.h +++ b/src/shared/libaudit-util.h @@ -3,7 +3,7 @@ #include "shared-forward.h" -int dlopen_libaudit(void); +int dlopen_libaudit(int log_level); #if HAVE_AUDIT # include /* IWYU pragma: export */ diff --git a/src/shared/libcrypt-util.c b/src/shared/libcrypt-util.c index df3ba146f9ed4..531684d64ef64 100644 --- a/src/shared/libcrypt-util.c +++ b/src/shared/libcrypt-util.c @@ -21,58 +21,6 @@ static DLSYM_PROTOTYPE(crypt_gensalt_ra) = NULL; static DLSYM_PROTOTYPE(crypt_preferred_method) = NULL; static DLSYM_PROTOTYPE(crypt_ra) = NULL; -int dlopen_libcrypt(void) { -#ifdef __GLIBC__ - static int cached = 0; - int r; - - if (libcrypt_dl) - return 0; /* Already loaded */ - - if (cached < 0) - return cached; /* Already tried, and failed. */ - - /* Several distributions like Debian/Ubuntu and OpenSUSE provide libxcrypt as libcrypt.so.1 - * (libcrypt.so.1.1 on some architectures), while others like Fedora/CentOS and Arch provide it as - * libcrypt.so.2. */ - SD_ELF_NOTE_DLOPEN( - "crypt", - "Support for hashing passwords", - SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, - "libcrypt.so.2", "libcrypt.so.1", "libcrypt.so.1.1"); - - _cleanup_(dlclosep) void *dl = NULL; - const char *dle = NULL; - FOREACH_STRING(soname, "libcrypt.so.2", "libcrypt.so.1", "libcrypt.so.1.1") { - r = dlopen_safe(soname, &dl, &dle); - if (r >= 0) { - log_debug("Loaded '%s' via dlopen().", soname); - break; - } - } - if (r < 0) { - log_debug_errno(r, "Failed to load libcrypt: %s", dle ?: STRERROR(r)); - return (cached = -EOPNOTSUPP); /* turn into recognizable error */ - } - - r = dlsym_many_or_warn( - dl, LOG_DEBUG, - DLSYM_ARG(crypt_gensalt_ra), - DLSYM_ARG(crypt_preferred_method), - DLSYM_ARG(crypt_ra)); - if (r < 0) - return (cached = r); - - libcrypt_dl = TAKE_PTR(dl); -#else - libcrypt_dl = NULL; - sym_crypt_gensalt_ra = missing_crypt_gensalt_ra; - sym_crypt_preferred_method = missing_crypt_preferred_method; - sym_crypt_ra = missing_crypt_ra; -#endif - return 0; -} - int make_salt(char **ret) { const char *e; char *salt; @@ -80,7 +28,7 @@ int make_salt(char **ret) { assert(ret); - r = dlopen_libcrypt(); + r = dlopen_libcrypt(LOG_DEBUG); if (r < 0) return r; @@ -127,7 +75,7 @@ int test_password_one(const char *hashed_password, const char *password) { assert(hashed_password); assert(password); - r = dlopen_libcrypt(); + r = dlopen_libcrypt(LOG_DEBUG); if (r < 0) return r; @@ -175,3 +123,60 @@ bool looks_like_hashed_password(const char *s) { return !STR_IN_SET(s, "x", "*"); } + +int dlopen_libcrypt(int log_level) { +#if HAVE_LIBCRYPT +#ifdef __GLIBC__ + static int cached = 0; + int r; + + if (libcrypt_dl) + return 0; /* Already loaded */ + + if (cached < 0) + return cached; /* Already tried, and failed. */ + + /* Several distributions like Debian/Ubuntu and OpenSUSE provide libxcrypt as libcrypt.so.1 + * (libcrypt.so.1.1 on some architectures), while others like Fedora/CentOS and Arch provide it as + * libcrypt.so.2. */ + SD_ELF_NOTE_DLOPEN( + "crypt", + "Support for hashing passwords", + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + "libcrypt.so.2", "libcrypt.so.1", "libcrypt.so.1.1"); + + _cleanup_(dlclosep) void *dl = NULL; + const char *dle = NULL; + FOREACH_STRING(soname, "libcrypt.so.2", "libcrypt.so.1", "libcrypt.so.1.1") { + r = dlopen_safe(soname, &dl, &dle); + if (r >= 0) { + log_debug("Loaded '%s' via dlopen().", soname); + break; + } + } + if (r < 0) { + log_full_errno(log_level, r, "Failed to load libcrypt: %s", dle ?: STRERROR(r)); + return (cached = -EOPNOTSUPP); /* turn into recognizable error */ + } + + r = dlsym_many_or_warn( + dl, log_level, + DLSYM_ARG(crypt_gensalt_ra), + DLSYM_ARG(crypt_preferred_method), + DLSYM_ARG(crypt_ra)); + if (r < 0) + return (cached = r); + + libcrypt_dl = TAKE_PTR(dl); +#else + libcrypt_dl = NULL; + sym_crypt_gensalt_ra = missing_crypt_gensalt_ra; + sym_crypt_preferred_method = missing_crypt_preferred_method; + sym_crypt_ra = missing_crypt_ra; +#endif + return 0; +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libcrypt support is not compiled in."); +#endif +} diff --git a/src/shared/libcrypt-util.h b/src/shared/libcrypt-util.h index 3f79916cbc0be..5c469b662cf02 100644 --- a/src/shared/libcrypt-util.h +++ b/src/shared/libcrypt-util.h @@ -4,7 +4,6 @@ #include "shared-forward.h" #if HAVE_LIBCRYPT -int dlopen_libcrypt(void); int make_salt(char **ret); int hash_password(const char *password, char **ret); int test_password_one(const char *hashed_password, const char *password); @@ -12,12 +11,11 @@ int test_password_many(char **hashed_password, const char *password); #else -static inline int dlopen_libcrypt(void) { - return -EOPNOTSUPP; -} static inline int hash_password(const char *password, char **ret) { return -EOPNOTSUPP; } #endif +int dlopen_libcrypt(int log_level); + bool looks_like_hashed_password(const char *s); diff --git a/src/shared/libfido2-util.c b/src/shared/libfido2-util.c index 18d020d34c225..cc00006af9d54 100644 --- a/src/shared/libfido2-util.c +++ b/src/shared/libfido2-util.c @@ -80,7 +80,7 @@ static void fido_log_propagate_handler(const char *s) { #endif -int dlopen_libfido2(void) { +int dlopen_libfido2(int log_level) { #if HAVE_LIBFIDO2 int r; @@ -91,7 +91,7 @@ int dlopen_libfido2(void) { "libfido2.so.1"); r = dlopen_many_sym_or_warn( - &libfido2_dl, "libfido2.so.1", LOG_DEBUG, + &libfido2_dl, "libfido2.so.1", log_level, DLSYM_ARG(fido_assert_allow_cred), DLSYM_ARG(fido_assert_free), DLSYM_ARG(fido_assert_hmac_secret_len), @@ -148,7 +148,8 @@ int dlopen_libfido2(void) { return 0; #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libfido2 support is not compiled in."); #endif } @@ -658,9 +659,9 @@ int fido2_use_hmac_hash( fido_dev_info_t *di = NULL; int r; - r = dlopen_libfido2(); + r = dlopen_libfido2(LOG_ERR); if (r < 0) - return log_error_errno(r, "FIDO2 support is not installed."); + return r; if (device) { r = fido2_is_cred_in_specific_token(device, rp_id, cid, cid_size, required); @@ -784,9 +785,9 @@ int fido2_generate_hmac_hash( assert((lock_with & ~(FIDO2ENROLL_PIN|FIDO2ENROLL_UP|FIDO2ENROLL_UV)) == 0); assert(iovec_is_set(salt)); - r = dlopen_libfido2(); + r = dlopen_libfido2(LOG_ERR); if (r < 0) - return log_error_errno(r, "FIDO2 token support is not installed."); + return r; d = sym_fido_dev_new(); if (!d) @@ -1188,9 +1189,9 @@ int fido2_list_devices(void) { fido_dev_info_t *di = NULL; int r; - r = dlopen_libfido2(); + r = dlopen_libfido2(LOG_ERR); if (r < 0) - return log_error_errno(r, "FIDO2 token support is not installed."); + return r; di = sym_fido_dev_info_new(allocated); if (!di) @@ -1282,9 +1283,9 @@ int fido2_find_device_auto(char **ret) { const char *path; int r; - r = dlopen_libfido2(); + r = dlopen_libfido2(LOG_ERR); if (r < 0) - return log_error_errno(r, "FIDO2 token support is not installed."); + return r; di = sym_fido_dev_info_new(di_size); if (!di) @@ -1359,9 +1360,9 @@ int fido2_have_device(const char *device) { /* Return == 0 if not devices are found, > 0 if at least one is found */ - r = dlopen_libfido2(); + r = dlopen_libfido2(LOG_ERR); if (r < 0) - return log_error_errno(r, "FIDO2 support is not installed."); + return r; if (device) { if (access(device, F_OK) < 0) { diff --git a/src/shared/libfido2-util.h b/src/shared/libfido2-util.h index c5d3875a0d5b5..4f88100be700e 100644 --- a/src/shared/libfido2-util.h +++ b/src/shared/libfido2-util.h @@ -16,7 +16,7 @@ typedef enum Fido2EnrollFlags { _FIDO2ENROLL_TYPE_INVALID = -EINVAL, } Fido2EnrollFlags; -int dlopen_libfido2(void); +int dlopen_libfido2(int log_level); #if HAVE_LIBFIDO2 #include diff --git a/src/shared/libmount-util.c b/src/shared/libmount-util.c index 0d63675667aea..27e98888d02a8 100644 --- a/src/shared/libmount-util.c +++ b/src/shared/libmount-util.c @@ -1,12 +1,15 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "libmount-util.h" +#include "log.h" + +#if HAVE_LIBMOUNT + #include #include "sd-dlopen.h" #include "fstab-util.h" -#include "libmount-util.h" -#include "log.h" static void *libmount_dl = NULL; @@ -42,50 +45,6 @@ DLSYM_PROTOTYPE(mnt_table_parse_stream) = NULL; DLSYM_PROTOTYPE(mnt_table_parse_swaps) = NULL; DLSYM_PROTOTYPE(mnt_unref_monitor) = NULL; -int dlopen_libmount(void) { - SD_ELF_NOTE_DLOPEN( - "mount", - "Support for mount enumeration", - SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, - "libmount.so.1"); - - return dlopen_many_sym_or_warn( - &libmount_dl, - "libmount.so.1", - LOG_DEBUG, - DLSYM_ARG(mnt_free_iter), - DLSYM_ARG(mnt_free_table), - DLSYM_ARG(mnt_fs_get_fs_options), - DLSYM_ARG(mnt_fs_get_fstype), - DLSYM_ARG(mnt_fs_get_id), - DLSYM_ARG(mnt_fs_get_option), - DLSYM_ARG(mnt_fs_get_options), - DLSYM_ARG(mnt_fs_get_passno), - DLSYM_ARG(mnt_fs_get_propagation), - DLSYM_ARG(mnt_fs_get_source), - DLSYM_ARG(mnt_fs_get_target), - DLSYM_ARG(mnt_fs_get_vfs_options), - DLSYM_ARG(mnt_get_builtin_optmap), - DLSYM_ARG(mnt_init_debug), - DLSYM_ARG(mnt_monitor_enable_kernel), - DLSYM_ARG(mnt_monitor_enable_userspace), - DLSYM_ARG(mnt_monitor_get_fd), - DLSYM_ARG(mnt_monitor_next_change), - DLSYM_ARG(mnt_new_iter), - DLSYM_ARG(mnt_new_monitor), - DLSYM_ARG(mnt_new_table), - DLSYM_ARG(mnt_optstr_get_flags), - DLSYM_ARG(mnt_table_find_devno), - DLSYM_ARG(mnt_table_find_target), - DLSYM_ARG(mnt_table_next_child_fs), - DLSYM_ARG(mnt_table_next_fs), - DLSYM_ARG(mnt_table_parse_file), - DLSYM_ARG(mnt_table_parse_mtab), - DLSYM_ARG(mnt_table_parse_stream), - DLSYM_ARG(mnt_table_parse_swaps), - DLSYM_ARG(mnt_unref_monitor)); -} - int libmount_parse_full( const char *path, FILE *source, @@ -103,7 +62,7 @@ int libmount_parse_full( assert(ret_table); assert(ret_iter); - r = dlopen_libmount(); + r = dlopen_libmount(LOG_DEBUG); if (r < 0) return r; @@ -156,3 +115,54 @@ int libmount_is_leaf( return r == 1; } + +#endif + +int dlopen_libmount(int log_level) { +#if HAVE_LIBMOUNT + SD_ELF_NOTE_DLOPEN( + "mount", + "Support for mount enumeration", + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + "libmount.so.1"); + + return dlopen_many_sym_or_warn( + &libmount_dl, + "libmount.so.1", + log_level, + DLSYM_ARG(mnt_free_iter), + DLSYM_ARG(mnt_free_table), + DLSYM_ARG(mnt_fs_get_fs_options), + DLSYM_ARG(mnt_fs_get_fstype), + DLSYM_ARG(mnt_fs_get_id), + DLSYM_ARG(mnt_fs_get_option), + DLSYM_ARG(mnt_fs_get_options), + DLSYM_ARG(mnt_fs_get_passno), + DLSYM_ARG(mnt_fs_get_propagation), + DLSYM_ARG(mnt_fs_get_source), + DLSYM_ARG(mnt_fs_get_target), + DLSYM_ARG(mnt_fs_get_vfs_options), + DLSYM_ARG(mnt_get_builtin_optmap), + DLSYM_ARG(mnt_init_debug), + DLSYM_ARG(mnt_monitor_enable_kernel), + DLSYM_ARG(mnt_monitor_enable_userspace), + DLSYM_ARG(mnt_monitor_get_fd), + DLSYM_ARG(mnt_monitor_next_change), + DLSYM_ARG(mnt_new_iter), + DLSYM_ARG(mnt_new_monitor), + DLSYM_ARG(mnt_new_table), + DLSYM_ARG(mnt_optstr_get_flags), + DLSYM_ARG(mnt_table_find_devno), + DLSYM_ARG(mnt_table_find_target), + DLSYM_ARG(mnt_table_next_child_fs), + DLSYM_ARG(mnt_table_next_fs), + DLSYM_ARG(mnt_table_parse_file), + DLSYM_ARG(mnt_table_parse_mtab), + DLSYM_ARG(mnt_table_parse_stream), + DLSYM_ARG(mnt_table_parse_swaps), + DLSYM_ARG(mnt_unref_monitor)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libmount support is not compiled in."); +#endif +} diff --git a/src/shared/libmount-util.h b/src/shared/libmount-util.h index bfbb00cd4afd4..7eb7b230b2ff3 100644 --- a/src/shared/libmount-util.h +++ b/src/shared/libmount-util.h @@ -42,8 +42,6 @@ extern DLSYM_PROTOTYPE(mnt_table_parse_stream); extern DLSYM_PROTOTYPE(mnt_table_parse_swaps); extern DLSYM_PROTOTYPE(mnt_unref_monitor); -int dlopen_libmount(void); - DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct libmnt_table*, sym_mnt_free_table, mnt_free_tablep, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct libmnt_iter*, sym_mnt_free_iter, mnt_free_iterp, NULL); @@ -79,9 +77,6 @@ int libmount_is_leaf( struct libmnt_monitor; -static inline int dlopen_libmount(void) { - return -EOPNOTSUPP; -} static inline void* sym_mnt_unref_monitor(struct libmnt_monitor *p) { assert(p == NULL); @@ -89,3 +84,5 @@ static inline void* sym_mnt_unref_monitor(struct libmnt_monitor *p) { } #endif + +int dlopen_libmount(int log_level); diff --git a/src/shared/meson.build b/src/shared/meson.build index 89c550504de8a..07b504797af68 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -3,6 +3,7 @@ shared_sources = files( 'acl-util.c', 'acpi-fpdt.c', + 'apparmor-util.c', 'ask-password-agent.c', 'ask-password-api.c', 'async.c', @@ -95,6 +96,7 @@ shared_sources = files( 'hostname-setup.c', 'hwdb-util.c', 'id128-print.c', + 'idn-util.c', 'ima-util.c', 'image-policy.c', 'import-util.c', @@ -117,6 +119,7 @@ shared_sources = files( 'libaudit-util.c', 'libcrypt-util.c', 'libfido2-util.c', + 'libmount-util.c', 'local-addresses.c', 'locale-setup.c', 'log-assert-critical.c', @@ -151,6 +154,7 @@ shared_sources = files( 'osc-context.c', 'output-mode.c', 'pager.c', + 'pam-util.c', 'parse-argument.c', 'parse-helpers.c', 'password-quality-util-passwdqc.c', @@ -284,22 +288,6 @@ if conf.get('HAVE_LIBBPF') == 1 shared_sources += files('bpf-link.c') endif -if conf.get('HAVE_PAM') == 1 - shared_sources += files('pam-util.c') -endif - -if conf.get('HAVE_LIBMOUNT') == 1 - shared_sources += files('libmount-util.c') -endif - -if conf.get('HAVE_LIBIDN2') == 1 - shared_sources += files('idn-util.c') -endif - -if conf.get('HAVE_APPARMOR') == 1 - shared_sources += files('apparmor-util.c') -endif - generate_ip_protocol_list = files('generate-ip-protocol-list.sh') ip_protocol_list_txt = custom_target( input : [generate_ip_protocol_list, ipproto_sources], diff --git a/src/shared/module-util.c b/src/shared/module-util.c index 8ad7dab180a66..9bf1a827b008f 100644 --- a/src/shared/module-util.c +++ b/src/shared/module-util.c @@ -27,32 +27,6 @@ DLSYM_PROTOTYPE(kmod_set_log_fn) = NULL; DLSYM_PROTOTYPE(kmod_unref) = NULL; DLSYM_PROTOTYPE(kmod_validate_resources) = NULL; -int dlopen_libkmod(void) { - SD_ELF_NOTE_DLOPEN( - "kmod", - "Support for loading kernel modules", - SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, - "libkmod.so.2"); - - return dlopen_many_sym_or_warn( - &libkmod_dl, - "libkmod.so.2", - LOG_DEBUG, - DLSYM_ARG(kmod_list_next), - DLSYM_ARG(kmod_load_resources), - DLSYM_ARG(kmod_module_get_initstate), - DLSYM_ARG(kmod_module_get_module), - DLSYM_ARG(kmod_module_get_name), - DLSYM_ARG(kmod_module_new_from_lookup), - DLSYM_ARG(kmod_module_probe_insert_module), - DLSYM_ARG(kmod_module_unref), - DLSYM_ARG(kmod_module_unref_list), - DLSYM_ARG(kmod_new), - DLSYM_ARG(kmod_set_log_fn), - DLSYM_ARG(kmod_unref), - DLSYM_ARG(kmod_validate_resources)); -} - static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { char ***denylist = ASSERT_PTR(data); int r; @@ -180,7 +154,7 @@ int module_setup_context(struct kmod_ctx **ret) { assert(ret); - r = dlopen_libkmod(); + r = dlopen_libkmod(LOG_DEBUG); if (r < 0) return r; @@ -196,3 +170,34 @@ int module_setup_context(struct kmod_ctx **ret) { } #endif + +int dlopen_libkmod(int log_level) { +#if HAVE_KMOD + SD_ELF_NOTE_DLOPEN( + "kmod", + "Support for loading kernel modules", + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + "libkmod.so.2"); + + return dlopen_many_sym_or_warn( + &libkmod_dl, + "libkmod.so.2", + log_level, + DLSYM_ARG(kmod_list_next), + DLSYM_ARG(kmod_load_resources), + DLSYM_ARG(kmod_module_get_initstate), + DLSYM_ARG(kmod_module_get_module), + DLSYM_ARG(kmod_module_get_name), + DLSYM_ARG(kmod_module_new_from_lookup), + DLSYM_ARG(kmod_module_probe_insert_module), + DLSYM_ARG(kmod_module_unref), + DLSYM_ARG(kmod_module_unref_list), + DLSYM_ARG(kmod_new), + DLSYM_ARG(kmod_set_log_fn), + DLSYM_ARG(kmod_unref), + DLSYM_ARG(kmod_validate_resources)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libkmod support is not compiled in."); +#endif +} diff --git a/src/shared/module-util.h b/src/shared/module-util.h index f5eaf35c90916..629979722c734 100644 --- a/src/shared/module-util.h +++ b/src/shared/module-util.h @@ -23,8 +23,6 @@ extern DLSYM_PROTOTYPE(kmod_set_log_fn); extern DLSYM_PROTOTYPE(kmod_unref); extern DLSYM_PROTOTYPE(kmod_validate_resources); -int dlopen_libkmod(void); - DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct kmod_ctx*, sym_kmod_unref, kmod_unrefp, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct kmod_module*, sym_kmod_module_unref, kmod_module_unrefp, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct kmod_list*, sym_kmod_module_unref_list, kmod_module_unref_listp, NULL); @@ -41,9 +39,6 @@ int module_setup_context(struct kmod_ctx **ret); struct kmod_ctx; -static inline int dlopen_libkmod(void) { - return -EOPNOTSUPP; -} static inline int module_setup_context(struct kmod_ctx **ret) { return -EOPNOTSUPP; @@ -54,3 +49,5 @@ static inline int module_load_and_warn(struct kmod_ctx *ctx, const char *module, } #endif + +int dlopen_libkmod(int log_level); diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index 45ffd9113b7e6..12d3da82e220e 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -451,7 +451,7 @@ int bind_remount_one_with_mountinfo( rewind(proc_self_mountinfo); - r = dlopen_libmount(); + r = dlopen_libmount(LOG_DEBUG); if (r < 0) return r; @@ -900,7 +900,7 @@ int mount_option_mangle( * The validity of options stored in '*ret_remaining_options' is not checked. * If 'options' is NULL, this just copies 'mount_flags' to *ret_mount_flags. */ - r = dlopen_libmount(); + r = dlopen_libmount(LOG_DEBUG); if (r < 0) return r; diff --git a/src/shared/pam-util.c b/src/shared/pam-util.c index 0f04e97377b3e..9728c3e00da07 100644 --- a/src/shared/pam-util.c +++ b/src/shared/pam-util.c @@ -1,5 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "log.h" +#include "pam-util.h" + +#if HAVE_PAM + #include #include @@ -11,8 +16,6 @@ #include "errno-util.h" #include "fd-util.h" #include "format-util.h" -#include "log.h" -#include "pam-util.h" #include "process-util.h" #include "stdio-util.h" #include "string-util.h" @@ -35,34 +38,6 @@ DLSYM_PROTOTYPE(pam_strerror) = NULL; DLSYM_PROTOTYPE(pam_syslog) = NULL; DLSYM_PROTOTYPE(pam_vsyslog) = NULL; -int dlopen_libpam(void) { - SD_ELF_NOTE_DLOPEN( - "pam", - "Support for LinuxPAM", - SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, - "libpam.so.0"); - - return dlopen_many_sym_or_warn( - &libpam_dl, - "libpam.so.0", - LOG_DEBUG, - DLSYM_ARG(pam_acct_mgmt), - DLSYM_ARG(pam_close_session), - DLSYM_ARG(pam_end), - DLSYM_ARG(pam_get_data), - DLSYM_ARG(pam_get_item), - DLSYM_ARG(pam_getenvlist), - DLSYM_ARG(pam_open_session), - DLSYM_ARG(pam_putenv), - DLSYM_ARG(pam_set_data), - DLSYM_ARG(pam_set_item), - DLSYM_ARG(pam_setcred), - DLSYM_ARG(pam_start), - DLSYM_ARG(pam_strerror), - DLSYM_ARG(pam_syslog), - DLSYM_ARG(pam_vsyslog)); -} - void pam_log_setup(void) { /* Make sure we don't leak the syslog fd we open by opening/closing the fd each time. */ log_set_open_when_needed(true); @@ -385,3 +360,38 @@ int pam_prompt_graceful(pam_handle_t *pamh, int style, char **ret_response, cons return PAM_SUCCESS; } + +#endif + +int dlopen_libpam(int log_level) { +#if HAVE_PAM + SD_ELF_NOTE_DLOPEN( + "pam", + "Support for LinuxPAM", + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + "libpam.so.0"); + + return dlopen_many_sym_or_warn( + &libpam_dl, + "libpam.so.0", + log_level, + DLSYM_ARG(pam_acct_mgmt), + DLSYM_ARG(pam_close_session), + DLSYM_ARG(pam_end), + DLSYM_ARG(pam_get_data), + DLSYM_ARG(pam_get_item), + DLSYM_ARG(pam_getenvlist), + DLSYM_ARG(pam_open_session), + DLSYM_ARG(pam_putenv), + DLSYM_ARG(pam_set_data), + DLSYM_ARG(pam_set_item), + DLSYM_ARG(pam_setcred), + DLSYM_ARG(pam_start), + DLSYM_ARG(pam_strerror), + DLSYM_ARG(pam_syslog), + DLSYM_ARG(pam_vsyslog)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libpam support is not compiled in."); +#endif +} diff --git a/src/shared/pam-util.h b/src/shared/pam-util.h index 2464e144fb4e7..96d522d8f9bfb 100644 --- a/src/shared/pam-util.h +++ b/src/shared/pam-util.h @@ -27,8 +27,6 @@ extern DLSYM_PROTOTYPE(pam_strerror); extern DLSYM_PROTOTYPE(pam_syslog); extern DLSYM_PROTOTYPE(pam_vsyslog); -int dlopen_libpam(void); - void pam_log_setup(void); int errno_to_pam_error(int error) _const_; @@ -92,10 +90,6 @@ int pam_get_data_many_internal(pam_handle_t *pamh, ...) _sentinel_; int pam_prompt_graceful(pam_handle_t *pamh, int style, char **ret_response, const char *fmt, ...) _printf_(4,5); -#else - -static inline int dlopen_libpam(void) { - return -EOPNOTSUPP; -} - #endif + +int dlopen_libpam(int log_level); diff --git a/src/shared/password-quality-util-passwdqc.c b/src/shared/password-quality-util-passwdqc.c index 33c77b01a0cef..6ce858c744622 100644 --- a/src/shared/password-quality-util-passwdqc.c +++ b/src/shared/password-quality-util-passwdqc.c @@ -2,6 +2,9 @@ #include "password-quality-util-passwdqc.h" +#include "errno-util.h" /* IWYU pragma: keep */ +#include "log.h" /* IWYU pragma: keep */ + #if HAVE_PASSWDQC #include @@ -10,8 +13,6 @@ #include "alloc-util.h" #include "dlfcn-util.h" -#include "errno-util.h" -#include "log.h" #include "memory-util.h" #include "strv.h" @@ -34,7 +35,7 @@ static int pwqc_allocate_context(passwdqc_params_t **ret) { assert(ret); - r = dlopen_passwdqc(); + r = dlopen_passwdqc(LOG_DEBUG); if (r < 0) return r; @@ -137,7 +138,7 @@ int check_password_quality( #endif -int dlopen_passwdqc(void) { +int dlopen_passwdqc(int log_level) { #if HAVE_PASSWDQC SD_ELF_NOTE_DLOPEN( "passwdqc", @@ -146,7 +147,7 @@ int dlopen_passwdqc(void) { "libpasswdqc.so.1"); return dlopen_many_sym_or_warn( - &passwdqc_dl, "libpasswdqc.so.1", LOG_DEBUG, + &passwdqc_dl, "libpasswdqc.so.1", log_level, DLSYM_ARG(passwdqc_params_reset), DLSYM_ARG(passwdqc_params_load), DLSYM_ARG(passwdqc_params_parse), @@ -154,6 +155,7 @@ int dlopen_passwdqc(void) { DLSYM_ARG(passwdqc_check), DLSYM_ARG(passwdqc_random)); #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libpasswdqc support is not compiled in."); #endif } diff --git a/src/shared/password-quality-util-passwdqc.h b/src/shared/password-quality-util-passwdqc.h index e813829b63bec..2ae00aa620b86 100644 --- a/src/shared/password-quality-util-passwdqc.h +++ b/src/shared/password-quality-util-passwdqc.h @@ -8,4 +8,4 @@ int suggest_passwords(void); int check_password_quality(const char *password, const char *old, const char *username, char **ret_error); #endif -int dlopen_passwdqc(void); +int dlopen_passwdqc(int log_level); diff --git a/src/shared/password-quality-util-pwquality.c b/src/shared/password-quality-util-pwquality.c index 3eba9495b642c..dd0cdcfa136a5 100644 --- a/src/shared/password-quality-util-pwquality.c +++ b/src/shared/password-quality-util-pwquality.c @@ -2,6 +2,9 @@ #include "password-quality-util-pwquality.h" +#include "errno-util.h" +#include "log.h" + #if HAVE_PWQUALITY #include @@ -12,8 +15,6 @@ #include "alloc-util.h" #include "dlfcn-util.h" -#include "errno-util.h" -#include "log.h" #include "password-quality-util.h" #include "string-util.h" #include "strv.h" @@ -72,7 +73,7 @@ static int pwq_allocate_context(pwquality_settings_t **ret) { assert(ret); - r = dlopen_pwquality(); + r = dlopen_pwquality(LOG_DEBUG); if (r < 0) return r; @@ -153,7 +154,7 @@ int check_password_quality(const char *password, const char *old, const char *us #endif -int dlopen_pwquality(void) { +int dlopen_pwquality(int log_level) { #if HAVE_PWQUALITY SD_ELF_NOTE_DLOPEN( "pwquality", @@ -162,7 +163,7 @@ int dlopen_pwquality(void) { "libpwquality.so.1"); return dlopen_many_sym_or_warn( - &pwquality_dl, "libpwquality.so.1", LOG_DEBUG, + &pwquality_dl, "libpwquality.so.1", log_level, DLSYM_ARG(pwquality_check), DLSYM_ARG(pwquality_default_settings), DLSYM_ARG(pwquality_free_settings), @@ -172,6 +173,7 @@ int dlopen_pwquality(void) { DLSYM_ARG(pwquality_set_int_value), DLSYM_ARG(pwquality_strerror)); #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libpwquality support is not compiled in."); #endif } diff --git a/src/shared/password-quality-util-pwquality.h b/src/shared/password-quality-util-pwquality.h index 829025a10d3d9..468734f590b17 100644 --- a/src/shared/password-quality-util-pwquality.h +++ b/src/shared/password-quality-util-pwquality.h @@ -8,4 +8,4 @@ int suggest_passwords(void); int check_password_quality(const char *password, const char *old, const char *username, char **ret_error); #endif -int dlopen_pwquality(void); +int dlopen_pwquality(int log_level); diff --git a/src/shared/pcre2-util.c b/src/shared/pcre2-util.c index 22a7635170bab..3ac76c1f9c9b8 100644 --- a/src/shared/pcre2-util.c +++ b/src/shared/pcre2-util.c @@ -28,7 +28,7 @@ DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( const struct hash_ops pcre2_code_hash_ops_free = {}; #endif -int dlopen_pcre2(void) { +int dlopen_pcre2(int log_level) { #if HAVE_PCRE2 SD_ELF_NOTE_DLOPEN( "pcre2", @@ -45,7 +45,7 @@ int dlopen_pcre2(void) { * manually anymore. C is weird. 🤯 */ return dlopen_many_sym_or_warn( - &pcre2_dl, "libpcre2-8.so.0", LOG_ERR, + &pcre2_dl, "libpcre2-8.so.0", log_level, DLSYM_ARG(pcre2_match_data_create), DLSYM_ARG(pcre2_match_data_free), DLSYM_ARG(pcre2_code_free), @@ -54,7 +54,8 @@ int dlopen_pcre2(void) { DLSYM_ARG(pcre2_match), DLSYM_ARG(pcre2_get_ovector_pointer)); #else - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "PCRE2 support is not compiled in."); + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "PCRE2 support is not compiled in."); #endif } @@ -67,7 +68,7 @@ int pattern_compile_and_log(const char *pattern, PatternCompileCase case_, pcre2 assert(pattern); - r = dlopen_pcre2(); + r = dlopen_pcre2(LOG_ERR); if (r < 0) return r; diff --git a/src/shared/pcre2-util.h b/src/shared/pcre2-util.h index 8c85c6199430a..ba8827fb0749b 100644 --- a/src/shared/pcre2-util.h +++ b/src/shared/pcre2-util.h @@ -44,4 +44,4 @@ typedef enum PatternCompileCase { int pattern_compile_and_log(const char *pattern, PatternCompileCase case_, pcre2_code **ret); int pattern_matches_and_log(pcre2_code *compiled_pattern, const char *message, size_t size, size_t *ret_ovec); -int dlopen_pcre2(void); +int dlopen_pcre2(int log_level); diff --git a/src/shared/pcrextend-util.c b/src/shared/pcrextend-util.c index 6598bf82142c0..79efdc4574e2f 100644 --- a/src/shared/pcrextend-util.c +++ b/src/shared/pcrextend-util.c @@ -37,7 +37,7 @@ static int device_get_file_system_word( assert(ret); #if HAVE_BLKID - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_DEBUG); if (r < 0) return r; diff --git a/src/shared/pkcs11-util.c b/src/shared/pkcs11-util.c index 96b25c4ac36b8..eea7fec8930f3 100644 --- a/src/shared/pkcs11-util.c +++ b/src/shared/pkcs11-util.c @@ -65,7 +65,7 @@ int uri_from_string(const char *p, P11KitUri **ret) { assert(p); assert(ret); - r = dlopen_p11kit(); + r = dlopen_p11kit(LOG_DEBUG); if (r < 0) return r; @@ -85,7 +85,7 @@ P11KitUri *uri_from_module_info(const CK_INFO *info) { assert(info); - if (dlopen_p11kit() < 0) + if (dlopen_p11kit(LOG_DEBUG) < 0) return NULL; uri = sym_p11_kit_uri_new(); @@ -101,7 +101,7 @@ P11KitUri *uri_from_slot_info(const CK_SLOT_INFO *slot_info) { assert(slot_info); - if (dlopen_p11kit() < 0) + if (dlopen_p11kit(LOG_DEBUG) < 0) return NULL; uri = sym_p11_kit_uri_new(); @@ -117,7 +117,7 @@ P11KitUri *uri_from_token_info(const CK_TOKEN_INFO *token_info) { assert(token_info); - if (dlopen_p11kit() < 0) + if (dlopen_p11kit(LOG_DEBUG) < 0) return NULL; uri = sym_p11_kit_uri_new(); @@ -221,7 +221,7 @@ int pkcs11_token_login_by_pin( assert(m); assert(token_info); - r = dlopen_p11kit(); + r = dlopen_p11kit(LOG_DEBUG); if (r < 0) return r; @@ -283,7 +283,7 @@ int pkcs11_token_login( assert(m); assert(token_info); - r = dlopen_p11kit(); + r = dlopen_p11kit(LOG_DEBUG); if (r < 0) return r; @@ -675,7 +675,7 @@ int pkcs11_token_read_x509_certificate( assert(ret_cert); - r = dlopen_p11kit(); + r = dlopen_p11kit(LOG_DEBUG); if (r < 0) return r; @@ -1248,7 +1248,7 @@ int pkcs11_token_acquire_rng( assert(m); - r = dlopen_p11kit(); + r = dlopen_p11kit(LOG_DEBUG); if (r < 0) return r; @@ -1334,7 +1334,7 @@ static int slot_process( assert(m); - r = dlopen_p11kit(); + r = dlopen_p11kit(LOG_DEBUG); if (r < 0) return r; @@ -1417,7 +1417,7 @@ static int module_process( assert(m); - r = dlopen_p11kit(); + r = dlopen_p11kit(LOG_DEBUG); if (r < 0) return r; @@ -1481,7 +1481,7 @@ int pkcs11_find_token( _cleanup_(p11_kit_uri_freep) P11KitUri *search_uri = NULL; int r; - r = dlopen_p11kit(); + r = dlopen_p11kit(LOG_DEBUG); if (r < 0) return r; @@ -1740,7 +1740,7 @@ static int list_callback( assert(slot_info); assert(token_info); - r = dlopen_p11kit(); + r = dlopen_p11kit(LOG_DEBUG); if (r < 0) return r; @@ -1784,7 +1784,7 @@ static int list_callback( } #endif -int dlopen_p11kit(void) { +int dlopen_p11kit(int log_level) { #if HAVE_P11KIT SD_ELF_NOTE_DLOPEN( "p11-kit", @@ -1794,7 +1794,7 @@ int dlopen_p11kit(void) { return dlopen_many_sym_or_warn( &p11kit_dl, - "libp11-kit.so.0", LOG_DEBUG, + "libp11-kit.so.0", log_level, DLSYM_ARG(p11_kit_module_get_name), DLSYM_ARG(p11_kit_modules_finalize_and_release), DLSYM_ARG(p11_kit_modules_load_and_initialize), @@ -1812,7 +1812,8 @@ int dlopen_p11kit(void) { DLSYM_ARG(p11_kit_uri_new), DLSYM_ARG(p11_kit_uri_parse)); #else - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "p11kit support is not compiled in."); + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libp11-kit support is not compiled in."); #endif } @@ -1858,7 +1859,7 @@ static int auto_callback( assert(slot_info); assert(token_info); - r = dlopen_p11kit(); + r = dlopen_p11kit(LOG_DEBUG); if (r < 0) return r; diff --git a/src/shared/pkcs11-util.h b/src/shared/pkcs11-util.h index 51279eabc47ad..92d850b6e5008 100644 --- a/src/shared/pkcs11-util.h +++ b/src/shared/pkcs11-util.h @@ -106,7 +106,7 @@ int pkcs11_crypt_device_callback( #endif -int dlopen_p11kit(void); +int dlopen_p11kit(int log_level); typedef struct { const char *friendly_name; diff --git a/src/shared/qrcode-util.c b/src/shared/qrcode-util.c index ee6d3c40f93fa..d1c639e74c1f0 100644 --- a/src/shared/qrcode-util.c +++ b/src/shared/qrcode-util.c @@ -28,7 +28,7 @@ static DLSYM_PROTOTYPE(QRcode_encodeString) = NULL; static DLSYM_PROTOTYPE(QRcode_free) = NULL; #endif -int dlopen_qrencode(void) { +int dlopen_qrencode(int log_level) { #if HAVE_QRENCODE int r; @@ -40,7 +40,7 @@ int dlopen_qrencode(void) { FOREACH_STRING(s, "libqrencode.so.4", "libqrencode.so.3") { r = dlopen_many_sym_or_warn( - &qrcode_dl, s, LOG_DEBUG, + &qrcode_dl, s, log_level, DLSYM_ARG(QRcode_encodeString), DLSYM_ARG(QRcode_free)); if (r >= 0) @@ -49,7 +49,8 @@ int dlopen_qrencode(void) { return r; #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libqrencode support is not compiled in."); #endif } @@ -210,7 +211,7 @@ int print_qrcode_full( if (check_tty && !colors_enabled()) return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Colors are disabled, cannot print qrcode"); - r = dlopen_qrencode(); + r = dlopen_qrencode(LOG_DEBUG); if (r < 0) return r; diff --git a/src/shared/qrcode-util.h b/src/shared/qrcode-util.h index 9a624e29e13b7..667ff8e3ff7cc 100644 --- a/src/shared/qrcode-util.h +++ b/src/shared/qrcode-util.h @@ -13,7 +13,7 @@ int print_qrcode_full( unsigned tty_height, bool check_tty); -int dlopen_qrencode(void); +int dlopen_qrencode(int log_level); static inline int print_qrcode(FILE *out, const char *header, const char *string) { return print_qrcode_full(out, header, string, UINT_MAX, UINT_MAX, UINT_MAX, UINT_MAX, true); diff --git a/src/shared/reread-partition-table.c b/src/shared/reread-partition-table.c index 8f99b67134de7..9d908ee948360 100644 --- a/src/shared/reread-partition-table.c +++ b/src/shared/reread-partition-table.c @@ -287,7 +287,7 @@ static int reread_partition_table_full(sd_device *dev, int fd, RereadPartitionTa } #if HAVE_BLKID - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_DEBUG); if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) { log_device_debug(dev, "We don't have libblkid, falling back to BLKRRPART on '%s'.", p); return fallback_ioctl(dev, fd, flags); diff --git a/src/shared/seccomp-util.c b/src/shared/seccomp-util.c index 49328ccde2e4c..167a0e40de8c3 100644 --- a/src/shared/seccomp-util.c +++ b/src/shared/seccomp-util.c @@ -50,32 +50,6 @@ DLSYM_PROTOTYPE(seccomp_rule_add_exact) = NULL; DLSYM_PROTOTYPE(seccomp_syscall_resolve_name) = NULL; DLSYM_PROTOTYPE(seccomp_syscall_resolve_num_arch) = NULL; -int dlopen_libseccomp(void) { - SD_ELF_NOTE_DLOPEN( - "seccomp", - "Support for Seccomp Sandboxes", - SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, - "libseccomp.so.2"); - - return dlopen_many_sym_or_warn( - &libseccomp_dl, - "libseccomp.so.2", - LOG_DEBUG, - DLSYM_ARG(seccomp_api_get), - DLSYM_ARG(seccomp_arch_add), - DLSYM_ARG(seccomp_arch_exist), - DLSYM_ARG(seccomp_arch_native), - DLSYM_ARG(seccomp_arch_remove), - DLSYM_ARG(seccomp_attr_set), - DLSYM_ARG(seccomp_init), - DLSYM_ARG(seccomp_load), - DLSYM_ARG(seccomp_release), - DLSYM_ARG(seccomp_rule_add_array), - DLSYM_ARG(seccomp_rule_add_exact), - DLSYM_ARG(seccomp_syscall_resolve_name), - DLSYM_ARG(seccomp_syscall_resolve_num_arch)); -} - /* This array will be modified at runtime as seccomp_restrict_archs is called. */ uint32_t seccomp_local_archs[] = { @@ -283,7 +257,7 @@ int seccomp_init_for_arch(scmp_filter_ctx *ret, uint32_t arch, uint32_t default_ /* Much like seccomp_init(), but initializes the filter for one specific architecture only, without affecting * any others. Also, turns off the NNP fiddling. */ - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -343,7 +317,7 @@ bool is_seccomp_available(void) { static int cached_enabled = -1; if (cached_enabled < 0) { - if (dlopen_libseccomp() < 0) + if (dlopen_libseccomp(LOG_DEBUG) < 0) return (cached_enabled = false); int b = secure_getenv_bool("SYSTEMD_SECCOMP"); @@ -1093,7 +1067,7 @@ int seccomp_add_syscall_filter_item( } else { int id, r; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -1173,7 +1147,7 @@ int seccomp_load_syscall_filter_set(uint32_t default_action, const SyscallFilter /* The one-stop solution: allocate a seccomp object, add the specified filter to it, and apply it. Once for * each local arch. */ - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -1239,7 +1213,7 @@ int seccomp_load_syscall_filter_set_raw(uint32_t default_action, Hashmap* filter if (hashmap_isempty(filter) && default_action == SCMP_ACT_ALLOW) return 0; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -1362,7 +1336,7 @@ int seccomp_parse_syscall_filter( } else { int id; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) { if (!FLAGS_SET(flags, SECCOMP_PARSE_PERMISSIVE)) return r; @@ -1420,7 +1394,7 @@ int seccomp_restrict_namespaces(unsigned long retain) { if (FLAGS_SET(retain, NAMESPACE_FLAGS_ALL)) return 0; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -1548,7 +1522,7 @@ int seccomp_protect_sysctl(void) { uint32_t arch; int r; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -1600,7 +1574,7 @@ int seccomp_protect_syslog(void) { uint32_t arch; int r; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -1637,7 +1611,7 @@ int seccomp_restrict_address_families(Set *address_families, bool allow_list) { uint32_t arch; int r; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -1823,7 +1797,7 @@ int seccomp_restrict_realtime_full(int error_code) { assert(error_code > 0); - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -1928,7 +1902,7 @@ int seccomp_memory_deny_write_execute(void) { unsigned loaded = 0; int r; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -2048,7 +2022,7 @@ int seccomp_restrict_archs(Set *archs) { int r; bool blocked_new = false; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -2141,7 +2115,7 @@ int seccomp_filter_set_add_by_name(Hashmap *filter, bool add, const char *name) assert(filter); assert(name); - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -2190,7 +2164,7 @@ int seccomp_lock_personality(unsigned long personality) { if (personality >= PERSONALITY_INVALID) return -EINVAL; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -2228,7 +2202,7 @@ int seccomp_protect_hostname(void) { uint32_t arch; int r; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -2444,7 +2418,7 @@ int seccomp_restrict_suid_sgid(void) { uint32_t arch; int r, k; - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -2571,7 +2545,7 @@ int seccomp_suppress_sync(void) { * * Additionally, O_SYNC/O_DSYNC are masked. */ - r = dlopen_libseccomp(); + r = dlopen_libseccomp(LOG_DEBUG); if (r < 0) return r; @@ -2628,6 +2602,37 @@ int seccomp_suppress_sync(void) { #endif +int dlopen_libseccomp(int log_level) { +#if HAVE_SECCOMP + SD_ELF_NOTE_DLOPEN( + "seccomp", + "Support for Seccomp Sandboxes", + SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + "libseccomp.so.2"); + + return dlopen_many_sym_or_warn( + &libseccomp_dl, + "libseccomp.so.2", + log_level, + DLSYM_ARG(seccomp_api_get), + DLSYM_ARG(seccomp_arch_add), + DLSYM_ARG(seccomp_arch_exist), + DLSYM_ARG(seccomp_arch_native), + DLSYM_ARG(seccomp_arch_remove), + DLSYM_ARG(seccomp_attr_set), + DLSYM_ARG(seccomp_init), + DLSYM_ARG(seccomp_load), + DLSYM_ARG(seccomp_release), + DLSYM_ARG(seccomp_rule_add_array), + DLSYM_ARG(seccomp_rule_add_exact), + DLSYM_ARG(seccomp_syscall_resolve_name), + DLSYM_ARG(seccomp_syscall_resolve_num_arch)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libseccomp support is not compiled in."); +#endif +} + bool seccomp_errno_or_action_is_valid(int n) { return n == SECCOMP_ERROR_NUMBER_KILL || errno_is_valid(n); } diff --git a/src/shared/seccomp-util.h b/src/shared/seccomp-util.h index 51c2ba650501b..7037ef52d17d7 100644 --- a/src/shared/seccomp-util.h +++ b/src/shared/seccomp-util.h @@ -23,8 +23,6 @@ extern DLSYM_PROTOTYPE(seccomp_rule_add_exact); extern DLSYM_PROTOTYPE(seccomp_syscall_resolve_name); extern DLSYM_PROTOTYPE(seccomp_syscall_resolve_num_arch); -int dlopen_libseccomp(void); - DECLARE_STRING_TABLE_LOOKUP_TO_STRING(seccomp_arch, uint32_t); int seccomp_arch_from_string(const char *n, uint32_t *ret); @@ -163,12 +161,11 @@ static inline bool is_seccomp_available(void) { return false; } -static inline int dlopen_libseccomp(void) { - return -EOPNOTSUPP; -} #endif +int dlopen_libseccomp(int log_level); + /* This is a special value to be used where syscall filters otherwise expect errno numbers, will be replaced with real seccomp action. */ enum { diff --git a/src/shared/selinux-util.c b/src/shared/selinux-util.c index 6b8e318a4c8d0..19e0d2b488d4d 100644 --- a/src/shared/selinux-util.c +++ b/src/shared/selinux-util.c @@ -5,6 +5,8 @@ #include #include +#include "log.h" + #if HAVE_SELINUX #include #include @@ -20,7 +22,6 @@ #include "alloc-util.h" #include "fd-util.h" #include "label.h" -#include "log.h" #include "path-util.h" #include "string-util.h" #include "time-util.h" @@ -90,8 +91,10 @@ DLSYM_PROTOTYPE(setfilecon_raw) = NULL; DLSYM_PROTOTYPE(setfscreatecon_raw) = NULL; DLSYM_PROTOTYPE(setsockcreatecon_raw) = NULL; DLSYM_PROTOTYPE(string_to_security_class) = NULL; +#endif -int dlopen_libselinux(void) { +int dlopen_libselinux(int log_level) { +#if HAVE_SELINUX SD_ELF_NOTE_DLOPEN( "selinux", "Support for SELinux", @@ -101,7 +104,7 @@ int dlopen_libselinux(void) { return dlopen_many_sym_or_warn( &libselinux_dl, "libselinux.so.1", - LOG_DEBUG, + log_level, DLSYM_ARG(avc_open), DLSYM_ARG(context_free), DLSYM_ARG(context_new), @@ -136,13 +139,16 @@ int dlopen_libselinux(void) { DLSYM_ARG(setfscreatecon_raw), DLSYM_ARG(setsockcreatecon_raw), DLSYM_ARG(string_to_security_class)); -} +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libselinux support is not compiled in."); #endif +} bool mac_selinux_use(void) { #if HAVE_SELINUX if (_unlikely_(cached_use < 0)) { - if (dlopen_libselinux() < 0) + if (dlopen_libselinux(LOG_DEBUG) < 0) return (cached_use = false); cached_use = sym_is_selinux_enabled() > 0; @@ -162,7 +168,7 @@ bool mac_selinux_enforcing(void) { /* If the SELinux status page has been successfully opened, retrieve the enforcing * status over it to avoid system calls in security_getenforce(). */ - if (dlopen_libselinux() < 0) + if (dlopen_libselinux(LOG_DEBUG) < 0) return false; if (have_status_page) @@ -188,7 +194,7 @@ static int open_label_db(void) { struct mallinfo2 before_mallinfo = {}; int r; - r = dlopen_libselinux(); + r = dlopen_libselinux(LOG_DEBUG); if (r < 0) return r; @@ -302,7 +308,7 @@ void mac_selinux_maybe_reload(void) { if (!initialized) return; - if (dlopen_libselinux() < 0) + if (dlopen_libselinux(LOG_DEBUG) < 0) return; /* Do not use selinux_status_updated(3), cause since libselinux 3.2 selinux_check_access(3), @@ -352,7 +358,7 @@ static int selinux_log_glue(int type, const char *fmt, ...) { void mac_selinux_disable_logging(void) { #if HAVE_SELINUX /* Turn off all of SELinux' own logging, we want to do that ourselves */ - if (dlopen_libselinux() < 0) + if (dlopen_libselinux(LOG_DEBUG) < 0) return; sym_selinux_set_callback(SELINUX_CB_LOG, (const union selinux_callback) { .func_log = selinux_log_glue }); diff --git a/src/shared/selinux-util.h b/src/shared/selinux-util.h index ab499e8c4fff2..f73503bc4b29e 100644 --- a/src/shared/selinux-util.h +++ b/src/shared/selinux-util.h @@ -13,8 +13,6 @@ #include "dlfcn-util.h" -int dlopen_libselinux(void); - extern DLSYM_PROTOTYPE(avc_open); extern DLSYM_PROTOTYPE(context_free); extern DLSYM_PROTOTYPE(context_new); @@ -54,15 +52,14 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(char*, sym_freecon, freeconp, NULL); #else -static inline int dlopen_libselinux(void) { - return -EOPNOTSUPP; -} static inline void freeconp(char **p) { assert(*p == NULL); } #endif +int dlopen_libselinux(int log_level); + #define _cleanup_freecon_ _cleanup_(freeconp) /* This accepts 0 error, like _zerook(). */ diff --git a/src/shared/shift-uid.c b/src/shared/shift-uid.c index 2dcd5f2359715..e455306be91f9 100644 --- a/src/shared/shift-uid.c +++ b/src/shared/shift-uid.c @@ -165,7 +165,7 @@ static int patch_acls(int fd, const char *name, const struct stat *st, uid_t shi if (!inode_type_can_acl(st->st_mode)) return 0; - r = dlopen_libacl(); + r = dlopen_libacl(LOG_DEBUG); if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) return 0; if (r < 0) diff --git a/src/shared/tar-util.c b/src/shared/tar-util.c index 3f7dc88a6a9af..21d228c26367b 100644 --- a/src/shared/tar-util.c +++ b/src/shared/tar-util.c @@ -511,11 +511,9 @@ static int archive_entry_read_acl( assert(c > 0); #if HAVE_ACL - r = dlopen_libacl(); - if (r < 0) { - log_debug_errno(r, "Not restoring ACL data on inode as libacl is not available: %m"); + r = dlopen_libacl(LOG_DEBUG); + if (r < 0) return 0; - } _cleanup_(acl_freep) acl_t a = NULL; a = sym_acl_init(c); @@ -1480,10 +1478,8 @@ static int archive_item( #if HAVE_ACL if (inode_type_can_acl(sx->stx_mode)) { - r = dlopen_libacl(); - if (r < 0) - log_debug_errno(r, "No trying to read ACL off inode, as libacl support is not available: %m"); - else { + r = dlopen_libacl(LOG_DEBUG); + if (r >= 0) { r = sym_acl_extended_file(FORMAT_PROC_FD_PATH(inode_fd)); if (r < 0 && !ERRNO_IS_NOT_SUPPORTED(errno)) return log_error_errno(errno, "Failed check if '%s' has ACLs: %m", path); diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index fa0fa71130b57..986b2831e1991 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -128,7 +128,7 @@ static DLSYM_PROTOTYPE(Tss2_MU_UINT32_Marshal) = NULL; static DLSYM_PROTOTYPE(Tss2_RC_Decode) = NULL; -static int dlopen_tpm2_esys(void) { +static int dlopen_tpm2_esys(int log_level) { int r; SD_ELF_NOTE_DLOPEN( @@ -138,7 +138,7 @@ static int dlopen_tpm2_esys(void) { "libtss2-esys.so.0"); r = dlopen_many_sym_or_warn( - &libtss2_esys_dl, "libtss2-esys.so.0", LOG_DEBUG, + &libtss2_esys_dl, "libtss2-esys.so.0", log_level, DLSYM_ARG(Esys_Create), DLSYM_ARG(Esys_CreateLoaded), DLSYM_ARG(Esys_CreatePrimary), @@ -194,7 +194,7 @@ static int dlopen_tpm2_esys(void) { return 0; } -static int dlopen_tpm2_rc(void) { +static int dlopen_tpm2_rc(int log_level) { SD_ELF_NOTE_DLOPEN( "tpm", "Support for TPM", @@ -202,11 +202,11 @@ static int dlopen_tpm2_rc(void) { "libtss2-rc.so.0"); return dlopen_many_sym_or_warn( - &libtss2_rc_dl, "libtss2-rc.so.0", LOG_DEBUG, + &libtss2_rc_dl, "libtss2-rc.so.0", log_level, DLSYM_ARG(Tss2_RC_Decode)); } -static int dlopen_tpm2_mu(void) { +static int dlopen_tpm2_mu(int log_level) { SD_ELF_NOTE_DLOPEN( "tpm", "Support for TPM", @@ -214,7 +214,7 @@ static int dlopen_tpm2_mu(void) { "libtss2-mu.so.0"); return dlopen_many_sym_or_warn( - &libtss2_mu_dl, "libtss2-mu.so.0", LOG_DEBUG, + &libtss2_mu_dl, "libtss2-mu.so.0", log_level, DLSYM_ARG(Tss2_MU_TPM2_CC_Marshal), DLSYM_ARG(Tss2_MU_TPM2_HANDLE_Marshal), DLSYM_ARG(Tss2_MU_TPM2B_DIGEST_Marshal), @@ -236,7 +236,7 @@ static int dlopen_tpm2_mu(void) { DLSYM_ARG(Tss2_MU_UINT32_Marshal)); } -static int dlopen_tpm2_tcti_device(void) { +static int dlopen_tpm2_tcti_device(int log_level) { /* The "device" TCTI is the most relevant one, let's also load it explicitly on dlopen_tpm2(), even * if we don't resolve any symbols here. */ @@ -248,34 +248,36 @@ static int dlopen_tpm2_tcti_device(void) { return dlopen_verbose( &libtss2_tcti_device_dl, - "libtss2-tcti-device.so.0"); + "libtss2-tcti-device.so.0", + log_level); } #endif -int dlopen_tpm2(void) { +int dlopen_tpm2(int log_level) { #if HAVE_TPM2 int r; - r = dlopen_tpm2_esys(); + r = dlopen_tpm2_esys(log_level); if (r < 0) return r; - r = dlopen_tpm2_rc(); + r = dlopen_tpm2_rc(log_level); if (r < 0) return r; - r = dlopen_tpm2_mu(); + r = dlopen_tpm2_mu(log_level); if (r < 0) return r; - r = dlopen_tpm2_tcti_device(); + r = dlopen_tpm2_tcti_device(log_level); if (r < 0) return r; return 0; #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "TPM2 support is not compiled in."); #endif } @@ -874,9 +876,9 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { .n_ref = 1, }; - r = dlopen_tpm2(); + r = dlopen_tpm2(LOG_DEBUG); if (r < 0) - return log_debug_errno(r, "TPM2 support not installed: %m"); + return r; if (!device) { device = secure_getenv("SYSTEMD_TPM2_DEVICE"); @@ -3524,9 +3526,9 @@ int tpm2_calculate_pubkey_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name) assert(public); assert(ret_name); - r = dlopen_tpm2(); + r = dlopen_tpm2(LOG_DEBUG); if (r < 0) - return log_debug_errno(r, "TPM2 support not installed: %m"); + return r; if (public->nameAlg != TPM2_ALG_SHA256) return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), @@ -3608,9 +3610,9 @@ int tpm2_calculate_nv_index_name(const TPMS_NV_PUBLIC *nvpublic, TPM2B_NAME *ret assert(nvpublic); assert(ret_name); - r = dlopen_tpm2(); + r = dlopen_tpm2(LOG_DEBUG); if (r < 0) - return log_debug_errno(r, "TPM2 support not installed: %m"); + return r; if (nvpublic->nameAlg != TPM2_ALG_SHA256) return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), @@ -3664,9 +3666,9 @@ int tpm2_calculate_policy_auth_value(TPM2B_DIGEST *digest) { assert(digest); assert(digest->size == SHA256_DIGEST_SIZE); - r = dlopen_tpm2(); + r = dlopen_tpm2(LOG_DEBUG); if (r < 0) - return log_debug_errno(r, "TPM2 support not installed: %m"); + return r; uint8_t buf[sizeof(command)]; size_t offset = 0; @@ -3725,9 +3727,9 @@ int tpm2_calculate_policy_signed(TPM2B_DIGEST *digest, const TPM2B_NAME *name) { assert(digest->size == SHA256_DIGEST_SIZE); assert(name); - r = dlopen_tpm2(); + r = dlopen_tpm2(LOG_DEBUG); if (r < 0) - return log_debug_errno(r, "TPM2 support not installed: %m"); + return r; uint8_t buf[sizeof(command)]; size_t offset = 0; @@ -3870,9 +3872,9 @@ int tpm2_calculate_policy_authorize_nv( assert(digest); assert(digest->size == SHA256_DIGEST_SIZE); - r = dlopen_tpm2(); + r = dlopen_tpm2(LOG_DEBUG); if (r < 0) - return log_debug_errno(r, "TPM2 support not installed: %m"); + return r; uint8_t buf[sizeof(command)]; size_t offset = 0; @@ -4005,9 +4007,9 @@ int tpm2_calculate_policy_or(const TPM2B_DIGEST *branches, size_t n_branches, TP if (n_branches > 8) return -E2BIG; - r = dlopen_tpm2(); + r = dlopen_tpm2(LOG_ERR); if (r < 0) - return log_error_errno(r, "TPM2 support not installed: %m"); + return r; uint8_t buf[sizeof(command)]; size_t offset = 0; @@ -4060,9 +4062,9 @@ int tpm2_calculate_policy_pcr( assert(digest); assert(digest->size == SHA256_DIGEST_SIZE); - r = dlopen_tpm2(); + r = dlopen_tpm2(LOG_DEBUG); if (r < 0) - return log_debug_errno(r, "TPM2 support not installed: %m"); + return r; TPML_PCR_SELECTION pcr_selection; _cleanup_free_ TPM2B_DIGEST *values = NULL; @@ -4152,9 +4154,9 @@ int tpm2_calculate_policy_authorize( assert(digest); assert(digest->size == SHA256_DIGEST_SIZE); - r = dlopen_tpm2(); + r = dlopen_tpm2(LOG_DEBUG); if (r < 0) - return log_debug_errno(r, "TPM2 support not installed: %m"); + return r; uint8_t buf[sizeof(command)]; size_t offset = 0; @@ -6528,9 +6530,9 @@ int tpm2_list_devices(bool legend, bool quiet) { _cleanup_closedir_ DIR *d = NULL; int r; - r = dlopen_tpm2(); + r = dlopen_tpm2(LOG_ERR); if (r < 0) - return log_error_errno(r, "TPM2 support is not installed."); + return r; t = table_new("path", "device", "driver"); if (!t) @@ -6600,9 +6602,9 @@ int tpm2_find_device_auto(char **ret) { _cleanup_closedir_ DIR *d = NULL; int r; - r = dlopen_tpm2(); + r = dlopen_tpm2(LOG_DEBUG); if (r < 0) - return log_debug_errno(r, "TPM2 support is not installed."); + return r; d = opendir("/sys/class/tpmrm"); if (!d) { @@ -8482,9 +8484,9 @@ int tpm2_load_public_key_file(const char *path, TPM2B_PUBLIC *ret) { assert(path); assert(ret); - r = dlopen_tpm2(); + r = dlopen_tpm2(LOG_DEBUG); if (r < 0) - return log_debug_errno(r, "TPM2 support not installed: %m"); + return r; r = read_full_file(path, &device_key_buffer, &device_key_buffer_size); if (r < 0) @@ -9145,15 +9147,15 @@ Tpm2Support tpm2_support_full(Tpm2Support mask) { support |= TPM2_SUPPORT_SYSTEM; if ((mask & (TPM2_SUPPORT_LIBRARIES|TPM2_SUPPORT_LIBTSS2_ALL)) != 0) { - r = dlopen_tpm2_esys(); + r = dlopen_tpm2_esys(LOG_DEBUG); if (r >= 0) support |= TPM2_SUPPORT_LIBTSS2_ESYS; - r = dlopen_tpm2_rc(); + r = dlopen_tpm2_rc(LOG_DEBUG); if (r >= 0) support |= TPM2_SUPPORT_LIBTSS2_RC; - r = dlopen_tpm2_mu(); + r = dlopen_tpm2_mu(LOG_DEBUG); if (r >= 0) support |= TPM2_SUPPORT_LIBTSS2_MU; diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 5ada96e8e1174..8576f278cf195 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -39,7 +39,7 @@ static inline bool TPM2_PCR_MASK_VALID(uint32_t pcr_mask) { #define TPM2_N_HASH_ALGORITHMS 4U -int dlopen_tpm2(void); +int dlopen_tpm2(int log_level); #if HAVE_TPM2 diff --git a/src/shutdown/detach-swap.c b/src/shutdown/detach-swap.c index f064473ef77a2..04efbfeb36d54 100644 --- a/src/shutdown/detach-swap.c +++ b/src/shutdown/detach-swap.c @@ -36,9 +36,9 @@ int swap_list_get(const char *swaps, SwapDevice **head) { assert(head); - r = dlopen_libmount(); + r = dlopen_libmount(LOG_ERR); if (r < 0) - return log_error_errno(r, "Cannot enumerate swap partitions, no libmount support."); + return r; t = sym_mnt_new_table(); i = sym_mnt_new_iter(MNT_ITER_FORWARD); diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 0ac35d1b56b8d..0ea1e38d91f99 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -565,7 +565,7 @@ static int unmerge( bool need_to_reload; int r; - (void) dlopen_libmount(); + (void) dlopen_libmount(LOG_DEBUG); r = need_reload(image_class, hierarchies, no_reload); if (r < 0) @@ -2373,9 +2373,9 @@ static int merge(ImageClass image_class, int r; - (void) dlopen_cryptsetup(); - (void) dlopen_libblkid(); - (void) dlopen_libmount(); + (void) dlopen_cryptsetup(LOG_DEBUG); + (void) dlopen_libblkid(LOG_DEBUG); + (void) dlopen_libmount(LOG_DEBUG); _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; r = pidref_safe_fork("(sd-merge)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_NEW_MOUNTNS, &pidref); diff --git a/src/sysupdate/sysupdate-partition.c b/src/sysupdate/sysupdate-partition.c index 118bd71e1817a..0b346da62d18d 100644 --- a/src/sysupdate/sysupdate-partition.c +++ b/src/sysupdate/sysupdate-partition.c @@ -161,7 +161,7 @@ int find_suitable_partition( POINTER_MAY_BE_NULL(partition_type); assert(ret); - r = dlopen_fdisk(); + r = dlopen_fdisk(LOG_DEBUG); if (r < 0) return r; @@ -230,7 +230,7 @@ int patch_partition( if (change == 0) /* Nothing to do */ return 0; - r = dlopen_fdisk(); + r = dlopen_fdisk(LOG_DEBUG); if (r < 0) return r; diff --git a/src/sysupdate/sysupdate-resource.c b/src/sysupdate/sysupdate-resource.c index acd9604f2945d..fa5621e88c404 100644 --- a/src/sysupdate/sysupdate-resource.c +++ b/src/sysupdate/sysupdate-resource.c @@ -229,7 +229,7 @@ static int resource_load_from_blockdev(Resource *rr) { assert(rr); - r = dlopen_fdisk(); + r = dlopen_fdisk(LOG_DEBUG); if (r < 0) return r; diff --git a/src/test/test-dlopen-so.c b/src/test/test-dlopen-so.c index a9121ae9f9730..c2eaac7b53d92 100644 --- a/src/test/test-dlopen-so.c +++ b/src/test/test-dlopen-so.c @@ -29,12 +29,12 @@ #include "tests.h" #include "tpm2-util.h" -#define ASSERT_DLOPEN(func, cond) \ - do { \ - if (cond) \ - ASSERT_OK(func()); \ - else \ - ASSERT_ERROR(func(), EOPNOTSUPP); \ +#define ASSERT_DLOPEN(func, cond) \ + do { \ + if (cond) \ + ASSERT_OK(func(LOG_DEBUG)); \ + else \ + ASSERT_ERROR(func(LOG_DEBUG), EOPNOTSUPP); \ } while (false) static int run(int argc, char **argv) { diff --git a/src/test/test-execute.c b/src/test/test-execute.c index ebd0260892bfd..e14205bdf86a3 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -1633,7 +1633,7 @@ static int intro(void) { if (path_is_read_only_fs("/sys") > 0) return log_tests_skipped("/sys is mounted read-only"); - r = dlopen_libmount(); + r = dlopen_libmount(LOG_DEBUG); if (r < 0) return log_tests_skipped("libmount not available."); diff --git a/src/test/test-kexec.c b/src/test/test-kexec.c index 2a400f75d0cd8..a8dd397acc4cd 100644 --- a/src/test/test-kexec.c +++ b/src/test/test-kexec.c @@ -104,7 +104,7 @@ TEST(gzip_round_trip) { gz_path[] = "/tmp/test-kexec-gz.XXXXXX"; int r; - r = dlopen_zlib(); + r = dlopen_zlib(LOG_DEBUG); if (r < 0) { log_tests_skipped("zlib not available"); return; @@ -144,7 +144,7 @@ TEST(zboot_synthetic) { zboot_path[] = "/tmp/test-kexec-zboot.XXXXXX"; int r; - r = dlopen_zlib(); + r = dlopen_zlib(LOG_DEBUG); if (r < 0) { log_tests_skipped("zlib not available"); return; diff --git a/src/test/test-load-fragment.c b/src/test/test-load-fragment.c index 69d2d05b765c7..3e318a36c91d3 100644 --- a/src/test/test-load-fragment.c +++ b/src/test/test-load-fragment.c @@ -972,7 +972,7 @@ TEST(config_parse_log_filter_patterns) { TEST_PATTERN("~foobar", 0, 1), }; - if (ERRNO_IS_NOT_SUPPORTED(dlopen_pcre2())) + if (ERRNO_IS_NOT_SUPPORTED(dlopen_pcre2(LOG_DEBUG))) return (void) log_tests_skipped("PCRE2 support is not available"); FOREACH_ELEMENT(test, regex_tests) { diff --git a/src/test/test-namespace.c b/src/test/test-namespace.c index 899f8f635103c..7497596cc56fc 100644 --- a/src/test/test-namespace.c +++ b/src/test/test-namespace.c @@ -213,7 +213,7 @@ TEST(protect_kernel_logs) { return; } - r = dlopen_libmount(); + r = dlopen_libmount(LOG_DEBUG); if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) { (void) log_tests_skipped("libmount support not compiled in"); return; diff --git a/src/test/test-netlink-manual.c b/src/test/test-netlink-manual.c index 7934aa879d78e..d6f140e0aac7d 100644 --- a/src/test/test-netlink-manual.c +++ b/src/test/test-netlink-manual.c @@ -15,9 +15,9 @@ static int load_module(const char *mod_name) { struct kmod_list *l; int r; - r = dlopen_libkmod(); + r = dlopen_libkmod(LOG_ERR); if (r < 0) - return log_error_errno(r, "Failed to load libkmod: %m"); + return r; ctx = sym_kmod_new(NULL, NULL); if (!ctx) diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 7e41f6929924a..9975ffca3935d 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -1247,7 +1247,7 @@ static int parse_acl_cond_exec( assert(cond_exec); assert(ret); - r = dlopen_libacl(); + r = dlopen_libacl(LOG_DEBUG); if (r < 0) return r; @@ -1366,7 +1366,7 @@ static int path_set_acl( assert(c); - r = dlopen_libacl(); + r = dlopen_libacl(LOG_DEBUG); if (r < 0) return r; diff --git a/src/udev/test-udev-rule-runner.c b/src/udev/test-udev-rule-runner.c index 34755b1f8b3a7..1acbe2f0909c8 100644 --- a/src/udev/test-udev-rule-runner.c +++ b/src/udev/test-udev-rule-runner.c @@ -103,7 +103,7 @@ static int run(int argc, char *argv[]) { /* Let's make sure the test runs with selinux assumed disabled. */ #if HAVE_SELINUX - if (dlopen_libselinux() >= 0) + if (dlopen_libselinux(LOG_DEBUG) >= 0) sym_fini_selinuxmnt(); #endif mac_selinux_retest(); diff --git a/src/udev/udev-builtin-blkid.c b/src/udev/udev-builtin-blkid.c index edadb71c7ab4f..ca40b62d782b9 100644 --- a/src/udev/udev-builtin-blkid.c +++ b/src/udev/udev-builtin-blkid.c @@ -513,7 +513,7 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) { {} }; - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_DEBUG); if (r < 0) return log_device_debug_errno(dev, r, "blkid not available: %m"); diff --git a/src/udev/udevd.c b/src/udev/udevd.c index 840febbd42b3d..593eee89b8bde 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -58,11 +58,11 @@ int run_udevd(int argc, char *argv[]) { return log_error_errno(r, "Failed to create /run/udev: %m"); /* Load some shared libraries before we fork any workers */ - (void) dlopen_libacl(); - (void) dlopen_libblkid(); - (void) dlopen_libkmod(); - (void) dlopen_libmount(); - (void) dlopen_tpm2(); + (void) dlopen_libacl(LOG_DEBUG); + (void) dlopen_libblkid(LOG_DEBUG); + (void) dlopen_libkmod(LOG_DEBUG); + (void) dlopen_libmount(LOG_DEBUG); + (void) dlopen_tpm2(LOG_DEBUG); if (arg_daemonize) { pid_t pid; diff --git a/src/validatefs/validatefs.c b/src/validatefs/validatefs.c index 1106eaf1fe991..44a58387f05b1 100644 --- a/src/validatefs/validatefs.c +++ b/src/validatefs/validatefs.c @@ -291,9 +291,9 @@ static int validate_gpt_metadata_one(sd_device *d, const char *path, const Valid assert(d); assert(f); - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_ERR); if (r < 0) - return log_error_errno(r, "Cannot validate GPT constraints, refusing."); + return r; _cleanup_close_ int block_fd = sd_device_open(d, O_RDONLY|O_CLOEXEC|O_NONBLOCK); if (block_fd < 0) From fcff95831909f0ff9eb8be6ac61c411bc09fa566 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 8 Apr 2026 14:31:00 +0000 Subject: [PATCH 1172/1296] journal-remote: convert to the new option parser Replace the getopt_long()-based parser with the FOREACH_OPTION / OPTION_* macros from src/shared/options.h, mirroring the recent conversions of nspawn and vmspawn. Each option's metadata (long name, short name, metavar and help text) now lives next to its parsing logic, and the --help text is generated from those definitions via option_parser_get_help_table() instead of being hard-coded. Positional file arguments are collected via option_parser_get_args() rather than strv_skip(argv, optind). --- src/journal-remote/journal-remote-main.c | 160 +++++++++-------------- 1 file changed, 61 insertions(+), 99 deletions(-) diff --git a/src/journal-remote/journal-remote-main.c b/src/journal-remote/journal-remote-main.c index 9cb84bbe1e64b..d5277ad7ef1b3 100644 --- a/src/journal-remote/journal-remote-main.c +++ b/src/journal-remote/journal-remote-main.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-daemon.h" @@ -12,6 +11,7 @@ #include "daemon-util.h" #include "extract-word.h" #include "fd-util.h" +#include "format-table.h" #include "format-util.h" #include "fileio.h" #include "hashmap.h" @@ -21,6 +21,7 @@ #include "logs-show.h" #include "main-func.h" #include "microhttpd-util.h" +#include "options.h" #include "parse-argument.h" #include "parse-helpers.h" #include "parse-util.h" @@ -838,155 +839,120 @@ static int parse_config(void) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-journal-remote.service", "8", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...] {FILE|-}...\n\n" - "Write external journal events to journal file(s).\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --url=URL Read events from systemd-journal-gatewayd at URL\n" - " --getter=COMMAND Read events from the output of COMMAND\n" - " --listen-raw=ADDR Listen for connections at ADDR\n" - " --listen-http=ADDR Listen for HTTP connections at ADDR\n" - " --listen-https=ADDR Listen for HTTPS connections at ADDR\n" - " -o --output=FILE|DIR Write output to FILE or DIR/external-*.journal\n" - " --compress[=BOOL] Use compression in the output journal (default: yes)\n" - " --seal[=BOOL] Use event sealing (default: no)\n" - " --key=FILENAME SSL key in PEM format (default:\n" - " \"" PRIV_KEY_FILE "\")\n" - " --cert=FILENAME SSL certificate in PEM format (default:\n" - " \"" CERT_FILE "\")\n" - " --trust=FILENAME|all SSL CA certificate or disable checking (default:\n" - " \"" TRUST_FILE "\")\n" - " --gnutls-log=CATEGORY...\n" - " Specify a list of gnutls logging categories\n" - " --split-mode=none|host How many output files to create\n" - "\nNote: file descriptors from sd_listen_fds() will be consumed, too.\n" - "\nSee the %s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] {FILE|-}...\n" + "\n%sWrite external journal events to journal file(s).%s\n" + "\n%sOptions:%s\n", program_invocation_short_name, + ansi_highlight(), + ansi_normal(), + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nNote: file descriptors from sd_listen_fds() will be consumed, too.\n" + "\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_URL, - ARG_LISTEN_RAW, - ARG_LISTEN_HTTP, - ARG_LISTEN_HTTPS, - ARG_GETTER, - ARG_SPLIT_MODE, - ARG_COMPRESS, - ARG_SEAL, - ARG_KEY, - ARG_CERT, - ARG_TRUST, - ARG_GNUTLS_LOG, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "url", required_argument, NULL, ARG_URL }, - { "getter", required_argument, NULL, ARG_GETTER }, - { "listen-raw", required_argument, NULL, ARG_LISTEN_RAW }, - { "listen-http", required_argument, NULL, ARG_LISTEN_HTTP }, - { "listen-https", required_argument, NULL, ARG_LISTEN_HTTPS }, - { "output", required_argument, NULL, 'o' }, - { "split-mode", required_argument, NULL, ARG_SPLIT_MODE }, - { "compress", optional_argument, NULL, ARG_COMPRESS }, - { "seal", optional_argument, NULL, ARG_SEAL }, - { "key", required_argument, NULL, ARG_KEY }, - { "cert", required_argument, NULL, ARG_CERT }, - { "trust", required_argument, NULL, ARG_TRUST }, - { "gnutls-log", required_argument, NULL, ARG_GNUTLS_LOG }, - {} - }; - - int c, r; + int r; bool type_a, type_b; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "ho:", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_URL: - r = free_and_strdup_warn(&arg_url, optarg); + OPTION_LONG("url", "URL", "Read events from systemd-journal-gatewayd at URL"): + r = free_and_strdup_warn(&arg_url, arg); if (r < 0) return r; break; - case ARG_GETTER: - r = free_and_strdup_warn(&arg_getter, optarg); + OPTION_LONG("getter", "COMMAND", "Read events from the output of COMMAND"): + r = free_and_strdup_warn(&arg_getter, arg); if (r < 0) return r; break; - case ARG_LISTEN_RAW: - r = free_and_strdup_warn(&arg_listen_raw, optarg); + OPTION_LONG("listen-raw", "ADDR", "Listen for connections at ADDR"): + r = free_and_strdup_warn(&arg_listen_raw, arg); if (r < 0) return r; break; - case ARG_LISTEN_HTTP: + OPTION_LONG("listen-http", "ADDR", "Listen for HTTP connections at ADDR"): if (arg_listen_http || http_socket >= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot currently use --listen-http= more than once"); - r = negative_fd(optarg); + r = negative_fd(arg); if (r >= 0) http_socket = r; else { - r = free_and_strdup_warn(&arg_listen_http, optarg); + r = free_and_strdup_warn(&arg_listen_http, arg); if (r < 0) return r; } break; - case ARG_LISTEN_HTTPS: + OPTION_LONG("listen-https", "ADDR", "Listen for HTTPS connections at ADDR"): if (arg_listen_https || https_socket >= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot currently use --listen-https= more than once"); - r = negative_fd(optarg); + r = negative_fd(arg); if (r >= 0) https_socket = r; else { - r = free_and_strdup_warn(&arg_listen_https, optarg); + r = free_and_strdup_warn(&arg_listen_https, arg); if (r < 0) return r; } break; - case ARG_KEY: - r = free_and_strdup_warn(&arg_key, optarg); + OPTION_LONG("key", "FILENAME", "SSL key in PEM format (default: \"" PRIV_KEY_FILE "\")"): + r = free_and_strdup_warn(&arg_key, arg); if (r < 0) return r; break; - case ARG_CERT: - r = free_and_strdup_warn(&arg_cert, optarg); + OPTION_LONG("cert", "FILENAME", "SSL certificate in PEM format (default: \"" CERT_FILE "\")"): + r = free_and_strdup_warn(&arg_cert, arg); if (r < 0) return r; break; - case ARG_TRUST: + OPTION_LONG("trust", "FILENAME|all", + "SSL CA certificate or disable checking (default: \"" TRUST_FILE "\")"): #if HAVE_GNUTLS - r = free_and_strdup_warn(&arg_trust, optarg); + r = free_and_strdup_warn(&arg_trust, arg); if (r < 0) return r; #else @@ -994,33 +960,35 @@ static int parse_argv(int argc, char *argv[]) { #endif break; - case 'o': - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_output); + OPTION('o', "output", "FILE|DIR", "Write output to FILE or DIR/external-*.journal"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_output); if (r < 0) return r; break; - case ARG_SPLIT_MODE: - arg_split_mode = journal_write_split_mode_from_string(optarg); + OPTION_LONG("split-mode", "none|host", "How many output files to create"): + arg_split_mode = journal_write_split_mode_from_string(arg); if (arg_split_mode == _JOURNAL_WRITE_SPLIT_INVALID) - return log_error_errno(arg_split_mode, "Invalid split mode: %s", optarg); + return log_error_errno(arg_split_mode, "Invalid split mode: %s", arg); break; - case ARG_COMPRESS: - r = parse_boolean_argument("--compress", optarg, &arg_compress); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "compress", "BOOL", + "Use compression in the output journal (default: yes)"): + r = parse_boolean_argument("--compress", arg, &arg_compress); if (r < 0) return r; break; - case ARG_SEAL: - r = parse_boolean_argument("--seal", optarg, &arg_seal); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "seal", "BOOL", + "Use event sealing (default: no)"): + r = parse_boolean_argument("--seal", arg, &arg_seal); if (r < 0) return r; break; - case ARG_GNUTLS_LOG: + OPTION_LONG("gnutls-log", "CATEGORY,...", "Specify a list of gnutls logging categories"): #if HAVE_GNUTLS - for (const char *p = optarg;;) { + for (const char *p = arg;;) { _cleanup_free_ char *word = NULL; r = extract_first_word(&p, &word, ",", 0); @@ -1036,15 +1004,9 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --gnutls-log= is not available."); #endif break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - arg_files = strv_copy(strv_skip(argv, optind)); + arg_files = strv_copy(option_parser_get_args(&state)); if (!arg_files) return log_oom(); From 012d87c1fc3f22d7bc23a4389e710082c6666fc5 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 27 Mar 2026 14:38:09 +0000 Subject: [PATCH 1173/1296] vmspawn,journal-remote: add journal forwarding disk usage options Add options to vmspawn to configure journal-remote disk usage limits when forwarding journal entries from the VM. These are passed through as --max-use=, --keep-free=, --max-file-size=, and --max-files= command-line arguments to systemd-journal-remote. Add --max-use=, --keep-free=, --max-file-size=, and --max-files= command-line options to systemd-journal-remote to allow overriding the corresponding settings from the configuration file. Add $SYSTEMD_JOURNAL_REMOTE_CONFIG_FILE environment variable support to systemd-journal-remote. When set, the specified file is used instead of the default configuration file and drop-in directories. When set to the empty string or /dev/null, configuration file parsing is skipped entirely. vmspawn sets this to /dev/null in the child process to avoid inheriting the host's journal-remote configuration. Make fork_notify() argv parameter optional. When NULL is passed, fork_notify() returns 0 in the child (with $NOTIFY_SOCKET set) and lets the caller run custom code before exec. Returns 1 in the parent. This allows vmspawn to set environment variables in the child without polluting the parent process. Co-developed-by: Claude Opus 4.6 --- docs/ENVIRONMENT.md | 5 ++ man/systemd-journal-remote.service.xml | 13 +++++ man/systemd-vmspawn.xml | 15 ++++++ shell-completion/bash/systemd-vmspawn | 2 +- src/journal-remote/journal-remote-main.c | 41 +++++++++++++++ src/shared/fork-notify.c | 10 ++-- src/vmspawn/vmspawn.c | 66 +++++++++++++++++++++++- 7 files changed, 147 insertions(+), 5 deletions(-) diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index 5390754661879..53f4a1a60c896 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -679,6 +679,11 @@ SYSTEMD_HOME_DEBUG_SUFFIX=foo \ string format. Overrides the default maximum allowed size for a file-descriptor based input record to be stored in the journal. +* `$SYSTEMD_JOURNAL_REMOTE_CONFIG_FILE` – path to a configuration file for + `systemd-journal-remote`. When set, the specified file is used instead of the + default configuration file and drop-in directories. If set to the empty string + or `/dev/null`, configuration file parsing is skipped entirely. + * `$SYSTEMD_CATALOG` – path to the compiled catalog database file to use for `journalctl -x`, `journalctl --update-catalog`, `journalctl --list-catalog` and related calls. diff --git a/man/systemd-journal-remote.service.xml b/man/systemd-journal-remote.service.xml index d6258ce2fcd0f..7beb96403d195 100644 --- a/man/systemd-journal-remote.service.xml +++ b/man/systemd-journal-remote.service.xml @@ -333,6 +333,19 @@ + + + + + + + These options override the corresponding settings from the configuration file + (see journal-remote.conf5). + See that page for the descriptions of these options. + + + + diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 5749136a5d310..5c5ec4ccbcd55 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -668,6 +668,21 @@ + + + + + + + These options configure the corresponding settings of + systemd-journal-remote8 + when forwarding journal entries from the VM. See + journal-remote.conf5 + for the descriptions of these settings. + + + + diff --git a/shell-completion/bash/systemd-vmspawn b/shell-completion/bash/systemd-vmspawn index b2b3f4f5a8ba3..efa0dae58de04 100644 --- a/shell-completion/bash/systemd-vmspawn +++ b/shell-completion/bash/systemd-vmspawn @@ -38,7 +38,7 @@ _systemd_vmspawn() { [BIND]='--bind --bind-ro' [SSH_KEY]='--ssh-key' [CONSOLE]='--console' - [ARG]='--cpus --ram --vsock-cid -M --machine --uuid --private-users --background --set-credential --load-credential' + [ARG]='--cpus --ram --vsock-cid -M --machine --uuid --private-users --background --set-credential --load-credential --forward-journal-max-use --forward-journal-keep-free --forward-journal-max-file-size --forward-journal-max-files' [IMAGE_FORMAT]='--image-format' [IMAGE_DISK_TYPE]='--image-disk-type' ) diff --git a/src/journal-remote/journal-remote-main.c b/src/journal-remote/journal-remote-main.c index d5277ad7ef1b3..4867bf360946f 100644 --- a/src/journal-remote/journal-remote-main.c +++ b/src/journal-remote/journal-remote-main.c @@ -25,6 +25,7 @@ #include "parse-argument.h" #include "parse-helpers.h" #include "parse-util.h" +#include "path-util.h" #include "pretty-print.h" #include "process-util.h" #include "socket-netlink.h" @@ -829,6 +830,22 @@ static int parse_config(void) { {} }; + const char *config_file = secure_getenv("SYSTEMD_JOURNAL_REMOTE_CONFIG_FILE"); + if (config_file) { + if (isempty(config_file) || path_equal(config_file, "/dev/null")) + return 0; + + return config_parse( + /* unit= */ NULL, + config_file, + /* f= */ NULL, + "Remote\0", + config_item_table_lookup, items, + CONFIG_PARSE_WARN, + /* userdata= */ NULL, + /* ret_stat= */ NULL); + } + return config_parse_standard_file_with_dropins( "systemd/journal-remote.conf", "Remote\0", @@ -1004,6 +1021,30 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --gnutls-log= is not available."); #endif break; + + OPTION_LONG("max-use", "BYTES", "Maximum disk space to use"): + r = parse_size(arg, 1024, &arg_max_use); + if (r < 0) + return log_error_errno(r, "Failed to parse --max-use= value: %s", arg); + break; + + OPTION_LONG("keep-free", "BYTES", "Minimum disk space to keep free"): + r = parse_size(arg, 1024, &arg_keep_free); + if (r < 0) + return log_error_errno(r, "Failed to parse --keep-free= value: %s", arg); + break; + + OPTION_LONG("max-file-size", "BYTES", "Maximum size of individual journal files"): + r = parse_size(arg, 1024, &arg_max_size); + if (r < 0) + return log_error_errno(r, "Failed to parse --max-file-size= value: %s", arg); + break; + + OPTION_LONG("max-files", "N", "Maximum number of journal files to keep"): + r = safe_atou64(arg, &arg_n_max_files); + if (r < 0) + return log_error_errno(r, "Failed to parse --max-files= value: %s", arg); + break; } arg_files = strv_copy(option_parser_get_args(&state)); diff --git a/src/shared/fork-notify.c b/src/shared/fork-notify.c index 307197ac19701..e9686786fe719 100644 --- a/src/shared/fork-notify.c +++ b/src/shared/fork-notify.c @@ -90,7 +90,6 @@ static int on_child_notify(sd_event_source *s, int fd, uint32_t revents, void *u int fork_notify(char * const *argv, PidRef *ret_pidref) { int r; - assert(!strv_isempty(argv)); assert(ret_pidref); if (!is_main_thread()) @@ -119,7 +118,7 @@ int fork_notify(char * const *argv, PidRef *ret_pidref) { if (r < 0) return r; - if (DEBUG_LOGGING) { + if (DEBUG_LOGGING && argv) { _cleanup_free_ char *l = quote_command_line(argv, SHELL_ESCAPE_EMPTY); log_debug("Invoking '%s' as child.", strnull(l)); } @@ -141,6 +140,11 @@ int fork_notify(char * const *argv, PidRef *ret_pidref) { _exit(EXIT_MEMORY); } + if (!argv) { + *ret_pidref = TAKE_PIDREF(child); + return 0; /* Let the caller run custom code in the child */ + } + r = invoke_callout_binary(argv[0], argv); log_debug_errno(r, "Failed to invoke %s: %m", argv[0]); _exit(EXIT_EXEC); @@ -164,7 +168,7 @@ int fork_notify(char * const *argv, PidRef *ret_pidref) { *ret_pidref = TAKE_PIDREF(child); - return 0; + return 1; /* In the parent */ } static void fork_notify_terminate_internal(PidRef *pidref) { diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index fa8c3402ffc1a..b7f03501f76b0 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -34,6 +34,7 @@ #include "escape.h" #include "ether-addr-util.h" #include "event-util.h" +#include "exit-status.h" #include "extract-word.h" #include "fd-util.h" #include "fileio.h" @@ -159,6 +160,10 @@ static bool arg_firmware_describe = false; static Set *arg_firmware_features_include = NULL; static Set *arg_firmware_features_exclude = NULL; static char *arg_forward_journal = NULL; +static uint64_t arg_forward_journal_max_use = UINT64_MAX; +static uint64_t arg_forward_journal_keep_free = UINT64_MAX; +static uint64_t arg_forward_journal_max_file_size = UINT64_MAX; +static uint64_t arg_forward_journal_max_files = UINT64_MAX; static int arg_register = -1; static bool arg_keep_unit = false; static sd_id128_t arg_uuid = {}; @@ -844,6 +849,30 @@ static int parse_argv(int argc, char *argv[]) { return r; break; + OPTION_LONG("forward-journal-max-use", "BYTES", "Maximum disk space for forwarded journal"): + r = parse_size(arg, 1024, &arg_forward_journal_max_use); + if (r < 0) + return log_error_errno(r, "Failed to parse --forward-journal-max-use= value: %s", optarg); + break; + + OPTION_LONG("forward-journal-keep-free", "BYTES", "Minimum disk space to keep free"): + r = parse_size(arg, 1024, &arg_forward_journal_keep_free); + if (r < 0) + return log_error_errno(r, "Failed to parse --forward-journal-keep-free= value: %s", optarg); + break; + + OPTION_LONG("forward-journal-max-file-size", "BYTES", "Maximum size of individual journal files"): + r = parse_size(arg, 1024, &arg_forward_journal_max_file_size); + if (r < 0) + return log_error_errno(r, "Failed to parse --forward-journal-max-file-size= value: %s", optarg); + break; + + OPTION_LONG("forward-journal-max-files", "N", "Maximum number of journal files to keep"): + r = safe_atou64(arg, &arg_forward_journal_max_files); + if (r < 0) + return log_error_errno(r, "Failed to parse --forward-journal-max-files= value: %s", optarg); + break; + OPTION_LONG("pass-ssh-key", "BOOL", "Create an SSH key to access the VM"): r = parse_boolean_argument("--pass-ssh-key=", arg, &arg_pass_ssh_key); if (r < 0) @@ -923,6 +952,12 @@ static int parse_argv(int argc, char *argv[]) { if (arg_ram_slots > 0 && arg_ram_max == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Memory hotplug slots require a maximum RAM size"); + if ((arg_forward_journal_max_use != UINT64_MAX || + arg_forward_journal_keep_free != UINT64_MAX || + arg_forward_journal_max_file_size != UINT64_MAX || + arg_forward_journal_max_files != UINT64_MAX) && !arg_forward_journal) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--forward-journal-max-use=/--forward-journal-keep-free=/--forward-journal-max-file-size=/--forward-journal-max-files= require --forward-journal=."); + if (arg_ephemeral && arg_extra_drives.n_drives > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --ephemeral with --extra-drive="); @@ -1628,9 +1663,38 @@ static int start_systemd_journal_remote( if (!argv) return log_oom(); - r = fork_notify(argv, ret_pidref); + if (arg_forward_journal_max_use != UINT64_MAX && + strv_extendf(&argv, "--max-use=%" PRIu64, arg_forward_journal_max_use) < 0) + return log_oom(); + + if (arg_forward_journal_keep_free != UINT64_MAX && + strv_extendf(&argv, "--keep-free=%" PRIu64, arg_forward_journal_keep_free) < 0) + return log_oom(); + + if (arg_forward_journal_max_file_size != UINT64_MAX && + strv_extendf(&argv, "--max-file-size=%" PRIu64, arg_forward_journal_max_file_size) < 0) + return log_oom(); + + if (arg_forward_journal_max_files != UINT64_MAX && + strv_extendf(&argv, "--max-files=%" PRIu64, arg_forward_journal_max_files) < 0) + return log_oom(); + + r = fork_notify(/* argv= */ NULL, ret_pidref); if (r < 0) return r; + if (r == 0) { + /* In the child */ + if (setenv("SYSTEMD_JOURNAL_REMOTE_CONFIG_FILE", + "/dev/null", + /* overwrite= */ true) < 0) { + log_debug_errno(errno, "Failed to set $SYSTEMD_JOURNAL_REMOTE_CONFIG_FILE: %m"); + _exit(EXIT_MEMORY); + } + + r = invoke_callout_binary(argv[0], argv); + log_error_errno(r, "Failed to invoke %s: %m", argv[0]); + _exit(EXIT_EXEC); + } if (ret_listen_address) *ret_listen_address = TAKE_PTR(listen_address); From cc8f398202e3455a93fd79be8e454e6beedd7989 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 29 Mar 2026 11:15:35 +0000 Subject: [PATCH 1174/1296] nspawn: add --forward-journal= and --forward-journal-*= options Add --forward-journal=FILE|DIR to forward the container's journal entries to the host via systemd-journal-remote. When specified, nspawn starts systemd-journal-remote listening on a Unix socket, bind-mounts it into the container at /run/host/journal/socket, and passes a journal.forward_to_socket credential pointing to it. Add --forward-journal-max-use=, --forward-journal-keep-free=, --forward-journal-max-file-size=, and --forward-journal-max-files= to configure disk usage limits for the forwarded journal. Consolidate nspawn's per-machine on-disk state under a single runtime directory at /run/systemd/nspawn//. The container rootdir mount point moves from /tmp/nspawn-root-XXXXXX to /root, the unix-export directory moves from /run/systemd/nspawn/unix-export/ to /unix-export, and the journal-remote socket lives at /journal-remote-socket. Update ssh-generator and ssh-proxy to follow the new unix-export path layout. Extract fork_journal_remote() into fork-notify.{c,h} as a shared helper used by both nspawn and vmspawn, replacing vmspawn's start_systemd_journal_remote(). Extract runtime_directory_make() into path-lookup.{c,h} as a shared helper used by both nspawn and vmspawn, replacing vmspawn's inline runtime directory creation logic. Co-developed-by: Claude Opus 4.6 --- man/systemd-nspawn.xml | 30 +++++ shell-completion/bash/systemd-nspawn | 4 +- shell-completion/zsh/_systemd-nspawn | 5 + src/libsystemd/sd-path/path-lookup.c | 39 +++++++ src/libsystemd/sd-path/path-lookup.h | 1 + src/nspawn/nspawn.c | 169 +++++++++++++++++++++++---- src/shared/fork-notify.c | 94 +++++++++++++++ src/shared/fork-notify.h | 9 ++ src/ssh-generator/ssh-generator.c | 2 +- src/ssh-generator/ssh-proxy.c | 4 +- src/vmspawn/vmspawn.c | 141 +++------------------- 11 files changed, 346 insertions(+), 152 deletions(-) diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 045aa60db81f7..54d6f83915c7a 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -1590,6 +1590,36 @@ After=sys-subsystem-net-devices-ens1.device + + + + Forward the container's journal to the host by starting + systemd-journal-remote8 + listening on a Unix socket that is bind-mounted into the container. The container's + systemd-journald8 + connects to the socket via the journal.forward_to_socket credential and streams + journal entries to the host in real-time. Takes a path to a journal file or directory where the received + entries will be stored. If the path ends in .journal, entries are written to a single + file; otherwise, entries are split per host into the specified directory. + + + + + + + + + + + These options configure the corresponding settings of + systemd-journal-remote8 + when forwarding journal entries from the container. See + journal-remote.conf5 + for the descriptions of these settings. + + + + diff --git a/shell-completion/bash/systemd-nspawn b/shell-completion/bash/systemd-nspawn index b39d3cbd6d854..85f3023083c55 100644 --- a/shell-completion/bash/systemd-nspawn +++ b/shell-completion/bash/systemd-nspawn @@ -78,7 +78,9 @@ _systemd_nspawn() { --network-ipvlan --network-veth-extra --network-zone -p --port --system-call-filter --overlay --overlay-ro --settings --rlimit --hostname --no-new-privileges --oom-score-adjust --cpu-affinity --resolv-conf --timezone --root-hash-sig --background --oci-bundle --verity-data - --restrict-address-families' + --restrict-address-families + --forward-journal --forward-journal-max-use --forward-journal-keep-free + --forward-journal-max-file-size --forward-journal-max-files' ) _init_completion || return diff --git a/shell-completion/zsh/_systemd-nspawn b/shell-completion/zsh/_systemd-nspawn index ee28fa74759ab..10dc527236c52 100644 --- a/shell-completion/zsh/_systemd-nspawn +++ b/shell-completion/zsh/_systemd-nspawn @@ -54,4 +54,9 @@ _arguments \ "--notify-ready=[Control when the ready notification is sent]:options:(yes no)" \ "--suppress-sync=[Control whether to suppress disk synchronization for the container payload]:options:(yes no)" \ '--restrict-address-families=[Restrict socket address families accessible in the container.]: : _message "address families"' \ + '--forward-journal=[Forward the container journal to the host via systemd-journal-remote.]: : _files' \ + '--forward-journal-max-use=[Maximum disk space used by forwarded journal files.]: : _message bytes' \ + '--forward-journal-keep-free=[Disk space to keep free for forwarded journal files.]: : _message bytes' \ + '--forward-journal-max-file-size=[Maximum size of individual forwarded journal files.]: : _message bytes' \ + '--forward-journal-max-files=[Maximum number of forwarded journal files.]: : _message number' \ '*:: : _normal' diff --git a/src/libsystemd/sd-path/path-lookup.c b/src/libsystemd/sd-path/path-lookup.c index 32c14fb14a7d5..4e4abaebf8f1c 100644 --- a/src/libsystemd/sd-path/path-lookup.c +++ b/src/libsystemd/sd-path/path-lookup.c @@ -5,6 +5,7 @@ #include "alloc-util.h" #include "fs-util.h" #include "log.h" +#include "mkdir.h" #include "path-lookup.h" #include "path-util.h" #include "stat-util.h" @@ -101,6 +102,44 @@ int runtime_directory(RuntimeScope scope, const char *fallback_suffix, char **re return 1; } +int runtime_directory_make(RuntimeScope scope, const char *subdir, char **ret_dir, char **ret_dir_destroy) { + _cleanup_free_ char *dir = NULL, *destroy = NULL; + int r; + + assert(subdir); + assert(ret_dir); + assert(ret_dir_destroy); + + /* Use runtime_directory() (not _generic()) so that when we run in a systemd service + * with RuntimeDirectory= set, we pick up $RUNTIME_DIRECTORY and place our stuff into the + * directory the service manager prepared for us. When the env var is unset, we fall back + * to the provided subdirectory under /run (or the $XDG_RUNTIME_DIR equivalent in user scope) + * and take care of creation and destruction ourselves. */ + r = runtime_directory(scope, subdir, &dir); + if (r < 0) + return r; + + if (r > 0) { + /* $RUNTIME_DIRECTORY was not set, so we got the fallback path and need to create and + * clean up the directory ourselves. */ + destroy = strdup(dir); + if (!destroy) + return -ENOMEM; + + r = mkdir_p(dir, 0755); + if (r < 0) + return r; + } + + /* When $RUNTIME_DIRECTORY is set the service manager created the directory for us and + * will destroy it (or preserve it, per RuntimeDirectoryPreserve=) when the service stops. */ + + *ret_dir = TAKE_PTR(dir); + *ret_dir_destroy = TAKE_PTR(destroy); + + return 0; +} + static const char* const user_data_unit_paths[] = { "/usr/local/lib/systemd/user", "/usr/local/share/systemd/user", diff --git a/src/libsystemd/sd-path/path-lookup.h b/src/libsystemd/sd-path/path-lookup.h index 67a4f5d69cf0f..8dcbf766e6a1f 100644 --- a/src/libsystemd/sd-path/path-lookup.h +++ b/src/libsystemd/sd-path/path-lookup.h @@ -60,6 +60,7 @@ void lookup_paths_done(LookupPaths *p); int config_directory_generic(RuntimeScope scope, const char *suffix, char **ret); int runtime_directory_generic(RuntimeScope scope, const char *suffix, char **ret); int runtime_directory(RuntimeScope scope, const char *fallback_suffix, char **ret); +int runtime_directory_make(RuntimeScope scope, const char *subdir, char **ret_dir, char **ret_dir_destroy); /* We don't treat /etc/xdg/systemd/ in these functions as the xdg base dir spec suggests because we assume * that is a link to /etc/systemd/ anyway. */ diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index c7ccfd49963d3..b1c8defb6c46a 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -49,6 +49,7 @@ #include "fd-util.h" #include "fdset.h" #include "fileio.h" +#include "fork-notify.h" #include "format-table.h" #include "format-util.h" #include "fs-util.h" @@ -95,6 +96,7 @@ #include "pager.h" #include "parse-argument.h" #include "parse-util.h" +#include "path-lookup.h" #include "path-util.h" #include "pidref.h" #include "polkit-agent.h" @@ -133,6 +135,7 @@ /* The notify socket inside the container it can use to talk to nspawn using the sd_notify(3) protocol */ #define NSPAWN_NOTIFY_SOCKET_PATH "/run/host/notify" #define NSPAWN_MOUNT_TUNNEL "/run/host/incoming" +#define NSPAWN_JOURNAL_SOCKET_PATH "/run/host/journal/socket" #define EXIT_FORCE_RESTART 133 @@ -262,6 +265,11 @@ static char *arg_background = NULL; static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID; static bool arg_cleanup = false; static bool arg_ask_password = true; +static char *arg_forward_journal = NULL; +static uint64_t arg_forward_journal_max_use = UINT64_MAX; +static uint64_t arg_forward_journal_keep_free = UINT64_MAX; +static uint64_t arg_forward_journal_max_file_size = UINT64_MAX; +static uint64_t arg_forward_journal_max_files = UINT64_MAX; STATIC_DESTRUCTOR_REGISTER(arg_directory, freep); STATIC_DESTRUCTOR_REGISTER(arg_template, freep); @@ -303,6 +311,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_restrict_address_families, set_freep); STATIC_DESTRUCTOR_REGISTER(arg_settings_filename, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); STATIC_DESTRUCTOR_REGISTER(arg_background, freep); +STATIC_DESTRUCTOR_REGISTER(arg_forward_journal, freep); static int parse_private_users( const char *s, @@ -1250,6 +1259,36 @@ static int parse_argv(int argc, char *argv[]) { arg_settings_mask |= SETTING_LINK_JOURNAL; break; + OPTION_LONG("forward-journal", "FILE|DIR", "Forward the container's journal to the host"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_forward_journal); + if (r < 0) + return r; + break; + + OPTION_LONG("forward-journal-max-use", "BYTES", "Maximum disk space for forwarded journal"): + r = parse_size(arg, 1024, &arg_forward_journal_max_use); + if (r < 0) + return log_error_errno(r, "Failed to parse --forward-journal-max-use= value: %s", optarg); + break; + + OPTION_LONG("forward-journal-keep-free", "BYTES", "Minimum disk space to keep free"): + r = parse_size(arg, 1024, &arg_forward_journal_keep_free); + if (r < 0) + return log_error_errno(r, "Failed to parse --forward-journal-keep-free= value: %s", optarg); + break; + + OPTION_LONG("forward-journal-max-file-size", "BYTES", "Maximum size of individual journal files"): + r = parse_size(arg, 1024, &arg_forward_journal_max_file_size); + if (r < 0) + return log_error_errno(r, "Failed to parse --forward-journal-max-file-size= value: %s", optarg); + break; + + OPTION_LONG("forward-journal-max-files", "N", "Maximum number of journal files to keep"): + r = safe_atou64(arg, &arg_forward_journal_max_files); + if (r < 0) + return log_error_errno(r, "Failed to parse --forward-journal-max-files= value: %s", optarg); + break; + OPTION_GROUP("Mounts"): {} OPTION_LONG("bind", "PATH[:PATH[:OPTIONS]]", @@ -1446,6 +1485,12 @@ static int parse_argv(int argc, char *argv[]) { arg_caps_retain |= arg_private_network ? UINT64_C(1) << CAP_NET_ADMIN : 0; arg_caps_retain &= ~minus; + if ((arg_forward_journal_max_use != UINT64_MAX || + arg_forward_journal_keep_free != UINT64_MAX || + arg_forward_journal_max_file_size != UINT64_MAX || + arg_forward_journal_max_files != UINT64_MAX) && !arg_forward_journal) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--forward-journal-max-use=/--forward-journal-keep-free=/--forward-journal-max-file-size=/--forward-journal-max-files= require --forward-journal=."); + /* Make sure to parse environment before we reset the settings mask below */ r = parse_environment(); if (r < 0) @@ -3708,9 +3753,10 @@ static int setup_notify_child(const void *directory) { return TAKE_FD(fd); } -static int setup_unix_export_dir_outside(char **ret) { +static int setup_unix_export_dir_outside(const char *runtime_dir, char **ret) { int r; + assert(runtime_dir); assert(ret); if (arg_userns_mode == USER_NAMESPACE_MANAGED) { @@ -3719,7 +3765,7 @@ static int setup_unix_export_dir_outside(char **ret) { } _cleanup_free_ char *p = NULL; - p = path_join("/run/systemd/nspawn/unix-export", arg_machine); + p = path_join(runtime_dir, "unix-export"); if (!p) return log_oom(); @@ -5103,6 +5149,7 @@ static int load_oci_bundle(void) { } static int run_container( + const char *runtime_dir, const char *directory, int mount_fd, DissectedImage *dissected_image, @@ -5141,7 +5188,7 @@ static int run_container( assert_se(sigaddset(&mask_chld, SIGCHLD) == 0); /* Set up the unix export host directory on the host first */ - r = setup_unix_export_dir_outside(&unix_export_host_dir); + r = setup_unix_export_dir_outside(runtime_dir, &unix_export_host_dir); if (r < 0) return r; @@ -5907,18 +5954,22 @@ static int cant_be_in_netns(void) { return 0; } -static void cleanup_propagation_and_export_directories(void) { - const char *p; +static void cleanup_propagation_and_export_directories(const char *runtime_dir) { + _cleanup_free_ char *p = NULL; - if (!arg_machine || arg_runtime_scope != RUNTIME_SCOPE_SYSTEM) + if (!runtime_dir || arg_userns_mode == USER_NAMESPACE_MANAGED) return; - p = strjoina("/run/systemd/nspawn/propagate/", arg_machine); - (void) rm_rf(p, REMOVE_ROOT); + p = path_join("/run/systemd/nspawn/propagate", arg_machine); + if (p) + (void) rm_rf(p, REMOVE_ROOT); - p = strjoina("/run/systemd/nspawn/unix-export/", arg_machine); - (void) umount2(p, MNT_DETACH|UMOUNT_NOFOLLOW); - (void) rmdir(p); + free(p); + p = path_join(runtime_dir, "unix-export"); + if (p) { + (void) umount2(p, MNT_DETACH|UMOUNT_NOFOLLOW); + (void) rmdir(p); + } } static int do_cleanup(void) { @@ -5931,7 +5982,16 @@ static int do_cleanup(void) { if (r < 0) return r; - cleanup_propagation_and_export_directories(); + _cleanup_free_ char *subdir = path_join("systemd/nspawn", arg_machine); + if (!subdir) + return log_oom(); + + _cleanup_free_ char *runtime_dir = NULL; + r = runtime_directory(arg_runtime_scope, subdir, &runtime_dir); + if (r < 0) + return r; + + cleanup_propagation_and_export_directories(runtime_dir); return 0; } @@ -5951,6 +6011,9 @@ static int run(int argc, char *argv[]) { _cleanup_(sd_netlink_unrefp) sd_netlink *nfnl = NULL; _cleanup_(pidref_done) PidRef pid = PIDREF_NULL; _cleanup_(sd_varlink_unrefp) sd_varlink *nsresource_link = NULL, *mountfsd_link = NULL; + _cleanup_free_ char *runtime_dir = NULL, *subdir = NULL; + _cleanup_(rm_rf_physical_and_freep) char *runtime_dir_destroy = NULL; + _cleanup_(fork_notify_terminate) PidRef journal_remote_pidref = PIDREF_NULL; log_setup(); @@ -6440,6 +6503,22 @@ static int run(int argc, char *argv[]) { } else assert_not_reached(); + subdir = path_join("systemd/nspawn", arg_machine); + if (!subdir) { + r = log_oom(); + goto finish; + } + + r = runtime_directory_make( + arg_runtime_scope, + subdir, + &runtime_dir, + &runtime_dir_destroy); + if (r < 0) { + log_error_errno(r, "Failed to create runtime directory: %m"); + goto finish; + } + /* Create a temporary place to mount stuff. */ r = mkdtemp_malloc("/tmp/nspawn-root-XXXXXX", &rootdir); if (r < 0) { @@ -6486,8 +6565,61 @@ static int run(int argc, char *argv[]) { expose_args.nfnl = nfnl; } + if (arg_forward_journal) { + _cleanup_free_ char *socket_path = path_join(runtime_dir, "journal-remote-socket"); + if (!socket_path) { + r = log_oom(); + goto finish; + } + + union sockaddr_union sa; + r = sockaddr_un_set_path(&sa.un, socket_path); + if (r < 0) { + log_error_errno(r, "Failed to set AF_UNIX path to '%s': %m", socket_path); + goto finish; + } + + (void) sockaddr_un_unlink(&sa.un); + + r = fork_journal_remote( + socket_path, + arg_forward_journal, + arg_forward_journal_max_use, + arg_forward_journal_keep_free, + arg_forward_journal_max_file_size, + arg_forward_journal_max_files, + &journal_remote_pidref); + if (r < 0) + goto finish; + + CustomMount *cm = custom_mount_add(&arg_custom_mounts, &arg_n_custom_mounts, CUSTOM_MOUNT_BIND); + if (!cm) { + r = log_oom(); + goto finish; + } + + cm->source = TAKE_PTR(socket_path); + cm->read_only = true; + cm->destination = strdup(NSPAWN_JOURNAL_SOCKET_PATH); + if (!cm->destination) { + r = log_oom(); + goto finish; + } + + r = machine_credential_add(&arg_credentials, "journal.forward_to_socket", NSPAWN_JOURNAL_SOCKET_PATH, SIZE_MAX); + if (r == -EEXIST) { + log_error_errno(r, "Credential 'journal.forward_to_socket' already set via --set-credential=, refusing --forward-journal=."); + goto finish; + } + if (r < 0) { + log_error_errno(r, "Failed to add 'journal.forward_to_socket' credential: %m"); + goto finish; + } + } + for (;;) { r = run_container( + runtime_dir, rootdir, mount_fd, dissected_image, @@ -6528,18 +6660,7 @@ static int run(int argc, char *argv[]) { log_warning_errno(errno, "Can't remove image file '%s', ignoring: %m", arg_image); } - if (arg_machine && arg_userns_mode != USER_NAMESPACE_MANAGED) { - const char *p; - - p = strjoina("/run/systemd/nspawn/propagate/", arg_machine); - (void) rm_rf(p, REMOVE_ROOT); - - p = strjoina("/run/systemd/nspawn/unix-export/", arg_machine); - (void) umount2(p, MNT_DETACH|UMOUNT_NOFOLLOW); - (void) rmdir(p); - } - - cleanup_propagation_and_export_directories(); + cleanup_propagation_and_export_directories(runtime_dir); expose_port_flush(nfnl, arg_expose_ports, AF_INET, &expose_args.address4); expose_port_flush(nfnl, arg_expose_ports, AF_INET6, &expose_args.address6); diff --git a/src/shared/fork-notify.c b/src/shared/fork-notify.c index e9686786fe719..066ee29115faa 100644 --- a/src/shared/fork-notify.c +++ b/src/shared/fork-notify.c @@ -3,14 +3,19 @@ #include #include +#include "alloc-util.h" #include "build-path.h" +#include "chase.h" +#include "chattr-util.h" #include "escape.h" #include "event-util.h" #include "exit-status.h" +#include "fd-util.h" #include "fork-notify.h" #include "log.h" #include "notify-recv.h" #include "parse-util.h" +#include "path-util.h" #include "pidref.h" #include "process-util.h" #include "runtime-scope.h" @@ -238,3 +243,92 @@ int journal_fork(RuntimeScope scope, char * const* units, OutputMode output, Pid return fork_notify(argv, ret_pidref); } + +int fork_journal_remote( + const char *listen_address, + const char *output, + uint64_t max_use, + uint64_t keep_free, + uint64_t max_file_size, + uint64_t max_files, + PidRef *ret_pidref) { + + int r; + + assert(listen_address); + assert(output); + assert(ret_pidref); + + ChaseFlags chase_flags = CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY; + if (endswith(output, ".journal")) + chase_flags |= CHASE_PARENT; + + _cleanup_close_ int fd = -EBADF; + r = chase(output, /* root= */ NULL, chase_flags, /* ret_path= */ NULL, &fd); + if (r < 0) + return log_error_errno(r, "Failed to create journal directory for '%s': %m", output); + + r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL); + if (r < 0) + log_debug_errno(r, "Failed to set NOCOW flag on journal directory for '%s', ignoring: %m", output); + + _cleanup_free_ char *sd_socket_activate = NULL; + r = find_executable("systemd-socket-activate", &sd_socket_activate); + if (r < 0) + return log_error_errno(r, "Failed to find systemd-socket-activate binary: %m"); + + _cleanup_free_ char *sd_journal_remote = NULL; + r = find_executable_full( + "systemd-journal-remote", + /* root= */ NULL, + STRV_MAKE(LIBEXECDIR), + /* use_path_envvar= */ true, + &sd_journal_remote, + /* ret_fd= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to find systemd-journal-remote binary: %m"); + + _cleanup_strv_free_ char **argv = strv_new( + sd_socket_activate, + "--listen", listen_address, + sd_journal_remote, + "--output", output, + "--split-mode", endswith(output, ".journal") ? "none" : "host"); + if (!argv) + return log_oom(); + + if (max_use != UINT64_MAX && + strv_extendf(&argv, "--max-use=%" PRIu64, max_use) < 0) + return log_oom(); + + if (keep_free != UINT64_MAX && + strv_extendf(&argv, "--keep-free=%" PRIu64, keep_free) < 0) + return log_oom(); + + if (max_file_size != UINT64_MAX && + strv_extendf(&argv, "--max-file-size=%" PRIu64, max_file_size) < 0) + return log_oom(); + + if (max_files != UINT64_MAX && + strv_extendf(&argv, "--max-files=%" PRIu64, max_files) < 0) + return log_oom(); + + r = fork_notify(/* argv= */ NULL, ret_pidref); + if (r < 0) + return r; + if (r == 0) { + /* In the child */ + if (setenv("SYSTEMD_JOURNAL_REMOTE_CONFIG_FILE", + "/dev/null", + /* overwrite= */ true) < 0) { + log_debug_errno(errno, "Failed to set $SYSTEMD_JOURNAL_REMOTE_CONFIG_FILE: %m"); + _exit(EXIT_MEMORY); + } + + r = invoke_callout_binary(argv[0], argv); + log_debug_errno(r, "Failed to invoke %s: %m", argv[0]); + _exit(EXIT_EXEC); + } + + return 0; +} diff --git a/src/shared/fork-notify.h b/src/shared/fork-notify.h index 2dbfe368a4664..cc241beff9335 100644 --- a/src/shared/fork-notify.h +++ b/src/shared/fork-notify.h @@ -11,3 +11,12 @@ void fork_notify_terminate(PidRef *pidref); void fork_notify_terminate_many(sd_event_source **array, size_t n); int journal_fork(RuntimeScope scope, char * const *units, OutputMode output, PidRef *ret_pidref); + +int fork_journal_remote( + const char *listen_address, + const char *output, + uint64_t max_use, + uint64_t keep_free, + uint64_t max_file_size, + uint64_t max_files, + PidRef *ret_pidref); diff --git a/src/ssh-generator/ssh-generator.c b/src/ssh-generator/ssh-generator.c index bc01250a55b12..ae062fc58da47 100644 --- a/src/ssh-generator/ssh-generator.c +++ b/src/ssh-generator/ssh-generator.c @@ -338,7 +338,7 @@ static int add_export_unix_socket( return r; log_debug("Binding SSH to AF_UNIX socket /run/host/unix-export/ssh\n" - "→ connect via 'ssh unix/run/systemd/nspawn/unix-export/\?\?\?/ssh' from host"); + "→ connect via 'ssh unix/run/systemd/nspawn/\?\?\?/unix-export/ssh' from host"); return 0; } diff --git a/src/ssh-generator/ssh-proxy.c b/src/ssh-generator/ssh-proxy.c index fa42c5bee8021..79d8ee056568e 100644 --- a/src/ssh-generator/ssh-proxy.c +++ b/src/ssh-generator/ssh-proxy.c @@ -349,11 +349,11 @@ static int process_machine(const char *machine, const char *port) { if (!streq_ptr(p.service, "systemd-nspawn")) return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Don't know how to SSH into '%s' container %s.", p.service, machine); - r = runtime_directory_generic(scope, "systemd/nspawn/unix-export", &path); + r = runtime_directory_generic(scope, "systemd/nspawn", &path); if (r < 0) return log_error_errno(r, "Failed to determine runtime directory: %m"); - if (!path_extend(&path, machine, "ssh")) + if (!path_extend(&path, machine, "unix-export", "ssh")) return log_oom(); r = is_socket(path); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index b7f03501f76b0..8ccd631762cca 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -25,8 +25,6 @@ #include "bus-locator.h" #include "bus-util.h" #include "capability-util.h" -#include "chase.h" -#include "chattr-util.h" #include "common-signal.h" #include "copy.h" #include "discover-image.h" @@ -34,7 +32,6 @@ #include "escape.h" #include "ether-addr-util.h" #include "event-util.h" -#include "exit-status.h" #include "extract-word.h" #include "fd-util.h" #include "fileio.h" @@ -1621,87 +1618,6 @@ static int start_tpm( return 0; } -static int start_systemd_journal_remote( - const char *scope, - unsigned port, - const char *sd_socket_activate, - char **ret_listen_address, - PidRef *ret_pidref) { - - int r; - - assert(scope); - assert(sd_socket_activate); - - _cleanup_free_ char *scope_prefix = NULL; - r = unit_name_to_prefix(scope, &scope_prefix); - if (r < 0) - return log_error_errno(r, "Failed to strip .scope suffix from scope: %m"); - - _cleanup_free_ char *listen_address = NULL; - if (asprintf(&listen_address, "vsock:2:%u", port) < 0) - return log_oom(); - - _cleanup_free_ char *sd_journal_remote = NULL; - r = find_executable_full( - "systemd-journal-remote", - /* root= */ NULL, - STRV_MAKE(LIBEXECDIR), - /* use_path_envvar= */ true, /* systemd-journal-remote should be installed in - * LIBEXECDIR, but for supporting fancy setups. */ - &sd_journal_remote, - /* ret_fd= */ NULL); - if (r < 0) - return log_error_errno(r, "Failed to find systemd-journal-remote binary: %m"); - - _cleanup_strv_free_ char **argv = strv_new( - sd_socket_activate, - "--listen", listen_address, - sd_journal_remote, - "--output", arg_forward_journal, - "--split-mode", endswith(arg_forward_journal, ".journal") ? "none" : "host"); - if (!argv) - return log_oom(); - - if (arg_forward_journal_max_use != UINT64_MAX && - strv_extendf(&argv, "--max-use=%" PRIu64, arg_forward_journal_max_use) < 0) - return log_oom(); - - if (arg_forward_journal_keep_free != UINT64_MAX && - strv_extendf(&argv, "--keep-free=%" PRIu64, arg_forward_journal_keep_free) < 0) - return log_oom(); - - if (arg_forward_journal_max_file_size != UINT64_MAX && - strv_extendf(&argv, "--max-file-size=%" PRIu64, arg_forward_journal_max_file_size) < 0) - return log_oom(); - - if (arg_forward_journal_max_files != UINT64_MAX && - strv_extendf(&argv, "--max-files=%" PRIu64, arg_forward_journal_max_files) < 0) - return log_oom(); - - r = fork_notify(/* argv= */ NULL, ret_pidref); - if (r < 0) - return r; - if (r == 0) { - /* In the child */ - if (setenv("SYSTEMD_JOURNAL_REMOTE_CONFIG_FILE", - "/dev/null", - /* overwrite= */ true) < 0) { - log_debug_errno(errno, "Failed to set $SYSTEMD_JOURNAL_REMOTE_CONFIG_FILE: %m"); - _exit(EXIT_MEMORY); - } - - r = invoke_callout_binary(argv[0], argv); - log_error_errno(r, "Failed to invoke %s: %m", argv[0]); - _exit(EXIT_EXEC); - } - - if (ret_listen_address) - *ret_listen_address = TAKE_PTR(listen_address); - - return 0; -} - static int discover_root(char **ret) { int r; _cleanup_(dissected_image_unrefp) DissectedImage *image = NULL; @@ -2725,13 +2641,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return log_oom(); /* Create our runtime directory. We need this for the QMP varlink control socket, the QEMU - * config file, TPM state, virtiofsd sockets, runtime mounts, and SSH key material. - * - * Use runtime_directory() (not _generic()) so that when vmspawn runs in a systemd service - * with RuntimeDirectory= set, we pick up $RUNTIME_DIRECTORY and place our stuff into the - * directory the service manager prepared for us. When the env var is unset, we fall back - * to /run/systemd/vmspawn// (or the $XDG_RUNTIME_DIR equivalent in user scope) - * and take care of creation and destruction ourselves. */ + * config file, TPM state, virtiofsd sockets, runtime mounts, and SSH key material. */ _cleanup_free_ char *runtime_dir = NULL, *runtime_dir_suffix = NULL; _cleanup_(rm_rf_physical_and_freep) char *runtime_dir_destroy = NULL; @@ -2739,27 +2649,14 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (!runtime_dir_suffix) return log_oom(); - r = runtime_directory(arg_runtime_scope, runtime_dir_suffix, &runtime_dir); + r = runtime_directory_make(arg_runtime_scope, runtime_dir_suffix, &runtime_dir, &runtime_dir_destroy); if (r < 0) - return log_error_errno(r, "Failed to determine runtime directory: %m"); - if (r > 0) { - /* $RUNTIME_DIRECTORY was not set, so we got the fallback path and need to create and - * clean up the directory ourselves. - * - * If a previous vmspawn instance was killed without cleanup (e.g. SIGKILL), the directory may - * already exist with stale contents. This is harmless: varlink's sockaddr_un_unlink() removes stale - * sockets before bind(), and other files (QEMU config, SSH keys) are created fresh. This matches - * nspawn's approach of not proactively cleaning stale runtime directories. */ - r = mkdir_p(runtime_dir, 0755); - if (r < 0) - return log_error_errno(r, "Failed to create runtime directory '%s': %m", runtime_dir); + return log_error_errno(r, "Failed to create runtime directory: %m"); - runtime_dir_destroy = strdup(runtime_dir); - if (!runtime_dir_destroy) - return log_oom(); - } - /* When $RUNTIME_DIRECTORY is set the service manager created the directory for us and - * will destroy it (or preserve it, per RuntimeDirectoryPreserve=) when the service stops. */ + /* If a previous vmspawn instance was killed without cleanup (e.g. SIGKILL), the directory may + * already exist with stale contents. This is harmless: varlink's sockaddr_un_unlink() removes stale + * sockets before bind(), and other files (QEMU config, SSH keys) are created fresh. This matches + * nspawn's approach of not proactively cleaning stale runtime directories. */ log_debug("Using runtime directory: %s", runtime_dir); @@ -3471,25 +3368,21 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (arg_forward_journal) { _cleanup_free_ char *listen_address = NULL; - - ChaseFlags chase_flags = CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY; - if (endswith(arg_forward_journal, ".journal")) - chase_flags |= CHASE_PARENT; - - _cleanup_close_ int journal_fd = -EBADF; - r = chase(arg_forward_journal, /* root= */ NULL, chase_flags, /* ret_path= */ NULL, &journal_fd); - if (r < 0) - return log_error_errno(r, "Failed to create journal directory for '%s': %m", arg_forward_journal); - - r = chattr_fd(journal_fd, FS_NOCOW_FL, FS_NOCOW_FL); - if (r < 0) - log_debug_errno(r, "Failed to set NOCOW flag on journal directory for '%s', ignoring: %m", arg_forward_journal); + if (asprintf(&listen_address, "vsock:2:%u", child_cid) < 0) + return log_oom(); if (!GREEDY_REALLOC(children, n_children + 1)) return log_oom(); _cleanup_(fork_notify_terminate) PidRef child = PIDREF_NULL; - r = start_systemd_journal_remote(unit, child_cid, sd_socket_activate, &listen_address, &child); + r = fork_journal_remote( + listen_address, + arg_forward_journal, + arg_forward_journal_max_use, + arg_forward_journal_keep_free, + arg_forward_journal_max_file_size, + arg_forward_journal_max_files, + &child); if (r < 0) return r; From 8030e0b19ef7c0e823d84dd08ad38a2d88e0a230 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Wed, 22 Apr 2026 19:12:23 +0200 Subject: [PATCH 1175/1296] test: wrap mount/umount when running with sanitizers On Fedora Rawhide mount/umount is linked against libsystemd, which then breaks the binaries in sanitizer runs, as we try to run instrumented code from an uninstrumented binary: bash-5.3# ldd /usr/bin/mount linux-vdso.so.1 (0x00007fa757ef9000) libmount.so.1 => /lib64/libmount.so.1 (0x00007fa757e84000) libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fa757e51000) libc.so.6 => /lib64/libc.so.6 (0x00007fa757c56000) libblkid.so.1 => /lib64/libblkid.so.1 (0x00007fa757c16000) libsystemd.so.0 => /lib64/libsystemd.so.0 (0x00007fa757400000) libpcre2-8.so.0 => /lib64/libpcre2-8.so.0 (0x00007fa75734f000) /lib64/ld-linux-x86-64.so.2 (0x00007fa757efb000) libclang_rt.asan.so => /usr/lib/clang/22/lib/x86_64-redhat-linux-gnu/libclang_rt.asan.so (0x00007fa756800000) libm.so.6 => /lib64/libm.so.6 (0x00007fa7566e4000) libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fa7566b7000) libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007fa756400000) bash-5.3# mount ==458==ASan runtime does not come first in initial library list; you should either link runtime to your application or manually preload it with LD_PRELOAD. This then breaks the whole machine, as mount is quite essential during boot. Let's just add mount/umount to the list of wrapped binaries to fix this. --- mkosi/mkosi.sanitizers/mkosi.postinst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mkosi/mkosi.sanitizers/mkosi.postinst b/mkosi/mkosi.sanitizers/mkosi.postinst index 118433125462b..88ba2c2bc098e 100755 --- a/mkosi/mkosi.sanitizers/mkosi.postinst +++ b/mkosi/mkosi.sanitizers/mkosi.postinst @@ -84,6 +84,7 @@ wrap=( mdadm mkfs.btrfs mksquashfs + mount multipath multipathd nvme @@ -99,6 +100,7 @@ wrap=( su tar tgtd + umount unix_chkpwd useradd userdel From 9149c7595305a7c4d105d5d33ba25733af4302eb Mon Sep 17 00:00:00 2001 From: Clayton Craft Date: Wed, 22 Apr 2026 11:00:04 -0700 Subject: [PATCH 1176/1296] ukify: fix default path for hwids The documentation and commit that added this seem to suggest this should be under /usr/lib/systemd fixes 117ec9db7e71357837190833d7731bc61ae54ecc --- src/ukify/ukify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ukify/ukify.py b/src/ukify/ukify.py index 6f492bc9ba07f..03d475e5e763e 100755 --- a/src/ukify/ukify.py +++ b/src/ukify/ukify.py @@ -1401,7 +1401,7 @@ def make_uki(opts: UkifyConfig) -> None: if opts.hwids is not None: hwids = parse_hwid_dir(Path(opts.hwids)) else: - hwids_dir = Path(f'/tmp/s/usr/lib/systemd/boot/hwids/{opts.efi_arch}') + hwids_dir = Path(f'/usr/lib/systemd/boot/hwids/{opts.efi_arch}') if hwids_dir.is_dir(): print(f'Automatically building .hwids section from {hwids_dir}', file=sys.stderr) hwids = parse_hwid_dir(hwids_dir) From 909efb6f2e85b74e8b982027f42a344f7199158e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 20 Apr 2026 19:47:38 +0000 Subject: [PATCH 1177/1296] shared: load libgnutls and libmicrohttpd via dlopen Convert the GnuTLS and libmicrohttpd usage in journal-remote to the dlopen pattern used by other optional shared libraries. A new src/shared/gnutls-util.{h,c} declares the GnuTLS entry points via DLSYM_PROTOTYPE and resolves them in dlopen_gnutls(); microhttpd-util is moved from src/journal-remote to src/shared and gains analogous DLSYM_PROTOTYPEs plus dlopen_microhttpd(). Callers in journal-gatewayd, journal-remote-main and microhttpd-util itself call the sym_* wrappers and invoke dlopen_gnutls()/dlopen_microhttpd() at their entry points. setup_gnutls_logger() no longer fails when libgnutls is missing at runtime; it logs a notice and returns 0 so journal-gatewayd starts up without TLS dependencies installed. The meson files gain libgnutls_cflags and libmicrohttpd_cflags partial dependencies that expose include paths and compile flags only. Every systemd-journal-{gatewayd,remote,upload} target switches to the cflags variant, dropping the direct libgnutls/libmicrohttpd link. The gatewayd->remote object-sharing dance for microhttpd-util.o goes away since the code now lives in libshared. test-dlopen-so gains assertions for dlopen_gnutls and dlopen_microhttpd. --- meson.build | 2 + mkosi/mkosi.conf.d/centos-fedora/mkosi.conf | 1 + mkosi/mkosi.conf.d/opensuse/mkosi.conf | 1 + src/journal-remote/journal-gatewayd.c | 63 ++++++---- src/journal-remote/journal-remote-main.c | 43 ++++--- src/journal-remote/meson.build | 13 +- src/shared/gnutls-util.c | 51 ++++++++ src/shared/gnutls-util.h | 32 +++++ src/shared/meson.build | 4 + .../microhttpd-util.c | 119 ++++++++++++++---- .../microhttpd-util.h | 30 ++++- src/test/meson.build | 2 + src/test/test-dlopen-so.c | 4 + 13 files changed, 287 insertions(+), 78 deletions(-) create mode 100644 src/shared/gnutls-util.c create mode 100644 src/shared/gnutls-util.h rename src/{journal-remote => shared}/microhttpd-util.c (65%) rename src/{journal-remote => shared}/microhttpd-util.h (76%) diff --git a/meson.build b/meson.build index 287456ad6eb5a..95d95c43cd27f 100644 --- a/meson.build +++ b/meson.build @@ -1181,6 +1181,7 @@ libmicrohttpd = dependency('libmicrohttpd', version : '>= 0.9.33', required : get_option('microhttpd')) conf.set10('HAVE_MICROHTTPD', libmicrohttpd.found()) +libmicrohttpd_cflags = libmicrohttpd.partial_dependency(includes: true, compile_args: true) libcryptsetup = get_option('libcryptsetup') libcryptsetup_plugins = get_option('libcryptsetup-plugins') @@ -1252,6 +1253,7 @@ libgnutls = dependency('gnutls', version : '>= 3.1.4', required : get_option('gnutls')) conf.set10('HAVE_GNUTLS', libgnutls.found()) +libgnutls_cflags = libgnutls.partial_dependency(includes: true, compile_args: true) libopenssl = dependency('openssl', version : '>= 3.0.0', diff --git a/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf b/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf index dd00fa737cfa9..fc9ffd58c968b 100644 --- a/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf +++ b/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf @@ -43,6 +43,7 @@ Packages= kernel-core knot libcap-ng-utils + libmicrohttpd libucontext man-db nmap-ncat diff --git a/mkosi/mkosi.conf.d/opensuse/mkosi.conf b/mkosi/mkosi.conf.d/opensuse/mkosi.conf index b0593e3f1ab9d..1198d2c15c4cc 100644 --- a/mkosi/mkosi.conf.d/opensuse/mkosi.conf +++ b/mkosi/mkosi.conf.d/opensuse/mkosi.conf @@ -57,6 +57,7 @@ Packages= libcap-progs libdw-devel libdw1 + libmicrohttpd12 libtss2-tcti-device0 libz1 man diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index 7cb884622490e..42e3f6df29572 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -311,7 +311,7 @@ static int request_parse_accept( assert(m); assert(connection); - header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept"); + header = sym_MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept"); if (!header) return 0; @@ -459,7 +459,7 @@ static int request_parse_range( assert(m); assert(connection); - range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range"); + range = sym_MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range"); if (!range) return 0; @@ -566,7 +566,7 @@ static int request_parse_arguments( assert(connection); m->argument_parse_error = 0; - MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m); + sym_MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m); return m->argument_parse_error; } @@ -616,14 +616,14 @@ static int request_handler_entries( if (r < 0) return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal."); - response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL); + response = sym_MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL); if (!response) return respond_oom(connection); - if (MHD_add_response_header(response, "Content-Type", mime_types[m->mode]) == MHD_NO) + if (sym_MHD_add_response_header(response, "Content-Type", mime_types[m->mode]) == MHD_NO) return respond_oom(connection); - return MHD_queue_response(connection, MHD_HTTP_OK, response); + return sym_MHD_queue_response(connection, MHD_HTTP_OK, response); } static int output_field(FILE *f, OutputMode m, const char *d, size_t l) { @@ -744,14 +744,14 @@ static int request_handler_fields( if (r < 0) return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields."); - response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL); + response = sym_MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL); if (!response) return respond_oom(connection); - if (MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]) == MHD_NO) + if (sym_MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]) == MHD_NO) return respond_oom(connection); - return MHD_queue_response(connection, MHD_HTTP_OK, response); + return sym_MHD_queue_response(connection, MHD_HTTP_OK, response); } static int request_handler_redirect( @@ -767,16 +767,16 @@ static int request_handler_redirect( if (asprintf(&page, "Please continue to the journal browser.", target) < 0) return respond_oom(connection); - response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE); + response = sym_MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE); if (!response) return respond_oom(connection); TAKE_PTR(page); - if (MHD_add_response_header(response, "Content-Type", "text/html") == MHD_NO || - MHD_add_response_header(response, "Location", target) == MHD_NO) + if (sym_MHD_add_response_header(response, "Content-Type", "text/html") == MHD_NO || + sym_MHD_add_response_header(response, "Location", target) == MHD_NO) return respond_oom(connection); - return MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response); + return sym_MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response); } static int request_handler_file( @@ -799,15 +799,15 @@ static int request_handler_file( if (fstat(fd, &st) < 0) return mhd_respondf(connection, errno, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m"); - response = MHD_create_response_from_fd_at_offset64(st.st_size, fd, 0); + response = sym_MHD_create_response_from_fd_at_offset64(st.st_size, fd, 0); if (!response) return respond_oom(connection); TAKE_FD(fd); - if (MHD_add_response_header(response, "Content-Type", mime_type) == MHD_NO) + if (sym_MHD_add_response_header(response, "Content-Type", mime_type) == MHD_NO) return respond_oom(connection); - return MHD_queue_response(connection, MHD_HTTP_OK, response); + return sym_MHD_queue_response(connection, MHD_HTTP_OK, response); } static int get_virtualization(char **v) { @@ -899,15 +899,15 @@ static int request_handler_machine( if (r < 0) return respond_oom(connection); - response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE); + response = sym_MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE); if (!response) return respond_oom(connection); TAKE_PTR(json); - if (MHD_add_response_header(response, "Content-Type", "application/json") == MHD_NO) + if (sym_MHD_add_response_header(response, "Content-Type", "application/json") == MHD_NO) return respond_oom(connection); - return MHD_queue_response(connection, MHD_HTTP_OK, response); + return sym_MHD_queue_response(connection, MHD_HTTP_OK, response); } static int output_boot(FILE *f, LogId boot, int boot_display_index) { @@ -1026,14 +1026,14 @@ static int request_handler_boots( if (r < 0) return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to seek in journal: %m"); - response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_boots, m, NULL); + response = sym_MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_boots, m, NULL); if (!response) return respond_oom(connection); - if (MHD_add_response_header(response, "Content-Type", "application/json-seq") == MHD_NO) + if (sym_MHD_add_response_header(response, "Content-Type", "application/json-seq") == MHD_NO) return respond_oom(connection); - return MHD_queue_response(connection, MHD_HTTP_OK, response); + return sym_MHD_queue_response(connection, MHD_HTTP_OK, response); } static mhd_result request_handler( @@ -1290,6 +1290,10 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + r = dlopen_microhttpd(LOG_ERR); + if (r < 0) + return r; + journal_browse_prepare(); assert_se(sigaction(SIGTERM, &sigterm, NULL) >= 0); @@ -1323,11 +1327,16 @@ static int run(int argc, char *argv[]) { { MHD_OPTION_HTTPS_MEM_TRUST, 0, arg_trust_pem }; } - d = MHD_start_daemon(flags, 19531, - NULL, NULL, - request_handler, NULL, - MHD_OPTION_ARRAY, opts, - MHD_OPTION_END); + d = sym_MHD_start_daemon( + flags, + /* port= */ 19531, + /* acp= */ NULL, + /* acp_cls= */ NULL, + request_handler, + /* dh_cls= */ NULL, + MHD_OPTION_ARRAY, + opts, + MHD_OPTION_END); if (!d) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to start daemon!"); diff --git a/src/journal-remote/journal-remote-main.c b/src/journal-remote/journal-remote-main.c index 4867bf360946f..1fbcc27815210 100644 --- a/src/journal-remote/journal-remote-main.c +++ b/src/journal-remote/journal-remote-main.c @@ -108,7 +108,7 @@ static MHDDaemonWrapper* MHDDaemonWrapper_free(MHDDaemonWrapper *d) { d->timer_event = sd_event_source_unref(d->timer_event); if (d->daemon) - MHD_stop_daemon(d->daemon); + sym_MHD_stop_daemon(d->daemon); return mfree(d); } @@ -361,7 +361,7 @@ static mhd_result request_handler( if (*connection_cls) { RemoteSource *source = *connection_cls; - header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Content-Encoding"); + header = sym_MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Content-Encoding"); if (header) { Compression c = compression_from_string_harder(header); if (c <= 0 || !compression_supported(c)) @@ -382,12 +382,12 @@ static mhd_result request_handler( if (!streq(url, "/upload")) return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found."); - header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Content-Type"); + header = sym_MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Content-Type"); if (!header || !streq(header, "application/vnd.fdo.journal")) return mhd_respond(connection, MHD_HTTP_UNSUPPORTED_MEDIA_TYPE, "Content-Type: application/vnd.fdo.journal is required."); - header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Transfer-Encoding"); + header = sym_MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Transfer-Encoding"); if (header) { if (!strcaseeq(header, "chunked")) return mhd_respondf(connection, 0, MHD_HTTP_BAD_REQUEST, @@ -396,7 +396,7 @@ static mhd_result request_handler( chunked = true; } - header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Content-Length"); + header = sym_MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Content-Length"); if (header) { size_t len; @@ -420,8 +420,8 @@ static mhd_result request_handler( { const union MHD_ConnectionInfo *ci; - ci = MHD_get_connection_info(connection, - MHD_CONNECTION_INFO_CONNECTION_FD); + ci = sym_MHD_get_connection_info(connection, + MHD_CONNECTION_INFO_CONNECTION_FD); if (!ci) { log_error("MHD_get_connection_info failed: cannot get remote fd"); return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, @@ -464,6 +464,12 @@ static int setup_microhttpd_server(RemoteServer *s, const char *trust) { #if HAVE_MICROHTTPD + int r; + + r = dlopen_microhttpd(LOG_ERR); + if (r < 0) + return r; + struct MHD_OptionItem opts[] = { { MHD_OPTION_EXTERNAL_LOGGER, (intptr_t) microhttpd_logger}, { MHD_OPTION_NOTIFY_COMPLETED, (intptr_t) request_meta_free}, @@ -483,7 +489,7 @@ static int setup_microhttpd_server(RemoteServer *s, _cleanup_(MHDDaemonWrapper_freep) MHDDaemonWrapper *d = NULL; const union MHD_DaemonInfo *info; - int r, epoll_fd; + int epoll_fd; assert(fd >= 0); @@ -526,18 +532,23 @@ static int setup_microhttpd_server(RemoteServer *s, d->fd = (uint64_t) fd; - d->daemon = MHD_start_daemon(flags, 0, - NULL, NULL, - request_handler, NULL, - MHD_OPTION_ARRAY, opts, - MHD_OPTION_END); + d->daemon = sym_MHD_start_daemon( + flags, + /* port= */ 0, + /* acp= */ NULL, + /* acp_cls= */ NULL, + request_handler, + /* dh_cls= */ NULL, + MHD_OPTION_ARRAY, + opts, + MHD_OPTION_END); if (!d->daemon) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to start μhttp daemon"); log_debug("Started MHD %s daemon on fd:%d (wrapper @ %p)", key ? "HTTPS" : "HTTP", fd, d); - info = MHD_get_daemon_info(d->daemon, MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY); + info = sym_MHD_get_daemon_info(d->daemon, MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY); if (!info) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "μhttp returned NULL daemon info"); @@ -609,12 +620,12 @@ static int dispatch_http_event(sd_event_source *event, int r; MHD_UNSIGNED_LONG_LONG timeout = ULLONG_MAX; - r = MHD_run(d->daemon); + r = sym_MHD_run(d->daemon); if (r == MHD_NO) // FIXME: unregister daemon return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "MHD_run failed!"); - if (MHD_get_timeout(d->daemon, &timeout) == MHD_NO) + if (sym_MHD_get_timeout(d->daemon, &timeout) == MHD_NO) timeout = ULLONG_MAX; r = sd_event_source_set_time(d->timer_event, timeout); diff --git a/src/journal-remote/meson.build b/src/journal-remote/meson.build index c8d97526d378a..f6aa71349c24c 100644 --- a/src/journal-remote/meson.build +++ b/src/journal-remote/meson.build @@ -3,9 +3,6 @@ systemd_journal_gatewayd_sources = files( 'journal-gatewayd.c', ) -systemd_journal_gatewayd_extract_sources = files( - 'microhttpd-util.c', -) systemd_journal_remote_sources = files('journal-remote-main.c') systemd_journal_remote_extract_sources = files( @@ -24,7 +21,7 @@ systemd_journal_upload_extract_sources = files( ) common_deps = [ - libgnutls, + libgnutls_cflags, liblz4_cflags, libxz_cflags, libzstd_cflags, @@ -40,8 +37,7 @@ executables += [ 'HAVE_MICROHTTPD', ], 'sources' : systemd_journal_gatewayd_sources, - 'extract' : systemd_journal_gatewayd_extract_sources, - 'dependencies' : common_deps + [libmicrohttpd], + 'dependencies' : common_deps + [libmicrohttpd_cflags], }, libexec_template + { 'name' : 'systemd-journal-remote', @@ -52,8 +48,7 @@ executables += [ 'install' : conf.get('ENABLE_REMOTE') == 1, 'sources' : systemd_journal_remote_sources, 'extract' : systemd_journal_remote_extract_sources, - 'objects' : conf.get('HAVE_MICROHTTPD') == 1 ? ['systemd-journal-gatewayd'] : [], - 'dependencies' : common_deps + [libmicrohttpd], + 'dependencies' : common_deps + [libmicrohttpd_cflags], }, libexec_template + { 'name' : 'systemd-journal-upload', @@ -75,7 +70,7 @@ executables += [ fuzz_template + { 'sources' : files('fuzz-journal-remote.c'), 'objects' : ['systemd-journal-remote'], - 'dependencies' : common_deps + [libmicrohttpd], + 'dependencies' : common_deps + [libmicrohttpd_cflags], }, ] diff --git a/src/shared/gnutls-util.c b/src/shared/gnutls-util.c new file mode 100644 index 0000000000000..1f17c4f5a1693 --- /dev/null +++ b/src/shared/gnutls-util.c @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-dlopen.h" + +#include "gnutls-util.h" +#include "log.h" /* IWYU pragma: keep */ + +#if HAVE_GNUTLS +static void *gnutls_dl = NULL; + +DLSYM_PROTOTYPE(gnutls_certificate_get_peers) = NULL; +DLSYM_PROTOTYPE(gnutls_certificate_type_get) = NULL; +DLSYM_PROTOTYPE(gnutls_certificate_verification_status_print) = NULL; +DLSYM_PROTOTYPE(gnutls_certificate_verify_peers2) = NULL; +DLSYM_PROTOTYPE(gnutls_free) = NULL; +DLSYM_PROTOTYPE(gnutls_global_set_log_function) = NULL; +DLSYM_PROTOTYPE(gnutls_global_set_log_level) = NULL; +DLSYM_PROTOTYPE(gnutls_x509_crt_deinit) = NULL; +DLSYM_PROTOTYPE(gnutls_x509_crt_get_dn) = NULL; +DLSYM_PROTOTYPE(gnutls_x509_crt_import) = NULL; +DLSYM_PROTOTYPE(gnutls_x509_crt_init) = NULL; +#endif + +int dlopen_gnutls(int log_level) { +#if HAVE_GNUTLS + SD_ELF_NOTE_DLOPEN( + "gnutls", + "Support for TLS via GnuTLS", + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libgnutls.so.30"); + + return dlopen_many_sym_or_warn( + &gnutls_dl, + "libgnutls.so.30", + log_level, + DLSYM_ARG(gnutls_certificate_get_peers), + DLSYM_ARG(gnutls_certificate_type_get), + DLSYM_ARG(gnutls_certificate_verification_status_print), + DLSYM_ARG(gnutls_certificate_verify_peers2), + DLSYM_ARG(gnutls_free), + DLSYM_ARG(gnutls_global_set_log_function), + DLSYM_ARG(gnutls_global_set_log_level), + DLSYM_ARG(gnutls_x509_crt_deinit), + DLSYM_ARG(gnutls_x509_crt_get_dn), + DLSYM_ARG(gnutls_x509_crt_import), + DLSYM_ARG(gnutls_x509_crt_init)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "gnutls support is not compiled in."); +#endif +} diff --git a/src/shared/gnutls-util.h b/src/shared/gnutls-util.h new file mode 100644 index 0000000000000..a110b437c3823 --- /dev/null +++ b/src/shared/gnutls-util.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +int dlopen_gnutls(int log_level); + +#if HAVE_GNUTLS +# include /* IWYU pragma: export */ +# include /* IWYU pragma: export */ + +/* gnutls.h installs a function-like macro that wraps gnutls_free() and NULLs the passed pointer. We use + * dlsym to resolve the underlying function pointer variable, so undef the macro here to keep the variable + * name visible for DLSYM_PROTOTYPE/DLSYM_ARG. */ +# ifdef gnutls_free +# undef gnutls_free +# endif + +# include "dlfcn-util.h" + +extern DLSYM_PROTOTYPE(gnutls_certificate_get_peers); +extern DLSYM_PROTOTYPE(gnutls_certificate_type_get); +extern DLSYM_PROTOTYPE(gnutls_certificate_verification_status_print); +extern DLSYM_PROTOTYPE(gnutls_certificate_verify_peers2); +extern DLSYM_PROTOTYPE(gnutls_free); +extern DLSYM_PROTOTYPE(gnutls_global_set_log_function); +extern DLSYM_PROTOTYPE(gnutls_global_set_log_level); +extern DLSYM_PROTOTYPE(gnutls_x509_crt_deinit); +extern DLSYM_PROTOTYPE(gnutls_x509_crt_get_dn); +extern DLSYM_PROTOTYPE(gnutls_x509_crt_import); +extern DLSYM_PROTOTYPE(gnutls_x509_crt_init); +#endif diff --git a/src/shared/meson.build b/src/shared/meson.build index 07b504797af68..1ab14a5c92a22 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -90,6 +90,7 @@ shared_sources = files( 'fstab-util.c', 'generator.c', 'geneve-util.c', + 'gnutls-util.c', 'gpt.c', 'group-record.c', 'hibernate-util.c', @@ -134,6 +135,7 @@ shared_sources = files( 'macvlan-util.c', 'main-func.c', 'metrics.c', + 'microhttpd-util.c', 'mkdir-label.c', 'mkfs-util.c', 'module-util.c', @@ -393,8 +395,10 @@ libshared_deps = [threads, libfdisk_cflags, libfido2_cflags, libgcrypt_cflags, + libgnutls_cflags, libidn2_cflags, libkmod_cflags, + libmicrohttpd_cflags, libmount_cflags, libopenssl, libp11kit_cflags, diff --git a/src/journal-remote/microhttpd-util.c b/src/shared/microhttpd-util.c similarity index 65% rename from src/journal-remote/microhttpd-util.c rename to src/shared/microhttpd-util.c index 32751e85e1c34..6dd55d8a0769c 100644 --- a/src/journal-remote/microhttpd-util.c +++ b/src/shared/microhttpd-util.c @@ -2,17 +2,74 @@ #include -#if HAVE_GNUTLS -#include -#include -#endif +#include "sd-dlopen.h" #include "alloc-util.h" +#include "gnutls-util.h" #include "log.h" #include "microhttpd-util.h" #include "string-util.h" #include "strv.h" +#if HAVE_MICROHTTPD +static void *microhttpd_dl = NULL; + +DLSYM_PROTOTYPE(MHD_add_response_header) = NULL; +DLSYM_PROTOTYPE(MHD_create_response_from_buffer) = NULL; +DLSYM_PROTOTYPE(MHD_create_response_from_callback) = NULL; +#if MHD_VERSION < 0x00094203 +DLSYM_PROTOTYPE(MHD_create_response_from_fd_at_offset) = NULL; +#else +DLSYM_PROTOTYPE(MHD_create_response_from_fd_at_offset64) = NULL; +#endif +DLSYM_PROTOTYPE(MHD_destroy_response) = NULL; +DLSYM_PROTOTYPE(MHD_get_connection_info) = NULL; +DLSYM_PROTOTYPE(MHD_get_connection_values) = NULL; +DLSYM_PROTOTYPE(MHD_get_daemon_info) = NULL; +DLSYM_PROTOTYPE(MHD_get_timeout) = NULL; +DLSYM_PROTOTYPE(MHD_lookup_connection_value) = NULL; +DLSYM_PROTOTYPE(MHD_queue_response) = NULL; +DLSYM_PROTOTYPE(MHD_run) = NULL; +DLSYM_PROTOTYPE(MHD_start_daemon) = NULL; +DLSYM_PROTOTYPE(MHD_stop_daemon) = NULL; +#endif + +int dlopen_microhttpd(int log_level) { +#if HAVE_MICROHTTPD + SD_ELF_NOTE_DLOPEN( + "microhttpd", + "Support for embedded HTTP server via libmicrohttpd", + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libmicrohttpd.so.12"); + + return dlopen_many_sym_or_warn( + µhttpd_dl, + "libmicrohttpd.so.12", + log_level, + DLSYM_ARG(MHD_add_response_header), + DLSYM_ARG(MHD_create_response_from_buffer), + DLSYM_ARG(MHD_create_response_from_callback), +#if MHD_VERSION < 0x00094203 + DLSYM_ARG(MHD_create_response_from_fd_at_offset), +#else + DLSYM_ARG(MHD_create_response_from_fd_at_offset64), +#endif + DLSYM_ARG(MHD_destroy_response), + DLSYM_ARG(MHD_get_connection_info), + DLSYM_ARG(MHD_get_connection_values), + DLSYM_ARG(MHD_get_daemon_info), + DLSYM_ARG(MHD_get_timeout), + DLSYM_ARG(MHD_lookup_connection_value), + DLSYM_ARG(MHD_queue_response), + DLSYM_ARG(MHD_run), + DLSYM_ARG(MHD_start_daemon), + DLSYM_ARG(MHD_stop_daemon)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libmicrohttpd support is not compiled in."); +#endif +} + #if HAVE_MICROHTTPD void microhttpd_logger(void *arg, const char *fmt, va_list ap) { @@ -36,18 +93,18 @@ int mhd_respond_internal( assert(connection); _cleanup_(MHD_destroy_responsep) struct MHD_Response *response - = MHD_create_response_from_buffer(size, (char*) buffer, mode); + = sym_MHD_create_response_from_buffer(size, (char*) buffer, mode); if (!response) return MHD_NO; log_debug("Queueing response %u: %s", code, buffer); if (encoding) - if (MHD_add_response_header(response, "Accept-Encoding", encoding) == MHD_NO) + if (sym_MHD_add_response_header(response, "Accept-Encoding", encoding) == MHD_NO) return MHD_NO; - if (MHD_add_response_header(response, "Content-Type", "text/plain") == MHD_NO) + if (sym_MHD_add_response_header(response, "Content-Type", "text/plain") == MHD_NO) return MHD_NO; - return MHD_queue_response(connection, code, response); + return sym_MHD_queue_response(connection, code, response); } int mhd_respond_oom(struct MHD_Connection *connection) { @@ -116,7 +173,7 @@ static void log_reset_gnutls_level(void) { for (i = ELEMENTSOF(gnutls_log_map) - 1; i >= 0; i--) if (gnutls_log_map[i].enabled) { log_debug("Setting gnutls log level to %d", i); - gnutls_global_set_log_level(i); + sym_gnutls_global_set_log_level(i); break; } } @@ -140,7 +197,16 @@ static int log_enable_gnutls_category(const char *cat) { int setup_gnutls_logger(char **categories) { int r; - gnutls_global_set_log_function(log_func_gnutls); + r = dlopen_gnutls(LOG_DEBUG); + if (r < 0) { + if (categories) + log_notice("Ignoring specified gnutls logging categories -- gnutls not available."); + else + log_debug("GnuTLS not available, skipping logger setup."); + return 0; + } + + sym_gnutls_global_set_log_function(log_func_gnutls); if (categories) STRV_FOREACH(cat, categories) { @@ -160,17 +226,19 @@ static int verify_cert_authorized(gnutls_session_t session) { gnutls_datum_t out; int r; - r = gnutls_certificate_verify_peers2(session, &status); + r = sym_gnutls_certificate_verify_peers2(session, &status); if (r < 0) return log_error_errno(r, "gnutls_certificate_verify_peers2 failed: %m"); - type = gnutls_certificate_type_get(session); - r = gnutls_certificate_verification_status_print(status, type, &out, 0); + type = sym_gnutls_certificate_type_get(session); + r = sym_gnutls_certificate_verification_status_print(status, type, &out, 0); if (r < 0) return log_error_errno(r, "gnutls_certificate_verification_status_print failed: %m"); log_debug("Certificate status: %s", out.data); - gnutls_free(out.data); + /* gnutls_free is declared as a function pointer variable (not a function), so sym_gnutls_free + * ends up as a pointer-to-function-pointer and must be explicitly dereferenced to be called. */ + (*sym_gnutls_free)(out.data); return status == 0 ? 0 : -EPERM; } @@ -184,12 +252,12 @@ static int get_client_cert(gnutls_session_t session, gnutls_x509_crt_t *client_c assert(session); assert(client_cert); - pcert = gnutls_certificate_get_peers(session, &listsize); + pcert = sym_gnutls_certificate_get_peers(session, &listsize); if (!pcert || !listsize) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to retrieve certificate chain"); - r = gnutls_x509_crt_init(&cert); + r = sym_gnutls_x509_crt_init(&cert); if (r < 0) { log_error("Failed to initialize client certificate"); return r; @@ -197,10 +265,10 @@ static int get_client_cert(gnutls_session_t session, gnutls_x509_crt_t *client_c /* Note that by passing values between 0 and listsize here, you can get access to the CA's certs */ - r = gnutls_x509_crt_import(cert, &pcert[0], GNUTLS_X509_FMT_DER); + r = sym_gnutls_x509_crt_import(cert, &pcert[0], GNUTLS_X509_FMT_DER); if (r < 0) { log_error("Failed to import client certificate"); - gnutls_x509_crt_deinit(cert); + sym_gnutls_x509_crt_deinit(cert); return r; } @@ -215,7 +283,7 @@ static int get_auth_dn(gnutls_x509_crt_t client_cert, char **buf) { assert(buf); assert(*buf == NULL); - r = gnutls_x509_crt_get_dn(client_cert, NULL, &len); + r = sym_gnutls_x509_crt_get_dn(client_cert, NULL, &len); if (r != GNUTLS_E_SHORT_MEMORY_BUFFER) { log_error("gnutls_x509_crt_get_dn failed"); return r; @@ -225,14 +293,15 @@ static int get_auth_dn(gnutls_x509_crt_t client_cert, char **buf) { if (!*buf) return log_oom(); - gnutls_x509_crt_get_dn(client_cert, *buf, &len); + sym_gnutls_x509_crt_get_dn(client_cert, *buf, &len); return 0; } static void gnutls_x509_crt_deinitp(gnutls_x509_crt_t *p) { assert(p); - gnutls_x509_crt_deinit(*p); + if (*p) + sym_gnutls_x509_crt_deinit(*p); } int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) { @@ -247,8 +316,12 @@ int check_permissions(struct MHD_Connection *connection, int *code, char **hostn *code = 0; - ci = MHD_get_connection_info(connection, - MHD_CONNECTION_INFO_GNUTLS_SESSION); + r = dlopen_gnutls(LOG_ERR); + if (r < 0) + return r; + + ci = sym_MHD_get_connection_info(connection, + MHD_CONNECTION_INFO_GNUTLS_SESSION); if (!ci) { log_error("MHD_get_connection_info failed: session is unencrypted"); *code = mhd_respond(connection, MHD_HTTP_FORBIDDEN, diff --git a/src/journal-remote/microhttpd-util.h b/src/shared/microhttpd-util.h similarity index 76% rename from src/journal-remote/microhttpd-util.h rename to src/shared/microhttpd-util.h index 80142e24f59c5..488ba7ea6e883 100644 --- a/src/journal-remote/microhttpd-util.h +++ b/src/shared/microhttpd-util.h @@ -1,11 +1,15 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "shared-forward.h" + +int dlopen_microhttpd(int log_level); + #if HAVE_MICROHTTPD #include -#include "shared-forward.h" +#include "dlfcn-util.h" /* Those defines are added when options are renamed. If the old names * are not '#define'd, then they are not deprecated yet and there are @@ -58,6 +62,26 @@ # define mhd_result int #endif +extern DLSYM_PROTOTYPE(MHD_add_response_header); +extern DLSYM_PROTOTYPE(MHD_create_response_from_buffer); +extern DLSYM_PROTOTYPE(MHD_create_response_from_callback); +#if MHD_VERSION < 0x00094203 +extern DLSYM_PROTOTYPE(MHD_create_response_from_fd_at_offset); +# define sym_MHD_create_response_from_fd_at_offset64 sym_MHD_create_response_from_fd_at_offset +#else +extern DLSYM_PROTOTYPE(MHD_create_response_from_fd_at_offset64); +#endif +extern DLSYM_PROTOTYPE(MHD_destroy_response); +extern DLSYM_PROTOTYPE(MHD_get_connection_info); +extern DLSYM_PROTOTYPE(MHD_get_connection_values); +extern DLSYM_PROTOTYPE(MHD_get_daemon_info); +extern DLSYM_PROTOTYPE(MHD_get_timeout); +extern DLSYM_PROTOTYPE(MHD_lookup_connection_value); +extern DLSYM_PROTOTYPE(MHD_queue_response); +extern DLSYM_PROTOTYPE(MHD_run); +extern DLSYM_PROTOTYPE(MHD_start_daemon); +extern DLSYM_PROTOTYPE(MHD_stop_daemon); + void microhttpd_logger(void *arg, const char *fmt, va_list ap) _printf_(2, 0); /* respond_oom() must be usable with return, hence this form. */ @@ -107,7 +131,7 @@ int check_permissions(struct MHD_Connection *connection, int *code, char **hostn */ int setup_gnutls_logger(char **categories); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct MHD_Daemon*, MHD_stop_daemon, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct MHD_Response*, MHD_destroy_response, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct MHD_Daemon*, sym_MHD_stop_daemon, MHD_stop_daemonp, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct MHD_Response*, sym_MHD_destroy_response, MHD_destroy_responsep, NULL); #endif diff --git a/src/test/meson.build b/src/test/meson.build index 7089d623d185a..9544c166a5928 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -302,7 +302,9 @@ executables += [ 'dependencies' : [ libblkid_cflags, libfdisk_cflags, + libgnutls_cflags, libkmod_cflags, + libmicrohttpd_cflags, libmount_cflags, libp11kit_cflags, libseccomp_cflags, diff --git a/src/test/test-dlopen-so.c b/src/test/test-dlopen-so.c index c2eaac7b53d92..d3121981cf50a 100644 --- a/src/test/test-dlopen-so.c +++ b/src/test/test-dlopen-so.c @@ -10,6 +10,7 @@ #include "elf-util.h" #include "fdisk-util.h" #include "gcrypt-util.h" +#include "gnutls-util.h" #include "idn-util.h" #include "libarchive-util.h" #include "libaudit-util.h" @@ -17,6 +18,7 @@ #include "libfido2-util.h" #include "libmount-util.h" #include "main-func.h" +#include "microhttpd-util.h" #include "module-util.h" #include "pam-util.h" #include "password-quality-util-passwdqc.h" @@ -52,6 +54,7 @@ static int run(int argc, char **argv) { ASSERT_DLOPEN(dlopen_elf, HAVE_ELFUTILS); ASSERT_DLOPEN(dlopen_fdisk, HAVE_LIBFDISK); ASSERT_DLOPEN(dlopen_gcrypt, HAVE_GCRYPT); + ASSERT_DLOPEN(dlopen_gnutls, HAVE_GNUTLS); ASSERT_DLOPEN(dlopen_idn, HAVE_LIBIDN2); ASSERT_DLOPEN(dlopen_libacl, HAVE_ACL); ASSERT_DLOPEN(dlopen_libapparmor, HAVE_APPARMOR); @@ -67,6 +70,7 @@ static int run(int argc, char **argv) { ASSERT_DLOPEN(dlopen_libselinux, HAVE_SELINUX); ASSERT_DLOPEN(dlopen_xz, HAVE_XZ); ASSERT_DLOPEN(dlopen_lz4, HAVE_LZ4); + ASSERT_DLOPEN(dlopen_microhttpd, HAVE_MICROHTTPD); ASSERT_DLOPEN(dlopen_p11kit, HAVE_P11KIT); ASSERT_DLOPEN(dlopen_passwdqc, HAVE_PASSWDQC); ASSERT_DLOPEN(dlopen_pcre2, HAVE_PCRE2); From 4114bf7e700fa2c6877230ca1199056cfbafc4e7 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 22 Apr 2026 21:17:17 +0200 Subject: [PATCH 1178/1296] repart: Fix xopenat_full() error handling --- src/repart/repart.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index e307bfe13f280..e91d32ef0b9d2 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -9227,7 +9227,7 @@ static int context_minimize(Context *context) { attrs & FS_NOCOW_FL ? XO_NOCOW : 0, 0600); if (fd < 0) - return log_error_errno(errno, "Failed to open temporary file %s: %m", temp); + return log_error_errno(fd, "Failed to open temporary file %s: %m", temp); if (fstype_is_ro(p->format) || is_btrfs) /* Read-only filesystems and btrfs (with mkfs.btrfs --shrink) produce a minimal @@ -9441,7 +9441,7 @@ static int context_minimize(Context *context) { attrs & FS_NOCOW_FL ? XO_NOCOW : 0, 0600); if (fd < 0) - return log_error_errno(errno, "Failed to open temporary file %s: %m", temp); + return log_error_errno(fd, "Failed to open temporary file %s: %m", temp); r = partition_format_verity_hash(context, p, temp, dp->copy_blocks_path); if (r < 0) @@ -10604,7 +10604,7 @@ static int find_root(Context *context) { fd = xopenat_full(AT_FDCWD, arg_node, O_RDONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOFOLLOW, XO_NOCOW, 0666); if (fd < 0) - return log_error_errno(errno, "Failed to create '%s': %m", arg_node); + return log_error_errno(fd, "Failed to create '%s': %m", arg_node); context->node = TAKE_PTR(s); context->node_is_our_file = true; From 43dab5ea8797e45e0702f8ee89cdf25e577a652b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 20 Apr 2026 20:04:21 +0000 Subject: [PATCH 1179/1296] cryptsetup: load libcryptsetup via dlopen in setup binaries Convert systemd-cryptsetup, systemd-cryptenroll, systemd-veritysetup and systemd-integritysetup to go through the existing dlopen wrapper for libcryptsetup instead of linking the library directly. Each binary calls dlopen_cryptsetup() at the start of its run() and uses the sym_* variants for every libcryptsetup entry point. Extend cryptsetup-util.{h,c} to cover the libcryptsetup symbols that these binaries use and that the wrapper was missing: crypt_activate_by_token_pin, crypt_deactivate, crypt_init_data_device, crypt_keyslot_status, crypt_set_keyring_to_link (conditional on HAVE_CRYPT_SET_KEYRING_TO_LINK), crypt_status and crypt_token_external_path. With no direct callers of crypt_free() left, drop the non-sym crypt_freep cleanup variant and rename sym_crypt_freep back to crypt_freep via DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME, matching the naming convention used by other dlopen wrappers (acl_freep, xkb_context_unrefp, ...). Update the remaining users in src/shared, src/repart, src/home and src/growfs to the new name. The four affected meson targets switch from libcryptsetup to libcryptsetup_cflags so they no longer record a DT_NEEDED entry for libcryptsetup.so.12. --- src/cryptenroll/cryptenroll-fido2.c | 8 +-- src/cryptenroll/cryptenroll-list.c | 4 +- src/cryptenroll/cryptenroll-password.c | 10 +-- src/cryptenroll/cryptenroll-pkcs11.c | 4 +- src/cryptenroll/cryptenroll-recovery.c | 6 +- src/cryptenroll/cryptenroll-tpm2.c | 6 +- src/cryptenroll/cryptenroll-wipe.c | 26 ++++---- src/cryptenroll/cryptenroll.c | 16 +++-- src/cryptenroll/meson.build | 2 +- src/cryptsetup/cryptsetup.c | 88 +++++++++++++------------- src/cryptsetup/meson.build | 2 +- src/growfs/growfs.c | 2 +- src/home/homework-luks.c | 6 +- src/integritysetup/integritysetup.c | 20 +++--- src/integritysetup/meson.build | 2 +- src/repart/repart.c | 4 +- src/shared/cryptsetup-util.c | 18 ++++++ src/shared/cryptsetup-util.h | 16 +++-- src/shared/dissect-image.c | 8 +-- src/veritysetup/meson.build | 2 +- src/veritysetup/veritysetup.c | 26 ++++---- 21 files changed, 157 insertions(+), 119 deletions(-) diff --git a/src/cryptenroll/cryptenroll-fido2.c b/src/cryptenroll/cryptenroll-fido2.c index 822cf26c896a3..600207e947b83 100644 --- a/src/cryptenroll/cryptenroll-fido2.c +++ b/src/cryptenroll/cryptenroll-fido2.c @@ -54,7 +54,7 @@ int load_volume_key_fido2( if (passphrase_size < 0) return log_oom(); - r = crypt_volume_key_get( + r = sym_crypt_volume_key_get( cd, CRYPT_ANY_SLOT, ret_vk, @@ -95,9 +95,9 @@ int enroll_fido2( assert_se(iovec_is_set(volume_key)); assert_se(device); - assert_se(node = crypt_get_device_name(cd)); + assert_se(node = sym_crypt_get_device_name(cd)); - un = strempty(crypt_get_uuid(cd)); + un = strempty(sym_crypt_get_uuid(cd)); if (salt_file) r = fido2_read_salt_file( @@ -140,7 +140,7 @@ int enroll_fido2( if (r < 0) return log_error_errno(r, "Failed to set minimal PBKDF: %m"); - keyslot = crypt_keyslot_add_by_volume_key( + keyslot = sym_crypt_keyslot_add_by_volume_key( cd, CRYPT_ANY_SLOT, volume_key->iov_base, diff --git a/src/cryptenroll/cryptenroll-list.c b/src/cryptenroll/cryptenroll-list.c index bca9f74c3ab02..ab7637e61634f 100644 --- a/src/cryptenroll/cryptenroll-list.c +++ b/src/cryptenroll/cryptenroll-list.c @@ -26,11 +26,11 @@ int list_enrolled(struct crypt_device *cd) { assert(cd); /* First step, find out all currently used slots */ - assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0); + assert_se((slot_max = sym_crypt_keyslot_max(CRYPT_LUKS2)) > 0); for (int slot = 0; slot < slot_max; slot++) { crypt_keyslot_info status; - status = crypt_keyslot_status(cd, slot); + status = sym_crypt_keyslot_status(cd, slot); if (!IN_SET(status, CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST)) continue; diff --git a/src/cryptenroll/cryptenroll-password.c b/src/cryptenroll/cryptenroll-password.c index 26503187133b4..e189cce23aba0 100644 --- a/src/cryptenroll/cryptenroll-password.c +++ b/src/cryptenroll/cryptenroll-password.c @@ -31,7 +31,7 @@ int load_volume_key_password( if (r < 0) return log_error_errno(r, "Failed to acquire password from environment: %m"); if (r > 0) { - r = crypt_volume_key_get( + r = sym_crypt_volume_key_get( cd, CRYPT_ANY_SLOT, ret_vk, @@ -81,7 +81,7 @@ int load_volume_key_password( r = -EPERM; STRV_FOREACH(p, passwords) { - r = crypt_volume_key_get( + r = sym_crypt_volume_key_get( cd, CRYPT_ANY_SLOT, ret_vk, @@ -114,7 +114,7 @@ int enroll_password( assert(cd); assert(iovec_is_set(volume_key)); - assert_se(node = crypt_get_device_name(cd)); + assert_se(node = sym_crypt_get_device_name(cd)); r = getenv_steal_erase("NEWPASSWORD", &new_password); if (r < 0) @@ -123,7 +123,7 @@ int enroll_password( _cleanup_free_ char *disk_path = NULL, *id = NULL; unsigned i = 5; - assert_se(node = crypt_get_device_name(cd)); + assert_se(node = sym_crypt_get_device_name(cd)); (void) suggest_passwords(); @@ -196,7 +196,7 @@ int enroll_password( else if (r == 0) log_warning("Specified password does not pass quality checks (%s), proceeding anyway.", error); - keyslot = crypt_keyslot_add_by_volume_key( + keyslot = sym_crypt_keyslot_add_by_volume_key( cd, CRYPT_ANY_SLOT, volume_key->iov_base, diff --git a/src/cryptenroll/cryptenroll-pkcs11.c b/src/cryptenroll/cryptenroll-pkcs11.c index 7ddb9f871ba71..51c2a5fa77e38 100644 --- a/src/cryptenroll/cryptenroll-pkcs11.c +++ b/src/cryptenroll/cryptenroll-pkcs11.c @@ -53,7 +53,7 @@ int enroll_pkcs11(struct crypt_device *cd, const struct iovec *volume_key,const assert_se(iovec_is_set(volume_key)); assert_se(uri); - assert_se(node = crypt_get_device_name(cd)); + assert_se(node = sym_crypt_get_device_name(cd)); r = pkcs11_acquire_public_key( uri, @@ -80,7 +80,7 @@ int enroll_pkcs11(struct crypt_device *cd, const struct iovec *volume_key,const if (r < 0) return log_error_errno(r, "Failed to set minimal PBKDF: %m"); - int keyslot = crypt_keyslot_add_by_volume_key( + int keyslot = sym_crypt_keyslot_add_by_volume_key( cd, CRYPT_ANY_SLOT, volume_key->iov_base, diff --git a/src/cryptenroll/cryptenroll-recovery.c b/src/cryptenroll/cryptenroll-recovery.c index f9a588da26a3e..85ec13f128290 100644 --- a/src/cryptenroll/cryptenroll-recovery.c +++ b/src/cryptenroll/cryptenroll-recovery.c @@ -24,7 +24,7 @@ int enroll_recovery( assert_se(cd); assert_se(iovec_is_set(volume_key)); - assert_se(node = crypt_get_device_name(cd)); + assert_se(node = sym_crypt_get_device_name(cd)); r = make_recovery_key(&password); if (r < 0) @@ -34,7 +34,7 @@ int enroll_recovery( if (r < 0) return log_error_errno(r, "Failed to set minimal PBKDF: %m"); - keyslot = crypt_keyslot_add_by_volume_key( + keyslot = sym_crypt_keyslot_add_by_volume_key( cd, CRYPT_ANY_SLOT, volume_key->iov_base, @@ -93,7 +93,7 @@ int enroll_recovery( return keyslot; rollback: - q = crypt_keyslot_destroy(cd, keyslot); + q = sym_crypt_keyslot_destroy(cd, keyslot); if (q < 0) log_debug_errno(q, "Unable to remove key slot we just added again, can't rollback, sorry: %m"); diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c index 50abca43639b8..a0c64e8601ee4 100644 --- a/src/cryptenroll/cryptenroll-tpm2.c +++ b/src/cryptenroll/cryptenroll-tpm2.c @@ -272,7 +272,7 @@ int load_volume_key_tpm2( if (passphrase_size < 0) return log_oom(); - r = crypt_volume_key_get( + r = sym_crypt_volume_key_get( cd, CRYPT_ANY_SLOT, ret_vk, @@ -329,7 +329,7 @@ int enroll_tpm2(struct crypt_device *cd, assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask)); assert(ret_slot_to_wipe); - assert_se(node = crypt_get_device_name(cd)); + assert_se(node = sym_crypt_get_device_name(cd)); if (use_pin) { r = get_pin(&pin_str, &flags); @@ -579,7 +579,7 @@ int enroll_tpm2(struct crypt_device *cd, if (r < 0) return log_error_errno(r, "Failed to set minimal PBKDF: %m"); - keyslot = crypt_keyslot_add_by_volume_key( + keyslot = sym_crypt_keyslot_add_by_volume_key( cd, CRYPT_ANY_SLOT, volume_key->iov_base, diff --git a/src/cryptenroll/cryptenroll-wipe.c b/src/cryptenroll/cryptenroll-wipe.c index 1ae92bf91b8fb..a8638d2b3a777 100644 --- a/src/cryptenroll/cryptenroll-wipe.c +++ b/src/cryptenroll/cryptenroll-wipe.c @@ -15,7 +15,7 @@ static int find_all_slots(struct crypt_device *cd, Set *wipe_slots, Set *keep_sl assert(cd); assert(wipe_slots); - assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0); + assert_se((slot_max = sym_crypt_keyslot_max(CRYPT_LUKS2)) > 0); /* Finds all currently assigned slots, and adds them to 'wipe_slots', except if listed already in 'keep_slots' */ @@ -27,7 +27,7 @@ static int find_all_slots(struct crypt_device *cd, Set *wipe_slots, Set *keep_sl set_contains(wipe_slots, INT_TO_PTR(slot))) continue; - status = crypt_keyslot_status(cd, slot); + status = sym_crypt_keyslot_status(cd, slot); if (!IN_SET(status, CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST)) continue; @@ -44,12 +44,12 @@ static int find_empty_passphrase_slots(struct crypt_device *cd, Set *wipe_slots, assert(cd); assert(wipe_slots); - assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0); + assert_se((slot_max = sym_crypt_keyslot_max(CRYPT_LUKS2)) > 0); /* Finds all slots with an empty passphrase assigned (i.e. "") and adds them to 'wipe_slots', except * if listed already in 'keep_slots' */ - r = crypt_get_volume_key_size(cd); + r = sym_crypt_get_volume_key_size(cd); if (r <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine LUKS volume key size"); vks = (size_t) r; @@ -63,7 +63,7 @@ static int find_empty_passphrase_slots(struct crypt_device *cd, Set *wipe_slots, set_contains(wipe_slots, INT_TO_PTR(slot))) continue; - status = crypt_keyslot_status(cd, slot); + status = sym_crypt_keyslot_status(cd, slot); if (!IN_SET(status, CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST)) continue; @@ -71,7 +71,7 @@ static int find_empty_passphrase_slots(struct crypt_device *cd, Set *wipe_slots, if (!vk) return log_oom(); - r = crypt_volume_key_get(cd, slot, vk, &vks, "", 0); + r = sym_crypt_volume_key_get(cd, slot, vk, &vks, "", 0); if (r < 0) { log_debug_errno(r, "Failed to acquire volume key from slot %i with empty password, ignoring: %m", slot); continue; @@ -164,7 +164,7 @@ static int find_slots_by_mask( if ((by_mask & (1U << ENROLL_PASSWORD)) != 0) { int slot_max; - assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0); + assert_se((slot_max = sym_crypt_keyslot_max(CRYPT_LUKS2)) > 0); for (int slot = 0; slot < slot_max; slot++) { crypt_keyslot_info status; @@ -177,7 +177,7 @@ static int find_slots_by_mask( if (set_contains(listed_slots, INT_TO_PTR(slot))) /* This has a token, hence is not a password. */ continue; - status = crypt_keyslot_status(cd, slot); + status = sym_crypt_keyslot_status(cd, slot); if (!IN_SET(status, CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST)) /* Not actually assigned? */ continue; @@ -273,7 +273,7 @@ static bool slots_remain(struct crypt_device *cd, Set *wipe_slots, Set *keep_slo int slot_max; assert(cd); - assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0); + assert_se((slot_max = sym_crypt_keyslot_max(CRYPT_LUKS2)) > 0); /* Checks if any slots remaining in the LUKS2 header if we remove all slots listed in 'wipe_slots' * (keeping those listed in 'keep_slots') */ @@ -281,7 +281,7 @@ static bool slots_remain(struct crypt_device *cd, Set *wipe_slots, Set *keep_slo for (int slot = 0; slot < slot_max; slot++) { crypt_keyslot_info status; - status = crypt_keyslot_status(cd, slot); + status = sym_crypt_keyslot_status(cd, slot); if (!IN_SET(status, CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST)) continue; @@ -338,7 +338,7 @@ int wipe_slots(struct crypt_device *cd, if (set_put(keep_slots, INT_TO_PTR(except_slot)) < 0) return log_oom(); - assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0); + assert_se((slot_max = sym_crypt_keyslot_max(CRYPT_LUKS2)) > 0); /* Maintain another set of the slots we intend to wipe */ for (size_t i = 0; i < n_explicit_slots; i++) { @@ -425,7 +425,7 @@ int wipe_slots(struct crypt_device *cd, * first.) */ ret = 0; for (size_t i = n_ordered_slots; i > 0; i--) { - r = crypt_keyslot_destroy(cd, ordered_slots[i - 1]); + r = sym_crypt_keyslot_destroy(cd, ordered_slots[i - 1]); if (r < 0) { if (r == -ENOENT) log_warning_errno(r, "Failed to wipe non-existent slot %i, continuing.", ordered_slots[i - 1]); @@ -438,7 +438,7 @@ int wipe_slots(struct crypt_device *cd, } for (size_t i = n_ordered_tokens; i > 0; i--) { - r = crypt_token_json_set(cd, ordered_tokens[i - 1], NULL); + r = sym_crypt_token_json_set(cd, ordered_tokens[i - 1], NULL); if (r < 0) { log_warning_errno(r, "Failed to wipe token %i, continuing: %m", ordered_tokens[i - 1]); if (ret == 0) diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index e9bb27c254886..46b3a546c9b9b 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -649,7 +649,7 @@ static int check_for_homed(struct crypt_device *cd) { /* Politely refuse operating on homed volumes. The enrolled tokens for the user record and the LUKS2 * volume should not get out of sync. */ - for (int token = 0; token < crypt_token_max(CRYPT_LUKS2); token++) { + for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token++) { r = cryptsetup_get_token_as_json(cd, token, "systemd-homed", NULL); if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE)) continue; @@ -688,7 +688,7 @@ static int load_volume_key_keyfile( if (r < 0) return log_error_errno(r, "Reading keyfile %s failed: %m", arg_unlock_keyfile); - r = crypt_volume_key_get( + r = sym_crypt_volume_key_get( cd, CRYPT_ANY_SLOT, ret_vk, @@ -710,13 +710,13 @@ static int prepare_luks( assert(ret_cd); - r = crypt_init(&cd, arg_node); + r = sym_crypt_init(&cd, arg_node); if (r < 0) return log_error_errno(r, "Failed to allocate libcryptsetup context: %m"); cryptsetup_enable_logging(cd); - r = crypt_load(cd, CRYPT_LUKS2, NULL); + r = sym_crypt_load(cd, CRYPT_LUKS2, NULL); if (r < 0) return log_error_errno(r, "Failed to load LUKS2 superblock of %s: %m", arg_node); @@ -729,7 +729,7 @@ static int prepare_luks( return 0; } - r = crypt_get_volume_key_size(cd); + r = sym_crypt_get_volume_key_size(cd); if (r <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine LUKS volume key size"); @@ -783,11 +783,13 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + r = dlopen_cryptsetup(LOG_ERR); + if (r < 0) + return r; + /* A delicious drop of snake oil */ (void) safe_mlockall(MCL_CURRENT|MCL_FUTURE|MCL_ONFAULT); - cryptsetup_enable_logging(NULL); - if (arg_enroll_type < 0) r = prepare_luks(&cd, /* ret_volume_key= */ NULL); /* No need to unlock device if we don't need the volume key because we don't need to enroll anything */ else diff --git a/src/cryptenroll/meson.build b/src/cryptenroll/meson.build index 11265c8b41cc0..2d882343d3078 100644 --- a/src/cryptenroll/meson.build +++ b/src/cryptenroll/meson.build @@ -21,7 +21,7 @@ executables += [ 'public' : true, 'sources' : systemd_cryptenroll_sources, 'dependencies' : [ - libcryptsetup, + libcryptsetup_cflags, libdl, libfido2_cflags, libopenssl, diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index ff9449abd1669..8e5161eba05d4 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -823,19 +823,19 @@ static PassphraseType check_registered_passwords(struct crypt_device *cd) { assert(cd); - if (!streq_ptr(crypt_get_type(cd), CRYPT_LUKS2)) { - log_debug("%s: not a LUKS2 device, only passphrases are supported", crypt_get_device_name(cd)); + if (!streq_ptr(sym_crypt_get_type(cd), CRYPT_LUKS2)) { + log_debug("%s: not a LUKS2 device, only passphrases are supported", sym_crypt_get_device_name(cd)); return PASSPHRASE_REGULAR; } /* Search all used slots */ - assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0); + assert_se((slot_max = sym_crypt_keyslot_max(CRYPT_LUKS2)) > 0); slots = new(bool, slot_max); if (!slots) return log_oom(); for (int slot = 0; slot < slot_max; slot++) - slots[slot] = IN_SET(crypt_keyslot_status(cd, slot), CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST); + slots[slot] = IN_SET(sym_crypt_keyslot_status(cd, slot), CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST); /* Iterate all LUKS2 tokens and keep track of all their slots */ for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token++) { @@ -1132,7 +1132,7 @@ static int measure_keyslot( return log_oom(); _cleanup_free_ char *s = NULL; - s = strjoin("cryptsetup-keyslot:", escaped, ":", strempty(crypt_get_uuid(cd)), ":", strempty(mechanism), ":", strempty(k)); + s = strjoin("cryptsetup-keyslot:", escaped, ":", strempty(sym_crypt_get_uuid(cd)), ":", strempty(mechanism), ":", strempty(k)); if (!s) return log_oom(); @@ -1207,7 +1207,7 @@ static int measured_crypt_activate_by_volume_key( key_id, arg_fixate_volume_key); } - r = crypt_activate_by_volume_key(cd, name, volume_key, volume_key_size, flags); + r = sym_crypt_activate_by_volume_key(cd, name, volume_key, volume_key_size, flags); if (r == -EEXIST) /* volume is already active */ return log_external_activation(r, name); if (r < 0) @@ -1250,7 +1250,7 @@ static int measured_crypt_activate_by_passphrase( if (arg_tpm2_measure_pcr == UINT_MAX && !arg_fixate_volume_key) goto shortcut; - r = crypt_get_volume_key_size(cd); + r = sym_crypt_get_volume_key_size(cd); if (r < 0) return r; if (r == 0) { @@ -1262,14 +1262,14 @@ static int measured_crypt_activate_by_passphrase( if (!vk) return -ENOMEM; - keyslot = crypt_volume_key_get(cd, keyslot, vk, &vks, passphrase, passphrase_size); + keyslot = sym_crypt_volume_key_get(cd, keyslot, vk, &vks, passphrase, passphrase_size); if (keyslot < 0) return keyslot; return measured_crypt_activate_by_volume_key(cd, name, mechanism, keyslot, vk, vks, flags); shortcut: - keyslot = crypt_activate_by_passphrase(cd, name, keyslot, passphrase, passphrase_size, flags); + keyslot = sym_crypt_activate_by_passphrase(cd, name, keyslot, passphrase, passphrase_size, flags); if (keyslot == -EEXIST) /* volume is already active */ return log_external_activation(keyslot, name); if (keyslot < 0) @@ -1320,7 +1320,7 @@ static int attach_tcrypt( if (key_data) { params.passphrase = key_data->iov_base; params.passphrase_size = key_data->iov_len; - r = crypt_load(cd, CRYPT_TCRYPT, ¶ms); + r = sym_crypt_load(cd, CRYPT_TCRYPT, ¶ms); } else if (key_file) { r = read_one_line_file(key_file, &passphrase); if (r < 0) { @@ -1329,13 +1329,13 @@ static int attach_tcrypt( } params.passphrase = passphrase; params.passphrase_size = strlen(passphrase); - r = crypt_load(cd, CRYPT_TCRYPT, ¶ms); + r = sym_crypt_load(cd, CRYPT_TCRYPT, ¶ms); } else { r = -EINVAL; STRV_FOREACH(p, passwords){ params.passphrase = *p; params.passphrase_size = strlen(*p); - r = crypt_load(cd, CRYPT_TCRYPT, ¶ms); + r = sym_crypt_load(cd, CRYPT_TCRYPT, ¶ms); if (r >= 0) break; } @@ -1353,7 +1353,7 @@ static int attach_tcrypt( return r; } - return log_error_errno(r, "Failed to load tcrypt superblock on device %s: %m", crypt_get_device_name(cd)); + return log_error_errno(r, "Failed to load tcrypt superblock on device %s: %m", sym_crypt_get_device_name(cd)); } r = measured_crypt_activate_by_volume_key( @@ -1365,7 +1365,7 @@ static int attach_tcrypt( /* volume_key_size= */ 0, flags); if (r < 0) - return log_error_errno(r, "Failed to activate tcrypt device %s: %m", crypt_get_device_name(cd)); + return log_error_errno(r, "Failed to activate tcrypt device %s: %m", sym_crypt_get_device_name(cd)); return 0; } @@ -1509,7 +1509,7 @@ static bool use_token_plugins(void) { if (r == 0) return false; - return crypt_token_external_path(); + return sym_crypt_token_external_path(); #else return false; #endif @@ -1554,7 +1554,7 @@ static int crypt_activate_by_token_pin_ask_password( _cleanup_strv_free_erase_ char **pins = NULL; int r; - r = crypt_activate_by_token_pin(cd, name, type, CRYPT_ANY_TOKEN, /* pin= */ NULL, /* pin_size= */ 0, userdata, activation_flags); + r = sym_crypt_activate_by_token_pin(cd, name, type, CRYPT_ANY_TOKEN, /* pin= */ NULL, /* pin_size= */ 0, userdata, activation_flags); if (r > 0) /* returns unlocked keyslot id on success */ return 0; if (r == -EEXIST) /* volume is already active */ @@ -1567,7 +1567,7 @@ static int crypt_activate_by_token_pin_ask_password( return r; STRV_FOREACH(p, pins) { - r = crypt_activate_by_token_pin(cd, name, type, CRYPT_ANY_TOKEN, *p, strlen(*p), userdata, activation_flags); + r = sym_crypt_activate_by_token_pin(cd, name, type, CRYPT_ANY_TOKEN, *p, strlen(*p), userdata, activation_flags); if (r > 0) /* returns unlocked keyslot id on success */ return 0; if (r == -EEXIST) /* volume is already active */ @@ -1597,7 +1597,7 @@ static int crypt_activate_by_token_pin_ask_password( return r; STRV_FOREACH(p, pins) { - r = crypt_activate_by_token_pin(cd, name, type, CRYPT_ANY_TOKEN, *p, strlen(*p), userdata, activation_flags); + r = sym_crypt_activate_by_token_pin(cd, name, type, CRYPT_ANY_TOKEN, *p, strlen(*p), userdata, activation_flags); if (r > 0) /* returns unlocked keyslot id on success */ return 0; if (r == -EEXIST) /* volume is already active */ @@ -1658,7 +1658,7 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "FIDO2 mode with manual parameters selected, but no keyfile specified, refusing."); - friendly = friendly_disk_name(crypt_get_device_name(cd), name); + friendly = friendly_disk_name(sym_crypt_get_device_name(cd), name); if (!friendly) return log_oom(); @@ -1776,7 +1776,7 @@ static int attach_luks2_by_pkcs11_via_plugin( #if HAVE_LIBCRYPTSETUP_PLUGINS int r; - if (!streq_ptr(crypt_get_type(cd), CRYPT_LUKS2)) + if (!streq_ptr(sym_crypt_get_type(cd), CRYPT_LUKS2)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Automatic PKCS#11 metadata requires LUKS2 device."); systemd_pkcs11_plugin_params params = { @@ -1786,7 +1786,7 @@ static int attach_luks2_by_pkcs11_via_plugin( .askpw_flags = arg_ask_password_flags, }; - r = crypt_activate_by_token_pin(cd, name, "systemd-pkcs11", CRYPT_ANY_TOKEN, NULL, 0, ¶ms, flags); + r = sym_crypt_activate_by_token_pin(cd, name, "systemd-pkcs11", CRYPT_ANY_TOKEN, NULL, 0, ¶ms, flags); if (r > 0) /* returns unlocked keyslot id on success */ r = 0; if (r == -EEXIST) /* volume is already active */ @@ -1842,7 +1842,7 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11( return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "PKCS#11 mode selected but no key file specified, refusing."); } - friendly = friendly_disk_name(crypt_get_device_name(cd), name); + friendly = friendly_disk_name(sym_crypt_get_device_name(cd), name); if (!friendly) return log_oom(); @@ -2036,7 +2036,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( assert(name); assert(arg_tpm2_device || arg_tpm2_device_auto); - friendly = friendly_disk_name(crypt_get_device_name(cd), name); + friendly = friendly_disk_name(sym_crypt_get_device_name(cd), name); if (!friendly) return log_oom(); @@ -2382,7 +2382,7 @@ static int attach_luks_or_plain_or_bitlk( assert(cd); assert(name); - if ((!arg_type && !crypt_get_type(cd)) || streq_ptr(arg_type, CRYPT_PLAIN)) { + if ((!arg_type && !sym_crypt_get_type(cd)) || streq_ptr(arg_type, CRYPT_PLAIN)) { struct crypt_params_plain params = { .offset = arg_offset, .skip = arg_skip, @@ -2421,7 +2421,7 @@ static int attach_luks_or_plain_or_bitlk( /* In contrast to what the name crypt_format() might suggest this doesn't actually format * anything, it just configures encryption parameters when used for plain mode. */ - r = crypt_format(cd, CRYPT_PLAIN, cipher, cipher_mode, NULL, NULL, arg_keyfile_size, ¶ms); + r = sym_crypt_format(cd, CRYPT_PLAIN, cipher, cipher_mode, NULL, NULL, arg_keyfile_size, ¶ms); if (r < 0) return log_error_errno(r, "Loading of cryptographic parameters failed: %m"); @@ -2430,10 +2430,10 @@ static int attach_luks_or_plain_or_bitlk( } log_info("Set cipher %s, mode %s, key size %i bits for device %s.", - crypt_get_cipher(cd), - crypt_get_cipher_mode(cd), - crypt_get_volume_key_size(cd)*8, - crypt_get_device_name(cd)); + sym_crypt_get_cipher(cd), + sym_crypt_get_cipher_mode(cd), + sym_crypt_get_volume_key_size(cd)*8, + sym_crypt_get_device_name(cd)); if (token_type == TOKEN_TPM2) return attach_luks_or_plain_or_bitlk_by_tpm2(cd, name, key_file, key_data, until, flags, pass_volume_key); @@ -2643,19 +2643,19 @@ static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata) if (arg_header) { if (streq_ptr(arg_type, CRYPT_TCRYPT)){ log_debug("tcrypt header: %s", arg_header); - r = crypt_init_data_device(&cd, arg_header, source); + r = sym_crypt_init_data_device(&cd, arg_header, source); } else { log_debug("LUKS header: %s", arg_header); - r = crypt_init(&cd, arg_header); + r = sym_crypt_init(&cd, arg_header); } } else - r = crypt_init(&cd, source); + r = sym_crypt_init(&cd, source); if (r < 0) return log_error_errno(r, "crypt_init() failed: %m"); cryptsetup_enable_logging(cd); - status = crypt_status(cd, volume); + status = sym_crypt_status(cd, volume); if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) { log_info("Volume %s already active.", volume); return 0; @@ -2680,21 +2680,21 @@ static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata) } if (!arg_type || STR_IN_SET(arg_type, ANY_LUKS, CRYPT_LUKS1, CRYPT_LUKS2)) { - r = crypt_load(cd, !arg_type || streq(arg_type, ANY_LUKS) ? CRYPT_LUKS : arg_type, NULL); + r = sym_crypt_load(cd, !arg_type || streq(arg_type, ANY_LUKS) ? CRYPT_LUKS : arg_type, NULL); if (r < 0) - return log_error_errno(r, "Failed to load LUKS superblock on device %s: %m", crypt_get_device_name(cd)); + return log_error_errno(r, "Failed to load LUKS superblock on device %s: %m", sym_crypt_get_device_name(cd)); /* since cryptsetup 2.7.0 (Jan 2024) */ #if HAVE_CRYPT_SET_KEYRING_TO_LINK if (arg_link_key_description) { - r = crypt_set_keyring_to_link(cd, arg_link_key_description, NULL, arg_link_key_type, arg_link_keyring); + r = sym_crypt_set_keyring_to_link(cd, arg_link_key_description, NULL, arg_link_key_type, arg_link_keyring); if (r < 0) log_warning_errno(r, "Failed to set keyring or key description to link volume key in, ignoring: %m"); } #endif if (arg_header) { - r = crypt_set_data_device(cd, source); + r = sym_crypt_set_data_device(cd, source); if (r < 0) return log_error_errno(r, "Failed to set LUKS data device %s: %m", source); } @@ -2716,14 +2716,14 @@ static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata) return 0; } - log_debug_errno(r, "Token activation unsuccessful for device %s: %m", crypt_get_device_name(cd)); + log_debug_errno(r, "Token activation unsuccessful for device %s: %m", sym_crypt_get_device_name(cd)); } } if (streq_ptr(arg_type, CRYPT_BITLK)) { - r = crypt_load(cd, CRYPT_BITLK, NULL); + r = sym_crypt_load(cd, CRYPT_BITLK, NULL); if (r < 0) - return log_error_errno(r, "Failed to load Bitlocker superblock on device %s: %m", crypt_get_device_name(cd)); + return log_error_errno(r, "Failed to load Bitlocker superblock on device %s: %m", sym_crypt_get_device_name(cd)); } bool use_cached_passphrase = true, try_discover_key = !key_file; @@ -2850,7 +2850,7 @@ static int verb_detach(int argc, char *argv[], uintptr_t _data, void *userdata) if (!filename_is_valid(volume)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); - r = crypt_init_by_name(&cd, volume); + r = sym_crypt_init_by_name(&cd, volume); if (r == -ENODEV) { log_info("Volume %s already inactive.", volume); return 0; @@ -2860,7 +2860,7 @@ static int verb_detach(int argc, char *argv[], uintptr_t _data, void *userdata) cryptsetup_enable_logging(cd); - r = crypt_deactivate(cd, volume); + r = sym_crypt_deactivate(cd, volume); if (r < 0) return log_error_errno(r, "Failed to deactivate '%s': %m", volume); @@ -2879,7 +2879,9 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - cryptsetup_enable_logging(NULL); + r = dlopen_cryptsetup(LOG_ERR); + if (r < 0) + return r; return dispatch_verb_with_args(args, NULL); } diff --git a/src/cryptsetup/meson.build b/src/cryptsetup/meson.build index b36354fb0ad0d..9249f70177b37 100644 --- a/src/cryptsetup/meson.build +++ b/src/cryptsetup/meson.build @@ -18,7 +18,7 @@ executables += [ 'public' : true, 'sources' : systemd_cryptsetup_sources, 'dependencies' : [ - libcryptsetup, + libcryptsetup_cflags, libfido2_cflags, libmount_cflags, libopenssl, diff --git a/src/growfs/growfs.c b/src/growfs/growfs.c index 6f39736958475..b544538d9d36b 100644 --- a/src/growfs/growfs.c +++ b/src/growfs/growfs.c @@ -28,7 +28,7 @@ static bool arg_dry_run = false; #if HAVE_LIBCRYPTSETUP static int resize_crypt_luks_device(dev_t devno, const char *fstype, dev_t main_devno) { _cleanup_free_ char *devpath = NULL, *main_devpath = NULL; - _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; _cleanup_close_ int main_devfd = -EBADF; uint64_t size; int r; diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index 3bf993ef53f2e..96ac65a6fbbee 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -411,7 +411,7 @@ static int luks_setup( key_serial_t *ret_key_serial) { _cleanup_(keyring_unlinkp) key_serial_t key_serial = -1; - _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; _cleanup_(erase_and_freep) void *vk = NULL; sd_id128_t p; size_t vks; @@ -522,7 +522,7 @@ static int acquire_open_luks_device( HomeSetup *setup, bool graceful) { - _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; int r; assert(h); @@ -1781,7 +1781,7 @@ static int luks_format( struct crypt_device **ret) { _cleanup_(user_record_unrefp) UserRecord *reduced = NULL; - _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; _cleanup_(erase_and_freep) void *volume_key = NULL; struct crypt_pbkdf_type good_pbkdf, minimal_pbkdf; _cleanup_free_ char *text = NULL; diff --git a/src/integritysetup/integritysetup.c b/src/integritysetup/integritysetup.c index 29581a0522647..9a373e124be3e 100644 --- a/src/integritysetup/integritysetup.c +++ b/src/integritysetup/integritysetup.c @@ -126,19 +126,19 @@ static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata) return r; } - r = crypt_init(&cd, device); + r = sym_crypt_init(&cd, device); if (r < 0) return log_error_errno(r, "Failed to open integrity device %s: %m", device); cryptsetup_enable_logging(cd); - status = crypt_status(cd, volume); + status = sym_crypt_status(cd, volume); if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) { log_info("Volume %s already active.", volume); return 0; } - r = crypt_load(cd, + r = sym_crypt_load(cd, CRYPT_INTEGRITY, &(struct crypt_params_integrity) { .journal_watermark = arg_percent, @@ -149,12 +149,12 @@ static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata) return log_error_errno(r, "Failed to load integrity superblock: %m"); if (!isempty(arg_existing_data_device)) { - r = crypt_set_data_device(cd, arg_existing_data_device); + r = sym_crypt_set_data_device(cd, arg_existing_data_device); if (r < 0) return log_error_errno(r, "Failed to add separate data device: %m"); } - r = crypt_activate_by_volume_key(cd, volume, key_buf, key_buf_size, arg_activate_flags); + r = sym_crypt_activate_by_volume_key(cd, volume, key_buf, key_buf_size, arg_activate_flags); if (r < 0) return log_error_errno(r, "Failed to set up integrity device: %m"); @@ -172,7 +172,7 @@ static int verb_detach(int argc, char *argv[], uintptr_t _data, void *userdata) if (!filename_is_valid(volume)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); - r = crypt_init_by_name(&cd, volume); + r = sym_crypt_init_by_name(&cd, volume); if (r == -ENODEV) { log_info("Volume %s already inactive.", volume); return 0; @@ -182,7 +182,7 @@ static int verb_detach(int argc, char *argv[], uintptr_t _data, void *userdata) cryptsetup_enable_logging(cd); - r = crypt_deactivate(cd, volume); + r = sym_crypt_deactivate(cd, volume); if (r < 0) return log_error_errno(r, "Failed to deactivate: %m"); @@ -190,12 +190,16 @@ static int verb_detach(int argc, char *argv[], uintptr_t _data, void *userdata) } static int run(int argc, char *argv[]) { + int r; + if (argv_looks_like_help(argc, argv)) return help(); log_setup(); - cryptsetup_enable_logging(NULL); + r = dlopen_cryptsetup(LOG_ERR); + if (r < 0) + return r; umask(0022); diff --git a/src/integritysetup/meson.build b/src/integritysetup/meson.build index dd2eb60cf6973..4f3601e681938 100644 --- a/src/integritysetup/meson.build +++ b/src/integritysetup/meson.build @@ -9,7 +9,7 @@ executables += [ 'name' : 'systemd-integritysetup', 'sources' : files('integritysetup.c'), 'extract' : files('integrity-util.c'), - 'dependencies' : libcryptsetup, + 'dependencies' : libcryptsetup_cflags, }, generator_template + { 'name' : 'systemd-integritysetup-generator', diff --git a/src/repart/repart.c b/src/repart/repart.c index e91d32ef0b9d2..91cd77b448f0b 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -5253,7 +5253,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta return log_oom(); } - _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; r = sym_crypt_init(&cd, offline ? hp : node); if (r < 0) return log_error_errno(r, "Failed to allocate libcryptsetup context for %s: %m", hp); @@ -5709,7 +5709,7 @@ static int partition_format_verity_hash( #if HAVE_LIBCRYPTSETUP Partition *dp; _cleanup_(partition_target_freep) PartitionTarget *t = NULL; - _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; _cleanup_free_ char *hint = NULL; int r; diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c index fd5ffd706dca5..0d96d82c621ba 100644 --- a/src/shared/cryptsetup-util.c +++ b/src/shared/cryptsetup-util.c @@ -21,7 +21,9 @@ static void *cryptsetup_dl = NULL; DLSYM_PROTOTYPE(crypt_activate_by_passphrase) = NULL; DLSYM_PROTOTYPE(crypt_activate_by_signed_key) = NULL; +DLSYM_PROTOTYPE(crypt_activate_by_token_pin) = NULL; DLSYM_PROTOTYPE(crypt_activate_by_volume_key) = NULL; +DLSYM_PROTOTYPE(crypt_deactivate) = NULL; DLSYM_PROTOTYPE(crypt_deactivate_by_name) = NULL; DLSYM_PROTOTYPE(crypt_format) = NULL; DLSYM_PROTOTYPE(crypt_free) = NULL; @@ -37,9 +39,11 @@ DLSYM_PROTOTYPE(crypt_get_volume_key_size) = NULL; DLSYM_PROTOTYPE(crypt_header_restore) = NULL; DLSYM_PROTOTYPE(crypt_init) = NULL; DLSYM_PROTOTYPE(crypt_init_by_name) = NULL; +DLSYM_PROTOTYPE(crypt_init_data_device) = NULL; DLSYM_PROTOTYPE(crypt_keyslot_add_by_volume_key) = NULL; DLSYM_PROTOTYPE(crypt_keyslot_destroy) = NULL; DLSYM_PROTOTYPE(crypt_keyslot_max) = NULL; +DLSYM_PROTOTYPE(crypt_keyslot_status) = NULL; DLSYM_PROTOTYPE(crypt_load) = NULL; DLSYM_PROTOTYPE(crypt_metadata_locking) = NULL; DLSYM_PROTOTYPE(crypt_persistent_flags_get) = NULL; @@ -51,10 +55,15 @@ DLSYM_PROTOTYPE(crypt_resume_by_volume_key) = NULL; DLSYM_PROTOTYPE(crypt_set_data_device) = NULL; DLSYM_PROTOTYPE(crypt_set_data_offset) = NULL; DLSYM_PROTOTYPE(crypt_set_debug_level) = NULL; +#if HAVE_CRYPT_SET_KEYRING_TO_LINK +DLSYM_PROTOTYPE(crypt_set_keyring_to_link) = NULL; +#endif DLSYM_PROTOTYPE(crypt_set_log_callback) = NULL; DLSYM_PROTOTYPE(crypt_set_metadata_size) = NULL; DLSYM_PROTOTYPE(crypt_set_pbkdf_type) = NULL; +DLSYM_PROTOTYPE(crypt_status) = NULL; DLSYM_PROTOTYPE(crypt_suspend) = NULL; +DLSYM_PROTOTYPE(crypt_token_external_path) = NULL; DLSYM_PROTOTYPE(crypt_token_json_get) = NULL; DLSYM_PROTOTYPE(crypt_token_json_set) = NULL; DLSYM_PROTOTYPE(crypt_token_max) = NULL; @@ -287,7 +296,9 @@ int dlopen_cryptsetup(int log_level) { &cryptsetup_dl, "libcryptsetup.so.12", log_level, DLSYM_ARG(crypt_activate_by_passphrase), DLSYM_ARG(crypt_activate_by_signed_key), + DLSYM_ARG(crypt_activate_by_token_pin), DLSYM_ARG(crypt_activate_by_volume_key), + DLSYM_ARG(crypt_deactivate), DLSYM_ARG(crypt_deactivate_by_name), DLSYM_ARG(crypt_format), DLSYM_ARG(crypt_free), @@ -303,9 +314,11 @@ int dlopen_cryptsetup(int log_level) { DLSYM_ARG(crypt_header_restore), DLSYM_ARG(crypt_init), DLSYM_ARG(crypt_init_by_name), + DLSYM_ARG(crypt_init_data_device), DLSYM_ARG(crypt_keyslot_add_by_volume_key), DLSYM_ARG(crypt_keyslot_destroy), DLSYM_ARG(crypt_keyslot_max), + DLSYM_ARG(crypt_keyslot_status), DLSYM_ARG(crypt_load), DLSYM_ARG(crypt_metadata_locking), DLSYM_ARG(crypt_persistent_flags_get), @@ -317,10 +330,15 @@ int dlopen_cryptsetup(int log_level) { DLSYM_ARG(crypt_set_data_device), DLSYM_ARG(crypt_set_data_offset), DLSYM_ARG(crypt_set_debug_level), +#if HAVE_CRYPT_SET_KEYRING_TO_LINK + DLSYM_ARG(crypt_set_keyring_to_link), +#endif DLSYM_ARG(crypt_set_log_callback), DLSYM_ARG(crypt_set_metadata_size), DLSYM_ARG(crypt_set_pbkdf_type), + DLSYM_ARG(crypt_status), DLSYM_ARG(crypt_suspend), + DLSYM_ARG(crypt_token_external_path), DLSYM_ARG(crypt_token_json_get), DLSYM_ARG(crypt_token_json_set), DLSYM_ARG(crypt_token_max), diff --git a/src/shared/cryptsetup-util.h b/src/shared/cryptsetup-util.h index 27e704869fe5a..4e994ce9f9951 100644 --- a/src/shared/cryptsetup-util.h +++ b/src/shared/cryptsetup-util.h @@ -9,7 +9,9 @@ extern DLSYM_PROTOTYPE(crypt_activate_by_passphrase); extern DLSYM_PROTOTYPE(crypt_activate_by_signed_key); +extern DLSYM_PROTOTYPE(crypt_activate_by_token_pin); extern DLSYM_PROTOTYPE(crypt_activate_by_volume_key); +extern DLSYM_PROTOTYPE(crypt_deactivate); extern DLSYM_PROTOTYPE(crypt_deactivate_by_name); extern DLSYM_PROTOTYPE(crypt_format); extern DLSYM_PROTOTYPE(crypt_free); @@ -25,9 +27,11 @@ extern DLSYM_PROTOTYPE(crypt_get_volume_key_size); extern DLSYM_PROTOTYPE(crypt_header_restore); extern DLSYM_PROTOTYPE(crypt_init); extern DLSYM_PROTOTYPE(crypt_init_by_name); +extern DLSYM_PROTOTYPE(crypt_init_data_device); extern DLSYM_PROTOTYPE(crypt_keyslot_add_by_volume_key); extern DLSYM_PROTOTYPE(crypt_keyslot_destroy); extern DLSYM_PROTOTYPE(crypt_keyslot_max); +extern DLSYM_PROTOTYPE(crypt_keyslot_status); extern DLSYM_PROTOTYPE(crypt_load); extern DLSYM_PROTOTYPE(crypt_metadata_locking); extern DLSYM_PROTOTYPE(crypt_persistent_flags_get); @@ -39,10 +43,15 @@ extern DLSYM_PROTOTYPE(crypt_resume_by_volume_key); extern DLSYM_PROTOTYPE(crypt_set_data_device); extern DLSYM_PROTOTYPE(crypt_set_data_offset); extern DLSYM_PROTOTYPE(crypt_set_debug_level); +#if HAVE_CRYPT_SET_KEYRING_TO_LINK +extern DLSYM_PROTOTYPE(crypt_set_keyring_to_link); +#endif extern DLSYM_PROTOTYPE(crypt_set_log_callback); extern DLSYM_PROTOTYPE(crypt_set_metadata_size); extern DLSYM_PROTOTYPE(crypt_set_pbkdf_type); +extern DLSYM_PROTOTYPE(crypt_status); extern DLSYM_PROTOTYPE(crypt_suspend); +extern DLSYM_PROTOTYPE(crypt_token_external_path); extern DLSYM_PROTOTYPE(crypt_token_json_get); extern DLSYM_PROTOTYPE(crypt_token_json_set); extern DLSYM_PROTOTYPE(crypt_token_max); @@ -55,10 +64,9 @@ extern DLSYM_PROTOTYPE(crypt_volume_key_keyring); extern DLSYM_PROTOTYPE(crypt_wipe); extern DLSYM_PROTOTYPE(crypt_get_integrity_info); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, crypt_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, sym_crypt_free, NULL); - -/* Be careful, this works with dlopen_cryptsetup(), that is, it calls sym_crypt_free() instead of crypt_free(). */ +/* Be careful, these work with dlopen_cryptsetup(), that is, they call sym_crypt_free() instead of + * crypt_free() and hence depend on dlopen_cryptsetup() having been called. */ +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct crypt_device *, sym_crypt_free, crypt_freep, NULL); #define crypt_free_and_replace(a, b) \ free_and_replace_full(a, b, sym_crypt_free) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 043c7060fb3de..c51e7b964e12e 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -2925,7 +2925,7 @@ static int decrypt_partition( DecryptedImage *d) { _cleanup_free_ char *node = NULL, *name = NULL; - _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; _cleanup_close_ int fd = -EBADF; int r; @@ -2995,7 +2995,7 @@ static int verity_can_reuse( struct crypt_device **ret_cd) { /* If the same volume was already open, check that the root hashes match, and reuse it if they do */ - _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; struct crypt_params_verity crypt_params = {}; int r; @@ -3283,7 +3283,7 @@ static int verity_partition( PartitionPolicyFlags policy_flags, DecryptedImage *d) { - _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; _cleanup_free_ char *node = NULL, *name = NULL; _cleanup_close_ int mount_node_fd = -EBADF; int r; @@ -3353,7 +3353,7 @@ static int verity_partition( * retry a few times before giving up. */ for (unsigned i = 0; i < N_DEVICE_NODE_LIST_ATTEMPTS; i++) { _cleanup_(dm_deferred_remove_cleanp) char *restore_deferred_remove = NULL; - _cleanup_(sym_crypt_freep) struct crypt_device *existing_cd = NULL; + _cleanup_(crypt_freep) struct crypt_device *existing_cd = NULL; _cleanup_close_ int fd = -EBADF; /* First, check if the device already exists. */ diff --git a/src/veritysetup/meson.build b/src/veritysetup/meson.build index cc9ac80a61554..21432d41f1658 100644 --- a/src/veritysetup/meson.build +++ b/src/veritysetup/meson.build @@ -8,7 +8,7 @@ executables += [ libexec_template + { 'name' : 'systemd-veritysetup', 'sources' : files('veritysetup.c'), - 'dependencies' : libcryptsetup, + 'dependencies' : libcryptsetup_cflags, }, generator_template + { 'name' : 'systemd-veritysetup-generator', diff --git a/src/veritysetup/veritysetup.c b/src/veritysetup/veritysetup.c index 2e44aab963357..b5d57fd1fe80c 100644 --- a/src/veritysetup/veritysetup.c +++ b/src/veritysetup/veritysetup.c @@ -376,13 +376,13 @@ static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata) return log_error_errno(r, "Failed to decode root hash signature data from udev data device: %m"); } - r = crypt_init(&cd, verity_device); + r = sym_crypt_init(&cd, verity_device); if (r < 0) return log_error_errno(r, "Failed to open verity device %s: %m", verity_device); cryptsetup_enable_logging(cd); - status = crypt_status(cd, volume); + status = sym_crypt_status(cd, volume); if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) { log_info("Volume %s already active.", volume); return 0; @@ -396,7 +396,7 @@ static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata) .fec_roots = arg_fec_roots, }; - r = crypt_load(cd, CRYPT_VERITY, &p); + r = sym_crypt_load(cd, CRYPT_VERITY, &p); if (r < 0) return log_error_errno(r, "Failed to load verity superblock: %m"); } else { @@ -416,28 +416,28 @@ static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata) .flags = CRYPT_VERITY_NO_HEADER, }; - r = crypt_format(cd, CRYPT_VERITY, NULL, NULL, arg_uuid, NULL, 0, &p); + r = sym_crypt_format(cd, CRYPT_VERITY, NULL, NULL, arg_uuid, NULL, 0, &p); if (r < 0) return log_error_errno(r, "Failed to format verity superblock: %m"); } - r = crypt_set_data_device(cd, data_device); + r = sym_crypt_set_data_device(cd, data_device); if (r < 0) return log_error_errno(r, "Failed to configure data device: %m"); if (arg_root_hash_signature_size > 0) { - r = crypt_activate_by_signed_key(cd, volume, rh, rh_size, arg_root_hash_signature, arg_root_hash_signature_size, arg_activate_flags); + r = sym_crypt_activate_by_signed_key(cd, volume, rh, rh_size, arg_root_hash_signature, arg_root_hash_signature_size, arg_activate_flags); if (r < 0) { log_info_errno(r, "Unable to activate verity device '%s' with root hash signature (%m), retrying without.", volume); - r = crypt_activate_by_volume_key(cd, volume, rh, rh_size, arg_activate_flags); + r = sym_crypt_activate_by_volume_key(cd, volume, rh, rh_size, arg_activate_flags); if (r < 0) return log_error_errno(r, "Failed to activate verity device '%s' both with and without root hash signature: %m", volume); log_info("Activation of verity device '%s' succeeded without root hash signature.", volume); } } else - r = crypt_activate_by_volume_key(cd, volume, rh, rh_size, arg_activate_flags); + r = sym_crypt_activate_by_volume_key(cd, volume, rh, rh_size, arg_activate_flags); if (r < 0) return log_error_errno(r, "Failed to set up verity device '%s': %m", volume); @@ -460,7 +460,7 @@ static int verb_detach(int argc, char *argv[], uintptr_t _data, void *userdata) if (!filename_is_valid(volume)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); - r = crypt_init_by_name(&cd, volume); + r = sym_crypt_init_by_name(&cd, volume); if (r == -ENODEV) { log_info("Volume %s 'already' inactive.", volume); return 0; @@ -470,7 +470,7 @@ static int verb_detach(int argc, char *argv[], uintptr_t _data, void *userdata) cryptsetup_enable_logging(cd); - r = crypt_deactivate(cd, volume); + r = sym_crypt_deactivate(cd, volume); if (r < 0) return log_error_errno(r, "Failed to deactivate volume '%s': %m", volume); @@ -478,12 +478,16 @@ static int verb_detach(int argc, char *argv[], uintptr_t _data, void *userdata) } static int run(int argc, char *argv[]) { + int r; + if (argv_looks_like_help(argc, argv)) return help(); log_setup(); - cryptsetup_enable_logging(NULL); + r = dlopen_cryptsetup(LOG_ERR); + if (r < 0) + return r; umask(0022); From 724e5b51e7c837210fddfac42ccd8ccee7d385a1 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 23 Apr 2026 08:01:04 +0000 Subject: [PATCH 1180/1296] shared: drop redundant cryptsetup_enable_logging(NULL) calls These were only used to implicitly load libcryptsetup at startup. dlopen_cryptsetup() now calls cryptsetup_enable_logging(NULL) itself, and every code path that uses libcryptsetup calls dlopen_cryptsetup() before doing so, so the upfront calls are no longer needed. --- src/growfs/growfs.c | 4 ---- src/home/homework.c | 2 -- src/repart/repart.c | 4 ---- 3 files changed, 10 deletions(-) diff --git a/src/growfs/growfs.c b/src/growfs/growfs.c index b544538d9d36b..ff6a5909e795c 100644 --- a/src/growfs/growfs.c +++ b/src/growfs/growfs.c @@ -93,10 +93,6 @@ static int maybe_resize_underlying_device( assert(mountfd >= 0); assert(mountpath); -#if HAVE_LIBCRYPTSETUP - cryptsetup_enable_logging(NULL); -#endif - r = get_block_device_harder_fd(mountfd, &devno); if (r < 0) return log_error_errno(r, "Failed to determine underlying block device of \"%s\": %m", diff --git a/src/home/homework.c b/src/home/homework.c index 578d5cce914f3..d63d481447382 100644 --- a/src/home/homework.c +++ b/src/home/homework.c @@ -2005,8 +2005,6 @@ static int run(int argc, char *argv[]) { log_setup(); - cryptsetup_enable_logging(NULL); - umask(0022); if (argc < 2 || argc > 3) diff --git a/src/repart/repart.c b/src/repart/repart.c index 91cd77b448f0b..fac2bd817acee 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -11222,10 +11222,6 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; -#if HAVE_LIBCRYPTSETUP - cryptsetup_enable_logging(NULL); -#endif - if (arg_varlink) return vl_server(); From 240b26503b26cddb0275a4e0e2b6918950379c69 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 23 Apr 2026 08:10:24 +0000 Subject: [PATCH 1181/1296] shared: drop redundant dlopen_cryptsetup() calls from cryptsetup_* helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cryptsetup_set_minimal_pbkdf(), cryptsetup_get_token_as_json() and cryptsetup_add_token_json() each take a struct crypt_device *cd, which can only be obtained by first calling sym_crypt_init*() — and that already requires dlopen_cryptsetup() to have succeeded. The internal calls here were only implicitly re-loading a library the caller is guaranteed to have already loaded. --- src/shared/cryptsetup-util.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c index 0d96d82c621ba..47a148459d95f 100644 --- a/src/shared/cryptsetup-util.c +++ b/src/shared/cryptsetup-util.c @@ -133,10 +133,6 @@ int cryptsetup_set_minimal_pbkdf(struct crypt_device *cd) { /* Sets a minimal PKBDF in case we already have a high entropy key. */ - r = dlopen_cryptsetup(LOG_DEBUG); - if (r < 0) - return r; - r = sym_crypt_set_pbkdf_type(cd, &minimal_pbkdf); if (r < 0) return r; @@ -164,10 +160,6 @@ int cryptsetup_get_token_as_json( * -EMEDIUMTYPE → "verify_type" specified and doesn't match token's type */ - r = dlopen_cryptsetup(LOG_DEBUG); - if (r < 0) - return r; - r = sym_crypt_token_json_get(cd, idx, &text); if (r < 0) return r; @@ -197,10 +189,6 @@ int cryptsetup_add_token_json(struct crypt_device *cd, sd_json_variant *v) { _cleanup_free_ char *text = NULL; int r; - r = dlopen_cryptsetup(LOG_DEBUG); - if (r < 0) - return r; - r = sd_json_variant_format(v, 0, &text); if (r < 0) return log_debug_errno(r, "Failed to format token data for LUKS: %m"); From 079361e8f0ad1deb711e377472ce2fcaa210bb56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 22 Apr 2026 17:01:45 +0200 Subject: [PATCH 1182/1296] meson: concatenate donors specified in 'objects' Previously, we'd only honour the last donor. --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 95d95c43cd27f..a902bc96aa204 100644 --- a/meson.build +++ b/meson.build @@ -2242,7 +2242,7 @@ foreach dict : executables foreach val : dict.get('objects', []) obj = objects_by_name[val] - kwargs += { 'objects' : obj['objects'] } + kwargs += { 'objects' : kwargs.get('objects', []) + obj['objects'] } include_directories += obj['include_directories'] endforeach From d9506e7df71aab1f0f0ba929db1707dbe3b5f92c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 22 Apr 2026 16:33:12 +0200 Subject: [PATCH 1183/1296] meson: move fuzz-journald-util.c to fuzz-journal-audit The .c file is shared between various fuzz-journal-* binaries. It was added to 32bd43d768a4bdd54481c5e37ce9ea3d1009a824, but that is somewhat ugly. Let's add it to the alphabetially first fuzzer and share from there. Follow-up for 32bd43d768a4bdd54481c5e37ce9ea3d1009a824 and 85b5acde869baa51f5618fa503eafac3dccbf379. --- src/journal/meson.build | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/journal/meson.build b/src/journal/meson.build index 1bec605b0ccf0..142d2246c1fe0 100644 --- a/src/journal/meson.build +++ b/src/journal/meson.build @@ -21,14 +21,6 @@ systemd_journald_extract_sources = files( 'journald-wall.c', ) -if want_fuzz_tests - # Build fuzz-journald-util.c as part of systemd-journald so we only - # compile it once instead of once per fuzz test. - systemd_journald_extract_sources += files( - 'fuzz-journald-util.c', - ) -endif - journald_gperf_c = custom_target( input : 'journald-gperf.gperf', output : 'journald-gperf.c', @@ -63,7 +55,10 @@ journal_test_template = test_template + { } journal_fuzz_template = fuzz_template + { - 'objects' : ['systemd-journald'], + 'objects' : [ + 'fuzz-journald-audit', + 'systemd-journald', + ], 'dependencies' : libselinux_cflags, } @@ -138,8 +133,11 @@ executables += [ libselinux_cflags, ], }, - journal_fuzz_template + { + fuzz_template + { 'sources' : files('fuzz-journald-audit.c'), + # fuzz-journald-util.c is shared with the other fuzzers below. + 'extract' : files('fuzz-journald-util.c'), + 'objects' : ['systemd-journald'], }, journal_fuzz_template + { 'sources' : files('fuzz-journald-kmsg.c'), From 9a3a861ff8c56f85d86b2276917d5bc7a1a331fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 23 Apr 2026 11:09:11 +0200 Subject: [PATCH 1184/1296] measure: fix oom check Pointed out in review. --- src/measure/measure-tool.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/measure/measure-tool.c b/src/measure/measure-tool.c index 6a2e2994f9fea..4abb77aeb84a7 100644 --- a/src/measure/measure-tool.c +++ b/src/measure/measure-tool.c @@ -1096,10 +1096,8 @@ static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) return log_error_errno(r, "Failed to decode PCR value '%s': %m", s); if (!sd_json_format_enabled(arg_json_format_flags)) { - _cleanup_free_ char *f = NULL; - - f = hexmem(h, l); - if (!h) + _cleanup_free_ char *f = hexmem(h, l); + if (!f) return log_oom(); if (bank == arg_banks) { From f59b1f1a7dd2677b71a4ea4a47c951da85fff3a5 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 22 Apr 2026 23:43:36 +0200 Subject: [PATCH 1185/1296] userdbctl: drop unused variable --- src/userdb/userdbctl.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/userdb/userdbctl.c b/src/userdb/userdbctl.c index 8da35172c54f9..6b4371aa509b1 100644 --- a/src/userdb/userdbctl.c +++ b/src/userdb/userdbctl.c @@ -1072,13 +1072,9 @@ static int verb_display_services(int argc, char *argv[], uintptr_t _data, void * (void) table_set_sort(t, (size_t) 0); FOREACH_DIRENT(de, d, return -errno) { - _cleanup_free_ char *j = NULL, *no = NULL; + _cleanup_free_ char *no = NULL; _cleanup_close_ int fd = -EBADF; - j = path_join("/run/systemd/userdb/", de->d_name); - if (!j) - return log_oom(); - fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); if (fd < 0) return log_error_errno(errno, "Failed to allocate AF_UNIX/SOCK_STREAM socket: %m"); From 8cb4ff428e5f84551e578017bbb3a87893b1dd67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 23 Apr 2026 10:10:31 +0200 Subject: [PATCH 1186/1296] shared/options: add a 'data' parameter to options This mirrors a similar field in Verb. In some cases it convenient to pass a fixed value to the parser. --- src/shared/options.h | 11 ++++++++--- src/test/test-options.c | 12 ++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/shared/options.h b/src/shared/options.h index cd9f64fd4949a..974c21d16a692 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -19,10 +19,11 @@ typedef struct Option { char short_code; const char *long_code; const char *metavar; + uintptr_t data; const char *help; } Option; -#define _OPTION(counter, fl, sc, lc, mv, h) \ +#define _OPTION(counter, fl, sc, lc, mv, d, h) \ _section_("SYSTEMD_OPTIONS") \ _alignptr_ \ _used_ \ @@ -35,6 +36,7 @@ typedef struct Option { .short_code = sc, \ .long_code = lc, \ .metavar = mv, \ + .data = d, \ .help = h, \ }; \ case (0x100 + counter) @@ -43,14 +45,17 @@ typedef struct Option { * The define is structured as 'case' so that it can be followed by ':' and indented appropriately. */ #define OPTION_GROUP(gr) \ - _OPTION(__COUNTER__, OPTION_GROUP_MARKER, /* sc= */ 0, /* lc= */ gr, /* mv= */ NULL, /* h= */ NULL) + _OPTION(__COUNTER__, OPTION_GROUP_MARKER, /* sc= */ 0, /* lc= */ gr, /* mv= */ NULL, /* d= */ 0u, /* h= */ NULL) -#define OPTION_FULL(fl, sc, lc, mv, h) _OPTION(__COUNTER__, fl, sc, lc, mv, h) +#define OPTION_FULL_DATA(fl, sc, lc, mv, d, h) _OPTION(__COUNTER__, fl, sc, lc, mv, d, h) +#define OPTION_FULL(fl, sc, lc, mv, h) OPTION_FULL_DATA(fl, sc, lc, mv, /* d= */ 0u, h) #define OPTION(sc, lc, mv, h) OPTION_FULL(/* fl= */ 0, sc, lc, mv, h) #define OPTION_LONG(lc, mv, h) OPTION(/* sc= */ 0, lc, mv, h) #define OPTION_LONG_FLAGS(fl, lc, mv, h) OPTION_FULL(fl, /* sc= */ 0, lc, mv, h) +#define OPTION_LONG_DATA(lc, mv, d, h) OPTION_FULL_DATA(/* fl= */ 0, /* sc= */ 0, lc, mv, d, h) #define OPTION_SHORT(sc, mv, h) OPTION(sc, /* lc= */ NULL, mv, h) #define OPTION_SHORT_FLAGS(fl, sc, mv, h) OPTION_FULL(fl, sc, /* lc= */ NULL, mv, h) +#define OPTION_SHORT_DATA(sc, mv, d, h) OPTION_FULL_DATA(/* fl= */ 0, sc, /* lc= */ NULL, mv, d, h) #define OPTION_POSITIONAL OPTION_FULL(OPTION_POSITIONAL_ENTRY, /* sc= */ 0, "(positional)", /* mv= */ NULL, /* h= */ NULL) #define OPTION_HELP_VERBATIM(lc, h) OPTION_FULL(OPTION_HELP_ENTRY_VERBATIM, /* sc= */ 0, lc, /* mv= */ NULL, h) diff --git a/src/test/test-options.c b/src/test/test-options.c index 05fd4bcbe0761..687bafd4a2b9b 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -772,6 +772,12 @@ static void test_macros_parse_one( if (entries[i].short_code != 0) ASSERT_EQ(opt->short_code, entries[i].short_code); ASSERT_TRUE(streq_ptr(arg, entries[i].argument)); + + if (streq_ptr(entries[i].long_code, "optional2")) + ASSERT_EQ(opt->data, 666u); + else + ASSERT_EQ(opt->data, 0u); + i++; switch (c) { @@ -796,6 +802,10 @@ static void test_macros_parse_one( OPTION_FULL(OPTION_OPTIONAL_ARG, 'o', "optional", "ARG", "Optional arg option"): break; + /* OPTION_FULL_DATA: optional arg */ + OPTION_FULL_DATA(OPTION_OPTIONAL_ARG, 'O', "optional2", "ARG", 666, "Optional arg option"): + break; + /* OPTION_FULL: stops parsing */ OPTION_FULL(OPTION_STOPS_PARSING, 0, "exec", NULL, "Stop parsing after this"): break; @@ -1015,6 +1025,7 @@ TEST(option_macros) { "-v", "--required=rval", "--optional=oval", + "--optional2=oval", "--debug", "pos2", "-o", @@ -1025,6 +1036,7 @@ TEST(option_macros) { { .short_code = 'v' }, { "required", "rval" }, { "optional", "oval" }, + { "optional2", "oval" }, { "debug" }, { "optional", NULL }, { "help" }, From 1d78c2d327cbd4e738d0f1281a976a771f643517 Mon Sep 17 00:00:00 2001 From: Nandakumar Raghavan Date: Tue, 21 Apr 2026 13:14:17 +0000 Subject: [PATCH 1187/1296] gpt-auto-generator: do not fail on missing libcryptsetup when verity is not used add_veritysetup() is called unconditionally from add_root_mount() and add_usr_mount() whenever in_initrd() is true, to generate units that only activate if verity devices appear. However, when compiled without libcryptsetup, this function returned a hard error, causing the entire generator to fail even when no verity protection is in use. Change the #else fallback to log a debug message and return 0, matching the pattern already used by add_root_cryptsetup(). --- src/gpt-auto-generator/gpt-auto-generator.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index 6716a8d1aaf7c..abbb955e5992e 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -295,8 +295,8 @@ static int add_veritysetup( return 0; #else - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "Partition is Verity protected, but systemd-gpt-auto-generator was compiled without libcryptsetup support."); + log_warning("Compiled without libcryptsetup support, skipping verity setup for '%s'.", id); + return 0; #endif } #endif From b54ef83414234ac0a742895dd645632662b5aa77 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 22 Apr 2026 15:38:10 +0100 Subject: [PATCH 1188/1296] repart: trim NUL bytes from verity sig split artifact The verity signature partition content is a bare JSON object. Repart pads it with zeros to fill the GPT partition. But when splitting out the content as an individual file, the padding remains, so it's not a valid text file. jq started rejecting files with NUL bytes to fix a security issue: https://github.com/jqlang/jq/commit/6374ae0bcdfe33a18eb0ae6db28493b1f34a0a5b Trim the output when writing these files out. --- src/repart/repart.c | 27 ++++++++++++++++++++++++--- test/units/TEST-58-REPART.sh | 8 ++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index 91cd77b448f0b..75fb79c48a959 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -7754,9 +7754,30 @@ static int context_split(Context *context) { if (lseek(fd, p->offset, SEEK_SET) < 0) return log_error_errno(errno, "Failed to seek to partition offset: %m"); - r = copy_bytes(fd, fdt, p->new_size, COPY_REFLINK|COPY_HOLES|COPY_TRUNCATE); - if (r < 0) - return log_error_errno(r, "Failed to copy to split partition %s: %m", p->split_path); + /* Verity signature partitions contain a JSON object NUL-padded out to the partition + * size. The on-disk partition must keep the padding, but the split-out file is a + * standalone artifact, so trim the trailing NUL bytes there to avoid tripping jq. */ + if (partition_designator_is_verity_sig(p->type.designator)) { + _cleanup_free_ char *buf = malloc(p->new_size); + if (!buf) + return log_oom(); + + r = loop_read_exact(fd, buf, p->new_size, /* do_poll= */ false); + if (r < 0) + return log_error_errno(r, "Failed to read verity signature partition: %m"); + + size_t len = strnlen(buf, p->new_size); + if (len == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Verity signature partition is empty"); + + r = loop_write(fdt, buf, len); + if (r < 0) + return log_error_errno(r, "Failed to write to split partition %s: %m", p->split_path); + } else { + r = copy_bytes(fd, fdt, p->new_size, COPY_REFLINK|COPY_HOLES|COPY_TRUNCATE); + if (r < 0) + return log_error_errno(r, "Failed to copy to split partition %s: %m", p->split_path); + } } return 0; diff --git a/test/units/TEST-58-REPART.sh b/test/units/TEST-58-REPART.sh index fb3dbcfad5d65..7b536fa09209f 100755 --- a/test/units/TEST-58-REPART.sh +++ b/test/units/TEST-58-REPART.sh @@ -941,6 +941,7 @@ EOF --dry-run=no \ --empty=create \ --size=auto \ + --split=yes \ --json=pretty \ --private-key="$defs/verity.key" \ --certificate="$defs/verity.crt" \ @@ -953,6 +954,13 @@ EOF assert_eq "$drh" "$hrh" assert_eq "$hrh" "$srh" + # The split-out verity signature file should be a valid JSON document (i.e. trailing NUL padding + # from the on-disk partition must be trimmed when writing the split file). + sig_split=$(jq -r ".[] | select(.type == \"root-${architecture}-verity-sig\") | .split_path" <<<"$output") + assert_neq "$sig_split" "" + assert_neq "$sig_split" "null" + jq . "$sig_split" >/dev/null + # Check that offline signing works and the resulting image is valid output=$(systemd-repart --offline="$OFFLINE" \ From f9ac6aced93828f4dea2f622d88d6f6c06e7538a Mon Sep 17 00:00:00 2001 From: Antonio Alvarez Feijoo Date: Thu, 23 Apr 2026 08:41:30 +0200 Subject: [PATCH 1189/1296] repart: raise log level to LOG_ERR if dlopen_fdisk() fails libfdisk is required by systemd-repart and it silently exits if dlopen fails (unless the debug log level is set): ``` $ SYSTEMD_LOG_LEVEL=debug systemd-repart Shared library 'libfdisk.so.1' is not available: libfdisk.so.1: cannot open shared object file: No such file or directory $ echo $? 1 ``` Follow-up for d49f3f287a0bf72b5b473980cf435f0c0c2413d0 --- src/repart/repart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index 75fb79c48a959..b089689e736ce 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -11239,7 +11239,7 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - r = dlopen_fdisk(LOG_DEBUG); + r = dlopen_fdisk(LOG_ERR); if (r < 0) return r; From 658e5ac06f80ee2078b034f7cc483204d7f91c5e Mon Sep 17 00:00:00 2001 From: Bret Comnes Date: Wed, 25 Mar 2026 22:59:09 -0700 Subject: [PATCH 1190/1296] Revert "resolve: refuse traffic from the local host only for queries" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 526f1594daec073269c3e70ee7914f6dd8740d5c. This revert is necessary because the change breaks mDNS hostname stability whenever a DNS-SD service calls UnregisterService. When a service unregisters (e.g. on process restart), manager_refresh_rrs() clears and re-adds all RRs in PROBING state, which sends a multicast announcement (QR=1). The kernel reflects this back to resolved's own socket. Because the local-address check was moved inside the query-only branch by the reverted commit, the reply path in on_mdns_packet() is now unguarded. The looped-back announcement matches the pending probe transaction and completes it with DNS_TRANSACTION_SUCCESS. Since the zone item is still in PROBING state (not ESTABLISHED), dns_zone_item_notify() sets we_lost=true and calls dns_zone_item_conflict(), which invokes manager_next_hostname() and renames the hostname (e.g. foo.local → foo4.local). This happens reliably on every restart of any service using RegisterService/UnregisterService (homebridge, avahi-compat wrappers, etc.). The top-level local-address check in on_mdns_packet() suppresses all looped-back multicast traffic before the reply/query split. Restoring it there is consistent with the overall design: dns_scope_check_conflicts() already has its own manager_packet_from_local_address() guard and is unaffected. A more targeted long-term fix (e.g. guarding dns_transaction_process_reply() for mDNS, or avoiding unnecessary re-probing of already-established records in manager_refresh_rrs()) can be pursued separately. --- src/resolve/resolved-mdns.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c index 5026b10ff4c8b..fb20ba9cd0286 100644 --- a/src/resolve/resolved-mdns.c +++ b/src/resolve/resolved-mdns.c @@ -413,6 +413,14 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us if (r <= 0) return r; + /* Refuse traffic from the local host, to avoid query loops. However, allow legacy mDNS + * unicast queries through anyway (we never send those ourselves, hence no risk). + * i.e. check for the source port nr. */ + if (p->sender_port == MDNS_PORT && manager_packet_from_local_address(m, p)) { + log_debug("Got mDNS UDP packet from local host, ignoring."); + return 0; + } + scope = manager_find_scope(m, p); if (!scope) { log_debug("Got mDNS UDP packet on unknown scope. Ignoring."); @@ -529,14 +537,6 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us if (unsolicited_packet) mdns_notify_browsers_unsolicited_updates(m, p->answer, p->family); } else if (dns_packet_validate_query(p) > 0) { - /* Refuse traffic from the local host, to avoid query loops. However, allow legacy mDNS - * unicast queries through anyway (we never send those ourselves, hence no risk). - * i.e. check for the source port nr. */ - if (p->sender_port == MDNS_PORT && manager_packet_from_local_address(m, p)) { - log_debug("Got mDNS UDP packet from local host, ignoring."); - return 0; - } - log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p)); r = mdns_scope_process_query(scope, p); From 341251b6e3eb38a9908192f0a144ade57606dff9 Mon Sep 17 00:00:00 2001 From: Antonio Alvarez Feijoo Date: Thu, 23 Apr 2026 15:39:14 +0200 Subject: [PATCH 1191/1296] dissect-image: fix typo in log message --- src/shared/dissect-image.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index b66b5455dae17..7ad85077b4047 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -3072,7 +3072,7 @@ static int validate_signature_userspace(const VeritySettings *verity, const char return 0; } if (!b) { - log_debug("Userspace dm-verity signature authentication disabled via systemd.allow_userspace_verity= kernel command line variable."); + log_debug("Userspace dm-verity signature authentication disabled via systemd.allow_userspace_verity= kernel command line option."); return 0; } From dfa5aa07b5637cb9a9f46d7908c964217940a073 Mon Sep 17 00:00:00 2001 From: Antonio Alvarez Feijoo Date: Thu, 23 Apr 2026 15:39:29 +0200 Subject: [PATCH 1192/1296] man: clarify that /etc/verity.d only parses certificates with the .crt extension Exposed in the dracut testsuite while adding tests for sysexts: ``` [ 2.972948] localhost (sd-merge)[510]: Validation of dm-verity signature failed via the kernel, trying userspace validation instead: Required key not available [ 2.972993] localhost (sd-merge)[510]: Skipping file '/etc/verity.d/dracut.pem', suffix is not '.crt'. [ 2.973045] localhost (sd-merge)[510]: No userspace dm-verity certificates found. ``` --- man/kernel-command-line.xml | 4 ++-- man/systemd-mountfsd.service.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 0ad3c9c772f3e..83544b3606464 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -711,8 +711,8 @@ systemd.allow_userspace_verity= Takes a boolean argument. Controls whether disk images that are Verity protected may - be authenticated in userspace signature checks via /etc/verity.d/ (and related - directories) public key drop-ins, or whether in-kernel signature checking only. Defaults to + be authenticated in userspace signature checks via /etc/verity.d/*.crt (and + related directories) public key drop-ins, or whether in-kernel signature checking only. Defaults to on. diff --git a/man/systemd-mountfsd.service.xml b/man/systemd-mountfsd.service.xml index 7cc607c4c5c48..2e623a2728140 100644 --- a/man/systemd-mountfsd.service.xml +++ b/man/systemd-mountfsd.service.xml @@ -45,7 +45,7 @@ /usr/lib/ it is assumed to be trusted. If the disk image contains a Verity enabled disk image, along with a signature - partition with a key in the kernel keyring or in /etc/verity.d/ (and related + partition with a key in the kernel keyring or in /etc/verity.d/*.crt (and related directories) the disk image is considered trusted. From ac2a0355fbf9b209cec4a4b8a41d512d10c17661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 19:22:11 +0200 Subject: [PATCH 1193/1296] pcrlock: reorder function definitions to match --help The order was random, different in the source code, different in the verb list, different in --help. Order source code by --help to make the next patch manageable. --- src/pcrlock/pcrlock.c | 3130 ++++++++++++++++++++--------------------- 1 file changed, 1565 insertions(+), 1565 deletions(-) diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index fd8b9e95a4fd0..7820774fe08db 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -2968,2102 +2968,2102 @@ static int unlink_pcrlock(const char *default_pcrlock_path) { return 0; } -static int verb_lock_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *records = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - if (arg_pcr_mask == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No PCR specified, refusing."); +static int event_log_reduce_to_safe_pcrs(EventLog *el, uint32_t *pcrs) { + _cleanup_free_ char *dropped = NULL, *kept = NULL; - if (argc >= 2) { - f = fopen(argv[1], "re"); - if (!f) - return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); - } + assert(el); + assert(pcrs); - r = make_pcrlock_record_from_stream(arg_pcr_mask, f ?: stdin, &records); - if (r < 0) - return r; + /* When we compile a new PCR policy we don't want to bind to PCRs which are fishy for one of three + * reasons: + * + * 1. The PCR value doesn't match the event log + * 2. The event log for the PCR contains measurements we don't know responsible components for + * 3. The event log for the PCR does not contain measurements for components we know + * + * This function checks for the three conditions and drops the PCR from the mask. + */ - return write_pcrlock(records, NULL); -} + for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) { -static int verb_unlock_simple(int argc, char *argv[], uintptr_t _data, void *userdata) { - return unlink_pcrlock(NULL); -} + if (!BIT_SET(*pcrs, pcr)) + continue; -static int verb_lock_secureboot_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { - static const struct { - sd_id128_t id; - const char *name; - int synthesize_empty; /* 0 → fail, > 0 → synthesize empty db, < 0 → skip */ - } variables[] = { - { EFI_VENDOR_GLOBAL, "SecureBoot", 0 }, - { EFI_VENDOR_GLOBAL, "PK", 1 }, - { EFI_VENDOR_GLOBAL, "KEK", 1 }, - { EFI_VENDOR_DATABASE, "db", 1 }, - { EFI_VENDOR_DATABASE, "dbx", 1 }, - { EFI_VENDOR_DATABASE, "dbt", -1 }, - { EFI_VENDOR_DATABASE, "dbr", -1 }, - }; + if (!event_log_pcr_checks_out(el, el->registers + pcr)) { + log_notice("PCR %" PRIu32 " (%s) value does not match event log. Removing from set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr))); + goto drop; + } - _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; - int r; + if (!el->registers[pcr].fully_recognized) { + log_notice("PCR %" PRIu32 " (%s) event log contains unrecognized measurements. Removing from set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr))); + goto drop; + } - /* Generates expected records from the current SecureBoot state, as readable in the EFI variables - * right now. */ + if (BIT_SET(el->missing_component_pcrs, pcr)) { + log_notice("PCR %" PRIu32 " (%s) is touched by component we can't find in event log. Removing from set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr))); + goto drop; + } - FOREACH_ELEMENT(vv, variables) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL; + log_info("PCR %" PRIu32 " (%s) matches event log and fully consists of recognized measurements. Including in set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr))); - _cleanup_free_ char *name = NULL; - if (asprintf(&name, "%s-" SD_ID128_UUID_FORMAT_STR, vv->name, SD_ID128_FORMAT_VAL(vv->id)) < 0) + if (strextendf_with_separator(&kept, ", ", "%" PRIu32 " (%s)", pcr, tpm2_pcr_index_to_string(pcr)) < 0) return log_oom(); - _cleanup_free_ void *data = NULL; - size_t data_size; - r = efi_get_variable(name, NULL, &data, &data_size); - if (r < 0) { - if (r != -ENOENT || vv->synthesize_empty == 0) - return log_error_errno(r, "Failed to read EFI variable '%s': %m", name); - if (vv->synthesize_empty < 0) - continue; - - /* If the main database variables are not set we don't consider this an error, but - * measure an empty database instead. */ - log_debug("EFI variable %s is not set, synthesizing empty variable for measurement.", name); - data_size = 0; - } + continue; - _cleanup_free_ char16_t* name16 = utf8_to_utf16(vv->name, SIZE_MAX); - if (!name16) - return log_oom(); - size_t name16_bytes = char16_strlen(name16) * 2; + drop: + *pcrs &= ~(UINT32_C(1) << pcr); - size_t vdata_size = offsetof(UEFI_VARIABLE_DATA, unicodeName) + name16_bytes + data_size; - _cleanup_free_ UEFI_VARIABLE_DATA *vdata = malloc(vdata_size); - if (!vdata) + if (strextendf_with_separator(&dropped, ", ", "%" PRIu32 " (%s)", pcr, tpm2_pcr_index_to_string(pcr)) < 0) return log_oom(); + } - *vdata = (UEFI_VARIABLE_DATA) { - .unicodeNameLength = name16_bytes / 2, - .variableDataLength = data_size, - }; - - efi_id128_to_guid(vv->id, vdata->variableName); - memcpy(mempcpy(vdata->unicodeName, name16, name16_bytes), data, data_size); - - r = make_pcrlock_record(TPM2_PCR_SECURE_BOOT_POLICY /* =7 */, vdata, vdata_size, &record); - if (r < 0) - return r; + if (dropped) + log_notice("PCRs dropped from protection mask: %s", dropped); + else + log_debug("No PCRs dropped from protection mask."); - r = sd_json_variant_append_array(&array, record); - if (r < 0) - return log_error_errno(r, "Failed to append to JSON array: %m"); - } + if (kept) + log_notice("PCRs in protection mask: %s", kept); + else + log_notice("No PCRs kept in protection mask."); - return write_pcrlock(array, PCRLOCK_SECUREBOOT_POLICY_PATH); + return 0; } -static int verb_unlock_secureboot_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { - return unlink_pcrlock(PCRLOCK_SECUREBOOT_POLICY_PATH); -} +static int pcr_prediction_add_result( + Tpm2PCRPrediction *context, + Tpm2PCRPredictionResult *result, + uint32_t pcr, + const char *path) { -static int event_log_record_is_secureboot_variable(EventLogRecord *rec, sd_id128_t uuid, const char *name) { - _cleanup_free_ char *found_name = NULL; - sd_id128_t found_uuid; + _cleanup_free_ Tpm2PCRPredictionResult *copy = NULL; int r; - assert(rec); - assert(name); - - if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) - return false; - - if (rec->pcr != TPM2_PCR_SECURE_BOOT_POLICY) - return false; - - if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) - return false; + assert(context); + assert(result); - if (rec->firmware_event_type != EV_EFI_VARIABLE_DRIVER_CONFIG) - return false; + copy = newdup(Tpm2PCRPredictionResult, result, 1); + if (!copy) + return log_oom(); - r = event_log_record_parse_variable_data(rec, &found_uuid, &found_name); - if (r == -EBADMSG) - return false; + r = ordered_set_ensure_put(context->results + pcr, &tpm2_pcr_prediction_result_hash_ops, copy); + if (r == -EEXIST) /* Multiple identical results for the same PCR are totally expected */ + return 0; if (r < 0) - return r; + return log_error_errno(r, "Failed to insert result into set: %m"); - if (!sd_id128_equal(found_uuid, uuid)) - return false; + log_debug("Added prediction result %u for PCR %" PRIu32 " (path: %s)", ordered_set_size(context->results[pcr]), pcr, strempty(path)); - return streq(found_name, name); + TAKE_PTR(copy); + return 0; } -static bool event_log_record_is_secureboot_authority(EventLogRecord *rec) { - assert(rec); - - if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) - return false; - - if (rec->pcr != TPM2_PCR_SECURE_BOOT_POLICY) - return false; +static const EVP_MD* evp_from_tpm2_alg(uint16_t alg) { + const char *name; - if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) - return false; + name = tpm2_hash_alg_to_string(alg); + if (!name) + return NULL; - return rec->firmware_event_type == EV_EFI_VARIABLE_AUTHORITY; + return EVP_get_digestbyname(name); } -static int event_log_ensure_secureboot_consistency(EventLog *el) { - static const struct { - sd_id128_t id; - const char *name; - bool required; - } table[] = { - { EFI_VENDOR_GLOBAL, "SecureBoot", true }, - { EFI_VENDOR_GLOBAL, "PK", true }, - { EFI_VENDOR_GLOBAL, "KEK", true }, - { EFI_VENDOR_DATABASE, "db", true }, - { EFI_VENDOR_DATABASE, "dbx", true }, - { EFI_VENDOR_DATABASE, "dbt", false }, - { EFI_VENDOR_DATABASE, "dbr", false }, - // FIXME: ensure we also find the separator here - }; - - EventLogRecord *records[ELEMENTSOF(table)] = {}; - EventLogRecord *first_authority = NULL; - - assert(el); +static int event_log_component_variant_calculate( + Tpm2PCRPredictionResult *result, + EventLogComponentVariant *variant, + uint32_t pcr) { - /* Ensures that the PCR 7 records are complete and in order. Before we lock down PCR 7 we want to - * ensure its state is actually consistent. */ + assert(result); + assert(variant); - FOREACH_ARRAY(rr, el->records, el->n_records) { + FOREACH_ARRAY(rr, variant->records, variant->n_records) { EventLogRecord *rec = *rr; - size_t found = SIZE_MAX; - - if (event_log_record_is_secureboot_authority(rec)) { - if (first_authority) - continue; - first_authority = rec; - // FIXME: also check that each authority record's data is also listed in 'db' + if (!EVENT_LOG_RECORD_IS_PCR(rec)) continue; - } - for (size_t i = 0; i < ELEMENTSOF(table); i++) - if (event_log_record_is_secureboot_variable(rec, table[i].id, table[i].name)) { - found = i; - break; - } - if (found == SIZE_MAX) + if (rec->pcr != pcr) continue; - /* Require the authority records always come *after* database measurements */ - if (first_authority) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "SecureBoot authority before variable, refusing."); + for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *md_ctx = NULL; + EventLogRecordBank *b; - /* Check for duplicates */ - if (records[found]) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate '%s' record, refusing.", rec->description); + if (result->hash[i].size <= 0) /* already invalidated */ + continue; - /* Check for order */ - for (size_t j = found + 1; j < ELEMENTSOF(table); j++) - if (records[j]) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'%s' record before '%s' record, refusing.", records[j]->description, rec->description); + b = event_log_record_find_bank(rec, tpm2_hash_algorithms[i]); + if (!b) { + /* Can't calculate, hence invalidate */ + result->hash[i] = (TPM2B_DIGEST) {}; + continue; + } - records[found] = rec; - } + md_ctx = EVP_MD_CTX_new(); + if (!md_ctx) + return log_oom(); - /* Check for existence */ - for (size_t i = 0; i < ELEMENTSOF(table); i++) - if (table[i].required && !records[i]) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Required record '%s' not found, refusing.", table[i].name); - - /* At this point we know that all required variables have been measured, in the right order. */ - return 0; -} - -static int verb_lock_secureboot_authority(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; - _cleanup_(event_log_freep) EventLog *el = NULL; - int r; + const EVP_MD *md = ASSERT_PTR(evp_from_tpm2_alg(tpm2_hash_algorithms[i])); - /* Lock down the EV_EFI_VARIABLE_AUTHORITY records from the existing log. Note that there's not too - * much value in locking this down too much, since it stores only the result of the primary database - * checks, and that's what we should bind policy to. Moreover it's hard to predict, since extension - * card firmware validation will result in additional records here. */ + int sz = EVP_MD_size(md); + assert(sz > 0); + assert((size_t) sz <= sizeof_field(TPM2B_DIGEST, buffer)); - if (!is_efi_secure_boot()) { - log_info("SecureBoot disabled, not generating authority .pcrlock file."); - return unlink_pcrlock(PCRLOCK_SECUREBOOT_AUTHORITY_PATH); - } + assert(sz == tpm2_hash_alg_to_size(tpm2_hash_algorithms[i])); - el = event_log_new(); - if (!el) - return log_oom(); + assert(result->hash[i].size == (size_t) sz); + assert(b->hash.size == (size_t) sz); - r = event_log_add_algorithms_from_environment(el); - if (r < 0) - return r; + if (EVP_DigestInit_ex(md_ctx, md, NULL) != 1) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to initialize message digest."); - r = event_log_load(el); - if (r < 0) - return r; + if (EVP_DigestUpdate(md_ctx, result->hash[i].buffer, sz) != 1) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash bank value."); - r = event_log_read_pcrs(el); - if (r < 0) - return r; + if (EVP_DigestUpdate(md_ctx, b->hash.buffer, sz) != 1) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash data value."); - r = event_log_calculate_pcrs(el); - if (r < 0) - return r; + unsigned l = (unsigned) sz; + if (EVP_DigestFinal_ex(md_ctx, result->hash[i].buffer, &l) != 1) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize message digest."); - /* Before we base anything on the event log records, let's check that the event log state checks - * out. */ + assert(l == (unsigned) sz); + } + } - r = event_log_pcr_mask_checks_out(el, UINT32_C(1) << TPM2_PCR_SECURE_BOOT_POLICY); - if (r < 0) - return r; + return 0; +} - r = event_log_validate_record_hashes(el); - if (r < 0) - return r; +static int event_log_predict_pcrs( + EventLog *el, + Tpm2PCRPrediction *context, + Tpm2PCRPredictionResult *parent_result, + size_t component_index, + uint32_t pcr, + const char *path) { - r = event_log_ensure_secureboot_consistency(el); - if (r < 0) - return r; + EventLogComponent *component; + int count = 0, r; - FOREACH_ARRAY(rr, el->records, el->n_records) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL; - EventLogRecord *rec = *rr; + assert(el); + assert(context); + assert(parent_result); - if (!event_log_record_is_secureboot_authority(rec)) - continue; + /* Check if we reached the end of the components, generate a result, and backtrack */ + if (component_index >= el->n_components || + (arg_location_end && strcmp(el->components[component_index]->id, arg_location_end) > 0)) { + r = pcr_prediction_add_result(context, parent_result, pcr, path); + if (r < 0) + return r; - log_debug("Locking down authority '%s'.", strna(rec->description)); + return 1; + } - LIST_FOREACH(banks, bank, rec->banks) { - r = sd_json_variant_append_arraybo( - &digests, - SD_JSON_BUILD_PAIR_STRING("hashAlg", tpm2_hash_alg_to_string(bank->algorithm)), - SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(bank->hash.buffer, bank->hash.size))); - if (r < 0) - return log_error_errno(r, "Failed to build digests array: %m"); - } + component = ASSERT_PTR(el->components[component_index]); - r = sd_json_variant_append_arraybo( - &array, - SD_JSON_BUILD_PAIR_UNSIGNED("pcr", rec->pcr), - SD_JSON_BUILD_PAIR_VARIANT("digests", digests)); + /* Check if we are just about to process a component after start, if so record a result and continue. */ + if (arg_location_start && strcmp(component->id, arg_location_start) > 0) { + r = pcr_prediction_add_result(context, parent_result, pcr, path); if (r < 0) - return log_error_errno(r, "Failed to build record array: %m"); + return r; } - return write_pcrlock(array, PCRLOCK_SECUREBOOT_AUTHORITY_PATH); -} - -static int verb_unlock_secureboot_authority(int argc, char *argv[], uintptr_t _data, void *userdata) { - return unlink_pcrlock(PCRLOCK_SECUREBOOT_AUTHORITY_PATH); -} - -static int verb_lock_gpt(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL, *record = NULL; - _cleanup_(sd_device_unrefp) sd_device *d = NULL; - uint8_t h[2 * 4096]; /* space for at least two 4K sectors. GPT header should definitely be in here */ - uint64_t start, n_members, member_size; - _cleanup_close_ int fd = -EBADF; - const GptHeader *p; - size_t found = 0; - ssize_t n; - int r; + FOREACH_ARRAY(ii, component->variants, component->n_variants) { + _cleanup_free_ Tpm2PCRPredictionResult *result = NULL; + EventLogComponentVariant *variant = *ii; + _cleanup_free_ char *subpath = NULL; - r = block_device_new_from_path( - argc >= 2 ? argv[1] : "/", - BLOCK_DEVICE_LOOKUP_WHOLE_DISK|BLOCK_DEVICE_LOOKUP_BACKING|BLOCK_DEVICE_LOOKUP_ORIGINATING, - &d); - if (r < 0) - return log_error_errno(r, "Failed to determine root block device: %m"); + /* Operate on a copy of the result */ - fd = sd_device_open(d, O_CLOEXEC|O_RDONLY|O_NOCTTY); - if (fd < 0) - return log_error_errno(fd, "Failed to open root block device: %m"); + if (path) + subpath = strjoin(path, ":", component->id); + else + subpath = strdup(component->id); + if (!subpath) + return log_oom(); - n = pread(fd, &h, sizeof(h), 0); - if (n < 0) - return log_error_errno(errno, "Failed to read GPT header of block device: %m"); - if ((size_t) n != sizeof(h)) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read trying to read GPT header."); + if (!streq(component->id, variant->id)) + if (!strextend(&subpath, "@", variant->id)) + return log_oom(); - /* Try a couple of sector sizes */ - for (size_t sz = 512; sz <= 4096; sz <<= 1) { - assert(sizeof(h) >= sz * 2); - p = (const GptHeader*) (h + sz); /* 2nd sector */ + result = newdup(Tpm2PCRPredictionResult, parent_result, 1); + if (!result) + return log_oom(); - if (!gpt_header_has_signature(p)) - continue; + r = event_log_component_variant_calculate( + result, + variant, + pcr); + if (r < 0) + return r; - if (found != 0) - return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), - "Disk has partition table for multiple sector sizes, refusing."); + r = event_log_predict_pcrs( + el, + context, + result, + component_index + 1, /* Next component */ + pcr, + subpath); + if (r < 0) + return r; - found = sz; + count += r; } - if (found == 0) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), - "Disk does not have GPT partition table, refusing."); + return count; +} - p = (const GptHeader*) (h + found); +static ssize_t event_log_calculate_component_combinations(EventLog *el) { + ssize_t count = 1; + assert(el); - if (le32toh(p->header_size) > found) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), - "GPT header size over long (%" PRIu32 "), refusing.", le32toh(p->header_size)); + FOREACH_ARRAY(cc, el->components, el->n_components) { + EventLogComponent *c = *cc; - start = le64toh(p->partition_entry_lba); - if (start > UINT64_MAX / found) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), - "Partition table start offset overflow, refusing."); + assert(c->n_variants > 0); - member_size = le32toh(p->size_of_partition_entry); - if (member_size < sizeof(GptPartitionEntry)) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), - "Partition entry size too short, refusing."); + /* Overflow check */ + if (c->n_variants > (size_t) (SSIZE_MAX/count)) + return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Too many component combinations."); + count *= c->n_variants; + } - n_members = le32toh(p->number_of_partition_entries); - uint64_t member_bufsz = n_members * member_size; - if (member_bufsz > 1U*1024U*1024U) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), - "Partition table size too large, refusing."); + return count; +} - member_bufsz = ROUND_UP(member_bufsz, found); +static int event_log_show_predictions(Tpm2PCRPrediction *context, uint16_t alg) { + int r; - _cleanup_free_ void *members = malloc(member_bufsz); - if (!members) - return log_oom(); + assert(context); - n = pread(fd, members, member_bufsz, start * found); - if (n < 0) - return log_error_errno(errno, "Failed to read GPT partition table entries: %m"); - if ((size_t) n != member_bufsz) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading GPT partition table entries."); + pager_open(arg_pager_flags); - size_t vdata_size = le32toh(p->header_size) + sizeof(le64_t) + member_size * n_members; - _cleanup_free_ void *vdata = malloc0(vdata_size); - if (!vdata) - return log_oom(); + if (sd_json_format_enabled(arg_json_format_flags)) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; - void *n_measured_entries = mempcpy(vdata, p, sizeof(GptHeader)); /* n_measured_entries is a 64bit value */ + for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *aj = NULL; - void *qq = (uint8_t*) n_measured_entries + sizeof(le64_t); + r = tpm2_pcr_prediction_to_json( + context, + tpm2_hash_algorithms[i], + &aj); + if (r < 0) + return r; - for (uint64_t i = 0; i < n_members; i++) { - const GptPartitionEntry *entry = (const GptPartitionEntry*) ((const uint8_t*) members + (member_size * i)); + if (sd_json_variant_elements(aj) == 0) + continue; - if (memeqzero(entry->partition_type_guid, sizeof(entry->partition_type_guid))) - continue; + r = sd_json_variant_set_field( + &j, + tpm2_hash_alg_to_string(tpm2_hash_algorithms[i]), + aj); + if (r < 0) + return log_error_errno(r, "Failed to add prediction bank to object: %m"); + } - qq = mempcpy(qq, entry, member_size); - unaligned_write_le64(n_measured_entries, unaligned_read_le64(n_measured_entries) + 1); + if (!j) { + r = sd_json_variant_new_object(&j, NULL, 0); + if (r < 0) + return log_error_errno(r, "Failed to allocated empty object: %m"); + } + + sd_json_variant_dump(j, arg_json_format_flags, /* f= */ NULL, /* prefix= */ NULL); + return 0; } - vdata_size = (uint8_t*) qq - (uint8_t*) vdata; + for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) { + Tpm2PCRPredictionResult *result; + if (!BIT_SET(context->pcrs, pcr)) + continue; - r = make_pcrlock_record(TPM2_PCR_BOOT_LOADER_CONFIG /* =5 */, vdata, vdata_size, &record); - if (r < 0) - return r; + if (ordered_set_isempty(context->results[pcr])) { + printf("No results for PCR %u (%s).\n", pcr, tpm2_pcr_index_to_string(pcr)); + continue; + } - r = sd_json_variant_new_array(&array, &record, 1); - if (r < 0) - return log_error_errno(r, "Failed to append to JSON array: %m"); + printf("%sResults for PCR %u (%s):%s\n", ansi_underline(), pcr, tpm2_pcr_index_to_string(pcr), ansi_normal()); - return write_pcrlock(array, PCRLOCK_GPT_PATH); -} + ORDERED_SET_FOREACH(result, context->results[pcr]) { -static int verb_unlock_gpt(int argc, char *argv[], uintptr_t _data, void *userdata) { - return unlink_pcrlock(PCRLOCK_GPT_PATH); -} + _cleanup_free_ char *aa = NULL, *h = NULL; + const char *a; -static bool event_log_record_is_separator(const EventLogRecord *rec) { - assert(rec); + TPM2B_DIGEST *hash = tpm2_pcr_prediction_result_get_hash(result, alg); + if (!hash) + continue; - /* Recognizes EV_SEPARATOR events */ + a = ASSERT_PTR(tpm2_hash_alg_to_string(alg)); + aa = strdup(a); + if (!aa) + return log_oom(); - if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) - return false; + ascii_strlower(aa); - if (rec->firmware_event_type != EV_SEPARATOR) - return false; + h = hexmem(hash->buffer, hash->size); + if (!h) + return log_oom(); - return rec->event_payload_valid == EVENT_PAYLOAD_VALID_YES; /* Insist the record is consistent */ + printf(" %s%-6s:%s %s\n", ansi_grey(), aa, ansi_normal(), h); + } + } + + return 0; } -static int event_log_record_is_action_calling_efi_app(const EventLogRecord *rec) { - _cleanup_free_ char *d = NULL; - int r; +static int tpm2_pcr_prediction_run( + EventLog *el, + Tpm2PCRPrediction *context) { - assert(rec); + int r; - /* Recognizes the special EV_EFI_ACTION that is issues when the firmware passes control to the boot loader. */ + assert(el); + assert(context); - if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) - return false; + for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) { + _cleanup_free_ Tpm2PCRPredictionResult *result = NULL; - if (rec->pcr != TPM2_PCR_BOOT_LOADER_CODE) - return false; + if (!BIT_SET(context->pcrs, pcr)) + continue; - if (rec->firmware_event_type != EV_EFI_ACTION) - return false; + result = new0(Tpm2PCRPredictionResult, 1); + if (!result) + return log_oom(); - if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) /* Insist the record is consistent */ - return false; + for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) + event_log_initial_pcr_state(el, pcr, tpm2_hash_alg_to_size(tpm2_hash_algorithms[i]), result->hash + i); - r = make_cstring(rec->firmware_payload, rec->firmware_payload_size, MAKE_CSTRING_ALLOW_TRAILING_NUL, &d); - if (r < 0) - return r; + r = event_log_predict_pcrs( + el, + context, + result, + /* component_index= */ 0, + pcr, + /* path= */ NULL); + if (r < 0) + return r; + } - return streq(d, "Calling EFI Application from Boot Option"); + return 0; } -static void enable_json_sse(void) { - /* We shall write this to a single output stream? We have to output two files, hence try to be smart - * and enable JSON SSE */ - - if (!arg_pcrlock_path && arg_pcrlock_auto) - return; +static int verb_predict(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(tpm2_pcr_prediction_done) Tpm2PCRPrediction context = { + arg_pcr_mask != 0 ? arg_pcr_mask : DEFAULT_PCR_MASK, + }; + _cleanup_(event_log_freep) EventLog *el = NULL; + ssize_t count; + int r; - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_SSE)) - return; + r = event_log_load_and_process(&el); + if (r < 0) + return r; - log_notice("Enabling JSON_SEQ mode, since writing two .pcrlock files to single output."); - arg_json_format_flags |= SD_JSON_FORMAT_SSE; -} + count = event_log_calculate_component_combinations(el); + if (count < 0) + return count; -static int verb_lock_firmware(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *array_early = NULL, *array_late = NULL; - _cleanup_(event_log_freep) EventLog *el = NULL; - uint32_t always_mask, separator_mask, separator_seen_mask = 0, action_seen_mask = 0; - const char *default_pcrlock_early_path, *default_pcrlock_late_path; - int r; + log_info("%zi combinations of components.", count); - enable_json_sse(); + r = event_log_reduce_to_safe_pcrs(el, &context.pcrs); + if (r < 0) + return r; - /* The PCRs we intend to cover. Note that we measure firmware, external *and* boot loader code/config - * here – but the latter only until the "separator" events are seen, which tell us where transition - * into OS boot loader happens. This reflects the fact that on some systems the firmware already - * measures some firmware-supplied apps into PCR 4. (e.g. Thinkpad X1 Gen9) */ - if (endswith(argv[0], "firmware-code")) { - always_mask = (UINT32_C(1) << TPM2_PCR_PLATFORM_CODE) | /* → 0 */ - (UINT32_C(1) << TPM2_PCR_EXTERNAL_CODE); /* → 2 */ + r = tpm2_pcr_prediction_run(el, &context); + if (r < 0) + return r; - separator_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CODE; /* → 4 */ + return event_log_show_predictions(&context, el->primary_algorithm); +} - default_pcrlock_early_path = PCRLOCK_FIRMWARE_CODE_EARLY_PATH; - default_pcrlock_late_path = PCRLOCK_FIRMWARE_CODE_LATE_PATH; - } else { - assert(endswith(argv[0], "firmware-config")); - always_mask = (UINT32_C(1) << TPM2_PCR_PLATFORM_CONFIG) | /* → 1 */ - (UINT32_C(1) << TPM2_PCR_EXTERNAL_CONFIG); /* → 3 */ +static int remove_policy_file(const char *path) { + assert(path); - separator_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CONFIG; /* → 5 */ + if (unlink(path) < 0) { + if (errno == ENOENT) + return 0; - default_pcrlock_early_path = PCRLOCK_FIRMWARE_CONFIG_EARLY_PATH; - default_pcrlock_late_path = PCRLOCK_FIRMWARE_CONFIG_LATE_PATH; + return log_error_errno(errno, "Failed to remove policy file '%s': %m", path); } - el = event_log_new(); - if (!el) - return log_oom(); + log_info("Removed policy file '%s'.", path); + return 1; +} - r = event_log_add_algorithms_from_environment(el); - if (r < 0) - return r; +static int determine_boot_policy_file(char **ret_path, char **ret_credential_name) { + int r; - r = event_log_load(el); + _cleanup_free_ char *path = NULL; + r = get_global_boot_credentials_path(&path); if (r < 0) return r; + if (r == 0) { + if (ret_path) + *ret_path = NULL; + if (ret_credential_name) + *ret_credential_name = NULL; + return 0; /* not found! */ + } - r = event_log_read_pcrs(el); + sd_id128_t machine_id; + r = sd_id128_get_machine(&machine_id); if (r < 0) - return r; + return log_error_errno(r, "Failed to read machine ID: %m"); - r = event_log_calculate_pcrs(el); + r = boot_entry_token_ensure( + /* root= */ NULL, + /* conf_root= */ NULL, + machine_id, + /* machine_id_is_random= */ false, + &arg_entry_token_type, + &arg_entry_token); if (r < 0) return r; - r = event_log_validate_record_hashes(el); - if (r < 0) - return r; + _cleanup_free_ char *fn = strjoin("pcrlock.", arg_entry_token, ".cred"); + if (!fn) + return log_oom(); - /* Before we base anything on the event log records for any of the selected PCRs, let's check that - * the event log state checks out for them. */ + if (!filename_is_valid(fn)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name '%s' would not be a valid file name, refusing.", fn); - r = event_log_pcr_mask_checks_out(el, always_mask|separator_mask); - if (r < 0) - return r; + _cleanup_free_ char *joined = NULL; + if (ret_path) { + joined = path_join(path, fn); + if (!joined) + return log_oom(); + } - // FIXME: before doing this, validate ahead-of-time that EV_SEPARATOR records exist for all entries, - // and exactly once + _cleanup_free_ char *cn = NULL; + if (ret_credential_name) { + /* The .cred suffix of the file is stripped when PID 1 imports the credential, hence exclude it from + * the embedded credential name. */ + cn = strjoin("pcrlock.", arg_entry_token); + if (!cn) + return log_oom(); - FOREACH_ARRAY(rr, el->records, el->n_records) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL; - EventLogRecord *rec = *rr; - uint32_t bit = UINT32_C(1) << rec->pcr; + ascii_strlower(cn); /* lowercase this file, no matter what, since stored on VFAT, and we don't want + * to run into case change incompatibilities */ + } - if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) - continue; + if (ret_path) + *ret_path = TAKE_PTR(joined); - if (!FLAGS_SET(always_mask, bit) && - !(FLAGS_SET(separator_mask, bit) && !FLAGS_SET(separator_seen_mask|action_seen_mask, bit))) - continue; + if (ret_credential_name) + *ret_credential_name = TAKE_PTR(cn); - /* If we hit the separator record, we stop processing the PCRs listed in `separator_mask` */ - if (event_log_record_is_separator(rec)) { - separator_seen_mask |= bit; - continue; - } + return 1; /* found! */ +} - /* If we hit the special "Calling EFI Application from Boot Option" action we treat this the - * same as a separator here, as that's where firmware passes control to boot loader. Note - * that some EFI implementations forget to generate one of them. */ - r = event_log_record_is_action_calling_efi_app(rec); - if (r < 0) - return log_error_errno(r, "Failed to check if event is 'Calling EFI Application from Boot Option' action: %m"); - if (r > 0) { - action_seen_mask |= bit; - continue; - } - - LIST_FOREACH(banks, bank, rec->banks) { - r = sd_json_variant_append_arraybo( - &digests, - SD_JSON_BUILD_PAIR_STRING("hashAlg", tpm2_hash_alg_to_string(bank->algorithm)), - SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(bank->hash.buffer, bank->hash.size))); - if (r < 0) - return log_error_errno(r, "Failed to build digests array: %m"); - } +static int write_boot_policy_file(const char *json_text) { + _cleanup_free_ char *boot_policy_file = NULL, *credential_name = NULL; + int r; - r = sd_json_variant_append_arraybo( - FLAGS_SET(separator_seen_mask, bit) ? &array_late : &array_early, - SD_JSON_BUILD_PAIR_UNSIGNED("pcr", rec->pcr), - SD_JSON_BUILD_PAIR_VARIANT("digests", digests)); - if (r < 0) - return log_error_errno(r, "Failed to build record array: %m"); - } + assert(json_text); - r = write_pcrlock(array_early, default_pcrlock_early_path); + r = determine_boot_policy_file(&boot_policy_file, &credential_name); if (r < 0) return r; - - return write_pcrlock(array_late, default_pcrlock_late_path); -} - -static int verb_unlock_firmware(int argc, char *argv[], uintptr_t _data, void *userdata) { - const char *default_pcrlock_early_path, *default_pcrlock_late_path; - int r; - - if (endswith(argv[0], "firmware-code")) { - default_pcrlock_early_path = PCRLOCK_FIRMWARE_CODE_EARLY_PATH; - default_pcrlock_late_path = PCRLOCK_FIRMWARE_CODE_LATE_PATH; - } else { - default_pcrlock_early_path = PCRLOCK_FIRMWARE_CONFIG_EARLY_PATH; - default_pcrlock_late_path = PCRLOCK_FIRMWARE_CONFIG_LATE_PATH; + if (r == 0) { + log_info("Did not find XBOOTLDR/ESP partition, not writing boot policy file."); + return 0; } - r = unlink_pcrlock(default_pcrlock_early_path); + _cleanup_(iovec_done) struct iovec encoded = {}; + r = encrypt_credential_and_warn( + CRED_AES256_GCM_BY_NULL, + credential_name, + now(CLOCK_REALTIME), + /* not_after= */ USEC_INFINITY, + /* tpm2_device= */ NULL, + /* tpm2_hash_pcr_mask= */ 0, + /* tpm2_pubkey_path= */ NULL, + /* tpm2_pubkey_pcr_mask= */ 0, + UID_INVALID, + &IOVEC_MAKE_STRING(json_text), + CREDENTIAL_ALLOW_NULL, + &encoded); if (r < 0) - return r; - - if (arg_pcrlock_path) /* if the path is specified don't delete the same thing twice */ - return 0; + return log_error_errno(r, "Failed to encode policy as credential: %m"); - r = unlink_pcrlock(default_pcrlock_late_path); + r = write_base64_file_at( + AT_FDCWD, + boot_policy_file, + &encoded, + WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_MKDIR_0755|WRITE_STRING_FILE_LABEL); if (r < 0) - return r; + return log_error_errno(r, "Failed to write boot policy file to '%s': %m", boot_policy_file); - return 0; + log_info("Written new boot policy to '%s'.", boot_policy_file); + return 1; } -static int verb_lock_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL; - _cleanup_free_ char *word = NULL; +static int make_policy(bool force, RecoveryPinMode recovery_pin_mode) { int r; - r = pcrextend_machine_id_word(&word); + /* Here's how this all works: after predicting all possible PCR values for next boot (with + * alternatives) we'll calculate a policy from it as a combination of PolicyPCR + PolicyOR + * expressions. This is then stored in an NV index. When a component of the boot process is changed a + * new prediction is made and the NV index updated (which automatically invalidates any older + * policies). + * + * Whenever we want to lock an encrypted object (for example FDE) against this policy, we'll use a + * PolicyAuthorizeNV expression that pins the NV index in the policy, and permits access to any + * policies matching the current NV index contents. + * + * We grant world-readable read access to the NV index. Write access is controlled by a PIN (which we + * either generate locally or which the user can provide us with) which can also be used for + * recovery. This PIN is sealed to the TPM and is locked via PolicyAuthorizeNV to the NV index it + * protects (i.e. we dogfood 🌭 🐶 hard here). This means in order to update such a policy we need + * the policy to pass. + * + * Information about the used NV Index, the SRK of the TPM, the sealed PIN and the current PCR + * prediction data are stored in a JSON file in /var/lib/. In order to be able to unlock root disks + * this data must be also copied to the ESP so that it is available to the initrd. The data is not + * sensitive, as SRK and NV index are pinned by it, and the prediction data must match the NV index + * to be useful. */ + + usec_t start_usec = now(CLOCK_MONOTONIC); + + _cleanup_(event_log_freep) EventLog *el = NULL; + r = event_log_load_and_process(&el); if (r < 0) return r; - r = make_pcrlock_record(TPM2_PCR_SYSTEM_IDENTITY /* = 15 */, word, SIZE_MAX, &record); + _cleanup_(tpm2_pcr_prediction_done) Tpm2PCRPrediction new_prediction = { + arg_pcr_mask != 0 ? arg_pcr_mask : DEFAULT_PCR_MASK, + }; + r = event_log_reduce_to_safe_pcrs(el, &new_prediction.pcrs); if (r < 0) return r; - r = sd_json_variant_new_array(&array, &record, 1); - if (r < 0) - return log_error_errno(r, "Failed to create record array: %m"); + if (!force && new_prediction.pcrs == 0) + log_notice("Set of PCRs to use for policy is empty. Generated policy will not provide any protection in its current form. Proceeding."); - return write_pcrlock(array, PCRLOCK_MACHINE_ID_PATH); -} + usec_t predict_start_usec = now(CLOCK_MONOTONIC); -static int verb_unlock_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) { - return unlink_pcrlock(PCRLOCK_MACHINE_ID_PATH); -} + r = tpm2_pcr_prediction_run(el, &new_prediction); + if (r < 0) + return r; -static int pcrlock_file_system_path(const char *normalized_path, char **ret) { - _cleanup_free_ char *s = NULL; + log_info("Predicted future PCRs in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), predict_start_usec), 1)); - assert(normalized_path); - assert(ret); + _cleanup_(sd_json_variant_unrefp) sd_json_variant *new_prediction_json = NULL; + r = tpm2_pcr_prediction_to_json(&new_prediction, el->primary_algorithm, &new_prediction_json); + if (r < 0) + return r; - if (path_equal(normalized_path, "/")) - s = strdup(PCRLOCK_ROOT_FILE_SYSTEM_PATH); - else { - /* We reuse the escaping we use for turning paths into unit names */ - _cleanup_free_ char *escaped = NULL; + if (DEBUG_LOGGING) + (void) sd_json_variant_dump(new_prediction_json, SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO, stderr, NULL); - assert(normalized_path[0] == '/'); - assert(normalized_path[1] != '/'); + /* v257 and older mistakenly used --pcrlock= for the path. To keep backward compatibility, let's fallback to it when + * --policy= is unspecified but --pcrlock is specified. */ + if (!arg_policy_path && arg_pcrlock_path) { + log_notice("Specified --pcrlock= option for make-policy command. Please use --policy= instead."); - escaped = unit_name_escape(normalized_path + 1); - if (!escaped) + arg_policy_path = strdup(arg_pcrlock_path); + if (!arg_policy_path) return log_oom(); - - s = strjoin(PCRLOCK_FILE_SYSTEM_PATH_PREFIX, escaped, ".pcrlock"); } - if (!s) - return log_oom(); - *ret = TAKE_PTR(s); - return 0; -} - -static int verb_lock_file_system(int argc, char *argv[], uintptr_t _data, void *userdata) { - const char* paths[3] = {}; - int r; - - if (argc > 1) - paths[0] = argv[1]; - else { - dev_t a, b; - paths[0] = "/"; + _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy old_policy = {}; + r = tpm2_pcrlock_policy_load(arg_policy_path, &old_policy); + if (r < 0) + return r; - r = get_block_device("/", &a); - if (r < 0) - return log_error_errno(r, "Failed to get device of root file system: %m"); + bool have_old_policy = r > 0; - r = get_block_device("/var", &b); - if (r < 0) - return log_error_errno(r, "Failed to get device of /var/ file system: %m"); + /* When we update the policy the old serializations for NV, SRK, PIN remain the same */ + _cleanup_(iovec_done) struct iovec + nv_blob = TAKE_STRUCT(old_policy.nv_handle), + nv_public_blob = TAKE_STRUCT(old_policy.nv_public), + srk_blob = TAKE_STRUCT(old_policy.srk_handle), + pin_public = TAKE_STRUCT(old_policy.pin_public), + pin_private = TAKE_STRUCT(old_policy.pin_private); - /* if backing device is distinct, then measure /var/ too */ - if (a != b) - paths[1] = "/var"; + if (have_old_policy) { + if (arg_nv_index != 0 && old_policy.nv_index != arg_nv_index) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Stored policy references different NV index (0x%x) than specified (0x%x), refusing.", old_policy.nv_index, arg_nv_index); - enable_json_sse(); + if (!force && + old_policy.algorithm == el->primary_algorithm && + tpm2_pcr_prediction_equal(&old_policy.prediction, &new_prediction, el->primary_algorithm)) { + log_info("Prediction is identical to current policy, skipping update."); + return 0; /* NOP */ + } } - STRV_FOREACH(p, paths) { - _cleanup_free_ char *word = NULL, *normalized_path = NULL, *pcrlock_file = NULL; - _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL; + _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL; + r = tpm2_context_new_or_warn(/* device= */ NULL, &tc); + if (r < 0) + return r; - r = pcrextend_file_system_word(*p, &word, &normalized_path); - if (r < 0) - return r; + if (!tpm2_supports_command(tc, TPM2_CC_PolicyAuthorizeNV)) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 does not support PolicyAuthorizeNV command, refusing."); + if (!tpm2_supports_alg(tc, TPM2_ALG_SHA256)) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 does not support SHA-256 hash algorithm, refusing."); - r = pcrlock_file_system_path(normalized_path, &pcrlock_file); - if (r < 0) - return r; + _cleanup_(tpm2_handle_freep) Tpm2Handle *srk_handle = NULL; - r = make_pcrlock_record(TPM2_PCR_SYSTEM_IDENTITY /* = 15 */, word, SIZE_MAX, &record); + r = tpm2_deserialize( + tc, + &srk_blob, + &srk_handle); + if (r < 0) + return log_error_errno(r, "Failed to deserialize SRK TR: %m"); + if (r == 0) { + r = tpm2_get_or_create_srk( + tc, + /* session= */ NULL, + /* ret_public= */ NULL, + /* ret_name= */ NULL, + /* ret_qname= */ NULL, + &srk_handle); if (r < 0) - return r; + return log_error_errno(r, "Failed to install SRK: %m"); + } - r = sd_json_variant_new_array(&array, &record, 1); - if (r < 0) - return log_error_errno(r, "Failed to create record array: %m"); + _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL; + r = tpm2_make_encryption_session( + tc, + srk_handle, + /* bind_key= */ &TPM2_HANDLE_NONE, + &encryption_session); + if (r < 0) + return log_error_errno(r, "Failed to allocate encryption session: %m"); - r = write_pcrlock(array, pcrlock_file); + /* Acquire a recovery PIN, either from the user, or create a randomized one */ + _cleanup_(erase_and_freep) char *pin = NULL; + if (recovery_pin_mode == RECOVERY_PIN_QUERY) { + r = getenv_steal_erase("PIN", &pin); if (r < 0) - return r; - } - - return 0; -} + return log_error_errno(r, "Failed to acquire PIN from environment: %m"); + if (r == 0) { + _cleanup_strv_free_erase_ char **l = NULL; -static int verb_unlock_file_system(int argc, char *argv[], uintptr_t _data, void *userdata) { - const char* paths[3] = {}; - int r; + AskPasswordRequest req = { + .tty_fd = -EBADF, + .message = "Recovery PIN", + .id = "pcrlock-recovery-pin", + .credential = "pcrlock.recovery-pin", + .until = USEC_INFINITY, + .hup_fd = -EBADF, + }; - if (argc > 1) - paths[0] = argv[1]; - else { - paths[0] = "/"; - paths[1] = "/var"; - } + r = ask_password_auto( + &req, + /* flags= */ 0, + &l); + if (r < 0) + return log_error_errno(r, "Failed to query for recovery PIN: %m"); - STRV_FOREACH(p, paths) { - _cleanup_free_ char *normalized_path = NULL, *pcrlock_file = NULL; + if (strv_length(l) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single PIN only."); - r = chase(*p, NULL, 0, &normalized_path, NULL); - if (r < 0) - return log_error_errno(r, "Failed to normal path '%s': %m", argv[1]); + pin = TAKE_PTR(l[0]); + l = mfree(l); + } - r = pcrlock_file_system_path(normalized_path, &pcrlock_file); + } else if (!have_old_policy) { + r = make_recovery_key(&pin); if (r < 0) - return r; + return log_error_errno(r, "Failed to generate a randomized recovery PIN: %m"); - r = unlink_pcrlock(pcrlock_file); - if (r < 0) - return r; + if (recovery_pin_mode == RECOVERY_PIN_SHOW) + printf("%s Selected recovery PIN is: %s%s%s\n", + glyph(GLYPH_LOCK_AND_KEY), + ansi_highlight_cyan(), + pin, + ansi_normal()); } - return 0; -} - -static int verb_lock_pe(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; - _cleanup_close_ int fd = -EBADF; - int r; - - // FIXME: Maybe also generate a matching EV_EFI_VARIABLE_AUTHORITY records here for each signature that - // covers this PE plus its hash, as alternatives under the same component name + _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL; + TPM2_HANDLE nv_index = 0; - if (argc >= 2) { - fd = open(argv[1], O_RDONLY|O_CLOEXEC); - if (fd < 0) - return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); - } + r = tpm2_deserialize(tc, &nv_blob, &nv_handle); + if (r < 0) + return log_error_errno(r, "Failed to deserialize NV index TR: %m"); + if (r > 0) + nv_index = old_policy.nv_index; - if (arg_pcr_mask == 0) - arg_pcr_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CODE; + TPM2B_AUTH auth = {}; + CLEANUP_ERASE(auth); - for (uint32_t i = 0; i < TPM2_PCRS_MAX; i++) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL; + if (pin) { + r = tpm2_auth_value_from_pin(TPM2_ALG_SHA256, pin, &auth); + if (r < 0) + return log_error_errno(r, "Failed to hash PIN: %m"); + } else { + assert(iovec_is_set(&pin_public)); + assert(iovec_is_set(&pin_private)); - if (!BIT_SET(arg_pcr_mask, i)) - continue; + log_debug("Retrieving PIN from sealed data."); - FOREACH_ARRAY(pa, tpm2_hash_algorithms, TPM2_N_HASH_ALGORITHMS) { - _cleanup_free_ void *hash = NULL; - size_t hash_size; - const EVP_MD *md; - const char *a; + usec_t pin_start_usec = now(CLOCK_MONOTONIC); - assert_se(a = tpm2_hash_alg_to_string(*pa)); - assert_se(md = EVP_get_digestbyname(a)); + _cleanup_(iovec_done_erase) struct iovec secret = {}; + for (unsigned attempt = 0;; attempt++) { + _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL; - r = pe_hash(fd < 0 ? STDIN_FILENO : fd, md, &hash, &hash_size); + r = tpm2_make_policy_session( + tc, + srk_handle, + encryption_session, + &policy_session); if (r < 0) - return log_error_errno(r, "Failed to hash PE binary: %m"); + return log_error_errno(r, "Failed to allocate policy session: %m"); - r = sd_json_variant_append_arraybo( - &digests, - SD_JSON_BUILD_PAIR_STRING("hashAlg", a), - SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(hash, hash_size))); + r = tpm2_policy_super_pcr( + tc, + policy_session, + &old_policy.prediction, + old_policy.algorithm); if (r < 0) - return log_error_errno(r, "Failed to build JSON digest object: %m"); - } - - r = sd_json_variant_append_arraybo( - &array, - SD_JSON_BUILD_PAIR_UNSIGNED("pcr", i), - SD_JSON_BUILD_PAIR_VARIANT("digests", digests)); - if (r < 0) - return log_error_errno(r, "Failed to append record object: %m"); - } + return r; - return write_pcrlock(array, NULL); -} + r = tpm2_policy_authorize_nv( + tc, + policy_session, + nv_handle, + NULL); + if (r < 0) + return log_error_errno(r, "Failed to submit AuthorizeNV policy: %m"); -typedef void* SectionHashArray[_UNIFIED_SECTION_MAX * TPM2_N_HASH_ALGORITHMS]; + r = tpm2_unseal_data( + tc, + &pin_public, + &pin_private, + srk_handle, + policy_session, + encryption_session, + &secret); + if (r < 0 && (r != -ESTALE || attempt >= 16)) + return log_error_errno(r, "Failed to unseal PIN: %m"); + if (r == 0) + break; -static void section_hashes_array_done(SectionHashArray *array) { - assert(array); + log_debug("Trying again (attempt %u), as PCR values changed during unlock attempt.", attempt+1); + } - for (size_t i = 0; i < _UNIFIED_SECTION_MAX * TPM2_N_HASH_ALGORITHMS; i++) - free((*array)[i]); -} + if (secret.iov_len > sizeof_field(TPM2B_AUTH, buffer)) + return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Decrypted PIN too large."); -static int verb_lock_uki(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL, *pe_digests = NULL; - _cleanup_(section_hashes_array_done) SectionHashArray section_hashes = {}; - size_t hash_sizes[TPM2_N_HASH_ALGORITHMS]; - _cleanup_close_ int fd = -EBADF; - int r; + auth = (TPM2B_AUTH) { + .size = secret.iov_len, + }; - if (arg_pcr_mask != 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "PCR not configurable for UKI lock down."); + memcpy_safe(auth.buffer, secret.iov_base, secret.iov_len); - if (argc >= 2) { - fd = open(argv[1], O_RDONLY|O_CLOEXEC); - if (fd < 0) - return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); + log_info("Retrieved PIN from TPM2 in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pin_start_usec), 1)); } - for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { - _cleanup_free_ void *peh = NULL; - const EVP_MD *md; - const char *a; + /* Now convert the PIN into an HMAC-SHA256 key that we can use in PolicySigned to protect access to the nvindex with */ + _cleanup_(tpm2_handle_freep) Tpm2Handle *pin_handle = NULL; + r = tpm2_hmac_key_from_pin(tc, encryption_session, &auth, &pin_handle); + if (r < 0) + return r; - assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i])); - assert_se(md = EVP_get_digestbyname(a)); + TPM2B_NV_PUBLIC nv_public = {}; + usec_t nv_index_start_usec = now(CLOCK_MONOTONIC); - r = pe_hash(fd < 0 ? STDIN_FILENO : fd, md, &peh, hash_sizes + i); + if (!iovec_is_set(&nv_blob)) { + _cleanup_(Esys_Freep) TPM2B_NAME *pin_name = NULL; + r = tpm2_get_name( + tc, + pin_handle, + &pin_name); if (r < 0) - return log_error_errno(r, "Failed to hash PE binary: %m"); + return log_error_errno(r, "Failed to get name of PIN from TPM2: %m"); - r = sd_json_variant_append_arraybo( - &pe_digests, - SD_JSON_BUILD_PAIR_STRING("hashAlg", a), - SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(peh, hash_sizes[i]))); + TPM2B_DIGEST recovery_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); + r = tpm2_calculate_policy_signed(&recovery_policy_digest, pin_name); if (r < 0) - return log_error_errno(r, "Failed to build JSON digest object: %m"); + return log_error_errno(r, "Failed to calculate PolicySigned policy: %m"); - r = uki_hash(fd < 0 ? STDIN_FILENO : fd, md, section_hashes + (i * _UNIFIED_SECTION_MAX), hash_sizes + i); + log_debug("Allocating NV index to write PCR policy to..."); + r = tpm2_define_policy_nv_index( + tc, + encryption_session, + arg_nv_index, + &recovery_policy_digest, + &nv_index, + &nv_handle, + &nv_public); + if (r == -EEXIST) + return log_error_errno(r, "NV index 0x%" PRIx32 " already allocated.", arg_nv_index); if (r < 0) - return log_error_errno(r, "Failed to UKI hash PE binary: %m"); + return log_error_errno(r, "Failed to allocate NV index: %m"); } - r = sd_json_variant_append_arraybo( - &array, - SD_JSON_BUILD_PAIR_UNSIGNED("pcr", TPM2_PCR_BOOT_LOADER_CODE), - SD_JSON_BUILD_PAIR_VARIANT("digests", pe_digests)); + _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL; + r = tpm2_make_policy_session( + tc, + srk_handle, + encryption_session, + &policy_session); if (r < 0) - return log_error_errno(r, "Failed to append record object: %m"); + return log_error_errno(r, "Failed to allocate policy session: %m"); - for (UnifiedSection section = 0; section < _UNIFIED_SECTION_MAX; section++) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *section_digests = NULL, *record = NULL; + r = tpm2_policy_signed_hmac_sha256( + tc, + policy_session, + pin_handle, + &IOVEC_MAKE(auth.buffer, auth.size), + /* ret_policy_digest= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to submit authentication value policy: %m"); - if (!unified_section_measure(section)) - continue; + log_debug("Calculating new PCR policy to write..."); + TPM2B_DIGEST new_super_pcr_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); - for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { - const char *a; - void *hash; + usec_t pcr_policy_start_usec = now(CLOCK_MONOTONIC); - hash = section_hashes[i * _UNIFIED_SECTION_MAX + section]; - if (!hash) - continue; + r = tpm2_calculate_policy_super_pcr( + &new_prediction, + el->primary_algorithm, + &new_super_pcr_policy_digest); + if (r < 0) + return log_error_errno(r, "Failed to calculate super PCR policy: %m"); - assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i])); + log_info("Calculated new PCR policy in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pcr_policy_start_usec), 1)); - r = sd_json_variant_append_arraybo( - §ion_digests, - SD_JSON_BUILD_PAIR_STRING("hashAlg", a), - SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(hash, hash_sizes[i]))); - if (r < 0) - return log_error_errno(r, "Failed to build JSON digest object: %m"); - } + log_debug("Writing new PCR policy to NV index..."); + r = tpm2_write_policy_nv_index( + tc, + policy_session, + nv_index, + nv_handle, + &new_super_pcr_policy_digest); + if (r < 0) + return log_error_errno(r, "Failed to write to NV index: %m"); - if (!section_digests) - continue; + log_info("Updated NV index in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), nv_index_start_usec), 1)); - /* So we have digests for this section, hence generate a record for the section name first. */ - r = make_pcrlock_record(TPM2_PCR_KERNEL_BOOT /* =11 */, unified_sections[section], strlen(unified_sections[section]) + 1, &record); + assert(iovec_is_set(&pin_public) == iovec_is_set(&pin_private)); + if (!iovec_is_set(&pin_public)) { + TPM2B_DIGEST authnv_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); + + r = tpm2_calculate_policy_authorize_nv(&nv_public, &authnv_policy_digest); if (r < 0) - return r; + return log_error_errno(r, "Failed to calculate AuthorizeNV policy: %m"); - r = sd_json_variant_append_array(&array, record); + struct iovec data = { + .iov_base = auth.buffer, + .iov_len = auth.size, + }; + + usec_t pin_seal_start_usec = now(CLOCK_MONOTONIC); + + log_debug("Sealing PIN to NV index policy..."); + r = tpm2_seal_data( + tc, + &data, + srk_handle, + encryption_session, + &authnv_policy_digest, + &pin_public, + &pin_private); if (r < 0) - return log_error_errno(r, "Failed to append JSON record array: %m"); + return log_error_errno(r, "Failed to seal PIN to NV auth policy: %m"); - /* And then append a record for the section contents digests as well */ - r = sd_json_variant_append_arraybo( - &array, - SD_JSON_BUILD_PAIR_UNSIGNED("pcr", TPM2_PCR_KERNEL_BOOT), - SD_JSON_BUILD_PAIR_VARIANT("digests", section_digests)); + log_info("Sealed PIN in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pin_seal_start_usec), 1)); + } + + if (!iovec_is_set(&nv_blob)) { + r = tpm2_serialize(tc, nv_handle, &nv_blob); if (r < 0) - return log_error_errno(r, "Failed to append record object: %m"); + return log_error_errno(r, "Failed to serialize NV index TR: %m"); } - return write_pcrlock(array, NULL); -} + if (!iovec_is_set(&srk_blob)) { + r = tpm2_serialize(tc, srk_handle, &srk_blob); + if (r < 0) + return log_error_errno(r, "Failed to serialize SRK index TR: %m"); + } -static int verb_lock_kernel_cmdline(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL; - _cleanup_free_ char *cmdline = NULL; - int r; + if (!iovec_is_set(&nv_public_blob)) { + r = tpm2_marshal_nv_public(&nv_public, &nv_public_blob.iov_base, &nv_public_blob.iov_len); + if (r < 0) + return log_error_errno(r, "Failed to marshal NV public area: %m"); + } - if (argc > 1) { - if (empty_or_dash(argv[1])) - r = read_full_stream(stdin, &cmdline, NULL); - else - r = read_full_file(argv[1], &cmdline, NULL); - } else - r = proc_cmdline(&cmdline); + _cleanup_(sd_json_variant_unrefp) sd_json_variant *new_configuration_json = NULL; + r = sd_json_buildo( + &new_configuration_json, + SD_JSON_BUILD_PAIR_STRING("pcrBank", tpm2_hash_alg_to_string(el->primary_algorithm)), + SD_JSON_BUILD_PAIR_VARIANT("pcrValues", new_prediction_json), + SD_JSON_BUILD_PAIR_INTEGER("nvIndex", nv_index), + JSON_BUILD_PAIR_IOVEC_BASE64("nvHandle", &nv_blob), + JSON_BUILD_PAIR_IOVEC_BASE64("nvPublic", &nv_public_blob), + JSON_BUILD_PAIR_IOVEC_BASE64("srkHandle", &srk_blob), + JSON_BUILD_PAIR_IOVEC_BASE64("pinPublic", &pin_public), + JSON_BUILD_PAIR_IOVEC_BASE64("pinPrivate", &pin_private)); if (r < 0) - return log_error_errno(r, "Failed to read cmdline: %m"); - - delete_trailing_chars(cmdline, "\n"); - - _cleanup_free_ char16_t *u = NULL; - u = utf8_to_utf16(cmdline, SIZE_MAX); - if (!u) - return log_oom(); + return log_error_errno(r, "Failed to generate JSON: %m"); - r = make_pcrlock_record(TPM2_PCR_KERNEL_INITRD /* = 9 */, u, char16_strlen(u)*2+2, &record); + _cleanup_free_ char *text = NULL; + r = sd_json_variant_format(new_configuration_json, 0, &text); if (r < 0) - return r; + return log_error_errno(r, "Failed to format new configuration to JSON: %m"); - r = sd_json_variant_new_array(&array, &record, 1); + const char *path = arg_policy_path ?: (in_initrd() ? "/run/systemd/pcrlock.json" : "/var/lib/systemd/pcrlock.json"); + r = write_string_file(path, text, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_MKDIR_0755|WRITE_STRING_FILE_LABEL); if (r < 0) - return log_error_errno(r, "Failed to create record array: %m"); + return log_error_errno(r, "Failed to write new configuration to '%s': %m", path); - r = write_pcrlock(array, PCRLOCK_KERNEL_CMDLINE_PATH); - if (r < 0) - return r; + if (!arg_policy_path && !in_initrd()) { + r = remove_policy_file("/run/systemd/pcrlock.json"); + if (r < 0) + return r; + } - return 0; -} + log_info("Written new policy to '%s' and digest to TPM2 NV index 0x%x.", path, nv_index); -static int verb_unlock_kernel_cmdline(int argc, char *argv[], uintptr_t _data, void *userdata) { - return unlink_pcrlock(PCRLOCK_KERNEL_CMDLINE_PATH); -} + (void) write_boot_policy_file(text); -static int verb_lock_kernel_initrd(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *records = NULL; - _cleanup_fclose_ FILE *f = NULL; - uint32_t pcr_mask = UINT32_C(1) << TPM2_PCR_KERNEL_INITRD; - int r; + log_info("Overall time spent: %s", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), start_usec), 1)); - if (argc >= 2) { - f = fopen(argv[1], "re"); - if (!f) - return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); - } + return 1; /* installed new policy */ +} - r = make_pcrlock_record_from_stream(pcr_mask, f ?: stdin, &records); - if (r < 0) - return r; +static int verb_make_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { + int r; - r = write_pcrlock(records, PCRLOCK_KERNEL_INITRD_PATH); + r = make_policy(arg_force, arg_recovery_pin); if (r < 0) return r; return 0; } -static int verb_unlock_kernel_initrd(int argc, char *argv[], uintptr_t _data, void *userdata) { - return unlink_pcrlock(PCRLOCK_KERNEL_INITRD_PATH); -} - -static int event_log_reduce_to_safe_pcrs(EventLog *el, uint32_t *pcrs) { - _cleanup_free_ char *dropped = NULL, *kept = NULL; - - assert(el); - assert(pcrs); +static int undefine_policy_nv_index( + uint32_t nv_index, + const struct iovec *nv_blob, + const struct iovec *srk_blob) { + int r; - /* When we compile a new PCR policy we don't want to bind to PCRs which are fishy for one of three - * reasons: - * - * 1. The PCR value doesn't match the event log - * 2. The event log for the PCR contains measurements we don't know responsible components for - * 3. The event log for the PCR does not contain measurements for components we know - * - * This function checks for the three conditions and drops the PCR from the mask. - */ + assert(nv_blob); + assert(srk_blob); - for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) { + _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL; + r = tpm2_context_new_or_warn(/* device= */ NULL, &tc); + if (r < 0) + return r; - if (!BIT_SET(*pcrs, pcr)) - continue; + _cleanup_(tpm2_handle_freep) Tpm2Handle *srk_handle = NULL; + r = tpm2_deserialize( + tc, + srk_blob, + &srk_handle); + if (r < 0) + return log_error_errno(r, "Failed to deserialize SRK TR: %m"); - if (!event_log_pcr_checks_out(el, el->registers + pcr)) { - log_notice("PCR %" PRIu32 " (%s) value does not match event log. Removing from set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr))); - goto drop; - } + _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL; + r = tpm2_deserialize( + tc, + nv_blob, + &nv_handle); + if (r < 0) + return log_error_errno(r, "Failed to deserialize NV TR: %m"); - if (!el->registers[pcr].fully_recognized) { - log_notice("PCR %" PRIu32 " (%s) event log contains unrecognized measurements. Removing from set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr))); - goto drop; - } + _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL; + r = tpm2_make_encryption_session( + tc, + srk_handle, + /* bind_key= */ &TPM2_HANDLE_NONE, + &encryption_session); + if (r < 0) + return r; - if (BIT_SET(el->missing_component_pcrs, pcr)) { - log_notice("PCR %" PRIu32 " (%s) is touched by component we can't find in event log. Removing from set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr))); - goto drop; - } + r = tpm2_undefine_nv_index( + tc, + encryption_session, + nv_index, + nv_handle); + if (r < 0) + return r; - log_info("PCR %" PRIu32 " (%s) matches event log and fully consists of recognized measurements. Including in set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr))); + log_info("Removed NV index 0x%x", nv_index); + return 0; +} - if (strextendf_with_separator(&kept, ", ", "%" PRIu32 " (%s)", pcr, tpm2_pcr_index_to_string(pcr)) < 0) - return log_oom(); +static int remove_policy(void) { + int ret = 0, r; - continue; + _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy policy = {}; + r = tpm2_pcrlock_policy_load(arg_policy_path, &policy); + if (r == 0) { + log_info("No policy found."); + return 0; + } - drop: - *pcrs &= ~(UINT32_C(1) << pcr); + if (r < 0) + log_notice("Failed to load old policy file, assuming it is corrupted, removing."); + else { + r = undefine_policy_nv_index(policy.nv_index, &policy.nv_handle, &policy.srk_handle); + if (r < 0) + log_notice("Failed to remove NV index, assuming data out of date, removing policy file."); - if (strextendf_with_separator(&dropped, ", ", "%" PRIu32 " (%s)", pcr, tpm2_pcr_index_to_string(pcr)) < 0) - return log_oom(); + RET_GATHER(ret, r); } - if (dropped) - log_notice("PCRs dropped from protection mask: %s", dropped); - else - log_debug("No PCRs dropped from protection mask."); + if (arg_policy_path) + RET_GATHER(ret, remove_policy_file(arg_policy_path)); + else { + RET_GATHER(ret, remove_policy_file("/var/lib/systemd/pcrlock.json")); + RET_GATHER(ret, remove_policy_file("/run/systemd/pcrlock.json")); + } - if (kept) - log_notice("PCRs in protection mask: %s", kept); - else - log_notice("No PCRs kept in protection mask."); + _cleanup_free_ char *boot_policy_file = NULL; + r = determine_boot_policy_file(&boot_policy_file, /* ret_credential_name= */ NULL); + if (r == 0) + log_info("Did not find XBOOTLDR/ESP partition, not removing boot policy file."); + else if (r > 0) { + RET_GATHER(ret, remove_policy_file(boot_policy_file)); + } else + RET_GATHER(ret, r); - return 0; + return ret; } -static int pcr_prediction_add_result( - Tpm2PCRPrediction *context, - Tpm2PCRPredictionResult *result, - uint32_t pcr, - const char *path) { +static int verb_remove_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { + return remove_policy(); +} - _cleanup_free_ Tpm2PCRPredictionResult *copy = NULL; +static int test_tpm2_support_pcrlock(Tpm2Support *ret) { int r; - assert(context); - assert(result); + assert(ret); - copy = newdup(Tpm2PCRPredictionResult, result, 1); - if (!copy) - return log_oom(); + /* First check basic support */ + Tpm2Support s = tpm2_support(); - r = ordered_set_ensure_put(context->results + pcr, &tpm2_pcr_prediction_result_hash_ops, copy); - if (r == -EEXIST) /* Multiple identical results for the same PCR are totally expected */ - return 0; - if (r < 0) - return log_error_errno(r, "Failed to insert result into set: %m"); + /* If basic support is available, let's also check the things we need for systemd-pcrlock */ + if (s == TPM2_SUPPORT_FULL) { + _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL; + r = tpm2_context_new_or_warn(/* device= */ NULL, &tc); + if (r < 0) + return r; - log_debug("Added prediction result %u for PCR %" PRIu32 " (path: %s)", ordered_set_size(context->results[pcr]), pcr, strempty(path)); + /* We strictly need TPM2_CC_PolicyAuthorizeNV for systemd-pcrlock to work */ + SET_FLAG(s, TPM2_SUPPORT_AUTHORIZE_NV, tpm2_supports_command(tc, TPM2_CC_PolicyAuthorizeNV)); - TAKE_PTR(copy); + log_debug("PolicyAuthorizeNV supported: %s", yes_no(FLAGS_SET(s, TPM2_SUPPORT_AUTHORIZE_NV))); + + /* We also strictly need SHA-256 to work */ + SET_FLAG(s, TPM2_SUPPORT_SHA256, tpm2_supports_alg(tc, TPM2_ALG_SHA256)); + + log_debug("SHA-256 supported: %s", yes_no(FLAGS_SET(s, TPM2_SUPPORT_SHA256))); + } + + *ret = s; return 0; } -static const EVP_MD* evp_from_tpm2_alg(uint16_t alg) { - const char *name; +static int verb_is_supported(int argc, char *argv[], uintptr_t _data, void *userdata) { + int r; - name = tpm2_hash_alg_to_string(alg); - if (!name) - return NULL; + Tpm2Support s; + r = test_tpm2_support_pcrlock(&s); + if (r < 0) + return r; - return EVP_get_digestbyname(name); -} + if (!arg_quiet) { + if (s == (TPM2_SUPPORT_FULL|TPM2_SUPPORT_API_PCRLOCK)) + printf("%syes%s\n", ansi_green(), ansi_normal()); + else if (FLAGS_SET(s, TPM2_SUPPORT_FULL)) + printf("%sobsolete%s\n", ansi_red(), ansi_normal()); + else if (s == TPM2_SUPPORT_NONE) + printf("%sno%s\n", ansi_red(), ansi_normal()); + else + printf("%spartial%s\n", ansi_yellow(), ansi_normal()); + } -static int event_log_component_variant_calculate( - Tpm2PCRPredictionResult *result, - EventLogComponentVariant *variant, - uint32_t pcr) { + assert_cc((TPM2_SUPPORT_API|TPM2_SUPPORT_API_PCRLOCK) <= 255); /* make sure this is safe to use as process exit status */ - assert(result); - assert(variant); + return ~s & (TPM2_SUPPORT_API|TPM2_SUPPORT_API_PCRLOCK); +} - FOREACH_ARRAY(rr, variant->records, variant->n_records) { - EventLogRecord *rec = *rr; +static int event_log_record_is_action_calling_efi_app(const EventLogRecord *rec) { + _cleanup_free_ char *d = NULL; + int r; - if (!EVENT_LOG_RECORD_IS_PCR(rec)) - continue; + assert(rec); - if (rec->pcr != pcr) - continue; + /* Recognizes the special EV_EFI_ACTION that is issues when the firmware passes control to the boot loader. */ - for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { - _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *md_ctx = NULL; - EventLogRecordBank *b; + if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) + return false; - if (result->hash[i].size <= 0) /* already invalidated */ - continue; + if (rec->pcr != TPM2_PCR_BOOT_LOADER_CODE) + return false; - b = event_log_record_find_bank(rec, tpm2_hash_algorithms[i]); - if (!b) { - /* Can't calculate, hence invalidate */ - result->hash[i] = (TPM2B_DIGEST) {}; - continue; - } + if (rec->firmware_event_type != EV_EFI_ACTION) + return false; - md_ctx = EVP_MD_CTX_new(); - if (!md_ctx) - return log_oom(); + if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) /* Insist the record is consistent */ + return false; - const EVP_MD *md = ASSERT_PTR(evp_from_tpm2_alg(tpm2_hash_algorithms[i])); + r = make_cstring(rec->firmware_payload, rec->firmware_payload_size, MAKE_CSTRING_ALLOW_TRAILING_NUL, &d); + if (r < 0) + return r; - int sz = EVP_MD_size(md); - assert(sz > 0); - assert((size_t) sz <= sizeof_field(TPM2B_DIGEST, buffer)); + return streq(d, "Calling EFI Application from Boot Option"); +} - assert(sz == tpm2_hash_alg_to_size(tpm2_hash_algorithms[i])); +static void enable_json_sse(void) { + /* We shall write this to a single output stream? We have to output two files, hence try to be smart + * and enable JSON SSE */ - assert(result->hash[i].size == (size_t) sz); - assert(b->hash.size == (size_t) sz); + if (!arg_pcrlock_path && arg_pcrlock_auto) + return; - if (EVP_DigestInit_ex(md_ctx, md, NULL) != 1) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to initialize message digest."); + if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_SSE)) + return; - if (EVP_DigestUpdate(md_ctx, result->hash[i].buffer, sz) != 1) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash bank value."); + log_notice("Enabling JSON_SEQ mode, since writing two .pcrlock files to single output."); + arg_json_format_flags |= SD_JSON_FORMAT_SSE; +} - if (EVP_DigestUpdate(md_ctx, b->hash.buffer, sz) != 1) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash data value."); +static bool event_log_record_is_separator(const EventLogRecord *rec) { + assert(rec); - unsigned l = (unsigned) sz; - if (EVP_DigestFinal_ex(md_ctx, result->hash[i].buffer, &l) != 1) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize message digest."); + /* Recognizes EV_SEPARATOR events */ - assert(l == (unsigned) sz); - } - } + if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) + return false; - return 0; + if (rec->firmware_event_type != EV_SEPARATOR) + return false; + + return rec->event_payload_valid == EVENT_PAYLOAD_VALID_YES; /* Insist the record is consistent */ } -static int event_log_predict_pcrs( - EventLog *el, - Tpm2PCRPrediction *context, - Tpm2PCRPredictionResult *parent_result, - size_t component_index, - uint32_t pcr, - const char *path) { +static int verb_lock_firmware(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array_early = NULL, *array_late = NULL; + _cleanup_(event_log_freep) EventLog *el = NULL; + uint32_t always_mask, separator_mask, separator_seen_mask = 0, action_seen_mask = 0; + const char *default_pcrlock_early_path, *default_pcrlock_late_path; + int r; - EventLogComponent *component; - int count = 0, r; + enable_json_sse(); - assert(el); - assert(context); - assert(parent_result); + /* The PCRs we intend to cover. Note that we measure firmware, external *and* boot loader code/config + * here – but the latter only until the "separator" events are seen, which tell us where transition + * into OS boot loader happens. This reflects the fact that on some systems the firmware already + * measures some firmware-supplied apps into PCR 4. (e.g. Thinkpad X1 Gen9) */ + if (endswith(argv[0], "firmware-code")) { + always_mask = (UINT32_C(1) << TPM2_PCR_PLATFORM_CODE) | /* → 0 */ + (UINT32_C(1) << TPM2_PCR_EXTERNAL_CODE); /* → 2 */ - /* Check if we reached the end of the components, generate a result, and backtrack */ - if (component_index >= el->n_components || - (arg_location_end && strcmp(el->components[component_index]->id, arg_location_end) > 0)) { - r = pcr_prediction_add_result(context, parent_result, pcr, path); - if (r < 0) - return r; + separator_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CODE; /* → 4 */ - return 1; - } + default_pcrlock_early_path = PCRLOCK_FIRMWARE_CODE_EARLY_PATH; + default_pcrlock_late_path = PCRLOCK_FIRMWARE_CODE_LATE_PATH; + } else { + assert(endswith(argv[0], "firmware-config")); + always_mask = (UINT32_C(1) << TPM2_PCR_PLATFORM_CONFIG) | /* → 1 */ + (UINT32_C(1) << TPM2_PCR_EXTERNAL_CONFIG); /* → 3 */ - component = ASSERT_PTR(el->components[component_index]); + separator_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CONFIG; /* → 5 */ - /* Check if we are just about to process a component after start, if so record a result and continue. */ - if (arg_location_start && strcmp(component->id, arg_location_start) > 0) { - r = pcr_prediction_add_result(context, parent_result, pcr, path); - if (r < 0) - return r; + default_pcrlock_early_path = PCRLOCK_FIRMWARE_CONFIG_EARLY_PATH; + default_pcrlock_late_path = PCRLOCK_FIRMWARE_CONFIG_LATE_PATH; } - FOREACH_ARRAY(ii, component->variants, component->n_variants) { - _cleanup_free_ Tpm2PCRPredictionResult *result = NULL; - EventLogComponentVariant *variant = *ii; - _cleanup_free_ char *subpath = NULL; + el = event_log_new(); + if (!el) + return log_oom(); - /* Operate on a copy of the result */ + r = event_log_add_algorithms_from_environment(el); + if (r < 0) + return r; - if (path) - subpath = strjoin(path, ":", component->id); - else - subpath = strdup(component->id); - if (!subpath) - return log_oom(); + r = event_log_load(el); + if (r < 0) + return r; - if (!streq(component->id, variant->id)) - if (!strextend(&subpath, "@", variant->id)) - return log_oom(); + r = event_log_read_pcrs(el); + if (r < 0) + return r; - result = newdup(Tpm2PCRPredictionResult, parent_result, 1); - if (!result) - return log_oom(); + r = event_log_calculate_pcrs(el); + if (r < 0) + return r; - r = event_log_component_variant_calculate( - result, - variant, - pcr); - if (r < 0) - return r; - - r = event_log_predict_pcrs( - el, - context, - result, - component_index + 1, /* Next component */ - pcr, - subpath); - if (r < 0) - return r; - - count += r; - } - - return count; -} - -static ssize_t event_log_calculate_component_combinations(EventLog *el) { - ssize_t count = 1; - assert(el); - - FOREACH_ARRAY(cc, el->components, el->n_components) { - EventLogComponent *c = *cc; - - assert(c->n_variants > 0); - - /* Overflow check */ - if (c->n_variants > (size_t) (SSIZE_MAX/count)) - return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Too many component combinations."); - count *= c->n_variants; - } - - return count; -} + r = event_log_validate_record_hashes(el); + if (r < 0) + return r; -static int event_log_show_predictions(Tpm2PCRPrediction *context, uint16_t alg) { - int r; + /* Before we base anything on the event log records for any of the selected PCRs, let's check that + * the event log state checks out for them. */ - assert(context); + r = event_log_pcr_mask_checks_out(el, always_mask|separator_mask); + if (r < 0) + return r; - pager_open(arg_pager_flags); + // FIXME: before doing this, validate ahead-of-time that EV_SEPARATOR records exist for all entries, + // and exactly once - if (sd_json_format_enabled(arg_json_format_flags)) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + FOREACH_ARRAY(rr, el->records, el->n_records) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL; + EventLogRecord *rec = *rr; + uint32_t bit = UINT32_C(1) << rec->pcr; - for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *aj = NULL; + if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) + continue; - r = tpm2_pcr_prediction_to_json( - context, - tpm2_hash_algorithms[i], - &aj); - if (r < 0) - return r; + if (!FLAGS_SET(always_mask, bit) && + !(FLAGS_SET(separator_mask, bit) && !FLAGS_SET(separator_seen_mask|action_seen_mask, bit))) + continue; - if (sd_json_variant_elements(aj) == 0) - continue; + /* If we hit the separator record, we stop processing the PCRs listed in `separator_mask` */ + if (event_log_record_is_separator(rec)) { + separator_seen_mask |= bit; + continue; + } - r = sd_json_variant_set_field( - &j, - tpm2_hash_alg_to_string(tpm2_hash_algorithms[i]), - aj); - if (r < 0) - return log_error_errno(r, "Failed to add prediction bank to object: %m"); + /* If we hit the special "Calling EFI Application from Boot Option" action we treat this the + * same as a separator here, as that's where firmware passes control to boot loader. Note + * that some EFI implementations forget to generate one of them. */ + r = event_log_record_is_action_calling_efi_app(rec); + if (r < 0) + return log_error_errno(r, "Failed to check if event is 'Calling EFI Application from Boot Option' action: %m"); + if (r > 0) { + action_seen_mask |= bit; + continue; } - if (!j) { - r = sd_json_variant_new_object(&j, NULL, 0); + LIST_FOREACH(banks, bank, rec->banks) { + r = sd_json_variant_append_arraybo( + &digests, + SD_JSON_BUILD_PAIR_STRING("hashAlg", tpm2_hash_alg_to_string(bank->algorithm)), + SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(bank->hash.buffer, bank->hash.size))); if (r < 0) - return log_error_errno(r, "Failed to allocated empty object: %m"); + return log_error_errno(r, "Failed to build digests array: %m"); } - sd_json_variant_dump(j, arg_json_format_flags, /* f= */ NULL, /* prefix= */ NULL); - return 0; + r = sd_json_variant_append_arraybo( + FLAGS_SET(separator_seen_mask, bit) ? &array_late : &array_early, + SD_JSON_BUILD_PAIR_UNSIGNED("pcr", rec->pcr), + SD_JSON_BUILD_PAIR_VARIANT("digests", digests)); + if (r < 0) + return log_error_errno(r, "Failed to build record array: %m"); } - for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) { - Tpm2PCRPredictionResult *result; - if (!BIT_SET(context->pcrs, pcr)) - continue; - - if (ordered_set_isempty(context->results[pcr])) { - printf("No results for PCR %u (%s).\n", pcr, tpm2_pcr_index_to_string(pcr)); - continue; - } - - printf("%sResults for PCR %u (%s):%s\n", ansi_underline(), pcr, tpm2_pcr_index_to_string(pcr), ansi_normal()); - - ORDERED_SET_FOREACH(result, context->results[pcr]) { + r = write_pcrlock(array_early, default_pcrlock_early_path); + if (r < 0) + return r; - _cleanup_free_ char *aa = NULL, *h = NULL; - const char *a; + return write_pcrlock(array_late, default_pcrlock_late_path); +} - TPM2B_DIGEST *hash = tpm2_pcr_prediction_result_get_hash(result, alg); - if (!hash) - continue; +static int verb_unlock_firmware(int argc, char *argv[], uintptr_t _data, void *userdata) { + const char *default_pcrlock_early_path, *default_pcrlock_late_path; + int r; - a = ASSERT_PTR(tpm2_hash_alg_to_string(alg)); - aa = strdup(a); - if (!aa) - return log_oom(); + if (endswith(argv[0], "firmware-code")) { + default_pcrlock_early_path = PCRLOCK_FIRMWARE_CODE_EARLY_PATH; + default_pcrlock_late_path = PCRLOCK_FIRMWARE_CODE_LATE_PATH; + } else { + default_pcrlock_early_path = PCRLOCK_FIRMWARE_CONFIG_EARLY_PATH; + default_pcrlock_late_path = PCRLOCK_FIRMWARE_CONFIG_LATE_PATH; + } - ascii_strlower(aa); + r = unlink_pcrlock(default_pcrlock_early_path); + if (r < 0) + return r; - h = hexmem(hash->buffer, hash->size); - if (!h) - return log_oom(); + if (arg_pcrlock_path) /* if the path is specified don't delete the same thing twice */ + return 0; - printf(" %s%-6s:%s %s\n", ansi_grey(), aa, ansi_normal(), h); - } - } + r = unlink_pcrlock(default_pcrlock_late_path); + if (r < 0) + return r; return 0; } -static int tpm2_pcr_prediction_run( - EventLog *el, - Tpm2PCRPrediction *context) { +static int verb_lock_secureboot_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { + static const struct { + sd_id128_t id; + const char *name; + int synthesize_empty; /* 0 → fail, > 0 → synthesize empty db, < 0 → skip */ + } variables[] = { + { EFI_VENDOR_GLOBAL, "SecureBoot", 0 }, + { EFI_VENDOR_GLOBAL, "PK", 1 }, + { EFI_VENDOR_GLOBAL, "KEK", 1 }, + { EFI_VENDOR_DATABASE, "db", 1 }, + { EFI_VENDOR_DATABASE, "dbx", 1 }, + { EFI_VENDOR_DATABASE, "dbt", -1 }, + { EFI_VENDOR_DATABASE, "dbr", -1 }, + }; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; int r; - assert(el); - assert(context); + /* Generates expected records from the current SecureBoot state, as readable in the EFI variables + * right now. */ - for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) { - _cleanup_free_ Tpm2PCRPredictionResult *result = NULL; + FOREACH_ELEMENT(vv, variables) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL; - if (!BIT_SET(context->pcrs, pcr)) - continue; + _cleanup_free_ char *name = NULL; + if (asprintf(&name, "%s-" SD_ID128_UUID_FORMAT_STR, vv->name, SD_ID128_FORMAT_VAL(vv->id)) < 0) + return log_oom(); - result = new0(Tpm2PCRPredictionResult, 1); - if (!result) + _cleanup_free_ void *data = NULL; + size_t data_size; + r = efi_get_variable(name, NULL, &data, &data_size); + if (r < 0) { + if (r != -ENOENT || vv->synthesize_empty == 0) + return log_error_errno(r, "Failed to read EFI variable '%s': %m", name); + if (vv->synthesize_empty < 0) + continue; + + /* If the main database variables are not set we don't consider this an error, but + * measure an empty database instead. */ + log_debug("EFI variable %s is not set, synthesizing empty variable for measurement.", name); + data_size = 0; + } + + _cleanup_free_ char16_t* name16 = utf8_to_utf16(vv->name, SIZE_MAX); + if (!name16) return log_oom(); + size_t name16_bytes = char16_strlen(name16) * 2; - for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) - event_log_initial_pcr_state(el, pcr, tpm2_hash_alg_to_size(tpm2_hash_algorithms[i]), result->hash + i); + size_t vdata_size = offsetof(UEFI_VARIABLE_DATA, unicodeName) + name16_bytes + data_size; + _cleanup_free_ UEFI_VARIABLE_DATA *vdata = malloc(vdata_size); + if (!vdata) + return log_oom(); - r = event_log_predict_pcrs( - el, - context, - result, - /* component_index= */ 0, - pcr, - /* path= */ NULL); + *vdata = (UEFI_VARIABLE_DATA) { + .unicodeNameLength = name16_bytes / 2, + .variableDataLength = data_size, + }; + + efi_id128_to_guid(vv->id, vdata->variableName); + memcpy(mempcpy(vdata->unicodeName, name16, name16_bytes), data, data_size); + + r = make_pcrlock_record(TPM2_PCR_SECURE_BOOT_POLICY /* =7 */, vdata, vdata_size, &record); if (r < 0) return r; + + r = sd_json_variant_append_array(&array, record); + if (r < 0) + return log_error_errno(r, "Failed to append to JSON array: %m"); } - return 0; + return write_pcrlock(array, PCRLOCK_SECUREBOOT_POLICY_PATH); } -static int verb_predict(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(tpm2_pcr_prediction_done) Tpm2PCRPrediction context = { - arg_pcr_mask != 0 ? arg_pcr_mask : DEFAULT_PCR_MASK, - }; - _cleanup_(event_log_freep) EventLog *el = NULL; - ssize_t count; +static int verb_unlock_secureboot_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { + return unlink_pcrlock(PCRLOCK_SECUREBOOT_POLICY_PATH); +} + +static int event_log_record_is_secureboot_variable(EventLogRecord *rec, sd_id128_t uuid, const char *name) { + _cleanup_free_ char *found_name = NULL; + sd_id128_t found_uuid; int r; - r = event_log_load_and_process(&el); - if (r < 0) - return r; + assert(rec); + assert(name); - count = event_log_calculate_component_combinations(el); - if (count < 0) - return count; + if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) + return false; - log_info("%zi combinations of components.", count); + if (rec->pcr != TPM2_PCR_SECURE_BOOT_POLICY) + return false; - r = event_log_reduce_to_safe_pcrs(el, &context.pcrs); - if (r < 0) - return r; + if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) + return false; - r = tpm2_pcr_prediction_run(el, &context); + if (rec->firmware_event_type != EV_EFI_VARIABLE_DRIVER_CONFIG) + return false; + + r = event_log_record_parse_variable_data(rec, &found_uuid, &found_name); + if (r == -EBADMSG) + return false; if (r < 0) return r; - return event_log_show_predictions(&context, el->primary_algorithm); -} - -static int remove_policy_file(const char *path) { - assert(path); + if (!sd_id128_equal(found_uuid, uuid)) + return false; - if (unlink(path) < 0) { - if (errno == ENOENT) - return 0; + return streq(found_name, name); +} - return log_error_errno(errno, "Failed to remove policy file '%s': %m", path); - } +static bool event_log_record_is_secureboot_authority(EventLogRecord *rec) { + assert(rec); - log_info("Removed policy file '%s'.", path); - return 1; -} + if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) + return false; -static int determine_boot_policy_file(char **ret_path, char **ret_credential_name) { - int r; + if (rec->pcr != TPM2_PCR_SECURE_BOOT_POLICY) + return false; - _cleanup_free_ char *path = NULL; - r = get_global_boot_credentials_path(&path); - if (r < 0) - return r; - if (r == 0) { - if (ret_path) - *ret_path = NULL; - if (ret_credential_name) - *ret_credential_name = NULL; - return 0; /* not found! */ - } + if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) + return false; - sd_id128_t machine_id; - r = sd_id128_get_machine(&machine_id); - if (r < 0) - return log_error_errno(r, "Failed to read machine ID: %m"); + return rec->firmware_event_type == EV_EFI_VARIABLE_AUTHORITY; +} - r = boot_entry_token_ensure( - /* root= */ NULL, - /* conf_root= */ NULL, - machine_id, - /* machine_id_is_random= */ false, - &arg_entry_token_type, - &arg_entry_token); - if (r < 0) - return r; +static int event_log_ensure_secureboot_consistency(EventLog *el) { + static const struct { + sd_id128_t id; + const char *name; + bool required; + } table[] = { + { EFI_VENDOR_GLOBAL, "SecureBoot", true }, + { EFI_VENDOR_GLOBAL, "PK", true }, + { EFI_VENDOR_GLOBAL, "KEK", true }, + { EFI_VENDOR_DATABASE, "db", true }, + { EFI_VENDOR_DATABASE, "dbx", true }, + { EFI_VENDOR_DATABASE, "dbt", false }, + { EFI_VENDOR_DATABASE, "dbr", false }, + // FIXME: ensure we also find the separator here + }; - _cleanup_free_ char *fn = strjoin("pcrlock.", arg_entry_token, ".cred"); - if (!fn) - return log_oom(); + EventLogRecord *records[ELEMENTSOF(table)] = {}; + EventLogRecord *first_authority = NULL; - if (!filename_is_valid(fn)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name '%s' would not be a valid file name, refusing.", fn); + assert(el); - _cleanup_free_ char *joined = NULL; - if (ret_path) { - joined = path_join(path, fn); - if (!joined) - return log_oom(); - } + /* Ensures that the PCR 7 records are complete and in order. Before we lock down PCR 7 we want to + * ensure its state is actually consistent. */ - _cleanup_free_ char *cn = NULL; - if (ret_credential_name) { - /* The .cred suffix of the file is stripped when PID 1 imports the credential, hence exclude it from - * the embedded credential name. */ - cn = strjoin("pcrlock.", arg_entry_token); - if (!cn) - return log_oom(); + FOREACH_ARRAY(rr, el->records, el->n_records) { + EventLogRecord *rec = *rr; + size_t found = SIZE_MAX; - ascii_strlower(cn); /* lowercase this file, no matter what, since stored on VFAT, and we don't want - * to run into case change incompatibilities */ - } + if (event_log_record_is_secureboot_authority(rec)) { + if (first_authority) + continue; - if (ret_path) - *ret_path = TAKE_PTR(joined); + first_authority = rec; + // FIXME: also check that each authority record's data is also listed in 'db' + continue; + } - if (ret_credential_name) - *ret_credential_name = TAKE_PTR(cn); + for (size_t i = 0; i < ELEMENTSOF(table); i++) + if (event_log_record_is_secureboot_variable(rec, table[i].id, table[i].name)) { + found = i; + break; + } + if (found == SIZE_MAX) + continue; - return 1; /* found! */ -} + /* Require the authority records always come *after* database measurements */ + if (first_authority) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "SecureBoot authority before variable, refusing."); -static int write_boot_policy_file(const char *json_text) { - _cleanup_free_ char *boot_policy_file = NULL, *credential_name = NULL; - int r; + /* Check for duplicates */ + if (records[found]) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate '%s' record, refusing.", rec->description); - assert(json_text); + /* Check for order */ + for (size_t j = found + 1; j < ELEMENTSOF(table); j++) + if (records[j]) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'%s' record before '%s' record, refusing.", records[j]->description, rec->description); - r = determine_boot_policy_file(&boot_policy_file, &credential_name); - if (r < 0) - return r; - if (r == 0) { - log_info("Did not find XBOOTLDR/ESP partition, not writing boot policy file."); - return 0; + records[found] = rec; } - _cleanup_(iovec_done) struct iovec encoded = {}; - r = encrypt_credential_and_warn( - CRED_AES256_GCM_BY_NULL, - credential_name, - now(CLOCK_REALTIME), - /* not_after= */ USEC_INFINITY, - /* tpm2_device= */ NULL, - /* tpm2_hash_pcr_mask= */ 0, - /* tpm2_pubkey_path= */ NULL, - /* tpm2_pubkey_pcr_mask= */ 0, - UID_INVALID, - &IOVEC_MAKE_STRING(json_text), - CREDENTIAL_ALLOW_NULL, - &encoded); - if (r < 0) - return log_error_errno(r, "Failed to encode policy as credential: %m"); - - r = write_base64_file_at( - AT_FDCWD, - boot_policy_file, - &encoded, - WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_MKDIR_0755|WRITE_STRING_FILE_LABEL); - if (r < 0) - return log_error_errno(r, "Failed to write boot policy file to '%s': %m", boot_policy_file); + /* Check for existence */ + for (size_t i = 0; i < ELEMENTSOF(table); i++) + if (table[i].required && !records[i]) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Required record '%s' not found, refusing.", table[i].name); - log_info("Written new boot policy to '%s'.", boot_policy_file); - return 1; + /* At this point we know that all required variables have been measured, in the right order. */ + return 0; } -static int make_policy(bool force, RecoveryPinMode recovery_pin_mode) { +static int verb_lock_secureboot_authority(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; + _cleanup_(event_log_freep) EventLog *el = NULL; int r; - /* Here's how this all works: after predicting all possible PCR values for next boot (with - * alternatives) we'll calculate a policy from it as a combination of PolicyPCR + PolicyOR - * expressions. This is then stored in an NV index. When a component of the boot process is changed a - * new prediction is made and the NV index updated (which automatically invalidates any older - * policies). - * - * Whenever we want to lock an encrypted object (for example FDE) against this policy, we'll use a - * PolicyAuthorizeNV expression that pins the NV index in the policy, and permits access to any - * policies matching the current NV index contents. - * - * We grant world-readable read access to the NV index. Write access is controlled by a PIN (which we - * either generate locally or which the user can provide us with) which can also be used for - * recovery. This PIN is sealed to the TPM and is locked via PolicyAuthorizeNV to the NV index it - * protects (i.e. we dogfood 🌭 🐶 hard here). This means in order to update such a policy we need - * the policy to pass. - * - * Information about the used NV Index, the SRK of the TPM, the sealed PIN and the current PCR - * prediction data are stored in a JSON file in /var/lib/. In order to be able to unlock root disks - * this data must be also copied to the ESP so that it is available to the initrd. The data is not - * sensitive, as SRK and NV index are pinned by it, and the prediction data must match the NV index - * to be useful. */ + /* Lock down the EV_EFI_VARIABLE_AUTHORITY records from the existing log. Note that there's not too + * much value in locking this down too much, since it stores only the result of the primary database + * checks, and that's what we should bind policy to. Moreover it's hard to predict, since extension + * card firmware validation will result in additional records here. */ - usec_t start_usec = now(CLOCK_MONOTONIC); + if (!is_efi_secure_boot()) { + log_info("SecureBoot disabled, not generating authority .pcrlock file."); + return unlink_pcrlock(PCRLOCK_SECUREBOOT_AUTHORITY_PATH); + } - _cleanup_(event_log_freep) EventLog *el = NULL; - r = event_log_load_and_process(&el); + el = event_log_new(); + if (!el) + return log_oom(); + + r = event_log_add_algorithms_from_environment(el); if (r < 0) return r; - _cleanup_(tpm2_pcr_prediction_done) Tpm2PCRPrediction new_prediction = { - arg_pcr_mask != 0 ? arg_pcr_mask : DEFAULT_PCR_MASK, - }; - r = event_log_reduce_to_safe_pcrs(el, &new_prediction.pcrs); + r = event_log_load(el); if (r < 0) return r; - if (!force && new_prediction.pcrs == 0) - log_notice("Set of PCRs to use for policy is empty. Generated policy will not provide any protection in its current form. Proceeding."); - - usec_t predict_start_usec = now(CLOCK_MONOTONIC); + r = event_log_read_pcrs(el); + if (r < 0) + return r; - r = tpm2_pcr_prediction_run(el, &new_prediction); + r = event_log_calculate_pcrs(el); if (r < 0) return r; - log_info("Predicted future PCRs in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), predict_start_usec), 1)); + /* Before we base anything on the event log records, let's check that the event log state checks + * out. */ - _cleanup_(sd_json_variant_unrefp) sd_json_variant *new_prediction_json = NULL; - r = tpm2_pcr_prediction_to_json(&new_prediction, el->primary_algorithm, &new_prediction_json); + r = event_log_pcr_mask_checks_out(el, UINT32_C(1) << TPM2_PCR_SECURE_BOOT_POLICY); if (r < 0) return r; - if (DEBUG_LOGGING) - (void) sd_json_variant_dump(new_prediction_json, SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO, stderr, NULL); + r = event_log_validate_record_hashes(el); + if (r < 0) + return r; - /* v257 and older mistakenly used --pcrlock= for the path. To keep backward compatibility, let's fallback to it when - * --policy= is unspecified but --pcrlock is specified. */ - if (!arg_policy_path && arg_pcrlock_path) { - log_notice("Specified --pcrlock= option for make-policy command. Please use --policy= instead."); + r = event_log_ensure_secureboot_consistency(el); + if (r < 0) + return r; - arg_policy_path = strdup(arg_pcrlock_path); - if (!arg_policy_path) - return log_oom(); + FOREACH_ARRAY(rr, el->records, el->n_records) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL; + EventLogRecord *rec = *rr; + + if (!event_log_record_is_secureboot_authority(rec)) + continue; + + log_debug("Locking down authority '%s'.", strna(rec->description)); + + LIST_FOREACH(banks, bank, rec->banks) { + r = sd_json_variant_append_arraybo( + &digests, + SD_JSON_BUILD_PAIR_STRING("hashAlg", tpm2_hash_alg_to_string(bank->algorithm)), + SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(bank->hash.buffer, bank->hash.size))); + if (r < 0) + return log_error_errno(r, "Failed to build digests array: %m"); + } + + r = sd_json_variant_append_arraybo( + &array, + SD_JSON_BUILD_PAIR_UNSIGNED("pcr", rec->pcr), + SD_JSON_BUILD_PAIR_VARIANT("digests", digests)); + if (r < 0) + return log_error_errno(r, "Failed to build record array: %m"); } - _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy old_policy = {}; - r = tpm2_pcrlock_policy_load(arg_policy_path, &old_policy); + return write_pcrlock(array, PCRLOCK_SECUREBOOT_AUTHORITY_PATH); +} + +static int verb_unlock_secureboot_authority(int argc, char *argv[], uintptr_t _data, void *userdata) { + return unlink_pcrlock(PCRLOCK_SECUREBOOT_AUTHORITY_PATH); +} + +static int verb_lock_gpt(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL, *record = NULL; + _cleanup_(sd_device_unrefp) sd_device *d = NULL; + uint8_t h[2 * 4096]; /* space for at least two 4K sectors. GPT header should definitely be in here */ + uint64_t start, n_members, member_size; + _cleanup_close_ int fd = -EBADF; + const GptHeader *p; + size_t found = 0; + ssize_t n; + int r; + + r = block_device_new_from_path( + argc >= 2 ? argv[1] : "/", + BLOCK_DEVICE_LOOKUP_WHOLE_DISK|BLOCK_DEVICE_LOOKUP_BACKING|BLOCK_DEVICE_LOOKUP_ORIGINATING, + &d); if (r < 0) - return r; + return log_error_errno(r, "Failed to determine root block device: %m"); - bool have_old_policy = r > 0; + fd = sd_device_open(d, O_CLOEXEC|O_RDONLY|O_NOCTTY); + if (fd < 0) + return log_error_errno(fd, "Failed to open root block device: %m"); - /* When we update the policy the old serializations for NV, SRK, PIN remain the same */ - _cleanup_(iovec_done) struct iovec - nv_blob = TAKE_STRUCT(old_policy.nv_handle), - nv_public_blob = TAKE_STRUCT(old_policy.nv_public), - srk_blob = TAKE_STRUCT(old_policy.srk_handle), - pin_public = TAKE_STRUCT(old_policy.pin_public), - pin_private = TAKE_STRUCT(old_policy.pin_private); + n = pread(fd, &h, sizeof(h), 0); + if (n < 0) + return log_error_errno(errno, "Failed to read GPT header of block device: %m"); + if ((size_t) n != sizeof(h)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read trying to read GPT header."); - if (have_old_policy) { - if (arg_nv_index != 0 && old_policy.nv_index != arg_nv_index) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Stored policy references different NV index (0x%x) than specified (0x%x), refusing.", old_policy.nv_index, arg_nv_index); + /* Try a couple of sector sizes */ + for (size_t sz = 512; sz <= 4096; sz <<= 1) { + assert(sizeof(h) >= sz * 2); + p = (const GptHeader*) (h + sz); /* 2nd sector */ - if (!force && - old_policy.algorithm == el->primary_algorithm && - tpm2_pcr_prediction_equal(&old_policy.prediction, &new_prediction, el->primary_algorithm)) { - log_info("Prediction is identical to current policy, skipping update."); - return 0; /* NOP */ - } + if (!gpt_header_has_signature(p)) + continue; + + if (found != 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), + "Disk has partition table for multiple sector sizes, refusing."); + + found = sz; } - _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL; - r = tpm2_context_new_or_warn(/* device= */ NULL, &tc); + if (found == 0) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Disk does not have GPT partition table, refusing."); + + p = (const GptHeader*) (h + found); + + if (le32toh(p->header_size) > found) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "GPT header size over long (%" PRIu32 "), refusing.", le32toh(p->header_size)); + + start = le64toh(p->partition_entry_lba); + if (start > UINT64_MAX / found) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Partition table start offset overflow, refusing."); + + member_size = le32toh(p->size_of_partition_entry); + if (member_size < sizeof(GptPartitionEntry)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Partition entry size too short, refusing."); + + n_members = le32toh(p->number_of_partition_entries); + uint64_t member_bufsz = n_members * member_size; + if (member_bufsz > 1U*1024U*1024U) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Partition table size too large, refusing."); + + member_bufsz = ROUND_UP(member_bufsz, found); + + _cleanup_free_ void *members = malloc(member_bufsz); + if (!members) + return log_oom(); + + n = pread(fd, members, member_bufsz, start * found); + if (n < 0) + return log_error_errno(errno, "Failed to read GPT partition table entries: %m"); + if ((size_t) n != member_bufsz) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading GPT partition table entries."); + + size_t vdata_size = le32toh(p->header_size) + sizeof(le64_t) + member_size * n_members; + _cleanup_free_ void *vdata = malloc0(vdata_size); + if (!vdata) + return log_oom(); + + void *n_measured_entries = mempcpy(vdata, p, sizeof(GptHeader)); /* n_measured_entries is a 64bit value */ + + void *qq = (uint8_t*) n_measured_entries + sizeof(le64_t); + + for (uint64_t i = 0; i < n_members; i++) { + const GptPartitionEntry *entry = (const GptPartitionEntry*) ((const uint8_t*) members + (member_size * i)); + + if (memeqzero(entry->partition_type_guid, sizeof(entry->partition_type_guid))) + continue; + + qq = mempcpy(qq, entry, member_size); + unaligned_write_le64(n_measured_entries, unaligned_read_le64(n_measured_entries) + 1); + } + + vdata_size = (uint8_t*) qq - (uint8_t*) vdata; + + r = make_pcrlock_record(TPM2_PCR_BOOT_LOADER_CONFIG /* =5 */, vdata, vdata_size, &record); if (r < 0) return r; - if (!tpm2_supports_command(tc, TPM2_CC_PolicyAuthorizeNV)) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 does not support PolicyAuthorizeNV command, refusing."); - if (!tpm2_supports_alg(tc, TPM2_ALG_SHA256)) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 does not support SHA-256 hash algorithm, refusing."); + r = sd_json_variant_new_array(&array, &record, 1); + if (r < 0) + return log_error_errno(r, "Failed to append to JSON array: %m"); - _cleanup_(tpm2_handle_freep) Tpm2Handle *srk_handle = NULL; + return write_pcrlock(array, PCRLOCK_GPT_PATH); +} - r = tpm2_deserialize( - tc, - &srk_blob, - &srk_handle); - if (r < 0) - return log_error_errno(r, "Failed to deserialize SRK TR: %m"); - if (r == 0) { - r = tpm2_get_or_create_srk( - tc, - /* session= */ NULL, - /* ret_public= */ NULL, - /* ret_name= */ NULL, - /* ret_qname= */ NULL, - &srk_handle); - if (r < 0) - return log_error_errno(r, "Failed to install SRK: %m"); +static int verb_unlock_gpt(int argc, char *argv[], uintptr_t _data, void *userdata) { + return unlink_pcrlock(PCRLOCK_GPT_PATH); +} + +static int verb_lock_pe(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; + _cleanup_close_ int fd = -EBADF; + int r; + + // FIXME: Maybe also generate a matching EV_EFI_VARIABLE_AUTHORITY records here for each signature that + // covers this PE plus its hash, as alternatives under the same component name + + if (argc >= 2) { + fd = open(argv[1], O_RDONLY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); } - _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL; - r = tpm2_make_encryption_session( - tc, - srk_handle, - /* bind_key= */ &TPM2_HANDLE_NONE, - &encryption_session); - if (r < 0) - return log_error_errno(r, "Failed to allocate encryption session: %m"); + if (arg_pcr_mask == 0) + arg_pcr_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CODE; - /* Acquire a recovery PIN, either from the user, or create a randomized one */ - _cleanup_(erase_and_freep) char *pin = NULL; - if (recovery_pin_mode == RECOVERY_PIN_QUERY) { - r = getenv_steal_erase("PIN", &pin); - if (r < 0) - return log_error_errno(r, "Failed to acquire PIN from environment: %m"); - if (r == 0) { - _cleanup_strv_free_erase_ char **l = NULL; + for (uint32_t i = 0; i < TPM2_PCRS_MAX; i++) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL; - AskPasswordRequest req = { - .tty_fd = -EBADF, - .message = "Recovery PIN", - .id = "pcrlock-recovery-pin", - .credential = "pcrlock.recovery-pin", - .until = USEC_INFINITY, - .hup_fd = -EBADF, - }; + if (!BIT_SET(arg_pcr_mask, i)) + continue; - r = ask_password_auto( - &req, - /* flags= */ 0, - &l); - if (r < 0) - return log_error_errno(r, "Failed to query for recovery PIN: %m"); + FOREACH_ARRAY(pa, tpm2_hash_algorithms, TPM2_N_HASH_ALGORITHMS) { + _cleanup_free_ void *hash = NULL; + size_t hash_size; + const EVP_MD *md; + const char *a; - if (strv_length(l) != 1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single PIN only."); + assert_se(a = tpm2_hash_alg_to_string(*pa)); + assert_se(md = EVP_get_digestbyname(a)); - pin = TAKE_PTR(l[0]); - l = mfree(l); + r = pe_hash(fd < 0 ? STDIN_FILENO : fd, md, &hash, &hash_size); + if (r < 0) + return log_error_errno(r, "Failed to hash PE binary: %m"); + + r = sd_json_variant_append_arraybo( + &digests, + SD_JSON_BUILD_PAIR_STRING("hashAlg", a), + SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(hash, hash_size))); + if (r < 0) + return log_error_errno(r, "Failed to build JSON digest object: %m"); } - } else if (!have_old_policy) { - r = make_recovery_key(&pin); + r = sd_json_variant_append_arraybo( + &array, + SD_JSON_BUILD_PAIR_UNSIGNED("pcr", i), + SD_JSON_BUILD_PAIR_VARIANT("digests", digests)); if (r < 0) - return log_error_errno(r, "Failed to generate a randomized recovery PIN: %m"); + return log_error_errno(r, "Failed to append record object: %m"); + } - if (recovery_pin_mode == RECOVERY_PIN_SHOW) - printf("%s Selected recovery PIN is: %s%s%s\n", - glyph(GLYPH_LOCK_AND_KEY), - ansi_highlight_cyan(), - pin, - ansi_normal()); + return write_pcrlock(array, NULL); +} + +static int verb_unlock_simple(int argc, char *argv[], uintptr_t _data, void *userdata) { + return unlink_pcrlock(NULL); +} + +typedef void* SectionHashArray[_UNIFIED_SECTION_MAX * TPM2_N_HASH_ALGORITHMS]; + +static void section_hashes_array_done(SectionHashArray *array) { + assert(array); + + for (size_t i = 0; i < _UNIFIED_SECTION_MAX * TPM2_N_HASH_ALGORITHMS; i++) + free((*array)[i]); +} + +static int verb_lock_uki(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL, *pe_digests = NULL; + _cleanup_(section_hashes_array_done) SectionHashArray section_hashes = {}; + size_t hash_sizes[TPM2_N_HASH_ALGORITHMS]; + _cleanup_close_ int fd = -EBADF; + int r; + + if (arg_pcr_mask != 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "PCR not configurable for UKI lock down."); + + if (argc >= 2) { + fd = open(argv[1], O_RDONLY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); } - _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL; - TPM2_HANDLE nv_index = 0; + for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { + _cleanup_free_ void *peh = NULL; + const EVP_MD *md; + const char *a; - r = tpm2_deserialize(tc, &nv_blob, &nv_handle); - if (r < 0) - return log_error_errno(r, "Failed to deserialize NV index TR: %m"); - if (r > 0) - nv_index = old_policy.nv_index; + assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i])); + assert_se(md = EVP_get_digestbyname(a)); - TPM2B_AUTH auth = {}; - CLEANUP_ERASE(auth); + r = pe_hash(fd < 0 ? STDIN_FILENO : fd, md, &peh, hash_sizes + i); + if (r < 0) + return log_error_errno(r, "Failed to hash PE binary: %m"); - if (pin) { - r = tpm2_auth_value_from_pin(TPM2_ALG_SHA256, pin, &auth); + r = sd_json_variant_append_arraybo( + &pe_digests, + SD_JSON_BUILD_PAIR_STRING("hashAlg", a), + SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(peh, hash_sizes[i]))); if (r < 0) - return log_error_errno(r, "Failed to hash PIN: %m"); - } else { - assert(iovec_is_set(&pin_public)); - assert(iovec_is_set(&pin_private)); + return log_error_errno(r, "Failed to build JSON digest object: %m"); - log_debug("Retrieving PIN from sealed data."); + r = uki_hash(fd < 0 ? STDIN_FILENO : fd, md, section_hashes + (i * _UNIFIED_SECTION_MAX), hash_sizes + i); + if (r < 0) + return log_error_errno(r, "Failed to UKI hash PE binary: %m"); + } - usec_t pin_start_usec = now(CLOCK_MONOTONIC); + r = sd_json_variant_append_arraybo( + &array, + SD_JSON_BUILD_PAIR_UNSIGNED("pcr", TPM2_PCR_BOOT_LOADER_CODE), + SD_JSON_BUILD_PAIR_VARIANT("digests", pe_digests)); + if (r < 0) + return log_error_errno(r, "Failed to append record object: %m"); - _cleanup_(iovec_done_erase) struct iovec secret = {}; - for (unsigned attempt = 0;; attempt++) { - _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL; + for (UnifiedSection section = 0; section < _UNIFIED_SECTION_MAX; section++) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *section_digests = NULL, *record = NULL; - r = tpm2_make_policy_session( - tc, - srk_handle, - encryption_session, - &policy_session); - if (r < 0) - return log_error_errno(r, "Failed to allocate policy session: %m"); + if (!unified_section_measure(section)) + continue; - r = tpm2_policy_super_pcr( - tc, - policy_session, - &old_policy.prediction, - old_policy.algorithm); - if (r < 0) - return r; + for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { + const char *a; + void *hash; - r = tpm2_policy_authorize_nv( - tc, - policy_session, - nv_handle, - NULL); - if (r < 0) - return log_error_errno(r, "Failed to submit AuthorizeNV policy: %m"); + hash = section_hashes[i * _UNIFIED_SECTION_MAX + section]; + if (!hash) + continue; - r = tpm2_unseal_data( - tc, - &pin_public, - &pin_private, - srk_handle, - policy_session, - encryption_session, - &secret); - if (r < 0 && (r != -ESTALE || attempt >= 16)) - return log_error_errno(r, "Failed to unseal PIN: %m"); - if (r == 0) - break; + assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i])); - log_debug("Trying again (attempt %u), as PCR values changed during unlock attempt.", attempt+1); + r = sd_json_variant_append_arraybo( + §ion_digests, + SD_JSON_BUILD_PAIR_STRING("hashAlg", a), + SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(hash, hash_sizes[i]))); + if (r < 0) + return log_error_errno(r, "Failed to build JSON digest object: %m"); } - if (secret.iov_len > sizeof_field(TPM2B_AUTH, buffer)) - return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Decrypted PIN too large."); + if (!section_digests) + continue; - auth = (TPM2B_AUTH) { - .size = secret.iov_len, - }; + /* So we have digests for this section, hence generate a record for the section name first. */ + r = make_pcrlock_record(TPM2_PCR_KERNEL_BOOT /* =11 */, unified_sections[section], strlen(unified_sections[section]) + 1, &record); + if (r < 0) + return r; - memcpy_safe(auth.buffer, secret.iov_base, secret.iov_len); + r = sd_json_variant_append_array(&array, record); + if (r < 0) + return log_error_errno(r, "Failed to append JSON record array: %m"); - log_info("Retrieved PIN from TPM2 in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pin_start_usec), 1)); + /* And then append a record for the section contents digests as well */ + r = sd_json_variant_append_arraybo( + &array, + SD_JSON_BUILD_PAIR_UNSIGNED("pcr", TPM2_PCR_KERNEL_BOOT), + SD_JSON_BUILD_PAIR_VARIANT("digests", section_digests)); + if (r < 0) + return log_error_errno(r, "Failed to append record object: %m"); } - /* Now convert the PIN into an HMAC-SHA256 key that we can use in PolicySigned to protect access to the nvindex with */ - _cleanup_(tpm2_handle_freep) Tpm2Handle *pin_handle = NULL; - r = tpm2_hmac_key_from_pin(tc, encryption_session, &auth, &pin_handle); + return write_pcrlock(array, NULL); +} + +static int verb_lock_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL; + _cleanup_free_ char *word = NULL; + int r; + + r = pcrextend_machine_id_word(&word); if (r < 0) return r; - TPM2B_NV_PUBLIC nv_public = {}; - usec_t nv_index_start_usec = now(CLOCK_MONOTONIC); + r = make_pcrlock_record(TPM2_PCR_SYSTEM_IDENTITY /* = 15 */, word, SIZE_MAX, &record); + if (r < 0) + return r; - if (!iovec_is_set(&nv_blob)) { - _cleanup_(Esys_Freep) TPM2B_NAME *pin_name = NULL; - r = tpm2_get_name( - tc, - pin_handle, - &pin_name); - if (r < 0) - return log_error_errno(r, "Failed to get name of PIN from TPM2: %m"); + r = sd_json_variant_new_array(&array, &record, 1); + if (r < 0) + return log_error_errno(r, "Failed to create record array: %m"); - TPM2B_DIGEST recovery_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); - r = tpm2_calculate_policy_signed(&recovery_policy_digest, pin_name); - if (r < 0) - return log_error_errno(r, "Failed to calculate PolicySigned policy: %m"); + return write_pcrlock(array, PCRLOCK_MACHINE_ID_PATH); +} - log_debug("Allocating NV index to write PCR policy to..."); - r = tpm2_define_policy_nv_index( - tc, - encryption_session, - arg_nv_index, - &recovery_policy_digest, - &nv_index, - &nv_handle, - &nv_public); - if (r == -EEXIST) - return log_error_errno(r, "NV index 0x%" PRIx32 " already allocated.", arg_nv_index); - if (r < 0) - return log_error_errno(r, "Failed to allocate NV index: %m"); - } +static int verb_unlock_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) { + return unlink_pcrlock(PCRLOCK_MACHINE_ID_PATH); +} - _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL; - r = tpm2_make_policy_session( - tc, - srk_handle, - encryption_session, - &policy_session); - if (r < 0) - return log_error_errno(r, "Failed to allocate policy session: %m"); +static int pcrlock_file_system_path(const char *normalized_path, char **ret) { + _cleanup_free_ char *s = NULL; - r = tpm2_policy_signed_hmac_sha256( - tc, - policy_session, - pin_handle, - &IOVEC_MAKE(auth.buffer, auth.size), - /* ret_policy_digest= */ NULL); - if (r < 0) - return log_error_errno(r, "Failed to submit authentication value policy: %m"); + assert(normalized_path); + assert(ret); - log_debug("Calculating new PCR policy to write..."); - TPM2B_DIGEST new_super_pcr_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); + if (path_equal(normalized_path, "/")) + s = strdup(PCRLOCK_ROOT_FILE_SYSTEM_PATH); + else { + /* We reuse the escaping we use for turning paths into unit names */ + _cleanup_free_ char *escaped = NULL; - usec_t pcr_policy_start_usec = now(CLOCK_MONOTONIC); + assert(normalized_path[0] == '/'); + assert(normalized_path[1] != '/'); - r = tpm2_calculate_policy_super_pcr( - &new_prediction, - el->primary_algorithm, - &new_super_pcr_policy_digest); - if (r < 0) - return log_error_errno(r, "Failed to calculate super PCR policy: %m"); + escaped = unit_name_escape(normalized_path + 1); + if (!escaped) + return log_oom(); - log_info("Calculated new PCR policy in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pcr_policy_start_usec), 1)); + s = strjoin(PCRLOCK_FILE_SYSTEM_PATH_PREFIX, escaped, ".pcrlock"); + } + if (!s) + return log_oom(); - log_debug("Writing new PCR policy to NV index..."); - r = tpm2_write_policy_nv_index( - tc, - policy_session, - nv_index, - nv_handle, - &new_super_pcr_policy_digest); - if (r < 0) - return log_error_errno(r, "Failed to write to NV index: %m"); + *ret = TAKE_PTR(s); + return 0; +} - log_info("Updated NV index in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), nv_index_start_usec), 1)); +static int verb_lock_file_system(int argc, char *argv[], uintptr_t _data, void *userdata) { + const char* paths[3] = {}; + int r; - assert(iovec_is_set(&pin_public) == iovec_is_set(&pin_private)); - if (!iovec_is_set(&pin_public)) { - TPM2B_DIGEST authnv_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); + if (argc > 1) + paths[0] = argv[1]; + else { + dev_t a, b; + paths[0] = "/"; - r = tpm2_calculate_policy_authorize_nv(&nv_public, &authnv_policy_digest); + r = get_block_device("/", &a); if (r < 0) - return log_error_errno(r, "Failed to calculate AuthorizeNV policy: %m"); - - struct iovec data = { - .iov_base = auth.buffer, - .iov_len = auth.size, - }; - - usec_t pin_seal_start_usec = now(CLOCK_MONOTONIC); + return log_error_errno(r, "Failed to get device of root file system: %m"); - log_debug("Sealing PIN to NV index policy..."); - r = tpm2_seal_data( - tc, - &data, - srk_handle, - encryption_session, - &authnv_policy_digest, - &pin_public, - &pin_private); + r = get_block_device("/var", &b); if (r < 0) - return log_error_errno(r, "Failed to seal PIN to NV auth policy: %m"); + return log_error_errno(r, "Failed to get device of /var/ file system: %m"); + + /* if backing device is distinct, then measure /var/ too */ + if (a != b) + paths[1] = "/var"; - log_info("Sealed PIN in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pin_seal_start_usec), 1)); + enable_json_sse(); } - if (!iovec_is_set(&nv_blob)) { - r = tpm2_serialize(tc, nv_handle, &nv_blob); - if (r < 0) - return log_error_errno(r, "Failed to serialize NV index TR: %m"); - } + STRV_FOREACH(p, paths) { + _cleanup_free_ char *word = NULL, *normalized_path = NULL, *pcrlock_file = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL; - if (!iovec_is_set(&srk_blob)) { - r = tpm2_serialize(tc, srk_handle, &srk_blob); + r = pcrextend_file_system_word(*p, &word, &normalized_path); if (r < 0) - return log_error_errno(r, "Failed to serialize SRK index TR: %m"); - } + return r; - if (!iovec_is_set(&nv_public_blob)) { - r = tpm2_marshal_nv_public(&nv_public, &nv_public_blob.iov_base, &nv_public_blob.iov_len); + r = pcrlock_file_system_path(normalized_path, &pcrlock_file); if (r < 0) - return log_error_errno(r, "Failed to marshal NV public area: %m"); - } - - _cleanup_(sd_json_variant_unrefp) sd_json_variant *new_configuration_json = NULL; - r = sd_json_buildo( - &new_configuration_json, - SD_JSON_BUILD_PAIR_STRING("pcrBank", tpm2_hash_alg_to_string(el->primary_algorithm)), - SD_JSON_BUILD_PAIR_VARIANT("pcrValues", new_prediction_json), - SD_JSON_BUILD_PAIR_INTEGER("nvIndex", nv_index), - JSON_BUILD_PAIR_IOVEC_BASE64("nvHandle", &nv_blob), - JSON_BUILD_PAIR_IOVEC_BASE64("nvPublic", &nv_public_blob), - JSON_BUILD_PAIR_IOVEC_BASE64("srkHandle", &srk_blob), - JSON_BUILD_PAIR_IOVEC_BASE64("pinPublic", &pin_public), - JSON_BUILD_PAIR_IOVEC_BASE64("pinPrivate", &pin_private)); - if (r < 0) - return log_error_errno(r, "Failed to generate JSON: %m"); + return r; - _cleanup_free_ char *text = NULL; - r = sd_json_variant_format(new_configuration_json, 0, &text); - if (r < 0) - return log_error_errno(r, "Failed to format new configuration to JSON: %m"); + r = make_pcrlock_record(TPM2_PCR_SYSTEM_IDENTITY /* = 15 */, word, SIZE_MAX, &record); + if (r < 0) + return r; - const char *path = arg_policy_path ?: (in_initrd() ? "/run/systemd/pcrlock.json" : "/var/lib/systemd/pcrlock.json"); - r = write_string_file(path, text, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_MKDIR_0755|WRITE_STRING_FILE_LABEL); - if (r < 0) - return log_error_errno(r, "Failed to write new configuration to '%s': %m", path); + r = sd_json_variant_new_array(&array, &record, 1); + if (r < 0) + return log_error_errno(r, "Failed to create record array: %m"); - if (!arg_policy_path && !in_initrd()) { - r = remove_policy_file("/run/systemd/pcrlock.json"); + r = write_pcrlock(array, pcrlock_file); if (r < 0) return r; } - log_info("Written new policy to '%s' and digest to TPM2 NV index 0x%x.", path, nv_index); + return 0; +} - (void) write_boot_policy_file(text); +static int verb_unlock_file_system(int argc, char *argv[], uintptr_t _data, void *userdata) { + const char* paths[3] = {}; + int r; - log_info("Overall time spent: %s", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), start_usec), 1)); + if (argc > 1) + paths[0] = argv[1]; + else { + paths[0] = "/"; + paths[1] = "/var"; + } - return 1; /* installed new policy */ -} + STRV_FOREACH(p, paths) { + _cleanup_free_ char *normalized_path = NULL, *pcrlock_file = NULL; -static int verb_make_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { - int r; + r = chase(*p, NULL, 0, &normalized_path, NULL); + if (r < 0) + return log_error_errno(r, "Failed to normal path '%s': %m", argv[1]); - r = make_policy(arg_force, arg_recovery_pin); - if (r < 0) - return r; + r = pcrlock_file_system_path(normalized_path, &pcrlock_file); + if (r < 0) + return r; + + r = unlink_pcrlock(pcrlock_file); + if (r < 0) + return r; + } return 0; } -static int undefine_policy_nv_index( - uint32_t nv_index, - const struct iovec *nv_blob, - const struct iovec *srk_blob) { +static int verb_lock_kernel_cmdline(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL; + _cleanup_free_ char *cmdline = NULL; int r; - assert(nv_blob); - assert(srk_blob); - - _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL; - r = tpm2_context_new_or_warn(/* device= */ NULL, &tc); + if (argc > 1) { + if (empty_or_dash(argv[1])) + r = read_full_stream(stdin, &cmdline, NULL); + else + r = read_full_file(argv[1], &cmdline, NULL); + } else + r = proc_cmdline(&cmdline); if (r < 0) - return r; + return log_error_errno(r, "Failed to read cmdline: %m"); - _cleanup_(tpm2_handle_freep) Tpm2Handle *srk_handle = NULL; - r = tpm2_deserialize( - tc, - srk_blob, - &srk_handle); - if (r < 0) - return log_error_errno(r, "Failed to deserialize SRK TR: %m"); + delete_trailing_chars(cmdline, "\n"); - _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL; - r = tpm2_deserialize( - tc, - nv_blob, - &nv_handle); - if (r < 0) - return log_error_errno(r, "Failed to deserialize NV TR: %m"); + _cleanup_free_ char16_t *u = NULL; + u = utf8_to_utf16(cmdline, SIZE_MAX); + if (!u) + return log_oom(); - _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL; - r = tpm2_make_encryption_session( - tc, - srk_handle, - /* bind_key= */ &TPM2_HANDLE_NONE, - &encryption_session); + r = make_pcrlock_record(TPM2_PCR_KERNEL_INITRD /* = 9 */, u, char16_strlen(u)*2+2, &record); if (r < 0) return r; - r = tpm2_undefine_nv_index( - tc, - encryption_session, - nv_index, - nv_handle); + r = sd_json_variant_new_array(&array, &record, 1); + if (r < 0) + return log_error_errno(r, "Failed to create record array: %m"); + + r = write_pcrlock(array, PCRLOCK_KERNEL_CMDLINE_PATH); if (r < 0) return r; - log_info("Removed NV index 0x%x", nv_index); return 0; } -static int remove_policy(void) { - int ret = 0, r; - - _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy policy = {}; - r = tpm2_pcrlock_policy_load(arg_policy_path, &policy); - if (r == 0) { - log_info("No policy found."); - return 0; - } +static int verb_unlock_kernel_cmdline(int argc, char *argv[], uintptr_t _data, void *userdata) { + return unlink_pcrlock(PCRLOCK_KERNEL_CMDLINE_PATH); +} - if (r < 0) - log_notice("Failed to load old policy file, assuming it is corrupted, removing."); - else { - r = undefine_policy_nv_index(policy.nv_index, &policy.nv_handle, &policy.srk_handle); - if (r < 0) - log_notice("Failed to remove NV index, assuming data out of date, removing policy file."); +static int verb_lock_kernel_initrd(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *records = NULL; + _cleanup_fclose_ FILE *f = NULL; + uint32_t pcr_mask = UINT32_C(1) << TPM2_PCR_KERNEL_INITRD; + int r; - RET_GATHER(ret, r); + if (argc >= 2) { + f = fopen(argv[1], "re"); + if (!f) + return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); } - if (arg_policy_path) - RET_GATHER(ret, remove_policy_file(arg_policy_path)); - else { - RET_GATHER(ret, remove_policy_file("/var/lib/systemd/pcrlock.json")); - RET_GATHER(ret, remove_policy_file("/run/systemd/pcrlock.json")); - } + r = make_pcrlock_record_from_stream(pcr_mask, f ?: stdin, &records); + if (r < 0) + return r; - _cleanup_free_ char *boot_policy_file = NULL; - r = determine_boot_policy_file(&boot_policy_file, /* ret_credential_name= */ NULL); - if (r == 0) - log_info("Did not find XBOOTLDR/ESP partition, not removing boot policy file."); - else if (r > 0) { - RET_GATHER(ret, remove_policy_file(boot_policy_file)); - } else - RET_GATHER(ret, r); + r = write_pcrlock(records, PCRLOCK_KERNEL_INITRD_PATH); + if (r < 0) + return r; - return ret; + return 0; } -static int verb_remove_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { - return remove_policy(); +static int verb_unlock_kernel_initrd(int argc, char *argv[], uintptr_t _data, void *userdata) { + return unlink_pcrlock(PCRLOCK_KERNEL_INITRD_PATH); } -static int test_tpm2_support_pcrlock(Tpm2Support *ret) { +static int verb_lock_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *records = NULL; + _cleanup_fclose_ FILE *f = NULL; int r; - assert(ret); - - /* First check basic support */ - Tpm2Support s = tpm2_support(); - - /* If basic support is available, let's also check the things we need for systemd-pcrlock */ - if (s == TPM2_SUPPORT_FULL) { - _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL; - r = tpm2_context_new_or_warn(/* device= */ NULL, &tc); - if (r < 0) - return r; - - /* We strictly need TPM2_CC_PolicyAuthorizeNV for systemd-pcrlock to work */ - SET_FLAG(s, TPM2_SUPPORT_AUTHORIZE_NV, tpm2_supports_command(tc, TPM2_CC_PolicyAuthorizeNV)); - - log_debug("PolicyAuthorizeNV supported: %s", yes_no(FLAGS_SET(s, TPM2_SUPPORT_AUTHORIZE_NV))); - - /* We also strictly need SHA-256 to work */ - SET_FLAG(s, TPM2_SUPPORT_SHA256, tpm2_supports_alg(tc, TPM2_ALG_SHA256)); + if (arg_pcr_mask == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No PCR specified, refusing."); - log_debug("SHA-256 supported: %s", yes_no(FLAGS_SET(s, TPM2_SUPPORT_SHA256))); + if (argc >= 2) { + f = fopen(argv[1], "re"); + if (!f) + return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); } - *ret = s; - return 0; -} - -static int verb_is_supported(int argc, char *argv[], uintptr_t _data, void *userdata) { - int r; - - Tpm2Support s; - r = test_tpm2_support_pcrlock(&s); + r = make_pcrlock_record_from_stream(arg_pcr_mask, f ?: stdin, &records); if (r < 0) return r; - if (!arg_quiet) { - if (s == (TPM2_SUPPORT_FULL|TPM2_SUPPORT_API_PCRLOCK)) - printf("%syes%s\n", ansi_green(), ansi_normal()); - else if (FLAGS_SET(s, TPM2_SUPPORT_FULL)) - printf("%sobsolete%s\n", ansi_red(), ansi_normal()); - else if (s == TPM2_SUPPORT_NONE) - printf("%sno%s\n", ansi_red(), ansi_normal()); - else - printf("%spartial%s\n", ansi_yellow(), ansi_normal()); - } - - assert_cc((TPM2_SUPPORT_API|TPM2_SUPPORT_API_PCRLOCK) <= 255); /* make sure this is safe to use as process exit status */ - - return ~s & (TPM2_SUPPORT_API|TPM2_SUPPORT_API_PCRLOCK); + return write_pcrlock(records, NULL); } static int help(void) { From 33061044685a449dd98c747a371c530d29afbf98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 23:07:11 +0200 Subject: [PATCH 1194/1296] pcrlock: convert to the new option and verb parsers The VERB definitions are done in order to retain the logical presentation of verbs in lock+unlock pairs. Previously --help output was too wide, it now fits in 80 columns. Cosmetic changes in --help output only. Co-developed-by: Claude Opus 4.7 --- src/pcrlock/pcrlock.c | 343 ++++++++++++++++++++---------------------- 1 file changed, 159 insertions(+), 184 deletions(-) diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index 7820774fe08db..62a84a26cb688 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -38,6 +37,7 @@ #include "list.h" #include "main-func.h" #include "mkdir-label.h" +#include "options.h" #include "ordered-set.h" #include "parse-argument.h" #include "parse-util.h" @@ -2500,6 +2500,8 @@ static int event_log_load_and_process(EventLog **ret) { return 0; } +VERB(verb_show_log, "log", NULL, VERB_ANY, 1, VERB_DEFAULT, + "Show measurement log"); static int verb_show_log(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *log_table = NULL, *pcr_table = NULL; _cleanup_(event_log_freep) EventLog *el = NULL; @@ -2613,6 +2615,8 @@ static int event_log_record_to_cel(EventLogRecord *record, uint64_t *recnum, sd_ return 0; } +VERB_NOARG(verb_show_cel, "cel", + "Show measurement log in TCG CEL-JSON format"); static int verb_show_cel(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; _cleanup_(event_log_freep) EventLog *el = NULL; @@ -2648,6 +2652,8 @@ static int verb_show_cel(int argc, char *argv[], uintptr_t _data, void *userdata return 0; } +VERB_NOARG(verb_list_components, "list-components", + "List defined .pcrlock components"); static int verb_list_components(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(event_log_freep) EventLog *el = NULL; _cleanup_(table_unrefp) Table *table = NULL; @@ -3348,6 +3354,8 @@ static int tpm2_pcr_prediction_run( return 0; } +VERB_NOARG(verb_predict, "predict", + "Predict PCR values"); static int verb_predict(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(tpm2_pcr_prediction_done) Tpm2PCRPrediction context = { arg_pcr_mask != 0 ? arg_pcr_mask : DEFAULT_PCR_MASK, @@ -3927,6 +3935,8 @@ static int make_policy(bool force, RecoveryPinMode recovery_pin_mode) { return 1; /* installed new policy */ } +VERB_NOARG(verb_make_policy, "make-policy", + "Predict PCR values and generate TPM2 policy from it"); static int verb_make_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -4027,6 +4037,8 @@ static int remove_policy(void) { return ret; } +VERB_NOARG(verb_remove_policy, "remove-policy", + "Remove TPM2 policy"); static int verb_remove_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { return remove_policy(); } @@ -4061,6 +4073,8 @@ static int test_tpm2_support_pcrlock(Tpm2Support *ret) { return 0; } +VERB_NOARG(verb_is_supported, "is-supported", + "Tests if TPM2 supports necessary features"); static int verb_is_supported(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -4140,6 +4154,10 @@ static bool event_log_record_is_separator(const EventLogRecord *rec) { return rec->event_payload_valid == EVENT_PAYLOAD_VALID_YES; /* Insist the record is consistent */ } +VERB_GROUP("Protections"); + +VERB(verb_lock_firmware, "lock-firmware-code", NULL, VERB_ANY, 2, 0, + "Generate a .pcrlock file from current firmware code"); static int verb_lock_firmware(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *array_early = NULL, *array_late = NULL; _cleanup_(event_log_freep) EventLog *el = NULL; @@ -4259,6 +4277,8 @@ static int verb_lock_firmware(int argc, char *argv[], uintptr_t _data, void *use return write_pcrlock(array_late, default_pcrlock_late_path); } +VERB_NOARG(verb_unlock_firmware, "unlock-firmware-code", + "Remove .pcrlock file for firmware code"); static int verb_unlock_firmware(int argc, char *argv[], uintptr_t _data, void *userdata) { const char *default_pcrlock_early_path, *default_pcrlock_late_path; int r; @@ -4285,6 +4305,14 @@ static int verb_unlock_firmware(int argc, char *argv[], uintptr_t _data, void *u return 0; } +VERB(verb_lock_firmware, "lock-firmware-config", NULL, VERB_ANY, 2, 0, + "Generate a .pcrlock file from current firmware configuration"); + +VERB_NOARG(verb_unlock_firmware, "unlock-firmware-config", + "Remove .pcrlock file for firmware configuration"); + +VERB_NOARG(verb_lock_secureboot_policy, "lock-secureboot-policy", + "Generate a .pcrlock file from current SecureBoot policy"); static int verb_lock_secureboot_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { static const struct { sd_id128_t id; @@ -4358,6 +4386,8 @@ static int verb_lock_secureboot_policy(int argc, char *argv[], uintptr_t _data, return write_pcrlock(array, PCRLOCK_SECUREBOOT_POLICY_PATH); } +VERB_NOARG(verb_unlock_secureboot_policy, "unlock-secureboot-policy", + "Remove .pcrlock file for SecureBoot policy"); static int verb_unlock_secureboot_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { return unlink_pcrlock(PCRLOCK_SECUREBOOT_POLICY_PATH); } @@ -4479,6 +4509,8 @@ static int event_log_ensure_secureboot_consistency(EventLog *el) { return 0; } +VERB_NOARG(verb_lock_secureboot_authority, "lock-secureboot-authority", + "Generate a .pcrlock file from current SecureBoot authority"); static int verb_lock_secureboot_authority(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; _cleanup_(event_log_freep) EventLog *el = NULL; @@ -4558,10 +4590,14 @@ static int verb_lock_secureboot_authority(int argc, char *argv[], uintptr_t _dat return write_pcrlock(array, PCRLOCK_SECUREBOOT_AUTHORITY_PATH); } +VERB_NOARG(verb_unlock_secureboot_authority, "unlock-secureboot-authority", + "Remove .pcrlock file for SecureBoot authority"); static int verb_unlock_secureboot_authority(int argc, char *argv[], uintptr_t _data, void *userdata) { return unlink_pcrlock(PCRLOCK_SECUREBOOT_AUTHORITY_PATH); } +VERB(verb_lock_gpt, "lock-gpt", "[DISK]", VERB_ANY, 2, 0, + "Generate a .pcrlock file from GPT header"); static int verb_lock_gpt(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL, *record = NULL; _cleanup_(sd_device_unrefp) sd_device *d = NULL; @@ -4675,10 +4711,14 @@ static int verb_lock_gpt(int argc, char *argv[], uintptr_t _data, void *userdata return write_pcrlock(array, PCRLOCK_GPT_PATH); } +VERB_NOARG(verb_unlock_gpt, "unlock-gpt", + "Remove .pcrlock file for GPT header"); static int verb_unlock_gpt(int argc, char *argv[], uintptr_t _data, void *userdata) { return unlink_pcrlock(PCRLOCK_GPT_PATH); } +VERB(verb_lock_pe, "lock-pe", "[BINARY]", VERB_ANY, 2, 0, + "Generate a .pcrlock file from PE binary"); static int verb_lock_pe(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; _cleanup_close_ int fd = -EBADF; @@ -4734,6 +4774,8 @@ static int verb_lock_pe(int argc, char *argv[], uintptr_t _data, void *userdata) return write_pcrlock(array, NULL); } +VERB_NOARG(verb_unlock_simple, "unlock-pe", + "Remove .pcrlock file for PE binary"); static int verb_unlock_simple(int argc, char *argv[], uintptr_t _data, void *userdata) { return unlink_pcrlock(NULL); } @@ -4747,6 +4789,8 @@ static void section_hashes_array_done(SectionHashArray *array) { free((*array)[i]); } +VERB(verb_lock_uki, "lock-uki", "[UKI]", VERB_ANY, 2, 0, + "Generate a .pcrlock file from UKI PE binary"); static int verb_lock_uki(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL, *pe_digests = NULL; _cleanup_(section_hashes_array_done) SectionHashArray section_hashes = {}; @@ -4842,6 +4886,11 @@ static int verb_lock_uki(int argc, char *argv[], uintptr_t _data, void *userdata return write_pcrlock(array, NULL); } +VERB_NOARG(verb_unlock_simple, "unlock-uki", + "Remove .pcrlock file for UKI PE binary"); + +VERB_NOARG(verb_lock_machine_id, "lock-machine-id", + "Generate a .pcrlock file from current machine ID"); static int verb_lock_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL; _cleanup_free_ char *word = NULL; @@ -4862,6 +4911,8 @@ static int verb_lock_machine_id(int argc, char *argv[], uintptr_t _data, void *u return write_pcrlock(array, PCRLOCK_MACHINE_ID_PATH); } +VERB_NOARG(verb_unlock_machine_id, "unlock-machine-id", + "Remove .pcrlock file for machine ID"); static int verb_unlock_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) { return unlink_pcrlock(PCRLOCK_MACHINE_ID_PATH); } @@ -4894,6 +4945,8 @@ static int pcrlock_file_system_path(const char *normalized_path, char **ret) { return 0; } +VERB(verb_lock_file_system, "lock-file-system", "[PATH]", VERB_ANY, 2, 0, + "Generate a .pcrlock file from current root fs + /var/"); static int verb_lock_file_system(int argc, char *argv[], uintptr_t _data, void *userdata) { const char* paths[3] = {}; int r; @@ -4947,6 +5000,8 @@ static int verb_lock_file_system(int argc, char *argv[], uintptr_t _data, void * return 0; } +VERB(verb_unlock_file_system, "unlock-file-system", "[PATH]", VERB_ANY, 2, 0, + "Remove .pcrlock file for root fs + /var/"); static int verb_unlock_file_system(int argc, char *argv[], uintptr_t _data, void *userdata) { const char* paths[3] = {}; int r; @@ -4977,6 +5032,8 @@ static int verb_unlock_file_system(int argc, char *argv[], uintptr_t _data, void return 0; } +VERB(verb_lock_kernel_cmdline, "lock-kernel-cmdline", "[FILE]", VERB_ANY, 2, 0, + "Generate a .pcrlock file from kernel command line"); static int verb_lock_kernel_cmdline(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL; _cleanup_free_ char *cmdline = NULL; @@ -5014,10 +5071,14 @@ static int verb_lock_kernel_cmdline(int argc, char *argv[], uintptr_t _data, voi return 0; } +VERB_NOARG(verb_unlock_kernel_cmdline, "unlock-kernel-cmdline", + "Remove .pcrlock file for kernel command line"); static int verb_unlock_kernel_cmdline(int argc, char *argv[], uintptr_t _data, void *userdata) { return unlink_pcrlock(PCRLOCK_KERNEL_CMDLINE_PATH); } +VERB(verb_lock_kernel_initrd, "lock-kernel-initrd", "FILE", VERB_ANY, 2, 0, + "Generate a .pcrlock file from an initrd file"); static int verb_lock_kernel_initrd(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *records = NULL; _cleanup_fclose_ FILE *f = NULL; @@ -5041,10 +5102,14 @@ static int verb_lock_kernel_initrd(int argc, char *argv[], uintptr_t _data, void return 0; } +VERB_NOARG(verb_unlock_kernel_initrd, "unlock-kernel-initrd", + "Remove .pcrlock file for an initrd file"); static int verb_unlock_kernel_initrd(int argc, char *argv[], uintptr_t _data, void *userdata) { return unlink_pcrlock(PCRLOCK_KERNEL_INITRD_PATH); } +VERB(verb_lock_raw, "lock-raw", "[FILE]", VERB_ANY, 2, 0, + "Generate a .pcrlock file from raw data"); static int verb_lock_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *records = NULL; _cleanup_fclose_ FILE *f = NULL; @@ -5068,162 +5133,108 @@ static int verb_lock_raw(int argc, char *argv[], uintptr_t _data, void *userdata static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *commands = NULL, *protections = NULL, *options = NULL; int r; r = terminal_urlify_man("systemd-pcrlock", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n%5$sManage a TPM2 PCR lock.%6$s\n" - "\n%3$sCommands:%4$s\n" - " log Show measurement log\n" - " cel Show measurement log in TCG CEL-JSON format\n" - " list-components List defined .pcrlock components\n" - " predict Predict PCR values\n" - " make-policy Predict PCR values and generate TPM2 policy from it\n" - " remove-policy Remove TPM2 policy\n" - " is-supported Tests if TPM2 supports necessary features\n" - "\n%3$sProtections:%4$s\n" - " lock-firmware-code Generate a .pcrlock file from current firmware code\n" - " unlock-firmware-code Remove .pcrlock file for firmware code\n" - " lock-firmware-config Generate a .pcrlock file from current firmware configuration\n" - " unlock-firmware-config Remove .pcrlock file for firmware configuration\n" - " lock-secureboot-policy Generate a .pcrlock file from current SecureBoot policy\n" - " unlock-secureboot-policy Remove .pcrlock file for SecureBoot policy\n" - " lock-secureboot-authority Generate a .pcrlock file from current SecureBoot authority\n" - " unlock-secureboot-authority Remove .pcrlock file for SecureBoot authority\n" - " lock-gpt [DISK] Generate a .pcrlock file from GPT header\n" - " unlock-gpt Remove .pcrlock file for GPT header\n" - " lock-pe [BINARY] Generate a .pcrlock file from PE binary\n" - " unlock-pe Remove .pcrlock file for PE binary\n" - " lock-uki [UKI] Generate a .pcrlock file from UKI PE binary\n" - " unlock-uki Remove .pcrlock file for UKI PE binary\n" - " lock-machine-id Generate a .pcrlock file from current machine ID\n" - " unlock-machine-id Remove .pcrlock file for machine ID\n" - " lock-file-system [PATH] Generate a .pcrlock file from current root fs + /var/\n" - " unlock-file-system [PATH] Remove .pcrlock file for root fs + /var/\n" - " lock-kernel-cmdline [FILE] Generate a .pcrlock file from kernel command line\n" - " unlock-kernel-cmdline Remove .pcrlock file for kernel command line\n" - " lock-kernel-initrd FILE Generate a .pcrlock file from an initrd file\n" - " unlock-kernel-initrd Remove .pcrlock file for an initrd file\n" - " lock-raw [FILE] Generate a .pcrlock file from raw data\n" - " unlock-raw Remove .pcrlock file for raw data\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version\n" - " --no-pager Do not pipe output into a pager\n" - " --json=pretty|short|off Generate JSON output\n" - " --raw-description Show raw firmware record data as description in table\n" - " --pcr=NR Generate .pcrlock for specified PCR\n" - " --nv-index=NUMBER Use the specified NV index, instead of a random one\n" - " --components=PATH Directory to read .pcrlock files from\n" - " --location=STRING[:STRING]\n" - " Do not process components beyond this component name\n" - " --recovery-pin=MODE Controls whether to show, hide, or ask for a recovery PIN\n" - " --pcrlock=PATH .pcrlock file to write expected PCR measurement to\n" - " --policy=PATH JSON file to write policy output to\n" - " --force Write policy even if it matches existing policy\n" - " --entry-token=machine-id|os-id|os-image-id|auto|literal:…\n" - " Boot entry token to use for this installation\n" - " -q --quiet Suppress unnecessary output\n" - "\nSee the %2$s for details.\n", + r = verbs_get_help_table(&commands); + if (r < 0) + return r; + + r = verbs_get_help_table_group("Protections", &protections); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, commands, protections, options); + + printf("%s [OPTIONS...] COMMAND ...\n" + "\n%sManage a TPM2 PCR lock.%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), ansi_normal()); - return 0; -} + printf("\n%sCommands:%s\n", ansi_underline(), ansi_normal()); + r = table_print_or_warn(commands); + if (r < 0) + return r; -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} + printf("\n%sProtections:%s\n", ansi_underline(), ansi_normal()); + r = table_print_or_warn(protections); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_JSON, - ARG_RAW_DESCRIPTION, - ARG_PCR, - ARG_NV_INDEX, - ARG_COMPONENTS, - ARG_LOCATION, - ARG_RECOVERY_PIN, - ARG_PCRLOCK, - ARG_POLICY, - ARG_FORCE, - ARG_ENTRY_TOKEN, - }; + printf("\n%sOptions:%s\n", ansi_underline(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "json", required_argument, NULL, ARG_JSON }, - { "raw-description", no_argument, NULL, ARG_RAW_DESCRIPTION }, - { "pcr", required_argument, NULL, ARG_PCR }, - { "nv-index", required_argument, NULL, ARG_NV_INDEX }, - { "components", required_argument, NULL, ARG_COMPONENTS }, - { "location", required_argument, NULL, ARG_LOCATION }, - { "recovery-pin", required_argument, NULL, ARG_RECOVERY_PIN }, - { "pcrlock", required_argument, NULL, ARG_PCRLOCK }, - { "policy", required_argument, NULL, ARG_POLICY }, - { "force", no_argument, NULL, ARG_FORCE }, - { "entry-token", required_argument, NULL, ARG_ENTRY_TOKEN }, - { "quiet", no_argument, NULL, 'q' }, - {} - }; + printf("\nSee the %s for details.\n", link); + return 0; +} - bool auto_location = true; - int c, r; +VERB_NOARG(verb_unlock_simple, "unlock-raw", + "Remove .pcrlock file for raw data"); + +VERB_COMMON_HELP_HIDDEN(help); +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); + + OptionParser state = { argc, argv }; + const char *arg; + bool auto_location = true; + int r; - while ((c = getopt_long(argc, argv, "hq", options, NULL)) >= 0) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); if (r <= 0) return r; break; - case ARG_RAW_DESCRIPTION: + OPTION_LONG("raw-description", NULL, + "Show raw firmware record data as description in table"): arg_raw_description = true; break; - case ARG_PCR: { - r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_pcr_mask); + OPTION_LONG("pcr", "NR", + "Generate .pcrlock for specified PCR"): + r = tpm2_parse_pcr_argument_to_mask(arg, &arg_pcr_mask); if (r < 0) - return log_error_errno(r, "Failed to parse PCR specification: %s", optarg); - + return log_error_errno(r, "Failed to parse PCR specification: %s", arg); break; - } - case ARG_NV_INDEX: - if (isempty(optarg)) + OPTION_LONG("nv-index", "NUMBER", + "Use the specified NV index, instead of a random one"): + if (isempty(arg)) arg_nv_index = 0; else { uint32_t u; - r = safe_atou32_full(optarg, 16, &u); + r = safe_atou32_full(arg, 16, &u); if (r < 0) - return log_error_errno(r, "Failed to parse --nv-index= argument: %s", optarg); + return log_error_errno(r, "Failed to parse --nv-index= argument: %s", arg); if (u < TPM2_NV_INDEX_FIRST || u > TPM2_NV_INDEX_LAST) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument for --nv-index= outside of valid range 0x%" PRIx32 "…0x%" PRIx32 ": 0x%" PRIx32, @@ -5233,10 +5244,11 @@ static int parse_argv(int argc, char *argv[]) { } break; - case ARG_COMPONENTS: { + OPTION_LONG("components", "PATH", + "Directory to read .pcrlock files from"): { _cleanup_free_ char *p = NULL; - r = parse_path_argument(optarg, /* suppress_root= */ false, &p); + r = parse_path_argument(arg, /* suppress_root= */ false, &p); if (r < 0) return r; @@ -5247,21 +5259,22 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_LOCATION: { + OPTION_LONG("location", "START[:END]", + "Do not process components beyond this component name"): { _cleanup_free_ char *start = NULL, *end = NULL; const char *e; auto_location = false; - if (isempty(optarg)) { + if (isempty(arg)) { arg_location_start = mfree(arg_location_start); arg_location_end = mfree(arg_location_end); break; } - e = strchr(optarg, ':'); + e = strchr(arg, ':'); if (e) { - start = strndup(optarg, e - optarg); + start = strndup(arg, e - arg); if (!start) return log_oom(); @@ -5269,11 +5282,11 @@ static int parse_argv(int argc, char *argv[]) { if (!end) return log_oom(); } else { - start = strdup(optarg); + start = strdup(arg); if (!start) return log_oom(); - end = strdup(optarg); + end = strdup(arg); if (!end) return log_oom(); } @@ -5288,17 +5301,19 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_RECOVERY_PIN: - arg_recovery_pin = recovery_pin_mode_from_string(optarg); + OPTION_LONG("recovery-pin", "MODE", + "Controls whether to show, hide, or ask for a recovery PIN"): + arg_recovery_pin = recovery_pin_mode_from_string(arg); if (arg_recovery_pin < 0) - return log_error_errno(arg_recovery_pin, "Failed to parse --recovery-pin= mode: %s", optarg); + return log_error_errno(arg_recovery_pin, "Failed to parse --recovery-pin= mode: %s", arg); break; - case ARG_PCRLOCK: - if (empty_or_dash(optarg)) + OPTION_LONG("pcrlock", "PATH", + ".pcrlock file to write expected PCR measurement to"): + if (empty_or_dash(arg)) arg_pcrlock_path = mfree(arg_pcrlock_path); else { - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_pcrlock_path); + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_pcrlock_path); if (r < 0) return r; } @@ -5306,36 +5321,34 @@ static int parse_argv(int argc, char *argv[]) { arg_pcrlock_auto = false; break; - case ARG_POLICY: - if (empty_or_dash(optarg)) + OPTION_LONG("policy", "PATH", + "JSON file to write policy output to"): + if (empty_or_dash(arg)) arg_policy_path = mfree(arg_policy_path); else { - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_policy_path); + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_policy_path); if (r < 0) return r; } break; - case ARG_FORCE: + OPTION_LONG("force", NULL, + "Write policy even if it matches existing policy"): arg_force = true; break; - case ARG_ENTRY_TOKEN: - r = parse_boot_entry_token_type(optarg, &arg_entry_token_type, &arg_entry_token); + OPTION_LONG("entry-token", "TOKEN", + "Boot entry token to use for this installation " + "(machine-id, os-id, os-image-id, auto, literal:…)"): + r = parse_boot_entry_token_type(arg, &arg_entry_token_type, &arg_entry_token); if (r < 0) return r; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress unnecessary output"): arg_quiet = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (auto_location) { @@ -5359,49 +5372,10 @@ static int parse_argv(int argc, char *argv[]) { arg_pager_flags |= PAGER_DISABLE; } + *ret_args = option_parser_get_args(&state); return 1; } -static int pcrlock_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "log", VERB_ANY, 1, VERB_DEFAULT, verb_show_log }, - { "cel", VERB_ANY, 1, 0, verb_show_cel }, - { "list-components", VERB_ANY, 1, 0, verb_list_components }, - { "predict", VERB_ANY, 1, 0, verb_predict }, - { "lock-firmware-code", VERB_ANY, 2, 0, verb_lock_firmware }, - { "unlock-firmware-code", VERB_ANY, 1, 0, verb_unlock_firmware }, - { "lock-firmware-config", VERB_ANY, 2, 0, verb_lock_firmware }, - { "unlock-firmware-config", VERB_ANY, 1, 0, verb_unlock_firmware }, - { "lock-secureboot-policy", VERB_ANY, 1, 0, verb_lock_secureboot_policy }, - { "unlock-secureboot-policy", VERB_ANY, 1, 0, verb_unlock_secureboot_policy }, - { "lock-secureboot-authority", VERB_ANY, 1, 0, verb_lock_secureboot_authority }, - { "unlock-secureboot-authority", VERB_ANY, 1, 0, verb_unlock_secureboot_authority }, - { "lock-gpt", VERB_ANY, 2, 0, verb_lock_gpt }, - { "unlock-gpt", VERB_ANY, 1, 0, verb_unlock_gpt }, - { "lock-pe", VERB_ANY, 2, 0, verb_lock_pe }, - { "unlock-pe", VERB_ANY, 1, 0, verb_unlock_simple }, - { "lock-uki", VERB_ANY, 2, 0, verb_lock_uki }, - { "unlock-uki", VERB_ANY, 1, 0, verb_unlock_simple }, - { "lock-machine-id", VERB_ANY, 1, 0, verb_lock_machine_id }, - { "unlock-machine-id", VERB_ANY, 1, 0, verb_unlock_machine_id }, - { "lock-file-system", VERB_ANY, 2, 0, verb_lock_file_system }, - { "unlock-file-system", VERB_ANY, 2, 0, verb_unlock_file_system }, - { "lock-kernel-cmdline", VERB_ANY, 2, 0, verb_lock_kernel_cmdline }, - { "unlock-kernel-cmdline", VERB_ANY, 1, 0, verb_unlock_kernel_cmdline }, - { "lock-kernel-initrd", VERB_ANY, 2, 0, verb_lock_kernel_initrd }, - { "unlock-kernel-initrd", VERB_ANY, 1, 0, verb_unlock_kernel_initrd }, - { "lock-raw", VERB_ANY, 2, 0, verb_lock_raw }, - { "unlock-raw", VERB_ANY, 1, 0, verb_unlock_simple }, - { "make-policy", VERB_ANY, 1, 0, verb_make_policy }, - { "remove-policy", VERB_ANY, 1, 0, verb_remove_policy }, - { "is-supported", VERB_ANY, 1, 0, verb_is_supported }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int vl_method_read_event_log(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { _cleanup_(event_log_freep) EventLog *el = NULL; uint64_t recnum = 0; @@ -5493,7 +5467,8 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -5525,7 +5500,7 @@ static int run(int argc, char *argv[]) { return EXIT_SUCCESS; } - return pcrlock_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); From f5a99402d84cf44a14e6aedbba30333b28e94324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 23:29:42 +0200 Subject: [PATCH 1195/1296] test: enable check-{help,version}-systemd-pcrlock This is a normal user-facing program, so it should be tested in the usual fashion. --- src/pcrlock/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pcrlock/meson.build b/src/pcrlock/meson.build index ff2b0f0cb2419..c5b609ed4aa67 100644 --- a/src/pcrlock/meson.build +++ b/src/pcrlock/meson.build @@ -16,6 +16,7 @@ executables += [ libopenssl, tpm2, ], + 'public' : true, }, ] From 14c7014d7faa21ae8558982acfa2a45500ba3fb7 Mon Sep 17 00:00:00 2001 From: vlefebvre Date: Wed, 22 Apr 2026 17:36:04 +0200 Subject: [PATCH 1196/1296] mkosi: user and group bin needed for a test * Fix the test TEST-02-UNITTESTS for openSUSE environment. --- mkosi/mkosi.conf.d/opensuse/mkosi.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/mkosi/mkosi.conf.d/opensuse/mkosi.conf b/mkosi/mkosi.conf.d/opensuse/mkosi.conf index 1198d2c15c4cc..5abebc04cf18b 100644 --- a/mkosi/mkosi.conf.d/opensuse/mkosi.conf +++ b/mkosi/mkosi.conf.d/opensuse/mkosi.conf @@ -85,6 +85,7 @@ Packages= softhsm squashfs stress-ng + system-user-bin tgt timezone tpm2.0-tools From f6583875057bd2f1e53a4fc00f6e8f817b0931bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 22 Apr 2026 14:37:28 +0200 Subject: [PATCH 1197/1296] test-chase-manual: convert to the new option parser --help now has help strings. --no_autofs is renamed to --no-autofs. Co-developed-by: Claude Opus 4.7 --- src/test/test-chase-manual.c | 120 ++++++++++++++++------------------- 1 file changed, 56 insertions(+), 64 deletions(-) diff --git a/src/test/test-chase-manual.c b/src/test/test-chase-manual.c index 376cd12c42628..0e760aeb89676 100644 --- a/src/test/test-chase-manual.c +++ b/src/test/test-chase-manual.c @@ -1,84 +1,75 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include "chase.h" #include "fd-util.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" +#include "strv.h" #include "tests.h" -static char *arg_root = NULL; +static const char *arg_root = NULL; static int arg_flags = 0; static bool arg_open = false; -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_ROOT = 0x1000, - ARG_OPEN, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "root", required_argument, NULL, ARG_ROOT }, - { "open", no_argument, NULL, ARG_OPEN }, - - { "prefix-root", no_argument, NULL, CHASE_PREFIX_ROOT }, - { "nonexistent", no_argument, NULL, CHASE_NONEXISTENT }, - { "no_autofs", no_argument, NULL, CHASE_NO_AUTOFS }, - { "trigger-autofs", no_argument, NULL, CHASE_TRIGGER_AUTOFS }, - { "safe", no_argument, NULL, CHASE_SAFE }, - { "trail-slash", no_argument, NULL, CHASE_TRAIL_SLASH }, - { "step", no_argument, NULL, CHASE_STEP }, - { "nofollow", no_argument, NULL, CHASE_NOFOLLOW }, - { "warn", no_argument, NULL, CHASE_WARN }, - {} - }; - - int c; - - assert_se(argc >= 0); - assert_se(argv); - - while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0) +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] path...\n" + "\nExercise chase() function on specified paths.\n\n", + program_invocation_short_name); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + return 0; +} + +static int parse_argv(int argc, char *argv[], char ***ret_args) { + assert(argc >= 0); + assert(argv); + assert(ret_args); + + OptionParser state = { argc, argv }; + const Option *opt; + const char *arg; + + FOREACH_OPTION_FULL(&state, c, &opt, &arg, /* on_error= */ return c) switch (c) { - case 'h': - printf("Syntax:\n" - " %s [OPTION...] path...\n" - "Options:\n" - , argv[0]); - FOREACH_ARRAY(option, options, ELEMENTSOF(options) - 1) - printf(" --%s\n", option->name); - return 0; - - case ARG_ROOT: - arg_root = optarg; + OPTION_COMMON_HELP: + return help(); + + OPTION_LONG("root", "PATH", "Operate below specified root directory"): + arg_root = arg; break; - case ARG_OPEN: + OPTION_LONG("open", NULL, "Open the resolved path"): arg_open = true; break; - case CHASE_PREFIX_ROOT: - case CHASE_NONEXISTENT: - case CHASE_NO_AUTOFS: - case CHASE_TRIGGER_AUTOFS: - case CHASE_SAFE: - case CHASE_TRAIL_SLASH: - case CHASE_STEP: - case CHASE_NOFOLLOW: - case CHASE_WARN: - arg_flags |= c; + OPTION_LONG_DATA("prefix-root", NULL, CHASE_PREFIX_ROOT, "Prefix path with --root"): {} + OPTION_LONG_DATA("nonexistent", NULL, CHASE_NONEXISTENT, "Allow path to not exist"): {} + OPTION_LONG_DATA("no-autofs", NULL, CHASE_NO_AUTOFS, "Return -EREMOTE if autofs mount point found"): {} + OPTION_LONG_DATA("trigger-autofs", NULL, CHASE_TRIGGER_AUTOFS, "Trigger autofs mounts"): {} + OPTION_LONG_DATA("safe", NULL, CHASE_SAFE, "Refuse privilege boundary crossings"): {} + OPTION_LONG_DATA("trail-slash", NULL, CHASE_TRAIL_SLASH, "Preserve trailing slash"): {} + OPTION_LONG_DATA("step", NULL, CHASE_STEP, "Execute a single normalization step"): {} + OPTION_LONG_DATA("nofollow", NULL, CHASE_NOFOLLOW, "Do not follow the path's right-most component"): {} + OPTION_LONG_DATA("warn", NULL, CHASE_WARN, "Emit a warning on error"): + arg_flags |= opt->data; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind == argc) + *ret_args = option_parser_get_args(&state); + if (strv_isempty(*ret_args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "At least one argument is required."); return 1; @@ -89,18 +80,19 @@ static int run(int argc, char **argv) { test_setup_logging(LOG_DEBUG); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - for (int i = optind; i < argc; i++) { + STRV_FOREACH(a, args) { _cleanup_free_ char *p = NULL; _cleanup_close_ int fd = -EBADF; - printf("%s ", argv[i]); + printf("%s ", *a); fflush(stdout); - r = chase(argv[i], arg_root, arg_flags, &p, arg_open ? &fd : NULL); + r = chase(*a, arg_root, arg_flags, &p, arg_open ? &fd : NULL); if (r < 0) log_error_errno(r, "failed: %m"); else { From 663a831089af0373d01e6718f2ef84244f4093f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 17 Apr 2026 10:26:02 +0200 Subject: [PATCH 1198/1296] stdio-bridge: convert to the new option parser Co-developed-by: Claude Opus 4.7 --- src/stdio-bridge/stdio-bridge.c | 79 +++++++++++++-------------------- 1 file changed, 31 insertions(+), 48 deletions(-) diff --git a/src/stdio-bridge/stdio-bridge.c b/src/stdio-bridge/stdio-bridge.c index 52c87559a4933..42f365ad3ec18 100644 --- a/src/stdio-bridge/stdio-bridge.c +++ b/src/stdio-bridge/stdio-bridge.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -11,9 +10,11 @@ #include "bus-internal.h" #include "bus-util.h" #include "errno-util.h" +#include "format-table.h" #include "io-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "time-util.h" @@ -23,84 +24,66 @@ static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; static bool arg_quiet = false; static int help(void) { - printf("%s [OPTIONS...]\n\n" - "Forward messages between a pipe or socket and a D-Bus bus.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -p --bus-path=PATH Path to the bus address (default: %s)\n" - " --system Connect to system bus\n" - " --user Connect to user bus\n" - " -M --machine=CONTAINER Name of local container to connect to\n" - " -q --quiet Fail silently instead of logging errors\n", - program_invocation_short_name, DEFAULT_SYSTEM_BUS_ADDRESS); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n" + "\nForward messages between a pipe or socket and a D-Bus bus.\n\n", + program_invocation_short_name); + + r = table_print_or_warn(options); + if (r < 0) + return r; return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_USER, - ARG_SYSTEM, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "bus-path", required_argument, NULL, 'p' }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "machine", required_argument, NULL, 'M' }, - { "quiet", no_argument, NULL, 'q' }, - {}, - }; - - int c, r; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hp:M:", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_USER: + OPTION_LONG("user", NULL, "Connect to user bus"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Connect to system bus"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case 'p': - arg_bus_path = optarg; + OPTION('p', "bus-path", "PATH", + "Path to the bus address (default: " DEFAULT_SYSTEM_BUS_ADDRESS ")"): + arg_bus_path = arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_bus_path, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(arg, &arg_bus_path, &arg_transport); if (r < 0) return r; break; - case 'q': + OPTION('q', "quiet", NULL, "Fail silently instead of logging errors"): arg_quiet = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (argc > optind) + if (option_parser_get_n_args(&state) > 0) return log_full_errno(arg_quiet ? LOG_DEBUG : LOG_ERR, SYNTHETIC_ERRNO(EINVAL), "%s takes no arguments.", program_invocation_short_name); From 108f6045cc94b6ee6821e6762ecc6640e5aba13d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 23 Apr 2026 10:17:52 +0200 Subject: [PATCH 1199/1296] shutdown: convert to the new option parser Co-developed-by: Claude Opus 4.7 --- src/shutdown/shutdown.c | 96 +++++++++++++++++------------------------ 1 file changed, 40 insertions(+), 56 deletions(-) diff --git a/src/shutdown/shutdown.c b/src/shutdown/shutdown.c index 1fb0f422e53b7..5e6ff9f68f591 100644 --- a/src/shutdown/shutdown.c +++ b/src/shutdown/shutdown.c @@ -3,7 +3,6 @@ Copyright © 2010 ProFUSION embedded systems ***/ -#include #include #include #include @@ -29,10 +28,10 @@ #include "fd-util.h" #include "fileio.h" #include "format-util.h" -#include "getopt-defs.h" #include "initrd-util.h" #include "killall.h" #include "log.h" +#include "options.h" #include "parse-util.h" #include "pidref.h" #include "printk-util.h" @@ -58,103 +57,88 @@ static uint8_t arg_exit_code = 0; static usec_t arg_timeout = DEFAULT_TIMEOUT_USEC; static int parse_argv(int argc, char *argv[]) { - enum { - COMMON_GETOPT_ARGS, - SHUTDOWN_GETOPT_ARGS, - }; - - static const struct option options[] = { - COMMON_GETOPT_OPTIONS, - SHUTDOWN_GETOPT_OPTIONS, - {} - }; - - int c, r; - assert(argc >= 1); assert(argv); - /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long() - * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */ - optind = 0; + /* The interface is: the verb must stay in argv[1]. Any extra positional arguments + * are warned about and ignored. See 4b5d8d0f22ae61ceb45a25391354ba53b43ee992. */ - /* "-" prevents getopt from permuting argv[] and moving the verb away - * from argv[1]. Our interface to initrd promises it'll be there. */ - while ((c = getopt_long(argc, argv, "-", options, NULL)) >= 0) + OptionParser state = { argc, argv, OPTION_PARSER_RETURN_POSITIONAL_ARGS }; + const char *arg; + int r; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case ARG_LOG_LEVEL: - r = log_set_max_level_from_string(optarg); + OPTION_COMMON_LOG_LEVEL: + r = log_set_max_level_from_string(arg); if (r < 0) - log_warning_errno(r, "Failed to parse log level %s, ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse log level %s, ignoring: %m", arg); break; - case ARG_LOG_TARGET: - r = log_set_target_from_string(optarg); + OPTION_COMMON_LOG_TARGET: + r = log_set_target_from_string(arg); if (r < 0) - log_warning_errno(r, "Failed to parse log target %s, ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse log target %s, ignoring: %m", arg); break; - case ARG_LOG_COLOR: - - if (optarg) { - r = log_show_color_from_string(optarg); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "log-color", "BOOL", + "Highlight important messages"): + if (arg) { + r = log_show_color_from_string(arg); if (r < 0) - log_warning_errno(r, "Failed to parse log color setting %s, ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse log color setting %s, ignoring: %m", arg); } else log_show_color(true); break; - case ARG_LOG_LOCATION: - if (optarg) { - r = log_show_location_from_string(optarg); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "log-location", "BOOL", + "Include code location in messages"): + if (arg) { + r = log_show_location_from_string(arg); if (r < 0) - log_warning_errno(r, "Failed to parse log location setting %s, ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse log location setting %s, ignoring: %m", arg); } else log_show_location(true); break; - case ARG_LOG_TIME: - - if (optarg) { - r = log_show_time_from_string(optarg); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "log-time", "BOOL", + "Prefix messages with current time"): + if (arg) { + r = log_show_time_from_string(arg); if (r < 0) - log_warning_errno(r, "Failed to parse log time setting %s, ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse log time setting %s, ignoring: %m", arg); } else log_show_time(true); break; - case ARG_EXIT_CODE: - r = safe_atou8(optarg, &arg_exit_code); + OPTION_LONG("exit-code", "N", + "Exit code for reboot/kexec"): + r = safe_atou8(arg, &arg_exit_code); if (r < 0) - log_warning_errno(r, "Failed to parse exit code %s, ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse exit code %s, ignoring: %m", arg); break; - case ARG_TIMEOUT: - r = parse_sec(optarg, &arg_timeout); + OPTION_LONG("timeout", "TIME", + "Overall shutdown timeout"): + r = parse_sec(arg, &arg_timeout); if (r < 0) - log_warning_errno(r, "Failed to parse shutdown timeout %s, ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse shutdown timeout %s, ignoring: %m", arg); break; - case '\001': + OPTION_POSITIONAL: if (!arg_verb) - arg_verb = optarg; + arg_verb = arg; else - log_warning("Got extraneous arguments, ignoring."); + log_warning("Got extraneous argument, ignoring."); break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (!arg_verb) From 39c5bee7c12f3de32953b24d56e681ba2d2db0c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 23 Apr 2026 10:34:42 +0200 Subject: [PATCH 1200/1296] network-generator: convert to the new option parser --help is the same except for whitespace. Co-developed-by: Claude Opus 4.7 --- .../generator/network-generator-main.c | 64 +++++++++---------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/src/network/generator/network-generator-main.c b/src/network/generator/network-generator-main.c index d64f65bc44a55..f9acf405006ef 100644 --- a/src/network/generator/network-generator-main.c +++ b/src/network/generator/network-generator-main.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" @@ -8,15 +7,18 @@ #include "creds-util.h" #include "errno-util.h" #include "fd-util.h" +#include "format-table.h" #include "fs-util.h" #include "generator.h" #include "log.h" #include "main-func.h" #include "mkdir.h" #include "network-generator.h" +#include "options.h" #include "path-util.h" #include "proc-cmdline.h" #include "string-util.h" +#include "strv.h" #define NETWORK_UNIT_DIRECTORY "/run/systemd/network/" @@ -148,52 +150,47 @@ static int context_save(Context *context) { } static int help(void) { - printf("%s [OPTIONS...] [-- KERNEL_CMDLINE]\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --root=PATH Operate on an alternate filesystem root\n", + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] [-- KERNEL_CMDLINE]\n\n", program_invocation_short_name); + r = table_print_or_warn(options); + if (r < 0) + return r; + return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_ROOT, - }; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "root", required_argument, NULL, ARG_ROOT }, - {}, - }; - int c; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_ROOT: - arg_root = optarg; + OPTION_LONG("root", "PATH", + "Operate on an alternate filesystem root"): + arg_root = arg; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state); return 1; } @@ -205,20 +202,21 @@ static int run(int argc, char *argv[]) { umask(0022); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - if (optind >= argc) { + if (strv_isempty(args)) { r = proc_cmdline_parse(parse_cmdline_item, &context, 0); if (r < 0) return log_warning_errno(r, "Failed to parse kernel command line: %m"); } else { - for (int i = optind; i < argc; i++) { + STRV_FOREACH(a, args) { _cleanup_free_ char *word = NULL; char *value; - word = strdup(argv[i]); + word = strdup(*a); if (!word) return log_oom(); From b7f2c23da97f9a6e622886dd5a67ea78d8963aed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 22 Apr 2026 15:09:45 +0200 Subject: [PATCH 1201/1296] test-libudev: convert to the new option parser The program now has a proper --help output. (Not on purpose. It's just easier to do same thing as everywhere else.) Co-developed-by: Claude Opus 4.7 --- src/libudev/test-libudev.c | 72 +++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/src/libudev/test-libudev.c b/src/libudev/test-libudev.c index f15cbc3a91edc..48d5ac4d9d960 100644 --- a/src/libudev/test-libudev.c +++ b/src/libudev/test-libudev.c @@ -1,15 +1,16 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include "devnum-util.h" #include "fd-util.h" +#include "format-table.h" #include "libudev-list-internal.h" #include "log.h" #include "main-func.h" #include "libudev-util.h" +#include "options.h" #include "string-util.h" #include "tests.h" #include "version.h" @@ -404,52 +405,57 @@ static void test_list(void) { assert_se(!udev_list_entry_get_by_name(e, "ccc")); } -static int parse_args(int argc, char *argv[], const char **syspath, const char **subsystem) { - static const struct option options[] = { - { "syspath", required_argument, NULL, 'p' }, - { "subsystem", required_argument, NULL, 's' }, - { "debug", no_argument, NULL, 'd' }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "monitor", no_argument, NULL, 'm' }, - {} - }; - int c; +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL; + int r; + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n\n", program_invocation_short_name); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + return 0; +} + +static int parse_args(int argc, char *argv[], const char **syspath, const char **subsystem) { + assert(argc >= 0); + assert(argv); assert(syspath); assert(subsystem); - while ((c = getopt_long(argc, argv, "p:s:dhVm", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'p': - *syspath = optarg; + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + printf("%s\n", GIT_VERSION); + return 0; + + OPTION('p', "syspath", "PATH", "Syspath to test"): + *syspath = arg; break; - case 's': - *subsystem = optarg; + OPTION('s', "subsystem", "SUBSYSTEM", "Subsystem to enumerate"): + *subsystem = arg; break; - case 'd': + OPTION('d', "debug", NULL, "Enable debug logging"): log_set_max_level(LOG_DEBUG); break; - case 'h': - printf("--debug --syspath= --subsystem= --help\n"); - return 0; - - case 'V': - printf("%s\n", GIT_VERSION); - return 0; - - case 'm': + OPTION('m', "monitor", NULL, "Run monitor test"): arg_monitor = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } return 1; From 04ad6079bbb37f791f4a80f2de0c3b29df5b0751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 22 Apr 2026 23:59:30 +0200 Subject: [PATCH 1202/1296] sbsign: convert to the new option and verb parsers The options --private-key, --private-key-source, --certificate, --certificate-source are almost identical in sbsign, but are described slightly differently. Add OPTION_COMMON_ macros that are parametrized to keep the purpose of the --private-key and --certificate options in the description. Co-developed-by: Claude Opus 4.7 --- src/bootctl/bootctl.c | 16 ++--- src/sbsign/sbsign.c | 149 +++++++++++++++++------------------------- src/shared/options.h | 14 ++++ 3 files changed, 78 insertions(+), 101 deletions(-) diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index 00ae512668e7f..9ec25962853b4 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -605,16 +605,13 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return r; break; - OPTION_LONG("private-key", "PATH|URI", - "Private key for Secure Boot auto-enrollment"): + OPTION_COMMON_PRIVATE_KEY("Private key for Secure Boot auto-enrollment"): r = free_and_strdup_warn(&arg_private_key, arg); if (r < 0) return r; break; - OPTION_LONG("private-key-source", "SOURCE", - "Specify how to use the private key " - "(file, provider:PROVIDER, engine:ENGINE)"): + OPTION_COMMON_PRIVATE_KEY_SOURCE: r = parse_openssl_key_source_argument(arg, &arg_private_key_source, &arg_private_key_source_type); @@ -622,18 +619,13 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return r; break; - OPTION_LONG("certificate", "PATH|URI", - "PEM certificate to use when setting up Secure Boot auto-enrollment, " - "or a provider specific designation if --certificate-source= is used"): + OPTION_COMMON_CERTIFICATE("PEM certificate to use when setting up Secure Boot auto-enrollment"): r = free_and_strdup_warn(&arg_certificate, arg); if (r < 0) return r; break; - OPTION_LONG("certificate-source", "SOURCE", - "Specify how to interpret the certificate from --certificate=. " - "Allows the certificate to be loaded from an OpenSSL provider " - "(file, provider:PROVIDER)"): + OPTION_COMMON_CERTIFICATE_SOURCE: r = parse_openssl_certificate_source_argument(arg, &arg_certificate_source, &arg_certificate_source_type); diff --git a/src/sbsign/sbsign.c b/src/sbsign/sbsign.c index f54dacf65a49d..a8c554ffcfb50 100644 --- a/src/sbsign/sbsign.c +++ b/src/sbsign/sbsign.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" @@ -11,12 +10,14 @@ #include "efi-fundamental.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "fs-util.h" #include "install-file.h" #include "io-util.h" #include "log.h" #include "main-func.h" #include "openssl-util.h" +#include "options.h" #include "parse-argument.h" #include "pe-binary.h" #include "pretty-print.h" @@ -47,119 +48,96 @@ STATIC_DESTRUCTOR_REGISTER(arg_signed_data_signature, freep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-sbsign", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n%5$sSign binaries for EFI Secure Boot%6$s\n" - "\n%3$sCommands:%4$s\n" - " sign EXEFILE Sign the given binary for EFI Secure Boot\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version\n" - " --output Where to write the signed PE binary\n" - " --certificate=PATH|URI\n" - " PEM certificate to use for signing, or a provider\n" - " specific designation if --certificate-source= is used\n" - " --certificate-source=file|provider:PROVIDER\n" - " Specify how to interpret the certificate from\n" - " --certificate=. Allows the certificate to be loaded\n" - " from an OpenSSL provider\n" - " --private-key=KEY Private key (PEM) to sign with\n" - " --private-key-source=file|provider:PROVIDER|engine:ENGINE\n" - " Specify how to use KEY for --private-key=. Allows\n" - " an OpenSSL engine/provider to be used for signing\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; - return 0; -} + r = option_parser_get_help_table(&options); + if (r < 0) + return r; -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} + (void) table_sync_column_widths(0, verbs, options); -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_OUTPUT, - ARG_CERTIFICATE, - ARG_CERTIFICATE_SOURCE, - ARG_PRIVATE_KEY, - ARG_PRIVATE_KEY_SOURCE, - ARG_PREPARE_OFFLINE_SIGNING, - ARG_SIGNED_DATA, - ARG_SIGNED_DATA_SIGNATURE, - }; + printf("%s [OPTIONS...] COMMAND ...\n" + "\n%sSign binaries for EFI Secure Boot%s\n" + "\n%sCommands:%s\n", + program_invocation_short_name, + ansi_highlight(), ansi_normal(), + ansi_underline(), ansi_normal()); - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "output", required_argument, NULL, ARG_OUTPUT }, - { "certificate", required_argument, NULL, ARG_CERTIFICATE }, - { "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE }, - { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, - { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, - { "prepare-offline-signing", no_argument, NULL, ARG_PREPARE_OFFLINE_SIGNING }, - { "signed-data", required_argument, NULL, ARG_SIGNED_DATA }, - { "signed-data-signature", required_argument, NULL, ARG_SIGNED_DATA_SIGNATURE }, - {} - }; + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + printf("\n%sOptions:%s\n", ansi_underline(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); + return 0; +} - int c, r; +VERB_COMMON_HELP_HIDDEN(help); +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); + + OptionParser state = { argc, argv }; + const char *arg; + int r; - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_OUTPUT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_output); + OPTION_LONG("output", "PATH", + "Where to write the signed PE binary"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_output); if (r < 0) return r; break; - case ARG_CERTIFICATE: - r = free_and_strdup_warn(&arg_certificate, optarg); + OPTION_COMMON_CERTIFICATE("PEM certificate to use for signing"): + r = free_and_strdup_warn(&arg_certificate, arg); if (r < 0) return r; break; - case ARG_CERTIFICATE_SOURCE: + OPTION_COMMON_CERTIFICATE_SOURCE: r = parse_openssl_certificate_source_argument( - optarg, + arg, &arg_certificate_source, &arg_certificate_source_type); if (r < 0) return r; break; - case ARG_PRIVATE_KEY: - r = free_and_strdup_warn(&arg_private_key, optarg); + OPTION_COMMON_PRIVATE_KEY("Private key (PEM) to sign with"): + r = free_and_strdup_warn(&arg_private_key, arg); if (r < 0) return r; break; - case ARG_PRIVATE_KEY_SOURCE: + OPTION_COMMON_PRIVATE_KEY_SOURCE: r = parse_openssl_key_source_argument( - optarg, + arg, &arg_private_key_source, &arg_private_key_source_type); if (r < 0) @@ -167,29 +145,23 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_PREPARE_OFFLINE_SIGNING: + OPTION_LONG("prepare-offline-signing", NULL, /* help= */ NULL): arg_prepare_offline_signing = true; break; - case ARG_SIGNED_DATA: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_signed_data); + OPTION_LONG("signed-data", "PATH", /* help= */ NULL): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_signed_data); if (r < 0) return r; break; - case ARG_SIGNED_DATA_SIGNATURE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_signed_data_signature); + OPTION_LONG("signed-data-signature", "PATH", /* help= */ NULL): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_signed_data_signature); if (r < 0) return r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_private_key_source && !arg_certificate) @@ -201,6 +173,7 @@ static int parse_argv(int argc, char *argv[]) { if (arg_prepare_offline_signing && (arg_private_key || arg_signed_data || arg_signed_data_signature)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--prepare-offline-signing cannot be used with --private-key=, --signed-data= or --signed-data-signature="); + *ret_args = option_parser_get_args(&state); return 1; } @@ -442,6 +415,8 @@ static int pkcs7_add_digest_attribute(PKCS7 *p7, BIO *data, PKCS7_SIGNER_INFO *s return 0; } +VERB(verb_sign, "sign", "EXEFILE", 2, 2, 0, + "Sign the given binary for EFI Secure Boot"); static int verb_sign(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL; _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = NULL; @@ -742,20 +717,16 @@ static int verb_sign(int argc, char *argv[], uintptr_t _data, void *userdata) { } static int run(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "sign", 2, 2, 0, verb_sign }, - {} - }; int r; log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/shared/options.h b/src/shared/options.h index 974c21d16a692..d86766f90e46c 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -94,6 +94,20 @@ typedef struct Option { #define OPTION_COMMON_LOWERCASE_J \ OPTION_SHORT('j', NULL, \ "Equivalent to --json=pretty (on TTY) or --json=short (otherwise)") +#define OPTION_COMMON_PRIVATE_KEY(purpose) \ + OPTION_LONG("private-key", "PATH|URI", purpose) +#define OPTION_COMMON_PRIVATE_KEY_SOURCE \ + OPTION_LONG("private-key-source", "SOURCE", \ + "Specify how to use the private key " \ + "(file, provider:PROVIDER, engine:ENGINE)") +#define OPTION_COMMON_CERTIFICATE(purpose) \ + OPTION_LONG("certificate", "PATH|URI", purpose \ + ", or a provider-specific designation if --certificate-source= is used") +#define OPTION_COMMON_CERTIFICATE_SOURCE \ + OPTION_LONG("certificate-source", "SOURCE", \ + "Specify how to interpret the certificate from --certificate=. " \ + "Allows the certificate to be loaded from an OpenSSL provider " \ + "(file, provider:PROVIDER)") /* This is magically mapped to the beginning and end of the section */ extern const Option __start_SYSTEMD_OPTIONS[]; From 82a95edc9257a9088445c48721a5e2e846e867dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 23 Apr 2026 00:32:59 +0200 Subject: [PATCH 1203/1296] repart: convert to the new option parser The metavars for a few options were changed to be shorter, so that the automatic alignment works better. Overall, I think the new version is as least as legible as the old one. The synopsis for -S/-C/-P is fixed, they do not take an argument. Co-developed-by: Claude Opus 4.7 --- src/repart/repart.c | 733 +++++++++++++++++++------------------------- 1 file changed, 310 insertions(+), 423 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index e307bfe13f280..becdacd554f0b 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include @@ -61,6 +60,7 @@ #include "mountpoint-util.h" #include "nulstr-util.h" #include "openssl-util.h" +#include "options.h" #include "parse-argument.h" #include "parse-helpers.h" #include "parse-util.h" @@ -9606,355 +9606,164 @@ static int help(void) { if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] [DEVICE]\n" - "\n%5$sGrow and add partitions to a partition table, and generate disk images (DDIs).%6$s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - "\n%3$sOperation:%4$s\n" - " --dry-run=BOOL Whether to run dry-run operation\n" - " --empty=MODE One of refuse, allow, require, force, create; controls\n" - " how to handle empty disks lacking partition tables\n" - " --offline=BOOL Whether to build the image offline\n" - " --discard=BOOL Whether to discard backing blocks for new partitions\n" - " --sector-size=SIZE Set the logical sector size for the image\n" - " --grain-size=BYTES Set the grain size for partition alignment\n" - " --architecture=ARCH Set the generic architecture for the image\n" - " --size=BYTES Grow loopback file to specified size\n" - " --seed=UUID 128-bit seed UUID to derive all UUIDs from\n" - " --split=BOOL Whether to generate split artifacts\n" - "\n%3$sOutput:%4$s\n" - " --pretty=BOOL Whether to show pretty summary before doing changes\n" - " --json=pretty|short|off\n" - " Generate JSON output\n" - "\n%3$sFactory Reset:%4$s\n" - " --factory-reset=BOOL Whether to remove data partitions before recreating\n" - " them\n" - " --can-factory-reset Test whether factory reset is defined\n" - "\n%3$sConfiguration & Image Control:%4$s\n" - " --root=PATH Operate relative to root path\n" - " --image=PATH Operate relative to image file\n" - " --image-policy=POLICY\n" - " Specify disk image dissection policy\n" - " --definitions=DIR Find partition definitions in specified directory\n" - " --list-devices List candidate block devices to operate on\n" - "\n%3$sVerity:%4$s\n" - " --private-key=PATH|URI\n" - " Private key to use when generating verity roothash\n" - " signatures, or an engine or provider specific\n" - " designation if --private-key-source= is used\n" - " --private-key-source=file|provider:PROVIDER|engine:ENGINE\n" - " Specify how to use KEY for --private-key=. Allows\n" - " an OpenSSL engine/provider to be used when generating\n" - " verity roothash signatures\n" - " --certificate=PATH|URI\n" - " PEM certificate to use when generating verity roothash\n" - " signatures, or a provider specific designation if\n" - " --certificate-source= is used\n" - " --certificate-source=file|provider:PROVIDER\n" - " Specify how to interpret the certificate from\n" - " --certificate=. Allows the certificate to be loaded\n" - " from an OpenSSL provider\n" - " --join-signature=HASH:SIG\n" - " Specify root hash and pkcs7 signature of root hash for\n" - " verity as a tuple of hex encoded hash and a DER\n" - " encoded PKCS7, either as a path to a file or as an\n" - " ASCII base64 encoded string prefixed by 'base64:'\n" - "\n%3$sEncryption:%4$s\n" - " --key-file=PATH Key to use when encrypting partitions\n" - " --tpm2-device=PATH Path to TPM2 device node to use\n" - " --tpm2-device-key=PATH\n" - " Enroll a TPM2 device using its public key\n" - " --tpm2-seal-key-handle=HANDLE\n" - " Specify handle of key to use for sealing\n" - " --tpm2-pcrs=PCR1+PCR2+PCR3+…\n" - " TPM2 PCR indexes to use for TPM2 enrollment\n" - " --tpm2-public-key=PATH\n" - " Enroll signed TPM2 PCR policy against PEM public key\n" - " --tpm2-public-key-pcrs=PCR1+PCR2+PCR3+…\n" - " Enroll signed TPM2 PCR policy for specified TPM2 PCRs\n" - " --tpm2-pcrlock=PATH\n" - " Specify pcrlock policy to lock against\n" - "\n%3$sPartition Control:%4$s\n" - " --include-partitions=PARTITION1,PARTITION2,PARTITION3,…\n" - " Ignore partitions not of the specified types\n" - " --exclude-partitions=PARTITION1,PARTITION2,PARTITION3,…\n" - " Ignore partitions of the specified types\n" - " --defer-partitions=PARTITION1,PARTITION2,PARTITION3,…\n" - " Take partitions of the specified types into account\n" - " but don't populate them yet\n" - " --defer-partitions-empty=yes\n" - " Defer all partitions marked for formatting as empty\n" - " --defer-partitions-factory-reset=yes\n" - " Defer all partitions marked for factory reset\n" - "\n%3$sCopying:%4$s\n" - " -s --copy-source=PATH Specify the primary source tree to copy files from\n" - " --copy-from=IMAGE Copy partitions from the given image(s)\n" - "\n%3$sDDI Profile:%4$s\n" - " -S --make-ddi=sysext Make a system extension DDI\n" - " -C --make-ddi=confext Make a configuration extension DDI\n" - " -P --make-ddi=portable Make a portable service DDI\n" - "\n%3$sAuxiliary Resource Generation:%4$s\n" - " --append-fstab=MODE One of no, auto, replace; controls how to join the\n" - " content of a pre-existing fstab with the generated one\n" - " --generate-fstab=PATH\n" - " Write fstab configuration to the given path\n" - " --generate-crypttab=PATH\n" - " Write crypttab configuration to the given path\n" - "\n%3$sEl Torito boot catalog:%4$s\n" - " --el-torito=BOOL Whether to add a boot catalog to boot the ESP\n" - " --el-torito-system=STRING\n" - " Set the system identifier in the ISO9660 descriptor\n" - " --el-torito-volume=STRING\n" - " Set the volume identifier in the ISO9660 descriptor\n" - " --el-torito-publisher=STRING\n" - " Set the publisher identifier in the ISO9660 descriptor\n" - "\nSee the %2$s for details.\n", + static const char *const option_groups[] = { + "Options", + "Operation", + "Output", + "Factory Reset", + "Configuration & Image Control", + "Verity", + "Encryption", + "Partition Control", + "Copying", + "DDI Profile", + "Auxiliary Resource Generation", + "El Torito boot catalog", + }; + + _cleanup_(table_unref_many) Table *option_tables[ELEMENTSOF(option_groups) + 1] = {}; + + for (size_t i = 0; i < ELEMENTSOF(option_groups); i++) { + r = option_parser_get_help_table_group(option_groups[i], &option_tables[i]); + if (r < 0) + return r; + } + + (void) table_sync_column_widths(0, + option_tables[0], option_tables[1], option_tables[2], + option_tables[3], option_tables[4], option_tables[5], + option_tables[6], option_tables[7], option_tables[8], + option_tables[9], option_tables[10], option_tables[11]); + + printf("%s [OPTIONS...] [DEVICE]\n" + "\n%sGrow and add partitions to a partition table, and generate disk images (DDIs).%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), ansi_normal()); + for (size_t i = 0; i < ELEMENTSOF(option_groups); i++) { + printf("\n%s%s:%s\n", ansi_underline(), option_groups[i], ansi_normal()); + + r = table_print_or_warn(option_tables[i]); + if (r < 0) + return r; + } + + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_DRY_RUN, - ARG_EMPTY, - ARG_DISCARD, - ARG_FACTORY_RESET, - ARG_CAN_FACTORY_RESET, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_SEED, - ARG_PRETTY, - ARG_DEFINITIONS, - ARG_SIZE, - ARG_JSON, - ARG_KEY_FILE, - ARG_PRIVATE_KEY, - ARG_PRIVATE_KEY_SOURCE, - ARG_CERTIFICATE, - ARG_CERTIFICATE_SOURCE, - ARG_TPM2_DEVICE, - ARG_TPM2_DEVICE_KEY, - ARG_TPM2_SEAL_KEY_HANDLE, - ARG_TPM2_PCRS, - ARG_TPM2_PUBLIC_KEY, - ARG_TPM2_PUBLIC_KEY_PCRS, - ARG_TPM2_PCRLOCK, - ARG_SPLIT, - ARG_INCLUDE_PARTITIONS, - ARG_EXCLUDE_PARTITIONS, - ARG_DEFER_PARTITIONS, - ARG_DEFER_PARTITIONS_EMPTY, - ARG_DEFER_PARTITIONS_FACTORY_RESET, - ARG_SECTOR_SIZE, - ARG_GRAIN_SIZE, - ARG_SKIP_PARTITIONS, - ARG_ARCHITECTURE, - ARG_OFFLINE, - ARG_COPY_FROM, - ARG_MAKE_DDI, - ARG_APPEND_FSTAB, - ARG_GENERATE_FSTAB, - ARG_GENERATE_CRYPTTAB, - ARG_LIST_DEVICES, - ARG_JOIN_SIGNATURE, - ARG_ELTORITO, - ARG_ELTORITO_SYSTEM, - ARG_ELTORITO_VOLUME, - ARG_ELTORITO_PUBLISHER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "dry-run", required_argument, NULL, ARG_DRY_RUN }, - { "empty", required_argument, NULL, ARG_EMPTY }, - { "discard", required_argument, NULL, ARG_DISCARD }, - { "factory-reset", required_argument, NULL, ARG_FACTORY_RESET }, - { "can-factory-reset", no_argument, NULL, ARG_CAN_FACTORY_RESET }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "seed", required_argument, NULL, ARG_SEED }, - { "pretty", required_argument, NULL, ARG_PRETTY }, - { "definitions", required_argument, NULL, ARG_DEFINITIONS }, - { "size", required_argument, NULL, ARG_SIZE }, - { "json", required_argument, NULL, ARG_JSON }, - { "key-file", required_argument, NULL, ARG_KEY_FILE }, - { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, - { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, - { "certificate", required_argument, NULL, ARG_CERTIFICATE }, - { "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE }, - { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, - { "tpm2-device-key", required_argument, NULL, ARG_TPM2_DEVICE_KEY }, - { "tpm2-seal-key-handle", required_argument, NULL, ARG_TPM2_SEAL_KEY_HANDLE }, - { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS }, - { "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY }, - { "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS }, - { "tpm2-pcrlock", required_argument, NULL, ARG_TPM2_PCRLOCK }, - { "split", required_argument, NULL, ARG_SPLIT }, - { "include-partitions", required_argument, NULL, ARG_INCLUDE_PARTITIONS }, - { "exclude-partitions", required_argument, NULL, ARG_EXCLUDE_PARTITIONS }, - { "defer-partitions", required_argument, NULL, ARG_DEFER_PARTITIONS }, - { "defer-partitions-empty", required_argument, NULL, ARG_DEFER_PARTITIONS_EMPTY }, - { "defer-partitions-factory-reset", required_argument, NULL, ARG_DEFER_PARTITIONS_FACTORY_RESET }, - { "sector-size", required_argument, NULL, ARG_SECTOR_SIZE }, - { "grain-size", required_argument, NULL, ARG_GRAIN_SIZE }, - { "architecture", required_argument, NULL, ARG_ARCHITECTURE }, - { "offline", required_argument, NULL, ARG_OFFLINE }, - { "copy-from", required_argument, NULL, ARG_COPY_FROM }, - { "copy-source", required_argument, NULL, 's' }, - { "make-ddi", required_argument, NULL, ARG_MAKE_DDI }, - { "append-fstab", required_argument, NULL, ARG_APPEND_FSTAB }, - { "generate-fstab", required_argument, NULL, ARG_GENERATE_FSTAB }, - { "generate-crypttab", required_argument, NULL, ARG_GENERATE_CRYPTTAB }, - { "list-devices", no_argument, NULL, ARG_LIST_DEVICES }, - { "join-signature", required_argument, NULL, ARG_JOIN_SIGNATURE }, - { "el-torito", required_argument, NULL, ARG_ELTORITO }, - { "el-torito-system", required_argument, NULL, ARG_ELTORITO_SYSTEM }, - { "el-torito-volume", required_argument, NULL, ARG_ELTORITO_VOLUME }, - { "el-torito-publisher", required_argument, NULL, ARG_ELTORITO_PUBLISHER }, - {} - }; - - bool auto_public_key_pcr_mask = true, auto_pcrlock = true; - int c, r; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hs:SCP", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + bool auto_public_key_pcr_mask = true, auto_pcrlock = true; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_GROUP("Options"): {} + + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_NO_LEGEND: + OPTION_COMMON_NO_LEGEND: arg_legend = false; break; - case ARG_DRY_RUN: - r = parse_boolean_argument("--dry-run=", optarg, &arg_dry_run); + OPTION_GROUP("Operation"): {} + + OPTION_LONG("dry-run", "BOOL", + "Whether to run dry-run operation"): + r = parse_boolean_argument("--dry-run=", arg, &arg_dry_run); if (r < 0) return r; break; - case ARG_EMPTY: - if (isempty(optarg)) { + OPTION_LONG("empty", "MODE", + "How to handle empty disks lacking partition tables (refuse, allow, require, force, create)"): + if (isempty(arg)) { arg_empty = EMPTY_UNSET; break; } - arg_empty = empty_mode_from_string(optarg); + arg_empty = empty_mode_from_string(arg); if (arg_empty < 0) - return log_error_errno(arg_empty, "Failed to parse --empty= parameter: %s", optarg); + return log_error_errno(arg_empty, "Failed to parse --empty= parameter: %s", arg); break; - case ARG_DISCARD: - r = parse_boolean_argument("--discard=", optarg, &arg_discard); - if (r < 0) - return r; - break; + OPTION_LONG("offline", "BOOL", + "Whether to build the image offline"): + if (streq(arg, "auto")) + arg_offline = -1; + else { + r = parse_boolean_argument("--offline=", arg, NULL); + if (r < 0) + return r; - case ARG_FACTORY_RESET: - r = parse_boolean_argument("--factory-reset=", optarg, NULL); - if (r < 0) - return r; - arg_factory_reset = r; - break; + arg_offline = r; + } - case ARG_CAN_FACTORY_RESET: - arg_can_factory_reset = true; break; - case ARG_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root); + OPTION_LONG("discard", "BOOL", + "Whether to discard backing blocks for new partitions"): + r = parse_boolean_argument("--discard=", arg, &arg_discard); if (r < 0) return r; break; - case ARG_IMAGE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image); + OPTION_LONG("sector-size", "SIZE", + "Set the logical sector size for the image"): + r = parse_sector_size(arg, &arg_sector_size); if (r < 0) return r; - arg_relax_copy_block_security = false; - break; - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); + OPTION_LONG("grain-size", "BYTES", + "Set the grain size for partition alignment"): + r = parse_size(arg, 1024, &arg_grain_size); if (r < 0) - return r; - break; - - case ARG_SEED: - if (isempty(optarg)) { - arg_seed = SD_ID128_NULL; - arg_randomize = false; - } else if (streq(optarg, "random")) - arg_randomize = true; - else { - r = sd_id128_from_string(optarg, &arg_seed); - if (r < 0) - return log_error_errno(r, "Failed to parse seed: %s", optarg); - - arg_randomize = false; - } + return log_error_errno(r, "Failed to parse --grain-size= parameter: %s", arg); + if (arg_grain_size < 512 || !ISPOWEROF2(arg_grain_size)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Grain size must be a power of 2 >= 512."); break; - case ARG_PRETTY: - r = parse_boolean_argument("--pretty=", optarg, NULL); + OPTION_LONG("architecture", "ARCH", + "Set the generic architecture for the image"): + r = architecture_from_string(arg); if (r < 0) - return r; - arg_pretty = r; - break; + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid architecture '%s'.", arg); - case ARG_DEFINITIONS: { - _cleanup_free_ char *path = NULL; - r = parse_path_argument(optarg, false, &path); - if (r < 0) - return r; - if (strv_consume(&arg_definitions, TAKE_PTR(path)) < 0) - return log_oom(); + arg_architecture = r; break; - } - case ARG_SIZE: { + OPTION_LONG("size", "BYTES", + "Grow loopback file to specified size"): { uint64_t parsed, rounded; - if (streq(optarg, "auto")) { + if (streq(arg, "auto")) { arg_size = UINT64_MAX; arg_size_auto = true; break; } - r = parse_size(optarg, 1024, &parsed); + r = parse_size(arg, 1024, &parsed); if (r < 0) - return log_error_errno(r, "Failed to parse --size= parameter: %s", optarg); + return log_error_errno(r, "Failed to parse --size= parameter: %s", arg); rounded = round_up_size(parsed, 4096); if (rounded == 0) @@ -9971,59 +9780,168 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_LONG("seed", "UUID", + "128-bit seed UUID to derive all UUIDs from"): + if (isempty(arg)) { + arg_seed = SD_ID128_NULL; + arg_randomize = false; + } else if (streq(arg, "random")) + arg_randomize = true; + else { + r = sd_id128_from_string(arg, &arg_seed); + if (r < 0) + return log_error_errno(r, "Failed to parse seed: %s", arg); + + arg_randomize = false; + } + + break; + + OPTION_LONG("split", "BOOL", + "Whether to generate split artifacts"): + r = parse_boolean_argument("--split=", arg, NULL); + if (r < 0) + return r; + + arg_split = r; + break; + + OPTION_GROUP("Output"): {} + + OPTION_LONG("pretty", "BOOL", + "Whether to show pretty summary before doing changes"): + r = parse_boolean_argument("--pretty=", arg, NULL); + if (r < 0) + return r; + arg_pretty = r; + break; + + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); if (r <= 0) return r; break; - case ARG_KEY_FILE: { - r = parse_key_file(optarg, &arg_key); + OPTION_GROUP("Factory Reset"): {} + + OPTION_LONG("factory-reset", "BOOL", + "Whether to remove data partitions before recreating them"): + r = parse_boolean_argument("--factory-reset=", arg, NULL); + if (r < 0) + return r; + arg_factory_reset = r; + break; + + OPTION_LONG("can-factory-reset", NULL, + "Test whether factory reset is defined"): + arg_can_factory_reset = true; + break; + + OPTION_GROUP("Configuration & Image Control"): {} + + OPTION_LONG("root", "PATH", + "Operate relative to root path"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_root); if (r < 0) return r; break; - } - case ARG_PRIVATE_KEY: { - r = free_and_strdup_warn(&arg_private_key, optarg); + OPTION_LONG("image", "PATH", + "Operate relative to image file"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image); + if (r < 0) + return r; + + arg_relax_copy_block_security = false; + + break; + + OPTION_LONG("image-policy", "POLICY", + "Specify disk image dissection policy"): + r = parse_image_policy_argument(arg, &arg_image_policy); + if (r < 0) + return r; + break; + + OPTION_LONG("definitions", "DIR", + "Find partition definitions in specified directory"): { + _cleanup_free_ char *path = NULL; + r = parse_path_argument(arg, false, &path); if (r < 0) return r; + if (strv_consume(&arg_definitions, TAKE_PTR(path)) < 0) + return log_oom(); break; } - case ARG_PRIVATE_KEY_SOURCE: + OPTION_LONG("list-devices", NULL, + "List candidate block devices to operate on"): + r = blockdev_list(BLOCKDEV_LIST_REQUIRE_PARTITION_SCANNING|BLOCKDEV_LIST_SHOW_SYMLINKS|BLOCKDEV_LIST_IGNORE_ZRAM, /* ret_devices= */ NULL, /* ret_n_devices= */ NULL); + if (r < 0) + return r; + + return 0; + + OPTION_GROUP("Verity"): {} + + OPTION_COMMON_PRIVATE_KEY("Private key to use when generating verity roothash signatures"): + r = free_and_strdup_warn(&arg_private_key, arg); + if (r < 0) + return r; + break; + + OPTION_COMMON_PRIVATE_KEY_SOURCE: r = parse_openssl_key_source_argument( - optarg, + arg, &arg_private_key_source, &arg_private_key_source_type); if (r < 0) return r; break; - case ARG_CERTIFICATE: - r = free_and_strdup_warn(&arg_certificate, optarg); + OPTION_COMMON_CERTIFICATE("PEM certificate to use when generating verity roothash signatures"): + r = free_and_strdup_warn(&arg_certificate, arg); if (r < 0) return r; break; - case ARG_CERTIFICATE_SOURCE: + OPTION_COMMON_CERTIFICATE_SOURCE: r = parse_openssl_certificate_source_argument( - optarg, + arg, &arg_certificate_source, &arg_certificate_source_type); if (r < 0) return r; break; - case ARG_TPM2_DEVICE: { + OPTION_LONG("join-signature", "HASH:SIG", + "Specify root hash and pkcs7 signature of root hash for verity as a tuple of " + "hex-encoded hash and a DER-encoded PKCS7, either as a path to a file or as an " + "ASCII base64-encoded string prefixed by 'base64:'"): + r = parse_join_signature(arg, &arg_verity_settings); + if (r < 0) + return r; + break; + + OPTION_GROUP("Encryption"): {} + + OPTION_LONG("key-file", "PATH", + "Key to use when encrypting partitions"): + r = parse_key_file(arg, &arg_key); + if (r < 0) + return r; + break; + + OPTION_LONG("tpm2-device", "PATH", + "Path to TPM2 device node to use"): { _cleanup_free_ char *device = NULL; - if (streq(optarg, "list")) + if (streq(arg, "list")) return tpm2_list_devices(/* legend= */ true, /* quiet= */ false); - if (!streq(optarg, "auto")) { - device = strdup(optarg); + if (!streq(arg, "auto")) { + device = strdup(arg); if (!device) return log_oom(); } @@ -10033,64 +9951,65 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_TPM2_DEVICE_KEY: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_device_key); + OPTION_LONG("tpm2-device-key", "PATH", + "Enroll a TPM2 device using its public key"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_device_key); if (r < 0) return r; break; - case ARG_TPM2_SEAL_KEY_HANDLE: - r = safe_atou32_full(optarg, 16, &arg_tpm2_seal_key_handle); + OPTION_LONG("tpm2-seal-key-handle", "HANDLE", + "Specify handle of key to use for sealing"): + r = safe_atou32_full(arg, 16, &arg_tpm2_seal_key_handle); if (r < 0) - return log_error_errno(r, "Could not parse TPM2 seal key handle index '%s': %m", optarg); + return log_error_errno(r, "Could not parse TPM2 seal key handle index '%s': %m", arg); break; - case ARG_TPM2_PCRS: - r = tpm2_parse_pcr_argument_append(optarg, &arg_tpm2_hash_pcr_values, &arg_tpm2_n_hash_pcr_values); + OPTION_LONG("tpm2-pcrs", "PCR1+PCR2+…", + "TPM2 PCR indexes to use for TPM2 enrollment"): + r = tpm2_parse_pcr_argument_append(arg, &arg_tpm2_hash_pcr_values, &arg_tpm2_n_hash_pcr_values); if (r < 0) return r; break; - case ARG_TPM2_PUBLIC_KEY: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_public_key); + OPTION_LONG("tpm2-public-key", "PATH", + "Enroll signed TPM2 PCR policy against PEM public key"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_public_key); if (r < 0) return r; break; - case ARG_TPM2_PUBLIC_KEY_PCRS: + OPTION_LONG("tpm2-public-key-pcrs", "PCR1+PCR2+…", + "Enroll signed TPM2 PCR policy for specified TPM2 PCRs"): auto_public_key_pcr_mask = false; - r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_public_key_pcr_mask); + r = tpm2_parse_pcr_argument_to_mask(arg, &arg_tpm2_public_key_pcr_mask); if (r < 0) return r; break; - case ARG_TPM2_PCRLOCK: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_pcrlock); + OPTION_LONG("tpm2-pcrlock", "PATH", + "Specify pcrlock policy to lock against"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_pcrlock); if (r < 0) return r; auto_pcrlock = false; break; - case ARG_SPLIT: - r = parse_boolean_argument("--split=", optarg, NULL); - if (r < 0) - return r; - - arg_split = r; - break; + OPTION_GROUP("Partition Control"): {} - case ARG_INCLUDE_PARTITIONS: + OPTION_LONG("include-partitions", "PART1,PART2…", + "Ignore partitions not of the specified types"): if (arg_filter_partitions_type == FILTER_PARTITIONS_EXCLUDE) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Combination of --include-partitions= and --exclude-partitions= is invalid."); - r = parse_partition_types(optarg, &arg_filter_partitions, &arg_n_filter_partitions); + r = parse_partition_types(arg, &arg_filter_partitions, &arg_n_filter_partitions); if (r < 0) return r; @@ -10098,12 +10017,13 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_EXCLUDE_PARTITIONS: + OPTION_LONG("exclude-partitions", "PART1,PART2…", + "Ignore partitions of the specified types"): if (arg_filter_partitions_type == FILTER_PARTITIONS_INCLUDE) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Combination of --include-partitions= and --exclude-partitions= is invalid."); - r = parse_partition_types(optarg, &arg_filter_partitions, &arg_n_filter_partitions); + r = parse_partition_types(arg, &arg_filter_partitions, &arg_n_filter_partitions); if (r < 0) return r; @@ -10111,68 +10031,44 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_DEFER_PARTITIONS: - r = parse_partition_types(optarg, &arg_defer_partitions, &arg_n_defer_partitions); + OPTION_LONG("defer-partitions", "PART1,PART2…", + "Take partitions of the specified types into account but don't populate them yet"): + r = parse_partition_types(arg, &arg_defer_partitions, &arg_n_defer_partitions); if (r < 0) return r; break; - case ARG_DEFER_PARTITIONS_EMPTY: - r = parse_boolean_argument("--defer-partitions-empty=", optarg, &arg_defer_partitions_empty); + OPTION_LONG("defer-partitions-empty", "BOOL", + "Defer all partitions marked for formatting as empty"): + r = parse_boolean_argument("--defer-partitions-empty=", arg, &arg_defer_partitions_empty); if (r < 0) return r; break; - case ARG_DEFER_PARTITIONS_FACTORY_RESET: - r = parse_boolean_argument("--defer-partitions-factory-reset=", optarg, &arg_defer_partitions_factory_reset); + OPTION_LONG("defer-partitions-factory-reset", "BOOL", + "Defer all partitions marked for factory reset"): + r = parse_boolean_argument("--defer-partitions-factory-reset=", arg, &arg_defer_partitions_factory_reset); if (r < 0) return r; break; - case ARG_SECTOR_SIZE: - r = parse_sector_size(optarg, &arg_sector_size); - if (r < 0) - return r; - - break; - - case ARG_GRAIN_SIZE: - r = parse_size(optarg, 1024, &arg_grain_size); - if (r < 0) - return log_error_errno(r, "Failed to parse --grain-size= parameter: %s", optarg); - if (arg_grain_size < 512 || !ISPOWEROF2(arg_grain_size)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Grain size must be a power of 2 >= 512."); - - break; + OPTION_GROUP("Copying"): {} - case ARG_ARCHITECTURE: - r = architecture_from_string(optarg); + OPTION('s', "copy-source", "PATH", + "Specify the primary source tree to copy files from"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_copy_source); if (r < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid architecture '%s'.", optarg); - - arg_architecture = r; - break; - - case ARG_OFFLINE: - if (streq(optarg, "auto")) - arg_offline = -1; - else { - r = parse_boolean_argument("--offline=", optarg, NULL); - if (r < 0) - return r; - - arg_offline = r; - } - + return r; break; - case ARG_COPY_FROM: { + OPTION_LONG("copy-from", "IMAGE", + "Copy partitions from the given image"): { _cleanup_free_ char *p = NULL; - r = parse_path_argument(optarg, /* suppress_root= */ false, &p); + r = parse_path_argument(arg, /* suppress_root= */ false, &p); if (r < 0) return r; @@ -10182,120 +10078,110 @@ static int parse_argv(int argc, char *argv[]) { break; } - case 's': - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_copy_source); - if (r < 0) - return r; - break; + OPTION_GROUP("DDI Profile"): {} - case ARG_MAKE_DDI: - if (!filename_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid DDI type: %s", optarg); + OPTION_LONG("make-ddi", "TYPE", + "Create a DDI of the given type"): + if (!filename_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid DDI type: %s", arg); - r = free_and_strdup_warn(&arg_make_ddi, optarg); + r = free_and_strdup_warn(&arg_make_ddi, arg); if (r < 0) return r; break; - case 'S': + OPTION_SHORT('S', NULL, "Same as --make-ddi=sysext, make a system extension"): r = free_and_strdup_warn(&arg_make_ddi, "sysext"); if (r < 0) return r; break; - case 'C': + OPTION_SHORT('C', NULL, "Same as --make-ddi=confext, make a configuration extension"): r = free_and_strdup_warn(&arg_make_ddi, "confext"); if (r < 0) return r; break; - case 'P': + OPTION_SHORT('P', NULL, "Same as --make-ddi=portable, make a portable service"): r = free_and_strdup_warn(&arg_make_ddi, "portable"); if (r < 0) return r; break; - case ARG_APPEND_FSTAB: - if (isempty(optarg)) { + OPTION_GROUP("Auxiliary Resource Generation"): {} + + OPTION_LONG("append-fstab", "MODE", + "How to join the content of a pre-existing fstab with the generated one " + "(no, auto, replace)"): + if (isempty(arg)) { arg_append_fstab = APPEND_AUTO; break; } - arg_append_fstab = append_mode_from_string(optarg); + arg_append_fstab = append_mode_from_string(arg); if (arg_append_fstab < 0) - return log_error_errno(arg_append_fstab, "Failed to parse --append-fstab= parameter: %s", optarg); + return log_error_errno(arg_append_fstab, "Failed to parse --append-fstab= parameter: %s", arg); break; - case ARG_GENERATE_FSTAB: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_generate_fstab); + OPTION_LONG("generate-fstab", "PATH", + "Write fstab configuration to the given path"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_generate_fstab); if (r < 0) return r; break; - case ARG_GENERATE_CRYPTTAB: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_generate_crypttab); + OPTION_LONG("generate-crypttab", "PATH", + "Write crypttab configuration to the given path"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_generate_crypttab); if (r < 0) return r; break; - case ARG_LIST_DEVICES: - r = blockdev_list(BLOCKDEV_LIST_REQUIRE_PARTITION_SCANNING|BLOCKDEV_LIST_SHOW_SYMLINKS|BLOCKDEV_LIST_IGNORE_ZRAM, /* ret_devices= */ NULL, /* ret_n_devices= */ NULL); - if (r < 0) - return r; - - return 0; - - case ARG_JOIN_SIGNATURE: - r = parse_join_signature(optarg, &arg_verity_settings); - if (r < 0) - return r; - break; + OPTION_GROUP("El Torito boot catalog"): {} - case ARG_ELTORITO: - r = parse_boolean_argument("--el-torito=", optarg, &arg_eltorito); + OPTION_LONG("el-torito", "BOOL", + "Whether to add a boot catalog to boot the ESP"): + r = parse_boolean_argument("--el-torito=", arg, &arg_eltorito); if (r < 0) return r; break; - case ARG_ELTORITO_SYSTEM: - if (!iso9660_system_name_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-system=.", optarg); + OPTION_LONG("el-torito-system", "STRING", + "Set the system identifier in the ISO9660 descriptor"): + if (!iso9660_system_name_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-system=.", arg); - r = free_and_strdup_warn(&arg_eltorito_system, optarg); + r = free_and_strdup_warn(&arg_eltorito_system, arg); if (r < 0) return r; break; - case ARG_ELTORITO_VOLUME: - if (!iso9660_volume_name_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-volume=.", optarg); + OPTION_LONG("el-torito-volume", "STRING", + "Set the volume identifier in the ISO9660 descriptor"): + if (!iso9660_volume_name_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-volume=.", arg); - r = free_and_strdup_warn(&arg_eltorito_volume, optarg); + r = free_and_strdup_warn(&arg_eltorito_volume, arg); if (r < 0) return r; break; - case ARG_ELTORITO_PUBLISHER: - if (!iso9660_publisher_name_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-publisher=.", optarg); + OPTION_LONG("el-torito-publisher", "STRING", + "Set the publisher identifier in the ISO9660 descriptor"): + if (!iso9660_publisher_name_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-publisher=.", arg); - r = free_and_strdup_warn(&arg_eltorito_publisher, optarg); + r = free_and_strdup_warn(&arg_eltorito_publisher, arg); if (r < 0) return r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (argc - optind > 1) + if (option_parser_get_n_args(&state) > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected at most one argument, the path to the block device or image file."); @@ -10375,11 +10261,12 @@ static int parse_argv(int argc, char *argv[]) { arg_relax_copy_block_security = true; } - if (argc > optind) { - if (empty_or_dash(argv[optind])) + char **args = option_parser_get_args(&state); + if (!strv_isempty(args)) { + if (empty_or_dash(args[0])) arg_node_none = true; else { - arg_node = strdup(argv[optind]); + arg_node = strdup(args[0]); if (!arg_node) return log_oom(); arg_node_none = false; From aa7ebb1b629a1ce9b1748f50f3d025a1671ea28d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 23 Apr 2026 19:07:17 +0200 Subject: [PATCH 1204/1296] repart: use parse_tristate_argument_with_auto in one more place --- src/repart/repart.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index becdacd554f0b..9982fc7450a52 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -9705,16 +9705,9 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("offline", "BOOL", "Whether to build the image offline"): - if (streq(arg, "auto")) - arg_offline = -1; - else { - r = parse_boolean_argument("--offline=", arg, NULL); - if (r < 0) - return r; - - arg_offline = r; - } - + r = parse_tristate_argument_with_auto("--offline=", arg, &arg_offline); + if (r < 0) + return r; break; OPTION_LONG("discard", "BOOL", From 69d1f600d072b729c6b406519aa349e56c30da50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 23 Apr 2026 09:39:04 +0200 Subject: [PATCH 1205/1296] measure: reorder verb functions to match --help --- src/measure/measure-tool.c | 294 ++++++++++++++++++------------------- 1 file changed, 147 insertions(+), 147 deletions(-) diff --git a/src/measure/measure-tool.c b/src/measure/measure-tool.c index 4abb77aeb84a7..bd1339ddfa677 100644 --- a/src/measure/measure-tool.c +++ b/src/measure/measure-tool.c @@ -393,6 +393,153 @@ static int parse_argv(int argc, char *argv[]) { return 1; } +static int compare_reported_pcr_nr(uint32_t pcr, const char *varname, const char *description) { + _cleanup_free_ char *s = NULL; + uint32_t v; + int r; + + r = efi_get_variable_string(varname, &s); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to read EFI variable '%s': %m", varname); + + r = safe_atou32(s, &v); + if (r < 0) + return log_error_errno(r, "Failed to parse EFI variable '%s': %s", varname, s); + + if (pcr != v) + log_warning("PCR number reported by stub for %s (%" PRIu32 ") different from our expectation (%" PRIu32 ").\n" + "The measurements are likely inconsistent.", description, v, pcr); + + return 0; +} + +static int validate_stub(void) { + uint64_t features; + bool found = false; + int r; + + if (!tpm2_is_fully_supported()) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Sorry, system lacks full TPM2 support."); + + r = efi_stub_get_features(&features); + if (r < 0) + return log_error_errno(r, "Unable to get stub features: %m"); + + if (!FLAGS_SET(features, EFI_STUB_FEATURE_THREE_PCRS)) + log_warning("Warning: current kernel image does not support measuring itself, the command line or initrd system extension images.\n" + "The PCR measurements seen are unlikely to be valid."); + + r = compare_reported_pcr_nr(TPM2_PCR_KERNEL_BOOT, EFI_LOADER_VARIABLE_STR("StubPcrKernelImage"), "kernel image"); + if (r < 0) + return r; + + STRV_FOREACH(bank, arg_banks) { + _cleanup_free_ char *b = NULL, *p = NULL; + + b = strdup(*bank); + if (!b) + return log_oom(); + + if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/", ascii_strlower(b)) < 0) + return log_oom(); + + if (access(p, F_OK) < 0) { + if (errno != ENOENT) + return log_error_errno(errno, "Failed to detect if '%s' exists: %m", b); + } else + found = true; + } + + if (!found) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "None of the select PCR banks appear to exist."); + + return 0; +} + +static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + int r; + + r = validate_stub(); + if (r < 0) + return r; + + STRV_FOREACH(bank, arg_banks) { + _cleanup_free_ char *b = NULL, *p = NULL, *s = NULL; + _cleanup_free_ void *h = NULL; + size_t l; + + b = strdup(*bank); + if (!b) + return log_oom(); + + if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/%" PRIu32, ascii_strlower(b), (uint32_t) TPM2_PCR_KERNEL_BOOT) < 0) + return log_oom(); + + r = read_virtual_file(p, 4096, &s, NULL); + if (r == -ENOENT) + continue; + if (r < 0) + return log_error_errno(r, "Failed to read '%s': %m", p); + + r = unhexmem(strstrip(s), &h, &l); + if (r < 0) + return log_error_errno(r, "Failed to decode PCR value '%s': %m", s); + + if (!sd_json_format_enabled(arg_json_format_flags)) { + _cleanup_free_ char *f = hexmem(h, l); + if (!f) + return log_oom(); + + if (bank == arg_banks) { + /* before the first line for each PCR, write a short descriptive text to + * stderr, and leave the primary content on stdout */ + fflush(stdout); + fprintf(stderr, "%s# PCR[%" PRIu32 "] %s%s%s\n", + ansi_grey(), + (uint32_t) TPM2_PCR_KERNEL_BOOT, + tpm2_pcr_index_to_string(TPM2_PCR_KERNEL_BOOT), + memeqzero(h, l) ? " (NOT SET!)" : "", + ansi_normal()); + fflush(stderr); + } + + printf("%" PRIu32 ":%s=%s\n", (uint32_t) TPM2_PCR_KERNEL_BOOT, b, f); + + } else { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *bv = NULL, *a = NULL; + + r = sd_json_buildo( + &bv, + SD_JSON_BUILD_PAIR_INTEGER("pcr", TPM2_PCR_KERNEL_BOOT), + SD_JSON_BUILD_PAIR_HEX("hash", h, l)); + if (r < 0) + return log_error_errno(r, "Failed to build JSON object: %m"); + + a = sd_json_variant_ref(sd_json_variant_by_key(v, b)); + + r = sd_json_variant_append_array(&a, bv); + if (r < 0) + return log_error_errno(r, "Failed to append PCR entry to JSON array: %m"); + + r = sd_json_variant_set_field(&v, b, a); + if (r < 0) + return log_error_errno(r, "Failed to add bank info to object: %m"); + } + } + + if (sd_json_format_enabled(arg_json_format_flags)) { + if (arg_json_format_flags & (SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO)) + pager_open(arg_pager_flags); + + sd_json_variant_dump(v, arg_json_format_flags, stdout, NULL); + } + + return 0; +} + /* The PCR 11 state for one specific bank */ typedef struct PcrState { char *bank; @@ -1000,153 +1147,6 @@ static int verb_policy_digest(int argc, char *argv[], uintptr_t _data, void *use return build_policy_digest(/* sign= */ false); } -static int compare_reported_pcr_nr(uint32_t pcr, const char *varname, const char *description) { - _cleanup_free_ char *s = NULL; - uint32_t v; - int r; - - r = efi_get_variable_string(varname, &s); - if (r == -ENOENT) - return 0; - if (r < 0) - return log_error_errno(r, "Failed to read EFI variable '%s': %m", varname); - - r = safe_atou32(s, &v); - if (r < 0) - return log_error_errno(r, "Failed to parse EFI variable '%s': %s", varname, s); - - if (pcr != v) - log_warning("PCR number reported by stub for %s (%" PRIu32 ") different from our expectation (%" PRIu32 ").\n" - "The measurements are likely inconsistent.", description, v, pcr); - - return 0; -} - -static int validate_stub(void) { - uint64_t features; - bool found = false; - int r; - - if (!tpm2_is_fully_supported()) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Sorry, system lacks full TPM2 support."); - - r = efi_stub_get_features(&features); - if (r < 0) - return log_error_errno(r, "Unable to get stub features: %m"); - - if (!FLAGS_SET(features, EFI_STUB_FEATURE_THREE_PCRS)) - log_warning("Warning: current kernel image does not support measuring itself, the command line or initrd system extension images.\n" - "The PCR measurements seen are unlikely to be valid."); - - r = compare_reported_pcr_nr(TPM2_PCR_KERNEL_BOOT, EFI_LOADER_VARIABLE_STR("StubPcrKernelImage"), "kernel image"); - if (r < 0) - return r; - - STRV_FOREACH(bank, arg_banks) { - _cleanup_free_ char *b = NULL, *p = NULL; - - b = strdup(*bank); - if (!b) - return log_oom(); - - if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/", ascii_strlower(b)) < 0) - return log_oom(); - - if (access(p, F_OK) < 0) { - if (errno != ENOENT) - return log_error_errno(errno, "Failed to detect if '%s' exists: %m", b); - } else - found = true; - } - - if (!found) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "None of the select PCR banks appear to exist."); - - return 0; -} - -static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - int r; - - r = validate_stub(); - if (r < 0) - return r; - - STRV_FOREACH(bank, arg_banks) { - _cleanup_free_ char *b = NULL, *p = NULL, *s = NULL; - _cleanup_free_ void *h = NULL; - size_t l; - - b = strdup(*bank); - if (!b) - return log_oom(); - - if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/%" PRIu32, ascii_strlower(b), (uint32_t) TPM2_PCR_KERNEL_BOOT) < 0) - return log_oom(); - - r = read_virtual_file(p, 4096, &s, NULL); - if (r == -ENOENT) - continue; - if (r < 0) - return log_error_errno(r, "Failed to read '%s': %m", p); - - r = unhexmem(strstrip(s), &h, &l); - if (r < 0) - return log_error_errno(r, "Failed to decode PCR value '%s': %m", s); - - if (!sd_json_format_enabled(arg_json_format_flags)) { - _cleanup_free_ char *f = hexmem(h, l); - if (!f) - return log_oom(); - - if (bank == arg_banks) { - /* before the first line for each PCR, write a short descriptive text to - * stderr, and leave the primary content on stdout */ - fflush(stdout); - fprintf(stderr, "%s# PCR[%" PRIu32 "] %s%s%s\n", - ansi_grey(), - (uint32_t) TPM2_PCR_KERNEL_BOOT, - tpm2_pcr_index_to_string(TPM2_PCR_KERNEL_BOOT), - memeqzero(h, l) ? " (NOT SET!)" : "", - ansi_normal()); - fflush(stderr); - } - - printf("%" PRIu32 ":%s=%s\n", (uint32_t) TPM2_PCR_KERNEL_BOOT, b, f); - - } else { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *bv = NULL, *a = NULL; - - r = sd_json_buildo( - &bv, - SD_JSON_BUILD_PAIR_INTEGER("pcr", TPM2_PCR_KERNEL_BOOT), - SD_JSON_BUILD_PAIR_HEX("hash", h, l)); - if (r < 0) - return log_error_errno(r, "Failed to build JSON object: %m"); - - a = sd_json_variant_ref(sd_json_variant_by_key(v, b)); - - r = sd_json_variant_append_array(&a, bv); - if (r < 0) - return log_error_errno(r, "Failed to append PCR entry to JSON array: %m"); - - r = sd_json_variant_set_field(&v, b, a); - if (r < 0) - return log_error_errno(r, "Failed to add bank info to object: %m"); - } - } - - if (sd_json_format_enabled(arg_json_format_flags)) { - if (arg_json_format_flags & (SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO)) - pager_open(arg_pager_flags); - - sd_json_variant_dump(v, arg_json_format_flags, stdout, NULL); - } - - return 0; -} - static int measure_main(int argc, char *argv[]) { static const Verb verbs[] = { { "help", VERB_ANY, VERB_ANY, 0, verb_help }, From ed469ffc07010bb4a6b4bc2e14c02873b700d151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 23 Apr 2026 09:21:29 +0200 Subject: [PATCH 1206/1296] measure: convert to the new option and verb parsers Previously, we had a nice third 'UKI PE Section' column with the section names. This is now moved into the help strings, which means that the nice alignment is lost. Previous behaviour could be restored by constructing the table manually, but I'm not sure if this is worth the trouble. Co-developed-by: Claude Opus 4.7 --- src/fundamental/uki.h | 2 +- src/measure/measure-tool.c | 352 ++++++++++++++++--------------------- 2 files changed, 152 insertions(+), 202 deletions(-) diff --git a/src/fundamental/uki.h b/src/fundamental/uki.h index 8d67b13b8b5c9..627538a0eb057 100644 --- a/src/fundamental/uki.h +++ b/src/fundamental/uki.h @@ -15,7 +15,7 @@ typedef enum UnifiedSection { UNIFIED_SECTION_DTB, UNIFIED_SECTION_UNAME, UNIFIED_SECTION_SBAT, - UNIFIED_SECTION_PCRSIG, + UNIFIED_SECTION_PCRSIG, /* This is is not measured actually */ UNIFIED_SECTION_PCRPKEY, UNIFIED_SECTION_PROFILE, UNIFIED_SECTION_DTBAUTO, diff --git a/src/measure/measure-tool.c b/src/measure/measure-tool.c index bd1339ddfa677..af52e5aeb86de 100644 --- a/src/measure/measure-tool.c +++ b/src/measure/measure-tool.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-json.h" @@ -12,10 +11,12 @@ #include "efivars.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "hexdecoct.h" #include "log.h" #include "main-func.h" #include "openssl-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -65,71 +66,55 @@ STATIC_DESTRUCTOR_REGISTER(arg_sections, free_sections); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL, *options2 = NULL; int r; r = terminal_urlify_man("systemd-measure", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n%5$sPre-calculate and sign PCR hash for a unified kernel image (UKI).%6$s\n" - "\n%3$sCommands:%4$s\n" - " status Show current PCR values\n" - " calculate Calculate expected PCR values\n" - " sign Calculate and sign expected PCR values\n" - " policy-digest Calculate expected TPM2 policy digests\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version\n" - " --no-pager Do not pipe output into a pager\n" - " -c --current Use current PCR values\n" - " --phase=PHASE Specify a boot phase to sign for\n" - " --bank=DIGEST Select TPM bank (SHA1, SHA256, SHA384, SHA512)\n" - " --tpm2-device=PATH Use specified TPM2 device\n" - " --private-key=KEY Private key (PEM) to sign with\n" - " --private-key-source=file|provider:PROVIDER|engine:ENGINE\n" - " Specify how to use KEY for --private-key=. Allows\n" - " an OpenSSL engine/provider to be used for signing\n" - " --public-key=KEY Public key (PEM) to validate against\n" - " --certificate=PATH|URI\n" - " PEM certificate to use for signing, or a provider\n" - " specific designation if --certificate-source= is used\n" - " --certificate-source=file|provider:PROVIDER\n" - " Specify how to interpret the certificate from\n" - " --certificate=. Allows the certificate to be loaded\n" - " from an OpenSSL provider\n" - " --json=MODE Output as JSON\n" - " -j Same as --json=pretty on tty, --json=short otherwise\n" - " --append=PATH Load specified JSON signature, and append new signature to it\n" - "\n%3$sUKI PE Section Options:%4$s %3$sUKI PE Section%4$s\n" - " --linux=PATH Path to Linux kernel image file %7$s .linux\n" - " --osrel=PATH Path to os-release file %7$s .osrel\n" - " --cmdline=PATH Path to file with kernel command line %7$s .cmdline\n" - " --initrd=PATH Path to initrd image file %7$s .initrd\n" - " --ucode=PATH Path to microcode image file %7$s .ucode\n" - " --splash=PATH Path to splash bitmap file %7$s .splash\n" - " --dtb=PATH Path to DeviceTree file %7$s .dtb\n" - " --dtbauto=PATH Path to DeviceTree file for auto selection %7$s .dtbauto\n" - " --uname=PATH Path to 'uname -r' file %7$s .uname\n" - " --sbat=PATH Path to SBAT file %7$s .sbat\n" - " --pcrpkey=PATH Path to public key for PCR signatures %7$s .pcrpkey\n" - " --profile=PATH Path to profile file %7$s .profile\n" - " --hwids=PATH Path to HWIDs file %7$s .hwids\n" - "\nSee the %2$s for details.\n", + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + r = option_parser_get_help_table_group("UKI PE Section Options", &options2); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options, options2); + + printf("%s [OPTIONS...] COMMAND ...\n" + "\n%sPre-calculate and sign PCR hash for a unified kernel image (UKI).%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal(), - glyph(GLYPH_ARROW_RIGHT)); + ansi_highlight(), ansi_normal(), + ansi_underline(), ansi_normal()); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + printf("\n%sOptions:%s\n", ansi_underline(), ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\n%sUKI PE Section Options:%s\n", ansi_underline(), ansi_normal()); + + r = table_print_or_warn(options2); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} +VERB_COMMON_HELP_HIDDEN(help); static char *normalize_phase(const char *s) { _cleanup_strv_free_ char **l = NULL; @@ -146,110 +131,56 @@ static char *normalize_phase(const char *s) { return strv_join(strv_remove(l, ""), ":"); } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - _ARG_SECTION_FIRST, - ARG_LINUX = _ARG_SECTION_FIRST, - ARG_OSREL, - ARG_CMDLINE, - ARG_INITRD, - ARG_UCODE, - ARG_SPLASH, - ARG_DTB, - ARG_UNAME, - ARG_SBAT, - _ARG_PCRSIG, /* the .pcrsig section is not input for signing, hence not actually an argument here */ - ARG_PCRPKEY, - ARG_PROFILE, - ARG_DTBAUTO, - ARG_HWIDS, - _ARG_SECTION_LAST, - ARG_EFIFW = _ARG_SECTION_LAST, - ARG_BANK, - ARG_PRIVATE_KEY, - ARG_PRIVATE_KEY_SOURCE, - ARG_PUBLIC_KEY, - ARG_CERTIFICATE, - ARG_CERTIFICATE_SOURCE, - ARG_TPM2_DEVICE, - ARG_JSON, - ARG_PHASE, - ARG_APPEND, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "version", no_argument, NULL, ARG_VERSION }, - { "linux", required_argument, NULL, ARG_LINUX }, - { "osrel", required_argument, NULL, ARG_OSREL }, - { "cmdline", required_argument, NULL, ARG_CMDLINE }, - { "initrd", required_argument, NULL, ARG_INITRD }, - { "ucode", required_argument, NULL, ARG_UCODE }, - { "splash", required_argument, NULL, ARG_SPLASH }, - { "dtb", required_argument, NULL, ARG_DTB }, - { "dtbauto", required_argument, NULL, ARG_DTBAUTO }, - { "uname", required_argument, NULL, ARG_UNAME }, - { "sbat", required_argument, NULL, ARG_SBAT }, - { "pcrpkey", required_argument, NULL, ARG_PCRPKEY }, - { "profile", required_argument, NULL, ARG_PROFILE }, - { "hwids", required_argument, NULL, ARG_HWIDS }, - { "current", no_argument, NULL, 'c' }, - { "bank", required_argument, NULL, ARG_BANK }, - { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, - { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, - { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, - { "public-key", required_argument, NULL, ARG_PUBLIC_KEY }, - { "certificate", required_argument, NULL, ARG_CERTIFICATE }, - { "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE }, - { "json", required_argument, NULL, ARG_JSON }, - { "phase", required_argument, NULL, ARG_PHASE }, - { "append", required_argument, NULL, ARG_APPEND }, - {} - }; - - int c, r; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - /* Make sure the arguments list and the section list, stays in sync */ - assert_cc(_ARG_SECTION_FIRST + _UNIFIED_SECTION_MAX == _ARG_SECTION_LAST + 1); + OptionParser state = { argc, argv }; + const Option *opt; + const char *arg; + int r; - while ((c = getopt_long(argc, argv, "hjc", options, NULL)) >= 0) + FOREACH_OPTION_FULL(&state, c, &opt, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case _ARG_SECTION_FIRST..._ARG_SECTION_LAST: { - UnifiedSection section = c - _ARG_SECTION_FIRST; + OPTION('c', "current", NULL, + "Use current PCR values"): + arg_current = true; + break; + + OPTION_LONG("phase", "PHASE", + "Specify a boot phase to sign for"): { + char *n; - r = parse_path_argument(optarg, /* suppress_root= */ false, arg_sections + section); + n = normalize_phase(arg); + if (!n) + return log_oom(); + + r = strv_consume(&arg_phase, TAKE_PTR(n)); if (r < 0) return r; - break; - } - case 'c': - arg_current = true; break; + } - case ARG_BANK: { + OPTION_LONG("bank", "DIGEST", + "Select TPM bank (SHA1, SHA256, SHA384, SHA512)"): { const EVP_MD *implementation; - implementation = EVP_get_digestbyname(optarg); + implementation = EVP_get_digestbyname(arg); if (!implementation) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", arg); if (strv_extend(&arg_banks, EVP_MD_name(implementation)) < 0) return log_oom(); @@ -257,16 +188,33 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_PRIVATE_KEY: - r = free_and_strdup_warn(&arg_private_key, optarg); + OPTION_LONG("tpm2-device", "PATH", + "Use specified TPM2 device"): { + _cleanup_free_ char *device = NULL; + + if (streq(arg, "list")) + return tpm2_list_devices(/* legend= */ true, /* quiet= */ false); + + if (!streq(arg, "auto")) { + device = strdup(arg); + if (!device) + return log_oom(); + } + + free_and_replace(arg_tpm2_device, device); + break; + } + + OPTION_COMMON_PRIVATE_KEY("Private key (PEM) to sign with"): + r = free_and_strdup_warn(&arg_private_key, arg); if (r < 0) return r; break; - case ARG_PRIVATE_KEY_SOURCE: + OPTION_COMMON_PRIVATE_KEY_SOURCE: r = parse_openssl_key_source_argument( - optarg, + arg, &arg_private_key_source, &arg_private_key_source_type); if (r < 0) @@ -274,81 +222,85 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_PUBLIC_KEY: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_public_key); + OPTION_LONG("public-key", "KEY", + "Public key (PEM) to validate against"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_public_key); if (r < 0) return r; break; - case ARG_CERTIFICATE: - r = free_and_strdup_warn(&arg_certificate, optarg); + OPTION_COMMON_CERTIFICATE("PEM certificate to use for signing"): + r = free_and_strdup_warn(&arg_certificate, arg); if (r < 0) return r; break; - case ARG_CERTIFICATE_SOURCE: + OPTION_COMMON_CERTIFICATE_SOURCE: r = parse_openssl_certificate_source_argument( - optarg, + arg, &arg_certificate_source, &arg_certificate_source_type); if (r < 0) return r; break; - case ARG_TPM2_DEVICE: { - _cleanup_free_ char *device = NULL; - - if (streq(optarg, "list")) - return tpm2_list_devices(/* legend= */ true, /* quiet= */ false); - - if (!streq(optarg, "auto")) { - device = strdup(optarg); - if (!device) - return log_oom(); - } - - free_and_replace(arg_tpm2_device, device); - break; - } - - case 'j': - arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; - break; - - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); if (r <= 0) return r; break; - case ARG_PHASE: { - char *n; - - n = normalize_phase(optarg); - if (!n) - return log_oom(); + OPTION_COMMON_LOWERCASE_J: + arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; + break; - r = strv_consume(&arg_phase, TAKE_PTR(n)); + OPTION_LONG("append", "PATH", + "Load specified JSON signature, and append new signature to it"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_append); if (r < 0) return r; break; - } - case ARG_APPEND: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_append); + OPTION_GROUP("UKI PE Section Options"): {} + + OPTION_LONG_DATA("linux", "PATH", UNIFIED_SECTION_LINUX, + "Path to Linux kernel image file (→ .linux)"): {} + OPTION_LONG_DATA("osrel", "PATH", UNIFIED_SECTION_OSREL, + "Path to os-release file (→ .osrel)"): {} + OPTION_LONG_DATA("cmdline", "PATH", UNIFIED_SECTION_CMDLINE, + "Path to file with kernel command line (→ .cmdline)"): {} + OPTION_LONG_DATA("initrd", "PATH", UNIFIED_SECTION_INITRD, + "Path to initrd image file (→ .initrd)"): {} + OPTION_LONG_DATA("ucode", "PATH", UNIFIED_SECTION_UCODE, + "Path to microcode image file (→ .ucode)"): {} + OPTION_LONG_DATA("splash", "PATH", UNIFIED_SECTION_SPLASH, + "Path to splash bitmap file (→ .splash)"): {} + OPTION_LONG_DATA("dtb", "PATH", UNIFIED_SECTION_DTB, + "Path to DeviceTree file (→ .dtb)"): {} + OPTION_LONG_DATA("dtbauto", "PATH", UNIFIED_SECTION_DTBAUTO, + "Path to DeviceTree file for auto selection (→ .dtbauto)"): {} + OPTION_LONG_DATA("uname", "PATH", UNIFIED_SECTION_UNAME, + "Path to 'uname -r' file (→ .uname)"): {} + OPTION_LONG_DATA("sbat", "PATH", UNIFIED_SECTION_SBAT, + "Path to SBAT file (→ .sbat)"): {} + /* The .pcrsig section is not input for signing, hence not actually an argument here */ + OPTION_LONG_DATA("pcrpkey", "PATH", UNIFIED_SECTION_PCRPKEY, + "Path to public key for PCR signatures (→ .pcrpkey)"): {} + OPTION_LONG_DATA("profile", "PATH", UNIFIED_SECTION_PROFILE, + "Path to profile file (→ .profile)"): {} + OPTION_LONG_DATA("hwids", "PATH", UNIFIED_SECTION_HWIDS, + "Path to HWIDs file (→ .hwids)"): + /* Make sure that if new sections are added, the list here is updated. */ + assert_cc(UNIFIED_SECTION_HWIDS + 1 + 1 /* FIXME */ == _UNIFIED_SECTION_MAX); + assert(opt->data < _UNIFIED_SECTION_MAX); + + r = parse_path_argument(arg, /* suppress_root= */ false, arg_sections + opt->data); if (r < 0) return r; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_public_key && arg_certificate) @@ -390,6 +342,8 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); log_debug("Measuring boot phases: %s", j); + + *ret_args = option_parser_get_args(&state); return 1; } @@ -458,6 +412,8 @@ static int validate_stub(void) { return 0; } +VERB(verb_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT, + "Show current PCR values"); static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; int r; @@ -858,6 +814,8 @@ static void pcr_states_restore(PcrState *pcr_states, size_t n) { } } +VERB_NOARG(verb_calculate, "calculate", + "Calculate expected PCR values"); static int verb_calculate(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL; _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL; @@ -1139,37 +1097,29 @@ static int build_policy_digest(bool sign) { return 0; } +VERB_NOARG(verb_sign, "sign", + "Calculate and sign expected PCR values"); static int verb_sign(int argc, char *argv[], uintptr_t _data, void *userdata) { return build_policy_digest(/* sign= */ true); } +VERB_NOARG(verb_policy_digest, "policy-digest", + "Calculate expected TPM2 policy digests"); static int verb_policy_digest(int argc, char *argv[], uintptr_t _data, void *userdata) { return build_policy_digest(/* sign= */ false); } -static int measure_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, - { "calculate", VERB_ANY, 1, 0, verb_calculate }, - { "policy-digest", VERB_ANY, 1, 0, verb_policy_digest }, - { "sign", VERB_ANY, 1, 0, verb_sign }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { int r; log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return measure_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); From 6fdc0f96f778bfce6dec92adf3031660bb1daf12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 23 Apr 2026 11:48:56 +0200 Subject: [PATCH 1207/1296] measure: also measure forgotten .efifw section --- src/measure/measure-tool.c | 6 ++++-- test/units/TEST-70-TPM2.measure.sh | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/measure/measure-tool.c b/src/measure/measure-tool.c index af52e5aeb86de..09c04d888c833 100644 --- a/src/measure/measure-tool.c +++ b/src/measure/measure-tool.c @@ -292,9 +292,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG_DATA("profile", "PATH", UNIFIED_SECTION_PROFILE, "Path to profile file (→ .profile)"): {} OPTION_LONG_DATA("hwids", "PATH", UNIFIED_SECTION_HWIDS, - "Path to HWIDs file (→ .hwids)"): + "Path to HWIDs file (→ .hwids)"): {} + OPTION_LONG_DATA("efifw", "PATH", UNIFIED_SECTION_EFIFW, + "Path to EFI firmware file (→ .efifw)"): {} /* Make sure that if new sections are added, the list here is updated. */ - assert_cc(UNIFIED_SECTION_HWIDS + 1 + 1 /* FIXME */ == _UNIFIED_SECTION_MAX); + assert_cc(UNIFIED_SECTION_EFIFW + 1 == _UNIFIED_SECTION_MAX); assert(opt->data < _UNIFIED_SECTION_MAX); r = parse_path_argument(arg, /* suppress_root= */ false, arg_sections + opt->data); diff --git a/test/units/TEST-70-TPM2.measure.sh b/test/units/TEST-70-TPM2.measure.sh index bf30bd57b3340..30fa51e52137c 100755 --- a/test/units/TEST-70-TPM2.measure.sh +++ b/test/units/TEST-70-TPM2.measure.sh @@ -45,6 +45,12 @@ cat >/tmp/result.json </tmp/result < Date: Thu, 23 Apr 2026 11:57:18 +0200 Subject: [PATCH 1208/1296] TODO: add one more entry This is something that should be fixed for usability, but it's something between a missing feature and a bug. Since nobody has complained about this, it probably can wait. --- TODO.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/TODO.md b/TODO.md index a178e8b9fb1a1..d77225c5720ca 100644 --- a/TODO.md +++ b/TODO.md @@ -2411,10 +2411,12 @@ SPDX-License-Identifier: LGPL-2.1-or-later - **systemd-measure tool:** - pre-calculate PCR 12 (command line) + PCR 13 (sysext) the same way we can precalculate PCR 11 - -- systemd-measure: add --pcrpkey-auto as an alternative to --pcrpkey=, where it - would just use the same public key specified with --public-key= (or the one - automatically derived from --private-key=). + - add --pcrpkey-auto as an alternative to --pcrpkey=, where it would just use + the same public key specified with --public-key= (or the one automatically + derived from --private-key=). + - allow multiple --initrd=, --efifw=, --dtbauto=, etc., params and pad and + concatenate the contents in the same way that ukify does, so we end up with + the expected measurement. - systemd-mount should only consider modern file systems when mounting, similar to systemd-dissect From b4764836396465521181a1d8d2817d078cec0864 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 23 Apr 2026 18:46:57 +0200 Subject: [PATCH 1209/1296] various: use empty block not break after OPTION_GROUP Use the same style everywhere. --- src/bootctl/bootctl.c | 6 ++---- src/imds/imdsd.c | 3 +-- src/sysusers/sysusers.c | 3 +-- src/test/test-options.c | 3 +-- src/tmpfiles/tmpfiles.c | 3 +-- src/vmspawn/vmspawn.c | 30 ++++++++++-------------------- 6 files changed, 16 insertions(+), 32 deletions(-) diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index 9ec25962853b4..59a93d07c3f22 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -422,8 +422,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - OPTION_GROUP("Block Device Discovery Commands"): - break; + OPTION_GROUP("Block Device Discovery Commands"): {} OPTION('p', "print-esp-path", NULL, "Print path to the EFI System Partition mount point"): {} OPTION_LONG("print-path", NULL, /* help= */ NULL): /* Compatibility alias */ @@ -455,8 +454,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { arg_print_efi_architecture = true; break; - OPTION_GROUP("Options"): - break; + OPTION_GROUP("Options"): {} OPTION_COMMON_HELP: return help(); diff --git a/src/imds/imdsd.c b/src/imds/imdsd.c index 8ab3498656602..b3d177116119e 100644 --- a/src/imds/imdsd.c +++ b/src/imds/imdsd.c @@ -2348,8 +2348,7 @@ static int parse_argv(int argc, char *argv[]) { break; /* The following all configure endpoint information explicitly */ - OPTION_GROUP("Manual Endpoint Configuration"): - break; + OPTION_GROUP("Manual Endpoint Configuration"): {} OPTION_LONG("vendor", "VENDOR", "Specify IMDS vendor literally"): if (isempty(arg)) { diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index 1f79e2face5e4..ff391cb316578 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -2121,8 +2121,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_COMMON_VERSION: return version(); - OPTION_GROUP("Options"): - break; + OPTION_GROUP("Options"): {} OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): r = parse_path_argument(arg, /* suppress_root= */ false, &arg_root); diff --git a/src/test/test-options.c b/src/test/test-options.c index 687bafd4a2b9b..1c9073b7a56d7 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -811,8 +811,7 @@ static void test_macros_parse_one( break; /* OPTION_GROUP: group marker (never returned by parser) */ - OPTION_GROUP("Advanced"): - break; + OPTION_GROUP("Advanced"): {} /* OPTION_LONG: long only, in the "Advanced" group */ OPTION_LONG("debug", NULL, "Enable debug mode"): diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 9975ffca3935d..0c133b08c84fe 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -4225,8 +4225,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_COMMON_VERSION: return version(); - OPTION_GROUP("Options"): - break; + OPTION_GROUP("Options"): {} OPTION_LONG("user", NULL, "Execute user configuration"): arg_runtime_scope = RUNTIME_SCOPE_USER; diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index fa8c3402ffc1a..fef8353a2c812 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -359,8 +359,7 @@ static int parse_argv(int argc, char *argv[]) { arg_runtime_scope = RUNTIME_SCOPE_USER; break; - OPTION_GROUP("Image"): - break; + OPTION_GROUP("Image"): {} OPTION('D', "directory", "PATH", "Root directory for the VM"): r = parse_path_argument(arg, /* suppress_root= */ false, &arg_directory); @@ -393,8 +392,7 @@ static int parse_argv(int argc, char *argv[]) { "Invalid image disk type: %s", arg); break; - OPTION_GROUP("Host Configuration"): - break; + OPTION_GROUP("Host Configuration"): {} OPTION_LONG("cpus", "CPUS", "Configure number of CPUs in guest"): {} OPTION_LONG("qemu-smp", "CPUS", /* help= */ NULL): /* Compat alias */ @@ -655,8 +653,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(r, "Failed to parse --grow-image= parameter: %s", arg); break; - OPTION_GROUP("Execution"): - break; + OPTION_GROUP("Execution"): {} OPTION('s', "smbios11", "STRING", "Pass an arbitrary SMBIOS Type #11 string to the VM"): if (isempty(arg)) { @@ -677,8 +674,7 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - OPTION_GROUP("System Identity"): - break; + OPTION_GROUP("System Identity"): {} OPTION('M', "machine", "NAME", "Set the machine name for the VM"): if (isempty(arg)) @@ -702,8 +698,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(r, "Invalid UUID: %s", arg); break; - OPTION_GROUP("Properties"): - break; + OPTION_GROUP("Properties"): {} OPTION('S', "slice", "SLICE", "Place the VM in the specified slice"): { _cleanup_free_ char *mangled = NULL; @@ -732,8 +727,7 @@ static int parse_argv(int argc, char *argv[]) { arg_keep_unit = true; break; - OPTION_GROUP("User Namespacing"): - break; + OPTION_GROUP("User Namespacing"): {} OPTION_LONG("private-users", "UIDBASE[:NUIDS]", "Configure the UID/GID range to map into the virtiofsd namespace"): @@ -742,8 +736,7 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - OPTION_GROUP("Mounts"): - break; + OPTION_GROUP("Mounts"): {} OPTION_LONG("bind", "SOURCE[:TARGET]", "Mount a file or directory from the host into the VM"): {} OPTION_LONG("bind-ro", "SOURCE[:TARGET]", "Mount a file or directory, but read-only"): { @@ -835,8 +828,7 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); break; - OPTION_GROUP("Integration"): - break; + OPTION_GROUP("Integration"): {} OPTION_LONG("forward-journal", "FILE|DIR", "Forward the VM's journal to the host"): r = parse_path_argument(arg, /* suppress_root= */ false, &arg_forward_journal); @@ -864,8 +856,7 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - OPTION_GROUP("Input/Output"): - break; + OPTION_GROUP("Input/Output"): {} OPTION_LONG("console", "MODE", "Console mode (interactive, native, gui, read-only or headless)"): @@ -890,8 +881,7 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - OPTION_GROUP("Credentials"): - break; + OPTION_GROUP("Credentials"): {} OPTION_LONG("set-credential", "ID:VALUE", "Pass a credential with literal value to the VM"): r = machine_credential_set(&arg_credentials, arg); From 4985c70162ae78eff81b990c0c49ce82302f004e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 23 Apr 2026 15:03:06 +0000 Subject: [PATCH 1210/1296] qmp-client: add synchronous qmp_client_call() Add a synchronous counterpart to qmp_client_invoke() that pumps the client's own process()/wait() loop until the reply for the issued command id arrives, mirroring sd_varlink_call()'s contract: *ret_result and *ret_error_desc are borrowed pointers into c->current, valid until the next qmp_client_call(), and a QMP error surfaces as -EIO when the caller doesn't ask for the description. Factor the command-build + slot-insert + enqueue sequence shared with qmp_client_invoke() into qmp_client_send(). A NULL callback marks the slot as synchronous: dispatch_reply still matches on id (so unknown ids continue to be logged and discarded, preserving async-only robustness), but skips the TAKE_PTR + callback invocation and leaves c->current pinned for qmp_client_call() to read out. Cover the three paths in test-qmp-client: successful reply, QMP error with ret_error_desc, and QMP error returned as -EIO. --- src/shared/qmp-client.c | 96 +++++++++++++++++++++++-- src/shared/qmp-client.h | 10 +++ src/test/test-qmp-client.c | 142 +++++++++++++++++++++++++++++++++++++ 3 files changed, 242 insertions(+), 6 deletions(-) diff --git a/src/shared/qmp-client.c b/src/shared/qmp-client.c index dec965a46032b..53a30abe5a9b5 100644 --- a/src/shared/qmp-client.c +++ b/src/shared/qmp-client.c @@ -232,6 +232,11 @@ static int qmp_client_dispatch_reply(QmpClient *c) { return 1; } + /* Synchronous slot (no callback): leave c->current pinned so qmp_client_call() can + * pick up the reply and hand out borrowed pointers into it. */ + if (!pending->callback) + return 1; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = TAKE_PTR(c->current); error = qmp_parse_response(v, &result, &desc); @@ -250,9 +255,11 @@ static void qmp_client_fail_pending(QmpClient *c, int error) { assert(c); while ((p = set_steal_first(c->slots))) { - r = p->callback(c, /* result= */ NULL, /* error_desc= */ NULL, error, p->userdata); - if (r < 0) - json_stream_log_errno(&c->stream, r, "Command callback returned error, ignoring: %m"); + if (p->callback) { + r = p->callback(c, /* result= */ NULL, /* error_desc= */ NULL, error, p->userdata); + if (r < 0) + json_stream_log_errno(&c->stream, r, "Command callback returned error, ignoring: %m"); + } free(p); } } @@ -692,12 +699,16 @@ static QmpClientArgs* qmp_client_args_close_fds(QmpClientArgs *p) { DEFINE_TRIVIAL_CLEANUP_FUNC(QmpClientArgs*, qmp_client_args_close_fds); -int qmp_client_invoke( +/* Shared send path for qmp_client_invoke() and qmp_client_call(). A NULL callback registers + * a "synchronous" slot: dispatch_reply leaves c->current pinned on match instead of invoking + * a callback, so qmp_client_call() can hand out borrowed pointers into the reply. */ +static int qmp_client_send( QmpClient *c, const char *command, QmpClientArgs *args, qmp_command_callback_t callback, - void *userdata) { + void *userdata, + uint64_t *ret_id) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *cmd = NULL; _cleanup_free_ QmpSlot *pending = NULL; @@ -709,7 +720,6 @@ int qmp_client_invoke( assert(c); assert(command); - assert(callback); r = qmp_client_ensure_running(c); if (r < 0) @@ -748,9 +758,83 @@ int qmp_client_invoke( TAKE_PTR(pending); TAKE_PTR(fds_owner); + + if (ret_id) + *ret_id = id; return 0; } +int qmp_client_invoke( + QmpClient *c, + const char *command, + QmpClientArgs *args, + qmp_command_callback_t callback, + void *userdata) { + + assert(callback); + return qmp_client_send(c, command, args, callback, userdata, /* ret_id= */ NULL); +} + +int qmp_client_call( + QmpClient *c, + const char *command, + QmpClientArgs *args, + sd_json_variant **ret_result, + const char **ret_error_desc) { + + uint64_t id; + int r; + + assert_return(c, -EINVAL); + assert_return(command, -EINVAL); + + /* Drop any reply pinned by a previous qmp_client_call() before we pin a new one. */ + qmp_client_clear_current(c); + + /* NULL callback marks this as a synchronous slot: dispatch_reply matches on id like + * any other slot (so stray unknown-id replies still get logged and dropped), but + * pins c->current for us instead of invoking a callback. */ + r = qmp_client_send(c, command, args, /* callback= */ NULL, /* userdata= */ NULL, &id); + if (r < 0) + return r; + + /* Pump the loop until our sync slot fires (removed from c->slots, c->current pinned). */ + for (;;) { + if (c->state == QMP_CLIENT_DISCONNECTED) + return -ECONNRESET; + + if (!set_contains(c->slots, &(QmpSlot) { .id = id })) { + assert(c->current); + break; + } + + r = qmp_client_process(c); + if (r < 0) + return r; + if (r > 0) + continue; + + r = qmp_client_wait(c, USEC_INFINITY); + if (r < 0) + return r; + } + + sd_json_variant *result = NULL; + const char *desc = NULL; + int error = qmp_parse_response(c->current, &result, &desc); + + /* If caller doesn't ask for the error string, surface the error as the return code. */ + if (!ret_error_desc && error < 0) + return error; + + if (ret_result) + *ret_result = result; + if (ret_error_desc) + *ret_error_desc = desc; + + return 1; +} + void qmp_client_bind_event(QmpClient *c, qmp_event_callback_t callback, void *userdata) { assert(c); c->event_callback = callback; diff --git a/src/shared/qmp-client.h b/src/shared/qmp-client.h index dbe65162e9722..b3ee8262e01e9 100644 --- a/src/shared/qmp-client.h +++ b/src/shared/qmp-client.h @@ -62,6 +62,16 @@ int qmp_client_invoke( qmp_command_callback_t callback, void *userdata); +/* Synchronous send + receive. Pumps the event loop until the reply arrives. *ret_result and + * *ret_error_desc are borrowed pointers into the last reply, valid until the next + * qmp_client_call(). Same contract as sd_varlink_call(). */ +int qmp_client_call( + QmpClient *client, + const char *command, + QmpClientArgs *args, + sd_json_variant **ret_result, + const char **ret_error_desc); + void qmp_client_bind_event(QmpClient *c, qmp_event_callback_t callback, void *userdata); void qmp_client_bind_disconnect(QmpClient *c, qmp_disconnect_callback_t callback, void *userdata); int qmp_client_set_description(QmpClient *c, const char *description); diff --git a/src/test/test-qmp-client.c b/src/test/test-qmp-client.c index 9bfe48770f798..e8683ee76a177 100644 --- a/src/test/test-qmp-client.c +++ b/src/test/test-qmp-client.c @@ -473,6 +473,148 @@ TEST(qmp_client_invoke_failure_closes_fds) { ASSERT_EQ(errno, EBADF); } +/* Reads one command, asserts its execute name, and replies with a QMP error object carrying + * the given description. Mirrors mock_qmp_expect_and_reply() but on the error branch. */ +static void mock_qmp_expect_and_reply_error(int fd, const char *expected_command, const char *error_desc) { + _cleanup_free_ char *buf = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *cmd = NULL, *error_obj = NULL, *response = NULL; + + buf = ASSERT_NOT_NULL(new(char, 4096)); + + ssize_t n = read(fd, buf, 4095); + assert_se(n > 0); + buf[n] = '\0'; + + ASSERT_OK(sd_json_parse(buf, 0, &cmd, NULL, NULL)); + + sd_json_variant *execute = ASSERT_NOT_NULL(sd_json_variant_by_key(cmd, "execute")); + ASSERT_STREQ(sd_json_variant_string(execute), expected_command); + + sd_json_variant *id = ASSERT_NOT_NULL(sd_json_variant_by_key(cmd, "id")); + + ASSERT_OK(sd_json_buildo( + &error_obj, + SD_JSON_BUILD_PAIR_STRING("class", "GenericError"), + SD_JSON_BUILD_PAIR_STRING("desc", error_desc))); + + ASSERT_OK(sd_json_buildo( + &response, + SD_JSON_BUILD_PAIR("error", SD_JSON_BUILD_VARIANT(error_obj)), + SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_VARIANT(id)))); + + mock_qmp_write_json(fd, response); +} + +/* Drives a small wire dance for the sync call test: greeting, capabilities, one successful + * command reply, and two error replies (one for the ret_error_desc path, one for the -EIO + * path). */ +static _noreturn_ void mock_qmp_server_call(int fd) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *status_return = NULL; + + mock_qmp_write_literal(fd, + "{\"QMP\": {\"version\": {\"qemu\": {\"micro\": 0, \"minor\": 0, \"major\": 9}}, \"capabilities\": []}}"); + + mock_qmp_expect_and_reply(fd, "qmp_capabilities", NULL); + + ASSERT_OK(sd_json_buildo( + &status_return, + SD_JSON_BUILD_PAIR_BOOLEAN("running", true), + SD_JSON_BUILD_PAIR_STRING("status", "running"))); + mock_qmp_expect_and_reply(fd, "query-status", status_return); + + mock_qmp_expect_and_reply_error(fd, "stop", "not running"); + mock_qmp_expect_and_reply_error(fd, "stop", "still not running"); + + safe_close(fd); + _exit(EXIT_SUCCESS); +} + +TEST(qmp_client_call) { + _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; + _cleanup_(pidref_done_sigkill_wait) PidRef pid = PIDREF_NULL; + int qmp_fds[2]; + int r; + + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + + r = ASSERT_OK(pidref_safe_fork("(mock-qmp-call)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid)); + if (r == 0) { + safe_close(qmp_fds[0]); + mock_qmp_server_call(qmp_fds[1]); + } + safe_close(qmp_fds[1]); + + /* qmp_client_call() drives its own process()+wait() pump, so no event loop needed. */ + ASSERT_OK(qmp_client_connect_fd(&client, qmp_fds[0])); + + /* Successful call: borrowed result pointer is valid until the next call. */ + sd_json_variant *result = NULL; + const char *error_desc = NULL; + ASSERT_EQ(qmp_client_call(client, "query-status", NULL, &result, &error_desc), 1); + ASSERT_NULL(error_desc); + ASSERT_NOT_NULL(result); + + sd_json_variant *running = ASSERT_NOT_NULL(sd_json_variant_by_key(result, "running")); + ASSERT_TRUE(sd_json_variant_boolean(running)); + sd_json_variant *status = ASSERT_NOT_NULL(sd_json_variant_by_key(result, "status")); + ASSERT_STREQ(sd_json_variant_string(status), "running"); + + /* QMP error with ret_error_desc provided: returns 1, result NULL, desc set. */ + result = (sd_json_variant*) 0x1; /* poison to catch lack-of-write */ + error_desc = NULL; + ASSERT_EQ(qmp_client_call(client, "stop", NULL, &result, &error_desc), 1); + ASSERT_NULL(result); + ASSERT_STREQ(error_desc, "not running"); + + /* QMP error without ret_error_desc: surfaces as -EIO. */ + ASSERT_EQ(qmp_client_call(client, "stop", NULL, NULL, NULL), -EIO); +} + +/* Server variant for the sync-call disconnect test: greets, accepts capabilities, reads one + * command without replying, then closes the socket so the client sees EOF mid-wait. */ +static _noreturn_ void mock_qmp_server_call_disconnect(int fd) { + _cleanup_free_ char *buf = NULL; + + mock_qmp_write_literal(fd, + "{\"QMP\": {\"version\": {\"qemu\": {\"micro\": 0, \"minor\": 0, \"major\": 9}}, \"capabilities\": []}}"); + + mock_qmp_expect_and_reply(fd, "qmp_capabilities", NULL); + + /* Consume the stop command but don't reply — just close to trigger EOF while the + * client is blocked in qmp_client_call()'s process+wait pump. */ + buf = ASSERT_NOT_NULL(new(char, 4096)); + ssize_t n = read(fd, buf, 4095); + assert_se(n > 0); + + safe_close(fd); + _exit(EXIT_SUCCESS); +} + +TEST(qmp_client_call_disconnect) { + _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; + _cleanup_(pidref_done_sigkill_wait) PidRef pid = PIDREF_NULL; + int qmp_fds[2]; + int r; + + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + + r = ASSERT_OK(pidref_safe_fork("(mock-qmp-call-disc)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid)); + if (r == 0) { + safe_close(qmp_fds[0]); + mock_qmp_server_call_disconnect(qmp_fds[1]); + } + safe_close(qmp_fds[1]); + + ASSERT_OK(qmp_client_connect_fd(&client, qmp_fds[0])); + + /* The server reads our stop command and closes without replying. qmp_client_call() + * is driving its own pump, so it must notice the EOF, transition to DISCONNECTED, + * and return a disconnect error rather than hanging. */ + r = qmp_client_call(client, "stop", NULL, NULL, NULL); + ASSERT_TRUE(r < 0); + ASSERT_TRUE(ERRNO_IS_NEG_DISCONNECT(r)); +} + TEST(qmp_schema_has_member) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *schema = NULL; From bbadd35596949678009e3f5a7cf4689853998b79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Taveira=20Ara=C3=BAjo?= Date: Thu, 16 Apr 2026 12:13:30 -0700 Subject: [PATCH 1211/1296] networkd: allow route table names for VRF.Table= Allow `[VRF] Table=` to accept route table names in addition to numeric table identifiers. These may be predefined route table names or names configured with `networkd.conf` `RouteTable=`. There was an earlier attempt to make `VRF.Table=` accept names in f98dd1e707, but it wired the setting to `config_parse_route_table()`. That parser was a `[Route]` section parser, not a generic scalar parser for netdevs: it expected network/route parser state and created a `Route` object. It was therefore reverted by 40352cf0c1. This commit replaces the uint32 parser with `manager_get_route_table_from_string()`, the generic table parser already used by route/rule, DHCP/RA `RouteTable=`, and WireGuard `RouteTable=` in `.netdev` files. The VRF semantics stay unchanged. The commit retains the existing behavior of the deprecated `TableId=` field. Co-developed-by: OpenAI Codex --- man/systemd.netdev.xml | 5 ++++- src/network/netdev/netdev-gperf.gperf | 2 +- src/network/netdev/vrf.c | 32 +++++++++++++++++++++++++++ src/network/netdev/vrf.h | 2 ++ src/network/test-network.c | 24 ++++++++++++++++++++ 5 files changed, 63 insertions(+), 2 deletions(-) diff --git a/man/systemd.netdev.xml b/man/systemd.netdev.xml index 6a84b7a648cef..6879518b4b8a3 100644 --- a/man/systemd.netdev.xml +++ b/man/systemd.netdev.xml @@ -2683,7 +2683,10 @@ Ports=eth2 Table= - The numeric routing table identifier. This setting is compulsory. + The routing table identifier. Takes a route table name or number. Route table names + may be predefined or configured with RouteTable= in + networkd.conf5. + This setting is compulsory. diff --git a/src/network/netdev/netdev-gperf.gperf b/src/network/netdev/netdev-gperf.gperf index 250b6cf7bcde9..269a974542408 100644 --- a/src/network/netdev/netdev-gperf.gperf +++ b/src/network/netdev/netdev-gperf.gperf @@ -249,7 +249,7 @@ Bridge.MulticastIGMPVersion, config_parse_bridge_igmp_version, Bridge.FDBMaxLearned, config_parse_bridge_fdb_max_learned, 0, offsetof(Bridge, fdb_max_learned) Bridge.LinkLocalLearning, config_parse_tristate, 0, offsetof(Bridge, linklocal_learn) VRF.TableId, config_parse_uint32, 0, offsetof(Vrf, table) /* deprecated */ -VRF.Table, config_parse_uint32, 0, offsetof(Vrf, table) +VRF.Table, config_parse_vrf_table, 0, offsetof(Vrf, table) BareUDP.DestinationPort, config_parse_ip_port, 0, offsetof(BareUDP, dest_port) BareUDP.MinSourcePort, config_parse_ip_port, 0, offsetof(BareUDP, min_port) BareUDP.EtherType, config_parse_bare_udp_iftype, 0, offsetof(BareUDP, iftype) diff --git a/src/network/netdev/vrf.c b/src/network/netdev/vrf.c index 540c269f2d38d..d91ccf394ae32 100644 --- a/src/network/netdev/vrf.c +++ b/src/network/netdev/vrf.c @@ -4,8 +4,40 @@ #include "sd-netlink.h" +#include "networkd-route-util.h" #include "vrf.h" +int config_parse_vrf_table( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Vrf *vrf = ASSERT_PTR(userdata); + uint32_t *table = ASSERT_PTR(data); + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = manager_get_route_table_from_string(vrf->meta.manager, rvalue, table); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse %s=, ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + return 0; +} + static int netdev_vrf_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) { assert(!link); assert(m); diff --git a/src/network/netdev/vrf.h b/src/network/netdev/vrf.h index 7bf94478567a7..a794c237c98de 100644 --- a/src/network/netdev/vrf.h +++ b/src/network/netdev/vrf.h @@ -11,3 +11,5 @@ typedef struct Vrf { DEFINE_NETDEV_CAST(VRF, Vrf); extern const NetDevVTable vrf_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_vrf_table); diff --git a/src/network/test-network.c b/src/network/test-network.c index 6582d3b074079..ab3a86e10b193 100644 --- a/src/network/test-network.c +++ b/src/network/test-network.c @@ -11,6 +11,7 @@ #include "networkd-route-util.h" #include "strv.h" #include "tests.h" +#include "vrf.h" TEST(deserialize_in_addr) { _cleanup_free_ struct in_addr *addresses = NULL; @@ -150,6 +151,29 @@ TEST(route_tables) { test_route_tables_one(manager, "local", 255); } +TEST(vrf_table) { + _cleanup_(manager_freep) Manager *manager = NULL; + Vrf vrf = {}; + + ASSERT_OK(manager_new(&manager, /* test_mode= */ true)); + ASSERT_OK(manager_setup(manager)); + + vrf.meta.manager = manager; + + ASSERT_OK(config_parse_vrf_table("netdev", "filename", 1, "VRF", 1, "Table", 0, "default", &vrf.table, &vrf)); + ASSERT_EQ(vrf.table, 253U); + + ASSERT_OK(config_parse_route_table_names("manager", "filename", 1, "section", 1, "RouteTable", 0, "vrf-test:1234", manager, manager)); + ASSERT_OK(config_parse_vrf_table("netdev", "filename", 1, "VRF", 1, "Table", 0, "vrf-test", &vrf.table, &vrf)); + ASSERT_EQ(vrf.table, 1234U); + + ASSERT_OK(config_parse_vrf_table("netdev", "filename", 1, "VRF", 1, "Table", 0, "5678", &vrf.table, &vrf)); + ASSERT_EQ(vrf.table, 5678U); + + ASSERT_OK(config_parse_vrf_table("netdev", "filename", 1, "VRF", 1, "Table", 0, "no-such-table", &vrf.table, &vrf)); + ASSERT_EQ(vrf.table, 5678U); +} + TEST(manager_enumerate) { _cleanup_(manager_freep) Manager *manager = NULL; From 6a3b0e847509fd3281989d3f3db1fb5104815d72 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 18 Mar 2026 19:26:22 +0900 Subject: [PATCH 1212/1296] sd-dhcp-client: move the object definition to the header Then, we can split the long sd-dhcp-client.c into small pieces later. This also drops redundant typedef, which is also in sd-dhcp-client.h. --- src/libsystemd-network/dhcp-client-internal.h | 67 ++++++++++++++++++- src/libsystemd-network/sd-dhcp-client.c | 67 ------------------- 2 files changed, 65 insertions(+), 69 deletions(-) diff --git a/src/libsystemd-network/dhcp-client-internal.h b/src/libsystemd-network/dhcp-client-internal.h index f75ca71b4350c..07e61b0d760e1 100644 --- a/src/libsystemd-network/dhcp-client-internal.h +++ b/src/libsystemd-network/dhcp-client-internal.h @@ -3,8 +3,11 @@ #include "sd-dhcp-client.h" -#include "sd-forward.h" +#include "dhcp-client-id-internal.h" +#include "ether-addr-util.h" #include "network-common.h" +#include "sd-forward.h" +#include "socket-util.h" typedef enum DHCPState { DHCP_STATE_STOPPED, @@ -22,7 +25,67 @@ typedef enum DHCPState { DECLARE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_state, DHCPState); -typedef struct sd_dhcp_client sd_dhcp_client; +struct sd_dhcp_client { + unsigned n_ref; + + DHCPState state; + sd_event *event; + int event_priority; + sd_event_source *timeout_resend; + + int ifindex; + char *ifname; + + sd_device *dev; + + int fd; + uint16_t port; + uint16_t server_port; + union sockaddr_union link; + sd_event_source *receive_message; + bool request_broadcast; + Set *req_opts; + bool anonymize; + bool rapid_commit; + be32_t last_addr; + struct hw_addr_data hw_addr; + struct hw_addr_data bcast_addr; + uint16_t arp_type; + sd_dhcp_client_id client_id; + char *hostname; + char *vendor_class_identifier; + char *mudurl; + char **user_class; + uint32_t mtu; + usec_t fallback_lease_lifetime; + uint32_t xid; + usec_t start_time; + usec_t t1_time; + usec_t t2_time; + usec_t expire_time; + uint64_t discover_attempt; + uint64_t request_attempt; + uint64_t max_discover_attempts; + uint64_t max_request_attempts; + OrderedHashmap *extra_options; + OrderedHashmap *vendor_options; + sd_event_source *timeout_t1; + sd_event_source *timeout_t2; + sd_event_source *timeout_expire; + sd_event_source *timeout_ipv6_only_mode; + sd_dhcp_client_callback_t callback; + void *userdata; + sd_dhcp_client_callback_t state_callback; + void *state_userdata; + sd_dhcp_lease *lease; + usec_t start_delay; + int ip_service_type; + int socket_priority; + bool socket_priority_set; + bool ipv6_acquired; + bool bootp; + bool send_release; +}; int dhcp_client_set_state_callback( sd_dhcp_client *client, diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 19f85e09df945..c530e6c222666 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -7,11 +7,8 @@ #include #include -#include "sd-dhcp-client.h" - #include "alloc-util.h" #include "device-util.h" -#include "dhcp-client-id-internal.h" #include "dhcp-client-internal.h" #include "dhcp-lease-internal.h" #include "dhcp-network.h" @@ -19,7 +16,6 @@ #include "dhcp-packet.h" #include "dns-domain.h" #include "errno-util.h" -#include "ether-addr-util.h" #include "event-util.h" #include "fd-util.h" #include "hostname-util.h" @@ -28,7 +24,6 @@ #include "network-common.h" #include "random-util.h" #include "set.h" -#include "socket-util.h" #include "sort-util.h" #include "string-table.h" #include "string-util.h" @@ -44,68 +39,6 @@ #define TRANSIENT_FAILURE_ATTEMPTS 3 /* Arbitrary limit: how many attempts are considered enough to report * transient failure. */ -struct sd_dhcp_client { - unsigned n_ref; - - DHCPState state; - sd_event *event; - int event_priority; - sd_event_source *timeout_resend; - - int ifindex; - char *ifname; - - sd_device *dev; - - int fd; - uint16_t port; - uint16_t server_port; - union sockaddr_union link; - sd_event_source *receive_message; - bool request_broadcast; - Set *req_opts; - bool anonymize; - bool rapid_commit; - be32_t last_addr; - struct hw_addr_data hw_addr; - struct hw_addr_data bcast_addr; - uint16_t arp_type; - sd_dhcp_client_id client_id; - char *hostname; - char *vendor_class_identifier; - char *mudurl; - char **user_class; - uint32_t mtu; - usec_t fallback_lease_lifetime; - uint32_t xid; - usec_t start_time; - usec_t t1_time; - usec_t t2_time; - usec_t expire_time; - uint64_t discover_attempt; - uint64_t request_attempt; - uint64_t max_discover_attempts; - uint64_t max_request_attempts; - OrderedHashmap *extra_options; - OrderedHashmap *vendor_options; - sd_event_source *timeout_t1; - sd_event_source *timeout_t2; - sd_event_source *timeout_expire; - sd_event_source *timeout_ipv6_only_mode; - sd_dhcp_client_callback_t callback; - void *userdata; - sd_dhcp_client_callback_t state_callback; - void *state_userdata; - sd_dhcp_lease *lease; - usec_t start_delay; - int ip_service_type; - int socket_priority; - bool socket_priority_set; - bool ipv6_acquired; - bool bootp; - bool send_release; -}; - static const uint8_t default_req_opts[] = { SD_DHCP_OPTION_SUBNET_MASK, SD_DHCP_OPTION_ROUTER, From 3817ab9e1412bb47e3b47cdba663eed4985dae2b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 24 Apr 2026 07:28:30 +0900 Subject: [PATCH 1213/1296] sd-dhcp-client: add one missing assertion Found and suggested by Claude. Nice! --- src/libsystemd-network/sd-dhcp-client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index c530e6c222666..6bc96a47e7113 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -1476,7 +1476,7 @@ static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) } static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) { - sd_dhcp_client *client = userdata; + sd_dhcp_client *client = ASSERT_PTR(userdata); DHCP_CLIENT_DONT_DESTROY(client); int r; From 16d08897bbe1c6c9357bf1a845dd3efe2a5fd316 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 10 Mar 2026 08:57:15 +0900 Subject: [PATCH 1214/1296] sd-dhcp-client: open socket when necessary and close it when unnecessary To make gracefully ignore unexpected messages from outside at unexpected timing. This potentially reduces work load to handle such messages, and slightly reduces attack surface by malicious DHCP messages. This also makes the socket fd is owned by the relevant IO event source. Except for the performance optimization and security hardening, this should not change any behaviors. So, just refactoring. --- src/libsystemd-network/dhcp-client-internal.h | 12 +- src/libsystemd-network/dhcp-client-send.c | 187 ++++++++++++++++++ src/libsystemd-network/dhcp-client-send.h | 18 ++ src/libsystemd-network/fuzz-dhcp-client.c | 1 + src/libsystemd-network/meson.build | 1 + src/libsystemd-network/sd-dhcp-client.c | 151 ++------------ 6 files changed, 239 insertions(+), 131 deletions(-) create mode 100644 src/libsystemd-network/dhcp-client-send.c create mode 100644 src/libsystemd-network/dhcp-client-send.h diff --git a/src/libsystemd-network/dhcp-client-internal.h b/src/libsystemd-network/dhcp-client-internal.h index 07e61b0d760e1..b59f0e632ddb6 100644 --- a/src/libsystemd-network/dhcp-client-internal.h +++ b/src/libsystemd-network/dhcp-client-internal.h @@ -38,7 +38,6 @@ struct sd_dhcp_client { sd_device *dev; - int fd; uint16_t port; uint16_t server_port; union sockaddr_union link; @@ -93,6 +92,17 @@ int dhcp_client_set_state_callback( void *userdata); int dhcp_client_get_state(sd_dhcp_client *client); +int client_receive_message_raw( + sd_event_source *s, + int fd, + uint32_t revents, + void *userdata); +int client_receive_message_udp( + sd_event_source *s, + int fd, + uint32_t revents, + void *userdata); + /* If we are invoking callbacks of a dhcp-client, ensure unreffing the * client from the callback doesn't destroy the object we are working * on */ diff --git a/src/libsystemd-network/dhcp-client-send.c b/src/libsystemd-network/dhcp-client-send.c new file mode 100644 index 0000000000000..a802cb5e86744 --- /dev/null +++ b/src/libsystemd-network/dhcp-client-send.c @@ -0,0 +1,187 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-event.h" + +#include "dhcp-client-internal.h" +#include "dhcp-client-send.h" +#include "dhcp-lease-internal.h" /* IWYU pragma: keep */ +#include "dhcp-network.h" +#include "dhcp-packet.h" +#include "fd-util.h" +#include "socket-util.h" + +static int client_get_socket(sd_dhcp_client *client, int domain) { + int r, d, fd; + + assert(client); + assert(IN_SET(domain, AF_PACKET, AF_INET)); + + if (!client->receive_message) + return -EBADF; + + fd = sd_event_source_get_io_fd(client->receive_message); + if (fd < 0) + return fd; + + r = getsockopt_int(fd, SOL_SOCKET, SO_DOMAIN, &d); + if (r < 0) + return r; + + if (d != domain) + return -EBADF; + + return fd; +} + +static int client_setup_io_event( + sd_dhcp_client *client, + int fd, + sd_event_io_handler_t callback, + const char *description) { + + int r; + + assert(client); + assert(fd >= 0); + assert(callback); + assert(description); + + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + r = sd_event_add_io(client->event, &s, fd, EPOLLIN, callback, client); + if (r < 0) + return r; + + r = sd_event_source_set_priority(s, client->event_priority); + if (r < 0) + return r; + + r = sd_event_source_set_description(s, description); + if (r < 0) + return r; + + r = sd_event_source_set_io_fd_own(s, true); + if (r < 0) + return r; + + sd_event_source_disable_unref(client->receive_message); + client->receive_message = TAKE_PTR(s); + return 0; +} + +int dhcp_client_send_raw( + sd_dhcp_client *client, + bool expect_reply, + DHCPPacket *packet, + size_t optoffset) { + + _cleanup_close_ int fd_close = -EBADF; + int r, fd; + + assert(client); + assert(packet); + + fd = client_get_socket(client, AF_PACKET); + if (fd < 0) { + fd = dhcp_network_bind_raw_socket( + client->ifindex, + &client->link, + client->xid, + &client->hw_addr, + &client->bcast_addr, + client->arp_type, + client->port, + client->socket_priority_set, + client->socket_priority); + if (fd < 0) + return fd; + + fd_close = fd; + } + + dhcp_packet_append_ip_headers( + packet, + INADDR_ANY, + client->port, + INADDR_BROADCAST, + client->server_port, + sizeof(DHCPPacket) + optoffset, + client->ip_service_type); + + r = dhcp_network_send_raw_socket( + fd, + &client->link, + packet, + sizeof(DHCPPacket) + optoffset); + if (r < 0) + return r; + + if (!expect_reply) { + /* We do not expect any replies, hence stop the IO event source if enabled. */ + client->receive_message = sd_event_source_disable_unref(client->receive_message); + return 0; + } + + if (fd_close < 0) + return 0; /* Already opened socket is reused. Not necessary to setup new IO event source. */ + + r = client_setup_io_event(client, fd, client_receive_message_raw, "dhcp4-receive-message-raw"); + if (r < 0) + return r; + + TAKE_FD(fd_close); + return 0; +} + +int dhcp_client_send_udp( + sd_dhcp_client *client, + bool expect_reply, + DHCPPacket *packet, + size_t optoffset) { + + _cleanup_close_ int fd_close = -EBADF; + int r, fd; + + assert(client); + assert(packet); + + if (!client->lease || client->lease->address == 0) + return -EADDRNOTAVAIL; + + fd = client_get_socket(client, AF_INET); + if (fd < 0) { + fd = dhcp_network_bind_udp_socket( + client->ifindex, + client->lease->address, + client->port, + client->ip_service_type); + if (fd < 0) + return fd; + + fd_close = fd; + } + + r = dhcp_network_send_udp_socket( + fd, + client->lease->server_address, + client->server_port, + &packet->dhcp, + sizeof(DHCPMessage) + optoffset); + if (r < 0) + return r; + + if (!expect_reply) { + /* We do not expect any replies, hence stop the IO event source if enabled. */ + client->receive_message = sd_event_source_disable_unref(client->receive_message); + return 0; + } + + if (fd_close < 0) + return 0; /* Already opened socket is reused. Not necessary to setup new IO event source. */ + + r = client_setup_io_event(client, fd, client_receive_message_udp, "dhcp4-receive-message-udp"); + if (r < 0) + return r; + + TAKE_FD(fd_close); + return 0; +} diff --git a/src/libsystemd-network/dhcp-client-send.h b/src/libsystemd-network/dhcp-client-send.h new file mode 100644 index 0000000000000..2dbb6a0878dcc --- /dev/null +++ b/src/libsystemd-network/dhcp-client-send.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-forward.h" + +#include "dhcp-protocol.h" + +int dhcp_client_send_raw( + sd_dhcp_client *client, + bool expect_reply, + DHCPPacket *packet, + size_t optoffset); + +int dhcp_client_send_udp( + sd_dhcp_client *client, + bool expect_reply, + DHCPPacket *packet, + size_t optoffset); diff --git a/src/libsystemd-network/fuzz-dhcp-client.c b/src/libsystemd-network/fuzz-dhcp-client.c index 7a59faff6312b..21f693c4873a3 100644 --- a/src/libsystemd-network/fuzz-dhcp-client.c +++ b/src/libsystemd-network/fuzz-dhcp-client.c @@ -3,6 +3,7 @@ #include #include "dhcp-network.h" +#include "fd-util.h" #include "fuzz.h" #include "network-internal.h" #include "sd-dhcp-client.c" diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index 2eeabc075e04f..d1e13d99b536f 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -2,6 +2,7 @@ libsystemd_network_sources = files( 'arp-util.c', + 'dhcp-client-send.c', 'dhcp-network.c', 'dhcp-option.c', 'dhcp-packet.c', diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 6bc96a47e7113..88a831e9cefc4 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -10,14 +10,13 @@ #include "alloc-util.h" #include "device-util.h" #include "dhcp-client-internal.h" +#include "dhcp-client-send.h" #include "dhcp-lease-internal.h" -#include "dhcp-network.h" #include "dhcp-option.h" #include "dhcp-packet.h" #include "dns-domain.h" #include "errno-util.h" #include "event-util.h" -#include "fd-util.h" #include "hostname-util.h" #include "iovec-util.h" #include "memory-util.h" @@ -73,16 +72,6 @@ static const uint8_t default_req_opts_anonymize[] = { SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY, /* 252 */ }; -static int client_receive_message_raw( - sd_event_source *s, - int fd, - uint32_t revents, - void *userdata); -static int client_receive_message_udp( - sd_event_source *s, - int fd, - uint32_t revents, - void *userdata); static void client_stop(sd_dhcp_client *client, int error); static int client_restart(sd_dhcp_client *client); @@ -635,18 +624,22 @@ static int client_notify(sd_dhcp_client *client, int event) { return 0; } -static void client_initialize(sd_dhcp_client *client) { +static void client_disable_event_sources(sd_dhcp_client *client) { assert(client); client->receive_message = sd_event_source_disable_unref(client->receive_message); - client->fd = safe_close(client->fd); - (void) event_source_disable(client->timeout_resend); (void) event_source_disable(client->timeout_t1); (void) event_source_disable(client->timeout_t2); (void) event_source_disable(client->timeout_expire); (void) event_source_disable(client->timeout_ipv6_only_mode); +} + +static void client_initialize(sd_dhcp_client *client) { + assert(client); + + client_disable_event_sources(client); client->discover_attempt = 0; client->request_attempt = 0; @@ -896,18 +889,6 @@ static int client_append_fqdn_option( return r; } -static int dhcp_client_send_raw( - sd_dhcp_client *client, - DHCPPacket *packet, - size_t len) { - - dhcp_packet_append_ip_headers(packet, INADDR_ANY, client->port, - INADDR_BROADCAST, client->server_port, len, client->ip_service_type); - - return dhcp_network_send_raw_socket(client->fd, &client->link, - packet, len); -} - static int client_append_common_discover_request_options(sd_dhcp_client *client, DHCPPacket *packet, size_t *optoffset, size_t optlen) { sd_dhcp_option *j; int r; @@ -1022,7 +1003,7 @@ static int client_send_dhcp_discover(sd_dhcp_client *client) { if (r < 0) return r; - r = dhcp_client_send_raw(client, discover, sizeof(DHCPPacket) + optoffset); + r = dhcp_client_send_raw(client, /* expect_reply= */ true, discover, optoffset); if (r < 0) return r; @@ -1060,7 +1041,7 @@ static int client_send_bootp_discover(sd_dhcp_client *client) { optoffset = 60; } - r = dhcp_client_send_raw(client, discover, sizeof(DHCPPacket) + optoffset); + r = dhcp_client_send_raw(client, /* expect_reply= */ true, discover, optoffset); if (r < 0) return r; @@ -1153,13 +1134,9 @@ static int client_send_request(sd_dhcp_client *client) { return r; if (client->state == DHCP_STATE_RENEWING) - r = dhcp_network_send_udp_socket(client->fd, - client->lease->server_address, - client->server_port, - &request->dhcp, - sizeof(DHCPMessage) + optoffset); + r = dhcp_client_send_udp(client, /* expect_reply= */ true, request, optoffset); else - r = dhcp_client_send_raw(client, request, sizeof(DHCPPacket) + optoffset); + r = dhcp_client_send_raw(client, /* expect_reply= */ true, request, optoffset); if (r < 0) return r; @@ -1328,34 +1305,6 @@ static int client_timeout_resend( return 0; } -static int client_initialize_io_events( - sd_dhcp_client *client, - sd_event_io_handler_t io_callback) { - - int r; - - assert(client); - assert(client->event); - assert(io_callback); - - _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; - r = sd_event_add_io(client->event, &s, client->fd, EPOLLIN, io_callback, client); - if (r < 0) - return r; - - r = sd_event_source_set_priority(s, client->event_priority); - if (r < 0) - return r; - - r = sd_event_source_set_description(s, "dhcp4-receive-message"); - if (r < 0) - return r; - - sd_event_source_disable_unref(client->receive_message); - client->receive_message = TAKE_PTR(s); - return 0; -} - static int client_initialize_time_events(sd_dhcp_client *client) { assert(client); assert(client->event); @@ -1375,45 +1324,20 @@ static int client_initialize_time_events(sd_dhcp_client *client) { /* force_reset= */ true); } -static int client_initialize_events(sd_dhcp_client *client, sd_event_io_handler_t io_callback) { - int r; - - assert(client); - assert(io_callback); - - r = client_initialize_io_events(client, io_callback); - if (r < 0) - return r; - - return client_initialize_time_events(client); -} - static int client_start_delayed(sd_dhcp_client *client) { - int r; - assert_return(client, -EINVAL); assert_return(client->event, -EINVAL); assert_return(client->ifindex > 0, -EINVAL); - assert_return(client->fd < 0, -EBUSY); assert_return(client->xid == 0, -EINVAL); assert_return(IN_SET(client->state, DHCP_STATE_STOPPED, DHCP_STATE_INIT_REBOOT), -EBUSY); client->xid = random_u32(); - - r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid, - &client->hw_addr, &client->bcast_addr, - client->arp_type, client->port, - client->socket_priority_set, client->socket_priority); - if (r < 0) - return r; - client->fd = r; - client->start_time = now(CLOCK_BOOTTIME); if (client->state == DHCP_STATE_STOPPED) client->state = DHCP_STATE_INIT; - return client_initialize_events(client, client_receive_message_raw); + return client_initialize_time_events(client); } static int client_start(sd_dhcp_client *client) { @@ -1452,23 +1376,12 @@ static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) int r; client->receive_message = sd_event_source_disable_unref(client->receive_message); - client->fd = safe_close(client->fd); client_set_state(client, DHCP_STATE_REBINDING); client->discover_attempt = 0; client->request_attempt = 0; - r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid, - &client->hw_addr, &client->bcast_addr, - client->arp_type, client->port, - client->socket_priority_set, client->socket_priority); - if (r < 0) { - client_stop(client, r); - return 0; - } - client->fd = r; - - r = client_initialize_events(client, client_receive_message_raw); + r = client_initialize_time_events(client); if (r < 0) client_stop(client, r); @@ -1713,7 +1626,7 @@ static int client_enter_requesting(sd_dhcp_client *client) { assert(client); assert(client->lease); - (void) event_source_disable(client->timeout_resend); + client_disable_event_sources(client); if (client->lease->ipv6_only_preferred_usec > 0) { if (client->ipv6_acquired) { @@ -1905,23 +1818,7 @@ static int client_enter_bound_now(sd_dhcp_client *client, int notify_event) { if (r < 0) log_dhcp_client_errno(client, r, "could not set lease timeouts: %m"); - if (client->bootp) { - client->receive_message = sd_event_source_disable_unref(client->receive_message); - client->fd = safe_close(client->fd); - } else { - r = dhcp_network_bind_udp_socket(client->ifindex, client->lease->address, client->port, client->ip_service_type); - if (r < 0) - return log_dhcp_client_errno(client, r, "could not bind UDP socket: %m"); - - client->receive_message = sd_event_source_disable_unref(client->receive_message); - close_and_replace(client->fd, r); - r = client_initialize_io_events(client, client_receive_message_udp); - if (r < 0) - return r; - } - client_notify(client, notify_event); - return 0; } @@ -1941,8 +1838,9 @@ static int client_enter_bound(sd_dhcp_client *client, int notify_event) { assert(client); assert(client->lease); + client_disable_event_sources(client); + client->start_delay = 0; - (void) event_source_disable(client->timeout_resend); /* RFC 8925 section 3.2 * If the client is in the INIT-REBOOT state, it SHOULD stop the DHCPv4 configuration process or @@ -2098,7 +1996,7 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, s return 0; } -static int client_receive_message_udp( +int client_receive_message_udp( sd_event_source *s, int fd, uint32_t revents, @@ -2151,7 +2049,7 @@ static int client_receive_message_udp( return 0; } -static int client_receive_message_raw( +int client_receive_message_raw( sd_event_source *s, int fd, uint32_t revents, @@ -2330,15 +2228,10 @@ static int client_send_release_or_decline(sd_dhcp_client *client, uint8_t type) switch (type) { case DHCP_RELEASE: - r = dhcp_network_send_udp_socket( - client->fd, - client->lease->server_address, - client->server_port, - &packet->dhcp, - sizeof(DHCPMessage) + optoffset); + r = dhcp_client_send_udp(client, /* expect_reply= */ false, packet, optoffset); break; case DHCP_DECLINE: - r = dhcp_client_send_raw(client, packet, sizeof(DHCPPacket) + optoffset); + r = dhcp_client_send_raw(client, /* expect_reply= */ false, packet, optoffset); break; default: assert_not_reached(); @@ -2401,7 +2294,6 @@ int sd_dhcp_client_set_ipv6_connectivity(sd_dhcp_client *client, int have) { int sd_dhcp_client_interrupt_ipv6_only_mode(sd_dhcp_client *client) { assert_return(client, -EINVAL); assert_return(sd_dhcp_client_is_running(client), -ESTALE); - assert_return(client->fd >= 0, -EINVAL); if (sd_event_source_get_enabled(client->timeout_ipv6_only_mode, NULL) <= 0) return 0; @@ -2497,7 +2389,6 @@ int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) { .n_ref = 1, .state = DHCP_STATE_STOPPED, .ifindex = -1, - .fd = -EBADF, .mtu = DHCP_MIN_PACKET_SIZE, .port = DHCP_PORT_CLIENT, .server_port = DHCP_PORT_SERVER, From c32105081fc25dd6fdd9378e221479ac7509c779 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 13 Mar 2026 14:23:06 +0900 Subject: [PATCH 1215/1296] sd-dhcp-client: introduce client_send_discover() No functional change, just refactoring. --- src/libsystemd-network/sd-dhcp-client.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 88a831e9cefc4..720f23cba8ce9 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -965,7 +965,6 @@ static int client_send_dhcp_discover(sd_dhcp_client *client) { int r; assert(client); - assert(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_SELECTING)); r = client_message_init(client, DHCP_DISCOVER, &discover, &optlen, &optoffset); if (r < 0) @@ -1008,7 +1007,6 @@ static int client_send_dhcp_discover(sd_dhcp_client *client) { return r; log_dhcp_client(client, "DISCOVER"); - return 0; } @@ -1018,7 +1016,6 @@ static int client_send_bootp_discover(sd_dhcp_client *client) { int r; assert(client); - assert(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_SELECTING)); r = client_message_init(client, DHCP_DISCOVER, &discover, &optlen, &optoffset); if (r < 0) @@ -1049,6 +1046,15 @@ static int client_send_bootp_discover(sd_dhcp_client *client) { return 0; } +static int client_send_discover(sd_dhcp_client *client) { + assert(client); + assert(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_SELECTING)); + + return client->bootp ? + client_send_bootp_discover(client) : + client_send_dhcp_discover(client); +} + static int client_send_request(sd_dhcp_client *client) { _cleanup_free_ DHCPPacket *request = NULL; size_t optoffset, optlen; @@ -1241,10 +1247,7 @@ static int client_timeout_resend( switch (client->state) { case DHCP_STATE_INIT: - if (client->bootp) - r = client_send_bootp_discover(client); - else - r = client_send_dhcp_discover(client); + r = client_send_discover(client); if (r >= 0) { client_set_state(client, DHCP_STATE_SELECTING); client->discover_attempt = 0; @@ -1253,10 +1256,7 @@ static int client_timeout_resend( break; case DHCP_STATE_SELECTING: - if (client->bootp) - r = client_send_bootp_discover(client); - else - r = client_send_dhcp_discover(client); + r = client_send_discover(client); if (r < 0 && client->discover_attempt >= client->max_discover_attempts) goto error; break; From ffafefbcceb8a4afada4cb1172b9057e1a7c56cc Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 14 Mar 2026 06:34:19 +0900 Subject: [PATCH 1216/1296] sd-dhcp-client: replace max_request_attempts with constant macro --- src/libsystemd-network/dhcp-client-internal.h | 1 - src/libsystemd-network/sd-dhcp-client.c | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/libsystemd-network/dhcp-client-internal.h b/src/libsystemd-network/dhcp-client-internal.h index b59f0e632ddb6..e08ea3deda0ff 100644 --- a/src/libsystemd-network/dhcp-client-internal.h +++ b/src/libsystemd-network/dhcp-client-internal.h @@ -65,7 +65,6 @@ struct sd_dhcp_client { uint64_t discover_attempt; uint64_t request_attempt; uint64_t max_discover_attempts; - uint64_t max_request_attempts; OrderedHashmap *extra_options; OrderedHashmap *vendor_options; sd_event_source *timeout_t1; diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 720f23cba8ce9..7cfebcb5963d3 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -35,6 +35,7 @@ #define RESTART_AFTER_NAK_MIN_USEC (1 * USEC_PER_SEC) #define RESTART_AFTER_NAK_MAX_USEC (30 * USEC_PER_MINUTE) +#define MAX_REQUEST_ATTEMPTS 5 #define TRANSIENT_FAILURE_ATTEMPTS 3 /* Arbitrary limit: how many attempts are considered enough to report * transient failure. */ @@ -1222,7 +1223,7 @@ static int client_timeout_resend( break; case DHCP_STATE_REQUESTING: case DHCP_STATE_BOUND: - if (client->request_attempt >= client->max_request_attempts) + if (client->request_attempt >= MAX_REQUEST_ATTEMPTS) goto error; client->request_attempt++; @@ -1266,7 +1267,7 @@ static int client_timeout_resend( case DHCP_STATE_RENEWING: case DHCP_STATE_REBINDING: r = client_send_request(client); - if (r < 0 && client->request_attempt >= client->max_request_attempts) + if (r < 0 && client->request_attempt >= MAX_REQUEST_ATTEMPTS) goto error; if (client->state == DHCP_STATE_INIT_REBOOT) @@ -1292,7 +1293,7 @@ static int client_timeout_resend( /* Avoid REQUEST infinite loop. Per RFC 2131 section 3.1.5: if the client receives neither a DHCPACK or a DHCPNAK message after employing the retransmission algorithm, the client reverts to INIT state and restarts the initialization process */ - if (client->request_attempt >= client->max_request_attempts) { + if (client->request_attempt >= MAX_REQUEST_ATTEMPTS) { log_dhcp_client(client, "Max REQUEST attempts reached. Restarting..."); r = client_restart(client); if (r >= 0) @@ -2394,7 +2395,6 @@ int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) { .server_port = DHCP_PORT_SERVER, .anonymize = !!anonymize, .max_discover_attempts = UINT64_MAX, - .max_request_attempts = 5, .ip_service_type = -1, }; /* NOTE: this could be moved to a function. */ From 0b3cbfd1bc247c7323ba0286b6df8961a68f2fbd Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 13 Mar 2026 14:33:42 +0900 Subject: [PATCH 1217/1296] sd-dhcp-client: enter the SELECTING state before sending DHCPDISCOVER Similarly, enter the REBOOTING state before sending DHCPREQUEST on reboot. Also, this makes DHCPREQUEST message is sent several times also in REBOOTING state. Previously, we wait about 4 seconds after DHCPREQUEST on reboot, and entered the init state if no response. Now we wait 1 second after the first DHCPREQUEST, resend another DHCPREQUEST, wait 2 seconds, then enter the init state if no response. So, even in the worst case, we have slight speed up. --- src/libsystemd-network/sd-dhcp-client.c | 113 ++++++++++-------------- 1 file changed, 48 insertions(+), 65 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 7cfebcb5963d3..f037f80cdfd76 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -35,6 +35,7 @@ #define RESTART_AFTER_NAK_MIN_USEC (1 * USEC_PER_SEC) #define RESTART_AFTER_NAK_MAX_USEC (30 * USEC_PER_MINUTE) +#define MAX_REQUEST_ATTEMPTS_ON_REBOOTING 2 #define MAX_REQUEST_ATTEMPTS 5 #define TRANSIENT_FAILURE_ATTEMPTS 3 /* Arbitrary limit: how many attempts are considered enough to report * transient failure. */ @@ -1049,7 +1050,7 @@ static int client_send_bootp_discover(sd_dhcp_client *client) { static int client_send_discover(sd_dhcp_client *client) { assert(client); - assert(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_SELECTING)); + assert(client->state == DHCP_STATE_SELECTING); return client->bootp ? client_send_bootp_discover(client) : @@ -1090,10 +1091,9 @@ static int client_send_request(sd_dhcp_client *client) { 4, &client->lease->address); if (r < 0) return r; - break; - case DHCP_STATE_INIT_REBOOT: + case DHCP_STATE_REBOOTING: /* ’server identifier’ MUST NOT be filled in, ’requested IP address’ option MUST be filled in with client’s notion of its previously assigned address. ’ciaddr’ MUST be zero. @@ -1119,16 +1119,10 @@ static int client_send_request(sd_dhcp_client *client) { This message MUST be broadcast to the 0xffffffff IP broadcast address. */ request->dhcp.ciaddr = client->lease->address; - break; - case DHCP_STATE_INIT: - case DHCP_STATE_SELECTING: - case DHCP_STATE_REBOOTING: - case DHCP_STATE_BOUND: - case DHCP_STATE_STOPPED: default: - return -EINVAL; + assert_not_reached(); } r = client_append_common_discover_request_options(client, request, &optoffset, optlen); @@ -1153,8 +1147,8 @@ static int client_send_request(sd_dhcp_client *client) { log_dhcp_client(client, "REQUEST (requesting)"); break; - case DHCP_STATE_INIT_REBOOT: - log_dhcp_client(client, "REQUEST (init-reboot)"); + case DHCP_STATE_REBOOTING: + log_dhcp_client(client, "REQUEST (rebooting)"); break; case DHCP_STATE_RENEWING: @@ -1166,14 +1160,12 @@ static int client_send_request(sd_dhcp_client *client) { break; default: - log_dhcp_client(client, "REQUEST (invalid)"); + assert_not_reached(); } return 0; } -static int client_start(sd_dhcp_client *client); - static int client_timeout_resend( sd_event_source *s, uint64_t usec, @@ -1201,39 +1193,43 @@ static int client_timeout_resend( next_timeout = client_compute_reacquisition_timeout(time_now, client->expire_time); break; - case DHCP_STATE_REBOOTING: - /* start over as we did not receive a timely ack or nak */ - client_initialize(client); - - r = client_start(client); - if (r < 0) - goto error; - - log_dhcp_client(client, "REBOOTED"); - return 0; - case DHCP_STATE_INIT: - case DHCP_STATE_INIT_REBOOT: + client_set_state(client, DHCP_STATE_SELECTING); + _fallthrough_; + case DHCP_STATE_SELECTING: - if (client->discover_attempt >= client->max_discover_attempts) + if (client->discover_attempt >= client->max_discover_attempts) { + r = -ETIMEDOUT; goto error; + } client->discover_attempt++; next_timeout = client_compute_request_timeout(client->discover_attempt); break; + + case DHCP_STATE_INIT_REBOOT: + client_set_state(client, DHCP_STATE_REBOOTING); + _fallthrough_; + + case DHCP_STATE_REBOOTING: + /* There is nothing explicitly mentioned about retry interval on reboot. Let's reuse the same + * algorithm as in the requesting state below, but slightly speed up for faster reboot. */ + + if (client->request_attempt >= MAX_REQUEST_ATTEMPTS_ON_REBOOTING) + goto restart; + + client->request_attempt++; + next_timeout = client_compute_request_timeout(client->request_attempt) / 4; + break; + case DHCP_STATE_REQUESTING: - case DHCP_STATE_BOUND: if (client->request_attempt >= MAX_REQUEST_ATTEMPTS) - goto error; + goto restart; client->request_attempt++; next_timeout = client_compute_request_timeout(client->request_attempt); break; - case DHCP_STATE_STOPPED: - r = -EINVAL; - goto error; - default: assert_not_reached(); } @@ -1247,58 +1243,45 @@ static int client_timeout_resend( goto error; switch (client->state) { - case DHCP_STATE_INIT: - r = client_send_discover(client); - if (r >= 0) { - client_set_state(client, DHCP_STATE_SELECTING); - client->discover_attempt = 0; - } else if (client->discover_attempt >= client->max_discover_attempts) - goto error; - break; - case DHCP_STATE_SELECTING: r = client_send_discover(client); if (r < 0 && client->discover_attempt >= client->max_discover_attempts) goto error; + + if (client->discover_attempt >= TRANSIENT_FAILURE_ATTEMPTS) + client_notify(client, SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE); + break; + + case DHCP_STATE_REBOOTING: + r = client_send_request(client); + if (r < 0 && client->request_attempt >= MAX_REQUEST_ATTEMPTS_ON_REBOOTING) + goto restart; break; - case DHCP_STATE_INIT_REBOOT: case DHCP_STATE_REQUESTING: case DHCP_STATE_RENEWING: case DHCP_STATE_REBINDING: r = client_send_request(client); if (r < 0 && client->request_attempt >= MAX_REQUEST_ATTEMPTS) - goto error; - - if (client->state == DHCP_STATE_INIT_REBOOT) - client_set_state(client, DHCP_STATE_REBOOTING); - break; - - case DHCP_STATE_REBOOTING: - case DHCP_STATE_BOUND: + goto restart; break; - case DHCP_STATE_STOPPED: default: - r = -EINVAL; - goto error; + assert_not_reached(); } - if (client->discover_attempt >= TRANSIENT_FAILURE_ATTEMPTS) - client_notify(client, SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE); - return 0; -error: +restart: /* Avoid REQUEST infinite loop. Per RFC 2131 section 3.1.5: if the client receives neither a DHCPACK or a DHCPNAK message after employing the retransmission algorithm, the client reverts to INIT state and restarts the initialization process */ - if (client->request_attempt >= MAX_REQUEST_ATTEMPTS) { - log_dhcp_client(client, "Max REQUEST attempts reached. Restarting..."); - r = client_restart(client); - if (r >= 0) - return 0; - } + log_dhcp_client(client, "Max REQUEST attempts reached. Restarting..."); + r = client_restart(client); + if (r >= 0) + return 0; + +error: client_stop(client, r); /* Errors were dealt with when stopping the client, don't spill From ffc3d5f812e899cf32d2dc610988cabb2f3f5810 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 14 Mar 2026 01:15:22 +0900 Subject: [PATCH 1218/1296] sd-dhcp-client: initialize event source and so on in client_start_delayed() When we start the client, any previous state/configuration should be cleaned. Let's effectively do the same thing as client_initialize() in that function. This also several assertions in client_start_delayed() to sd_dhcp_client_start(). These kind of checks should be done earlier. --- src/libsystemd-network/sd-dhcp-client.c | 35 ++++++++++--------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index f037f80cdfd76..f301018d4f7a2 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -1309,17 +1309,17 @@ static int client_initialize_time_events(sd_dhcp_client *client) { } static int client_start_delayed(sd_dhcp_client *client) { - assert_return(client, -EINVAL); - assert_return(client->event, -EINVAL); - assert_return(client->ifindex > 0, -EINVAL); - assert_return(client->xid == 0, -EINVAL); - assert_return(IN_SET(client->state, DHCP_STATE_STOPPED, DHCP_STATE_INIT_REBOOT), -EBUSY); + assert(client); + DHCP_CLIENT_DONT_DESTROY(client); + + client_disable_event_sources(client); + client->lease = sd_dhcp_lease_unref(client->lease); client->xid = random_u32(); client->start_time = now(CLOCK_BOOTTIME); - if (client->state == DHCP_STATE_STOPPED) - client->state = DHCP_STATE_INIT; + if (client->state != DHCP_STATE_INIT_REBOOT) + client_set_state(client, DHCP_STATE_INIT); return client_initialize_time_events(client); } @@ -1340,16 +1340,12 @@ static int client_timeout_expire(sd_event_source *s, uint64_t usec, void *userda client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED); - /* lease was lost, start over if not freed or stopped in callback */ - if (client->state != DHCP_STATE_STOPPED) { - client_initialize(client); + if (client->state == DHCP_STATE_STOPPED) + return 0; /* The notify callback stopped the client. */ - r = client_start(client); - if (r < 0) { - client_stop(client, r); - return 0; - } - } + r = client_start(client); + if (r < 0) + client_stop(client, r); return 0; } @@ -1861,8 +1857,6 @@ static int client_restart(sd_dhcp_client *client) { client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED); - client_initialize(client); - r = client_start_delayed(client); if (r < 0) return r; @@ -2120,12 +2114,12 @@ int sd_dhcp_client_start(sd_dhcp_client *client) { int r; assert_return(client, -EINVAL); + assert_return(client->event, -EINVAL); + assert_return(client->ifindex > 0, -EINVAL); /* Note, do not reset the flag in client_initialize(), as it is also called on expire. */ client->ipv6_acquired = false; - client_initialize(client); - /* If no client identifier exists, construct an RFC 4361-compliant one */ if (!sd_dhcp_client_id_is_set(&client->client_id)) { r = sd_dhcp_client_set_iaid_duid_en(client, /* iaid_set= */ false, /* iaid= */ 0); @@ -2282,7 +2276,6 @@ int sd_dhcp_client_interrupt_ipv6_only_mode(sd_dhcp_client *client) { if (sd_event_source_get_enabled(client->timeout_ipv6_only_mode, NULL) <= 0) return 0; - client_initialize(client); return client_start(client); } From f0b9b679ffb30de3d2b3ef3623912a65ac157b67 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 14 Mar 2026 01:26:45 +0900 Subject: [PATCH 1219/1296] sd-dhcp-client: simply enter renewing/rebinding state send DHCPREQUEST on T1/T2 It is not necessary to enable another timer event source to send DHCPREQUEST from the T1/T2 timer event source. Just call the callback function for sending message. Also, T1 hits only we have a bound lease. Drop spurious conditions. --- src/libsystemd-network/sd-dhcp-client.c | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index f301018d4f7a2..7a08861eb8190 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -1353,38 +1353,28 @@ static int client_timeout_expire(sd_event_source *s, uint64_t usec, void *userda static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) { sd_dhcp_client *client = ASSERT_PTR(userdata); DHCP_CLIENT_DONT_DESTROY(client); - int r; + /* Explicitly close the unicast socket opened during renewing. On success path, the socket will be + * closed anyway on sending broadcast DHCPREQUEST, but let's explicitly close it here for failure + * path to ignore all unicast replies from now on. */ client->receive_message = sd_event_source_disable_unref(client->receive_message); client_set_state(client, DHCP_STATE_REBINDING); client->discover_attempt = 0; client->request_attempt = 0; - r = client_initialize_time_events(client); - if (r < 0) - client_stop(client, r); - - return 0; + return client_timeout_resend(s, usec, userdata); } static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) { sd_dhcp_client *client = ASSERT_PTR(userdata); DHCP_CLIENT_DONT_DESTROY(client); - int r; - if (client->lease) - client_set_state(client, DHCP_STATE_RENEWING); - else if (client->state != DHCP_STATE_INIT) - client_set_state(client, DHCP_STATE_INIT_REBOOT); + client_set_state(client, DHCP_STATE_RENEWING); client->discover_attempt = 0; client->request_attempt = 0; - r = client_initialize_time_events(client); - if (r < 0) - client_stop(client, r); - - return 0; + return client_timeout_resend(s, usec, userdata); } static int dhcp_option_parse_and_verify( From bb5b2f9e5a340407394226a60af21658e88c64a2 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 15 Apr 2026 08:47:42 +0900 Subject: [PATCH 1220/1296] sd-dhcp-client: simplify the implementation of IPv6 Only mode support This drop delay after ACK, as it has many problems. See comment in sd_dhcp_client_is_waiting_for_ipv6_connectivity() for more details. This way, the logic becomes much much simpler. Also, do not restart the client if we lost IPv6 connectivity in sd_dhcp_client side, but restart the client by networkd. As, sd_dhcp_client does not know if we can start the client or not, e.g., the interface may be currently down. --- src/libsystemd-network/dhcp-client-internal.h | 1 - src/libsystemd-network/sd-dhcp-client.c | 167 +++++++----------- src/network/networkd-dhcp4.c | 28 +-- src/systemd/sd-dhcp-client.h | 2 +- 4 files changed, 78 insertions(+), 120 deletions(-) diff --git a/src/libsystemd-network/dhcp-client-internal.h b/src/libsystemd-network/dhcp-client-internal.h index e08ea3deda0ff..fab4ff24aaf99 100644 --- a/src/libsystemd-network/dhcp-client-internal.h +++ b/src/libsystemd-network/dhcp-client-internal.h @@ -70,7 +70,6 @@ struct sd_dhcp_client { sd_event_source *timeout_t1; sd_event_source *timeout_t2; sd_event_source *timeout_expire; - sd_event_source *timeout_ipv6_only_mode; sd_dhcp_client_callback_t callback; void *userdata; sd_dhcp_client_callback_t state_callback; diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 7a08861eb8190..76558bdd72e04 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -635,7 +635,6 @@ static void client_disable_event_sources(sd_dhcp_client *client) { (void) event_source_disable(client->timeout_t1); (void) event_source_disable(client->timeout_t2); (void) event_source_disable(client->timeout_expire); - (void) event_source_disable(client->timeout_ipv6_only_mode); } static void client_initialize(sd_dhcp_client *client) { @@ -1293,8 +1292,6 @@ static int client_initialize_time_events(sd_dhcp_client *client) { assert(client); assert(client->event); - (void) event_source_disable(client->timeout_ipv6_only_mode); - return event_reset_time_relative( client->event, &client->timeout_resend, @@ -1566,39 +1563,17 @@ static int client_handle_offer_or_rapid_ack(sd_dhcp_client *client, DHCPMessage return 0; } -static int client_enter_requesting_now(sd_dhcp_client *client) { - assert(client); - - client_set_state(client, DHCP_STATE_REQUESTING); - client->discover_attempt = 0; - client->request_attempt = 0; - - return event_reset_time(client->event, &client->timeout_resend, - CLOCK_BOOTTIME, 0, 0, - client_timeout_resend, client, - client->event_priority, "dhcp4-resend-timer", - /* force_reset= */ true); -} - -static int client_enter_requesting_delayed(sd_event_source *s, uint64_t usec, void *userdata) { - sd_dhcp_client *client = ASSERT_PTR(userdata); - DHCP_CLIENT_DONT_DESTROY(client); - int r; - - r = client_enter_requesting_now(client); - if (r < 0) - client_stop(client, r); - - return 0; -} - static int client_enter_requesting(sd_dhcp_client *client) { assert(client); assert(client->lease); client_disable_event_sources(client); - if (client->lease->ipv6_only_preferred_usec > 0) { + client_set_state(client, DHCP_STATE_REQUESTING); + client->discover_attempt = 0; + client->request_attempt = 0; + + if (sd_dhcp_client_is_waiting_for_ipv6_connectivity(client)) { if (client->ipv6_acquired) { log_dhcp_client(client, "Received an OFFER with IPv6-only preferred option, and the host already acquired IPv6 connectivity, stopping DHCPv4 client."); @@ -1608,16 +1583,19 @@ static int client_enter_requesting(sd_dhcp_client *client) { log_dhcp_client(client, "Received an OFFER with IPv6-only preferred option, delaying to send REQUEST with %s.", FORMAT_TIMESPAN(client->lease->ipv6_only_preferred_usec, USEC_PER_SEC)); - - return event_reset_time_relative(client->event, &client->timeout_ipv6_only_mode, - CLOCK_BOOTTIME, - client->lease->ipv6_only_preferred_usec, 0, - client_enter_requesting_delayed, client, - client->event_priority, "dhcp4-ipv6-only-mode-timer", - /* force_reset= */ true); } - return client_enter_requesting_now(client); + return event_reset_time_relative( + client->event, + &client->timeout_resend, + CLOCK_BOOTTIME, + client->lease->ipv6_only_preferred_usec, + /* accuracy= */ 0, + client_timeout_resend, + client, + client->event_priority, + "dhcp4-resend-timer", + /* force_reset= */ true); } static bool lease_equal(const sd_dhcp_lease *a, const sd_dhcp_lease *b) { @@ -1770,14 +1748,19 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { return 0; } -static int client_enter_bound_now(sd_dhcp_client *client, int notify_event) { +static int client_enter_bound(sd_dhcp_client *client, int notify_event) { int r; assert(client); + assert(client->lease); if (IN_SET(client->state, DHCP_STATE_REQUESTING, DHCP_STATE_REBOOTING)) notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE; + client_disable_event_sources(client); + + client->start_delay = 0; + client_set_state(client, DHCP_STATE_BOUND); client->discover_attempt = 0; client->request_attempt = 0; @@ -1786,61 +1769,12 @@ static int client_enter_bound_now(sd_dhcp_client *client, int notify_event) { r = client_set_lease_timeouts(client); if (r < 0) - log_dhcp_client_errno(client, r, "could not set lease timeouts: %m"); + log_dhcp_client_errno(client, r, "Failed to set lease timeouts: %m"); client_notify(client, notify_event); return 0; } -static int client_enter_bound_delayed(sd_event_source *s, uint64_t usec, void *userdata) { - sd_dhcp_client *client = ASSERT_PTR(userdata); - DHCP_CLIENT_DONT_DESTROY(client); - int r; - - r = client_enter_bound_now(client, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); - if (r < 0) - client_stop(client, r); - - return 0; -} - -static int client_enter_bound(sd_dhcp_client *client, int notify_event) { - assert(client); - assert(client->lease); - - client_disable_event_sources(client); - - client->start_delay = 0; - - /* RFC 8925 section 3.2 - * If the client is in the INIT-REBOOT state, it SHOULD stop the DHCPv4 configuration process or - * disable the IPv4 stack completely for V6ONLY_WAIT seconds or until the network attachment event, - * whichever happens first. - * - * In the below, the condition uses REBOOTING, instead of INIT-REBOOT, as the client state has - * already transitioned from INIT-REBOOT to REBOOTING after sending a DHCPREQUEST message. */ - if (client->state == DHCP_STATE_REBOOTING && client->lease->ipv6_only_preferred_usec > 0) { - if (client->ipv6_acquired) { - log_dhcp_client(client, - "Received an ACK with IPv6-only preferred option, and the host already acquired IPv6 connectivity, stopping DHCPv4 client."); - return sd_dhcp_client_stop(client); - } - - log_dhcp_client(client, - "Received an ACK with IPv6-only preferred option, delaying to enter bound state with %s.", - FORMAT_TIMESPAN(client->lease->ipv6_only_preferred_usec, USEC_PER_SEC)); - - return event_reset_time_relative(client->event, &client->timeout_ipv6_only_mode, - CLOCK_BOOTTIME, - client->lease->ipv6_only_preferred_usec, 0, - client_enter_bound_delayed, client, - client->event_priority, "dhcp4-ipv6-only-mode", - /* force_reset= */ true); - } - - return client_enter_bound_now(client, notify_event); -} - static int client_restart(sd_dhcp_client *client) { int r; assert(client); @@ -2107,9 +2041,6 @@ int sd_dhcp_client_start(sd_dhcp_client *client) { assert_return(client->event, -EINVAL); assert_return(client->ifindex > 0, -EINVAL); - /* Note, do not reset the flag in client_initialize(), as it is also called on expire. */ - client->ipv6_acquired = false; - /* If no client identifier exists, construct an RFC 4361-compliant one */ if (!sd_dhcp_client_id_is_set(&client->client_id)) { r = sd_dhcp_client_set_iaid_duid_en(client, /* iaid_set= */ false, /* iaid= */ 0); @@ -2245,28 +2176,49 @@ int sd_dhcp_client_stop(sd_dhcp_client *client) { return 0; } +int sd_dhcp_client_is_waiting_for_ipv6_connectivity(sd_dhcp_client *client) { + /* Note that we intentionally do not implement the following behavior: + * + * RFC 8925, section 3.2: + * If the client is in the INIT-REBOOT state, it SHOULD stop the DHCPv4 configuration process or + * disable the IPv4 stack completely for V6ONLY_WAIT seconds or until the next network attachment + * event, whichever occurs first. + * + * Delaying the application of an acquired IPv4 address after DHCPACK introduces several issues: + * + * - If T1 is reached before the address is assigned to the interface, the client cannot send a + * unicast DHCPREQUEST during RENEWING. + * + * - If the client is stopped before the address is configured, it cannot send a DHCPRELEASE message, + * which also requires a valid source address. + * + * While these issues could be worked around, doing so would significantly complicate the + * implementation and violate assumptions in the DHCP state machine as defined in RFC 2131. + * + * Instead, we only honor the IPv6-Only Preferred delay (Option 108) in the REQUESTING state, i.e. + * before any DHCPREQUEST has been sent. */ + + return + client && + client->state == DHCP_STATE_REQUESTING && + client->request_attempt == 0 && + client->lease && + client->lease->ipv6_only_preferred_usec > 0; +} + int sd_dhcp_client_set_ipv6_connectivity(sd_dhcp_client *client, int have) { if (!client) return 0; - /* We have already received a message with IPv6-Only preferred option, and are waiting for IPv6 - * connectivity or timeout, let's stop the client. */ - if (have && sd_event_source_get_enabled(client->timeout_ipv6_only_mode, NULL) > 0) - return sd_dhcp_client_stop(client); - - /* Otherwise, save that the host already has IPv6 connectivity. */ client->ipv6_acquired = have; - return 0; -} - -int sd_dhcp_client_interrupt_ipv6_only_mode(sd_dhcp_client *client) { - assert_return(client, -EINVAL); - assert_return(sd_dhcp_client_is_running(client), -ESTALE); - if (sd_event_source_get_enabled(client->timeout_ipv6_only_mode, NULL) <= 0) - return 0; + if (have && sd_dhcp_client_is_waiting_for_ipv6_connectivity(client)) { + log_dhcp_client(client, + "Acquired IPv6 connectivity before sending REQUEST, stopping DHCPv4 client."); + return sd_dhcp_client_stop(client); + } - return client_start(client); + return 0; } int sd_dhcp_client_attach_event(sd_dhcp_client *client, sd_event *event, int64_t priority) { @@ -2322,7 +2274,6 @@ static sd_dhcp_client* dhcp_client_free(sd_dhcp_client *client) { sd_event_source_unref(client->timeout_t1); sd_event_source_unref(client->timeout_t2); sd_event_source_unref(client->timeout_expire); - sd_event_source_unref(client->timeout_ipv6_only_mode); sd_dhcp_client_detach_event(client); diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index f274a0c4d94a0..dc6b78e326c34 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -1733,6 +1733,8 @@ int dhcp4_update_mac(Link *link) { } int dhcp4_update_ipv6_connectivity(Link *link) { + int r; + assert(link); if (!link->network) @@ -1744,16 +1746,20 @@ int dhcp4_update_ipv6_connectivity(Link *link) { if (!link->dhcp_client) return 0; - /* If the client is running, set the current connectivity. */ - if (sd_dhcp_client_is_running(link->dhcp_client)) - return sd_dhcp_client_set_ipv6_connectivity(link->dhcp_client, link_has_ipv6_connectivity(link)); + bool have = link_has_ipv6_connectivity(link); + r = sd_dhcp_client_set_ipv6_connectivity(link->dhcp_client, have); + if (r < 0) + return r; - /* If the client has been already stopped or not started yet, let's check the current connectivity - * and start the client if necessary. */ - if (link_has_ipv6_connectivity(link)) - return 0; + /* If we do not have IPv6 connectivity, and the client has been already stopped or not started yet, + * let's start the client if possible. */ + if (!have && !sd_dhcp_client_is_running(link->dhcp_client)) { + r = dhcp4_start_full(link, /* set_ipv6_connectivity= */ false); + if (r < 0) + return r; + } - return dhcp4_start_full(link, /* set_ipv6_connectivity= */ false); + return 0; } int dhcp4_start_full(Link *link, bool set_ipv6_connectivity) { @@ -1805,8 +1811,10 @@ int dhcp4_renew(Link *link) { return dhcp4_start(link); /* The client may be waiting for IPv6 connectivity. Let's restart the client in that case. */ - if (dhcp_client_get_state(link->dhcp_client) != DHCP_STATE_BOUND) - return sd_dhcp_client_interrupt_ipv6_only_mode(link->dhcp_client); + if (sd_dhcp_client_is_waiting_for_ipv6_connectivity(link->dhcp_client)) { + sd_dhcp_client_stop(link->dhcp_client); + return dhcp4_start(link); + } /* Otherwise, send a RENEW command. */ return sd_dhcp_client_send_renew(link->dhcp_client); diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h index 033d5ad894ec3..378271aef885d 100644 --- a/src/systemd/sd-dhcp-client.h +++ b/src/systemd/sd-dhcp-client.h @@ -159,7 +159,7 @@ int sd_dhcp_client_start(sd_dhcp_client *client); int sd_dhcp_client_send_decline(sd_dhcp_client *client); int sd_dhcp_client_send_renew(sd_dhcp_client *client); int sd_dhcp_client_set_ipv6_connectivity(sd_dhcp_client *client, int have); -int sd_dhcp_client_interrupt_ipv6_only_mode(sd_dhcp_client *client); +int sd_dhcp_client_is_waiting_for_ipv6_connectivity(sd_dhcp_client *client); _SD_DECLARE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_client); From b831cd00e6777b32b08cc1848b5ff9c6c981f128 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 19 Apr 2026 08:37:53 +0900 Subject: [PATCH 1221/1296] sd-dhcp-client: propagate failure in setting timer and stop the client If we fail to setup timer event sources about the lease lifetime or T1/T2, then the lease will be never updated, and the user (networkd) will not receive any notification about the expire. The situation is terrible. Let's stop the client with error code earlier, and notify the failure to networkd. --- src/libsystemd-network/sd-dhcp-client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 76558bdd72e04..f6ab4b34dfcc6 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -1769,7 +1769,7 @@ static int client_enter_bound(sd_dhcp_client *client, int notify_event) { r = client_set_lease_timeouts(client); if (r < 0) - log_dhcp_client_errno(client, r, "Failed to set lease timeouts: %m"); + return log_dhcp_client_errno(client, r, "Failed to set lease timeouts: %m"); client_notify(client, notify_event); return 0; From 2dff63a31ce33e4525f68351b47516a69b27334c Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 14 Mar 2026 01:33:54 +0900 Subject: [PATCH 1222/1296] sd-dhcp-client: notify SD_DHCP_CLIENT_EVENT_EXPIRED only when we already have a bound lease Otherwise, if we emit the notification without a valid bound lease, networkd may be confused (of course should not, but for safety). Also, increment the delay before calling client_start_delayed(). Otherwise, the first reboot is done instantaneously. --- src/libsystemd-network/sd-dhcp-client.c | 45 +++++++++++++++---------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index f6ab4b34dfcc6..6ff6329ccf138 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -1328,6 +1328,34 @@ static int client_start(sd_dhcp_client *client) { return client_start_delayed(client); } +static int client_restart(sd_dhcp_client *client) { + assert(client); + DHCP_CLIENT_DONT_DESTROY(client); + + /* This is called when we receive a DHCPNAK or could not receive any replies. */ + + /* First, if we have a bound lease, then notify it is expired. */ + if (IN_SET(client->state, DHCP_STATE_BOUND, DHCP_STATE_RENEWING, DHCP_STATE_REBINDING)) { + client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED); + + if (client->state == DHCP_STATE_STOPPED) + return 0; /* The notify callback stopped the client. */ + } + + /* On reboot, DHCPNAK or no reply suggests that the network is changed or the address is already + * used by another host. Let's restart the client immediately without any delay to speed up the + * reboot process. */ + if (client->state == DHCP_STATE_REBOOTING) + return client_start(client); + + /* Otherwise, we should restart the client with a short delay. */ + client->start_delay = CLAMP(client->start_delay * 2, + RESTART_AFTER_NAK_MIN_USEC, RESTART_AFTER_NAK_MAX_USEC); + + log_dhcp_client(client, "REBOOT in %s", FORMAT_TIMESPAN(client->start_delay, USEC_PER_SEC)); + return client_start_delayed(client); +} + static int client_timeout_expire(sd_event_source *s, uint64_t usec, void *userdata) { sd_dhcp_client *client = userdata; DHCP_CLIENT_DONT_DESTROY(client); @@ -1775,23 +1803,6 @@ static int client_enter_bound(sd_dhcp_client *client, int notify_event) { return 0; } -static int client_restart(sd_dhcp_client *client) { - int r; - assert(client); - - client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED); - - r = client_start_delayed(client); - if (r < 0) - return r; - - log_dhcp_client(client, "REBOOT in %s", FORMAT_TIMESPAN(client->start_delay, USEC_PER_SEC)); - - client->start_delay = CLAMP(client->start_delay * 2, - RESTART_AFTER_NAK_MIN_USEC, RESTART_AFTER_NAK_MAX_USEC); - return 0; -} - static int client_verify_message_header(sd_dhcp_client *client, DHCPMessage *message, size_t len) { const uint8_t *expected_chaddr = NULL; uint8_t expected_hlen = 0; From 6ee065ed9980056264369c6da8d3401f9b583e17 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 14 Mar 2026 16:45:53 +0900 Subject: [PATCH 1223/1296] sd-dhcp-client: rework discover/request_attempts counter discover_attempts should be reset only when - the client is stopped, to make the counter starts from zero on the next invocation. - we acquire a bound lease, to make the counter starts from zero when the lease is expired. request_attempts should be reset only when the client enter a new state that sends DHCPREQUEST, that is, when enter one of the REBOOTING, REQUESTING, RENEWING, and REBINDING state. This moves resetting counter to client_set_state() as it should happen only when the state transition. --- src/libsystemd-network/sd-dhcp-client.c | 37 +++++++++++++++---------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 6ff6329ccf138..ad7714d596d09 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -607,6 +607,28 @@ static void client_set_state(sd_dhcp_client *client, DHCPState state) { client->state = state; + switch (state) { + case DHCP_STATE_STOPPED: + case DHCP_STATE_BOUND: + /* In these cases, the next DHCPDISCOVER message will be sent in a new cycle. + * Hence, clear the counter for DHCPDISCOVER messages. */ + client->discover_attempt = 0; + break; + + case DHCP_STATE_REBOOTING: + case DHCP_STATE_REQUESTING: + case DHCP_STATE_RENEWING: + case DHCP_STATE_REBINDING: + /* In these cases, the next DHCPREQUEST message will be the first message in this new state. + * Hence, clear the counter for DHCPREQUEST messages. */ + client->request_attempt = 0; + break; + + default: + /* otherwise, do not reset the counters. */ + ; + } + if (client->state_callback) client->state_callback(client, state, client->state_userdata); } @@ -642,9 +664,6 @@ static void client_initialize(sd_dhcp_client *client) { client_disable_event_sources(client); - client->discover_attempt = 0; - client->request_attempt = 0; - client_set_state(client, DHCP_STATE_STOPPED); client->xid = 0; @@ -1385,8 +1404,6 @@ static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) client->receive_message = sd_event_source_disable_unref(client->receive_message); client_set_state(client, DHCP_STATE_REBINDING); - client->discover_attempt = 0; - client->request_attempt = 0; return client_timeout_resend(s, usec, userdata); } @@ -1396,8 +1413,6 @@ static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) DHCP_CLIENT_DONT_DESTROY(client); client_set_state(client, DHCP_STATE_RENEWING); - client->discover_attempt = 0; - client->request_attempt = 0; return client_timeout_resend(s, usec, userdata); } @@ -1598,8 +1613,6 @@ static int client_enter_requesting(sd_dhcp_client *client) { client_disable_event_sources(client); client_set_state(client, DHCP_STATE_REQUESTING); - client->discover_attempt = 0; - client->request_attempt = 0; if (sd_dhcp_client_is_waiting_for_ipv6_connectivity(client)) { if (client->ipv6_acquired) { @@ -1790,8 +1803,6 @@ static int client_enter_bound(sd_dhcp_client *client, int notify_event) { client->start_delay = 0; client_set_state(client, DHCP_STATE_BOUND); - client->discover_attempt = 0; - client->request_attempt = 0; client->last_addr = client->lease->address; @@ -2030,11 +2041,9 @@ int sd_dhcp_client_send_renew(sd_dhcp_client *client) { if (!sd_dhcp_client_is_running(client) || client->state != DHCP_STATE_BOUND || client->bootp) return 0; /* do nothing */ - client->start_delay = 0; - client->discover_attempt = 1; - client->request_attempt = 1; client_set_state(client, DHCP_STATE_RENEWING); + client->start_delay = 0; return client_initialize_time_events(client); } From e06115cb8b372d7e20e600a7c917d30c48c349fd Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 16 Apr 2026 04:07:25 +0900 Subject: [PATCH 1224/1296] sd-dhcp-client: add FIXME comment about the state callback At least currently, it is a theoretical concern, as networkd does not change the client state in the callback. --- src/libsystemd-network/sd-dhcp-client.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index ad7714d596d09..9742ab833bd5b 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -629,6 +629,8 @@ static void client_set_state(sd_dhcp_client *client, DHCPState state) { ; } + // FIXME: If the state callback changes the state, we may not safely free/stop the client, and the + // state machine diagram becomes needlessly complicated. Introduce a guard to avoid that. */ if (client->state_callback) client->state_callback(client, state, client->state_userdata); } From 8c3f64e61fd42541f56c5de1b0f926f72f9d935e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 20 Apr 2026 13:08:29 +0000 Subject: [PATCH 1225/1296] tree-wide: Load libcrypto and libssl via dlopen() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Until now OpenSSL was linked into every binary and library that needed cryptography, pulling libcrypto (and, for resolved, libssl) into the address space of services that never touch them at runtime. This commit moves all OpenSSL usage behind the same dlopen helper pattern that we already use for other optional libraries (libpam, libseccomp, libxz, …) so libcrypto/libssl are only loaded on demand. The bulk of the work lives in src/shared/crypto-util.{c,h} (libcrypto) and src/shared/ssl-util.{c,h} (libssl), which replace the previous src/shared/openssl-util.{c,h}: - crypto-util.{c,h} declares every libcrypto function we call via DLSYM_PROTOTYPE() and resolves them inside dlopen_libcrypto(). - ssl-util.{c,h} holds the libssl-specific DLSYM_PROTOTYPEs, dlopen_libssl(), and the SSL_freep cleanup helper, so translation units that only need libcrypto do not pull in libssl declarations. - Callers refer to the symbols through sym_* aliases rather than the original names. - Convenience macros that used to be provided by the OpenSSL headers (OPENSSL_free, BN_num_bytes, the sk_TYPE_* helpers, …) are reimplemented as sym_* wrappers so no code path needs to fall back to the linker-resolved symbols. - All _cleanup_ helpers are redefined in terms of the sym_* variants (EVP_PKEY_freep, X509_freep, BIO_freep, …) so cleanup attributes keep working without pulling in libcrypto symbols at link time. - The public crypto-util.c entry points (openssl_pubkey_from_pem, openssl_digest_many, openssl_hmac_many, openssl_cipher_many, kdf_ss_derive, kdf_kb_hmac_derive, rsa_* / ecc_* helpers, pubkey_fingerprint, digest_and_sign, pkcs7_new, x509_fingerprint, openssl_extract_public_key, pkey_generate_volume_keys, the load_* helpers, …) now call dlopen_libcrypto() at entry before touching any sym_* pointer. The call sites across the tree have been converted to call dlopen_libcrypto()/dlopen_libssl() at the appropriate entry point before their first sym_* use, and to use sym_* variants throughout: - bootctl, sbsign, measure, pcrlock, pcrextend, tpm2-setup, repart, cryptsetup, cryptenroll, homectl, homed, homework, keyutil, sysupdate, creds, import, dissect-image, pe-binary, pkcs11-util, pkcs7-util, tpm2-util, creds-util. resolved additionally dlopens libssl for DoT. The meson build files are updated to depend on libopenssl_cflags (a new partial dependency that exposes include paths and compile flags only, not the linker flags) instead of libopenssl for every target that previously linked against OpenSSL. Nothing links against libcrypto or libssl directly anymore. A new src/sbsign/authenticode.c hosts the Authenticode ASN.1 type definitions that used to live inline in sbsign.c. The OpenSSL ASN1_SEQUENCE / ASN1_CHOICE / IMPLEMENT_ASN1_FUNCTIONS macros expand to code that references libcrypto symbols directly, so to keep this translation unit unlinked from libcrypto we redirect ASN1_item_* to the sym_* variants via #define and wrap the ASN1_*_it() getters (which appear as constant function pointers in static initializers) in small trampoline functions that forward to the sym_* pointers at runtime. test-dlopen-so gains assertions for dlopen_libcrypto and dlopen_libssl so the dlopen contract is exercised in CI, and the openssl-specific test was renamed from test-openssl.c to test-crypto-util.c to match the new header naming. --- meson.build | 1 + src/basic/basic-forward.h | 10 + src/bootctl/bootctl-install.c | 35 +- src/bootctl/bootctl.c | 2 +- src/bootctl/meson.build | 2 +- src/creds/meson.build | 2 +- src/cryptenroll/cryptenroll-pkcs11.c | 2 +- src/cryptenroll/meson.build | 2 +- src/cryptsetup/cryptsetup.c | 9 +- src/cryptsetup/meson.build | 2 +- src/home/homectl-pkcs11.c | 2 +- src/home/homectl.c | 8 +- src/home/homed-manager-bus.c | 6 +- src/home/homed-manager.c | 30 +- src/home/homed-manager.h | 2 - src/home/homework-fscrypt.c | 56 +- src/home/homework-luks.c | 34 +- src/home/meson.build | 6 +- src/home/user-record-sign.c | 8 +- src/home/user-record-sign.h | 2 - src/import/meson.build | 2 +- src/import/pull-common.c | 1 + src/import/pull-job.c | 20 +- src/import/pull-job.h | 3 +- src/import/pull-oci.c | 1 + src/import/pull-raw.c | 1 + src/keyutil/keyutil.c | 26 +- src/keyutil/meson.build | 2 +- src/measure/measure-tool.c | 60 +- src/measure/meson.build | 2 +- src/pcrextend/meson.build | 2 +- src/pcrextend/pcrextend.c | 9 +- src/pcrlock/meson.build | 2 +- src/pcrlock/pcrlock-firmware.c | 7 +- src/pcrlock/pcrlock.c | 70 +- src/repart/meson.build | 4 +- src/repart/repart.c | 24 +- src/resolve/meson.build | 2 +- src/resolve/resolvectl.c | 2 +- src/resolve/resolved-dns-dnssec.c | 155 +-- src/resolve/resolved-dnstls.c | 105 +- src/resolve/resolved-dnstls.h | 2 - src/sbsign/authenticode.c | 125 ++ src/sbsign/authenticode.h | 65 +- src/sbsign/meson.build | 7 +- src/sbsign/sbsign.c | 131 +- src/shared/creds-util.c | 105 +- src/shared/{openssl-util.c => crypto-util.c} | 1164 ++++++++++++++--- src/shared/crypto-util.h | 406 ++++++ src/shared/dissect-image.c | 26 +- src/shared/meson.build | 5 +- src/shared/openssl-util.h | 196 --- src/shared/pe-binary.c | 33 +- src/shared/pe-binary.h | 1 - src/shared/pkcs11-util.c | 119 +- src/shared/pkcs11-util.h | 4 - src/shared/pkcs7-util.c | 22 +- src/shared/shared-forward.h | 1 + src/shared/ssl-util.c | 75 ++ src/shared/ssl-util.h | 46 + src/shared/tpm2-util.c | 78 +- src/shared/tpm2-util.h | 3 +- src/sysupdate/meson.build | 2 +- src/test/meson.build | 13 +- .../{test-openssl.c => test-crypto-util.c} | 47 +- src/test/test-cryptolib.c | 27 - src/test/test-dlopen-so.c | 4 + src/test/test-tpm2.c | 15 +- src/tpm2-setup/meson.build | 2 +- src/tpm2-setup/tpm2-setup.c | 11 +- 70 files changed, 2394 insertions(+), 1062 deletions(-) create mode 100644 src/sbsign/authenticode.c rename src/shared/{openssl-util.c => crypto-util.c} (51%) create mode 100644 src/shared/crypto-util.h delete mode 100644 src/shared/openssl-util.h create mode 100644 src/shared/ssl-util.c create mode 100644 src/shared/ssl-util.h rename src/test/{test-openssl.c => test-crypto-util.c} (93%) delete mode 100644 src/test/test-cryptolib.c diff --git a/meson.build b/meson.build index a902bc96aa204..4f1a791bc7651 100644 --- a/meson.build +++ b/meson.build @@ -1258,6 +1258,7 @@ libgnutls_cflags = libgnutls.partial_dependency(includes: true, compile_args: tr libopenssl = dependency('openssl', version : '>= 3.0.0', required : get_option('openssl')) +libopenssl_cflags = libopenssl.partial_dependency(includes: true, compile_args: true) conf.set10('HAVE_OPENSSL', libopenssl.found()) libp11kit = dependency('p11-kit-1', diff --git a/src/basic/basic-forward.h b/src/basic/basic-forward.h index 1ca9ecfeff43e..396056a8e55eb 100644 --- a/src/basic/basic-forward.h +++ b/src/basic/basic-forward.h @@ -69,6 +69,15 @@ struct fdisk_context; struct fdisk_table; struct crypt_device; +typedef struct buf_mem_st BUF_MEM; +typedef struct evp_pkey_st EVP_PKEY; +typedef struct evp_md_st EVP_MD; +typedef struct evp_md_ctx_st EVP_MD_CTX; +typedef struct ssl_st SSL; +typedef struct ssl_ctx_st SSL_CTX; +typedef struct ssl_session_st SSL_SESSION; +typedef struct x509_st X509; + /* basic/ forward declarations */ typedef void (*hash_func_t)(const void *p, struct siphash *state); @@ -111,6 +120,7 @@ typedef struct Set Set; typedef struct dual_timestamp dual_timestamp; typedef struct triple_timestamp triple_timestamp; +typedef struct Compressor Compressor; typedef struct ConfFile ConfFile; typedef struct LockFile LockFile; typedef struct PidRef PidRef; diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index a8ac742b9a760..20958d0b0bc6f 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -7,6 +7,7 @@ #include "sd-varlink.h" #include "alloc-util.h" +#include "ask-password-api.h" #include "blockdev-util.h" #include "boot-entry.h" #include "bootctl.h" @@ -15,6 +16,7 @@ #include "bootctl-util.h" #include "chase.h" #include "copy.h" +#include "crypto-util.h" #include "dirent-util.h" #include "efi-api.h" #include "efi-fundamental.h" @@ -31,7 +33,6 @@ #include "json-util.h" #include "kernel-config.h" #include "log.h" -#include "openssl-util.h" #include "parse-argument.h" #include "path-util.h" #include "pe-binary.h" @@ -117,11 +118,11 @@ static void install_context_done(InstallContext *c) { c->xbootldr_fd = safe_close(c->xbootldr_fd); #if HAVE_OPENSSL if (c->secure_boot_private_key) { - EVP_PKEY_free(c->secure_boot_private_key); + sym_EVP_PKEY_free(c->secure_boot_private_key); c->secure_boot_private_key = NULL; } if (c->secure_boot_certificate) { - X509_free(c->secure_boot_certificate); + sym_X509_free(c->secure_boot_certificate); c->secure_boot_certificate = NULL; } #endif @@ -1035,12 +1036,16 @@ static int install_secure_boot_auto_enroll(InstallContext *c) { if (!c->secure_boot_certificate || !c->secure_boot_private_key) return 0; + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + _cleanup_free_ uint8_t *dercert = NULL; int dercertsz; - dercertsz = i2d_X509(c->secure_boot_certificate, &dercert); + dercertsz = sym_i2d_X509(c->secure_boot_certificate, &dercert); if (dercertsz < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert X.509 certificate to DER: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); if (c->esp_fd < 0) return c->esp_fd; @@ -1087,7 +1092,7 @@ static int install_secure_boot_auto_enroll(InstallContext *c) { FOREACH_STRING(db, "PK", "KEK", "db") { _cleanup_(BIO_freep) BIO *bio = NULL; - bio = BIO_new(BIO_s_mem()); + bio = sym_BIO_new(sym_BIO_s_mem()); if (!bio) return log_oom(); @@ -1096,34 +1101,34 @@ static int install_secure_boot_auto_enroll(InstallContext *c) { return log_oom(); /* Don't count the trailing NUL terminator. */ - if (BIO_write(bio, db16, char16_strsize(db16) - sizeof(char16_t)) < 0) + if (sym_BIO_write(bio, db16, char16_strsize(db16) - sizeof(char16_t)) < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write variable name to bio"); EFI_GUID *guid = STR_IN_SET(db, "PK", "KEK") ? &(EFI_GUID) EFI_GLOBAL_VARIABLE : &(EFI_GUID) EFI_IMAGE_SECURITY_DATABASE_GUID; - if (BIO_write(bio, guid, sizeof(*guid)) < 0) + if (sym_BIO_write(bio, guid, sizeof(*guid)) < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write variable GUID to bio"); - if (BIO_write(bio, &attrs, sizeof(attrs)) < 0) + if (sym_BIO_write(bio, &attrs, sizeof(attrs)) < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write variable attributes to bio"); - if (BIO_write(bio, ×tamp, sizeof(timestamp)) < 0) + if (sym_BIO_write(bio, ×tamp, sizeof(timestamp)) < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write timestamp to bio"); - if (BIO_write(bio, siglist, siglistsz) < 0) + if (sym_BIO_write(bio, siglist, siglistsz) < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write signature list to bio"); _cleanup_(PKCS7_freep) PKCS7 *p7 = NULL; - p7 = PKCS7_sign(c->secure_boot_certificate, c->secure_boot_private_key, /* certs= */ NULL, bio, PKCS7_DETACHED|PKCS7_NOATTR|PKCS7_BINARY|PKCS7_NOSMIMECAP); + p7 = sym_PKCS7_sign(c->secure_boot_certificate, c->secure_boot_private_key, /* certs= */ NULL, bio, PKCS7_DETACHED|PKCS7_NOATTR|PKCS7_BINARY|PKCS7_NOSMIMECAP); if (!p7) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to calculate PKCS7 signature: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); _cleanup_free_ uint8_t *sig = NULL; - int sigsz = i2d_PKCS7(p7, &sig); + int sigsz = sym_i2d_PKCS7(p7, &sig); if (sigsz < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert PKCS7 signature to DER: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); size_t authsz = offsetof(EFI_VARIABLE_AUTHENTICATION_2, AuthInfo.CertData) + sigsz; _cleanup_free_ EFI_VARIABLE_AUTHENTICATION_2 *auth = malloc(authsz); diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index 59a93d07c3f22..942ef4d681875 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -17,6 +17,7 @@ #include "bootctl-unlink.h" #include "bootctl-util.h" #include "build.h" +#include "crypto-util.h" #include "devnum-util.h" #include "dissect-image.h" #include "efi-loader.h" @@ -30,7 +31,6 @@ #include "loop-util.h" #include "main-func.h" #include "mount-util.h" -#include "openssl-util.h" #include "options.h" #include "pager.h" #include "parse-argument.h" diff --git a/src/bootctl/meson.build b/src/bootctl/meson.build index 8cfbb7c14acb0..f8349df7168e3 100644 --- a/src/bootctl/meson.build +++ b/src/bootctl/meson.build @@ -23,6 +23,6 @@ executables += [ ], 'sources' : bootctl_sources, 'link_with' : boot_link_with, - 'dependencies' : [libopenssl], + 'dependencies' : [libopenssl_cflags], }, ] diff --git a/src/creds/meson.build b/src/creds/meson.build index a6e66495b6059..dc4a5a28ae316 100644 --- a/src/creds/meson.build +++ b/src/creds/meson.build @@ -11,7 +11,7 @@ executables += [ 'sources' : files('creds.c'), 'dependencies' : [ libmount_cflags, - libopenssl, + libopenssl_cflags, threads, ], }, diff --git a/src/cryptenroll/cryptenroll-pkcs11.c b/src/cryptenroll/cryptenroll-pkcs11.c index 51c2a5fa77e38..ae678f96e477d 100644 --- a/src/cryptenroll/cryptenroll-pkcs11.c +++ b/src/cryptenroll/cryptenroll-pkcs11.c @@ -2,10 +2,10 @@ #include "alloc-util.h" #include "cryptenroll-pkcs11.h" +#include "crypto-util.h" #include "cryptsetup-util.h" #include "hexdecoct.h" #include "json-util.h" -#include "openssl-util.h" #include "pkcs11-util.h" #if HAVE_P11KIT && HAVE_OPENSSL diff --git a/src/cryptenroll/meson.build b/src/cryptenroll/meson.build index 2d882343d3078..8213a0e672572 100644 --- a/src/cryptenroll/meson.build +++ b/src/cryptenroll/meson.build @@ -24,7 +24,7 @@ executables += [ libcryptsetup_cflags, libdl, libfido2_cflags, - libopenssl, + libopenssl_cflags, libp11kit_cflags, ], }, diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 8e5161eba05d4..d772ba9a9afee 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -12,6 +12,7 @@ #include "alloc-util.h" #include "ask-password-api.h" #include "build.h" +#include "crypto-util.h" #include "cryptsetup-fido2.h" #include "cryptsetup-keyfile.h" #include "cryptsetup-pkcs11.h" @@ -537,6 +538,10 @@ static int parse_one_option(const char *option) { #if HAVE_OPENSSL _cleanup_strv_free_ char **l = NULL; + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + l = strv_split(val, ":"); if (!l) return log_oom(); @@ -544,11 +549,11 @@ static int parse_one_option(const char *option) { STRV_FOREACH(i, l) { const EVP_MD *implementation; - implementation = EVP_get_digestbyname(*i); + implementation = sym_EVP_get_digestbyname(*i); if (!implementation) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", val); - if (strv_extend(&arg_tpm2_measure_banks, EVP_MD_name(implementation)) < 0) + if (strv_extend(&arg_tpm2_measure_banks, sym_EVP_MD_get0_name(implementation)) < 0) return log_oom(); } #else diff --git a/src/cryptsetup/meson.build b/src/cryptsetup/meson.build index 9249f70177b37..9b7f3fa344da5 100644 --- a/src/cryptsetup/meson.build +++ b/src/cryptsetup/meson.build @@ -21,7 +21,7 @@ executables += [ libcryptsetup_cflags, libfido2_cflags, libmount_cflags, - libopenssl, + libopenssl_cflags, libp11kit_cflags, ], }, diff --git a/src/home/homectl-pkcs11.c b/src/home/homectl-pkcs11.c index a72aecf135643..3ef1b80c225e3 100644 --- a/src/home/homectl-pkcs11.c +++ b/src/home/homectl-pkcs11.c @@ -2,12 +2,12 @@ #include "sd-json.h" +#include "crypto-util.h" #include "errno-util.h" #include "hexdecoct.h" #include "homectl-pkcs11.h" #include "libcrypt-util.h" #include "log.h" -#include "openssl-util.h" #include "pkcs11-util.h" #include "string-util.h" #include "strv.h" diff --git a/src/home/homectl.c b/src/home/homectl.c index 4ebf47ca9e75a..271e03587502b 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -18,6 +18,7 @@ #include "cgroup-util.h" #include "chase.h" #include "creds-util.h" +#include "crypto-util.h" #include "dirent-util.h" #include "dns-domain.h" #include "env-util.h" @@ -39,7 +40,6 @@ #include "libfido2-util.h" #include "locale-util.h" #include "main-func.h" -#include "openssl-util.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -5316,13 +5316,17 @@ static int verb_list_signing_keys(int argc, char *argv[], uintptr_t _data, void /* Let's decode the PEM key to DER (so that we lose prefix/suffix), then truncate it * for display reasons. */ + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + _cleanup_(EVP_PKEY_freep) EVP_PKEY *key = NULL; r = openssl_pubkey_from_pem(pem, SIZE_MAX, &key); if (r < 0) return log_error_errno(r, "Failed to parse PEM: %m"); _cleanup_free_ void *der = NULL; - int n = i2d_PUBKEY(key, (unsigned char**) &der); + int n = sym_i2d_PUBKEY(key, (unsigned char**) &der); if (n < 0) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to encode key as DER."); diff --git a/src/home/homed-manager-bus.c b/src/home/homed-manager-bus.c index f35268567218e..b8385c781e2b4 100644 --- a/src/home/homed-manager-bus.c +++ b/src/home/homed-manager-bus.c @@ -12,6 +12,7 @@ #include "bus-message-util.h" #include "bus-object.h" #include "bus-polkit.h" +#include "crypto-util.h" #include "fileio.h" #include "format-util.h" #include "home-util.h" @@ -22,7 +23,6 @@ #include "homed-manager-bus.h" #include "homed-operation.h" #include "log.h" -#include "openssl-util.h" #include "path-util.h" #include "set.h" #include "string-util.h" @@ -936,14 +936,14 @@ static bool manager_has_public_key(Manager *m, EVP_PKEY *needle) { EVP_PKEY *pkey; HASHMAP_FOREACH(pkey, m->public_keys) { - r = EVP_PKEY_eq(pkey, needle); + r = sym_EVP_PKEY_eq(pkey, needle); if (r > 0) return true; /* EVP_PKEY_eq() returns -1 and -2 too under some conditions, which we'll all treat as "not the same" */ } - r = EVP_PKEY_eq(m->private_key, needle); + r = sym_EVP_PKEY_eq(m->private_key, needle); if (r > 0) return true; diff --git a/src/home/homed-manager.c b/src/home/homed-manager.c index 6c229abadcf6a..fc6fe8a4b1c44 100644 --- a/src/home/homed-manager.c +++ b/src/home/homed-manager.c @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -25,6 +24,7 @@ #include "clean-ipc.h" #include "common-signal.h" #include "conf-files.h" +#include "crypto-util.h" #include "device-util.h" #include "dirent-util.h" #include "errno-util.h" @@ -43,7 +43,6 @@ #include "homed-varlink.h" #include "mkdir.h" #include "notify-recv.h" -#include "openssl-util.h" #include "ordered-set.h" #include "quota-util.h" #include "random-util.h" @@ -313,7 +312,7 @@ Manager* manager_free(Manager *m) { m->homes_by_sysfs = hashmap_free(m->homes_by_sysfs); if (m->private_key) - EVP_PKEY_free(m->private_key); + sym_EVP_PKEY_free(m->private_key); hashmap_free(m->public_keys); @@ -1317,7 +1316,7 @@ static int manager_load_key_pair(Manager *m) { assert(m); if (m->private_key) { - EVP_PKEY_free(m->private_key); + sym_EVP_PKEY_free(m->private_key); m->private_key = NULL; } @@ -1337,7 +1336,7 @@ static int manager_load_key_pair(Manager *m) { if (st.st_uid != 0 || (st.st_mode & 0077) != 0) return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Private key file is readable by more than the root user"); - m->private_key = PEM_read_PrivateKey(f, NULL, NULL, NULL); + m->private_key = sym_PEM_read_PrivateKey(f, NULL, NULL, NULL); if (!m->private_key) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to load private key pair"); @@ -1353,20 +1352,20 @@ static int manager_generate_key_pair(Manager *m) { int r; if (m->private_key) { - EVP_PKEY_free(m->private_key); + sym_EVP_PKEY_free(m->private_key); m->private_key = NULL; } - ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL); + ctx = sym_EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL); if (!ctx) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to allocate Ed25519 key generation context."); - if (EVP_PKEY_keygen_init(ctx) <= 0) + if (sym_EVP_PKEY_keygen_init(ctx) <= 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize Ed25519 key generation context."); log_info("Generating key pair for signing local user identity records."); - if (EVP_PKEY_keygen(ctx, &m->private_key) <= 0) + if (sym_EVP_PKEY_keygen(ctx, &m->private_key) <= 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to generate Ed25519 key pair"); log_info("Successfully created Ed25519 key pair."); @@ -1378,7 +1377,7 @@ static int manager_generate_key_pair(Manager *m) { if (r < 0) return log_error_errno(r, "Failed to open key file for writing: %m"); - if (PEM_write_PUBKEY(fpublic, m->private_key) <= 0) + if (sym_PEM_write_PUBKEY(fpublic, m->private_key) <= 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write public key."); (void) fchmod(fileno(fpublic), 0444); /* Make public key world readable */ @@ -1394,7 +1393,7 @@ static int manager_generate_key_pair(Manager *m) { if (r < 0) return log_error_errno(r, "Failed to open key file for writing: %m"); - if (PEM_write_PrivateKey(fprivate, m->private_key, NULL, NULL, 0, NULL, NULL) <= 0) + if (sym_PEM_write_PrivateKey(fprivate, m->private_key, NULL, NULL, 0, NULL, NULL) <= 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write private key pair."); (void) fchmod(fileno(fprivate), 0400); /* Make private key root readable */ @@ -1459,7 +1458,8 @@ int manager_sign_user_record(Manager *m, UserRecord *u, UserRecord **ret, sd_bus return user_record_sign(u, m->private_key, ret); } -DEFINE_HASH_OPS_FULL(public_key_hash_ops, char, string_hash_func, string_compare_func, free, EVP_PKEY, EVP_PKEY_free); +/* dlopen_libcrypto() must have been called before populating this hashmap. */ +DEFINE_HASH_OPS_FULL(public_key_hash_ops, char, string_hash_func, string_compare_func, free, EVP_PKEY, sym_EVP_PKEY_free); static int manager_load_public_key_one(Manager *m, const char *path) { _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; @@ -1495,7 +1495,7 @@ static int manager_load_public_key_one(Manager *m, const char *path) { if (st.st_uid != 0 || (st.st_mode & 0022) != 0) return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Public key file %s is writable by more than the root user, refusing.", path); - pkey = PEM_read_PUBKEY(f, &pkey, NULL, NULL); + pkey = sym_PEM_read_PUBKEY(f, &pkey, NULL, NULL); if (!pkey) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse public key file %s.", path); @@ -1537,6 +1537,10 @@ int manager_startup(Manager *m) { assert(m); + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + r = manager_listen_notify(m); if (r < 0) return r; diff --git a/src/home/homed-manager.h b/src/home/homed-manager.h index fe1041e01e5fc..a399c31bf8fb1 100644 --- a/src/home/homed-manager.h +++ b/src/home/homed-manager.h @@ -1,8 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include - #include "homed-forward.h" #include "user-record.h" diff --git a/src/home/homework-fscrypt.c b/src/home/homework-fscrypt.c index c2134142ded6c..6f8ae4b8c9c1c 100644 --- a/src/home/homework-fscrypt.c +++ b/src/home/homework-fscrypt.c @@ -1,8 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include #include #include #include @@ -10,6 +8,7 @@ #include #include "alloc-util.h" +#include "crypto-util.h" #include "errno-util.h" #include "fd-util.h" #include "format-util.h" @@ -25,7 +24,6 @@ #include "mkdir.h" #include "mount-util.h" #include "nulstr-util.h" -#include "openssl-util.h" #include "parse-util.h" #include "process-util.h" #include "random-util.h" @@ -180,8 +178,8 @@ static void calculate_key_descriptor( /* Derive the key descriptor from the volume key via double SHA512, in order to be compatible with e4crypt */ - assert_se(SHA512(key, key_size, hashed) == hashed); - assert_se(SHA512(hashed, sizeof(hashed), hashed2) == hashed2); + assert_se(sym_SHA512(key, key_size, hashed) == hashed); + assert_se(sym_SHA512(hashed, sizeof(hashed), hashed2) == hashed2); assert_cc(sizeof(hashed2) >= FS_KEY_DESCRIPTOR_SIZE); @@ -211,6 +209,10 @@ static int fscrypt_slot_try_one( assert(encrypted_size > 0); assert(match_key_descriptor); + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + /* Our construction is like this: * * 1. In each key slot we store a salt value plus the encrypted volume key @@ -226,37 +228,37 @@ static int fscrypt_slot_try_one( CLEANUP_ERASE(derived); - if (PKCS5_PBKDF2_HMAC( + if (sym_PKCS5_PBKDF2_HMAC( password, strlen(password), salt, salt_size, - 0xFFFF, EVP_sha512(), + 0xFFFF, sym_EVP_sha512(), sizeof(derived), derived) != 1) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed."); - context = EVP_CIPHER_CTX_new(); + context = sym_EVP_CIPHER_CTX_new(); if (!context) return log_oom(); /* We use AES256 in counter mode */ - assert_se(cc = EVP_aes_256_ctr()); + assert_se(cc = sym_EVP_aes_256_ctr()); /* We only use the first half of the derived key */ - assert(sizeof(derived) >= (size_t) EVP_CIPHER_key_length(cc)); + assert(sizeof(derived) >= (size_t) sym_EVP_CIPHER_get_key_length(cc)); - if (EVP_DecryptInit_ex(context, cc, NULL, derived, NULL) != 1) + if (sym_EVP_DecryptInit_ex(context, cc, NULL, derived, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize decryption context."); - decrypted_size = encrypted_size + EVP_CIPHER_key_length(cc) * 2; + decrypted_size = encrypted_size + sym_EVP_CIPHER_get_key_length(cc) * 2; decrypted = malloc(decrypted_size); if (!decrypted) return log_oom(); - if (EVP_DecryptUpdate(context, (uint8_t*) decrypted, &decrypted_size_out1, encrypted, encrypted_size) != 1) + if (sym_EVP_DecryptUpdate(context, (uint8_t*) decrypted, &decrypted_size_out1, encrypted, encrypted_size) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decrypt volume key."); assert((size_t) decrypted_size_out1 <= decrypted_size); - if (EVP_DecryptFinal_ex(context, (uint8_t*) decrypted + decrypted_size_out1, &decrypted_size_out2) != 1) + if (sym_EVP_DecryptFinal_ex(context, (uint8_t*) decrypted + decrypted_size_out1, &decrypted_size_out2) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finish decryption of volume key."); assert((size_t) decrypted_size_out1 + (size_t) decrypted_size_out2 < decrypted_size); @@ -484,43 +486,47 @@ static int fscrypt_slot_set( size_t encrypted_size; ssize_t ss; + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + r = crypto_random_bytes(salt, sizeof(salt)); if (r < 0) return log_error_errno(r, "Failed to generate salt: %m"); CLEANUP_ERASE(derived); - if (PKCS5_PBKDF2_HMAC( + if (sym_PKCS5_PBKDF2_HMAC( password, strlen(password), salt, sizeof(salt), - 0xFFFF, EVP_sha512(), + 0xFFFF, sym_EVP_sha512(), sizeof(derived), derived) != 1) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed"); - context = EVP_CIPHER_CTX_new(); + context = sym_EVP_CIPHER_CTX_new(); if (!context) return log_oom(); /* We use AES256 in counter mode */ - cc = EVP_aes_256_ctr(); + cc = sym_EVP_aes_256_ctr(); /* We only use the first half of the derived key */ - assert(sizeof(derived) >= (size_t) EVP_CIPHER_key_length(cc)); + assert(sizeof(derived) >= (size_t) sym_EVP_CIPHER_get_key_length(cc)); - if (EVP_EncryptInit_ex(context, cc, NULL, derived, NULL) != 1) + if (sym_EVP_EncryptInit_ex(context, cc, NULL, derived, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize encryption context."); - encrypted_size = volume_key_size + EVP_CIPHER_key_length(cc) * 2; + encrypted_size = volume_key_size + sym_EVP_CIPHER_get_key_length(cc) * 2; encrypted = malloc(encrypted_size); if (!encrypted) return log_oom(); - if (EVP_EncryptUpdate(context, (uint8_t*) encrypted, &encrypted_size_out1, volume_key, volume_key_size) != 1) + if (sym_EVP_EncryptUpdate(context, (uint8_t*) encrypted, &encrypted_size_out1, volume_key, volume_key_size) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt volume key."); assert((size_t) encrypted_size_out1 <= encrypted_size); - if (EVP_EncryptFinal_ex(context, (uint8_t*) encrypted + encrypted_size_out1, &encrypted_size_out2) != 1) + if (sym_EVP_EncryptFinal_ex(context, (uint8_t*) encrypted + encrypted_size_out1, &encrypted_size_out2) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finish encryption of volume key."); assert((size_t) encrypted_size_out1 + (size_t) encrypted_size_out2 < encrypted_size); @@ -569,6 +575,10 @@ int home_create_fscrypt( assert(setup); assert(ret_home); + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + assert_se(ip = user_record_image_path(h)); r = tempfn_random(ip, "homework", &d); diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index 96ac65a6fbbee..e85153d61dbc2 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -21,6 +21,7 @@ #include "blockdev-util.h" #include "btrfs-util.h" #include "chattr-util.h" +#include "crypto-util.h" #include "cryptsetup-util.h" #include "device-util.h" #include "devnum-util.h" @@ -48,7 +49,6 @@ #include "memory-util.h" #include "mkdir.h" #include "mkfs-util.h" -#include "openssl-util.h" #include "parse-util.h" #include "path-util.h" #include "pidref.h" @@ -805,6 +805,10 @@ static int crypt_device_to_evp_cipher(struct crypt_device *cd, const EVP_CIPHER assert(cd); assert(ret); + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + /* Let's find the right OpenSSL EVP_CIPHER object that matches the encryption settings of the LUKS * device */ @@ -832,12 +836,12 @@ static int crypt_device_to_evp_cipher(struct crypt_device *cd, const EVP_CIPHER if (asprintf(&cipher_name, "%s-%zu-%s", cipher, key_bits, cipher_mode) < 0) return log_oom(); - cc = EVP_get_cipherbyname(cipher_name); + cc = sym_EVP_get_cipherbyname(cipher_name); if (!cc) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Selected cipher mode '%s' not supported, can't encrypt JSON record.", cipher_name); /* Verify that our key length calculations match what OpenSSL thinks */ - r = EVP_CIPHER_key_length(cc); + r = sym_EVP_CIPHER_get_key_length(cc); if (r < 0 || (uint64_t) r != key_size) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Key size of selected cipher doesn't meet our expectations."); @@ -909,27 +913,27 @@ static int luks_validate_home_record( r = crypt_device_to_evp_cipher(cd, &cc); if (r < 0) return r; - if (iv_size > INT_MAX || EVP_CIPHER_iv_length(cc) != (int) iv_size) + if (iv_size > INT_MAX || sym_EVP_CIPHER_get_iv_length(cc) != (int) iv_size) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "IV size doesn't match."); - context = EVP_CIPHER_CTX_new(); + context = sym_EVP_CIPHER_CTX_new(); if (!context) return log_oom(); - if (EVP_DecryptInit_ex(context, cc, NULL, volume_key, iv) != 1) + if (sym_EVP_DecryptInit_ex(context, cc, NULL, volume_key, iv) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize decryption context."); - decrypted_size = encrypted_size + EVP_CIPHER_key_length(cc) * 2; + decrypted_size = encrypted_size + sym_EVP_CIPHER_get_key_length(cc) * 2; decrypted = new(char, decrypted_size); if (!decrypted) return log_oom(); - if (EVP_DecryptUpdate(context, (uint8_t*) decrypted, &decrypted_size_out1, encrypted, encrypted_size) != 1) + if (sym_EVP_DecryptUpdate(context, (uint8_t*) decrypted, &decrypted_size_out1, encrypted, encrypted_size) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decrypt JSON record."); assert((size_t) decrypted_size_out1 <= decrypted_size); - if (EVP_DecryptFinal_ex(context, (uint8_t*) decrypted + decrypted_size_out1, &decrypted_size_out2) != 1) + if (sym_EVP_DecryptFinal_ex(context, (uint8_t*) decrypted + decrypted_size_out1, &decrypted_size_out2) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finish decryption of JSON record."); assert((size_t) decrypted_size_out1 + (size_t) decrypted_size_out2 < decrypted_size); @@ -990,8 +994,8 @@ static int format_luks_token_text( if (r < 0) return r; - key_size = EVP_CIPHER_key_length(cc); - iv_size = EVP_CIPHER_iv_length(cc); + key_size = sym_EVP_CIPHER_get_key_length(cc); + iv_size = sym_EVP_CIPHER_get_iv_length(cc); if (iv_size > 0) { iv = malloc(iv_size); @@ -1003,11 +1007,11 @@ static int format_luks_token_text( return log_error_errno(r, "Failed to generate IV: %m"); } - context = EVP_CIPHER_CTX_new(); + context = sym_EVP_CIPHER_CTX_new(); if (!context) return log_oom(); - if (EVP_EncryptInit_ex(context, cc, NULL, volume_key, iv) != 1) + if (sym_EVP_EncryptInit_ex(context, cc, NULL, volume_key, iv) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize encryption context."); r = sd_json_variant_format(hr->json, 0, &text); @@ -1021,12 +1025,12 @@ static int format_luks_token_text( if (!encrypted) return log_oom(); - if (EVP_EncryptUpdate(context, encrypted, &encrypted_size_out1, (uint8_t*) text, text_length) != 1) + if (sym_EVP_EncryptUpdate(context, encrypted, &encrypted_size_out1, (uint8_t*) text, text_length) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt JSON record."); assert((size_t) encrypted_size_out1 <= encrypted_size); - if (EVP_EncryptFinal_ex(context, (uint8_t*) encrypted + encrypted_size_out1, &encrypted_size_out2) != 1) + if (sym_EVP_EncryptFinal_ex(context, (uint8_t*) encrypted + encrypted_size_out1, &encrypted_size_out2) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finish encryption of JSON record."); assert((size_t) encrypted_size_out1 + (size_t) encrypted_size_out2 <= encrypted_size); diff --git a/src/home/meson.build b/src/home/meson.build index b051bf580c803..53c5675c83f88 100644 --- a/src/home/meson.build +++ b/src/home/meson.build @@ -65,7 +65,7 @@ executables += [ 'extract' : systemd_homed_extract_sources, 'dependencies' : [ libm, - libopenssl, + libopenssl_cflags, threads, ], }, @@ -76,7 +76,7 @@ executables += [ 'dependencies' : [ libblkid_cflags, libfdisk_cflags, - libopenssl, + libopenssl_cflags, libp11kit_cflags, threads, ], @@ -88,7 +88,7 @@ executables += [ 'objects' : ['systemd-homed'], 'dependencies' : [ libdl, - libopenssl, + libopenssl_cflags, libp11kit_cflags, threads, ], diff --git a/src/home/user-record-sign.c b/src/home/user-record-sign.c index 7a80ef1e7ab91..6bc97af27a675 100644 --- a/src/home/user-record-sign.c +++ b/src/home/user-record-sign.c @@ -1,9 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "alloc-util.h" +#include "crypto-util.h" #include "json-util.h" #include "log.h" -#include "openssl-util.h" #include "user-record-sign.h" #include "user-record.h" @@ -118,14 +118,14 @@ int user_record_verify(UserRecord *ur, EVP_PKEY *public_key) { if (r < 0) return r; - md_ctx = EVP_MD_CTX_new(); + md_ctx = sym_EVP_MD_CTX_new(); if (!md_ctx) return -ENOMEM; - if (EVP_DigestVerifyInit(md_ctx, NULL, NULL, NULL, public_key) <= 0) + if (sym_EVP_DigestVerifyInit(md_ctx, NULL, NULL, NULL, public_key) <= 0) return -EIO; - if (EVP_DigestVerify(md_ctx, signature, signature_size, (uint8_t*) text, strlen(text)) <= 0) { + if (sym_EVP_DigestVerify(md_ctx, signature, signature_size, (uint8_t*) text, strlen(text)) <= 0) { n_bad++; continue; } diff --git a/src/home/user-record-sign.h b/src/home/user-record-sign.h index 3007d00b01d01..673c7b2b372aa 100644 --- a/src/home/user-record-sign.h +++ b/src/home/user-record-sign.h @@ -1,8 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include - #include "shared-forward.h" int user_record_sign(UserRecord *ur, EVP_PKEY *private_key, UserRecord **ret); diff --git a/src/import/meson.build b/src/import/meson.build index 63e632a6cd1fb..c2879c5d843cf 100644 --- a/src/import/meson.build +++ b/src/import/meson.build @@ -31,7 +31,7 @@ executables += [ 'pull-tar.c', ), 'objects' : ['systemd-importd'], - 'dependencies' : libopenssl, + 'dependencies' : libopenssl_cflags, }, libexec_template + { 'name' : 'systemd-import', diff --git a/src/import/pull-common.c b/src/import/pull-common.c index ac921addb28aa..49f87bcac44e2 100644 --- a/src/import/pull-common.c +++ b/src/import/pull-common.c @@ -10,6 +10,7 @@ #include "fd-util.h" #include "hexdecoct.h" #include "io-util.h" +#include "iovec-util.h" #include "log.h" #include "memory-util.h" #include "os-util.h" diff --git a/src/import/pull-job.c b/src/import/pull-job.c index 69346ff4778fe..4c3fb05dd3533 100644 --- a/src/import/pull-job.c +++ b/src/import/pull-job.c @@ -6,6 +6,8 @@ #include #include "alloc-util.h" +#include "compress.h" +#include "crypto-util.h" #include "curl-util.h" #include "fd-util.h" #include "format-util.h" @@ -57,7 +59,7 @@ PullJob* pull_job_unref(PullJob *j) { j->compress = compressor_free(j->compress); if (j->checksum_ctx) - EVP_MD_CTX_free(j->checksum_ctx); + sym_EVP_MD_CTX_free(j->checksum_ctx); free(j->url); free(j->etag); @@ -138,7 +140,7 @@ int pull_job_restart(PullJob *j, const char *new_url) { j->compress = compressor_free(j->compress); if (j->checksum_ctx) { - EVP_MD_CTX_free(j->checksum_ctx); + sym_EVP_MD_CTX_free(j->checksum_ctx); j->checksum_ctx = NULL; } @@ -279,7 +281,7 @@ void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { goto finish; } - r = EVP_DigestFinal_ex(j->checksum_ctx, j->checksum.iov_base, &checksum_len); + r = sym_EVP_DigestFinal_ex(j->checksum_ctx, j->checksum.iov_base, &checksum_len); if (r == 0) { r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get checksum."); goto finish; @@ -294,7 +296,7 @@ void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { goto finish; } - log_debug("%s of %s is %s.", EVP_MD_CTX_get0_name(j->checksum_ctx), pull_job_description(j), h); + log_debug("%s of %s is %s.", sym_EVP_MD_CTX_get0_name(j->checksum_ctx), pull_job_description(j), h); } if (iovec_is_set(&j->expected_checksum) && @@ -448,7 +450,7 @@ static int pull_job_write_compressed(PullJob *j, const struct iovec *data) { "Content length incorrect."); if (j->checksum_ctx) { - r = EVP_DigestUpdate(j->checksum_ctx, data->iov_base, data->iov_len); + r = sym_EVP_DigestUpdate(j->checksum_ctx, data->iov_base, data->iov_len); if (r == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Could not hash chunk."); @@ -485,11 +487,15 @@ static int pull_job_open_disk(PullJob *j) { } if (j->calc_checksum) { - j->checksum_ctx = EVP_MD_CTX_new(); + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + + j->checksum_ctx = sym_EVP_MD_CTX_new(); if (!j->checksum_ctx) return log_oom(); - r = EVP_DigestInit_ex(j->checksum_ctx, EVP_sha256(), NULL); + r = sym_EVP_DigestInit_ex(j->checksum_ctx, sym_EVP_sha256(), NULL); if (r == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize hash context."); diff --git a/src/import/pull-job.h b/src/import/pull-job.h index 1daa006c1c373..0b878292f096b 100644 --- a/src/import/pull-job.h +++ b/src/import/pull-job.h @@ -3,9 +3,8 @@ #include #include +#include -#include "compress.h" -#include "openssl-util.h" #include "shared-forward.h" typedef struct CurlGlue CurlGlue; diff --git a/src/import/pull-oci.c b/src/import/pull-oci.c index f4878e3c87d31..acea93b09de9a 100644 --- a/src/import/pull-oci.c +++ b/src/import/pull-oci.c @@ -31,6 +31,7 @@ #include "pull-oci.h" #include "rm-rf.h" #include "set.h" +#include "sha256-fundamental.h" #include "signal-util.h" #include "stat-util.h" #include "string-util.h" diff --git a/src/import/pull-raw.c b/src/import/pull-raw.c index 6fde8c5f8bccc..0ddde7c091962 100644 --- a/src/import/pull-raw.c +++ b/src/import/pull-raw.c @@ -13,6 +13,7 @@ #include "import-common.h" #include "import-util.h" #include "install-file.h" +#include "iovec-util.h" #include "log.h" #include "mkdir-label.h" #include "pull-common.h" diff --git a/src/keyutil/keyutil.c b/src/keyutil/keyutil.c index dcdd26422674f..01f27dcdedcc5 100644 --- a/src/keyutil/keyutil.c +++ b/src/keyutil/keyutil.c @@ -3,13 +3,13 @@ #include "alloc-util.h" #include "ask-password-api.h" #include "build.h" +#include "crypto-util.h" #include "fd-util.h" #include "fileio.h" #include "format-table.h" #include "fs-util.h" #include "log.h" #include "main-func.h" -#include "openssl-util.h" #include "options.h" #include "parse-argument.h" #include "pretty-print.h" @@ -240,6 +240,10 @@ static int verb_extract_public(int argc, char *argv[], uintptr_t _data, void *us return r; } + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + r = openssl_load_x509_certificate( arg_certificate_source_type, arg_certificate_source, @@ -248,7 +252,7 @@ static int verb_extract_public(int argc, char *argv[], uintptr_t _data, void *us if (r < 0) return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate); - public_key = X509_get_pubkey(certificate); + public_key = sym_X509_get_pubkey(certificate); if (!public_key) return log_error_errno( SYNTHETIC_ERRNO(EIO), @@ -288,7 +292,7 @@ static int verb_extract_public(int argc, char *argv[], uintptr_t _data, void *us } else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "One of --certificate=, or --private-key= must be specified"); - if (PEM_write_PUBKEY(stdout, public_key) == 0) + if (sym_PEM_write_PUBKEY(stdout, public_key) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write public key to stdout"); return 0; @@ -317,7 +321,7 @@ static int verb_extract_certificate(int argc, char *argv[], uintptr_t _data, voi if (r < 0) return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate); - if (PEM_write_X509(stdout, certificate) == 0) + if (sym_PEM_write_X509(stdout, certificate) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write certificate to stdout."); return 0; @@ -376,18 +380,18 @@ static int verb_pkcs7(int argc, char *argv[], uintptr_t _data, void *userdata) { if (content_len == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Content file %s is empty", arg_content); - if (!PKCS7_content_new(pkcs7, NID_pkcs7_data)) + if (!sym_PKCS7_content_new(pkcs7, NID_pkcs7_data)) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Error creating new PKCS7 content field"); - ASN1_STRING_set0(pkcs7->d.sign->contents->d.data, TAKE_PTR(content), content_len); + sym_ASN1_STRING_set0(pkcs7->d.sign->contents->d.data, TAKE_PTR(content), content_len); } else - if (PKCS7_set_detached(pkcs7, true) == 0) + if (sym_PKCS7_set_detached(pkcs7, true) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS#7 detached attribute: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); /* Add PKCS1 signature to PKCS7_SIGNER_INFO */ - ASN1_STRING_set0(signer_info->enc_digest, TAKE_PTR(pkcs1), pkcs1_len); + sym_ASN1_STRING_set0(signer_info->enc_digest, TAKE_PTR(pkcs1), pkcs1_len); _cleanup_fclose_ FILE *output = NULL; _cleanup_(unlink_and_freep) char *tmp = NULL; @@ -395,9 +399,9 @@ static int verb_pkcs7(int argc, char *argv[], uintptr_t _data, void *userdata) { if (r < 0) return log_error_errno(r, "Failed to open temporary file: %m"); - if (!i2d_PKCS7_fp(output, pkcs7)) + if (!sym_i2d_PKCS7_fp(output, pkcs7)) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write PKCS#7 file: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); r = flink_tmpfile(output, tmp, arg_output, LINK_TMPFILE_REPLACE|LINK_TMPFILE_SYNC); if (r < 0) diff --git a/src/keyutil/meson.build b/src/keyutil/meson.build index 956f6039895de..ae3db9a276cf3 100644 --- a/src/keyutil/meson.build +++ b/src/keyutil/meson.build @@ -7,6 +7,6 @@ executables += [ 'HAVE_OPENSSL', ], 'sources' : files('keyutil.c'), - 'dependencies' : libopenssl, + 'dependencies' : libopenssl_cflags, }, ] diff --git a/src/measure/measure-tool.c b/src/measure/measure-tool.c index 09c04d888c833..44619cab7db4b 100644 --- a/src/measure/measure-tool.c +++ b/src/measure/measure-tool.c @@ -7,6 +7,7 @@ #include "alloc-util.h" #include "ask-password-api.h" #include "build.h" +#include "crypto-util.h" #include "efi-loader.h" #include "efivars.h" #include "fd-util.h" @@ -15,7 +16,6 @@ #include "hexdecoct.h" #include "log.h" #include "main-func.h" -#include "openssl-util.h" #include "options.h" #include "pager.h" #include "parse-argument.h" @@ -178,11 +178,15 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { "Select TPM bank (SHA1, SHA256, SHA384, SHA512)"): { const EVP_MD *implementation; - implementation = EVP_get_digestbyname(arg); + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + + implementation = sym_EVP_get_digestbyname(arg); if (!implementation) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", arg); - if (strv_extend(&arg_banks, EVP_MD_name(implementation)) < 0) + if (strv_extend(&arg_banks, sym_EVP_MD_get0_name(implementation)) < 0) return log_oom(); break; @@ -529,7 +533,7 @@ static void evp_md_ctx_free_all(EVP_MD_CTX **md[]) { return; for (size_t i = 0; (*md)[i]; i++) - EVP_MD_CTX_free((*md)[i]); + sym_EVP_MD_CTX_free((*md)[i]); *md = mfree(*md); } @@ -546,22 +550,22 @@ static int pcr_state_extend(PcrState *pcr_state, const void *data, size_t sz) { /* Extends a (virtual) PCR by the given data */ - mc = EVP_MD_CTX_new(); + mc = sym_EVP_MD_CTX_new(); if (!mc) return log_oom(); - if (EVP_DigestInit_ex(mc, pcr_state->md, NULL) != 1) + if (sym_EVP_DigestInit_ex(mc, pcr_state->md, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize %s context.", pcr_state->bank); /* First thing we do, is hash the old PCR value */ - if (EVP_DigestUpdate(mc, pcr_state->value, pcr_state->value_size) != 1) + if (sym_EVP_DigestUpdate(mc, pcr_state->value, pcr_state->value_size) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest."); /* Then, we hash the new data */ - if (EVP_DigestUpdate(mc, data, sz) != 1) + if (sym_EVP_DigestUpdate(mc, data, sz) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest."); - if (EVP_DigestFinal_ex(mc, pcr_state->value, &value_size) != 1) + if (sym_EVP_DigestFinal_ex(mc, pcr_state->value, &value_size) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize hash context."); assert(value_size == pcr_state->value_size); @@ -629,11 +633,11 @@ static int measure_kernel(PcrState *pcr_states, size_t n) { return log_oom(); for (size_t i = 0; i < n; i++) { - mdctx[i] = EVP_MD_CTX_new(); + mdctx[i] = sym_EVP_MD_CTX_new(); if (!mdctx[i]) return log_oom(); - if (EVP_DigestInit_ex(mdctx[i], pcr_states[i].md, NULL) != 1) + if (sym_EVP_DigestInit_ex(mdctx[i], pcr_states[i].md, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize data %s context.", pcr_states[i].bank); } @@ -648,7 +652,7 @@ static int measure_kernel(PcrState *pcr_states, size_t n) { break; for (size_t i = 0; i < n; i++) - if (EVP_DigestUpdate(mdctx[i], buffer, sz) != 1) + if (sym_EVP_DigestUpdate(mdctx[i], buffer, sz) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest."); m += sz; @@ -668,7 +672,7 @@ static int measure_kernel(PcrState *pcr_states, size_t n) { return log_oom(); /* Measure name of section */ - if (EVP_Digest(unified_sections[c], strlen(unified_sections[c]) + 1, data_hash, &data_hash_size, pcr_states[i].md, NULL) != 1) + if (sym_EVP_Digest(unified_sections[c], strlen(unified_sections[c]) + 1, data_hash, &data_hash_size, pcr_states[i].md, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash section name with %s.", pcr_states[i].bank); assert(data_hash_size == (unsigned) pcr_states[i].value_size); @@ -678,7 +682,7 @@ static int measure_kernel(PcrState *pcr_states, size_t n) { return r; /* Retrieve hash of data and measure it */ - if (EVP_DigestFinal_ex(mdctx[i], data_hash, &data_hash_size) != 1) + if (sym_EVP_DigestFinal_ex(mdctx[i], data_hash, &data_hash_size) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize hash context."); assert(data_hash_size == (unsigned) pcr_states[i].value_size); @@ -719,7 +723,7 @@ static int measure_phase(PcrState *pcr_states, size_t n, const char *phase) { _cleanup_free_ void *b = NULL; int bsz; - bsz = EVP_MD_size(pcr_states[i].md); + bsz = sym_EVP_MD_get_size(pcr_states[i].md); assert(bsz > 0); b = malloc(bsz); @@ -727,7 +731,7 @@ static int measure_phase(PcrState *pcr_states, size_t n, const char *phase) { return log_oom(); /* First hash the word itself */ - if (EVP_Digest(*word, wl, b, NULL, pcr_states[i].md, NULL) != 1) + if (sym_EVP_Digest(*word, wl, b, NULL, pcr_states[i].md, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash word '%s'.", *word); /* And then extend the PCR with the resulting hash */ @@ -757,13 +761,13 @@ static int pcr_states_allocate(PcrState **ret) { _cleanup_free_ char *b = NULL; int sz; - assert_se(implementation = EVP_get_digestbyname(*d)); /* Must work, we already checked while parsing command line */ + assert_se(implementation = sym_EVP_get_digestbyname(*d)); /* Must work, we already checked while parsing command line */ - b = strdup(EVP_MD_name(implementation)); + b = strdup(sym_EVP_MD_get0_name(implementation)); if (!b) return log_oom(); - sz = EVP_MD_size(implementation); + sz = sym_EVP_MD_get_size(implementation); if (sz <= 0 || sz >= INT_MAX) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unexpected digest size: %i", sz); @@ -986,11 +990,11 @@ static int build_policy_digest(bool sign) { if (!pubkeyf) return log_error_errno(errno, "Failed to open public key file '%s': %m", arg_public_key); - pubkey = PEM_read_PUBKEY(pubkeyf, NULL, NULL, NULL); + pubkey = sym_PEM_read_PUBKEY(pubkeyf, NULL, NULL, NULL); if (!pubkey) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse public key '%s'.", arg_public_key); } else if (certificate) { - pubkey = X509_get_pubkey(certificate); + pubkey = sym_X509_get_pubkey(certificate); if (!pubkey) return log_error_errno( SYNTHETIC_ERRNO(EIO), @@ -1026,7 +1030,7 @@ static int build_policy_digest(bool sign) { for (size_t i = 0; i < n; i++) { PcrState *p = pcr_states + i; - int tpmalg = tpm2_hash_alg_from_string(EVP_MD_name(p->md)); + int tpmalg = tpm2_hash_alg_from_string(sym_EVP_MD_get0_name(p->md)); if (tpmalg < 0) return log_error_errno(tpmalg, "Unsupported PCR bank"); @@ -1044,19 +1048,19 @@ static int build_policy_digest(bool sign) { size_t ss = 0; if (privkey) { /* We always use SHA256 for signing currently. Regardless of the bank. */ - const EVP_MD *sha256 = ASSERT_PTR(EVP_get_digestbyname("sha256")); + const EVP_MD *sha256 = ASSERT_PTR(sym_EVP_get_digestbyname("sha256")); r = digest_and_sign(sha256, privkey, pcr_policy_digest.buffer, pcr_policy_digest.size, &sig, &ss); if (r == -EADDRNOTAVAIL) - return log_error_errno(r, "Hash algorithm '%s' not available while signing. (Maybe OS security policy disables this algorithm?)", EVP_MD_name(p->md)); + return log_error_errno(r, "Hash algorithm '%s' not available while signing. (Maybe OS security policy disables this algorithm?)", sym_EVP_MD_get0_name(p->md)); if (r < 0) - return log_error_errno(r, "Failed to sign PCR policy with hash algorithm '%s': %m", EVP_MD_name(p->md)); + return log_error_errno(r, "Failed to sign PCR policy with hash algorithm '%s': %m", sym_EVP_MD_get0_name(p->md)); } _cleanup_free_ void *pubkey_fp = NULL; size_t pubkey_fp_size = 0; if (pubkey) { - r = pubkey_fingerprint(pubkey, EVP_sha256(), &pubkey_fp, &pubkey_fp_size); + r = pubkey_fingerprint(pubkey, sym_EVP_sha256(), &pubkey_fp, &pubkey_fp_size); if (r < 0) return r; } @@ -1121,6 +1125,10 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + return dispatch_verb_with_args(args, NULL); } diff --git a/src/measure/meson.build b/src/measure/meson.build index e4e4f579dfa06..ff777dc5d9842 100644 --- a/src/measure/meson.build +++ b/src/measure/meson.build @@ -9,6 +9,6 @@ executables += [ 'HAVE_TPM2', ], 'sources' : files('measure-tool.c'), - 'dependencies' : libopenssl, + 'dependencies' : libopenssl_cflags, }, ] diff --git a/src/pcrextend/meson.build b/src/pcrextend/meson.build index 3a8824eaa8444..f2f5f3b46e3e8 100644 --- a/src/pcrextend/meson.build +++ b/src/pcrextend/meson.build @@ -11,7 +11,7 @@ executables += [ ], 'sources' : files('pcrextend.c'), 'dependencies' : [ - libopenssl, + libopenssl_cflags, tpm2, ], }, diff --git a/src/pcrextend/pcrextend.c b/src/pcrextend/pcrextend.c index 639331ba97dc8..c92d3f981124a 100644 --- a/src/pcrextend/pcrextend.c +++ b/src/pcrextend/pcrextend.c @@ -6,6 +6,7 @@ #include "alloc-util.h" #include "build.h" +#include "crypto-util.h" #include "efi-loader.h" #include "escape.h" #include "format-table.h" @@ -96,11 +97,15 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_LONG("bank", "DIGEST", "Select TPM PCR bank (SHA1, SHA256)"): { const EVP_MD *implementation; - implementation = EVP_get_digestbyname(arg); + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + + implementation = sym_EVP_get_digestbyname(arg); if (!implementation) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", arg); - if (strv_extend(&arg_banks, EVP_MD_name(implementation)) < 0) + if (strv_extend(&arg_banks, sym_EVP_MD_get0_name(implementation)) < 0) return log_oom(); break; diff --git a/src/pcrlock/meson.build b/src/pcrlock/meson.build index c5b609ed4aa67..a80cd31947977 100644 --- a/src/pcrlock/meson.build +++ b/src/pcrlock/meson.build @@ -13,7 +13,7 @@ executables += [ ), 'dependencies' : [ libm, - libopenssl, + libopenssl_cflags, tpm2, ], 'public' : true, diff --git a/src/pcrlock/pcrlock-firmware.c b/src/pcrlock/pcrlock-firmware.c index 81481dc168968..5abf66077e7e4 100644 --- a/src/pcrlock/pcrlock-firmware.c +++ b/src/pcrlock/pcrlock-firmware.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - +#include "crypto-util.h" #include "log.h" #include "memory-util.h" #include "pcrlock-firmware.h" @@ -149,13 +148,13 @@ int validate_firmware_header( continue; } - implementation = EVP_get_digestbyname(a); + implementation = sym_EVP_get_digestbyname(a); if (!implementation) { log_notice("Event log advertises hash algorithm '%s' we don't implement, can't validate.", a); continue; } - if (EVP_MD_size(implementation) != id->digestSizes[i].digestSize) + if (sym_EVP_MD_get_size(implementation) != id->digestSizes[i].digestSize) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Advertised digest size for '%s' is wrong, refusing.", a); } diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index 62a84a26cb688..ecf7b18c351bf 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -18,6 +17,7 @@ #include "color-util.h" #include "conf-files.h" #include "creds-util.h" +#include "crypto-util.h" #include "efi-api.h" #include "efivars.h" #include "env-util.h" @@ -1346,7 +1346,7 @@ static int event_log_calculate_pcrs(EventLog *el) { const char *a; assert_se(a = tpm2_hash_alg_to_string(el->algorithms[i])); - assert_se(md = EVP_get_digestbyname(a)); + assert_se(md = sym_EVP_get_digestbyname(a)); el->mds[i] = md; } @@ -1354,7 +1354,7 @@ static int event_log_calculate_pcrs(EventLog *el) { for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) for (size_t i = 0; i < el->n_algorithms; i++) { EventLogRegisterBank *b = el->registers[pcr].banks + i; - event_log_initial_pcr_state(el, pcr, EVP_MD_size(el->mds[i]), &b->calculated); + event_log_initial_pcr_state(el, pcr, sym_EVP_MD_get_size(el->mds[i]), &b->calculated); } FOREACH_ARRAY(rr, el->records, el->n_records) { @@ -1379,20 +1379,20 @@ static int event_log_calculate_pcrs(EventLog *el) { reg_b = reg->banks + i; - mc = EVP_MD_CTX_new(); + mc = sym_EVP_MD_CTX_new(); if (!mc) return log_oom(); - if (EVP_DigestInit_ex(mc, el->mds[i], NULL) != 1) + if (sym_EVP_DigestInit_ex(mc, el->mds[i], NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize %s message digest context.", n); - if (EVP_DigestUpdate(mc, reg_b->calculated.buffer, reg_b->calculated.size) != 1) + if (sym_EVP_DigestUpdate(mc, reg_b->calculated.buffer, reg_b->calculated.size) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest."); - if (EVP_DigestUpdate(mc, rec_b->hash.buffer, rec_b->hash.size) != 1) + if (sym_EVP_DigestUpdate(mc, rec_b->hash.buffer, rec_b->hash.size) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest."); - if (EVP_DigestFinal_ex(mc, reg_b->calculated.buffer, &sz) != 1) + if (sym_EVP_DigestFinal_ex(mc, reg_b->calculated.buffer, &sz) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize hash context."); assert(sz == reg_b->calculated.size); @@ -1481,7 +1481,7 @@ static int event_log_record_validate_hash_firmware( strict = false; } - int mdsz = EVP_MD_size(md); + int mdsz = sym_EVP_MD_get_size(md); assert(mdsz > 0); assert((size_t) mdsz <= sizeof_field(TPM2B_DIGEST, buffer)); @@ -1491,13 +1491,13 @@ static int event_log_record_validate_hash_firmware( unsigned dsz = mdsz; - if (EVP_Digest(hdata, hsz, payload_hash.buffer, &dsz, md, NULL) != 1) + if (sym_EVP_Digest(hdata, hsz, payload_hash.buffer, &dsz, md, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to calculate event payload hash."); assert(dsz == (unsigned) mdsz); /* If this didn't match then let's try the alternative format here, if we have one, and check things then. */ if (memcmp_nn(bank->hash.buffer, bank->hash.size, payload_hash.buffer, payload_hash.size) != 0 && hdata_alternative) { - if (EVP_Digest(hdata_alternative, hsz_alternative, payload_hash.buffer, &dsz, md, NULL) != 1) + if (sym_EVP_Digest(hdata_alternative, hsz_alternative, payload_hash.buffer, &dsz, md, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to calculate event payload hash."); assert(dsz == (unsigned) mdsz); } @@ -1541,7 +1541,7 @@ static int event_log_record_validate_hash_userspace( assert(sd_json_variant_is_string(js)); s = sd_json_variant_string(js); - mdsz = EVP_MD_size(md); + mdsz = sym_EVP_MD_get_size(md); assert(mdsz > 0); payload_hash_size = mdsz; @@ -1549,7 +1549,7 @@ static int event_log_record_validate_hash_userspace( if (!payload_hash) return log_oom(); - if (EVP_Digest(s, strlen(s), payload_hash, &payload_hash_size, md, NULL) != 1) + if (sym_EVP_Digest(s, strlen(s), payload_hash, &payload_hash_size, md, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to calculate event payload hash."); assert((int) payload_hash_size == mdsz); @@ -1575,7 +1575,7 @@ static int event_log_validate_record_hashes(EventLog *el) { const char *a; assert_se(a = tpm2_hash_alg_to_string(bank->algorithm)); - assert_se(md = EVP_get_digestbyname(a)); + assert_se(md = sym_EVP_get_digestbyname(a)); r = event_log_record_validate_hash_firmware(*rr, bank, md); if (r < 0) @@ -2788,8 +2788,8 @@ static int make_pcrlock_record( const char *a; assert_se(a = tpm2_hash_alg_to_string(*pa)); - assert_se(md = EVP_get_digestbyname(a)); - hash_ssize = EVP_MD_size(md); + assert_se(md = sym_EVP_get_digestbyname(a)); + hash_ssize = sym_EVP_MD_get_size(md); assert(hash_ssize > 0); hash_usize = hash_ssize; @@ -2797,7 +2797,7 @@ static int make_pcrlock_record( if (!hash) return log_oom(); - if (EVP_Digest(data, data_size, hash, &hash_usize, md, NULL) != 1) + if (sym_EVP_Digest(data, data_size, hash, &hash_usize, md, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash data with algorithm '%s'.", a); r = sd_json_variant_append_arraybo( @@ -2821,7 +2821,7 @@ static void evp_md_ctx_free_all(EVP_MD_CTX *(*md)[TPM2_N_HASH_ALGORITHMS]) { assert(md); FOREACH_ARRAY(alg, *md, TPM2_N_HASH_ALGORITHMS) if (*alg) - EVP_MD_CTX_free(*alg); + sym_EVP_MD_CTX_free(*alg); } static int make_pcrlock_record_from_stream( @@ -2841,13 +2841,13 @@ static int make_pcrlock_record_from_stream( const EVP_MD *md; assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i])); - assert_se(md = EVP_get_digestbyname(a)); + assert_se(md = sym_EVP_get_digestbyname(a)); - mdctx[i] = EVP_MD_CTX_new(); + mdctx[i] = sym_EVP_MD_CTX_new(); if (!mdctx[i]) return log_oom(); - if (EVP_DigestInit_ex(mdctx[i], md, NULL) != 1) + if (sym_EVP_DigestInit_ex(mdctx[i], md, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to initialize message digest for %s.", a); } @@ -2863,7 +2863,7 @@ static int make_pcrlock_record_from_stream( break; for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) - if (EVP_DigestUpdate(mdctx[i], buffer, n) != 1) + if (sym_EVP_DigestUpdate(mdctx[i], buffer, n) != 1) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data."); } @@ -2873,12 +2873,12 @@ static int make_pcrlock_record_from_stream( unsigned hash_usize; assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i])); - hash_ssize = EVP_MD_CTX_size(mdctx[i]); + hash_ssize = sym_EVP_MD_CTX_get_size(mdctx[i]); assert(hash_ssize > 0 && hash_ssize <= EVP_MAX_MD_SIZE); hash_usize = hash_ssize; unsigned char hash[hash_usize]; - if (EVP_DigestFinal_ex(mdctx[i], hash, &hash_usize) != 1) + if (sym_EVP_DigestFinal_ex(mdctx[i], hash, &hash_usize) != 1) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize hash context for algorithn '%s'.", a); @@ -3072,7 +3072,7 @@ static const EVP_MD* evp_from_tpm2_alg(uint16_t alg) { if (!name) return NULL; - return EVP_get_digestbyname(name); + return sym_EVP_get_digestbyname(name); } static int event_log_component_variant_calculate( @@ -3106,13 +3106,13 @@ static int event_log_component_variant_calculate( continue; } - md_ctx = EVP_MD_CTX_new(); + md_ctx = sym_EVP_MD_CTX_new(); if (!md_ctx) return log_oom(); const EVP_MD *md = ASSERT_PTR(evp_from_tpm2_alg(tpm2_hash_algorithms[i])); - int sz = EVP_MD_size(md); + int sz = sym_EVP_MD_get_size(md); assert(sz > 0); assert((size_t) sz <= sizeof_field(TPM2B_DIGEST, buffer)); @@ -3121,17 +3121,17 @@ static int event_log_component_variant_calculate( assert(result->hash[i].size == (size_t) sz); assert(b->hash.size == (size_t) sz); - if (EVP_DigestInit_ex(md_ctx, md, NULL) != 1) + if (sym_EVP_DigestInit_ex(md_ctx, md, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to initialize message digest."); - if (EVP_DigestUpdate(md_ctx, result->hash[i].buffer, sz) != 1) + if (sym_EVP_DigestUpdate(md_ctx, result->hash[i].buffer, sz) != 1) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash bank value."); - if (EVP_DigestUpdate(md_ctx, b->hash.buffer, sz) != 1) + if (sym_EVP_DigestUpdate(md_ctx, b->hash.buffer, sz) != 1) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash data value."); unsigned l = (unsigned) sz; - if (EVP_DigestFinal_ex(md_ctx, result->hash[i].buffer, &l) != 1) + if (sym_EVP_DigestFinal_ex(md_ctx, result->hash[i].buffer, &l) != 1) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize message digest."); assert(l == (unsigned) sz); @@ -4749,7 +4749,7 @@ static int verb_lock_pe(int argc, char *argv[], uintptr_t _data, void *userdata) const char *a; assert_se(a = tpm2_hash_alg_to_string(*pa)); - assert_se(md = EVP_get_digestbyname(a)); + assert_se(md = sym_EVP_get_digestbyname(a)); r = pe_hash(fd < 0 ? STDIN_FILENO : fd, md, &hash, &hash_size); if (r < 0) @@ -4813,7 +4813,7 @@ static int verb_lock_uki(int argc, char *argv[], uintptr_t _data, void *userdata const char *a; assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i])); - assert_se(md = EVP_get_digestbyname(a)); + assert_se(md = sym_EVP_get_digestbyname(a)); r = pe_hash(fd < 0 ? STDIN_FILENO : fd, md, &peh, hash_sizes + i); if (r < 0) @@ -5472,6 +5472,10 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + if (arg_varlink) { _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; diff --git a/src/repart/meson.build b/src/repart/meson.build index b7c70be068574..9b89f56f7a0f2 100644 --- a/src/repart/meson.build +++ b/src/repart/meson.build @@ -16,7 +16,7 @@ executables += [ libblkid_cflags, libfdisk_cflags, libmount_cflags, - libopenssl, + libopenssl_cflags, threads, ], }, @@ -34,7 +34,7 @@ executables += [ libblkid_cflags, libfdisk_cflags, libmount_cflags, - libopenssl, + libopenssl_cflags, threads, ], }, diff --git a/src/repart/repart.c b/src/repart/repart.c index e585313713236..0aab755d2cf25 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -24,6 +24,7 @@ #include "conf-parser.h" #include "constants.h" #include "copy.h" +#include "crypto-util.h" #include "cryptsetup-util.h" #include "device-util.h" #include "devnum-util.h" @@ -59,7 +60,6 @@ #include "mount-util.h" #include "mountpoint-util.h" #include "nulstr-util.h" -#include "openssl-util.h" #include "options.h" #include "parse-argument.h" #include "parse-helpers.h" @@ -985,9 +985,11 @@ static Context* context_free(Context *context) { free(context->node); #if HAVE_OPENSSL - X509_free(context->certificate); + if (context->certificate) + sym_X509_free(context->certificate); openssl_ask_password_ui_free(context->ui); - EVP_PKEY_free(context->private_key); + if (context->private_key) + sym_EVP_PKEY_free(context->private_key); #endif context->link = sd_varlink_unref(context->link); @@ -5826,7 +5828,7 @@ static int sign_verity_roothash( _cleanup_(PKCS7_freep) PKCS7 *p7 = NULL; _cleanup_free_ char *hex = NULL; _cleanup_free_ uint8_t *sig = NULL; - int sigsz; + int sigsz, r; assert(context); assert(context->certificate); @@ -5835,23 +5837,27 @@ static int sign_verity_roothash( assert(iovec_is_set(roothash)); assert(ret_signature); + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + hex = hexmem(roothash->iov_base, roothash->iov_len); if (!hex) return log_oom(); - rb = BIO_new_mem_buf(hex, -1); + rb = sym_BIO_new_mem_buf(hex, -1); if (!rb) return log_oom(); - p7 = PKCS7_sign(context->certificate, context->private_key, NULL, rb, PKCS7_DETACHED|PKCS7_NOATTR|PKCS7_BINARY); + p7 = sym_PKCS7_sign(context->certificate, context->private_key, NULL, rb, PKCS7_DETACHED|PKCS7_NOATTR|PKCS7_BINARY); if (!p7) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to calculate PKCS7 signature: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - sigsz = i2d_PKCS7(p7, &sig); + sigsz = sym_i2d_PKCS7(p7, &sig); if (sigsz < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert PKCS7 signature to DER: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); *ret_signature = IOVEC_MAKE(TAKE_PTR(sig), sigsz); diff --git a/src/resolve/meson.build b/src/resolve/meson.build index b9b2e24b18123..5802889746e97 100644 --- a/src/resolve/meson.build +++ b/src/resolve/meson.build @@ -70,7 +70,7 @@ endif resolve_common_template = { 'dependencies' : [ libidn2_cflags, - libopenssl, + libopenssl_cflags, libm, threads, ], diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index 8cc9e73f93cd5..1452131b59e7d 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -20,6 +20,7 @@ #include "bus-locator.h" #include "bus-message-util.h" #include "bus-util.h" +#include "crypto-util.h" #include "dns-configuration.h" #include "dns-domain.h" #include "dns-packet.h" @@ -34,7 +35,6 @@ #include "main-func.h" #include "missing-network.h" #include "netlink-util.h" -#include "openssl-util.h" #include "ordered-set.h" #include "pager.h" #include "parse-argument.h" diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 1a634810beafe..017c370b0eb52 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -2,6 +2,7 @@ #include "alloc-util.h" #include "bitmap.h" +#include "crypto-util.h" #include "dns-answer.h" #include "dns-domain.h" #include "dns-rr.h" @@ -10,19 +11,12 @@ #include "log.h" #include "memory-util.h" #include "memstream-util.h" -#include "openssl-util.h" #include "resolved-dns-dnssec.h" #include "sort-util.h" #include "string-table.h" #include "string-util.h" #include "time-util.h" -#if HAVE_OPENSSL -DISABLE_WARNING_DEPRECATED_DECLARATIONS; -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(RSA*, RSA_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EC_KEY*, EC_KEY_free, NULL); -REENABLE_WARNING; -#endif #define VERIFY_RRS_MAX 256 #define MAX_KEY_SIZE (32*1024) @@ -84,49 +78,49 @@ static int dnssec_rsa_verify_raw( assert(hash_algorithm); - e = BN_bin2bn(exponent, exponent_size, NULL); + e = sym_BN_bin2bn(exponent, exponent_size, NULL); if (!e) return -EIO; - m = BN_bin2bn(modulus, modulus_size, NULL); + m = sym_BN_bin2bn(modulus, modulus_size, NULL); if (!m) return -EIO; - rpubkey = RSA_new(); + rpubkey = sym_RSA_new(); if (!rpubkey) return -ENOMEM; - if (RSA_set0_key(rpubkey, m, e, NULL) <= 0) + if (sym_RSA_set0_key(rpubkey, m, e, NULL) <= 0) return -EIO; e = m = NULL; - if ((size_t) RSA_size(rpubkey) != signature_size) + if ((size_t) sym_RSA_size(rpubkey) != signature_size) return -EINVAL; - epubkey = EVP_PKEY_new(); + epubkey = sym_EVP_PKEY_new(); if (!epubkey) return -ENOMEM; - if (EVP_PKEY_assign_RSA(epubkey, RSAPublicKey_dup(rpubkey)) <= 0) + if (sym_EVP_PKEY_assign_RSA(epubkey, sym_RSAPublicKey_dup(rpubkey)) <= 0) return -EIO; - ctx = EVP_PKEY_CTX_new(epubkey, NULL); + ctx = sym_EVP_PKEY_CTX_new(epubkey, NULL); if (!ctx) return -ENOMEM; - if (EVP_PKEY_verify_init(ctx) <= 0) + if (sym_EVP_PKEY_verify_init(ctx) <= 0) return -EIO; - if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0) + if (sym_EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0) return -EIO; - if (EVP_PKEY_CTX_set_signature_md(ctx, hash_algorithm) <= 0) + if (sym_EVP_PKEY_CTX_set_signature_md(ctx, hash_algorithm) <= 0) return -EIO; - r = EVP_PKEY_verify(ctx, signature, signature_size, data, data_size); + r = sym_EVP_PKEY_verify(ctx, signature, signature_size, data, data_size); if (r < 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), - "Signature verification failed: 0x%lx", ERR_get_error()); + "Signature verification failed: 0x%lx", sym_ERR_get_error()); REENABLE_WARNING; return r; @@ -207,58 +201,58 @@ static int dnssec_ecdsa_verify_raw( assert(hash_algorithm); - ec_group = EC_GROUP_new_by_curve_name(curve); + ec_group = sym_EC_GROUP_new_by_curve_name(curve); if (!ec_group) return -ENOMEM; - p = EC_POINT_new(ec_group); + p = sym_EC_POINT_new(ec_group); if (!p) return -ENOMEM; - bctx = BN_CTX_new(); + bctx = sym_BN_CTX_new(); if (!bctx) return -ENOMEM; - if (EC_POINT_oct2point(ec_group, p, key, key_size, bctx) <= 0) + if (sym_EC_POINT_oct2point(ec_group, p, key, key_size, bctx) <= 0) return -EIO; - eckey = EC_KEY_new(); + eckey = sym_EC_KEY_new(); if (!eckey) return -ENOMEM; - if (EC_KEY_set_group(eckey, ec_group) <= 0) + if (sym_EC_KEY_set_group(eckey, ec_group) <= 0) return -EIO; - if (EC_KEY_set_public_key(eckey, p) <= 0) + if (sym_EC_KEY_set_public_key(eckey, p) <= 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), - "EC_KEY_set_public_key failed: 0x%lx", ERR_get_error()); + "EC_KEY_set_public_key failed: 0x%lx", sym_ERR_get_error()); - if (EC_KEY_check_key(eckey) != 1) + if (sym_EC_KEY_check_key(eckey) != 1) return log_debug_errno(SYNTHETIC_ERRNO(EIO), - "EC_KEY_check_key failed: 0x%lx", ERR_get_error()); + "EC_KEY_check_key failed: 0x%lx", sym_ERR_get_error()); - r = BN_bin2bn(signature_r, signature_r_size, NULL); + r = sym_BN_bin2bn(signature_r, signature_r_size, NULL); if (!r) return -EIO; - s = BN_bin2bn(signature_s, signature_s_size, NULL); + s = sym_BN_bin2bn(signature_s, signature_s_size, NULL); if (!s) return -EIO; /* TODO: We should eventually use the EVP API once it supports ECDSA signature verification */ - sig = ECDSA_SIG_new(); + sig = sym_ECDSA_SIG_new(); if (!sig) return -ENOMEM; - if (ECDSA_SIG_set0(sig, r, s) <= 0) + if (sym_ECDSA_SIG_set0(sig, r, s) <= 0) return -EIO; r = s = NULL; - k = ECDSA_do_verify(data, data_size, sig, eckey); + k = sym_ECDSA_do_verify(data, data_size, sig, eckey); if (k < 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), - "Signature verification failed: 0x%lx", ERR_get_error()); + "Signature verification failed: 0x%lx", sym_ERR_get_error()); REENABLE_WARNING; return k; @@ -326,30 +320,30 @@ static int dnssec_eddsa_verify_raw( q[0] = 0x04; /* Prepend 0x04 to indicate an uncompressed key */ memcpy(q+1, signature, signature_size); - evkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, key, key_size); + evkey = sym_EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, key, key_size); if (!evkey) return log_debug_errno(SYNTHETIC_ERRNO(EIO), - "EVP_PKEY_new_raw_public_key failed: 0x%lx", ERR_get_error()); + "EVP_PKEY_new_raw_public_key failed: 0x%lx", sym_ERR_get_error()); - pctx = EVP_PKEY_CTX_new(evkey, NULL); + pctx = sym_EVP_PKEY_CTX_new(evkey, NULL); if (!pctx) return -ENOMEM; - ctx = EVP_MD_CTX_new(); + ctx = sym_EVP_MD_CTX_new(); if (!ctx) return -ENOMEM; /* This prevents EVP_DigestVerifyInit from managing pctx and complicating our free logic. */ - EVP_MD_CTX_set_pkey_ctx(ctx, pctx); + sym_EVP_MD_CTX_set_pkey_ctx(ctx, pctx); /* One might be tempted to use EVP_PKEY_verify_init, but see Ed25519(7ssl). */ - if (EVP_DigestVerifyInit(ctx, &pctx, NULL, NULL, evkey) <= 0) + if (sym_EVP_DigestVerifyInit(ctx, &pctx, NULL, NULL, evkey) <= 0) return -EIO; - r = EVP_DigestVerify(ctx, signature, signature_size, data, data_size); + r = sym_EVP_DigestVerify(ctx, signature, signature_size, data, data_size); if (r < 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), - "Signature verification failed: 0x%lx", ERR_get_error()); + "Signature verification failed: 0x%lx", sym_ERR_get_error()); return r; } @@ -382,12 +376,12 @@ static int dnssec_eddsa_verify( } static int md_add_uint8(EVP_MD_CTX *ctx, uint8_t v) { - return EVP_DigestUpdate(ctx, &v, sizeof(v)); + return sym_EVP_DigestUpdate(ctx, &v, sizeof(v)); } static int md_add_uint16(EVP_MD_CTX *ctx, uint16_t v) { v = htobe16(v); - return EVP_DigestUpdate(ctx, &v, sizeof(v)); + return sym_EVP_DigestUpdate(ctx, &v, sizeof(v)); } static void fwrite_uint8(FILE *fp, uint8_t v) { @@ -504,17 +498,17 @@ static const EVP_MD* algorithm_to_implementation_id(uint8_t algorithm) { case DNSSEC_ALGORITHM_RSASHA1: case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1: - return EVP_sha1(); + return sym_EVP_sha1(); case DNSSEC_ALGORITHM_RSASHA256: case DNSSEC_ALGORITHM_ECDSAP256SHA256: - return EVP_sha256(); + return sym_EVP_sha256(); case DNSSEC_ALGORITHM_ECDSAP384SHA384: - return EVP_sha384(); + return sym_EVP_sha384(); case DNSSEC_ALGORITHM_RSASHA512: - return EVP_sha512(); + return sym_EVP_sha512(); default: return NULL; @@ -645,19 +639,19 @@ static int dnssec_rrset_verify_sig( if (!md_algorithm) return -EOPNOTSUPP; - _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *ctx = sym_EVP_MD_CTX_new(); if (!ctx) return -ENOMEM; /* If the signature algorithm is supported by systemd-resolved but disabled by host policy, * also return -EOPNOTSUPP. */ - if (EVP_DigestInit_ex(ctx, md_algorithm, NULL) <= 0) + if (sym_EVP_DigestInit_ex(ctx, md_algorithm, NULL) <= 0) return -EOPNOTSUPP; - if (EVP_DigestUpdate(ctx, sig_data, sig_size) <= 0) + if (sym_EVP_DigestUpdate(ctx, sig_data, sig_size) <= 0) return -EIO; - if (EVP_DigestFinal_ex(ctx, hash, &hash_size) <= 0) + if (sym_EVP_DigestFinal_ex(ctx, hash, &hash_size) <= 0) return -EIO; assert(hash_size > 0); @@ -709,6 +703,11 @@ int dnssec_verify_rrset( assert(rrsig); assert(dnskey); assert(result); + + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + assert(rrsig->key->type == DNS_TYPE_RRSIG); assert(dnskey->key->type == DNS_TYPE_DNSKEY); @@ -1041,13 +1040,13 @@ static const EVP_MD* digest_to_hash_md(uint8_t algorithm) { switch (algorithm) { case DNSSEC_DIGEST_SHA1: - return EVP_sha1(); + return sym_EVP_sha1(); case DNSSEC_DIGEST_SHA256: - return EVP_sha256(); + return sym_EVP_sha256(); case DNSSEC_DIGEST_SHA384: - return EVP_sha384(); + return sym_EVP_sha384(); default: return NULL; @@ -1062,6 +1061,10 @@ int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, assert(dnskey); assert(ds); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + /* Implements DNSKEY verification by a DS, according to RFC 4035, section 5.2 */ if (dnskey->key->type != DNS_TYPE_DNSKEY) @@ -1092,22 +1095,22 @@ int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *ctx = NULL; uint8_t result[EVP_MAX_MD_SIZE]; - unsigned hash_size = EVP_MD_size(md_algorithm); + unsigned hash_size = sym_EVP_MD_get_size(md_algorithm); assert(hash_size > 0); if (ds->ds.digest_size != hash_size) return 0; - ctx = EVP_MD_CTX_new(); + ctx = sym_EVP_MD_CTX_new(); if (!ctx) return -ENOMEM; /* If the digest is supported by systemd-resolved but disabled by host policy, also return -EOPNOTSUPP */ - if (EVP_DigestInit_ex(ctx, md_algorithm, NULL) <= 0) + if (sym_EVP_DigestInit_ex(ctx, md_algorithm, NULL) <= 0) return -EOPNOTSUPP; - if (EVP_DigestUpdate(ctx, wire_format, encoded_length) <= 0) + if (sym_EVP_DigestUpdate(ctx, wire_format, encoded_length) <= 0) return -EIO; if (mask_revoke) @@ -1121,10 +1124,10 @@ int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, r = md_add_uint8(ctx, dnskey->dnskey.algorithm); if (r <= 0) return r; - if (EVP_DigestUpdate(ctx, dnskey->dnskey.key, dnskey->dnskey.key_size) <= 0) + if (sym_EVP_DigestUpdate(ctx, dnskey->dnskey.key, dnskey->dnskey.key_size) <= 0) return -EIO; - if (EVP_DigestFinal_ex(ctx, result, NULL) <= 0) + if (sym_EVP_DigestFinal_ex(ctx, result, NULL) <= 0) return -EIO; return memcmp(result, ds->ds.digest, ds->ds.digest_size) == 0; @@ -1183,7 +1186,7 @@ static const EVP_MD* nsec3_hash_to_hash_md(uint8_t algorithm) { switch (algorithm) { case NSEC3_ALGORITHM_SHA1: - return EVP_sha1(); + return sym_EVP_sha1(); default: return NULL; @@ -1198,6 +1201,10 @@ int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { assert(name); assert(ret); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + if (nsec3->key->type != DNS_TYPE_NSEC3) return -EINVAL; @@ -1210,41 +1217,41 @@ int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { if (!algorithm) return -EOPNOTSUPP; - size_t hash_size = EVP_MD_size(algorithm); + size_t hash_size = sym_EVP_MD_get_size(algorithm); assert(hash_size > 0); if (nsec3->nsec3.next_hashed_name_size != hash_size) return -EINVAL; - _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *ctx = sym_EVP_MD_CTX_new(); if (!ctx) return -ENOMEM; - if (EVP_DigestInit_ex(ctx, algorithm, NULL) <= 0) + if (sym_EVP_DigestInit_ex(ctx, algorithm, NULL) <= 0) return -EOPNOTSUPP; r = dns_name_to_wire_format(name, wire_format, sizeof(wire_format), true); if (r < 0) return r; - if (EVP_DigestUpdate(ctx, wire_format, r) <= 0) + if (sym_EVP_DigestUpdate(ctx, wire_format, r) <= 0) return -EIO; - if (EVP_DigestUpdate(ctx, nsec3->nsec3.salt, nsec3->nsec3.salt_size) <= 0) + if (sym_EVP_DigestUpdate(ctx, nsec3->nsec3.salt, nsec3->nsec3.salt_size) <= 0) return -EIO; uint8_t result[EVP_MAX_MD_SIZE]; - if (EVP_DigestFinal_ex(ctx, result, NULL) <= 0) + if (sym_EVP_DigestFinal_ex(ctx, result, NULL) <= 0) return -EIO; for (unsigned k = 0; k < nsec3->nsec3.iterations; k++) { - if (EVP_DigestInit_ex(ctx, algorithm, NULL) <= 0) + if (sym_EVP_DigestInit_ex(ctx, algorithm, NULL) <= 0) return -EOPNOTSUPP; - if (EVP_DigestUpdate(ctx, result, hash_size) <= 0) + if (sym_EVP_DigestUpdate(ctx, result, hash_size) <= 0) return -EIO; - if (EVP_DigestUpdate(ctx, nsec3->nsec3.salt, nsec3->nsec3.salt_size) <= 0) + if (sym_EVP_DigestUpdate(ctx, nsec3->nsec3.salt, nsec3->nsec3.salt_size) <= 0) return -EIO; - if (EVP_DigestFinal_ex(ctx, result, NULL) <= 0) + if (sym_EVP_DigestFinal_ex(ctx, result, NULL) <= 0) return -EIO; } diff --git a/src/resolve/resolved-dnstls.c b/src/resolve/resolved-dnstls.c index 042077ab066dd..f59c2fa348f67 100644 --- a/src/resolve/resolved-dnstls.c +++ b/src/resolve/resolved-dnstls.c @@ -4,22 +4,21 @@ #error This source file requires DNS-over-TLS to be enabled and OpenSSL to be available. #endif -#include -#include #include #include "alloc-util.h" -#include "openssl-util.h" +#include "crypto-util.h" #include "log.h" #include "resolved-dns-server.h" #include "resolved-dns-stream.h" #include "resolved-dnstls.h" #include "resolved-manager.h" +#include "ssl-util.h" static char *dnstls_error_string(int ssl_error, char *buf, size_t count) { assert(buf || count == 0); if (ssl_error == SSL_ERROR_SSL) - ERR_error_string_n(ERR_get_error(), buf, count); + sym_ERR_error_string_n(sym_ERR_get_error(), buf, count); else snprintf(buf, count, "SSL_get_error()=%d", ssl_error); return buf; @@ -54,7 +53,7 @@ static int dnstls_flush_write_buffer(DnsStream *stream) { stream->dnstls_events |= EPOLLOUT; return -EAGAIN; } else { - BIO_reset(SSL_get_wbio(stream->dnstls_data.ssl)); + sym_BIO_reset(sym_SSL_get_wbio(stream->dnstls_data.ssl)); stream->dnstls_data.buffer_offset = 0; } } @@ -72,55 +71,55 @@ int dnstls_stream_connect_tls(DnsStream *stream, DnsServer *server) { assert(stream->manager); assert(server); - rb = BIO_new_socket(stream->fd, 0); + rb = sym_BIO_new_socket(stream->fd, 0); if (!rb) return -ENOMEM; - wb = BIO_new(BIO_s_mem()); + wb = sym_BIO_new(sym_BIO_s_mem()); if (!wb) return -ENOMEM; - BIO_get_mem_ptr(wb, &stream->dnstls_data.write_buffer); + sym_BIO_get_mem_ptr(wb, &stream->dnstls_data.write_buffer); stream->dnstls_data.buffer_offset = 0; - s = SSL_new(stream->manager->dnstls_data.ctx); + s = sym_SSL_new(stream->manager->dnstls_data.ctx); if (!s) return -ENOMEM; - SSL_set_connect_state(s); - r = SSL_set_session(s, server->dnstls_data.session); + sym_SSL_set_connect_state(s); + r = sym_SSL_set_session(s, server->dnstls_data.session); if (r == 0) return -EIO; - SSL_set_bio(s, TAKE_PTR(rb), TAKE_PTR(wb)); + sym_SSL_set_bio(s, TAKE_PTR(rb), TAKE_PTR(wb)); if (server->manager->dns_over_tls_mode == DNS_OVER_TLS_YES) { X509_VERIFY_PARAM *v; - SSL_set_verify(s, SSL_VERIFY_PEER, NULL); - v = SSL_get0_param(s); + sym_SSL_set_verify(s, SSL_VERIFY_PEER, NULL); + v = sym_SSL_get0_param(s); if (server->server_name) { - X509_VERIFY_PARAM_set_hostflags(v, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); - if (X509_VERIFY_PARAM_set1_host(v, server->server_name, 0) == 0) + sym_X509_VERIFY_PARAM_set_hostflags(v, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); + if (sym_X509_VERIFY_PARAM_set1_host(v, server->server_name, 0) == 0) return -ECONNREFUSED; } else { const unsigned char *ip; ip = server->family == AF_INET ? (const unsigned char*) &server->address.in.s_addr : server->address.in6.s6_addr; - if (X509_VERIFY_PARAM_set1_ip(v, ip, FAMILY_ADDRESS_SIZE(server->family)) == 0) + if (sym_X509_VERIFY_PARAM_set1_ip(v, ip, FAMILY_ADDRESS_SIZE(server->family)) == 0) return -ECONNREFUSED; } } if (server->server_name) { - r = SSL_set_tlsext_host_name(s, server->server_name); + r = sym_SSL_set_tlsext_host_name(s, server->server_name); if (r <= 0) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set server name: %s", DNSTLS_ERROR_STRING(SSL_ERROR_SSL)); } - ERR_clear_error(); - stream->dnstls_data.handshake = SSL_do_handshake(s); + sym_ERR_clear_error(); + stream->dnstls_data.handshake = sym_SSL_do_handshake(s); if (stream->dnstls_data.handshake <= 0) { - error = SSL_get_error(s, stream->dnstls_data.handshake); + error = sym_SSL_get_error(s, stream->dnstls_data.handshake); if (!IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) return log_debug_errno(SYNTHETIC_ERRNO(ECONNREFUSED), "Failed to invoke SSL_do_handshake: %s", DNSTLS_ERROR_STRING(error)); @@ -131,7 +130,7 @@ int dnstls_stream_connect_tls(DnsStream *stream, DnsServer *server) { r = dnstls_flush_write_buffer(stream); if (r < 0 && r != -EAGAIN) { - SSL_free(TAKE_PTR(stream->dnstls_data.ssl)); + sym_SSL_free(TAKE_PTR(stream->dnstls_data.ssl)); return r; } @@ -143,7 +142,7 @@ void dnstls_stream_free(DnsStream *stream) { assert(stream->encrypted); if (stream->dnstls_data.ssl) - SSL_free(stream->dnstls_data.ssl); + sym_SSL_free(stream->dnstls_data.ssl); } int dnstls_stream_on_io(DnsStream *stream, uint32_t revents) { @@ -161,8 +160,8 @@ int dnstls_stream_on_io(DnsStream *stream, uint32_t revents) { } if (stream->dnstls_data.shutdown) { - ERR_clear_error(); - r = SSL_shutdown(stream->dnstls_data.ssl); + sym_ERR_clear_error(); + r = sym_SSL_shutdown(stream->dnstls_data.ssl); if (r == 0) { stream->dnstls_events = 0; @@ -172,7 +171,7 @@ int dnstls_stream_on_io(DnsStream *stream, uint32_t revents) { return -EAGAIN; } else if (r < 0) { - error = SSL_get_error(stream->dnstls_data.ssl, r); + error = sym_SSL_get_error(stream->dnstls_data.ssl, r); if (IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) { stream->dnstls_events = error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT; @@ -198,10 +197,10 @@ int dnstls_stream_on_io(DnsStream *stream, uint32_t revents) { dns_stream_unref(stream); return DNSTLS_STREAM_CLOSED; } else if (stream->dnstls_data.handshake <= 0) { - ERR_clear_error(); - stream->dnstls_data.handshake = SSL_do_handshake(stream->dnstls_data.ssl); + sym_ERR_clear_error(); + stream->dnstls_data.handshake = sym_SSL_do_handshake(stream->dnstls_data.ssl); if (stream->dnstls_data.handshake <= 0) { - error = SSL_get_error(stream->dnstls_data.ssl, stream->dnstls_data.handshake); + error = sym_SSL_get_error(stream->dnstls_data.ssl, stream->dnstls_data.handshake); if (IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) { stream->dnstls_events = error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT; r = dnstls_flush_write_buffer(stream); @@ -233,18 +232,18 @@ int dnstls_stream_shutdown(DnsStream *stream, int error) { assert(stream->dnstls_data.ssl); if (stream->server) { - s = SSL_get1_session(stream->dnstls_data.ssl); + s = sym_SSL_get1_session(stream->dnstls_data.ssl); if (s) { if (stream->server->dnstls_data.session) - SSL_SESSION_free(stream->server->dnstls_data.session); + sym_SSL_SESSION_free(stream->server->dnstls_data.session); stream->server->dnstls_data.session = s; } } if (error == ETIMEDOUT) { - ERR_clear_error(); - r = SSL_shutdown(stream->dnstls_data.ssl); + sym_ERR_clear_error(); + r = sym_SSL_shutdown(stream->dnstls_data.ssl); if (r == 0) { if (!stream->dnstls_data.shutdown) { stream->dnstls_data.shutdown = true; @@ -259,7 +258,7 @@ int dnstls_stream_shutdown(DnsStream *stream, int error) { return -EAGAIN; } else if (r < 0) { - ssl_error = SSL_get_error(stream->dnstls_data.ssl, r); + ssl_error = sym_SSL_get_error(stream->dnstls_data.ssl, r); if (IN_SET(ssl_error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) { stream->dnstls_events = ssl_error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT; r = dnstls_flush_write_buffer(stream); @@ -291,10 +290,10 @@ static ssize_t dnstls_stream_write(DnsStream *stream, const char *buf, size_t co int error, r; ssize_t ss; - ERR_clear_error(); - ss = r = SSL_write(stream->dnstls_data.ssl, buf, count); + sym_ERR_clear_error(); + ss = r = sym_SSL_write(stream->dnstls_data.ssl, buf, count); if (r <= 0) { - error = SSL_get_error(stream->dnstls_data.ssl, r); + error = sym_SSL_get_error(stream->dnstls_data.ssl, r); if (IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) { stream->dnstls_events = error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT; ss = -EAGAIN; @@ -352,10 +351,10 @@ ssize_t dnstls_stream_read(DnsStream *stream, void *buf, size_t count) { assert(stream->dnstls_data.ssl); assert(buf); - ERR_clear_error(); - ss = r = SSL_read(stream->dnstls_data.ssl, buf, count); + sym_ERR_clear_error(); + ss = r = sym_SSL_read(stream->dnstls_data.ssl, buf, count); if (r <= 0) { - error = SSL_get_error(stream->dnstls_data.ssl, r); + error = sym_SSL_get_error(stream->dnstls_data.ssl, r); if (IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) { /* If we receive SSL_ERROR_WANT_READ here, there are two possible scenarios: * OpenSSL needs to renegotiate (so we want to get an EPOLLIN event), or @@ -390,7 +389,7 @@ void dnstls_server_free(DnsServer *server) { assert(server); if (server->dnstls_data.session) - SSL_SESSION_free(server->dnstls_data.session); + sym_SSL_SESSION_free(server->dnstls_data.session); } int dnstls_manager_init(Manager *manager) { @@ -398,25 +397,33 @@ int dnstls_manager_init(Manager *manager) { assert(manager); - manager->dnstls_data.ctx = SSL_CTX_new(TLS_client_method()); + r = dlopen_libcrypto(LOG_WARNING); + if (r < 0) + return r; + + r = dlopen_libssl(LOG_WARNING); + if (r < 0) + return r; + + manager->dnstls_data.ctx = sym_SSL_CTX_new(sym_TLS_client_method()); if (!manager->dnstls_data.ctx) return log_warning_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to create SSL context: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - r = SSL_CTX_set_min_proto_version(manager->dnstls_data.ctx, TLS1_2_VERSION); + r = sym_SSL_CTX_set_min_proto_version(manager->dnstls_data.ctx, TLS1_2_VERSION); if (r == 0) return log_warning_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to set protocol version on SSL context: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - (void) SSL_CTX_set_options(manager->dnstls_data.ctx, SSL_OP_NO_COMPRESSION); + (void) sym_SSL_CTX_set_options(manager->dnstls_data.ctx, SSL_OP_NO_COMPRESSION); - r = SSL_CTX_set_default_verify_paths(manager->dnstls_data.ctx); + r = sym_SSL_CTX_set_default_verify_paths(manager->dnstls_data.ctx); if (r == 0) return log_warning_errno(SYNTHETIC_ERRNO(EIO), "Failed to load system trust store: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); return 0; } @@ -424,5 +431,5 @@ void dnstls_manager_free(Manager *manager) { assert(manager); if (manager->dnstls_data.ctx) - SSL_CTX_free(manager->dnstls_data.ctx); + sym_SSL_CTX_free(manager->dnstls_data.ctx); } diff --git a/src/resolve/resolved-dnstls.h b/src/resolve/resolved-dnstls.h index 35a8d785defa1..b87b84a0d5cad 100644 --- a/src/resolve/resolved-dnstls.h +++ b/src/resolve/resolved-dnstls.h @@ -7,8 +7,6 @@ #error This source file requires OpenSSL to be available. #endif -#include - #include "resolved-forward.h" typedef struct DnsTlsManagerData { diff --git a/src/sbsign/authenticode.c b/src/sbsign/authenticode.c new file mode 100644 index 0000000000000..9971cf9f26f04 --- /dev/null +++ b/src/sbsign/authenticode.c @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "authenticode.h" +#include "crypto-util.h" + +/* OpenSSL's ASN1_SEQUENCE/ASN1_CHOICE/IMPLEMENT_ASN1_FUNCTIONS macros expand to code that references + * libcrypto symbols directly, which would force us to link this object against libcrypto and defeat the + * dlopen approach used everywhere else. We work around that in two different ways depending on where the + * reference appears in the macro expansion: + * + * - ASN1_item_new/ASN1_item_free/ASN1_item_d2i/ASN1_item_i2d are called from the bodies of the functions + * generated by IMPLEMENT_ASN1_FUNCTIONS. We can simply #define them to the matching sym_* variants, so + * the generated bodies end up calling our dlopen'd pointers. + * + * - ASN1_ANY_it/ASN1_BIT_STRING_it/... (the "_it" getters) are embedded as function pointers in the + * static const ASN1_ADB / ASN1_TEMPLATE tables emitted by the ASN1_SEQUENCE/ASN1_CHOICE macros. Static + * initializers can only contain constant expressions, so we can't point them at the sym_* variables + * (which are non-const globals filled in at dlopen time). Instead we define static trampoline + * functions (openssl_ASN1_*_it) whose addresses are constant, each forwarding to the corresponding + * sym_* pointer at runtime, and #define the OpenSSL names to our trampolines before the tables are + * emitted. + * + * Note that this file must only call these redefined macros indirectly, via the IMPLEMENT_ASN1_FUNCTIONS + * expansions, and callers of those generated wrappers (e.g. sbsign.c) must have dlopen_libcrypto() + * before invoking them, otherwise the sym_* pointers will still be NULL. */ + +static const ASN1_ITEM* openssl_ASN1_ANY_it(void) { + assert(sym_ASN1_ANY_it); + return sym_ASN1_ANY_it(); +} + +static const ASN1_ITEM* openssl_ASN1_BIT_STRING_it(void) { + assert(sym_ASN1_BIT_STRING_it); + return sym_ASN1_BIT_STRING_it(); +} + +static const ASN1_ITEM* openssl_ASN1_BMPSTRING_it(void) { + assert(sym_ASN1_BMPSTRING_it); + return sym_ASN1_BMPSTRING_it(); +} + +static const ASN1_ITEM* openssl_ASN1_IA5STRING_it(void) { + assert(sym_ASN1_IA5STRING_it); + return sym_ASN1_IA5STRING_it(); +} + +static const ASN1_ITEM* openssl_ASN1_OBJECT_it(void) { + assert(sym_ASN1_OBJECT_it); + return sym_ASN1_OBJECT_it(); +} + +static const ASN1_ITEM* openssl_ASN1_OCTET_STRING_it(void) { + assert(sym_ASN1_OCTET_STRING_it); + return sym_ASN1_OCTET_STRING_it(); +} + +#define ASN1_item_new sym_ASN1_item_new +#define ASN1_item_free sym_ASN1_item_free +#define ASN1_item_d2i sym_ASN1_item_d2i +#define ASN1_item_i2d sym_ASN1_item_i2d +#define ASN1_ANY_it openssl_ASN1_ANY_it +#define ASN1_BIT_STRING_it openssl_ASN1_BIT_STRING_it +#define ASN1_BMPSTRING_it openssl_ASN1_BMPSTRING_it +#define ASN1_IA5STRING_it openssl_ASN1_IA5STRING_it +#define ASN1_OBJECT_it openssl_ASN1_OBJECT_it +#define ASN1_OCTET_STRING_it openssl_ASN1_OCTET_STRING_it + +ASN1_SEQUENCE(SpcAttributeTypeAndOptionalValue) = { + ASN1_SIMPLE(SpcAttributeTypeAndOptionalValue, type, ASN1_OBJECT), + ASN1_OPT(SpcAttributeTypeAndOptionalValue, value, ASN1_ANY) +} ASN1_SEQUENCE_END(SpcAttributeTypeAndOptionalValue); + +IMPLEMENT_ASN1_FUNCTIONS(SpcAttributeTypeAndOptionalValue); + +ASN1_SEQUENCE(AlgorithmIdentifier) = { + ASN1_SIMPLE(AlgorithmIdentifier, algorithm, ASN1_OBJECT), + ASN1_OPT(AlgorithmIdentifier, parameters, ASN1_ANY) +} ASN1_SEQUENCE_END(AlgorithmIdentifier) + +IMPLEMENT_ASN1_FUNCTIONS(AlgorithmIdentifier); + +ASN1_SEQUENCE(DigestInfo) = { + ASN1_SIMPLE(DigestInfo, digestAlgorithm, AlgorithmIdentifier), + ASN1_SIMPLE(DigestInfo, digest, ASN1_OCTET_STRING) +} ASN1_SEQUENCE_END(DigestInfo); + +IMPLEMENT_ASN1_FUNCTIONS(DigestInfo); + +ASN1_SEQUENCE(SpcIndirectDataContent) = { + ASN1_SIMPLE(SpcIndirectDataContent, data, SpcAttributeTypeAndOptionalValue), + ASN1_SIMPLE(SpcIndirectDataContent, messageDigest, DigestInfo) +} ASN1_SEQUENCE_END(SpcIndirectDataContent); + +IMPLEMENT_ASN1_FUNCTIONS(SpcIndirectDataContent); + +ASN1_CHOICE(SpcString) = { + ASN1_IMP_OPT(SpcString, value.unicode, ASN1_BMPSTRING, 0), + ASN1_IMP_OPT(SpcString, value.ascii, ASN1_IA5STRING, 1) +} ASN1_CHOICE_END(SpcString); + +IMPLEMENT_ASN1_FUNCTIONS(SpcString); + +ASN1_SEQUENCE(SpcSerializedObject) = { + ASN1_SIMPLE(SpcSerializedObject, classId, ASN1_OCTET_STRING), + ASN1_SIMPLE(SpcSerializedObject, serializedData, ASN1_OCTET_STRING) +} ASN1_SEQUENCE_END(SpcSerializedObject); + +IMPLEMENT_ASN1_FUNCTIONS(SpcSerializedObject); + +ASN1_CHOICE(SpcLink) = { + ASN1_IMP_OPT(SpcLink, value.url, ASN1_IA5STRING, 0), + ASN1_IMP_OPT(SpcLink, value.moniker, SpcSerializedObject, 1), + ASN1_EXP_OPT(SpcLink, value.file, SpcString, 2) +} ASN1_CHOICE_END(SpcLink); + +IMPLEMENT_ASN1_FUNCTIONS(SpcLink); + +ASN1_SEQUENCE(SpcPeImageData) = { + ASN1_SIMPLE(SpcPeImageData, flags, ASN1_BIT_STRING), + ASN1_EXP_OPT(SpcPeImageData, file, SpcLink, 0) +} ASN1_SEQUENCE_END(SpcPeImageData) + +IMPLEMENT_ASN1_FUNCTIONS(SpcPeImageData); diff --git a/src/sbsign/authenticode.h b/src/sbsign/authenticode.h index 95d5499a8e061..c076fe36241d8 100644 --- a/src/sbsign/authenticode.h +++ b/src/sbsign/authenticode.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include +#include #include "shared-forward.h" @@ -15,13 +15,6 @@ typedef struct { DECLARE_ASN1_FUNCTIONS(SpcAttributeTypeAndOptionalValue); -ASN1_SEQUENCE(SpcAttributeTypeAndOptionalValue) = { - ASN1_SIMPLE(SpcAttributeTypeAndOptionalValue, type, ASN1_OBJECT), - ASN1_OPT(SpcAttributeTypeAndOptionalValue, value, ASN1_ANY) -} ASN1_SEQUENCE_END(SpcAttributeTypeAndOptionalValue); - -IMPLEMENT_ASN1_FUNCTIONS(SpcAttributeTypeAndOptionalValue); - typedef struct { ASN1_OBJECT *algorithm; ASN1_TYPE *parameters; @@ -29,13 +22,6 @@ typedef struct { DECLARE_ASN1_FUNCTIONS(AlgorithmIdentifier); -ASN1_SEQUENCE(AlgorithmIdentifier) = { - ASN1_SIMPLE(AlgorithmIdentifier, algorithm, ASN1_OBJECT), - ASN1_OPT(AlgorithmIdentifier, parameters, ASN1_ANY) -} ASN1_SEQUENCE_END(AlgorithmIdentifier) - -IMPLEMENT_ASN1_FUNCTIONS(AlgorithmIdentifier); - typedef struct { AlgorithmIdentifier *digestAlgorithm; ASN1_OCTET_STRING *digest; @@ -43,13 +29,6 @@ typedef struct { DECLARE_ASN1_FUNCTIONS(DigestInfo); -ASN1_SEQUENCE(DigestInfo) = { - ASN1_SIMPLE(DigestInfo, digestAlgorithm, AlgorithmIdentifier), - ASN1_SIMPLE(DigestInfo, digest, ASN1_OCTET_STRING) -} ASN1_SEQUENCE_END(DigestInfo); - -IMPLEMENT_ASN1_FUNCTIONS(DigestInfo); - typedef struct { SpcAttributeTypeAndOptionalValue *data; DigestInfo *messageDigest; @@ -57,15 +36,6 @@ typedef struct { DECLARE_ASN1_FUNCTIONS(SpcIndirectDataContent); -ASN1_SEQUENCE(SpcIndirectDataContent) = { - ASN1_SIMPLE(SpcIndirectDataContent, data, SpcAttributeTypeAndOptionalValue), - ASN1_SIMPLE(SpcIndirectDataContent, messageDigest, DigestInfo) -} ASN1_SEQUENCE_END(SpcIndirectDataContent); - -IMPLEMENT_ASN1_FUNCTIONS(SpcIndirectDataContent); - -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcIndirectDataContent*, SpcIndirectDataContent_free, NULL); - typedef struct { int type; union { @@ -76,13 +46,6 @@ typedef struct { DECLARE_ASN1_FUNCTIONS(SpcString); -ASN1_CHOICE(SpcString) = { - ASN1_IMP_OPT(SpcString, value.unicode, ASN1_BMPSTRING, 0), - ASN1_IMP_OPT(SpcString, value.ascii, ASN1_IA5STRING, 1) -} ASN1_CHOICE_END(SpcString); - -IMPLEMENT_ASN1_FUNCTIONS(SpcString); - typedef struct { ASN1_OCTET_STRING *classId; ASN1_OCTET_STRING *serializedData; @@ -90,13 +53,6 @@ typedef struct { DECLARE_ASN1_FUNCTIONS(SpcSerializedObject); -ASN1_SEQUENCE(SpcSerializedObject) = { - ASN1_SIMPLE(SpcSerializedObject, classId, ASN1_OCTET_STRING), - ASN1_SIMPLE(SpcSerializedObject, serializedData, ASN1_OCTET_STRING) -} ASN1_SEQUENCE_END(SpcSerializedObject); - -IMPLEMENT_ASN1_FUNCTIONS(SpcSerializedObject); - typedef struct { int type; union { @@ -108,16 +64,6 @@ typedef struct { DECLARE_ASN1_FUNCTIONS(SpcLink); -ASN1_CHOICE(SpcLink) = { - ASN1_IMP_OPT(SpcLink, value.url, ASN1_IA5STRING, 0), - ASN1_IMP_OPT(SpcLink, value.moniker, SpcSerializedObject, 1), - ASN1_EXP_OPT(SpcLink, value.file, SpcString, 2) -} ASN1_CHOICE_END(SpcLink); - -IMPLEMENT_ASN1_FUNCTIONS(SpcLink); - -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcLink*, SpcLink_free, NULL); - typedef struct { ASN1_BIT_STRING *flags; SpcLink *file; @@ -125,11 +71,6 @@ typedef struct { DECLARE_ASN1_FUNCTIONS(SpcPeImageData); -ASN1_SEQUENCE(SpcPeImageData) = { - ASN1_SIMPLE(SpcPeImageData, flags, ASN1_BIT_STRING), - ASN1_EXP_OPT(SpcPeImageData, file, SpcLink, 0) -} ASN1_SEQUENCE_END(SpcPeImageData) - -IMPLEMENT_ASN1_FUNCTIONS(SpcPeImageData); - +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcIndirectDataContent*, SpcIndirectDataContent_free, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcLink*, SpcLink_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcPeImageData*, SpcPeImageData_free, NULL); diff --git a/src/sbsign/meson.build b/src/sbsign/meson.build index b6e0dbcde9c03..f28d4648f94a0 100644 --- a/src/sbsign/meson.build +++ b/src/sbsign/meson.build @@ -6,7 +6,10 @@ executables += [ 'conditions' : [ 'HAVE_OPENSSL', ], - 'sources' : files('sbsign.c'), - 'dependencies' : libopenssl, + 'sources' : files( + 'sbsign.c', + 'authenticode.c', + ), + 'dependencies' : libopenssl_cflags, }, ] diff --git a/src/sbsign/sbsign.c b/src/sbsign/sbsign.c index a8c554ffcfb50..012f39dcc3094 100644 --- a/src/sbsign/sbsign.c +++ b/src/sbsign/sbsign.c @@ -4,9 +4,11 @@ #include "alloc-util.h" #include "ansi-color.h" +#include "ask-password-api.h" #include "authenticode.h" #include "build.h" #include "copy.h" +#include "crypto-util.h" #include "efi-fundamental.h" #include "fd-util.h" #include "fileio.h" @@ -16,7 +18,6 @@ #include "io-util.h" #include "log.h" #include "main-func.h" -#include "openssl-util.h" #include "options.h" #include "parse-argument.h" #include "pe-binary.h" @@ -203,13 +204,13 @@ static int spc_indirect_data_content_new(const void *digest, size_t digestsz, ui return log_oom(); link->value.file->type = 0; - link->value.file->value.unicode = ASN1_BMPSTRING_new(); + link->value.file->value.unicode = sym_ASN1_BMPSTRING_new(); if (!link->value.file->value.unicode) return log_oom(); - if (ASN1_STRING_set(link->value.file->value.unicode, obsolete, sizeof(obsolete)) == 0) + if (sym_ASN1_STRING_set(link->value.file->value.unicode, obsolete, sizeof(obsolete)) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ASN1 string: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); _cleanup_(SpcPeImageData_freep) SpcPeImageData *peid = SpcPeImageData_new(); if (!peid) @@ -221,46 +222,46 @@ static int spc_indirect_data_content_new(const void *digest, size_t digestsz, ui int peidrawsz = i2d_SpcPeImageData(peid, &peidraw); if (peidrawsz < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert SpcPeImageData to BER: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); _cleanup_(SpcIndirectDataContent_freep) SpcIndirectDataContent *idc = SpcIndirectDataContent_new(); - idc->data->value = ASN1_TYPE_new(); + idc->data->value = sym_ASN1_TYPE_new(); if (!idc->data->value) return log_oom(); idc->data->value->type = V_ASN1_SEQUENCE; - idc->data->value->value.sequence = ASN1_STRING_new(); + idc->data->value->value.sequence = sym_ASN1_STRING_new(); if (!idc->data->value->value.sequence) return log_oom(); - idc->data->type = OBJ_txt2obj(SPC_PE_IMAGE_DATA_OBJID, /* no_name= */ 1); + idc->data->type = sym_OBJ_txt2obj(SPC_PE_IMAGE_DATA_OBJID, /* no_name= */ 1); if (!idc->data->type) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get SpcPeImageData object: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (!ASN1_STRING_set(idc->data->value->value.sequence, peidraw, peidrawsz)) + if (!sym_ASN1_STRING_set(idc->data->value->value.sequence, peidraw, peidrawsz)) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ASN1_STRING data."); - idc->messageDigest->digestAlgorithm->algorithm = OBJ_nid2obj(NID_sha256); + idc->messageDigest->digestAlgorithm->algorithm = sym_OBJ_nid2obj(NID_sha256); if (!idc->messageDigest->digestAlgorithm->algorithm) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get SHA256 object: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - idc->messageDigest->digestAlgorithm->parameters = ASN1_TYPE_new(); + idc->messageDigest->digestAlgorithm->parameters = sym_ASN1_TYPE_new(); if (!idc->messageDigest->digestAlgorithm->parameters) return log_oom(); idc->messageDigest->digestAlgorithm->parameters->type = V_ASN1_NULL; - if (ASN1_OCTET_STRING_set(idc->messageDigest->digest, digest, digestsz) == 0) + if (sym_ASN1_OCTET_STRING_set(idc->messageDigest->digest, digest, digestsz) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set digest: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); _cleanup_free_ uint8_t *idcraw = NULL; int idcrawsz = i2d_SpcIndirectDataContent(idc, &idcraw); if (idcrawsz < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert SpcIndirectDataContent to BER: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); *ret_idc = TAKE_PTR(idcraw); *ret_idcsz = (size_t) idcrawsz; @@ -276,12 +277,12 @@ static int asn1_timestamp(ASN1_TIME **ret) { usec_t epoch = parse_source_date_epoch(); if (epoch == USEC_INFINITY) { - time = X509_gmtime_adj(NULL, 0); + time = sym_X509_gmtime_adj(NULL, 0); if (!time) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get current time: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); } else { - time = ASN1_TIME_set(NULL, (time_t) (epoch / USEC_PER_SEC)); + time = sym_ASN1_TIME_set(NULL, (time_t) (epoch / USEC_PER_SEC)); if (!time) return log_oom(); } @@ -323,37 +324,37 @@ static int pkcs7_new_with_attributes( } /* Add an empty SMIMECAP attribute to indicate we don't have any SMIME capabilities. */ - _cleanup_(x509_algor_free_manyp) STACK_OF(X509_ALGOR) *smcap = sk_X509_ALGOR_new_null(); + _cleanup_(x509_algor_free_manyp) STACK_OF(X509_ALGOR) *smcap = (STACK_OF(X509_ALGOR)*) sym_OPENSSL_sk_new_null(); if (!smcap) return log_oom(); - if (PKCS7_add_attrib_smimecap(si, smcap) == 0) + if (sym_PKCS7_add_attrib_smimecap(si, smcap) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add smimecap signed attribute to signer info: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (PKCS7_add_attrib_content_type(si, NULL) == 0) + if (sym_PKCS7_add_attrib_content_type(si, NULL) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add content type signed attribute to signer info: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); _cleanup_(ASN1_TIME_freep) ASN1_TIME *time = NULL; r = asn1_timestamp(&time); if (r < 0) return r; - if (PKCS7_add0_attrib_signing_time(si, time) == 0) + if (sym_PKCS7_add0_attrib_signing_time(si, time) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add signing time signed attribute to signer info: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); TAKE_PTR(time); - ASN1_OBJECT *idc = OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, /* no_name= */ true); + ASN1_OBJECT *idc = sym_OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, /* no_name= */ true); if (!idc) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get SpcIndirectDataContent object: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (PKCS7_add_signed_attribute(si, NID_pkcs9_contentType, V_ASN1_OBJECT, idc) == 0) + if (sym_PKCS7_add_signed_attribute(si, NID_pkcs9_contentType, V_ASN1_OBJECT, idc) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add signed attribute to pkcs7 signer info: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); *ret_p7 = TAKE_PTR(p7); *ret_si = TAKE_PTR(si); @@ -363,23 +364,23 @@ static int pkcs7_new_with_attributes( static int pkcs7_populate_data_bio(PKCS7* p7, const void *data, size_t size, BIO **ret) { assert(ret); - _cleanup_(BIO_free_allp) BIO *bio = PKCS7_dataInit(p7, NULL); + _cleanup_(BIO_free_allp) BIO *bio = sym_PKCS7_dataInit(p7, NULL); if (!bio) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to create PKCS7 data bio: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); int tag, class; long psz; const uint8_t *p = data; /* This function weirdly enough reports errors by setting the 0x80 bit in its return value. */ - if (ASN1_get_object(&p, &psz, &tag, &class, size) & 0x80) + if (sym_ASN1_get_object(&p, &psz, &tag, &class, size) & 0x80) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse ASN.1 object: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (BIO_write(bio, p, psz) < 0) + if (sym_BIO_write(bio, p, psz) < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write to PKCS7 data bio: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); *ret = TAKE_PTR(bio); @@ -391,26 +392,26 @@ static int pkcs7_add_digest_attribute(PKCS7 *p7, BIO *data, PKCS7_SIGNER_INFO *s assert(data); assert(si); - BIO *mdbio = BIO_find_type(data, BIO_TYPE_MD); + BIO *mdbio = sym_BIO_find_type(data, BIO_TYPE_MD); if (!mdbio) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to find digest bio: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); EVP_MD_CTX *mdc; - if (BIO_get_md_ctx(mdbio, &mdc) <= 0) + if (sym_BIO_get_md_ctx(mdbio, &mdc) <= 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get digest context from bio: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); unsigned char digest[EVP_MAX_MD_SIZE]; unsigned digestsz; - if (EVP_DigestFinal_ex(mdc, digest, &digestsz) == 0) + if (sym_EVP_DigestFinal_ex(mdc, digest, &digestsz) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get digest: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (PKCS7_add1_attrib_digest(si, digest, digestsz) == 0) + if (sym_PKCS7_add1_attrib_digest(si, digest, digestsz) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add PKCS9 message digest signed attribute to signer info: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); return 0; } @@ -425,6 +426,10 @@ static int verb_sign(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(iovec_done) struct iovec signed_attributes_signature = {}; int r; + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + if (argc < 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No input file specified"); @@ -487,9 +492,9 @@ static int verb_sign(int argc, char *argv[], uintptr_t _data, void *userdata) { return log_error_errno(r, "Failed to read signed attributes file '%s': %m", arg_signed_data); const uint8_t *p = content; - if (!ASN1_item_d2i((ASN1_VALUE **) &signed_attributes, &p, contentsz, ASN1_ITEM_rptr(PKCS7_ATTR_SIGN))) + if (!sym_ASN1_item_d2i((ASN1_VALUE **) &signed_attributes, &p, contentsz, sym_PKCS7_ATTR_SIGN_it())) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse signed attributes: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); } if (arg_signed_data_signature) { @@ -526,7 +531,7 @@ static int verb_sign(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_free_ void *pehash = NULL; size_t pehashsz; - r = pe_hash(srcfd, EVP_sha256(), &pehash, &pehashsz); + r = pe_hash(srcfd, sym_EVP_sha256(), &pehash, &pehashsz); if (r < 0) return log_error_errno(r, "Failed to hash PE binary %s: %m", argv[0]); @@ -555,10 +560,10 @@ static int verb_sign(int argc, char *argv[], uintptr_t _data, void *userdata) { return r; _cleanup_free_ unsigned char *abuf = NULL; - int alen = ASN1_item_i2d((ASN1_VALUE *)si->auth_attr, &abuf, ASN1_ITEM_rptr(PKCS7_ATTR_SIGN)); + int alen = sym_ASN1_item_i2d((ASN1_VALUE *)si->auth_attr, &abuf, sym_PKCS7_ATTR_SIGN_it()); if (alen < 0 || !abuf) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert signed attributes ASN.1 to DER: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); r = loop_write(dstfd, abuf, alen); if (r < 0) @@ -573,51 +578,51 @@ static int verb_sign(int argc, char *argv[], uintptr_t _data, void *userdata) { } if (iovec_is_set(&signed_attributes_signature)) - ASN1_STRING_set0(si->enc_digest, TAKE_PTR(signed_attributes_signature.iov_base), signed_attributes_signature.iov_len); + sym_ASN1_STRING_set0(si->enc_digest, TAKE_PTR(signed_attributes_signature.iov_base), signed_attributes_signature.iov_len); else { _cleanup_(BIO_free_allp) BIO *bio = NULL; r = pkcs7_populate_data_bio(p7, idcraw, idcrawsz, &bio); if (r < 0) return r; - if (PKCS7_dataFinal(p7, bio) == 0) + if (sym_PKCS7_dataFinal(p7, bio) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to sign data: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); } - _cleanup_(PKCS7_freep) PKCS7 *p7c = PKCS7_new(); + _cleanup_(PKCS7_freep) PKCS7 *p7c = sym_PKCS7_new(); if (!p7c) return log_oom(); - p7c->type = OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, /* no_name= */ true); + p7c->type = sym_OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, /* no_name= */ true); if (!p7c->type) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get SpcIndirectDataContent object: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - p7c->d.other = ASN1_TYPE_new(); + p7c->d.other = sym_ASN1_TYPE_new(); if (!p7c->d.other) return log_oom(); p7c->d.other->type = V_ASN1_SEQUENCE; - p7c->d.other->value.sequence = ASN1_STRING_new(); + p7c->d.other->value.sequence = sym_ASN1_STRING_new(); if (!p7c->d.other->value.sequence) return log_oom(); - if (ASN1_STRING_set(p7c->d.other->value.sequence, idcraw, idcrawsz) == 0) + if (sym_ASN1_STRING_set(p7c->d.other->value.sequence, idcraw, idcrawsz) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ASN1 string: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (PKCS7_set_content(p7, p7c) == 0) + if (sym_PKCS7_set_content(p7, p7c) == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS7 data: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); TAKE_PTR(p7c); _cleanup_free_ uint8_t *sig = NULL; - int sigsz = i2d_PKCS7(p7, &sig); + int sigsz = sym_i2d_PKCS7(p7, &sig); if (sigsz < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert PKCS7 signature to DER: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL; _cleanup_free_ PeHeader *pe_header = NULL; diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index d3383aebb6faf..dd2a93844dbdd 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -6,10 +6,6 @@ #include "efivars.h" #include "time-util.h" -#if HAVE_OPENSSL -#include -#endif - #include "sd-id128.h" #include "sd-json.h" #include "sd-varlink.h" @@ -19,6 +15,7 @@ #include "chattr-util.h" #include "copy.h" #include "creds-util.h" +#include "crypto-util.h" #include "efi-api.h" #include "env-util.h" #include "errno-util.h" @@ -32,7 +29,6 @@ #include "log.h" #include "memory-util.h" #include "mkdir-label.h" -#include "openssl-util.h" #include "parse-util.h" #include "path-util.h" #include "random-util.h" @@ -734,6 +730,7 @@ static int sha256_hash_host_and_tpm2_key( _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *md = NULL; unsigned l; + int r; assert(iovec_is_valid(host_key)); assert(iovec_is_valid(tpm2_key)); @@ -741,22 +738,26 @@ static int sha256_hash_host_and_tpm2_key( /* Combines the host key and the TPM2 HMAC hash into a SHA256 hash value we'll use as symmetric encryption key. */ - md = EVP_MD_CTX_new(); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + md = sym_EVP_MD_CTX_new(); if (!md) return log_oom(); - if (EVP_DigestInit_ex(md, EVP_sha256(), NULL) != 1) + if (sym_EVP_DigestInit_ex(md, sym_EVP_sha256(), NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initial SHA256 context."); - if (iovec_is_set(host_key) && EVP_DigestUpdate(md, host_key->iov_base, host_key->iov_len) != 1) + if (iovec_is_set(host_key) && sym_EVP_DigestUpdate(md, host_key->iov_base, host_key->iov_len) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash host key."); - if (iovec_is_set(tpm2_key) && EVP_DigestUpdate(md, tpm2_key->iov_base, tpm2_key->iov_len) != 1) + if (iovec_is_set(tpm2_key) && sym_EVP_DigestUpdate(md, tpm2_key->iov_base, tpm2_key->iov_len) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash TPM2 key."); - assert(EVP_MD_CTX_size(md) == SHA256_DIGEST_LENGTH); + assert(sym_EVP_MD_CTX_get_size(md) == SHA256_DIGEST_LENGTH); - if (EVP_DigestFinal_ex(md, ret, &l) != 1) + if (sym_EVP_DigestFinal_ex(md, ret, &l) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize SHA256 hash."); assert(l == SHA256_DIGEST_LENGTH); @@ -1039,16 +1040,20 @@ int encrypt_credential_and_warn( return r; } - assert_se(cc = EVP_aes_256_gcm()); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + assert_se(cc = sym_EVP_aes_256_gcm()); - ksz = EVP_CIPHER_key_length(cc); + ksz = sym_EVP_CIPHER_get_key_length(cc); assert(ksz == sizeof(md)); - bsz = EVP_CIPHER_block_size(cc); + bsz = sym_EVP_CIPHER_get_block_size(cc); assert(bsz > 0); assert((size_t) bsz <= CREDENTIAL_FIELD_SIZE_MAX); - ivsz = EVP_CIPHER_iv_length(cc); + ivsz = sym_EVP_CIPHER_get_iv_length(cc); if (ivsz > 0) { assert((size_t) ivsz <= CREDENTIAL_FIELD_SIZE_MAX); @@ -1059,14 +1064,14 @@ int encrypt_credential_and_warn( tsz = 16; /* FIXME: On OpenSSL 3 there is EVP_CIPHER_CTX_get_tag_length(), until then let's hardcode this */ - context = EVP_CIPHER_CTX_new(); + context = sym_EVP_CIPHER_CTX_new(); if (!context) return log_error_errno(SYNTHETIC_ERRNO(ENOMEM), "Failed to allocate encryption object: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (EVP_EncryptInit_ex(context, cc, NULL, md, iv.iov_base) != 1) + if (sym_EVP_EncryptInit_ex(context, cc, NULL, md, iv.iov_base) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize encryption context: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); /* Just an upper estimate */ output.iov_len = @@ -1131,9 +1136,9 @@ int encrypt_credential_and_warn( } /* Pass the encrypted + TPM2 header + scoped header as AAD */ - if (EVP_EncryptUpdate(context, NULL, &added, output.iov_base, p) != 1) + if (sym_EVP_EncryptUpdate(context, NULL, &added, output.iov_base, p) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to write AAD data: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); /* Now construct the metadata header */ ml = strlen_ptr(name); @@ -1147,27 +1152,27 @@ int encrypt_credential_and_warn( memcpy_safe(m->name, name, ml); /* And encrypt the metadata header */ - if (EVP_EncryptUpdate(context, (uint8_t*) output.iov_base + p, &added, (const unsigned char*) m, ALIGN8(offsetof(struct metadata_credential_header, name) + ml)) != 1) + if (sym_EVP_EncryptUpdate(context, (uint8_t*) output.iov_base + p, &added, (const unsigned char*) m, ALIGN8(offsetof(struct metadata_credential_header, name) + ml)) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt metadata header: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); assert(added >= 0); assert((size_t) added <= output.iov_len - p); p += added; /* Then encrypt the plaintext */ - if (EVP_EncryptUpdate(context, (uint8_t*) output.iov_base + p, &added, input->iov_base, input->iov_len) != 1) + if (sym_EVP_EncryptUpdate(context, (uint8_t*) output.iov_base + p, &added, input->iov_base, input->iov_len) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt data: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); assert(added >= 0); assert((size_t) added <= output.iov_len - p); p += added; /* Finalize */ - if (EVP_EncryptFinal_ex(context, (uint8_t*) output.iov_base + p, &added) != 1) + if (sym_EVP_EncryptFinal_ex(context, (uint8_t*) output.iov_base + p, &added) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize data encryption: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); assert(added >= 0); assert((size_t) added <= output.iov_len - p); @@ -1176,9 +1181,9 @@ int encrypt_credential_and_warn( assert(p <= output.iov_len - tsz); /* Append tag */ - if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, tsz, (uint8_t*) output.iov_base + p) != 1) + if (sym_EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, tsz, (uint8_t*) output.iov_base + p) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get tag: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); p += tsz; assert(p <= output.iov_len); @@ -1435,59 +1440,63 @@ int decrypt_credential_and_warn( return r; } - assert_se(cc = EVP_aes_256_gcm()); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + assert_se(cc = sym_EVP_aes_256_gcm()); /* Make sure cipher expectations match the header */ - if (EVP_CIPHER_key_length(cc) != (int) le32toh(h->key_size)) + if (sym_EVP_CIPHER_get_key_length(cc) != (int) le32toh(h->key_size)) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected key size in header."); - if (EVP_CIPHER_block_size(cc) != (int) le32toh(h->block_size)) + if (sym_EVP_CIPHER_get_block_size(cc) != (int) le32toh(h->block_size)) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected block size in header."); - context = EVP_CIPHER_CTX_new(); + context = sym_EVP_CIPHER_CTX_new(); if (!context) return log_error_errno(SYNTHETIC_ERRNO(ENOMEM), "Failed to allocate decryption object: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (EVP_DecryptInit_ex(context, cc, NULL, NULL, NULL) != 1) + if (sym_EVP_DecryptInit_ex(context, cc, NULL, NULL, NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize decryption context: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, le32toh(h->iv_size), NULL) != 1) + if (sym_EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, le32toh(h->iv_size), NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set IV size on decryption context: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (EVP_DecryptInit_ex(context, NULL, NULL, md, h->iv) != 1) + if (sym_EVP_DecryptInit_ex(context, NULL, NULL, md, h->iv) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set IV and key: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (EVP_DecryptUpdate(context, NULL, &added, input->iov_base, p) != 1) + if (sym_EVP_DecryptUpdate(context, NULL, &added, input->iov_base, p) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to write AAD data: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); plaintext.iov_base = malloc(input->iov_len - p - le32toh(h->tag_size)); if (!plaintext.iov_base) return -ENOMEM; - if (EVP_DecryptUpdate( + if (sym_EVP_DecryptUpdate( context, plaintext.iov_base, &added, (uint8_t*) input->iov_base + p, input->iov_len - p - le32toh(h->tag_size)) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decrypt data: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); assert(added >= 0); assert((size_t) added <= input->iov_len - p - le32toh(h->tag_size)); plaintext.iov_len = added; - if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, le32toh(h->tag_size), (uint8_t*) input->iov_base + input->iov_len - le32toh(h->tag_size)) != 1) + if (sym_EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, le32toh(h->tag_size), (uint8_t*) input->iov_base + input->iov_len - le32toh(h->tag_size)) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set tag: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (EVP_DecryptFinal_ex(context, (uint8_t*) plaintext.iov_base + plaintext.iov_len, &added) != 1) + if (sym_EVP_DecryptFinal_ex(context, (uint8_t*) plaintext.iov_base + plaintext.iov_len, &added) != 1) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Decryption failed (incorrect key?): %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); plaintext.iov_len += added; diff --git a/src/shared/openssl-util.c b/src/shared/crypto-util.c similarity index 51% rename from src/shared/openssl-util.c rename to src/shared/crypto-util.c index 18c6b06b17dc1..bf770f2432175 100644 --- a/src/shared/openssl-util.c +++ b/src/shared/crypto-util.c @@ -1,33 +1,634 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-dlopen.h" + #include "alloc-util.h" #include "ask-password-api.h" +#include "crypto-util.h" +#include "dlfcn-util.h" #include "fd-util.h" #include "fileio.h" #include "hexdecoct.h" #include "log.h" #include "memory-util.h" #include "memstream-util.h" -#include "openssl-util.h" #include "random-util.h" #include "string-util.h" #include "strv.h" #if HAVE_OPENSSL -# include -# include +# include +# include +# include +# include # if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) # include +# endif + +# ifndef OPENSSL_NO_UI_CONSOLE +# include +# endif + +struct OpenSSLAskPasswordUI { + AskPasswordRequest request; +#ifndef OPENSSL_NO_UI_CONSOLE + UI_METHOD *method; +#endif +}; + +static void *libcrypto_dl = NULL; + +static DLSYM_PROTOTYPE(ASN1_INTEGER_dup) = NULL; +static DLSYM_PROTOTYPE(ASN1_INTEGER_free) = NULL; +static DLSYM_PROTOTYPE(ASN1_INTEGER_set) = NULL; +DLSYM_PROTOTYPE(ASN1_ANY_it) = NULL; +DLSYM_PROTOTYPE(ASN1_BIT_STRING_it) = NULL; +DLSYM_PROTOTYPE(ASN1_BMPSTRING_it) = NULL; +DLSYM_PROTOTYPE(ASN1_BMPSTRING_new) = NULL; +DLSYM_PROTOTYPE(ASN1_IA5STRING_it) = NULL; +DLSYM_PROTOTYPE(ASN1_OBJECT_it) = NULL; +DLSYM_PROTOTYPE(ASN1_OCTET_STRING_free) = NULL; +DLSYM_PROTOTYPE(ASN1_OCTET_STRING_it) = NULL; +DLSYM_PROTOTYPE(ASN1_OCTET_STRING_set) = NULL; +DLSYM_PROTOTYPE(ASN1_STRING_get0_data) = NULL; +DLSYM_PROTOTYPE(ASN1_STRING_length) = NULL; +DLSYM_PROTOTYPE(ASN1_STRING_new) = NULL; +DLSYM_PROTOTYPE(ASN1_STRING_set) = NULL; +DLSYM_PROTOTYPE(ASN1_STRING_set0) = NULL; +DLSYM_PROTOTYPE(ASN1_TIME_free) = NULL; +DLSYM_PROTOTYPE(ASN1_TIME_set) = NULL; +DLSYM_PROTOTYPE(ASN1_TYPE_new) = NULL; +DLSYM_PROTOTYPE(ASN1_get_object) = NULL; +DLSYM_PROTOTYPE(ASN1_item_d2i) = NULL; +DLSYM_PROTOTYPE(ASN1_item_free) = NULL; +DLSYM_PROTOTYPE(ASN1_item_i2d) = NULL; +DLSYM_PROTOTYPE(ASN1_item_new) = NULL; +DLSYM_PROTOTYPE(BIO_ctrl) = NULL; +DLSYM_PROTOTYPE(BIO_find_type) = NULL; +DLSYM_PROTOTYPE(BIO_free) = NULL; +DLSYM_PROTOTYPE(BIO_free_all) = NULL; +DLSYM_PROTOTYPE(BIO_new) = NULL; +DLSYM_PROTOTYPE(BIO_new_mem_buf) = NULL; +DLSYM_PROTOTYPE(BIO_new_socket) = NULL; +DLSYM_PROTOTYPE(BIO_s_mem) = NULL; +DLSYM_PROTOTYPE(BIO_write) = NULL; +DLSYM_PROTOTYPE(BN_CTX_free) = NULL; +DLSYM_PROTOTYPE(BN_CTX_new) = NULL; +DLSYM_PROTOTYPE(BN_bin2bn) = NULL; +static DLSYM_PROTOTYPE(BN_bn2bin) = NULL; +DLSYM_PROTOTYPE(BN_bn2nativepad) = NULL; +DLSYM_PROTOTYPE(BN_free) = NULL; +DLSYM_PROTOTYPE(BN_new) = NULL; +DLSYM_PROTOTYPE(BN_num_bits) = NULL; +DLSYM_PROTOTYPE(CRYPTO_free) = NULL; +DLSYM_PROTOTYPE(ECDSA_SIG_free) = NULL; +DLSYM_PROTOTYPE(EC_GROUP_free) = NULL; +DLSYM_PROTOTYPE(EC_GROUP_get0_generator) = NULL; +DLSYM_PROTOTYPE(EC_GROUP_get0_order) = NULL; +DLSYM_PROTOTYPE(EC_GROUP_get_curve) = NULL; +DLSYM_PROTOTYPE(EC_GROUP_get_curve_name) = NULL; +DLSYM_PROTOTYPE(EC_GROUP_get_field_type) = NULL; +DLSYM_PROTOTYPE(EC_GROUP_new_by_curve_name) = NULL; +DLSYM_PROTOTYPE(EC_POINT_free) = NULL; +DLSYM_PROTOTYPE(EC_POINT_new) = NULL; +DLSYM_PROTOTYPE(EC_POINT_oct2point) = NULL; +static DLSYM_PROTOTYPE(EC_POINT_point2buf) = NULL; +DLSYM_PROTOTYPE(EC_POINT_point2oct) = NULL; +static DLSYM_PROTOTYPE(EC_POINT_set_affine_coordinates) = NULL; +DLSYM_PROTOTYPE(ERR_clear_error) = NULL; +DLSYM_PROTOTYPE(ERR_error_string) = NULL; +DLSYM_PROTOTYPE(ERR_error_string_n) = NULL; +DLSYM_PROTOTYPE(ERR_get_error) = NULL; +static DLSYM_PROTOTYPE(ERR_peek_last_error) = NULL; +DLSYM_PROTOTYPE(EVP_CIPHER_CTX_ctrl) = NULL; +DLSYM_PROTOTYPE(EVP_CIPHER_CTX_free) = NULL; +static DLSYM_PROTOTYPE(EVP_CIPHER_CTX_get_block_size) = NULL; +DLSYM_PROTOTYPE(EVP_CIPHER_CTX_new) = NULL; +static DLSYM_PROTOTYPE(EVP_CIPHER_fetch) = NULL; +DLSYM_PROTOTYPE(EVP_CIPHER_free) = NULL; +DLSYM_PROTOTYPE(EVP_CIPHER_get_block_size) = NULL; +DLSYM_PROTOTYPE(EVP_CIPHER_get_iv_length) = NULL; +DLSYM_PROTOTYPE(EVP_CIPHER_get_key_length) = NULL; +DLSYM_PROTOTYPE(EVP_DecryptFinal_ex) = NULL; +DLSYM_PROTOTYPE(EVP_DecryptInit_ex) = NULL; +DLSYM_PROTOTYPE(EVP_DecryptUpdate) = NULL; +DLSYM_PROTOTYPE(EVP_Digest) = NULL; +DLSYM_PROTOTYPE(EVP_DigestFinal_ex) = NULL; +DLSYM_PROTOTYPE(EVP_DigestInit_ex) = NULL; +static DLSYM_PROTOTYPE(EVP_DigestSign) = NULL; +static DLSYM_PROTOTYPE(EVP_DigestSignInit) = NULL; +DLSYM_PROTOTYPE(EVP_DigestUpdate) = NULL; +DLSYM_PROTOTYPE(EVP_DigestVerify) = NULL; +DLSYM_PROTOTYPE(EVP_DigestVerifyInit) = NULL; +DLSYM_PROTOTYPE(EVP_EncryptFinal_ex) = NULL; +static DLSYM_PROTOTYPE(EVP_EncryptInit) = NULL; +DLSYM_PROTOTYPE(EVP_EncryptInit_ex) = NULL; +DLSYM_PROTOTYPE(EVP_EncryptUpdate) = NULL; +static DLSYM_PROTOTYPE(EVP_KDF_CTX_free) = NULL; +static DLSYM_PROTOTYPE(EVP_KDF_CTX_new) = NULL; +static DLSYM_PROTOTYPE(EVP_KDF_derive) = NULL; +static DLSYM_PROTOTYPE(EVP_KDF_fetch) = NULL; +static DLSYM_PROTOTYPE(EVP_KDF_free) = NULL; +DLSYM_PROTOTYPE(EVP_MAC_CTX_free) = NULL; +static DLSYM_PROTOTYPE(EVP_MAC_CTX_get_mac_size) = NULL; +static DLSYM_PROTOTYPE(EVP_MAC_CTX_new) = NULL; +static DLSYM_PROTOTYPE(EVP_MAC_fetch) = NULL; +static DLSYM_PROTOTYPE(EVP_MAC_final) = NULL; +DLSYM_PROTOTYPE(EVP_MAC_free) = NULL; +static DLSYM_PROTOTYPE(EVP_MAC_init) = NULL; +static DLSYM_PROTOTYPE(EVP_MAC_update) = NULL; +DLSYM_PROTOTYPE(EVP_MD_CTX_free) = NULL; +DLSYM_PROTOTYPE(EVP_MD_CTX_get0_md) = NULL; +DLSYM_PROTOTYPE(EVP_MD_CTX_new) = NULL; +DLSYM_PROTOTYPE(EVP_MD_CTX_set_pkey_ctx) = NULL; +static DLSYM_PROTOTYPE(EVP_MD_fetch) = NULL; +DLSYM_PROTOTYPE(EVP_MD_free) = NULL; +DLSYM_PROTOTYPE(EVP_MD_get0_name) = NULL; +DLSYM_PROTOTYPE(EVP_MD_get_size) = NULL; +static DLSYM_PROTOTYPE(EVP_MD_get_type) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_CTX_free) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_CTX_new) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_CTX_new_from_name) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_CTX_new_id) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_CTX_set0_rsa_oaep_label) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_CTX_set_ec_paramgen_curve_nid) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_CTX_set_rsa_oaep_md) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_CTX_set_rsa_padding) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_CTX_set_signature_md) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_derive) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_derive_init) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_derive_set_peer) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_encrypt) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_encrypt_init) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_eq) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_free) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_fromdata) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_fromdata_init) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_get1_encoded_public_key) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_get_base_id) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_get_bits) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_get_bn_param) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_get_group_name) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_get_id) = NULL; +static DLSYM_PROTOTYPE(EVP_PKEY_get_utf8_string_param) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_keygen) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_keygen_init) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_new) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_new_raw_public_key) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_verify) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_verify_init) = NULL; +DLSYM_PROTOTYPE(EVP_aes_256_ctr) = NULL; +DLSYM_PROTOTYPE(EVP_aes_256_gcm) = NULL; +DLSYM_PROTOTYPE(EVP_get_cipherbyname) = NULL; +DLSYM_PROTOTYPE(EVP_get_digestbyname) = NULL; +DLSYM_PROTOTYPE(EVP_sha1) = NULL; +DLSYM_PROTOTYPE(EVP_sha256) = NULL; +DLSYM_PROTOTYPE(EVP_sha384) = NULL; +DLSYM_PROTOTYPE(EVP_sha512) = NULL; +DLSYM_PROTOTYPE(HMAC) = NULL; +DLSYM_PROTOTYPE(SHA1) = NULL; +DLSYM_PROTOTYPE(SHA512) = NULL; +DLSYM_PROTOTYPE(OBJ_nid2obj) = NULL; +DLSYM_PROTOTYPE(OBJ_nid2sn) = NULL; +DLSYM_PROTOTYPE(OBJ_sn2nid) = NULL; +DLSYM_PROTOTYPE(OBJ_txt2obj) = NULL; +DLSYM_PROTOTYPE(OPENSSL_sk_new_null) = NULL; +DLSYM_PROTOTYPE(OPENSSL_sk_num) = NULL; +DLSYM_PROTOTYPE(OPENSSL_sk_pop_free) = NULL; +DLSYM_PROTOTYPE(OPENSSL_sk_push) = NULL; +DLSYM_PROTOTYPE(OPENSSL_sk_value) = NULL; +DLSYM_PROTOTYPE(OSSL_EC_curve_nid2name) = NULL; +static DLSYM_PROTOTYPE(OSSL_PARAM_BLD_new) = NULL; +static DLSYM_PROTOTYPE(OSSL_PARAM_BLD_free) = NULL; +static DLSYM_PROTOTYPE(OSSL_PARAM_BLD_push_octet_string) = NULL; +static DLSYM_PROTOTYPE(OSSL_PARAM_BLD_push_utf8_string) = NULL; +static DLSYM_PROTOTYPE(OSSL_PARAM_BLD_to_param) = NULL; +DLSYM_PROTOTYPE(OSSL_PARAM_construct_BN) = NULL; +DLSYM_PROTOTYPE(OSSL_PARAM_construct_end) = NULL; +DLSYM_PROTOTYPE(OSSL_PARAM_construct_octet_string) = NULL; +DLSYM_PROTOTYPE(OSSL_PARAM_construct_utf8_string) = NULL; +DLSYM_PROTOTYPE(OSSL_PARAM_free) = NULL; +static DLSYM_PROTOTYPE(OSSL_PROVIDER_try_load) = NULL; +static DLSYM_PROTOTYPE(OSSL_STORE_INFO_free) = NULL; +static DLSYM_PROTOTYPE(OSSL_STORE_INFO_get1_CERT) = NULL; +static DLSYM_PROTOTYPE(OSSL_STORE_INFO_get1_PKEY) = NULL; +static DLSYM_PROTOTYPE(OSSL_STORE_close) = NULL; +static DLSYM_PROTOTYPE(OSSL_STORE_expect) = NULL; +static DLSYM_PROTOTYPE(OSSL_STORE_load) = NULL; +static DLSYM_PROTOTYPE(OSSL_STORE_open) = NULL; +DLSYM_PROTOTYPE(PEM_read_PUBKEY) = NULL; +DLSYM_PROTOTYPE(PEM_read_PrivateKey) = NULL; +DLSYM_PROTOTYPE(PEM_read_X509) = NULL; +static DLSYM_PROTOTYPE(PEM_read_bio_PrivateKey) = NULL; +static DLSYM_PROTOTYPE(PEM_read_bio_X509) = NULL; +DLSYM_PROTOTYPE(PKCS5_PBKDF2_HMAC) = NULL; +DLSYM_PROTOTYPE(PEM_write_PUBKEY) = NULL; +DLSYM_PROTOTYPE(PEM_write_PrivateKey) = NULL; +DLSYM_PROTOTYPE(PEM_write_X509) = NULL; +DLSYM_PROTOTYPE(PKCS7_SIGNER_INFO_free) = NULL; +DLSYM_PROTOTYPE(PKCS7_ATTR_SIGN_it) = NULL; +static DLSYM_PROTOTYPE(PKCS7_SIGNER_INFO_new) = NULL; +static DLSYM_PROTOTYPE(PKCS7_SIGNER_INFO_set) = NULL; +DLSYM_PROTOTYPE(PKCS7_add0_attrib_signing_time) = NULL; +DLSYM_PROTOTYPE(PKCS7_add1_attrib_digest) = NULL; +DLSYM_PROTOTYPE(PKCS7_add_attrib_content_type) = NULL; +DLSYM_PROTOTYPE(PKCS7_add_attrib_smimecap) = NULL; +static DLSYM_PROTOTYPE(PKCS7_add_certificate) = NULL; +DLSYM_PROTOTYPE(PKCS7_add_signed_attribute) = NULL; +static DLSYM_PROTOTYPE(PKCS7_add_signer) = NULL; +DLSYM_PROTOTYPE(PKCS7_content_new) = NULL; +DLSYM_PROTOTYPE(PKCS7_ctrl) = NULL; +DLSYM_PROTOTYPE(PKCS7_dataFinal) = NULL; +DLSYM_PROTOTYPE(PKCS7_dataInit) = NULL; +DLSYM_PROTOTYPE(PKCS7_free) = NULL; +DLSYM_PROTOTYPE(PKCS7_get_signer_info) = NULL; +DLSYM_PROTOTYPE(PKCS7_new) = NULL; +DLSYM_PROTOTYPE(PKCS7_set_content) = NULL; +static DLSYM_PROTOTYPE(PKCS7_set_type) = NULL; +DLSYM_PROTOTYPE(PKCS7_sign) = NULL; +DLSYM_PROTOTYPE(PKCS7_verify) = NULL; +static DLSYM_PROTOTYPE(X509_ALGOR_set0) = NULL; +DLSYM_PROTOTYPE(X509_NAME_free) = NULL; +DLSYM_PROTOTYPE(X509_ALGOR_free) = NULL; +DLSYM_PROTOTYPE(X509_ATTRIBUTE_free) = NULL; +DLSYM_PROTOTYPE(X509_NAME_oneline) = NULL; +static DLSYM_PROTOTYPE(X509_NAME_set) = NULL; +DLSYM_PROTOTYPE(X509_VERIFY_PARAM_set1_host) = NULL; +DLSYM_PROTOTYPE(X509_VERIFY_PARAM_set1_ip) = NULL; +DLSYM_PROTOTYPE(X509_VERIFY_PARAM_set_hostflags) = NULL; +DLSYM_PROTOTYPE(X509_free) = NULL; +DLSYM_PROTOTYPE(X509_gmtime_adj) = NULL; +static DLSYM_PROTOTYPE(X509_get0_serialNumber) = NULL; +static DLSYM_PROTOTYPE(X509_get_issuer_name) = NULL; +DLSYM_PROTOTYPE(X509_get_pubkey) = NULL; +static DLSYM_PROTOTYPE(X509_get_signature_info) = NULL; +DLSYM_PROTOTYPE(X509_get_subject_name) = NULL; +DLSYM_PROTOTYPE(d2i_ASN1_OCTET_STRING) = NULL; +DLSYM_PROTOTYPE(d2i_ECPKParameters) = NULL; +DLSYM_PROTOTYPE(d2i_PKCS7) = NULL; +DLSYM_PROTOTYPE(d2i_PUBKEY) = NULL; +DLSYM_PROTOTYPE(d2i_X509) = NULL; +DLSYM_PROTOTYPE(i2d_ASN1_INTEGER) = NULL; +DLSYM_PROTOTYPE(i2d_PKCS7) = NULL; +DLSYM_PROTOTYPE(i2d_PKCS7_fp) = NULL; +DLSYM_PROTOTYPE(i2d_PUBKEY) = NULL; +static DLSYM_PROTOTYPE(i2d_PUBKEY_fp) = NULL; +static DLSYM_PROTOTYPE(i2d_PublicKey) = NULL; +DLSYM_PROTOTYPE(i2d_X509) = NULL; +DLSYM_PROTOTYPE(i2d_X509_NAME) = NULL; + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(OSSL_PARAM_BLD*, sym_OSSL_PARAM_BLD_free, OSSL_PARAM_BLD_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(OSSL_STORE_CTX*, sym_OSSL_STORE_close, OSSL_STORE_closep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(OSSL_STORE_INFO*, sym_OSSL_STORE_INFO_free, OSSL_STORE_INFO_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EVP_KDF*, sym_EVP_KDF_free, EVP_KDF_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EVP_KDF_CTX*, sym_EVP_KDF_CTX_free, EVP_KDF_CTX_freep, NULL); + +#if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) DISABLE_WARNING_DEPRECATED_DECLARATIONS; -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ENGINE*, ENGINE_free, NULL); +static DLSYM_PROTOTYPE(ENGINE_by_id) = NULL; +static DLSYM_PROTOTYPE(ENGINE_free) = NULL; +static DLSYM_PROTOTYPE(ENGINE_init) = NULL; +static DLSYM_PROTOTYPE(ENGINE_load_private_key) = NULL; REENABLE_WARNING; -# endif + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(ENGINE*, sym_ENGINE_free, ENGINE_freep, NULL); +#endif + +#if !defined(OPENSSL_NO_DEPRECATED_3_0) +DISABLE_WARNING_DEPRECATED_DECLARATIONS; +DLSYM_PROTOTYPE(ECDSA_SIG_new) = NULL; +DLSYM_PROTOTYPE(ECDSA_SIG_set0) = NULL; +DLSYM_PROTOTYPE(ECDSA_do_verify) = NULL; +DLSYM_PROTOTYPE(EC_KEY_check_key) = NULL; +DLSYM_PROTOTYPE(EC_KEY_free) = NULL; +DLSYM_PROTOTYPE(EC_KEY_new) = NULL; +DLSYM_PROTOTYPE(EC_KEY_set_group) = NULL; +DLSYM_PROTOTYPE(EC_KEY_set_public_key) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_assign) = NULL; +DLSYM_PROTOTYPE(RSA_free) = NULL; +DLSYM_PROTOTYPE(RSA_new) = NULL; +DLSYM_PROTOTYPE(RSA_set0_key) = NULL; +DLSYM_PROTOTYPE(RSA_size) = NULL; +DLSYM_PROTOTYPE(RSAPublicKey_dup) = NULL; +REENABLE_WARNING; +#endif #ifndef OPENSSL_NO_UI_CONSOLE -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(UI_METHOD*, UI_destroy_method, NULL); +static DLSYM_PROTOTYPE(UI_OpenSSL) = NULL; +static DLSYM_PROTOTYPE(UI_create_method) = NULL; +static DLSYM_PROTOTYPE(UI_destroy_method) = NULL; +static DLSYM_PROTOTYPE(UI_get0_output_string) = NULL; +static DLSYM_PROTOTYPE(UI_get_default_method) = NULL; +static DLSYM_PROTOTYPE(UI_get_method) = NULL; +static DLSYM_PROTOTYPE(UI_get_string_type) = NULL; +static DLSYM_PROTOTYPE(UI_method_get_ex_data) = NULL; +static DLSYM_PROTOTYPE(UI_method_get_reader) = NULL; +static DLSYM_PROTOTYPE(UI_method_set_ex_data) = NULL; +static DLSYM_PROTOTYPE(UI_method_set_reader) = NULL; +static DLSYM_PROTOTYPE(UI_set_default_method) = NULL; +static DLSYM_PROTOTYPE(UI_set_result) = NULL; + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(UI_METHOD*, sym_UI_destroy_method, UI_destroy_methodp, NULL); #endif +#endif + +int dlopen_libcrypto(int log_level) { +#if HAVE_OPENSSL + SD_ELF_NOTE_DLOPEN( + "libcrypto", + "Support for cryptographic operations", + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libcrypto.so.3"); + + return dlopen_many_sym_or_warn( + &libcrypto_dl, + "libcrypto.so.3", + log_level, + DLSYM_ARG(ASN1_ANY_it), + DLSYM_ARG(ASN1_BIT_STRING_it), + DLSYM_ARG(ASN1_BMPSTRING_it), + DLSYM_ARG(ASN1_BMPSTRING_new), + DLSYM_ARG(ASN1_get_object), + DLSYM_ARG(ASN1_IA5STRING_it), + DLSYM_ARG(ASN1_INTEGER_dup), + DLSYM_ARG(ASN1_INTEGER_free), + DLSYM_ARG(ASN1_INTEGER_set), + DLSYM_ARG(ASN1_item_d2i), + DLSYM_ARG(ASN1_item_free), + DLSYM_ARG(ASN1_item_i2d), + DLSYM_ARG(ASN1_item_new), + DLSYM_ARG(ASN1_OBJECT_it), + DLSYM_ARG(ASN1_OCTET_STRING_free), + DLSYM_ARG(ASN1_OCTET_STRING_it), + DLSYM_ARG(ASN1_OCTET_STRING_set), + DLSYM_ARG(ASN1_STRING_get0_data), + DLSYM_ARG(ASN1_STRING_length), + DLSYM_ARG(ASN1_STRING_new), + DLSYM_ARG(ASN1_STRING_set), + DLSYM_ARG(ASN1_STRING_set0), + DLSYM_ARG(ASN1_TIME_free), + DLSYM_ARG(ASN1_TIME_set), + DLSYM_ARG(ASN1_TYPE_new), + DLSYM_ARG(BIO_ctrl), + DLSYM_ARG(BIO_find_type), + DLSYM_ARG(BIO_free_all), + DLSYM_ARG(BIO_free), + DLSYM_ARG(BIO_new_mem_buf), + DLSYM_ARG(BIO_new_socket), + DLSYM_ARG(BIO_new), + DLSYM_ARG(BIO_s_mem), + DLSYM_ARG(BIO_write), + DLSYM_ARG(BN_bin2bn), + DLSYM_ARG(BN_bn2bin), + DLSYM_ARG(BN_bn2nativepad), + DLSYM_ARG(BN_CTX_free), + DLSYM_ARG(BN_CTX_new), + DLSYM_ARG(BN_free), + DLSYM_ARG(BN_new), + DLSYM_ARG(BN_num_bits), + DLSYM_ARG(CRYPTO_free), + DLSYM_ARG(d2i_ASN1_OCTET_STRING), + DLSYM_ARG(d2i_ECPKParameters), + DLSYM_ARG(d2i_PKCS7), + DLSYM_ARG(d2i_PUBKEY), + DLSYM_ARG(d2i_X509), + DLSYM_ARG(EC_GROUP_free), + DLSYM_ARG(EC_GROUP_get_curve_name), + DLSYM_ARG(EC_GROUP_get_curve), + DLSYM_ARG(EC_GROUP_get_field_type), + DLSYM_ARG(EC_GROUP_get0_generator), + DLSYM_ARG(EC_GROUP_get0_order), + DLSYM_ARG(EC_GROUP_new_by_curve_name), + DLSYM_ARG(EC_POINT_free), + DLSYM_ARG(EC_POINT_new), + DLSYM_ARG(EC_POINT_oct2point), + DLSYM_ARG(EC_POINT_point2buf), + DLSYM_ARG(EC_POINT_point2oct), + DLSYM_ARG(EC_POINT_set_affine_coordinates), + DLSYM_ARG(ECDSA_SIG_free), +#if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) + DLSYM_ARG_FORCE(ENGINE_by_id), + DLSYM_ARG_FORCE(ENGINE_free), + DLSYM_ARG_FORCE(ENGINE_init), + DLSYM_ARG_FORCE(ENGINE_load_private_key), +#endif +#if !defined(OPENSSL_NO_DEPRECATED_3_0) + DLSYM_ARG_FORCE(EC_KEY_check_key), + DLSYM_ARG_FORCE(EC_KEY_free), + DLSYM_ARG_FORCE(EC_KEY_new), + DLSYM_ARG_FORCE(EC_KEY_set_group), + DLSYM_ARG_FORCE(EC_KEY_set_public_key), + DLSYM_ARG_FORCE(ECDSA_do_verify), + DLSYM_ARG_FORCE(ECDSA_SIG_new), + DLSYM_ARG_FORCE(ECDSA_SIG_set0), + DLSYM_ARG_FORCE(EVP_PKEY_assign), + DLSYM_ARG_FORCE(RSA_free), + DLSYM_ARG_FORCE(RSA_new), + DLSYM_ARG_FORCE(RSA_set0_key), + DLSYM_ARG_FORCE(RSA_size), + DLSYM_ARG_FORCE(RSAPublicKey_dup), +#endif + DLSYM_ARG(ERR_clear_error), + DLSYM_ARG(ERR_error_string_n), + DLSYM_ARG(ERR_error_string), + DLSYM_ARG(ERR_get_error), + DLSYM_ARG(ERR_peek_last_error), + DLSYM_ARG(EVP_aes_256_ctr), + DLSYM_ARG(EVP_aes_256_gcm), + DLSYM_ARG(EVP_CIPHER_CTX_ctrl), + DLSYM_ARG(EVP_CIPHER_CTX_free), + DLSYM_ARG(EVP_CIPHER_CTX_get_block_size), + DLSYM_ARG(EVP_CIPHER_CTX_new), + DLSYM_ARG(EVP_CIPHER_fetch), + DLSYM_ARG(EVP_CIPHER_free), + DLSYM_ARG(EVP_CIPHER_get_block_size), + DLSYM_ARG(EVP_CIPHER_get_iv_length), + DLSYM_ARG(EVP_CIPHER_get_key_length), + DLSYM_ARG(EVP_DecryptFinal_ex), + DLSYM_ARG(EVP_DecryptInit_ex), + DLSYM_ARG(EVP_DecryptUpdate), + DLSYM_ARG(EVP_Digest), + DLSYM_ARG(EVP_DigestFinal_ex), + DLSYM_ARG(EVP_DigestInit_ex), + DLSYM_ARG(EVP_DigestSign), + DLSYM_ARG(EVP_DigestSignInit), + DLSYM_ARG(EVP_DigestUpdate), + DLSYM_ARG(EVP_DigestVerify), + DLSYM_ARG(EVP_DigestVerifyInit), + DLSYM_ARG(EVP_EncryptFinal_ex), + DLSYM_ARG(EVP_EncryptInit_ex), + DLSYM_ARG(EVP_EncryptInit), + DLSYM_ARG(EVP_EncryptUpdate), + DLSYM_ARG(EVP_get_cipherbyname), + DLSYM_ARG(EVP_get_digestbyname), + DLSYM_ARG(EVP_KDF_CTX_free), + DLSYM_ARG(EVP_KDF_CTX_new), + DLSYM_ARG(EVP_KDF_derive), + DLSYM_ARG(EVP_KDF_fetch), + DLSYM_ARG(EVP_KDF_free), + DLSYM_ARG(EVP_MAC_CTX_free), + DLSYM_ARG(EVP_MAC_CTX_get_mac_size), + DLSYM_ARG(EVP_MAC_CTX_new), + DLSYM_ARG(EVP_MAC_fetch), + DLSYM_ARG(EVP_MAC_final), + DLSYM_ARG(EVP_MAC_free), + DLSYM_ARG(EVP_MAC_init), + DLSYM_ARG(EVP_MAC_update), + DLSYM_ARG(EVP_MD_CTX_free), + DLSYM_ARG(EVP_MD_CTX_get0_md), + DLSYM_ARG(EVP_MD_CTX_new), + DLSYM_ARG(EVP_MD_CTX_set_pkey_ctx), + DLSYM_ARG(EVP_MD_fetch), + DLSYM_ARG(EVP_MD_free), + DLSYM_ARG(EVP_MD_get_size), + DLSYM_ARG(EVP_MD_get_type), + DLSYM_ARG(EVP_MD_get0_name), + DLSYM_ARG(EVP_PKEY_CTX_free), + DLSYM_ARG(EVP_PKEY_CTX_new_from_name), + DLSYM_ARG(EVP_PKEY_CTX_new_id), + DLSYM_ARG(EVP_PKEY_CTX_new), + DLSYM_ARG(EVP_PKEY_CTX_set_ec_paramgen_curve_nid), + DLSYM_ARG(EVP_PKEY_CTX_set_rsa_oaep_md), + DLSYM_ARG(EVP_PKEY_CTX_set_rsa_padding), + DLSYM_ARG(EVP_PKEY_CTX_set_signature_md), + DLSYM_ARG(EVP_PKEY_CTX_set0_rsa_oaep_label), + DLSYM_ARG(EVP_PKEY_derive_init), + DLSYM_ARG(EVP_PKEY_derive_set_peer), + DLSYM_ARG(EVP_PKEY_derive), + DLSYM_ARG(EVP_PKEY_encrypt_init), + DLSYM_ARG(EVP_PKEY_encrypt), + DLSYM_ARG(EVP_PKEY_eq), + DLSYM_ARG(EVP_PKEY_free), + DLSYM_ARG(EVP_PKEY_fromdata_init), + DLSYM_ARG(EVP_PKEY_fromdata), + DLSYM_ARG(EVP_PKEY_get_base_id), + DLSYM_ARG(EVP_PKEY_get_bits), + DLSYM_ARG(EVP_PKEY_get_bn_param), + DLSYM_ARG(EVP_PKEY_get_group_name), + DLSYM_ARG(EVP_PKEY_get_id), + DLSYM_ARG(EVP_PKEY_get_utf8_string_param), + DLSYM_ARG(EVP_PKEY_get1_encoded_public_key), + DLSYM_ARG(EVP_PKEY_keygen_init), + DLSYM_ARG(EVP_PKEY_keygen), + DLSYM_ARG(EVP_PKEY_new_raw_public_key), + DLSYM_ARG(EVP_PKEY_new), + DLSYM_ARG(EVP_PKEY_verify_init), + DLSYM_ARG(EVP_PKEY_verify), + DLSYM_ARG(EVP_sha1), + DLSYM_ARG(EVP_sha256), + DLSYM_ARG(EVP_sha384), + DLSYM_ARG(EVP_sha512), + DLSYM_ARG(HMAC), + DLSYM_ARG(i2d_ASN1_INTEGER), + DLSYM_ARG(i2d_PKCS7_fp), + DLSYM_ARG(i2d_PKCS7), + DLSYM_ARG(i2d_PUBKEY_fp), + DLSYM_ARG(i2d_PUBKEY), + DLSYM_ARG(i2d_PublicKey), + DLSYM_ARG(i2d_X509_NAME), + DLSYM_ARG(i2d_X509), + DLSYM_ARG(OBJ_nid2obj), + DLSYM_ARG(OBJ_nid2sn), + DLSYM_ARG(OBJ_sn2nid), + DLSYM_ARG(OBJ_txt2obj), + DLSYM_ARG(OPENSSL_sk_new_null), + DLSYM_ARG(OPENSSL_sk_num), + DLSYM_ARG(OPENSSL_sk_pop_free), + DLSYM_ARG(OPENSSL_sk_push), + DLSYM_ARG(OPENSSL_sk_value), + DLSYM_ARG(OSSL_EC_curve_nid2name), + DLSYM_ARG(OSSL_PARAM_BLD_free), + DLSYM_ARG(OSSL_PARAM_BLD_new), + DLSYM_ARG(OSSL_PARAM_BLD_push_octet_string), + DLSYM_ARG(OSSL_PARAM_BLD_push_utf8_string), + DLSYM_ARG(OSSL_PARAM_BLD_to_param), + DLSYM_ARG(OSSL_PARAM_construct_BN), + DLSYM_ARG(OSSL_PARAM_construct_end), + DLSYM_ARG(OSSL_PARAM_construct_octet_string), + DLSYM_ARG(OSSL_PARAM_construct_utf8_string), + DLSYM_ARG(OSSL_PARAM_free), + DLSYM_ARG(OSSL_PROVIDER_try_load), + DLSYM_ARG(OSSL_STORE_close), + DLSYM_ARG(OSSL_STORE_expect), + DLSYM_ARG(OSSL_STORE_INFO_free), + DLSYM_ARG(OSSL_STORE_INFO_get1_CERT), + DLSYM_ARG(OSSL_STORE_INFO_get1_PKEY), + DLSYM_ARG(OSSL_STORE_load), + DLSYM_ARG(OSSL_STORE_open), + DLSYM_ARG(PEM_read_bio_PrivateKey), + DLSYM_ARG(PEM_read_bio_X509), + DLSYM_ARG(PEM_read_PrivateKey), + DLSYM_ARG(PEM_read_PUBKEY), + DLSYM_ARG(PEM_read_X509), + DLSYM_ARG(PEM_write_PrivateKey), + DLSYM_ARG(PEM_write_PUBKEY), + DLSYM_ARG(PEM_write_X509), + DLSYM_ARG(PKCS5_PBKDF2_HMAC), + DLSYM_ARG(PKCS7_add_attrib_content_type), + DLSYM_ARG(PKCS7_add_attrib_smimecap), + DLSYM_ARG(PKCS7_add_certificate), + DLSYM_ARG(PKCS7_add_signed_attribute), + DLSYM_ARG(PKCS7_add_signer), + DLSYM_ARG(PKCS7_add0_attrib_signing_time), + DLSYM_ARG(PKCS7_add1_attrib_digest), + DLSYM_ARG(PKCS7_ATTR_SIGN_it), + DLSYM_ARG(PKCS7_content_new), + DLSYM_ARG(PKCS7_ctrl), + DLSYM_ARG(PKCS7_dataFinal), + DLSYM_ARG(PKCS7_dataInit), + DLSYM_ARG(PKCS7_free), + DLSYM_ARG(PKCS7_get_signer_info), + DLSYM_ARG(PKCS7_new), + DLSYM_ARG(PKCS7_set_content), + DLSYM_ARG(PKCS7_set_type), + DLSYM_ARG(PKCS7_sign), + DLSYM_ARG(PKCS7_SIGNER_INFO_free), + DLSYM_ARG(PKCS7_SIGNER_INFO_new), + DLSYM_ARG(PKCS7_SIGNER_INFO_set), + DLSYM_ARG(PKCS7_verify), + DLSYM_ARG(SHA1), + DLSYM_ARG(SHA512), +#ifndef OPENSSL_NO_UI_CONSOLE + DLSYM_ARG(UI_create_method), + DLSYM_ARG(UI_destroy_method), + DLSYM_ARG(UI_get_default_method), + DLSYM_ARG(UI_get_method), + DLSYM_ARG(UI_get_string_type), + DLSYM_ARG(UI_get0_output_string), + DLSYM_ARG(UI_method_get_ex_data), + DLSYM_ARG(UI_method_get_reader), + DLSYM_ARG(UI_method_set_ex_data), + DLSYM_ARG(UI_method_set_reader), + DLSYM_ARG(UI_OpenSSL), + DLSYM_ARG(UI_set_default_method), + DLSYM_ARG(UI_set_result), +#endif + DLSYM_ARG(X509_ALGOR_free), + DLSYM_ARG(X509_ALGOR_set0), + DLSYM_ARG(X509_ATTRIBUTE_free), + DLSYM_ARG(X509_free), + DLSYM_ARG(X509_get_issuer_name), + DLSYM_ARG(X509_get_pubkey), + DLSYM_ARG(X509_get_signature_info), + DLSYM_ARG(X509_get_subject_name), + DLSYM_ARG(X509_get0_serialNumber), + DLSYM_ARG(X509_gmtime_adj), + DLSYM_ARG(X509_NAME_free), + DLSYM_ARG(X509_NAME_oneline), + DLSYM_ARG(X509_NAME_set), + DLSYM_ARG(X509_VERIFY_PARAM_set_hostflags), + DLSYM_ARG(X509_VERIFY_PARAM_set1_host), + DLSYM_ARG(X509_VERIFY_PARAM_set1_ip)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libcrypto support is not compiled in."); +#endif +} + +#if HAVE_OPENSSL + /* For each error in the OpenSSL thread error queue, log the provided message and the OpenSSL error * string. If there are no errors in the OpenSSL thread queue, this logs the message with "No OpenSSL * errors." This logs at level debug. Returns -EIO (or -ENOMEM). */ @@ -45,19 +646,25 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(UI_METHOD*, UI_destroy_method, NULL); ({ \ int UNIQ_T(R, u) = 0; \ for (;;) { \ - unsigned long UNIQ_T(E, u) = ERR_get_error(); \ + unsigned long UNIQ_T(E, u) = sym_ERR_get_error(); \ if (UNIQ_T(E, u) == 0) \ break; \ - ERR_error_string_n(UNIQ_T(E, u), buf, max); \ + sym_ERR_error_string_n(UNIQ_T(E, u), buf, max); \ UNIQ_T(R, u) = log_debug_errno(SYNTHETIC_ERRNO(EIO), fmt ": %s", ##__VA_ARGS__, buf); \ } \ UNIQ_T(R, u); \ }) int openssl_pubkey_from_pem(const void *pem, size_t pem_size, EVP_PKEY **ret) { + int r; + assert(pem); assert(ret); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + if (pem_size == SIZE_MAX) pem_size = strlen(pem); @@ -66,7 +673,7 @@ int openssl_pubkey_from_pem(const void *pem, size_t pem_size, EVP_PKEY **ret) { if (!f) return log_oom_debug(); - _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = PEM_read_PUBKEY(f, /* x= */ NULL, /* pam_password_cb= */ NULL, /* userdata= */ NULL); + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = sym_PEM_read_PUBKEY(f, /* x= */ NULL, /* pam_password_cb= */ NULL, /* userdata= */ NULL); if (!pkey) return log_openssl_errors("Failed to parse PEM"); @@ -75,15 +682,21 @@ int openssl_pubkey_from_pem(const void *pem, size_t pem_size, EVP_PKEY **ret) { } int openssl_pubkey_to_pem(EVP_PKEY *pkey, char **ret) { + int r; + assert(pkey); assert(ret); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + _cleanup_(memstream_done) MemStream m = {}; FILE *f = memstream_init(&m); if (!f) return -ENOMEM; - if (PEM_write_PUBKEY(f, pkey) <= 0) + if (sym_PEM_write_PUBKEY(f, pkey) <= 0) return -EIO; return memstream_finalize(&m, ret, /* ret_size= */ NULL); @@ -94,15 +707,21 @@ int openssl_pubkey_to_pem(EVP_PKEY *pkey, char **ret) { * e.g. shake128. Returns 0 on success, -EOPNOTSUPP if the algorithm is not supported, or < 0 for any other * error. */ int openssl_digest_size(const char *digest_alg, size_t *ret_digest_size) { + int r; + assert(digest_alg); assert(ret_digest_size); - _cleanup_(EVP_MD_freep) EVP_MD *md = EVP_MD_fetch(NULL, digest_alg, NULL); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(EVP_MD_freep) EVP_MD *md = sym_EVP_MD_fetch(NULL, digest_alg, NULL); if (!md) return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Digest algorithm '%s' not supported.", digest_alg); - size_t digest_size = EVP_MD_get_size(md); + size_t digest_size = sym_EVP_MD_get_size(md); if (digest_size == 0) return log_openssl_errors("Failed to get Digest size"); @@ -127,20 +746,24 @@ int openssl_digest_many( assert(ret_digest); /* ret_digest_size is optional, as caller may already know the digest size */ - _cleanup_(EVP_MD_freep) EVP_MD *md = EVP_MD_fetch(NULL, digest_alg, NULL); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(EVP_MD_freep) EVP_MD *md = sym_EVP_MD_fetch(NULL, digest_alg, NULL); if (!md) return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Digest algorithm '%s' not supported.", digest_alg); - _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *ctx = sym_EVP_MD_CTX_new(); if (!ctx) return log_openssl_errors("Failed to create new EVP_MD_CTX"); - if (!EVP_DigestInit_ex(ctx, md, NULL)) + if (!sym_EVP_DigestInit_ex(ctx, md, NULL)) return log_openssl_errors("Failed to initialize EVP_MD_CTX"); for (size_t i = 0; i < n_data; i++) - if (!EVP_DigestUpdate(ctx, data[i].iov_base, data[i].iov_len)) + if (!sym_EVP_DigestUpdate(ctx, data[i].iov_base, data[i].iov_len)) return log_openssl_errors("Failed to update Digest"); size_t digest_size; @@ -153,7 +776,7 @@ int openssl_digest_many( return log_oom_debug(); unsigned size; - if (!EVP_DigestFinal_ex(ctx, buf, &size)) + if (!sym_EVP_DigestFinal_ex(ctx, buf, &size)) return log_openssl_errors("Failed to finalize Digest"); assert(size == digest_size); @@ -177,44 +800,50 @@ int openssl_hmac_many( void **ret_digest, size_t *ret_digest_size) { + int r; + assert(digest_alg); assert(key); assert(data || n_data == 0); assert(ret_digest); /* ret_digest_size is optional, as caller may already know the digest size */ - _cleanup_(EVP_MD_freep) EVP_MD *md = EVP_MD_fetch(NULL, digest_alg, NULL); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(EVP_MD_freep) EVP_MD *md = sym_EVP_MD_fetch(NULL, digest_alg, NULL); if (!md) return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Digest algorithm '%s' not supported.", digest_alg); - _cleanup_(EVP_MAC_freep) EVP_MAC *mac = EVP_MAC_fetch(NULL, "HMAC", NULL); + _cleanup_(EVP_MAC_freep) EVP_MAC *mac = sym_EVP_MAC_fetch(NULL, "HMAC", NULL); if (!mac) return log_openssl_errors("Failed to create new EVP_MAC"); - _cleanup_(EVP_MAC_CTX_freep) EVP_MAC_CTX *ctx = EVP_MAC_CTX_new(mac); + _cleanup_(EVP_MAC_CTX_freep) EVP_MAC_CTX *ctx = sym_EVP_MAC_CTX_new(mac); if (!ctx) return log_openssl_errors("Failed to create new EVP_MAC_CTX"); - _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); + _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = sym_OSSL_PARAM_BLD_new(); if (!bld) return log_openssl_errors("Failed to create new OSSL_PARAM_BLD"); - if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_MAC_PARAM_DIGEST, (char*) digest_alg, 0)) + if (!sym_OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_MAC_PARAM_DIGEST, (char*) digest_alg, 0)) return log_openssl_errors("Failed to set HMAC OSSL_MAC_PARAM_DIGEST"); - _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld); + _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = sym_OSSL_PARAM_BLD_to_param(bld); if (!params) return log_openssl_errors("Failed to build HMAC OSSL_PARAM"); - if (!EVP_MAC_init(ctx, key, key_size, params)) + if (!sym_EVP_MAC_init(ctx, key, key_size, params)) return log_openssl_errors("Failed to initialize EVP_MAC_CTX"); for (size_t i = 0; i < n_data; i++) - if (!EVP_MAC_update(ctx, data[i].iov_base, data[i].iov_len)) + if (!sym_EVP_MAC_update(ctx, data[i].iov_base, data[i].iov_len)) return log_openssl_errors("Failed to update HMAC"); - size_t digest_size = EVP_MAC_CTX_get_mac_size(ctx); + size_t digest_size = sym_EVP_MAC_CTX_get_mac_size(ctx); if (digest_size == 0) return log_openssl_errors("Failed to get HMAC digest size"); @@ -223,7 +852,7 @@ int openssl_hmac_many( return log_oom_debug(); size_t size; - if (!EVP_MAC_final(ctx, buf, &size, digest_size)) + if (!sym_EVP_MAC_final(ctx, buf, &size, digest_size)) return log_openssl_errors("Failed to finalize HMAC"); assert(size == digest_size); @@ -253,6 +882,8 @@ int openssl_cipher_many( void **ret, size_t *ret_size) { + int r; + assert(alg); assert(bits > 0); assert(mode); @@ -262,28 +893,32 @@ int openssl_cipher_many( assert(ret); assert(ret_size); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + _cleanup_free_ char *cipher_alg = NULL; if (asprintf(&cipher_alg, "%s-%zu-%s", alg, bits, mode) < 0) return log_oom_debug(); - _cleanup_(EVP_CIPHER_freep) EVP_CIPHER *cipher = EVP_CIPHER_fetch(NULL, cipher_alg, NULL); + _cleanup_(EVP_CIPHER_freep) EVP_CIPHER *cipher = sym_EVP_CIPHER_fetch(NULL, cipher_alg, NULL); if (!cipher) return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Cipher algorithm '%s' not supported.", cipher_alg); - _cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + _cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *ctx = sym_EVP_CIPHER_CTX_new(); if (!ctx) return log_openssl_errors("Failed to create new EVP_CIPHER_CTX"); /* Verify enough key data was provided. */ - int cipher_key_length = EVP_CIPHER_key_length(cipher); + int cipher_key_length = sym_EVP_CIPHER_get_key_length(cipher); assert(cipher_key_length >= 0); if ((size_t) cipher_key_length > key_size) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Not enough key bytes provided, require %d", cipher_key_length); /* Verify enough IV data was provided or, if no IV was provided, use a zeroed buffer for IV data. */ - int cipher_iv_length = EVP_CIPHER_iv_length(cipher); + int cipher_iv_length = sym_EVP_CIPHER_get_iv_length(cipher); assert(cipher_iv_length >= 0); _cleanup_free_ void *zero_iv = NULL; if (iv_size == 0) { @@ -298,10 +933,10 @@ int openssl_cipher_many( return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Not enough IV bytes provided, require %d", cipher_iv_length); - if (!EVP_EncryptInit(ctx, cipher, key, iv)) + if (!sym_EVP_EncryptInit(ctx, cipher, key, iv)) return log_openssl_errors("Failed to initialize EVP_CIPHER_CTX."); - int cipher_block_size = EVP_CIPHER_CTX_block_size(ctx); + int cipher_block_size = sym_EVP_CIPHER_CTX_get_block_size(ctx); assert(cipher_block_size > 0); _cleanup_free_ uint8_t *buf = NULL; @@ -313,7 +948,7 @@ int openssl_cipher_many( return log_oom_debug(); int update_size; - if (!EVP_EncryptUpdate(ctx, &buf[size], &update_size, data[i].iov_base, data[i].iov_len)) + if (!sym_EVP_EncryptUpdate(ctx, &buf[size], &update_size, data[i].iov_base, data[i].iov_len)) return log_openssl_errors("Failed to update Cipher."); size += update_size; @@ -323,7 +958,7 @@ int openssl_cipher_many( return log_oom_debug(); int final_size; - if (!EVP_EncryptFinal_ex(ctx, &buf[size], &final_size)) + if (!sym_EVP_EncryptFinal_ex(ctx, &buf[size], &final_size)) return log_openssl_errors("Failed to finalize Cipher."); *ret = TAKE_PTR(buf); @@ -347,20 +982,26 @@ int kdf_ss_derive( size_t derive_size, void **ret) { + int r; + assert(digest); assert(key); assert(derive_size > 0); assert(ret); - _cleanup_(EVP_KDF_freep) EVP_KDF *kdf = EVP_KDF_fetch(NULL, "SSKDF", NULL); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(EVP_KDF_freep) EVP_KDF *kdf = sym_EVP_KDF_fetch(NULL, "SSKDF", NULL); if (!kdf) return log_openssl_errors("Failed to create new EVP_KDF"); - _cleanup_(EVP_KDF_CTX_freep) EVP_KDF_CTX *ctx = EVP_KDF_CTX_new(kdf); + _cleanup_(EVP_KDF_CTX_freep) EVP_KDF_CTX *ctx = sym_EVP_KDF_CTX_new(kdf); if (!ctx) return log_openssl_errors("Failed to create new EVP_KDF_CTX"); - _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); + _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = sym_OSSL_PARAM_BLD_new(); if (!bld) return log_openssl_errors("Failed to create new OSSL_PARAM_BLD"); @@ -368,25 +1009,25 @@ int kdf_ss_derive( if (!buf) return log_oom_debug(); - if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_DIGEST, (char*) digest, 0)) + if (!sym_OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_DIGEST, (char*) digest, 0)) return log_openssl_errors("Failed to add KDF-SS OSSL_KDF_PARAM_DIGEST"); - if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_KEY, (char*) key, key_size)) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_KEY, (char*) key, key_size)) return log_openssl_errors("Failed to add KDF-SS OSSL_KDF_PARAM_KEY"); if (salt) - if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_SALT, (char*) salt, salt_size)) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_SALT, (char*) salt, salt_size)) return log_openssl_errors("Failed to add KDF-SS OSSL_KDF_PARAM_SALT"); if (info) - if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_INFO, (char*) info, info_size)) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_INFO, (char*) info, info_size)) return log_openssl_errors("Failed to add KDF-SS OSSL_KDF_PARAM_INFO"); - _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld); + _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = sym_OSSL_PARAM_BLD_to_param(bld); if (!params) return log_openssl_errors("Failed to build KDF-SS OSSL_PARAM"); - if (EVP_KDF_derive(ctx, buf, derive_size, params) <= 0) + if (sym_EVP_KDF_derive(ctx, buf, derive_size, params) <= 0) return log_openssl_errors("OpenSSL KDF-SS derive failed"); *ret = TAKE_PTR(buf); @@ -413,6 +1054,8 @@ int kdf_kb_hmac_derive( size_t derive_size, void **ret) { + int r; + assert(mode); assert(strcaseeq(mode, "COUNTER") || strcaseeq(mode, "FEEDBACK")); assert(digest); @@ -423,44 +1066,48 @@ int kdf_kb_hmac_derive( assert(derive_size > 0); assert(ret); - _cleanup_(EVP_KDF_freep) EVP_KDF *kdf = EVP_KDF_fetch(NULL, "KBKDF", NULL); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(EVP_KDF_freep) EVP_KDF *kdf = sym_EVP_KDF_fetch(NULL, "KBKDF", NULL); if (!kdf) return log_openssl_errors("Failed to create new EVP_KDF"); - _cleanup_(EVP_KDF_CTX_freep) EVP_KDF_CTX *ctx = EVP_KDF_CTX_new(kdf); + _cleanup_(EVP_KDF_CTX_freep) EVP_KDF_CTX *ctx = sym_EVP_KDF_CTX_new(kdf); if (!ctx) return log_openssl_errors("Failed to create new EVP_KDF_CTX"); - _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); + _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = sym_OSSL_PARAM_BLD_new(); if (!bld) return log_openssl_errors("Failed to create new OSSL_PARAM_BLD"); - if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_MAC, (char*) "HMAC", 0)) + if (!sym_OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_MAC, (char*) "HMAC", 0)) return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_MAC"); - if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_MODE, (char*) mode, 0)) + if (!sym_OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_MODE, (char*) mode, 0)) return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_MODE"); - if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_DIGEST, (char*) digest, 0)) + if (!sym_OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_DIGEST, (char*) digest, 0)) return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_DIGEST"); if (key) - if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_KEY, (char*) key, key_size)) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_KEY, (char*) key, key_size)) return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_KEY"); if (salt) - if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_SALT, (char*) salt, salt_size)) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_SALT, (char*) salt, salt_size)) return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_SALT"); if (info) - if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_INFO, (char*) info, info_size)) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_INFO, (char*) info, info_size)) return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_INFO"); if (seed) - if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_SEED, (char*) seed, seed_size)) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_SEED, (char*) seed, seed_size)) return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_SEED"); - _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld); + _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = sym_OSSL_PARAM_BLD_to_param(bld); if (!params) return log_openssl_errors("Failed to build KDF-KB OSSL_PARAM"); @@ -468,7 +1115,7 @@ int kdf_kb_hmac_derive( if (!buf) return log_oom_debug(); - if (EVP_KDF_derive(ctx, buf, derive_size, params) <= 0) + if (sym_EVP_KDF_derive(ctx, buf, derive_size, params) <= 0) return log_openssl_errors("OpenSSL KDF-KB derive failed"); *ret = TAKE_PTR(buf); @@ -486,28 +1133,33 @@ int rsa_encrypt_bytes( _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = NULL; _cleanup_free_ void *b = NULL; size_t l; + int r; assert(ret_encrypt_key); assert(ret_encrypt_key_size); - ctx = EVP_PKEY_CTX_new(pkey, NULL); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + ctx = sym_EVP_PKEY_CTX_new(pkey, NULL); if (!ctx) return log_openssl_errors("Failed to allocate public key context"); - if (EVP_PKEY_encrypt_init(ctx) <= 0) + if (sym_EVP_PKEY_encrypt_init(ctx) <= 0) return log_openssl_errors("Failed to initialize public key context"); - if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0) + if (sym_EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0) return log_openssl_errors("Failed to configure PKCS#1 padding"); - if (EVP_PKEY_encrypt(ctx, NULL, &l, decrypted_key, decrypted_key_size) <= 0) + if (sym_EVP_PKEY_encrypt(ctx, NULL, &l, decrypted_key, decrypted_key_size) <= 0) return log_openssl_errors("Failed to determine encrypted key size"); b = malloc(l); if (!b) return -ENOMEM; - if (EVP_PKEY_encrypt(ctx, b, &l, decrypted_key, decrypted_key_size) <= 0) + if (sym_EVP_PKEY_encrypt(ctx, b, &l, decrypted_key, decrypted_key_size) <= 0) return log_openssl_errors("Failed to determine encrypted key size"); *ret_encrypt_key = TAKE_PTR(b); @@ -526,6 +1178,8 @@ int rsa_oaep_encrypt_bytes( void **ret_encrypt_key, size_t *ret_encrypt_key_size) { + int r; + assert(pkey); assert(digest_alg); assert(label); @@ -534,42 +1188,46 @@ int rsa_oaep_encrypt_bytes( assert(ret_encrypt_key); assert(ret_encrypt_key_size); - _cleanup_(EVP_MD_freep) EVP_MD *md = EVP_MD_fetch(NULL, digest_alg, NULL); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(EVP_MD_freep) EVP_MD *md = sym_EVP_MD_fetch(NULL, digest_alg, NULL); if (!md) return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Digest algorithm '%s' not supported.", digest_alg); - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new((EVP_PKEY*) pkey, NULL); + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = sym_EVP_PKEY_CTX_new((EVP_PKEY*) pkey, NULL); if (!ctx) return log_openssl_errors("Failed to create new EVP_PKEY_CTX"); - if (EVP_PKEY_encrypt_init(ctx) <= 0) + if (sym_EVP_PKEY_encrypt_init(ctx) <= 0) return log_openssl_errors("Failed to initialize EVP_PKEY_CTX"); - if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) + if (sym_EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) return log_openssl_errors("Failed to configure RSA-OAEP padding"); - if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx, md) <= 0) + if (sym_EVP_PKEY_CTX_set_rsa_oaep_md(ctx, md) <= 0) return log_openssl_errors("Failed to configure RSA-OAEP MD"); _cleanup_free_ char *duplabel = strdup(label); if (!duplabel) return log_oom_debug(); - if (EVP_PKEY_CTX_set0_rsa_oaep_label(ctx, duplabel, strlen(duplabel) + 1) <= 0) + if (sym_EVP_PKEY_CTX_set0_rsa_oaep_label(ctx, duplabel, strlen(duplabel) + 1) <= 0) return log_openssl_errors("Failed to configure RSA-OAEP label"); /* ctx owns this now, don't free */ TAKE_PTR(duplabel); size_t size = 0; - if (EVP_PKEY_encrypt(ctx, NULL, &size, decrypted_key, decrypted_key_size) <= 0) + if (sym_EVP_PKEY_encrypt(ctx, NULL, &size, decrypted_key, decrypted_key_size) <= 0) return log_openssl_errors("Failed to determine RSA-OAEP encrypted key size"); _cleanup_free_ void *buf = malloc(size); if (!buf) return log_oom_debug(); - if (EVP_PKEY_encrypt(ctx, buf, &size, decrypted_key, decrypted_key_size) <= 0) + if (sym_EVP_PKEY_encrypt(ctx, buf, &size, decrypted_key, decrypted_key_size) <= 0) return log_openssl_errors("Failed to RSA-OAEP encrypt"); *ret_encrypt_key = TAKE_PTR(buf); @@ -583,7 +1241,7 @@ int rsa_pkey_to_suitable_key_size( size_t *ret_suitable_key_size) { size_t suitable_key_size; - int bits; + int bits, r; assert(pkey); assert(ret_suitable_key_size); @@ -591,10 +1249,14 @@ int rsa_pkey_to_suitable_key_size( /* Analyzes the specified public key and that it is RSA. If so, will return a suitable size for a * disk encryption key to encrypt with RSA for use in PKCS#11 security token schemes. */ - if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA) + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + if (sym_EVP_PKEY_get_base_id(pkey) != EVP_PKEY_RSA) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "X.509 certificate does not refer to RSA key."); - bits = EVP_PKEY_bits(pkey); + bits = sym_EVP_PKEY_get_bits(pkey); log_debug("Bits in RSA key: %i", bits); /* We use PKCS#1 padding for the RSA cleartext, hence let's leave some extra space for it, hence only @@ -612,6 +1274,7 @@ int rsa_pkey_to_suitable_key_size( * in big-endian format, e.g. wrap it with htobe32() for uint32_t. */ int rsa_pkey_from_n_e(const void *n, size_t n_size, const void *e, size_t e_size, EVP_PKEY **ret) { _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + int r; assert(n); assert(n_size != 0); @@ -619,18 +1282,22 @@ int rsa_pkey_from_n_e(const void *n, size_t n_size, const void *e, size_t e_size assert(e_size != 0); assert(ret); - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = sym_EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); if (!ctx) return log_openssl_errors("Failed to create new EVP_PKEY_CTX"); - if (EVP_PKEY_fromdata_init(ctx) <= 0) + if (sym_EVP_PKEY_fromdata_init(ctx) <= 0) return log_openssl_errors("Failed to initialize EVP_PKEY_CTX"); OSSL_PARAM params[3]; #if __BYTE_ORDER == __BIG_ENDIAN - params[0] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_N, (void*)n, n_size); - params[1] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_E, (void*)e, e_size); + params[0] = sym_OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_N, (void*)n, n_size); + params[1] = sym_OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_E, (void*)e, e_size); #else _cleanup_free_ void *native_n = memdup_reverse(n, n_size); if (!native_n) @@ -640,12 +1307,12 @@ int rsa_pkey_from_n_e(const void *n, size_t n_size, const void *e, size_t e_size if (!native_e) return log_oom_debug(); - params[0] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_N, native_n, n_size); - params[1] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_E, native_e, e_size); + params[0] = sym_OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_N, native_n, n_size); + params[1] = sym_OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_E, native_e, e_size); #endif - params[2] = OSSL_PARAM_construct_end(); + params[2] = sym_OSSL_PARAM_construct_end(); - if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) + if (sym_EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) return log_openssl_errors("Failed to create RSA EVP_PKEY"); *ret = TAKE_PTR(pkey); @@ -661,27 +1328,33 @@ int rsa_pkey_to_n_e( void **ret_e, size_t *ret_e_size) { + int r; + assert(pkey); assert(ret_n); assert(ret_n_size); assert(ret_e); assert(ret_e_size); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + _cleanup_(BN_freep) BIGNUM *bn_n = NULL; - if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &bn_n)) + if (!sym_EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &bn_n)) return log_openssl_errors("Failed to get RSA n"); _cleanup_(BN_freep) BIGNUM *bn_e = NULL; - if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &bn_e)) + if (!sym_EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &bn_e)) return log_openssl_errors("Failed to get RSA e"); - size_t n_size = BN_num_bytes(bn_n), e_size = BN_num_bytes(bn_e); + size_t n_size = sym_BN_num_bytes(bn_n), e_size = sym_BN_num_bytes(bn_e); _cleanup_free_ void *n = malloc(n_size), *e = malloc(e_size); if (!n || !e) return log_oom_debug(); - assert(BN_bn2bin(bn_n, n) == (int) n_size); - assert(BN_bn2bin(bn_e, e) == (int) e_size); + assert(sym_BN_bn2bin(bn_n, n) == (int) n_size); + assert(sym_BN_bn2bin(bn_e, e) == (int) e_size); *ret_n = TAKE_PTR(n); *ret_n_size = n_size; @@ -700,58 +1373,64 @@ int ecc_pkey_from_curve_x_y( size_t y_size, EVP_PKEY **ret) { + int r; + assert(x); assert(y); assert(ret); - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = sym_EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); if (!ctx) return log_openssl_errors("Failed to create new EVP_PKEY_CTX"); - _cleanup_(BN_freep) BIGNUM *bn_x = BN_bin2bn(x, x_size, NULL); + _cleanup_(BN_freep) BIGNUM *bn_x = sym_BN_bin2bn(x, x_size, NULL); if (!bn_x) return log_openssl_errors("Failed to create BIGNUM x"); - _cleanup_(BN_freep) BIGNUM *bn_y = BN_bin2bn(y, y_size, NULL); + _cleanup_(BN_freep) BIGNUM *bn_y = sym_BN_bin2bn(y, y_size, NULL); if (!bn_y) return log_openssl_errors("Failed to create BIGNUM y"); - _cleanup_(EC_GROUP_freep) EC_GROUP *group = EC_GROUP_new_by_curve_name(curve_id); + _cleanup_(EC_GROUP_freep) EC_GROUP *group = sym_EC_GROUP_new_by_curve_name(curve_id); if (!group) return log_openssl_errors("ECC curve id %d not supported", curve_id); - _cleanup_(EC_POINT_freep) EC_POINT *point = EC_POINT_new(group); + _cleanup_(EC_POINT_freep) EC_POINT *point = sym_EC_POINT_new(group); if (!point) return log_openssl_errors("Failed to create new EC_POINT"); - if (!EC_POINT_set_affine_coordinates(group, point, bn_x, bn_y, NULL)) + if (!sym_EC_POINT_set_affine_coordinates(group, point, bn_x, bn_y, NULL)) return log_openssl_errors("Failed to set ECC coordinates"); - if (EVP_PKEY_fromdata_init(ctx) <= 0) + if (sym_EVP_PKEY_fromdata_init(ctx) <= 0) return log_openssl_errors("Failed to initialize EVP_PKEY_CTX"); - _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); + _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = sym_OSSL_PARAM_BLD_new(); if (!bld) return log_openssl_errors("Failed to create new OSSL_PARAM_BLD"); - if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_GROUP_NAME, (char*) OSSL_EC_curve_nid2name(curve_id), 0)) + if (!sym_OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_GROUP_NAME, (char*) sym_OSSL_EC_curve_nid2name(curve_id), 0)) return log_openssl_errors("Failed to add ECC OSSL_PKEY_PARAM_GROUP_NAME"); _cleanup_(OPENSSL_freep) void *pbuf = NULL; size_t pbuf_len = 0; - pbuf_len = EC_POINT_point2buf(group, point, POINT_CONVERSION_UNCOMPRESSED, (unsigned char**) &pbuf, NULL); + pbuf_len = sym_EC_POINT_point2buf(group, point, POINT_CONVERSION_UNCOMPRESSED, (unsigned char**) &pbuf, NULL); if (pbuf_len == 0) return log_openssl_errors("Failed to convert ECC point to buffer"); - if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, pbuf, pbuf_len)) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, pbuf, pbuf_len)) return log_openssl_errors("Failed to add ECC OSSL_PKEY_PARAM_PUB_KEY"); - _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld); + _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = sym_OSSL_PARAM_BLD_to_param(bld); if (!params) return log_openssl_errors("Failed to build ECC OSSL_PARAM"); _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; - if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) + if (sym_EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) return log_openssl_errors("Failed to create ECC EVP_PKEY"); *ret = TAKE_PTR(pkey); @@ -767,38 +1446,42 @@ int ecc_pkey_to_curve_x_y( size_t *ret_y_size) { _cleanup_(BN_freep) BIGNUM *bn_x = NULL, *bn_y = NULL; - int curve_id; + int curve_id, r; assert(pkey); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + size_t name_size; - if (!EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, NULL, 0, &name_size)) + if (!sym_EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, NULL, 0, &name_size)) return log_openssl_errors("Failed to get ECC group name size"); _cleanup_free_ char *name = new(char, name_size + 1); if (!name) return log_oom_debug(); - if (!EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, name, name_size + 1, NULL)) + if (!sym_EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, name, name_size + 1, NULL)) return log_openssl_errors("Failed to get ECC group name"); - curve_id = OBJ_sn2nid(name); + curve_id = sym_OBJ_sn2nid(name); if (curve_id == NID_undef) return log_openssl_errors("Failed to get ECC curve id"); - if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_X, &bn_x)) + if (!sym_EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_X, &bn_x)) return log_openssl_errors("Failed to get ECC point x"); - if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_Y, &bn_y)) + if (!sym_EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_Y, &bn_y)) return log_openssl_errors("Failed to get ECC point y"); - size_t x_size = BN_num_bytes(bn_x), y_size = BN_num_bytes(bn_y); + size_t x_size = sym_BN_num_bytes(bn_x), y_size = sym_BN_num_bytes(bn_y); _cleanup_free_ void *x = malloc(x_size), *y = malloc(y_size); if (!x || !y) return log_oom_debug(); - assert(BN_bn2bin(bn_x, x) == (int) x_size); - assert(BN_bn2bin(bn_y, y) == (int) y_size); + assert(sym_BN_bn2bin(bn_x, x) == (int) x_size); + assert(sym_BN_bn2bin(bn_y, y) == (int) y_size); if (ret_curve_id) *ret_curve_id = curve_id; @@ -816,20 +1499,26 @@ int ecc_pkey_to_curve_x_y( /* Generate a new ECC key for the specified ECC curve id. */ int ecc_pkey_new(int curve_id, EVP_PKEY **ret) { + int r; + assert(ret); - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = sym_EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); if (!ctx) return log_openssl_errors("Failed to create new EVP_PKEY_CTX"); - if (EVP_PKEY_keygen_init(ctx) <= 0) + if (sym_EVP_PKEY_keygen_init(ctx) <= 0) return log_openssl_errors("Failed to initialize EVP_PKEY_CTX"); - if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, curve_id) <= 0) + if (sym_EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, curve_id) <= 0) return log_openssl_errors("Failed to set ECC curve %d", curve_id); _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; - if (EVP_PKEY_keygen(ctx, &pkey) <= 0) + if (sym_EVP_PKEY_keygen(ctx, &pkey) <= 0) return log_openssl_errors("Failed to generate ECC key"); *ret = TAKE_PTR(pkey); @@ -847,30 +1536,36 @@ int ecc_ecdh(const EVP_PKEY *private_pkey, void **ret_shared_secret, size_t *ret_shared_secret_size) { + int r; + assert(private_pkey); assert(peer_pkey); assert(ret_shared_secret); assert(ret_shared_secret_size); - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new((EVP_PKEY*) private_pkey, NULL); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = sym_EVP_PKEY_CTX_new((EVP_PKEY*) private_pkey, NULL); if (!ctx) return log_openssl_errors("Failed to create new EVP_PKEY_CTX"); - if (EVP_PKEY_derive_init(ctx) <= 0) + if (sym_EVP_PKEY_derive_init(ctx) <= 0) return log_openssl_errors("Failed to initialize EVP_PKEY_CTX"); - if (EVP_PKEY_derive_set_peer(ctx, (EVP_PKEY*) peer_pkey) <= 0) + if (sym_EVP_PKEY_derive_set_peer(ctx, (EVP_PKEY*) peer_pkey) <= 0) return log_openssl_errors("Failed to set ECC derive peer"); size_t shared_secret_size; - if (EVP_PKEY_derive(ctx, NULL, &shared_secret_size) <= 0) + if (sym_EVP_PKEY_derive(ctx, NULL, &shared_secret_size) <= 0) return log_openssl_errors("Failed to get ECC shared secret size"); _cleanup_(erase_and_freep) void *shared_secret = malloc(shared_secret_size); if (!shared_secret) return log_oom_debug(); - if (EVP_PKEY_derive(ctx, (unsigned char*) shared_secret, &shared_secret_size) <= 0) + if (sym_EVP_PKEY_derive(ctx, (unsigned char*) shared_secret, &shared_secret_size) <= 0) return log_openssl_errors("Failed to derive ECC shared secret"); *ret_shared_secret = TAKE_PTR(shared_secret); @@ -885,6 +1580,7 @@ int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_s int sz, lsz, msz; unsigned umsz; unsigned char *dd; + int r; /* Calculates a message digest of the DER encoded public key */ @@ -893,7 +1589,11 @@ int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_s assert(ret); assert(ret_size); - sz = i2d_PublicKey(pk, NULL); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + sz = sym_i2d_PublicKey(pk, NULL); if (sz < 0) return log_openssl_errors("Unable to convert public key to DER format"); @@ -901,21 +1601,21 @@ int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_s if (!d) return log_oom_debug(); - lsz = i2d_PublicKey(pk, &dd); + lsz = sym_i2d_PublicKey(pk, &dd); if (lsz < 0) return log_openssl_errors("Unable to convert public key to DER format"); - m = EVP_MD_CTX_new(); + m = sym_EVP_MD_CTX_new(); if (!m) return log_openssl_errors("Failed to create new EVP_MD_CTX"); - if (EVP_DigestInit_ex(m, md, NULL) != 1) - return log_openssl_errors("Failed to initialize %s context", EVP_MD_name(md)); + if (sym_EVP_DigestInit_ex(m, md, NULL) != 1) + return log_openssl_errors("Failed to initialize %s context", sym_EVP_MD_get0_name(md)); - if (EVP_DigestUpdate(m, d, lsz) != 1) - return log_openssl_errors("Failed to run %s context", EVP_MD_name(md)); + if (sym_EVP_DigestUpdate(m, d, lsz) != 1) + return log_openssl_errors("Failed to run %s context", sym_EVP_MD_get0_name(md)); - msz = EVP_MD_size(md); + msz = sym_EVP_MD_get_size(md); assert(msz > 0); h = malloc(msz); @@ -923,7 +1623,7 @@ int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_s return log_oom_debug(); umsz = msz; - if (EVP_DigestFinal_ex(m, h, &umsz) != 1) + if (sym_EVP_DigestFinal_ex(m, h, &umsz) != 1) return log_openssl_errors("Failed to finalize hash context"); assert(umsz == (unsigned) msz); @@ -946,6 +1646,10 @@ int digest_and_sign( assert(ret); assert(ret_size); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + if (size == 0) data = ""; /* make sure to pass a valid pointer to OpenSSL */ else { @@ -955,28 +1659,28 @@ int digest_and_sign( size = strlen(data); } - _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* mdctx = sym_EVP_MD_CTX_new(); if (!mdctx) return log_openssl_errors("Failed to create new EVP_MD_CTX"); - if (EVP_DigestSignInit(mdctx, NULL, md, NULL, privkey) != 1) { + if (sym_EVP_DigestSignInit(mdctx, NULL, md, NULL, privkey) != 1) { /* Distro security policies often disable support for SHA-1. Let's return a recognizable * error for that case. */ - bool invalid_digest = ERR_GET_REASON(ERR_peek_last_error()) == EVP_R_INVALID_DIGEST; + bool invalid_digest = ERR_GET_REASON(sym_ERR_peek_last_error()) == EVP_R_INVALID_DIGEST; r = log_openssl_errors("Failed to initialize signature context"); return invalid_digest ? -EADDRNOTAVAIL : r; } /* Determine signature size */ size_t ss; - if (EVP_DigestSign(mdctx, NULL, &ss, data, size) != 1) + if (sym_EVP_DigestSign(mdctx, NULL, &ss, data, size) != 1) return log_openssl_errors("Failed to determine size of signature"); _cleanup_free_ void *sig = malloc(ss); if (!sig) return log_oom_debug(); - if (EVP_DigestSign(mdctx, sig, &ss, data, size) != 1) + if (sym_EVP_DigestSign(mdctx, sig, &ss, data, size) != 1) return log_openssl_errors("Failed to sign data"); *ret = TAKE_PTR(sig); @@ -985,6 +1689,8 @@ int digest_and_sign( } int pkcs7_new(X509 *certificate, EVP_PKEY *private_key, const char *hash_algorithm, PKCS7 **ret_p7, PKCS7_SIGNER_INFO **ret_si) { + int r; + assert(certificate); assert(ret_p7); @@ -994,67 +1700,71 @@ int pkcs7_new(X509 *certificate, EVP_PKEY *private_key, const char *hash_algorit * copied into the signer info's "enc_digest" field. If the signing hash algorithm is not provided, * SHA-256 is used. */ - _cleanup_(PKCS7_freep) PKCS7 *p7 = PKCS7_new(); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(PKCS7_freep) PKCS7 *p7 = sym_PKCS7_new(); if (!p7) return log_oom(); - if (PKCS7_set_type(p7, NID_pkcs7_signed) == 0) + if (sym_PKCS7_set_type(p7, NID_pkcs7_signed) == 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS7 type: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (PKCS7_content_new(p7, NID_pkcs7_data) == 0) + if (sym_PKCS7_content_new(p7, NID_pkcs7_data) == 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS7 content: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (PKCS7_add_certificate(p7, certificate) == 0) + if (sym_PKCS7_add_certificate(p7, certificate) == 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS7 certificate: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); int x509_pknid = 0; - if (X509_get_signature_info(certificate, NULL, &x509_pknid, NULL, NULL) == 0) + if (sym_X509_get_signature_info(certificate, NULL, &x509_pknid, NULL, NULL) == 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get X509 digest NID: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - const EVP_MD *md = EVP_get_digestbyname(hash_algorithm ?: "SHA256"); + const EVP_MD *md = sym_EVP_get_digestbyname(hash_algorithm ?: "SHA256"); if (!md) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get digest algorithm '%s'", hash_algorithm ?: "SHA256"); - _cleanup_(PKCS7_SIGNER_INFO_freep) PKCS7_SIGNER_INFO *si = PKCS7_SIGNER_INFO_new(); + _cleanup_(PKCS7_SIGNER_INFO_freep) PKCS7_SIGNER_INFO *si = sym_PKCS7_SIGNER_INFO_new(); if (!si) return log_oom(); if (private_key) { - if (PKCS7_SIGNER_INFO_set(si, certificate, private_key, md) <= 0) + if (sym_PKCS7_SIGNER_INFO_set(si, certificate, private_key, md) <= 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to configure signer info: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); } else { - if (ASN1_INTEGER_set(si->version, 1) == 0) + if (sym_ASN1_INTEGER_set(si->version, 1) == 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set signer info version: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (X509_NAME_set(&si->issuer_and_serial->issuer, X509_get_issuer_name(certificate)) == 0) + if (sym_X509_NAME_set(&si->issuer_and_serial->issuer, sym_X509_get_issuer_name(certificate)) == 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set signer info issuer: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - ASN1_INTEGER_free(si->issuer_and_serial->serial); - si->issuer_and_serial->serial = ASN1_INTEGER_dup(X509_get0_serialNumber(certificate)); + sym_ASN1_INTEGER_free(si->issuer_and_serial->serial); + si->issuer_and_serial->serial = sym_ASN1_INTEGER_dup(sym_X509_get0_serialNumber(certificate)); if (!si->issuer_and_serial->serial) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set signer info serial: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (X509_ALGOR_set0(si->digest_alg, OBJ_nid2obj(EVP_MD_type(md)), V_ASN1_NULL, NULL) == 0) + if (sym_X509_ALGOR_set0(si->digest_alg, sym_OBJ_nid2obj(sym_EVP_MD_get_type(md)), V_ASN1_NULL, NULL) == 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set signer info digest algorithm: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); - if (X509_ALGOR_set0(si->digest_enc_alg, OBJ_nid2obj(x509_pknid), V_ASN1_NULL, NULL) == 0) + if (sym_X509_ALGOR_set0(si->digest_enc_alg, sym_OBJ_nid2obj(x509_pknid), V_ASN1_NULL, NULL) == 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set signer info signing algorithm: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); } - if (PKCS7_add_signer(p7, si) == 0) + if (sym_PKCS7_add_signer(p7, si) == 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS7 signer info: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); *ret_p7 = TAKE_PTR(p7); if (ret_si) @@ -1114,7 +1824,11 @@ static int ecc_pkey_generate_volume_keys( _cleanup_free_ char *curve_name = NULL; size_t len = 0; - if (EVP_PKEY_get_group_name(pkey, NULL, 0, &len) != 1 || len == 0) + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + if (sym_EVP_PKEY_get_group_name(pkey, NULL, 0, &len) != 1 || len == 0) return log_openssl_errors("Failed to determine PKEY group name length"); len++; @@ -1122,10 +1836,10 @@ static int ecc_pkey_generate_volume_keys( if (!curve_name) return log_oom_debug(); - if (EVP_PKEY_get_group_name(pkey, curve_name, len, &len) != 1) + if (sym_EVP_PKEY_get_group_name(pkey, curve_name, len, &len) != 1) return log_openssl_errors("Failed to get PKEY group name"); - r = ecc_pkey_new(OBJ_sn2nid(curve_name), &pkey_new); + r = ecc_pkey_new(sym_OBJ_sn2nid(curve_name), &pkey_new); if (r < 0) return log_debug_errno(r, "Failed to generate a new EC keypair: %m"); @@ -1135,7 +1849,7 @@ static int ecc_pkey_generate_volume_keys( /* EVP_PKEY_get1_encoded_public_key() always returns uncompressed format of EC points. See https://github.com/openssl/openssl/discussions/22835 */ - saved_key_size = EVP_PKEY_get1_encoded_public_key(pkey_new, &saved_key); + saved_key_size = sym_EVP_PKEY_get1_encoded_public_key(pkey_new, &saved_key); if (saved_key_size == 0) return log_openssl_errors("Failed to convert the generated public key to SEC1 format"); @@ -1195,13 +1909,19 @@ int pkey_generate_volume_keys( void **ret_saved_key, size_t *ret_saved_key_size) { + int r; + assert(pkey); assert(ret_decrypted_key); assert(ret_decrypted_key_size); assert(ret_saved_key); assert(ret_saved_key_size); - int type = EVP_PKEY_get_base_id(pkey); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + int type = sym_EVP_PKEY_get_base_id(pkey); switch (type) { case EVP_PKEY_RSA: @@ -1214,7 +1934,7 @@ int pkey_generate_volume_keys( return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine a type of public key."); default: - return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported public key type: %s", OBJ_nid2sn(type)); + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported public key type: %s", sym_OBJ_nid2sn(type)); } } @@ -1224,18 +1944,24 @@ static int load_key_from_provider( UI_METHOD *ui_method, EVP_PKEY **ret) { + int r; + assert(provider); assert(private_key_uri); assert(ret); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + /* Load the provider so that this can work without any custom written configuration in /etc/. * Also load the 'default' as that seems to be the recommendation. */ - if (!OSSL_PROVIDER_try_load(/* ctx= */ NULL, provider, /* retain_fallbacks= */ true)) + if (!sym_OSSL_PROVIDER_try_load(/* ctx= */ NULL, provider, /* retain_fallbacks= */ true)) return log_openssl_errors("Failed to load OpenSSL provider '%s'", provider); - if (!OSSL_PROVIDER_try_load(/* ctx= */ NULL, "default", /* retain_fallbacks= */ true)) + if (!sym_OSSL_PROVIDER_try_load(/* ctx= */ NULL, "default", /* retain_fallbacks= */ true)) return log_openssl_errors("Failed to load OpenSSL provider 'default'"); - _cleanup_(OSSL_STORE_closep) OSSL_STORE_CTX *store = OSSL_STORE_open( + _cleanup_(OSSL_STORE_closep) OSSL_STORE_CTX *store = sym_OSSL_STORE_open( private_key_uri, ui_method, /* ui_data= */ NULL, @@ -1244,14 +1970,14 @@ static int load_key_from_provider( if (!store) return log_openssl_errors("Failed to open OpenSSL store via '%s'", private_key_uri); - if (OSSL_STORE_expect(store, OSSL_STORE_INFO_PKEY) == 0) + if (sym_OSSL_STORE_expect(store, OSSL_STORE_INFO_PKEY) == 0) return log_openssl_errors("Failed to filter store by private keys"); - _cleanup_(OSSL_STORE_INFO_freep) OSSL_STORE_INFO *info = OSSL_STORE_load(store); + _cleanup_(OSSL_STORE_INFO_freep) OSSL_STORE_INFO *info = sym_OSSL_STORE_load(store); if (!info) return log_openssl_errors("Failed to load OpenSSL store via '%s'", private_key_uri); - _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = OSSL_STORE_INFO_get1_PKEY(info); + _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = sym_OSSL_STORE_INFO_get1_PKEY(info); if (!private_key) return log_openssl_errors("Failed to load private key via '%s'", private_key_uri); @@ -1261,20 +1987,28 @@ static int load_key_from_provider( } static int load_key_from_engine(const char *engine, const char *private_key_uri, UI_METHOD *ui_method, EVP_PKEY **ret) { +#if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) + int r; +#endif + assert(engine); assert(private_key_uri); assert(ret); #if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + DISABLE_WARNING_DEPRECATED_DECLARATIONS; - _cleanup_(ENGINE_freep) ENGINE *e = ENGINE_by_id(engine); + _cleanup_(ENGINE_freep) ENGINE *e = sym_ENGINE_by_id(engine); if (!e) return log_openssl_errors("Failed to load signing engine '%s'", engine); - if (ENGINE_init(e) == 0) + if (sym_ENGINE_init(e) == 0) return log_openssl_errors("Failed to initialize signing engine '%s'", engine); - _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = ENGINE_load_private_key(e, private_key_uri, ui_method, /* callback_data= */ NULL); + _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = sym_ENGINE_load_private_key(e, private_key_uri, ui_method, /* callback_data= */ NULL); if (!private_key) return log_openssl_errors("Failed to load private key from '%s'", private_key_uri); REENABLE_WARNING; @@ -1291,14 +2025,14 @@ static int load_key_from_engine(const char *engine, const char *private_key_uri, static int openssl_ask_password_ui_read(UI *ui, UI_STRING *uis) { int r; - switch(UI_get_string_type(uis)) { + switch(sym_UI_get_string_type(uis)) { case UIT_PROMPT: { /* If no ask password request was configured use the default openssl UI. */ - AskPasswordRequest *req = (AskPasswordRequest*) UI_method_get_ex_data(UI_get_method(ui), 0); + AskPasswordRequest *req = (AskPasswordRequest*) sym_UI_method_get_ex_data(sym_UI_get_method(ui), 0); if (!req) - return (UI_method_get_reader(UI_OpenSSL()))(ui, uis); + return (sym_UI_method_get_reader(sym_UI_OpenSSL()))(ui, uis); - req->message = UI_get0_output_string(uis); + req->message = sym_UI_get0_output_string(uis); _cleanup_strv_free_ char **l = NULL; r = ask_password_auto(req, ASK_PASSWORD_ACCEPT_CACHED|ASK_PASSWORD_PUSH_CACHE, &l); @@ -1312,7 +2046,7 @@ static int openssl_ask_password_ui_read(UI *ui, UI_STRING *uis) { return 0; } - if (UI_set_result(ui, uis, *l) != 0) { + if (sym_UI_set_result(ui, uis, *l) != 0) { log_openssl_errors("Failed to set user interface result"); return 0; } @@ -1320,7 +2054,7 @@ static int openssl_ask_password_ui_read(UI *ui, UI_STRING *uis) { return 1; } default: - return (UI_method_get_reader(UI_OpenSSL()))(ui, uis); + return (sym_UI_method_get_reader(sym_UI_OpenSSL()))(ui, uis); } } #endif @@ -1335,6 +2069,10 @@ static int openssl_load_private_key_from_file(const char *path, EVP_PKEY **ret) assert(path); assert(ret); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + r = read_full_file_full( AT_FDCWD, path, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, @@ -1343,14 +2081,14 @@ static int openssl_load_private_key_from_file(const char *path, EVP_PKEY **ret) if (r < 0) return log_debug_errno(r, "Failed to read key file '%s': %m", path); - kb = BIO_new_mem_buf(rawkey, rawkeysz); + kb = sym_BIO_new_mem_buf(rawkey, rawkeysz); if (!kb) return log_oom_debug(); - pk = PEM_read_bio_PrivateKey(kb, NULL, NULL, NULL); + pk = sym_PEM_read_bio_PrivateKey(kb, NULL, NULL, NULL); if (!pk) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse PEM private key: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); *ret = TAKE_PTR(pk); @@ -1358,15 +2096,23 @@ static int openssl_load_private_key_from_file(const char *path, EVP_PKEY **ret) } static int openssl_ask_password_ui_new(const AskPasswordRequest *request, OpenSSLAskPasswordUI **ret) { +#ifndef OPENSSL_NO_UI_CONSOLE + int r; +#endif + assert(request); assert(ret); #ifndef OPENSSL_NO_UI_CONSOLE - _cleanup_(UI_destroy_methodp) UI_METHOD *method = UI_create_method("systemd-ask-password"); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(UI_destroy_methodp) UI_METHOD *method = sym_UI_create_method("systemd-ask-password"); if (!method) return log_openssl_errors("Failed to initialize openssl user interface"); - if (UI_method_set_reader(method, openssl_ask_password_ui_read) != 0) + if (sym_UI_method_set_reader(method, openssl_ask_password_ui_read) != 0) return log_openssl_errors("Failed to set openssl user interface reader"); OpenSSLAskPasswordUI *ui = new(OpenSSLAskPasswordUI, 1); @@ -1378,9 +2124,9 @@ static int openssl_ask_password_ui_new(const AskPasswordRequest *request, OpenSS .request = *request, }; - UI_set_default_method(ui->method); + sym_UI_set_default_method(ui->method); - if (UI_method_set_ex_data(ui->method, 0, &ui->request) == 0) + if (sym_UI_method_set_ex_data(ui->method, 0, &ui->request) == 0) return log_openssl_errors("Failed to set extra data for UI method"); *ret = TAKE_PTR(ui); @@ -1400,6 +2146,10 @@ static int load_x509_certificate_from_file(const char *path, X509 **ret) { assert(path); assert(ret); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + r = read_full_file_full( AT_FDCWD, path, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_CONNECT_SOCKET, @@ -1408,14 +2158,14 @@ static int load_x509_certificate_from_file(const char *path, X509 **ret) { if (r < 0) return log_debug_errno(r, "Failed to read certificate file '%s': %m", path); - cb = BIO_new_mem_buf(rawcert, rawcertsz); + cb = sym_BIO_new_mem_buf(rawcert, rawcertsz); if (!cb) return log_oom_debug(); - cert = PEM_read_bio_X509(cb, NULL, NULL, NULL); + cert = sym_PEM_read_bio_X509(cb, NULL, NULL, NULL); if (!cert) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse X.509 certificate: %s", - ERR_error_string(ERR_get_error(), NULL)); + sym_ERR_error_string(sym_ERR_get_error(), NULL)); *ret = TAKE_PTR(cert); @@ -1423,18 +2173,24 @@ static int load_x509_certificate_from_file(const char *path, X509 **ret) { } static int load_x509_certificate_from_provider(const char *provider, const char *certificate_uri, X509 **ret) { + int r; + assert(provider); assert(certificate_uri); assert(ret); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + /* Load the provider so that this can work without any custom written configuration in /etc/. * Also load the 'default' as that seems to be the recommendation. */ - if (!OSSL_PROVIDER_try_load(/* ctx= */ NULL, provider, /* retain_fallbacks= */ true)) + if (!sym_OSSL_PROVIDER_try_load(/* ctx= */ NULL, provider, /* retain_fallbacks= */ true)) return log_openssl_errors("Failed to load OpenSSL provider '%s'", provider); - if (!OSSL_PROVIDER_try_load(/* ctx= */ NULL, "default", /* retain_fallbacks= */ true)) + if (!sym_OSSL_PROVIDER_try_load(/* ctx= */ NULL, "default", /* retain_fallbacks= */ true)) return log_openssl_errors("Failed to load OpenSSL provider 'default'"); - _cleanup_(OSSL_STORE_closep) OSSL_STORE_CTX *store = OSSL_STORE_open( + _cleanup_(OSSL_STORE_closep) OSSL_STORE_CTX *store = sym_OSSL_STORE_open( certificate_uri, /* ui_method= */ NULL, /* ui_method= */ NULL, @@ -1443,14 +2199,14 @@ static int load_x509_certificate_from_provider(const char *provider, const char if (!store) return log_openssl_errors("Failed to open OpenSSL store via '%s'", certificate_uri); - if (OSSL_STORE_expect(store, OSSL_STORE_INFO_CERT) == 0) + if (sym_OSSL_STORE_expect(store, OSSL_STORE_INFO_CERT) == 0) return log_openssl_errors("Failed to filter store by X.509 certificates"); - _cleanup_(OSSL_STORE_INFO_freep) OSSL_STORE_INFO *info = OSSL_STORE_load(store); + _cleanup_(OSSL_STORE_INFO_freep) OSSL_STORE_INFO *info = sym_OSSL_STORE_load(store); if (!info) return log_openssl_errors("Failed to load OpenSSL store via '%s'", certificate_uri); - _cleanup_(X509_freep) X509 *cert = OSSL_STORE_INFO_get1_CERT(info); + _cleanup_(X509_freep) X509 *cert = sym_OSSL_STORE_INFO_get1_CERT(info); if (!cert) return log_openssl_errors("Failed to load certificate via '%s'", certificate_uri); @@ -1464,20 +2220,24 @@ OpenSSLAskPasswordUI* openssl_ask_password_ui_free(OpenSSLAskPasswordUI *ui) { return NULL; #ifndef OPENSSL_NO_UI_CONSOLE - assert(UI_get_default_method() == ui->method); - UI_set_default_method(UI_OpenSSL()); - UI_destroy_method(ui->method); + assert(sym_UI_get_default_method() == ui->method); + sym_UI_set_default_method(sym_UI_OpenSSL()); + sym_UI_destroy_method(ui->method); #endif return mfree(ui); } int x509_fingerprint(X509 *cert, uint8_t buffer[static SHA256_DIGEST_SIZE]) { _cleanup_free_ uint8_t *der = NULL; - int dersz; + int dersz, r; assert(cert); - dersz = i2d_X509(cert, &der); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + dersz = sym_i2d_X509(cert, &der); if (dersz < 0) return log_openssl_errors("Unable to convert PEM certificate to DER format"); @@ -1581,12 +2341,16 @@ int openssl_extract_public_key(EVP_PKEY *private_key, EVP_PKEY **ret) { assert(private_key); assert(ret); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + _cleanup_(memstream_done) MemStream m = {}; FILE *tf = memstream_init(&m); if (!tf) return -ENOMEM; - if (i2d_PUBKEY_fp(tf, private_key) != 1) + if (sym_i2d_PUBKEY_fp(tf, private_key) != 1) return -EIO; _cleanup_(erase_and_freep) char *buf = NULL; @@ -1596,7 +2360,7 @@ int openssl_extract_public_key(EVP_PKEY *private_key, EVP_PKEY **ret) { return r; const unsigned char *t = (const unsigned char*) buf; - if (!d2i_PUBKEY(ret, &t, len)) + if (!sym_d2i_PUBKEY(ret, &t, len)) return -EIO; return 0; diff --git a/src/shared/crypto-util.h b/src/shared/crypto-util.h new file mode 100644 index 0000000000000..08c5e1ac8511d --- /dev/null +++ b/src/shared/crypto-util.h @@ -0,0 +1,406 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" +#include "iovec-util.h" +#include "sha256.h" + +typedef enum CertificateSourceType { + OPENSSL_CERTIFICATE_SOURCE_FILE, + OPENSSL_CERTIFICATE_SOURCE_PROVIDER, + _OPENSSL_CERTIFICATE_SOURCE_MAX, + _OPENSSL_CERTIFICATE_SOURCE_INVALID = -EINVAL, +} CertificateSourceType; + +typedef enum KeySourceType { + OPENSSL_KEY_SOURCE_FILE, + OPENSSL_KEY_SOURCE_ENGINE, + OPENSSL_KEY_SOURCE_PROVIDER, + _OPENSSL_KEY_SOURCE_MAX, + _OPENSSL_KEY_SOURCE_INVALID = -EINVAL, +} KeySourceType; + +typedef struct OpenSSLAskPasswordUI OpenSSLAskPasswordUI; + +int parse_openssl_certificate_source_argument(const char *argument, char **certificate_source, CertificateSourceType *certificate_source_type); + +int parse_openssl_key_source_argument(const char *argument, char **private_key_source, KeySourceType *private_key_source_type); + +int dlopen_libcrypto(int log_level); + +#define X509_FINGERPRINT_SIZE SHA256_DIGEST_SIZE + +#if HAVE_OPENSSL +# include /* IWYU pragma: export */ +# include /* IWYU pragma: export */ +# include /* IWYU pragma: export */ +# include /* IWYU pragma: export */ +# include /* IWYU pragma: export */ +# include /* IWYU pragma: export */ +# include /* IWYU pragma: export */ +# include /* IWYU pragma: export */ +# include /* IWYU pragma: export */ +# include /* IWYU pragma: export */ +# include /* IWYU pragma: export */ +# include /* IWYU pragma: export */ + +# include "dlfcn-util.h" + +extern DLSYM_PROTOTYPE(ASN1_ANY_it); +extern DLSYM_PROTOTYPE(ASN1_BIT_STRING_it); +extern DLSYM_PROTOTYPE(ASN1_BMPSTRING_it); +extern DLSYM_PROTOTYPE(ASN1_BMPSTRING_new); +extern DLSYM_PROTOTYPE(ASN1_get_object); +extern DLSYM_PROTOTYPE(ASN1_IA5STRING_it); +extern DLSYM_PROTOTYPE(ASN1_item_d2i); +extern DLSYM_PROTOTYPE(ASN1_item_free); +extern DLSYM_PROTOTYPE(ASN1_item_i2d); +extern DLSYM_PROTOTYPE(ASN1_item_new); +extern DLSYM_PROTOTYPE(ASN1_OBJECT_it); +extern DLSYM_PROTOTYPE(ASN1_OCTET_STRING_free); +extern DLSYM_PROTOTYPE(ASN1_OCTET_STRING_it); +extern DLSYM_PROTOTYPE(ASN1_OCTET_STRING_set); +extern DLSYM_PROTOTYPE(ASN1_STRING_get0_data); +extern DLSYM_PROTOTYPE(ASN1_STRING_length); +extern DLSYM_PROTOTYPE(ASN1_STRING_new); +extern DLSYM_PROTOTYPE(ASN1_STRING_set); +extern DLSYM_PROTOTYPE(ASN1_STRING_set0); +extern DLSYM_PROTOTYPE(ASN1_TIME_free); +extern DLSYM_PROTOTYPE(ASN1_TIME_set); +extern DLSYM_PROTOTYPE(ASN1_TYPE_new); +extern DLSYM_PROTOTYPE(BIO_ctrl); +extern DLSYM_PROTOTYPE(BIO_find_type); +extern DLSYM_PROTOTYPE(BIO_free_all); +extern DLSYM_PROTOTYPE(BIO_free); +extern DLSYM_PROTOTYPE(BIO_new_mem_buf); +extern DLSYM_PROTOTYPE(BIO_new_socket); +extern DLSYM_PROTOTYPE(BIO_new); +extern DLSYM_PROTOTYPE(BIO_s_mem); +extern DLSYM_PROTOTYPE(BIO_write); +extern DLSYM_PROTOTYPE(BN_bin2bn); +extern DLSYM_PROTOTYPE(BN_bn2nativepad); +extern DLSYM_PROTOTYPE(BN_CTX_free); +extern DLSYM_PROTOTYPE(BN_CTX_new); +extern DLSYM_PROTOTYPE(BN_free); +extern DLSYM_PROTOTYPE(BN_new); +extern DLSYM_PROTOTYPE(BN_num_bits); +extern DLSYM_PROTOTYPE(CRYPTO_free); +extern DLSYM_PROTOTYPE(d2i_ASN1_OCTET_STRING); +extern DLSYM_PROTOTYPE(d2i_ECPKParameters); +extern DLSYM_PROTOTYPE(d2i_PKCS7); +extern DLSYM_PROTOTYPE(d2i_PUBKEY); +extern DLSYM_PROTOTYPE(d2i_X509); +extern DLSYM_PROTOTYPE(EC_GROUP_free); +extern DLSYM_PROTOTYPE(EC_GROUP_get_curve_name); +extern DLSYM_PROTOTYPE(EC_GROUP_get_curve); +extern DLSYM_PROTOTYPE(EC_GROUP_get_field_type); +extern DLSYM_PROTOTYPE(EC_GROUP_get0_generator); +extern DLSYM_PROTOTYPE(EC_GROUP_get0_order); +extern DLSYM_PROTOTYPE(EC_GROUP_new_by_curve_name); +extern DLSYM_PROTOTYPE(EC_POINT_free); +extern DLSYM_PROTOTYPE(EC_POINT_new); +extern DLSYM_PROTOTYPE(EC_POINT_oct2point); +extern DLSYM_PROTOTYPE(EC_POINT_point2oct); +extern DLSYM_PROTOTYPE(ECDSA_SIG_free); +extern DLSYM_PROTOTYPE(ERR_clear_error); +extern DLSYM_PROTOTYPE(ERR_error_string_n); +extern DLSYM_PROTOTYPE(ERR_error_string); +extern DLSYM_PROTOTYPE(ERR_get_error); +extern DLSYM_PROTOTYPE(EVP_aes_256_ctr); +extern DLSYM_PROTOTYPE(EVP_aes_256_gcm); +extern DLSYM_PROTOTYPE(EVP_CIPHER_CTX_ctrl); +extern DLSYM_PROTOTYPE(EVP_CIPHER_CTX_free); +extern DLSYM_PROTOTYPE(EVP_CIPHER_CTX_new); +extern DLSYM_PROTOTYPE(EVP_CIPHER_free); +extern DLSYM_PROTOTYPE(EVP_CIPHER_get_block_size); +extern DLSYM_PROTOTYPE(EVP_CIPHER_get_iv_length); +extern DLSYM_PROTOTYPE(EVP_CIPHER_get_key_length); +extern DLSYM_PROTOTYPE(EVP_DecryptFinal_ex); +extern DLSYM_PROTOTYPE(EVP_DecryptInit_ex); +extern DLSYM_PROTOTYPE(EVP_DecryptUpdate); +extern DLSYM_PROTOTYPE(EVP_Digest); +extern DLSYM_PROTOTYPE(EVP_DigestFinal_ex); +extern DLSYM_PROTOTYPE(EVP_DigestInit_ex); +extern DLSYM_PROTOTYPE(EVP_DigestUpdate); +extern DLSYM_PROTOTYPE(EVP_DigestVerify); +extern DLSYM_PROTOTYPE(EVP_DigestVerifyInit); +extern DLSYM_PROTOTYPE(EVP_EncryptFinal_ex); +extern DLSYM_PROTOTYPE(EVP_EncryptInit_ex); +extern DLSYM_PROTOTYPE(EVP_EncryptUpdate); +extern DLSYM_PROTOTYPE(EVP_get_cipherbyname); +extern DLSYM_PROTOTYPE(EVP_get_digestbyname); +extern DLSYM_PROTOTYPE(EVP_MAC_CTX_free); +extern DLSYM_PROTOTYPE(EVP_MAC_free); +extern DLSYM_PROTOTYPE(EVP_MD_CTX_free); +extern DLSYM_PROTOTYPE(EVP_MD_CTX_get0_md); +extern DLSYM_PROTOTYPE(EVP_MD_CTX_new); +extern DLSYM_PROTOTYPE(EVP_MD_CTX_set_pkey_ctx); +extern DLSYM_PROTOTYPE(EVP_MD_free); +extern DLSYM_PROTOTYPE(EVP_MD_get_size); +extern DLSYM_PROTOTYPE(EVP_MD_get0_name); +extern DLSYM_PROTOTYPE(EVP_PKEY_CTX_free); +extern DLSYM_PROTOTYPE(EVP_PKEY_CTX_new_from_name); +extern DLSYM_PROTOTYPE(EVP_PKEY_CTX_new_id); +extern DLSYM_PROTOTYPE(EVP_PKEY_CTX_new); +extern DLSYM_PROTOTYPE(EVP_PKEY_CTX_set_rsa_padding); +extern DLSYM_PROTOTYPE(EVP_PKEY_CTX_set_signature_md); +extern DLSYM_PROTOTYPE(EVP_PKEY_eq); +extern DLSYM_PROTOTYPE(EVP_PKEY_free); +extern DLSYM_PROTOTYPE(EVP_PKEY_fromdata_init); +extern DLSYM_PROTOTYPE(EVP_PKEY_fromdata); +extern DLSYM_PROTOTYPE(EVP_PKEY_get_id); +extern DLSYM_PROTOTYPE(EVP_PKEY_keygen_init); +extern DLSYM_PROTOTYPE(EVP_PKEY_keygen); +extern DLSYM_PROTOTYPE(EVP_PKEY_new_raw_public_key); +extern DLSYM_PROTOTYPE(EVP_PKEY_new); +extern DLSYM_PROTOTYPE(EVP_PKEY_verify_init); +extern DLSYM_PROTOTYPE(EVP_PKEY_verify); +extern DLSYM_PROTOTYPE(EVP_sha1); +extern DLSYM_PROTOTYPE(EVP_sha256); +extern DLSYM_PROTOTYPE(EVP_sha384); +extern DLSYM_PROTOTYPE(EVP_sha512); +extern DLSYM_PROTOTYPE(HMAC); +extern DLSYM_PROTOTYPE(i2d_ASN1_INTEGER); +extern DLSYM_PROTOTYPE(i2d_PKCS7_fp); +extern DLSYM_PROTOTYPE(i2d_PKCS7); +extern DLSYM_PROTOTYPE(i2d_PUBKEY); +extern DLSYM_PROTOTYPE(i2d_X509_NAME); +extern DLSYM_PROTOTYPE(i2d_X509); +extern DLSYM_PROTOTYPE(OBJ_nid2obj); +extern DLSYM_PROTOTYPE(OBJ_nid2sn); +extern DLSYM_PROTOTYPE(OBJ_sn2nid); +extern DLSYM_PROTOTYPE(OBJ_txt2obj); +extern DLSYM_PROTOTYPE(OPENSSL_sk_new_null); +extern DLSYM_PROTOTYPE(OPENSSL_sk_num); +extern DLSYM_PROTOTYPE(OPENSSL_sk_pop_free); +extern DLSYM_PROTOTYPE(OPENSSL_sk_push); +extern DLSYM_PROTOTYPE(OPENSSL_sk_value); +extern DLSYM_PROTOTYPE(OSSL_EC_curve_nid2name); +extern DLSYM_PROTOTYPE(OSSL_PARAM_construct_BN); +extern DLSYM_PROTOTYPE(OSSL_PARAM_construct_end); +extern DLSYM_PROTOTYPE(OSSL_PARAM_construct_octet_string); +extern DLSYM_PROTOTYPE(OSSL_PARAM_construct_utf8_string); +extern DLSYM_PROTOTYPE(OSSL_PARAM_free); +extern DLSYM_PROTOTYPE(PEM_read_PrivateKey); +extern DLSYM_PROTOTYPE(PEM_read_PUBKEY); +extern DLSYM_PROTOTYPE(PEM_read_X509); +extern DLSYM_PROTOTYPE(PEM_write_PrivateKey); +extern DLSYM_PROTOTYPE(PEM_write_PUBKEY); +extern DLSYM_PROTOTYPE(PEM_write_X509); +extern DLSYM_PROTOTYPE(PKCS5_PBKDF2_HMAC); +extern DLSYM_PROTOTYPE(PKCS7_add_attrib_content_type); +extern DLSYM_PROTOTYPE(PKCS7_add_attrib_smimecap); +extern DLSYM_PROTOTYPE(PKCS7_add_signed_attribute); +extern DLSYM_PROTOTYPE(PKCS7_add0_attrib_signing_time); +extern DLSYM_PROTOTYPE(PKCS7_add1_attrib_digest); +extern DLSYM_PROTOTYPE(PKCS7_ATTR_SIGN_it); +extern DLSYM_PROTOTYPE(PKCS7_content_new); +extern DLSYM_PROTOTYPE(PKCS7_ctrl); +extern DLSYM_PROTOTYPE(PKCS7_dataFinal); +extern DLSYM_PROTOTYPE(PKCS7_dataInit); +extern DLSYM_PROTOTYPE(PKCS7_free); +extern DLSYM_PROTOTYPE(PKCS7_get_signer_info); +extern DLSYM_PROTOTYPE(PKCS7_new); +extern DLSYM_PROTOTYPE(PKCS7_set_content); +extern DLSYM_PROTOTYPE(PKCS7_sign); +extern DLSYM_PROTOTYPE(PKCS7_SIGNER_INFO_free); +extern DLSYM_PROTOTYPE(PKCS7_verify); +extern DLSYM_PROTOTYPE(SHA1); +extern DLSYM_PROTOTYPE(SHA512); +extern DLSYM_PROTOTYPE(X509_ALGOR_free); +extern DLSYM_PROTOTYPE(X509_ATTRIBUTE_free); +extern DLSYM_PROTOTYPE(X509_free); +extern DLSYM_PROTOTYPE(X509_get_pubkey); +extern DLSYM_PROTOTYPE(X509_get_subject_name); +extern DLSYM_PROTOTYPE(X509_gmtime_adj); +extern DLSYM_PROTOTYPE(X509_NAME_free); +extern DLSYM_PROTOTYPE(X509_NAME_oneline); +extern DLSYM_PROTOTYPE(X509_VERIFY_PARAM_set_hostflags); +extern DLSYM_PROTOTYPE(X509_VERIFY_PARAM_set1_host); +extern DLSYM_PROTOTYPE(X509_VERIFY_PARAM_set1_ip); + +#if !defined(OPENSSL_NO_DEPRECATED_3_0) +DISABLE_WARNING_DEPRECATED_DECLARATIONS; +extern DLSYM_PROTOTYPE(ECDSA_SIG_new); +extern DLSYM_PROTOTYPE(ECDSA_SIG_set0); +extern DLSYM_PROTOTYPE(ECDSA_do_verify); +extern DLSYM_PROTOTYPE(EC_KEY_check_key); +extern DLSYM_PROTOTYPE(EC_KEY_free); +extern DLSYM_PROTOTYPE(EC_KEY_new); +extern DLSYM_PROTOTYPE(EC_KEY_set_group); +extern DLSYM_PROTOTYPE(EC_KEY_set_public_key); +extern DLSYM_PROTOTYPE(EVP_PKEY_assign); +extern DLSYM_PROTOTYPE(RSA_free); +extern DLSYM_PROTOTYPE(RSA_new); +extern DLSYM_PROTOTYPE(RSA_set0_key); +extern DLSYM_PROTOTYPE(RSA_size); +extern DLSYM_PROTOTYPE(RSAPublicKey_dup); +REENABLE_WARNING; + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EC_KEY*, sym_EC_KEY_free, EC_KEY_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(RSA*, sym_RSA_free, RSA_freep, NULL); +#endif + +/* Mirrors of OpenSSL macros that go through our dlopen'd sym_* variants, so we don't end up linking against + * libcrypto just for these. */ +#define sym_BIO_get_md_ctx(b, mdcp) sym_BIO_ctrl((b), BIO_C_GET_MD_CTX, 0, (char*) (mdcp)) +#define sym_BIO_get_mem_ptr(b, pp) sym_BIO_ctrl((b), BIO_C_GET_BUF_MEM_PTR, 0, (char *) (pp)) +#define sym_BIO_reset(b) sym_BIO_ctrl((b), BIO_CTRL_RESET, 0, NULL) +#define sym_BN_num_bytes(a) ((sym_BN_num_bits(a) + 7) / 8) +#define sym_EVP_MD_CTX_get_size(ctx) sym_EVP_MD_get_size(sym_EVP_MD_CTX_get0_md(ctx)) +#define sym_EVP_MD_CTX_get0_name(ctx) sym_EVP_MD_get0_name(sym_EVP_MD_CTX_get0_md(ctx)) +#define sym_EVP_PKEY_assign_RSA(pkey, rsa) sym_EVP_PKEY_assign((pkey), EVP_PKEY_RSA, (rsa)) +#define sym_OPENSSL_free(addr) sym_CRYPTO_free((addr), OPENSSL_FILE, OPENSSL_LINE) +#define sym_PKCS7_set_detached(p, v) sym_PKCS7_ctrl((p), PKCS7_OP_SET_DETACHED_SIGNATURE, (v), NULL) + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_MACRO_RENAME(void*, sym_OPENSSL_free, OPENSSL_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(ASN1_OCTET_STRING*, sym_ASN1_OCTET_STRING_free, ASN1_OCTET_STRING_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(ASN1_TIME*, sym_ASN1_TIME_free, ASN1_TIME_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(BIGNUM*, sym_BN_free, BN_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(BIO*, sym_BIO_free_all, BIO_free_allp, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(BIO*, sym_BIO_free, BIO_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(BN_CTX*, sym_BN_CTX_free, BN_CTX_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EC_GROUP*, sym_EC_GROUP_free, EC_GROUP_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EC_POINT*, sym_EC_POINT_free, EC_POINT_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(ECDSA_SIG*, sym_ECDSA_SIG_free, ECDSA_SIG_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EVP_CIPHER_CTX*, sym_EVP_CIPHER_CTX_free, EVP_CIPHER_CTX_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EVP_CIPHER*, sym_EVP_CIPHER_free, EVP_CIPHER_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EVP_MAC_CTX*, sym_EVP_MAC_CTX_free, EVP_MAC_CTX_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EVP_MAC*, sym_EVP_MAC_free, EVP_MAC_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EVP_MD_CTX*, sym_EVP_MD_CTX_free, EVP_MD_CTX_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EVP_MD*, sym_EVP_MD_free, EVP_MD_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EVP_PKEY_CTX*, sym_EVP_PKEY_CTX_free, EVP_PKEY_CTX_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EVP_PKEY*, sym_EVP_PKEY_free, EVP_PKEY_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(OSSL_PARAM*, sym_OSSL_PARAM_free, OSSL_PARAM_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(PKCS7_SIGNER_INFO*, sym_PKCS7_SIGNER_INFO_free, PKCS7_SIGNER_INFO_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(PKCS7*, sym_PKCS7_free, PKCS7_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(X509_NAME*, sym_X509_NAME_free, X509_NAME_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(X509*, sym_X509_free, X509_freep, NULL); + +/* Stack-of macros that go through the dlopen'd sym_OPENSSL_sk_* variants, mirroring the sk_TYPE_OP() helpers + * from and friends. */ +#define sym_sk_X509_new_null() \ + ((STACK_OF(X509)*) sym_OPENSSL_sk_new_null()) +#define sym_sk_X509_push(sk, ptr) \ + sym_OPENSSL_sk_push(ossl_check_X509_sk_type(sk), ossl_check_X509_type(ptr)) +#define sym_sk_X509_pop_free(sk, freefunc) \ + sym_OPENSSL_sk_pop_free(ossl_check_X509_sk_type(sk), ossl_check_X509_freefunc_type(freefunc)) +#define sym_sk_X509_ALGOR_pop_free(sk, freefunc) \ + sym_OPENSSL_sk_pop_free(ossl_check_X509_ALGOR_sk_type(sk), ossl_check_X509_ALGOR_freefunc_type(freefunc)) +#define sym_sk_X509_ATTRIBUTE_pop_free(sk, freefunc) \ + sym_OPENSSL_sk_pop_free(ossl_check_X509_ATTRIBUTE_sk_type(sk), ossl_check_X509_ATTRIBUTE_freefunc_type(freefunc)) +#define sym_sk_PKCS7_SIGNER_INFO_num(sk) \ + sym_OPENSSL_sk_num(ossl_check_const_PKCS7_SIGNER_INFO_sk_type(sk)) +#define sym_sk_PKCS7_SIGNER_INFO_value(sk, idx) \ + ((PKCS7_SIGNER_INFO*) sym_OPENSSL_sk_value(ossl_check_const_PKCS7_SIGNER_INFO_sk_type(sk), (idx))) + +static inline STACK_OF(X509_ALGOR) *x509_algor_free_many(STACK_OF(X509_ALGOR) *attrs) { + if (!attrs) + return NULL; + + sym_sk_X509_ALGOR_pop_free(attrs, sym_X509_ALGOR_free); + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(STACK_OF(X509_ALGOR)*, x509_algor_free_many, NULL); + +static inline STACK_OF(X509_ATTRIBUTE) *x509_attribute_free_many(STACK_OF(X509_ATTRIBUTE) *attrs) { + if (!attrs) + return NULL; + + sym_sk_X509_ATTRIBUTE_pop_free(attrs, sym_X509_ATTRIBUTE_free); + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(STACK_OF(X509_ATTRIBUTE)*, x509_attribute_free_many, NULL); + +static inline void sk_X509_free_allp(STACK_OF(X509) **sk) { + if (!sk || !*sk) + return; + + sym_sk_X509_pop_free(*sk, sym_X509_free); +} + +int openssl_pubkey_from_pem(const void *pem, size_t pem_size, EVP_PKEY **ret); +int openssl_pubkey_to_pem(EVP_PKEY *pkey, char **ret); + +int openssl_digest_size(const char *digest_alg, size_t *ret_digest_size); + +int openssl_digest_many(const char *digest_alg, const struct iovec data[], size_t n_data, void **ret_digest, size_t *ret_digest_size); + +static inline int openssl_digest(const char *digest_alg, const void *buf, size_t len, void **ret_digest, size_t *ret_digest_size) { + return openssl_digest_many(digest_alg, &IOVEC_MAKE((void*) buf, len), 1, ret_digest, ret_digest_size); +} + +int openssl_hmac_many(const char *digest_alg, const void *key, size_t key_size, const struct iovec data[], size_t n_data, void **ret_digest, size_t *ret_digest_size); + +static inline int openssl_hmac(const char *digest_alg, const void *key, size_t key_size, const void *buf, size_t len, void **ret_digest, size_t *ret_digest_size) { + return openssl_hmac_many(digest_alg, key, key_size, &IOVEC_MAKE((void*) buf, len), 1, ret_digest, ret_digest_size); +} + +int openssl_cipher_many(const char *alg, size_t bits, const char *mode, const void *key, size_t key_size, const void *iv, size_t iv_size, const struct iovec data[], size_t n_data, void **ret, size_t *ret_size); + +static inline int openssl_cipher(const char *alg, size_t bits, const char *mode, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *buf, size_t len, void **ret, size_t *ret_size) { + return openssl_cipher_many(alg, bits, mode, key, key_size, iv, iv_size, &IOVEC_MAKE((void*) buf, len), 1, ret, ret_size); +} + +int kdf_ss_derive(const char *digest, const void *key, size_t key_size, const void *salt, size_t salt_size, const void *info, size_t info_size, size_t derive_size, void **ret); + +int kdf_kb_hmac_derive(const char *mode, const char *digest, const void *key, size_t key_size, const void *salt, size_t salt_size, const void *info, size_t info_size, const void *seed, size_t seed_size, size_t derive_size, void **ret); + +int rsa_encrypt_bytes(EVP_PKEY *pkey, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size); + +int rsa_oaep_encrypt_bytes(const EVP_PKEY *pkey, const char *digest_alg, const char *label, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size); + +int rsa_pkey_to_suitable_key_size(EVP_PKEY *pkey, size_t *ret_suitable_key_size); + +int rsa_pkey_from_n_e(const void *n, size_t n_size, const void *e, size_t e_size, EVP_PKEY **ret); + +int rsa_pkey_to_n_e(const EVP_PKEY *pkey, void **ret_n, size_t *ret_n_size, void **ret_e, size_t *ret_e_size); + +int ecc_pkey_from_curve_x_y(int curve_id, const void *x, size_t x_size, const void *y, size_t y_size, EVP_PKEY **ret); + +int ecc_pkey_to_curve_x_y(const EVP_PKEY *pkey, int *ret_curve_id, void **ret_x, size_t *ret_x_size, void **ret_y, size_t *ret_y_size); + +int ecc_pkey_new(int curve_id, EVP_PKEY **ret); + +int ecc_ecdh(const EVP_PKEY *private_pkey, const EVP_PKEY *peer_pkey, void **ret_shared_secret, size_t *ret_shared_secret_size); + +int pkey_generate_volume_keys(EVP_PKEY *pkey, void **ret_decrypted_key, size_t *ret_decrypted_key_size, void **ret_saved_key, size_t *ret_saved_key_size); + +int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size); + +int digest_and_sign(const EVP_MD *md, EVP_PKEY *privkey, const void *data, size_t size, void **ret, size_t *ret_size); + +int pkcs7_new(X509 *certificate, EVP_PKEY *private_key, const char *hash_algorithm, PKCS7 **ret_p7, PKCS7_SIGNER_INFO **ret_si); + +int string_hashsum(const char *s, size_t len, const char *md_algorithm, char **ret); +static inline int string_hashsum_sha224(const char *s, size_t len, char **ret) { + return string_hashsum(s, len, "SHA224", ret); +} +static inline int string_hashsum_sha256(const char *s, size_t len, char **ret) { + return string_hashsum(s, len, "SHA256", ret); +} + +int x509_fingerprint(X509 *cert, uint8_t buffer[static X509_FINGERPRINT_SIZE]); + +int openssl_load_x509_certificate( + CertificateSourceType certificate_source_type, + const char *certificate_source, + const char *certificate, + X509 **ret); + +int openssl_load_private_key( + KeySourceType private_key_source_type, + const char *private_key_source, + const char *private_key, + const AskPasswordRequest *request, + EVP_PKEY **ret_private_key, + OpenSSLAskPasswordUI **ret_user_interface); + +int openssl_extract_public_key(EVP_PKEY *private_key, EVP_PKEY **ret); + +OpenSSLAskPasswordUI* openssl_ask_password_ui_free(OpenSSLAskPasswordUI *ui); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OpenSSLAskPasswordUI*, openssl_ask_password_ui_free, NULL); + +#endif diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 7252278768851..662739ea0fe9c 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -6,12 +6,6 @@ #include #include -#if HAVE_OPENSSL -#include -#include -#include -#endif - #include "sd-device.h" #include "sd-id128.h" #include "sd-json.h" @@ -26,6 +20,7 @@ #include "conf-files.h" #include "constants.h" #include "copy.h" +#include "crypto-util.h" #include "cryptsetup-util.h" #include "device-private.h" #include "devnum-util.h" @@ -57,7 +52,6 @@ #include "mountpoint-util.h" #include "namespace-util.h" #include "nulstr-util.h" -#include "openssl-util.h" #include "os-util.h" #include "path-util.h" #include "pcrextend-util.h" @@ -3087,6 +3081,10 @@ static int validate_signature_userspace(const VeritySettings *verity, const char assert(iovec_is_set(&verity->root_hash)); assert(iovec_is_set(&verity->root_hash_sig)); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + /* Because installing a signature certificate into the kernel chain is so messy, let's optionally do * userspace validation. */ @@ -3099,7 +3097,7 @@ static int validate_signature_userspace(const VeritySettings *verity, const char } const unsigned char *d = verity->root_hash_sig.iov_base; - p7 = d2i_PKCS7(NULL, &d, (long) verity->root_hash_sig.iov_len); + p7 = sym_d2i_PKCS7(NULL, &d, (long) verity->root_hash_sig.iov_len); if (!p7) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse PKCS7 DER signature data."); @@ -3107,11 +3105,11 @@ static int validate_signature_userspace(const VeritySettings *verity, const char if (!s) return log_oom_debug(); - bio = BIO_new_mem_buf(s, strlen(s)); + bio = sym_BIO_new_mem_buf(s, strlen(s)); if (!bio) return log_oom_debug(); - sk = sk_X509_new_null(); + sk = sym_sk_X509_new_null(); if (!sk) return log_oom_debug(); @@ -3125,23 +3123,23 @@ static int validate_signature_userspace(const VeritySettings *verity, const char continue; } - c = PEM_read_X509(f, NULL, NULL, NULL); + c = sym_PEM_read_X509(f, NULL, NULL, NULL); if (!c) { log_debug("Failed to load X509 certificate '%s', ignoring.", *i); continue; } - if (sk_X509_push(sk, c) == 0) + if (sym_sk_X509_push(sk, c) == 0) return log_oom_debug(); TAKE_PTR(c); } - r = PKCS7_verify(p7, sk, NULL, bio, NULL, PKCS7_NOINTERN|PKCS7_NOVERIFY); + r = sym_PKCS7_verify(p7, sk, NULL, bio, NULL, PKCS7_NOINTERN|PKCS7_NOVERIFY); if (r) log_debug("Userspace PKCS#7 validation succeeded."); else - log_debug("Userspace PKCS#7 validation failed: %s", ERR_error_string(ERR_get_error(), NULL)); + log_debug("Userspace PKCS#7 validation failed: %s", sym_ERR_error_string(sym_ERR_get_error(), NULL)); return r; #else diff --git a/src/shared/meson.build b/src/shared/meson.build index 1ab14a5c92a22..741dbb60a451a 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -51,6 +51,7 @@ shared_sources = files( 'coredump-util.c', 'cpu-set-util.c', 'creds-util.c', + 'crypto-util.c', 'cryptsetup-fido2.c', 'cryptsetup-tpm2.c', 'cryptsetup-util.c', @@ -151,7 +152,6 @@ shared_sources = files( 'nsresource.c', 'numa-util.c', 'open-file.c', - 'openssl-util.c', 'options.c', 'osc-context.c', 'output-mode.c', @@ -197,6 +197,7 @@ shared_sources = files( 'socket-label.c', 'socket-netlink.c', 'specifier.c', + 'ssl-util.c', 'switch-root.c', 'swtpm-util.c', 'tar-util.c', @@ -400,7 +401,7 @@ libshared_deps = [threads, libkmod_cflags, libmicrohttpd_cflags, libmount_cflags, - libopenssl, + libopenssl_cflags, libp11kit_cflags, libpam_cflags, libpcre2_cflags, diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h deleted file mode 100644 index 218641e06fe61..0000000000000 --- a/src/shared/openssl-util.h +++ /dev/null @@ -1,196 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include "ask-password-api.h" -#include "shared-forward.h" -#include "iovec-util.h" -#include "sha256.h" - -typedef enum CertificateSourceType { - OPENSSL_CERTIFICATE_SOURCE_FILE, - OPENSSL_CERTIFICATE_SOURCE_PROVIDER, - _OPENSSL_CERTIFICATE_SOURCE_MAX, - _OPENSSL_CERTIFICATE_SOURCE_INVALID = -EINVAL, -} CertificateSourceType; - -typedef enum KeySourceType { - OPENSSL_KEY_SOURCE_FILE, - OPENSSL_KEY_SOURCE_ENGINE, - OPENSSL_KEY_SOURCE_PROVIDER, - _OPENSSL_KEY_SOURCE_MAX, - _OPENSSL_KEY_SOURCE_INVALID = -EINVAL, -} KeySourceType; - -typedef struct OpenSSLAskPasswordUI OpenSSLAskPasswordUI; - -int parse_openssl_certificate_source_argument(const char *argument, char **certificate_source, CertificateSourceType *certificate_source_type); - -int parse_openssl_key_source_argument(const char *argument, char **private_key_source, KeySourceType *private_key_source_type); - -#define X509_FINGERPRINT_SIZE SHA256_DIGEST_SIZE - -#if HAVE_OPENSSL -# include /* IWYU pragma: export */ -# include /* IWYU pragma: export */ -# include /* IWYU pragma: export */ -# include /* IWYU pragma: export */ -# include /* IWYU pragma: export */ -# include /* IWYU pragma: export */ -# include /* IWYU pragma: export */ -# include /* IWYU pragma: export */ -# include /* IWYU pragma: export */ -# include /* IWYU pragma: export */ -# include /* IWYU pragma: export */ -# include /* IWYU pragma: export */ -# include /* IWYU pragma: export */ -# ifndef OPENSSL_NO_UI_CONSOLE -# include /* IWYU pragma: export */ -# endif -# include /* IWYU pragma: export */ - -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_MACRO(void*, OPENSSL_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ASN1_OCTET_STRING*, ASN1_OCTET_STRING_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ASN1_TIME*, ASN1_TIME_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free_all, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIGNUM*, BN_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BN_CTX*, BN_CTX_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EC_GROUP*, EC_GROUP_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EC_POINT*, EC_POINT_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ECDSA_SIG*, ECDSA_SIG_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_CIPHER*, EVP_CIPHER_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_CIPHER_CTX*, EVP_CIPHER_CTX_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_KDF*, EVP_KDF_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_KDF_CTX*, EVP_KDF_CTX_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MAC*, EVP_MAC_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MAC_CTX*, EVP_MAC_CTX_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD*, EVP_MD_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD_CTX*, EVP_MD_CTX_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_PKEY*, EVP_PKEY_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_PKEY_CTX*, EVP_PKEY_CTX_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_PARAM*, OSSL_PARAM_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_PARAM_BLD*, OSSL_PARAM_BLD_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_STORE_CTX*, OSSL_STORE_close, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_STORE_INFO*, OSSL_STORE_INFO_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(PKCS7*, PKCS7_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(PKCS7_SIGNER_INFO*, PKCS7_SIGNER_INFO_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SSL*, SSL_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509*, X509_free, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509_NAME*, X509_NAME_free, NULL); - -static inline STACK_OF(X509_ALGOR) *x509_algor_free_many(STACK_OF(X509_ALGOR) *attrs) { - if (!attrs) - return NULL; - - sk_X509_ALGOR_pop_free(attrs, X509_ALGOR_free); - return NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(STACK_OF(X509_ALGOR)*, x509_algor_free_many, NULL); - -static inline STACK_OF(X509_ATTRIBUTE) *x509_attribute_free_many(STACK_OF(X509_ATTRIBUTE) *attrs) { - if (!attrs) - return NULL; - - sk_X509_ATTRIBUTE_pop_free(attrs, X509_ATTRIBUTE_free); - return NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(STACK_OF(X509_ATTRIBUTE)*, x509_attribute_free_many, NULL); - -static inline void sk_X509_free_allp(STACK_OF(X509) **sk) { - if (!sk || !*sk) - return; - - sk_X509_pop_free(*sk, X509_free); -} - -int openssl_pubkey_from_pem(const void *pem, size_t pem_size, EVP_PKEY **ret); -int openssl_pubkey_to_pem(EVP_PKEY *pkey, char **ret); - -int openssl_digest_size(const char *digest_alg, size_t *ret_digest_size); - -int openssl_digest_many(const char *digest_alg, const struct iovec data[], size_t n_data, void **ret_digest, size_t *ret_digest_size); - -static inline int openssl_digest(const char *digest_alg, const void *buf, size_t len, void **ret_digest, size_t *ret_digest_size) { - return openssl_digest_many(digest_alg, &IOVEC_MAKE((void*) buf, len), 1, ret_digest, ret_digest_size); -} - -int openssl_hmac_many(const char *digest_alg, const void *key, size_t key_size, const struct iovec data[], size_t n_data, void **ret_digest, size_t *ret_digest_size); - -static inline int openssl_hmac(const char *digest_alg, const void *key, size_t key_size, const void *buf, size_t len, void **ret_digest, size_t *ret_digest_size) { - return openssl_hmac_many(digest_alg, key, key_size, &IOVEC_MAKE((void*) buf, len), 1, ret_digest, ret_digest_size); -} - -int openssl_cipher_many(const char *alg, size_t bits, const char *mode, const void *key, size_t key_size, const void *iv, size_t iv_size, const struct iovec data[], size_t n_data, void **ret, size_t *ret_size); - -static inline int openssl_cipher(const char *alg, size_t bits, const char *mode, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *buf, size_t len, void **ret, size_t *ret_size) { - return openssl_cipher_many(alg, bits, mode, key, key_size, iv, iv_size, &IOVEC_MAKE((void*) buf, len), 1, ret, ret_size); -} - -int kdf_ss_derive(const char *digest, const void *key, size_t key_size, const void *salt, size_t salt_size, const void *info, size_t info_size, size_t derive_size, void **ret); - -int kdf_kb_hmac_derive(const char *mode, const char *digest, const void *key, size_t key_size, const void *salt, size_t salt_size, const void *info, size_t info_size, const void *seed, size_t seed_size, size_t derive_size, void **ret); - -int rsa_encrypt_bytes(EVP_PKEY *pkey, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size); - -int rsa_oaep_encrypt_bytes(const EVP_PKEY *pkey, const char *digest_alg, const char *label, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size); - -int rsa_pkey_to_suitable_key_size(EVP_PKEY *pkey, size_t *ret_suitable_key_size); - -int rsa_pkey_from_n_e(const void *n, size_t n_size, const void *e, size_t e_size, EVP_PKEY **ret); - -int rsa_pkey_to_n_e(const EVP_PKEY *pkey, void **ret_n, size_t *ret_n_size, void **ret_e, size_t *ret_e_size); - -int ecc_pkey_from_curve_x_y(int curve_id, const void *x, size_t x_size, const void *y, size_t y_size, EVP_PKEY **ret); - -int ecc_pkey_to_curve_x_y(const EVP_PKEY *pkey, int *ret_curve_id, void **ret_x, size_t *ret_x_size, void **ret_y, size_t *ret_y_size); - -int ecc_pkey_new(int curve_id, EVP_PKEY **ret); - -int ecc_ecdh(const EVP_PKEY *private_pkey, const EVP_PKEY *peer_pkey, void **ret_shared_secret, size_t *ret_shared_secret_size); - -int pkey_generate_volume_keys(EVP_PKEY *pkey, void **ret_decrypted_key, size_t *ret_decrypted_key_size, void **ret_saved_key, size_t *ret_saved_key_size); - -int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size); - -int digest_and_sign(const EVP_MD *md, EVP_PKEY *privkey, const void *data, size_t size, void **ret, size_t *ret_size); - -int pkcs7_new(X509 *certificate, EVP_PKEY *private_key, const char *hash_algorithm, PKCS7 **ret_p7, PKCS7_SIGNER_INFO **ret_si); - -int string_hashsum(const char *s, size_t len, const char *md_algorithm, char **ret); -static inline int string_hashsum_sha224(const char *s, size_t len, char **ret) { - return string_hashsum(s, len, "SHA224", ret); -} -static inline int string_hashsum_sha256(const char *s, size_t len, char **ret) { - return string_hashsum(s, len, "SHA256", ret); -} - -int x509_fingerprint(X509 *cert, uint8_t buffer[static X509_FINGERPRINT_SIZE]); - -int openssl_load_x509_certificate( - CertificateSourceType certificate_source_type, - const char *certificate_source, - const char *certificate, - X509 **ret); - -int openssl_load_private_key( - KeySourceType private_key_source_type, - const char *private_key_source, - const char *private_key, - const AskPasswordRequest *request, - EVP_PKEY **ret_private_key, - OpenSSLAskPasswordUI **ret_user_interface); - -int openssl_extract_public_key(EVP_PKEY *private_key, EVP_PKEY **ret); - -struct OpenSSLAskPasswordUI { - AskPasswordRequest request; -#ifndef OPENSSL_NO_UI_CONSOLE - UI_METHOD *method; -#endif -}; - -OpenSSLAskPasswordUI* openssl_ask_password_ui_free(OpenSSLAskPasswordUI *ui); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OpenSSLAskPasswordUI*, openssl_ask_password_ui_free, NULL); -#endif diff --git a/src/shared/pe-binary.c b/src/shared/pe-binary.c index da54428306c11..b4b180d701337 100644 --- a/src/shared/pe-binary.c +++ b/src/shared/pe-binary.c @@ -4,6 +4,7 @@ #include #include "alloc-util.h" +#include "crypto-util.h" #include "hexdecoct.h" #include "log.h" #include "pe-binary.h" @@ -325,7 +326,7 @@ static int hash_file(int fd, EVP_MD_CTX *md_ctx, uint64_t offset, uint64_t size) if ((size_t) n != m) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read while hashing."); - if (EVP_DigestUpdate(md_ctx, buffer, m) != 1) + if (sym_EVP_DigestUpdate(md_ctx, buffer, m) != 1) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data."); offset += m; @@ -358,6 +359,10 @@ int pe_hash(int fd, assert(ret_hash_size); assert(ret_hash); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + if (fstat(fd, &st) < 0) return log_debug_errno(errno, "Failed to stat file: %m"); r = stat_verify_regular(&st); @@ -376,11 +381,11 @@ int pe_hash(int fd, if (!certificate_table) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "File lacks certificate table."); - mdctx = EVP_MD_CTX_new(); + mdctx = sym_EVP_MD_CTX_new(); if (!mdctx) return log_oom_debug(); - if (EVP_DigestInit_ex(mdctx, md, NULL) != 1) + if (sym_EVP_DigestInit_ex(mdctx, md, NULL) != 1) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to allocate message digest."); /* Everything from beginning of file to CheckSum field in PE header */ @@ -428,11 +433,11 @@ int pe_hash(int fd, return r; /* If the file size is not a multiple of 8 bytes, pad the hash with zero bytes. */ - if (st.st_size % 8 != 0 && EVP_DigestUpdate(mdctx, (const uint8_t[8]) {}, 8 - (st.st_size % 8)) != 1) + if (st.st_size % 8 != 0 && sym_EVP_DigestUpdate(mdctx, (const uint8_t[8]) {}, 8 - (st.st_size % 8)) != 1) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data."); } - int hsz = EVP_MD_CTX_size(mdctx); + int hsz = sym_EVP_MD_CTX_get_size(mdctx); if (hsz < 0) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get hash size."); @@ -441,7 +446,7 @@ int pe_hash(int fd, if (!hash) return log_oom_debug(); - if (EVP_DigestFinal_ex(mdctx, hash, &hash_size) != 1) + if (sym_EVP_DigestFinal_ex(mdctx, hash, &hash_size) != 1) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize hash function."); assert(hash_size == (unsigned) hsz); @@ -525,6 +530,10 @@ int uki_hash(int fd, assert(ret_hashes); assert(ret_hash_size); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + r = pe_load_headers(fd, &dos_header, &pe_header); if (r < 0) return r; @@ -533,7 +542,7 @@ int uki_hash(int fd, if (r < 0) return r; - int hsz = EVP_MD_size(md); + int hsz = sym_EVP_MD_get_size(md); if (hsz < 0) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get hash size."); @@ -553,11 +562,11 @@ int uki_hash(int fd, if (hashes[i]) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate section"); - mdctx = EVP_MD_CTX_new(); + mdctx = sym_EVP_MD_CTX_new(); if (!mdctx) return log_oom_debug(); - if (EVP_DigestInit_ex(mdctx, md, NULL) != 1) + if (sym_EVP_DigestInit_ex(mdctx, md, NULL) != 1) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to allocate message digest."); r = hash_file(fd, mdctx, le32toh(section->PointerToRawData), MIN(le32toh(section->VirtualSize), le32toh(section->SizeOfRawData))); @@ -571,7 +580,7 @@ int uki_hash(int fd, while (remaining > 0) { size_t sz = MIN(sizeof(zeroes), remaining); - if (EVP_DigestUpdate(mdctx, zeroes, sz) != 1) + if (sym_EVP_DigestUpdate(mdctx, zeroes, sz) != 1) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data."); remaining -= sz; @@ -583,7 +592,7 @@ int uki_hash(int fd, return log_oom_debug(); unsigned hash_size = (unsigned) hsz; - if (EVP_DigestFinal_ex(mdctx, hashes[i], &hash_size) != 1) + if (sym_EVP_DigestFinal_ex(mdctx, hashes[i], &hash_size) != 1) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize hash function."); assert(hash_size == (unsigned) hsz); @@ -592,7 +601,7 @@ int uki_hash(int fd, _cleanup_free_ char *hs = NULL; hs = hexmem(hashes[i], hsz); - log_debug("Section %s with %s is %s.", n, EVP_MD_name(md), strna(hs)); + log_debug("Section %s with %s is %s.", n, sym_EVP_MD_get0_name(md), strna(hs)); } } diff --git a/src/shared/pe-binary.h b/src/shared/pe-binary.h index 0f748a87d7d25..4b5dc243fe77f 100644 --- a/src/shared/pe-binary.h +++ b/src/shared/pe-binary.h @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "openssl-util.h" #include "sparse-endian.h" #include "uki.h" diff --git a/src/shared/pkcs11-util.c b/src/shared/pkcs11-util.c index eea7fec8930f3..a4c5bb83f7d9e 100644 --- a/src/shared/pkcs11-util.c +++ b/src/shared/pkcs11-util.c @@ -1,16 +1,20 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#if HAVE_OPENSSL +# include +#endif + #include "sd-dlopen.h" #include "alloc-util.h" #include "ask-password-api.h" +#include "crypto-util.h" #include "dlfcn-util.h" #include "env-util.h" #include "escape.h" #include "format-table.h" #include "log.h" #include "memory-util.h" -#include "openssl-util.h" #include "pkcs11-util.h" #include "random-util.h" #include "string-util.h" @@ -429,7 +433,7 @@ static int read_public_key_info( "Failed to read CKA_PUBLIC_KEY_INFO: %s", sym_p11_kit_strerror(rv)); const unsigned char *value = attribute.pValue; - pkey = d2i_PUBKEY(NULL, &value, attribute.ulValueLen); + pkey = sym_d2i_PUBKEY(NULL, &value, attribute.ulValueLen); if (!pkey) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse CKA_PUBLIC_KEY_INFO"); @@ -449,6 +453,10 @@ int pkcs11_token_read_public_key( assert(ret_pkey); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + r = read_public_key_info(m, session, object, &pkey); if (r >= 0) { *ret_pkey = TAKE_PTR(pkey); @@ -543,67 +551,67 @@ int pkcs11_token_read_public_key( _cleanup_(ASN1_OCTET_STRING_freep) ASN1_OCTET_STRING *os = NULL; const unsigned char *ec_params_value = ec_attributes[0].pValue; - group = d2i_ECPKParameters(NULL, &ec_params_value, ec_attributes[0].ulValueLen); + group = sym_d2i_ECPKParameters(NULL, &ec_params_value, ec_attributes[0].ulValueLen); if (!group) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode CKA_EC_PARAMS."); const unsigned char *ec_point_value = ec_attributes[1].pValue; - os = d2i_ASN1_OCTET_STRING(NULL, &ec_point_value, ec_attributes[1].ulValueLen); + os = sym_d2i_ASN1_OCTET_STRING(NULL, &ec_point_value, ec_attributes[1].ulValueLen); if (!os) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode CKA_EC_POINT."); - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = sym_EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); if (!ctx) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to create an EVP_PKEY_CTX for EC."); - if (EVP_PKEY_fromdata_init(ctx) != 1) + if (sym_EVP_PKEY_fromdata_init(ctx) != 1) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to init an EVP_PKEY_CTX for EC."); OSSL_PARAM ec_params[8] = { /* We need to drop the const from the data param, because ec_params is * modified below. But we'll not modify ec_params[0]. */ OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, - (unsigned char *) ASN1_STRING_get0_data(os), - ASN1_STRING_length(os)), + (unsigned char *) sym_ASN1_STRING_get0_data(os), + sym_ASN1_STRING_length(os)), }; _cleanup_free_ void *order = NULL, *p = NULL, *a = NULL, *b = NULL, *generator = NULL; size_t order_size, p_size, a_size, b_size, generator_size; - int nid = EC_GROUP_get_curve_name(group); + int nid = sym_EC_GROUP_get_curve_name(group); if (nid != NID_undef) { - const char* name = OSSL_EC_curve_nid2name(nid); - ec_params[1] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, (char*)name, strlen(name)); - ec_params[2] = OSSL_PARAM_construct_end(); + const char* name = sym_OSSL_EC_curve_nid2name(nid); + ec_params[1] = sym_OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, (char*)name, strlen(name)); + ec_params[2] = sym_OSSL_PARAM_construct_end(); } else { - const char *field_type = EC_GROUP_get_field_type(group) == NID_X9_62_prime_field ? + const char *field_type = sym_EC_GROUP_get_field_type(group) == NID_X9_62_prime_field ? "prime-field" : "characteristic-two-field"; - const BIGNUM *bn_order = EC_GROUP_get0_order(group); + const BIGNUM *bn_order = sym_EC_GROUP_get0_order(group); - _cleanup_(BN_CTX_freep) BN_CTX *bnctx = BN_CTX_new(); + _cleanup_(BN_CTX_freep) BN_CTX *bnctx = sym_BN_CTX_new(); if (!bnctx) return log_oom_debug(); - _cleanup_(BN_freep) BIGNUM *bn_p = BN_new(); + _cleanup_(BN_freep) BIGNUM *bn_p = sym_BN_new(); if (!bn_p) return log_oom_debug(); - _cleanup_(BN_freep) BIGNUM *bn_a = BN_new(); + _cleanup_(BN_freep) BIGNUM *bn_a = sym_BN_new(); if (!bn_a) return log_oom_debug(); - _cleanup_(BN_freep) BIGNUM *bn_b = BN_new(); + _cleanup_(BN_freep) BIGNUM *bn_b = sym_BN_new(); if (!bn_b) return log_oom_debug(); - if (EC_GROUP_get_curve(group, bn_p, bn_a, bn_b, bnctx) != 1) + if (sym_EC_GROUP_get_curve(group, bn_p, bn_a, bn_b, bnctx) != 1) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to extract EC parameters from EC_GROUP."); - order_size = BN_num_bytes(bn_order); - p_size = BN_num_bytes(bn_p); - a_size = BN_num_bytes(bn_a); - b_size = BN_num_bytes(bn_b); + order_size = sym_BN_num_bytes(bn_order); + p_size = sym_BN_num_bytes(bn_p); + a_size = sym_BN_num_bytes(bn_a); + b_size = sym_BN_num_bytes(bn_b); order = malloc(order_size); if (!order) @@ -621,14 +629,14 @@ int pkcs11_token_read_public_key( if (!b) return log_oom_debug(); - if (BN_bn2nativepad(bn_order, order, order_size) <= 0 || - BN_bn2nativepad(bn_p, p, p_size) <= 0 || - BN_bn2nativepad(bn_a, a, a_size) <= 0 || - BN_bn2nativepad(bn_b, b, b_size) <= 0 ) + if (sym_BN_bn2nativepad(bn_order, order, order_size) <= 0 || + sym_BN_bn2nativepad(bn_p, p, p_size) <= 0 || + sym_BN_bn2nativepad(bn_a, a, a_size) <= 0 || + sym_BN_bn2nativepad(bn_b, b, b_size) <= 0 ) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to store EC parameters in native byte order."); - const EC_POINT *point_gen = EC_GROUP_get0_generator(group); - generator_size = EC_POINT_point2oct(group, point_gen, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, bnctx); + const EC_POINT *point_gen = sym_EC_GROUP_get0_generator(group); + generator_size = sym_EC_POINT_point2oct(group, point_gen, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, bnctx); if (generator_size == 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine size of a EC generator."); @@ -636,20 +644,20 @@ int pkcs11_token_read_public_key( if (!generator) return log_oom_debug(); - generator_size = EC_POINT_point2oct(group, point_gen, POINT_CONVERSION_UNCOMPRESSED, generator, generator_size, bnctx); + generator_size = sym_EC_POINT_point2oct(group, point_gen, POINT_CONVERSION_UNCOMPRESSED, generator, generator_size, bnctx); if (generator_size == 0) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert a EC generator to octet string."); - ec_params[1] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_EC_FIELD_TYPE, (char*)field_type, strlen(field_type)); - ec_params[2] = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_EC_GENERATOR, generator, generator_size); - ec_params[3] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_ORDER, order, order_size); - ec_params[4] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_P, p, p_size); - ec_params[5] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_A, a, a_size); - ec_params[6] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_B, b, b_size); - ec_params[7] = OSSL_PARAM_construct_end(); + ec_params[1] = sym_OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_EC_FIELD_TYPE, (char*)field_type, strlen(field_type)); + ec_params[2] = sym_OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_EC_GENERATOR, generator, generator_size); + ec_params[3] = sym_OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_ORDER, order, order_size); + ec_params[4] = sym_OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_P, p, p_size); + ec_params[5] = sym_OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_A, a, a_size); + ec_params[6] = sym_OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_B, b, b_size); + ec_params[7] = sym_OSSL_PARAM_construct_end(); } - if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, ec_params) != 1) + if (sym_EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, ec_params) != 1) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to create EVP_PKEY from EC parameters."); break; } @@ -695,16 +703,20 @@ int pkcs11_token_read_x509_certificate( return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to read X.509 certificate data off token: %s", sym_p11_kit_strerror(rv)); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + const unsigned char *p = attribute.pValue; - _cleanup_(X509_freep) X509 *x509 = d2i_X509(NULL, &p, attribute.ulValueLen); + _cleanup_(X509_freep) X509 *x509 = sym_d2i_X509(NULL, &p, attribute.ulValueLen); if (!x509) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse X.509 certificate."); - const X509_NAME *name = X509_get_subject_name(x509); + const X509_NAME *name = sym_X509_get_subject_name(x509); if (!name) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to acquire X.509 subject name."); - _cleanup_free_ char *t = X509_NAME_oneline(name, NULL, 0); + _cleanup_free_ char *t = sym_X509_NAME_oneline(name, NULL, 0); if (!t) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to format X.509 subject name as string."); @@ -1009,23 +1021,27 @@ static int ecc_convert_to_compressed( _cleanup_free_ void *compressed_point = NULL; size_t compressed_point_size; + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + const unsigned char *ec_params_value = ec_params_attr.pValue; - group = d2i_ECPKParameters(NULL, &ec_params_value, ec_params_attr.ulValueLen); + group = sym_d2i_ECPKParameters(NULL, &ec_params_value, ec_params_attr.ulValueLen); if (!group) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode CKA_EC_PARAMS"); - point = EC_POINT_new(group); + point = sym_EC_POINT_new(group); if (!point) return log_oom(); - bnctx = BN_CTX_new(); + bnctx = sym_BN_CTX_new(); if (!bnctx) return log_oom(); - if (EC_POINT_oct2point(group, point, uncompressed_point, uncompressed_point_size, bnctx) != 1) + if (sym_EC_POINT_oct2point(group, point, uncompressed_point, uncompressed_point_size, bnctx) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode an uncompressed EC point"); - compressed_point_size = EC_POINT_point2oct(group, point, POINT_CONVERSION_COMPRESSED, NULL, 0, bnctx); + compressed_point_size = sym_EC_POINT_point2oct(group, point, POINT_CONVERSION_COMPRESSED, NULL, 0, bnctx); if (compressed_point_size == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine size of a compressed EC point"); @@ -1033,7 +1049,7 @@ static int ecc_convert_to_compressed( if (!compressed_point) return log_oom(); - compressed_point_size = EC_POINT_point2oct(group, point, POINT_CONVERSION_COMPRESSED, compressed_point, compressed_point_size, bnctx); + compressed_point_size = sym_EC_POINT_point2oct(group, point, POINT_CONVERSION_COMPRESSED, compressed_point, compressed_point_size, bnctx); if (compressed_point_size == 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert a EC point to compressed format"); @@ -1521,7 +1537,8 @@ struct pkcs11_acquire_public_key_callback_data { static void pkcs11_acquire_public_key_callback_data_release(struct pkcs11_acquire_public_key_callback_data *data) { erase_and_free(data->pin_used); - EVP_PKEY_free(data->pkey); + if (data->pkey) + sym_EVP_PKEY_free(data->pkey); } static int pkcs11_acquire_public_key_callback( @@ -1673,7 +1690,11 @@ static int pkcs11_acquire_public_key_callback( if (r < 0) return log_error_errno(r, "Failed to read a found X.509 certificate."); - pkey = X509_get_pubkey(cert); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + pkey = sym_X509_get_pubkey(cert); if (!pkey) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to extract public key from X.509 certificate."); } diff --git a/src/shared/pkcs11-util.h b/src/shared/pkcs11-util.h index 92d850b6e5008..7864598180f62 100644 --- a/src/shared/pkcs11-util.h +++ b/src/shared/pkcs11-util.h @@ -1,10 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#if HAVE_OPENSSL -# include -#endif - #if HAVE_P11KIT # include /* IWYU pragma: export */ # include /* IWYU pragma: export */ diff --git a/src/shared/pkcs7-util.c b/src/shared/pkcs7-util.c index 0a0e102adf7d9..4d22d90421a99 100644 --- a/src/shared/pkcs7-util.c +++ b/src/shared/pkcs7-util.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "alloc-util.h" -#include "openssl-util.h" +#include "crypto-util.h" #include "pkcs7-util.h" #include "log.h" @@ -28,6 +28,10 @@ int pkcs7_extract_signers( Signer **ret_signers, size_t *ret_n_signers) { +#if HAVE_OPENSSL + int r; +#endif + assert(ret_signers); assert(ret_n_signers); @@ -35,16 +39,20 @@ int pkcs7_extract_signers( return -EBADMSG; #if HAVE_OPENSSL + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + const unsigned char *d = sig->iov_base; _cleanup_(PKCS7_freep) PKCS7 *p7 = NULL; - p7 = d2i_PKCS7(/* a= */ NULL, &d, (long) sig->iov_len); + p7 = sym_d2i_PKCS7(/* a= */ NULL, &d, (long) sig->iov_len); if (!p7) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse PKCS7 DER signature data."); - STACK_OF(PKCS7_SIGNER_INFO) *sinfos = PKCS7_get_signer_info(p7); + STACK_OF(PKCS7_SIGNER_INFO) *sinfos = sym_PKCS7_get_signer_info(p7); if (!sinfos) return log_debug_errno(SYNTHETIC_ERRNO(ENODATA), "No signature information in PKCS7 signature?"); - int n = sk_PKCS7_SIGNER_INFO_num(sinfos); + int n = sym_sk_PKCS7_SIGNER_INFO_num(sinfos); if (n == 0) return log_debug_errno(SYNTHETIC_ERRNO(ENODATA), "No signatures in PKCS7 signature, refusing."); if (n > SIGNERS_MAX) /* safety net, in case people send us weirdly complex signatures */ @@ -59,17 +67,17 @@ int pkcs7_extract_signers( CLEANUP_ARRAY(signers, n_signers, signer_free_many); for (int i = 0; i < n; i++) { - PKCS7_SIGNER_INFO *si = sk_PKCS7_SIGNER_INFO_value(PKCS7_get_signer_info(p7), i); + PKCS7_SIGNER_INFO *si = sym_sk_PKCS7_SIGNER_INFO_value(sym_PKCS7_get_signer_info(p7), i); if (!si) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get signer information."); _cleanup_(signer_done) Signer signer = {}; _cleanup_free_ unsigned char *p = NULL; - int len = i2d_X509_NAME(si->issuer_and_serial->issuer, &p); + int len = sym_i2d_X509_NAME(si->issuer_and_serial->issuer, &p); signer.issuer = IOVEC_MAKE(TAKE_PTR(p), len); - len = i2d_ASN1_INTEGER(si->issuer_and_serial->serial, &p); + len = sym_i2d_ASN1_INTEGER(si->issuer_and_serial->serial, &p); signer.serial = IOVEC_MAKE(TAKE_PTR(p), len); signers[n_signers++] = TAKE_STRUCT(signer); diff --git a/src/shared/shared-forward.h b/src/shared/shared-forward.h index 38349d9dbbcc3..1a19b42499ca5 100644 --- a/src/shared/shared-forward.h +++ b/src/shared/shared-forward.h @@ -45,6 +45,7 @@ typedef enum UserDBFlags UserDBFlags; typedef enum UserRecordLoadFlags UserRecordLoadFlags; typedef enum UserStorage UserStorage; +typedef struct AskPasswordRequest AskPasswordRequest; typedef struct Bitmap Bitmap; typedef struct BootConfig BootConfig; typedef struct BPFProgram BPFProgram; diff --git a/src/shared/ssl-util.c b/src/shared/ssl-util.c new file mode 100644 index 0000000000000..82ed54d037e14 --- /dev/null +++ b/src/shared/ssl-util.c @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-dlopen.h" + +#include "log.h" /* IWYU pragma: keep */ +#include "ssl-util.h" + +#if HAVE_OPENSSL + +static void *libssl_dl = NULL; + +DLSYM_PROTOTYPE(SSL_ctrl) = NULL; +DLSYM_PROTOTYPE(SSL_CTX_ctrl) = NULL; +DLSYM_PROTOTYPE(SSL_CTX_free) = NULL; +DLSYM_PROTOTYPE(SSL_CTX_new) = NULL; +DLSYM_PROTOTYPE(SSL_CTX_set_default_verify_paths) = NULL; +DLSYM_PROTOTYPE(SSL_CTX_set_options) = NULL; +DLSYM_PROTOTYPE(SSL_do_handshake) = NULL; +DLSYM_PROTOTYPE(SSL_free) = NULL; +DLSYM_PROTOTYPE(SSL_get_error) = NULL; +DLSYM_PROTOTYPE(SSL_get_wbio) = NULL; +DLSYM_PROTOTYPE(SSL_get0_param) = NULL; +DLSYM_PROTOTYPE(SSL_get1_session) = NULL; +DLSYM_PROTOTYPE(SSL_new) = NULL; +DLSYM_PROTOTYPE(SSL_read) = NULL; +DLSYM_PROTOTYPE(SSL_SESSION_free) = NULL; +DLSYM_PROTOTYPE(SSL_set_bio) = NULL; +DLSYM_PROTOTYPE(SSL_set_connect_state) = NULL; +DLSYM_PROTOTYPE(SSL_set_session) = NULL; +DLSYM_PROTOTYPE(SSL_set_verify) = NULL; +DLSYM_PROTOTYPE(SSL_shutdown) = NULL; +DLSYM_PROTOTYPE(SSL_write) = NULL; +DLSYM_PROTOTYPE(TLS_client_method) = NULL; + +#endif + +int dlopen_libssl(int log_level) { +#if HAVE_OPENSSL + SD_ELF_NOTE_DLOPEN( + "libssl", + "Support for TLS", + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libssl.so.3"); + + return dlopen_many_sym_or_warn( + &libssl_dl, + "libssl.so.3", + log_level, + DLSYM_ARG(SSL_ctrl), + DLSYM_ARG(SSL_CTX_ctrl), + DLSYM_ARG(SSL_CTX_free), + DLSYM_ARG(SSL_CTX_new), + DLSYM_ARG(SSL_CTX_set_default_verify_paths), + DLSYM_ARG(SSL_CTX_set_options), + DLSYM_ARG(SSL_do_handshake), + DLSYM_ARG(SSL_free), + DLSYM_ARG(SSL_get_error), + DLSYM_ARG(SSL_get_wbio), + DLSYM_ARG(SSL_get0_param), + DLSYM_ARG(SSL_get1_session), + DLSYM_ARG(SSL_new), + DLSYM_ARG(SSL_read), + DLSYM_ARG(SSL_SESSION_free), + DLSYM_ARG(SSL_set_bio), + DLSYM_ARG(SSL_set_connect_state), + DLSYM_ARG(SSL_set_session), + DLSYM_ARG(SSL_set_verify), + DLSYM_ARG(SSL_shutdown), + DLSYM_ARG(SSL_write), + DLSYM_ARG(TLS_client_method)); +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libssl support is not compiled in."); +#endif +} diff --git a/src/shared/ssl-util.h b/src/shared/ssl-util.h new file mode 100644 index 0000000000000..7deb028cfe3de --- /dev/null +++ b/src/shared/ssl-util.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +int dlopen_libssl(int log_level); + +#if HAVE_OPENSSL + +# include /* IWYU pragma: export */ + +# include "dlfcn-util.h" + +extern DLSYM_PROTOTYPE(SSL_ctrl); +extern DLSYM_PROTOTYPE(SSL_CTX_ctrl); +extern DLSYM_PROTOTYPE(SSL_CTX_free); +extern DLSYM_PROTOTYPE(SSL_CTX_new); +extern DLSYM_PROTOTYPE(SSL_CTX_set_default_verify_paths); +extern DLSYM_PROTOTYPE(SSL_CTX_set_options); +extern DLSYM_PROTOTYPE(SSL_do_handshake); +extern DLSYM_PROTOTYPE(SSL_free); +extern DLSYM_PROTOTYPE(SSL_get_error); +extern DLSYM_PROTOTYPE(SSL_get_wbio); +extern DLSYM_PROTOTYPE(SSL_get0_param); +extern DLSYM_PROTOTYPE(SSL_get1_session); +extern DLSYM_PROTOTYPE(SSL_new); +extern DLSYM_PROTOTYPE(SSL_read); +extern DLSYM_PROTOTYPE(SSL_SESSION_free); +extern DLSYM_PROTOTYPE(SSL_set_bio); +extern DLSYM_PROTOTYPE(SSL_set_connect_state); +extern DLSYM_PROTOTYPE(SSL_set_session); +extern DLSYM_PROTOTYPE(SSL_set_verify); +extern DLSYM_PROTOTYPE(SSL_shutdown); +extern DLSYM_PROTOTYPE(SSL_write); +extern DLSYM_PROTOTYPE(TLS_client_method); + +/* Mirrors of OpenSSL macros that go through our dlopen'd sym_* variants, so we don't end up linking against + * libssl just for these. */ +#define sym_SSL_set_tlsext_host_name(s, name) \ + sym_SSL_ctrl((s), SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, (void *) (name)) +#define sym_SSL_CTX_set_min_proto_version(ctx, version) \ + sym_SSL_CTX_ctrl((ctx), SSL_CTRL_SET_MIN_PROTO_VERSION, (version), NULL) + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(SSL*, sym_SSL_free, SSL_freep, NULL); + +#endif diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index d972d39d76f94..9fe3e018693fc 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -13,6 +13,7 @@ #include "chase.h" #include "constants.h" #include "creds-util.h" +#include "crypto-util.h" #include "cryptsetup-util.h" #include "device-private.h" #include "device-util.h" @@ -51,10 +52,6 @@ #include "unaligned.h" #include "virt.h" -#if HAVE_OPENSSL -# include -#endif - #if HAVE_TPM2 static void *libtss2_esys_dl = NULL; static void *libtss2_rc_dl = NULL; @@ -3087,11 +3084,15 @@ int tpm2_get_good_pcr_banks_strv( #if HAVE_OPENSSL _cleanup_free_ TPMI_ALG_HASH *algs = NULL; _cleanup_strv_free_ char **l = NULL; - int n_algs; + int n_algs, r; assert(c); assert(ret); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + n_algs = tpm2_get_good_pcr_banks(c, pcr_mask, &algs); if (n_algs < 0) return n_algs; @@ -3105,11 +3106,11 @@ int tpm2_get_good_pcr_banks_strv( if (!salg) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unknown PCR algorithm, can't measure."); - implementation = EVP_get_digestbyname(salg); + implementation = sym_EVP_get_digestbyname(salg); if (!implementation) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unsupported PCR algorithm, can't measure."); - n = strdup(ASSERT_PTR(EVP_MD_name(implementation))); + n = strdup(ASSERT_PTR(sym_EVP_MD_get0_name(implementation))); if (!n) return log_oom_debug(); @@ -3783,6 +3784,10 @@ int tpm2_policy_signed_hmac_sha256( * specified in the hmac_key parameter. The secret key must be loaded into the TPM already and * referenced in hmac_key_handle. */ + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + log_debug("Submitting PolicySigned policy for HMAC-SHA256."); /* Acquire the nonce from the TPM that we shall sign */ @@ -3818,7 +3823,7 @@ int tpm2_policy_signed_hmac_sha256( unsigned hmac_signature_size = sizeof(hmac_signature); /* And sign this with our key */ - if (!HMAC(EVP_sha256(), + if (!sym_HMAC(sym_EVP_sha256(), hmac_key->iov_base, hmac_key->iov_len, digest_to_sign.buffer, @@ -4555,6 +4560,10 @@ int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret) assert(pkey); assert(ret); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + TPMT_PUBLIC public = { .nameAlg = TPM2_ALG_SHA256, .objectAttributes = TPMA_OBJECT_DECRYPT | TPMA_OBJECT_SIGN_ENCRYPT | TPMA_OBJECT_USERWITHAUTH, @@ -4564,7 +4573,7 @@ int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret) }, }; - int key_id = EVP_PKEY_get_id(pkey); + int key_id = sym_EVP_PKEY_get_id(pkey); switch (key_id) { case EVP_PKEY_EC: { public.type = TPM2_ALG_ECC; @@ -4662,8 +4671,12 @@ int tpm2_tpm2b_public_to_fingerprint( if (r < 0) return r; + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + /* Hardcode fingerprint to SHA256 */ - return pubkey_fingerprint(pkey, EVP_sha256(), ret_fingerprint, ret_fingerprint_size); + return pubkey_fingerprint(pkey, sym_EVP_sha256(), ret_fingerprint, ret_fingerprint_size); #else return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); #endif @@ -6812,17 +6825,21 @@ static int tpm2_userspace_log( if (fd < 0) /* Apparently tpm2_local_log_open() failed earlier, let's not complain again */ return 0; + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + for (size_t i = 0; i < values->count; i++) { const EVP_MD *implementation; const char *a; assert_se(a = tpm2_hash_alg_to_string(values->digests[i].hashAlg)); - assert_se(implementation = EVP_get_digestbyname(a)); + assert_se(implementation = sym_EVP_get_digestbyname(a)); r = sd_json_variant_append_arraybo( &array, SD_JSON_BUILD_PAIR_STRING("hashAlg", a), - SD_JSON_BUILD_PAIR_HEX("digest", &values->digests[i].digest, EVP_MD_size(implementation))); + SD_JSON_BUILD_PAIR_HEX("digest", &values->digests[i].digest, sym_EVP_MD_get_size(implementation))); if (r < 0) return log_debug_errno(r, "Failed to append digest object to JSON array: %m"); } @@ -6880,6 +6897,7 @@ int tpm2_pcr_extend_bytes( _cleanup_close_ int log_fd = -EBADF; TPML_DIGEST_VALUES values = {}; TSS2_RC rc; + int r; assert(c); assert(iovec_is_valid(data)); @@ -6894,19 +6912,23 @@ int tpm2_pcr_extend_bytes( if (strv_isempty(banks)) return 0; + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + STRV_FOREACH(bank, banks) { const EVP_MD *implementation; int id; - assert_se(implementation = EVP_get_digestbyname(*bank)); + assert_se(implementation = sym_EVP_get_digestbyname(*bank)); if (values.count >= ELEMENTSOF(values.digests)) return log_debug_errno(SYNTHETIC_ERRNO(E2BIG), "Too many banks selected."); - if ((size_t) EVP_MD_size(implementation) > sizeof(values.digests[values.count].digest)) + if ((size_t) sym_EVP_MD_get_size(implementation) > sizeof(values.digests[values.count].digest)) return log_debug_errno(SYNTHETIC_ERRNO(E2BIG), "Hash result too large for TPM2."); - id = tpm2_hash_alg_from_string(EVP_MD_name(implementation)); + id = tpm2_hash_alg_from_string(sym_EVP_MD_get0_name(implementation)); if (id < 0) return log_debug_errno(id, "Can't map hash name to TPM2."); @@ -6919,9 +6941,9 @@ int tpm2_pcr_extend_bytes( * some unrelated purpose, who knows). Hence we instead measure an HMAC signature of a * private non-secret string instead. */ if (iovec_is_set(secret) > 0) { - if (!HMAC(implementation, secret->iov_base, secret->iov_len, data->iov_base, data->iov_len, (unsigned char*) &values.digests[values.count].digest, NULL)) + if (!sym_HMAC(implementation, secret->iov_base, secret->iov_len, data->iov_base, data->iov_len, (unsigned char*) &values.digests[values.count].digest, NULL)) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to calculate HMAC of data to measure."); - } else if (EVP_Digest(data->iov_base, data->iov_len, (unsigned char*) &values.digests[values.count].digest, NULL, implementation, NULL) != 1) + } else if (sym_EVP_Digest(data->iov_base, data->iov_len, (unsigned char*) &values.digests[values.count].digest, NULL, implementation, NULL) != 1) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash data to measure."); values.count++; @@ -7054,6 +7076,10 @@ int tpm2_nvpcr_extend_bytes( assert(iovec_is_valid(data)); assert(iovec_is_valid(secret)); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + _cleanup_(nvpcr_data_done) NvPCRData p = {}; r = nvpcr_data_load(name, &p); if (r < 0) @@ -7077,10 +7103,10 @@ int tpm2_nvpcr_extend_bytes( return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported algorithm for NvPCR, refusing."); const EVP_MD *implementation; - assert_se(implementation = EVP_get_digestbyname(an)); + assert_se(implementation = sym_EVP_get_digestbyname(an)); _cleanup_(iovec_done) struct iovec digest = { - .iov_len = EVP_MD_size(implementation), + .iov_len = sym_EVP_MD_get_size(implementation), }; digest.iov_base = malloc(digest.iov_len); @@ -7091,9 +7117,9 @@ int tpm2_nvpcr_extend_bytes( data = &iovec_empty; if (iovec_is_set(secret)) { - if (!HMAC(implementation, secret->iov_base, secret->iov_len, data->iov_base, data->iov_len, digest.iov_base, NULL)) + if (!sym_HMAC(implementation, secret->iov_base, secret->iov_len, data->iov_base, data->iov_len, digest.iov_base, NULL)) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to calculate HMAC of data to measure."); - } else if (EVP_Digest(data->iov_base, data->iov_len, digest.iov_base, NULL, implementation, NULL) != 1) + } else if (sym_EVP_Digest(data->iov_base, data->iov_len, digest.iov_base, NULL, implementation, NULL) != 1) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash data to measure."); _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL; @@ -7595,10 +7621,14 @@ int tpm2_nvpcr_initialize( if (!an) return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported algorithm for NvPCR, refusing."); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + const EVP_MD *implementation; - assert_se(implementation = EVP_get_digestbyname(an)); + assert_se(implementation = sym_EVP_get_digestbyname(an)); - int digest_size = EVP_MD_get_size(implementation); + int digest_size = sym_EVP_MD_get_size(implementation); assert_se(digest_size > 0); if ((size_t) digest_size > sizeof_field(TPM2B_MAX_NV_BUFFER, buffer)) @@ -7619,7 +7649,7 @@ int tpm2_nvpcr_initialize( CLEANUP_ERASE(buf); /* We measure HMAC(anchor_secret, name) into the NvPCR to anchor it on our secret. */ - if (!HMAC(implementation, anchor_secret->iov_base, anchor_secret->iov_len, hmac_buffer, hmac_buffer_size, buf.buffer, NULL)) + if (!sym_HMAC(implementation, anchor_secret->iov_base, anchor_secret->iov_len, hmac_buffer, hmac_buffer_size, buf.buffer, NULL)) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to calculate HMAC of data to measure."); _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL; diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 8576f278cf195..b5a8ec1c1eaf7 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -2,8 +2,9 @@ #pragma once #include "bitfield.h" -#include "openssl-util.h" +#include "iovec-util.h" #include "shared-forward.h" +#include "sha256-fundamental.h" typedef enum TPM2Flags { TPM2_FLAGS_USE_PIN = 1 << 0, diff --git a/src/sysupdate/meson.build b/src/sysupdate/meson.build index 68ac0e14ee89c..3a1ee1a048c62 100644 --- a/src/sysupdate/meson.build +++ b/src/sysupdate/meson.build @@ -29,7 +29,7 @@ executables += [ 'extract' : systemd_sysupdate_extract_sources, 'dependencies' : [ libfdisk_cflags, - libopenssl, + libopenssl_cflags, threads, ], }, diff --git a/src/test/meson.build b/src/test/meson.build index 9544c166a5928..6f9a24eb04483 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -289,8 +289,8 @@ executables += [ 'c_args' : ['-fno-sanitize=all', '-fno-optimize-sibling-calls', '-O1'], }, test_template + { - 'sources' : files('test-cryptolib.c'), - 'dependencies' : libopenssl, + 'sources' : files('test-crypto-util.c'), + 'dependencies' : libopenssl_cflags, 'conditions' : ['HAVE_OPENSSL'], }, test_template + { @@ -410,14 +410,9 @@ executables += [ 'dependencies' : libdl, 'conditions' : ['ENABLE_NSS'], }, - test_template + { - 'sources' : files('test-openssl.c'), - 'dependencies' : libopenssl, - 'conditions' : ['HAVE_OPENSSL'], - }, test_template + { 'sources' : files('test-pkcs7-util.c'), - 'dependencies' : libopenssl, + 'dependencies' : libopenssl_cflags, 'conditions' : ['HAVE_OPENSSL'], }, test_template + { @@ -494,7 +489,7 @@ executables += [ }, test_template + { 'sources' : files('test-tpm2.c'), - 'dependencies' : libopenssl, + 'dependencies' : libopenssl_cflags, 'timeout' : 120, }, test_template + { diff --git a/src/test/test-openssl.c b/src/test/test-crypto-util.c similarity index 93% rename from src/test/test-openssl.c rename to src/test/test-crypto-util.c index a09484a2ba8ad..a2fd090ed3fab 100644 --- a/src/test/test-openssl.c +++ b/src/test/test-crypto-util.c @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "openssl-util.h" +#include "crypto-util.h" #include "tests.h" TEST(openssl_pkey_from_pem) { @@ -42,16 +42,16 @@ TEST(rsa_pkey_n_e) { _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; assert_se(rsa_pkey_from_n_e(n, n_len, &e, sizeof(e), &pkey) >= 0); - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL); + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = sym_EVP_PKEY_CTX_new(pkey, NULL); assert_se(ctx); - assert_se(EVP_PKEY_verify_init(ctx) == 1); + assert_se(sym_EVP_PKEY_verify_init(ctx) == 1); const char *msg = "this is a secret"; DEFINE_HEX_PTR(sig, "14b53e0c6ad99a350c3d7811e8160f4ae03ad159815bb91bddb9735b833588df2eac221fbd3fc4ece0dd63bfaeddfdaf4ae67021e759f3638bc194836413414f54e8c4d01c9c37fa4488ea2ef772276b8a33822a53c97b1c35acfb4bc621cfb8fad88f0cf7d5491f05236886afbf9ed47f9469536482f50f74a20defa59d99676bed62a17b5eb98641df5a2f8080fa4b24f2749cc152fa65ba34c14022fcb27f1b36f52021950d7b9b6c3042c50b84cfb7d55a5f9235bfd58e1bf1f604eb93416c5fb5fd90cb68f1270dfa9daf67f52c604f62c2f2beee5e7e672b0e6e9833dd43dba99b77668540c850c9a81a5ea7aaf6297383e6135bd64572362333121fc7"); - assert_se(EVP_PKEY_verify(ctx, sig, sig_len, (unsigned char*) msg, strlen(msg)) == 1); + assert_se(sym_EVP_PKEY_verify(ctx, sig, sig_len, (unsigned char*) msg, strlen(msg)) == 1); DEFINE_HEX_PTR(invalid_sig, "1234"); - assert_se(EVP_PKEY_verify(ctx, invalid_sig, invalid_sig_len, (unsigned char*) msg, strlen(msg)) != 1); + assert_se(sym_EVP_PKEY_verify(ctx, invalid_sig, invalid_sig_len, (unsigned char*) msg, strlen(msg)) != 1); _cleanup_free_ void *n2 = NULL, *e2 = NULL; size_t n2_size, e2_size; @@ -69,16 +69,16 @@ TEST(ecc_pkey_curve_x_y) { _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; assert_se(ecc_pkey_from_curve_x_y(curveid, x, x_len, y, y_len, &pkey) >= 0); - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL); + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = sym_EVP_PKEY_CTX_new(pkey, NULL); assert_se(ctx); - assert_se(EVP_PKEY_verify_init(ctx) == 1); + assert_se(sym_EVP_PKEY_verify_init(ctx) == 1); const char *msg = "this is a secret"; DEFINE_HEX_PTR(sig, "3045022100f6ca10f7ed57a020679899b26dd5ac5a1079265885e2a6477f527b6a3f02b5ca02207b550eb3e7b69360aff977f7f6afac99c3f28266b6c5338ce373f6b59263000a"); - assert_se(EVP_PKEY_verify(ctx, sig, sig_len, (unsigned char*) msg, strlen(msg)) == 1); + assert_se(sym_EVP_PKEY_verify(ctx, sig, sig_len, (unsigned char*) msg, strlen(msg)) == 1); DEFINE_HEX_PTR(invalid_sig, "1234"); - assert_se(EVP_PKEY_verify(ctx, invalid_sig, invalid_sig_len, (unsigned char*) msg, strlen(msg)) != 1); + assert_se(sym_EVP_PKEY_verify(ctx, invalid_sig, invalid_sig_len, (unsigned char*) msg, strlen(msg)) != 1); _cleanup_free_ void *x2 = NULL, *y2 = NULL; size_t x2_size, y2_size; @@ -465,4 +465,31 @@ TEST(ecc_ecdh) { assert_se(memcmp_nn(secretAC, secretAC_size, secretAB, secretAB_size) != 0); } -DEFINE_TEST_MAIN(LOG_DEBUG); +TEST(string_hashsum) { + _cleanup_free_ char *out1 = NULL, *out2 = NULL, *out3 = NULL, *out4 = NULL; + + ASSERT_OK(string_hashsum("asdf", 4, "SHA224", &out1)); + /* echo -n 'asdf' | sha224sum - */ + ASSERT_STREQ(out1, "7872a74bcbf298a1e77d507cd95d4f8d96131cbbd4cdfc571e776c8a"); + + ASSERT_OK(string_hashsum("asdf", 4, "SHA256", &out2)); + /* echo -n 'asdf' | sha256sum - */ + ASSERT_STREQ(out2, "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b"); + + ASSERT_OK(string_hashsum("", 0, "SHA224", &out3)); + /* echo -n '' | sha224sum - */ + ASSERT_STREQ(out3, "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f"); + + ASSERT_OK(string_hashsum("", 0, "SHA256", &out4)); + /* echo -n '' | sha256sum - */ + ASSERT_STREQ(out4, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); +} + +static int intro(void) { + if (dlopen_libcrypto(LOG_DEBUG) < 0) + return log_tests_skipped("libcrypto is not available"); + + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/src/test/test-cryptolib.c b/src/test/test-cryptolib.c deleted file mode 100644 index d86236bafe028..0000000000000 --- a/src/test/test-cryptolib.c +++ /dev/null @@ -1,27 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "alloc-util.h" -#include "openssl-util.h" -#include "tests.h" - -TEST(string_hashsum) { - _cleanup_free_ char *out1 = NULL, *out2 = NULL, *out3 = NULL, *out4 = NULL; - - ASSERT_OK(string_hashsum("asdf", 4, "SHA224", &out1)); - /* echo -n 'asdf' | sha224sum - */ - ASSERT_STREQ(out1, "7872a74bcbf298a1e77d507cd95d4f8d96131cbbd4cdfc571e776c8a"); - - ASSERT_OK(string_hashsum("asdf", 4, "SHA256", &out2)); - /* echo -n 'asdf' | sha256sum - */ - ASSERT_STREQ(out2, "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b"); - - ASSERT_OK(string_hashsum("", 0, "SHA224", &out3)); - /* echo -n '' | sha224sum - */ - ASSERT_STREQ(out3, "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f"); - - ASSERT_OK(string_hashsum("", 0, "SHA256", &out4)); - /* echo -n '' | sha256sum - */ - ASSERT_STREQ(out4, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); -} - -DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-dlopen-so.c b/src/test/test-dlopen-so.c index d3121981cf50a..7421a77024f1b 100644 --- a/src/test/test-dlopen-so.c +++ b/src/test/test-dlopen-so.c @@ -5,6 +5,7 @@ #include "blkid-util.h" #include "bpf-dlopen.h" #include "compress.h" +#include "crypto-util.h" #include "cryptsetup-util.h" #include "curl-util.h" #include "elf-util.h" @@ -28,6 +29,7 @@ #include "qrcode-util.h" #include "seccomp-util.h" #include "selinux-util.h" +#include "ssl-util.h" #include "tests.h" #include "tpm2-util.h" @@ -68,6 +70,8 @@ static int run(int argc, char **argv) { ASSERT_DLOPEN(dlopen_libpam, HAVE_PAM); ASSERT_DLOPEN(dlopen_libseccomp, HAVE_SECCOMP); ASSERT_DLOPEN(dlopen_libselinux, HAVE_SELINUX); + ASSERT_DLOPEN(dlopen_libcrypto, HAVE_OPENSSL); + ASSERT_DLOPEN(dlopen_libssl, HAVE_OPENSSL); ASSERT_DLOPEN(dlopen_xz, HAVE_XZ); ASSERT_DLOPEN(dlopen_lz4, HAVE_LZ4); ASSERT_DLOPEN(dlopen_microhttpd, HAVE_MICROHTTPD); diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c index 78e2d234ab5c7..c9a2e0a9bb80b 100644 --- a/src/test/test-tpm2.c +++ b/src/test/test-tpm2.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "architecture.h" +#include "crypto-util.h" #include "hexdecoct.h" #include "tests.h" #include "tpm2-util.h" @@ -782,25 +783,25 @@ TEST(tpm2b_public_to_openssl_pkey) { _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_rsa = NULL; assert_se(tpm2_tpm2b_public_to_openssl_pkey(&public, &pkey_rsa) >= 0); - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx_rsa = EVP_PKEY_CTX_new(pkey_rsa, NULL); + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx_rsa = sym_EVP_PKEY_CTX_new(pkey_rsa, NULL); assert_se(ctx_rsa); - assert_se(EVP_PKEY_verify_init(ctx_rsa) == 1); - assert_se(EVP_PKEY_CTX_set_signature_md(ctx_rsa, EVP_sha256()) > 0); + assert_se(sym_EVP_PKEY_verify_init(ctx_rsa) == 1); + assert_se(sym_EVP_PKEY_CTX_set_signature_md(ctx_rsa, sym_EVP_sha256()) > 0); DEFINE_HEX_PTR(sig_rsa, "9f70a9e68911be3ec464cae91126328307bf355872127e042d6c61e0a80982872c151033bcf727abfae5fc9500c923120011e7ef4aa5fc690a59a034697b6022c141b4b209e2df6f4b282288cd9181073fbe7158ce113c79d87623423c1f3996ff931e59cc91db74f8e8656215b1436fc93ddec0f1f8fa8510826e674b250f047e6cba94c95ff98072a286baca94646b577974a1e00d56c21944e38960d8ee90511a2f938e5cf1ac7b7cc7ff8e3ac001d321254d3e4f988b90e9f6f873c26ecd0a12a626b3474833cdbb9e9f793238f6c97ee5b75a1a89bb7a7858d34ecfa6d34ac58d95085e6c4fbbebd47a4364be2725c2c6b3fa15d916f3c0b62a66fe76ae"); - assert_se(EVP_PKEY_verify(ctx_rsa, sig_rsa, sig_rsa_len, (unsigned char*) msg, msg_len) == 1); + assert_se(sym_EVP_PKEY_verify(ctx_rsa, sig_rsa, sig_rsa_len, (unsigned char*) msg, msg_len) == 1); /* ECC */ tpm2b_public_ecc_init(&public, TPM2_ECC_NIST_P256, "6fc0ecf3645c673ab7e86d1ec5b315afb950257c5f68ab23296160006711fac2", "8dd2ef7a2c9ecede91493ba98c8fb3f893aff325c6a1e0f752c657b2d6ca1413"); _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_ecc = NULL; assert_se(tpm2_tpm2b_public_to_openssl_pkey(&public, &pkey_ecc) >= 0); - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx_ecc = EVP_PKEY_CTX_new(pkey_ecc, NULL); + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx_ecc = sym_EVP_PKEY_CTX_new(pkey_ecc, NULL); assert_se(ctx_ecc); - assert_se(EVP_PKEY_verify_init(ctx_ecc) == 1); + assert_se(sym_EVP_PKEY_verify_init(ctx_ecc) == 1); DEFINE_HEX_PTR(sig_ecc, "304602210092447ac0b5b32e90923f79bb4aba864b9c546a9900cf193a83243d35d189a2110221009a8b4df1dfa85e225eff9c606694d4d205a7a3968c9552f50bc2790209a90001"); - assert_se(EVP_PKEY_verify(ctx_ecc, sig_ecc, sig_ecc_len, (unsigned char*) msg, msg_len) == 1); + assert_se(sym_EVP_PKEY_verify(ctx_ecc, sig_ecc, sig_ecc_len, (unsigned char*) msg, msg_len) == 1); } static void get_tpm2b_public_from_pem(const void *pem, size_t pem_size, TPM2B_PUBLIC *ret) { diff --git a/src/tpm2-setup/meson.build b/src/tpm2-setup/meson.build index bac29cbbdcabe..e87a13d8e66c9 100644 --- a/src/tpm2-setup/meson.build +++ b/src/tpm2-setup/meson.build @@ -10,7 +10,7 @@ executables += [ 'HAVE_TPM2', ], 'dependencies' : [ - libopenssl, + libopenssl_cflags, ], }, libexec_template + { diff --git a/src/tpm2-setup/tpm2-setup.c b/src/tpm2-setup/tpm2-setup.c index 74e13219f7a27..b779650b73384 100644 --- a/src/tpm2-setup/tpm2-setup.c +++ b/src/tpm2-setup/tpm2-setup.c @@ -9,6 +9,7 @@ #include "build.h" #include "conf-files.h" #include "constants.h" +#include "crypto-util.h" #include "errno-util.h" #include "fd-util.h" #include "fileio.h" @@ -127,7 +128,7 @@ static void public_key_data_done(struct public_key_data *d) { assert(d); if (d->pkey) { - EVP_PKEY_free(d->pkey); + sym_EVP_PKEY_free(d->pkey); d->pkey = NULL; } if (d->public) { @@ -148,7 +149,7 @@ static int public_key_make_fingerprint(struct public_key_data *d) { assert(!d->fingerprint); assert(!d->fingerprint_hex); - r = pubkey_fingerprint(d->pkey, EVP_sha256(), &d->fingerprint, &d->fingerprint_size); + r = pubkey_fingerprint(d->pkey, sym_EVP_sha256(), &d->fingerprint, &d->fingerprint_size); if (r < 0) return log_error_errno(r, "Failed to calculate fingerprint of public key: %m"); @@ -320,7 +321,7 @@ static int setup_srk(void) { if (r < 0) return log_error_errno(r, "Failed to open SRK public key file '%s' for writing: %m", pem_path); - if (PEM_write_PUBKEY(f, tpm2_key.pkey) <= 0) + if (sym_PEM_write_PUBKEY(f, tpm2_key.pkey) <= 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write SRK public key file '%s'.", pem_path); if (fchmod(fileno(f), 0444) < 0) @@ -504,6 +505,10 @@ static int run(int argc, char *argv[]) { return EXIT_SUCCESS; } + r = dlopen_libcrypto(LOG_ERR); + if (r < 0) + return r; + umask(0022); /* Execute both jobs, and then return unlisted errors preferably, and listed errors From 1b15c70e0d74330d4c067c1805b2ee5866104a6c Mon Sep 17 00:00:00 2001 From: Simon de Vlieger Date: Sat, 18 Apr 2026 19:37:34 +0200 Subject: [PATCH 1226/1296] shared: find-esp fsroot check skip When running with `SYSTEMD_RELAX_ESP_CHECKS=1` the fsroot check is still being ran; preventing (for example) `bootctl` from operating a on a tree as it expects a filesystem to be mounted where it finds the ESP (or XBOOTLDR). Expand the enum with an additional option to skip the fsroot checks and enable it by default when `SYSTEMD_RELAX_ESP_CHECKS=1`. See these RFEs [1], [2] for rationale. [1]: https://github.com/systemd/systemd/issues/29871 [2]: https://github.com/systemd/systemd/issues/41707 Signed-off-by: Simon de Vlieger --- src/shared/find-esp.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/shared/find-esp.c b/src/shared/find-esp.c index f04e3a14dcbb2..7337d9f999018 100644 --- a/src/shared/find-esp.c +++ b/src/shared/find-esp.c @@ -31,6 +31,7 @@ typedef enum VerifyESPFlags { VERIFY_ESP_UNPRIVILEGED_MODE = 1 << 1, /* Call into udev rather than blkid */ VERIFY_ESP_SKIP_FSTYPE_CHECK = 1 << 2, /* Skip filesystem check */ VERIFY_ESP_SKIP_DEVICE_CHECK = 1 << 3, /* Skip device node check */ + VERIFY_ESP_SKIP_FSROOT_CHECK = 1 << 4, /* Skip fsroot check */ } VerifyESPFlags; static VerifyESPFlags verify_esp_flags_init(int unprivileged_mode, const char *env_name_for_relaxing) { @@ -48,7 +49,7 @@ static VerifyESPFlags verify_esp_flags_init(int unprivileged_mode, const char *e if (r < 0 && r != -ENXIO) log_debug_errno(r, "Failed to parse $%s environment variable, assuming false.", env_name_for_relaxing); else if (r > 0) - flags |= VERIFY_ESP_SKIP_FSTYPE_CHECK | VERIFY_ESP_SKIP_DEVICE_CHECK; + flags |= VERIFY_ESP_SKIP_FSTYPE_CHECK | VERIFY_ESP_SKIP_DEVICE_CHECK | VERIFY_ESP_SKIP_FSROOT_CHECK; if (detect_container() > 0) flags |= VERIFY_ESP_SKIP_DEVICE_CHECK; @@ -356,9 +357,11 @@ static int verify_esp( } dev_t devid = 0; - r = verify_fsroot_dir(p, fd, flags, FLAGS_SET(flags, VERIFY_ESP_SKIP_DEVICE_CHECK) ? NULL : &devid); - if (r < 0) - return r; + if (!FLAGS_SET(flags, VERIFY_ESP_SKIP_FSROOT_CHECK)) { + r = verify_fsroot_dir(p, fd, flags, FLAGS_SET(flags, VERIFY_ESP_SKIP_DEVICE_CHECK) ? NULL : &devid); + if (r < 0) + return r; + } /* In a container we don't have access to block devices, skip this part of the verification, we trust * the container manager set everything up correctly on its own. */ @@ -742,9 +745,11 @@ static int verify_xbootldr( r, "Failed to open directory \"%s\": %m", path); dev_t devid = 0; - r = verify_fsroot_dir(p, fd, flags, FLAGS_SET(flags, VERIFY_ESP_SKIP_DEVICE_CHECK) ? NULL : &devid); - if (r < 0) - return r; + if (!FLAGS_SET(flags, VERIFY_ESP_SKIP_FSROOT_CHECK)) { + r = verify_fsroot_dir(p, fd, flags, FLAGS_SET(flags, VERIFY_ESP_SKIP_DEVICE_CHECK) ? NULL : &devid); + if (r < 0) + return r; + } if (FLAGS_SET(flags, VERIFY_ESP_SKIP_DEVICE_CHECK)) { if (ret_uuid) From ed6cf6bed8d5bbd8f0fa16ea9d025095db9022f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 11:32:33 +0200 Subject: [PATCH 1227/1296] inhibit: convert to the new option parser --help is the same, except for common options and a rewording of description of --what. Co-developed-by: Claude Opus 4.6 --- src/login/inhibit.c | 152 +++++++++++++++++--------------------------- 1 file changed, 57 insertions(+), 95 deletions(-) diff --git a/src/login/inhibit.c b/src/login/inhibit.c index 493f06f24e4e4..4002d3fe4ae76 100644 --- a/src/login/inhibit.c +++ b/src/login/inhibit.c @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -18,6 +17,7 @@ #include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "pidref.h" @@ -170,139 +170,100 @@ static int print_inhibitors(sd_bus *bus) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-inhibit", "1", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...] COMMAND ...\n" - "\n%sExecute a process while inhibiting shutdown/sleep/idle.%s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-ask-password Do not attempt interactive authorization\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " --json=pretty|short|off\n" - " Generate JSON output\n" - " --what=WHAT Operations to inhibit, colon separated list of:\n" - " shutdown, sleep, idle, handle-power-key,\n" - " handle-suspend-key, handle-hibernate-key,\n" - " handle-lid-switch\n" - " --who=STRING A descriptive string who is inhibiting\n" - " --why=STRING A descriptive string why is being inhibited\n" - " --mode=MODE One of block, block-weak, or delay\n" - " --list List active inhibitors\n" - "\nSee the %s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] COMMAND ...\n\n" + "%sExecute a process while inhibiting shutdown/sleep/idle.%s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_WHAT, - ARG_WHO, - ARG_WHY, - ARG_MODE, - ARG_LIST, - ARG_NO_ASK_PASSWORD, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_JSON, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "what", required_argument, NULL, ARG_WHAT }, - { "who", required_argument, NULL, ARG_WHO }, - { "why", required_argument, NULL, ARG_WHY }, - { "mode", required_argument, NULL, ARG_MODE }, - { "list", no_argument, NULL, ARG_LIST }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "json", required_argument, NULL, ARG_JSON }, - {} - }; - - int c, r; - +static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argc >= 0); assert(argv); + assert(remaining_args); - /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long() - * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */ - optind = 0; - while ((c = getopt_long(argc, argv, "+h", options, NULL)) >= 0) + OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_WHAT: - arg_what = optarg; + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; break; - case ARG_WHO: - arg_who = optarg; + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; break; - case ARG_WHY: - arg_why = optarg; + OPTION_COMMON_NO_LEGEND: + arg_legend = false; break; - case ARG_MODE: - arg_mode = optarg; + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); + if (r <= 0) + return r; break; - case ARG_LIST: - arg_action = ACTION_LIST; + OPTION_LONG("what", "WHAT", + "Operations to inhibit, colon separated list " + "(shutdown, sleep, idle, handle-power-key, " + "handle-suspend-key, handle-hibernate-key, " + "handle-lid-switch)"): + arg_what = arg; break; - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; + OPTION_LONG("who", "STRING", + "A descriptive string who is inhibiting"): + arg_who = arg; break; - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; + OPTION_LONG("why", "STRING", + "A descriptive string why is being inhibited"): + arg_why = arg; break; - case ARG_NO_LEGEND: - arg_legend = false; + OPTION_LONG("mode", "MODE", "One of block, block-weak, or delay"): + arg_mode = arg; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); - if (r <= 0) - return r; - + OPTION_LONG("list", NULL, "List active inhibitors"): + arg_action = ACTION_LIST; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (arg_action == ACTION_INHIBIT && optind == argc) - arg_action = ACTION_LIST; + char **args = option_parser_get_args(&state); - else if (arg_action == ACTION_INHIBIT && optind >= argc) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Missing command line to execute."); + if (arg_action == ACTION_INHIBIT && strv_isempty(args)) + arg_action = ACTION_LIST; + *remaining_args = args; return 1; } @@ -312,7 +273,8 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -337,7 +299,7 @@ static int run(int argc, char *argv[]) { arg_what = "idle:sleep:shutdown"; if (!arg_who) { - w = strv_join(argv + optind, " "); + w = strv_join(args, " "); if (!w) return log_oom(); @@ -354,7 +316,7 @@ static int run(int argc, char *argv[]) { if (fd < 0) return log_error_errno(fd, "Failed to inhibit: %s", bus_error_message(&error, fd)); - arguments = strv_copy(argv + optind); + arguments = strv_copy(args); if (!arguments) return log_oom(); @@ -370,7 +332,7 @@ static int run(int argc, char *argv[]) { _exit(EXIT_FAILURE); } - return pidref_wait_for_terminate_and_check(argv[optind], &pidref, WAIT_LOG_ABNORMAL); + return pidref_wait_for_terminate_and_check(args[0], &pidref, WAIT_LOG_ABNORMAL); } } From bcbf53b8582d1dd3b47a9170e778475e37532528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 23 Apr 2026 00:54:19 +0200 Subject: [PATCH 1228/1296] journal-gatewayd: convert to the new option parser Co-developed-by: Claude Opus 4.7 --- src/journal-remote/journal-gatewayd.c | 123 ++++++++++---------------- 1 file changed, 46 insertions(+), 77 deletions(-) diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index 42e3f6df29572..7052fd000bd5e 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include @@ -18,6 +17,7 @@ #include "errno-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "glob-util.h" #include "hostname-setup.h" #include "hostname-util.h" @@ -28,6 +28,7 @@ #include "main-func.h" #include "memory-util.h" #include "microhttpd-util.h" +#include "options.h" #include "os-util.h" #include "output-mode.h" #include "parse-util.h" @@ -1090,92 +1091,52 @@ static mhd_result request_handler( static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-journal-gatewayd.service", "8", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...] ...\n\n" - "HTTP server for journal events.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --cert=CERT.PEM Server certificate in PEM format\n" - " --key=KEY.PEM Server key in PEM format\n" - " --trust=CERT.PEM Certificate authority certificate in PEM format\n" - " --system Serve system journal\n" - " --user Serve the user journal for the current user\n" - " -m --merge Serve all available journals\n" - " -D --directory=PATH Serve journal files in directory\n" - " --file=PATH Serve this journal file\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - link); + "HTTP server for journal events.\n\n", + program_invocation_short_name); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_KEY, - ARG_CERT, - ARG_TRUST, - ARG_USER, - ARG_SYSTEM, - ARG_MERGE, - ARG_FILE, - }; - - int r, c; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "key", required_argument, NULL, ARG_KEY }, - { "cert", required_argument, NULL, ARG_CERT }, - { "trust", required_argument, NULL, ARG_TRUST }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "merge", no_argument, NULL, 'm' }, - { "directory", required_argument, NULL, 'D' }, - { "file", required_argument, NULL, ARG_FILE }, - {} - }; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hD:", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_KEY: - if (arg_key_pem) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Key file specified twice"); - r = read_full_file_full( - AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX, - READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, - NULL, - &arg_key_pem, NULL); - if (r < 0) - return log_error_errno(r, "Failed to read key file: %m"); - assert(arg_key_pem); - break; - - case ARG_CERT: + OPTION_LONG("cert", "CERT.PEM", "Server certificate in PEM format"): if (arg_cert_pem) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Certificate file specified twice"); r = read_full_file_full( - AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX, + AT_FDCWD, arg, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_CONNECT_SOCKET, NULL, &arg_cert_pem, NULL); @@ -1184,13 +1145,27 @@ static int parse_argv(int argc, char *argv[]) { assert(arg_cert_pem); break; - case ARG_TRUST: + OPTION_LONG("key", "KEY.PEM", "Server key in PEM format"): + if (arg_key_pem) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Key file specified twice"); + r = read_full_file_full( + AT_FDCWD, arg, UINT64_MAX, SIZE_MAX, + READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, + NULL, + &arg_key_pem, NULL); + if (r < 0) + return log_error_errno(r, "Failed to read key file: %m"); + assert(arg_key_pem); + break; + + OPTION_LONG("trust", "CERT.PEM", "Certificate authority certificate in PEM format"): #if HAVE_GNUTLS if (arg_trust_pem) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "CA certificate file specified twice"); r = read_full_file_full( - AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX, + AT_FDCWD, arg, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_CONNECT_SOCKET, NULL, &arg_trust_pem, NULL); @@ -1203,38 +1178,32 @@ static int parse_argv(int argc, char *argv[]) { "Option --trust= is not available."); #endif - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Serve system journal"): arg_journal_type |= SD_JOURNAL_SYSTEM; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Serve the user journal for the current user"): arg_journal_type |= SD_JOURNAL_CURRENT_USER; break; - case 'm': + OPTION('m', "merge", NULL, "Serve all available journals"): arg_merge = true; break; - case 'D': - r = free_and_strdup_warn(&arg_directory, optarg); + OPTION('D', "directory", "PATH", "Serve journal files in directory"): + r = free_and_strdup_warn(&arg_directory, arg); if (r < 0) return r; break; - case ARG_FILE: - r = glob_extend(&arg_file, optarg, GLOB_NOCHECK); + OPTION_LONG("file", "PATH", "Serve this journal file"): + r = glob_extend(&arg_file, arg, GLOB_NOCHECK); if (r < 0) return log_error_errno(r, "Failed to add paths: %m"); break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind < argc) + if (option_parser_get_n_args(&state) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program does not take arguments."); From b8a96ab34fdc92d7a6a06b8e1465f361622cfb6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 23 Apr 2026 21:24:08 +0200 Subject: [PATCH 1229/1296] storagetm: convert to the new option parser Co-developed-by: Claude Opus 4.7 --- src/storagetm/storagetm.c | 86 +++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 49 deletions(-) diff --git a/src/storagetm/storagetm.c b/src/storagetm/storagetm.c index 7a4596e4724ef..2b0ed742c1a16 100644 --- a/src/storagetm/storagetm.c +++ b/src/storagetm/storagetm.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -17,12 +16,14 @@ #include "device-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "fs-util.h" #include "hashmap.h" #include "id128-util.h" #include "local-addresses.h" #include "main-func.h" #include "mountpoint-util.h" +#include "options.h" #include "os-util.h" #include "path-util.h" #include "plymouth-util.h" @@ -47,99 +48,86 @@ STATIC_DESTRUCTOR_REGISTER(arg_nqn, freep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-storagetm", "8", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...] [DEVICE...]\n" - "\n%sExpose a block device or regular file as NVMe-TCP volume.%s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --nqn=STRING Select NQN (NVMe Qualified Name)\n" - " -a --all Expose all devices\n" - " --list-devices List candidate block devices to operate on\n" - "\nSee the %s for details.\n", + "\n%sExpose a block device or regular file as NVMe-TCP volume.%s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_NQN = 0x100, - ARG_VERSION, - ARG_LIST_DEVICES, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "nqn", required_argument, NULL, ARG_NQN }, - { "all", no_argument, NULL, 'a' }, - { "list-devices", no_argument, NULL, ARG_LIST_DEVICES }, - {} - }; - - int r, c; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "ha", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NQN: - if (!filename_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "NQN invalid: %s", optarg); + OPTION_LONG("nqn", "STRING", + "Select NQN (NVMe Qualified Name)"): + if (!filename_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "NQN invalid: %s", arg); - if (free_and_strdup(&arg_nqn, optarg) < 0) + if (free_and_strdup(&arg_nqn, arg) < 0) return log_oom(); break; - case 'a': + OPTION('a', "all", NULL, "Expose all devices"): arg_all++; break; - case ARG_LIST_DEVICES: + OPTION_LONG("list-devices", NULL, + "List candidate block devices to operate on"): r = blockdev_list(BLOCKDEV_LIST_SHOW_SYMLINKS|BLOCKDEV_LIST_IGNORE_ZRAM, /* ret_devices= */ NULL, /* ret_n_devices= */ NULL); if (r < 0) return r; return 0; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + char **args = option_parser_get_args(&state); if (arg_all > 0) { - if (argc > optind) + if (!strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expects no further arguments if --all/-a is specified."); } else { - if (optind >= argc) + if (strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expecting device name or --all/-a."); - for (int i = optind; i < argc; i++) - if (!path_is_valid(argv[i])) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path: %s", argv[i]); + STRV_FOREACH(a, args) + if (!path_is_valid(*a)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path: %s", *a); - arg_devices = strv_copy(argv + optind); + arg_devices = strv_copy(args); + if (!arg_devices) + return log_oom(); } if (!arg_nqn) { From 3f575a7aefb0c3b2652f6a92dc76029a4c740d9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 23 Apr 2026 22:02:31 +0200 Subject: [PATCH 1230/1296] keyutil: use OPTION_COMMON macros in a few places Somehow those slipped through. --- src/keyutil/keyutil.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/keyutil/keyutil.c b/src/keyutil/keyutil.c index dcdd26422674f..c29c4a0fa42ad 100644 --- a/src/keyutil/keyutil.c +++ b/src/keyutil/keyutil.c @@ -100,15 +100,13 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_COMMON_VERSION: return version(); - OPTION_LONG("private-key", "KEY", "Private key in PEM format"): + OPTION_COMMON_PRIVATE_KEY("Private key in PEM format"): r = free_and_strdup_warn(&arg_private_key, arg); if (r < 0) return r; break; - OPTION_LONG("private-key-source", "SOURCE", - "Specify how to use KEY for --private-key= " - "(file, provider:PROVIDER, engine:ENGINE)"): + OPTION_COMMON_PRIVATE_KEY_SOURCE: r = parse_openssl_key_source_argument( arg, &arg_private_key_source, @@ -117,17 +115,13 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return r; break; - OPTION_LONG("certificate", "PATH|URI", - "PEM certificate to use for signing, " - "or a provider-specific designation if --certificate-source= is used"): + OPTION_COMMON_CERTIFICATE("PEM certificate to use for signing"): r = free_and_strdup_warn(&arg_certificate, arg); if (r < 0) return r; break; - OPTION_LONG("certificate-source", "SOURCE", - "Specify how to interpret the certificate from --certificate= " - "(file, provider:PROVIDER)"): + OPTION_COMMON_CERTIFICATE_SOURCE: r = parse_openssl_certificate_source_argument( arg, &arg_certificate_source, @@ -226,7 +220,7 @@ static int verb_validate(int argc, char *argv[], uintptr_t _data, void *userdata VERB_NOARG(verb_extract_public, "extract-public", "Extract a public key"); -VERB(verb_extract_public, "public", NULL, VERB_ANY, 1, 0, NULL); /* Deprecated alias */ +VERB_NOARG(verb_extract_public, "public", /* help= */ NULL); /* Deprecated but kept for backward compat. */ static int verb_extract_public(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(EVP_PKEY_freep) EVP_PKEY *public_key = NULL; int r; From d6f0f3ebdf02b2f8bcf3b480907f0b76c55a4040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 23 Apr 2026 22:10:22 +0200 Subject: [PATCH 1231/1296] tty-ask-password-agent: convert to the new option parser --help is identical except for whitespace. Co-developed-by: Claude Opus 4.7 --- .../tty-ask-password-agent.c | 94 +++++++------------ 1 file changed, 34 insertions(+), 60 deletions(-) diff --git a/src/tty-ask-password-agent/tty-ask-password-agent.c b/src/tty-ask-password-agent/tty-ask-password-agent.c index 1ee2239aa4a59..b927871d211c3 100644 --- a/src/tty-ask-password-agent/tty-ask-password-agent.c +++ b/src/tty-ask-password-agent/tty-ask-password-agent.c @@ -4,7 +4,6 @@ ***/ #include -#include #include #include #include @@ -23,12 +22,14 @@ #include "errno-util.h" #include "exit-status.h" #include "fd-util.h" -#include "format-util.h" #include "fileio.h" +#include "format-table.h" +#include "format-util.h" #include "inotify-util.h" #include "io-util.h" #include "main-func.h" #include "mkdir-label.h" +#include "options.h" #include "path-util.h" #include "pidref.h" #include "pretty-print.h" @@ -442,112 +443,85 @@ static int process_and_watch_password_files(bool watch) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-tty-ask-password-agent", "1", &link); if (r < 0) return log_oom(); - printf("%s [OPTIONS...]\n\n" - "%sProcess system password requests.%s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --list Show pending password requests\n" - " --query Process pending password requests\n" - " --watch Continuously process password requests\n" - " --wall Continuously forward password requests to wall\n" - " --plymouth Ask question with Plymouth instead of on TTY\n" - " --console[=DEVICE] Ask question on /dev/console (or DEVICE if specified)\n" - " instead of the current TTY\n" - "\nSee the %s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n" + "\n%sProcess system password requests.%s\n\n", program_invocation_short_name, ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_LIST = 0x100, - ARG_QUERY, - ARG_WATCH, - ARG_WALL, - ARG_PLYMOUTH, - ARG_CONSOLE, - ARG_VERSION - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "list", no_argument, NULL, ARG_LIST }, - { "query", no_argument, NULL, ARG_QUERY }, - { "watch", no_argument, NULL, ARG_WATCH }, - { "wall", no_argument, NULL, ARG_WALL }, - { "plymouth", no_argument, NULL, ARG_PLYMOUTH }, - { "console", optional_argument, NULL, ARG_CONSOLE }, - {} - }; - - int r, c; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_LIST: + OPTION_LONG("list", NULL, "Show pending password requests"): arg_action = ACTION_LIST; break; - case ARG_QUERY: + OPTION_LONG("query", NULL, "Process pending password requests"): arg_action = ACTION_QUERY; break; - case ARG_WATCH: + OPTION_LONG("watch", NULL, "Continuously process password requests"): arg_action = ACTION_WATCH; break; - case ARG_WALL: + OPTION_LONG("wall", NULL, "Continuously forward password requests to wall"): arg_action = ACTION_WALL; break; - case ARG_PLYMOUTH: + OPTION_LONG("plymouth", NULL, + "Ask question with Plymouth instead of on TTY"): arg_plymouth = true; break; - case ARG_CONSOLE: + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "console", "DEVICE", + "Ask question on /dev/console (or DEVICE if specified) instead of the current TTY"): arg_console = true; - if (optarg) { - if (isempty(optarg)) + if (arg) { + if (isempty(arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty console device path is not allowed."); - r = free_and_strdup_warn(&arg_device, optarg); + r = free_and_strdup_warn(&arg_device, arg); if (r < 0) return r; } break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind != argc) + if (option_parser_get_n_args(&state) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s takes no arguments.", program_invocation_short_name); From 5e6ce500685c6a31b895b2623277eed68a71835a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 23 Apr 2026 22:13:06 +0200 Subject: [PATCH 1232/1296] sysctl: rename local Option struct to SysctlOption Avoid collision with Option struct from options.h (option parser). Co-developed-by: Claude Opus 4.7 --- src/sysctl/sysctl.c | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/sysctl/sysctl.c b/src/sysctl/sysctl.c index cf7dea24f58d4..7ef9a0d3f9ce7 100644 --- a/src/sysctl/sysctl.c +++ b/src/sysctl/sysctl.c @@ -29,13 +29,13 @@ static PagerFlags arg_pager_flags = 0; STATIC_DESTRUCTOR_REGISTER(arg_prefixes, strv_freep); -typedef struct Option { +typedef struct SysctlOption { char *key; char *value; bool ignore_failure; -} Option; +} SysctlOption; -static Option* option_free(Option *o) { +static SysctlOption* sysctl_option_free(SysctlOption *o) { if (!o) return NULL; @@ -45,11 +45,11 @@ static Option* option_free(Option *o) { return mfree(o); } -DEFINE_TRIVIAL_CLEANUP_FUNC(Option*, option_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(SysctlOption*, sysctl_option_free); DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( - option_hash_ops, + sysctl_option_hash_ops, char, string_hash_func, string_compare_func, - Option, option_free); + SysctlOption, sysctl_option_free); static bool test_prefix(const char *p) { if (strv_isempty(arg_prefixes)) @@ -58,20 +58,20 @@ static bool test_prefix(const char *p) { return path_startswith_strv(p, arg_prefixes); } -static Option* option_new( +static SysctlOption* sysctl_option_new( const char *key, const char *value, bool ignore_failure) { - _cleanup_(option_freep) Option *o = NULL; + _cleanup_(sysctl_option_freep) SysctlOption *o = NULL; assert(key); - o = new(Option, 1); + o = new(SysctlOption, 1); if (!o) return NULL; - *o = (Option) { + *o = (SysctlOption) { .key = strdup(key), .value = value ? strdup(value) : NULL, .ignore_failure = ignore_failure, @@ -108,7 +108,7 @@ static int sysctl_write_or_warn(const char *key, const char *value, bool ignore_ return 0; } -static int apply_glob_option_with_prefix(OrderedHashmap *sysctl_options, Option *option, const char *prefix) { +static int apply_glob_option_with_prefix(OrderedHashmap *sysctl_options, SysctlOption *option, const char *prefix) { _cleanup_strv_free_ char **paths = NULL; _cleanup_free_ char *pattern = NULL; int r; @@ -179,7 +179,7 @@ static int apply_glob_option_with_prefix(OrderedHashmap *sysctl_options, Option return r; } -static int apply_glob_option(OrderedHashmap *sysctl_options, Option *option) { +static int apply_glob_option(OrderedHashmap *sysctl_options, SysctlOption *option) { int r = 0; if (strv_isempty(arg_prefixes)) @@ -191,7 +191,7 @@ static int apply_glob_option(OrderedHashmap *sysctl_options, Option *option) { } static int apply_all(OrderedHashmap *sysctl_options) { - Option *option; + SysctlOption *option; int r = 0; ORDERED_HASHMAP_FOREACH(option, sysctl_options) { @@ -253,7 +253,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer, bool if (!string_is_glob(key) && !test_prefix(key)) return 0; - Option *existing = ordered_hashmap_get(*sysctl_options, key); + SysctlOption *existing = ordered_hashmap_get(*sysctl_options, key); if (existing) { if (streq_ptr(value, existing->value)) { existing->ignore_failure = existing->ignore_failure || ignore_failure; @@ -262,14 +262,14 @@ static int parse_line(const char *fname, unsigned line, const char *buffer, bool log_syntax(NULL, LOG_DEBUG, fname, line, 0, "Overwriting earlier assignment of '%s'.", key); - option_free(ordered_hashmap_remove(*sysctl_options, key)); + sysctl_option_free(ordered_hashmap_remove(*sysctl_options, key)); } - _cleanup_(option_freep) Option *option = option_new(key, value, ignore_failure); + _cleanup_(sysctl_option_freep) SysctlOption *option = sysctl_option_new(key, value, ignore_failure); if (!option) return log_oom(); - r = ordered_hashmap_ensure_put(sysctl_options, &option_hash_ops, option->key, option); + r = ordered_hashmap_ensure_put(sysctl_options, &sysctl_option_hash_ops, option->key, option); if (r < 0) return log_error_errno(r, "Failed to add sysctl variable '%s' to hashmap: %m", key); From ee287be0adf57178e76096755addd16eb0a0d0ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 23 Apr 2026 22:23:47 +0200 Subject: [PATCH 1233/1296] sysctl: convert to the new option parser --help output is the same except for common strings and command reordering. Co-developed-by: Claude Opus 4.7 --- src/sysctl/sysctl.c | 128 ++++++++++++++++++++------------------------ 1 file changed, 59 insertions(+), 69 deletions(-) diff --git a/src/sysctl/sysctl.c b/src/sysctl/sysctl.c index 7ef9a0d3f9ce7..39041ea384502 100644 --- a/src/sysctl/sysctl.c +++ b/src/sysctl/sysctl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -10,10 +9,12 @@ #include "constants.h" #include "creds-util.h" #include "errno-util.h" +#include "format-table.h" #include "glob-util.h" #include "hashmap.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pager.h" #include "path-util.h" #include "pretty-print.h" @@ -314,122 +315,110 @@ static int cat_config(char **files) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *commands = NULL, *options = NULL; int r; r = terminal_urlify_man("systemd-sysctl.service", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] [CONFIGURATION FILE...]\n" - "\n%2$sApplies kernel sysctl settings.%4$s\n" - "\n%3$sCommands:%4$s\n" - " --cat-config Show configuration files\n" - " --tldr Show non-comment parts of configuration\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\n%3$sOptions:%4$s\n" - " --prefix=PATH Only apply rules with the specified prefix\n" - " --no-pager Do not pipe output into a pager\n" - " --strict Fail on any kind of failures\n" - " --inline Treat arguments as configuration lines\n" - "\nSee the %5$s for details.\n", + r = option_parser_get_help_table(&commands); + if (r < 0) + return r; + + r = option_parser_get_help_table_group("Options", &options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, commands, options); + + printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n" + "\n%sApplies kernel sysctl settings.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, ansi_highlight(), - ansi_underline(), ansi_normal(), - link); + ansi_underline(), + ansi_normal()); - return 0; -} + r = table_print_or_warn(commands); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { + printf("\n%sOptions:%s\n", ansi_underline(), ansi_normal()); - enum { - ARG_VERSION = 0x100, - ARG_CAT_CONFIG, - ARG_TLDR, - ARG_PREFIX, - ARG_NO_PAGER, - ARG_STRICT, - ARG_INLINE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "cat-config", no_argument, NULL, ARG_CAT_CONFIG }, - { "tldr", no_argument, NULL, ARG_TLDR }, - { "prefix", required_argument, NULL, ARG_PREFIX }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "strict", no_argument, NULL, ARG_STRICT }, - { "inline", no_argument, NULL, ARG_INLINE }, - {} - }; + r = table_print_or_warn(options); + if (r < 0) + return r; - int c; + printf("\nSee the %s for details.\n", link); + return 0; +} +static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argc >= 0); assert(argv); + assert(remaining_args); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_CAT_CONFIG: + OPTION_COMMON_CAT_CONFIG: arg_cat_flags = CAT_CONFIG_ON; break; - case ARG_TLDR: + OPTION_COMMON_TLDR: arg_cat_flags = CAT_TLDR; break; - case ARG_PREFIX: { - const char *s; - char *p; + OPTION_GROUP("Options"): {} + + OPTION_LONG("prefix", "PATH", + "Only apply rules with the specified prefix"): { + _cleanup_free_ char *normalized = strdup(arg); + if (!normalized) + return log_oom(); + sysctl_normalize(normalized); /* We used to require people to specify absolute paths * in /proc/sys in the past. This is kinda useless, but * we need to keep compatibility. We now support any * sysctl name available. */ - sysctl_normalize(optarg); - - s = path_startswith(optarg, "/proc/sys"); - p = strdup(s ?: optarg); - if (!p) - return log_oom(); + const char *s = path_startswith(normalized, "/proc/sys"); - if (strv_consume(&arg_prefixes, p) < 0) + if (strv_extend(&arg_prefixes, s ?: normalized) < 0) return log_oom(); break; } - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_STRICT: + OPTION_LONG("strict", NULL, + "Fail on any kind of failures"): arg_strict = true; break; - case ARG_INLINE: + OPTION_LONG("inline", NULL, + "Treat arguments as configuration lines"): arg_inline = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (arg_cat_flags != CAT_CONFIG_OFF && argc > optind) + *remaining_args = option_parser_get_args(&state); + + if (arg_cat_flags != CAT_CONFIG_OFF && !strv_isempty(*remaining_args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Positional arguments are not allowed with --cat-config/--tldr."); @@ -440,7 +429,8 @@ static int run(int argc, char *argv[]) { _cleanup_ordered_hashmap_free_ OrderedHashmap *sysctl_options = NULL; int r; - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -448,10 +438,10 @@ static int run(int argc, char *argv[]) { umask(0022); - if (argc > optind) { + if (!strv_isempty(args)) { unsigned pos = 0; - STRV_FOREACH(arg, strv_skip(argv, optind)) { + STRV_FOREACH(arg, args) { if (arg_inline) /* Use (argument):n, where n==1 for the first positional arg */ RET_GATHER(r, parse_line("(argument)", ++pos, *arg, /* invalid_config= */ NULL, &sysctl_options)); From 90a068ab5d1fa671a4d4e75e3890a33a5bd00136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 23 Apr 2026 22:27:59 +0200 Subject: [PATCH 1234/1296] socket-activate: convert to the new option parser --help is identical except for whitespace and common option strings. Co-developed-by: Claude Opus 4.7 --- src/socket-activate/socket-activate.c | 120 +++++++++++--------------- 1 file changed, 52 insertions(+), 68 deletions(-) diff --git a/src/socket-activate/socket-activate.c b/src/socket-activate/socket-activate.c index e7102b62a1aee..2f0c81a1111d0 100644 --- a/src/socket-activate/socket-activate.c +++ b/src/socket-activate/socket-activate.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -14,9 +13,11 @@ #include "errno-util.h" #include "escape.h" #include "fd-util.h" +#include "format-table.h" #include "format-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "pidfd-util.h" #include "pidref.h" #include "pretty-print.h" @@ -320,83 +321,63 @@ static int install_chld_handler(void) { static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-socket-activate", "1", &link); if (r < 0) return log_oom(); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + printf("%s [OPTIONS...] COMMAND ...\n" "\n%sListen on sockets and launch child on connection.%s\n" - "\nOptions:\n" - " -h --help Show this help and exit\n" - " --version Print version string and exit\n" - " -l --listen=ADDR Listen for raw connections at ADDR\n" - " -d --datagram Listen on datagram instead of stream socket\n" - " --seqpacket Listen on SOCK_SEQPACKET instead of stream socket\n" - " -a --accept Spawn separate child for each connection\n" - " -E --setenv=NAME[=VALUE] Pass an environment variable to children\n" - " --fdname=NAME[:NAME...] Specify names for file descriptors\n" - " --inetd Enable inetd file descriptor passing protocol\n" - " --now Start instantly instead of waiting for connection\n" - "\nNote: file descriptors from sd_listen_fds() will be passed through.\n" - "\nSee the %s for details.\n", + "\n%sOptions:%s\n", program_invocation_short_name, ansi_highlight(), ansi_normal(), - link); + ansi_underline(), + ansi_normal()); + + r = table_print_or_warn(options); + if (r < 0) + return r; + printf("\nNote: file descriptors from sd_listen_fds() will be passed through.\n" + "\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_FDNAME, - ARG_SEQPACKET, - ARG_INETD, - ARG_NOW, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "datagram", no_argument, NULL, 'd' }, - { "seqpacket", no_argument, NULL, ARG_SEQPACKET }, - { "listen", required_argument, NULL, 'l' }, - { "accept", no_argument, NULL, 'a' }, - { "setenv", required_argument, NULL, 'E' }, - { "environment", required_argument, NULL, 'E' }, /* legacy alias */ - { "fdname", required_argument, NULL, ARG_FDNAME }, - { "inetd", no_argument, NULL, ARG_INETD }, - { "now", no_argument, NULL, ARG_NOW }, - {} - }; - - int c, r; - +static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argc >= 0); assert(argv); + assert(remaining_args); + + OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; + const char *arg; + int r; - /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long() - * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */ - optind = 0; - while ((c = getopt_long(argc, argv, "+hl:aE:d", options, NULL)) >= 0) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'l': - r = strv_extend(&arg_listen, optarg); + OPTION('l', "listen", "ADDR", + "Listen for raw connections at ADDR"): + r = strv_extend(&arg_listen, arg); if (r < 0) return log_oom(); break; - case 'd': + OPTION('d', "datagram", NULL, + "Listen on datagram instead of stream socket"): if (arg_socket_type == SOCK_SEQPACKET) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--datagram may not be combined with --seqpacket."); @@ -404,7 +385,8 @@ static int parse_argv(int argc, char *argv[]) { arg_socket_type = SOCK_DGRAM; break; - case ARG_SEQPACKET: + OPTION_LONG("seqpacket", NULL, + "Listen on SOCK_SEQPACKET instead of stream socket"): if (arg_socket_type == SOCK_DGRAM) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--seqpacket may not be combined with --datagram."); @@ -412,20 +394,24 @@ static int parse_argv(int argc, char *argv[]) { arg_socket_type = SOCK_SEQPACKET; break; - case 'a': + OPTION('a', "accept", NULL, + "Spawn separate child for each connection"): arg_accept = true; break; - case 'E': - r = strv_env_replace_strdup_passthrough(&arg_setenv, optarg); + OPTION('E', "setenv", "NAME[=VALUE]", + "Pass an environment variable to children"): {} + OPTION_LONG("environment", "NAME[=VALUE]", /* help= */ NULL): /* legacy alias */ + r = strv_env_replace_strdup_passthrough(&arg_setenv, arg); if (r < 0) - return log_error_errno(r, "Cannot assign environment variable %s: %m", optarg); + return log_error_errno(r, "Cannot assign environment variable %s: %m", arg); break; - case ARG_FDNAME: { + OPTION_LONG("fdname", "NAME[:NAME...]", + "Specify names for file descriptors"): { _cleanup_strv_free_ char **names = NULL; - names = strv_split(optarg, ":"); + names = strv_split(arg, ":"); if (!names) return log_oom(); @@ -446,22 +432,19 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_INETD: + OPTION_LONG("inetd", NULL, + "Enable inetd file descriptor passing protocol"): arg_inetd = true; break; - case ARG_NOW: + OPTION_LONG("now", NULL, + "Start instantly instead of waiting for connection"): arg_now = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind == argc) + *remaining_args = option_parser_get_args(&state); + if (strv_isempty(*remaining_args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s: command to execute is missing.", program_invocation_short_name); @@ -488,11 +471,12 @@ static int run(int argc, char **argv) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - exec_argv = strv_copy(argv + optind); + exec_argv = strv_copy(args); if (!exec_argv) return log_oom(); From bd2b0e651453bc8c47bf2bce5b9f927fd6a572b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 23 Apr 2026 22:47:05 +0200 Subject: [PATCH 1235/1296] ptyfwd: convert to the new option parser --help is the same except for common option strings and indentation. Co-developed-by: Claude Opus 4.7 --- src/ptyfwd/ptyfwd-tool.c | 90 +++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 53 deletions(-) diff --git a/src/ptyfwd/ptyfwd-tool.c b/src/ptyfwd/ptyfwd-tool.c index 519bd3641d514..089c84a40c33c 100644 --- a/src/ptyfwd/ptyfwd-tool.c +++ b/src/ptyfwd/ptyfwd-tool.c @@ -1,14 +1,15 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" #include "build.h" #include "event-util.h" #include "fd-util.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "pidref.h" #include "pretty-print.h" @@ -28,95 +29,77 @@ STATIC_DESTRUCTOR_REGISTER(arg_title, freep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-pty-forward", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n%5$sRun command with a custom terminal background color or title.%6$s\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version\n" - " -q --quiet Suppress information messages during runtime\n" - " --read-only Do not accept any user input on stdin\n" - " --background=COLOR Set ANSI color for background\n" - " --title=TITLE Set terminal title\n" - "\nSee the %2$s for details.\n", + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] COMMAND ...\n" + "\n%sRun command with a custom terminal background color or title.%s\n" + "\n%sOptions:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), + ansi_normal(), + ansi_underline(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_READ_ONLY, - ARG_BACKGROUND, - ARG_TITLE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "quiet", no_argument, NULL, 'q' }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - { "background", required_argument, NULL, ARG_BACKGROUND }, - { "title", required_argument, NULL, ARG_TITLE }, - {} - }; - - int c, r; - +static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argc >= 0); assert(argv); + assert(remaining_args); - optind = 0; - while ((c = getopt_long(argc, argv, "+hq", options, NULL)) >= 0) + OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; + const char *arg; + int r; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'q': + OPTION('q', "quiet", NULL, "Suppress information messages during runtime"): arg_quiet = true; break; - case ARG_READ_ONLY: + OPTION_LONG("read-only", NULL, "Do not accept any user input on stdin"): arg_read_only = true; break; - case ARG_BACKGROUND: - r = parse_background_argument(optarg, &arg_background); + OPTION_LONG("background", "COLOR", "Set ANSI color for background"): + r = parse_background_argument(arg, &arg_background); if (r < 0) return r; break; - case ARG_TITLE: - r = free_and_strdup_warn(&arg_title, optarg); + OPTION_LONG("title", "TITLE", "Set terminal title"): + r = free_and_strdup_warn(&arg_title, arg); if (r < 0) return r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind >= argc) + if (option_parser_get_n_args(&state) == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected command line, refusing."); + *remaining_args = option_parser_get_args(&state); return 1; } @@ -155,11 +138,12 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - _cleanup_strv_free_ char **l = strv_copy(argv + optind); + _cleanup_strv_free_ char **l = strv_copy(args); if (!l) return log_oom(); From 349bb00979ca192352eb137fd91fd038428528d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 23 Apr 2026 22:56:20 +0200 Subject: [PATCH 1236/1296] sysupdate: reorder verb functions and parse_argv cases to match --help --transfer-source= is moved up to a better place. Co-developed-by: Claude Opus 4.7 --- src/sysupdate/sysupdate.c | 142 +++++++++++++++++++------------------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 76b6507f9a438..254b6bc121e33 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -1518,29 +1518,6 @@ static int verb_check_new(int argc, char *argv[], uintptr_t _data, void *userdat return EXIT_SUCCESS; } -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; - int r; - - assert(argc <= 1); - - 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); - if (r < 0) - return r; - - r = context_make_offline(&context, loop_device ? loop_device->node : NULL, /* requires_enabled_transfers= */ false); - if (r < 0) - return r; - - return context_vacuum(context, 0, NULL); -} - typedef enum { UPDATE_ACTION_ACQUIRE = 1 << 0, UPDATE_ACTION_INSTALL = 1 << 1, @@ -1626,6 +1603,29 @@ static int verb_acquire(int argc, char *argv[], uintptr_t _data, void *userdata) return verb_update_impl(argc, argv, UPDATE_ACTION_ACQUIRE); } +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; + int r; + + assert(argc <= 1); + + 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); + if (r < 0) + return r; + + r = context_make_offline(&context, loop_device ? loop_device->node : NULL, /* requires_enabled_transfers= */ false); + if (r < 0) + return r; + + return context_vacuum(context, 0, NULL); +} + static int verb_pending_or_reboot(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(context_freep) Context* context = NULL; _cleanup_free_ char *booted_version = NULL; @@ -1820,6 +1820,8 @@ static int help(void) { " --image=PATH Operate on disk image as filesystem root\n" " --image-policy=POLICY\n" " Specify disk image dissection policy\n" + " --transfer-source=PATH\n" + " Specify the directory to transfer sources from\n" " -m --instances-max=INT How many instances to maintain\n" " --sync=BOOL Controls whether to sync data to disk\n" " --verify=BOOL Force signature verification on or off\n" @@ -1829,8 +1831,6 @@ static int help(void) { " --no-legend Do not show the headers and footers\n" " --json=pretty|short|off\n" " Generate JSON output\n" - " --transfer-source=PATH\n" - " Specify the directory to transfer sources from\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -1866,20 +1866,20 @@ static int parse_argv(int argc, char *argv[]) { static const struct option options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, + { "component", required_argument, NULL, 'C' }, { "definitions", required_argument, NULL, ARG_DEFINITIONS }, - { "instances-max", required_argument, NULL, 'm' }, - { "sync", required_argument, NULL, ARG_SYNC }, - { "json", required_argument, NULL, ARG_JSON }, { "root", required_argument, NULL, ARG_ROOT }, { "image", required_argument, NULL, ARG_IMAGE }, { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "reboot", no_argument, NULL, ARG_REBOOT }, - { "component", required_argument, NULL, 'C' }, + { "transfer-source", required_argument, NULL, ARG_TRANSFER_SOURCE }, + { "instances-max", required_argument, NULL, 'm' }, + { "sync", required_argument, NULL, ARG_SYNC }, { "verify", required_argument, NULL, ARG_VERIFY }, + { "reboot", no_argument, NULL, ARG_REBOOT }, { "offline", no_argument, NULL, ARG_OFFLINE }, - { "transfer-source", required_argument, NULL, ARG_TRANSFER_SOURCE }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, + { "json", required_argument, NULL, ARG_JSON }, {} }; @@ -1898,25 +1898,22 @@ static int parse_argv(int argc, char *argv[]) { case ARG_VERSION: return version(); - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; - break; - - case ARG_NO_LEGEND: - arg_legend = false; - break; + case 'C': + if (isempty(optarg)) { + arg_component = mfree(arg_component); + break; + } - case 'm': - r = safe_atou64(optarg, &arg_instances_max); + r = component_name_valid(optarg); if (r < 0) - return log_error_errno(r, "Failed to parse --instances-max= parameter: %s", optarg); - - break; + return log_error_errno(r, "Failed to determine if component name is valid: %m"); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", optarg); - case ARG_SYNC: - r = parse_boolean_argument("--sync=", optarg, &arg_sync); + r = free_and_strdup_warn(&arg_component, optarg); if (r < 0) return r; + break; case ARG_DEFINITIONS: @@ -1925,13 +1922,6 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); - if (r <= 0) - return r; - - break; - case ARG_ROOT: r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root); if (r < 0) @@ -1950,26 +1940,24 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - case ARG_REBOOT: - arg_reboot = true; - break; + case ARG_TRANSFER_SOURCE: + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_transfer_source); + if (r < 0) + return r; - case 'C': - if (isempty(optarg)) { - arg_component = mfree(arg_component); - break; - } + break; - r = component_name_valid(optarg); + case 'm': + r = safe_atou64(optarg, &arg_instances_max); if (r < 0) - return log_error_errno(r, "Failed to determine if component name is valid: %m"); - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", optarg); + return log_error_errno(r, "Failed to parse --instances-max= parameter: %s", optarg); - r = free_and_strdup_warn(&arg_component, optarg); + break; + + case ARG_SYNC: + r = parse_boolean_argument("--sync=", optarg, &arg_sync); if (r < 0) return r; - break; case ARG_VERIFY: { @@ -1983,13 +1971,25 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_REBOOT: + arg_reboot = true; + break; + case ARG_OFFLINE: arg_offline = true; break; - case ARG_TRANSFER_SOURCE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_transfer_source); - if (r < 0) + case ARG_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; + + case ARG_NO_LEGEND: + arg_legend = false; + break; + + case ARG_JSON: + r = parse_json_argument(optarg, &arg_json_format_flags); + if (r <= 0) return r; break; From d0cda17c6f893409dbd1b0ba9a07fb03a3c684e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 23 Apr 2026 22:41:04 +0200 Subject: [PATCH 1237/1296] sysupdate: convert to the new option and verb parsers Co-developed-by: Claude Opus 4.7 --- src/sysupdate/sysupdate.c | 249 +++++++++++++++++--------------------- 1 file changed, 108 insertions(+), 141 deletions(-) diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 254b6bc121e33..a4bf835108ab6 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-daemon.h" @@ -16,6 +15,7 @@ #include "loop-util.h" #include "main-func.h" #include "mount-util.h" +#include "options.h" #include "os-util.h" #include "pager.h" #include "parse-argument.h" @@ -1275,6 +1275,8 @@ static int process_image( return 0; } +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; @@ -1343,6 +1345,8 @@ 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; @@ -1477,6 +1481,8 @@ static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata return 0; } +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; @@ -1590,6 +1596,8 @@ static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flag return 0; } +VERB(verb_update, "update", "[VERSION]", VERB_ANY, 2, 0, + "Install new version now"); static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata) { UpdateActionFlags flags = UPDATE_ACTION_INSTALL; @@ -1599,10 +1607,14 @@ static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata) return verb_update_impl(argc, argv, flags); } +VERB(verb_acquire, "acquire", "[VERSION]", VERB_ANY, 2, 0, + "Acquire (download) new version now"); static int verb_acquire(int argc, char *argv[], uintptr_t _data, void *userdata) { return verb_update_impl(argc, argv, UPDATE_ACTION_ACQUIRE); } +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; @@ -1626,6 +1638,10 @@ static int verb_vacuum(int argc, char *argv[], uintptr_t _data, void *userdata) return context_vacuum(context, 0, NULL); } +VERB(verb_pending_or_reboot, "pending", NULL, 1, 1, 0, + "Report whether a newer version is installed than currently booted"); +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_free_ char *booted_version = NULL; @@ -1700,6 +1716,8 @@ static int component_name_valid(const char *c) { return filename_is_valid(j); } +VERB_NOARG(verb_components, "components", + "Show list of components"); static int verb_components(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; @@ -1792,178 +1810,149 @@ static int verb_components(int argc, char *argv[], uintptr_t _data, void *userda static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *common_options = NULL, *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-sysupdate", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] [VERSION]\n" - "\n%5$sUpdate OS images.%6$s\n" - "\n%3$sCommands:%4$s\n" - " list [VERSION] Show installed and available versions\n" - " features [FEATURE] Show optional features\n" - " check-new Check if there's a new version available\n" - " update [VERSION] Install new version now\n" - " acquire [VERSION] Acquire (download) new version now\n" - " vacuum Make room, by deleting old versions\n" - " pending Report whether a newer version is installed than\n" - " currently booted\n" - " reboot Reboot if a newer version is installed than booted\n" - " components Show list of components\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\n%3$sOptions:%4$s\n" - " -C --component=NAME Select component to update\n" - " --definitions=DIR Find transfer definitions in specified directory\n" - " --root=PATH Operate on an alternate filesystem root\n" - " --image=PATH Operate on disk image as filesystem root\n" - " --image-policy=POLICY\n" - " Specify disk image dissection policy\n" - " --transfer-source=PATH\n" - " Specify the directory to transfer sources from\n" - " -m --instances-max=INT How many instances to maintain\n" - " --sync=BOOL Controls whether to sync data to disk\n" - " --verify=BOOL Force signature verification on or off\n" - " --reboot Reboot after updating to newer version\n" - " --offline Do not fetch metadata from the network\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " --json=pretty|short|off\n" - " Generate JSON output\n" - "\nSee the %2$s for details.\n", + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + r = option_parser_get_help_table(&common_options); + if (r < 0) + return r; + + r = option_parser_get_help_table_group("Options", &options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, common_options, options); + + printf("%s [OPTIONS...] [VERSION]\n" + "\n%sUpdate OS images.%s\n" + "\n%sCommands:%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + ansi_highlight(), ansi_normal(), + ansi_underline(), ansi_normal()); - return 0; -} + r = table_print_or_warn(verbs); + if (r < 0) + return r; -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); + r = table_print_or_warn(common_options); + if (r < 0) + return r; + + printf("\n%sOptions:%s\n", ansi_underline(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); + return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_SYNC, - ARG_DEFINITIONS, - ARG_JSON, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_REBOOT, - ARG_VERIFY, - ARG_OFFLINE, - ARG_TRANSFER_SOURCE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "component", required_argument, NULL, 'C' }, - { "definitions", required_argument, NULL, ARG_DEFINITIONS }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "transfer-source", required_argument, NULL, ARG_TRANSFER_SOURCE }, - { "instances-max", required_argument, NULL, 'm' }, - { "sync", required_argument, NULL, ARG_SYNC }, - { "verify", required_argument, NULL, ARG_VERIFY }, - { "reboot", no_argument, NULL, ARG_REBOOT }, - { "offline", no_argument, NULL, ARG_OFFLINE }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "json", required_argument, NULL, ARG_JSON }, - {} - }; - - int c, r; +VERB_COMMON_HELP_HIDDEN(help); +static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argc >= 0); assert(argv); + assert(remaining_args); - while ((c = getopt_long(argc, argv, "hm:C:", options, NULL)) >= 0) { + OptionParser state = { argc, argv }; + const char *arg; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'C': - if (isempty(optarg)) { + OPTION_GROUP("Options"): + break; + + OPTION('C', "component", "NAME", + "Select component to update"): + if (isempty(arg)) { arg_component = mfree(arg_component); break; } - r = component_name_valid(optarg); + r = component_name_valid(arg); if (r < 0) return log_error_errno(r, "Failed to determine if component name is valid: %m"); if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", arg); - r = free_and_strdup_warn(&arg_component, optarg); + r = free_and_strdup_warn(&arg_component, arg); if (r < 0) return r; break; - case ARG_DEFINITIONS: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_definitions); + OPTION_LONG("definitions", "DIR", + "Find transfer definitions in specified directory"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_definitions); if (r < 0) return r; break; - case ARG_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root); + OPTION_LONG("root", "PATH", + "Operate on an alternate filesystem root"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_root); if (r < 0) return r; break; - case ARG_IMAGE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image); + OPTION_LONG("image", "PATH", + "Operate on disk image as filesystem root"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image); if (r < 0) return r; break; - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); + OPTION_LONG("image-policy", "POLICY", + "Specify disk image dissection policy"): + r = parse_image_policy_argument(arg, &arg_image_policy); if (r < 0) return r; break; - case ARG_TRANSFER_SOURCE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_transfer_source); + OPTION_LONG("transfer-source", "PATH", + "Specify the directory to transfer sources from"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_transfer_source); if (r < 0) return r; break; - case 'm': - r = safe_atou64(optarg, &arg_instances_max); + OPTION('m', "instances-max", "INT", + "How many instances to maintain"): + r = safe_atou64(arg, &arg_instances_max); if (r < 0) - return log_error_errno(r, "Failed to parse --instances-max= parameter: %s", optarg); + return log_error_errno(r, "Failed to parse --instances-max= parameter: %s", arg); break; - case ARG_SYNC: - r = parse_boolean_argument("--sync=", optarg, &arg_sync); + OPTION_LONG("sync", "BOOL", + "Controls whether to sync data to disk"): + r = parse_boolean_argument("--sync=", arg, &arg_sync); if (r < 0) return r; break; - case ARG_VERIFY: { + OPTION_LONG("verify", "BOOL", + "Force signature verification on or off"): { bool b; - r = parse_boolean_argument("--verify=", optarg, &b); + r = parse_boolean_argument("--verify=", arg, &b); if (r < 0) return r; @@ -1971,36 +1960,31 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_REBOOT: + OPTION_LONG("reboot", NULL, + "Reboot after updating to newer version"): arg_reboot = true; break; - case ARG_OFFLINE: + OPTION_LONG("offline", NULL, + "Do not fetch metadata from the network"): arg_offline = true; break; - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_NO_LEGEND: + OPTION_COMMON_NO_LEGEND: arg_legend = false; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); if (r <= 0) return r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } if (arg_image && arg_root) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported."); @@ -2011,38 +1995,21 @@ static int parse_argv(int argc, char *argv[]) { if (arg_definitions && arg_component) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --definitions= and --component= switches may not be combined."); + *remaining_args = option_parser_get_args(&state); return 1; } -static int sysupdate_main(int argc, char *argv[]) { - - static const Verb verbs[] = { - { "list", VERB_ANY, 2, VERB_DEFAULT, verb_list }, - { "components", VERB_ANY, 1, 0, verb_components }, - { "features", VERB_ANY, 2, 0, verb_features }, - { "check-new", VERB_ANY, 1, 0, verb_check_new }, - { "update", VERB_ANY, 2, 0, verb_update }, - { "acquire", VERB_ANY, 2, 0, verb_acquire }, - { "vacuum", VERB_ANY, 1, 0, verb_vacuum }, - { "reboot", 1, 1, 0, verb_pending_or_reboot }, - { "pending", 1, 1, 0, verb_pending_or_reboot }, - { "help", VERB_ANY, 1, 0, verb_help }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { int r; log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return sysupdate_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); From a95b55057156b3150a3ef2a3e3f170e2ad59deaa Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 24 Apr 2026 00:08:30 +0200 Subject: [PATCH 1238/1296] ansi-color: add support for italics markup --- src/basic/ansi-color.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/basic/ansi-color.h b/src/basic/ansi-color.h index 1ddb9c6681c87..20f6b3bf62dcf 100644 --- a/src/basic/ansi-color.h +++ b/src/basic/ansi-color.h @@ -91,6 +91,8 @@ bool looks_like_ansi_color_code(const char *str); #define ANSI_UNDERLINE "\x1B[0;4m" #define ANSI_ADD_UNDERLINE "\x1B[4m" #define ANSI_ADD_UNDERLINE_GREY ANSI_ADD_UNDERLINE "\x1B[58:5:245m" +#define ANSI_ITALICS "\x1B[0;3m" +#define ANSI_ADD_ITALICS "\x1B[3m" #define ANSI_HIGHLIGHT "\x1B[0;1;39m" #define ANSI_HIGHLIGHT_UNDERLINE "\x1B[0;1;4m" @@ -180,6 +182,15 @@ static inline const char* ansi_add_underline_grey(void) { (colors_enabled() ? ANSI_ADD_UNDERLINE_GREY : ANSI_ADD_UNDERLINE) : ""; } +static inline const char* ansi_italics(void) { + /* We hook italics also into the underline checks, close enough */ + return underline_enabled() ? ANSI_ITALICS : ""; +} + +static inline const char* ansi_add_italics(void) { + return underline_enabled() ? ANSI_ADD_ITALICS : ""; +} + #define DEFINE_ANSI_FUNC_UNDERLINE(name, NAME) \ static inline const char* ansi_##name##_underline(void) { \ return underline_enabled() ? ANSI_##NAME##_UNDERLINE : \ From 89a4dba4a7fc0579e6a5552f257d33cbce3aba28 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 24 Apr 2026 06:17:14 +0000 Subject: [PATCH 1239/1296] test-qmp-client: drive the mock QMP servers through JsonStream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mock servers previously framed messages by hand: loop_write()+"\r\n" on the way out, a single read(fd, buf, 4095)+sd_json_parse() on the way in, and a custom recvmsg()+CMSG_FOREACH() for the fd-passing test. That only works when each write-on-the-wire happens to be delivered in its own recv(); the moment a test wants to issue back-to-back commands without waiting for replies, the kernel coalesces them and sd_json_parse() chokes on two concatenated objects. Route the mock servers through the same JsonStream transport the client uses: a tiny mock_qmp_init/recv/send/send_literal layer over json_stream takes care of the CRLF delimiter, the output queue, and SCM_RIGHTS. The recv helper loops parse→read→wait so coalesced inbound bytes get fed out one complete message at a time. Drop the bespoke mock_qmp_recv_command() and replace it with json_stream_set_allow_fd_passing_input() + json_stream_{get_n,take,close}_input_fds() in the fd-first test. EOF signalling moves from an explicit safe_close() to the _cleanup_(json_stream_done) on the JsonStream. --- src/test/test-qmp-client.c | 279 ++++++++++++++++++------------------- 1 file changed, 135 insertions(+), 144 deletions(-) diff --git a/src/test/test-qmp-client.c b/src/test/test-qmp-client.c index e8683ee76a177..c70be2c0f4ea2 100644 --- a/src/test/test-qmp-client.c +++ b/src/test/test-qmp-client.c @@ -9,42 +9,79 @@ #include "errno-util.h" #include "fd-util.h" -#include "io-util.h" +#include "json-stream.h" #include "pidref.h" #include "process-util.h" #include "qmp-client.h" -#include "socket-util.h" #include "string-util.h" #include "tests.h" -/* Mock QMP server: runs in the child process of a fork, communicates via one end of a socketpair. */ +/* Mock QMP server: runs in the child process of a fork, communicates via one end of a socketpair. + * Uses JsonStream as the transport so framing (CRLF delimiter, message queuing, SCM_RIGHTS) is + * handled the same way as on the client side — individual recv() syscalls may coalesce multiple + * messages, and the parser must re-emit each one on its own. */ -static void mock_qmp_write_json(int fd, sd_json_variant *v) { - _cleanup_free_ char *s = NULL; +/* We drive the stream manually via read/parse/wait; always report READING so json_stream_wait() + * asks for POLLIN. */ +static JsonStreamPhase mock_qmp_phase(void *userdata) { + return JSON_STREAM_PHASE_READING; +} - ASSERT_OK(sd_json_variant_format(v, 0, &s)); - ASSERT_NOT_NULL(strextend(&s, "\r\n")); - ASSERT_OK(loop_write(fd, s, SIZE_MAX)); +/* Never reached — we don't wire the mock stream up to sd-event — but required at init. */ +static int mock_qmp_dispatch(void *userdata) { + return 0; } -static void mock_qmp_write_literal(int fd, const char *msg) { - ASSERT_OK(loop_write(fd, msg, SIZE_MAX)); - ASSERT_OK(loop_write(fd, "\r\n", 2)); +static void mock_qmp_init(JsonStream *s, int fd) { + static const JsonStreamParams params = { + .delimiter = "\r\n", + .phase = mock_qmp_phase, + .dispatch = mock_qmp_dispatch, + }; + + ASSERT_OK(json_stream_init(s, ¶ms)); + ASSERT_OK(json_stream_connect_fd_pair(s, fd, fd)); } -/* Read a command from the QMP client, verify it contains the expected command name, extract the id, - * and send a reply with that id. If reply_data is NULL, an empty return object is sent. */ -static void mock_qmp_expect_and_reply(int fd, const char *expected_command, sd_json_variant *reply_data) { - _cleanup_free_ char *buf = NULL; - _cleanup_(sd_json_variant_unrefp) sd_json_variant *cmd = NULL, *reply_obj = NULL, *response = NULL; +/* Read one complete JSON message, blocking until available. Handles the case where multiple + * client messages arrived coalesced into a single recv(): the parser walks the input buffer + * one CRLF-delimited message at a time. */ +static void mock_qmp_recv(JsonStream *s, sd_json_variant **ret) { + int r; - buf = ASSERT_NOT_NULL(new(char, 4096)); + for (;;) { + r = ASSERT_OK(json_stream_parse(s, ret)); + if (r > 0) + return; - ssize_t n = read(fd, buf, 4095); - assert_se(n > 0); - buf[n] = '\0'; + r = ASSERT_OK(json_stream_read(s)); + if (r > 0) + continue; - ASSERT_OK(sd_json_parse(buf, 0, &cmd, NULL, NULL)); + ASSERT_OK(json_stream_wait(s, USEC_INFINITY)); + } +} + +/* Enqueue one JSON variant and block until it has been fully written. */ +static void mock_qmp_send(JsonStream *s, sd_json_variant *v) { + ASSERT_OK(json_stream_enqueue(s, v)); + ASSERT_OK(json_stream_flush(s)); +} + +/* Parse a literal JSON string and send it. Used for fixed greetings and unsolicited events. */ +static void mock_qmp_send_literal(JsonStream *s, const char *msg) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + ASSERT_OK(sd_json_parse(msg, 0, &v, NULL, NULL)); + mock_qmp_send(s, v); +} + +/* Read a command from the client, verify it contains the expected command name, and send a + * reply carrying the same id. If reply_data is NULL, an empty return object is sent. */ +static void mock_qmp_expect_and_reply(JsonStream *s, const char *expected_command, sd_json_variant *reply_data) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *cmd = NULL, *reply_obj = NULL, *response = NULL; + + mock_qmp_recv(s, &cmd); sd_json_variant *execute = ASSERT_NOT_NULL(sd_json_variant_by_key(cmd, "execute")); ASSERT_STREQ(sd_json_variant_string(execute), expected_command); @@ -59,38 +96,64 @@ static void mock_qmp_expect_and_reply(int fd, const char *expected_command, sd_j SD_JSON_BUILD_PAIR("return", SD_JSON_BUILD_VARIANT(reply_data ?: reply_obj)), SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_VARIANT(id)))); - mock_qmp_write_json(fd, response); + mock_qmp_send(s, response); +} + +/* Same shape as mock_qmp_expect_and_reply() but replies with a QMP error object. */ +static void mock_qmp_expect_and_reply_error(JsonStream *s, const char *expected_command, const char *error_desc) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *cmd = NULL, *error_obj = NULL, *response = NULL; + + mock_qmp_recv(s, &cmd); + + sd_json_variant *execute = ASSERT_NOT_NULL(sd_json_variant_by_key(cmd, "execute")); + ASSERT_STREQ(sd_json_variant_string(execute), expected_command); + + sd_json_variant *id = ASSERT_NOT_NULL(sd_json_variant_by_key(cmd, "id")); + + ASSERT_OK(sd_json_buildo( + &error_obj, + SD_JSON_BUILD_PAIR_STRING("class", "GenericError"), + SD_JSON_BUILD_PAIR_STRING("desc", error_desc))); + + ASSERT_OK(sd_json_buildo( + &response, + SD_JSON_BUILD_PAIR("error", SD_JSON_BUILD_VARIANT(error_obj)), + SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_VARIANT(id)))); + + mock_qmp_send(s, response); } static _noreturn_ void mock_qmp_server(int fd) { + _cleanup_(json_stream_done) JsonStream s = {}; _cleanup_(sd_json_variant_unrefp) sd_json_variant *status_return = NULL; + mock_qmp_init(&s, fd); + /* Send QMP greeting */ - mock_qmp_write_literal(fd, + mock_qmp_send_literal(&s, "{\"QMP\": {\"version\": {\"qemu\": {\"micro\": 0, \"minor\": 2, \"major\": 9}}, \"capabilities\": [\"oob\"]}}"); /* Accept qmp_capabilities */ - mock_qmp_expect_and_reply(fd, "qmp_capabilities", NULL); + mock_qmp_expect_and_reply(&s, "qmp_capabilities", NULL); /* Accept query-status, reply with running state */ ASSERT_OK(sd_json_buildo( &status_return, SD_JSON_BUILD_PAIR_BOOLEAN("running", true), SD_JSON_BUILD_PAIR_STRING("status", "running"))); - mock_qmp_expect_and_reply(fd, "query-status", status_return); + mock_qmp_expect_and_reply(&s, "query-status", status_return); /* Accept stop */ - mock_qmp_expect_and_reply(fd, "stop", NULL); + mock_qmp_expect_and_reply(&s, "stop", NULL); /* Send a STOP event */ - mock_qmp_write_literal(fd, + mock_qmp_send_literal(&s, "{\"event\": \"STOP\", \"timestamp\": {\"seconds\": 1234, \"microseconds\": 5678}}"); /* Accept cont */ - mock_qmp_expect_and_reply(fd, "cont", NULL); + mock_qmp_expect_and_reply(&s, "cont", NULL); - /* Close to trigger EOF */ - safe_close(fd); + /* json_stream_done() on cleanup closes our fd and signals EOF. */ _exit(EXIT_SUCCESS); } @@ -166,8 +229,7 @@ TEST(qmp_client_basic) { ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); - r = pidref_safe_fork("(mock-qmp)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid); - ASSERT_OK(r); + r = ASSERT_OK(pidref_safe_fork("(mock-qmp)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid)); if (r == 0) { safe_close(qmp_fds[0]); @@ -232,20 +294,21 @@ TEST(qmp_client_eof) { ASSERT_OK(sd_event_new(&event)); ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); - r = pidref_safe_fork("(mock-qmp-eof)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid); - ASSERT_OK(r); + r = ASSERT_OK(pidref_safe_fork("(mock-qmp-eof)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid)); if (r == 0) { + _cleanup_(json_stream_done) JsonStream s = {}; + safe_close(qmp_fds[0]); + mock_qmp_init(&s, qmp_fds[1]); /* Send greeting and accept capabilities, then die */ - mock_qmp_write_literal(qmp_fds[1], + mock_qmp_send_literal(&s, "{\"QMP\": {\"version\": {\"qemu\": {\"micro\": 0, \"minor\": 0, \"major\": 9}}, \"capabilities\": []}}"); - mock_qmp_expect_and_reply(qmp_fds[1], "qmp_capabilities", NULL); + mock_qmp_expect_and_reply(&s, "qmp_capabilities", NULL); - /* Close immediately to trigger EOF */ - safe_close(qmp_fds[1]); + /* _exit() closes our fd via kernel teardown, signalling EOF to the peer. */ _exit(EXIT_SUCCESS); } @@ -272,71 +335,31 @@ TEST(qmp_client_eof) { ASSERT_EQ(si.si_status, EXIT_SUCCESS); } -/* Read one QMP command from fd (one recvmsg, expecting it fits in the buffer for typical - * test commands). Returns the number of SCM_RIGHTS fds that arrived attached to the read, - * stores the first received fd in *ret_received_fd (or -EBADF if none) and closes any extras, - * and parses the JSON into *ret_cmd. */ -static size_t mock_qmp_recv_command(int fd, sd_json_variant **ret_cmd, int *ret_received_fd) { - char buf[4096]; - char ctrl[CMSG_SPACE(sizeof(int) * 4)]; - struct iovec iov = { .iov_base = buf, .iov_len = sizeof(buf) - 1 }; - struct msghdr mh = { - .msg_iov = &iov, .msg_iovlen = 1, - .msg_control = ctrl, .msg_controllen = sizeof(ctrl), - }; - size_t n_fds = 0; - int received_fd = -EBADF; - - ssize_t n = recvmsg(fd, &mh, MSG_CMSG_CLOEXEC); - assert_se(n > 0); - buf[n] = '\0'; - - struct cmsghdr *cmsg; - CMSG_FOREACH(cmsg, &mh) { - if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) - continue; - size_t k = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); - int *fds = (int*) CMSG_DATA(cmsg); - for (size_t i = 0; i < k; i++) { - if (received_fd < 0) - received_fd = fds[i]; - else - safe_close(fds[i]); - } - n_fds += k; - } - - ASSERT_OK(sd_json_parse(buf, 0, ret_cmd, NULL, NULL)); - - if (ret_received_fd) - *ret_received_fd = received_fd; - else if (received_fd >= 0) - safe_close(received_fd); - - return n_fds; -} - /* Mock QMP server for the fd-on-first-invoke regression. Drives the wire dance: * greeting → (recv qmp_capabilities, expect 0 fds) → reply → * (recv add-fd, expect exactly 1 fd) → reply - * Asserts the cmsg fd counts directly so a regression flips the child to + * Asserts the attached fd counts directly so a regression flips the child to * exit_failure and the parent test fails on the wait-for-terminate. */ static _noreturn_ void mock_qmp_server_fd_first(int fd) { + _cleanup_(json_stream_done) JsonStream s = {}; _cleanup_(sd_json_variant_unrefp) sd_json_variant *cap_cmd = NULL, *addfd_cmd = NULL, *cap_reply = NULL, *addfd_return = NULL, *addfd_reply = NULL; - size_t n_fds; - int received_fd = -EBADF; + + mock_qmp_init(&s, fd); + /* Accept SCM_RIGHTS on incoming messages so we can count how many fds the client + * attaches to each sendmsg. */ + ASSERT_OK(json_stream_set_allow_fd_passing_input(&s, true, /* with_sockopt= */ true)); /* Greeting */ - mock_qmp_write_literal(fd, + mock_qmp_send_literal(&s, "{\"QMP\": {\"version\": {\"qemu\": {\"micro\": 0, \"minor\": 0, \"major\": 9}}, \"capabilities\": []}}"); /* Receive qmp_capabilities — must arrive with NO fds attached. */ - n_fds = mock_qmp_recv_command(fd, &cap_cmd, /* ret_received_fd= */ NULL); - ASSERT_EQ(n_fds, (size_t) 0); + mock_qmp_recv(&s, &cap_cmd); + ASSERT_EQ(json_stream_get_n_input_fds(&s), (size_t) 0); ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(cap_cmd, "execute")), "qmp_capabilities"); sd_json_variant *cap_id = ASSERT_NOT_NULL(sd_json_variant_by_key(cap_cmd, "id")); @@ -344,14 +367,13 @@ static _noreturn_ void mock_qmp_server_fd_first(int fd) { &cap_reply, SD_JSON_BUILD_PAIR("return", SD_JSON_BUILD_EMPTY_OBJECT), SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_VARIANT(cap_id)))); - mock_qmp_write_json(fd, cap_reply); + mock_qmp_send(&s, cap_reply); /* Receive add-fd — must arrive with EXACTLY ONE fd attached. */ - n_fds = mock_qmp_recv_command(fd, &addfd_cmd, &received_fd); - ASSERT_EQ(n_fds, (size_t) 1); - ASSERT_TRUE(received_fd >= 0); + mock_qmp_recv(&s, &addfd_cmd); + ASSERT_EQ(json_stream_get_n_input_fds(&s), (size_t) 1); ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(addfd_cmd, "execute")), "add-fd"); - safe_close(received_fd); + json_stream_close_input_fds(&s); sd_json_variant *addfd_id = ASSERT_NOT_NULL(sd_json_variant_by_key(addfd_cmd, "id")); ASSERT_OK(sd_json_buildo( @@ -362,9 +384,8 @@ static _noreturn_ void mock_qmp_server_fd_first(int fd) { &addfd_reply, SD_JSON_BUILD_PAIR("return", SD_JSON_BUILD_VARIANT(addfd_return)), SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_VARIANT(addfd_id)))); - mock_qmp_write_json(fd, addfd_reply); + mock_qmp_send(&s, addfd_reply); - safe_close(fd); _exit(EXIT_SUCCESS); } @@ -387,8 +408,7 @@ TEST(qmp_client_first_invoke_with_fd) { ASSERT_OK(sd_event_new(&event)); ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); - r = pidref_safe_fork("(mock-qmp-fd-first)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid); - ASSERT_OK(r); + r = ASSERT_OK(pidref_safe_fork("(mock-qmp-fd-first)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid)); if (r == 0) { safe_close(qmp_fds[0]); @@ -473,59 +493,29 @@ TEST(qmp_client_invoke_failure_closes_fds) { ASSERT_EQ(errno, EBADF); } -/* Reads one command, asserts its execute name, and replies with a QMP error object carrying - * the given description. Mirrors mock_qmp_expect_and_reply() but on the error branch. */ -static void mock_qmp_expect_and_reply_error(int fd, const char *expected_command, const char *error_desc) { - _cleanup_free_ char *buf = NULL; - _cleanup_(sd_json_variant_unrefp) sd_json_variant *cmd = NULL, *error_obj = NULL, *response = NULL; - - buf = ASSERT_NOT_NULL(new(char, 4096)); - - ssize_t n = read(fd, buf, 4095); - assert_se(n > 0); - buf[n] = '\0'; - - ASSERT_OK(sd_json_parse(buf, 0, &cmd, NULL, NULL)); - - sd_json_variant *execute = ASSERT_NOT_NULL(sd_json_variant_by_key(cmd, "execute")); - ASSERT_STREQ(sd_json_variant_string(execute), expected_command); - - sd_json_variant *id = ASSERT_NOT_NULL(sd_json_variant_by_key(cmd, "id")); - - ASSERT_OK(sd_json_buildo( - &error_obj, - SD_JSON_BUILD_PAIR_STRING("class", "GenericError"), - SD_JSON_BUILD_PAIR_STRING("desc", error_desc))); - - ASSERT_OK(sd_json_buildo( - &response, - SD_JSON_BUILD_PAIR("error", SD_JSON_BUILD_VARIANT(error_obj)), - SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_VARIANT(id)))); - - mock_qmp_write_json(fd, response); -} - /* Drives a small wire dance for the sync call test: greeting, capabilities, one successful * command reply, and two error replies (one for the ret_error_desc path, one for the -EIO * path). */ static _noreturn_ void mock_qmp_server_call(int fd) { + _cleanup_(json_stream_done) JsonStream s = {}; _cleanup_(sd_json_variant_unrefp) sd_json_variant *status_return = NULL; - mock_qmp_write_literal(fd, + mock_qmp_init(&s, fd); + + mock_qmp_send_literal(&s, "{\"QMP\": {\"version\": {\"qemu\": {\"micro\": 0, \"minor\": 0, \"major\": 9}}, \"capabilities\": []}}"); - mock_qmp_expect_and_reply(fd, "qmp_capabilities", NULL); + mock_qmp_expect_and_reply(&s, "qmp_capabilities", NULL); ASSERT_OK(sd_json_buildo( &status_return, SD_JSON_BUILD_PAIR_BOOLEAN("running", true), SD_JSON_BUILD_PAIR_STRING("status", "running"))); - mock_qmp_expect_and_reply(fd, "query-status", status_return); + mock_qmp_expect_and_reply(&s, "query-status", status_return); - mock_qmp_expect_and_reply_error(fd, "stop", "not running"); - mock_qmp_expect_and_reply_error(fd, "stop", "still not running"); + mock_qmp_expect_and_reply_error(&s, "stop", "not running"); + mock_qmp_expect_and_reply_error(&s, "stop", "still not running"); - safe_close(fd); _exit(EXIT_SUCCESS); } @@ -573,20 +563,21 @@ TEST(qmp_client_call) { /* Server variant for the sync-call disconnect test: greets, accepts capabilities, reads one * command without replying, then closes the socket so the client sees EOF mid-wait. */ static _noreturn_ void mock_qmp_server_call_disconnect(int fd) { - _cleanup_free_ char *buf = NULL; + _cleanup_(json_stream_done) JsonStream s = {}; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *stop_cmd = NULL; + + mock_qmp_init(&s, fd); - mock_qmp_write_literal(fd, + mock_qmp_send_literal(&s, "{\"QMP\": {\"version\": {\"qemu\": {\"micro\": 0, \"minor\": 0, \"major\": 9}}, \"capabilities\": []}}"); - mock_qmp_expect_and_reply(fd, "qmp_capabilities", NULL); + mock_qmp_expect_and_reply(&s, "qmp_capabilities", NULL); - /* Consume the stop command but don't reply — just close to trigger EOF while the - * client is blocked in qmp_client_call()'s process+wait pump. */ - buf = ASSERT_NOT_NULL(new(char, 4096)); - ssize_t n = read(fd, buf, 4095); - assert_se(n > 0); + /* Consume the stop command but don't reply — json_stream_done() on cleanup closes + * our fd, triggering EOF while the client is blocked in qmp_client_call()'s + * process+wait pump. */ + mock_qmp_recv(&s, &stop_cmd); - safe_close(fd); _exit(EXIT_SUCCESS); } From 54c124eb6cf8a5d8430b491a0171a1dd62506182 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 24 Apr 2026 06:07:31 +0000 Subject: [PATCH 1240/1296] qmp-client: make QmpSlot a public, refcounted, cancellable handle QmpSlot now stores a back-reference to its QmpClient (mirroring sd_bus_slot) and is exposed as a public refcounted type via qmp_slot_ref/qmp_slot_unref. qmp_client_invoke() gains an optional QmpSlot **ret_slot out-parameter matching sd_bus_call_async(): passing non-NULL hands back a reference whose unref cancels the pending call (the callback is deregistered; a late reply is logged and discarded as unknown-id). Internally slots come in two flavours, following sd_bus's model: floating (owned by the client's pending set, used when ret_slot is NULL) and non-floating (ref held by the caller, slot holds a ref on the client). qmp_slot_disconnect() centralizes the teardown so the reply-dispatched, explicit-cancel, and client-teardown paths all converge on the same idempotent cleanup. qmp_client_call()'s sync slot is now non-floating and observes completion by watching slot->client go NULL instead of set_contains() on an id. --- src/shared/qmp-client.c | 183 ++++++++++++++++++++++++-------- src/shared/qmp-client.h | 13 ++- src/shared/shared-forward.h | 1 + src/test/test-qmp-client-qemu.c | 14 +-- src/test/test-qmp-client.c | 131 +++++++++++++++++++++-- src/vmspawn/vmspawn-qmp.c | 48 ++++----- src/vmspawn/vmspawn-varlink.c | 4 +- 7 files changed, 308 insertions(+), 86 deletions(-) diff --git a/src/shared/qmp-client.c b/src/shared/qmp-client.c index 53a30abe5a9b5..45bc0d8dbd22e 100644 --- a/src/shared/qmp-client.c +++ b/src/shared/qmp-client.c @@ -30,11 +30,14 @@ typedef enum QmpClientState { QMP_CLIENT_HANDSHAKE_GREETING_RECEIVED, \ QMP_CLIENT_HANDSHAKE_CAPABILITIES_SENT) -typedef struct QmpSlot { +struct QmpSlot { + unsigned n_ref; + QmpClient *client; /* NULL once disconnected (reply dispatched, cancelled, or client died) */ uint64_t id; + bool floating; qmp_command_callback_t callback; void *userdata; -} QmpSlot; +}; struct QmpClient { unsigned n_ref; @@ -69,9 +72,92 @@ static int qmp_slot_compare_func(const QmpSlot *a, const QmpSlot *b) { DEFINE_PRIVATE_HASH_OPS(qmp_slot_hash_ops, QmpSlot, qmp_slot_hash_func, qmp_slot_compare_func); +/* Break the slot's connection to the client: remove from the lookup set, drop whichever reference + * is implied by the slot's floating-ness. For floating slots, the set is the sole owner, so with + * unref=true we also drop the slot's n_ref (usually dropping it to zero and freeing). For + * non-floating slots, we release the back-reference the slot holds on the client. + * + * Safe to call multiple times: once slot->client is NULL, subsequent calls are no-ops. */ +static void qmp_slot_disconnect(QmpSlot *slot, bool unref) { + assert(slot); + + if (!slot->client) + return; + + QmpClient *client = slot->client; + + set_remove(client->slots, slot); + slot->client = NULL; + + if (!slot->floating) + qmp_client_unref(client); + else if (unref) + /* May re-enter via qmp_slot_free→qmp_slot_disconnect(,false) if this drops the + * last ref, but the early return above makes that recursion a no-op. */ + qmp_slot_unref(slot); +} + +static QmpSlot* qmp_slot_free(QmpSlot *slot) { + if (!slot) + return NULL; + + /* Idempotent: if the slot was already disconnected (reply dispatched, explicit cancel, + * or client-side teardown), this is a no-op. Otherwise it removes us from the set and + * drops our client reference (for non-floating slots). */ + qmp_slot_disconnect(slot, /* unref= */ false); + + return mfree(slot); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(QmpSlot, qmp_slot, qmp_slot_free); + +QmpClient* qmp_slot_get_client(QmpSlot *slot) { + assert(slot); + return slot->client; +} + +static int qmp_slot_new( + QmpClient *client, + bool floating, + uint64_t id, + qmp_command_callback_t callback, + void *userdata, + QmpSlot **ret) { + + int r; + + assert(client); + assert(ret); + + _cleanup_(qmp_slot_unrefp) QmpSlot *slot = new(QmpSlot, 1); + if (!slot) + return -ENOMEM; + + *slot = (QmpSlot) { + .n_ref = 1, + .client = NULL, /* wired up below, after set_put succeeds */ + .id = id, + .floating = floating, + .callback = callback, + .userdata = userdata, + }; + + r = set_ensure_put(&client->slots, &qmp_slot_hash_ops, slot); + if (r < 0) + return r; + assert(r > 0); + + slot->client = client; + if (!floating) + qmp_client_ref(client); + + *ret = TAKE_PTR(slot); + return 0; +} + static void qmp_client_clear(QmpClient *c); -static QmpClient* qmp_client_destroy(QmpClient *c) { +static QmpClient* qmp_client_free(QmpClient *c) { if (!c) return NULL; @@ -80,8 +166,7 @@ static QmpClient* qmp_client_destroy(QmpClient *c) { return mfree(c); } -DEFINE_PRIVATE_TRIVIAL_REF_FUNC(QmpClient, qmp_client); -DEFINE_TRIVIAL_UNREF_FUNC(QmpClient, qmp_client, qmp_client_destroy); +DEFINE_TRIVIAL_REF_UNREF_FUNC(QmpClient, qmp_client, qmp_client_free); static void qmp_client_clear_current(QmpClient *c) { assert(c); @@ -225,42 +310,59 @@ static int qmp_client_dispatch_reply(QmpClient *c) { return 1; } - _cleanup_free_ QmpSlot *pending = set_remove(c->slots, &(QmpSlot) { .id = id }); - if (!pending) { + QmpSlot *slot = set_get(c->slots, &(QmpSlot) { .id = id }); + if (!slot) { qmp_client_clear_current(c); json_stream_log(&c->stream, "Discarding QMP response with unknown id %" PRIu64, id); return 1; } /* Synchronous slot (no callback): leave c->current pinned so qmp_client_call() can - * pick up the reply and hand out borrowed pointers into it. */ - if (!pending->callback) + * pick up the reply and hand out borrowed pointers into it. The sync caller owns a + * ref on the slot and detects completion by observing slot->client turning NULL. */ + if (!slot->callback) { + qmp_slot_disconnect(slot, /* unref= */ true); return 1; + } _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = TAKE_PTR(c->current); error = qmp_parse_response(v, &result, &desc); - r = pending->callback(c, result, desc, error, pending->userdata); + /* Pin the slot across the callback regardless of floating-ness. For a floating slot, + * disconnect(unref=true) drops the set's implicit ref which would otherwise free it + * out from under the callback. */ + qmp_slot_ref(slot); + + r = slot->callback(c, result, desc, error, slot->userdata); if (r < 0) json_stream_log_errno(&c->stream, r, "Command callback returned error, ignoring: %m"); + qmp_slot_disconnect(slot, /* unref= */ true); + qmp_slot_unref(slot); + return 1; } -/* Fail all pending async commands with the given error. Called on disconnect. */ +/* Fail all pending commands with the given error. Called on disconnect. */ static void qmp_client_fail_pending(QmpClient *c, int error) { - QmpSlot *p; + QmpSlot *slot; int r; assert(c); - while ((p = set_steal_first(c->slots))) { - if (p->callback) { - r = p->callback(c, /* result= */ NULL, /* error_desc= */ NULL, error, p->userdata); + while ((slot = set_first(c->slots))) { + /* Keep alive across the callback and past disconnect (which may unref it for + * floating slots). */ + qmp_slot_ref(slot); + + if (slot->callback) { + r = slot->callback(c, /* result= */ NULL, /* error_desc= */ NULL, error, slot->userdata); if (r < 0) json_stream_log_errno(&c->stream, r, "Command callback returned error, ignoring: %m"); } - free(p); + + qmp_slot_disconnect(slot, /* unref= */ true); + qmp_slot_unref(slot); } } @@ -701,17 +803,19 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(QmpClientArgs*, qmp_client_args_close_fds); /* Shared send path for qmp_client_invoke() and qmp_client_call(). A NULL callback registers * a "synchronous" slot: dispatch_reply leaves c->current pinned on match instead of invoking - * a callback, so qmp_client_call() can hand out borrowed pointers into the reply. */ + * a callback, so qmp_client_call() can hand out borrowed pointers into the reply. If ret_slot + * is NULL the slot is allocated as floating (owned by c->slots); otherwise a reference is + * handed back to the caller. */ static int qmp_client_send( QmpClient *c, const char *command, QmpClientArgs *args, qmp_command_callback_t callback, void *userdata, - uint64_t *ret_id) { + QmpSlot **ret_slot) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *cmd = NULL; - _cleanup_free_ QmpSlot *pending = NULL; + _cleanup_(qmp_slot_unrefp) QmpSlot *slot = NULL; /* Closes any fds in args on every early-return path; TAKE_PTR()'d on the success path * below once json_stream_enqueue_full() has taken ownership of them. */ _cleanup_(qmp_client_args_close_fdsp) QmpClientArgs *fds_owner = args; @@ -729,50 +833,40 @@ static int qmp_client_send( if (r < 0) return r; - pending = new(QmpSlot, 1); - if (!pending) - return -ENOMEM; - - *pending = (QmpSlot) { - .id = id, - .callback = callback, - .userdata = userdata, - }; - - r = set_ensure_put(&c->slots, &qmp_slot_hash_ops, pending); + r = qmp_slot_new(c, /* floating= */ !ret_slot, id, callback, userdata, &slot); if (r < 0) return r; - assert(r > 0); r = json_stream_enqueue_full(&c->stream, cmd, args ? args->fds_consume : NULL, args ? args->n_fds : 0); - if (r < 0) { - set_remove(c->slots, pending); - return r; - } + if (r < 0) + return r; /* slot cleanup disconnects it */ /* Arm defer so process() drains the output on the next iteration. */ if (c->defer_event_source) (void) sd_event_source_set_enabled(c->defer_event_source, SD_EVENT_ON); - TAKE_PTR(pending); TAKE_PTR(fds_owner); - if (ret_id) - *ret_id = id; + if (ret_slot) + *ret_slot = TAKE_PTR(slot); + else + TAKE_PTR(slot); /* floating: c->slots keeps it alive until dispatch */ + return 0; } int qmp_client_invoke( QmpClient *c, + QmpSlot **ret_slot, const char *command, QmpClientArgs *args, qmp_command_callback_t callback, void *userdata) { assert(callback); - return qmp_client_send(c, command, args, callback, userdata, /* ret_id= */ NULL); + return qmp_client_send(c, command, args, callback, userdata, ret_slot); } int qmp_client_call( @@ -782,7 +876,7 @@ int qmp_client_call( sd_json_variant **ret_result, const char **ret_error_desc) { - uint64_t id; + _cleanup_(qmp_slot_unrefp) QmpSlot *slot = NULL; int r; assert_return(c, -EINVAL); @@ -793,17 +887,18 @@ int qmp_client_call( /* NULL callback marks this as a synchronous slot: dispatch_reply matches on id like * any other slot (so stray unknown-id replies still get logged and dropped), but - * pins c->current for us instead of invoking a callback. */ - r = qmp_client_send(c, command, args, /* callback= */ NULL, /* userdata= */ NULL, &id); + * pins c->current for us instead of invoking a callback. The slot is non-floating so + * we can observe dispatch by watching slot->client go NULL. */ + r = qmp_client_send(c, command, args, /* callback= */ NULL, /* userdata= */ NULL, &slot); if (r < 0) return r; - /* Pump the loop until our sync slot fires (removed from c->slots, c->current pinned). */ + /* Pump the loop until our sync slot fires (disconnected by dispatch, c->current pinned). */ for (;;) { if (c->state == QMP_CLIENT_DISCONNECTED) return -ECONNRESET; - if (!set_contains(c->slots, &(QmpSlot) { .id = id })) { + if (!slot->client) { assert(c->current); break; } diff --git a/src/shared/qmp-client.h b/src/shared/qmp-client.h index b3ee8262e01e9..7a477f7e4fa37 100644 --- a/src/shared/qmp-client.h +++ b/src/shared/qmp-client.h @@ -54,9 +54,12 @@ bool qmp_client_is_idle(QmpClient *c); /* True iff the connection is dead. Stable terminal state — once set, it stays set. */ bool qmp_client_is_disconnected(QmpClient *c); -/* Async send. Returns 0 on send (callback will fire later), negative errno on failure. */ +/* Async send. Returns 0 on send (callback will fire later), negative errno on failure. If + * ret_slot is non-NULL, returns a reference to a QmpSlot which can be used to cancel the call + * (by unreffing it before the reply arrives). */ int qmp_client_invoke( QmpClient *client, + QmpSlot **ret_slot, const char *command, QmpClientArgs *args, qmp_command_callback_t callback, @@ -78,10 +81,14 @@ int qmp_client_set_description(QmpClient *c, const char *description); sd_event* qmp_client_get_event(QmpClient *c); unsigned qmp_client_next_fdset_id(QmpClient *client); -QmpClient* qmp_client_unref(QmpClient *p); - +DECLARE_TRIVIAL_REF_UNREF_FUNC(QmpClient, qmp_client); DEFINE_TRIVIAL_CLEANUP_FUNC(QmpClient *, qmp_client_unref); +DECLARE_TRIVIAL_REF_UNREF_FUNC(QmpSlot, qmp_slot); +DEFINE_TRIVIAL_CLEANUP_FUNC(QmpSlot *, qmp_slot_unref); + +QmpClient* qmp_slot_get_client(QmpSlot *slot); + /* Returns true iff any object entry in schema (result of query-qmp-schema) has a member with this * name. QEMU's introspection replaces type names with opaque numeric ids, so lookup-by-type-name is * impossible — but member names are real. Use only when the member name is unique in the schema. */ diff --git a/src/shared/shared-forward.h b/src/shared/shared-forward.h index 1a19b42499ca5..1207fe8a25826 100644 --- a/src/shared/shared-forward.h +++ b/src/shared/shared-forward.h @@ -80,6 +80,7 @@ typedef struct MStack MStack; typedef struct OpenFile OpenFile; typedef struct Pkcs11EncryptedKey Pkcs11EncryptedKey; typedef struct QmpClient QmpClient; +typedef struct QmpSlot QmpSlot; typedef struct Table Table; typedef struct Tpm2Context Tpm2Context; typedef struct Tpm2Handle Tpm2Handle; diff --git a/src/test/test-qmp-client-qemu.c b/src/test/test-qmp-client-qemu.c index ec520cc270a04..df8d3c6e21599 100644 --- a/src/test/test-qmp-client-qemu.c +++ b/src/test/test-qmp-client-qemu.c @@ -133,7 +133,7 @@ TEST(qmp_client_qemu_handshake_and_schema) { /* query-qmp-schema returns ~200KB -- validates the buffered reader handles large multi-read() * responses correctly. The handshake completes transparently inside invoke(). */ - r = qmp_client_invoke(client, "query-qmp-schema", NULL, on_test_result, &t); + r = qmp_client_invoke(client, /* ret_slot= */ NULL, "query-qmp-schema", NULL, on_test_result, &t); if (r < 0) { log_tests_skipped_errno(r, "QMP invoke failed (handshake or send)"); return; @@ -153,7 +153,7 @@ TEST(qmp_client_qemu_handshake_and_schema) { qmp_test_result_done(&t); /* Clean shutdown */ - ASSERT_OK(qmp_client_invoke(client, "quit", NULL, on_test_result, &t)); + ASSERT_OK(qmp_client_invoke(client, /* ret_slot= */ NULL, "quit", NULL, on_test_result, &t)); qmp_test_wait(event, &t); ASSERT_EQ(t.error, 0); qmp_test_result_done(&t); @@ -197,7 +197,7 @@ TEST(qmp_client_qemu_query_status) { /* query-status validates response parsing against real QEMU output format. * The handshake completes transparently inside invoke(). */ - r = qmp_client_invoke(client, "query-status", NULL, on_test_result, &t); + r = qmp_client_invoke(client, /* ret_slot= */ NULL, "query-status", NULL, on_test_result, &t); if (r < 0) { log_tests_skipped_errno(r, "QMP invoke failed (handshake or send)"); return; @@ -219,13 +219,13 @@ TEST(qmp_client_qemu_query_status) { qmp_test_result_done(&t); /* Test stop + cont to exercise command sequencing and id correlation */ - ASSERT_OK(qmp_client_invoke(client, "stop", NULL, on_test_result, &t)); + ASSERT_OK(qmp_client_invoke(client, /* ret_slot= */ NULL, "stop", NULL, on_test_result, &t)); qmp_test_wait(event, &t); ASSERT_EQ(t.error, 0); qmp_test_result_done(&t); /* Verify status changed */ - ASSERT_OK(qmp_client_invoke(client, "query-status", NULL, on_test_result, &t)); + ASSERT_OK(qmp_client_invoke(client, /* ret_slot= */ NULL, "query-status", NULL, on_test_result, &t)); qmp_test_wait(event, &t); ASSERT_EQ(t.error, 0); ASSERT_NOT_NULL(t.result); @@ -236,13 +236,13 @@ TEST(qmp_client_qemu_query_status) { qmp_test_result_done(&t); - ASSERT_OK(qmp_client_invoke(client, "cont", NULL, on_test_result, &t)); + ASSERT_OK(qmp_client_invoke(client, /* ret_slot= */ NULL, "cont", NULL, on_test_result, &t)); qmp_test_wait(event, &t); ASSERT_EQ(t.error, 0); qmp_test_result_done(&t); /* Clean shutdown */ - ASSERT_OK(qmp_client_invoke(client, "quit", NULL, on_test_result, &t)); + ASSERT_OK(qmp_client_invoke(client, /* ret_slot= */ NULL, "quit", NULL, on_test_result, &t)); qmp_test_wait(event, &t); ASSERT_EQ(t.error, 0); qmp_test_result_done(&t); diff --git a/src/test/test-qmp-client.c b/src/test/test-qmp-client.c index c70be2c0f4ea2..bbc3f15286953 100644 --- a/src/test/test-qmp-client.c +++ b/src/test/test-qmp-client.c @@ -248,7 +248,7 @@ TEST(qmp_client_basic) { qmp_client_bind_event(client, test_event_callback, &event_received); /* Execute query-status */ - ASSERT_OK(qmp_client_invoke(client, "query-status", NULL, on_test_result, &t)); + ASSERT_OK(qmp_client_invoke(client, /* ret_slot= */ NULL, "query-status", NULL, on_test_result, &t)); qmp_test_wait(event, &t); ASSERT_EQ(t.error, 0); ASSERT_NOT_NULL(t.result); @@ -262,13 +262,13 @@ TEST(qmp_client_basic) { qmp_test_result_done(&t); /* Execute stop */ - ASSERT_OK(qmp_client_invoke(client, "stop", NULL, on_test_result, &t)); + ASSERT_OK(qmp_client_invoke(client, /* ret_slot= */ NULL, "stop", NULL, on_test_result, &t)); qmp_test_wait(event, &t); ASSERT_EQ(t.error, 0); qmp_test_result_done(&t); /* Execute cont -- the STOP event should be dispatched by the IO callback */ - ASSERT_OK(qmp_client_invoke(client, "cont", NULL, on_test_result, &t)); + ASSERT_OK(qmp_client_invoke(client, /* ret_slot= */ NULL, "cont", NULL, on_test_result, &t)); qmp_test_wait(event, &t); ASSERT_EQ(t.error, 0); qmp_test_result_done(&t); @@ -320,7 +320,7 @@ TEST(qmp_client_eof) { /* Executing a command should fail with a disconnect error because the server * closed. The handshake may succeed or fail inside invoke() — either way the * invoke itself or the async callback should report a disconnect. */ - r = qmp_client_invoke(client, "query-status", NULL, on_test_result, &t); + r = qmp_client_invoke(client, /* ret_slot= */ NULL, "query-status", NULL, on_test_result, &t); if (r < 0) ASSERT_TRUE(ERRNO_IS_NEG_DISCONNECT(r)); else { @@ -430,7 +430,7 @@ TEST(qmp_client_first_invoke_with_fd) { /* THIS is the previously-broken pattern: very first invoke against the client, * carrying an fd, with the handshake still pending. */ - ASSERT_OK(qmp_client_invoke(client, "add-fd", + ASSERT_OK(qmp_client_invoke(client, /* ret_slot= */ NULL, "add-fd", QMP_CLIENT_ARGS_FD(args, TAKE_FD(fd_to_pass)), on_test_result, &t)); @@ -478,7 +478,7 @@ TEST(qmp_client_invoke_failure_closes_fds) { /* invoke must fail because the peer is gone. The TAKE_FD inside the macro * has already zeroed our local fd_to_pass; if invoke leaked the fd here, * the fd would stay open in our process. */ - int r = qmp_client_invoke(client, "add-fd", + int r = qmp_client_invoke(client, /* ret_slot= */ NULL, "add-fd", QMP_CLIENT_ARGS_FD(args, TAKE_FD(fd_to_pass)), on_test_result, &t); ASSERT_TRUE(r < 0); @@ -493,6 +493,125 @@ TEST(qmp_client_invoke_failure_closes_fds) { ASSERT_EQ(errno, EBADF); } +/* Mock for the slot lifecycle + cancel tests: greets, accepts capabilities, then accepts + * query-status and stop, replying with dummy returns. A cancelled query-status still gets + * sent on the wire (cancel merely removes the pending slot), so the server must be prepared + * to read and reply to it. */ +static _noreturn_ void mock_qmp_server_slot(int fd) { + _cleanup_(json_stream_done) JsonStream s = {}; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *status_return = NULL; + + mock_qmp_init(&s, fd); + + mock_qmp_send_literal(&s, + "{\"QMP\": {\"version\": {\"qemu\": {\"micro\": 0, \"minor\": 0, \"major\": 9}}, \"capabilities\": []}}"); + + mock_qmp_expect_and_reply(&s, "qmp_capabilities", NULL); + + ASSERT_OK(sd_json_buildo( + &status_return, + SD_JSON_BUILD_PAIR_BOOLEAN("running", true), + SD_JSON_BUILD_PAIR_STRING("status", "running"))); + mock_qmp_expect_and_reply(&s, "query-status", status_return); + + mock_qmp_expect_and_reply(&s, "stop", NULL); + + _exit(EXIT_SUCCESS); +} + +/* Verify that when qmp_client_invoke() returns a slot, qmp_slot_get_client() tracks the + * connection state: the client pointer is reported while the call is in flight, and flipped + * back to NULL once the reply has been dispatched. The caller must still be able to drop its + * ref safely after that. */ +TEST(qmp_client_invoke_slot_lifecycle) { + _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(pidref_done_sigkill_wait) PidRef pid = PIDREF_NULL; + _cleanup_(qmp_slot_unrefp) QmpSlot *slot = NULL; + QmpTestResult t = {}; + int qmp_fds[2]; + int r; + + ASSERT_OK(sd_event_new(&event)); + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + + r = ASSERT_OK(pidref_safe_fork("(mock-qmp-slot-life)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid)); + if (r == 0) { + safe_close(qmp_fds[0]); + mock_qmp_server_slot(qmp_fds[1]); + } + safe_close(qmp_fds[1]); + + ASSERT_OK(qmp_client_connect_fd(&client, qmp_fds[0])); + ASSERT_OK(qmp_client_attach_event(client, event, SD_EVENT_PRIORITY_NORMAL)); + + ASSERT_OK(qmp_client_invoke(client, &slot, "query-status", NULL, on_test_result, &t)); + + /* While in flight the slot still references its client. */ + ASSERT_NOT_NULL(slot); + ASSERT_PTR_EQ(qmp_slot_get_client(slot), client); + + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + ASSERT_NOT_NULL(t.result); + + /* Once dispatched, the slot is disconnected from the client but still owned by us. */ + ASSERT_NULL(qmp_slot_get_client(slot)); + + qmp_test_result_done(&t); + + /* Drop our ref explicitly (out of order w.r.t. cleanup) to exercise the + * already-disconnected path in qmp_slot_free(). */ + slot = qmp_slot_unref(slot); + ASSERT_NULL(slot); +} + +/* Verify that dropping the only reference on a pending slot before the reply arrives cancels + * the callback. The command is already enqueued on the stream at that point, so the server + * still sees it and replies — but the reply lands on an unknown id and is discarded. */ +TEST(qmp_client_invoke_slot_cancel) { + _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; + _cleanup_(pidref_done_sigkill_wait) PidRef pid = PIDREF_NULL; + QmpTestResult t_cancelled = {}; + QmpSlot *slot = NULL; + int qmp_fds[2]; + int r; + + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + + r = ASSERT_OK(pidref_safe_fork("(mock-qmp-slot-cancel)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid)); + if (r == 0) { + safe_close(qmp_fds[0]); + mock_qmp_server_slot(qmp_fds[1]); + } + safe_close(qmp_fds[1]); + + /* Drive without an event loop so the subsequent qmp_client_call() owns all pumping; + * it serializes write→read round-trips, which avoids the mock server seeing the + * cancelled query-status and the follow-up stop concatenated into a single recv(). */ + ASSERT_OK(qmp_client_connect_fd(&client, qmp_fds[0])); + + ASSERT_OK(qmp_client_invoke(client, &slot, "query-status", NULL, on_test_result, &t_cancelled)); + ASSERT_NOT_NULL(slot); + + /* Drop our sole ref → slot disconnects itself from the client's pending set. The + * enqueued query-status is still on the wire; when its reply arrives, dispatch_reply + * won't find a matching slot and will log-and-discard it. */ + slot = qmp_slot_unref(slot); + ASSERT_NULL(slot); + + /* Synchronous call drives its own process+wait pump: it first drains the already- + * enqueued query-status write, consumes (and discards) its reply, then sends stop + * and waits for that reply. Any improper fire of the cancelled callback would have + * happened during that process() pass. */ + ASSERT_EQ(qmp_client_call(client, "stop", NULL, NULL, NULL), 1); + + /* The cancelled callback must never have fired. */ + ASSERT_FALSE(t_cancelled.done); + ASSERT_NULL(t_cancelled.result); + ASSERT_NULL(t_cancelled.error_desc); +} + /* Drives a small wire dance for the sync call test: greeting, capabilities, one successful * command reply, and two error replies (one for the ret_error_desc path, one for the -EIO * path). */ diff --git a/src/vmspawn/vmspawn-qmp.c b/src/vmspawn/vmspawn-qmp.c index 4171772ee7c84..f69d2a5e7c64c 100644 --- a/src/vmspawn/vmspawn-qmp.c +++ b/src/vmspawn/vmspawn-qmp.c @@ -126,7 +126,7 @@ static int qmp_fdset_add(QmpClient *qmp, int fd_consume, char **ret_path) { if (asprintf(&path, "/dev/fdset/%u", id) < 0) return -ENOMEM; - r = qmp_client_invoke(qmp, "add-fd", QMP_CLIENT_ARGS_FD(args, TAKE_FD(fd)), + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "add-fd", QMP_CLIENT_ARGS_FD(args, TAKE_FD(fd)), on_qmp_setup_complete, (void*) "add-fd"); if (r < 0) return r; @@ -221,7 +221,7 @@ static int qmp_add_file_node(QmpClient *qmp, const QmpFileNodeParams *p) { if (r < 0) return r; - return qmp_client_invoke(qmp, "blockdev-add", QMP_CLIENT_ARGS(args), on_qmp_setup_complete, (void*) "blockdev-add"); + return qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(args), on_qmp_setup_complete, (void*) "blockdev-add"); } /* Get the virtual size of an image from the fd directly. For raw images the virtual size @@ -320,7 +320,7 @@ static int on_ephemeral_create_concluded(QmpClient *qmp, void *userdata) { if (r < 0) return log_error_errno(r, "Failed to build overlay format JSON for '%s': %m", ctx->node_name); - r = qmp_client_invoke(qmp, "blockdev-add", QMP_CLIENT_ARGS(fmt_args), on_qmp_setup_complete, (void*) "blockdev-add"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(fmt_args), on_qmp_setup_complete, (void*) "blockdev-add"); if (r < 0) return r; @@ -339,7 +339,7 @@ static int on_ephemeral_create_concluded(QmpClient *qmp, void *userdata) { if (r < 0) return log_error_errno(r, "Failed to build device_add JSON for '%s': %m", ctx->node_name); - r = qmp_client_invoke(qmp, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_setup_complete, (void*) "device_add"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_setup_complete, (void*) "device_add"); if (r < 0) return r; @@ -408,7 +408,7 @@ static int qmp_setup_ephemeral_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, D if (r < 0) return r; - r = qmp_client_invoke(qmp, "blockdev-add", QMP_CLIENT_ARGS(base_fmt_args), on_qmp_setup_complete, (void*) "blockdev-add"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(base_fmt_args), on_qmp_setup_complete, (void*) "blockdev-add"); if (r < 0) return log_error_errno(r, "Failed to send blockdev-add for base format '%s': %m", drive->path); @@ -424,7 +424,7 @@ static int qmp_setup_ephemeral_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, D if (r < 0) return r; - r = qmp_client_invoke(qmp, "blockdev-add", QMP_CLIENT_ARGS(overlay_file_args), on_qmp_setup_complete, (void*) "blockdev-add"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(overlay_file_args), on_qmp_setup_complete, (void*) "blockdev-add"); if (r < 0) return log_error_errno(r, "Failed to send blockdev-add for overlay file '%s': %m", drive->path); @@ -482,7 +482,7 @@ static int qmp_setup_ephemeral_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, D TAKE_PTR(ectx); - r = qmp_client_invoke(qmp, "blockdev-create", QMP_CLIENT_ARGS(cmd_args), on_qmp_setup_complete, (void*) "blockdev-create"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-create", QMP_CLIENT_ARGS(cmd_args), on_qmp_setup_complete, (void*) "blockdev-create"); if (r < 0) return log_error_errno(r, "Failed to send blockdev-create for '%s': %m", drive->path); @@ -532,7 +532,7 @@ static int qmp_setup_regular_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, Dri if (r < 0) return r; - r = qmp_client_invoke(qmp, "blockdev-add", QMP_CLIENT_ARGS(fmt_args), on_qmp_setup_complete, (void*) "blockdev-add"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(fmt_args), on_qmp_setup_complete, (void*) "blockdev-add"); if (r < 0) return log_error_errno(r, "Failed to send blockdev-add format for '%s': %m", drive->path); @@ -542,7 +542,7 @@ static int qmp_setup_regular_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, Dri if (r < 0) return r; - r = qmp_client_invoke(qmp, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_setup_complete, (void*) "device_add"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_setup_complete, (void*) "device_add"); if (r < 0) return log_error_errno(r, "Failed to send device_add for '%s': %m", drive->path); @@ -584,7 +584,7 @@ int vmspawn_qmp_setup_network(VmspawnQmpBridge *bridge, NetworkInfo *network) { if (r < 0) return log_error_errno(r, "Failed to build getfd JSON: %m"); - r = qmp_client_invoke(qmp, "getfd", QMP_CLIENT_ARGS_FD(getfd_args, TAKE_FD(network->fd)), + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "getfd", QMP_CLIENT_ARGS_FD(getfd_args, TAKE_FD(network->fd)), on_qmp_setup_complete, (void*) "getfd"); if (r < 0) return log_error_errno(r, "Failed to send getfd for TAP fd: %m"); @@ -606,7 +606,7 @@ int vmspawn_qmp_setup_network(VmspawnQmpBridge *bridge, NetworkInfo *network) { if (r < 0) return log_error_errno(r, "Failed to build netdev_add JSON: %m"); - r = qmp_client_invoke(qmp, "netdev_add", QMP_CLIENT_ARGS(netdev_args), on_qmp_setup_complete, (void*) "netdev_add"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "netdev_add", QMP_CLIENT_ARGS(netdev_args), on_qmp_setup_complete, (void*) "netdev_add"); if (r < 0) return log_error_errno(r, "Failed to send netdev_add: %m"); @@ -623,7 +623,7 @@ int vmspawn_qmp_setup_network(VmspawnQmpBridge *bridge, NetworkInfo *network) { if (r < 0) return log_error_errno(r, "Failed to build NIC device_add JSON: %m"); - r = qmp_client_invoke(qmp, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_setup_complete, (void*) "device_add"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_setup_complete, (void*) "device_add"); if (r < 0) return log_error_errno(r, "Failed to send NIC device_add: %m"); @@ -658,7 +658,7 @@ static int vmspawn_qmp_setup_one_virtiofs(QmpClient *qmp, const VirtiofsInfo *vf if (r < 0) return log_error_errno(r, "Failed to build chardev-add JSON for '%s': %m", vfs->id); - r = qmp_client_invoke(qmp, "chardev-add", QMP_CLIENT_ARGS(chardev_args), on_qmp_setup_complete, (void*) "chardev-add"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "chardev-add", QMP_CLIENT_ARGS(chardev_args), on_qmp_setup_complete, (void*) "chardev-add"); if (r < 0) return log_error_errno(r, "Failed to send chardev-add '%s': %m", vfs->id); @@ -675,7 +675,7 @@ static int vmspawn_qmp_setup_one_virtiofs(QmpClient *qmp, const VirtiofsInfo *vf if (r < 0) return log_error_errno(r, "Failed to build virtiofs device_add JSON for '%s': %m", vfs->id); - r = qmp_client_invoke(qmp, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_setup_complete, (void*) "device_add"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_setup_complete, (void*) "device_add"); if (r < 0) return log_error_errno(r, "Failed to send virtiofs device_add '%s': %m", vfs->id); @@ -719,7 +719,7 @@ int vmspawn_qmp_setup_vsock(VmspawnQmpBridge *bridge, VsockInfo *vsock) { if (r < 0) return log_error_errno(r, "Failed to build getfd JSON for VSOCK: %m"); - r = qmp_client_invoke(qmp, "getfd", QMP_CLIENT_ARGS_FD(getfd_args, TAKE_FD(vsock->fd)), + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "getfd", QMP_CLIENT_ARGS_FD(getfd_args, TAKE_FD(vsock->fd)), on_qmp_setup_complete, (void*) "getfd"); if (r < 0) return log_error_errno(r, "Failed to send getfd for VSOCK fd: %m"); @@ -736,7 +736,7 @@ int vmspawn_qmp_setup_vsock(VmspawnQmpBridge *bridge, VsockInfo *vsock) { if (r < 0) return log_error_errno(r, "Failed to build VSOCK device_add JSON: %m"); - r = qmp_client_invoke(qmp, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_setup_complete, (void*) "device_add"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_setup_complete, (void*) "device_add"); if (r < 0) return log_error_errno(r, "Failed to send VSOCK device_add: %m"); @@ -766,7 +766,7 @@ static int qmp_setup_scsi_controller(QmpClient *qmp, const char *pcie_port) { if (r < 0) return log_error_errno(r, "Failed to build SCSI controller JSON: %m"); - r = qmp_client_invoke(qmp, "device_add", QMP_CLIENT_ARGS(args), on_qmp_setup_complete, (void*) "device_add"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(args), on_qmp_setup_complete, (void*) "device_add"); if (r < 0) return log_error_errno(r, "Failed to send SCSI controller device_add: %m"); @@ -909,8 +909,8 @@ static int on_io_uring_probe_add_reply( if (r < 0) return r; - return qmp_client_invoke(c, "blockdev-del", QMP_CLIENT_ARGS(del_args), - on_io_uring_probe_del_reply, bridge); + return qmp_client_invoke(c, /* ret_slot= */ NULL, "blockdev-del", QMP_CLIENT_ARGS(del_args), + on_io_uring_probe_del_reply, bridge); } static int probe_io_uring(QmpClient *c, VmspawnQmpBridge *bridge) { @@ -930,8 +930,8 @@ static int probe_io_uring(QmpClient *c, VmspawnQmpBridge *bridge) { if (r < 0) return r; - return qmp_client_invoke(c, "blockdev-add", QMP_CLIENT_ARGS(args), - on_io_uring_probe_add_reply, bridge); + return qmp_client_invoke(c, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(args), + on_io_uring_probe_add_reply, bridge); } static int on_probe_schema_reply( @@ -965,8 +965,8 @@ static int probe_schema(QmpClient *c, VmspawnQmpBridge *bridge) { assert(c); assert(bridge); - return qmp_client_invoke(c, "query-qmp-schema", QMP_CLIENT_ARGS(NULL), - on_probe_schema_reply, bridge); + return qmp_client_invoke(c, /* ret_slot= */ NULL, "query-qmp-schema", QMP_CLIENT_ARGS(NULL), + on_probe_schema_reply, bridge); } int vmspawn_qmp_init(VmspawnQmpBridge **ret, int fd, sd_event *event) { @@ -1055,5 +1055,5 @@ static int on_cont_complete( int vmspawn_qmp_start(VmspawnQmpBridge *bridge) { assert(bridge); - return qmp_client_invoke(bridge->qmp, "cont", /* args= */ NULL, on_cont_complete, /* userdata= */ NULL); + return qmp_client_invoke(bridge->qmp, /* ret_slot= */ NULL, "cont", /* args= */ NULL, on_cont_complete, /* userdata= */ NULL); } diff --git a/src/vmspawn/vmspawn-varlink.c b/src/vmspawn/vmspawn-varlink.c index c73372e7a1a64..4a11b6fd4e103 100644 --- a/src/vmspawn/vmspawn-varlink.c +++ b/src/vmspawn/vmspawn-varlink.c @@ -71,7 +71,7 @@ static int qmp_execute_varlink_async( sd_varlink_ref(link); - r = qmp_client_invoke(ctx->bridge->qmp, command, QMP_CLIENT_ARGS(arguments), callback, link); + r = qmp_client_invoke(ctx->bridge->qmp, /* ret_slot= */ NULL, command, QMP_CLIENT_ARGS(arguments), callback, link); if (r < 0) sd_varlink_unref(link); @@ -241,7 +241,7 @@ static int dispatch_pending_job(VmspawnQmpBridge *bridge, sd_json_variant *data) if (r < 0) return sd_event_exit(qmp_client_get_event(bridge->qmp), r); - r = qmp_client_invoke(bridge->qmp, "job-dismiss", QMP_CLIENT_ARGS(dismiss_args), + r = qmp_client_invoke(bridge->qmp, /* ret_slot= */ NULL, "job-dismiss", QMP_CLIENT_ARGS(dismiss_args), on_job_dismiss_complete, /* userdata= */ NULL); if (r < 0) return sd_event_exit(qmp_client_get_event(bridge->qmp), r); From 5f420abe1a86eca2ca0a9509edf96979530e885d Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 24 Apr 2026 07:34:07 +0000 Subject: [PATCH 1241/1296] json-stream: stop concatenating fd-bearing queue items with prior output-buffer bytes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit json_stream_format_queue() drains queued output items into the output buffer and stages their fds in n_output_fds, relying on the downstream sendmsg() to deliver bytes-and-ancillary atomically as one SCM_RIGHTS message. If the output buffer already holds bytes (from a prior fast-path enqueue that hasn't been sent yet or from a partial write), concatenating a new fd-bearing item's JSON into it means the next sendmsg() ships the combined bytes with those fds attached — violating the per-message fd boundary on transports where that boundary is load-bearing. Bail out of the drain loop when we would cross that boundary, so the next write() first sends the buffered bytes with no ancillary, then pulls the fd-bearing item into a clean buffer and ships it on its own sendmsg. This only produces an observable difference for SOCK_SEQPACKET / SOCK_DGRAM consumers: on those transports each sendmsg() is its own datagram with its own SCM_RIGHTS cmsg, so whether we concatenate matters. On AF_UNIX SOCK_STREAM (today's sole consumer shape, used by sd-varlink and the QMP client) the kernel absorbs a preceding non-scm skb forward into the next scm-bearing skb's recv, so per-sendmsg separation is invisible to the receiver anyway — the guard is cheap defensive sender hygiene there, not a behaviour change. It becomes load- bearing the moment a SEQPACKET/DGRAM consumer wires JsonStream up. --- src/libsystemd/sd-json/json-stream.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/libsystemd/sd-json/json-stream.c b/src/libsystemd/sd-json/json-stream.c index 1900d1c7da4d3..3775691d47f67 100644 --- a/src/libsystemd/sd-json/json-stream.c +++ b/src/libsystemd/sd-json/json-stream.c @@ -990,8 +990,9 @@ static int json_stream_format_queue(JsonStream *s) { assert(s); - /* Drain entries out of the output queue and format them into the output buffer. Stop - * if there are unwritten output_fds, since adding more would corrupt the fd boundary. */ + /* Drain entries out of the output queue and format them into the output buffer. + * Stop if there are unwritten output_fds or if the next item carries fds but + * the output buffer is non-empty, since adding more would corrupt the fd boundary. */ while (s->output_queue) { assert(s->n_output_queue > 0); @@ -1000,8 +1001,24 @@ static int json_stream_format_queue(JsonStream *s) { return 0; JsonStreamQueueItem *q = s->output_queue; - _cleanup_free_ int *array = NULL; + /* If the next item carries fds but the output buffer still holds bytes from + * a prior fast-path enqueue or a partial write, we must not concatenate its + * JSON into that same buffer: the subsequent sendmsg() in json_stream_write() + * would attach the fds to the combined bytes and break the message-to-fd boundary. + * Stop here and let json_stream_write() drain the buffer first; the next write() + * call will pull this item into a clean buffer. + * + * Note: this only produces a difference on SOCK_SEQPACKET / SOCK_DGRAM, where + * each sendmsg() is its own datagram with its own SCM_RIGHTS cmsg. On AF_UNIX + * SOCK_STREAM the kernel absorbs a preceding non-scm skb forward into the + * next scm-bearing skb's recv, so per-sendmsg separation is invisible to the + * receiver anyway. Kept as cheap defensive sender hygiene that's necessary + * the moment a SEQPACKET/DGRAM consumer wires JsonStream up. */ + if (q->n_fds > 0 && s->output_buffer_size > 0) + return 0; + + _cleanup_free_ int *array = NULL; if (q->n_fds > 0) { array = newdup(int, q->fds, q->n_fds); if (!array) From 466662c8bd360e96aabd8325afba82a710c4e02e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 24 Apr 2026 07:51:44 +0000 Subject: [PATCH 1242/1296] qmp-client: eagerly enqueue qmp_capabilities on connect, drop the handshake state machine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QEMU's QMP greeting is an unsolicited, informational, server-initiated advertisement — it doesn't gate commands. The server accepts (and pipelines) commands the instant the socket is open. We were using it as a trigger to build qmp_capabilities and blocking callers via qmp_client_ensure_running() until the reply came back. Build qmp_capabilities inside qmp_client_connect_fd() instead and let the JsonStream output queue preserve FIFO ordering between it and any user command a subsequent invoke() enqueues. That satisfies QEMU's only ordering requirement (cap must precede other commands) without any blocking in the send path. Fallout: - Collapse QmpClientState to {RUNNING, DISCONNECTED}. The three HANDSHAKE_* states and the QMP_CLIENT_STATE_IS_HANDSHAKE() macro go away. - Drop qmp_client_dispatch_handshake(); fold greeting-drop into dispatch_reply as a one-line shape check. - Drop qmp_client_ensure_running() and its qmp_client_send() call site. send() now only refuses when state == DISCONNECTED. - The qmp_capabilities reply lands on an ordinary slot whose callback logs a protocol-level error and force-disconnects if cap negotiation failed, matching the old EPROTO behaviour at the same observable boundary. - qmp_client_phase() no longer special-cases the old handshake states; it maps directly to READING / AWAITING_REPLY based on whether slots are in flight. Test updates: - qmp_client_first_invoke_with_fd → qmp_client_invoke_with_fd. The scenario it was pinned to (push_fd+invoke staging order) has been structurally impossible since the QmpClientArgs rework in 8ad4adcb6f; eager-cap removes it a second way. The test now covers end-to-end fd-passing on the first invoke, accepting either recv carrying the single SCM_RIGHTS fd (AF_UNIX absorbs non-scm skbs forward into the next scm-bearing skb's recv, so the fd may surface with cap or add-fd depending on kernel scheduling — QEMU's FIFO fd queue handles either). - qmp_client_invoke_failure_closes_fds restructured around the new invariant: invoke no longer blocks and no longer returns ENOTCONN for a dead peer, so the fd-leak assertion moves to "still open while the JsonStream queue owns it, closed on client teardown" and the nested block is flattened into an explicit qmp_client_unref(). --- src/shared/qmp-client.c | 200 ++++++++++++------------------------- src/test/test-qmp-client.c | 90 ++++++++--------- 2 files changed, 101 insertions(+), 189 deletions(-) diff --git a/src/shared/qmp-client.c b/src/shared/qmp-client.c index 45bc0d8dbd22e..cc51259cd1290 100644 --- a/src/shared/qmp-client.c +++ b/src/shared/qmp-client.c @@ -14,22 +14,12 @@ #include "string-util.h" typedef enum QmpClientState { - QMP_CLIENT_HANDSHAKE_INITIAL, /* waiting for QMP greeting */ - QMP_CLIENT_HANDSHAKE_GREETING_RECEIVED, /* greeting received, sending qmp_capabilities */ - QMP_CLIENT_HANDSHAKE_CAPABILITIES_SENT, /* waiting for qmp_capabilities response */ - QMP_CLIENT_RUNNING, /* connected, ready for commands */ + QMP_CLIENT_RUNNING, /* connection alive; qmp_capabilities may still be in flight */ QMP_CLIENT_DISCONNECTED, /* connection closed */ _QMP_CLIENT_STATE_MAX, _QMP_CLIENT_STATE_INVALID = -EINVAL, } QmpClientState; -/* States routed to dispatch_handshake. */ -#define QMP_CLIENT_STATE_IS_HANDSHAKE(s) \ - IN_SET(s, \ - QMP_CLIENT_HANDSHAKE_INITIAL, \ - QMP_CLIENT_HANDSHAKE_GREETING_RECEIVED, \ - QMP_CLIENT_HANDSHAKE_CAPABILITIES_SENT) - struct QmpSlot { unsigned n_ref; QmpClient *client; /* NULL once disconnected (reply dispatched, cancelled, or client died) */ @@ -280,7 +270,7 @@ static int qmp_client_build_command( } /* Route c->current to event callback or matching async slot. Returns 1 on dispatch. */ -static int qmp_client_dispatch_reply(QmpClient *c) { +static int qmp_client_dispatch(QmpClient *c) { sd_json_variant *result = NULL; const char *desc = NULL; uint64_t id; @@ -298,6 +288,14 @@ static int qmp_client_dispatch_reply(QmpClient *c) { return 1; } + /* QEMU sends a one-shot greeting with a "QMP" key unsolicited on connect. We don't + * wait for it before sending qmp_capabilities (QEMU accepts commands the moment the + * socket is open), we detect it by the "QMP" key and drop it. */ + if (sd_json_variant_by_key(c->current, "QMP")) { + qmp_client_clear_current(c); + return 1; + } + /* Command responses carry an "id" matching a request we sent */ r = qmp_extract_response_id(c->current, &id); if (r < 0) { @@ -423,88 +421,6 @@ static bool qmp_client_test_disconnect(QmpClient *c) { return qmp_client_handle_disconnect(c); } -/* INITIAL → greeting → GREETING_RECEIVED → qmp_capabilities → CAPABILITIES_SENT → response → RUNNING. */ -static int qmp_client_dispatch_handshake(QmpClient *c) { - int r; - - assert(c); - assert(QMP_CLIENT_STATE_IS_HANDSHAKE(c->state)); - - if (!c->current) - return 0; - - /* Defensive: QEMU shouldn't emit events during capability negotiation, but if one - * arrives, dispatch it as an event rather than mis-parsing it as a handshake reply. */ - if (sd_json_variant_by_key(c->current, "event")) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = TAKE_PTR(c->current); - qmp_client_dispatch_event(c, v); - return 1; - } - - switch (c->state) { - - case QMP_CLIENT_HANDSHAKE_INITIAL: { - /* Waiting for QMP greeting. Take ownership so by_key()'s borrowed pointer - * stays valid through the case scope. */ - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = TAKE_PTR(c->current); - if (!sd_json_variant_by_key(v, "QMP")) - return json_stream_log_errno(&c->stream, SYNTHETIC_ERRNO(EPROTO), - "Expected QMP greeting, got something else"); - - c->state = QMP_CLIENT_HANDSHAKE_GREETING_RECEIVED; - - /* Fall through to immediately send capabilities */ - _fallthrough_; - } - - case QMP_CLIENT_HANDSHAKE_GREETING_RECEIVED: { - /* Send qmp_capabilities command */ - _cleanup_(sd_json_variant_unrefp) sd_json_variant *cmd = NULL; - r = sd_json_buildo( - &cmd, - SD_JSON_BUILD_PAIR_STRING("execute", "qmp_capabilities"), - SD_JSON_BUILD_PAIR_UNSIGNED("id", c->next_id++)); - if (r < 0) - return r; - - r = json_stream_enqueue(&c->stream, cmd); - if (r < 0) - return r; - - c->state = QMP_CLIENT_HANDSHAKE_CAPABILITIES_SENT; - return 1; - } - - case QMP_CLIENT_HANDSHAKE_CAPABILITIES_SENT: { - /* Take ownership so desc (borrowed from v's "error.desc") survives the format string. */ - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = TAKE_PTR(c->current); - const char *desc = NULL; - r = qmp_parse_response(v, /* ret_result= */ NULL, &desc); - if (r < 0) - return json_stream_log_errno(&c->stream, SYNTHETIC_ERRNO(EPROTO), - "qmp_capabilities failed: %s", desc); - - c->state = QMP_CLIENT_RUNNING; - return 1; - } - - default: - assert_not_reached(); - } -} - -static int qmp_client_dispatch(QmpClient *c) { - assert(c); - - if (!c->current) - return 0; - - if (QMP_CLIENT_STATE_IS_HANDSHAKE(c->state)) - return qmp_client_dispatch_handshake(c); - - return qmp_client_dispatch_reply(c); -} - /* Single step: write → dispatch → parse → read → disconnect. Matches sd_varlink_process(). */ int qmp_client_process(QmpClient *c) { int r; @@ -524,7 +440,7 @@ int qmp_client_process(QmpClient *c) { if (r != 0) goto finish; - /* 2. Dispatch — route based on state */ + /* 2. Dispatch — dispatch incoming messages to slots */ r = qmp_client_dispatch(c); if (r < 0) json_stream_log_errno(&c->stream, r, "Failed to dispatch QMP message: %m"); @@ -600,19 +516,14 @@ static JsonStreamPhase qmp_client_phase(void *userdata) { if (c->current) return JSON_STREAM_PHASE_OTHER; - /* During handshake we're waiting for the greeting or qmp_capabilities response. */ - if (QMP_CLIENT_STATE_IS_HANDSHAKE(c->state)) - return JSON_STREAM_PHASE_AWAITING_REPLY; - - /* Running with pending async commands — waiting for their responses. */ - if (c->state == QMP_CLIENT_RUNNING && !set_isempty(c->slots)) - return JSON_STREAM_PHASE_AWAITING_REPLY; - - /* Running with no pending commands — waiting for unsolicited events. */ - if (c->state == QMP_CLIENT_RUNNING) - return JSON_STREAM_PHASE_READING; + if (c->state != QMP_CLIENT_RUNNING) + return JSON_STREAM_PHASE_OTHER; - return JSON_STREAM_PHASE_OTHER; + /* Pending slots (user commands or the initial qmp_capabilities) → awaiting reply. + * Otherwise we're idling for unsolicited events. */ + return set_isempty(c->slots) + ? JSON_STREAM_PHASE_READING + : JSON_STREAM_PHASE_AWAITING_REPLY; } static int qmp_client_dispatch_cb(void *userdata) { @@ -630,33 +541,6 @@ static int qmp_client_defer_callback(sd_event_source *source, void *userdata) { return 1; } -/* Drive handshake to completion. Matches sd-bus's bus_ensure_running(). */ -static int qmp_client_ensure_running(QmpClient *c) { - int r; - - assert(c); - - if (c->state == QMP_CLIENT_RUNNING) - return 1; - - for (;;) { - if (c->state < 0 || c->state == QMP_CLIENT_DISCONNECTED) - return -ENOTCONN; - - r = qmp_client_process(c); - if (r < 0) - return r; - if (c->state == QMP_CLIENT_RUNNING) - return 1; - if (r > 0) - continue; - - r = qmp_client_wait(c, USEC_INFINITY); - if (r < 0) - return r; - } -} - static void qmp_client_detach_event(QmpClient *c) { if (!c) return; @@ -712,6 +596,37 @@ static int qmp_client_quit_callback(sd_event_source *source, void *userdata) { return 1; } +static int qmp_client_send( + QmpClient *c, + const char *command, + QmpClientArgs *args, + qmp_command_callback_t callback, + void *userdata, + QmpSlot **ret_slot); + +/* Reply callback for the eagerly-enqueued qmp_capabilities command. Success → we stay in + * RUNNING. Failure → negotiation is unrecoverable, force-disconnect so the next user op gets + * -ENOTCONN rather than hanging. */ +static int qmp_client_capabilities_reply( + QmpClient *c, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + /* qmp_client_handle_disconnect() below fails all pending slots, which re-enters this + * callback with -ECONNRESET on our own still-registered slot. Short-circuit that. */ + if (c->state == QMP_CLIENT_DISCONNECTED) + return 0; + + if (error >= 0) + return 0; + + json_stream_log_errno(&c->stream, error, "qmp_capabilities failed: %s", strna(error_desc)); + qmp_client_handle_disconnect(c); + return 0; +} + int qmp_client_connect_fd(QmpClient **ret, int fd) { _cleanup_(qmp_client_unrefp) QmpClient *c = NULL; int r; @@ -725,7 +640,7 @@ int qmp_client_connect_fd(QmpClient **ret, int fd) { *c = (QmpClient) { .n_ref = 1, - .state = QMP_CLIENT_HANDSHAKE_INITIAL, + .state = QMP_CLIENT_RUNNING, .next_id = 1, }; @@ -744,6 +659,16 @@ int qmp_client_connect_fd(QmpClient **ret, int fd) { if (r < 0) return r; + /* Eagerly queue qmp_capabilities. QEMU accepts commands as soon as the socket opens + * — its greeting is informational and doesn't gate writes on our side. FIFO ordering + * of the output queue guarantees cap precedes any user command a later invoke() + * enqueues, which is all QEMU actually requires. */ + r = qmp_client_send(c, "qmp_capabilities", /* args= */ NULL, + qmp_client_capabilities_reply, /* userdata= */ NULL, + /* ret_slot= */ NULL); + if (r < 0) + return r; + *ret = TAKE_PTR(c); return 0; } @@ -825,9 +750,8 @@ static int qmp_client_send( assert(c); assert(command); - r = qmp_client_ensure_running(c); - if (r < 0) - return r; + if (c->state == QMP_CLIENT_DISCONNECTED) + return -ENOTCONN; r = qmp_client_build_command(c, command, args ? args->arguments : NULL, &cmd, &id); if (r < 0) diff --git a/src/test/test-qmp-client.c b/src/test/test-qmp-client.c index bbc3f15286953..befee02484588 100644 --- a/src/test/test-qmp-client.c +++ b/src/test/test-qmp-client.c @@ -335,12 +335,13 @@ TEST(qmp_client_eof) { ASSERT_EQ(si.si_status, EXIT_SUCCESS); } -/* Mock QMP server for the fd-on-first-invoke regression. Drives the wire dance: - * greeting → (recv qmp_capabilities, expect 0 fds) → reply → - * (recv add-fd, expect exactly 1 fd) → reply - * Asserts the attached fd counts directly so a regression flips the child to - * exit_failure and the parent test fails on the wait-for-terminate. */ -static _noreturn_ void mock_qmp_server_fd_first(int fd) { +/* Mock QMP server for the fd-passing test. Drives the wire dance: + * greeting → recv qmp_capabilities → reply → recv add-fd → reply + * Asserts that exactly one SCM_RIGHTS fd arrives total across the two recvs. We can't + * require the fd to come attached to add-fd specifically: AF_UNIX coalesces the client's + * non-SCM cap sendmsg forward into the SCM-bearing add-fd sendmsg, so the fd may surface + * with either recv depending on kernel scheduling. QEMU's FIFO fd queue doesn't care. */ +static _noreturn_ void mock_qmp_server_fd(int fd) { _cleanup_(json_stream_done) JsonStream s = {}; _cleanup_(sd_json_variant_unrefp) sd_json_variant *cap_cmd = NULL, *addfd_cmd = NULL, @@ -349,18 +350,17 @@ static _noreturn_ void mock_qmp_server_fd_first(int fd) { *addfd_reply = NULL; mock_qmp_init(&s, fd); - /* Accept SCM_RIGHTS on incoming messages so we can count how many fds the client - * attaches to each sendmsg. */ ASSERT_OK(json_stream_set_allow_fd_passing_input(&s, true, /* with_sockopt= */ true)); /* Greeting */ mock_qmp_send_literal(&s, "{\"QMP\": {\"version\": {\"qemu\": {\"micro\": 0, \"minor\": 0, \"major\": 9}}, \"capabilities\": []}}"); - /* Receive qmp_capabilities — must arrive with NO fds attached. */ + /* Receive qmp_capabilities (may or may not carry the fd depending on coalescing). */ mock_qmp_recv(&s, &cap_cmd); - ASSERT_EQ(json_stream_get_n_input_fds(&s), (size_t) 0); + size_t n_fds_total = json_stream_get_n_input_fds(&s); ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(cap_cmd, "execute")), "qmp_capabilities"); + json_stream_close_input_fds(&s); sd_json_variant *cap_id = ASSERT_NOT_NULL(sd_json_variant_by_key(cap_cmd, "id")); ASSERT_OK(sd_json_buildo( @@ -369,12 +369,14 @@ static _noreturn_ void mock_qmp_server_fd_first(int fd) { SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_VARIANT(cap_id)))); mock_qmp_send(&s, cap_reply); - /* Receive add-fd — must arrive with EXACTLY ONE fd attached. */ + /* Receive add-fd (fd may already have been consumed with cap's recv). */ mock_qmp_recv(&s, &addfd_cmd); - ASSERT_EQ(json_stream_get_n_input_fds(&s), (size_t) 1); + n_fds_total += json_stream_get_n_input_fds(&s); ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(addfd_cmd, "execute")), "add-fd"); json_stream_close_input_fds(&s); + ASSERT_EQ(n_fds_total, (size_t) 1); + sd_json_variant *addfd_id = ASSERT_NOT_NULL(sd_json_variant_by_key(addfd_cmd, "id")); ASSERT_OK(sd_json_buildo( &addfd_return, @@ -389,13 +391,9 @@ static _noreturn_ void mock_qmp_server_fd_first(int fd) { _exit(EXIT_SUCCESS); } -/* Regression: pass an fd in the very first qmp_client_invoke() against a fresh client - * (lazy-bootstrap state, handshake not yet done). The previous push_fd+invoke split would - * stage the fd on the stream BEFORE qmp_client_ensure_running() drove the handshake; the - * handshake's qmp_capabilities enqueue would then steal the staged fd onto its own - * sendmsg. The new QmpClientArgs API stages fds inside invoke AFTER ensure_running, so - * the fd lands on add-fd's sendmsg as it should. */ -TEST(qmp_client_first_invoke_with_fd) { +/* End-to-end fd-passing through qmp_client_invoke() with QMP_CLIENT_ARGS_FD(): open a real + * fd, send add-fd, confirm the mock received a single SCM_RIGHTS fd and replied successfully. */ +TEST(qmp_client_invoke_with_fd) { _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(pidref_done) PidRef pid = PIDREF_NULL; @@ -408,11 +406,11 @@ TEST(qmp_client_first_invoke_with_fd) { ASSERT_OK(sd_event_new(&event)); ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); - r = ASSERT_OK(pidref_safe_fork("(mock-qmp-fd-first)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid)); + r = ASSERT_OK(pidref_safe_fork("(mock-qmp-fd)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid)); if (r == 0) { safe_close(qmp_fds[0]); - mock_qmp_server_fd_first(qmp_fds[1]); + mock_qmp_server_fd(qmp_fds[1]); } safe_close(qmp_fds[1]); @@ -424,12 +422,8 @@ TEST(qmp_client_first_invoke_with_fd) { ASSERT_OK(qmp_client_connect_fd(&client, qmp_fds[0])); ASSERT_OK(qmp_client_attach_event(client, event, SD_EVENT_PRIORITY_NORMAL)); - /* Build add-fd args. The fdset-id value is irrelevant — the mock server only - * cares that the fd arrived with the correct sendmsg. */ ASSERT_OK(sd_json_buildo(&args, SD_JSON_BUILD_PAIR_UNSIGNED("fdset-id", 0))); - /* THIS is the previously-broken pattern: very first invoke against the client, - * carrying an fd, with the handshake still pending. */ ASSERT_OK(qmp_client_invoke(client, /* ret_slot= */ NULL, "add-fd", QMP_CLIENT_ARGS_FD(args, TAKE_FD(fd_to_pass)), on_test_result, &t)); @@ -439,56 +433,50 @@ TEST(qmp_client_first_invoke_with_fd) { ASSERT_NOT_NULL(t.result); qmp_test_result_done(&t); - /* Wait for the mock server child. If it received fds in the wrong order it - * exited via the test-assertion failure path and si.si_status will be non-zero. */ + /* Wait for the mock. If its fd-count assertion tripped, si.si_status is non-zero. */ siginfo_t si = {}; ASSERT_OK(pidref_wait_for_terminate(&pid, &si)); ASSERT_EQ(si.si_code, CLD_EXITED); ASSERT_EQ(si.si_status, EXIT_SUCCESS); } -/* Regression: when qmp_client_invoke() fails before stage_fds runs (e.g. - * ensure_running() returns -ENOTCONN because the peer closed mid-handshake), the - * caller-supplied fds — already TAKE_FD()'d through QMP_CLIENT_ARGS_FD() — must be - * closed inside invoke. Otherwise they leak. */ +/* Regression: the caller-supplied fds — already TAKE_FD()'d through QMP_CLIENT_ARGS_FD() — + * must never leak, regardless of whether the invoke reaches the wire. Verified here via a + * dead peer: invoke enqueues (non-blocking), the queue item owns the fd, and client teardown + * must close it. */ TEST(qmp_client_invoke_failure_closes_fds) { - _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; _cleanup_close_ int fd_to_pass = -EBADF; + QmpClient *client = NULL; QmpTestResult t = {}; int qmp_fds[2]; int saved_fd_value; ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); - /* Close the peer end immediately so ensure_running()'s read sees EOF and - * the client transitions straight to DISCONNECTED inside the first invoke. */ + /* Close the peer end immediately so any write attempt sees EPIPE. */ safe_close(qmp_fds[1]); - ASSERT_OK(qmp_client_connect_fd(&client, qmp_fds[0])); - /* Deliberately do NOT attach to an event loop — invoke uses ensure_running()'s - * synchronous process+wait pump for the handshake. */ - fd_to_pass = open("/dev/null", O_RDWR|O_CLOEXEC); ASSERT_OK(fd_to_pass); saved_fd_value = fd_to_pass; /* remember the int value for the closed-check */ ASSERT_OK(sd_json_buildo(&args, SD_JSON_BUILD_PAIR_UNSIGNED("fdset-id", 0))); + ASSERT_OK(qmp_client_connect_fd(&client, qmp_fds[0])); - /* invoke must fail because the peer is gone. The TAKE_FD inside the macro - * has already zeroed our local fd_to_pass; if invoke leaked the fd here, - * the fd would stay open in our process. */ - int r = qmp_client_invoke(client, /* ret_slot= */ NULL, "add-fd", - QMP_CLIENT_ARGS_FD(args, TAKE_FD(fd_to_pass)), - on_test_result, &t); - ASSERT_TRUE(r < 0); - ASSERT_TRUE(ERRNO_IS_NEG_DISCONNECT(r)); + /* invoke no longer blocks on the handshake — it just enqueues. The fd is now + * owned by the underlying JsonStream output queue. */ + ASSERT_OK(qmp_client_invoke(client, /* ret_slot= */ NULL, "add-fd", + QMP_CLIENT_ARGS_FD(args, TAKE_FD(fd_to_pass)), + on_test_result, &t)); + ASSERT_EQ(fd_to_pass, -EBADF); /* TAKE_FD cleared our local handle */ + + /* The fd is still open here (held in JsonStream's queue). */ + ASSERT_OK_ERRNO(fcntl(saved_fd_value, F_GETFD)); - /* fd_to_pass should now be -EBADF (TAKE_FD'd) and the underlying kernel fd - * should have been closed by the qmp_client_args_close_fds cleanup in - * qmp_client_invoke(). fcntl on the old int returns EBADF only if the slot - * is genuinely free. */ - ASSERT_EQ(fd_to_pass, -EBADF); + /* Client teardown (json_stream_done) must close queued output fds, otherwise the + * saved fd number would still be valid. */ + client = qmp_client_unref(client); ASSERT_EQ(fcntl(saved_fd_value, F_GETFD), -1); ASSERT_EQ(errno, EBADF); } From 4ac6fc336d63f0b27de0961dc56c2fa75ba5c311 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 24 Apr 2026 07:51:53 +0000 Subject: [PATCH 1243/1296] test-qmp-client-qemu: exercise add-fd on the first invoke MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Covers the SCM_RIGHTS fd-passing path end-to-end against a real QEMU: open an eventfd, hand it off via QMP_CLIENT_ARGS_FD() on the very first qmp_client_invoke() against a fresh client, and verify QEMU's add-fd reply carries the expected fdset-id. Complements the mock-based unit test with an authoritative check that QEMU actually consumes the fd from its FIFO receive queue when processing the command — the AF_UNIX kernel behaviour around non-scm skb absorption into following scm-bearing recvs is real-traffic-shaped rather than mock-shaped. --- src/test/test-qmp-client-qemu.c | 73 +++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/test/test-qmp-client-qemu.c b/src/test/test-qmp-client-qemu.c index df8d3c6e21599..813b9c5687a50 100644 --- a/src/test/test-qmp-client-qemu.c +++ b/src/test/test-qmp-client-qemu.c @@ -9,6 +9,7 @@ * Skipped automatically if QEMU is not installed. */ #include +#include #include #include "sd-event.h" @@ -254,6 +255,78 @@ TEST(qmp_client_qemu_query_status) { pidref_done(&pidref); } +TEST(qmp_client_qemu_add_fd) { + _cleanup_free_ char *qemu = NULL; + _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + _cleanup_close_ int fd_to_pass = -EBADF; + QmpTestResult t = {}; + _cleanup_close_pair_ int qmp_fds[2] = EBADF_PAIR; + int r; + + if (find_qemu_binary(&qemu) < 0) { + log_tests_skipped("QEMU not found"); + return; + } + + ASSERT_OK(sd_event_new(&event)); + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + + ASSERT_OK(start_qemu(qemu, qmp_fds[1], &pidref)); + qmp_fds[1] = safe_close(qmp_fds[1]); + + r = qmp_client_connect_fd(&client, qmp_fds[0]); + if (r < 0) { + log_tests_skipped_errno(r, "QMP connect failed"); + return; + } + TAKE_FD(qmp_fds[0]); + + ASSERT_OK(qmp_client_attach_event(client, event, SD_EVENT_PRIORITY_NORMAL)); + + fd_to_pass = eventfd(0, EFD_CLOEXEC); + ASSERT_OK_ERRNO(fd_to_pass); + + ASSERT_OK(sd_json_buildo(&args, SD_JSON_BUILD_PAIR_UNSIGNED("fdset-id", 0))); + + /* Pass an fd via SCM_RIGHTS on the very first invoke against a fresh client: + * add-fd lands right after the eagerly-enqueued qmp_capabilities. QEMU processes cap + * first (no fd needed), then add-fd, popping the fd from its FIFO receive queue. */ + r = qmp_client_invoke(client, /* ret_slot= */ NULL, "add-fd", + QMP_CLIENT_ARGS_FD(args, TAKE_FD(fd_to_pass)), + on_test_result, &t); + if (r < 0) { + log_tests_skipped_errno(r, "QMP add-fd invoke failed"); + return; + } + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + ASSERT_NOT_NULL(t.result); + + sd_json_variant *fdset_id = ASSERT_NOT_NULL(sd_json_variant_by_key(t.result, "fdset-id")); + sd_json_variant *fd_v = ASSERT_NOT_NULL(sd_json_variant_by_key(t.result, "fd")); + ASSERT_EQ(sd_json_variant_unsigned(fdset_id), (uint64_t) 0); + log_info("add-fd returned fdset-id=%" PRIu64 ", fd=%" PRIu64, + sd_json_variant_unsigned(fdset_id), + sd_json_variant_unsigned(fd_v)); + + qmp_test_result_done(&t); + + /* Clean shutdown */ + ASSERT_OK(qmp_client_invoke(client, /* ret_slot= */ NULL, "quit", NULL, on_test_result, &t)); + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + qmp_test_result_done(&t); + + siginfo_t si = {}; + ASSERT_OK(pidref_wait_for_terminate(&pidref, &si)); + ASSERT_EQ(si.si_code, CLD_EXITED); + ASSERT_EQ(si.si_status, EXIT_SUCCESS); + pidref_done(&pidref); +} + static int intro(void) { /* QEMU dies between our last write and read on the QMP socket — without this we'd * get killed by the SIGPIPE the kernel raises on write-after-EOF. */ From 882049362d2967c40ba22956cd24b3d3b09e98df Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 16 Apr 2026 22:44:58 +0200 Subject: [PATCH 1244/1296] vmspawn: add QmpClient userdata and VmspawnQmpBridge.setup_done flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add qmp_client_set_userdata()/qmp_client_get_userdata() accessors mirroring the sd_varlink API, and wire up the VmspawnQmpBridge as userdata on the QmpClient so that command callbacks can retrieve it. Add a setup_done flag to VmspawnQmpBridge, set by on_cont_complete() when the VM has booted and all boot-time device setup is finished. This lets command callbacks differentiate boot-time errors (fatal — exit event loop) from runtime errors (recoverable — log and continue). Signed-off-by: Christian Brauner (Amutable) --- src/shared/qmp-client.c | 17 +++++++++++++++++ src/shared/qmp-client.h | 3 +++ src/vmspawn/vmspawn-qmp.c | 4 ++++ src/vmspawn/vmspawn-qmp.h | 1 + src/vmspawn/vmspawn-varlink.c | 1 + 5 files changed, 26 insertions(+) diff --git a/src/shared/qmp-client.c b/src/shared/qmp-client.c index cc51259cd1290..ad8f8bb24ac5f 100644 --- a/src/shared/qmp-client.c +++ b/src/shared/qmp-client.c @@ -49,6 +49,8 @@ struct QmpClient { QmpClientState state; sd_json_variant *current; /* most recently parsed message, pending dispatch */ + + void *userdata; }; static void qmp_slot_hash_func(const QmpSlot *p, struct siphash *state) { @@ -508,6 +510,21 @@ bool qmp_client_is_disconnected(QmpClient *c) { return c->state == QMP_CLIENT_DISCONNECTED; } +void* qmp_client_set_userdata(QmpClient *c, void *userdata) { + void *old; + + assert(c); + + old = c->userdata; + c->userdata = userdata; + return old; +} + +void* qmp_client_get_userdata(QmpClient *c) { + assert(c); + return c->userdata; +} + /* Map our state to the transport phase used for POLLIN / salvage / timeout decisions. */ static JsonStreamPhase qmp_client_phase(void *userdata) { QmpClient *c = ASSERT_PTR(userdata); diff --git a/src/shared/qmp-client.h b/src/shared/qmp-client.h index 7a477f7e4fa37..8f784731280da 100644 --- a/src/shared/qmp-client.h +++ b/src/shared/qmp-client.h @@ -54,6 +54,9 @@ bool qmp_client_is_idle(QmpClient *c); /* True iff the connection is dead. Stable terminal state — once set, it stays set. */ bool qmp_client_is_disconnected(QmpClient *c); +void* qmp_client_set_userdata(QmpClient *c, void *userdata); +void* qmp_client_get_userdata(QmpClient *c); + /* Async send. Returns 0 on send (callback will fire later), negative errno on failure. If * ret_slot is non-NULL, returns a reference to a QmpSlot which can be used to cancel the call * (by unreffing it before the reply arrives). */ diff --git a/src/vmspawn/vmspawn-qmp.c b/src/vmspawn/vmspawn-qmp.c index f69d2a5e7c64c..f0f987082c695 100644 --- a/src/vmspawn/vmspawn-qmp.c +++ b/src/vmspawn/vmspawn-qmp.c @@ -1042,6 +1042,8 @@ static int on_cont_complete( int error, void *userdata) { + VmspawnQmpBridge *bridge = ASSERT_PTR(qmp_client_get_userdata(client)); + assert(client); if (error < 0) { @@ -1049,6 +1051,8 @@ static int on_cont_complete( return sd_event_exit(qmp_client_get_event(client), error); } + /* VM is running — all boot-time device setup has completed. */ + bridge->setup_done = true; return 0; } diff --git a/src/vmspawn/vmspawn-qmp.h b/src/vmspawn/vmspawn-qmp.h index 8f8c26fb03e7d..15949c40a5e83 100644 --- a/src/vmspawn/vmspawn-qmp.h +++ b/src/vmspawn/vmspawn-qmp.h @@ -28,6 +28,7 @@ typedef struct VmspawnQmpBridge { QmpClient *qmp; Hashmap *pending_jobs; /* job_id (string, owned) -> PendingJob* */ VmspawnQmpFeatureFlags features; + bool setup_done; } VmspawnQmpBridge; VmspawnQmpBridge* vmspawn_qmp_bridge_free(VmspawnQmpBridge *b); diff --git a/src/vmspawn/vmspawn-varlink.c b/src/vmspawn/vmspawn-varlink.c index 4a11b6fd4e103..98d3fc73e911c 100644 --- a/src/vmspawn/vmspawn-varlink.c +++ b/src/vmspawn/vmspawn-varlink.c @@ -387,6 +387,7 @@ int vmspawn_varlink_setup( ctx->bridge = bridge; qmp_client_bind_event(ctx->bridge->qmp, on_qmp_event, ctx); qmp_client_bind_disconnect(ctx->bridge->qmp, on_qmp_disconnect, ctx); + qmp_client_set_userdata(ctx->bridge->qmp, ctx->bridge); log_debug("Varlink control server listening on %s", listen_address); From a5afc0f1071b6e371eda486064bc295f5289f67c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 16 Apr 2026 23:17:25 +0200 Subject: [PATCH 1245/1296] vmspawn: rename on_qmp_setup_complete() to on_qmp_complete() Pure rename, no functional change. Prepares for making this callback handle both boot-time (fatal) and runtime (non-fatal) errors based on the bridge setup_done flag. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn-qmp.c | 42 +++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/vmspawn/vmspawn-qmp.c b/src/vmspawn/vmspawn-qmp.c index f0f987082c695..32af5e4e3e456 100644 --- a/src/vmspawn/vmspawn-qmp.c +++ b/src/vmspawn/vmspawn-qmp.c @@ -85,7 +85,7 @@ void machine_config_done(MachineConfig *c) { /* Generic async QMP setup-completion callback. The userdata argument carries the * command name (as a string literal) for logging. On failure, request a clean * event loop exit so vmspawn shuts down instead of running a VM with missing devices. */ -static int on_qmp_setup_complete( +static int on_qmp_complete( QmpClient *client, sd_json_variant *result, const char *error_desc, @@ -127,7 +127,7 @@ static int qmp_fdset_add(QmpClient *qmp, int fd_consume, char **ret_path) { return -ENOMEM; r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "add-fd", QMP_CLIENT_ARGS_FD(args, TAKE_FD(fd)), - on_qmp_setup_complete, (void*) "add-fd"); + on_qmp_complete, (void*) "add-fd"); if (r < 0) return r; @@ -221,7 +221,7 @@ static int qmp_add_file_node(QmpClient *qmp, const QmpFileNodeParams *p) { if (r < 0) return r; - return qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(args), on_qmp_setup_complete, (void*) "blockdev-add"); + return qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(args), on_qmp_complete, (void*) "blockdev-add"); } /* Get the virtual size of an image from the fd directly. For raw images the virtual size @@ -320,7 +320,7 @@ static int on_ephemeral_create_concluded(QmpClient *qmp, void *userdata) { if (r < 0) return log_error_errno(r, "Failed to build overlay format JSON for '%s': %m", ctx->node_name); - r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(fmt_args), on_qmp_setup_complete, (void*) "blockdev-add"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(fmt_args), on_qmp_complete, (void*) "blockdev-add"); if (r < 0) return r; @@ -339,7 +339,7 @@ static int on_ephemeral_create_concluded(QmpClient *qmp, void *userdata) { if (r < 0) return log_error_errno(r, "Failed to build device_add JSON for '%s': %m", ctx->node_name); - r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_setup_complete, (void*) "device_add"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_complete, (void*) "device_add"); if (r < 0) return r; @@ -408,7 +408,7 @@ static int qmp_setup_ephemeral_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, D if (r < 0) return r; - r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(base_fmt_args), on_qmp_setup_complete, (void*) "blockdev-add"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(base_fmt_args), on_qmp_complete, (void*) "blockdev-add"); if (r < 0) return log_error_errno(r, "Failed to send blockdev-add for base format '%s': %m", drive->path); @@ -424,7 +424,7 @@ static int qmp_setup_ephemeral_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, D if (r < 0) return r; - r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(overlay_file_args), on_qmp_setup_complete, (void*) "blockdev-add"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(overlay_file_args), on_qmp_complete, (void*) "blockdev-add"); if (r < 0) return log_error_errno(r, "Failed to send blockdev-add for overlay file '%s': %m", drive->path); @@ -482,7 +482,7 @@ static int qmp_setup_ephemeral_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, D TAKE_PTR(ectx); - r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-create", QMP_CLIENT_ARGS(cmd_args), on_qmp_setup_complete, (void*) "blockdev-create"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-create", QMP_CLIENT_ARGS(cmd_args), on_qmp_complete, (void*) "blockdev-create"); if (r < 0) return log_error_errno(r, "Failed to send blockdev-create for '%s': %m", drive->path); @@ -532,7 +532,7 @@ static int qmp_setup_regular_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, Dri if (r < 0) return r; - r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(fmt_args), on_qmp_setup_complete, (void*) "blockdev-add"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(fmt_args), on_qmp_complete, (void*) "blockdev-add"); if (r < 0) return log_error_errno(r, "Failed to send blockdev-add format for '%s': %m", drive->path); @@ -542,7 +542,7 @@ static int qmp_setup_regular_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, Dri if (r < 0) return r; - r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_setup_complete, (void*) "device_add"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_complete, (void*) "device_add"); if (r < 0) return log_error_errno(r, "Failed to send device_add for '%s': %m", drive->path); @@ -585,7 +585,7 @@ int vmspawn_qmp_setup_network(VmspawnQmpBridge *bridge, NetworkInfo *network) { return log_error_errno(r, "Failed to build getfd JSON: %m"); r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "getfd", QMP_CLIENT_ARGS_FD(getfd_args, TAKE_FD(network->fd)), - on_qmp_setup_complete, (void*) "getfd"); + on_qmp_complete, (void*) "getfd"); if (r < 0) return log_error_errno(r, "Failed to send getfd for TAP fd: %m"); } @@ -606,7 +606,7 @@ int vmspawn_qmp_setup_network(VmspawnQmpBridge *bridge, NetworkInfo *network) { if (r < 0) return log_error_errno(r, "Failed to build netdev_add JSON: %m"); - r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "netdev_add", QMP_CLIENT_ARGS(netdev_args), on_qmp_setup_complete, (void*) "netdev_add"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "netdev_add", QMP_CLIENT_ARGS(netdev_args), on_qmp_complete, (void*) "netdev_add"); if (r < 0) return log_error_errno(r, "Failed to send netdev_add: %m"); @@ -623,7 +623,7 @@ int vmspawn_qmp_setup_network(VmspawnQmpBridge *bridge, NetworkInfo *network) { if (r < 0) return log_error_errno(r, "Failed to build NIC device_add JSON: %m"); - r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_setup_complete, (void*) "device_add"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_complete, (void*) "device_add"); if (r < 0) return log_error_errno(r, "Failed to send NIC device_add: %m"); @@ -658,7 +658,7 @@ static int vmspawn_qmp_setup_one_virtiofs(QmpClient *qmp, const VirtiofsInfo *vf if (r < 0) return log_error_errno(r, "Failed to build chardev-add JSON for '%s': %m", vfs->id); - r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "chardev-add", QMP_CLIENT_ARGS(chardev_args), on_qmp_setup_complete, (void*) "chardev-add"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "chardev-add", QMP_CLIENT_ARGS(chardev_args), on_qmp_complete, (void*) "chardev-add"); if (r < 0) return log_error_errno(r, "Failed to send chardev-add '%s': %m", vfs->id); @@ -675,7 +675,7 @@ static int vmspawn_qmp_setup_one_virtiofs(QmpClient *qmp, const VirtiofsInfo *vf if (r < 0) return log_error_errno(r, "Failed to build virtiofs device_add JSON for '%s': %m", vfs->id); - r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_setup_complete, (void*) "device_add"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_complete, (void*) "device_add"); if (r < 0) return log_error_errno(r, "Failed to send virtiofs device_add '%s': %m", vfs->id); @@ -720,7 +720,7 @@ int vmspawn_qmp_setup_vsock(VmspawnQmpBridge *bridge, VsockInfo *vsock) { return log_error_errno(r, "Failed to build getfd JSON for VSOCK: %m"); r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "getfd", QMP_CLIENT_ARGS_FD(getfd_args, TAKE_FD(vsock->fd)), - on_qmp_setup_complete, (void*) "getfd"); + on_qmp_complete, (void*) "getfd"); if (r < 0) return log_error_errno(r, "Failed to send getfd for VSOCK fd: %m"); @@ -736,7 +736,7 @@ int vmspawn_qmp_setup_vsock(VmspawnQmpBridge *bridge, VsockInfo *vsock) { if (r < 0) return log_error_errno(r, "Failed to build VSOCK device_add JSON: %m"); - r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_setup_complete, (void*) "device_add"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_complete, (void*) "device_add"); if (r < 0) return log_error_errno(r, "Failed to send VSOCK device_add: %m"); @@ -766,7 +766,7 @@ static int qmp_setup_scsi_controller(QmpClient *qmp, const char *pcie_port) { if (r < 0) return log_error_errno(r, "Failed to build SCSI controller JSON: %m"); - r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(args), on_qmp_setup_complete, (void*) "device_add"); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(args), on_qmp_complete, (void*) "device_add"); if (r < 0) return log_error_errno(r, "Failed to send SCSI controller device_add: %m"); @@ -910,7 +910,7 @@ static int on_io_uring_probe_add_reply( return r; return qmp_client_invoke(c, /* ret_slot= */ NULL, "blockdev-del", QMP_CLIENT_ARGS(del_args), - on_io_uring_probe_del_reply, bridge); + on_io_uring_probe_del_reply, bridge); } static int probe_io_uring(QmpClient *c, VmspawnQmpBridge *bridge) { @@ -931,7 +931,7 @@ static int probe_io_uring(QmpClient *c, VmspawnQmpBridge *bridge) { return r; return qmp_client_invoke(c, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(args), - on_io_uring_probe_add_reply, bridge); + on_io_uring_probe_add_reply, bridge); } static int on_probe_schema_reply( @@ -966,7 +966,7 @@ static int probe_schema(QmpClient *c, VmspawnQmpBridge *bridge) { assert(bridge); return qmp_client_invoke(c, /* ret_slot= */ NULL, "query-qmp-schema", QMP_CLIENT_ARGS(NULL), - on_probe_schema_reply, bridge); + on_probe_schema_reply, bridge); } int vmspawn_qmp_init(VmspawnQmpBridge **ret, int fd, sd_event *event) { From c697634ae220a84a8fcbb103bfcb812f31cdafd9 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 17 Apr 2026 00:23:20 +0200 Subject: [PATCH 1246/1296] vmspawn: heap-allocate each DriveInfo individually Change DriveInfos from a contiguous array of DriveInfo structs to an array of pointers to individually heap-allocated entries. Make all DriveInfo string fields owned (strdup'd) and add drive_info_new()/ drive_info_free() constructors matching the cleanup pattern. This prepares for the block device hotplug work where drives need to be handed off to a hashmap via TAKE_PTR without copying. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn-qmp.c | 38 +++++++++++++++++------ src/vmspawn/vmspawn-qmp.h | 26 +++++++++------- src/vmspawn/vmspawn.c | 65 ++++++++++++++++++++++----------------- 3 files changed, 80 insertions(+), 49 deletions(-) diff --git a/src/vmspawn/vmspawn-qmp.c b/src/vmspawn/vmspawn-qmp.c index 32af5e4e3e456..d467d68a89636 100644 --- a/src/vmspawn/vmspawn-qmp.c +++ b/src/vmspawn/vmspawn-qmp.c @@ -25,19 +25,37 @@ DEFINE_PRIVATE_HASH_OPS_FULL( char, string_hash_func, string_compare_func, free, PendingJob, pending_job_free); -void drive_info_done(DriveInfo *info) { - assert(info); - info->serial = mfree(info->serial); - info->node_name = mfree(info->node_name); - info->pcie_port = mfree(info->pcie_port); - info->fd = safe_close(info->fd); - info->overlay_fd = safe_close(info->overlay_fd); +DriveInfo* drive_info_new(void) { + DriveInfo *d = new(DriveInfo, 1); + if (!d) + return NULL; + + *d = (DriveInfo) { + .fd = -EBADF, + .overlay_fd = -EBADF, + }; + return d; +} + +DriveInfo* drive_info_free(DriveInfo *d) { + if (!d) + return NULL; + + free(d->path); + free(d->format); + free(d->disk_driver); + free(d->serial); + free(d->node_name); + free(d->pcie_port); + safe_close(d->fd); + safe_close(d->overlay_fd); + return mfree(d); } void drive_infos_done(DriveInfos *infos) { assert(infos); FOREACH_ARRAY(d, infos->drives, infos->n_drives) - drive_info_done(d); + drive_info_free(*d); infos->drives = mfree(infos->drives); infos->n_drives = 0; infos->scsi_pcie_port = mfree(infos->scsi_pcie_port); @@ -748,7 +766,7 @@ static bool drives_need_scsi_controller(DriveInfos *drives) { assert(drives); FOREACH_ARRAY(d, drives->drives, drives->n_drives) - if (STR_IN_SET(d->disk_driver, "scsi-hd", "scsi-cd")) + if (STR_IN_SET((*d)->disk_driver, "scsi-hd", "scsi-cd")) return true; return false; @@ -792,7 +810,7 @@ int vmspawn_qmp_setup_drives(VmspawnQmpBridge *bridge, DriveInfos *drives) { } FOREACH_ARRAY(d, drives->drives, drives->n_drives) { - r = qmp_setup_drive(bridge, qmp, d); + r = qmp_setup_drive(bridge, qmp, *d); if (r < 0) return r; } diff --git a/src/vmspawn/vmspawn-qmp.h b/src/vmspawn/vmspawn-qmp.h index 15949c40a5e83..35cc029ea763a 100644 --- a/src/vmspawn/vmspawn-qmp.h +++ b/src/vmspawn/vmspawn-qmp.h @@ -65,23 +65,27 @@ typedef enum QmpDriveFlags { QMP_DRIVE_DISCARD_NO_UNREF = 1u << 6, /* qcow2 only */ } QmpDriveFlags; -/* Drive info for QMP-based drive setup */ +/* Drive info for QMP-based drive setup. All string fields are owned. + * Each DriveInfo is individually heap-allocated so it can be handed off + * to the block device registry via TAKE_PTR. */ typedef struct DriveInfo { - const char *path; /* kept for logging only — not passed to QEMU */ - const char *format; /* "raw" or "qcow2" */ - const char *disk_driver; /* "virtio-blk-pci", "scsi-hd", "scsi-cd", "nvme" */ - char *serial; /* owned */ - char *node_name; /* owned */ - char *pcie_port; /* owned: pcie-root-port id for device_add bus (NULL on non-PCIe) */ - int fd; /* pre-opened image fd (owned, -EBADF if unused) */ - int overlay_fd; /* pre-opened anonymous overlay fd for ephemeral (owned, -EBADF if unused) */ + char *path; /* original path (for logging; not passed to QEMU) */ + char *format; /* "raw" or "qcow2" */ + char *disk_driver; /* "virtio-blk-pci", "scsi-hd", "scsi-cd", "nvme" */ + char *serial; + char *node_name; + char *pcie_port; /* pcie-root-port id for device_add bus (NULL on non-PCIe) */ + int fd; /* pre-opened image fd (-EBADF if unused) */ + int overlay_fd; /* pre-opened anonymous overlay fd for ephemeral (-EBADF if unused) */ QmpDriveFlags flags; } DriveInfo; -void drive_info_done(DriveInfo *info); +DriveInfo* drive_info_new(void); +DriveInfo* drive_info_free(DriveInfo *d); +DEFINE_TRIVIAL_CLEANUP_FUNC(DriveInfo *, drive_info_free); typedef struct DriveInfos { - DriveInfo *drives; + DriveInfo **drives; /* array of individually heap-allocated entries */ size_t n_drives; char *scsi_pcie_port; /* owned: pcie-root-port id for SCSI controller (NULL if no SCSI or non-PCIe) */ } DriveInfos; diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index e0ddcee9b16b1..01325bf9eb1e7 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -2303,6 +2303,8 @@ static int qemu_config_add_qmp_monitor(FILE *config_file, int bridge_fds[2], int } static int resolve_disk_driver(DiskType dt, const char *filename, DriveInfo *info) { + const char *driver; + size_t serial_max; int r; assert(filename); @@ -2310,34 +2312,34 @@ static int resolve_disk_driver(DiskType dt, const char *filename, DriveInfo *inf switch (dt) { case DISK_TYPE_VIRTIO_BLK: - info->disk_driver = "virtio-blk-pci"; - r = disk_serial(filename, DISK_SERIAL_MAX_LEN_VIRTIO_BLK, &info->serial); - if (r < 0) - return r; + driver = "virtio-blk-pci"; + serial_max = DISK_SERIAL_MAX_LEN_VIRTIO_BLK; break; case DISK_TYPE_VIRTIO_SCSI: - info->disk_driver = "scsi-hd"; - r = disk_serial(filename, DISK_SERIAL_MAX_LEN_SCSI, &info->serial); - if (r < 0) - return r; + driver = "scsi-hd"; + serial_max = DISK_SERIAL_MAX_LEN_SCSI; break; case DISK_TYPE_NVME: - info->disk_driver = "nvme"; - r = disk_serial(filename, DISK_SERIAL_MAX_LEN_NVME, &info->serial); - if (r < 0) - return r; + driver = "nvme"; + serial_max = DISK_SERIAL_MAX_LEN_NVME; break; case DISK_TYPE_VIRTIO_SCSI_CDROM: - info->disk_driver = "scsi-cd"; + driver = "scsi-cd"; + serial_max = DISK_SERIAL_MAX_LEN_SCSI; info->flags |= QMP_DRIVE_READ_ONLY; - r = disk_serial(filename, DISK_SERIAL_MAX_LEN_SCSI, &info->serial); - if (r < 0) - return r; break; default: assert_not_reached(); } + info->disk_driver = strdup(driver); + if (!info->disk_driver) + return log_oom(); + + r = disk_serial(filename, serial_max, &info->serial); + if (r < 0) + return r; + return 0; } @@ -2355,8 +2357,9 @@ static int prepare_primary_drive(const char *runtime_dir, DriveInfos *drives) { if (r < 0) return log_error_errno(r, "Failed to extract filename from path '%s': %m", arg_image); - DriveInfo *d = &drives->drives[drives->n_drives++]; - *d = (DriveInfo) { .fd = -EBADF, .overlay_fd = -EBADF }; + _cleanup_(drive_info_freep) DriveInfo *d = drive_info_new(); + if (!d) + return log_oom(); r = resolve_disk_driver(arg_image_disk_type, image_fn, d); if (r < 0) @@ -2376,10 +2379,10 @@ static int prepare_primary_drive(const char *runtime_dir, DriveInfos *drives) { if (r < 0) return log_error_errno(r, "Expected regular file or block device for image: %s", arg_image); - d->path = arg_image; - d->format = image_format_to_string(arg_image_format); + d->path = strdup(arg_image); + d->format = strdup(ASSERT_PTR(image_format_to_string(arg_image_format))); d->node_name = strdup("vmspawn"); - if (!d->node_name) + if (!d->path || !d->format || !d->node_name) return log_oom(); d->fd = TAKE_FD(image_fd); if (S_ISBLK(st.st_mode)) @@ -2406,6 +2409,7 @@ static int prepare_primary_drive(const char *runtime_dir, DriveInfos *drives) { d->flags |= QMP_DRIVE_NO_FLUSH; } + drives->drives[drives->n_drives++] = TAKE_PTR(d); return 0; } @@ -2423,8 +2427,9 @@ static int prepare_extra_drives(DriveInfos *drives) { DiskType dt = drive->disk_type >= 0 ? drive->disk_type : arg_image_disk_type; - DriveInfo *d = &drives->drives[drives->n_drives++]; - *d = (DriveInfo) { .fd = -EBADF, .overlay_fd = -EBADF }; + _cleanup_(drive_info_freep) DriveInfo *d = drive_info_new(); + if (!d) + return log_oom(); r = resolve_disk_driver(dt, drive_fn, d); if (r < 0) @@ -2445,8 +2450,10 @@ static int prepare_extra_drives(DriveInfos *drives) { "Block device '%s' cannot be used with 'qcow2' format, only 'raw' is supported.", drive->path); - d->path = drive->path; - d->format = image_format_to_string(drive->format); + d->path = strdup(drive->path); + d->format = strdup(ASSERT_PTR(image_format_to_string(drive->format))); + if (!d->path || !d->format) + return log_oom(); d->fd = TAKE_FD(drive_fd); if (S_ISBLK(drive_st.st_mode)) d->flags |= QMP_DRIVE_BLOCK_DEVICE; @@ -2454,6 +2461,8 @@ static int prepare_extra_drives(DriveInfos *drives) { if (asprintf(&d->node_name, "vmspawn_extra_%zu", extra_idx++) < 0) return log_oom(); + + drives->drives[drives->n_drives++] = TAKE_PTR(d); } return 0; @@ -2477,11 +2486,11 @@ static int assign_pcie_ports(MachineConfig *c) { /* Drives: non-SCSI drives get individual ports, SCSI controller gets one port */ bool need_scsi = false; FOREACH_ARRAY(d, drives->drives, drives->n_drives) { - if (STR_IN_SET(d->disk_driver, "scsi-hd", "scsi-cd")) { + if (STR_IN_SET((*d)->disk_driver, "scsi-hd", "scsi-cd")) { need_scsi = true; continue; } - if (asprintf(&d->pcie_port, "vmspawn-pcieport-%zu", port++) < 0) + if (asprintf(&(*d)->pcie_port, "vmspawn-pcieport-%zu", port++) < 0) return log_oom(); } if (need_scsi) @@ -2513,7 +2522,7 @@ static int prepare_device_info(const char *runtime_dir, MachineConfig *c) { /* Build drive info for QMP-based setup. vmspawn opens all image files and * passes fds to QEMU via add-fd — QEMU never needs filesystem access. */ - drives->drives = new0(DriveInfo, 1 + arg_extra_drives.n_drives); + drives->drives = new0(DriveInfo*, 1 + arg_extra_drives.n_drives); if (!drives->drives) return log_oom(); From 258c66784527bdffc8df0a3ad8437926bda5894c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 20 Apr 2026 09:28:54 +0200 Subject: [PATCH 1247/1296] shared: extract disk-spec parsing into machine-util MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the ImageFormat / DiskType enums and their string tables out of vmspawn's private settings header into a new src/shared/machine-util, and add parse_disk_spec() — the colon-prefix loop that turns "[FORMAT:][DISKTYPE:]PATH" into the two enums plus a normalized path. No behavior change for vmspawn. A follow-up machinectl attach-disk change accepts the same syntax and consumes the shared helper. Signed-off-by: Christian Brauner (Amutable) --- src/shared/machine-util.c | 102 +++++++++++++++++++++++++++++++++ src/shared/machine-util.h | 32 +++++++++++ src/shared/meson.build | 1 + src/vmspawn/vmspawn-settings.c | 16 ------ src/vmspawn/vmspawn-settings.h | 19 +----- src/vmspawn/vmspawn.c | 34 +---------- 6 files changed, 138 insertions(+), 66 deletions(-) create mode 100644 src/shared/machine-util.c create mode 100644 src/shared/machine-util.h diff --git a/src/shared/machine-util.c b/src/shared/machine-util.c new file mode 100644 index 0000000000000..fa5e46ace1e53 --- /dev/null +++ b/src/shared/machine-util.c @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "extract-word.h" +#include "machine-util.h" +#include "parse-argument.h" +#include "string-table.h" + +static const char *const image_format_table[_IMAGE_FORMAT_MAX] = { + [IMAGE_FORMAT_RAW] = "raw", + [IMAGE_FORMAT_QCOW2] = "qcow2", +}; + +DEFINE_STRING_TABLE_LOOKUP(image_format, ImageFormat); + +static const char *const disk_type_table[_DISK_TYPE_MAX] = { + [DISK_TYPE_VIRTIO_BLK] = "virtio-blk", + [DISK_TYPE_VIRTIO_SCSI] = "virtio-scsi", + [DISK_TYPE_NVME] = "nvme", + [DISK_TYPE_VIRTIO_SCSI_CDROM] = "scsi-cd", +}; + +DEFINE_STRING_TABLE_LOOKUP(disk_type, DiskType); + +/* Wire value for the io.systemd.VirtualMachineInstance.BlockDriver IDL enum. */ +static const char *const block_driver_table[_DISK_TYPE_MAX] = { + [DISK_TYPE_VIRTIO_BLK] = "virtio_blk", + [DISK_TYPE_VIRTIO_SCSI] = "scsi_hd", + [DISK_TYPE_NVME] = "nvme", + [DISK_TYPE_VIRTIO_SCSI_CDROM] = "scsi_cd", +}; + +DEFINE_STRING_TABLE_LOOKUP(block_driver, DiskType); + +/* QEMU -device driver name (e.g. "virtio-blk-pci"). */ +static const char *const qemu_device_driver_table[_DISK_TYPE_MAX] = { + [DISK_TYPE_VIRTIO_BLK] = "virtio-blk-pci", + [DISK_TYPE_VIRTIO_SCSI] = "scsi-hd", + [DISK_TYPE_NVME] = "nvme", + [DISK_TYPE_VIRTIO_SCSI_CDROM] = "scsi-cd", +}; + +DEFINE_STRING_TABLE_LOOKUP(qemu_device_driver, DiskType); + +int parse_disk_spec( + const char *arg, + ImageFormat *format, + DiskType *disk_type, + char **ret_path) { + + int r; + + assert(arg); + assert(format); + assert(disk_type); + assert(ret_path); + + ImageFormat parsed_format = *format; + DiskType parsed_disk_type = *disk_type; + const char *dp = arg; + + /* Format and disk-type vocabularies don't overlap, so prefixes may appear in any order. */ + for (;;) { + _cleanup_free_ char *word = NULL; + const char *save = dp; + + r = extract_first_word(&dp, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0 || !dp) { + /* No ':' remained after this word — rest is the path. */ + dp = save; + break; + } + + ImageFormat f = image_format_from_string(word); + if (f >= 0) { + parsed_format = f; + continue; + } + + DiskType dt = disk_type_from_string(word); + if (dt >= 0) { + parsed_disk_type = dt; + continue; + } + + /* Unknown prefix — rewind, remainder is the path. */ + dp = save; + break; + } + + _cleanup_free_ char *path = NULL; + r = parse_path_argument(dp, /* suppress_root= */ false, &path); + if (r < 0) + return r; + + *format = parsed_format; + *disk_type = parsed_disk_type; + *ret_path = TAKE_PTR(path); + return 0; +} diff --git a/src/shared/machine-util.h b/src/shared/machine-util.h new file mode 100644 index 0000000000000..3937ce170377e --- /dev/null +++ b/src/shared/machine-util.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +typedef enum ImageFormat { + IMAGE_FORMAT_RAW, + IMAGE_FORMAT_QCOW2, + _IMAGE_FORMAT_MAX, + _IMAGE_FORMAT_INVALID = -EINVAL, +} ImageFormat; + +typedef enum DiskType { + DISK_TYPE_VIRTIO_BLK, + DISK_TYPE_VIRTIO_SCSI, + DISK_TYPE_NVME, + DISK_TYPE_VIRTIO_SCSI_CDROM, + _DISK_TYPE_MAX, + _DISK_TYPE_INVALID = -EINVAL, +} DiskType; + +DECLARE_STRING_TABLE_LOOKUP(image_format, ImageFormat); +DECLARE_STRING_TABLE_LOOKUP(disk_type, DiskType); +DECLARE_STRING_TABLE_LOOKUP(block_driver, DiskType); +DECLARE_STRING_TABLE_LOOKUP(qemu_device_driver, DiskType); + +/* Parse "[FORMAT:][DISKTYPE:]PATH"; *format and *disk_type are in-out. */ +int parse_disk_spec( + const char *arg, + ImageFormat *format, + DiskType *disk_type, + char **ret_path); diff --git a/src/shared/meson.build b/src/shared/meson.build index 741dbb60a451a..56b823c50cd4f 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -133,6 +133,7 @@ shared_sources = files( 'machine-credential.c', 'machine-id-setup.c', 'machine-register.c', + 'machine-util.c', 'macvlan-util.c', 'main-func.c', 'metrics.c', diff --git a/src/vmspawn/vmspawn-settings.c b/src/vmspawn/vmspawn-settings.c index 9382172e2ca80..502189e7bea63 100644 --- a/src/vmspawn/vmspawn-settings.c +++ b/src/vmspawn/vmspawn-settings.c @@ -3,22 +3,6 @@ #include "string-table.h" #include "vmspawn-settings.h" -static const char *const image_format_table[_IMAGE_FORMAT_MAX] = { - [IMAGE_FORMAT_RAW] = "raw", - [IMAGE_FORMAT_QCOW2] = "qcow2", -}; - -DEFINE_STRING_TABLE_LOOKUP(image_format, ImageFormat); - -static const char *const disk_type_table[_DISK_TYPE_MAX] = { - [DISK_TYPE_VIRTIO_BLK] = "virtio-blk", - [DISK_TYPE_VIRTIO_SCSI] = "virtio-scsi", - [DISK_TYPE_NVME] = "nvme", - [DISK_TYPE_VIRTIO_SCSI_CDROM] = "scsi-cd", -}; - -DEFINE_STRING_TABLE_LOOKUP(disk_type, DiskType); - void extra_drive_context_done(ExtraDriveContext *ctx) { assert(ctx); diff --git a/src/vmspawn/vmspawn-settings.h b/src/vmspawn/vmspawn-settings.h index f02b499201ed8..596a66cecddb8 100644 --- a/src/vmspawn/vmspawn-settings.h +++ b/src/vmspawn/vmspawn-settings.h @@ -1,24 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "machine-util.h" #include "shared-forward.h" -typedef enum ImageFormat { - IMAGE_FORMAT_RAW, - IMAGE_FORMAT_QCOW2, - _IMAGE_FORMAT_MAX, - _IMAGE_FORMAT_INVALID = -EINVAL, -} ImageFormat; - -typedef enum DiskType { - DISK_TYPE_VIRTIO_BLK, - DISK_TYPE_VIRTIO_SCSI, - DISK_TYPE_NVME, - DISK_TYPE_VIRTIO_SCSI_CDROM, - _DISK_TYPE_MAX, - _DISK_TYPE_INVALID = -EINVAL, -} DiskType; - typedef struct ExtraDrive { char *path; ImageFormat format; @@ -69,6 +54,4 @@ typedef enum SettingsMask { DECLARE_STRING_TABLE_LOOKUP(console_mode, ConsoleMode); DECLARE_STRING_TABLE_LOOKUP(console_transport, ConsoleTransport); -DECLARE_STRING_TABLE_LOOKUP(disk_type, DiskType); DECLARE_STRING_TABLE_LOOKUP(firmware, Firmware); -DECLARE_STRING_TABLE_LOOKUP(image_format, ImageFormat); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 01325bf9eb1e7..1b696f7014480 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -752,39 +752,9 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("extra-drive", "[FORMAT:][DISKTYPE:]PATH", "Adds an additional disk to the VM"): { ImageFormat format = IMAGE_FORMAT_RAW; DiskType extra_disk_type = _DISK_TYPE_INVALID; - const char *dp = arg; - - /* Parse optional colon-separated prefixes. The format and disk type - * value sets don't overlap, so they can appear in any order. */ - for (;;) { - const char *colon = strchr(dp, ':'); - if (!colon) - break; - - _cleanup_free_ char *prefix = strndup(dp, colon - dp); - if (!prefix) - return log_oom(); - - ImageFormat f = image_format_from_string(prefix); - if (f >= 0) { - format = f; - dp = colon + 1; - continue; - } - - DiskType dt = disk_type_from_string(prefix); - if (dt >= 0) { - extra_disk_type = dt; - dp = colon + 1; - continue; - } - - /* Not a recognized prefix, treat the rest as the path */ - break; - } - _cleanup_free_ char *drive_path = NULL; - r = parse_path_argument(dp, /* suppress_root= */ false, &drive_path); + + r = parse_disk_spec(arg, &format, &extra_disk_type, &drive_path); if (r < 0) return r; From 74a07cb774cd9c8372a5e6868bdfdad9961dbee5 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 20 Apr 2026 11:07:38 +0200 Subject: [PATCH 1248/1296] vmspawn: use qemu_device_driver_to_string() in resolve_disk_driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the inline DiskType → QEMU device driver switch and call the shared helper instead. serial_max and the CD-ROM read-only flag stay inline since they are vmspawn-local. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 1b696f7014480..7ed21af77b16d 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -2273,7 +2273,6 @@ static int qemu_config_add_qmp_monitor(FILE *config_file, int bridge_fds[2], int } static int resolve_disk_driver(DiskType dt, const char *filename, DriveInfo *info) { - const char *driver; size_t serial_max; int r; @@ -2282,19 +2281,15 @@ static int resolve_disk_driver(DiskType dt, const char *filename, DriveInfo *inf switch (dt) { case DISK_TYPE_VIRTIO_BLK: - driver = "virtio-blk-pci"; serial_max = DISK_SERIAL_MAX_LEN_VIRTIO_BLK; break; case DISK_TYPE_VIRTIO_SCSI: - driver = "scsi-hd"; serial_max = DISK_SERIAL_MAX_LEN_SCSI; break; case DISK_TYPE_NVME: - driver = "nvme"; serial_max = DISK_SERIAL_MAX_LEN_NVME; break; case DISK_TYPE_VIRTIO_SCSI_CDROM: - driver = "scsi-cd"; serial_max = DISK_SERIAL_MAX_LEN_SCSI; info->flags |= QMP_DRIVE_READ_ONLY; break; @@ -2302,7 +2297,7 @@ static int resolve_disk_driver(DiskType dt, const char *filename, DriveInfo *inf assert_not_reached(); } - info->disk_driver = strdup(driver); + info->disk_driver = strdup(ASSERT_PTR(qemu_device_driver_to_string(dt))); if (!info->disk_driver) return log_oom(); From 4caa73563aca1c46d8da55b800a369d15e3e638c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 22 Apr 2026 00:17:21 +0200 Subject: [PATCH 1249/1296] qmp-client: widen next_fdset_id to uint64_t The fdset id is a monotonic counter; an unsigned int is more than wide enough today, but uint64_t matches the type of other QMP-internal counters (e.g. job ids) and avoids any worry about wraparound on long-running hosts. Update the storage in struct QmpClient, the qmp_client_next_fdset_id() return type, and the corresponding caller in qmp_fdset_add(), switching the sprintf format from %u to PRIu64. No behavioural change. Signed-off-by: Christian Brauner (Amutable) --- src/shared/qmp-client.c | 4 ++-- src/shared/qmp-client.h | 2 +- src/vmspawn/vmspawn-qmp.c | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/shared/qmp-client.c b/src/shared/qmp-client.c index ad8f8bb24ac5f..41b0c6dd57034 100644 --- a/src/shared/qmp-client.c +++ b/src/shared/qmp-client.c @@ -45,7 +45,7 @@ struct QmpClient { qmp_disconnect_callback_t disconnect_callback; void *disconnect_userdata; - unsigned next_fdset_id; /* monotonic fdset-id allocator for add-fd */ + uint64_t next_fdset_id; /* monotonic fdset-id allocator for add-fd */ QmpClientState state; sd_json_variant *current; /* most recently parsed message, pending dispatch */ @@ -893,7 +893,7 @@ sd_event* qmp_client_get_event(QmpClient *c) { return json_stream_get_event(&c->stream); } -unsigned qmp_client_next_fdset_id(QmpClient *c) { +uint64_t qmp_client_next_fdset_id(QmpClient *c) { assert(c); return c->next_fdset_id++; } diff --git a/src/shared/qmp-client.h b/src/shared/qmp-client.h index 8f784731280da..7dcd53355d06c 100644 --- a/src/shared/qmp-client.h +++ b/src/shared/qmp-client.h @@ -82,7 +82,7 @@ void qmp_client_bind_event(QmpClient *c, qmp_event_callback_t callback, void *us void qmp_client_bind_disconnect(QmpClient *c, qmp_disconnect_callback_t callback, void *userdata); int qmp_client_set_description(QmpClient *c, const char *description); sd_event* qmp_client_get_event(QmpClient *c); -unsigned qmp_client_next_fdset_id(QmpClient *client); +uint64_t qmp_client_next_fdset_id(QmpClient *client); DECLARE_TRIVIAL_REF_UNREF_FUNC(QmpClient, qmp_client); DEFINE_TRIVIAL_CLEANUP_FUNC(QmpClient *, qmp_client_unref); diff --git a/src/vmspawn/vmspawn-qmp.c b/src/vmspawn/vmspawn-qmp.c index d467d68a89636..6c1277f0829f5 100644 --- a/src/vmspawn/vmspawn-qmp.c +++ b/src/vmspawn/vmspawn-qmp.c @@ -128,7 +128,7 @@ static int qmp_fdset_add(QmpClient *qmp, int fd_consume, char **ret_path) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; _cleanup_close_ int fd = fd_consume; _cleanup_free_ char *path = NULL; - unsigned id; + uint64_t id; int r; assert(qmp); @@ -141,7 +141,7 @@ static int qmp_fdset_add(QmpClient *qmp, int fd_consume, char **ret_path) { if (r < 0) return r; - if (asprintf(&path, "/dev/fdset/%u", id) < 0) + if (asprintf(&path, "/dev/fdset/%" PRIu64, id) < 0) return -ENOMEM; r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "add-fd", QMP_CLIENT_ARGS_FD(args, TAKE_FD(fd)), From 6920dfc0f03f191ec12cfc482d78d9a5f05168d5 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 22 Apr 2026 00:18:27 +0200 Subject: [PATCH 1250/1296] vmspawn: move VMSPAWN_PCIE_HOTPLUG_SPARES to vmspawn-qmp.h Pure code motion, in preparation for the bridge-side hotplug machinery that needs the same constant to size its hotplug_port_owner[] array. The unsigned-suffix on the literal is dropped: the only consumer that compares against unsigned (vmspawn.c's pcie-port assert) is happy with a plain integer literal. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn-qmp.h | 2 ++ src/vmspawn/vmspawn.c | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vmspawn/vmspawn-qmp.h b/src/vmspawn/vmspawn-qmp.h index 35cc029ea763a..0bc73c90abc78 100644 --- a/src/vmspawn/vmspawn-qmp.h +++ b/src/vmspawn/vmspawn-qmp.h @@ -5,6 +5,8 @@ #include "shared-forward.h" +#define VMSPAWN_PCIE_HOTPLUG_SPARES 10 + /* Pending job continuation — called when a QMP background job reaches "concluded" state. * Used by blockdev-create to chain remaining drive setup after the job completes. */ typedef int (*pending_job_callback_t)(QmpClient *qmp, void *userdata); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 7ed21af77b16d..029ae4eacd263 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -102,9 +102,6 @@ #define DISK_SERIAL_MAX_LEN_NVME 20 #define DISK_SERIAL_MAX_LEN_VIRTIO_BLK 20 -/* Spare pcie-root-ports reserved for future runtime hotplug beyond the pre-wired devices. */ -#define VMSPAWN_PCIE_HOTPLUG_SPARES 10u - /* An enum controlling how auxiliary state for the VM are maintained, i.e. the TPM state and the EFI variable * NVRAM. */ typedef enum StateMode { From b34413db8e462a51feff98e427268a682ce9c7f7 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 22 Apr 2026 00:19:20 +0200 Subject: [PATCH 1251/1296] vmspawn-varlink: use error < 0 in async QMP completion callbacks The QMP client always passes either 0 or a negative errno; the != 0 check flagged values that cannot occur. Switch to the < 0 idiom used elsewhere in the tree, and reorder on_qmp_simple_complete so the error path is the first branch (the more conventional shape for callbacks). Equivalent in behaviour. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn-varlink.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vmspawn/vmspawn-varlink.c b/src/vmspawn/vmspawn-varlink.c index 98d3fc73e911c..a9b64667c52d0 100644 --- a/src/vmspawn/vmspawn-varlink.c +++ b/src/vmspawn/vmspawn-varlink.c @@ -51,10 +51,10 @@ static int on_qmp_simple_complete( assert(client); - if (error == 0) - (void) sd_varlink_reply(link, NULL); - else + if (error < 0) (void) qmp_error_to_varlink(link, error_desc, error); + else + (void) sd_varlink_reply(link, NULL); sd_varlink_unref(link); return 0; @@ -118,7 +118,7 @@ static int on_qmp_describe_complete( assert(client); - if (error != 0) { + if (error < 0) { (void) qmp_error_to_varlink(link, error_desc, error); return 0; } From 34d12197633924eeb2d43a6725abe70ce0ff5fb0 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 22 Apr 2026 00:19:48 +0200 Subject: [PATCH 1252/1296] vmspawn-varlink: simplify on_qmp_describe_complete result extraction Lift the running/status extraction out of the inline ternaries inside SD_JSON_BUILD_PAIR_*() into named local variables with explicit defaults. Pure readability change. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn-varlink.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/vmspawn/vmspawn-varlink.c b/src/vmspawn/vmspawn-varlink.c index a9b64667c52d0..94198923e01a6 100644 --- a/src/vmspawn/vmspawn-varlink.c +++ b/src/vmspawn/vmspawn-varlink.c @@ -123,13 +123,18 @@ static int on_qmp_describe_complete( return 0; } - sd_json_variant *running = sd_json_variant_by_key(result, "running"); - sd_json_variant *status = sd_json_variant_by_key(result, "status"); + sd_json_variant *running_v = sd_json_variant_by_key(result, "running"); + sd_json_variant *status_v = sd_json_variant_by_key(result, "status"); + + bool running = running_v ? sd_json_variant_boolean(running_v) : false; + + const char *status = status_v && sd_json_variant_is_string(status_v) ? + sd_json_variant_string(status_v) : "unknown"; (void) sd_varlink_replybo( link, - SD_JSON_BUILD_PAIR_BOOLEAN("running", running ? sd_json_variant_boolean(running) : false), - SD_JSON_BUILD_PAIR_STRING("status", status && sd_json_variant_is_string(status) ? sd_json_variant_string(status) : "unknown")); + SD_JSON_BUILD_PAIR_BOOLEAN("running", running), + SD_JSON_BUILD_PAIR_STRING("status", status)); return 0; } From 5b3a9f20a6992c6f048154a6d8ca3f1db0481eb8 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 22 Apr 2026 00:20:24 +0200 Subject: [PATCH 1253/1296] vmspawn-varlink: extract notify_event_subscribers from on_qmp_event Pure refactor: factor the subscriber-notification body out of on_qmp_event into a static helper. on_qmp_event keeps the JOB_STATUS_CHANGE short-circuit and otherwise delegates to the new helper. No behaviour change. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn-varlink.c | 38 +++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/vmspawn/vmspawn-varlink.c b/src/vmspawn/vmspawn-varlink.c index 94198923e01a6..d9beb5bfe3ff7 100644 --- a/src/vmspawn/vmspawn-varlink.c +++ b/src/vmspawn/vmspawn-varlink.c @@ -263,31 +263,21 @@ static int dispatch_pending_job(VmspawnQmpBridge *bridge, sd_json_variant *data) return 1; } -static int on_qmp_event( - QmpClient *client, - const char *event, - sd_json_variant *data, - void *userdata) { - - VmspawnVarlinkContext *ctx = ASSERT_PTR(userdata); +static int notify_event_subscribers(VmspawnVarlinkContext *ctx, const char *event_name, sd_json_variant *data) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *notification = NULL; sd_varlink *link; char **filter; int r; - assert(client); - assert(event); - - /* Dispatch job status changes to pending continuations (e.g. blockdev-create) */ - if (streq(event, "JOB_STATUS_CHANGE")) - return dispatch_pending_job(ctx->bridge, data); + assert(ctx); + assert(event_name); if (hashmap_isempty(ctx->subscribed)) return 0; r = sd_json_buildo( ¬ification, - SD_JSON_BUILD_PAIR_STRING("event", event), + SD_JSON_BUILD_PAIR_STRING("event", event_name), SD_JSON_BUILD_PAIR_CONDITION(!!data, "data", SD_JSON_BUILD_VARIANT(data))); if (r < 0) { log_warning_errno(r, "Failed to build event notification, ignoring: %m"); @@ -295,7 +285,7 @@ static int on_qmp_event( } HASHMAP_FOREACH_KEY(filter, link, ctx->subscribed) { - if (filter && !strv_contains(filter, event)) + if (filter && !strv_contains(filter, event_name)) continue; r = sd_varlink_notify(link, notification); @@ -306,6 +296,24 @@ static int on_qmp_event( return 0; } +static int on_qmp_event( + QmpClient *client, + const char *event, + sd_json_variant *data, + void *userdata) { + + VmspawnVarlinkContext *ctx = ASSERT_PTR(userdata); + + assert(client); + assert(event); + + /* Dispatch job status changes to pending continuations (e.g. blockdev-create) */ + if (streq(event, "JOB_STATUS_CHANGE")) + return dispatch_pending_job(ctx->bridge, data); + + return notify_event_subscribers(ctx, event, data); +} + /* Free all subscriber entries — varlink_subscriber_hash_ops handles * close + unref for each key and strv_free for each value. */ static void drain_event_subscribers(Hashmap **subscribed) { From 9ed1af2bd1638038033b774c17fd173dc089078a Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 22 Apr 2026 00:21:00 +0200 Subject: [PATCH 1254/1296] vmspawn-varlink: treat empty event subscription filter as catch-all MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A client supplying "filter": [] previously matched no events at all, because filter is checked with strv_contains() — an unintuitive corner case. Treat an empty filter strv identically to a NULL filter (deliver all events) by freeing the empty strv before it lands in the subscription map. Brings the API closer to least-surprise. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn-varlink.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vmspawn/vmspawn-varlink.c b/src/vmspawn/vmspawn-varlink.c index d9beb5bfe3ff7..8df234a9bba1e 100644 --- a/src/vmspawn/vmspawn-varlink.c +++ b/src/vmspawn/vmspawn-varlink.c @@ -159,6 +159,10 @@ static int vl_method_subscribe_events(sd_varlink *link, sd_json_variant *paramet if (r != 0) return r; + /* Treat [] identically to null: deliver all events. */ + if (strv_isempty(filter)) + filter = strv_free(filter); + sd_varlink_ref(link); r = hashmap_ensure_put(&ctx->subscribed, &varlink_subscriber_hash_ops, link, filter); From 06816dfee39d8419b9200448d8cc7656405a818f Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 22 Apr 2026 00:21:43 +0200 Subject: [PATCH 1255/1296] vmspawn-qmp: pass bridge to on_cont_complete via invoke userdata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The callback already has the bridge available — but it was reaching for it via qmp_client_get_userdata() instead of through its own userdata parameter. Pass the bridge directly from vmspawn_qmp_start() so the callback can read its argument the way the rest of the file does. No behaviour change. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn-qmp.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vmspawn/vmspawn-qmp.c b/src/vmspawn/vmspawn-qmp.c index 6c1277f0829f5..702e2e658e93b 100644 --- a/src/vmspawn/vmspawn-qmp.c +++ b/src/vmspawn/vmspawn-qmp.c @@ -1060,10 +1060,10 @@ static int on_cont_complete( int error, void *userdata) { - VmspawnQmpBridge *bridge = ASSERT_PTR(qmp_client_get_userdata(client)); - assert(client); + VmspawnQmpBridge *bridge = ASSERT_PTR(userdata); + if (error < 0) { log_error_errno(error, "Failed to resume QEMU execution: %s", strna(error_desc)); return sd_event_exit(qmp_client_get_event(client), error); @@ -1077,5 +1077,5 @@ static int on_cont_complete( int vmspawn_qmp_start(VmspawnQmpBridge *bridge) { assert(bridge); - return qmp_client_invoke(bridge->qmp, /* ret_slot= */ NULL, "cont", /* args= */ NULL, on_cont_complete, /* userdata= */ NULL); + return qmp_client_invoke(bridge->qmp, /* ret_slot= */ NULL, "cont", /* args= */ NULL, on_cont_complete, bridge); } From f0c1dc50ef0fcc38af438f445ceaed3cefb242d1 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 22 Apr 2026 00:23:02 +0200 Subject: [PATCH 1256/1296] vmspawn-qmp: convert DriveInfo to a refcounted object In preparation for runtime block-device hotplug, where in-flight QMP callbacks need to keep a slot reference on the DriveInfo while the bridge also holds it in its block-device registry. Today each DriveInfo has exactly one owner; switch the API from drive_info_free() / drive_info_freep to drive_info_ref() / drive_info_unref() / drive_info_unrefp so future code can take additional refs without the caller losing track of ownership. drive_info_new() initialises n_ref to 1 (one ref for the caller). The existing drive_infos_done() and the prepare_*_drive() callers in vmspawn.c are switched to the unref form. No behaviour change: each DriveInfo still has exactly one ref at every point in this commit. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn-qmp.c | 10 ++++++---- src/vmspawn/vmspawn-qmp.h | 5 +++-- src/vmspawn/vmspawn.c | 4 ++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/vmspawn/vmspawn-qmp.c b/src/vmspawn/vmspawn-qmp.c index 702e2e658e93b..23ae4f73f82d7 100644 --- a/src/vmspawn/vmspawn-qmp.c +++ b/src/vmspawn/vmspawn-qmp.c @@ -31,15 +31,15 @@ DriveInfo* drive_info_new(void) { return NULL; *d = (DriveInfo) { + .n_ref = 1, .fd = -EBADF, .overlay_fd = -EBADF, }; return d; } -DriveInfo* drive_info_free(DriveInfo *d) { - if (!d) - return NULL; +static DriveInfo* drive_info_free(DriveInfo *d) { + assert(d); free(d->path); free(d->format); @@ -52,10 +52,12 @@ DriveInfo* drive_info_free(DriveInfo *d) { return mfree(d); } +DEFINE_TRIVIAL_REF_UNREF_FUNC(DriveInfo, drive_info, drive_info_free); + void drive_infos_done(DriveInfos *infos) { assert(infos); FOREACH_ARRAY(d, infos->drives, infos->n_drives) - drive_info_free(*d); + drive_info_unref(*d); infos->drives = mfree(infos->drives); infos->n_drives = 0; infos->scsi_pcie_port = mfree(infos->scsi_pcie_port); diff --git a/src/vmspawn/vmspawn-qmp.h b/src/vmspawn/vmspawn-qmp.h index 0bc73c90abc78..391ac2d5bda4a 100644 --- a/src/vmspawn/vmspawn-qmp.h +++ b/src/vmspawn/vmspawn-qmp.h @@ -71,6 +71,7 @@ typedef enum QmpDriveFlags { * Each DriveInfo is individually heap-allocated so it can be handed off * to the block device registry via TAKE_PTR. */ typedef struct DriveInfo { + unsigned n_ref; char *path; /* original path (for logging; not passed to QEMU) */ char *format; /* "raw" or "qcow2" */ char *disk_driver; /* "virtio-blk-pci", "scsi-hd", "scsi-cd", "nvme" */ @@ -83,8 +84,8 @@ typedef struct DriveInfo { } DriveInfo; DriveInfo* drive_info_new(void); -DriveInfo* drive_info_free(DriveInfo *d); -DEFINE_TRIVIAL_CLEANUP_FUNC(DriveInfo *, drive_info_free); +DECLARE_TRIVIAL_REF_UNREF_FUNC(DriveInfo, drive_info); +DEFINE_TRIVIAL_CLEANUP_FUNC(DriveInfo *, drive_info_unref); typedef struct DriveInfos { DriveInfo **drives; /* array of individually heap-allocated entries */ diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 029ae4eacd263..e927812fe7a77 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -2319,7 +2319,7 @@ static int prepare_primary_drive(const char *runtime_dir, DriveInfos *drives) { if (r < 0) return log_error_errno(r, "Failed to extract filename from path '%s': %m", arg_image); - _cleanup_(drive_info_freep) DriveInfo *d = drive_info_new(); + _cleanup_(drive_info_unrefp) DriveInfo *d = drive_info_new(); if (!d) return log_oom(); @@ -2389,7 +2389,7 @@ static int prepare_extra_drives(DriveInfos *drives) { DiskType dt = drive->disk_type >= 0 ? drive->disk_type : arg_image_disk_type; - _cleanup_(drive_info_freep) DriveInfo *d = drive_info_new(); + _cleanup_(drive_info_unrefp) DriveInfo *d = drive_info_new(); if (!d) return log_oom(); From a57fbf4e6cf538855b5c17c8ce74e42a6acf65eb Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 22 Apr 2026 00:26:52 +0200 Subject: [PATCH 1257/1296] vmspawn-qmp: derive QMP node and device ids from a bridge counter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the caller-supplied DriveInfo.node_name with two QMP-internal strings generated at setup time from a monotonic per-bridge counter: qmp_node_name = "vmspawn--storage" (blockdev node-name) qmp_device_id = "vmspawn--disk" (qdev id) This is the naming scheme the upcoming runtime-hotplug add path needs: unique across the lifetime of the VM, decoupled from any user-visible id, and stable across the four QMP commands that make up an add (add-fd, blockdev-add, remove-fd, device_add). The boot-time setups don't care about uniqueness, but switching them now means the hotplug path can share qmp_build_device_add() and EphemeralDriveCtx without a parallel naming scheme. vmspawn.c stops assigning node_name in prepare_primary_drive (which used the literal "vmspawn") and prepare_extra_drives (which counted "vmspawn_extra_%zu"); both are replaced by the bridge counter at the point the drive is actually pushed into QEMU. Ephemeral helper-node names follow the same vmspawn--{base-file,base-fmt,overlay-file} convention; the blockdev-create job-id becomes vmspawn--overlay-create. EphemeralDriveCtx grows a qmp_device_id field (the qdev id is independent of the format node-name now) and renames node_name → qmp_node_name to match. No external behaviour change other than the new internal names. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn-qmp.c | 88 +++++++++++++++++++++------------------ src/vmspawn/vmspawn-qmp.h | 6 ++- src/vmspawn/vmspawn.c | 7 +--- 3 files changed, 53 insertions(+), 48 deletions(-) diff --git a/src/vmspawn/vmspawn-qmp.c b/src/vmspawn/vmspawn-qmp.c index 23ae4f73f82d7..bb7c9e06a3b38 100644 --- a/src/vmspawn/vmspawn-qmp.c +++ b/src/vmspawn/vmspawn-qmp.c @@ -45,7 +45,8 @@ static DriveInfo* drive_info_free(DriveInfo *d) { free(d->format); free(d->disk_driver); free(d->serial); - free(d->node_name); + free(d->qmp_node_name); + free(d->qmp_device_id); free(d->pcie_port); safe_close(d->fd); safe_close(d->overlay_fd); @@ -213,16 +214,17 @@ static int qmp_build_blockdev_add_format(const QmpFormatNodeParams *p, sd_json_v SD_JSON_BUILD_PAIR_CONDITION(!!p->backing, "backing", SD_JSON_BUILD_STRING(p->backing))); } -/* Build device_add JSON arguments for a drive */ static int qmp_build_device_add(const DriveInfo *drive, sd_json_variant **ret) { assert(drive); + assert(drive->qmp_node_name); + assert(drive->qmp_device_id); assert(ret); return sd_json_buildo( ret, SD_JSON_BUILD_PAIR_STRING("driver", drive->disk_driver), - SD_JSON_BUILD_PAIR_STRING("drive", drive->node_name), - SD_JSON_BUILD_PAIR_STRING("id", drive->node_name), + SD_JSON_BUILD_PAIR_STRING("drive", drive->qmp_node_name), + SD_JSON_BUILD_PAIR_STRING("id", drive->qmp_device_id), SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(drive->flags, QMP_DRIVE_BOOT), "bootindex", SD_JSON_BUILD_INTEGER(1)), SD_JSON_BUILD_PAIR_CONDITION(!!drive->serial, "serial", SD_JSON_BUILD_STRING(drive->serial)), SD_JSON_BUILD_PAIR_CONDITION(STR_IN_SET(drive->disk_driver, "scsi-hd", "scsi-cd"), @@ -292,23 +294,23 @@ static int get_image_virtual_size(int fd, const char *format, bool is_block_devi return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported image format '%s'", format); } -/* Ephemeral drive continuation — fired when the blockdev-create job concludes. - * Completes the drive setup by adding the overlay format node and the device. */ +/* Continuation state for on_ephemeral_create_concluded: overlay format + device_add. */ typedef struct EphemeralDriveCtx { - char *node_name; /* overlay format node name (= drive node name) */ + char *qmp_node_name; /* overlay format node name, "vmspawn--storage" */ + char *qmp_device_id; /* qdev id, "vmspawn--disk" */ char *overlay_file_node; char *base_fmt_node; - /* Fields for device_add */ char *disk_driver; - char *serial; /* NULL if unset */ - char *pcie_port; /* pcie-root-port bus for device_add (NULL on non-PCIe) */ - QmpDriveFlags flags; /* subset: QMP_DRIVE_DISCARD, QMP_DRIVE_DISCARD_NO_UNREF, QMP_DRIVE_BOOT */ + char *serial; + char *pcie_port; /* NULL on non-PCIe */ + QmpDriveFlags flags; /* subset forwarded to device_add */ } EphemeralDriveCtx; static EphemeralDriveCtx* ephemeral_drive_ctx_free(EphemeralDriveCtx *ctx) { if (!ctx) return NULL; - free(ctx->node_name); + free(ctx->qmp_node_name); + free(ctx->qmp_device_id); free(ctx->overlay_file_node); free(ctx->base_fmt_node); free(ctx->disk_driver); @@ -330,7 +332,7 @@ static int on_ephemeral_create_concluded(QmpClient *qmp, void *userdata) { /* Open formatted overlay as qcow2 with backing reference */ QmpFormatNodeParams overlay_fmt_params = { - .node_name = ctx->node_name, + .node_name = ctx->qmp_node_name, .format = "qcow2", .file_node_name = ctx->overlay_file_node, .backing = ctx->base_fmt_node, @@ -338,32 +340,32 @@ static int on_ephemeral_create_concluded(QmpClient *qmp, void *userdata) { }; r = qmp_build_blockdev_add_format(&overlay_fmt_params, &fmt_args); if (r < 0) - return log_error_errno(r, "Failed to build overlay format JSON for '%s': %m", ctx->node_name); + return log_error_errno(r, "Failed to build overlay format JSON for '%s': %m", ctx->qmp_node_name); r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(fmt_args), on_qmp_complete, (void*) "blockdev-add"); if (r < 0) return r; - /* device_add: attach to virtual hardware. Build a temporary DriveInfo as a - * read-only view into the continuation context to reuse qmp_build_device_add(). */ + /* Temporary DriveInfo view so we can reuse qmp_build_device_add(). */ const DriveInfo tmp = { - .disk_driver = ctx->disk_driver, - .node_name = ctx->node_name, - .serial = ctx->serial, - .pcie_port = ctx->pcie_port, - .flags = ctx->flags & QMP_DRIVE_BOOT, - .fd = -EBADF, - .overlay_fd = -EBADF, + .disk_driver = ctx->disk_driver, + .serial = ctx->serial, + .pcie_port = ctx->pcie_port, + .flags = ctx->flags & QMP_DRIVE_BOOT, + .fd = -EBADF, + .overlay_fd = -EBADF, + .qmp_node_name = ctx->qmp_node_name, + .qmp_device_id = ctx->qmp_device_id, }; r = qmp_build_device_add(&tmp, &device_args); if (r < 0) - return log_error_errno(r, "Failed to build device_add JSON for '%s': %m", ctx->node_name); + return log_error_errno(r, "Failed to build device_add JSON for '%s': %m", ctx->qmp_device_id); r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_complete, (void*) "device_add"); if (r < 0) return r; - log_debug("Queued ephemeral drive completion for '%s'", ctx->node_name); + log_debug("Queued ephemeral drive completion for '%s'", ctx->qmp_device_id); return 0; } @@ -379,11 +381,14 @@ static int qmp_setup_ephemeral_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, D assert(drive->fd >= 0); assert(drive->overlay_fd >= 0); - /* Node names: -base-file, -base-fmt, -overlay-file, */ - _cleanup_free_ char *base_file_node = strjoin(drive->node_name, "-base-file"); - _cleanup_free_ char *base_fmt_node = strjoin(drive->node_name, "-base-fmt"); - _cleanup_free_ char *overlay_file_node = strjoin(drive->node_name, "-overlay-file"); - if (!base_file_node || !base_fmt_node || !overlay_file_node) + uint64_t counter = bridge->next_block_counter++; + + _cleanup_free_ char *base_file_node = NULL, *base_fmt_node = NULL, *overlay_file_node = NULL; + if (asprintf(&drive->qmp_node_name, "vmspawn-%" PRIu64 "-storage", counter) < 0 || + asprintf(&drive->qmp_device_id, "vmspawn-%" PRIu64 "-disk", counter) < 0 || + asprintf(&base_file_node, "vmspawn-%" PRIu64 "-base-file", counter) < 0 || + asprintf(&base_fmt_node, "vmspawn-%" PRIu64 "-base-fmt", counter) < 0 || + asprintf(&overlay_file_node, "vmspawn-%" PRIu64 "-overlay-file", counter) < 0) return log_oom(); /* Read virtual size before passing the fd to QEMU (TAKE_FD consumes it) */ @@ -459,8 +464,8 @@ static int qmp_setup_ephemeral_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, D if (r < 0) return log_error_errno(r, "Failed to build blockdev-create options: %m"); - _cleanup_free_ char *job_id = strjoin("create-", drive->node_name); - if (!job_id) + _cleanup_free_ char *job_id = NULL; + if (asprintf(&job_id, "vmspawn-%" PRIu64 "-overlay-create", counter) < 0) return log_oom(); _cleanup_(sd_json_variant_unrefp) sd_json_variant *cmd_args = NULL; @@ -481,7 +486,8 @@ static int qmp_setup_ephemeral_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, D ectx_flags |= QMP_DRIVE_DISCARD_NO_UNREF; *ectx = (EphemeralDriveCtx) { - .node_name = strdup(drive->node_name), + .qmp_node_name = strdup(drive->qmp_node_name), + .qmp_device_id = strdup(drive->qmp_device_id), .overlay_file_node = strdup(overlay_file_node), .base_fmt_node = strdup(base_fmt_node), .disk_driver = strdup(drive->disk_driver), @@ -489,9 +495,9 @@ static int qmp_setup_ephemeral_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, D .pcie_port = drive->pcie_port ? strdup(drive->pcie_port) : NULL, .flags = ectx_flags, }; - if (!ectx->node_name || !ectx->overlay_file_node || !ectx->base_fmt_node || - !ectx->disk_driver || (drive->serial && !ectx->serial) || - (drive->pcie_port && !ectx->pcie_port)) + if (!ectx->qmp_node_name || !ectx->qmp_device_id || !ectx->overlay_file_node || + !ectx->base_fmt_node || !ectx->disk_driver || + (drive->serial && !ectx->serial) || (drive->pcie_port && !ectx->pcie_port)) return log_oom(); r = vmspawn_qmp_bridge_register_job(bridge, job_id, @@ -519,9 +525,11 @@ static int qmp_setup_regular_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, Dri assert(drive); assert(drive->fd >= 0); - /* Node names: -file, */ - _cleanup_free_ char *file_node_name = strjoin(drive->node_name, "-file"); - if (!file_node_name) + uint64_t counter = bridge->next_block_counter++; + _cleanup_free_ char *file_node_name = NULL; + if (asprintf(&drive->qmp_node_name, "vmspawn-%" PRIu64 "-storage", counter) < 0 || + asprintf(&drive->qmp_device_id, "vmspawn-%" PRIu64 "-disk", counter) < 0 || + asprintf(&file_node_name, "vmspawn-%" PRIu64 "-file", counter) < 0) return log_oom(); _cleanup_free_ char *fdset_path = NULL; @@ -542,7 +550,7 @@ static int qmp_setup_regular_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, Dri return log_error_errno(r, "Failed to send blockdev-add for '%s': %m", drive->path); QmpFormatNodeParams fmt_params = { - .node_name = drive->node_name, + .node_name = drive->qmp_node_name, .format = drive->format, .file_node_name = file_node_name, .flags = drive->flags & (QMP_DRIVE_READ_ONLY|QMP_DRIVE_DISCARD), diff --git a/src/vmspawn/vmspawn-qmp.h b/src/vmspawn/vmspawn-qmp.h index 391ac2d5bda4a..a58dfae3beb63 100644 --- a/src/vmspawn/vmspawn-qmp.h +++ b/src/vmspawn/vmspawn-qmp.h @@ -28,7 +28,8 @@ typedef enum VmspawnQmpFeatureFlags { typedef struct VmspawnQmpBridge { QmpClient *qmp; - Hashmap *pending_jobs; /* job_id (string, owned) -> PendingJob* */ + Hashmap *pending_jobs; /* blockdev-create continuations */ + uint64_t next_block_counter; /* monotonic counter feeding internal QMP names (vmspawn--*) */ VmspawnQmpFeatureFlags features; bool setup_done; } VmspawnQmpBridge; @@ -76,7 +77,8 @@ typedef struct DriveInfo { char *format; /* "raw" or "qcow2" */ char *disk_driver; /* "virtio-blk-pci", "scsi-hd", "scsi-cd", "nvme" */ char *serial; - char *node_name; + char *qmp_node_name; /* "vmspawn--storage" */ + char *qmp_device_id; /* "vmspawn--disk" */ char *pcie_port; /* pcie-root-port id for device_add bus (NULL on non-PCIe) */ int fd; /* pre-opened image fd (-EBADF if unused) */ int overlay_fd; /* pre-opened anonymous overlay fd for ephemeral (-EBADF if unused) */ diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index e927812fe7a77..3f99d0547cdb7 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -2343,8 +2343,7 @@ static int prepare_primary_drive(const char *runtime_dir, DriveInfos *drives) { d->path = strdup(arg_image); d->format = strdup(ASSERT_PTR(image_format_to_string(arg_image_format))); - d->node_name = strdup("vmspawn"); - if (!d->path || !d->format || !d->node_name) + if (!d->path || !d->format) return log_oom(); d->fd = TAKE_FD(image_fd); if (S_ISBLK(st.st_mode)) @@ -2380,7 +2379,6 @@ static int prepare_extra_drives(DriveInfos *drives) { assert(drives); - size_t extra_idx = 0; FOREACH_ARRAY(drive, arg_extra_drives.drives, arg_extra_drives.n_drives) { _cleanup_free_ char *drive_fn = NULL; r = path_extract_filename(drive->path, &drive_fn); @@ -2421,9 +2419,6 @@ static int prepare_extra_drives(DriveInfos *drives) { d->flags |= QMP_DRIVE_BLOCK_DEVICE; d->flags |= QMP_DRIVE_NO_FLUSH; - if (asprintf(&d->node_name, "vmspawn_extra_%zu", extra_idx++) < 0) - return log_oom(); - drives->drives[drives->n_drives++] = TAKE_PTR(d); } From b75b6041e014c721132258af85727fcabd383c58 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 22 Apr 2026 00:28:51 +0200 Subject: [PATCH 1258/1296] vmspawn-qmp: pipeline remove-fd after each blockdev-add QEMU keeps a monitor-side fd alive until either an explicit remove-fd arrives or the fdset's last duplicate is closed. Today vmspawn issues add-fd but never the matching remove-fd, so each fdset stays around for the lifetime of the VM even after the consuming blockdev is torn down. Pipelining a remove-fd directly after the blockdev-add that consumed the fd hands ownership entirely to the blockdev: the fdset auto-disposes when raw_close runs at blockdev-del time. This is the shape needed by hotplug, where blockdev-del must clean up everything without further coordination. Mechanically: - qmp_fdset_add() takes a callback/userdata pair (so callers control failure handling) and an optional out-param for the numeric fdset id. All boot-time callers keep using on_qmp_complete with a label. - A new qmp_fdset_remove() helper sends remove-fd with caller-supplied callback/userdata. - qmp_setup_ephemeral_drive captures both fdset ids and fires remove-fd immediately after each base/overlay file blockdev-add. - qmp_setup_regular_drive does the same for its single file blockdev-add. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn-qmp.c | 69 +++++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/src/vmspawn/vmspawn-qmp.c b/src/vmspawn/vmspawn-qmp.c index bb7c9e06a3b38..cfa887e1223a3 100644 --- a/src/vmspawn/vmspawn-qmp.c +++ b/src/vmspawn/vmspawn-qmp.c @@ -125,9 +125,15 @@ static int on_qmp_complete( return 0; } -/* Send add-fd via SCM_RIGHTS; return /dev/fdset/N. Allocations run before invoke so a late - * OOM cannot orphan an fdset on QEMU's side; *ret_path is only written on full success. */ -static int qmp_fdset_add(QmpClient *qmp, int fd_consume, char **ret_path) { +/* Send add-fd via SCM_RIGHTS; return /dev/fdset/N and the numeric fdset id. */ +static int qmp_fdset_add( + QmpClient *qmp, + int fd_consume, + qmp_command_callback_t callback, + void *userdata, + char **ret_path, + uint64_t *ret_fdset_id) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; _cleanup_close_ int fd = fd_consume; _cleanup_free_ char *path = NULL; @@ -136,6 +142,7 @@ static int qmp_fdset_add(QmpClient *qmp, int fd_consume, char **ret_path) { assert(qmp); assert(fd_consume >= 0); + assert(callback); assert(ret_path); id = qmp_client_next_fdset_id(qmp); @@ -148,14 +155,39 @@ static int qmp_fdset_add(QmpClient *qmp, int fd_consume, char **ret_path) { return -ENOMEM; r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "add-fd", QMP_CLIENT_ARGS_FD(args, TAKE_FD(fd)), - on_qmp_complete, (void*) "add-fd"); + callback, userdata); if (r < 0) return r; *ret_path = TAKE_PTR(path); + if (ret_fdset_id) + *ret_fdset_id = id; return 0; } +/* Issue remove-fd for an fdset whose dup is now held by a blockdev. The fdset + * persists until the dup is closed (in raw_close at blockdev-del time) — see + * QEMU's monitor/fds.c:177-181 on the fds/dup_fds split. */ +static int qmp_fdset_remove( + QmpClient *qmp, + uint64_t fdset_id, + qmp_command_callback_t callback, + void *userdata) { + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + int r; + + assert(qmp); + assert(callback); + + r = sd_json_buildo(&args, SD_JSON_BUILD_PAIR_UNSIGNED("fdset-id", fdset_id)); + if (r < 0) + return r; + + return qmp_client_invoke(qmp, /* ret_slot= */ NULL, "remove-fd", QMP_CLIENT_ARGS(args), + callback, userdata); +} + typedef struct QmpFileNodeParams { const char *node_name; const char *filename; @@ -399,12 +431,16 @@ static int qmp_setup_ephemeral_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, D /* Step 1-2: Pass both fds to QEMU */ _cleanup_free_ char *base_path = NULL; - r = qmp_fdset_add(qmp, TAKE_FD(drive->fd), &base_path); + uint64_t base_fdset_id; + r = qmp_fdset_add(qmp, TAKE_FD(drive->fd), + on_qmp_complete, (void*) "add-fd", &base_path, &base_fdset_id); if (r < 0) return log_error_errno(r, "Failed to send add-fd for base image '%s': %m", drive->path); _cleanup_free_ char *overlay_path = NULL; - r = qmp_fdset_add(qmp, TAKE_FD(drive->overlay_fd), &overlay_path); + uint64_t overlay_fdset_id; + r = qmp_fdset_add(qmp, TAKE_FD(drive->overlay_fd), + on_qmp_complete, (void*) "add-fd", &overlay_path, &overlay_fdset_id); if (r < 0) return log_error_errno(r, "Failed to send add-fd for overlay of '%s': %m", drive->path); @@ -421,6 +457,12 @@ static int qmp_setup_ephemeral_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, D if (r < 0) return log_error_errno(r, "Failed to send blockdev-add for base file '%s': %m", drive->path); + /* The base file node now holds a dup of the fd; release the monitor's + * original so the fdset auto-frees when raw_close runs at teardown. */ + r = qmp_fdset_remove(qmp, base_fdset_id, on_qmp_complete, (void*) "remove-fd"); + if (r < 0) + return log_error_errno(r, "Failed to send remove-fd for base image '%s': %m", drive->path); + /* Step 4: Base image format node (read-only) */ QmpFormatNodeParams base_fmt_params = { .node_name = base_fmt_node, @@ -453,6 +495,11 @@ static int qmp_setup_ephemeral_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, D if (r < 0) return log_error_errno(r, "Failed to send blockdev-add for overlay file '%s': %m", drive->path); + /* Same as for base: the overlay file node has the dup. */ + r = qmp_fdset_remove(qmp, overlay_fdset_id, on_qmp_complete, (void*) "remove-fd"); + if (r < 0) + return log_error_errno(r, "Failed to send remove-fd for overlay of '%s': %m", drive->path); + /* Step 6: Fire blockdev-create to format the overlay */ _cleanup_(sd_json_variant_unrefp) sd_json_variant *create_options = NULL; r = sd_json_buildo(&create_options, @@ -533,7 +580,9 @@ static int qmp_setup_regular_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, Dri return log_oom(); _cleanup_free_ char *fdset_path = NULL; - r = qmp_fdset_add(qmp, TAKE_FD(drive->fd), &fdset_path); + uint64_t fdset_id; + r = qmp_fdset_add(qmp, TAKE_FD(drive->fd), + on_qmp_complete, (void*) "add-fd", &fdset_path, &fdset_id); if (r < 0) return log_error_errno(r, "Failed to send add-fd for '%s': %m", drive->path); @@ -549,6 +598,12 @@ static int qmp_setup_regular_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, Dri if (r < 0) return log_error_errno(r, "Failed to send blockdev-add for '%s': %m", drive->path); + /* The file node now holds a dup of the fd; release the monitor's + * original so the fdset auto-frees when raw_close runs at teardown. */ + r = qmp_fdset_remove(qmp, fdset_id, on_qmp_complete, (void*) "remove-fd"); + if (r < 0) + return log_error_errno(r, "Failed to send remove-fd for '%s': %m", drive->path); + QmpFormatNodeParams fmt_params = { .node_name = drive->qmp_node_name, .format = drive->format, From 1dbfab132ae10fed5c91c6e09a3c3450e01ef261 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 22 Apr 2026 00:29:37 +0200 Subject: [PATCH 1259/1296] vmspawn-qmp: keep the event loop running on post-setup QMP failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit on_qmp_complete tears the event loop down on any QMP error, which is the right behaviour while we're still building the VM (a missing device means we'd boot a half-configured guest). Once the boot-time setup is finished and the guest is running, killing the event loop on a QMP error means a single failed runtime command (e.g. a hotplug device_add that the guest rejects) takes the whole VM down. Consult bridge->setup_done — already flipped at the end of boot setup — and skip the event-loop exit once it's set; logging is sufficient post-setup. The bridge is fetched from the qmp client userdata, the same pointer the rest of the file uses. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn-qmp.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/vmspawn/vmspawn-qmp.c b/src/vmspawn/vmspawn-qmp.c index cfa887e1223a3..7eff45f5e933f 100644 --- a/src/vmspawn/vmspawn-qmp.c +++ b/src/vmspawn/vmspawn-qmp.c @@ -103,9 +103,7 @@ void machine_config_done(MachineConfig *c) { vsock_info_done(&c->vsock); } -/* Generic async QMP setup-completion callback. The userdata argument carries the - * command name (as a string literal) for logging. On failure, request a clean - * event loop exit so vmspawn shuts down instead of running a VM with missing devices. */ +/* Generic completion callback; userdata is a string literal label. Exits the event loop on boot-time failures. */ static int on_qmp_complete( QmpClient *client, sd_json_variant *result, @@ -113,12 +111,17 @@ static int on_qmp_complete( int error, void *userdata) { - const char *label = ASSERT_PTR(userdata); - assert(client); + VmspawnQmpBridge *bridge = ASSERT_PTR(qmp_client_get_userdata(client)); + const char *label = ASSERT_PTR(userdata); + if (error < 0) { log_error_errno(error, "%s failed: %s", label, strna(error_desc)); + + if (bridge->setup_done) + return 0; + return sd_event_exit(qmp_client_get_event(client), error); } From 1d0a8e5dbd267c803e100d9030d70d327eddf8b1 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 22 Apr 2026 00:34:50 +0200 Subject: [PATCH 1260/1296] vmspawn-qmp: add the hotplug-capable block-device add machinery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is the bulk of the runtime block-device hotplug feature. The boot-time qmp_setup_regular_drive() path is rewritten on top of a new vmspawn_qmp_add_block_device() that owns the DriveInfo, drives a four QMP-command pipeline (add-fd → blockdev-add → remove-fd → device_add) through staged ref-counted callbacks, and registers the drive in a per-bridge block-device registry on success. New on the bridge: - block_devices: user-id → DriveInfo* registry (owned ref). - hotplug_port_owner[VMSPAWN_PCIE_HOTPLUG_SPARES]: per-port owner string for the spare pcie-root-ports, allocated/released through vmspawn_qmp_bridge_{allocate,release_pcie_port_by_idx}(). - scsi_controller_port_idx / scsi_controller_created: track the on-demand virtio-scsi-pci controller so the first SCSI hotplug creates it (against an allocated spare port) and subsequent ones just attach. - next_block_counter: already in place from the previous commit, now actually consumed by add_block_device(). New on DriveInfo: - bridge (weak), id (varlink-visible — caller-supplied or auto-set to qmp_device_id), disk_type (for List replies later), counter, qmp_node_name / qmp_device_id (already added), fdset_path, pcie_port_idx (the hotplug port reserved by this drive — stays set across the add pipeline so drive_info_free releases it when the registry ref drops at DEVICE_DELETED time), rollback_mask (BlockDeviceAddStage bits of completed stages — plus a FAILED sentinel that suppresses cascading errors), and link (NULL ⇒ boot-time, sd_event_exit on fail; non-NULL ⇒ hotplug, varlink reply on fail). Helpers: - drive_info_unref() now releases any reserved hotplug port through the bridge and unrefs link. - drive_info_add_fail(): single failure entry point — fires the teardown for completed stages, sets the FAILED bit, and sends either the varlink error or sd_event_exit. Boot-time failures (link == NULL) always exit the loop, so late-arriving ephemeral continuation replies don't get silenced after cont. - vmspawn_qmp_block_device_teardown(): post-hoc blockdev-del when blockdev-add succeeded but a later stage failed. - reply_qmp_error: varlink reply helper — disconnect errors map to io.systemd.MachineInstance.NotConnected, everything else goes through sd_varlink_error_errno. - on_add_observe_stage / on_add_blockdev_stage / on_add_device_add_complete: the staged callbacks that drive the add pipeline, each holding one slot ref on the DriveInfo. The ephemeral blockdev-create continuation reuses the latter two so its post-cont replies go through drive_info_add_fail instead of the generic on_qmp_complete (which would silently log under setup_done). - on_scsi_controller_complete: handles the SCSI controller setup (releases the reserved port on failure, propagates the boot-time fatal error policy). - qmp_build_blockdev_add_inline(): single blockdev-add JSON that creates the file+format pair as one node, used by the hotplug path (the boot-time helpers stay separate so they can stack with the ephemeral path's base/overlay format nodes). EphemeralDriveCtx is trimmed down to a DriveInfo ref plus the two ephemeral-local scratch node names (overlay-file, base-fmt). The copies of disk_driver/serial/pcie_port/flags/qmp_node_name/ qmp_device_id go away — the continuation reads them straight off the ref'd drive. qmp_setup_ephemeral_drive now sets drive->bridge / drive->id / drive->counter up front (matching the hotplug path) and folds the feature-dependent DISCARD_NO_UNREF into drive->flags so qmp_build_blockdev_add_format picks it up. vmspawn_qmp_bridge_free() unrefs the qmp client first — its pending callbacks may still reach for the bridge's hotplug port table when they drop their last DriveInfo ref — then tears down the hashmaps and the port owner strings. The boot-time qmp_setup_regular_drive() collapses to a thin wrapper that asserts "caller hasn't pre-set drive->id", takes ownership and calls vmspawn_qmp_add_block_device(); the dispatcher in vmspawn_qmp_setup_drives() now hands ownership over with TAKE_PTR. The previous qmp_setup_drive() dispatcher disappears (its body is inlined into the loop). vmspawn_qmp_init() initialises scsi_controller_port_idx to -1. The cosmetic (*d) → DriveInfo *drive = *d; locals in drives_need_scsi_controller (vmspawn-qmp.c) and assign_pcie_ports (vmspawn.c) ride along — they live in code touched by this commit and would otherwise produce churn. vmspawn_qmp_remove_block_device() — the symmetric remove API — is added in the next commit so this one stays focused on the add path. Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn-qmp.c | 613 +++++++++++++++++++++++++++++--------- src/vmspawn/vmspawn-qmp.h | 39 ++- src/vmspawn/vmspawn.c | 14 +- 3 files changed, 504 insertions(+), 162 deletions(-) diff --git a/src/vmspawn/vmspawn-qmp.c b/src/vmspawn/vmspawn-qmp.c index 7eff45f5e933f..9158d5b10a99b 100644 --- a/src/vmspawn/vmspawn-qmp.c +++ b/src/vmspawn/vmspawn-qmp.c @@ -6,9 +6,11 @@ #include "sd-event.h" #include "sd-json.h" +#include "sd-varlink.h" #include "alloc-util.h" #include "blockdev-util.h" +#include "errno-util.h" #include "ether-addr-util.h" #include "fd-util.h" #include "hashmap.h" @@ -19,12 +21,18 @@ #include "string-util.h" #include "strv.h" #include "vmspawn-qmp.h" +#include "vmspawn-util.h" DEFINE_PRIVATE_HASH_OPS_FULL( pending_job_hash_ops, char, string_hash_func, string_compare_func, free, PendingJob, pending_job_free); +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + block_devices_hash_ops, + char, string_hash_func, string_compare_func, + DriveInfo, drive_info_unref); + DriveInfo* drive_info_new(void) { DriveInfo *d = new(DriveInfo, 1); if (!d) @@ -34,20 +42,68 @@ DriveInfo* drive_info_new(void) { .n_ref = 1, .fd = -EBADF, .overlay_fd = -EBADF, + .pcie_port_idx = -1, }; return d; } +static int vmspawn_qmp_bridge_allocate_pcie_port( + VmspawnQmpBridge *bridge, + const char *owner_id, + char **ret_name, + int *ret_idx) { + + assert(bridge); + assert(owner_id); + assert(ret_name); + assert(ret_idx); + + for (int i = 0; i < VMSPAWN_PCIE_HOTPLUG_SPARES; i++) { + if (bridge->hotplug_port_owner[i]) + continue; + + _cleanup_free_ char *owner = strdup(owner_id), *name = NULL; + if (!owner || asprintf(&name, "vmspawn-hotplug-pci-root-port-%d", i) < 0) + return -ENOMEM; + + bridge->hotplug_port_owner[i] = TAKE_PTR(owner); + *ret_name = TAKE_PTR(name); + *ret_idx = i; + return 0; + } + + return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), + "No free PCIe hotplug port available for owner '%s'.", + owner_id); +} + +static void vmspawn_qmp_bridge_release_pcie_port_by_idx(VmspawnQmpBridge *bridge, int idx) { + assert(bridge); + + if (idx < 0) + return; + + assert(idx < VMSPAWN_PCIE_HOTPLUG_SPARES); + + bridge->hotplug_port_owner[idx] = mfree(bridge->hotplug_port_owner[idx]); +} + static DriveInfo* drive_info_free(DriveInfo *d) { assert(d); + if (d->bridge) + vmspawn_qmp_bridge_release_pcie_port_by_idx(d->bridge, d->pcie_port_idx); + free(d->path); free(d->format); free(d->disk_driver); free(d->serial); + free(d->pcie_port); + free(d->id); free(d->qmp_node_name); free(d->qmp_device_id); - free(d->pcie_port); + free(d->fdset_path); + sd_varlink_unref(d->link); safe_close(d->fd); safe_close(d->overlay_fd); return mfree(d); @@ -61,7 +117,6 @@ void drive_infos_done(DriveInfos *infos) { drive_info_unref(*d); infos->drives = mfree(infos->drives); infos->n_drives = 0; - infos->scsi_pcie_port = mfree(infos->scsi_pcie_port); } void network_info_done(NetworkInfo *info) { @@ -269,6 +324,43 @@ static int qmp_build_device_add(const DriveInfo *drive, sd_json_variant **ret) { "bus", SD_JSON_BUILD_STRING(drive->pcie_port))); } +/* Inline form: one blockdev-add creates format+file; one blockdev-del tears + * down the whole tree. Used for regular boot drives and hotplug. */ +static int qmp_build_blockdev_add_inline( + const char *node_name, + const char *format, + const char *filename, + const char *file_driver, + QmpDriveFlags flags, + VmspawnQmpFeatureFlags features, + sd_json_variant **ret) { + + bool use_io_uring = FLAGS_SET(features, VMSPAWN_QMP_FEATURE_IO_URING); + bool use_discard_no_unref = FLAGS_SET(flags, QMP_DRIVE_DISCARD_NO_UNREF); + + assert(node_name); + assert(format); + assert(filename); + assert(file_driver); + assert(ret); + + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_STRING("node-name", node_name), + SD_JSON_BUILD_PAIR_STRING("driver", format), + SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(flags, QMP_DRIVE_READ_ONLY), "read-only", SD_JSON_BUILD_BOOLEAN(true)), + SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(flags, QMP_DRIVE_DISCARD), "discard", JSON_BUILD_CONST_STRING("unmap")), + SD_JSON_BUILD_PAIR_CONDITION(use_discard_no_unref, "discard-no-unref", SD_JSON_BUILD_BOOLEAN(true)), + SD_JSON_BUILD_PAIR("file", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("driver", file_driver), + SD_JSON_BUILD_PAIR_STRING("filename", filename), + SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(flags, QMP_DRIVE_READ_ONLY), "read-only", SD_JSON_BUILD_BOOLEAN(true)), + SD_JSON_BUILD_PAIR_CONDITION(use_io_uring, "aio", JSON_BUILD_CONST_STRING("io_uring")), + SD_JSON_BUILD_PAIR("cache", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_BOOLEAN("direct", false), + SD_JSON_BUILD_PAIR_BOOLEAN("no-flush", FLAGS_SET(flags, QMP_DRIVE_NO_FLUSH))))))); +} + /* Issue blockdev-add for a file node. */ static int qmp_add_file_node(QmpClient *qmp, const QmpFileNodeParams *p) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; @@ -329,28 +421,27 @@ static int get_image_virtual_size(int fd, const char *format, bool is_block_devi return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported image format '%s'", format); } +/* Forward declarations — on_ephemeral_create_concluded routes failures through + * the shared block-device add callbacks defined further below. */ +static int drive_info_add_fail(DriveInfo *d, int error, const char *error_desc); +static int on_add_blockdev_stage(QmpClient *client, sd_json_variant *result, + const char *error_desc, int error, void *userdata); +static int on_add_device_add_complete(QmpClient *client, sd_json_variant *result, + const char *error_desc, int error, void *userdata); + /* Continuation state for on_ephemeral_create_concluded: overlay format + device_add. */ typedef struct EphemeralDriveCtx { - char *qmp_node_name; /* overlay format node name, "vmspawn--storage" */ - char *qmp_device_id; /* qdev id, "vmspawn--disk" */ + DriveInfo *drive; /* ref */ char *overlay_file_node; char *base_fmt_node; - char *disk_driver; - char *serial; - char *pcie_port; /* NULL on non-PCIe */ - QmpDriveFlags flags; /* subset forwarded to device_add */ } EphemeralDriveCtx; static EphemeralDriveCtx* ephemeral_drive_ctx_free(EphemeralDriveCtx *ctx) { if (!ctx) return NULL; - free(ctx->qmp_node_name); - free(ctx->qmp_device_id); + drive_info_unref(ctx->drive); free(ctx->overlay_file_node); free(ctx->base_fmt_node); - free(ctx->disk_driver); - free(ctx->serial); - free(ctx->pcie_port); return mfree(ctx); } @@ -363,50 +454,48 @@ static void ephemeral_drive_ctx_free_void(void *p) { static int on_ephemeral_create_concluded(QmpClient *qmp, void *userdata) { _cleanup_(ephemeral_drive_ctx_freep) EphemeralDriveCtx *ctx = ASSERT_PTR(userdata); _cleanup_(sd_json_variant_unrefp) sd_json_variant *fmt_args = NULL, *device_args = NULL; + _cleanup_(drive_info_unrefp) DriveInfo *slot_ref = NULL; + DriveInfo *drive = ctx->drive; int r; + assert(qmp); + /* Open formatted overlay as qcow2 with backing reference */ QmpFormatNodeParams overlay_fmt_params = { - .node_name = ctx->qmp_node_name, + .node_name = drive->qmp_node_name, .format = "qcow2", .file_node_name = ctx->overlay_file_node, .backing = ctx->base_fmt_node, - .flags = ctx->flags & (QMP_DRIVE_DISCARD|QMP_DRIVE_DISCARD_NO_UNREF), + .flags = drive->flags & (QMP_DRIVE_DISCARD|QMP_DRIVE_DISCARD_NO_UNREF), }; r = qmp_build_blockdev_add_format(&overlay_fmt_params, &fmt_args); if (r < 0) - return log_error_errno(r, "Failed to build overlay format JSON for '%s': %m", ctx->qmp_node_name); + return drive_info_add_fail(drive, r, NULL); - r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(fmt_args), on_qmp_complete, (void*) "blockdev-add"); + slot_ref = drive_info_ref(drive); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(fmt_args), + on_add_blockdev_stage, slot_ref); if (r < 0) - return r; + return drive_info_add_fail(drive, r, NULL); + TAKE_PTR(slot_ref); - /* Temporary DriveInfo view so we can reuse qmp_build_device_add(). */ - const DriveInfo tmp = { - .disk_driver = ctx->disk_driver, - .serial = ctx->serial, - .pcie_port = ctx->pcie_port, - .flags = ctx->flags & QMP_DRIVE_BOOT, - .fd = -EBADF, - .overlay_fd = -EBADF, - .qmp_node_name = ctx->qmp_node_name, - .qmp_device_id = ctx->qmp_device_id, - }; - r = qmp_build_device_add(&tmp, &device_args); + r = qmp_build_device_add(drive, &device_args); if (r < 0) - return log_error_errno(r, "Failed to build device_add JSON for '%s': %m", ctx->qmp_device_id); + return drive_info_add_fail(drive, r, NULL); - r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_complete, (void*) "device_add"); + slot_ref = drive_info_ref(drive); + r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), + on_add_device_add_complete, slot_ref); if (r < 0) - return r; + return drive_info_add_fail(drive, r, NULL); + TAKE_PTR(slot_ref); - log_debug("Queued ephemeral drive completion for '%s'", ctx->qmp_device_id); + log_debug("Queued ephemeral drive completion for '%s'", drive->qmp_device_id); return 0; } -/* Set up an ephemeral drive: base image (read-only) + anonymous qcow2 overlay (read-write). - * The final steps (overlay format + device_add) are deferred to a job continuation that - * fires when the blockdev-create job concludes. */ +/* Base image (read-only) + anonymous qcow2 overlay (read-write). Overlay format + * and device_add run from the blockdev-create continuation. */ static int qmp_setup_ephemeral_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, DriveInfo *drive) { int r; @@ -416,16 +505,24 @@ static int qmp_setup_ephemeral_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, D assert(drive->fd >= 0); assert(drive->overlay_fd >= 0); - uint64_t counter = bridge->next_block_counter++; + drive->bridge = bridge; + drive->counter = bridge->next_block_counter++; _cleanup_free_ char *base_file_node = NULL, *base_fmt_node = NULL, *overlay_file_node = NULL; - if (asprintf(&drive->qmp_node_name, "vmspawn-%" PRIu64 "-storage", counter) < 0 || - asprintf(&drive->qmp_device_id, "vmspawn-%" PRIu64 "-disk", counter) < 0 || - asprintf(&base_file_node, "vmspawn-%" PRIu64 "-base-file", counter) < 0 || - asprintf(&base_fmt_node, "vmspawn-%" PRIu64 "-base-fmt", counter) < 0 || - asprintf(&overlay_file_node, "vmspawn-%" PRIu64 "-overlay-file", counter) < 0) + if (asprintf(&drive->qmp_node_name, "vmspawn-%" PRIu64 "-storage", drive->counter) < 0 || + asprintf(&drive->qmp_device_id, "vmspawn-%" PRIu64 "-disk", drive->counter) < 0 || + asprintf(&base_file_node, "vmspawn-%" PRIu64 "-base-file", drive->counter) < 0 || + asprintf(&base_fmt_node, "vmspawn-%" PRIu64 "-base-fmt", drive->counter) < 0 || + asprintf(&overlay_file_node, "vmspawn-%" PRIu64 "-overlay-file", drive->counter) < 0) return log_oom(); + /* Auto-assigned user id reuses qmp_device_id (matching vmspawn_qmp_add_block_device). */ + if (!drive->id) { + drive->id = strdup(drive->qmp_device_id); + if (!drive->id) + return log_oom(); + } + /* Read virtual size before passing the fd to QEMU (TAKE_FD consumes it) */ uint64_t virtual_size; r = get_image_virtual_size(drive->fd, drive->format, FLAGS_SET(drive->flags, QMP_DRIVE_BLOCK_DEVICE), &virtual_size); @@ -515,7 +612,7 @@ static int qmp_setup_ephemeral_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, D return log_error_errno(r, "Failed to build blockdev-create options: %m"); _cleanup_free_ char *job_id = NULL; - if (asprintf(&job_id, "vmspawn-%" PRIu64 "-overlay-create", counter) < 0) + if (asprintf(&job_id, "vmspawn-%" PRIu64 "-overlay-create", drive->counter) < 0) return log_oom(); _cleanup_(sd_json_variant_unrefp) sd_json_variant *cmd_args = NULL; @@ -525,29 +622,23 @@ static int qmp_setup_ephemeral_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, D if (r < 0) return log_error_errno(r, "Failed to build blockdev-create JSON: %m"); + /* Fold DISCARD_NO_UNREF into drive->flags so the continuation's overlay format blockdev-add + * picks it up via drive->flags. */ + if (FLAGS_SET(drive->flags, QMP_DRIVE_DISCARD) && + FLAGS_SET(bridge->features, VMSPAWN_QMP_FEATURE_DISCARD_NO_UNREF)) + drive->flags |= QMP_DRIVE_DISCARD_NO_UNREF; + /* Register continuation: when the job concludes, fire overlay format + device_add */ _cleanup_(ephemeral_drive_ctx_freep) EphemeralDriveCtx *ectx = new(EphemeralDriveCtx, 1); if (!ectx) return log_oom(); - QmpDriveFlags ectx_flags = drive->flags & (QMP_DRIVE_DISCARD|QMP_DRIVE_BOOT); - if (FLAGS_SET(drive->flags, QMP_DRIVE_DISCARD) && - FLAGS_SET(bridge->features, VMSPAWN_QMP_FEATURE_DISCARD_NO_UNREF)) - ectx_flags |= QMP_DRIVE_DISCARD_NO_UNREF; - *ectx = (EphemeralDriveCtx) { - .qmp_node_name = strdup(drive->qmp_node_name), - .qmp_device_id = strdup(drive->qmp_device_id), + .drive = drive_info_ref(drive), .overlay_file_node = strdup(overlay_file_node), .base_fmt_node = strdup(base_fmt_node), - .disk_driver = strdup(drive->disk_driver), - .serial = drive->serial ? strdup(drive->serial) : NULL, - .pcie_port = drive->pcie_port ? strdup(drive->pcie_port) : NULL, - .flags = ectx_flags, }; - if (!ectx->qmp_node_name || !ectx->qmp_device_id || !ectx->overlay_file_node || - !ectx->base_fmt_node || !ectx->disk_driver || - (drive->serial && !ectx->serial) || (drive->pcie_port && !ectx->pcie_port)) + if (!ectx->overlay_file_node || !ectx->base_fmt_node) return log_oom(); r = vmspawn_qmp_bridge_register_job(bridge, job_id, @@ -559,91 +650,345 @@ static int qmp_setup_ephemeral_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, D TAKE_PTR(ectx); r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-create", QMP_CLIENT_ARGS(cmd_args), on_qmp_complete, (void*) "blockdev-create"); - if (r < 0) + if (r < 0) { + _unused_ _cleanup_(pending_job_freep) PendingJob *dead = hashmap_remove(bridge->pending_jobs, job_id); return log_error_errno(r, "Failed to send blockdev-create for '%s': %m", drive->path); + } log_debug("Queued ephemeral drive setup for '%s' (job %s)", drive->path, job_id); return 0; } -/* Set up a regular (non-ephemeral) drive: single file node + format node + device_add. */ -static int qmp_setup_regular_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, DriveInfo *drive) { +static int reply_qmp_error(sd_varlink *link, const char *error_desc, int error) { + assert(link); + + if (ERRNO_IS_DISCONNECT(error)) + return sd_varlink_error(link, "io.systemd.MachineInstance.NotConnected", NULL); + if (error_desc) + log_warning("QMP error: %s", error_desc); + return sd_varlink_error_errno(link, error < 0 ? error : -EIO); +} + +/* After the pipelined remove-fd at add time, QEMU auto-frees the fdset when + * raw_close (during blockdev-del) releases the last dup. So teardown only + * needs to fire blockdev-del. */ +static void vmspawn_qmp_block_device_teardown(QmpClient *client, const char *qmp_node_name, BlockDeviceStateFlags stages) { + assert(client); + assert(qmp_node_name); + + if (!FLAGS_SET(stages, BLOCK_DEVICE_STATE_BLOCKDEV_ADDED)) + return; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + if (sd_json_buildo(&args, SD_JSON_BUILD_PAIR_STRING("node-name", qmp_node_name)) >= 0) + (void) qmp_client_invoke(client, /* ret_slot= */ NULL, "blockdev-del", QMP_CLIENT_ARGS(args), + on_qmp_complete, (void*) "teardown blockdev-del"); +} + +/* Insert into the owning primary map and the non-owning qmp_device_id view. On + * secondary-put failure, roll back the primary so neither map carries a stale entry. */ +static int bridge_register_drive(VmspawnQmpBridge *b, DriveInfo *d) { + int r; + + assert(b); + assert(d); + assert(d->id); + assert(d->qmp_device_id); + + r = hashmap_ensure_put(&b->block_devices, &block_devices_hash_ops, + d->id, drive_info_ref(d)); + if (r < 0) { + drive_info_unref(d); + return r; + } + + r = hashmap_ensure_put(&b->block_devices_by_qmp_id, &string_hash_ops, + d->qmp_device_id, d); + if (r < 0) { + drive_info_unref(hashmap_remove(b->block_devices, d->id)); + return r; + } + + return 0; +} + +/* Drop the drive from both maps; returns the pointer removed from the primary + * (NULL if it wasn't there) so the caller can decide whether to unref. */ +static DriveInfo* bridge_unregister_drive(VmspawnQmpBridge *b, DriveInfo *d) { + assert(b); + assert(d); + + hashmap_remove_value(b->block_devices_by_qmp_id, d->qmp_device_id, d); + return hashmap_remove_value(b->block_devices, d->id, d); +} + +/* First-error entry point: marks FAILED so cascading callbacks no-op, drops + * the registry slot, then replies on the link or exits the loop. */ +static int drive_info_add_fail(DriveInfo *d, int error, const char *error_desc) { + assert(d); + + if (FLAGS_SET(d->state, BLOCK_DEVICE_STATE_ADD_FAILED)) + return 0; + + vmspawn_qmp_block_device_teardown(d->bridge->qmp, d->qmp_node_name, d->state); + d->state = BLOCK_DEVICE_STATE_ADD_FAILED; + + if (bridge_unregister_drive(d->bridge, d)) + drive_info_unref(d); + + if (d->link) { + (void) reply_qmp_error(d->link, error_desc, error); + d->link = sd_varlink_unref(d->link); + return 0; + } + + log_error_errno(error, "Block device '%s' setup failed: %s", + strna(d->id), strna(error_desc)); + + /* Boot-time (link == NULL) is always fatal — even for late-arriving ephemeral replies. */ + return sd_event_exit(qmp_client_get_event(d->bridge->qmp), error); +} + +/* Rolls back the up-front registry insert on a sync error path. */ +static void drive_info_unregister_on_failurep(DriveInfo **dp) { + assert(dp); + + DriveInfo *d = *dp; + if (!d) + return; + d->state |= BLOCK_DEVICE_STATE_ADD_FAILED; + if (bridge_unregister_drive(d->bridge, d)) + drive_info_unref(d); +} + +/* Shared by the intermediate stages that don't need to record a rollback bit + * (add-fd, remove-fd). Just forwards errors to drive_info_add_fail so cascades + * from earlier stage failures get suppressed via the FAILED sentinel. */ +static int on_add_observe_stage( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + _cleanup_(drive_info_unrefp) DriveInfo *d = ASSERT_PTR(userdata); + assert(client); + + if (error < 0) + return drive_info_add_fail(d, error, error_desc); + return 0; +} + +static int on_add_blockdev_stage( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + _cleanup_(drive_info_unrefp) DriveInfo *d = ASSERT_PTR(userdata); + assert(client); + + if (error < 0) + return drive_info_add_fail(d, error, error_desc); + + /* A sync error after blockdev-add was queued may have marked the chain FAILED. + * The blockdev node we just created is orphaned — tear it down retroactively + * and don't claim BLOCKDEV_ADDED on a drive that's already been unregistered. */ + if (FLAGS_SET(d->state, BLOCK_DEVICE_STATE_ADD_FAILED)) { + vmspawn_qmp_block_device_teardown(d->bridge->qmp, d->qmp_node_name, + BLOCK_DEVICE_STATE_BLOCKDEV_ADDED); + return 0; + } + + d->state |= BLOCK_DEVICE_STATE_BLOCKDEV_ADDED; + return 0; +} + +static int on_add_device_add_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + _cleanup_(drive_info_unrefp) DriveInfo *d = ASSERT_PTR(userdata); + + assert(client); + + if (error < 0) + return drive_info_add_fail(d, error, error_desc); + + if (FLAGS_SET(d->state, BLOCK_DEVICE_STATE_ADD_FAILED)) + return 0; + + if (d->link) { + (void) sd_varlink_replybo(d->link, SD_JSON_BUILD_PAIR_STRING("id", d->id)); + d->link = sd_varlink_unref(d->link); + } + + log_info("Block device '%s' attached", d->id); + return 0; +} + +static int on_scsi_controller_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + assert(client); + + VmspawnQmpBridge *bridge = ASSERT_PTR(qmp_client_get_userdata(client)); + + if (error < 0) { + /* QEMU's device_add is transactional — on error it calls object_unparent() + * before replying, so the "vmspawn_scsi" id is free for the next retry. */ + vmspawn_qmp_bridge_release_pcie_port_by_idx(bridge, bridge->scsi_controller_port_idx); + bridge->scsi_controller_port_idx = -1; + bridge->scsi_controller_created = false; + log_warning("virtio-scsi-pci controller setup failed: %s", strna(error_desc)); + if (!bridge->setup_done) + return sd_event_exit(qmp_client_get_event(client), error); + } + + return 0; +} + +static int qmp_setup_scsi_controller(VmspawnQmpBridge *bridge, const char *pcie_port) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + int r; + + assert(bridge); + + r = sd_json_buildo( + &args, + SD_JSON_BUILD_PAIR_STRING("driver", "virtio-scsi-pci"), + SD_JSON_BUILD_PAIR_STRING("id", "vmspawn_scsi"), + SD_JSON_BUILD_PAIR_CONDITION(!!pcie_port, "bus", SD_JSON_BUILD_STRING(pcie_port))); + if (r < 0) + return log_error_errno(r, "Failed to build SCSI controller JSON: %m"); + + r = qmp_client_invoke(bridge->qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(args), + on_scsi_controller_complete, NULL); + if (r < 0) + return log_error_errno(r, "Failed to send SCSI controller device_add: %m"); + + log_debug("Queued virtio-scsi-pci controller setup"); + return 0; +} + +static int vmspawn_qmp_add_block_device(VmspawnQmpBridge *bridge, DriveInfo *drive) { int r; assert(bridge); - assert(qmp); assert(drive); + assert(drive->format); + assert(drive->disk_driver); assert(drive->fd >= 0); - uint64_t counter = bridge->next_block_counter++; - _cleanup_free_ char *file_node_name = NULL; - if (asprintf(&drive->qmp_node_name, "vmspawn-%" PRIu64 "-storage", counter) < 0 || - asprintf(&drive->qmp_device_id, "vmspawn-%" PRIu64 "-disk", counter) < 0 || - asprintf(&file_node_name, "vmspawn-%" PRIu64 "-file", counter) < 0) + _unused_ _cleanup_(drive_info_unrefp) DriveInfo *owned = drive; + _cleanup_(drive_info_unrefp) DriveInfo *slot_ref = NULL; + _cleanup_(drive_info_unregister_on_failurep) DriveInfo *registered = NULL; + + drive->bridge = bridge; + drive->counter = bridge->next_block_counter++; + if (asprintf(&drive->qmp_node_name, "vmspawn-%" PRIu64 "-storage", drive->counter) < 0) return log_oom(); + if (asprintf(&drive->qmp_device_id, "vmspawn-%" PRIu64 "-disk", drive->counter) < 0) + return log_oom(); + /* Auto-assigned user ids reuse qmp_device_id. */ + if (!drive->id) { + drive->id = strdup(drive->qmp_device_id); + if (!drive->id) + return log_oom(); + } - _cleanup_free_ char *fdset_path = NULL; - uint64_t fdset_id; - r = qmp_fdset_add(qmp, TAKE_FD(drive->fd), - on_qmp_complete, (void*) "add-fd", &fdset_path, &fdset_id); + /* Reserve the registry slot up-front so the device_add callback's commit can't fail. */ + r = bridge_register_drive(bridge, drive); if (r < 0) - return log_error_errno(r, "Failed to send add-fd for '%s': %m", drive->path); + return r; + registered = drive; + + /* First SCSI hotplug needs a virtio-scsi-pci controller to attach to. */ + if (STR_IN_SET(drive->disk_driver, "scsi-hd", "scsi-cd") && !bridge->scsi_controller_created) { + _cleanup_free_ char *controller_port = NULL; + int controller_port_idx = -1; + if (ARCHITECTURE_NEEDS_PCIE_ROOT_PORTS) { + r = vmspawn_qmp_bridge_allocate_pcie_port(bridge, "vmspawn_scsi", + &controller_port, &controller_port_idx); + if (r == -EBUSY) + return log_error_errno(r, "No PCIe hotplug ports left for SCSI controller"); + if (r < 0) + return log_error_errno(r, "Failed to allocate PCIe hotplug port for SCSI controller: %m"); + } + + r = qmp_setup_scsi_controller(bridge, controller_port); + if (r < 0) { + vmspawn_qmp_bridge_release_pcie_port_by_idx(bridge, controller_port_idx); + return r; + } - QmpFileNodeParams file_params = { - .node_name = file_node_name, - .filename = fdset_path, - .driver = FLAGS_SET(drive->flags, QMP_DRIVE_BLOCK_DEVICE) ? "host_device" : "file", - .flags = drive->flags & (QMP_DRIVE_READ_ONLY|QMP_DRIVE_NO_FLUSH), - }; - if (FLAGS_SET(bridge->features, VMSPAWN_QMP_FEATURE_IO_URING)) - file_params.flags |= QMP_DRIVE_IO_URING; - r = qmp_add_file_node(qmp, &file_params); + /* Set before the reply so a second SCSI hotplug queued in the meantime + * doesn't re-create the controller; reset in on_scsi_controller_complete on error. */ + bridge->scsi_controller_port_idx = controller_port_idx; + bridge->scsi_controller_created = true; + } + + uint64_t fdset_id; + slot_ref = drive_info_ref(drive); + r = qmp_fdset_add(bridge->qmp, TAKE_FD(drive->fd), + on_add_observe_stage, slot_ref, &drive->fdset_path, &fdset_id); if (r < 0) - return log_error_errno(r, "Failed to send blockdev-add for '%s': %m", drive->path); + return r; + TAKE_PTR(slot_ref); - /* The file node now holds a dup of the fd; release the monitor's - * original so the fdset auto-frees when raw_close runs at teardown. */ - r = qmp_fdset_remove(qmp, fdset_id, on_qmp_complete, (void*) "remove-fd"); + _cleanup_(sd_json_variant_unrefp) sd_json_variant *blockdev_args = NULL; + r = qmp_build_blockdev_add_inline( + drive->qmp_node_name, drive->format, drive->fdset_path, + FLAGS_SET(drive->flags, QMP_DRIVE_BLOCK_DEVICE) ? "host_device" : "file", + drive->flags, bridge->features, &blockdev_args); if (r < 0) - return log_error_errno(r, "Failed to send remove-fd for '%s': %m", drive->path); + return r; - QmpFormatNodeParams fmt_params = { - .node_name = drive->qmp_node_name, - .format = drive->format, - .file_node_name = file_node_name, - .flags = drive->flags & (QMP_DRIVE_READ_ONLY|QMP_DRIVE_DISCARD), - }; - _cleanup_(sd_json_variant_unrefp) sd_json_variant *fmt_args = NULL; - r = qmp_build_blockdev_add_format(&fmt_params, &fmt_args); + slot_ref = drive_info_ref(drive); + r = qmp_client_invoke(bridge->qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(blockdev_args), + on_add_blockdev_stage, slot_ref); if (r < 0) return r; + TAKE_PTR(slot_ref); - r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(fmt_args), on_qmp_complete, (void*) "blockdev-add"); + /* Release the monitor's original fd; the blockdev-add above took a dup. */ + slot_ref = drive_info_ref(drive); + r = qmp_fdset_remove(bridge->qmp, fdset_id, on_add_observe_stage, slot_ref); if (r < 0) - return log_error_errno(r, "Failed to send blockdev-add format for '%s': %m", drive->path); + return r; + TAKE_PTR(slot_ref); - /* device_add: attach to virtual hardware */ _cleanup_(sd_json_variant_unrefp) sd_json_variant *device_args = NULL; r = qmp_build_device_add(drive, &device_args); if (r < 0) return r; - r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), on_qmp_complete, (void*) "device_add"); + slot_ref = drive_info_ref(drive); + r = qmp_client_invoke(bridge->qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(device_args), + on_add_device_add_complete, slot_ref); if (r < 0) - return log_error_errno(r, "Failed to send device_add for '%s': %m", drive->path); + return r; + TAKE_PTR(slot_ref); - log_debug("Queued drive setup for '%s'", drive->path); + TAKE_PTR(registered); return 0; } -/* Configure a single drive via QMP. Dispatches to ephemeral or regular setup. */ -static int qmp_setup_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, DriveInfo *drive) { +static int qmp_setup_regular_drive(VmspawnQmpBridge *bridge, DriveInfo *drive) { + assert(bridge); assert(drive); + assert(drive->fd >= 0); + assert(!drive->id); - if (drive->overlay_fd >= 0) - return qmp_setup_ephemeral_drive(bridge, qmp, drive); - - return qmp_setup_regular_drive(bridge, qmp, drive); + return vmspawn_qmp_add_block_device(bridge, drive); } int vmspawn_qmp_setup_network(VmspawnQmpBridge *bridge, NetworkInfo *network) { @@ -830,36 +1175,6 @@ int vmspawn_qmp_setup_vsock(VmspawnQmpBridge *bridge, VsockInfo *vsock) { return 0; } -static bool drives_need_scsi_controller(DriveInfos *drives) { - assert(drives); - - FOREACH_ARRAY(d, drives->drives, drives->n_drives) - if (STR_IN_SET((*d)->disk_driver, "scsi-hd", "scsi-cd")) - return true; - - return false; -} - -static int qmp_setup_scsi_controller(QmpClient *qmp, const char *pcie_port) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; - int r; - - r = sd_json_buildo( - &args, - SD_JSON_BUILD_PAIR_STRING("driver", "virtio-scsi-pci"), - SD_JSON_BUILD_PAIR_STRING("id", "vmspawn_scsi"), - SD_JSON_BUILD_PAIR_CONDITION(!!pcie_port, "bus", SD_JSON_BUILD_STRING(pcie_port))); - if (r < 0) - return log_error_errno(r, "Failed to build SCSI controller JSON: %m"); - - r = qmp_client_invoke(qmp, /* ret_slot= */ NULL, "device_add", QMP_CLIENT_ARGS(args), on_qmp_complete, (void*) "device_add"); - if (r < 0) - return log_error_errno(r, "Failed to send SCSI controller device_add: %m"); - - log_debug("Queued virtio-scsi-pci controller setup"); - return 0; -} - int vmspawn_qmp_setup_drives(VmspawnQmpBridge *bridge, DriveInfos *drives) { int r; @@ -869,16 +1184,15 @@ int vmspawn_qmp_setup_drives(VmspawnQmpBridge *bridge, DriveInfos *drives) { QmpClient *qmp = vmspawn_qmp_bridge_get_qmp(bridge); /* io_uring support was probed during vmspawn_qmp_init(). The cached result in - * bridge->features is passed to each file node setup call. */ - - if (drives_need_scsi_controller(drives)) { - r = qmp_setup_scsi_controller(qmp, drives->scsi_pcie_port); - if (r < 0) - return r; - } + * bridge->features is passed to each file node setup call. SCSI controller + * creation is handled on-demand by vmspawn_qmp_add_block_device() for the first + * SCSI drive, using the hotplug-spares pool. */ FOREACH_ARRAY(d, drives->drives, drives->n_drives) { - r = qmp_setup_drive(bridge, qmp, *d); + if ((*d)->overlay_fd >= 0) + r = qmp_setup_ephemeral_drive(bridge, qmp, *d); + else + r = qmp_setup_regular_drive(bridge, TAKE_PTR(*d)); if (r < 0) return r; } @@ -898,9 +1212,16 @@ VmspawnQmpBridge* vmspawn_qmp_bridge_free(VmspawnQmpBridge *b) { if (!b) return NULL; + /* Unref first: pending QMP callbacks may release hotplug ports through the bridge. */ + qmp_client_unref(b->qmp); + + hashmap_free(b->block_devices_by_qmp_id); + hashmap_free(b->block_devices); hashmap_free(b->pending_jobs); - qmp_client_unref(b->qmp); + FOREACH_ELEMENT(owner, b->hotplug_port_owner) + free(*owner); + return mfree(b); } @@ -1067,6 +1388,8 @@ int vmspawn_qmp_init(VmspawnQmpBridge **ret, int fd, sd_event *event) { if (!bridge) return log_oom(); + bridge->scsi_controller_port_idx = -1; + r = qmp_client_connect_fd(&bridge->qmp, fd); if (r < 0) return log_error_errno(r, "Failed to create QMP client: %m"); diff --git a/src/vmspawn/vmspawn-qmp.h b/src/vmspawn/vmspawn-qmp.h index a58dfae3beb63..f1407c6f2f5b1 100644 --- a/src/vmspawn/vmspawn-qmp.h +++ b/src/vmspawn/vmspawn-qmp.h @@ -3,6 +3,7 @@ #include +#include "machine-util.h" #include "shared-forward.h" #define VMSPAWN_PCIE_HOTPLUG_SPARES 10 @@ -28,10 +29,15 @@ typedef enum VmspawnQmpFeatureFlags { typedef struct VmspawnQmpBridge { QmpClient *qmp; - Hashmap *pending_jobs; /* blockdev-create continuations */ - uint64_t next_block_counter; /* monotonic counter feeding internal QMP names (vmspawn--*) */ + Hashmap *pending_jobs; /* blockdev-create continuations */ + Hashmap *block_devices; /* user_id (char*) → DriveInfo* (owned ref) */ + Hashmap *block_devices_by_qmp_id; /* qmp_device_id (char*) → DriveInfo* (non-owning view) */ + char *hotplug_port_owner[VMSPAWN_PCIE_HOTPLUG_SPARES]; /* owner id per port; NULL = free */ + int scsi_controller_port_idx; /* hotplug port idx taken by virtio-scsi-pci, -1 if none */ + uint64_t next_block_counter; /* monotonic counter feeding internal QMP names (vmspawn--*) */ VmspawnQmpFeatureFlags features; bool setup_done; + bool scsi_controller_created; /* virtio-scsi-pci has been device_add'd */ } VmspawnQmpBridge; VmspawnQmpBridge* vmspawn_qmp_bridge_free(VmspawnQmpBridge *b); @@ -68,21 +74,39 @@ typedef enum QmpDriveFlags { QMP_DRIVE_DISCARD_NO_UNREF = 1u << 6, /* qcow2 only */ } QmpDriveFlags; -/* Drive info for QMP-based drive setup. All string fields are owned. - * Each DriveInfo is individually heap-allocated so it can be handed off - * to the block device registry via TAKE_PTR. */ +typedef enum BlockDeviceStateFlags { + BLOCK_DEVICE_STATE_BLOCKDEV_ADDED = 1u << 0, + BLOCK_DEVICE_STATE_ADD_FAILED = 1u << 1, /* first error fired; suppress cascades */ +} BlockDeviceStateFlags; + +/* Ref-counted; each of the four add-stage QMP slots holds one ref. + * + * link == NULL → boot-time: failure calls sd_event_exit (if !setup_done). + * link != NULL → hotplug: failure replies via the varlink link. */ typedef struct DriveInfo { unsigned n_ref; + + /* Config */ char *path; /* original path (for logging; not passed to QEMU) */ char *format; /* "raw" or "qcow2" */ char *disk_driver; /* "virtio-blk-pci", "scsi-hd", "scsi-cd", "nvme" */ char *serial; - char *qmp_node_name; /* "vmspawn--storage" */ - char *qmp_device_id; /* "vmspawn--disk" */ char *pcie_port; /* pcie-root-port id for device_add bus (NULL on non-PCIe) */ int fd; /* pre-opened image fd (-EBADF if unused) */ int overlay_fd; /* pre-opened anonymous overlay fd for ephemeral (-EBADF if unused) */ QmpDriveFlags flags; + + /* Per-add-op state (populated by the add flow; zeroed at CLI-parse time) */ + VmspawnQmpBridge *bridge; /* weak */ + char *id; /* varlink-visible id (caller-supplied, or falls back to qmp_device_id) */ + DiskType disk_type; /* for the ListBlockDevices `driver` field */ + uint64_t counter; /* internal N used in qmp_node_name / qmp_device_id */ + char *qmp_node_name; /* "vmspawn--storage" */ + char *qmp_device_id; /* "vmspawn--disk" */ + char *fdset_path; /* "/dev/fdset/N" */ + int pcie_port_idx; /* hotplug port idx held by this drive; -1 once committed or unused */ + BlockDeviceStateFlags state; + sd_varlink *link; /* ref'd iff hotplug */ } DriveInfo; DriveInfo* drive_info_new(void); @@ -92,7 +116,6 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DriveInfo *, drive_info_unref); typedef struct DriveInfos { DriveInfo **drives; /* array of individually heap-allocated entries */ size_t n_drives; - char *scsi_pcie_port; /* owned: pcie-root-port id for SCSI controller (NULL if no SCSI or non-PCIe) */ } DriveInfos; void drive_infos_done(DriveInfos *infos); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 3f99d0547cdb7..1a349a1d634f3 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -2440,19 +2440,15 @@ static int assign_pcie_ports(MachineConfig *c) { size_t port = 0; - /* Drives: non-SCSI drives get individual ports, SCSI controller gets one port */ - bool need_scsi = false; + /* Non-SCSI drives get individual ports. SCSI controllers (if any) allocate + * from the hotplug-spares pool on demand at device-add time. */ FOREACH_ARRAY(d, drives->drives, drives->n_drives) { - if (STR_IN_SET((*d)->disk_driver, "scsi-hd", "scsi-cd")) { - need_scsi = true; + DriveInfo *drive = *d; + if (STR_IN_SET(drive->disk_driver, "scsi-hd", "scsi-cd")) continue; - } - if (asprintf(&(*d)->pcie_port, "vmspawn-pcieport-%zu", port++) < 0) + if (asprintf(&drive->pcie_port, "vmspawn-pcieport-%zu", port++) < 0) return log_oom(); } - if (need_scsi) - if (asprintf(&drives->scsi_pcie_port, "vmspawn-pcieport-%zu", port++) < 0) - return log_oom(); if (network->type) if (asprintf(&network->pcie_port, "vmspawn-pcieport-%zu", port++) < 0) From 93c04d4aba520afb4bf57e27fb0faac8268ed685 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 22 Apr 2026 10:19:09 +0200 Subject: [PATCH 1261/1296] vmspawn-qmp: add vmspawn_qmp_remove_block_device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hot-remove counterpart to vmspawn_qmp_add_block_device. Looks the drive up in the bridge's block_devices registry by caller-supplied id and dispatches device_del using the internal qmp_device_id; the varlink link gets the immediate ack/error reply once QEMU completes the request. Concurrency: a second remove for the same id while the first is in flight (between device_del dispatch and DEVICE_DELETED) would otherwise reach QEMU and earn a confusing 'already in the process of unplug' reply. Track the in-flight state with a new BLOCK_DEVICE_REMOVE_PENDING bit on the existing rollback_mask, and short-circuit duplicate calls with -EBUSY. The bit is cleared on device_del failure (the drive is still attached, so retries make sense) and naturally vanishes on success when the registry entry is dropped. DEVICE_DELETED handling: the actual blockdev-del + registry removal + pcie-port release is deferred to vmspawn_qmp_dispatch_device_deleted, which fires from on_qmp_event in vmspawn-varlink.c when the guest acks the eject. Hooking it from the existing QMP event dispatcher keeps the cleanup local to vmspawn-qmp.{c,h}. The function has no varlink callers in this PR — the io.systemd.VirtualMachineInstance method handler that forwards into it lands with the rest of the hotplug PR. Signed-off-by: Christian Brauner --- src/vmspawn/vmspawn-qmp.c | 83 +++++++++++++++++++++++++++++++++++ src/vmspawn/vmspawn-qmp.h | 3 ++ src/vmspawn/vmspawn-varlink.c | 5 +++ 3 files changed, 91 insertions(+) diff --git a/src/vmspawn/vmspawn-qmp.c b/src/vmspawn/vmspawn-qmp.c index 9158d5b10a99b..621c5e781a7a6 100644 --- a/src/vmspawn/vmspawn-qmp.c +++ b/src/vmspawn/vmspawn-qmp.c @@ -991,6 +991,89 @@ static int qmp_setup_regular_drive(VmspawnQmpBridge *bridge, DriveInfo *drive) { return vmspawn_qmp_add_block_device(bridge, drive); } +/* device_del completion is just QEMU acking the request; teardown happens + * in vmspawn_qmp_dispatch_device_deleted() once the guest acks the eject. */ +static int on_remove_device_del_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + _cleanup_(drive_info_unrefp) DriveInfo *drive = ASSERT_PTR(userdata); + _cleanup_(sd_varlink_unrefp) sd_varlink *link = TAKE_PTR(drive->link); + + assert(client); + assert(link); + + if (error < 0) { + /* device_del rejected: clear the pending bit so the caller can retry. */ + drive->state &= ~BLOCK_DEVICE_STATE_REMOVE_PENDING; + + return reply_qmp_error(link, error_desc, error); + } + + return sd_varlink_reply(link, NULL); +} + +int vmspawn_qmp_remove_block_device(VmspawnQmpBridge *bridge, sd_varlink *link, const char *id) { + int r; + + assert(bridge); + assert(link); + assert(id); + + DriveInfo *drive = hashmap_get(bridge->block_devices, id); + if (!drive) + return reply_qmp_error(link, "Unknown block device id", -ENOENT); + if (!FLAGS_SET(drive->state, BLOCK_DEVICE_STATE_BLOCKDEV_ADDED)) + return reply_qmp_error(link, "Block device add pending", -EBUSY); + if (FLAGS_SET(drive->state, BLOCK_DEVICE_STATE_REMOVE_PENDING)) + return reply_qmp_error(link, "Block device removal pending", -EBUSY); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + r = sd_json_buildo(&args, SD_JSON_BUILD_PAIR_STRING("id", drive->qmp_device_id)); + if (r < 0) + return sd_varlink_error_errno(link, r); + + assert(!drive->link); + drive->link = sd_varlink_ref(link); + drive->state |= BLOCK_DEVICE_STATE_REMOVE_PENDING; + + r = qmp_client_invoke(bridge->qmp, /* ret_slot= */ NULL, "device_del", QMP_CLIENT_ARGS(args), + on_remove_device_del_complete, drive_info_ref(drive)); + if (r < 0) { + drive->link = sd_varlink_unref(drive->link); + drive->state &= ~BLOCK_DEVICE_STATE_REMOVE_PENDING; + drive_info_unref(drive); + return sd_varlink_error_errno(link, r); + } + return 0; +} + +/* DEVICE_DELETED arrives once the guest has acked the eject; only then is it + * safe to drop the blockdev node and release the registry slot (and PCIe port). */ +int vmspawn_qmp_dispatch_device_deleted(VmspawnQmpBridge *bridge, sd_json_variant *data) { + assert(bridge); + + if (!data) + return 0; + + const char *qmp_device_id = sd_json_variant_string(sd_json_variant_by_key(data, "device")); + if (!qmp_device_id) + return 0; + + DriveInfo *drive = hashmap_get(bridge->block_devices_by_qmp_id, qmp_device_id); + if (!drive) + return 0; + + vmspawn_qmp_block_device_teardown(bridge->qmp, drive->qmp_node_name, drive->state); + + assert_se(bridge_unregister_drive(bridge, drive) == drive); + drive_info_unref(drive); + return 0; +} + int vmspawn_qmp_setup_network(VmspawnQmpBridge *bridge, NetworkInfo *network) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *netdev_args = NULL, *device_args = NULL; bool tap_by_fd; diff --git a/src/vmspawn/vmspawn-qmp.h b/src/vmspawn/vmspawn-qmp.h index f1407c6f2f5b1..d8403520c9afe 100644 --- a/src/vmspawn/vmspawn-qmp.h +++ b/src/vmspawn/vmspawn-qmp.h @@ -77,6 +77,7 @@ typedef enum QmpDriveFlags { typedef enum BlockDeviceStateFlags { BLOCK_DEVICE_STATE_BLOCKDEV_ADDED = 1u << 0, BLOCK_DEVICE_STATE_ADD_FAILED = 1u << 1, /* first error fired; suppress cascades */ + BLOCK_DEVICE_STATE_REMOVE_PENDING = 1u << 2, /* device_del in flight; reject concurrent removes */ } BlockDeviceStateFlags; /* Ref-counted; each of the four add-stage QMP slots holds one ref. @@ -176,3 +177,5 @@ int vmspawn_qmp_setup_drives(VmspawnQmpBridge *bridge, DriveInfos *drives); int vmspawn_qmp_setup_network(VmspawnQmpBridge *bridge, NetworkInfo *network); int vmspawn_qmp_setup_virtiofs(VmspawnQmpBridge *bridge, const VirtiofsInfos *virtiofs); int vmspawn_qmp_setup_vsock(VmspawnQmpBridge *bridge, VsockInfo *vsock); +int vmspawn_qmp_remove_block_device(VmspawnQmpBridge *bridge, sd_varlink *link, const char *id); +int vmspawn_qmp_dispatch_device_deleted(VmspawnQmpBridge *bridge, sd_json_variant *data); diff --git a/src/vmspawn/vmspawn-varlink.c b/src/vmspawn/vmspawn-varlink.c index 8df234a9bba1e..2e0daa6039f15 100644 --- a/src/vmspawn/vmspawn-varlink.c +++ b/src/vmspawn/vmspawn-varlink.c @@ -12,6 +12,7 @@ #include "varlink-io.systemd.QemuMachineInstance.h" #include "varlink-io.systemd.VirtualMachineInstance.h" #include "varlink-util.h" +#include "vmspawn-qmp.h" #include "vmspawn-varlink.h" DEFINE_PRIVATE_HASH_OPS_FULL( @@ -315,6 +316,10 @@ static int on_qmp_event( if (streq(event, "JOB_STATUS_CHANGE")) return dispatch_pending_job(ctx->bridge, data); + /* Notification still fans out below. */ + if (streq(event, "DEVICE_DELETED")) + (void) vmspawn_qmp_dispatch_device_deleted(ctx->bridge, data); + return notify_event_subscribers(ctx, event, data); } From 67cd0977cd96595432fe1b9848bfed4864b2f443 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 23 Apr 2026 10:22:17 +0200 Subject: [PATCH 1262/1296] vmspawn-varlink: drop AcquireQMP stub and QemuMachineInstance interface The AcquireQMP() method was a placeholder that always returned EOPNOTSUPP, reserving room for a future id-rewriting QMP multiplex proxy. The broader direction is for systemd-vmspawn to remain the single source of truth for VM control rather than exposing raw QMP to clients. Since AcquireQMP was the only method on io.systemd.QemuMachineInstance (and AlreadyAcquired was its only error), remove the whole interface along with the stub, and update the controlAddress field comment in io.systemd.Machine to stop referencing it. Signed-off-by: Christian Brauner (Amutable) --- src/shared/meson.build | 1 - src/shared/varlink-io.systemd.Machine.c | 2 +- .../varlink-io.systemd.QemuMachineInstance.c | 17 ----------------- .../varlink-io.systemd.QemuMachineInstance.h | 6 ------ src/vmspawn/vmspawn-varlink.c | 11 ++--------- 5 files changed, 3 insertions(+), 34 deletions(-) delete mode 100644 src/shared/varlink-io.systemd.QemuMachineInstance.c delete mode 100644 src/shared/varlink-io.systemd.QemuMachineInstance.h diff --git a/src/shared/meson.build b/src/shared/meson.build index 56b823c50cd4f..a94b78ba94ac5 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -238,7 +238,6 @@ shared_sources = files( 'varlink-io.systemd.Network.Link.c', 'varlink-io.systemd.PCRExtend.c', 'varlink-io.systemd.PCRLock.c', - 'varlink-io.systemd.QemuMachineInstance.c', 'varlink-io.systemd.Repart.c', 'varlink-io.systemd.Resolve.c', 'varlink-io.systemd.Resolve.Hook.c', diff --git a/src/shared/varlink-io.systemd.Machine.c b/src/shared/varlink-io.systemd.Machine.c index da373a3c207dd..cb1b0665d6092 100644 --- a/src/shared/varlink-io.systemd.Machine.c +++ b/src/shared/varlink-io.systemd.Machine.c @@ -57,7 +57,7 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_DEFINE_INPUT(vSockCid, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_INPUT(sshAddress, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_INPUT(sshPrivateKeyPath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("Varlink socket address for direct machine control. The server at this address is expected to implement io.systemd.MachineInstance and optionally io.systemd.VirtualMachineInstance and io.systemd.QemuMachineInstance."), + SD_VARLINK_FIELD_COMMENT("Varlink socket address for direct machine control. The server at this address is expected to implement io.systemd.MachineInstance and optionally io.systemd.VirtualMachineInstance."), SD_VARLINK_DEFINE_INPUT(controlAddress, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Controls whether to allocate a scope unit for the machine to register. If false, the client already took care of that and registered a service/scope specific to the machine."), SD_VARLINK_DEFINE_INPUT(allocateUnit, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), diff --git a/src/shared/varlink-io.systemd.QemuMachineInstance.c b/src/shared/varlink-io.systemd.QemuMachineInstance.c deleted file mode 100644 index b03fa2199c487..0000000000000 --- a/src/shared/varlink-io.systemd.QemuMachineInstance.c +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "varlink-io.systemd.QemuMachineInstance.h" - -static SD_VARLINK_DEFINE_METHOD_FULL( - AcquireQMP, - SD_VARLINK_REQUIRES_UPGRADE); - -static SD_VARLINK_DEFINE_ERROR(AlreadyAcquired); - -SD_VARLINK_DEFINE_INTERFACE( - io_systemd_QemuMachineInstance, - "io.systemd.QemuMachineInstance", - SD_VARLINK_SYMBOL_COMMENT("Acquire a direct QMP connection to the QEMU instance via protocol upgrade"), - &vl_method_AcquireQMP, - SD_VARLINK_SYMBOL_COMMENT("A QMP connection has already been acquired by another client"), - &vl_error_AlreadyAcquired); diff --git a/src/shared/varlink-io.systemd.QemuMachineInstance.h b/src/shared/varlink-io.systemd.QemuMachineInstance.h deleted file mode 100644 index 203dacb40c46b..0000000000000 --- a/src/shared/varlink-io.systemd.QemuMachineInstance.h +++ /dev/null @@ -1,6 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include "sd-varlink-idl.h" - -extern const sd_varlink_interface vl_interface_io_systemd_QemuMachineInstance; diff --git a/src/vmspawn/vmspawn-varlink.c b/src/vmspawn/vmspawn-varlink.c index 2e0daa6039f15..51a1091e40a0c 100644 --- a/src/vmspawn/vmspawn-varlink.c +++ b/src/vmspawn/vmspawn-varlink.c @@ -9,7 +9,6 @@ #include "string-util.h" #include "strv.h" #include "varlink-io.systemd.MachineInstance.h" -#include "varlink-io.systemd.QemuMachineInstance.h" #include "varlink-io.systemd.VirtualMachineInstance.h" #include "varlink-util.h" #include "vmspawn-qmp.h" @@ -184,10 +183,6 @@ static int vl_method_subscribe_events(sd_varlink *link, sd_json_variant *paramet return 0; } -static int vl_method_acquire_qmp(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { - return sd_varlink_error_errno(link, -EOPNOTSUPP); -} - static void vl_disconnect(sd_varlink_server *server, sd_varlink *link, void *userdata) { VmspawnVarlinkContext *ctx = ASSERT_PTR(userdata); @@ -372,8 +367,7 @@ int vmspawn_varlink_setup( r = sd_varlink_server_add_interface_many( ctx->varlink_server, &vl_interface_io_systemd_MachineInstance, - &vl_interface_io_systemd_VirtualMachineInstance, - &vl_interface_io_systemd_QemuMachineInstance); + &vl_interface_io_systemd_VirtualMachineInstance); if (r < 0) return log_error_errno(r, "Failed to add varlink interfaces: %m"); @@ -385,8 +379,7 @@ int vmspawn_varlink_setup( "io.systemd.MachineInstance.Resume", vl_method_resume, "io.systemd.MachineInstance.Reboot", vl_method_reboot, "io.systemd.MachineInstance.Describe", vl_method_describe, - "io.systemd.MachineInstance.SubscribeEvents", vl_method_subscribe_events, - "io.systemd.QemuMachineInstance.AcquireQMP", vl_method_acquire_qmp); + "io.systemd.MachineInstance.SubscribeEvents", vl_method_subscribe_events); if (r < 0) return log_error_errno(r, "Failed to bind varlink methods: %m"); From bbf197cda643bb371adbb79dab7f1db265647c4a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 24 Apr 2026 00:09:00 +0200 Subject: [PATCH 1263/1296] help-util: add helpers for generating uniform --help texts Let's introduce some helpers for generating uniform --help texts with some minimal ANSI styling. This shortens the help() functions generally, and allows us to change the style at a single, central place. This mostly just follows our current styling for --help, but it makes two updates to it: 1. The command line summary at the very top of the --help text is now prefixes with a grey ">" character to indicate it's a command line. 2. The human language introductionary description/abstract right after that command line is set in italics, to emphasize it's not dry, technical, structural information, but more human friendly prose. --- src/shared/help-util.c | 67 ++++++++++++++++++++++++++++++++++++++++++ src/shared/help-util.h | 10 +++++++ src/shared/meson.build | 1 + 3 files changed, 78 insertions(+) create mode 100644 src/shared/help-util.c create mode 100644 src/shared/help-util.h diff --git a/src/shared/help-util.c b/src/shared/help-util.c new file mode 100644 index 0000000000000..b88da2d259585 --- /dev/null +++ b/src/shared/help-util.c @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "ansi-color.h" +#include "help-util.h" +#include "pretty-print.h" + +/* These are helpers for putting together --help texts in a uniform way with a common output style. Each + * function generates a separate part of the --help text: + * + * 1. help_cmdline() outputs a brief summary of the command line syntax. (used at least once, in some cases + * multiple times.) This generally comes first in the output. + * + * 2. help_abstract() outputs a brief prose abstract of the command, should carry a single line of text + * that gives the user a hint what this tool does. Use only once, right after the last help_cmdline(). + * + * 3. help_section() can be used to format multiple sections of the --help text. It should be used at least + * once for an "Options:" section, but can be used more than once, for programs with many + * options/verbs. The first invocation should come right after help_abstract(). + * + * 4. Finally, help_man_page_reference() adds a final line linking the man page of the tool. This should be + * used only once, and terminates the --help text. + * + * Switches and verbs documentation should be inserted after each help_section(). For that ideally use + * options.[ch] APIs. */ + +void help_cmdline(const char *arguments) { + assert(arguments); + + printf("%s>%s %s %s\n", + ansi_grey(), + ansi_normal(), + program_invocation_short_name, + arguments); +} + +void help_abstract(const char *text) { + assert(text); + + printf("\n%s%s%s%s\n", + ansi_highlight(), + ansi_add_italics(), + text, + ansi_normal()); +} + +void help_section(const char *title) { + assert(title); + + printf("\n%s%s%s\n", + ansi_underline(), + title, + ansi_normal()); +} + +void help_man_page_reference(const char *page, const char *section) { + assert(page); + assert(section); + + /* Displaying --help texts generally should not fail, hence let's fall back to a simple string in + * case of OOM. */ + _cleanup_free_ char *link = NULL; + if (terminal_urlify_man(page, section, &link) < 0) + printf("\nSee the %s(%s) man page for details.\n", page, section); + else + printf("\nSee the %s for details.\n", link); +} diff --git a/src/shared/help-util.h b/src/shared/help-util.h new file mode 100644 index 0000000000000..380487213ff3f --- /dev/null +++ b/src/shared/help-util.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +void help_cmdline(const char *arguments); + +void help_abstract(const char *text); + +void help_section(const char *title); + +void help_man_page_reference(const char *page, const char *section); diff --git a/src/shared/meson.build b/src/shared/meson.build index 741dbb60a451a..7cae1ff23e2e3 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -94,6 +94,7 @@ shared_sources = files( 'gnutls-util.c', 'gpt.c', 'group-record.c', + 'help-util.c', 'hibernate-util.c', 'hostname-setup.c', 'hwdb-util.c', From 44c8bce3a04dff4013f35e80764cde85f79d4ee9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 24 Apr 2026 08:10:43 +0200 Subject: [PATCH 1264/1296] ac-power,notify,systemctl: port 3 tools over to new --help APIs Let's port over a few tools, to showcase the new logic. --- src/ac-power/ac-power.c | 23 ++++-------- src/notify/notify.c | 23 +++++------- src/systemctl/systemctl.c | 74 ++++++++++++++++++--------------------- 3 files changed, 50 insertions(+), 70 deletions(-) diff --git a/src/ac-power/ac-power.c b/src/ac-power/ac-power.c index dad4384ada74c..e773d8d4314f5 100644 --- a/src/ac-power/ac-power.c +++ b/src/ac-power/ac-power.c @@ -1,14 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "alloc-util.h" -#include "ansi-color.h" #include "battery-util.h" #include "build.h" #include "format-table.h" +#include "help-util.h" #include "log.h" #include "main-func.h" #include "options.h" -#include "pretty-print.h" #include "string-util.h" static bool arg_verbose = false; @@ -19,29 +17,22 @@ static enum { } arg_action = ACTION_AC_POWER; static int help(void) { - _cleanup_free_ char *link = NULL; - _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("systemd-ac-power", "1", &link); - if (r < 0) - return log_oom(); - + _cleanup_(table_unrefp) Table *options = NULL; r = option_parser_get_help_table(&options); if (r < 0) return r; - printf("%s [OPTIONS...]\n" - "\n%sReport whether we are connected to an external power source.%s\n" - "\nOptions:\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal()); + help_cmdline("[OPTIONS...]"); + help_abstract("Report whether we are connected to an external power source."); + + help_section("Options:"); r = table_print_or_warn(options); if (r < 0) return r; - printf("\nSee the %s for details.\n", link); + help_man_page_reference("systemd-ac-power", "1"); return 0; } diff --git a/src/notify/notify.c b/src/notify/notify.c index 2ac0129bae105..d3201c66bcfb7 100644 --- a/src/notify/notify.c +++ b/src/notify/notify.c @@ -17,13 +17,13 @@ #include "fdset.h" #include "format-table.h" #include "format-util.h" +#include "help-util.h" #include "log.h" #include "main-func.h" #include "notify-recv.h" #include "options.h" #include "parse-util.h" #include "pidref.h" -#include "pretty-print.h" #include "process-util.h" #include "signal-util.h" #include "string-util.h" @@ -57,31 +57,24 @@ STATIC_DESTRUCTOR_REGISTER(arg_fds, fdset_freep); STATIC_DESTRUCTOR_REGISTER(arg_fdname, freep); static int help(void) { - _cleanup_free_ char *link = NULL; - _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("systemd-notify", "1", &link); - if (r < 0) - return log_oom(); - + _cleanup_(table_unrefp) Table *options = NULL; r = option_parser_get_help_table(&options); if (r < 0) return r; - printf("%1$s [OPTIONS...] [VARIABLE=VALUE...]\n" - "%1$s [OPTIONS...] --exec [VARIABLE=VALUE...] ; -- CMDLINE...\n" - "%1$s [OPTIONS...] --fork -- CMDLINE...\n" - "\n%2$sNotify the init system about service status updates.%3$s\n\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal()); + help_cmdline("[OPTIONS...] [VARIABLE=VALUE...]"); + help_cmdline("[OPTIONS...] --exec [VARIABLE=VALUE...] ; -- CMDLINE..."); + help_cmdline("[OPTIONS...] --fork -- CMDLINE..."); + help_abstract("Notify the init system about service status updates."); + help_section("Options:"); r = table_print_or_warn(options); if (r < 0) return r; - printf("\nSee the %s for details.\n", link); + help_man_page_reference("systemd-notify", "1"); return 0; } diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index e5e7b412f568d..4f76c5150021f 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -10,6 +10,7 @@ #include "bus-util.h" #include "capsule-util.h" #include "extract-word.h" +#include "help-util.h" #include "image-policy.h" #include "install.h" #include "output-mode.h" @@ -17,7 +18,6 @@ #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" -#include "pretty-print.h" #include "static-destruct.h" #include "string-table.h" #include "string-util.h" @@ -108,19 +108,13 @@ STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); STATIC_DESTRUCTOR_REGISTER(arg_kill_subgroup, freep); static int systemctl_help(void) { - _cleanup_free_ char *link = NULL; - int r; - pager_open(arg_pager_flags); - r = terminal_urlify_man("systemctl", "1", &link); - if (r < 0) - return log_oom(); + help_cmdline("[OPTIONS...] COMMAND ..."); + help_abstract("Query or send control commands to the system manager."); - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%5$sQuery or send control commands to the system manager.%6$s\n" - "\n%3$sUnit Commands:%4$s\n" - " list-units [PATTERN...] List units currently in memory\n" + help_section("Unit Commands:"); + printf(" list-units [PATTERN...] List units currently in memory\n" " list-automounts [PATTERN...] List automount units currently in memory,\n" " ordered by path\n" " list-paths [PATTERN...] List path units currently in memory,\n" @@ -166,9 +160,10 @@ static int systemctl_help(void) { " reset-failed [PATTERN...] Reset failed state for all, one, or more\n" " units\n" " whoami [PID...] Return unit caller or specified PIDs are\n" - " part of\n" - "\n%3$sUnit File Commands:%4$s\n" - " list-unit-files [PATTERN...] List installed unit files\n" + " part of\n"); + + help_section("Unit File Commands:"); + printf(" list-unit-files [PATTERN...] List installed unit files\n" " enable [UNIT...|PATH...] Enable one or more unit files\n" " disable UNIT... Disable one or more unit files\n" " reenable UNIT... Reenable one or more unit files\n" @@ -189,25 +184,30 @@ static int systemctl_help(void) { " on specified one or more units\n" " edit UNIT... Edit one or more unit files\n" " get-default Get the name of the default target\n" - " set-default TARGET Set the default target\n" - "\n%3$sMachine Commands:%4$s\n" - " list-machines [PATTERN...] List local containers and host\n" - "\n%3$sJob Commands:%4$s\n" - " list-jobs [PATTERN...] List jobs\n" - " cancel [JOB...] Cancel all, one, or more jobs\n" - "\n%3$sEnvironment Commands:%4$s\n" - " show-environment Dump environment\n" + " set-default TARGET Set the default target\n"); + + help_section("Machine Commands:"); + printf(" list-machines [PATTERN...] List local containers and host\n"); + + help_section("Job Commands:"); + printf(" list-jobs [PATTERN...] List jobs\n" + " cancel [JOB...] Cancel all, one, or more jobs\n"); + + help_section("Environment Commands:"); + printf(" show-environment Dump environment\n" " set-environment VARIABLE=VALUE... Set one or more environment variables\n" " unset-environment VARIABLE... Unset one or more environment variables\n" - " import-environment VARIABLE... Import all or some environment variables\n" - "\n%3$sManager State Commands:%4$s\n" - " daemon-reload Reload systemd manager configuration\n" + " import-environment VARIABLE... Import all or some environment variables\n"); + + help_section("Manager State Commands:"); + printf(" daemon-reload Reload systemd manager configuration\n" " daemon-reexec Reexecute systemd manager\n" " log-level [LEVEL] Get/set logging threshold for manager\n" " log-target [TARGET] Get/set logging target for manager\n" - " service-watchdogs [BOOL] Get/set service watchdog state\n" - "\n%3$sSystem Commands:%4$s\n" - " is-system-running Check whether system is fully running\n" + " service-watchdogs [BOOL] Get/set service watchdog state\n"); + + help_section("System Commands:"); + printf(" is-system-running Check whether system is fully running\n" " default Enter system default mode\n" " rescue Enter system rescue mode\n" " emergency Enter system emergency mode\n" @@ -224,9 +224,10 @@ static int systemctl_help(void) { " hibernate Hibernate the system\n" " hybrid-sleep Hibernate and suspend the system\n" " suspend-then-hibernate Suspend the system, wake after a period of\n" - " time, and hibernate" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" + " time, and hibernate\n"); + + help_section("Options:"); + printf(" -h --help Show this help\n" " --version Show package version\n" " --system Connect to system manager\n" " --user Connect to user service manager\n" @@ -319,14 +320,9 @@ static int systemctl_help(void) { " --drop-in=NAME Edit unit files using the specified drop-in file name\n" " --when=TIME Schedule halt/power-off/reboot/kexec action after\n" " a certain timestamp\n" - " --stdin Read new contents of edited file from stdin\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + " --stdin Read new contents of edited file from stdin\n"); + + help_man_page_reference("systemctl", "1"); return 0; } From 59e78701ecb4039f42f5e77692af97e498118479 Mon Sep 17 00:00:00 2001 From: Stephane Chazelas Date: Fri, 24 Apr 2026 13:53:02 +0100 Subject: [PATCH 1265/1296] systemd-cat does not connect the standard *input* of a process to the journal The first paragraph of the description of the systemd-cat utility incorrectly referred to stdin when it obviously meant stderr: the other fd that it connects to the journal via a unix(7) domain socket, as clarified in the following paragraphs. I've also replaced "process" with "command" as in that mode, systemd-cat executes a file and does not spawn a process. --- man/systemd-cat.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/man/systemd-cat.xml b/man/systemd-cat.xml index b60984b8a0838..6691fc4d5621b 100644 --- a/man/systemd-cat.xml +++ b/man/systemd-cat.xml @@ -34,9 +34,9 @@ Description systemd-cat may be used to connect the - standard input and output of a process to the journal, or as a - filter tool in a shell pipeline to pass the output the previous - pipeline element generates to the journal. + standard output and error output of a command to the journal, or + as a filter tool in a shell pipeline to pass the output the + previous pipeline element generates to the journal. If no parameter is passed, systemd-cat will write everything it reads from standard input (stdin) to the From 143126cbcd1511dec8e241ca47f48bdf20a0f3dd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 24 Apr 2026 15:19:06 +0200 Subject: [PATCH 1266/1296] job: properly initialize all enum fields This changes the .result field to invalid initially, which arguably makes more sense than "done", which was previously the default. This is a correctnes fix, and afaics has no effect on the API, since we do not expose this 1:1 as D-Bus property: it's only seen on D-Bus as part of the job completion signal, at which part it is correctly initialized. Noticed while reviewing: https://github.com/systemd/systemd/pull/41583 --- src/core/job.c | 2 ++ src/core/job.h | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/job.c b/src/core/job.c index 7dd8a4617db26..920d246b8849a 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -41,6 +41,8 @@ Job* job_new_raw(Unit *unit) { .manager = unit->manager, .unit = unit, .type = _JOB_TYPE_INVALID, + .state = JOB_WAITING, + .result = _JOB_RESULT_INVALID, }; return j; diff --git a/src/core/job.h b/src/core/job.h index d8aa6ce17c53a..ff1d25ff5022b 100644 --- a/src/core/job.h +++ b/src/core/job.h @@ -106,7 +106,6 @@ typedef struct Job { JobType type; JobState state; - JobResult result; unsigned run_queue_idx; From d41555dd2cb8b3bc3876edd4869b3142048393fe Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 24 Apr 2026 13:31:46 +0100 Subject: [PATCH 1267/1296] nss-myhostname: fix maybe-uninitialized warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In resolute with gcc 15.2.0: 472s ../src/nss-myhostname/nss-myhostname.c: In function ‘_nss_myhostname_gethostbyname4_r’: 472s ../src/nss-myhostname/nss-myhostname.c:132:44: error: ‘local_address_ipv4’ may be used uninitialized [-Werror=maybe-uninitialized] 472s 132 | *(uint32_t*) r_tuple->addr = local_address_ipv4; 472s | ~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~ 472s ../src/nss-myhostname/nss-myhostname.c:42:18: note: ‘local_address_ipv4’ was declared here 472s 42 | uint32_t local_address_ipv4; 472s | ^~~~~~~~~~~~~~~~~~ --- src/nss-myhostname/nss-myhostname.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nss-myhostname/nss-myhostname.c b/src/nss-myhostname/nss-myhostname.c index b4a9775ef352b..83d968ff0b58c 100644 --- a/src/nss-myhostname/nss-myhostname.c +++ b/src/nss-myhostname/nss-myhostname.c @@ -39,7 +39,7 @@ enum nss_status _nss_myhostname_gethostbyname4_r( _cleanup_free_ char *hn = NULL; const char *canonical = NULL; int n_addresses = 0; - uint32_t local_address_ipv4; + uint32_t local_address_ipv4 = 0; size_t l, idx, ms; char *r_name; From 37adb410a2b62716b666dbf8359edf8a6546ff94 Mon Sep 17 00:00:00 2001 From: Nick Rosbrook Date: Fri, 24 Apr 2026 09:38:42 -0400 Subject: [PATCH 1268/1296] units: order networkd resolve hook After=network-pre.target Without this, the socket is available well before systemd-networkd.service is able to start, because of its own After=network-pre.target ordering. Then, if resolved handles queries before network-pre.target, it will hang waiting for networkd to reply to hook queries. This is currently happening in the wild with cloud-init. --- units/systemd-networkd-resolve-hook.socket | 1 + 1 file changed, 1 insertion(+) diff --git a/units/systemd-networkd-resolve-hook.socket b/units/systemd-networkd-resolve-hook.socket index 3c11b8e8de1c2..8a724bbc0c0d4 100644 --- a/units/systemd-networkd-resolve-hook.socket +++ b/units/systemd-networkd-resolve-hook.socket @@ -12,6 +12,7 @@ Description=Network Management Resolve Hook Socket Documentation=man:systemd-networkd.service(8) ConditionCapability=CAP_NET_ADMIN DefaultDependencies=no +After=network-pre.target Before=sockets.target shutdown.target Conflicts=shutdown.target From 7c87767d14ee2a26df8516357e8be56d882b66c6 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 15 Mar 2026 13:30:38 +0900 Subject: [PATCH 1269/1296] ip-util: introduce ip_checksum() It is equivalent to dhcp_packet_checksum(). It is generic and not specific to DHCP. Hence, renamed to ip_checksum(). --- src/libsystemd-network/dhcp-packet.c | 46 +++-------------------- src/libsystemd-network/dhcp-packet.h | 2 - src/libsystemd-network/ip-util.c | 38 +++++++++++++++++++ src/libsystemd-network/ip-util.h | 6 +++ src/libsystemd-network/meson.build | 4 ++ src/libsystemd-network/test-dhcp-client.c | 16 ++------ src/libsystemd-network/test-ip-util.c | 16 ++++++++ 7 files changed, 72 insertions(+), 56 deletions(-) create mode 100644 src/libsystemd-network/ip-util.c create mode 100644 src/libsystemd-network/ip-util.h create mode 100644 src/libsystemd-network/test-ip-util.c diff --git a/src/libsystemd-network/dhcp-packet.c b/src/libsystemd-network/dhcp-packet.c index 90eae88379ad5..3b17ad8ac427a 100644 --- a/src/libsystemd-network/dhcp-packet.c +++ b/src/libsystemd-network/dhcp-packet.c @@ -4,10 +4,10 @@ ***/ #include -#include #include "dhcp-option.h" #include "dhcp-packet.h" +#include "ip-util.h" #include "log.h" #include "memory-util.h" @@ -79,41 +79,6 @@ int dhcp_message_init( return 0; } -uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len) { - uint64_t *buf_64 = (uint64_t*)buf; - uint64_t *end_64 = buf_64 + (len / sizeof(uint64_t)); - uint64_t sum = 0; - - /* See RFC1071 */ - - while (buf_64 < end_64) { - sum += *buf_64; - if (sum < *buf_64) - /* wrap around in one's complement */ - sum++; - - buf_64++; - } - - if (len % sizeof(uint64_t)) { - /* If the buffer is not aligned to 64-bit, we need - to zero-pad the last few bytes and add them in */ - uint64_t buf_tail = 0; - - memcpy(&buf_tail, buf_64, len % sizeof(uint64_t)); - - sum += buf_tail; - if (sum < buf_tail) - /* wrap around */ - sum++; - } - - while (sum >> 16) - sum = (sum & 0xffff) + (sum >> 16); - - return ~sum; -} - void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr, uint16_t source_port, be32_t destination_addr, uint16_t destination_port, uint16_t len, int ip_service_type) { @@ -136,11 +101,11 @@ void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr, packet->udp.len = htobe16(len - DHCP_IP_SIZE); packet->ip.check = packet->udp.len; - packet->udp.check = dhcp_packet_checksum(&packet->ip.ttl, len - 8); + packet->udp.check = ip_checksum(&packet->ip.ttl, len - 8); packet->ip.ttl = IPDEFTTL; packet->ip.check = 0; - packet->ip.check = dhcp_packet_checksum((uint8_t*)&packet->ip, DHCP_IP_SIZE); + packet->ip.check = ip_checksum(&packet->ip, DHCP_IP_SIZE); } int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, uint16_t port) { @@ -193,7 +158,7 @@ int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, ui if all the other checks have passed */ - if (dhcp_packet_checksum((uint8_t*)&packet->ip, hdrlen)) + if (ip_checksum(&packet->ip, hdrlen)) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "ignoring packet: invalid IP checksum"); @@ -201,8 +166,7 @@ int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, ui packet->ip.check = packet->udp.len; packet->ip.ttl = 0; - if (dhcp_packet_checksum(&packet->ip.ttl, - be16toh(packet->udp.len) + 12)) + if (ip_checksum(&packet->ip.ttl, be16toh(packet->udp.len) + 12)) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "ignoring packet: invalid UDP checksum"); } diff --git a/src/libsystemd-network/dhcp-packet.h b/src/libsystemd-network/dhcp-packet.h index 967bd5d89df71..8a56383adda61 100644 --- a/src/libsystemd-network/dhcp-packet.h +++ b/src/libsystemd-network/dhcp-packet.h @@ -23,8 +23,6 @@ int dhcp_message_init( size_t optlen, size_t *ret_optoffset); -uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len); - void dhcp_packet_append_ip_headers( DHCPPacket *packet, be32_t source_addr, diff --git a/src/libsystemd-network/ip-util.c b/src/libsystemd-network/ip-util.c new file mode 100644 index 0000000000000..dd7e872b2f001 --- /dev/null +++ b/src/libsystemd-network/ip-util.c @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "iovec-util.h" +#include "ip-util.h" + +static uint64_t complement_sum(uint64_t a, uint64_t b) { + /* This performs one's complement addition (end-around carry). See RFC1071. */ + if (a <= UINT64_MAX - b) + return a + b; + + return a - (UINT64_MAX - b); +} + +static uint64_t checksum_iov(uint64_t sum, const struct iovec *iov) { + assert(iov); + + for (struct iovec i = *iov; iovec_is_set(&i); iovec_inc(&i, sizeof(uint64_t))) { + uint64_t t = 0; + memcpy(&t, i.iov_base, MIN(i.iov_len, sizeof(uint64_t))); + sum = complement_sum(sum, t); + } + + return sum; +} + +static uint16_t checksum_finalize(uint64_t sum) { + while ((sum >> 16) != 0) + sum = (sum & 0xffffu) + (sum >> 16); + + return ~sum; +} + +uint16_t ip_checksum(const void *buf, size_t len) { + /* See RFC1071 */ + return checksum_finalize(checksum_iov(0, &IOVEC_MAKE(buf, len))); +} diff --git a/src/libsystemd-network/ip-util.h b/src/libsystemd-network/ip-util.h new file mode 100644 index 0000000000000..dd11afbadac25 --- /dev/null +++ b/src/libsystemd-network/ip-util.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-forward.h" + +uint16_t ip_checksum(const void *buf, size_t len); diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index d1e13d99b536f..b0443c3695206 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -12,6 +12,7 @@ libsystemd_network_sources = files( 'dhcp6-protocol.c', 'icmp6-packet.c', 'icmp6-util.c', + 'ip-util.c', 'lldp-neighbor.c', 'lldp-network.c', 'ndisc-option.c', @@ -84,6 +85,9 @@ executables += [ network_test_template + { 'sources' : files('test-dhcp6-client.c'), }, + network_test_template + { + 'sources' : files('test-ip-util.c'), + }, network_test_template + { 'sources' : files('test-ipv4ll-manual.c'), 'type' : 'manual', diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index 97802e2c164e4..b2cadc07e5b1e 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -17,9 +17,9 @@ #include "dhcp-duid-internal.h" #include "dhcp-network.h" #include "dhcp-option.h" -#include "dhcp-packet.h" #include "ether-addr-util.h" #include "fd-util.h" +#include "ip-util.h" #include "log.h" #include "tests.h" @@ -107,16 +107,6 @@ TEST(dhcp_client_anonymize) { ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, 101)); } -TEST(dhcp_packet_checksum) { - uint8_t buf[20] = { - 0x45, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, - 0x40, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xff, 0xff, 0xff, 0xff - }; - - ASSERT_EQ(dhcp_packet_checksum(buf, 20), be16toh(0x78ae)); -} - TEST(dhcp_identifier_set_iaid) { uint32_t iaid_legacy; be32_t iaid; @@ -181,13 +171,13 @@ int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, const discover->ip.ttl = 0; discover->ip.check = discover->udp.len; - udp_check = ~dhcp_packet_checksum(&discover->ip.ttl, len - 8); + udp_check = ~ip_checksum(&discover->ip.ttl, len - 8); ASSERT_EQ(udp_check, 0xffff); discover->ip.ttl = IPDEFTTL; discover->ip.check = ip_check; - ip_check = ~dhcp_packet_checksum((uint8_t*) &discover->ip, sizeof(discover->ip)); + ip_check = ~ip_checksum((uint8_t*) &discover->ip, sizeof(discover->ip)); ASSERT_EQ(ip_check, 0xffff); ASSERT_NE(discover->dhcp.xid, 0u); diff --git a/src/libsystemd-network/test-ip-util.c b/src/libsystemd-network/test-ip-util.c new file mode 100644 index 0000000000000..58adb8f48ef26 --- /dev/null +++ b/src/libsystemd-network/test-ip-util.c @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "ip-util.h" +#include "tests.h" + +TEST(ip_checksum) { + uint8_t buf[20] = { + 0x45, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff + }; + + ASSERT_EQ(ip_checksum(buf, 20), be16toh(0x78ae)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); From e8497d682586d3b6d1075b6fb613a0487639d21d Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 15 Mar 2026 13:57:33 +0900 Subject: [PATCH 1270/1296] ip-util: introduce udp_packet_build() Then make dhcp_packet_append_ip_headers() just a wrapper of the new function. Currently, the wrapper is inefficient, but will be removed in a later commit. --- src/libsystemd-network/dhcp-client-send.c | 4 +- src/libsystemd-network/dhcp-packet.c | 59 ++++++----- src/libsystemd-network/dhcp-packet.h | 2 +- src/libsystemd-network/ip-util.c | 119 ++++++++++++++++++++++ src/libsystemd-network/ip-util.h | 19 ++++ src/libsystemd-network/sd-dhcp-server.c | 14 ++- 6 files changed, 187 insertions(+), 30 deletions(-) diff --git a/src/libsystemd-network/dhcp-client-send.c b/src/libsystemd-network/dhcp-client-send.c index a802cb5e86744..56306fbf53647 100644 --- a/src/libsystemd-network/dhcp-client-send.c +++ b/src/libsystemd-network/dhcp-client-send.c @@ -98,7 +98,7 @@ int dhcp_client_send_raw( fd_close = fd; } - dhcp_packet_append_ip_headers( + r = dhcp_packet_append_ip_headers( packet, INADDR_ANY, client->port, @@ -106,6 +106,8 @@ int dhcp_client_send_raw( client->server_port, sizeof(DHCPPacket) + optoffset, client->ip_service_type); + if (r < 0) + return r; r = dhcp_network_send_raw_socket( fd, diff --git a/src/libsystemd-network/dhcp-packet.c b/src/libsystemd-network/dhcp-packet.c index 3b17ad8ac427a..bc8f948d2ca4d 100644 --- a/src/libsystemd-network/dhcp-packet.c +++ b/src/libsystemd-network/dhcp-packet.c @@ -7,6 +7,8 @@ #include "dhcp-option.h" #include "dhcp-packet.h" +#include "iovec-util.h" +#include "iovec-wrapper.h" #include "ip-util.h" #include "log.h" #include "memory-util.h" @@ -79,33 +81,40 @@ int dhcp_message_init( return 0; } -void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr, - uint16_t source_port, be32_t destination_addr, - uint16_t destination_port, uint16_t len, int ip_service_type) { - packet->ip.version = IPVERSION; - packet->ip.ihl = DHCP_IP_SIZE / 4; - packet->ip.tot_len = htobe16(len); - - if (ip_service_type >= 0) - packet->ip.tos = ip_service_type; - else - packet->ip.tos = IPTOS_CLASS_CS6; - - packet->ip.protocol = IPPROTO_UDP; - packet->ip.saddr = source_addr; - packet->ip.daddr = destination_addr; - - packet->udp.source = htobe16(source_port); - packet->udp.dest = htobe16(destination_port); - - packet->udp.len = htobe16(len - DHCP_IP_SIZE); +int dhcp_packet_append_ip_headers( + DHCPPacket *packet, + be32_t source_addr, + uint16_t source_port, + be32_t destination_addr, + uint16_t destination_port, + uint16_t len, + int ip_service_type) { + + struct iphdr ip; + struct udphdr udp; + int r; - packet->ip.check = packet->udp.len; - packet->udp.check = ip_checksum(&packet->ip.ttl, len - 8); + assert(packet); + assert(len > offsetof(DHCPPacket, dhcp)); + + r = udp_packet_build( + source_addr, + source_port, + destination_addr, + destination_port, + ip_service_type, + &(struct iovec_wrapper) { + .iovec = &IOVEC_MAKE(&packet->dhcp, len - offsetof(DHCPPacket, dhcp)), + .count = 1, + }, + &ip, + &udp); + if (r < 0) + return r; - packet->ip.ttl = IPDEFTTL; - packet->ip.check = 0; - packet->ip.check = ip_checksum(&packet->ip, DHCP_IP_SIZE); + packet->ip = ip; + packet->udp = udp; + return 0; } int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, uint16_t port) { diff --git a/src/libsystemd-network/dhcp-packet.h b/src/libsystemd-network/dhcp-packet.h index 8a56383adda61..90ea2caac987a 100644 --- a/src/libsystemd-network/dhcp-packet.h +++ b/src/libsystemd-network/dhcp-packet.h @@ -23,7 +23,7 @@ int dhcp_message_init( size_t optlen, size_t *ret_optoffset); -void dhcp_packet_append_ip_headers( +int dhcp_packet_append_ip_headers( DHCPPacket *packet, be32_t source_addr, uint16_t source, diff --git a/src/libsystemd-network/ip-util.c b/src/libsystemd-network/ip-util.c index dd7e872b2f001..fcc22e15e3578 100644 --- a/src/libsystemd-network/ip-util.c +++ b/src/libsystemd-network/ip-util.c @@ -3,8 +3,22 @@ #include #include "iovec-util.h" +#include "iovec-wrapper.h" #include "ip-util.h" +union iphdr_union { + struct iphdr ip; + uint8_t buf[15 * 4]; /* ip->ihl is 4 bits, hence max length is 15 * 4 */ +}; + +struct udp_pseudo_header { + be32_t saddr; + be32_t daddr; + uint8_t unused; + uint8_t protocol; + be16_t len; +} _packed_; + static uint64_t complement_sum(uint64_t a, uint64_t b) { /* This performs one's complement addition (end-around carry). See RFC1071. */ if (a <= UINT64_MAX - b) @@ -36,3 +50,108 @@ uint16_t ip_checksum(const void *buf, size_t len) { /* See RFC1071 */ return checksum_finalize(checksum_iov(0, &IOVEC_MAKE(buf, len))); } + +static uint16_t iphdr_checksum(const union iphdr_union *ip) { + assert(ip); + return ip_checksum(ip, ip->ip.ihl * 4); +} + +static uint16_t udphdr_checksum( + be32_t saddr, + be32_t daddr, + const struct udphdr *udp, + const struct iovec_wrapper *payload) { + + assert(udp); + assert(payload); + + /* RFC 768 */ + + struct udp_pseudo_header pseudo = { + .saddr = saddr, + .daddr = daddr, + .protocol = IPPROTO_UDP, + .len = udp->len, + }; + + uint64_t sum = 0; + sum = checksum_iov(sum, &IOVEC_MAKE(&pseudo, sizeof(struct udp_pseudo_header))); + sum = checksum_iov(sum, &IOVEC_MAKE(udp, sizeof(struct udphdr))); + + uint8_t buf[2] = {}; + bool odd = false; + FOREACH_ARRAY(i, payload->iovec, payload->count) { + if (!iovec_is_set(i)) + continue; + + struct iovec v = *i; + if (odd) { + buf[1] = *(uint8_t*) v.iov_base; + sum = checksum_iov(sum, &IOVEC_MAKE(buf, 2)); + iovec_inc(&v, 1); + } + + odd = v.iov_len % 2; + if (odd) { + buf[0] = ((uint8_t*) v.iov_base)[v.iov_len - 1]; + v.iov_len--; + } + sum = checksum_iov(sum, &v); + } + if (odd) { + buf[1] = 0; + sum = checksum_iov(sum, &IOVEC_MAKE(buf, 2)); + } + + return checksum_finalize(sum); +} + +int udp_packet_build( + be32_t source_addr, + uint16_t source_port, + be32_t destination_addr, + uint16_t destination_port, + int ip_service_type, + const struct iovec_wrapper *payload, + struct iphdr *ret_iphdr, + struct udphdr *ret_udphdr) { + + assert(payload); + assert(ret_iphdr); + assert(ret_udphdr); + + /* When ip_service_type is negative, IPTOS_CLASS_CS6 will be used. Otherwise, it must be a valid TOS, + * hence must be in 0…255. Here, we only check its range. */ + if (ip_service_type > UINT8_MAX) + return -EINVAL; + + /* iphdr.tot_len is uint16_t, hence the total length must be <= UINT16_MAX. */ + size_t len = iovw_size(payload); + if (len > UDP_PAYLOAD_MAX_SIZE) + return -E2BIG; + + union iphdr_union ip = { + .ip.version = IPVERSION, + .ip.ihl = sizeof(struct iphdr) / 4, + .ip.tos = ip_service_type >= 0 ? ip_service_type : IPTOS_CLASS_CS6, + .ip.tot_len = htobe16(sizeof(struct iphdr) + sizeof(struct udphdr) + len), + .ip.ttl = IPDEFTTL, + .ip.protocol = IPPROTO_UDP, + .ip.saddr = source_addr, + .ip.daddr = destination_addr, + }; + + ip.ip.check = iphdr_checksum(&ip); + + struct udphdr udp = { + .source = htobe16(source_port), + .dest = htobe16(destination_port), + .len = htobe16(sizeof(struct udphdr) + len), + }; + + udp.check = udphdr_checksum(source_addr, destination_addr, &udp, payload); + + *ret_iphdr = ip.ip; + *ret_udphdr = udp; + return 0; +} diff --git a/src/libsystemd-network/ip-util.h b/src/libsystemd-network/ip-util.h index dd11afbadac25..b42ff38af2366 100644 --- a/src/libsystemd-network/ip-util.h +++ b/src/libsystemd-network/ip-util.h @@ -1,6 +1,25 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include +#include + #include "sd-forward.h" +#include "sparse-endian.h" + +/* This is a maximal UDP payload size in a packet when its IP header does not contain options. When a packet + * contains some IP options, then of course the allowed UDP payload size in the packet becomes smaller. */ +#define UDP_PAYLOAD_MAX_SIZE (UINT16_MAX - sizeof(struct iphdr) - sizeof(struct udphdr)) + uint16_t ip_checksum(const void *buf, size_t len); + +int udp_packet_build( + be32_t source_addr, + uint16_t source_port, + be32_t destination_addr, + uint16_t destination_port, + int ip_service_type, + const struct iovec_wrapper *payload, + struct iphdr *ret_iphdr, + struct udphdr *ret_udphdr); diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index fa0b830196983..ba2b035821918 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -322,6 +322,7 @@ static int dhcp_server_send_unicast_raw( .ll.sll_ifindex = server->ifindex, .ll.sll_halen = hlen, }; + int r; assert(server); assert(server->ifindex > 0); @@ -336,9 +337,16 @@ static int dhcp_server_send_unicast_raw( if (len > UINT16_MAX) return -EOVERFLOW; - dhcp_packet_append_ip_headers(packet, server->address, DHCP_PORT_SERVER, - packet->dhcp.yiaddr, - DHCP_PORT_CLIENT, len, -1); + r = dhcp_packet_append_ip_headers( + packet, + server->address, + DHCP_PORT_SERVER, + packet->dhcp.yiaddr, + DHCP_PORT_CLIENT, + len, + /* ip_service_type= */ -1); + if (r < 0) + return r; return dhcp_network_send_raw_socket(server->fd_raw, &link, packet, len); } From 11def23206d68d4a37b4ddc6b5a1b67c49554b30 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 17 Mar 2026 00:11:25 +0900 Subject: [PATCH 1271/1296] ip-util: introduce udp_packet_verify() This is mostly equivalent to dhcp_packet_verify_headers(), but - it optionally returns the UDP payload as iovec, and - supports IP header with options, - check packet length more strictly. --- src/libsystemd-network/dhcp-packet.c | 65 +---------- src/libsystemd-network/ip-util.c | 94 +++++++++++++++ src/libsystemd-network/ip-util.h | 6 + src/libsystemd-network/test-ip-util.c | 157 ++++++++++++++++++++++++++ 4 files changed, 258 insertions(+), 64 deletions(-) diff --git a/src/libsystemd-network/dhcp-packet.c b/src/libsystemd-network/dhcp-packet.c index bc8f948d2ca4d..27b09a25bbb4f 100644 --- a/src/libsystemd-network/dhcp-packet.c +++ b/src/libsystemd-network/dhcp-packet.c @@ -10,7 +10,6 @@ #include "iovec-util.h" #include "iovec-wrapper.h" #include "ip-util.h" -#include "log.h" #include "memory-util.h" #define DHCP_CLIENT_MIN_OPTIONS_SIZE 312 @@ -118,67 +117,5 @@ int dhcp_packet_append_ip_headers( } int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, uint16_t port) { - size_t hdrlen; - - assert(packet); - - if (len < sizeof(DHCPPacket)) - return 0; - - /* IP */ - - if (packet->ip.version != IPVERSION) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: not IPv4"); - - if (packet->ip.ihl < 5) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: IPv4 IHL (%i words) invalid", - packet->ip.ihl); - - hdrlen = packet->ip.ihl * 4; - if (hdrlen < 20) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: IPv4 IHL (%zu bytes) smaller than minimum (20 bytes)", - hdrlen); - - if (len < hdrlen) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: packet (%zu bytes) smaller than expected (%zu) by IP header", - len, hdrlen); - - /* UDP */ - - if (packet->ip.protocol != IPPROTO_UDP) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: not UDP"); - - if (len < hdrlen + be16toh(packet->udp.len)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: packet (%zu bytes) smaller than expected (%zu) by UDP header", - len, hdrlen + be16toh(packet->udp.len)); - - if (be16toh(packet->udp.dest) != port) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: to port %u, which is not the DHCP client port (%u)", - be16toh(packet->udp.dest), port); - - /* checksums - computing these is relatively expensive, so only do it - if all the other checks have passed - */ - - if (ip_checksum(&packet->ip, hdrlen)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: invalid IP checksum"); - - if (checksum && packet->udp.check) { - packet->ip.check = packet->udp.len; - packet->ip.ttl = 0; - - if (ip_checksum(&packet->ip.ttl, be16toh(packet->udp.len) + 12)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: invalid UDP checksum"); - } - - return 0; + return udp_packet_verify(&IOVEC_MAKE(packet, len), port, checksum, /* ret_payload= */ NULL); } diff --git a/src/libsystemd-network/ip-util.c b/src/libsystemd-network/ip-util.c index fcc22e15e3578..3f062a7ef004e 100644 --- a/src/libsystemd-network/ip-util.c +++ b/src/libsystemd-network/ip-util.c @@ -5,6 +5,7 @@ #include "iovec-util.h" #include "iovec-wrapper.h" #include "ip-util.h" +#include "log.h" union iphdr_union { struct iphdr ip; @@ -155,3 +156,96 @@ int udp_packet_build( *ret_udphdr = udp; return 0; } + +int udp_packet_verify( + const struct iovec *packet, + uint16_t port, + bool checksum, + struct iovec *ret_payload) { + + assert(packet); + + /* This verifies IP and UDP packet headers and optionally returns the UDP payload. */ + + /* IP */ + if (packet->iov_len < sizeof(struct iphdr)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: packet (%zu bytes) smaller than minimum IP header (%zu bytes), ignoring packet.", + packet->iov_len, sizeof(struct iphdr)); + + const union iphdr_union *ip = (const union iphdr_union*) packet->iov_base; + if (ip->ip.version != IPVERSION) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: packet is not IPv4, ignoring packet."); + + size_t iphdrlen = ip->ip.ihl * 4; + if (iphdrlen < sizeof(struct iphdr)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: IP header size (%zu bytes) smaller than minimum (%zu bytes), ignoring packet.", + iphdrlen, sizeof(struct iphdr)); + + if (packet->iov_len < iphdrlen) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: packet (%zu bytes) smaller than IP header size (%zu bytes), ignoring packet.", + packet->iov_len, iphdrlen); + + size_t totlen = be16toh(ip->ip.tot_len); + if (totlen < iphdrlen) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: packet size (%zu bytes) by IP header is smaller than the IP header size (%zu), ignoring packet.", + totlen, iphdrlen); + if (packet->iov_len < totlen) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: packet (%zu bytes) smaller than expected (%zu) by IP header, ignoring packet.", + packet->iov_len, totlen); + + if (ip->ip.protocol != IPPROTO_UDP) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: not UDP, ignoring packet."); + + if (iphdr_checksum(ip) != 0) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: invalid IP checksum, ignoring packet."); + + /* UDP */ + if (totlen < iphdrlen + sizeof(struct udphdr)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "UDP: packet (%zu bytes) smaller than IP header + UDP header, ignoring packet.", + totlen); + + const struct udphdr *udp = (const struct udphdr*) ((const uint8_t*) packet->iov_base + iphdrlen); + size_t udplen = be16toh(udp->len); + if (udplen < sizeof(struct udphdr)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "UDP: UDP datagram (%zu bytes) smaller than UDP header (%zu bytes), ignoring packet.", + udplen, sizeof(struct udphdr)); + + if (totlen != iphdrlen + udplen) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "UDP: packet length by IP header (%zu bytes) does not match with the one by UDP header " + "(IP header %zu bytes + UDP %zu bytes = %zu bytes), ignoring packet.", + totlen, iphdrlen, udplen, iphdrlen + udplen); + + if (be16toh(udp->dest) != port) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "UDP: to port %u, which is not the expected port (%u), ignoring packet.", + be16toh(udp->dest), port); + + /* Calculate the UDP payload length from the UDP header (udplen), rather than the input packet length + * (len). The packet may contain garbage at the end. */ + struct iovec payload = IOVEC_MAKE( + (const uint8_t*) packet->iov_base + iphdrlen + sizeof(struct udphdr), + udplen - sizeof(struct udphdr)); + if (checksum && udp->check != 0 && + udphdr_checksum(ip->ip.saddr, ip->ip.daddr, udp, + &(struct iovec_wrapper) { + .iovec = &payload, + .count = 1, + }) != 0) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "UDP: invalid UDP checksum, ignoring packet."); + + if (ret_payload) + *ret_payload = payload; + return 0; +} diff --git a/src/libsystemd-network/ip-util.h b/src/libsystemd-network/ip-util.h index b42ff38af2366..fbc602ace5958 100644 --- a/src/libsystemd-network/ip-util.h +++ b/src/libsystemd-network/ip-util.h @@ -23,3 +23,9 @@ int udp_packet_build( const struct iovec_wrapper *payload, struct iphdr *ret_iphdr, struct udphdr *ret_udphdr); + +int udp_packet_verify( + const struct iovec *packet, + uint16_t port, + bool checksum, + struct iovec *ret_payload); diff --git a/src/libsystemd-network/test-ip-util.c b/src/libsystemd-network/test-ip-util.c index 58adb8f48ef26..cf233ca91c575 100644 --- a/src/libsystemd-network/test-ip-util.c +++ b/src/libsystemd-network/test-ip-util.c @@ -1,6 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "iovec-util.h" +#include "iovec-wrapper.h" #include "ip-util.h" +#include "random-util.h" #include "tests.h" TEST(ip_checksum) { @@ -13,4 +16,158 @@ TEST(ip_checksum) { ASSERT_EQ(ip_checksum(buf, 20), be16toh(0x78ae)); } +static void create_packet(struct iphdr *ip, struct udphdr *udp, struct iovec_wrapper *payload, struct iovec *ret) { + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + ASSERT_OK(iovw_put(&iovw, ip, sizeof(struct iphdr))); + ASSERT_OK(iovw_put(&iovw, udp, sizeof(struct udphdr))); + ASSERT_OK(iovw_put_iovw(&iovw, payload)); + ASSERT_OK(iovw_concat(&iovw, ret)); +} + +TEST(udp_packet_build_and_verify) { + size_t n = random_u64_range(20) + 20; + + _cleanup_(iovw_done_free) struct iovec_wrapper payload = {}; + size_t i; + FOREACH_ARGUMENT(i, 1, 0, 1, 1, 3, 1, 2, 1, n, n, n + 1, n + 1, n + 2, n + 3, n + 4, n + 5, n + 6) { + struct iovec tmp = {}; + ASSERT_OK(random_bytes_allocate_iovec(i, &tmp)); + ASSERT_OK(iovw_consume_iov(&payload, &tmp)); + } + + struct iphdr ip; + struct udphdr udp; + ASSERT_OK(udp_packet_build( + /* source_addr= */ htobe32(0xC0020001), + /* source_port= */ 42, + /* destination_addr= */ htobe32(0xC0020002), + /* destination_port= */ 43, + /* ip_service_type= */ 7, + &payload, + &ip, + &udp)); + + _cleanup_(iovec_done) struct iovec joined = {}; + ASSERT_OK(iovw_concat(&payload, &joined)); + + struct iphdr ip2; + struct udphdr udp2; + ASSERT_OK(udp_packet_build( + /* source_addr= */ htobe32(0xC0020001), + /* source_port= */ 42, + /* destination_addr= */ htobe32(0xC0020002), + /* destination_port= */ 43, + /* ip_service_type= */ 7, + &(struct iovec_wrapper) { + .iovec = &joined, + .count = 1, + }, + &ip2, + &udp2)); + + ASSERT_EQ(memcmp(&ip, &ip2, sizeof(struct iphdr)), 0); + ASSERT_EQ(memcmp(&udp, &udp2, sizeof(struct udphdr)), 0); + + _cleanup_(iovec_done) struct iovec packet = {}; + create_packet(&ip, &udp, &payload, &packet); + + struct iovec iov; + ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &joined)); + ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ true, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &joined)); + + /* UDP port mismatch */ + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 42, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* truncated packet */ + ASSERT_ERROR(udp_packet_verify(&IOVEC_MAKE(packet.iov_base, packet.iov_len - 1), + /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* bad IP version */ + struct iphdr badip = ip; + badip.version = 6; + iovec_done(&packet); + create_packet(&badip, &udp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* bad IP header size */ + badip = ip; + badip.ihl = 1; + iovec_done(&packet); + create_packet(&badip, &udp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* packet size in IP header is smaller than IP header size */ + badip = ip; + badip.tot_len = htobe16(1); + iovec_done(&packet); + create_packet(&badip, &udp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* packet size in IP header is larger than the packet size */ + badip = ip; + badip.tot_len = htobe16(be16toh(ip.tot_len) + 1); + iovec_done(&packet); + create_packet(&badip, &udp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* IP protocol mismatch */ + badip = ip; + badip.protocol = IPPROTO_TCP; + iovec_done(&packet); + create_packet(&badip, &udp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* bad IP header checksum */ + badip = ip; + badip.check = ~ip.check; + iovec_done(&packet); + create_packet(&badip, &udp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* UDP length is smaller than the UDP header size */ + struct udphdr badudp = udp; + badudp.len = htobe16(1); + iovec_done(&packet); + create_packet(&ip, &badudp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* UDP length is smaller than the packet size */ + badudp = udp; + badudp.len = htobe16(be16toh(udp.len) - 1); + iovec_done(&packet); + create_packet(&ip, &badudp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* UDP length is larger than the packet size */ + badudp = udp; + badudp.len = htobe16(be16toh(udp.len) + 1); + iovec_done(&packet); + create_packet(&ip, &badudp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* bad UDP checksum */ + badudp = udp; + if (udp.check != UINT16_MAX) + badudp.check = ~udp.check; + else + badudp.check = 0xdeadu; + iovec_done(&packet); + create_packet(&ip, &badudp, &payload, &packet); + ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &joined)); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ true, /* ret_payload= */ NULL), EBADMSG); + + /* missing UDP checksum */ + badudp = udp; + badudp.check = 0; + iovec_done(&packet); + create_packet(&ip, &badudp, &payload, &packet); + ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &joined)); + ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ true, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &joined)); +} + DEFINE_TEST_MAIN(LOG_DEBUG); From 3b4e01637f45dad8bf9fde6da05ad43a14a5e92b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 14 Apr 2026 08:12:49 +0900 Subject: [PATCH 1272/1296] ip-util: define IPV4_MIN_REASSEMBLY_SIZE The number will be later used at several places. --- src/libsystemd-network/ip-util.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libsystemd-network/ip-util.h b/src/libsystemd-network/ip-util.h index fbc602ace5958..31fa4631c35f9 100644 --- a/src/libsystemd-network/ip-util.h +++ b/src/libsystemd-network/ip-util.h @@ -8,6 +8,12 @@ #include "sparse-endian.h" +/* RFC 791 + * Fragmentation and Reassembly. + * Every internet destination must be able to receive a datagram of 576 octets either in one piece or in + * fragments to be reassembled. */ +#define IPV4_MIN_REASSEMBLY_SIZE 576u + /* This is a maximal UDP payload size in a packet when its IP header does not contain options. When a packet * contains some IP options, then of course the allowed UDP payload size in the packet becomes smaller. */ #define UDP_PAYLOAD_MAX_SIZE (UINT16_MAX - sizeof(struct iphdr) - sizeof(struct udphdr)) From 6897562ea04c9a70622d5388c99e3e2ccf62e829 Mon Sep 17 00:00:00 2001 From: fecet Date: Sat, 25 Apr 2026 14:48:19 +0800 Subject: [PATCH 1273/1296] hwdb: sensor: add accel mount matrix for GPD WIN 5 The WIN 5 (DMI product G1618-05) ships the same BMI0160 accelerometer with the same physical mounting as the Win Max 2 (G1619-04), so reuse its mount matrix. Verified on hardware: without the matrix iio-sensor-proxy reports AccelerometerOrientation=normal regardless of physical pose, and applying the G1619-04 matrix makes orientation transitions (normal / left-up / right-up / bottom-up) track the device correctly. --- hwdb.d/60-sensor.hwdb | 1 + 1 file changed, 1 insertion(+) diff --git a/hwdb.d/60-sensor.hwdb b/hwdb.d/60-sensor.hwdb index 76f727573dadf..a3f243a660861 100644 --- a/hwdb.d/60-sensor.hwdb +++ b/hwdb.d/60-sensor.hwdb @@ -515,6 +515,7 @@ sensor:modalias:acpi:KIOX000A:*:dmi:bvnAmericanMegatrendsInc.:bvr5.11:bd03/20/20 sensor:modalias:acpi:KIOX000A:*:dmi:bvnAmericanMegatrendsInc.:bvr5.11:bd05/25/2017:*:svnDefaultstring:pnDefaultstring:pvrDefaultstring:rvnAMICorporation:rnDefaultstring:rvrDefaultstring:cvnDefaultstring:ct3:cvrDefaultstring:* ACCEL_LOCATION=base +sensor:modalias:acpi:BMI0160:*:dmi:*:svnGPD:pnG1618-05:* # WIN 5 sensor:modalias:acpi:BMI0160:*:dmi:*:svnGPD:pnG1619-04:* # Win Max 2 ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, 1 From 2f42b19c0f7b553d343b69691a5e0f1225f5a47d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 25 Apr 2026 11:58:50 +0200 Subject: [PATCH 1274/1296] v4l_id: convert to the new option parser The commandline check is tightened to reject extra arguments. Co-developed-by: Claude Opus 4.7 --- src/udev/v4l_id/v4l_id.c | 59 ++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/src/udev/v4l_id/v4l_id.c b/src/udev/v4l_id/v4l_id.c index dc4d41af2bfab..c5bfa935b2536 100644 --- a/src/udev/v4l_id/v4l_id.c +++ b/src/udev/v4l_id/v4l_id.c @@ -4,7 +4,6 @@ */ #include -#include #include #include #include @@ -12,43 +11,55 @@ #include "build.h" #include "errno-util.h" #include "fd-util.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "string-util.h" +#include "strv.h" #include "utf8.h" static const char *arg_device = NULL; +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_cmdline("[OPTIONS...] DEVICE"); + help_abstract("Video4Linux device identification."); + help_section("Options:"); + + return table_print_or_warn(options); +} + static int parse_argv(int argc, char *argv[]) { - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'v' }, - {} - }; - int c; - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + assert(argc >= 0); + assert(argv); + + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - printf("%s [OPTIONS...] DEVICE\n\n" - "Video4Linux device identification.\n\n" - " -h --help Show this help text\n" - " --version Show package version\n", - program_invocation_short_name); - return 0; - case 'v': + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: return version(); - case '?': - return -EINVAL; - default: - assert_not_reached(); } - if (!argv[optind]) + char **args = option_parser_get_args(&state); + if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "DEVICE argument missing."); + "Need exactly one DEVICE argument."); - arg_device = argv[optind]; + arg_device = args[0]; return 1; } From 0a8560eed873a5f89487630a19db550fdbee3c15 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 23 Apr 2026 09:29:20 +0200 Subject: [PATCH 1275/1296] varlink-util: add generic code that calls out to a 'hook' directory of sockets --- src/libsystemd/sd-varlink/varlink-util.c | 168 +++++++++++++++++++++++ src/libsystemd/sd-varlink/varlink-util.h | 2 + src/test/test-varlink.c | 109 +++++++++++++++ 3 files changed, 279 insertions(+) diff --git a/src/libsystemd/sd-varlink/varlink-util.c b/src/libsystemd/sd-varlink/varlink-util.c index 8b61627c562c9..475bec40d844f 100644 --- a/src/libsystemd/sd-varlink/varlink-util.c +++ b/src/libsystemd/sd-varlink/varlink-util.c @@ -1,10 +1,17 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-event.h" +#include "sd-varlink.h" + #include "alloc-util.h" #include "errno-util.h" +#include "fd-util.h" #include "log.h" +#include "path-util.h" #include "pidref.h" +#include "recurse-dir.h" #include "set.h" +#include "socket-util.h" #include "string-util.h" #include "varlink-internal.h" #include "varlink-util.h" @@ -214,3 +221,164 @@ DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( trivial_compare_func, sd_varlink, sd_varlink_unref); + +static int varlink_finish_idle(Set *s) { + int r; + + sd_varlink *vl; + bool fully_idle = true; + SET_FOREACH(vl, s) { + r = sd_varlink_is_idle(vl); + if (r < 0) + return r; + if (r == 0) + fully_idle = false; + else { + /* Idle? Then we can close the connection, and release some resources. */ + assert_se(set_remove(s, vl) == vl); + vl = sd_varlink_close_unref(vl); + } + } + + return fully_idle; +} + +#define VARLINK_EXECUTE_SOCKETS_MAX 255 + +ssize_t varlink_execute_directory( + const char *path, + const char *method, + sd_json_variant *parameters, + bool more, + usec_t timeout_usec, + sd_varlink_reply_t reply, + void *userdata) { + + int r; + + assert(path); + assert(method); + + /* Invokes the specified method on all Varlink sockets in the specified directory. Any reply + * will be dispatched to the reply callback. Blocks until the last reply has come in. + * + * Returns how many sockets were contacted. + * + * Usecase for all of this: hook directories, where components can link their sockets into to get + * notified about certain system events. */ + + _cleanup_close_ int fd = open(path, O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + return log_debug_errno(errno, "Failed to open '%s': %m", path); + + _cleanup_free_ DirectoryEntries *dentries = NULL; + r = readdir_all(fd, RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &dentries); + if (r < 0) + return log_debug_errno(r, "Failed to enumerate '%s': %m", path); + + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(set_freep) Set *links = NULL; + size_t t = 0; + FOREACH_ARRAY(dp, dentries->entries, dentries->n_entries) { + struct dirent *de = *dp; + + if (de->d_type != DT_SOCK) + continue; + + t++; + + _cleanup_free_ char *j = path_join(path, de->d_name); + if (!j) + return log_oom_debug(); + + if (set_size(links) >= VARLINK_EXECUTE_SOCKETS_MAX) { + log_debug("Too many sockets (%zu) in directory, skipping '%s'.", t, j); + continue; + } + + _cleanup_close_ int socket_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (socket_fd < 0) + return log_debug_errno(errno, "Failed to allocate AF_UNIX/SOCK_STREAM socket: %m"); + + r = connect_unix_path(socket_fd, fd, de->d_name); + if (r < 0) { + log_debug_errno(r, "Failed to connect to '%s', ignoring: %m", j); + continue; + } + + if (!event) { + r = sd_event_new(&event); + if (r < 0) + return log_debug_errno(r, "Failed to allocate event loop: %m"); + } + + _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; + r = sd_varlink_connect_fd(&link, socket_fd); + if (r < 0) + return log_debug_errno(r, "Failed to allocate Varlink connection: %m"); + + TAKE_FD(socket_fd); + + r = sd_varlink_attach_event(link, event, /* priority= */ 0); + if (r < 0) + return log_debug_errno(r, "Failed to attach varlink connection to event loop: %m"); + + sd_varlink_set_userdata(link, userdata); + + r = sd_varlink_bind_reply(link, reply); + if (r < 0) + return log_debug_errno(r, "Failed to bind reply callback: %m"); + + r = sd_varlink_set_description(link, j); + if (r < 0) + return log_debug_errno(r, "Failed to set description: %m"); + + r = sd_varlink_set_relative_timeout(link, timeout_usec); + if (r < 0) + return r; + + if (more) + r = sd_varlink_observe(link, method, parameters); + else + r = sd_varlink_invoke(link, method, parameters); + if (r < 0) + return log_debug_errno(r, "Failed to enqueue message on Varlink connection: %m"); + + if (set_ensure_consume(&links, &varlink_hash_ops, TAKE_PTR(link)) < 0) + return log_oom_debug(); + } + + size_t c = set_size(links); + + for (;;) { + if (event) { + int state = sd_event_get_state(event); + if (state < 0) + return state; + if (state == SD_EVENT_FINISHED) { + int x; + r = sd_event_get_exit_code(event, &x); + if (r < 0) + return r; + if (x != 0) + return x; + + break; + } + } + + r = varlink_finish_idle(links); + if (r < 0) + return r; + if (r > 0) + break; /* idle, we are done */ + + assert(event); + + r = sd_event_run(event, /* timeout= */ UINT64_MAX); + if (r < 0) + return r; + } + + return (ssize_t) c; +} diff --git a/src/libsystemd/sd-varlink/varlink-util.h b/src/libsystemd/sd-varlink/varlink-util.h index ba0f23225356a..d6ecb03c54533 100644 --- a/src/libsystemd/sd-varlink/varlink-util.h +++ b/src/libsystemd/sd-varlink/varlink-util.h @@ -29,3 +29,5 @@ int varlink_server_new( int varlink_check_privileged_peer(sd_varlink *vl); extern const struct hash_ops varlink_hash_ops; + +ssize_t varlink_execute_directory(const char *path, const char *method, sd_json_variant *parameters, bool more, usec_t timeout_usec, sd_varlink_reply_t reply, void *userdata); diff --git a/src/test/test-varlink.c b/src/test/test-varlink.c index 1bbc87c32c0f9..8cdbfafaa2ae9 100644 --- a/src/test/test-varlink.c +++ b/src/test/test-varlink.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "sd-event.h" @@ -14,6 +15,7 @@ #include "io-util.h" #include "json-util.h" #include "memfd-util.h" +#include "path-util.h" #include "rm-rf.h" #include "socket-util.h" #include "tests.h" @@ -936,4 +938,111 @@ TEST(upgrade_pipelining) { ASSERT_OK(-pthread_join(t, NULL)); } +typedef struct ExecDirServer { + sd_varlink_server *server; + sd_event *event; + const char *name; + pthread_t thread; +} ExecDirServer; + +static int method_execute_dir_ping(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + ExecDirServer *srv = ASSERT_PTR(userdata); + + return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRING("name", srv->name)); +} + +static void on_execute_dir_disconnect(sd_varlink_server *s, sd_varlink *link, void *userdata) { + ExecDirServer *srv = ASSERT_PTR(userdata); + + /* Only one client (from varlink_execute_directory()) connects per server — once it's gone, we're done. */ + ASSERT_OK(sd_event_exit(srv->event, 0)); +} + +static void *execute_dir_server_thread(void *arg) { + ExecDirServer *srv = arg; + + ASSERT_OK(sd_event_loop(srv->event)); + return NULL; +} + +static int execute_dir_reply(sd_varlink *link, sd_json_variant *parameters, const char *error_id, sd_varlink_reply_flags_t flags, void *userdata) { + size_t *count = ASSERT_PTR(userdata); + + ASSERT_NULL(error_id); + ASSERT_NOT_NULL(sd_json_variant_by_key(parameters, "name")); + + (*count)++; + return 0; +} + +TEST(execute_directory) { + _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; + static const char * const names[] = { "alpha", "beta", "gamma" }; + ExecDirServer servers[ELEMENTSOF(names)] = {}; + size_t reply_count = 0; + + ASSERT_OK(mkdtemp_malloc("/tmp/varlink-execdir-XXXXXX", &tmpdir)); + + for (size_t i = 0; i < ELEMENTSOF(names); i++) { + ExecDirServer *eds = servers + i; + servers[i].name = names[i]; + + _cleanup_free_ char *j = ASSERT_PTR(path_join(tmpdir, names[i])); + + ASSERT_OK(sd_event_new(&eds->event)); + ASSERT_OK(varlink_server_new(&eds->server, + SD_VARLINK_SERVER_INHERIT_USERDATA, + eds)); + ASSERT_OK(sd_varlink_server_bind_method(eds->server, "io.test.ExecDirPing", method_execute_dir_ping)); + ASSERT_OK(sd_varlink_server_bind_disconnect(eds->server, on_execute_dir_disconnect)); + ASSERT_OK(sd_varlink_server_listen_address(eds->server, j, 0600)); + ASSERT_OK(sd_varlink_server_attach_event(eds->server, eds->event, 0)); + + ASSERT_OK(-pthread_create(&eds->thread, NULL, execute_dir_server_thread, eds)); + } + + ASSERT_OK_EQ(varlink_execute_directory( + tmpdir, + "io.test.ExecDirPing", + /* parameters= */ NULL, + /* more= */ false, + /* timeout_usec= */ USEC_INFINITY, + execute_dir_reply, + &reply_count), (ssize_t) ELEMENTSOF(names)); + ASSERT_EQ(reply_count, (unsigned) ELEMENTSOF(names)); + + FOREACH_ELEMENT(eds, servers) { + ASSERT_OK(-pthread_join(eds->thread, NULL)); + eds->server = sd_varlink_server_unref(eds->server); + eds->event = sd_event_unref(eds->event); + } + + /* Calling the helper against a non-existent directory must fail. */ + _cleanup_free_ char *nope = NULL; + ASSERT_OK(asprintf(&nope, "%s/does-not-exist", tmpdir)); + ASSERT_FAIL(varlink_execute_directory( + nope, + "io.test.ExecDirPing", + /* parameters= */ NULL, + /* more= */ false, + /* timeout_usec= */ USEC_INFINITY, + execute_dir_reply, + &reply_count)); + + /* An empty directory must simply return 0 and not invoke the reply callback. */ + _cleanup_free_ char *empty = ASSERT_PTR(path_join(tmpdir, "empty")); + ASSERT_OK_ERRNO(mkdir(empty, 0755)); + + size_t count_before = reply_count; + ASSERT_OK_ZERO(varlink_execute_directory( + empty, + "io.test.ExecDirPing", + /* parameters= */ NULL, + /* more= */ false, + /* timeout_usec= */ USEC_INFINITY, + execute_dir_reply, + &reply_count)); + ASSERT_EQ(reply_count, count_before); +} + DEFINE_TEST_MAIN(LOG_DEBUG); From dad6381d45ad2e57d73a0fe7ed99c3635a3cbaf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 25 Apr 2026 12:05:47 +0200 Subject: [PATCH 1276/1296] mtd_probe: convert to the new option parser The commandline check is tightened. Previously the program would crash if called without an argument. Co-developed-by: Claude Opus 4.7 --- src/udev/mtd_probe/mtd_probe.c | 63 ++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/src/udev/mtd_probe/mtd_probe.c b/src/udev/mtd_probe/mtd_probe.c index d79a0617e21e0..1cc769ad76aed 100644 --- a/src/udev/mtd_probe/mtd_probe.c +++ b/src/udev/mtd_probe/mtd_probe.c @@ -19,48 +19,59 @@ */ #include -#include #include -#include #include #include "build.h" #include "errno-util.h" #include "fd-util.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" #include "main-func.h" #include "mtd_probe.h" +#include "options.h" +#include "strv.h" static const char *arg_device = NULL; +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_cmdline("[OPTIONS...] /dev/mtd[n]"); + help_abstract("Probe MTD devices."); + help_section("Options:"); + + return table_print_or_warn(options); +} + static int parse_argv(int argc, char *argv[]) { - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'v' }, - {} - }; - int c; - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + assert(argc >= 0); + assert(argv); + + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - printf("%s /dev/mtd[n]\n\n" - " -h --help Show this help text\n" - " --version Show package version\n", - program_invocation_short_name); - return 0; - case 'v': + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: return version(); - case '?': - return -EINVAL; - default: - assert_not_reached(); } - if (argc > 2) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Error: unexpected argument."); + char **args = option_parser_get_args(&state); + if (strv_length(args) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Need exactly one DEVICE argument."); - arg_device = argv[optind]; + arg_device = args[0]; return 1; } @@ -73,12 +84,12 @@ static int run(int argc, char** argv) { if (r <= 0) return r; - mtd_fd = open(argv[1], O_RDONLY|O_CLOEXEC|O_NOCTTY); + mtd_fd = open(arg_device, O_RDONLY|O_CLOEXEC|O_NOCTTY); if (mtd_fd < 0) { bool ignore = ERRNO_IS_DEVICE_ABSENT_OR_EMPTY(errno); log_full_errno(ignore ? LOG_DEBUG : LOG_WARNING, errno, "Failed to open device node '%s'%s: %m", - argv[1], ignore ? ", ignoring" : ""); + arg_device, ignore ? ", ignoring" : ""); return ignore ? 0 : -errno; } From e8e75a1c99bfade54f9af90cc039ea897e839595 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 25 Apr 2026 12:09:05 +0200 Subject: [PATCH 1277/1296] fido_id: convert to the new option parser The commandline check is tightened. Previously the program would crash if called without an argument. Co-developed-by: Claude Opus 4.7 --- src/udev/fido_id/fido_id.c | 56 +++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/src/udev/fido_id/fido_id.c b/src/udev/fido_id/fido_id.c index aa918a9798460..a301b9acdb087 100644 --- a/src/udev/fido_id/fido_id.c +++ b/src/udev/fido_id/fido_id.c @@ -7,7 +7,6 @@ */ #include -#include #include #include #include @@ -18,41 +17,54 @@ #include "device-util.h" #include "fd-util.h" #include "fido_id_desc.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "path-util.h" +#include "strv.h" #include "udev-util.h" static const char *arg_device = NULL; +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_cmdline("[OPTIONS...] SYSFS_PATH"); + help_abstract("Identify FIDO security tokens."); + help_section("Options:"); + + return table_print_or_warn(options); +} + static int parse_argv(int argc, char *argv[]) { - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'v' }, - {} - }; - int c; - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + assert(argc >= 0); + assert(argv); + + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': - printf("%s [OPTIONS...] SYSFS_PATH\n\n" - " -h --help Show this help text\n" - " --version Show package version\n", - program_invocation_short_name); - return 0; - case 'v': + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: return version(); - case '?': - return -EINVAL; - default: - assert_not_reached(); } - if (argc > 2) + char **args = option_parser_get_args(&state); + if (strv_length(args) > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Error: unexpected argument."); - arg_device = argv[optind]; + arg_device = args[0]; return 1; } From 9286f1f2997d2d547cef7574d612e22285e10374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 25 Apr 2026 12:17:27 +0200 Subject: [PATCH 1278/1296] dmi_memory_id: convert to the new option parser For some reason, the old code had duplicate {"version", 'v'} entry in the options table and a matching case that could not be reached. Commandline parsing is tightened to reject any positional arguments. Co-developed-by: Claude Opus 4.7 --- src/shared/options.h | 6 +++ src/udev/dmi_memory_id/dmi_memory_id.c | 64 ++++++++++++++------------ 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/src/shared/options.h b/src/shared/options.h index d86766f90e46c..f0c19af49c3d9 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -109,6 +109,12 @@ typedef struct Option { "Allows the certificate to be loaded from an OpenSSL provider " \ "(file, provider:PROVIDER)") +/* A form used in udev code for compatibility. -V is accepted but not documented. */ +#define OPTION_COMMON_VERSION_WITH_HIDDEN_V \ + OPTION_COMMON_VERSION: {} \ + OPTION_SHORT('V', NULL, /* help= */ NULL) + + /* This is magically mapped to the beginning and end of the section */ extern const Option __start_SYSTEMD_OPTIONS[]; extern const Option __stop_SYSTEMD_OPTIONS[]; diff --git a/src/udev/dmi_memory_id/dmi_memory_id.c b/src/udev/dmi_memory_id/dmi_memory_id.c index a2f2ed726312f..df683d00729d0 100644 --- a/src/udev/dmi_memory_id/dmi_memory_id.c +++ b/src/udev/dmi_memory_id/dmi_memory_id.c @@ -42,13 +42,15 @@ * https://www.dmtf.org/sites/default/files/DSP0270_1.0.1.pdf */ -#include #include #include "alloc-util.h" #include "build.h" #include "fileio.h" +#include "format-table.h" +#include "help-util.h" #include "main-func.h" +#include "options.h" #include "string-util.h" #include "udev-util.h" #include "unaligned.h" @@ -643,45 +645,49 @@ static int legacy_decode(const uint8_t *buf, const char *devmem, bool no_file_of } static int help(void) { - printf("%s [OPTIONS...]\n\n" - " -F --from-dump FILE Read DMI information from a binary file\n" - " -h --help Show this help text\n" - " --version Show package version\n", - program_invocation_short_name); - return 0; + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_cmdline("[OPTIONS...]"); + help_section("Options:"); + + return table_print_or_warn(options); } -static int parse_argv(int argc, char * const *argv) { - static const struct option options[] = { - { "from-dump", required_argument, NULL, 'F' }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'v' }, - {} - }; - int c; +static int parse_argv(int argc, char *argv[]) { + assert(argc >= 0); + assert(argv); + + OptionParser state = { argc, argv }; + const char *arg; - while ((c = getopt_long(argc, argv, "F:hV", options, NULL)) >= 0) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'F': - arg_source_file = optarg; - break; - case 'V': - return version(); - case 'h': + + OPTION_COMMON_HELP: return help(); - case '?': - return -EINVAL; - case 'v': + + OPTION_COMMON_VERSION_WITH_HIDDEN_V: return version(); - default: - assert_not_reached(); + + OPTION('F', "from-dump", "FILE", + "Read DMI information from a binary file"): + arg_source_file = arg; + break; } + if (option_parser_get_n_args(&state) > 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "This program takes no arguments."); + return 1; } -static int run(int argc, char* const* argv) { +static int run(int argc, char *argv[]) { _cleanup_free_ uint8_t *buf = NULL; bool no_file_offset = false; size_t size; From 3ad76abc43585109453449f9660aa34f4bb1d028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 25 Apr 2026 12:33:03 +0200 Subject: [PATCH 1279/1296] cdrom_id: convert to the new option parser The commandline check is tightened to reject extra arguments. Co-developed-by: Claude Opus 4.7 --- src/udev/cdrom_id/cdrom_id.c | 78 +++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/src/udev/cdrom_id/cdrom_id.c b/src/udev/cdrom_id/cdrom_id.c index d311f2b3222ec..1a167475d20d8 100644 --- a/src/udev/cdrom_id/cdrom_id.c +++ b/src/udev/cdrom_id/cdrom_id.c @@ -4,7 +4,6 @@ */ #include -#include #include #include #include @@ -13,11 +12,15 @@ #include "build.h" #include "errno-util.h" #include "fd-util.h" +#include "format-table.h" +#include "help-util.h" #include "main-func.h" +#include "options.h" #include "random-util.h" #include "sort-util.h" #include "string-table.h" #include "string-util.h" +#include "strv.h" #include "time-util.h" #include "udev-util.h" #include "unaligned.h" @@ -898,60 +901,63 @@ static void print_properties(const Context *c) { } static int help(void) { - printf("%s [OPTIONS...] DEVICE\n\n" - " -l --lock-media Lock the media (to enable eject request events)\n" - " -u --unlock-media Unlock the media\n" - " -e --eject-media Eject the media\n" - " -d --debug Print debug messages to stderr\n" - " -h --help Show this help text\n" - " --version Show package version\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; - return 0; + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_cmdline("[OPTIONS...] DEVICE"); + help_section("Options:"); + + return table_print_or_warn(options); } static int parse_argv(int argc, char *argv[]) { - static const struct option options[] = { - { "lock-media", no_argument, NULL, 'l' }, - { "unlock-media", no_argument, NULL, 'u' }, - { "eject-media", no_argument, NULL, 'e' }, - { "debug", no_argument, NULL, 'd' }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'v' }, - {} - }; - int c; + assert(argc >= 0); + assert(argv); + + OptionParser state = { argc, argv }; + const char *arg; - while ((c = getopt_long(argc, argv, "deluh", options, NULL)) >= 0) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'l': + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION('l', "lock-media", NULL, + "Lock the media (to enable eject request events)"): arg_lock = true; break; - case 'u': + + OPTION('u', "unlock-media", NULL, + "Unlock the media"): arg_unlock = true; break; - case 'e': + + OPTION('e', "eject-media", NULL, + "Eject the media"): arg_eject = true; break; - case 'd': + + OPTION('d', "debug", NULL, + "Print debug messages to stderr"): log_set_target(LOG_TARGET_CONSOLE); log_set_max_level(LOG_DEBUG); log_open(); break; - case 'h': - return help(); - case 'v': - return version(); - case '?': - return -EINVAL; - default: - assert_not_reached(); } - arg_node = argv[optind]; - if (!arg_node) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No device specified."); + char **args = option_parser_get_args(&state); + if (strv_length(args) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Need exactly one DEVICE argument."); + arg_node = args[0]; return 1; } From beea13b9d10ea241931dc1815228247e9b0d62d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 25 Apr 2026 12:43:12 +0200 Subject: [PATCH 1280/1296] ata_id: convert to the new option parser The commandline check is tightened to reject extra arguments. Co-developed-by: Claude Opus 4.7 --- src/udev/ata_id/ata_id.c | 64 +++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/src/udev/ata_id/ata_id.c b/src/udev/ata_id/ata_id.c index 508f99b01a06a..112456ffc2fd0 100644 --- a/src/udev/ata_id/ata_id.c +++ b/src/udev/ata_id/ata_id.c @@ -6,7 +6,6 @@ */ #include -#include #include #include #include @@ -18,8 +17,12 @@ #include "device-nodes.h" #include "errno-util.h" #include "fd-util.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" #include "main-func.h" +#include "options.h" +#include "strv.h" #include "udev-util.h" #include "unaligned.h" @@ -356,40 +359,47 @@ static int disk_identify(int fd, return 0; } +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_cmdline("[OPTIONS...] DEVICE"); + help_section("Options:"); + + return table_print_or_warn(options); +} + static int parse_argv(int argc, char *argv[]) { - static const struct option options[] = { - { "export", no_argument, NULL, 'x' }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'v' }, - {} - }; - int c; + assert(argc >= 0); + assert(argv); + + OptionParser state = { argc, argv }; + const char *arg; - while ((c = getopt_long(argc, argv, "xh", options, NULL)) >= 0) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'x': + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION('x', "export", NULL, + "Print values as environment keys"): arg_export = true; break; - case 'h': - printf("%s [OPTIONS...] DEVICE\n\n" - " -x --export Print values as environment keys\n" - " -h --help Show this help text\n" - " --version Show package version\n", - program_invocation_short_name); - return 0; - case 'v': - return version(); - case '?': - return -EINVAL; - default: - assert_not_reached(); } - if (!argv[optind]) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "DEVICE argument missing."); + char **args = option_parser_get_args(&state); + if (strv_length(args) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Need exactly one DEVICE argument."); - arg_device = argv[optind]; + arg_device = args[0]; return 1; } From 4339197f5d4f712bc900d8e09c892015d48b19bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 25 Apr 2026 13:31:43 +0200 Subject: [PATCH 1281/1296] shared/options: split out helper to format -o/--option= --- src/shared/options.c | 89 ++++++++++++++++++++++++++--------------- src/shared/options.h | 1 + src/test/test-options.c | 49 +++++++++++++++++++++++ 3 files changed, 107 insertions(+), 32 deletions(-) diff --git a/src/shared/options.c b/src/shared/options.c index fbf9a31181754..a4c6514508c43 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -311,6 +311,59 @@ size_t option_parser_get_n_args(const OptionParser *state) { return state->argc - state->positional_offset; } +char* option_get_synopsis(const char *prefix, const Option *opt, const char *joiner, bool show_metavar) { + assert(opt); + assert(!FLAGS_SET(opt->flags, OPTION_GROUP_MARKER)); /* A group marker should not be displayed */ + + if (!prefix) + prefix = ""; + + if (opt->flags & (OPTION_HELP_ENTRY_VERBATIM | OPTION_POSITIONAL_ENTRY)) + return strjoin(prefix, ASSERT_PTR(opt->long_code)); + + /* The option formatted appropriately for --help strings, error messages, and similar: + * ---=[] + * "=" is shown only when a long form is defined: -l --long=ARG, --long=ARG, -s ARG. + * The joiner arg is used between the short and long forms. + * As a special case, if the option has no long form and show_metavar is true, + * a space is used ('-a ARG' or '-a [ARG]'). + */ + assert(opt->short_code != 0 || opt->long_code); + + char sc[3] = ""; + if (opt->short_code != 0) + xsprintf(sc, "-%c", opt->short_code); + + if (show_metavar && opt->metavar && !opt->long_code) + joiner = " "; /* Return '-x ARG', no matter what joiner was specified. */ + else if (opt->short_code == 0 || !opt->long_code) + joiner = ""; + else if (!joiner) + joiner = " "; + + bool need_eq = option_takes_arg(opt) && opt->long_code; + if (!show_metavar) + return strjoin(prefix, + sc, + joiner, + opt->long_code ? "--" : "", + strempty(opt->long_code), + need_eq ? "=" : ""); + + bool need_quote = opt->metavar && strchr(opt->metavar, ' '); + return strjoin(prefix, + sc, + joiner, + opt->long_code ? "--" : "", + strempty(opt->long_code), + option_arg_optional(opt) ? "[" : "", + need_eq ? "=" : "", + need_quote ? "'" : "", + strempty(opt->metavar), + need_quote ? "'" : "", + option_arg_optional(opt) ? "]" : ""); +} + int _option_parser_get_help_table( const Option options[], const Option options_end[], @@ -341,38 +394,10 @@ int _option_parser_get_help_table( /* No help string — we do not show the option */ continue; - _cleanup_free_ char *s = NULL; - - if (FLAGS_SET(opt->flags, OPTION_HELP_ENTRY_VERBATIM)) { - assert(opt->long_code); - - s = strjoin(" ", - opt->long_code); - } else { - char sc[3] = " "; - if (opt->short_code != 0) - xsprintf(sc, "-%c", opt->short_code); - - /* We indent the option string by two spaces. We could set the minimum cell width and - * right-align for a similar result, but that'd be more work. This is only used for - * display. - * - * "=" is shown only when a long option is defined: -l --long=ARG, --long=ARG, -s ARG. - */ - bool need_eq = option_takes_arg(opt) && opt->long_code; - bool need_quote = opt->metavar && strchr(opt->metavar, ' '); - s = strjoin(" ", - sc, - " ", - opt->long_code ? "--" : "", - strempty(opt->long_code), - option_arg_optional(opt) ? "[" : "", - need_eq ? "=" : "", - need_quote ? "'" : "", - strempty(opt->metavar), - need_quote ? "'" : "", - option_arg_optional(opt) ? "]" : ""); - } + /* We indent the option string by two spaces. We could set the minimum cell width and + * right-align for a similar result, but that'd be more work. This is only used for + * display. */ + _cleanup_free_ char *s = option_get_synopsis(" ", opt, " ", /* show_metavar= */ true); if (!s) return log_oom(); diff --git a/src/shared/options.h b/src/shared/options.h index f0c19af49c3d9..59b20bc047cd3 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -172,6 +172,7 @@ char* option_parser_consume_next_arg(OptionParser *state); char** option_parser_get_args(const OptionParser *state); size_t option_parser_get_n_args(const OptionParser *state); +char* option_get_synopsis(const char *prefix, const Option *opt, const char *joiner, bool show_metavar); int _option_parser_get_help_table( const Option options[], diff --git a/src/test/test-options.c b/src/test/test-options.c index 1c9073b7a56d7..49e1b3069fc91 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -1302,4 +1302,53 @@ TEST(option_optional_arg_consume) { } } +static void test_option_get_synopsis_one( + const Option *opt, + const char *joiner, + bool show_metavar, + const char *expected) { + log_debug("%s", expected); + _cleanup_free_ char *s = option_get_synopsis(". ", opt, joiner, show_metavar); + ASSERT_STREQ(s, expected); +} + +TEST(option_get_synopsis) { + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', "xxx", "X" }, "/", true, ". -x/--xxx=X"); + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', "xxx", "X" }, NULL, true, ". -x --xxx=X"); + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', "xxx", "X" }, "/", false, ". -x/--xxx=" ); + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', "xxx", "X" }, " ", true, ". -x --xxx=X"); + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', "xxx", "X" }, " ", false, ". -x --xxx=" ); + test_option_get_synopsis_one(&(const Option) { 0, 0, 0, "xxx", "X" }, "+", true, ". --xxx=X" ); + test_option_get_synopsis_one(&(const Option) { 0, 0, 0, "xxx", "X" }, "+", false, ". --xxx=" ); + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', NULL, "X" }, " ", true, ". -x X" ); + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', NULL, "X" }, "/", false, ". -x" ); + + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', "xxx", "A B" }, "/", true, ". -x/--xxx='A B'"); + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', "xxx", "A B" }, " ", true, ". -x --xxx='A B'"); + test_option_get_synopsis_one(&(const Option) { 0, 0, 0, "xxx", "A B" }, "+", true, ". --xxx='A B'" ); + test_option_get_synopsis_one(&(const Option) { 0, 0, 'x', NULL, "A B" }, " ", true, ". -x 'A B'" ); + + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', "xxx", "X" }, "/", true, ". -x/--xxx[=X]"); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', "xxx", "X" }, NULL, true, ". -x --xxx[=X]"); + /* Note: --xxx[=] would be silly, so we show --xxx=. It's a corner case. Maybe this should change. */ + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', "xxx", "X" }, "/", false, ". -x/--xxx=" ); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', "xxx", "X" }, " ", true, ". -x --xxx[=X]"); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', "xxx", "X" }, " ", false, ". -x --xxx=" ); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 0, "xxx", "X" }, "+", true, ". --xxx[=X]" ); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 0, "xxx", "X" }, "+", false, ". --xxx=" ); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', NULL, "X" }, " ", true, ". -x [X]" ); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', NULL, "X" }, "/", false, ". -x" ); + + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', "xxx", "A B" }, "/", true, ". -x/--xxx[='A B']"); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', "xxx", "A B" }, " ", true, ". -x --xxx[='A B']"); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 0, "xxx", "A B" }, "+", true, ". --xxx[='A B']" ); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG, 'x', NULL, "A B" }, " ", true, ". -x ['A B']" ); + + test_option_get_synopsis_one(&(const Option) { 0, OPTION_OPTIONAL_ARG | OPTION_HELP_ENTRY | OPTION_STOPS_PARSING, + 'x', "xxx", "A B" }, "/", true, ". -x/--xxx[='A B']"); + + test_option_get_synopsis_one(&(const Option) { 0, OPTION_HELP_ENTRY_VERBATIM, 'u', "special special", "unused" }, "/", true, ". special special"); + test_option_get_synopsis_one(&(const Option) { 0, OPTION_POSITIONAL_ENTRY, 'u', "(fixed)", "unused" }, "/", true, ". (fixed)"); +} + DEFINE_TEST_MAIN(LOG_DEBUG); From a9122433c9c93f00e95b779397c5513c2f7c6927 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 26 Apr 2026 20:50:09 +0200 Subject: [PATCH 1282/1296] varlink: address some trivial points Claude found Just addressing Claude's last review from #41815 --- src/libsystemd/sd-varlink/varlink-util.c | 2 +- src/test/test-varlink.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libsystemd/sd-varlink/varlink-util.c b/src/libsystemd/sd-varlink/varlink-util.c index 475bec40d844f..7b8797c92c85b 100644 --- a/src/libsystemd/sd-varlink/varlink-util.c +++ b/src/libsystemd/sd-varlink/varlink-util.c @@ -335,7 +335,7 @@ ssize_t varlink_execute_directory( r = sd_varlink_set_relative_timeout(link, timeout_usec); if (r < 0) - return r; + return log_debug_errno(r, "Failed to set relative timeout: %m"); if (more) r = sd_varlink_observe(link, method, parameters); diff --git a/src/test/test-varlink.c b/src/test/test-varlink.c index 8cdbfafaa2ae9..72edc033dd068 100644 --- a/src/test/test-varlink.c +++ b/src/test/test-varlink.c @@ -1009,7 +1009,7 @@ TEST(execute_directory) { /* timeout_usec= */ USEC_INFINITY, execute_dir_reply, &reply_count), (ssize_t) ELEMENTSOF(names)); - ASSERT_EQ(reply_count, (unsigned) ELEMENTSOF(names)); + ASSERT_EQ(reply_count, ELEMENTSOF(names)); FOREACH_ELEMENT(eds, servers) { ASSERT_OK(-pthread_join(eds->thread, NULL)); From bad594f79a11b8896f1a830f86ae3beefac0d22f Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Sat, 25 Apr 2026 23:51:48 +0200 Subject: [PATCH 1283/1296] ssh-proxy: add a missing dispatch table sentinel Which was accidentally dropped in e6be5fb7200fb02e78e4f27f49a4d734b7b850a0. Follow-up for e6be5fb7200fb02e78e4f27f49a4d734b7b850a0. --- src/ssh-generator/ssh-proxy.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ssh-generator/ssh-proxy.c b/src/ssh-generator/ssh-proxy.c index 79d8ee056568e..f253036522796 100644 --- a/src/ssh-generator/ssh-proxy.c +++ b/src/ssh-generator/ssh-proxy.c @@ -337,6 +337,7 @@ static int process_machine(const char *machine, const char *port) { { "vSockCid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint32, voffsetof(p, cid), 0 }, { "class", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, class), SD_JSON_MANDATORY }, { "service", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, service), 0 }, + {} }; r = sd_json_dispatch(result, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); From 1851a76d6f98f3b3ead427efb5c1135e0fa9754f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 26 Apr 2026 20:51:26 +0200 Subject: [PATCH 1284/1296] notify: we typically say 'service manager' not 'init system', do so here too --- src/notify/notify.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/notify/notify.c b/src/notify/notify.c index d3201c66bcfb7..33d8fdf2c0912 100644 --- a/src/notify/notify.c +++ b/src/notify/notify.c @@ -67,7 +67,7 @@ static int help(void) { help_cmdline("[OPTIONS...] [VARIABLE=VALUE...]"); help_cmdline("[OPTIONS...] --exec [VARIABLE=VALUE...] ; -- CMDLINE..."); help_cmdline("[OPTIONS...] --fork -- CMDLINE..."); - help_abstract("Notify the init system about service status updates."); + help_abstract("Notify the service manager about service status updates."); help_section("Options:"); r = table_print_or_warn(options); @@ -644,7 +644,7 @@ static int run(int argc, char* argv[]) { if (r == -E2BIG) return log_error_errno(r, "Too many file descriptors passed."); if (r < 0) - return log_error_errno(r, "Failed to notify init system: %m"); + return log_error_errno(r, "Failed to notify service manager: %m"); if (r == 0) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "No status data could be sent: $NOTIFY_SOCKET was not set"); From cde589df880d7d1691d1c4366e0d2105d79a641b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 26 Apr 2026 20:53:28 +0200 Subject: [PATCH 1285/1296] help-util: reword comment a bit Based on comments here: https://github.com/systemd/systemd/pull/41805#discussion_r3141746275 --- src/shared/help-util.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/help-util.c b/src/shared/help-util.c index b88da2d259585..7e9d3e70be0c6 100644 --- a/src/shared/help-util.c +++ b/src/shared/help-util.c @@ -8,8 +8,8 @@ /* These are helpers for putting together --help texts in a uniform way with a common output style. Each * function generates a separate part of the --help text: * - * 1. help_cmdline() outputs a brief summary of the command line syntax. (used at least once, in some cases - * multiple times.) This generally comes first in the output. + * 1. help_cmdline() outputs a brief summary of the command line syntax. This shall be used at least once, + * in some cases multiple times. This generally comes first in the output. * * 2. help_abstract() outputs a brief prose abstract of the command, should carry a single line of text * that gives the user a hint what this tool does. Use only once, right after the last help_cmdline(). From 45ceb97f6a446fbd7308a99803b4b777f2beb031 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 23 Apr 2026 15:44:52 +0200 Subject: [PATCH 1286/1296] path-lookup: add state_directory_generic() helper inspired by runtime_directory_generic() --- src/libsystemd/sd-path/path-lookup.c | 24 ++++++++++++++++++++++++ src/libsystemd/sd-path/path-lookup.h | 4 ++++ 2 files changed, 28 insertions(+) diff --git a/src/libsystemd/sd-path/path-lookup.c b/src/libsystemd/sd-path/path-lookup.c index 4e4abaebf8f1c..b0f10e4b5af23 100644 --- a/src/libsystemd/sd-path/path-lookup.c +++ b/src/libsystemd/sd-path/path-lookup.c @@ -140,6 +140,30 @@ int runtime_directory_make(RuntimeScope scope, const char *subdir, char **ret_di return 0; } +int state_directory_generic(RuntimeScope scope, const char *suffix, char **ret) { + assert(ret); + + /* This does not bother with $STATE_DIRECTORY, and hence can be applied to get other service's state + * dir */ + + switch (scope) { + + case RUNTIME_SCOPE_USER: + return xdg_user_state_dir(suffix, ret); + + case RUNTIME_SCOPE_SYSTEM: { + char *d = path_join("/var/lib", suffix); + if (!d) + return -ENOMEM; + *ret = d; + return 0; + } + + default: + return -EINVAL; + } +} + static const char* const user_data_unit_paths[] = { "/usr/local/lib/systemd/user", "/usr/local/share/systemd/user", diff --git a/src/libsystemd/sd-path/path-lookup.h b/src/libsystemd/sd-path/path-lookup.h index 8dcbf766e6a1f..80e37a571c59c 100644 --- a/src/libsystemd/sd-path/path-lookup.h +++ b/src/libsystemd/sd-path/path-lookup.h @@ -61,6 +61,7 @@ int config_directory_generic(RuntimeScope scope, const char *suffix, char **ret) int runtime_directory_generic(RuntimeScope scope, const char *suffix, char **ret); int runtime_directory(RuntimeScope scope, const char *fallback_suffix, char **ret); int runtime_directory_make(RuntimeScope scope, const char *subdir, char **ret_dir, char **ret_dir_destroy); +int state_directory_generic(RuntimeScope scope, const char *suffix, char **ret); /* We don't treat /etc/xdg/systemd/ in these functions as the xdg base dir spec suggests because we assume * that is a link to /etc/systemd/ anyway. */ @@ -75,6 +76,9 @@ static inline int xdg_user_config_dir(const char *suffix, char **ret) { static inline int xdg_user_data_dir(const char *suffix, char **ret) { return sd_path_lookup(SD_PATH_USER_SHARED, suffix, ret); } +static inline int xdg_user_state_dir(const char *suffix, char **ret) { + return sd_path_lookup(SD_PATH_USER_STATE_PRIVATE, suffix, ret); +} bool path_is_user_data_dir(const char *path); bool path_is_user_config_dir(const char *path); From f0abcd5e0fd5ba309e91a2fe589727b814e2967b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 22 Apr 2026 23:43:17 +0200 Subject: [PATCH 1287/1296] format-table: add tristate field --- src/shared/format-table.c | 33 ++++++++++++++++++ src/shared/format-table.h | 1 + src/test/test-format-table.c | 66 ++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+) diff --git a/src/shared/format-table.c b/src/shared/format-table.c index 200890d3cdccf..432e054d4a70c 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -87,6 +87,7 @@ typedef struct TableData { union { uint8_t data[0]; /* data is generic array */ bool boolean; + int tristate; usec_t timestamp; usec_t timespan; uint64_t size; @@ -342,6 +343,7 @@ static size_t table_data_size(TableDataType type, const void *data) { case TABLE_PERCENT: case TABLE_IFINDEX: case TABLE_SIGNAL: + case TABLE_TRISTATE: return sizeof(int); case TABLE_IN_ADDR: @@ -935,6 +937,7 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) { uint64_t uint64; int percent; int ifindex; + int tristate; bool b; union in_addr_union address; sd_id128_t id128; @@ -972,6 +975,11 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) { data = &buffer.b; break; + case TABLE_TRISTATE: + buffer.tristate = va_arg(ap, int); + data = &buffer.tristate; + break; + case TABLE_TIMESTAMP: case TABLE_TIMESTAMP_UTC: case TABLE_TIMESTAMP_RELATIVE: @@ -1434,12 +1442,25 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t return strv_compare(a->strv, b->strv); case TABLE_BOOLEAN: + case TABLE_BOOLEAN_CHECKMARK: if (!a->boolean && b->boolean) return -1; if (a->boolean && !b->boolean) return 1; return 0; + case TABLE_TRISTATE: + /* NB: we do not use CMP() here, since we want to collapse all negative and all + * positive into one bucket each. */ + if ((a->tristate < 0 && b->tristate >= 0) || + (a->tristate == 0 && b->tristate > 0)) + return -1; + + if ((b->tristate < 0 && a->tristate >= 0) || + (b->tristate == 0 && a->tristate > 0)) + return 1; + return 0; + case TABLE_TIMESTAMP: case TABLE_TIMESTAMP_UTC: case TABLE_TIMESTAMP_RELATIVE: @@ -1691,6 +1712,12 @@ static const char* table_data_format( case TABLE_BOOLEAN_CHECKMARK: return glyph(d->boolean ? GLYPH_CHECK_MARK : GLYPH_CROSS_MARK); + case TABLE_TRISTATE: + if (d->tristate < 0) + return table_ersatz_string(t); + + return yes_no(d->tristate); + case TABLE_TIMESTAMP: case TABLE_TIMESTAMP_UTC: case TABLE_TIMESTAMP_RELATIVE: @@ -2776,6 +2803,12 @@ static int table_data_to_json(TableData *d, sd_json_variant **ret) { case TABLE_BOOLEAN: return sd_json_variant_new_boolean(ret, d->boolean); + case TABLE_TRISTATE: + if (d->tristate < 0) + return sd_json_variant_new_null(ret); + + return sd_json_variant_new_boolean(ret, d->tristate); + case TABLE_TIMESTAMP: case TABLE_TIMESTAMP_UTC: case TABLE_TIMESTAMP_RELATIVE: diff --git a/src/shared/format-table.h b/src/shared/format-table.h index 7665c93e593e6..5b98d49017524 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -20,6 +20,7 @@ typedef enum TableDataType { TABLE_VERSION, /* just like TABLE_STRING, but uses version comparison when sorting */ TABLE_BOOLEAN, TABLE_BOOLEAN_CHECKMARK, + TABLE_TRISTATE, TABLE_TIMESTAMP, TABLE_TIMESTAMP_UTC, TABLE_TIMESTAMP_RELATIVE, diff --git a/src/test/test-format-table.c b/src/test/test-format-table.c index 677adaa9c964d..9aae79e223987 100644 --- a/src/test/test-format-table.c +++ b/src/test/test-format-table.c @@ -581,6 +581,72 @@ TEST(table) { "5min 5min \n"); } +TEST(tristate) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *w = NULL; + _cleanup_(table_unrefp) Table *t = NULL; + _cleanup_free_ char *formatted = NULL; + + ASSERT_NOT_NULL((t = table_new("name", "flag"))); + + ASSERT_OK(table_add_many(t, + TABLE_STRING, "neg", + TABLE_TRISTATE, -1)); + ASSERT_OK(table_add_many(t, + TABLE_STRING, "zero", + TABLE_TRISTATE, 0)); + ASSERT_OK(table_add_many(t, + TABLE_STRING, "pos", + TABLE_TRISTATE, 1)); + + ASSERT_OK(table_format(t, &formatted)); + printf("%s\n", formatted); + ASSERT_STREQ(formatted, + "NAME FLAG\n" + "neg \n" + "zero no\n" + "pos yes\n"); + formatted = mfree(formatted); + + /* Try a non-default ersatz string. */ + table_set_ersatz_string(t, TABLE_ERSATZ_DASH); + ASSERT_OK(table_format(t, &formatted)); + printf("%s\n", formatted); + ASSERT_STREQ(formatted, + "NAME FLAG\n" + "neg -\n" + "zero no\n" + "pos yes\n"); + formatted = mfree(formatted); + + /* Sorting: -1 < 0 < 1 */ + ASSERT_OK(table_set_sort(t, (size_t) 1, SIZE_MAX)); + ASSERT_OK(table_format(t, &formatted)); + printf("%s\n", formatted); + ASSERT_STREQ(formatted, + "NAME FLAG\n" + "neg -\n" + "zero no\n" + "pos yes\n"); + formatted = mfree(formatted); + + /* JSON: -1 → null, 0 → false, positive → true */ + ASSERT_OK(table_to_json(t, &v)); + + ASSERT_OK(sd_json_build(&w, + SD_JSON_BUILD_ARRAY( + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("name", JSON_BUILD_CONST_STRING("neg")), + SD_JSON_BUILD_PAIR("flag", SD_JSON_BUILD_NULL)), + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("name", JSON_BUILD_CONST_STRING("zero")), + SD_JSON_BUILD_PAIR_BOOLEAN("flag", false)), + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("name", JSON_BUILD_CONST_STRING("pos")), + SD_JSON_BUILD_PAIR_BOOLEAN("flag", true))))); + + ASSERT_TRUE(sd_json_variant_equal(v, w)); +} + TEST(signed_integers) { _cleanup_(table_unrefp) Table *t = NULL; _cleanup_free_ char *formatted = NULL; From 5827ff2b9dd0b022d6d39119f9c6e1a7b7f57a69 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 22 Apr 2026 10:17:19 +0200 Subject: [PATCH 1288/1296] fs-util: add XO_AUTO_RW_RO --- src/basic/fs-util.c | 104 +++++++++++++++++++++++++++++++++++----- src/basic/fs-util.h | 1 + src/test/test-fs-util.c | 84 ++++++++++++++++++++++++++++++++ 3 files changed, 176 insertions(+), 13 deletions(-) diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index d041a9afcff77..3960938309fcd 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -1191,6 +1191,9 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ assert(!(FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT) && FLAGS_SET(xopen_flags, XO_SUBVOLUME))); assert(!(FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT) && FLAGS_SET(xopen_flags, XO_NOCOW))); + /* Don't specify an access mode if you want auto mode. */ + assert(!FLAGS_SET(xopen_flags, XO_AUTO_RW_RO) || (open_flags & O_ACCMODE_STRICT) == 0); + /* This is like openat(), but has a few tricks up its sleeves, extending behaviour: * * • O_DIRECTORY|O_CREAT is supported, which causes a directory to be created, and immediately @@ -1211,13 +1214,26 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ * • If mode is specified as MODE_INVALID, we'll use 0755 for dirs, and 0644 for regular files. * * • The dir fd can be passed as XAT_FDROOT, in which case any relative paths will be taken relative to the root fs. + * + * • If XO_AUTO_RW_RO is specified and the file cannot be opened in O_RDWR mode due to EACCES/EROFS or similar, retry in O_RDONLY mode. */ if (mode == MODE_INVALID) mode = (open_flags & O_DIRECTORY) ? 0755 : 0644; + if (FLAGS_SET(xopen_flags, XO_AUTO_RW_RO)) { + if (open_flags & O_DIRECTORY) { + /* Directories can only be opened in read-only mode */ + xopen_flags &= ~XO_AUTO_RW_RO; + open_flags |= O_RDONLY; + } else if (open_flags & O_PATH) + /* O_PATH is incompatible with O_RDONLY/O_RDWR → fail */ + return -EINVAL; + } + if (isempty(path)) { assert(!FLAGS_SET(open_flags, O_CREAT|O_EXCL)); + open_flags &= ~O_NOFOLLOW; if (FLAGS_SET(xopen_flags, XO_REGULAR)) { r = fd_verify_regular(dir_fd); @@ -1231,7 +1247,16 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ return r; } - return fd_reopen(dir_fd, open_flags & ~O_NOFOLLOW); + if (FLAGS_SET(xopen_flags, XO_AUTO_RW_RO)) { + /* First try: in r/w mode */ + fd = fd_reopen(dir_fd, open_flags|O_RDWR); + if (!ERRNO_IS_NEG_FS_WRITE_REFUSED(fd) && fd != -EISDIR) + return TAKE_FD(fd); + + open_flags |= O_RDONLY; + } + + return fd_reopen(dir_fd, open_flags); } _cleanup_close_ int _dir_fd = -EBADF; @@ -1292,10 +1317,23 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ } else if (FLAGS_SET(open_flags, O_CREAT|O_EXCL)) { /* In O_EXCL mode we can just create the thing, everything is dealt with for us */ - fd = openat(dir_fd, path, open_flags, mode); + + if (FLAGS_SET(xopen_flags, XO_AUTO_RW_RO)) { + fd = RET_NERRNO(openat(dir_fd, path, open_flags|O_RDWR, mode)); + if (ERRNO_IS_NEG_FS_WRITE_REFUSED(fd)) + open_flags |= O_RDONLY; + else if (fd < 0) { + r = fd; + goto error; + } + } + if (fd < 0) { - r = -errno; - goto error; + fd = openat(dir_fd, path, open_flags, mode); + if (fd < 0) { + r = -errno; + goto error; + } } made_file = true; @@ -1309,10 +1347,24 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ } /* Doesn't exist yet, then try to create it */ - fd = openat(dir_fd, path, open_flags|O_CREAT|O_EXCL, mode); + open_flags |= O_EXCL; + + if (FLAGS_SET(xopen_flags, XO_AUTO_RW_RO)) { + fd = RET_NERRNO(openat(dir_fd, path, open_flags|O_RDWR, mode)); + if (ERRNO_IS_NEG_FS_WRITE_REFUSED(fd)) + open_flags |= O_RDONLY; + else if (fd < 0) { + r = fd; + goto error; + } + } + if (fd < 0) { - r = -errno; - goto error; + fd = openat(dir_fd, path, open_flags, mode); + if (fd < 0) { + r = -errno; + goto error; + } } made_file = true; @@ -1322,10 +1374,24 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ if (r < 0) goto error; - fd = fd_reopen(inode_fd, open_flags & ~(O_NOFOLLOW|O_CREAT)); + open_flags &= ~(O_NOFOLLOW|O_CREAT); + + if (FLAGS_SET(xopen_flags, XO_AUTO_RW_RO)) { + fd = fd_reopen(inode_fd, open_flags|O_RDWR); + if (ERRNO_IS_NEG_FS_WRITE_REFUSED(fd)) + open_flags |= O_RDONLY; + else if (fd < 0) { + r = fd; + goto error; + } + } + if (fd < 0) { - r = fd; - goto error; + fd = fd_reopen(inode_fd, open_flags); + if (fd < 0) { + r = fd; + goto error; + } } } } @@ -1338,10 +1404,22 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ } else { /* XO_SOCKET also lands here: it requires O_PATH (see asserts above) so openat() pins * the inode without connecting, and fd_verify_socket() below enforces the type. */ - fd = openat_report_new(dir_fd, path, open_flags, mode, &made_file); + if (FLAGS_SET(xopen_flags, XO_AUTO_RW_RO)) { + fd = openat_report_new(dir_fd, path, O_RDWR|open_flags, mode, &made_file); + if (ERRNO_IS_NEG_FS_WRITE_REFUSED(fd) || fd == -EISDIR) + open_flags |= O_RDONLY; + else if (fd < 0) { + r = fd; + goto error; + } + } + if (fd < 0) { - r = fd; - goto error; + fd = openat_report_new(dir_fd, path, open_flags, mode, &made_file); + if (fd < 0) { + r = fd; + goto error; + } } } diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index c33a084d3fdbf..32283d4c1fbc5 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -115,6 +115,7 @@ typedef enum XOpenFlags { XO_REGULAR = 1 << 3, /* Fail if the inode is not a regular file */ XO_SOCKET = 1 << 4, /* Fail if the inode is not a socket */ XO_TRIGGER_AUTOMOUNT = 1 << 5, /* Trigger automounts via open_tree(). Requires O_PATH. */ + XO_AUTO_RW_RO = 1 << 6, /* Open in O_RDWR mode if possible, O_RDONLY if not */ } XOpenFlags; int open_mkdir_at_full(int dirfd, const char *path, int flags, XOpenFlags xopen_flags, mode_t mode); diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index d04fbc6768b20..23aa5a5815fd1 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -800,6 +800,90 @@ TEST(xopenat_trigger_automount) { ASSERT_OK_POSITIVE(fd_inode_same(fd, fd2)); } +TEST(xopenat_auto_rw_ro) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int tfd = -EBADF, fd = -EBADF; + int fl; + + assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0); + + /* Regular writable file: XO_AUTO_RW_RO should end up in O_RDWR. */ + + fd = xopenat_full(tfd, "rw", O_CREAT|O_EXCL|O_CLOEXEC, XO_AUTO_RW_RO, 0644); + assert_se(fd >= 0); + ASSERT_OK_ERRNO(fl = fcntl(fd, F_GETFL)); + assert_se((fl & O_ACCMODE) == O_RDWR); + fd = safe_close(fd); + + /* Same thing, but with XO_REGULAR set too. */ + + fd = xopenat_full(tfd, "rw2", O_CREAT|O_EXCL|O_CLOEXEC, XO_AUTO_RW_RO|XO_REGULAR, 0644); + assert_se(fd >= 0); + ASSERT_OK_ERRNO(fl = fcntl(fd, F_GETFL)); + assert_se((fl & O_ACCMODE) == O_RDWR); + fd = safe_close(fd); + + /* Reopen via empty path on an O_PATH fd must also end up in O_RDWR. */ + + _cleanup_close_ int path_fd = xopenat_full(tfd, "rw", O_PATH|O_CLOEXEC, 0, 0); + assert_se(path_fd >= 0); + fd = xopenat_full(path_fd, "", O_CLOEXEC, XO_AUTO_RW_RO, 0); + assert_se(fd >= 0); + ASSERT_OK_ERRNO(fl = fcntl(fd, F_GETFL)); + assert_se((fl & O_ACCMODE) == O_RDWR); + fd = safe_close(fd); + + /* Directories can only be opened read-only: XO_AUTO_RW_RO with O_DIRECTORY must fall back to O_RDONLY. */ + + fd = xopenat_full(tfd, "subdir", O_DIRECTORY|O_CREAT|O_CLOEXEC, XO_AUTO_RW_RO, 0755); + assert_se(fd >= 0); + ASSERT_OK_ERRNO(fl = fcntl(fd, F_GETFL)); + assert_se((fl & O_ACCMODE) == O_RDONLY); + fd = safe_close(fd); + + /* Same for opening an existing directory. */ + + fd = xopenat_full(tfd, "subdir", O_DIRECTORY|O_CLOEXEC, XO_AUTO_RW_RO, 0); + assert_se(fd >= 0); + ASSERT_OK_ERRNO(fl = fcntl(fd, F_GETFL)); + assert_se((fl & O_ACCMODE) == O_RDONLY); + fd = safe_close(fd); + + /* Fallback when the inode is not writable: create a file as read-only mode and verify that + * XO_AUTO_RW_RO falls back to O_RDONLY. Root bypasses mode bits via CAP_DAC_OVERRIDE, so skip + * this when running as root. */ + + if (geteuid() != 0) { + fd = openat(tfd, "ro", O_CREAT|O_EXCL|O_WRONLY|O_CLOEXEC, 0444); + assert_se(fd >= 0); + fd = safe_close(fd); + assert_se(fchmodat(tfd, "ro", 0444, 0) >= 0); + + /* Plain case: no XO_REGULAR. */ + fd = xopenat_full(tfd, "ro", O_CLOEXEC, XO_AUTO_RW_RO, 0); + assert_se(fd >= 0); + ASSERT_OK_ERRNO(fl = fcntl(fd, F_GETFL)); + assert_se((fl & O_ACCMODE) == O_RDONLY); + fd = safe_close(fd); + + /* With XO_REGULAR (exercises the pin-via-O_PATH + reopen path). */ + fd = xopenat_full(tfd, "ro", O_CLOEXEC, XO_AUTO_RW_RO|XO_REGULAR, 0); + assert_se(fd >= 0); + ASSERT_OK_ERRNO(fl = fcntl(fd, F_GETFL)); + assert_se((fl & O_ACCMODE) == O_RDONLY); + fd = safe_close(fd); + + /* Also exercise the empty-path/fd-reopen branch. */ + _cleanup_close_ int ro_path_fd = xopenat_full(tfd, "ro", O_PATH|O_CLOEXEC, 0, 0); + assert_se(ro_path_fd >= 0); + fd = xopenat_full(ro_path_fd, "", O_CLOEXEC, XO_AUTO_RW_RO, 0); + assert_se(fd >= 0); + ASSERT_OK_ERRNO(fl = fcntl(fd, F_GETFL)); + assert_se((fl & O_ACCMODE) == O_RDONLY); + fd = safe_close(fd); + } +} + TEST(xopenat_lock_full) { _cleanup_(rm_rf_physical_and_freep) char *t = NULL; _cleanup_close_ int tfd = -EBADF, fd = -EBADF; From 41a8cc281eb9d76e15b553209d0b7cda4861175e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 23 Apr 2026 08:58:48 +0200 Subject: [PATCH 1289/1296] blockdev-list: pick up read_only property --- src/shared/blockdev-list.c | 8 ++++++++ src/shared/blockdev-list.h | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/shared/blockdev-list.c b/src/shared/blockdev-list.c index d856b1cb48cac..19aa2b48e48f2 100644 --- a/src/shared/blockdev-list.c +++ b/src/shared/blockdev-list.c @@ -189,10 +189,17 @@ int blockdev_list(BlockDevListFlags flags, BlockDevice **ret_devices, size_t *re } _cleanup_free_ char *model = NULL, *vendor = NULL, *subsystem = NULL; + int ro = -1; if (FLAGS_SET(flags, BLOCKDEV_LIST_METADATA)) { (void) blockdev_get_prop(dev, "ID_MODEL_FROM_DATABASE", "ID_MODEL", &model); (void) blockdev_get_prop(dev, "ID_VENDOR_FROM_DATABASE", "ID_VENDOR", &vendor); (void) blockdev_get_subsystem(dev, &subsystem); + + r = device_get_sysattr_bool(dev, "ro"); + if (r < 0) + log_device_debug_errno(dev, r, "Failed to acquire read-only flag of device '%s', ignoring: %m", node); + else + ro = r; } if (ret_devices) { @@ -216,6 +223,7 @@ int blockdev_list(BlockDevListFlags flags, BlockDevice **ret_devices, size_t *re .model = TAKE_PTR(model), .vendor = TAKE_PTR(vendor), .subsystem = TAKE_PTR(subsystem), + .read_only = ro, }; } else { diff --git a/src/shared/blockdev-list.h b/src/shared/blockdev-list.h index 845f336be5b65..d82345435f7e2 100644 --- a/src/shared/blockdev-list.h +++ b/src/shared/blockdev-list.h @@ -10,7 +10,7 @@ typedef enum BlockDevListFlags { BLOCKDEV_LIST_REQUIRE_LUKS = 1 << 3, /* Only consider block devices with LUKS superblocks */ BLOCKDEV_LIST_IGNORE_ROOT = 1 << 4, /* Ignore the block device we are currently booted from */ BLOCKDEV_LIST_IGNORE_EMPTY = 1 << 5, /* Ignore disks of zero size (usually drives without a medium) */ - BLOCKDEV_LIST_METADATA = 1 << 6, /* Fill in model, vendor, subsystem */ + BLOCKDEV_LIST_METADATA = 1 << 6, /* Fill in model, vendor, subsystem, read_only */ } BlockDevListFlags; typedef struct BlockDevice { @@ -21,11 +21,13 @@ typedef struct BlockDevice { char *subsystem; uint64_t diskseq; uint64_t size; /* in bytes */ + int read_only; } BlockDevice; #define BLOCK_DEVICE_NULL (BlockDevice) { \ .diskseq = UINT64_MAX, \ .size = UINT64_MAX, \ + .read_only = -1, \ } void block_device_done(BlockDevice *d); From 71d82cc638363e5293930a883d81e8838c468d12 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 23 Apr 2026 15:46:10 +0200 Subject: [PATCH 1290/1296] blockdev-list: make BLOCKDEV_LIST_IGNORE_ROOT suppress all definitions of the root disk There are various definitions of the root disk, let's suppress them all if the flag is set. So far only the outermost is suppressed, which is a bit weird, given it's "further away" from the rootfs. --- src/shared/blockdev-list.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/shared/blockdev-list.c b/src/shared/blockdev-list.c index 19aa2b48e48f2..5b11c8169477f 100644 --- a/src/shared/blockdev-list.c +++ b/src/shared/blockdev-list.c @@ -8,6 +8,7 @@ #include "blockdev-util.h" #include "device-private.h" #include "device-util.h" +#include "devnum-util.h" #include "errno-util.h" #include "string-util.h" #include "strv.h" @@ -99,14 +100,21 @@ int blockdev_list(BlockDevListFlags flags, BlockDevice **ret_devices, size_t *re size_t n = 0; CLEANUP_ARRAY(l, n, block_device_array_free); - dev_t root_devno = 0; - if (FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_ROOT)) - if (blockdev_get_root(LOG_DEBUG, &root_devno) > 0) { - r = block_get_whole_disk(root_devno, &root_devno); + dev_t root_devno = 0, whole_root_devno = 0; + if (FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_ROOT)) { + r = blockdev_get_root(LOG_DEBUG, &root_devno); + if (r < 0) + log_debug_errno(r, "Failed to get block device of root device, ignoring: %m"); + else if (r > 0) { + r = block_get_whole_disk(root_devno, &whole_root_devno); if (r < 0) - log_debug_errno(r, "Failed to get whole block device of root device: %m"); + log_debug_errno(r, "Failed to get whole block device of root device, ignoring: %m"); } + /* It's fine if root_devno/whole_root_devno are zero here as devnum_set_and_equal() will + * happily take that into account – it is in fact its primary raison d'etre. */ + } + if (sd_device_enumerator_new(&e) < 0) return log_oom(); @@ -138,7 +146,8 @@ int blockdev_list(BlockDevListFlags flags, BlockDevice **ret_devices, size_t *re continue; } - if (devno == root_devno) + if (devnum_set_and_equal(devno, root_devno) || + devnum_set_and_equal(devno, whole_root_devno)) continue; } From f0483f308a4daed188deee776aa7a7a733293642 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 27 Apr 2026 02:04:38 +0900 Subject: [PATCH 1291/1296] format-table: fix potential segfault In format-table.h, TABLE_IN_ADDR is commented as "Takes a union in_addr_union (or a struct in_addr)". However, if we pass struct in_addr to table_add_many(), the function reads more than the size of the struct. --- src/shared/format-table.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/format-table.c b/src/shared/format-table.c index 432e054d4a70c..f678a6722cec9 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -1081,12 +1081,12 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) { break; case TABLE_IN_ADDR: - buffer.address = *va_arg(ap, union in_addr_union *); + buffer.address.in = *va_arg(ap, struct in_addr *); data = &buffer.address.in; break; case TABLE_IN6_ADDR: - buffer.address = *va_arg(ap, union in_addr_union *); + buffer.address.in6 = *va_arg(ap, struct in6_addr *); data = &buffer.address.in6; break; From 05fea7df1bd6579dc382455626e0e84acb2a8912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 25 Apr 2026 13:01:13 +0200 Subject: [PATCH 1292/1296] scsi_id: convert to the new option parser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Common page-code parsing extracted into a parse_page_code() helper. While at it, return real error values (-EINVAL, etc.) rather than -1, and rename retval to r throughout for consistency. The body of main is moved to new run. The closing of logging file descriptors is dropped. They will be closed automatically anyway. Not sure what the original purpose of that code was. The code is also modernized in various places… though more changes could be made. The return convention of help() and similar functions is changed to usual negative/0/1, where 0 means that the caller should quit. set_inq_values would return positive error values too, which was previously ignored. It's not entirely clear, but that doesn't seem to have been on purpose. --- src/udev/scsi_id/scsi_id.c | 371 +++++++++++++++------------------ src/udev/scsi_id/scsi_serial.c | 35 ++-- 2 files changed, 176 insertions(+), 230 deletions(-) diff --git a/src/udev/scsi_id/scsi_id.c b/src/udev/scsi_id/scsi_id.c index ba7a7fe5f522d..8f580a8cec6f5 100644 --- a/src/udev/scsi_id/scsi_id.c +++ b/src/udev/scsi_id/scsi_id.c @@ -5,7 +5,6 @@ */ #include -#include #include #include @@ -15,6 +14,10 @@ #include "extract-word.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" +#include "help-util.h" +#include "main-func.h" +#include "options.h" #include "parse-util.h" #include "scsi_id.h" #include "string-util.h" @@ -23,23 +26,6 @@ #include "udev-util.h" #include "utf8.h" -static const struct option options[] = { - { "device", required_argument, NULL, 'd' }, - { "config", required_argument, NULL, 'f' }, - { "page", required_argument, NULL, 'p' }, - { "denylisted", no_argument, NULL, 'b' }, - { "allowlisted", no_argument, NULL, 'g' }, - { "blacklisted", no_argument, NULL, 'b' }, /* backward compat */ - { "whitelisted", no_argument, NULL, 'g' }, /* backward compat */ - { "replace-whitespace", no_argument, NULL, 'u' }, - { "sg-version", required_argument, NULL, 's' }, - { "verbose", no_argument, NULL, 'v' }, - { "version", no_argument, NULL, 'V' }, /* don't advertise -V */ - { "export", no_argument, NULL, 'x' }, - { "help", no_argument, NULL, 'h' }, - {} -}; - static bool all_good = false; static bool dev_specified = false; static char config_file[MAX_PATH_LEN] = "/etc/scsi_id.config"; @@ -101,31 +87,22 @@ static void set_type(unsigned type_num, char *to, size_t len) { * * vendor and model can end in '\n'. */ -static int get_file_options(const char *vendor, const char *model, - int *argc, char ***newargv) { +static int get_file_options(const char *vendor, const char *model, char ***ret_argv) { _cleanup_free_ char *vendor_in = NULL, *model_in = NULL, *options_in = NULL; /* read in from file */ _cleanup_strv_free_ char **options_argv = NULL; - _cleanup_fclose_ FILE *f = NULL; - int lineno, r; + int r; - f = fopen(config_file, "re"); + _cleanup_fclose_ FILE *f = fopen(config_file, "re"); if (!f) { if (errno == ENOENT) - return 1; - else { - log_error_errno(errno, "can't open %s: %m", config_file); - return -1; - } + goto finish; + return log_error_errno(errno, "Cannot open %s: %m", config_file); } - *newargv = NULL; - lineno = 0; - for (;;) { + for (int lineno = 0;;) { _cleanup_free_ char *buffer = NULL, *key = NULL, *value = NULL; const char *buf; - vendor_in = model_in = options_in = NULL; - r = read_line(f, MAX_BUFFER_LEN, &buffer); if (r < 0) return log_error_errno(r, "read_line() on line %d of %s failed: %m", lineno, config_file); @@ -197,189 +174,179 @@ static int get_file_options(const char *vendor, const char *model, } - if (vendor_in == NULL && model_in == NULL && options_in == NULL) - return 1; /* No matches */ + if (!vendor_in && !model_in && !options_in) + goto finish; /* No matches */ - /* - * Something matched. Allocate newargv, and store - * values found in options_in. - */ + /* Something matched. Allocate newargv, and store values found in options_in. */ options_argv = strv_split(options_in, " \t"); if (!options_argv) return log_oom(); - r = strv_prepend(&options_argv, ""); /* getopt skips over argv[0] */ + r = strv_prepend(&options_argv, ""); /* argv[0] is skipped */ if (r < 0) return r; - *newargv = TAKE_PTR(options_argv); - *argc = strv_length(*newargv); + finish: + *ret_argv = TAKE_PTR(options_argv); + return !!*ret_argv; /* true if something matched, false otherwise */ +} + +static int parse_page_code(const char *value, enum page_code *ret) { + assert(value); + assert(ret); + + if (streq(value, "0x80")) + *ret = PAGE_80; + else if (streq(value, "0x83")) + *ret = PAGE_83; + else if (streq(value, "pre-spc3-83")) + *ret = PAGE_83_PRE_SPC3; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown page code '%s'", value); return 0; } -static void help(void) { - printf("Usage: %s [OPTION...] DEVICE\n\n" - "SCSI device identification.\n\n" - " -h --help Print this message\n" - " --version Print version of the program\n\n" - " -d --device= Device node for SG_IO commands\n" - " -f --config= Location of config file\n" - " -p --page=0x80|0x83|pre-spc3-83 SCSI page (0x80, 0x83, pre-spc3-83)\n" - " -s --sg-version=3|4 Use SGv3 or SGv4\n" - " -b --denylisted Treat device as denylisted\n" - " -g --allowlisted Treat device as allowlisted\n" - " -u --replace-whitespace Replace all whitespace by underscores\n" - " -v --verbose Verbose logging\n" - " -x --export Print values as environment keys\n", - program_invocation_short_name); +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_cmdline("[OPTION...] DEVICE"); + help_abstract("SCSI device identification."); + help_section("Options:"); + + return table_print_or_warn(options); } -static int set_options(int argc, char **argv, - char *maj_min_dev) { - int option, r; +static int set_options(int argc, char **argv, char *maj_min_dev) { + assert(argc >= 0); + assert(argv); - /* - * optind is a global extern used by getopt. Since we can call - * set_options twice (once for command line, and once for config - * file) we have to reset this back to 1. - */ - optind = 1; - while ((option = getopt_long(argc, argv, "d:f:gp:uvVxhbs:", options, NULL)) >= 0) - switch (option) { - case 'b': - all_good = false; - break; + OptionParser state = { argc, argv }; + const char *arg; + int r; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) + switch (c) { + + OPTION_COMMON_HELP: + return help(); - case 'd': + OPTION_COMMON_VERSION_WITH_HIDDEN_V: + return version(); + + OPTION('d', "device", "PATH", "Device node for SG_IO commands"): dev_specified = true; - strscpy(maj_min_dev, MAX_PATH_LEN, optarg); + strscpy(maj_min_dev, MAX_PATH_LEN, arg); break; - case 'f': - strscpy(config_file, MAX_PATH_LEN, optarg); + OPTION('f', "config", "PATH", "Location of config file"): + strscpy(config_file, MAX_PATH_LEN, arg); break; - case 'g': - all_good = true; + OPTION('p', "page", "0x80|0x83|pre-spc3-83", "SCSI page"): + r = parse_page_code(arg, &default_page_code); + if (r < 0) + return r; break; - case 'h': - help(); - exit(EXIT_SUCCESS); - - case 'p': - if (streq(optarg, "0x80")) - default_page_code = PAGE_80; - else if (streq(optarg, "0x83")) - default_page_code = PAGE_83; - else if (streq(optarg, "pre-spc3-83")) - default_page_code = PAGE_83_PRE_SPC3; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown page code '%s'", - optarg); + OPTION('s', "sg-version", "3|4", "Use SGv3 or SGv4"): + r = safe_atoi(arg, &sg_version); + if (r < 0) + return log_error_errno(r, "Invalid SG version '%s'", arg); + if (!IN_SET(sg_version, 3, 4)) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), + "Unknown SG version '%s'", arg); break; - case 's': - r = safe_atoi(optarg, &sg_version); - if (r < 0) - return log_error_errno(r, - "Invalid SG version '%s'", - optarg); - if (sg_version < 3 || sg_version > 4) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown SG version '%s'", - optarg); + OPTION('b', "denylisted", NULL, "Treat device as denylisted"): {} + OPTION('b', "blacklisted", NULL, /* help= */ NULL): /* backward compat */ + all_good = false; + break; + + OPTION('g', "allowlisted", NULL, "Treat device as allowlisted"): {} + OPTION('g', "whitelisted", NULL, /* help= */ NULL): /* backward compat */ + all_good = true; break; - case 'u': + OPTION('u', "replace-whitespace", NULL, "Replace all whitespace by underscores"): reformat_serial = true; break; - case 'v': + OPTION('v', "verbose", NULL, "Verbose logging"): log_set_target(LOG_TARGET_CONSOLE); log_set_max_level(LOG_DEBUG); log_open(); break; - case 'V': - version(); - exit(EXIT_SUCCESS); - - case 'x': + OPTION('x', "export", NULL, "Print values as environment keys"): export = true; break; - - case '?': - return -1; - - default: - assert_not_reached(); } - if (optind < argc && !dev_specified) { + char **args = option_parser_get_args(&state); + if (!strv_isempty(args) && !dev_specified) { dev_specified = true; - strscpy(maj_min_dev, MAX_PATH_LEN, argv[optind]); + strscpy(maj_min_dev, MAX_PATH_LEN, args[0]); } - return 0; + return 1; } -static int per_dev_options(struct scsi_id_device *dev_scsi, int *good_bad, int *page_code) { - _cleanup_strv_free_ char **newargv = NULL; - int retval; - int newargc; - int option; +static int per_dev_options(struct scsi_id_device *dev_scsi, int *good_bad, enum page_code *page_code) { + int r; + + assert(dev_scsi); + assert(good_bad); + assert(page_code); *good_bad = all_good; *page_code = default_page_code; - retval = get_file_options(vendor_str, model_str, &newargc, &newargv); - - optind = 1; /* reset this global extern */ - while (retval == 0) { - option = getopt_long(newargc, newargv, "bgp:", options, NULL); - if (option == -1) - break; + _cleanup_strv_free_ char **newargv = NULL; + r = get_file_options(vendor_str, model_str, &newargv); + if (r <= 0) + return r; - switch (option) { - case 'b': - *good_bad = 0; - break; + size_t newargc = strv_length(newargv); + if (newargc > INT_MAX) + return log_oom(); /* Close enough :) */ - case 'g': - *good_bad = 1; - break; + OptionParser state = { newargc, newargv }; + const Option *opt; + const char *arg; - case 'p': - if (streq(optarg, "0x80")) { - *page_code = PAGE_80; - } else if (streq(optarg, "0x83")) { - *page_code = PAGE_83; - } else if (streq(optarg, "pre-spc3-83")) { - *page_code = PAGE_83_PRE_SPC3; - } else { - log_error("Unknown page code '%s'", optarg); - retval = -1; - } - break; + /* We reuse the option parser, but only a subset of the options is supported here. + * If any others are encountered, return an error. */ - default: - log_error("Unknown or bad option '%c' (0x%x)", option, (unsigned) option); - retval = -1; - } - } + FOREACH_OPTION_FULL(&state, c, &opt, &arg, /* on_error= */ return c) + if (opt->short_code == 'b') + *good_bad = 0; + else if (opt->short_code == 'g') + *good_bad = 1; + else if (opt->short_code == 'p') { + r = parse_page_code(arg, page_code); + if (r < 0) + return r; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Option %s not supported in the config file.", + strnull(option_get_synopsis("", opt, "/", /* show_metavar=*/ false))); - return retval; + return 0; } static int set_inq_values(struct scsi_id_device *dev_scsi, const char *path) { - int retval; + int r; dev_scsi->use_sg = sg_version; - retval = scsi_std_inquiry(dev_scsi, path); - if (retval) - return retval; + r = scsi_std_inquiry(dev_scsi, path); + if (r < 0) + return r; encode_devnode_name(dev_scsi->vendor, vendor_enc_str, sizeof(vendor_enc_str)); encode_devnode_name(dev_scsi->model, model_enc_str, sizeof(model_enc_str)); @@ -400,28 +367,25 @@ static bool scsi_string_is_valid(const char *s) { /* * scsi_id: try to get an id, if one is found, printf it to stdout. - * returns a value passed to exit() - 0 if printed an id, else 1. */ static int scsi_id(char *maj_min_dev) { struct scsi_id_device dev_scsi = {}; - int good_dev; - int page_code; - int retval = 0; + enum page_code page_code; + int good_dev, r; - if (set_inq_values(&dev_scsi, maj_min_dev) < 0) { - retval = 1; - goto out; - } + r = set_inq_values(&dev_scsi, maj_min_dev); + if (r < 0) + return r; /* get per device (vendor + model) options from the config file */ - per_dev_options(&dev_scsi, &good_dev, &page_code); - if (!good_dev) { - retval = 1; - goto out; - } + r = per_dev_options(&dev_scsi, &good_dev, &page_code); + if (r < 0) + return r; + if (!good_dev) + return -EIO; /* read serial number from mode pages (no values for optical drives) */ - scsi_get_serial(&dev_scsi, maj_min_dev, page_code, MAX_SERIAL_LEN); + (void) scsi_get_serial(&dev_scsi, maj_min_dev, page_code, MAX_SERIAL_LEN); if (export) { char serial_str[MAX_SERIAL_LEN]; @@ -453,13 +417,11 @@ static int scsi_id(char *maj_min_dev) { printf("ID_TARGET_PORT=%s\n", dev_scsi.tgpt_group); if (scsi_string_is_valid(dev_scsi.unit_serial_number)) printf("ID_SCSI_SERIAL=%s\n", dev_scsi.unit_serial_number); - goto out; + return 0; } - if (dev_scsi.serial[0] == '\0') { - retval = 1; - goto out; - } + if (dev_scsi.serial[0] == '\0') + return -ENODATA; if (reformat_serial) { char serial_str[MAX_SERIAL_LEN]; @@ -467,19 +429,17 @@ static int scsi_id(char *maj_min_dev) { udev_replace_whitespace(dev_scsi.serial, serial_str, sizeof(serial_str)-1); udev_replace_chars(serial_str, NULL); printf("%s\n", serial_str); - goto out; + return 0; } printf("%s\n", dev_scsi.serial); -out: - return retval; + return 0; } -int main(int argc, char **argv) { +static int run(int argc, char **argv) { _cleanup_strv_free_ char **newargv = NULL; - int retval = 0; char maj_min_dev[MAX_PATH_LEN]; - int newargc; + int r; (void) udev_parse_config(); log_setup(); @@ -487,35 +447,30 @@ int main(int argc, char **argv) { /* * Get config file options. */ - retval = get_file_options(NULL, NULL, &newargc, &newargv); - if (retval < 0) { - retval = 1; - goto exit; - } - if (retval == 0) { - assert(newargv); - - if (set_options(newargc, newargv, maj_min_dev) < 0) { - retval = 2; - goto exit; - } + r = get_file_options(NULL, NULL, &newargv); + if (r < 0) + return r; + if (r == 1) { + size_t newargc = strv_length(newargv); + if (newargc > INT_MAX) + return log_oom(); /* Close enough :) */ + + r = set_options(newargc, newargv, maj_min_dev); + if (r <= 0) + return r; } /* * Get command line options (overriding any config file settings). */ - if (set_options(argc, argv, maj_min_dev) < 0) - exit(EXIT_FAILURE); - - if (!dev_specified) { - log_error("No device specified."); - retval = 1; - goto exit; - } + r = set_options(argc, argv, maj_min_dev); + if (r <= 0) + return r; - retval = scsi_id(maj_min_dev); + if (!dev_specified) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No device specified."); -exit: - log_close(); - return retval; + return scsi_id(maj_min_dev); } + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/udev/scsi_id/scsi_serial.c b/src/udev/scsi_id/scsi_serial.c index 95268635cb637..6d8974eaa3967 100644 --- a/src/udev/scsi_id/scsi_serial.c +++ b/src/udev/scsi_id/scsi_serial.c @@ -15,6 +15,7 @@ #include #include "devnum-util.h" +#include "fd-util.h" #include "hexdecoct.h" #include "log.h" #include "random-util.h" @@ -762,30 +763,23 @@ static int do_scsi_page80_inquiry(struct scsi_id_device *dev_scsi, int fd, } int scsi_std_inquiry(struct scsi_id_device *dev_scsi, const char *devname) { - int fd; - unsigned char buf[SCSI_INQ_BUFF_LEN]; + unsigned char buf[SCSI_INQ_BUFF_LEN] = {}; struct stat statbuf; - int err = 0; + int r; - fd = open(devname, O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY); - if (fd < 0) { - log_debug_errno(errno, "scsi_id: cannot open %s: %m", devname); - return 1; - } + _cleanup_close_ int fd = open(devname, O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY); + if (fd < 0) + return log_debug_errno(errno, "scsi_id: cannot open %s: %m", devname); + + if (fstat(fd, &statbuf) < 0) + return log_debug_errno(errno, "scsi_id: cannot stat %s: %m", devname); - if (fstat(fd, &statbuf) < 0) { - log_debug_errno(errno, "scsi_id: cannot stat %s: %m", devname); - err = 2; - goto out; - } format_devnum(statbuf.st_rdev, dev_scsi->kernel); - memzero(buf, SCSI_INQ_BUFF_LEN); - err = scsi_inquiry(dev_scsi, fd, 0, 0, buf, SCSI_INQ_BUFF_LEN); - if (err < 0) - goto out; + r = scsi_inquiry(dev_scsi, fd, 0, 0, buf, SCSI_INQ_BUFF_LEN); + if (r < 0) + return r; - err = 0; memcpy(dev_scsi->vendor, buf + 8, 8); dev_scsi->vendor[8] = '\0'; memcpy(dev_scsi->model, buf + 16, 16); @@ -793,10 +787,7 @@ int scsi_std_inquiry(struct scsi_id_device *dev_scsi, const char *devname) { memcpy(dev_scsi->revision, buf + 32, 4); dev_scsi->revision[4] = '\0'; dev_scsi->type = buf[0] & 0x1f; - -out: - close(fd); - return err; + return 0; } int scsi_get_serial(struct scsi_id_device *dev_scsi, const char *devname, From 33a37278aa81c2e9c4cdc15d470f4e5ee0ab2dc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 25 Apr 2026 19:06:55 +0200 Subject: [PATCH 1293/1296] iocost: convert to the new option parser --help output only has changes expected in the new style. Co-developed-by: Claude Opus 4.7 (1M context) --- src/udev/iocost/iocost.c | 88 +++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 46 deletions(-) diff --git a/src/udev/iocost/iocost.c b/src/udev/iocost/iocost.c index 3d473d469d5a8..0767ccba09525 100644 --- a/src/udev/iocost/iocost.c +++ b/src/udev/iocost/iocost.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-device.h" @@ -11,7 +10,10 @@ #include "conf-parser.h" #include "device-util.h" #include "devnum-util.h" +#include "format-table.h" +#include "help-util.h" #include "main-func.h" +#include "options.h" #include "string-util.h" #include "strv.h" #include "udev-util.h" @@ -50,53 +52,52 @@ static int parse_config(void) { } static int help(void) { - printf("%s [OPTIONS...]\n\n" - "Set up iocost model and qos solutions for block devices\n" - "\nCommands:\n" - " apply [SOLUTION] Apply solution for the device if\n" - " found, do nothing otherwise\n" - " query Query the known solution for\n" - " the device\n" - "\nOptions:\n" - " -h --help Show this help\n" - " --version Show package version\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; + int r; - return 0; -} + r = option_parser_get_help_table(&options); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - }; + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {} - }; + (void) table_sync_column_widths(0, options, verbs); + + help_cmdline("[OPTIONS...] COMMAND"); + help_abstract("Set up iocost model and qos solutions for block devices."); + + help_section("Commands:"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + help_section("Options:"); + return table_print_or_warn(options); +} - int c; +VERB_COMMON_HELP_HIDDEN(help); - assert(argc >= 1); +static int parse_argv(int argc, char *argv[], char ***remaining_args) { + assert(argc >= 0); assert(argv); + assert(remaining_args); + + OptionParser state = { argc, argv }; + const char *arg; - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *remaining_args = option_parser_get_args(&state); return 1; } @@ -280,32 +281,27 @@ static int query_solutions_for_path(const char *path) { return 0; } +VERB(verb_query, "query", "PATH", 2, 2, 0, + "Query the known solution for the device"); static int verb_query(int argc, char *argv[], uintptr_t _data, void *userdata) { return query_solutions_for_path(ASSERT_PTR(argv[1])); } +VERB(verb_apply, "apply", "PATH [SOLUTION]", 2, 3, 0, + "Apply solution for the device if found, do nothing otherwise"); static int verb_apply(int argc, char *argv[], uintptr_t _data, void *userdata) { return apply_solution_for_path( ASSERT_PTR(argv[1]), argc > 2 ? ASSERT_PTR(argv[2]) : NULL); } -static int iocost_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "query", 2, 2, 0, verb_query }, - { "apply", 2, 3, 0, verb_apply }, - {}, - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { + char **args = NULL; int r; log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -313,7 +309,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - return iocost_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); From 78208556b281bd6ee8a95d16a517152728010475 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 24 Apr 2026 16:00:21 +0200 Subject: [PATCH 1294/1296] job: add a "finished" state for jobs So far when a job completed we'd never transition into any new state, we'd just do some final processing work (such as notifying clients) and destroy it. Let's change that, and briefly enter a final state: "finished". This is useful so that code that notifies clients can generically send the quadruplet of id, type, state, result for any change notification and naturally can communicate job completion that way: by setting the state field to "finished". Prompted by the discussions in: #41583 --- src/core/job.c | 14 +++++++++----- src/core/job.h | 1 + 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/core/job.c b/src/core/job.c index 920d246b8849a..b4578a946c6a3 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -140,17 +140,18 @@ static void job_set_state(Job *j, JobState state) { if (j->state == state) return; + JobState old_state = j->state; j->state = state; if (!j->installed) return; if (j->state == JOB_RUNNING) + /* This job changed into running, count up */ j->manager->n_running_jobs++; - else { - assert(j->state == JOB_WAITING); + else if (old_state == JOB_RUNNING) { + /* This job changed away from running into another state, count down. */ assert(j->manager->n_running_jobs > 0); - j->manager->n_running_jobs--; if (j->manager->n_running_jobs <= 0) @@ -1018,6 +1019,8 @@ int job_finish_and_invalidate(Job *j, JobResult result, bool recursive, bool alr j->result = result; + job_set_state(j, JOB_FINISHED); + log_unit_debug(u, "Job %" PRIu32 " %s/%s finished, result=%s", j->id, u->id, job_type_to_string(t), job_result_to_string(result)); @@ -1646,8 +1649,9 @@ int job_get_after(Job *j, Job*** ret) { } static const char* const job_state_table[_JOB_STATE_MAX] = { - [JOB_WAITING] = "waiting", - [JOB_RUNNING] = "running", + [JOB_WAITING] = "waiting", + [JOB_RUNNING] = "running", + [JOB_FINISHED] = "finished", }; DEFINE_STRING_TABLE_LOOKUP(job_state, JobState); diff --git a/src/core/job.h b/src/core/job.h index ff1d25ff5022b..a8eca99505d51 100644 --- a/src/core/job.h +++ b/src/core/job.h @@ -52,6 +52,7 @@ enum JobType { typedef enum JobState { JOB_WAITING, JOB_RUNNING, + JOB_FINISHED, _JOB_STATE_MAX, _JOB_STATE_INVALID = -EINVAL, } JobState; From 0d6d2c9ebda27a7da2da249458eb44610968bc97 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 27 Apr 2026 12:34:27 +0200 Subject: [PATCH 1295/1296] vmspawn-varlink: treat QMP disconnect as success for Terminate QMP "quit" tells QEMU to exit, which races the reply with the socket EOF: sometimes the disconnect lands in qmp_client_fail_pending() with -ECONNRESET before the reply has been parsed. The shared completion callback then translates that into io.systemd.MachineInstance.NotConnected, turning the desired outcome into a varlink error. This is exactly what TEST-87-AUX-UTILS-VM exposes during its repeated start/pause/resume/terminate stress loop: a successful Pause/Describe followed milliseconds later by a Terminate that fails with NotConnected when the disconnect path wins the race. Give Terminate its own completion callback that treats disconnect-class errors as success, since QEMU shutting down is the whole point of "quit". The other simple commands (Pause, Resume, PowerOff, Reboot) keep the existing semantics: they expect QMP to remain alive, so NotConnected is the correct reply for them. Link: https://github.com/systemd/systemd/actions/runs/24986080288/job/73159585425?pr=41835 Signed-off-by: Christian Brauner (Amutable) --- src/vmspawn/vmspawn-varlink.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/vmspawn/vmspawn-varlink.c b/src/vmspawn/vmspawn-varlink.c index 51a1091e40a0c..9aa6afeae0385 100644 --- a/src/vmspawn/vmspawn-varlink.c +++ b/src/vmspawn/vmspawn-varlink.c @@ -60,6 +60,29 @@ static int on_qmp_simple_complete( return 0; } +/* "quit" tells QEMU to exit, which races the QMP reply with the socket EOF — sometimes the + * disconnect lands in qmp_client_fail_pending() before the reply has been parsed. For Terminate + * that's the desired outcome, so treat disconnect-class errors as success. */ +static int on_qmp_terminate_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + sd_varlink *link = ASSERT_PTR(userdata); + + assert(client); + + if (error < 0 && !ERRNO_IS_DISCONNECT(error)) + (void) qmp_error_to_varlink(link, error_desc, error); + else + (void) sd_varlink_reply(link, NULL); + + sd_varlink_unref(link); + return 0; +} + static int qmp_execute_varlink_async( VmspawnVarlinkContext *ctx, sd_varlink *link, @@ -87,7 +110,7 @@ static int qmp_execute_simple_async(sd_varlink *link, VmspawnVarlinkContext *ctx } static int vl_method_terminate(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { - return qmp_execute_simple_async(link, ASSERT_PTR(userdata), "quit"); + return qmp_execute_varlink_async(ASSERT_PTR(userdata), link, "quit", /* arguments= */ NULL, on_qmp_terminate_complete); } static int vl_method_pause(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { From 81c6ce9e752a2d5cd04c47d71e9fc2ad15a122a4 Mon Sep 17 00:00:00 2001 From: Simran Singh Date: Wed, 10 Dec 2025 06:14:53 +0530 Subject: [PATCH 1296/1296] clonesetup: add support to clone devices via /etc/clonetab Adds dm-clone device setup at boot via a new /etc/clonetab config file, following the crypttab/veritytab pattern. - Add systemd-clonesetup-generator to parse /etc/clonetab and generate units. - Add systemd-clonesetup binary to create/remove dm-clone devices via ioctl. - Add clonesetup.target for ordering dm-clone activation at boot. - Add region_size= option in clonetab to configure dm-clone hydration granularity. - Add clonetab(5) and systemd-clonesetup-generator(8) man pages. Fixes: https://github.com/systemd/systemd/issues/39500 --- docs/ENVIRONMENT.md | 4 + man/clonetab.xml | 119 +++++++++ man/rules/meson.build | 2 + man/systemd-clonesetup-generator.xml | 50 ++++ meson.build | 2 + src/basic/special.h | 1 + src/clonesetup/clonesetup-generator.c | 200 +++++++++++++++ src/clonesetup/clonesetup-ioctl.c | 213 ++++++++++++++++ src/clonesetup/clonesetup-ioctl.h | 16 ++ src/clonesetup/clonesetup.c | 229 ++++++++++++++++++ src/clonesetup/meson.build | 18 ++ .../TEST-90-CLONESETUP/meson.build | 8 + test/integration-tests/meson.build | 1 + test/units/TEST-90-CLONESETUP.sh | 107 ++++++++ units/clonesetup.target | 12 + units/meson.build | 4 + 16 files changed, 986 insertions(+) create mode 100644 man/clonetab.xml create mode 100644 man/systemd-clonesetup-generator.xml create mode 100644 src/clonesetup/clonesetup-generator.c create mode 100644 src/clonesetup/clonesetup-ioctl.c create mode 100644 src/clonesetup/clonesetup-ioctl.h create mode 100644 src/clonesetup/clonesetup.c create mode 100644 src/clonesetup/meson.build create mode 100644 test/integration-tests/TEST-90-CLONESETUP/meson.build create mode 100755 test/units/TEST-90-CLONESETUP.sh create mode 100644 units/clonesetup.target diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index 53f4a1a60c896..3804cdfefaa51 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -72,6 +72,10 @@ All tools: `/etc/veritytab`. Only useful for debugging. Currently only supported by `systemd-veritysetup-generator`. +* `$SYSTEMD_CLONETAB` — if set, use this path instead of + `/etc/clonetab`. Only useful for debugging. Currently only supported by + `systemd-clonesetup-generator`. + * `$SYSTEMD_DEFAULT_HOSTNAME` — override the compiled-in fallback hostname (relevant in particular for the system manager and `systemd-hostnamed`). Must be a valid hostname (either a single label or a FQDN). diff --git a/man/clonetab.xml b/man/clonetab.xml new file mode 100644 index 0000000000000..e19777f59237e --- /dev/null +++ b/man/clonetab.xml @@ -0,0 +1,119 @@ + + + + + + + + clonetab + systemd + + + + clonetab + 5 + + + + clonetab + Configuration for dm-clone block devices + + + + /etc/clonetab + + + + Description + + The /etc/clonetab file describes + dm-clone block devices that are set up during system boot. + + Empty lines and lines starting with the # + character are ignored. Each of the remaining lines describes one + dm-clone device. Fields are delimited by white space. + + Each line is in the formname source-device destination-device metadata-device [options] + The first four fields are mandatory, the fifth is optional. + + The four fields of /etc/clonetab are defined as follows: + + + + The first field contains the name of the resulting dm-clone device; its + block device is set up below /dev/mapper/. + + The second field contains a path to the read-only source block device. + This is the device whose data is cloned to the destination device. Reads to regions not + yet hydrated are served directly from this device. + + The third field contains a path to the writable destination block device. + The source device's data is copied here in the background. It must be at least as + large as the source device. + + The fourth field contains a path to the metadata block device. This + small device tracks which regions of the destination have been hydrated and is managed + exclusively by dm-clone. + + The fifth field, if present, contains comma-separated key=value + options. The following option is supported: + + + + + Controls the granularity of background hydration copying — how much + data is copied at a time. Region size is specified in 512-byte sectors, so + region_size=8 means 8 × 512 = 4KB per region. One region is the + atomic unit dm-clone tracks: it is either fully hydrated (copied to the destination) + or not, never partially. If a copy is interrupted mid-region, that whole region is + retried from scratch on next boot. Smaller regions mean finer progress tracking; + larger regions reduce metadata overhead. Must be a power of two between + 8 and 2097152 sectors. Defaults to + 8 (4KB). For background, see + dm-clone kernel documentation. + Device mapper sectors are always 512 bytes regardless of physical block size + (include/linux/blk_types.h: SECTOR_SIZE = 1 << 9). + + + + + If no options are needed, the field may be omitted entirely, + - may be used as a placeholder, or a quoted empty string + "" may be used (it is silently ignored). + + + + At early boot and when the system manager configuration is reloaded, this file is + translated into native systemd units by + systemd-clonesetup-generator8. + + + + Examples + + + /etc/clonetab + Clone a source device to a destination, using a separate metadata device: + mydevice /dev/sdb /dev/sdc /dev/sdd + + + + /etc/clonetab + Clone a source device to a destination, using a separate metadata device: + mydevice /dev/sdb /dev/sdc /dev/sdd region_size=16 + + + + + See Also + + systemd1 + systemd-clonesetup-generator8 + dmsetup8 + + + + diff --git a/man/rules/meson.build b/man/rules/meson.build index 525c56b1d3b34..34572f22279d1 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -10,6 +10,7 @@ manpages = [ ['coredump.conf', '5', ['coredump.conf.d'], 'ENABLE_COREDUMP'], ['coredumpctl', '1', [], 'ENABLE_COREDUMP'], ['crypttab', '5', [], 'HAVE_LIBCRYPTSETUP'], + ['clonetab', '5', [], ''], ['daemon', '7', [], ''], ['dnssec-trust-anchors.d', '5', @@ -1013,6 +1014,7 @@ manpages = [ ['systemd-creds', '1', [], ''], ['systemd-cryptenroll', '1', [], 'HAVE_LIBCRYPTSETUP'], ['systemd-cryptsetup-generator', '8', [], 'HAVE_LIBCRYPTSETUP'], + ['systemd-clonesetup-generator', '8', [], ''], ['systemd-cryptsetup', '8', ['systemd-cryptsetup@.service'], diff --git a/man/systemd-clonesetup-generator.xml b/man/systemd-clonesetup-generator.xml new file mode 100644 index 0000000000000..28c8b5ad004c4 --- /dev/null +++ b/man/systemd-clonesetup-generator.xml @@ -0,0 +1,50 @@ + + + + + + + + systemd-clonesetup-generator + systemd + + + + systemd-clonesetup-generator + 8 + + + + systemd-clonesetup-generator + Unit generator for /etc/clonetab + + + + /usr/lib/systemd/system-generators/systemd-clonesetup-generator + + + + Description + + systemd-clonesetup-generator is a generator that translates + /etc/clonetab into native systemd units early at boot and when + configuration of the system manager is reloaded. This will create + systemd-clonesetup@.service8 + units as necessary. + + systemd-clonesetup-generator implements + systemd.generator7. + + + + See Also + + systemd1 + clonetab5 + systemd-clonesetup@.service8 + dmsetup8 + + + + diff --git a/meson.build b/meson.build index 4f1a791bc7651..faf4209dfd1f9 100644 --- a/meson.build +++ b/meson.build @@ -291,6 +291,7 @@ conf.set_quoted('SYSTEMD_USERWORK_PATH', libexecdir / 'syst conf.set_quoted('SYSTEMD_MOUNTWORK_PATH', libexecdir / 'systemd-mountwork') conf.set_quoted('SYSTEMD_NSRESOURCEWORK_PATH', libexecdir / 'systemd-nsresourcework') conf.set_quoted('SYSTEMD_VERITYSETUP_PATH', libexecdir / 'systemd-veritysetup') +conf.set_quoted('SYSTEMD_CLONESETUP_PATH', bindir / 'systemd-clonesetup') conf.set_quoted('SYSTEM_CONFIG_UNIT_DIR', pkgsysconfdir / 'system') conf.set_quoted('SYSTEM_DATA_UNIT_DIR', systemunitdir) conf.set_quoted('SYSTEM_ENV_GENERATOR_DIR', systemenvgeneratordir) @@ -2075,6 +2076,7 @@ subdir('src/debug-generator') subdir('src/delta') subdir('src/detect-virt') subdir('src/dissect') +subdir('src/clonesetup') subdir('src/environment-d-generator') subdir('src/escape') subdir('src/factory-reset') diff --git a/src/basic/special.h b/src/basic/special.h index a5cdfebae57be..bdf62327f73cb 100644 --- a/src/basic/special.h +++ b/src/basic/special.h @@ -97,6 +97,7 @@ #define SPECIAL_PCRFS_ROOT_SERVICE "systemd-pcrfs-root.service" #define SPECIAL_VALIDATEFS_SERVICE "systemd-validatefs@.service" #define SPECIAL_HIBERNATE_RESUME_SERVICE "systemd-hibernate-resume.service" +#define SPECIAL_CLONESETUP_TARGET "clonesetup.target" /* Services systemd relies on */ #define SPECIAL_DBUS_SERVICE "dbus.service" diff --git a/src/clonesetup/clonesetup-generator.c b/src/clonesetup/clonesetup-generator.c new file mode 100644 index 0000000000000..32c5bb144a39d --- /dev/null +++ b/src/clonesetup/clonesetup-generator.c @@ -0,0 +1,200 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "alloc-util.h" +#include "errno-util.h" +#include "dropin.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "generator.h" +#include "log.h" +#include "path-util.h" +#include "special.h" +#include "string-util.h" +#include "unit-name.h" + +static const char *arg_dest = NULL; + +/* Generate unit files that call the systemd-clonesetup binary to create or remove clone devices. */ +static int generate_clone_units(const char *clone_name, const char *source_dev, const char *dest_dev, + const char *metadata_dev, const char *options) { + + /* unit files for each device */ + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *source_unit = NULL, *dest_unit = NULL, *metadata_unit = NULL, + *escaped_source = NULL, *escaped_dest = NULL, *escaped_metadata = NULL, + *escaped_options = NULL, *e = NULL, *unit = NULL, *clone_dev_path = NULL, + *dmname = NULL; + int r; + + assert(clone_name); + assert(source_dev); + assert(dest_dev); + assert(metadata_dev); + + /* create clone_dev_path that holds path for new cloned device */ + clone_dev_path = path_join("/dev/mapper", clone_name); + if (!clone_dev_path) + return log_oom(); + + /* escape clone name */ + e = unit_name_escape(clone_name); + if (!e) + return log_oom(); + + /* Generate unit name for the clone service */ + r = unit_name_build("systemd-clonesetup", e, ".service", &unit); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + /* Generate unit names for dependencies */ + r = unit_name_from_path(source_dev, ".device", &source_unit); + if (r < 0) + return log_error_errno(r, "Failed to generate source device unit name: %m"); + + r = unit_name_from_path(dest_dev, ".device", &dest_unit); + if (r < 0) + return log_error_errno(r, "Failed to generate dest device unit name: %m"); + + r = unit_name_from_path(metadata_dev, ".device", &metadata_unit); + if (r < 0) + return log_error_errno(r, "Failed to generate metadata device unit name: %m"); + + /* Escape device paths for ExecStart command */ + escaped_source = cescape(source_dev); + if (!escaped_source) + return log_oom(); + + escaped_dest = cescape(dest_dev); + if (!escaped_dest) + return log_oom(); + + escaped_metadata = cescape(metadata_dev); + if (!escaped_metadata) + return log_oom(); + + if (options) { + escaped_options = cescape(options); + if (!escaped_options) + return log_oom(); + } + + r = generator_open_unit_file(arg_dest, /* source = */ NULL, unit, &f); + if (r < 0) + return r; + + fprintf(f, + "[Unit]\n" + "Description=Create dm-clone device %1$s\n" + "Documentation=man:clonetab(5) man:systemd-clonesetup-generator(8)\n" + "DefaultDependencies=no\n" + "BindsTo=%2$s %3$s %4$s\n" + "Requires=%2$s %3$s %4$s\n" + "After=%2$s %3$s %4$s\n" + "Before=blockdev@dev-mapper-%5$s.target\n" + "Wants=blockdev@dev-mapper-%5$s.target\n" + "Conflicts=shutdown.target\n" + "\n" + "[Service]\n" + "Type=oneshot\n" + "RemainAfterExit=yes\n" + "ExecStart=" SYSTEMD_CLONESETUP_PATH " add '%6$s' '%7$s' '%8$s' '%9$s' '%10$s'\n" + "ExecStop=" SYSTEMD_CLONESETUP_PATH " remove %11$s\n" + "TimeoutSec=0\n", + clone_dev_path, + source_unit, dest_unit, metadata_unit, + e, + clone_name, escaped_source, escaped_dest, escaped_metadata, escaped_options ?: "", + clone_name); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write unit %s: %m", unit); + + /* symlink unit file to enable it */ + dmname = strjoin("dev-mapper-", e, ".device"); + if (!dmname) + return log_oom(); + + r = generator_add_symlink(arg_dest, dmname, "requires", unit); + if (r < 0) + return r; + + /* Extend device timeout to allow clone service to complete */ + r = write_drop_in(arg_dest, dmname, 40, "device-timeout", + "# Automatically generated by systemd-clonesetup-generator\n\n" + "[Unit]\n" + "JobTimeoutSec=infinity\n"); + if (r < 0) + log_warning_errno(r, "Failed to write device timeout drop-in: %m"); + + /* Add to clonesetup.target so it starts at boot */ + r = generator_add_symlink(arg_dest, SPECIAL_CLONESETUP_TARGET, "requires", unit); + if (r < 0) + return r; + + return 0; +} + +static int add_clone_devices(void) { + _cleanup_fclose_ FILE *f = NULL; + unsigned clone_line = 0; + int r, ret = 0; + const char *fname; + + fname = secure_getenv("SYSTEMD_CLONETAB") ?: "/etc/clonetab"; + + r = fopen_unlocked(fname, "re", &f); + if (r < 0) { + if (errno != ENOENT) + log_error_errno(errno, "Failed to open %s: %m", fname); + return 0; + } + + for (;;) { + _cleanup_free_ char *line = NULL, *src = NULL, *name = NULL, *dst = NULL, *meta = NULL, *options = NULL; + int k; + + r = read_stripped_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return log_error_errno(r, "Failed to read %s: %m", fname); + if (r == 0) + break; + + clone_line++; + + if (IN_SET(line[0], 0, '#')) + continue; + + k = sscanf(line, "%ms %ms %ms %ms %ms", &name, &src, &dst, &meta, &options); + if (k < 4 || k > 5) { + log_error("Failed to parse %s:%u, ignoring.", fname, clone_line); + continue; + } + + RET_GATHER(ret, generate_clone_units(name, src, dst, meta, options)); + } + + return ret; +} + +/* This generator reads /etc/clonetab and for each entry, writes unit files + * (creates systemd-clonesetup@.service and clonesetup.target.requires/systemd-clonesetup@.service) + * that clonesetup.target requires, and that run systemd-clonesetup (add device at boot, + * remove it at shutdown); systemd-clonesetup (used in systemd-clonesetup@.service) is the binary that + * uses device-mapper ioctls to create and remove the dm-clone devices. + * clonesetup.target groups these units so they run together at boot. + * Boot chain: sysinit.target has clonesetup.target in sysinit.target.wants/ (see units/meson.build), + * so at boot clonesetup.target starts and pulls in these units via clonesetup.target.requires/. */ +static int run(const char *dest, const char *dest_early, const char *dest_late) { + + /* dest usually is /run/systemd/generator */ + assert_se(arg_dest = dest); + + return add_clone_devices(); +} + +DEFINE_MAIN_GENERATOR_FUNCTION(run); diff --git a/src/clonesetup/clonesetup-ioctl.c b/src/clonesetup/clonesetup-ioctl.c new file mode 100644 index 0000000000000..2d3f29d789316 --- /dev/null +++ b/src/clonesetup/clonesetup-ioctl.c @@ -0,0 +1,213 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include + +#include "clonesetup-ioctl.h" +#include "device-private.h" +#include "fd-util.h" +#include "log.h" +#include "sd-device.h" +#include "string-util.h" + +/* Returns the size in bytes of the block device at dev_path. + * Loading the dm-clone table needs the source device size in sectors; sysfs + * reports size in 512-byte sectors. This reads sysfs and returns bytes so the + * caller can divide by 512 and pass the sector count to dm_clone_load_table(). */ +static int get_size(const char *dev_path, uint64_t *ret_size) { + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + uint64_t size; + int r; + + assert(dev_path); + assert(ret_size); + + r = sd_device_new_from_devname(&dev, dev_path); + if (r < 0) + return log_error_errno(r, "Failed to create device from '%s': %m", dev_path); + + r = device_get_sysattr_u64(dev, "size", &size); + if (r < 0) + return log_error_errno(r, "Failed to get device size for '%s': %m", dev_path); + + /* sysfs 'size' is in 512-byte sectors */ + *ret_size = size * 512; + return 0; +} + +/* Common helper used to run dm ioctls. */ +static int dm_ioctl_run(const char *name, uint32_t cmd, struct dm_ioctl *data, size_t data_size) { + _cleanup_close_ int fd = -EBADF; + struct dm_ioctl *dm = data; + + assert(name); + assert(data); + assert(data_size >= sizeof(struct dm_ioctl)); + + dm->version[0] = DM_VERSION_MAJOR; + dm->version[1] = DM_VERSION_MINOR; + dm->version[2] = DM_VERSION_PATCHLEVEL; + dm->data_size = data_size; + + assert(strlen(name) < sizeof_field(struct dm_ioctl, name)); + strncpy_exact(dm->name, name, sizeof(dm->name)); + + fd = open("/dev/mapper/control", O_RDWR | O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open /dev/mapper/control: %m"); + + if (ioctl(fd, cmd, dm) < 0) + return log_error_errno(errno, "DM ioctl failed: %m"); + + return 0; +} + +/* First dm ioctl needed to create a device. */ +static int dm_clone_create(const char *name) { + assert(name); + + struct dm_ioctl dm = {}; + return dm_ioctl_run(name, DM_DEV_CREATE, &dm, sizeof(dm)); +} + +/* Second dm ioctl needed to create a device. */ +static int dm_clone_load_table(const char *name, uint64_t size_sectors, const char *target_params) { + _cleanup_free_ void *dm_buf = NULL; + char *params_buf; + size_t params_len, dm_size; + struct dm_ioctl *dm; + struct dm_target_spec *tgt; + + assert(name); + assert(target_params); + + params_len = strlen(target_params) + 1; + dm_size = sizeof(struct dm_ioctl) + sizeof(struct dm_target_spec) + params_len; + dm_buf = malloc0(dm_size); + if (!dm_buf) + return -ENOMEM; + dm = dm_buf; + *dm = (struct dm_ioctl) { + .data_start = sizeof(struct dm_ioctl), + .target_count = 1, + }; + + tgt = (struct dm_target_spec *) ((uint8_t *) dm + dm->data_start); + *tgt = (struct dm_target_spec) { + .length = size_sectors, + .next = 0, + }; + strncpy(tgt->target_type, "clone", sizeof(tgt->target_type)); + + params_buf = (char *) ((uint8_t *) tgt + sizeof(struct dm_target_spec)); + strcpy(params_buf, target_params); + + return dm_ioctl_run(name, DM_TABLE_LOAD, dm, dm_size); +} + +/* Third and final dm ioctl needed to create a device. */ +static int dm_clone_activate(const char *name) { + assert(name); + + struct dm_ioctl dm = {}; + + return dm_ioctl_run(name, DM_DEV_SUSPEND, &dm, sizeof(dm)); +} + +/* Calls multiple dm ioctls to create device. */ +int dm_clone_create_device( + const char *name, + const char *source_dev, + const char *dest_dev, + const char *metadata_dev, + uint64_t region_size) { + + _cleanup_free_ char *target_params = NULL; + uint64_t src_dev_size_sectors, src_dev_size; + int r; + + assert(name); + assert(source_dev); + assert(dest_dev); + assert(metadata_dev); + + r = get_size(source_dev, &src_dev_size); + if (r < 0) + return r; + + /* The device mapper kernel API always uses 512-byte sectors, regardless of the + * physical block size of the device (all DM targets use sector_t which is 512B). + * Sysfs /sys/block//size also reports device size in 512-byte sectors. + * So we divide the byte size by 512 to get the sector count for the DM table. */ + src_dev_size_sectors = src_dev_size / 512; + + /* dm-clone target params: [options] + * region_size = region size in sectors, configurable via clonetab (default 8 = 4KB regions with 512-byte sectors) + * 1 = hydration threshold (regions to hydrate per batch) + * no_hydration = don't start automatic background hydration + * + * The DM table "target_params" string is passed directly to the kernel via ioctl(fd, DM_TABLE_LOAD, ...) in + * dm_clone_load_table as a raw byte buffer. The kernel's DM table parser (drivers/md/dm-table.c) simply splits the params string on whitespace, + * so the only constraint is that the paths in params - metadata_dev, dest_dev, source_dev, and region_size + * must not contain spaces, which standard /dev/ paths never do, so the below args do NOT require + * shell escaping */ + if (asprintf(&target_params, "%s %s %s %" PRIu64 " 1 no_hydration", metadata_dev, dest_dev, source_dev, region_size) < 0) + return log_oom(); + + r = dm_clone_create(name); + if (r < 0) + return r; + + r = dm_clone_load_table(name, src_dev_size_sectors, target_params); + if (r < 0) + return r; + + r = dm_clone_activate(name); + if (r < 0) + return r; + + log_info("Device %s active.", name); + return 0; +} + +/* Calls dm ioctl to send a message to the device. */ +int dm_clone_send_message(const char *name, const char *message) { + _cleanup_free_ void *dm_buf = NULL; + struct dm_ioctl *dm; + struct dm_target_msg *msg; + size_t dm_size, msg_len; + + assert(name); + assert(message); + + msg_len = strlen(message) + 1; + dm_size = sizeof(struct dm_ioctl) + sizeof(struct dm_target_msg) + msg_len; + dm_buf = malloc0(dm_size); + if (!dm_buf) + return -ENOMEM; + dm = dm_buf; + *dm = (struct dm_ioctl) { + .data_start = sizeof(struct dm_ioctl), + }; + + msg = (struct dm_target_msg *) ((uint8_t *) dm + dm->data_start); + strcpy(msg->message, message); + + return dm_ioctl_run(name, DM_TARGET_MSG, dm, dm_size); +} + +/* Calls dm ioctl to remove a device. */ +int dm_clone_remove_device(const char *name) { + struct dm_ioctl dm = {}; + int r; + + assert(name); + r = dm_ioctl_run(name, DM_DEV_REMOVE, &dm, sizeof(dm)); + if (r < 0) + return r; + + log_info("Device %s inactive.", name); + return 0; +} diff --git a/src/clonesetup/clonesetup-ioctl.h b/src/clonesetup/clonesetup-ioctl.h new file mode 100644 index 0000000000000..3c36a3336e08a --- /dev/null +++ b/src/clonesetup/clonesetup-ioctl.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +int dm_clone_create_device( + const char *name, + const char *source_dev, + const char *dest_dev, + const char *metadata_dev, + uint64_t region_size); + +int dm_clone_send_message(const char *name, const char *message); + +int dm_clone_remove_device(const char *name); + diff --git a/src/clonesetup/clonesetup.c b/src/clonesetup/clonesetup.c new file mode 100644 index 0000000000000..49f36960b55ad --- /dev/null +++ b/src/clonesetup/clonesetup.c @@ -0,0 +1,229 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include +#include +#include +#include /* access */ + +#include "alloc-util.h" +#include "argv-util.h" +#include "build.h" +#include "clonesetup-ioctl.h" +#include "log.h" +#include "main-func.h" +#include "pretty-print.h" +#include "verbs.h" +#include "path-util.h" /* path_join */ +#include "time-util.h" /* USEC_PER_SEC */ +#include "udev-util.h" /* device_wait_for_devlink */ +#include "extract-word.h" +#include "string-util.h" +#include "parse-util.h" + +/* region_size: Must be a power of 2 between 8 and 2097152 per dm-clone kernel docs. */ +#define DEFAULT_REGION_SIZE 8 +#define CLONE_MIN_REGION_SIZE (UINT64_C(1) << 3) /* 8 */ +#define CLONE_MAX_REGION_SIZE (UINT64_C(1) << 21) /* 2097152 */ + +static int parse_clone_options(const char *options, uint64_t *ret_region_size) { + *ret_region_size=DEFAULT_REGION_SIZE; + + for (;;) { + _cleanup_free_ char *word=NULL; + const char *val; + int r; + + /* extract_first_word: * + * Returns > 0 — successfully extracted a word * + * Returns 0 — no more words (end of string) * + * Returns < 0 — actual error (e.g. memory allocation failure) */ + r = extract_first_word(&options, &word, ",", EXTRACT_DONT_COALESCE_SEPARATORS); + if ( r < 0 ) + return log_error_errno(r, "Failed to parse clone options: %m"); + if ( r == 0 ) + break; + + /* region_size=N; size of each clone region in 512-byte sectors ( default 8 = 4KB ) + * Must be a power of 2 between 8 and 2097152 per dm-clone kernel docs. */ + /* treat "" and "-" as empty — common placeholders for "no options" */ + if (streq(word, "\"\"") || streq(word, "-")) + continue; + + if (( val = startswith(word, "region_size="))) { + uint64_t region_size; + r = safe_atou64(val, ®ion_size); + if (r < 0) { + log_warning("Invalid region size=value '%s', using default", val); + } else if (!ISPOWEROF2(region_size) || region_size < CLONE_MIN_REGION_SIZE || region_size > CLONE_MAX_REGION_SIZE) { + log_warning("region_size=%s must be a power of two between 8 and 2097152, using default.", val); + } else { + *ret_region_size=region_size; + } + } else { + /* currently only region_size is supported */ + log_warning("Unknown clone option '%s', ignoring.", word); + } + } + return 0; +} + +/* dm-clone device creation workflow: + * 1. Create the dm-clone device + * 2. Enable background hydration + * 3. (Optional) Replace with linear mapping to finalize */ +static int clone_device(const char *clone_name, const char *source_dev, const char *dest_dev, + const char *metadata_dev, const char *options) { + + _cleanup_free_ char *clone_dev_path = NULL; + int r; + + assert(clone_name); + assert(source_dev); + assert(dest_dev); + assert(metadata_dev); + + /* create clone device path to check if clone device already exists */ + clone_dev_path = path_join("/dev/mapper", clone_name); + if (!clone_dev_path) + return log_oom(); + + /* Check before calling the DM ioctl to give a cleaner error message; + * DM_DEV_CREATE would return EEXIST too, but with a less obvious message. */ + if (access(clone_dev_path, F_OK) >= 0) + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Device '%s' already exists.", clone_dev_path); + + uint64_t region_size; + r = parse_clone_options(options, ®ion_size); + if (r < 0) + return log_error_errno(r, "Failed to parse clone options: %m"); + + r = dm_clone_create_device(clone_name, source_dev, dest_dev, metadata_dev, region_size); + if (r < 0) + return log_error_errno(r, "Failed to create dm-clone device: %m"); + + /* Wait for udev to create /dev/mapper/ */ + r = device_wait_for_devlink(clone_dev_path, "block", 10 * USEC_PER_SEC, /* ret_inode = */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to wait for device %s: %m", clone_dev_path); + + r = dm_clone_send_message(clone_name, "enable_hydration"); + if (r < 0) + return log_error_errno(r, "Failed to send dm message: %m"); + + return 0; +} + +/* Arguments: systemd-clonesetup add NAME SOURCE-DEVICE DST_DEVICE META-DEVICE [OPTIONS] */ +static int verb_add(int argc, char *argv[], uintptr_t data, void *userdata) { + int r; + + assert(argc >= 5 && argc <= 6); + + const char *name = ASSERT_PTR(argv[1]), + *src_dev = ASSERT_PTR(argv[2]), + *dst_dev = ASSERT_PTR(argv[3]), + *meta_dev = ASSERT_PTR(argv[4]); + + const char *options = (argc == 6 ? argv[5] : ""); + + log_debug("%s %s %s %s %s opts=%s ", __func__, + name, src_dev, dst_dev, meta_dev, options); + + r = clone_device(name, src_dev, dst_dev, meta_dev, options); + if (r < 0) + return r; + + return 0; +} + +static int verb_remove(int argc, char *argv[], uintptr_t data, void *userdata) { + const char *name = ASSERT_PTR(argv[1]); + int r; + + r = dm_clone_remove_device(name); + if (r < 0) + return r; + + return 0; +} + +static int help(void) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("systemd-clonesetup", "8", &link); + if (r < 0) + return log_oom(); + + printf("%1$s add NAME SOURCE-DEVICE DST-DEVICE META-DEVICE [OPTIONS] \n" + "%1$s remove VOLUME\n\n" + "%2$sAdd or remove a dm clone device.%3$s\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + "\nSee the %4$s for details.\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal(), + link); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + if (argv_looks_like_help(argc, argv)) + return help(); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + switch (c) { + + case 'h': + return help(); + + case ARG_VERSION: + return version(); + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + + return 1; +} + +/* systemd-clonesetup uses device-mapper ioctls to create and remove the + * dm-clone devices. */ +static int run(int argc, char *argv[]) { + int r; + + log_setup(); + umask(0022); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + static const Verb verbs[] = { + { "add", 5, 6, 0, verb_add, 0 }, + { "remove", 2, 2, 0, verb_remove, 0 }, + {} + }; + return dispatch_verb(argc, argv, verbs, NULL); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/clonesetup/meson.build b/src/clonesetup/meson.build new file mode 100644 index 0000000000000..9e7cdfe347fa4 --- /dev/null +++ b/src/clonesetup/meson.build @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +systemd_clonesetup_sources = files( + 'clonesetup-ioctl.c', + 'clonesetup.c', +) + +executables += [ + executable_template + { + 'name' : 'systemd-clonesetup', + 'public' : true, + 'sources' : systemd_clonesetup_sources, + }, + generator_template + { + 'name' : 'systemd-clonesetup-generator', + 'sources' : files('clonesetup-generator.c'), + }, +] diff --git a/test/integration-tests/TEST-90-CLONESETUP/meson.build b/test/integration-tests/TEST-90-CLONESETUP/meson.build new file mode 100644 index 0000000000000..77370ce4588c4 --- /dev/null +++ b/test/integration-tests/TEST-90-CLONESETUP/meson.build @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +integration_tests += [ + integration_test_template + { + 'name' : fs.name(meson.current_source_dir()), + 'vm' : true, + }, +] diff --git a/test/integration-tests/meson.build b/test/integration-tests/meson.build index 7888283db81cb..1c283a5ce4fde 100644 --- a/test/integration-tests/meson.build +++ b/test/integration-tests/meson.build @@ -103,6 +103,7 @@ foreach dirname : [ 'TEST-87-AUX-UTILS-VM', 'TEST-88-UPGRADE', 'TEST-89-RESOLVED-MDNS', + 'TEST-90-CLONESETUP', ] subdir(dirname) endforeach diff --git a/test/units/TEST-90-CLONESETUP.sh b/test/units/TEST-90-CLONESETUP.sh new file mode 100755 index 0000000000000..757916c4c2dc4 --- /dev/null +++ b/test/units/TEST-90-CLONESETUP.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# Test clonesetup generator and systemd-clonesetup + +at_exit() { + set +e + + rm -f /etc/clonetab + [[ -e /tmp/clonetab.bak ]] && cp -fv /tmp/clonetab.bak /etc/clonetab + [[ -n "${LOOP_SRC:-}" ]] && losetup -d "$LOOP_SRC" + [[ -n "${LOOP_DST:-}" ]] && losetup -d "$LOOP_DST" + [[ -n "${LOOP_META:-}" ]] && losetup -d "$LOOP_META" + [[ -n "${WORKDIR:-}" ]] && rm -rf "$WORKDIR" + dmsetup remove testclonesetup 2>/dev/null || true + + systemctl daemon-reload +} + +trap at_exit EXIT + +clonesetup_start_and_check() { + local volume unit + + volume="${1:?}" + unit="systemd-clonesetup@$volume.service" + + # The unit existence check should always pass + [[ "$(systemctl show -P LoadState "$unit")" == loaded ]] + systemctl list-unit-files "$unit" + + systemctl start "$unit" + systemctl status "$unit" + test -e "/dev/mapper/$volume" + dmsetup status "$volume" + + systemctl stop "$unit" + # wait for udev to finish processing so the device node state is in sync + # before the API returns. + udevadm settle --timeout=10 + test ! -e "/dev/mapper/$volume" +} + +prereq() { + # Skip when kernel lacks dm-clone (CONFIG_DM_CLONE) + modprobe dm_clone 2>/dev/null || true + if [[ ! -d /sys/module/dm_clone ]]; then + echo "no dm-clone" >/skipped + exit 77 + fi + echo "Found required kernel module: dm_clone" +} + +prereq + +# Use a common workdir +WORKDIR="$(mktemp -d)" + +# Create test images for source, destination, and metadata +IMG_SRC="$WORKDIR/source.img" +IMG_DST="$WORKDIR/dest.img" +IMG_META="$WORKDIR/meta.img" + +truncate -s 32M "$IMG_SRC" +truncate -s 32M "$IMG_DST" +truncate -s 8M "$IMG_META" + +# Set up loop devices +LOOP_SRC="$(losetup --show --find "$IMG_SRC")" +LOOP_DST="$(losetup --show --find "$IMG_DST")" +LOOP_META="$(losetup --show --find "$IMG_META")" + +udevadm settle --timeout=60 + +# Backup existing clonetab if any +[[ -e /etc/clonetab ]] && cp -fv /etc/clonetab /tmp/clonetab.bak + +# Create test clonetab +cat >/etc/clonetab <